@weasel-js/core 1.9.3 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +2 -2
  3. package/dist/WeaselProvider.js +5 -5
  4. package/dist/affordances/composeAffordanceLayer.js +10 -20
  5. package/dist/affordances/composeAffordanceLayer.js.map +1 -1
  6. package/dist/affordances/cornerResize.js +25 -17
  7. package/dist/affordances/cornerResize.js.map +1 -1
  8. package/dist/affordances/hitAffordanceRegions.js +27 -20
  9. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  10. package/dist/affordances/rotationHandle.js +18 -18
  11. package/dist/affordances/rotationHandle.js.map +1 -1
  12. package/dist/animation/behaviors/momentum.js +93 -55
  13. package/dist/animation/behaviors/momentum.js.map +1 -1
  14. package/dist/animation/loop.js +10 -12
  15. package/dist/animation/loop.js.map +1 -1
  16. package/dist/animation/reflow.js +8 -8
  17. package/dist/animation/reflow.js.map +1 -1
  18. package/dist/animation/stagger.js +64 -56
  19. package/dist/animation/stagger.js.map +1 -1
  20. package/dist/animation/timeline/booking.js +62 -54
  21. package/dist/animation/timeline/booking.js.map +1 -1
  22. package/dist/animation/timeline/createTimeline.js +85 -76
  23. package/dist/animation/timeline/createTimeline.js.map +1 -1
  24. package/dist/animation/timeline/sampleTrack.js +13 -5
  25. package/dist/animation/timeline/sampleTrack.js.map +1 -1
  26. package/dist/animation/useAnimator.js +45 -35
  27. package/dist/animation/useAnimator.js.map +1 -1
  28. package/dist/animation/wrappers/animateLifecycle.js +31 -24
  29. package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
  30. package/dist/animation/wrappers/animateOnSetPose.js +28 -26
  31. package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
  32. package/dist/animation/wrappers/instantPose.js +13 -0
  33. package/dist/animation/wrappers/instantPose.js.map +1 -0
  34. package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
  35. package/dist/barrel/chrome.js +16 -0
  36. package/dist/barrel/input.js +19 -0
  37. package/dist/barrel/interactions.js +42 -0
  38. package/dist/barrel/paint.js +18 -0
  39. package/dist/barrel/paths.js +19 -0
  40. package/dist/barrel/renderer.js +20 -0
  41. package/dist/barrel/routing.js +51 -0
  42. package/dist/barrel/text.js +18 -0
  43. package/dist/barrel/viewport.js +52 -0
  44. package/dist/builtins-DPcdghtB.d.ts +1223 -0
  45. package/dist/canvas/Canvas.js +363 -448
  46. package/dist/canvas/Canvas.js.map +1 -1
  47. package/dist/canvas/CanvasView.js +164 -165
  48. package/dist/canvas/CanvasView.js.map +1 -1
  49. package/dist/canvas/CursorCoordsHud.js +37 -32
  50. package/dist/canvas/CursorCoordsHud.js.map +1 -1
  51. package/dist/canvas/DrawCanvas.js +49 -43
  52. package/dist/canvas/DrawCanvas.js.map +1 -1
  53. package/dist/canvas/MinimapCanvas.js +26 -25
  54. package/dist/canvas/MinimapCanvas.js.map +1 -1
  55. package/dist/canvas/NodeShape.js +59 -377
  56. package/dist/canvas/NodeShape.js.map +1 -1
  57. package/dist/canvas/PickHud.js +50 -47
  58. package/dist/canvas/PickHud.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
  60. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
  61. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
  62. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
  63. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
  64. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
  65. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
  66. package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
  67. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
  68. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +112 -70
  69. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  70. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
  71. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  72. package/dist/canvas/SceneCanvas/features.js +1 -0
  73. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  74. package/dist/canvas/SceneCanvas/isDev.js +12 -0
  75. package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
  76. package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
  77. package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
  78. package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
  79. package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
  80. package/dist/canvas/SceneCanvas/props.js +38 -0
  81. package/dist/canvas/SceneCanvas/props.js.map +1 -0
  82. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
  83. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  84. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
  85. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
  86. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  87. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
  88. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  89. package/dist/canvas/SceneCanvas.js +534 -788
  90. package/dist/canvas/SceneCanvas.js.map +1 -1
  91. package/dist/canvas/SceneViewCanvas.js +45 -43
  92. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  93. package/dist/canvas/buildSceneLayer.js +91 -0
  94. package/dist/canvas/buildSceneLayer.js.map +1 -0
  95. package/dist/canvas/buildSceneTree.js +16 -19
  96. package/dist/canvas/buildSceneTree.js.map +1 -1
  97. package/dist/canvas/canvasRenderer.js +68 -52
  98. package/dist/canvas/canvasRenderer.js.map +1 -1
  99. package/dist/canvas/deps/changes.js +13 -0
  100. package/dist/canvas/deps/changes.js.map +1 -0
  101. package/dist/canvas/deps/editAnchors.js +28 -28
  102. package/dist/canvas/deps/insert.js +119 -91
  103. package/dist/canvas/deps/insert.js.map +1 -1
  104. package/dist/canvas/deps/slice.js +46 -45
  105. package/dist/canvas/deps/slice.js.map +1 -1
  106. package/dist/canvas/deps/textEdit.js +10 -9
  107. package/dist/canvas/deps/textEdit.js.map +1 -1
  108. package/dist/canvas/derivedPath.js +46 -23
  109. package/dist/canvas/derivedPath.js.map +1 -1
  110. package/dist/canvas/getNodeAtPoint.js +7 -7
  111. package/dist/canvas/getNodeAtPoint.js.map +1 -1
  112. package/dist/canvas/hud.module.js +1 -1
  113. package/dist/canvas/hud.module.js.map +1 -1
  114. package/dist/canvas/kitRequirements.js +2 -2
  115. package/dist/canvas/kitRequirements.js.map +1 -1
  116. package/dist/canvas/layerOrder.js +13 -6
  117. package/dist/canvas/layerOrder.js.map +1 -1
  118. package/dist/canvas/minimapMath.js +12 -10
  119. package/dist/canvas/minimapMath.js.map +1 -1
  120. package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
  121. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
  122. package/dist/canvas/nodeShapes/imagePainter.js +87 -0
  123. package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
  124. package/dist/canvas/nodeShapes/memo.js +9 -0
  125. package/dist/canvas/nodeShapes/memo.js.map +1 -0
  126. package/dist/canvas/nodeShapes/nodePaint.js +57 -0
  127. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
  128. package/dist/canvas/nodeShapes/pathPainter.js +38 -0
  129. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
  130. package/dist/canvas/nodeShapes/poseBox.js +20 -0
  131. package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
  132. package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
  133. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
  134. package/dist/canvas/nodeShapes/shapePainter.js +72 -0
  135. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
  136. package/dist/canvas/nodeShapes/textPainter.js +55 -0
  137. package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
  138. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  139. package/dist/canvas/paintCull.js +10 -11
  140. package/dist/canvas/paintCull.js.map +1 -1
  141. package/dist/canvas/pickWalk.js +52 -63
  142. package/dist/canvas/pickWalk.js.map +1 -1
  143. package/dist/canvas/poseRotation.js +11 -23
  144. package/dist/canvas/poseRotation.js.map +1 -1
  145. package/dist/canvas/renderDebugSnapshot.js +5 -5
  146. package/dist/canvas/renderSceneToPixels.js +3 -1
  147. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  148. package/dist/canvas/sceneAdapter.js +66 -148
  149. package/dist/canvas/sceneAdapter.js.map +1 -1
  150. package/dist/canvas/sceneClipboard.js +112 -0
  151. package/dist/canvas/sceneClipboard.js.map +1 -0
  152. package/dist/canvas/sceneSlotWiring.js +4 -4
  153. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  154. package/dist/canvas/sceneViewRender.js +39 -40
  155. package/dist/canvas/sceneViewRender.js.map +1 -1
  156. package/dist/canvas/useCanvasRenderer.js +14 -6
  157. package/dist/canvas/useCanvasRenderer.js.map +1 -1
  158. package/dist/canvas/useCanvasView.js +58 -0
  159. package/dist/canvas/useCanvasView.js.map +1 -0
  160. package/dist/canvas/useFrameLoop.js +35 -26
  161. package/dist/canvas/useFrameLoop.js.map +1 -1
  162. package/dist/canvas/useSceneLayerList.js +21 -0
  163. package/dist/canvas/useSceneLayerList.js.map +1 -0
  164. package/dist/clipboard.d.ts +10 -6
  165. package/dist/clone.d.ts +4 -3
  166. package/dist/core/adapters/arrayAdapter.js +48 -39
  167. package/dist/core/adapters/arrayAdapter.js.map +1 -1
  168. package/dist/core/device/index.js +5 -4
  169. package/dist/core/device/useDeviceProfile.js +38 -44
  170. package/dist/core/device/useDeviceProfile.js.map +1 -1
  171. package/dist/core/device/watchDevicePixelRatio.js +17 -0
  172. package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
  173. package/dist/core/fillInPoseFrame.js.map +1 -1
  174. package/dist/core/geometry/poseDescriptor.js +21 -19
  175. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  176. package/dist/core/geometry/poseFrame.js +44 -0
  177. package/dist/core/geometry/poseFrame.js.map +1 -0
  178. package/dist/core/geometry/poseRotation.js.map +1 -1
  179. package/dist/core/geometry/regionHit.js +9 -10
  180. package/dist/core/geometry/regionHit.js.map +1 -1
  181. package/dist/core/geometry/unionBounds.js +31 -29
  182. package/dist/core/geometry/unionBounds.js.map +1 -1
  183. package/dist/core/isPlainObject.js +2 -11
  184. package/dist/core/ops/arrange.js +1 -0
  185. package/dist/core/ops/arrange.js.map +1 -1
  186. package/dist/core/ops/create.js +10 -5
  187. package/dist/core/ops/create.js.map +1 -1
  188. package/dist/core/ops/delete.js +12 -24
  189. package/dist/core/ops/delete.js.map +1 -1
  190. package/dist/core/ops/reorder/index.js +43 -40
  191. package/dist/core/ops/reorder/index.js.map +1 -1
  192. package/dist/core/ops/reorder/setChildOrder.js +34 -0
  193. package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
  194. package/dist/core/paintKinds.js +5 -5
  195. package/dist/core/paintKinds.js.map +1 -1
  196. package/dist/core/scene/NodeProperties.js.map +1 -1
  197. package/dist/core/scene/appliedLog.js +37 -0
  198. package/dist/core/scene/appliedLog.js.map +1 -0
  199. package/dist/core/scene/arrivalWindow.js +101 -0
  200. package/dist/core/scene/arrivalWindow.js.map +1 -0
  201. package/dist/core/scene/derivedPath.js.map +1 -1
  202. package/dist/core/scene/historyFacade.js +84 -0
  203. package/dist/core/scene/historyFacade.js.map +1 -0
  204. package/dist/core/scene/kitOps.js +173 -0
  205. package/dist/core/scene/kitOps.js.map +1 -0
  206. package/dist/core/scene/layerStack.js +38 -0
  207. package/dist/core/scene/layerStack.js.map +1 -0
  208. package/dist/core/scene/lockGuard.js +37 -0
  209. package/dist/core/scene/lockGuard.js.map +1 -0
  210. package/dist/core/scene/nodeFnFields.js +5 -6
  211. package/dist/core/scene/nodeFnFields.js.map +1 -1
  212. package/dist/core/scene/nodeMemo.js +45 -19
  213. package/dist/core/scene/nodeMemo.js.map +1 -1
  214. package/dist/core/scene/nodeStore.js +126 -0
  215. package/dist/core/scene/nodeStore.js.map +1 -0
  216. package/dist/core/scene/pickability.js +6 -0
  217. package/dist/core/scene/pickability.js.map +1 -0
  218. package/dist/core/scene/poseFeed.js +38 -38
  219. package/dist/core/scene/poseFeed.js.map +1 -1
  220. package/dist/core/scene/poseOverrides.js +34 -38
  221. package/dist/core/scene/poseOverrides.js.map +1 -1
  222. package/dist/core/scene/renderOrder.js +84 -0
  223. package/dist/core/scene/renderOrder.js.map +1 -0
  224. package/dist/core/scene/scene.js +289 -866
  225. package/dist/core/scene/scene.js.map +1 -1
  226. package/dist/core/scene/serialization.js +68 -0
  227. package/dist/core/scene/serialization.js.map +1 -0
  228. package/dist/core/scene/treeOrder.js +13 -0
  229. package/dist/core/scene/treeOrder.js.map +1 -0
  230. package/dist/core/scene/types.js.map +1 -1
  231. package/dist/core/viewport/meanScale.js +1 -1
  232. package/dist/core/viewport/meanScale.js.map +1 -1
  233. package/dist/core/viewport/view.js +12 -12
  234. package/dist/core/viewport/view.js.map +1 -1
  235. package/dist/core/viewport/zoomBounds.js +10 -8
  236. package/dist/core/viewport/zoomBounds.js.map +1 -1
  237. package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
  238. package/dist/debug/createDebugOverlayLayer.js +14 -14
  239. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  240. package/dist/debug/flag.js +2 -41
  241. package/dist/features/chrome-caps/useHoverTracking.js +22 -17
  242. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  243. package/dist/features/focus/gateLayer.js +3 -4
  244. package/dist/features/focus/gateLayer.js.map +1 -1
  245. package/dist/features/images/imageCache.js +19 -9
  246. package/dist/features/images/imageCache.js.map +1 -1
  247. package/dist/features/ingestion/contentHandlers.js +14 -11
  248. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  249. package/dist/features/ingestion/index.js +1 -1
  250. package/dist/features/ingestion/registerKitHandlers.js +7 -7
  251. package/dist/features/ingestion/weaselJsonHandler.js +17 -14
  252. package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
  253. package/dist/features/meshPaint/lazyKind.js +201 -35
  254. package/dist/features/meshPaint/lazyKind.js.map +1 -1
  255. package/dist/features/meshPaint/meshPaint.js +82 -79
  256. package/dist/features/meshPaint/meshPaint.js.map +1 -1
  257. package/dist/features/minimap/createMinimapContribution.js +42 -42
  258. package/dist/features/minimap/layers.js +44 -62
  259. package/dist/features/minimap/layers.js.map +1 -1
  260. package/dist/features/parallax/createParallaxPlane.js +10 -14
  261. package/dist/features/parallax/createParallaxPlane.js.map +1 -1
  262. package/dist/features/paths/anchorEdits.js +19 -8
  263. package/dist/features/paths/anchorEdits.js.map +1 -1
  264. package/dist/features/paths/anchors.js +31 -26
  265. package/dist/features/paths/anchors.js.map +1 -1
  266. package/dist/features/paths/booleans.js +2 -2
  267. package/dist/features/paths/index.js +13 -11
  268. package/dist/features/paths/markerCommands.js +37 -36
  269. package/dist/features/paths/markerCommands.js.map +1 -1
  270. package/dist/features/paths/pathInWorld.js +19 -19
  271. package/dist/features/paths/pathInWorld.js.map +1 -1
  272. package/dist/features/paths/pathLayer.js.map +1 -1
  273. package/dist/features/paths/strokeToPath.js +40 -0
  274. package/dist/features/paths/strokeToPath.js.map +1 -0
  275. package/dist/features/paths/tessellate/stroke.js.map +1 -1
  276. package/dist/features/patterns/commandPaint.js +18 -0
  277. package/dist/features/patterns/commandPaint.js.map +1 -0
  278. package/dist/features/patterns/index.js +5 -17
  279. package/dist/features/patterns/index.js.map +1 -1
  280. package/dist/features/patterns/rasterizeTile.js +17 -0
  281. package/dist/features/patterns/rasterizeTile.js.map +1 -0
  282. package/dist/features/patterns/resolveSpec.js +52 -44
  283. package/dist/features/patterns/resolveSpec.js.map +1 -1
  284. package/dist/features/poseRun/usePoseRun.js +8 -3
  285. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  286. package/dist/features/selection/SelectionContext.js +5 -0
  287. package/dist/features/selection/SelectionContext.js.map +1 -1
  288. package/dist/features/selection/overlay.js +18 -18
  289. package/dist/features/selection/overlay.js.map +1 -1
  290. package/dist/features/text/domOffsets.js +82 -0
  291. package/dist/features/text/domOffsets.js.map +1 -0
  292. package/dist/features/text/domRuns.js +103 -131
  293. package/dist/features/text/domRuns.js.map +1 -1
  294. package/dist/features/text/hitTest.js +5 -2
  295. package/dist/features/text/hitTest.js.map +1 -1
  296. package/dist/features/text/isTextData.js +8 -0
  297. package/dist/features/text/isTextData.js.map +1 -0
  298. package/dist/features/text/overlayDirection.js +15 -0
  299. package/dist/features/text/overlayDirection.js.map +1 -0
  300. package/dist/features/text/runs/rangeStyle.js +32 -34
  301. package/dist/features/text/runs/rangeStyle.js.map +1 -1
  302. package/dist/features/text/textCommand.js +34 -33
  303. package/dist/features/text/textCommand.js.map +1 -1
  304. package/dist/features/text/textToPath.js +40 -40
  305. package/dist/features/text/textToPath.js.map +1 -1
  306. package/dist/features/text/useSceneTextEdit.js +76 -82
  307. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  308. package/dist/features/text/useTextEdit.js +184 -160
  309. package/dist/features/text/useTextEdit.js.map +1 -1
  310. package/dist/features/tiling/createTiledLayer.js +24 -22
  311. package/dist/features/tiling/createTiledLayer.js.map +1 -1
  312. package/dist/index.d.ts +11033 -7749
  313. package/dist/index.js +316 -309
  314. package/dist/input/keyState.js +42 -43
  315. package/dist/input/keyState.js.map +1 -1
  316. package/dist/input/useKeyModifiers.js +11 -0
  317. package/dist/input/useKeyModifiers.js.map +1 -0
  318. package/dist/interactions/actions/align/align.js +8 -8
  319. package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
  320. package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
  321. package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
  322. package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
  323. package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
  324. package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
  325. package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
  326. package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
  327. package/dist/interactions/actions/defaults/align.js +9 -10
  328. package/dist/interactions/actions/defaults/align.js.map +1 -1
  329. package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
  330. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  331. package/dist/interactions/actions/defaults/areaSelect.js +35 -38
  332. package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
  333. package/dist/interactions/actions/defaults/clipboard.js +26 -10
  334. package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
  335. package/dist/interactions/actions/defaults/clone.js +44 -60
  336. package/dist/interactions/actions/defaults/clone.js.map +1 -1
  337. package/dist/interactions/actions/defaults/copySubtree.js +40 -0
  338. package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
  339. package/dist/interactions/actions/defaults/distribute.js +14 -13
  340. package/dist/interactions/actions/defaults/distribute.js.map +1 -1
  341. package/dist/interactions/actions/defaults/duplicate.js +17 -57
  342. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  343. package/dist/interactions/actions/defaults/editAnchors.js +18 -18
  344. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  345. package/dist/interactions/actions/defaults/flip.js +52 -46
  346. package/dist/interactions/actions/defaults/flip.js.map +1 -1
  347. package/dist/interactions/actions/defaults/group.js +43 -48
  348. package/dist/interactions/actions/defaults/group.js.map +1 -1
  349. package/dist/interactions/actions/defaults/ingest.js +35 -33
  350. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  351. package/dist/interactions/actions/defaults/insert.js +50 -51
  352. package/dist/interactions/actions/defaults/insert.js.map +1 -1
  353. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
  354. package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
  355. package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
  356. package/dist/interactions/actions/defaults/move.js +267 -249
  357. package/dist/interactions/actions/defaults/move.js.map +1 -1
  358. package/dist/interactions/actions/defaults/nudge.js +40 -68
  359. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  360. package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
  361. package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
  362. package/dist/interactions/actions/defaults/reorder.js +2 -0
  363. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  364. package/dist/interactions/actions/defaults/resize.js +104 -98
  365. package/dist/interactions/actions/defaults/resize.js.map +1 -1
  366. package/dist/interactions/actions/defaults/rotate.js +111 -110
  367. package/dist/interactions/actions/defaults/rotate.js.map +1 -1
  368. package/dist/interactions/actions/defaults/setFill.js +7 -7
  369. package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
  370. package/dist/interactions/actions/defaults/setStroke.js +12 -12
  371. package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
  372. package/dist/interactions/actions/defaults/sliceOps.js +19 -19
  373. package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
  374. package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
  375. package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
  376. package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
  377. package/dist/interactions/actions/flip/helpers.js +30 -17
  378. package/dist/interactions/actions/flip/helpers.js.map +1 -1
  379. package/dist/interactions/actions/hitSelection.js +8 -0
  380. package/dist/interactions/actions/hitSelection.js.map +1 -0
  381. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
  382. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
  383. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
  384. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
  385. package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
  386. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  387. package/dist/interactions/actions/move/gestureAdapter.js +32 -15
  388. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  389. package/dist/interactions/actions/move/landedDescendants.js +33 -0
  390. package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
  391. package/dist/interactions/actions/outlines/createOutlines.js +18 -18
  392. package/dist/interactions/actions/poseDescriptorDep.js +16 -4
  393. package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
  394. package/dist/interactions/actions/poseFrame.js +11 -6
  395. package/dist/interactions/actions/poseFrame.js.map +1 -1
  396. package/dist/interactions/actions/previewOverrides.js +5 -5
  397. package/dist/interactions/actions/previewOverrides.js.map +1 -1
  398. package/dist/interactions/actions/selectionSubtree.js +69 -0
  399. package/dist/interactions/actions/selectionSubtree.js.map +1 -0
  400. package/dist/interactions/actions/useStandardActions.js +66 -62
  401. package/dist/interactions/actions/useStandardActions.js.map +1 -1
  402. package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
  403. package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
  404. package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
  405. package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
  406. package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
  407. package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
  408. package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
  409. package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
  410. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
  411. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  412. package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
  413. package/dist/math.d.ts +2 -3
  414. package/dist/move.d.ts +12 -11
  415. package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
  416. package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
  417. package/dist/renderer/DrawCommand.js.map +1 -1
  418. package/dist/renderer/WeaselRenderer.js +75 -73
  419. package/dist/renderer/WeaselRenderer.js.map +1 -1
  420. package/dist/renderer/cache/GLImageCache.js +15 -5
  421. package/dist/renderer/cache/GLImageCache.js.map +1 -1
  422. package/dist/renderer/cache/GLMeshCache.js +23 -23
  423. package/dist/renderer/cache/GLMeshCache.js.map +1 -1
  424. package/dist/renderer/cache/GLTextureCache.js +18 -1
  425. package/dist/renderer/cache/GLTextureCache.js.map +1 -1
  426. package/dist/renderer/cache/cache.js.map +1 -1
  427. package/dist/renderer/cache/markerCommandCache.js.map +1 -1
  428. package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
  429. package/dist/renderer/cullDrawCommands.js +11 -11
  430. package/dist/renderer/draw/batch.js +133 -0
  431. package/dist/renderer/draw/batch.js.map +1 -0
  432. package/dist/renderer/draw/clip.js +89 -0
  433. package/dist/renderer/draw/clip.js.map +1 -0
  434. package/dist/renderer/draw/customShader.js +230 -0
  435. package/dist/renderer/draw/customShader.js.map +1 -0
  436. package/dist/renderer/draw/drawTriangles.js +8 -0
  437. package/dist/renderer/draw/drawTriangles.js.map +1 -0
  438. package/dist/renderer/draw/effects.js +28 -0
  439. package/dist/renderer/draw/effects.js.map +1 -0
  440. package/dist/renderer/draw/flushBatch.js +35 -0
  441. package/dist/renderer/draw/flushBatch.js.map +1 -0
  442. package/dist/renderer/draw/gradient.js +90 -0
  443. package/dist/renderer/draw/gradient.js.map +1 -0
  444. package/dist/renderer/draw/image.js +38 -0
  445. package/dist/renderer/draw/image.js.map +1 -0
  446. package/dist/renderer/draw/mesh.js +16 -0
  447. package/dist/renderer/draw/mesh.js.map +1 -0
  448. package/dist/renderer/draw/paintBind.js +70 -0
  449. package/dist/renderer/draw/paintBind.js.map +1 -0
  450. package/dist/renderer/draw/pathPaint.js +142 -0
  451. package/dist/renderer/draw/pathPaint.js.map +1 -0
  452. package/dist/renderer/draw/scratchPass.js +83 -0
  453. package/dist/renderer/draw/scratchPass.js.map +1 -0
  454. package/dist/renderer/draw/stencil.js +28 -0
  455. package/dist/renderer/draw/stencil.js.map +1 -0
  456. package/dist/renderer/draw/text.js +167 -0
  457. package/dist/renderer/draw/text.js.map +1 -0
  458. package/dist/renderer/draw/uniforms.js +77 -0
  459. package/dist/renderer/draw/uniforms.js.map +1 -0
  460. package/dist/renderer/draw.js +60 -1116
  461. package/dist/renderer/draw.js.map +1 -1
  462. package/dist/renderer/effects/EffectTargets.js +3 -0
  463. package/dist/renderer/effects/EffectTargets.js.map +1 -1
  464. package/dist/renderer/effects/composite.js +7 -5
  465. package/dist/renderer/effects/composite.js.map +1 -1
  466. package/dist/renderer/index.js +18 -17
  467. package/dist/renderer/math/color.js +63 -83
  468. package/dist/renderer/math/color.js.map +1 -1
  469. package/dist/renderer/math/mat3.js +15 -11
  470. package/dist/renderer/math/mat3.js.map +1 -1
  471. package/dist/renderer/math/viewToMat3.js +1 -1
  472. package/dist/renderer/math/viewToMat3.js.map +1 -1
  473. package/dist/renderer/shaders/ShaderProgram.js +13 -13
  474. package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
  475. package/dist/renderer/shaders/glyphPaint.js +3 -3
  476. package/dist/renderer/shaders/glyphPaint.js.map +1 -1
  477. package/dist/renderer/shaders/gradFill.js +2 -1
  478. package/dist/renderer/shaders/gradFill.js.map +1 -1
  479. package/dist/renderer/shaders/pathFill.js +2 -1
  480. package/dist/renderer/shaders/pathFill.js.map +1 -1
  481. package/dist/renderer/shaders/patternFill.js +2 -1
  482. package/dist/renderer/shaders/patternFill.js.map +1 -1
  483. package/dist/renderer/shaders/registerProgram.js.map +1 -1
  484. package/dist/renderer/strokeLengths.js.map +1 -1
  485. package/dist/renderer/textures/registerTexture.js +25 -5
  486. package/dist/renderer/textures/registerTexture.js.map +1 -1
  487. package/dist/renderer.d.ts +16 -15
  488. package/dist/renderer.js +17 -16
  489. package/dist/resize.d.ts +5 -6
  490. package/dist/resize.js +5 -5
  491. package/dist/style.css +1 -1
  492. package/dist/test-hook/install.js +12 -6
  493. package/dist/test-hook/install.js.map +1 -1
  494. package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
  495. package/dist/test-seams.d.ts +2 -2
  496. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  497. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  498. package/dist/tools/builtin/hand/useHandTool.js +1 -0
  499. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  500. package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
  501. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  502. package/dist/tools/builtin/line/useLineTool.js +1 -0
  503. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  504. package/dist/tools/builtin/pen/usePenTool.js +153 -103
  505. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  506. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  507. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  508. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
  509. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  510. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  511. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  512. package/dist/tools/builtin/select/selectionContributions.js +24 -23
  513. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  514. package/dist/tools/builtin/select/useSelectTool.js +51 -48
  515. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  516. package/dist/tools/builtin/shared/pointerFollower.js +17 -0
  517. package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
  518. package/dist/tools/builtin/slice/useSliceTool.js +49 -54
  519. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  520. package/dist/tools/builtin/star/useStarTool.js +22 -16
  521. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  522. package/dist/tools/builtin/text/useTextTool.js +1 -0
  523. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  524. package/dist/tools/useKeybindings.js +14 -30
  525. package/dist/tools/useKeybindings.js.map +1 -1
  526. package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
  527. package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
  528. package/dist/util/color.js +3 -3
  529. package/dist/util/color.js.map +1 -1
  530. package/dist/util/paint.js.map +1 -1
  531. package/dist/version.js +1 -1
  532. package/package.json +20 -18
  533. package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
  534. package/dist/builtins-C-VgUlWI.d.ts +0 -4514
  535. package/dist/core/isPlainObject.js.map +0 -1
  536. package/dist/debug/flag.js.map +0 -1
  537. package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
  538. package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
  539. package/dist/renderer/shaders/imageFill.js +0 -14
  540. package/dist/renderer/shaders/imageFill.js.map +0 -1
  541. package/dist/tools/prefs.js +0 -62
  542. package/dist/tools/prefs.js.map +0 -1
@@ -1,4514 +0,0 @@
1
- import { G as GlMat3, a as ShaderProgramHandle, b as ShaderUniform, c as ShaderProgram } from './registerProgram-BMZRDgrh.js';
2
- import { Mesh } from '@weasel-js/geom/tessellate';
3
- import { GradStop, ColorSpace, FillStyle, Stroke, ScreenLength } from '@weasel-js/paint';
4
- import { NodeId, Bounds, View, ModifierState, SelectionApi, ActionDeps, IngestItem, DragSample, DebugSink, Point2, LongPressState, AnyToolOf, Contribution as Contribution$1, ContributionChrome as ContributionChrome$1, Tool as Tool$1, ToolDef as ToolDef$1 } from '@weasel-js/routing';
5
- import { Path, EasingSpec, SpringPresetName, PolygonPath, Rect, Mat3 } from '@weasel-js/geom';
6
- import { ReactNode } from 'react';
7
- import * as _weasel_js_history from '@weasel-js/history';
8
- import { Op, History, SerializedHistory } from '@weasel-js/history';
9
- import { L as LayoutStrategy, a as ParallaxOpts, C as ContainerBounds, b as SceneAdapter, P as PlaneMap, R as RegionPickView, c as LayoutDropTargetMode, d as ReflowTransition, I as InsertAdapter } from './types-A37hYkeR.js';
10
- import { FontStyle } from '@weasel-js/font';
11
- import { ResolvedRun, TextStyle, TextVerticalAlign, StyledRun } from '@weasel-js/text';
12
- import { P as PoseDescriptor } from './poseDescriptor-D-OH-tu5.js';
13
- import { ActiveToolContextValue, ToolsApi as ToolsApi$1, UseToolsOptions as UseToolsOptions$1 } from '@weasel-js/routing/react';
14
- import { B as BoundsConstraint, P as PointSnapBehavior } from './types-B_9g6hMM.js';
15
- import { CursorSpec } from '@weasel-js/cursor';
16
- import { ClaimableGesture } from '@weasel-js/gestures';
17
-
18
- /**
19
- * Folding a node's pose up its parent chain to world, and back down. Lives
20
- * under `core/` so the scene can lay out in world without reaching up into
21
- * `features/`; `features/groups/composePose` re-exports all of it.
22
- */
23
- /** Minimal adapter needed by `composeWorldPose` and friends — pose lookup plus parent walk. */
24
- interface PoseAdapter<TPose> {
25
- getPose(id: string): TPose;
26
- getParent(id: string): string | null;
27
- /** Whether `id`'s pose is a frame its children are expressed in. Absent
28
- * means every node is one. A container whose pose derives from its children
29
- * is not: its children share its frame, so the fold passes over it. */
30
- definesFrame?(id: string): boolean;
31
- }
32
- /**
33
- * The transforms a `compose` represents **exactly**. A parent transform wider
34
- * than its strategy's closure is rounded to the nearest pose — for `'rigid'`
35
- * that means an anisotropically scaled parent turns a rotated child into a
36
- * parallelogram, which `{x, y, width, height, rotation}` cannot hold, so the
37
- * shear is dropped.
38
- *
39
- * Measured over 50,000 random parent/child pairs: rotation and translation
40
- * compose to within 5.7e-13, anisotropic scale to a right-angle error of 0.99.
41
- * See `docs/superpowers/specs/2026-09-10-group-as-frame-design.md`.
42
- */
43
- type PoseClosure = 'identity' | 'translation' | 'rigid';
44
- /** Consumer's pose-composition strategy for hierarchical scenes. `compose`
45
- * folds a child's pose (in parent's frame) up to the next frame; `decompose`
46
- * is its inverse. Default is IDENTITY — an absolute-pose scene where every
47
- * node already stores world coords (parent is grouping-only, no transform). */
48
- interface PoseComposition<TPose> {
49
- compose: (parent: TPose, child: TPose) => TPose;
50
- decompose: (parent: TPose, world: TPose) => TPose;
51
- closure: PoseClosure;
52
- }
53
- /** Default pose-composition strategy: IDENTITY. Both `compose` and
54
- * `decompose` return the child/world pose unchanged, modeling an
55
- * absolute-pose scene where every node stores world coords and parents are
56
- * grouping-only (no transform). With this strategy `composeWorldPose`
57
- * returns a node's own raw pose and `rebaseLocalPose` is a no-op. */
58
- declare const IDENTITY_POSE_COMPOSITION: PoseComposition<unknown>;
59
- /**
60
- * Walk `id`'s parent chain (root first to id last) and fold local poses into
61
- * a world pose via `compose`. Returns the world pose for `id`. Cycle-safe:
62
- * a visited-set guard breaks if the chain ever loops back to itself.
63
- *
64
- * `compose(parent, child)` interprets `child` as expressed *in `parent`'s
65
- * local frame* and returns the equivalent pose in the next frame up. For a
66
- * standard translation-only rect: `world = { x: p.x + c.x, y: p.y + c.y,
67
- * width: c.width, height: c.height }`.
68
- */
69
- declare function composeWorldPose<TPose>(adapter: PoseAdapter<TPose>, id: string, compose: (parent: TPose, child: TPose) => TPose): TPose;
70
- /**
71
- * Convert `worldPose` into a local pose expressed under `newParentId`'s
72
- * frame. Used when reparenting so the child's visual world position is
73
- * preserved despite the change of frame. Inverse of one `compose` step.
74
- *
75
- * `decompose(parent, world)` returns the local pose `child` such that
76
- * `compose(parent, child) === world`. For axis-aligned rects:
77
- * `child = { ...world, x: world.x - parent.x, y: world.y - parent.y }`.
78
- *
79
- * Pass `newParentId === null` for the root frame; the function returns
80
- * `worldPose` unchanged.
81
- */
82
- declare function rebaseLocalPose<TPose>(adapter: PoseAdapter<TPose>, worldPose: TPose, newParentId: string | null, compose: (parent: TPose, child: TPose) => TPose, decompose: (parent: TPose, world: TPose) => TPose): TPose;
83
-
84
- /**
85
- * Axis-aligned rectangle pose with optional rotation. The canonical pose
86
- * shape used by `composeRectPose` and the `unionBounds` helper. Rotation is
87
- * in radians, pivoted on the unrotated AABB center; absent === 0. Kit-side
88
- * code that consumes rotation already reads `pose.rotation ?? 0`
89
- * (`wrapNodeOutput`, `rotate/handle.ts`, `pathInWorld.ts`), so
90
- * the slot exists on every default scene whether or not the consumer
91
- * populates it.
92
- */
93
- interface RectPose {
94
- x: number;
95
- y: number;
96
- width: number;
97
- height: number;
98
- /** Rotation in radians around the unrotated AABB center. Absent === 0. */
99
- rotation?: number;
100
- }
101
-
102
- /** Brand a string as a NodeId. */
103
- declare const asNodeId: (s: string) => NodeId;
104
- /**
105
- * One dependency, as a derivation reads it: the node itself and the pose it is
106
- * painted at.
107
- *
108
- * The node comes along because a connector legitimately reads more than a box
109
- * — an edge that thickens with its endpoint's weight, or routes only to nodes
110
- * on a given layer, is answering off `data` and `layer`. The scene already
111
- * invalidates dependents on `kit:setData` and `kit:setLayer` for exactly that,
112
- * so handing over only the pose made the invalidation pay for a read nothing
113
- * could perform.
114
- */
115
- interface DerivedDep<TPose, TData = unknown, TLayer extends string = string> {
116
- node: Node<TData, TLayer, TPose>;
117
- /** Its override when it has one, else its own derived pose, else the pose
118
- * the document stores — `effectivePose`, the same answer the renderer uses. */
119
- pose: TPose;
120
- /** The path it derives, or `null` when it derives none. Read it to place
121
- * something *along* a dependency rather than beside it — a label on a
122
- * routed edge, a tick on a curve.
123
- *
124
- * Resolved on first read and memoized on the dependency, so a route costs
125
- * the same whether one node reads it or five, and a dependency nobody asks
126
- * about costs nothing. */
127
- readonly path: Path | null;
128
- }
129
- /** `F` with its parameters checked bivariantly, as a method's are, so that a
130
- * `Scene<D, L, MyPose>` is a `Scene<unknown, string, unknown>`. A callback for
131
- * an unrelated pose is still rejected; one for a narrower pose is not. */
132
- type Bivariant<F extends (...args: never[]) => unknown> = {
133
- method(...args: Parameters<F>): ReturnType<F>;
134
- }['method'];
135
- /** Computes a node's path from its dependencies: the type of a node's
136
- * `derivePath` and of each entry in `SceneRegistry.derivePath`. */
137
- type DerivePathFn<TPose, TData = unknown, TLayer extends string = string> = Bivariant<(node: Node<TData, TLayer, TPose>, deps: readonly (DerivedDep<TPose, TData, TLayer> | undefined)[]) => Path | null>;
138
- /** Computes a node's pose from its dependencies: the type of a node's
139
- * `derivePose` and of each entry in `SceneRegistry.derivePose`. */
140
- type DerivePoseFn<TPose, TData = unknown, TLayer extends string = string> = Bivariant<(node: Node<TData, TLayer, TPose>, deps: readonly (DerivedDep<TPose, TData, TLayer> | undefined)[]) => TPose | null>;
141
- /** A container's clip, from its pose: the type of `ContainerNode.clipFromPose`
142
- * and of each entry in `SceneRegistry.clipFromPose`. */
143
- type ClipFromPoseFn<TPose> = Bivariant<(pose: TPose) => Path | null>;
144
- interface NodeBase<TData, TLayer extends string, TPose> {
145
- id: NodeId;
146
- layer: TLayer;
147
- pose: TPose;
148
- data: TData;
149
- parent: NodeId | null;
150
- /** Nodes whose poses this node's geometry is computed from. Fixed at add
151
- * time. Absent or empty means the node's geometry is authored, which is the
152
- * normal case.
153
- *
154
- * `'children'` means "my own children, in child order" — a container that
155
- * hugs its contents, which a fixed id list cannot express because
156
- * reparenting would have to maintain it. The two forms differ in lifetime
157
- * as well as in membership: deleting a node deletes everything that names
158
- * it in `dependsOn`, but a container outlives the children it derives
159
- * from — an emptied group is still a group. */
160
- dependsOn?: readonly NodeId[] | 'children';
161
- /** Whether the hit-test walk can return this node. Default `true`; `false`
162
- * makes it transparent to picking, so a click on it lands on whatever is
163
- * behind — normally its own container.
164
- *
165
- * For content a container owns rather than content the user selects on its
166
- * own: the label inside a box, a body's rows. Without it the innermost hit
167
- * wins, and dragging a labeled box moves the label out of the box. It does
168
- * not hide the node from anything else — it still paints, still clips, and
169
- * a consumer can still select it by id. */
170
- pickable?: boolean;
171
- /** Computes this node's path from its dependencies, in `dependsOn` order —
172
- * each one the node and the pose it is painted at.
173
- * A dependency that has been removed arrives as `undefined`.
174
- * Returning `null` means "nothing to draw right now". Re-evaluated when a
175
- * dependency's world pose changes, never authored. Absolute-pose `Scene`
176
- * makes that the dependency's own pose, and an ancestor's move reaches it as
177
- * a `setPose` of its own from the container cascade. */
178
- derivePath?: DerivePathFn<TPose, TData, TLayer>;
179
- /** Computes this node's pose from its dependencies' poses, the same way
180
- * `derivePath` computes its path — same `dependsOn` list, same
181
- * registry-keyed serialization.
182
- *
183
- * Where a derived path is resolved at paint time and reaches only the
184
- * painter, a derived pose is what the node *is* at: it feeds bounds,
185
- * hit-testing, selection chrome, snapping and layout, and every reader
186
- * gets it through `effectivePose`. A pose override still wins over it —
187
- * that is what lets a drag preview a node the document says is elsewhere.
188
- *
189
- * Returning `null` means "I have nothing to derive from right now", and
190
- * the node falls back to its authored `pose`. `setPose` on a derived node
191
- * still writes that authored pose; it is simply not what anything reads. */
192
- derivePose?: DerivePoseFn<TPose, TData, TLayer>;
193
- }
194
- /** A node with no children — a shape, a label, an image. */
195
- interface LeafNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
196
- kind: 'leaf';
197
- }
198
- /** A node with an ordered list of children. This is the real group: what
199
- * Cmd+G creates, what SVG `<g>` round-trips to. A container has its own pose,
200
- * which its children's poses are relative to, and may optionally clip them. */
201
- interface ContainerNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
202
- kind: 'container';
203
- children: NodeId[];
204
- /** Optional clip-path source. Re-evaluated each render. Returning `null`
205
- * means "no clip for this container right now"; an empty / zero-area path
206
- * means "clip everything out" (children render nowhere). When set, the
207
- * renderer rasterizes the returned path into the stencil buffer and
208
- * paints descendants only where it covers. */
209
- clipFromPose?: ClipFromPoseFn<TPose>;
210
- /** How this container arranges its children. Whenever the container's
211
- * child set or order changes, or its bounds change size, the scene hands
212
- * the change to the strategy — `depart` for children that left it,
213
- * `arrive` for children that joined it, `childPoses` otherwise — and records the poses it writes in the same
214
- * undo entry as the change. A drag reads it too: it is the layout `move`
215
- * reflows and drops against. */
216
- layout?: LayoutStrategy<TPose>;
217
- }
218
- /** A pose a container's layout wrote — reported by {@link Scene.onReflow} —
219
- * in the node's stored frame. */
220
- interface LayoutMove<TPose> {
221
- id: NodeId;
222
- from: TPose;
223
- to: TPose;
224
- }
225
- /** How a scene reads a laid-out container's bounds and folds poses to the
226
- * world frame a {@link LayoutStrategy} works in. */
227
- interface LayoutFrame<TPose> {
228
- /** The container's bounds, from its world pose. Default: the pose's own
229
- * `x`/`y`/`width`/`height`; a pose without them is not laid out. */
230
- bounds?(pose: TPose): ContainerBounds | null;
231
- /** `pose` resized from bounds `from` to `to`, for a layout that grows its
232
- * container. Default: `to`'s fields written over the pose. */
233
- remap?(pose: TPose, from: ContainerBounds, to: ContainerBounds): TPose;
234
- /** Omit for the absolute-pose model. A scene whose canvas composes poses
235
- * (`<SceneCanvas poseComposition>`) passes the same strategy here. */
236
- composition?: PoseComposition<TPose>;
237
- }
238
- /** A node in the scene tree: either a leaf or a container. Re-exported
239
- * publicly as `SceneNode`, to avoid colliding with the DOM's `Node`. */
240
- type Node<TData, TLayer extends string, TPose = RectPose> = LeafNode<TData, TLayer, TPose> | ContainerNode<TData, TLayer, TPose>;
241
- interface LayerRecordBase<TLayer extends string> {
242
- id: TLayer;
243
- visible: boolean;
244
- locked: boolean;
245
- /** Draw this layer as a parallax plane: through a view derived from the
246
- * camera's (see `deriveParallaxView`), and picked through the same one.
247
- * Absent means the layer moves with the camera. */
248
- parallax?: ParallaxOpts;
249
- }
250
- /** A layer declared when the scene was created. Fixed set, no display name —
251
- * these are the kit's own render bands, not something a user manages. */
252
- interface SystemLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
253
- kind: 'system';
254
- }
255
- /** A layer the user created and can rename, reorder or delete. */
256
- interface UserLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
257
- kind: 'user';
258
- name: string;
259
- }
260
- /** Per-layer metadata held by the scene: whether it is visible and locked,
261
- * and where it sits in the render stack. Distinct from a node's `layer` tag,
262
- * which merely names one of these. */
263
- type LayerRecord<TLayer extends string> = SystemLayerRecord<TLayer> | UserLayerRecord<TLayer>;
264
- /** What `Scene.add` needs to mint a node. Everything except the id is
265
- * required; the id is generated unless one is supplied. */
266
- interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
267
- kind: 'leaf' | 'container';
268
- layer: TLayer;
269
- pose: TPose;
270
- data: TData;
271
- parent?: NodeId | null;
272
- index?: number;
273
- /** Explicit id wins over the Scene's `generateId` and the kit default. */
274
- id?: NodeId;
275
- /** Mirrors `SceneNode.pickable`. Omit for the default, which is pickable. */
276
- pickable?: boolean;
277
- /** Only meaningful when `kind === 'container'`. Attach a clip-path function
278
- * to the node; ignored for leaves. Mirrors `ContainerNode.clipFromPose`. */
279
- clipFromPose?: ClipFromPoseFn<TPose>;
280
- /** Mirrors `SceneNode.dependsOn`. */
281
- dependsOn?: readonly NodeId[] | 'children';
282
- /** Mirrors `SceneNode.derivePath`. Taken as a live function; its registry key is
283
- * looked up from it, never passed in. */
284
- derivePath?: DerivePathFn<TPose, TData, TLayer>;
285
- /** Mirrors `SceneNode.derivePose`, on the same terms as `derivePath`. */
286
- derivePose?: DerivePoseFn<TPose, TData, TLayer>;
287
- /** Mirrors `ContainerNode.layout`; ignored for leaves. */
288
- layout?: LayoutStrategy<TPose>;
289
- }
290
- /** A custom scene mutation registered with `Scene.registerOp`: how to apply
291
- * it and how to undo it. The pair is what makes it participate in history. */
292
- interface RegisteredOp<P> {
293
- apply: (payload: P) => void;
294
- revert: (payload: P) => void;
295
- }
296
- /** One of the layers a scene is created with. */
297
- interface SystemLayerSpec<TLayer extends string> {
298
- id: TLayer;
299
- visible?: boolean;
300
- locked?: boolean;
301
- /** See `LayerRecord.parallax`. */
302
- parallax?: ParallaxOpts;
303
- }
304
- /** Argument to `Scene.addLayer`. Always produces a `UserLayerRecord`
305
- * (`kind: 'user'`). */
306
- interface AddLayerSpec<TLayer extends string> {
307
- id: TLayer;
308
- name: string;
309
- /** Default `true`. */
310
- visible?: boolean;
311
- /** Default `false`. */
312
- locked?: boolean;
313
- /** See `LayerRecord.parallax`. */
314
- parallax?: ParallaxOpts;
315
- /** Render-stack position. Default: top of stack (highest render index). */
316
- index?: number;
317
- }
318
- /** One layer as it appears in a snapshot. A snapshot written before layer
319
- * kind was serialized carries neither field, and loads as a system layer —
320
- * which is what every layer in such a snapshot was. */
321
- interface SerializedLayer<TLayer extends string> extends SystemLayerSpec<TLayer> {
322
- kind?: 'system' | 'user';
323
- /** Present on a user layer, absent on a system one. */
324
- name?: string;
325
- }
326
- /** JSON-serializable shape of a Scene's current state. Produced by
327
- * `scene.toJSON()`; consumed by `sceneFromJSON()`. Function fields
328
- * (e.g., `clipFromPose`) appear as string keys (`clipFromPoseKey`) and
329
- * are resolved through `SceneRegistry` at load time. */
330
- interface SerializedScene<TData, TLayer extends string, TPose> {
331
- version: 1;
332
- /** The whole layer stack, system and user alike — the name predates user
333
- * layers and is kept so existing snapshots still parse. */
334
- systemLayers: readonly SerializedLayer<TLayer>[];
335
- nodes: readonly SerializedNode<TData, TLayer, TPose>[];
336
- }
337
- /** JSON-serializable shape of a single node. Mirrors `AddNodeSpec` but
338
- * with function fields replaced by registry keys. */
339
- interface SerializedNode<TData, TLayer extends string, TPose> {
340
- id: string;
341
- kind: 'leaf' | 'container';
342
- layer: TLayer;
343
- pose: TPose;
344
- data: TData;
345
- /** Parent id; omitted for roots. */
346
- parent?: string;
347
- /** Registry key for the container's clip-path factory.
348
- * Containers only; omitted when the container has no clip. */
349
- clipFromPoseKey?: string;
350
- /** Ids this node's geometry derives from, or `'children'`. Omitted when it
351
- * derives from nothing. */
352
- dependsOn?: readonly string[] | 'children';
353
- /** Mirrors `SceneNode.pickable`. Omitted when the node is pickable. */
354
- pickable?: boolean;
355
- /** Registry key for the node's `derivePath` function. Omitted when it has none. */
356
- derivePathKey?: string;
357
- /** Registry key for the node's `derivePose` function. Omitted when it has none. */
358
- derivePoseKey?: string;
359
- /** Registry key for the container's layout strategy. Omitted when it has none. */
360
- layoutKey?: string;
361
- }
362
- /** Per-scene registry mapping string keys to live function references.
363
- * Passed to `createScene({ ..., registry })` and `sceneFromJSON(json, { registry })`.
364
- * Each function-field type has its own keyed map. */
365
- interface SceneRegistry<TPose, TData = unknown, TLayer extends string = string> {
366
- /** Maps registry keys to `clipFromPose` factory functions for container nodes. */
367
- clipFromPose?: Readonly<Record<string, ClipFromPoseFn<TPose>>>;
368
- /** Maps registry keys to `derivePath` functions for nodes with `dependsOn`. */
369
- derivePath?: Readonly<Record<string, DerivePathFn<TPose, TData, TLayer>>>;
370
- /** Maps registry keys to `derivePose` functions for nodes with `dependsOn`. */
371
- derivePose?: Readonly<Record<string, DerivePoseFn<TPose, TData, TLayer>>>;
372
- /** Maps registry keys to container layout strategies. */
373
- layout?: Readonly<Record<string, LayoutStrategy<TPose>>>;
374
- }
375
- /**
376
- * Decides what one edit's changes to containers do to their children.
377
- * `arrivals` maps each container to the ids that joined it — by an `add`
378
- * under it or a `move` into it, whatever op or call made them — in the order
379
- * they arrived. `changed` names every container whose child set or order
380
- * changed, or whose pose changed size, arrivals included. Both are read once
381
- * the edit's other mutations have landed.
382
- *
383
- * Return the poses to write, in each node's stored frame; they become part of
384
- * the same edit, and so of the same undo step. An empty map writes nothing.
385
- * Return `null` to refuse: the whole edit is reverted (see
386
- * `SceneArrivalRefused`).
387
- *
388
- * Runs only for live edits. Undo, redo and a restored history replay the
389
- * arrangement that was decided the first time. With none installed, a scene
390
- * holding declared layouts (`ContainerNode.layout`) runs its own.
391
- */
392
- type SceneArrivalHandler<TPose> = (arrivals: ReadonlyMap<NodeId, readonly NodeId[]>, changed: ReadonlySet<NodeId>,
393
- /** The nodes that left each container in the edit, by the container they
394
- * left — removed, or moved out. */
395
- departures: ReadonlyMap<NodeId, readonly NodeId[]>) => ReadonlyMap<NodeId, TPose> | null;
396
- /** Options for `useScene` — the layers the scene has, what it starts out
397
- * holding, and how its history behaves. */
398
- interface UseSceneOptions<TData, TLayer extends string, TPose = RectPose> {
399
- systemLayers: readonly SystemLayerSpec<TLayer>[];
400
- initial?: readonly AddNodeSpec<TData, TLayer, TPose>[];
401
- ops?: Readonly<Record<string, RegisteredOp<unknown>>>;
402
- historyLimit?: number;
403
- /** Window (ms) within which consecutive same-shaped mutations merge into
404
- * the previous undo entry (matching per-op coalesce keys — e.g. repeated
405
- * `setPose` on the same node, or repeated `applyBatch` calls whose ops
406
- * carry matching `coalesceKey` multisets). `0` (default) disables
407
- * coalescing: every mutation is a discrete undo entry. Undo of a
408
- * coalesced entry returns to the state before the first merged mutation;
409
- * redo restores the latest. `scene.batch` entries never coalesce. */
410
- coalesceWindowMs?: number;
411
- generateId?: () => NodeId;
412
- /** Per-scene registry for non-serializable function fields (clipFromPose, etc.).
413
- * Required only when serializing/deserializing scenes that use function fields. */
414
- registry?: SceneRegistry<TPose, TData, TLayer>;
415
- /** How laid-out containers are measured. See {@link LayoutFrame}. */
416
- layoutFrame?: LayoutFrame<TPose>;
417
- /** When supplied, `scene.applyOps(ops, label)` consults this on every call.
418
- * If it returns a non-null `Journal`, ops are routed to the journal's
419
- * `applyBatch` instead of recording a new parent-history entry. The journal
420
- * drives adapter mutation internally (via `op.apply(adapter)`), so the
421
- * scene state changes as normal; only the history tracking differs.
422
- *
423
- * The accessor is called on every `applyOps` invocation so the caller can
424
- * swap the active journal in and out by updating the closure's reference
425
- * (e.g., an app's mode machine holds `let activeJournal: Journal | null`
426
- * and the accessor reads that variable).
427
- *
428
- * When the accessor isn't known at scene-construction time (typical for
429
- * mode machines that depend on `scene.history`), pass nothing here and
430
- * wire it after construction via `scene.setActiveJournalAccessor(fn)`. */
431
- getActiveJournal?: () => _weasel_js_history.Journal | null;
432
- /** Re-render the host on every scene mutation. Default `true`. Set `false`
433
- * when the scene is read by a frame loop rather than by a render — a game
434
- * loop, a simulation — and nothing in the host's DOM derives from it.
435
- * Read by `useScene`; `createScene` ignores it. */
436
- subscribe?: boolean;
437
- }
438
- /**
439
- * One node's ephemeral presentation override — what a frame loop wants to say
440
- * about a node without saying it about the document.
441
- *
442
- * Not document content: never recorded in history, never in `toJSON`, and
443
- * writing one does not bump `Scene.getVersion()`. Hoist one entry per node and
444
- * mutate it in place on a frame loop; `PoseOverrides.commit()` is what makes a
445
- * mutation visible.
446
- */
447
- interface PoseOverride<TPose> {
448
- /** Replaces the node's document pose everywhere the render and hit-test
449
- * paths read one, including the clip a container derives from its pose, and
450
- * winning over a `derivePose`. Resolved by `effectivePose` — reading
451
- * `node.pose` directly is how those paths came to disagree about where a
452
- * node is. */
453
- pose?: TPose;
454
- /** Multiplied into the node's painted alpha, on top of any `alphaFor`. */
455
- alpha?: number;
456
- }
457
- /**
458
- * The scene's ephemeral per-node overrides — see {@link PoseOverride}.
459
- *
460
- * The intended shape of a frame is: `set` each node once, mutate the entries
461
- * in place per frame, `commit()` once. `commit` is not optional bookkeeping —
462
- * the painter memo keys on pose *reference*, so a mutation without a commit
463
- * paints the previous frame with no error.
464
- *
465
- * To promote a frame to document state (dropping a drag, baking an animation),
466
- * write it once through `Scene.setPose` and `clear` the override.
467
- */
468
- interface PoseOverrides<TPose> {
469
- /** Store `entry` for `id` **by reference**; the caller keeps mutating it. */
470
- set(id: NodeId, entry: PoseOverride<TPose>): void;
471
- /** The entry `set` stored, by reference — what a writer checks to know
472
- * whether its own entry is still installed. Painting reads `read`. */
473
- get(id: NodeId): PoseOverride<TPose> | undefined;
474
- /** What the node is drawn and picked with: every source of overrides for
475
- * it folded into one, as of the last write or `commit()`. `undefined` for
476
- * a node nothing overrides. The object is reused per node. */
477
- read(id: NodeId): PoseOverride<TPose> | undefined;
478
- has(id: NodeId): boolean;
479
- /** The overridden ids, as a snapshot array. */
480
- ids(): readonly NodeId[];
481
- clear(id: NodeId): void;
482
- clearAll(): void;
483
- /** Publish this frame's in-place mutations: invalidate the painter memo for
484
- * every overridden node, then notify subscribers. */
485
- commit(): void;
486
- /** Notified after every write. The canvas uses this to repaint without a
487
- * scene version bump. */
488
- subscribe(fn: () => void): () => void;
489
- /** Monotonic write counter. A snapshot for observers that poll. */
490
- getGeneration(): number;
491
- }
492
- /**
493
- * The kit-owned scene tree: nodes, layers, and the undo history over both.
494
- *
495
- * A scene is logical, not visual — it says what exists and where, and nothing
496
- * about how it is painted. Every mutating method is undoable, and reads are
497
- * snapshots rather than live views. Nodes are addressed by `NodeId`; hold ids
498
- * across mutations, not node objects.
499
- *
500
- * Three type parameters keep it domain-agnostic: `TData` is the app's payload,
501
- * which the kit never inspects; `TPose` is the transform shape, `RectPose` by
502
- * default; `TLayer` is the union of layer names.
503
- */
504
- interface Scene<TData, TLayer extends string, TPose = RectPose> {
505
- readonly nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>;
506
- readonly roots: readonly NodeId[];
507
- readonly layers: readonly LayerRecord<TLayer>[];
508
- /** The registry this scene resolves node functions against — the consumer's
509
- * entries over the kit's. */
510
- readonly registry: SceneRegistry<TPose, TData, TLayer>;
511
- get(id: NodeId): Node<TData, TLayer, TPose> | undefined;
512
- /** A token for the node `id` names right now, for telling one node from a
513
- * later one that reuses its id. It holds for as long as the node stays in
514
- * the scene, whatever edits it takes, and is different every time the id
515
- * enters the scene — by `add`, by an undo or redo that brings it back, or
516
- * by `loadState`. `undefined` when `id` is not in the scene. Compare tokens
517
- * for equality only; their order means nothing. */
518
- incarnation(id: NodeId): number | undefined;
519
- childrenOf(id: NodeId): readonly NodeId[];
520
- ancestorsOf(id: NodeId): readonly NodeId[];
521
- renderOrder(): Iterable<NodeId>;
522
- /** The same layer-major sequence as {@link Scene.renderOrder}, as the nodes
523
- * themselves. Prefer this wherever the ids are only going to be resolved
524
- * back to nodes: the traversal already holds them, and re-looking each one
525
- * up was ~40% of the area hit-test's per-node cost. Cached until a
526
- * structural edit, so repeat calls hand back the same array — a snapshot,
527
- * not a live view, and not yours to mutate. */
528
- renderOrderNodes(): readonly Node<TData, TLayer, TPose>[];
529
- /** The nodes on one layer, in render order. Cached alongside
530
- * {@link Scene.renderOrderNodes} until a structural edit, so a caller that
531
- * only cares about one layer pays the tree walk once rather than once per
532
- * call. A snapshot, not a live view, and not yours to mutate. An unknown
533
- * layer id yields an empty array. */
534
- nodesOnLayer(layer: TLayer): readonly Node<TData, TLayer, TPose>[];
535
- add(spec: AddNodeSpec<TData, TLayer, TPose>): NodeId;
536
- /** Delete `id`, its **entire subtree**, and **everything that derives from**
537
- * any of those nodes — a node listing one of them in `dependsOn` goes too,
538
- * along with its own subtree, transitively. A dependent can live anywhere in
539
- * the tree, so this deletes nodes the caller never named and may unlink
540
- * several disjoint subtrees at once. Recorded as one undoable step; `undo()`
541
- * restores every one of them where it was, child order intact. */
542
- remove(id: NodeId): void;
543
- /** {@link remove} over several roots at once, as a **single** undoable step.
544
- * Ids resolve against the tree as it stands at the call, so an id that
545
- * another one would cascade away is absorbed rather than removed twice —
546
- * which is what makes it safe to pass a whole selection. Throws if any id is
547
- * not in the scene; an empty list does nothing and records no step. */
548
- removeMany(ids: readonly NodeId[]): void;
549
- /** Every node {@link removeMany} would take if given `ids`: each id, its
550
- * subtree, everything deriving from any of those, and those nodes' subtrees
551
- * in turn. The authoritative answer — the cascade relations live here, and
552
- * a caller reconstructing them from `dependsOn` and `children` drifts the
553
- * moment a third one is added. Ids not in the scene come back unchanged.
554
- *
555
- * A set, not a restore order: a dependent can be reached before the parent
556
- * it sits under, which is also in the closure. */
557
- removalClosure(ids: readonly NodeId[]): readonly NodeId[];
558
- update(id: NodeId, patch: {
559
- data: TData;
560
- }): void;
561
- setPose(id: NodeId, pose: TPose): void;
562
- /** Retag `id` to `layer`. On a **container this cascades**: every descendant
563
- * is moved to the same layer, recorded as a **single** undo step.
564
- *
565
- * Invariants:
566
- * - **Layer floor** — a child may not render below its parent, so retagging
567
- * to a layer *below* the node's parent throws. Retagging to the parent's
568
- * layer or any higher one is allowed; a node with no parent is
569
- * unconstrained.
570
- * - **No-op elision** — setting the layer a node already has does nothing
571
- * and pushes **no** history entry. */
572
- setLayer(id: NodeId, layer: TLayer): void;
573
- /** Point `id` at a different set of dependencies — retargeting an edge's end
574
- * onto another node, or switching a container between an id list and
575
- * `'children'`. Recorded as one undoable step.
576
- *
577
- * Order is significant: a derivation reads its dependencies positionally,
578
- * so `[b, a]` is not `[a, b]`. Ids need not be in the scene — a declared
579
- * dependency that appears later invalidates the node when it does, the same
580
- * as one declared at `add`. Passing `undefined` drops the declaration, after
581
- * which nothing cascades the node away.
582
- *
583
- * **No-op elision** — declaring what the node already declares does nothing
584
- * and pushes no history entry. */
585
- setDependsOn(id: NodeId, dependsOn: readonly NodeId[] | 'children' | undefined): void;
586
- /** Give container `id` a different layout — a strategy, its key in
587
- * `SceneRegistry.layout`, or `null` for none — and re-arrange its children
588
- * under it, as one undoable step. Throws for a leaf, or a key the registry
589
- * does not hold. A registered layout is recorded by its key, so a
590
- * serialized history restores it; an unregistered one lives only as long
591
- * as the session.
592
- *
593
- * **No-op elision** — setting the layout a container already has does
594
- * nothing and pushes no history entry. */
595
- setLayout(id: NodeId, layout: LayoutStrategy<TPose> | string | null): void;
596
- /** Reparent `id` under `parent` (or to a root when `parent` is `null`) at
597
- * `index` within the new sibling list, appending when `index` is omitted.
598
- * Siblings are reindexed. Recorded as one undoable step.
599
- *
600
- * Rejected (throws) when:
601
- * - `parent` exists but is a **leaf**, not a container;
602
- * - the move would form a **cycle** — `parent` is `id` itself or one of
603
- * `id`'s own descendants;
604
- * - it would drop `id` **below its new parent's layer** (child may not
605
- * render below its parent).
606
- *
607
- * `move(id, null)` — detaching to a root — is always allowed regardless of
608
- * layer, since a root has no parent to render beneath. */
609
- move(id: NodeId, parent: NodeId | null, index?: number): void;
610
- /** Shift `id` to `index` within its **current** parent's child list. Unlike
611
- * {@link move}, the parent never changes — only sibling order. */
612
- reorder(id: NodeId, index: number): void;
613
- setLayerVisible(layer: TLayer, visible: boolean): void;
614
- /** Lock or unlock a layer. A locked layer still paints, but its nodes are
615
- * out of reach: picking and area selection pass over them, the selection
616
- * drops them, and every node mutation on them throws. Locking is never
617
- * blocked by the lock, and neither are the other layer operations. */
618
- setLayerLocked(layer: TLayer, locked: boolean): void;
619
- /** Set or clear (`undefined`) a layer's parallax — see
620
- * `LayerRecord.parallax`. Undoable; an animation tweening it writes inside
621
- * {@link Scene.untracked} so no frame becomes an undo step. */
622
- setLayerParallax(layer: TLayer, parallax: ParallaxOpts | undefined): void;
623
- /** Whether `id` sits on a locked layer or under a container that does.
624
- * A container's lock covers its whole subtree, whatever layers the
625
- * descendants are tagged to. An id not in the scene is not locked. */
626
- isLocked(id: NodeId): boolean;
627
- /** Run `fn` with the lock guard lifted, for a programmatic edit that has to
628
- * reach a locked node. Undo and redo never need it. */
629
- unlocked<T>(fn: () => T): T;
630
- addLayer(spec: AddLayerSpec<TLayer>): void;
631
- /** Drop a user layer and every node tagged to it, as one undoable step.
632
- * Removal cascades, so this also deletes nodes **on other layers** that
633
- * derive from a node on this one. */
634
- removeLayer(layer: TLayer): void;
635
- renameLayer(layer: TLayer, name: string): void;
636
- moveLayer(layer: TLayer, index: number): void;
637
- registerOp<P>(kind: string, handler: RegisteredOp<P>): void;
638
- recordOp<P>(op: {
639
- kind: string;
640
- payload: P;
641
- }): void;
642
- /** Install (or clear) the active-journal accessor after scene construction.
643
- * Useful when the journal source (typically a mode machine) is built
644
- * with `scene.history` as a dependency — a chicken-and-egg situation
645
- * where the accessor can't be passed in via `UseSceneOptions`.
646
- *
647
- * Pass `null` to detach. Overrides any `getActiveJournal` set in
648
- * `UseSceneOptions`. */
649
- setActiveJournalAccessor(fn: (() => _weasel_js_history.Journal | null) | null): void;
650
- /** Install what the scene does when nodes join a container, replacing any
651
- * handler installed before; `null` removes it. Returns a function that
652
- * removes this handler, and does nothing once another has replaced it.
653
- * See {@link SceneArrivalHandler}. */
654
- setArrivalHandler(handler: SceneArrivalHandler<TPose> | null): () => void;
655
- /** Apply a batch of ops with journal-aware routing.
656
- *
657
- * - **Without active journal** (or no `getActiveJournal` in options):
658
- * the ops themselves are recorded as one undo entry on the scene's own
659
- * history, rebound to `adapter` — undo replays each op's `invert()`
660
- * against that same adapter. Consecutive `applyBatch` entries can
661
- * coalesce via matching op `coalesceKey`s when the scene opts into
662
- * `coalesceWindowMs`.
663
- * - **With active journal**: routes ops to `journal.applyBatch(ops, label)`.
664
- * The scene's history recording is suppressed for the duration so the
665
- * journal's inner history — not the scene's undo stack — tracks the batch.
666
- * Mutations still happen on `adapter` / scene state.
667
- *
668
- * `adapter` must be the same adapter the ops expect (typically a
669
- * `SceneCanvasAdapter`). Pass `this` from `sceneToAdapter` or a compatible
670
- * adapter. */
671
- applyBatch(ops: Op[], label: string, adapter: unknown): void;
672
- /** The transient set of active ids — "operate on these N as a unit".
673
- * Shared by every view over this scene unless a view supplies its own
674
- * (see `CanvasView.selection`). Not document content: it never appears
675
- * in `toJSON`. It does ride on history entries, so undo and redo put
676
- * back the selection an edit was made under; changing it is never an
677
- * undo step of its own. */
678
- getSelection(): readonly NodeId[];
679
- setSelection(ids: readonly NodeId[]): void;
680
- /** Per-node pose / alpha overrides the render and hit-test paths read
681
- * through. Like {@link Scene.getSelection} this is not document content:
682
- * writes are never recorded, never serialized, and do not bump
683
- * {@link Scene.getVersion}. See {@link PoseOverrides}. */
684
- readonly overrides: PoseOverrides<TPose>;
685
- /** The scene's undo/redo engine, in the shape `@weasel-js/history` defines —
686
- * what the kit's `undo` / `redo` actions resolve their `history` dep to, and
687
- * what a mode machine opens journals against. Mutating members route through
688
- * the scene's own wrappers, so an undo driven through this bumps
689
- * {@link Scene.getVersion} and notifies subscribers exactly as
690
- * {@link Scene.undo} does; `getVersion` / `subscribe` on it report the
691
- * engine's history version, not the scene's. */
692
- readonly history: History;
693
- undo(): boolean;
694
- redo(): boolean;
695
- canUndo(): boolean;
696
- canRedo(): boolean;
697
- batch<T>(label: string, fn: () => T): T;
698
- /** The layout container `id` declares, or `null` for a leaf or a
699
- * container that declares none. */
700
- layoutOf(id: NodeId): LayoutStrategy<TPose> | null;
701
- /** Called with the poses each live edit's layout pass wrote, after the
702
- * edit lands — never for an undo or redo, which restore poses rather than
703
- * arrange them. What a reflow animation glides from. */
704
- onReflow(listener: (moves: readonly LayoutMove<TPose>[]) => void): () => void;
705
- /** Run `fn` with every mutation applied but none recorded — for writes
706
- * that are not edits, such as a simulation stepping poses each frame.
707
- * Undo and redo move between recorded states and never restore an
708
- * untracked write; the next recorded change starts from whatever the
709
- * untracked writes left, so undoing it lands there. A `batch` inside
710
- * records nothing; inside a `batch`, these writes stay out of its entry.
711
- * Notifies once, reverts `fn`'s writes if it throws, and throws on
712
- * `applyBatch` and `history.apply` / `applyOps`, which exist to record. */
713
- untracked<T>(fn: () => T): T;
714
- /** Read-only snapshot of every history entry currently reachable from
715
- * the present state. Oldest applied first, then redoable entries in
716
- * the order they'd be re-applied. Each entry id is stable. */
717
- historyEntries(): readonly {
718
- id: string;
719
- label: string;
720
- }[];
721
- /** Index of the "current state". Equals the count of applied entries;
722
- * `0` means "nothing applied" (initial). */
723
- historyIndex(): number;
724
- /** Jump to the given history index by calling undo/redo repeatedly.
725
- * Clamps to [0, total]. Returns true if any movement occurred. */
726
- jumpToHistoryIndex(index: number): boolean;
727
- /** Snapshot the undo/redo history in a JSON-serializable form (the
728
- * engine's `SerializedHistory`). Entries containing any nameless op are
729
- * dropped (hand-rolled anonymous ops passed to `applyBatch`); kit and
730
- * consumer-registered ops always carry names. Payload JSON-safety
731
- * (e.g. typed arrays inside poses) is the caller's concern. Do not call
732
- * mid-`batch` — the open batch's ops are not yet recorded. */
733
- serializeHistory(): SerializedHistory;
734
- /** Replace the undo/redo history from a `serializeHistory()` snapshot.
735
- * Call on a scene whose node/layer state already matches the snapshot's
736
- * head state (i.e. right after `loadState` from the paired scene
737
- * snapshot); node state is NOT mutated. Ops re-registered via
738
- * `registerOp` before this call round-trip; unknown kinds become no-op
739
- * placeholders; external ops rebuild via the global op-factory registry
740
- * and replay against the `setHistoryAdapter` accessor. Restored entries
741
- * never coalesce with new ones. Notifies once. Do not call mid-`batch`:
742
- * the stacks are replaced underneath the open batch, whose eventual
743
- * flush would graft onto (and evict against) the restored stacks. */
744
- restoreHistory(snapshot: SerializedHistory): void;
745
- /** Install (or clear with `null`) the accessor for the adapter that
746
- * RESTORED external ops (recorded via `applyBatch`, rebuilt from a
747
- * `restoreHistory` snapshot) apply against on undo/redo. Resolved lazily
748
- * at each apply, so wiring order relative to `restoreHistory` doesn't
749
- * matter. Live `applyBatch` entries are unaffected (they bind their
750
- * call-site adapter). If unset when a restored op applies, the op is a
751
- * debug-warned no-op. */
752
- setHistoryAdapter(fn: (() => unknown) | null): void;
753
- /** Snapshot the current scene state to a JSON-serializable shape.
754
- * History (undo/redo stacks) is NOT captured. Function fields like
755
- * `ContainerNode.clipFromPose` are translated to string keys via the
756
- * scene's registry; throws if any function field has no matching key. */
757
- toJSON(): SerializedScene<TData, TLayer, TPose>;
758
- /** Replace this scene's entire node + layer state in place from a snapshot
759
- * produced by `toJSON()`. Unlike `sceneFromJSON`, the existing Scene
760
- * instance is preserved — holders such as `<SceneCanvas>` keep their
761
- * reference. History (undo/redo) is cleared, matching `sceneFromJSON`.
762
- * Bumps `getVersion()` and notifies subscribers exactly once.
763
- *
764
- * Throws on an unsupported version or unknown registry/layer ids; on a
765
- * malformed snapshot the scene is left empty or partially populated (callers should treat a
766
- * `loadState` throw as fatal and reload). Snapshots from `toJSON()` are
767
- * always well-formed. */
768
- loadState(json: SerializedScene<TData, TLayer, TPose>): void;
769
- subscribe(listener: () => void): () => void;
770
- /** Monotonically increasing version. Snapshot for `useSyncExternalStore`. */
771
- getVersion(): number;
772
- }
773
-
774
- /** Pixel dimensions of the canvas viewport. */
775
- interface ViewportDims {
776
- width: number;
777
- height: number;
778
- }
779
- /** Options for {@link fitViewToBounds}. */
780
- interface FitViewToBoundsOptions {
781
- /** Inset (in CSS px) around the bounds. Default 16. */
782
- padding?: number;
783
- /**
784
- * Override the kit's system max scale. Defaults to the kit-wide max zoom
785
- * used by `computeWheelAction` (`DEFAULT_MAX_ZOOM`).
786
- */
787
- maxScale?: number;
788
- /**
789
- * Override the kit's system min scale. Defaults to the kit-wide min zoom
790
- * used by `computeWheelAction` (`DEFAULT_MIN_ZOOM`).
791
- */
792
- minScale?: number;
793
- /**
794
- * Fit mode:
795
- * - `'contain'` (default): uniform scale = `min(availW/w, availH/h)`. Bounds fit entirely; one axis has letterboxing.
796
- * - `'fill'`: uniform scale = `max(...)`. Bounds overflow viewport on one axis.
797
- * - `'stretch'`: per-axis scale. Bounds match viewport exactly; scale is non-uniform.
798
- */
799
- mode?: 'contain' | 'fill' | 'stretch';
800
- /**
801
- * Where bounds that overflow the viewport on an axis sit, which happens when
802
- * `minScale` stops the fit or `mode` is `'fill'`. `'center'` (default) shows
803
- * their middle; `'start'` shows their left or top edge, inset by `padding`.
804
- * An axis that fits is centered either way.
805
- */
806
- anchor?: 'center' | 'start';
807
- }
808
- /**
809
- * Compute the target {@link View} that fits `bounds` (world space) inside
810
- * `viewportDims` (CSS px). The bounds are centered in the viewport with
811
- * uniform padding on all sides; the resulting scale is clamped to the
812
- * configured min/max.
813
- *
814
- * View semantics: `view.x` / `view.y` is the world point currently drawn at
815
- * the canvas top-left, and `view.scale.{x,y}` is pixels per world unit on
816
- * each axis (see `view.ts`). So:
817
- * screenX = (worldX - view.x) * view.scale.x
818
- * screenY = (worldY - view.y) * view.scale.y
819
- *
820
- * Empty / zero-sized bounds or viewports cannot be fit — in that case this
821
- * returns `currentView` unchanged and logs a `console.warn`. Callers that
822
- * want to no-op silently can detect zero area themselves before calling.
823
- */
824
- declare function fitViewToBounds(bounds: Bounds, viewportDims: ViewportDims, currentView: View, opts?: FitViewToBoundsOptions): View;
825
-
826
- interface GLMeshHandle {
827
- readonly vao: WebGLVertexArrayObject;
828
- readonly indexCount: number;
829
- readonly requiresStencil: boolean;
830
- readonly anchorA?: Uint32Array;
831
- readonly anchorB?: Uint32Array;
832
- readonly anchorT?: Float32Array;
833
- }
834
- /** Resources to release when a Mesh is reclaimed by GC. */
835
- interface MeshResources {
836
- vao: WebGLVertexArrayObject;
837
- vbo: WebGLBuffer;
838
- ibo: WebGLBuffer;
839
- }
840
- /**
841
- * Caches GL-side buffers + VAO per `Mesh` identity. Upload happens lazily
842
- * on first `handleFor(mesh)` call.
843
- *
844
- * **GC-aware cleanup (deferred-delete queue):** when a Mesh becomes
845
- * unreachable, the WeakMap entry is dropped automatically — but the
846
- * underlying GL resources (VAO, VBO, IBO) would leak forever without an
847
- * explicit `gl.delete*` call. We register each Mesh with a
848
- * `FinalizationRegistry`; when the Mesh is reclaimed, the finalizer pushes
849
- * the resources onto an internal queue. The renderer drains this queue at
850
- * the start of each `render()` call (via `drainPendingDeletes()`), when GL
851
- * is in a known state — no VAO bound, no draw mid-flight. Deleting from the
852
- * finalizer directly was racy (it could fire between two `gl.bindVertexArray`
853
- * calls inside `dispatch()`, sometimes corrupting the next draw); deferring
854
- * the actual delete to a known-safe point keeps the use-after-free risk to
855
- * zero.
856
- *
857
- * Caveats:
858
- * - Finalizer timing is non-deterministic; resources may live for a frame
859
- * or two beyond their Mesh, but they will be reclaimed.
860
- * - If the GL context is lost between finalizer and drain, the drain
861
- * no-ops safely (`gl.isContextLost()` makes deletes no-ops, and
862
- * `gl.deleteBuffer(null)` is allowed).
863
- *
864
- * The cache is GL-context-bound; if the context is lost and re-created, the
865
- * renderer should construct a new GLMeshCache. (Context loss handling lives
866
- * in `WeaselRenderer`.)
867
- */
868
- declare class GLMeshCache {
869
- private readonly gl;
870
- private readonly aPositionLoc;
871
- private readonly map;
872
- /** Meshes this context has drawn at least once; see `uploadRecurring`. */
873
- private readonly seen;
874
- private readonly finalizer;
875
- /** Every persistent upload not yet freed, so `dispose` can reach them. */
876
- private readonly persistent;
877
- private readonly pendingDeletes;
878
- private disposed;
879
- /** Transient resources allocated this frame; freed at end of render(). */
880
- private readonly transientThisFrame;
881
- constructor(gl: WebGL2RenderingContext, aPositionLoc: number);
882
- handleFor(mesh: Mesh): GLMeshHandle;
883
- /**
884
- * Upload a Mesh that the caller knows is single-use (e.g. the per-frame
885
- * stroke ribbon from `tessellateStroke`). Returns a handle backed by fresh
886
- * GL resources that the renderer will free deterministically at the end
887
- * of the current frame via `freeTransient()`. Bypasses the WeakMap cache
888
- * and the FinalizationRegistry, so transient meshes never wait on GC.
889
- */
890
- uploadTransient(mesh: Mesh): GLMeshHandle;
891
- /**
892
- * Upload a Mesh that may or may not recur across frames. Its first sight in
893
- * *this* context takes `uploadTransient`; every later one takes the
894
- * persistent handle. One transient upload to find out is cheaper than
895
- * stranding a persistent VAO on a mesh that never returns, whose release
896
- * would wait on GC. The transient upload does not populate the persistent
897
- * map, so steady-state reuse begins on the third frame.
898
- */
899
- uploadRecurring(mesh: Mesh): GLMeshHandle;
900
- /**
901
- * Free all transient resources allocated since the last call. Called by
902
- * the renderer at the end of each `render()`. Safe under context loss.
903
- */
904
- freeTransient(): void;
905
- /** @internal — for tests asserting the transient-list size. */
906
- _transientCount(): number;
907
- /**
908
- * Free GL resources whose Mesh has been GC'd since the last drain.
909
- * Called by the renderer at the top of each `render()`, before any draws.
910
- */
911
- drainPendingDeletes(): void;
912
- /**
913
- * Free every GL resource this cache holds — persistent, pending and
914
- * transient — and ignore finalizers from here on. Idempotent. Unlike the
915
- * other paths this does not wait on GC, which is what lets a renderer on a
916
- * context it does not own give back everything it uploaded.
917
- */
918
- dispose(): void;
919
- /** @internal — for tests asserting the queue size. */
920
- _pendingDeleteCount(): number;
921
- /** @internal — for tests that need to simulate the finalizer firing. */
922
- _enqueueDeleteForTest(resources: MeshResources): void;
923
- private upload;
924
- }
925
-
926
- /**
927
- * GL texture upload + cache for MSDF font atlases (and future images).
928
- *
929
- * Textures are keyed by a string id (the font family name or image id).
930
- * Pixels are uploaded once per id; a later upload of the same id reuses the
931
- * texture and only re-applies its wrap mode when that changed.
932
- * The cache is GL-context-bound; discard and create a new one on context loss.
933
- *
934
- * Atlas format: RGBA UNSIGNED_BYTE, linear filtering, no mipmaps.
935
- * Mipmap generation is deliberately skipped — MSDF works correctly with
936
- * linear filtering, and mipmap resampling corrupts the multi-channel SDF signal.
937
- */
938
- type TexSource$1 = HTMLImageElement | ImageBitmap | ImageData | HTMLCanvasElement;
939
- /** How a texture samples outside `0..1`. Pattern tiles need `'repeat'`;
940
- * everything else clamps. WebGL2 allows `REPEAT` on NPOT textures, so a
941
- * tile of any size tiles correctly. */
942
- type TextureWrap = 'clamp' | 'repeat';
943
- declare class GLTextureCache {
944
- private readonly gl;
945
- private readonly map;
946
- private readonly wraps;
947
- constructor(gl: WebGL2RenderingContext);
948
- has(id: string): boolean;
949
- /** Uploads the pixels once per id. `wrap` is re-applied whenever it differs
950
- * from what the id was last uploaded under: the same registered image can
951
- * be a clamped shader texture in one draw and a repeating pattern tile in
952
- * the next, and first-upload-wins gave the second one the first one's mode. */
953
- upload(id: string, source: TexSource$1, wrap?: TextureWrap): string;
954
- private applyWrap;
955
- /** Create a single-channel R8 texture from raw bytes (full upload).
956
- * No-op if `id` already exists. Same LINEAR/CLAMP params as `upload`;
957
- * UNPACK_ALIGNMENT dropped to 1 for non-4-aligned row widths. */
958
- uploadR8(id: string, width: number, height: number, data: Uint8Array): void;
959
- /** Patch a rect of an existing R8 texture with tightly-packed w×h bytes. */
960
- subImageR8(id: string, x: number, y: number, w: number, h: number, data: Uint8Array): void;
961
- bind(id: string, unit: number): void;
962
- /**
963
- * Delete every uploaded GL texture and clear the map. Called by
964
- * `WeaselRenderer.dispose()`. The cache is unusable but refillable
965
- * afterward — a subsequent `upload()` for a previously-seen id re-creates
966
- * the texture rather than restoring the deleted one.
967
- */
968
- free(): void;
969
- }
970
-
971
- /**
972
- * GL texture upload cache for ImageBitmap objects.
973
- *
974
- * Key: ImageBitmap (or pattern source object) identity (WeakMap) — lets GC
975
- * reclaim unreferenced bitmaps. The GL textures are NOT freed when the source
976
- * is gc'd; a source's owner that knows it will never be drawn again frees them
977
- * with {@link releaseImageSource}, and `dispose` frees them all.
978
- *
979
- * Wrapping is set once at upload time per the `repetition` parameter:
980
- * - undefined / 'no-repeat' → CLAMP_TO_EDGE
981
- * - 'repeat' → REPEAT (both axes)
982
- * - 'repeat-x' → REPEAT on S, CLAMP on T
983
- * - 'repeat-y' → CLAMP on S, REPEAT on T
984
- *
985
- * Convention §2: texels stored straight; shader premultiplies.
986
- */
987
- type PatternRepetition = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
988
- /** MIN_FILTER strategy for uploaded textures. */
989
- type ImageMinification = 'linear' | 'mipmap';
990
- type TexSource = ImageBitmap | ImageData | HTMLCanvasElement | HTMLImageElement;
991
- declare class GLImageCache {
992
- private readonly gl;
993
- private readonly minification;
994
- /** This context's `MAX_TEXTURE_SIZE`. */
995
- readonly maxTextureSide: number;
996
- private readonly map;
997
- /** Every texture `map` holds, so `dispose` can reach them — a WeakMap
998
- * cannot be enumerated. */
999
- private readonly textures;
1000
- /**
1001
- * MAG_FILTER each texture currently carries, so a redundant write can be
1002
- * skipped.
1003
- *
1004
- * The batch sets this per flush — the same bitmap can be drawn at both
1005
- * filters in one frame, and the value has to be right at the draw rather than
1006
- * at upload. Filtering is state on the *texture object*, though, not on the
1007
- * unit, so re-asserting a value it already has is a write to a live texture
1008
- * for no reason. On a large sheet that is not free: a consumer's wall samples
1009
- * a 5652px-square atlas, 122MB resident, and measured `sampling: 'nearest'`
1010
- * costing up to 8x `'linear'` there — where the linear pass re-asserts the
1011
- * upload default and the nearest pass changes state on every draw.
1012
- *
1013
- * Whether that is the cause is unproven (see `docs/TODO.md`), but the write
1014
- * was redundant either way.
1015
- */
1016
- private readonly magFilters;
1017
- /** `minification` selects the MIN_FILTER strategy for uploaded textures.
1018
- * `'linear'` (default) is the screen path's existing behavior. `'mipmap'`
1019
- * generates mipmaps and filters LINEAR_MIPMAP_LINEAR — required for
1020
- * quality minification when a large source bitmap is drawn small (the
1021
- * headless print/export path); bilinear-only minification undersamples
1022
- * and produces moiré. */
1023
- constructor(gl: WebGL2RenderingContext, minification?: ImageMinification);
1024
- /** Delete `key`'s texture, if it has one. */
1025
- release(key: object): void;
1026
- /** Delete every texture this cache uploaded and stop answering
1027
- * {@link releaseImageSource} and {@link maxImageTextureSide}. Idempotent. */
1028
- dispose(): void;
1029
- /** {@link dispose} without the deletes, for a cache whose context was lost:
1030
- * its textures died with it. */
1031
- abandon(): void;
1032
- upload(key: object, source: TexSource, repetition?: PatternRepetition): WebGLTexture;
1033
- bind(key: object, unit: number): void;
1034
- /** Set `key`'s MAG_FILTER, skipping the call when it already holds that
1035
- * value. Its texture must be bound to the active unit — callers pair this
1036
- * with `bind`. */
1037
- setMagFilter(key: object, filter: number): void;
1038
- }
1039
-
1040
- /**
1041
- * CPU gradient-ramp builder + the GL texture every baked ramp lives in.
1042
- *
1043
- * Each unique stop list is baked once into a 256-texel strip and written to its
1044
- * own **row** of one RGBA texture, keyed by `JSON.stringify(stops)`. One
1045
- * texture rather than one per ramp is what lets a gradient take a batch texture
1046
- * slot the way a bitmap or a font atlas does: every gradient in a frame samples
1047
- * the same unit, so a run does not break per gradient. The atlas is
1048
- * GL-context-bound; discard and recreate on context loss.
1049
- *
1050
- * Output convention §2: texels are stored as straight RGBA. The fragment
1051
- * shader applies premultiplication before writing outColor.
1052
- */
1053
-
1054
- /** Bake gradient stops into a 256-entry RGBA lookup strip, which the shader
1055
- * samples instead of evaluating stops per fragment. `space` is the gradient's
1056
- * `interpolate` — the ramp is where a non-sRGB blend is paid for, once. */
1057
- declare function buildGradientRamp(stops: GradStop[], space?: ColorSpace): Uint8ClampedArray;
1058
- declare class GradientRampAtlas {
1059
- private readonly gl;
1060
- /** Row per stop list, in least-recently-used order: a hit re-inserts, so the
1061
- * first entry is the row a full atlas recycles. */
1062
- private readonly rowByKey;
1063
- private texture;
1064
- private rows;
1065
- /** Mirror of the texture, so growth can respecify it without re-baking every
1066
- * ramp it already holds. */
1067
- private pixels;
1068
- private totalQueries;
1069
- private cacheHits;
1070
- constructor(gl: WebGL2RenderingContext);
1071
- /** Rows the atlas currently holds — its texture height. */
1072
- get height(): number;
1073
- /**
1074
- * Bake `stops` into a row and return it, reusing the row an identical stop
1075
- * list already holds.
1076
- *
1077
- * **A returned row outlives the frame only while the atlas has room.** Past
1078
- * `RAMP_ATLAS_MAX_ROWS` an upload recycles the least recently used row, which
1079
- * rewrites texels a row handed out earlier still names. Nothing that defers
1080
- * its draw past this call may hold a row across another `upload` without
1081
- * arranging to be flushed first.
1082
- */
1083
- upload(stops: GradStop[], space?: ColorSpace): number;
1084
- /**
1085
- * Whether uploading `stops` would move where existing rows sit — by growing
1086
- * the atlas, which changes every row's `v`, or by recycling one, which
1087
- * rewrites its texels.
1088
- *
1089
- * Anything holding a row past this call asks first and gets itself out of
1090
- * the way, because neither can be undone once it has happened.
1091
- */
1092
- wouldReshape(stops: GradStop[], space?: ColorSpace): boolean;
1093
- /**
1094
- * The `v` a row is sampled at — its center.
1095
- *
1096
- * The center is load-bearing: the atlas filters LINEAR, so a `v` anywhere
1097
- * else blends the ramp beside it into this one. At the center the neighbor's
1098
- * weight is exactly zero.
1099
- */
1100
- rowV(row: number): number;
1101
- bind(unit: number): void;
1102
- hitRate(): number;
1103
- resetStats(): void;
1104
- /**
1105
- * Delete the atlas texture and forget every row in it. Called by
1106
- * `WeaselRenderer.dispose()`. The CPU mirror goes with it, so the atlas is
1107
- * unusable but refillable afterward (stats are left as-is; call
1108
- * `resetStats()` separately if desired).
1109
- */
1110
- free(): void;
1111
- /** The row the next ramp is written to: a free one, a taller atlas, or the
1112
- * least recently used row of a full one. */
1113
- private claimRow;
1114
- /** Grow to `rows`, keeping every row at the index it already had — a row
1115
- * index is a stable name, and callers hold them. */
1116
- private resize;
1117
- private writeRow;
1118
- }
1119
-
1120
- /** Row-major 4×5 color matrix identity. */
1121
- declare const IDENTITY_COLOR_MATRIX: Float32Array<ArrayBuffer>;
1122
- interface GroupFrame {
1123
- transform?: GlMat3;
1124
- alpha?: number;
1125
- /** Row-major 4×5 color matrix (20 floats). Absent leaves the stack unchanged. */
1126
- colorMatrix?: Float32Array | number[];
1127
- }
1128
- declare class GroupState {
1129
- private transformStack;
1130
- private alphaStack;
1131
- private colorMatrixStack;
1132
- get transform(): GlMat3;
1133
- get alpha(): number;
1134
- get colorMatrix(): Float32Array;
1135
- push(frame: GroupFrame): void;
1136
- /**
1137
- * Push a frame whose children paint into a surface of their own.
1138
- *
1139
- * The transform still accumulates — the children draw where they would have
1140
- * drawn. Alpha and colour do not: they describe how the finished surface
1141
- * joins the frame, and applying them on the way in as well would fade the
1142
- * pixels an effect is about to read, then fade them again on the way out.
1143
- *
1144
- * Returns the pair the caller must apply at composite time — what `push`
1145
- * would have left on the stack — so the composition rules live here and not
1146
- * in the dispatcher.
1147
- */
1148
- pushIsolated(frame: GroupFrame): {
1149
- alpha: number;
1150
- colorMatrix: Float32Array;
1151
- };
1152
- /** Drop every pushed frame, leaving the root. A frame that throws part-way
1153
- * down the tree never pops, and the next frame would draw under leftovers. */
1154
- reset(): void;
1155
- pop(): void;
1156
- }
1157
-
1158
- /**
1159
- * One full-screen pass over what a group has already drawn.
1160
- *
1161
- * The renderer runs a group's effects in order, each reading the previous
1162
- * one's output through `u_source` and writing a whole new buffer — so an
1163
- * effect is free to read neighbouring pixels, which is the entire point and
1164
- * the one thing `colorMatrix` can never do.
1165
- *
1166
- * `uniforms` are the effect's own; `u_source`, `u_resolution` and `u_texel`
1167
- * come from the renderer and must not be passed here.
1168
- */
1169
- interface Effect {
1170
- program: ShaderProgramHandle;
1171
- uniforms?: Record<string, ShaderUniform>;
1172
- }
1173
- /**
1174
- * Register a fragment shader as an effect. Sugar over `registerProgram` with
1175
- * the effect vertex shader, and the reason a consumer never imports the
1176
- * prelude: an effect that registers with the *custom-shader* vertex shader
1177
- * compiles, runs, and samples its source upside down.
1178
- *
1179
- * The fragment shader reads `v_uv` and `u_source`, and may declare
1180
- * `u_resolution` / `u_texel`. See `effectPrelude.ts` for the full contract,
1181
- * including the premultiplied-alpha requirement.
1182
- *
1183
- * @experimental
1184
- */
1185
- declare function registerEffect(id: string, frag: string): ShaderProgramHandle;
1186
-
1187
- /**
1188
- * Solid-fill paint variant (subset of the full `FillStyle` union from
1189
- * `@weasel-js/core`). Kept for back-compat with step-1/2 consumers and
1190
- * because some code reads `fill.color` directly. Through step 4, fills can
1191
- * be any `FillStyle` variant — solid, pattern, or gradient.
1192
- */
1193
- interface SolidPaint {
1194
- fill?: 'solid';
1195
- /** Any CSS color string accepted by `parseColor`: hex, `rgb()`/`rgba()`, `hsl()`/`hsla()`, named, or `transparent`. */
1196
- color: string;
1197
- opacity?: number;
1198
- }
1199
- /** DrawCommand variants implemented through step 6. */
1200
- type DrawCommand = PathDrawCommand | GroupDrawCommand | TextDrawCommand | ImageDrawCommand | SpritesDrawCommand | ShaderDrawCommand;
1201
- /** Draw a path, filled and/or stroked. The workhorse command: every shape the
1202
- * kit draws that is not text, an image, or a custom shader is one of these. */
1203
- interface PathDrawCommand {
1204
- kind: 'path';
1205
- path: Path;
1206
- /** Any `FillStyle` variant: solid, pattern, or gradient (linear/radial/conic). */
1207
- fill?: FillStyle;
1208
- /** Stroke spec. Only solid `paint` supported through step 4. */
1209
- stroke?: Stroke;
1210
- /**
1211
- * Optional flat RGBA-per-path-anchor color array (length =
1212
- * `4 × countPathAnchors(path)`, floats in 0..1). The renderer
1213
- * arc-length-interpolates these per-anchor colors across the
1214
- * flattened/triangulated mesh between consecutive anchors using the
1215
- * mesh's `anchorA` / `anchorB` / `anchorT` parameterization.
1216
- *
1217
- * **`fill` must also be set when using `vertexColors`.** The renderer
1218
- * only enters the per-vertex shader path when the command has a fill
1219
- * (the fill provides the opacity uniform; the vertex colors override
1220
- * the fill's color). Pass any solid `fill` (e.g. `{ color: '#fff' }`)
1221
- * as the placeholder; the per-vertex colors win in the shader.
1222
- */
1223
- vertexColors?: number[];
1224
- }
1225
- /** Draw a list of commands under a shared transform, opacity, color matrix
1226
- * and clip. Groups nest, and their effects accumulate down the stack — this
1227
- * is how a container node's transform reaches its descendants. */
1228
- interface GroupDrawCommand {
1229
- kind: 'group';
1230
- transform?: GlMat3;
1231
- alpha?: number;
1232
- /**
1233
- * Optional 4×5 color matrix (row-major, 20 numbers) — `out = M₄ₓ₄ * in + bias`.
1234
- * Accumulated multiplicatively down the group stack. Defaults to identity.
1235
- */
1236
- colorMatrix?: number[];
1237
- /** Optional clip path. When set, the renderer rasterizes this path into
1238
- * the stencil buffer before drawing `children`; the children paint only
1239
- * where the clip covers. Nested groups with clips intersect — a child
1240
- * cannot escape an ancestor's clip. Max 7 nesting levels; the renderer
1241
- * throws if exceeded. */
1242
- clip?: Path;
1243
- /**
1244
- * Full-screen passes run over this group's own pixels, in order, before it
1245
- * is composited into its parent.
1246
- *
1247
- * Unlike every other field here, this does not accumulate down the group
1248
- * stack — it is a render-target boundary. The children draw into a buffer of
1249
- * their own, each effect reads the previous one's output, and the result is
1250
- * composited back under this group's `transform`, `alpha`, `colorMatrix` and
1251
- * whatever clip encloses it. So a blur here blurs this group and nothing
1252
- * around it, which is what a CSS `filter` on the canvas cannot do.
1253
- *
1254
- * An empty or absent list costs nothing: no buffer is allocated until a
1255
- * group asks for one.
1256
- */
1257
- effects?: readonly Effect[];
1258
- children: DrawCommand[];
1259
- }
1260
- /**
1261
- * Text draw command. Renders one or more runs at (`x`, `y`) in screen
1262
- * space, optionally word-wrapping at `maxWidth`. The renderer resolves
1263
- * each run's `(fontFamily, fontWeight, fontStyle)` to an MSDF atlas via
1264
- * `resolveFontVariant` and bucket-draws by atlas + color group.
1265
- *
1266
- * `style` carries node-level defaults (`lineHeight`, anti-alias width)
1267
- * that don't belong on individual runs.
1268
- */
1269
- interface TextDrawCommand {
1270
- kind: 'text';
1271
- x: number;
1272
- y: number;
1273
- runs: ResolvedRun[];
1274
- /** Wrap width. Absent never wraps. */
1275
- maxWidth?: number;
1276
- align?: 'left' | 'center' | 'right';
1277
- /** Spread each wrapped line across `width` — see `LayoutRunsOpts.justify`.
1278
- * `align` then sets only the lines it does not spread. Default `false`. */
1279
- justify?: boolean;
1280
- /** Box width `align` resolves within, from `x`. Default `maxWidth`; with
1281
- * neither, `x` is the line's left edge, midpoint or right edge. */
1282
- width?: number;
1283
- style: TextStyle;
1284
- /** Box height for vertical alignment. When set with `verticalAlign`,
1285
- * the laid-out block shifts within `[y, y+height]`. */
1286
- height?: number;
1287
- /** Default 'top' — the legacy top-anchored behavior. */
1288
- verticalAlign?: TextVerticalAlign;
1289
- }
1290
- /**
1291
- * Image draw command — renders `image` at screen-space rect (x, y, w, h).
1292
- * The image is stretched to fit; no tiling. Use a pattern FillStyle on a path
1293
- * for tiling.
1294
- */
1295
- interface ImageDrawCommand {
1296
- kind: 'image';
1297
- image: ImageBitmap;
1298
- x: number;
1299
- y: number;
1300
- w: number;
1301
- h: number;
1302
- opacity?: number;
1303
- /** Magnification filter. `'linear'` (default) smooths; `'nearest'` shows
1304
- * device pixels as hard squares — required by anything magnifying a
1305
- * framebuffer readback, where blur destroys the point of the readback. */
1306
- sampling?: 'linear' | 'nearest';
1307
- /** Sub-rectangle of `image` to draw, in bitmap pixels from the top-left.
1308
- * Omitted draws the whole bitmap. Not range-checked: a rect past the edge
1309
- * samples outside [0..1], which CLAMP_TO_EDGE smears. With
1310
- * `sampling: 'linear'` the filter reaches half a texel beyond `source`, so
1311
- * atlas frames need a gutter (see `SpriteSheet.spacing`) or `'nearest'`. */
1312
- source?: {
1313
- x: number;
1314
- y: number;
1315
- w: number;
1316
- h: number;
1317
- };
1318
- /** Mirror the sampled region within the destination rect. The quad does not
1319
- * move — a flipped draw covers exactly the pixels an unflipped one does. */
1320
- flipX?: boolean;
1321
- flipY?: boolean;
1322
- }
1323
- /** Floats per sprite in `SpritesDrawCommand.sprites`. */
1324
- declare const SPRITE_STRIDE = 9;
1325
- /**
1326
- * Draw many quads sampling one bitmap — an atlas, a sprite sheet, a wall of
1327
- * thumbnails. The same picture as a run of `ImageDrawCommand`s the renderer
1328
- * would coalesce anyway, handed over already packed so it never walks a
1329
- * command object per quad.
1330
- *
1331
- * Reach for it past a few thousand sprites. Below that a plain run of image
1332
- * commands merges into the same single draw and reads better; the packed form
1333
- * exists because at 20,000 the object walk is about half the frame.
1334
- *
1335
- * The sprites are one run: they share a texture, a filter, and whatever group
1336
- * transform, alpha, color matrix and clip are live, exactly as a merged run of
1337
- * image commands would. Anything varying per sprite is in the array.
1338
- */
1339
- interface SpritesDrawCommand {
1340
- kind: 'sprites';
1341
- image: ImageBitmap;
1342
- /** Magnification filter for the whole run, as `ImageDrawCommand.sampling`. */
1343
- sampling?: 'linear' | 'nearest';
1344
- /**
1345
- * `SPRITE_STRIDE` floats per sprite:
1346
- * `dx, dy, dw, dh, sx, sy, sw, sh, opacity`.
1347
- *
1348
- * Destination is in the group's coordinates; source is in bitmap pixels,
1349
- * like `ImageDrawCommand.source`. A negative `sw` or `sh` mirrors that axis
1350
- * within the source rect, which is what `flipX` / `flipY` do. A trailing
1351
- * partial sprite is ignored.
1352
- */
1353
- sprites: Float32Array;
1354
- }
1355
- /**
1356
- * Custom shader draw command. The renderer generates a quad over `bounds`
1357
- * and dispatches the consumer's fragment shader with the kit's vertex prelude.
1358
- *
1359
- * `uniforms` keys must match names declared in the consumer's fragment shader.
1360
- * The kit automatically sets `u_bounds`, `u_view`, and `u_proj` — do not
1361
- * declare those in `uniforms`.
1362
- *
1363
- * @experimental API may change before v2.
1364
- */
1365
- interface ShaderDrawCommand {
1366
- kind: 'shader';
1367
- program: ShaderProgramHandle;
1368
- uniforms: Record<string, ShaderUniform>;
1369
- /** Screen-space bounding rect in CSS pixels. */
1370
- bounds: {
1371
- x: number;
1372
- y: number;
1373
- w: number;
1374
- h: number;
1375
- };
1376
- }
1377
-
1378
- /**
1379
- * Clipping staged geometry to an axis-aligned rect on the CPU, so a rect clip
1380
- * needs no stencil and does not break the run.
1381
- *
1382
- * A stencil clip is GL state, so content inside one cannot share a draw with
1383
- * content outside it, and every clipped group paid a flush on the way in and
1384
- * another on the way out. A rect under a transform that keeps it axis-aligned
1385
- * is the common clip, and for that one the clip can ride the geometry instead:
1386
- * cut each staged polygon to the rect before it joins the run.
1387
- *
1388
- * Exact, not approximate: every attribute a batch vertex carries is affine
1389
- * across the polygon it belongs to, so interpolating along a cut edge gives
1390
- * the value the rasterizer would have produced there, and the cut polygon
1391
- * covers the same pixel centers the stencil would have passed.
1392
- */
1393
-
1394
- /** In the space batch vertices are staged in: after the model transform,
1395
- * before the projection. */
1396
- interface ClipRect {
1397
- x0: number;
1398
- y0: number;
1399
- x1: number;
1400
- y1: number;
1401
- }
1402
-
1403
- /**
1404
- * Growable vertex staging for a run of solid-fill geometry, image quads and
1405
- * glyphs.
1406
- *
1407
- * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
1408
- * uniforms the flush draws under.
1409
- *
1410
- * **One batch for all three, because a page interleaves them.** A grid of
1411
- * thumbnails is a ground rect under an atlas quad under a caption, per cell,
1412
- * and while each kind staged separately every one had to drain the others
1413
- * before it could stage — so a shape that batches perfectly in any one half
1414
- * alone paid a flush per command. Everything a vertex needs to say which it is
1415
- * fits in the same vertex: solids carry the UV of a 1x1 white texel and are
1416
- * their own color, quads carry their atlas UV and a white color, glyphs carry
1417
- * a font atlas UV, their text color, and a paint mode saying the texel is a
1418
- * distance field rather than a color. Gradients join as a fourth off the ramp
1419
- * atlas — a linear one without a mode of its own, since (ramp position, row) is
1420
- * what the plain mode already samples, and a radial or conic one with a mode
1421
- * that says its UV is a gradient-space coordinate to take a `length` or an
1422
- * `atan` of. See `shaders/batchFill.ts`.
1423
- *
1424
- * Colors ride the vertices because shapes in a run differ in color and a merged
1425
- * draw has one set of uniforms — and so does the model transform, applied here
1426
- * rather than uploaded, so shapes under different transforms still share a
1427
- * draw. The texture rides them too, as a slot index into the units the flush
1428
- * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out
1429
- * the rest per bitmap. A run holds as many bitmaps as there are slots, so an
1430
- * atlas is what makes a wall of one sheet coalesce and a handful of loose
1431
- * bitmaps no longer breaks a run per command.
1432
- */
1433
-
1434
- /**
1435
- * A gradient vertex's `a_uv`, each channel affine in the coordinates the
1436
- * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.
1437
- *
1438
- * One shape for all three gradients. A linear one's `v` row is the constant
1439
- * atlas row and its `u` row is the ramp position; a radial or conic one's two
1440
- * rows are the gradient-space coordinate, and the row rides `a_post` instead.
1441
- */
1442
- interface GradientUV {
1443
- ux: number;
1444
- uy: number;
1445
- u0: number;
1446
- vx: number;
1447
- vy: number;
1448
- v0: number;
1449
- }
1450
- declare class DrawBatch {
1451
- private readonly gl;
1452
- private readonly aPos;
1453
- private readonly aColor;
1454
- private readonly aUv;
1455
- private readonly aPost;
1456
- private readonly aSlot;
1457
- /** One ring per tier, cycled per flush. Slots are created on first use, so a
1458
- * renderer that flushes rarely allocates as few as it flushes. */
1459
- private readonly rings;
1460
- private readonly nextInRing;
1461
- /** The same, for flushes past the largest tier; these sets grow to fit. */
1462
- private readonly largeRing;
1463
- private nextLarge;
1464
- private verts;
1465
- private idx;
1466
- private nVerts;
1467
- private nIdx;
1468
- /** Whether the staged run is quads alone, so its indices are the canonical
1469
- * pattern and a slot already holding that pattern needs no upload. */
1470
- private pureRects;
1471
- /** The rect every push is cut to, or `null`. `draw.ts` sets it as rect
1472
- * clips enter and leave — see `batchClip.ts`. A push returns `false`, having
1473
- * staged nothing, for geometry it cannot cut exactly; see `clipQuad`. */
1474
- clip: ClipRect | null;
1475
- /** Polygon scratch for `clip`: a quad cut by four edges has at most eight
1476
- * corners. */
1477
- private readonly polyIn;
1478
- private readonly polyOut;
1479
- private readonly polyTmp;
1480
- constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
1481
- get length(): number;
1482
- /** Whether staging `vertices` more would put the run past the per-flush cap. */
1483
- wouldOverflow(vertices: number): boolean;
1484
- /**
1485
- * Append one rect's four corners through `m`, all carrying `rgba` (straight
1486
- * alpha). An affine maps a rect to a parallelogram, so two triangles still
1487
- * cover it and the batch draws at `u_model` identity.
1488
- */
1489
- pushRect(x: number, y: number, w: number, h: number, m: GlMat3, r: number, g: number, b: number, a: number): boolean;
1490
- /**
1491
- * Append one image quad: the destination rect `(x, y, w, h)` mapped through
1492
- * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the
1493
- * after-the-color-matrix alpha factor.
1494
- *
1495
- * Corners wind top-left, top-right, bottom-right, bottom-left with UVs
1496
- * following — the same winding `pushRect` uses, which is what lets a run of
1497
- * mixed rects and quads keep the canonical index pattern. The flips a command
1498
- * asks for are already in the `u` / `v` the caller passes.
1499
- *
1500
- * `slot` is the texture unit the corners sample, which `draw.ts` assigns per
1501
- * bitmap within the run.
1502
- */
1503
- pushQuad(x: number, y: number, w: number, h: number, m: GlMat3, u0: number, v0: number, u1: number, v1: number, post: number, slot: number): boolean;
1504
- /**
1505
- * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,
1506
- * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in
1507
- * `rgba`.
1508
- *
1509
- * `mode` says which channels of that atlas carry the distance field, and
1510
- * rides the vertices packed into the slot — so a run off a baked MSDF atlas
1511
- * and one off the runtime canvas bake still share a draw. The synthetic-bold
1512
- * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when
1513
- * it changes.
1514
- *
1515
- * **A synthetic oblique is sheared here rather than in the shader.** The old
1516
- * text program carried the baseline per vertex and skewed against
1517
- * `u_synthItalic`; the batch places its own corners, so the shear is one
1518
- * multiply while they are being placed, and `tanItalic` is 0 for an upright
1519
- * face. It has to happen before `m`, which is where the shader had it too.
1520
- *
1521
- * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s
1522
- * winding, which is what lets a run of mixed rects, image quads and glyphs
1523
- * keep the canonical index pattern.
1524
- */
1525
- pushGlyph(x0: number, y0: number, x1: number, y1: number, baselineY: number, tanItalic: number, m: GlMat3, u0: number, v0: number, u1: number, v1: number, r: number, g: number, b: number, a: number, slot: number, mode: number): boolean;
1526
- /**
1527
- * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped
1528
- * through `m`, sampling the ramp atlas at `slot`.
1529
- *
1530
- * `uv` gives each channel of `a_uv` as an affine function of the coordinates
1531
- * the corners arrive in, which is what lets a gradient ride the vertices at
1532
- * all: a linear one's ramp position is affine in position outright, and a
1533
- * radial or conic one's gradient-space *coordinate* is, even though the ramp
1534
- * position it yields is not. Either way the rasterizer's interpolation across
1535
- * a triangle is exact. `mode` says which of the two the fragment shader is
1536
- * looking at, and `post` carries the atlas row for the modes that read it —
1537
- * see `shaders/batchFill.ts`.
1538
- *
1539
- * Values outside 0..1 are the sampler's business; the atlas clamps to the
1540
- * edge texel, which is what the gradient shader's own `clamp` did.
1541
- */
1542
- pushGradientRect(x: number, y: number, w: number, h: number, m: GlMat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): boolean;
1543
- /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for
1544
- * what `uv`, `post` and `mode` carry. */
1545
- pushGradientMesh(mesh: Mesh, m: GlMat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): boolean;
1546
- /**
1547
- * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
1548
- * mesh's own indices are rebased onto the staged vertices, which is why the
1549
- * index buffer is uploaded per flush rather than written once.
1550
- */
1551
- pushMesh(mesh: Mesh, m: GlMat3, r: number, g: number, b: number, a: number): boolean;
1552
- /** Upload the staged geometry into the next set of buffers and bind its VAO.
1553
- * Returns the index count for the caller's `drawElements`. */
1554
- uploadAndBind(): number;
1555
- reset(): void;
1556
- dispose(): void;
1557
- private writeVertex;
1558
- /**
1559
- * Fit the quad just written at `v0` to `rect`: keep it if it lies inside,
1560
- * drop it if it misses, and cut it if it crosses — but only if its edges
1561
- * run along the axes. Cutting a slanted edge moves its endpoint off the
1562
- * subpixel grid the rasterizer snaps to, and the shortened edge then covers
1563
- * different samples than the whole one did; along an axis the cut edge is
1564
- * the same line exactly. So a slanted quad that crosses is rolled back and
1565
- * refused, for `draw.ts` to clip through the stencil instead.
1566
- *
1567
- * A cut axis-aligned quad is still four corners, so the run stays pure.
1568
- */
1569
- private clipQuad;
1570
- /** `clipQuad` for a mesh staged from `v0`. A mesh's edges are slanted in
1571
- * general, so one that crosses is refused rather than cut. */
1572
- private clipMesh;
1573
- /** Append the `n`-corner convex polygon in `poly` as a triangle fan. */
1574
- private appendFan;
1575
- /** Two triangles over the four corners just written. */
1576
- private pushQuadIndices;
1577
- /** Grow the CPU arrays so `vertices` / `indices` more fit. */
1578
- private reserve;
1579
- private nextRingSlot;
1580
- private nextLargeSlot;
1581
- private createSet;
1582
- private deleteSet;
1583
- }
1584
-
1585
- /** How to construct a `WeaselRenderer`: the GL context or canvas to draw
1586
- * into, the output size, and the quality knobs that separate screen
1587
- * rendering from print. */
1588
- /** A run of top-level commands, `[start, end)` by index, that
1589
- * {@link WeaselRenderer.lastFrameStats} accounts for separately — one render
1590
- * layer's commands, say. Spans are in order and do not overlap. */
1591
- interface RenderSpan {
1592
- id: string;
1593
- start: number;
1594
- end: number;
1595
- }
1596
- /** Per-frame options for {@link WeaselRenderer.render}. */
1597
- interface RenderOptions {
1598
- spans?: readonly RenderSpan[];
1599
- }
1600
- /** What one span cost. A batched run is drawn when something closes it, so
1601
- * its draw call counts toward the span that closed it. */
1602
- interface RenderSpanStats {
1603
- id: string;
1604
- drawCalls: number;
1605
- ms: number;
1606
- }
1607
- /** What one `render()` cost. See {@link WeaselRenderer.lastFrameStats}. */
1608
- interface RenderStats {
1609
- drawCalls: number;
1610
- ms: number;
1611
- spans: RenderSpanStats[];
1612
- }
1613
- /** Construction options for {@link WeaselRenderer}. Pass `gl` or `canvas`;
1614
- * a context passed in must have a stencil buffer. */
1615
- interface WeaselRendererOptions {
1616
- /** A WebGL2 context to draw with. Wins over `canvas`. */
1617
- gl?: WebGL2RenderingContext;
1618
- /** A canvas to open a stencil-backed WebGL2 context on, when `gl` is absent. */
1619
- canvas?: HTMLCanvasElement;
1620
- /** Drawing size in CSS pixels. */
1621
- width: number;
1622
- /** Drawing size in CSS pixels. */
1623
- height: number;
1624
- /** Device pixels per CSS pixel. */
1625
- dpr: number;
1626
- /** MIN_FILTER strategy for image/pattern textures (`GLImageCache`).
1627
- * Default `'linear'` — the existing screen behavior. The headless
1628
- * `renderSceneToPixels` path passes `'mipmap'` for print-quality
1629
- * minification. Explicitly passing `'linear'` is always valid. */
1630
- imageMinification?: ImageMinification;
1631
- /** Flatness tolerance for curve tessellation, in WORLD units (see
1632
- * `TessellateOptions.flattenTolerance`). When set, path fills are
1633
- * tessellated fresh at this tolerance per frame (transient pool) instead
1634
- * of served from the Path-identity mesh cache — the cache key does not
1635
- * include tolerance. Default: unset — the existing cached behavior at
1636
- * `DEFAULT_FLATTEN_TOLERANCE`. The headless path derives this from the
1637
- * requested output scale; screen callers normally leave it unset. */
1638
- flattenTolerance?: number;
1639
- /** Per-render synchronous bake budget for dynamic canvas-SDF glyphs.
1640
- * Default DEFAULT_BAKE_BUDGET (16). The headless renderSceneToPixels
1641
- * path passes Infinity so print never defers a glyph. */
1642
- bakeBudget?: number;
1643
- /** On-screen glyph size, in CSS pixels, at or above which text renders from
1644
- * tessellated font outlines instead of a distance field — see
1645
- * `OUTLINE_MIN_SCREEN_PX`. Only faces registered with
1646
- * `registerFontOutlines` are affected; everything else keeps its SDF tier
1647
- * whatever this says. Pass `Infinity` to disable the tier outright, or 0
1648
- * to use outlines wherever they exist (what the headless path does, since
1649
- * print has no reason to sample a field it could evaluate exactly). */
1650
- textOutlineMinScreenSize?: number;
1651
- }
1652
- /** Where a renderer draws inside a buffer it does not own.
1653
- *
1654
- * The rect's SIZE is the renderer's own `width`/`height` — `resize()` owns
1655
- * that, and a second copy here could disagree with the one `DrawContext`
1656
- * reports to screen-space layers. */
1657
- interface RenderTarget {
1658
- /** Top-left of this renderer's output within the drawing buffer, in CSS
1659
- * pixels, with the origin at the buffer's top-left. GL's bottom-left origin
1660
- * is handled internally. */
1661
- origin: {
1662
- x: number;
1663
- y: number;
1664
- };
1665
- /** Clear colour and stencil within the rect before drawing. Default true.
1666
- * Pass false only when the caller clears the whole buffer itself on behalf
1667
- * of every co-tenant — a frame that clears neither inherits its neighbour's
1668
- * stencil bits, and even-odd fills then fill their holes. */
1669
- clear?: boolean;
1670
- }
1671
- /**
1672
- * The WebGL2 renderer: takes a list of draw commands and paints them.
1673
- *
1674
- * It knows nothing about the scene — commands are the whole interface, which
1675
- * is what lets layers, HUD widgets and overlays all draw through the same
1676
- * pipeline. GPU resources (meshes, textures, gradient ramps) are cached across
1677
- * frames and keyed by identity, so re-issuing the same command is cheap.
1678
- */
1679
- declare class WeaselRenderer {
1680
- private readonly gl;
1681
- private stats;
1682
- private pathFill;
1683
- private pathFillVColor;
1684
- private imageFill;
1685
- private batchFill;
1686
- private gradFill;
1687
- private patternFill;
1688
- /** Compiled on first use: only atlas text under a texture paint needs one. */
1689
- private glyphPaintPrograms;
1690
- private meshCache;
1691
- private textureCache;
1692
- private imageCache;
1693
- private gradRamps;
1694
- private programRegistry;
1695
- /** The source each compiled custom program was built from — or, after a
1696
- * failed recompile, the source that failed, so it is not retried every frame. */
1697
- private programSources;
1698
- private quadVbo;
1699
- private quadIbo;
1700
- private drawBatch;
1701
- /** 1x1 white, so a batch flush sampling no image still samples something —
1702
- * see `shaders/batchFill.ts`. */
1703
- private whiteTexture;
1704
- private readonly groupState;
1705
- private widthCss;
1706
- private heightCss;
1707
- private dpr;
1708
- private canvas;
1709
- private target;
1710
- /** Offscreen buffers for group effects. Allocates nothing until a group
1711
- * with effects asks, so a canvas without them pays no memory. */
1712
- private readonly effectTargets;
1713
- private readonly paintScratch;
1714
- private readonly imageMinification;
1715
- private flattenTolerance?;
1716
- private readonly bakeBudget;
1717
- private readonly textOutlineMinScreenSize;
1718
- private contextLost;
1719
- private boundOnLost;
1720
- private boundOnRestored;
1721
- /** True after `dispose()`. A disposed renderer ignores further `render()`
1722
- * calls and `registerProgram()` calls. */
1723
- private disposed;
1724
- /** What registered paint kinds hang their GPU state on. One per context
1725
- * generation: a restore releases it and opens the next. */
1726
- private paintResources;
1727
- constructor(opts: WeaselRendererOptions);
1728
- private uploadQuadGeometry;
1729
- /**
1730
- * Compile a consumer-registered shader program against this renderer's GL context.
1731
- *
1732
- * Call once per renderer after the module-level `registerProgram()`.
1733
- * Throws `ShaderCompileError` if compilation fails.
1734
- *
1735
- * In dev mode, calling again with the same handle replaces the compiled program.
1736
- *
1737
- * @experimental
1738
- */
1739
- registerProgram(handle: ShaderProgramHandle): void;
1740
- private installProgram;
1741
- /** Recompile every program whose module-level source was re-registered
1742
- * since it was compiled. A source that fails keeps the old program drawing. */
1743
- private refreshPrograms;
1744
- /**
1745
- * The compiled program for `id`, compiling it against this context on first
1746
- * use. Unlike {@link registerProgram} this neither throws on a missing
1747
- * source nor on a compile failure: a paint kind asking for a program it
1748
- * never registered must decline the paint, not take down the frame.
1749
- */
1750
- private ensureProgram;
1751
- private glyphPaintProgram;
1752
- /** The GL state every frame assumes. Applied per `render()` rather than once
1753
- * at construction because a co-tenant sharing this context moves all of it
1754
- * between our frames. */
1755
- private applyGlState;
1756
- /** Viewport and scissor for this frame. Re-applied inside `render()` because
1757
- * a co-tenant on the same context moves both between our frames. */
1758
- private applyTarget;
1759
- /** Confine this renderer to a rect of its drawing buffer, or pass null to
1760
- * give it the whole buffer back. Takes effect from the next `render()`. */
1761
- setTarget(target: RenderTarget | null): void;
1762
- getTarget(): RenderTarget | null;
1763
- isContextLost(): boolean;
1764
- private onContextLost;
1765
- private onContextRestored;
1766
- /** Free every GL resource this renderer created and detach context-loss
1767
- * listeners when a canvas was supplied. Idempotent.
1768
- *
1769
- * That covers the built-in and consumer-registered programs (their shaders
1770
- * go with them), the shared quad geometry, the batch, the effect targets,
1771
- * and every cache: atlas and gradient-ramp textures, bitmap and pattern
1772
- * textures, and meshes — transient, persistent, and those whose Path was
1773
- * already collected. The context itself is left alone, so a caller-owned
1774
- * context can host renderer after renderer without accumulating anything.
1775
- *
1776
- * A disposed renderer ignores further `render()` and `registerProgram()`
1777
- * calls. */
1778
- dispose(): void;
1779
- /**
1780
- * Draw one frame.
1781
- *
1782
- * `viewMatrix` is the frame's world→screen transform. It is only consulted
1783
- * by `units: 'world'` gradients, which fall back to screen space without
1784
- * it — every other command carries its own transform in the stream, so
1785
- * callers with no view concept can keep calling `render(commands)`.
1786
- */
1787
- render(commands: DrawCommand[], viewMatrix?: GlMat3, opts?: RenderOptions): void;
1788
- /** What the last `render()` cost: GL draw calls issued and CPU milliseconds
1789
- * spent issuing them, in total and per requested span. GPU time is not
1790
- * measured; the GPU runs asynchronously. */
1791
- lastFrameStats(): RenderStats;
1792
- /** Change the curve-flattening tolerance after construction. `undefined`
1793
- * restores the cached `DEFAULT_FLATTEN_TOLERANCE` route. */
1794
- setFlattenTolerance(tolerance: number | undefined): void;
1795
- resize(dims: {
1796
- width: number;
1797
- height: number;
1798
- dpr: number;
1799
- }): void;
1800
- /** @internal */ _gl(): WebGL2RenderingContext;
1801
- /** @internal */ _pathFill(): ShaderProgram;
1802
- /** @internal */ _pathFillVColor(): ShaderProgram;
1803
- /** @internal */ _drawBatch(): DrawBatch;
1804
- /** @internal */ _imageFill(): ShaderProgram;
1805
- /** @internal */ _batchFill(): ShaderProgram;
1806
- /** @internal */ _gradFill(): ShaderProgram;
1807
- /** @internal */ _patternFill(): ShaderProgram;
1808
- /** @internal */ _meshCache(): GLMeshCache;
1809
- /** @internal */ _textureCache(): GLTextureCache;
1810
- /** @internal */ _imageCache(): GLImageCache;
1811
- /** @internal */ _gradRamps(): GradientRampAtlas;
1812
- /** @internal */ _groupState(): GroupState;
1813
- /** @internal */ _widthCss(): number;
1814
- /** @internal */ _heightCss(): number;
1815
- /** @internal */ _dpr(): number;
1816
- }
1817
-
1818
- /**
1819
- * Uniform-grid sprite sheet layout — frame index to the source rect an
1820
- * `ImageDrawCommand` samples. Pure arithmetic over the grid description; it
1821
- * never touches the bitmap.
1822
- */
1823
- /** A sprite sheet's grid. `margin` and `spacing` follow the Tiled / Aseprite
1824
- * tileset convention, so a sheet exported from either describes itself here. */
1825
- interface SpriteSheet {
1826
- frameWidth: number;
1827
- frameHeight: number;
1828
- columns: number;
1829
- /** Empty pixels around the whole grid. Default 0. */
1830
- margin?: number;
1831
- /** Empty pixels between adjacent cells — the gutter that keeps linear
1832
- * sampling off the neighboring frame. Default 0. */
1833
- spacing?: number;
1834
- }
1835
- /**
1836
- * Row-major source rect for frame `index`, counting from 0 at the top-left.
1837
- *
1838
- * `index` is not range-checked and does not wrap: past the last cell this
1839
- * returns a rect below the sheet, which draws as an edge smear. Wrapping is
1840
- * the animation's business (`frameRect(sheet, tick % count)`) — a sheet does
1841
- * not know how many of its cells are filled.
1842
- */
1843
- declare function frameRect(sheet: SpriteSheet, index: number): {
1844
- x: number;
1845
- y: number;
1846
- w: number;
1847
- h: number;
1848
- };
1849
-
1850
- /**
1851
- * View → GlMat3 helper for layer `draw` implementations on world-space layers.
1852
- *
1853
- * `View` is `@weasel-js/routing`'s, the one every viewport action is typed
1854
- * in. A type-only import emits no runtime edge, so there is no reason for the
1855
- * renderer to hold a second structurally-identical declaration and put it in
1856
- * the emitted `.d.ts` anonymously.
1857
- */
1858
-
1859
- /**
1860
- * Build the world→screen transform matrix for a `View`.
1861
- * Use as the `transform` field of a `kind: 'group'` DrawCommand to wrap
1862
- * world-space content emitted from a layer `draw` implementation.
1863
- *
1864
- * Mapping: `screen = (world − {view.x, view.y}) × {view.scale.x, view.scale.y}`.
1865
- *
1866
- * Column-major layout (matches `mat3.identity()`):
1867
- * `[scale.x, 0, 0, 0, scale.y, 0, -view.x*scale.x, -view.y*scale.y, 1]`.
1868
- */
1869
- declare function viewToMat3(view: View): GlMat3;
1870
-
1871
- /** A screen-space rectangle, CSS pixels. */
1872
- interface CullRect {
1873
- x: number;
1874
- y: number;
1875
- width: number;
1876
- height: number;
1877
- }
1878
- /**
1879
- * `cmds` without the commands that cannot put a pixel inside `rect`, where
1880
- * `transform` maps the commands' space to screen space — `viewToMat3(view)`
1881
- * for a world-space layer.
1882
- *
1883
- * Conservative by construction: a command is dropped only when a bound that
1884
- * encloses everything it can paint misses `rect`. Paths are bounded by their
1885
- * control hull plus the farthest a stroke can reach (miter spikes and square
1886
- * caps included), images and sprites by their quads, and every bound is taken
1887
- * through the accumulated transform as the AABB of its corners, so rotation
1888
- * only enlarges it. Text and shader commands are always kept, and so is a
1889
- * group with `effects` — an effect may move pixels. Clips are ignored, which
1890
- * can only keep more. A group whose children all go is dropped.
1891
- *
1892
- * Returns `cmds` itself when nothing was dropped.
1893
- */
1894
- declare function cullDrawCommands(cmds: DrawCommand[], transform: GlMat3, rect: CullRect): DrawCommand[];
1895
-
1896
- /**
1897
- * Read-only state that affordances consult on every render and hit-test
1898
- * call. Built via `buildChromeState` and held across renders, so affordances
1899
- * must not cache anything they read from it.
1900
- */
1901
- interface ChromeState {
1902
- /** Currently selected ids. Live; reflects useSelection's React state. */
1903
- readonly selection: readonly NodeId[];
1904
- /** True when the canvas is in multi-mode AND >= 2 ids are selected. */
1905
- readonly multiActive: boolean;
1906
- /** Bounds for any selection member id. Honors active-tool overlay state
1907
- * (move/resize/rotate ghosts → ghost bounds; otherwise → committed
1908
- * pose bounds). Returns null for unknown ids or ids whose bounds aren't
1909
- * computable. */
1910
- boundsOf(id: string): Bounds | null;
1911
- /** Multi-union AABB when `multiActive`. Recomputed from `boundsOf` over
1912
- * every selected id on each read, expanding rotated members to the extent
1913
- * of their ink; null otherwise. */
1914
- readonly unionBounds: Bounds | null;
1915
- /** Active modifier state at the moment of the call. */
1916
- readonly modifiers: ModifierState;
1917
- /** True iff the node's pose-descriptor declares it can carry a rotation.
1918
- * Affordances consult this to decide whether to expose rotate cursors /
1919
- * drag-bands. Defaults to `true` when the descriptor doesn't declare
1920
- * (back-compat) or when the id is unknown — the rotation gesture will
1921
- * no-op visually for poses without AABB fields, but the affordance
1922
- * doesn't lie about the cursor. Optional on the interface so unit-test
1923
- * call sites that construct `ChromeState` by hand keep compiling;
1924
- * affordances should treat an absent predicate as "true". */
1925
- canRotate?(id: string): boolean;
1926
- }
1927
-
1928
- /**
1929
- * @experimental
1930
- * A single interactive piece of chrome. Pure functions; the kit composes
1931
- * multiple affordances into a single RenderLayer per tool via
1932
- * `composeAffordanceLayer`.
1933
- *
1934
- * Affordances declare interactive regions in a target's *local* frame.
1935
- * The framework (`composeAffordanceLayer`) composes the target's bounds
1936
- * transform (rotation around the AABB center, when present) for both paint
1937
- * and hit-test, so the affordance never touches rotation, view.scale, or
1938
- * world↔screen math.
1939
- */
1940
- interface Affordance {
1941
- /** Stable id for debug overlays + visibility maps. */
1942
- id: string;
1943
- /** Enumerate this affordance's interactive regions. Each region lives in
1944
- * some target id's local frame (or in the world frame when `targetId`
1945
- * is `null`). Returning `[]` means "no chrome for this state" (no
1946
- * selection, multi-mode disabled, etc.). */
1947
- regions(state: ChromeState): readonly AffordanceRegion[];
1948
- /** Optional non-interactive decoration (e.g., a leader line drawn from
1949
- * a bounds edge to a handle — visual only, not draggable). Receives
1950
- * raw state + view because the decoration may live outside any single
1951
- * target's local frame. Most affordances leave this undefined. */
1952
- decorate?(state: ChromeState, view: View): DrawCommand[];
1953
- }
1954
- /**
1955
- * @experimental
1956
- * One interactive region produced by an affordance. The framework owns
1957
- * the local↔world transform for `targetId` (when non-null), so `shape`,
1958
- * `paint.sizePx`, and `hitRadiusPx` are always specified in coordinates
1959
- * the affordance can reason about directly.
1960
- */
1961
- interface AffordanceRegion<TScratch = unknown> {
1962
- /** Stable id, e.g. `corner-min-min`. Used for debug overlays + a11y. */
1963
- id: string;
1964
- /** Target id whose `state.boundsOf(targetId)` defines this region's
1965
- * local frame. `bounds.rotation` (if present) is the only transform
1966
- * applied — translation is the AABB origin; scale is identity. Pass
1967
- * `null` for affordances anchored to the viewport / world frame
1968
- * (identity transform). */
1969
- targetId: string | null;
1970
- /** Region geometry, expressed in the target's local frame.
1971
- *
1972
- * - `point` — square hit of half-extent `hitRadiusPx` screen pixels,
1973
- * axis-aligned on screen unless `turned`. `standoff` pushes the anchor `px`
1974
- * screen pixels off the edge whose outward normal (target-local) is
1975
- * `normal`, measured along that normal as it lands on screen — for
1976
- * chrome that floats a fixed distance off an edge at any zoom.
1977
- * - `rect` — axis-aligned rect (target rotation applies).
1978
- * - `annulus` — outer ellipse minus inner rect cutout. Used for
1979
- * invisible zones that sit *around* the AABB (e.g. rotate-on-
1980
- * hover band). The outer ellipse is defined by world-space
1981
- * semi-axes `rx` / `ry` around `(cx, cy)`; the inner rect is the
1982
- * same target-local rect that defines the AABB. Hit-test:
1983
- * inside outer ellipse AND outside inner rect. */
1984
- shape: {
1985
- kind: 'point';
1986
- x: number;
1987
- y: number;
1988
- hitRadiusPx: number;
1989
- standoff?: {
1990
- px: number;
1991
- normal: {
1992
- x: number;
1993
- y: number;
1994
- };
1995
- };
1996
- /** Turn the square to the target, at the angle the target's x axis
1997
- * lands on screen (`screenAngleOf`) — for handles painted turned. */
1998
- turned?: boolean;
1999
- } | {
2000
- kind: 'rect';
2001
- x: number;
2002
- y: number;
2003
- width: number;
2004
- height: number;
2005
- } | {
2006
- kind: 'annulus';
2007
- /** Outer-ellipse center (target-local). */
2008
- cx: number;
2009
- cy: number;
2010
- /** Outer-ellipse semi-axes (target-local). */
2011
- rx: number;
2012
- ry: number;
2013
- /** Inner rect (target-local) — the cutout. Typically the
2014
- * selection's AABB. */
2015
- innerX: number;
2016
- innerY: number;
2017
- innerWidth: number;
2018
- innerHeight: number;
2019
- /** Minimum band thickness outside the inner rect, in **screen**
2020
- * pixels. The framework widens `rx`/`ry` to at least
2021
- * `innerHalfExtent + pxExtent(minBandPx, view.scale)` on each axis
2022
- * for both paint and hit-test.
2023
- *
2024
- * This exists because the clamp has to know the view and the
2025
- * affordance doesn't: `ChromeState` carries no scale. Expressing the
2026
- * floor in world units instead (which is what the rotate ring used
2027
- * to do) makes the band shrink on screen as you zoom in, until the
2028
- * ring around a small shape is too thin to hover. */
2029
- minBandPx?: number;
2030
- };
2031
- /** Optional paint. World position is derived from `shape` + target
2032
- * transform; visual size stays in screen pixels (so handles don't
2033
- * warp under zoom or non-uniform scale). Omit for hit-only regions.
2034
- *
2035
- * - `square` — small fixed-size square (only valid over `point` shapes).
2036
- * - `annulus` — fill + stroke the annulus ring (only valid over
2037
- * `annulus` shapes). Uses even-odd fill rule to punch the inner-rect
2038
- * cutout.
2039
- * - `custom` — emit arbitrary draw commands; receives a {@link CustomPaintContext}. */
2040
- paint?: {
2041
- kind: 'square';
2042
- sizePx: number;
2043
- fill?: FillStyle;
2044
- stroke?: Stroke;
2045
- } | {
2046
- kind: 'annulus';
2047
- fill?: FillStyle;
2048
- stroke?: Stroke;
2049
- insetPx?: number;
2050
- } | {
2051
- kind: 'custom';
2052
- draw: (ctx: CustomPaintContext) => DrawCommand[];
2053
- };
2054
- /** Discriminator a press on this region reports as `AffordanceHit.kind` —
2055
- * the string routing specs match on (`'handle:top-left'`,
2056
- * `'rotate-handle'`, `'anchor:3'`). Omit for regions that only exist
2057
- * inside a consumer-registered layer, where the layer id is the
2058
- * discriminator; `buildAffordanceAt` falls back to
2059
- * `<affordanceId>:<regionId>`. */
2060
- hitKind?: string;
2061
- /** Cursor to show while hovering this region. Read by the hover-cursor
2062
- * pump in `useGestureDispatcher` via `AffordanceHit.cursor`, which
2063
- * `buildAffordanceAt` fills in from the region the walk landed on. */
2064
- cursor?: CursorSpec;
2065
- /** `'exclusive'` bars every binding whose target doesn't consult the
2066
- * affordance. Read only when the affordance is composed into a
2067
- * consumer-registered layer; kit chrome routes through
2068
- * `buildAffordanceAt`, which reports `'shared'`. */
2069
- strength?: 'exclusive' | 'shared';
2070
- /** Which gestures an exclusive claim bars. Omitted bars all of them. */
2071
- claimedKinds?: readonly ClaimableGesture[];
2072
- /** Drag binding produced when this region is hit. Lazily called so
2073
- * affordances don't pay binding-construction cost on every paint frame —
2074
- * state snapshots (e.g., capturing per-leaf poses at click time) belong
2075
- * inside `bind()`, not inside `regions()`. */
2076
- bind(): AffordanceBinding<TScratch>;
2077
- }
2078
- /** Context passed to a region's `paint.kind === 'custom'` draw callback.
2079
- * Provides both the world-space anchor (already transformed) and the
2080
- * original local shape, for affordances that want to do additional
2081
- * geometry themselves. */
2082
- interface CustomPaintContext {
2083
- /** World-space mapping of `shape`. For `point`, only `x`/`y` are set.
2084
- * For `rect`, all four fields are set. */
2085
- world: {
2086
- x: number;
2087
- y: number;
2088
- width?: number;
2089
- height?: number;
2090
- };
2091
- /** The original local shape (same object identity as `region.shape`). */
2092
- local: AffordanceRegion['shape'];
2093
- view: View;
2094
- state: ChromeState;
2095
- }
2096
- /**
2097
- * @experimental
2098
- * Result of an affordance hit — what the region computed about itself.
2099
- *
2100
- * `initialScratch` is the payload: what the region already knows (which
2101
- * corner, which target id) so the action that picks up the drag doesn't
2102
- * re-derive it. `<SceneCanvas>` reads it out of the layer hit-test and packs
2103
- * it into `AffordanceHit`, which flows to the matching action through
2104
- * `InvocationCtx.drag.affordance`.
2105
- *
2106
- * This used to also carry a `drag: DragChannel` naming the handlers the
2107
- * tool-routing dispatcher should wire up. Every implementation supplied a
2108
- * no-op stub that claimed, because the real routing had already moved to
2109
- * bindings; the field went with that dispatcher.
2110
- */
2111
- interface AffordanceBinding<TScratch = unknown> {
2112
- initialScratch?: TScratch;
2113
- }
2114
- /**
2115
- * The fields `buildAffordanceAt` lifts out of a region's `initialScratch`
2116
- * when it turns a region hit into an `AffordanceHit`.
2117
- *
2118
- * Scratch is otherwise opaque — whatever the affordance wants to hand the
2119
- * action that picks up the drag. These few names are the exception: they mean
2120
- * the same thing to every affordance, and the actions that consume them
2121
- * (`resizeAction`, `rotateAction`) read them off `AffordanceHit` rather than
2122
- * out of scratch. An affordance that doesn't set them simply produces a hit
2123
- * without those fields.
2124
- */
2125
- interface CommonAffordanceScratch {
2126
- /** The node (or `MULTI_RESIZE_TARGET_ID`) this chrome acts on. Becomes
2127
- * `AffordanceHit.targetIds`. */
2128
- targetId?: string;
2129
- /** For resize chrome: which corner stays pinned. Mirrors the kit's
2130
- * `ResizeAnchor`, spelled inline so `affordances/` doesn't take a type
2131
- * dependency on the gesture layer for one field. */
2132
- anchor?: {
2133
- x: 'min' | 'max' | 'free';
2134
- y: 'min' | 'max' | 'free';
2135
- };
2136
- /** World-space invariant point of the transform — the fixed corner for a
2137
- * resize, the pivot for a rotation. */
2138
- fixedPoint?: {
2139
- x: number;
2140
- y: number;
2141
- };
2142
- }
2143
- /**
2144
- * What a **registered layer's** `hitTest` returns. Extends `AffordanceBinding`
2145
- * so existing implementations keep typechecking; the added fields are how a
2146
- * consumer's own chrome says the things kit chrome says through
2147
- * `AffordanceRegion` — which cursor to show, and whether it owns the point
2148
- * outright.
2149
- */
2150
- interface LayerHit<TScratch = unknown> extends AffordanceBinding<TScratch> {
2151
- /** Cursor while the pointer is over this hit. Reaches the hover-cursor
2152
- * pump as `AffordanceHit.cursor`, the same path kit chrome uses. */
2153
- cursor?: CursorSpec;
2154
- /** `'exclusive'` bars every binding whose target doesn't consult the
2155
- * affordance. Omitted means `'shared'` — today's behavior. Same name and
2156
- * meaning as `AffordanceHit.strength`, which it becomes. */
2157
- strength?: 'exclusive' | 'shared';
2158
- /** Which gestures an exclusive claim bars. Omitted bars all of them. */
2159
- claimedKinds?: readonly ClaimableGesture[];
2160
- }
2161
-
2162
- /**
2163
- * Canvas size in CSS pixels — passed to `draw` for layers that anchor to
2164
- * canvas edges (e.g. the debug overlay's layer-list panel). The GL backend
2165
- * supplies it explicitly so layers don't have to know about DPR.
2166
- */
2167
- interface Dims {
2168
- width: number;
2169
- height: number;
2170
- }
2171
- /**
2172
- * What a layer's `draw` threw, and which layer threw it.
2173
- *
2174
- * A `draw` runs on the frame loop, so a throw that escapes it surfaces as an
2175
- * uncaught `requestAnimationFrame` error on the window and takes the whole
2176
- * frame with it — every other layer included. One broken layer painting
2177
- * nothing, named in the console, is the lesser wrong.
2178
- */
2179
- interface LayerDrawFailure {
2180
- layerId: string;
2181
- error: unknown;
2182
- }
2183
- /**
2184
- * Several consecutive layers composited as one.
2185
- *
2186
- * A layer's own `effects` run over that layer alone, which is the wrong
2187
- * picture whenever a pass reads neighboring pixels: `blur(A over B)` is not
2188
- * `blur(A) over blur(B)`, and it costs a buffer and a pass chain per layer. A
2189
- * group draws its members into one buffer, runs one chain over it, and
2190
- * composites the result back once.
2191
- *
2192
- * Membership is by `RenderLayer.id` — the same names `layerOrder` and
2193
- * `layerVisibility` use, not the `layers` map's slot keys.
2194
- *
2195
- * Only *consecutive* members share a buffer, because anything drawn between
2196
- * two members has to land between them. A group whose members are separated in
2197
- * the render order is drawn as one bracket per run, with a warning: the
2198
- * picture is right, the declaration almost certainly is not.
2199
- *
2200
- * This is a render-stack bracket, not a scene `ContainerNode` — it holds no
2201
- * ids, survives no reload, and nothing in the scene knows about it.
2202
- */
2203
- interface LayerGroup {
2204
- /** Names the group in warnings; not a layer id and never drawn. */
2205
- id: string;
2206
- /**
2207
- * Member layer ids. Order here is ignored — the render order decides.
2208
- *
2209
- * A name claims that layer and every layer under it in the `:` namespace, so
2210
- * `'scene'` covers the `scene:<layerId>` layers a scene with declared layers
2211
- * draws as — the same set `before`/`after: 'scene'` anchors against. The
2212
- * separator is required: `'scene'` does not reach `scenery`. A name matching
2213
- * no layer being drawn is warned about rather than ignored.
2214
- */
2215
- layers: readonly string[];
2216
- /**
2217
- * Passes over the group's combined pixels, in order. A thunk is re-read on
2218
- * every frame, so an animating radius costs no React render; an array is
2219
- * read once per frame either way.
2220
- */
2221
- effects?: readonly Effect[] | ((view: View, dims: Dims) => readonly Effect[]);
2222
- /** Opacity applied to the group's composited result, not to each member. */
2223
- alpha?: number;
2224
- /** 4×5 color matrix (row-major, 20 numbers) applied to the composited
2225
- * result. See `GroupDrawCommand.colorMatrix`. */
2226
- colorMatrix?: number[];
2227
- }
2228
- /** One layer's memoized output, keyed by layer id. Owned by the canvas that
2229
- * calls `drawLayers`, not by `drawLayers` itself — the function is pure. */
2230
- type LayerCommandCache = Map<string, {
2231
- deps: readonly unknown[];
2232
- cmds: DrawCommand[];
2233
- }>;
2234
- /**
2235
- * A single named render sub-layer within a canvas renderer.
2236
- *
2237
- * @template TData - The data object passed to each draw call.
2238
- */
2239
- /** Something outside React whose changes a paint reads — a mode registry, a
2240
- * scoping lookup. A notification repaints the canvas; the listener takes no
2241
- * arguments and the returned function unsubscribes. */
2242
- interface RedrawSource {
2243
- subscribe(listener: () => void): () => void;
2244
- }
2245
- /** The `subscribe` for a layer that draws `sources`: it notifies whenever any
2246
- * of them does. A thunk is read when the subscription is made. Every layer
2247
- * that draws other layers declares this, so wrapping never silences what
2248
- * repaints a source. */
2249
- declare function subscribeToSources(sources: readonly {
2250
- subscribe?: RedrawSource['subscribe'];
2251
- }[] | (() => readonly {
2252
- subscribe?: RedrawSource['subscribe'];
2253
- }[])): RedrawSource['subscribe'];
2254
- /** One layer of a canvas: `draw` turns `data` and the view into draw commands;
2255
- * the other fields control caching, visibility, coordinate space and hit-testing. */
2256
- interface RenderLayer<TData> {
2257
- /** Unique identifier used in visibility maps and ordering arrays. When a
2258
- * cache is in use, an id must identify the same logical layer across
2259
- * frames — reusing it for a different layer can serve cross-layer commands. */
2260
- id: string;
2261
- /** Human-readable name for UI toggles. */
2262
- label: string;
2263
- /**
2264
- * Emit a DrawCommand tree for the GL backend to dispatch.
2265
- *
2266
- * For world-space layers (the default), emit commands in WORLD COORDS —
2267
- * `drawLayers` automatically wraps them in `{ kind: 'group', transform:
2268
- * viewToMat3(view), ... }` before handing them to the renderer. Do NOT
2269
- * apply the view transform yourself.
2270
- *
2271
- * For screen-space layers (`space: 'screen'`), emit commands in CSS-pixel
2272
- * coords directly; `drawLayers` passes them through unchanged. If part
2273
- * of a screen-space layer's output needs to track the view, wrap that
2274
- * subset manually with `viewToMat3(view)`.
2275
- */
2276
- draw: (data: TData, view: View, dims: Dims) => DrawCommand[];
2277
- /**
2278
- * Optional cache key. When present and a `LayerCommandCache` is supplied to
2279
- * `drawLayers`, the layer's previous `DrawCommand[]` is reused as long as
2280
- * every entry is `Object.is`-equal to the previous call's. A layer with no
2281
- * `deps` rebuilds on every frame.
2282
- *
2283
- * **The returned commands must be treated as immutable.** A cached tree is
2284
- * handed to the renderer again on later frames, so mutating a tree you
2285
- * previously returned corrupts the cache silently rather than erroring.
2286
- *
2287
- * **Screen-space layers are not protected against a stale `view`/`dims`
2288
- * the way world-space layers are** (see `space` below) — include them in
2289
- * `deps` if `draw` reads them.
2290
- */
2291
- deps?: (data: TData, view: View, dims: Dims) => readonly unknown[];
2292
- /**
2293
- * Whether the layer is shown when no explicit visibility entry exists.
2294
- * Defaults to `true` when absent.
2295
- */
2296
- defaultVisible?: boolean;
2297
- /**
2298
- * When true, the layer is always drawn regardless of the visibility map.
2299
- * Useful for layers that must never be hidden (e.g. base grid).
2300
- */
2301
- alwaysOn?: boolean;
2302
- /**
2303
- * Coordinate space the layer draws in.
2304
- *
2305
- * - `'world'` (default): the layer's `draw` returns world-space commands;
2306
- * `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`
2307
- * automatically.
2308
- * - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)
2309
- * commands; `drawLayers` passes them through unchanged. World-anchored
2310
- * chrome inside a screen-space layer must call `worldToScreen` or wrap
2311
- * the relevant subset with `viewToMat3(view)` manually.
2312
- */
2313
- space?: 'world' | 'screen';
2314
- /**
2315
- * Where the layer learns that state `draw` reads, outside the scene and the
2316
- * props, has changed. The canvas painting it subscribes while the layer is
2317
- * mounted and repaints on every notification, so a layer reading a mode
2318
- * registry needs no consumer code to follow a mode switch.
2319
- */
2320
- subscribe?: RedrawSource['subscribe'];
2321
- /**
2322
- * Full-screen passes run over this layer's own pixels before it joins the
2323
- * frame — a blur here blurs the world and leaves the HUD drawn above it
2324
- * sharp, which is the thing a CSS `filter` on the `<canvas>` cannot do.
2325
- *
2326
- * Costs nothing while empty: the renderer allocates no offscreen buffer
2327
- * until a layer actually declares one. See `GroupDrawCommand.effects` for
2328
- * what a pass may read, and {@link LayerGroup} to run one chain over
2329
- * several layers at once instead of one chain each.
2330
- */
2331
- effects?: readonly Effect[];
2332
- /**
2333
- * Optional hit-test for **consumer-attached** layers.
2334
- *
2335
- * Only layers registered through `CanvasExtensionApi.registerLayer` are
2336
- * hit-tested: `hitTestExtras` walks them last-registered-first on
2337
- * pointerdown, and `<SceneCanvas>` folds the result into its `affordanceAt`
2338
- * thunk ahead of the kit's own selection chrome. First non-null result
2339
- * wins; null means "I don't claim this hit, try the next layer."
2340
- *
2341
- * Layers that reach the draw stack some other way — a `Tool.overlay`, an
2342
- * entry in the `layers` map — are painted but never hit-tested, so defining
2343
- * `hitTest` on one has no effect. (The kit's own chrome doesn't need it: it
2344
- * goes through `buildAffordanceAt`.)
2345
- *
2346
- * Coordinates are world-space. The `data` arg is the layer's
2347
- * configured data slot (same as `draw`); `view` and `dims` mirror
2348
- * `draw`'s arguments.
2349
- */
2350
- hitTest?: (worldX: number, worldY: number, data: TData, view: View, dims: Dims,
2351
- /** Chrome-caps visibility predicate. When supplied, the layer must
2352
- * not return a hit from any chrome element whose id reports
2353
- * `false`. Absent → every element is hittable. */
2354
- isVisible?: (id: string) => boolean) => LayerHit | null;
2355
- /**
2356
- * Called on every pointermove when no gesture is currently captured.
2357
- * Lets layers (e.g. HUD widgets) track hover state without participating
2358
- * in the drag pipeline. Coords are world-space; the layer is responsible
2359
- * for any further conversion (e.g. world→screen for screen-space layers)
2360
- * and for its own throttling.
2361
- */
2362
- onUncapturedMove?: (worldX: number, worldY: number, evt: PointerEvent, view: View, dims: Dims) => void;
2363
- /**
2364
- * Called when the cursor leaves the canvas element. Lets layers clear
2365
- * any hover state they're holding.
2366
- */
2367
- onUncapturedLeave?: () => void;
2368
- }
2369
- /** Where one layer's commands landed in {@link drawLayers}' output,
2370
- * `[start, end)`, and the CPU milliseconds its `draw` took. A layer group's
2371
- * run is one span under the group's id. */
2372
- interface LayerDrawSpan {
2373
- id: string;
2374
- start: number;
2375
- end: number;
2376
- buildMs: number;
2377
- }
2378
- /**
2379
- * Walk visible layers and concatenate their emitted DrawCommand arrays into
2380
- * one flat list, ready to feed to `WeaselRenderer.render(commands)`.
2381
- *
2382
- * Visibility resolution order:
2383
- * 1. `alwaysOn` — always drawn, ignores visibility map.
2384
- * 2. Explicit entry in `visibility` map — overrides default.
2385
- * 3. `layer.defaultVisible` — falls back to `true` when absent.
2386
- *
2387
- * Transform composition: world-space layers (the default; `space` unset or
2388
- * `'world'`) have their commands wrapped in a `kind: 'group'` with
2389
- * `viewToMat3(view)` before they reach the renderer. Screen-space layers
2390
- * (`space: 'screen'`) pass through unchanged.
2391
- *
2392
- * A layer whose `draw` throws is dropped for the frame and reported through
2393
- * `onLayerError` — the rest of the frame still paints.
2394
- *
2395
- * `groups` brackets runs of consecutive layers so they composite as one — see
2396
- * `LayerGroup`. A layer named by no group is emitted exactly as it was before
2397
- * groups existed, and a frame that declares none allocates nothing.
2398
- *
2399
- * `spans`, when passed, receives one {@link LayerDrawSpan} per layer drawn.
2400
- */
2401
- declare function drawLayers<TData>(layers: RenderLayer<TData>[], data: TData, visibility: Record<string, boolean>, order: string[] | undefined, view: View | undefined, dims: Dims, cache?: LayerCommandCache, onLayerError?: (failure: LayerDrawFailure) => void, groups?: readonly LayerGroup[], spans?: LayerDrawSpan[]): DrawCommand[];
2402
- /**
2403
- * Resolve one layer's visibility: `alwaysOn` wins, then an explicit entry in
2404
- * `visibility`, then `defaultVisible`, defaulting to shown.
2405
- */
2406
- declare function isLayerVisible<TData>(layer: RenderLayer<TData>, visibility: Record<string, boolean>): boolean;
2407
- /**
2408
- * Does this layer reach the screen at all — both gates, in the order
2409
- * `drawLayers` applies them.
2410
- *
2411
- * **A listed `order` is the whole list**, so omission from it drops a layer
2412
- * that `visibility` would have shown, and `alwaysOn` does not rescue it.
2413
- * Hit-testing asks this, not `isLayerVisible`: a layer that is not painted
2414
- * but still claims pointer events is a pointer landing on nothing the user
2415
- * can see.
2416
- */
2417
- declare function isLayerPainted<TData>(layer: RenderLayer<TData>, visibility: Record<string, boolean>, order: string[] | undefined): boolean;
2418
- /**
2419
- * Draw one layer and put its commands in the space its `space` declares:
2420
- * world-space output wrapped in a `viewToMat3(view)` group, screen-space
2421
- * output passed through.
2422
- *
2423
- * Anything rendering layers through a view — the canvas itself, a viewport
2424
- * node's inner pass — goes through here. A second copy of this rule that
2425
- * forgets the wrap draws world content at raw world coords, which looks
2426
- * plausible at the identity view and wrong everywhere else.
2427
- *
2428
- * A `draw` that throws yields no commands, and `onLayerError` is told which
2429
- * layer it was. The layer's cache entry goes with it, so the next frame is a
2430
- * real re-attempt rather than a stale tree served under fresh deps.
2431
- */
2432
- declare function drawOneLayer<TData>(layer: RenderLayer<TData>, data: TData, view: View, dims: Dims, cache?: LayerCommandCache, onLayerError?: (failure: LayerDrawFailure) => void): DrawCommand[];
2433
- /** Whether `b` matches `a` entry for entry under `Object.is` — how
2434
- * `RenderLayer.deps` is compared, and any cache keyed the same way. */
2435
- declare function depsUnchanged(a: readonly unknown[], b: readonly unknown[]): boolean;
2436
-
2437
- /** Per-axis limits on a view's position. Any side may be left open. */
2438
- interface PanBounds {
2439
- minX?: number;
2440
- maxX?: number;
2441
- minY?: number;
2442
- maxY?: number;
2443
- }
2444
- /** Momentum settings for a pan: how quickly a flung view slows, when it stops,
2445
- * and what happens at the pan limits. The `DecayLoopConfig` fields a caller
2446
- * chooses up front, without the per-gesture `velocity` / `onTick`. */
2447
- interface InertiaConfig {
2448
- friction?: number;
2449
- minSpeed?: number;
2450
- /** What to do when inertial pan reaches `bounds`. Default: no clamping. */
2451
- boundary?: 'stop' | 'bounce' | 'spring';
2452
- /** View-coordinate limits for boundary clamping. Requires `boundary` to take effect. */
2453
- bounds?: PanBounds;
2454
- }
2455
- /** How a decay should run: its starting velocity, how fast it slows, and what
2456
- * happens if it reaches the pan limits. */
2457
- interface DecayLoopConfig {
2458
- velocity: {
2459
- vx: number;
2460
- vy: number;
2461
- };
2462
- friction?: number;
2463
- minSpeed?: number;
2464
- /** Bounds for boundary clamping. Requires `boundary` to take effect. */
2465
- viewBounds?: PanBounds;
2466
- /**
2467
- * What to do when the accumulated position hits `viewBounds`. Default: no clamping.
2468
- * - `'stop'`: clamp at boundary, kill velocity component.
2469
- * - `'bounce'`: linear reflection — flip velocity sign, magnitude preserved.
2470
- * - `'spring'`: damped reflection — flip velocity sign and shrink magnitude
2471
- * by `SPRING_DAMPING` per bounce so the motion settles naturally.
2472
- */
2473
- boundary?: 'stop' | 'bounce' | 'spring';
2474
- /** Starting position for internal boundary tracking. Required when `viewBounds` is set. */
2475
- initialPosition?: {
2476
- x: number;
2477
- y: number;
2478
- };
2479
- onTick: (dx: number, dy: number) => void;
2480
- onEnd?: () => void;
2481
- }
2482
- /** A rAF loop that coasts a value to a stop under friction, reporting the
2483
- * per-frame delta. What turns a released pan drag into momentum scrolling. */
2484
- declare function useDecayLoop(): {
2485
- start: (config: DecayLoopConfig) => void;
2486
- cancel: () => void;
2487
- };
2488
-
2489
- /** Which of a node's two per-anchor color arrays an override applies to. */
2490
- type VertexColorChannel = 'fill' | 'stroke';
2491
- /** Function-form override: receives the consumer-supplied base color
2492
- * array and the current animation timestamp (ms, from the animator's
2493
- * clock). Returns a flat RGBA float array (values in 0..1, matching
2494
- * the renderer's `stroke.vertexColors` / `PathDrawCommand.vertexColors`
2495
- * color space) of the same length as `base`. */
2496
- type ColorOverrideFn = (base: readonly number[], tMs: number) => number[];
2497
- /** Either a static per-anchor RGBA float array (0..1) or a function-form
2498
- * override (see {@link ColorOverrideFn}). */
2499
- type ColorOverride = readonly number[] | ColorOverrideFn;
2500
- /** Per-node, per-channel store of color overrides. Attached to `useAnimator` as
2501
- * `animator.colorOverrides`; painted onto scene nodes by `<SceneCanvas animator>`
2502
- * and onto a `createPathLayer`'s nodes by its `colorOverrides` option. */
2503
- declare class ColorOverrideRegistry {
2504
- private readonly map;
2505
- private _version;
2506
- set(id: string, channel: VertexColorChannel, override: ColorOverride): void;
2507
- clear(id: string, channel: VertexColorChannel): void;
2508
- clearAll(): void;
2509
- get(id: string, channel: VertexColorChannel): ColorOverride | undefined;
2510
- /** Whether anything overrides `id`, on either channel. */
2511
- has(id: string): boolean;
2512
- /**
2513
- * The colors to paint on `id`'s `channel`, given the colors the painter would
2514
- * otherwise paint and the frame's timestamp. A function override needs that
2515
- * `base` and must return an array of its length; failing either, `base`
2516
- * stands.
2517
- */
2518
- resolve(id: string, channel: VertexColorChannel, base: readonly number[] | undefined, tMs: number): readonly number[] | undefined;
2519
- version(): number;
2520
- }
2521
-
2522
- /** One keyframe. `easing` shapes the approach INTO this key from the previous
2523
- * one, so the first key's easing is never consulted. */
2524
- interface Keyframe<T> {
2525
- /** Time within the track's timeline, in ms. */
2526
- t: number;
2527
- value: T;
2528
- /** A function, the name of a built-in, or cubic-bezier control points. */
2529
- easing?: EasingSpec;
2530
- }
2531
- /** A track sampled as a pure function of the playhead. Scrubbing one is free
2532
- * and order-independent. */
2533
- interface SampledTrack<T> {
2534
- kind: 'sampled';
2535
- label?: string;
2536
- /** Sorted ascending by `t`. Of keys sharing a time, the last in the array is
2537
- * the value at exactly that time. */
2538
- keys: Keyframe<T>[];
2539
- /** Required unless every key's value is a number, a number array or a plain
2540
- * object of numbers, all of one shape; those are lerped field by field. */
2541
- interpolate?: Interpolate<T>;
2542
- /** Built once per segment and cached. Takes precedence over `interpolate`. */
2543
- interpolator?: InterpolatorFactory<T>;
2544
- onTick: (value: T) => void;
2545
- }
2546
- /** A clock that events are booked against, reading in ms. An `AudioEngine`
2547
- * from `@weasel-js/audio` is one. */
2548
- interface TimelineClock {
2549
- now(): number;
2550
- }
2551
- /** What `book` hands back so the booking can be retracted. A `VoiceHandle` from
2552
- * `@weasel-js/audio` is one. */
2553
- interface EventBookingHandle {
2554
- stop(): void;
2555
- }
2556
- /** One entry on an `EventTrack`. Give it `fire`, `book`, or both. */
2557
- interface TimelineEvent {
2558
- /** Time within the track's timeline, in ms. */
2559
- t: number;
2560
- /** Runs on the frame that crosses the edge, told how far behind the frame it
2561
- * was crossed, in ms — never negative, and measured against `duration` on
2562
- * the loop seam, where the outgoing lap's tail fires after the wrap. */
2563
- fire?: (lateBy: number) => void;
2564
- /** Runs up to `booking.lookahead` before the edge on a timeline with a
2565
- * `booking`, once per crossing, with the `booking.clock` time the edge lands
2566
- * at — never earlier than that clock's `now()`. A returned handle is stopped
2567
- * if a pause, seek, rate change, loop change, edit or cancel invalidates the
2568
- * booking before the clock reaches `when`; the event is then booked again
2569
- * wherever playback next reaches it. A booking with no handle stands. */
2570
- book?: (when: number) => EventBookingHandle | void;
2571
- }
2572
- /** A track of edge crossings, in forward playback only — a `seek` neither
2573
- * fires nor books the span it skips. */
2574
- interface EventTrack {
2575
- kind: 'event';
2576
- label?: string;
2577
- /** Sorted ascending by `t`. */
2578
- events: TimelineEvent[];
2579
- }
2580
- /** How a timeline books its events against an outside clock. */
2581
- interface EventBooking {
2582
- clock: TimelineClock;
2583
- /** How far ahead of the playhead to book, in clock ms. Default 100. An event
2584
- * first reached later than this — after a frame longer than it — books late. */
2585
- lookahead?: number;
2586
- /** An event first reached more than this many clock ms after its edge is
2587
- * skipped instead of booked late. Default `Infinity`. */
2588
- maxLate?: number;
2589
- }
2590
- /** A nested timeline, evaluated at `playhead - at`. Children are NOT registered
2591
- * with the animator separately; the parent evaluates them. */
2592
- interface TimelineTrack {
2593
- kind: 'timeline';
2594
- label?: string;
2595
- at: number;
2596
- timeline: NestedTimeline;
2597
- }
2598
- /** Any track a timeline can hold. */
2599
- type Track = SampledTrack<any> | EventTrack | TimelineTrack;
2600
- /** What a child timeline may declare. The parent owns playback, so `loop`,
2601
- * `autoplay`, `onDone` and `cancelKey` have no meaning below the root. */
2602
- interface NestedTimeline {
2603
- tracks: Track[];
2604
- /** Defaults to the largest end time across `tracks`. */
2605
- duration?: number;
2606
- }
2607
- /** What `animator.timeline` takes: a root timeline, which owns playback. */
2608
- interface TimelineOptions extends NestedTimeline {
2609
- /** `true` loops forever, `n` loops n additional times. Default false. */
2610
- loop?: boolean | number;
2611
- /** Default true. When false the timeline registers but holds at t=0 until resumed. */
2612
- autoplay?: boolean;
2613
- /** Called when playback reaches the end of its last lap. */
2614
- onDone?: () => void;
2615
- /** Any new animation passed the same `cancelKey` cancels this one. */
2616
- cancelKey?: string;
2617
- /** Names this timeline in `animator.watch` events and `animator.live()`. */
2618
- label?: string;
2619
- /** Books each event's `book` against a clock the timeline does not own, so a
2620
- * consumer on that clock lands it at its true sub-frame time. The frame
2621
- * clock's mapping onto it is smoothed each frame and resynced on a jump. */
2622
- booking?: EventBooking;
2623
- }
2624
- /** What `animator.timeline` returns: playback control plus live editing. */
2625
- interface TimelineHandle extends AnimationHandle {
2626
- /** Move the playhead. Never fires event tracks, at any depth. */
2627
- seek(t: number): void;
2628
- /** Change the loop policy. `true` loops forever, `n` allows n more laps,
2629
- * `false` stops at `duration`. Sets policy only — a timeline already parked
2630
- * at `duration` does not restart, because `rearm` declines to revive one.
2631
- * Rewind it with `seek(0)` and `resume()` to play it again. */
2632
- setLoop(loop: boolean | number): void;
2633
- /** The loop policy as it now stands: `true` endless, `false` stopping at
2634
- * `duration`, `n` for n laps still allowed. A finite count falls as laps
2635
- * are consumed, matching what `setLoop` takes. */
2636
- loop(): boolean | number;
2637
- /** Current playhead in ms. */
2638
- time(): number;
2639
- duration(): number;
2640
- tracks(): readonly Track[];
2641
- /** Run `fn`, then recompute duration, drop cached interpolators, and notify.
2642
- * Every mutation must go through this — an edited keyframe otherwise keeps
2643
- * interpolating toward its old value with no visible error. */
2644
- edit(fn: () => void): void;
2645
- /** Notified after each `edit`. Returns an unsubscribe. */
2646
- subscribe(cb: () => void): () => void;
2647
- }
2648
-
2649
- /** Blends two `T` values at eased progress `t`. Called once per frame; see
2650
- * {@link InterpolatorFactory} when the blend has setup worth hoisting. */
2651
- type Interpolate<T> = (from: T, to: T, t: number) => T;
2652
- /** Factory interpolator: built ONCE at tween start with (from, to), the returned
2653
- * function is called with `t ∈ [0, 1]` each frame. Use for interpolators with
2654
- * expensive setup (color-space conversion, path-string parsing) — d3-interpolate's
2655
- * shape exactly. For cheap interpolations the per-tick `Interpolate<T>` form is
2656
- * fine; this is the escape hatch when setup-per-tick is wasteful. */
2657
- type InterpolatorFactory<T> = (from: T, to: T) => (t: number) => T;
2658
- /** A running animation. Cancel it, or bend its time — pausing and time-scaling
2659
- * act on this animation's own virtual clock, independent of the animator's. */
2660
- interface AnimationHandle {
2661
- /** Monotonic id assigned by the animator. */
2662
- id: number;
2663
- /** Cancel this animation. Idempotent — no-op once already finished/canceled. */
2664
- cancel(): void;
2665
- /** Freeze this animation's virtual clock. Idempotent. */
2666
- pause(): void;
2667
- /** Resume this animation's virtual clock. Idempotent. */
2668
- resume(): void;
2669
- /** Multiply this animation's virtual-clock rate by `scale`. 1 = normal. */
2670
- setTimeScale(scale: number): void;
2671
- /** This animation's own time scale. 1 once it has finished, since the
2672
- * animator no longer holds an entry to read. */
2673
- timeScale(): number;
2674
- /** True iff this handle is currently paused. */
2675
- isPaused(): boolean;
2676
- }
2677
- /** A duration-based animation from `from` to `to` over `ms`, shaped by an
2678
- * easing curve. Reach for a spring instead when the motion should respond to
2679
- * where the value already is rather than restart from a fixed duration. */
2680
- interface TweenOptions<T> {
2681
- from: T;
2682
- to: T;
2683
- ms: number;
2684
- easing?: EasingSpec;
2685
- /** Required when T is not `number`. For T = number, defaults to linear numeric lerp.
2686
- * Called per-tick with `(from, to, t)`. For interpolators with expensive setup,
2687
- * prefer `interpolator` which is built once at tween start. */
2688
- interpolate?: Interpolate<T>;
2689
- /** Factory interpolator built once at tween start. Takes precedence over
2690
- * `interpolate` when both are provided. Use this for d3-interpolate or any
2691
- * `(from, to) => (t) => v` shape. */
2692
- interpolator?: InterpolatorFactory<T>;
2693
- onTick: (value: T) => void;
2694
- onDone?: () => void;
2695
- /** Any new animation passed the same cancelKey cancels the prior one in flight. */
2696
- cancelKey?: string;
2697
- /** Names this animation in {@link AnimatorEvent}s and `animator.live()`. */
2698
- label?: string;
2699
- }
2700
- /** A spring animation: runs until the value settles on `to` rather than for a
2701
- * set duration, so it absorbs an initial velocity naturally. Non-numeric `T`
2702
- * needs the four vector helpers. */
2703
- interface SpringOptions<T> {
2704
- from: T;
2705
- to: T;
2706
- /** Initial velocity in T-units per second. Default: zero (T-shape-aware). */
2707
- velocity?: T;
2708
- preset?: SpringPresetName;
2709
- stiffness?: number;
2710
- damping?: number;
2711
- mass?: number;
2712
- interpolate?: Interpolate<T>;
2713
- /** Vector helpers — required for non-numeric T. When `from`, `to` and `velocity` are numbers, number
2714
- * arrays or plain objects of numeric fields of one shape, and stiffness, damping and mass are
2715
- * positive, the motion is solved in closed form and these only decide when it has come to rest —
2716
- * a `subtract` that wraps an angle won't change the path. */
2717
- add?: (a: T, b: T) => T;
2718
- subtract?: (a: T, b: T) => T;
2719
- scale?: (v: T, k: number) => T;
2720
- magnitude?: (v: T) => number;
2721
- /** Velocity magnitude below which the spring is considered settled. Default 0.01. */
2722
- restThreshold?: number;
2723
- onTick: (value: T) => void;
2724
- onDone?: () => void;
2725
- cancelKey?: string;
2726
- /** Names this animation in {@link AnimatorEvent}s and `animator.live()`. */
2727
- label?: string;
2728
- }
2729
- /** Spring and decay as one animation. With a `to`, a spring pulls toward it;
2730
- * with `to: null`, the value coasts on its velocity. Either can become the
2731
- * other mid-flight through the handle. */
2732
- interface PhysicsOptions<T> {
2733
- from: T;
2734
- /** Target. `null` ⇒ no spring force (decay-mode). */
2735
- to?: T | null;
2736
- /** Initial velocity in T-units per second. */
2737
- velocity?: T;
2738
- preset?: SpringPresetName;
2739
- stiffness?: number;
2740
- damping?: number;
2741
- mass?: number;
2742
- restThreshold?: number;
2743
- /** Vector helpers — required for non-numeric T. When `from`, `to` and `velocity` are numbers, number
2744
- * arrays or plain objects of numeric fields of one shape, and stiffness, damping and mass are
2745
- * positive, the motion is solved in closed form and these only decide when it has come to rest —
2746
- * a `subtract` that wraps an angle won't change the path. */
2747
- add?: (a: T, b: T) => T;
2748
- subtract?: (a: T, b: T) => T;
2749
- scale?: (v: T, k: number) => T;
2750
- magnitude?: (v: T) => number;
2751
- onTick: (value: T) => void;
2752
- onDone?: () => void;
2753
- cancelKey?: string;
2754
- /** Names this animation in {@link AnimatorEvent}s and `animator.live()`. */
2755
- label?: string;
2756
- }
2757
- /** An `AnimationHandle` that can also be steered while it runs — the point of
2758
- * the physics primitive. */
2759
- interface PhysicsHandle<T = unknown> extends AnimationHandle {
2760
- /** Retarget mid-flight. `null` ⇒ switch to decay-mode (no spring force). */
2761
- setTarget(to: T | null): void;
2762
- /** Replace the current velocity in T-units per second. */
2763
- setVelocity(v: T): void;
2764
- }
2765
- /** Momentum: coast from `from` at `velocity`, slowing by `friction` each
2766
- * second until below `threshold`. What a flick-to-pan leaves behind. */
2767
- interface DecayOptions<T> {
2768
- from: T;
2769
- velocity: T;
2770
- /** Per-second velocity multiplier in (0, 1). Default 0.95. */
2771
- friction?: number;
2772
- /** Velocity magnitude below which decay stops. Default 0.5. */
2773
- threshold?: number;
2774
- /** When `from` and `velocity` are numbers, number arrays or plain objects of numeric fields of one
2775
- * shape and `friction` is below 1, the motion is solved in closed form: `add` and `scale` go
2776
- * unused and `magnitude` only decides when it has come to rest. */
2777
- add: (a: T, b: T) => T;
2778
- scale: (v: T, k: number) => T;
2779
- magnitude: (v: T) => number;
2780
- onTick: (value: T) => void;
2781
- onDone?: () => void;
2782
- cancelKey?: string;
2783
- /** Names this animation in {@link AnimatorEvent}s and `animator.live()`. */
2784
- label?: string;
2785
- }
2786
- /** Options for `useAnimator`. Everything here is an injection seam for tests;
2787
- * the defaults are the real clock, rAF, and `setTimeout`. */
2788
- interface UseAnimatorOptions {
2789
- /** Optional clock injection for tests. Returns ms since some epoch. */
2790
- now?: () => number;
2791
- /** Optional rAF / cAF injection for tests. Defaults to window.requestAnimationFrame. */
2792
- requestFrame?: (cb: (t: number) => void) => number;
2793
- cancelFrame?: (handle: number) => void;
2794
- /** Optional `setTimeout` injection used by `stagger` for per-item delays.
2795
- * Defaults to the global `setTimeout`. Tests inject a virtual scheduler. */
2796
- setTimer?: (cb: () => void, ms: number) => unknown;
2797
- /** Companion to `setTimer`. Defaults to global `clearTimeout`. */
2798
- clearTimer?: (handle: unknown) => void;
2799
- }
2800
- /**
2801
- * Owns every running animation on a canvas and drives them from one rAF loop.
2802
- * Beyond the primitives (`tween`, `spring`, `decay`, `physics`) it offers
2803
- * composition — `loop`, `stagger` — and bulk control by handle, by cancel-key,
2804
- * or over everything at once.
2805
- *
2806
- * An animator does not know about the scene: animations report values through
2807
- * `onTick` and the caller decides what to do with them.
2808
- */
2809
- interface Animator {
2810
- tween<T>(opts: TweenOptions<T>): AnimationHandle;
2811
- spring<T>(opts: SpringOptions<T>): AnimationHandle;
2812
- decay<T>(opts: DecayOptions<T>): AnimationHandle;
2813
- /** Unified spring/decay primitive. With `to` set, behaves as a spring;
2814
- * with `to: null`, behaves as a velocity-driven decay. Supports
2815
- * mid-flight retargeting via the returned handle's `setTarget`. */
2816
- physics<T>(opts: PhysicsOptions<T>): PhysicsHandle<T>;
2817
- /** Cancel a specific animation by handle. Pose stays at current value (no jump). */
2818
- cancel(handle: AnimationHandle): void;
2819
- /** Cancel every animation currently active under `key`. */
2820
- cancelKey(key: string): void;
2821
- /** Cancel everything. Useful from a destructor or "reset scene" path. */
2822
- cancelAll(): void;
2823
- /** True iff at least one animation is active. With `key`, scoped to that cancelKey. */
2824
- isActive(key?: string): boolean;
2825
- /**
2826
- * True while the animator is currently executing an animation tick. Useful
2827
- * for adapter wrappers (e.g. `animateOnSetPose`) that need to detect
2828
- * "this `setPose` was called from inside another animation's onTick"
2829
- * (momentum decay, in-flight tween, spring) and avoid recursively
2830
- * scheduling a new wrap-animation that would fight the caller.
2831
- */
2832
- isTicking(): boolean;
2833
- /** Freeze every animation managed by this animator. */
2834
- pause(): void;
2835
- /** Resume every animation managed by this animator. */
2836
- resume(): void;
2837
- /** True iff the animator is currently globally paused. */
2838
- isPaused(): boolean;
2839
- /** Multiply every animation's virtual-clock rate by `scale`. 1 = normal. */
2840
- setTimeScale(scale: number): void;
2841
- /** The global time scale. Per-animation scales multiply on top of it. */
2842
- timeScale(): number;
2843
- /** Freeze every animation whose `cancelKey` matches. */
2844
- pauseKey(key: string): void;
2845
- /** Resume every animation whose `cancelKey` matches. */
2846
- resumeKey(key: string): void;
2847
- /** Set per-animation timeScale for every animation whose `cancelKey` matches. */
2848
- setTimeScaleByKey(key: string, scale: number): void;
2849
- /**
2850
- * Loop primitive: repeatedly invoke `factory` to produce a child animation.
2851
- * The factory must wire its returned handle's `onDone` to call `next` so
2852
- * the loop advances. Returns a handle whose pause/resume/setTimeScale/cancel
2853
- * delegate to the current in-flight child (and prevent future iterations
2854
- * on cancel).
2855
- *
2856
- * The loop is registered with the animator under a supervisor entry so
2857
- * `animator.cancel(handle)`, `animator.cancelKey(opts.cancelKey)`, and
2858
- * `animator.isActive(opts.cancelKey)` all work for it.
2859
- */
2860
- loop(factory: LoopFactory, opts?: LoopOptions): AnimationHandle;
2861
- /** Sugar over `loop` for the common case of looping a tween between two
2862
- * values with optional direction handling (`restart` | `reverse` |
2863
- * `alternate`). Registered with the animator like `loop`. */
2864
- tweenLoop<T>(opts: TweenLoopOptions<T>): AnimationHandle;
2865
- /**
2866
- * Stagger primitive: schedule a per-item animation, offset by `delay` ms
2867
- * per index (or a custom function of the index). Two forms:
2868
- * - Factory form: pass `factory` directly, returns a composite
2869
- * `AnimationHandle`.
2870
- * - Builder form: omit `factory`, get a `StaggerBuilder` for fluent
2871
- * `.each` / `.tween` / `.springPose` calls.
2872
- *
2873
- * The composite handle's `cancel` cancels pending timers AND in-flight
2874
- * children. `pause` / `resume` / `setTimeScale` propagate to in-flight
2875
- * children; `pause`/`resume` also freeze and thaw pending per-item timers
2876
- * (the remaining time before each pending fire is preserved across the
2877
- * pause).
2878
- *
2879
- * The stagger is registered with the animator under a supervisor entry so
2880
- * `animator.cancel(handle)`, `animator.cancelKey(opts.cancelKey)`, and
2881
- * `animator.isActive(opts.cancelKey)` all work for it.
2882
- */
2883
- stagger<TItem>(items: readonly TItem[], delay: StaggerDelay): StaggerBuilder<TItem>;
2884
- stagger<TItem>(items: readonly TItem[], delay: StaggerDelay, factory: StaggerFactory<TItem>, opts?: StaggerOptions): AnimationHandle;
2885
- /**
2886
- * Keyframe timeline. Registered like any other animation, so its playhead
2887
- * responds to `pause`, `setTimeScale` and `cancelKey`. Sampled tracks are a
2888
- * pure function of the playhead; event tracks fire only on forward playback.
2889
- */
2890
- timeline(opts: TimelineOptions): TimelineHandle;
2891
- /** Per-node, per-channel color override registry, painted onto scene nodes
2892
- * by `<SceneCanvas animator>` and read by `createPathLayer`. Used by `tweenVertexColors`,
2893
- * `springVertexColors`, `cycleVertexColors`, `staggerVertexColors`. Cleared
2894
- * automatically on animator unmount. */
2895
- colorOverrides: ColorOverrideRegistry;
2896
- /**
2897
- * Subscribe to a callback fired once per RAF frame while any animation is
2898
- * active. Returns an unsubscribe function. Used by consumers (typically
2899
- * `<SceneCanvas>`) that need to repaint when an animation's side-effect
2900
- * is read from a non-scene channel (e.g. `colorOverrides` consulted from
2901
- * a custom `drawOne`) — scene mutations naturally trigger a repaint, but
2902
- * `colorOverrides` writes do not.
2903
- *
2904
- * The callback fires AFTER the per-frame tick of each registered
2905
- * animation, so by the time it runs `colorOverrides.get(...)` returns
2906
- * the latest values. If no animations are active, no tick fires.
2907
- */
2908
- onTick(cb: () => void): () => void;
2909
- /**
2910
- * Keep the animator's RAF loop running until the returned cancel
2911
- * function is called. Use for animations whose effect is read on every
2912
- * frame but which don't have a natural progress state (e.g.
2913
- * `cycleVertexColors`, which expresses its current value as a function
2914
- * of `performance.now()` rather than as a tween from `from` to `to`).
2915
- * Without a keep-alive entry the loop would idle and `onTick` would
2916
- * stop firing even though the override is still installed.
2917
- */
2918
- keepAlive(): () => void;
2919
- /**
2920
- * Subscribe to the lifecycle of every animation this animator runs, the
2921
- * composites and timelines included. Returns an unsubscribe. Events arrive
2922
- * synchronously, as each thing happens — see {@link AnimatorEvent} for the
2923
- * order. With no listener attached nothing is built or delivered, so an
2924
- * unwatched animator pays nothing for this.
2925
- */
2926
- watch(listener: AnimatorListener): () => void;
2927
- /** What is running right now, in registration order: a fresh snapshot per
2928
- * call, for a debug overlay to draw. */
2929
- live(): LiveAnimation[];
2930
- }
2931
- /** Which animator call started an animation. A loop, tween loop or stagger is
2932
- * an entry of its own beside the children it runs; `keepAlive` holds the
2933
- * frame loop open and animates nothing. */
2934
- type AnimationKind = 'tween' | 'spring' | 'decay' | 'physics' | 'timeline' | 'loop' | 'tweenLoop' | 'stagger' | 'keepAlive';
2935
- /** Who an {@link AnimatorEvent} is about. One object per registration, so the
2936
- * same animation's events share it by identity. */
2937
- interface AnimationInfo {
2938
- /** The id on the animation's handle. */
2939
- readonly id: number;
2940
- readonly kind: AnimationKind;
2941
- /** The animation's `cancelKey`. Pose helpers key by node, as `pose:<id>`. */
2942
- readonly key?: string;
2943
- /** The animation's `label` option. */
2944
- readonly label?: string;
2945
- }
2946
- /** One row of `animator.live()`. */
2947
- interface LiveAnimation extends AnimationInfo {
2948
- /** Paused on its own handle. The animator-wide pause is `animator.isPaused()`. */
2949
- readonly paused: boolean;
2950
- /** Its own time scale; the animator's multiplies on top. */
2951
- readonly timeScale: number;
2952
- /** Virtual ms its clock has advanced since it started. */
2953
- readonly elapsed: number;
2954
- /** 0 to 1, for a tween or a timeline (the playhead over its duration).
2955
- * Absent where there is no end to measure against: physics, composites. */
2956
- readonly progress?: number;
2957
- }
2958
- /**
2959
- * One thing happening to an animation, as `animator.watch` delivers it.
2960
- *
2961
- * - `start` — registered. A finished timeline that `seek` or `edit` revives
2962
- * starts again, under the same id.
2963
- * - `end` — finished on its own. Delivered once every animation has ticked
2964
- * that frame, in registration order.
2965
- * - `cancel` — stopped by its handle, `cancel`, `cancelKey` or `cancelAll`.
2966
- * - `interrupt` — stopped because `by` claimed its `cancelKey`. Delivered
2967
- * before `by`'s `start`.
2968
- * - `error` — its tick threw (an `onTick`, `onDone` or interpolator), so it was
2969
- * taken off the table, after the frame's other animations ticked. In place of
2970
- * `cancel`, or after `end` when `onDone` threw. The error is also logged.
2971
- * - `lap` — a looping timeline wrapped; `lap` counts wraps since it started.
2972
- * - `fire` — an event on a timeline's event track was crossed, at any depth.
2973
- * `path` indexes from the root timeline's `tracks()` down through each
2974
- * nested timeline to the event track. Delivered after the event's own `fire`.
2975
- */
2976
- type AnimatorEvent = {
2977
- readonly type: 'start' | 'end' | 'cancel';
2978
- readonly animation: AnimationInfo;
2979
- } | {
2980
- readonly type: 'interrupt';
2981
- readonly animation: AnimationInfo;
2982
- readonly by: AnimationInfo;
2983
- } | {
2984
- readonly type: 'error';
2985
- readonly animation: AnimationInfo;
2986
- readonly error: unknown;
2987
- } | {
2988
- readonly type: 'lap';
2989
- readonly animation: AnimationInfo;
2990
- readonly lap: number;
2991
- } | {
2992
- readonly type: 'fire';
2993
- readonly animation: AnimationInfo;
2994
- readonly track: EventTrack;
2995
- readonly path: readonly number[];
2996
- readonly event: TimelineEvent;
2997
- /** As the event's own `fire` is told: ms behind the frame it was crossed. */
2998
- readonly lateBy: number;
2999
- };
3000
- /** Receives what `animator.watch` delivers. */
3001
- type AnimatorListener = (event: AnimatorEvent) => void;
3002
- /** Options for `Animator.loop`. */
3003
- interface LoopOptions {
3004
- /** Maximum number of iterations. Default Infinity. */
3005
- count?: number;
3006
- /** Invoked when the loop reaches `count` iterations naturally (not on cancel). */
3007
- onDone?: () => void;
3008
- /** Any new animation passed the same cancelKey cancels the prior one in flight.
3009
- * Also enables `animator.cancelKey` / `animator.isActive(key)` for this loop. */
3010
- cancelKey?: string;
3011
- /** Names this loop in {@link AnimatorEvent}s and `animator.live()`. */
3012
- label?: string;
3013
- }
3014
- /** A stagger run's own options: the factory form's fourth argument, and part
3015
- * of every fluent builder method's options. */
3016
- interface StaggerOptions {
3017
- /** Cancel-key for the supervising registration. `animator.cancelKey(key)`
3018
- * cancels the whole stagger; `animator.isActive(key)` returns true while
3019
- * any timer or child is alive. */
3020
- cancelKey?: string;
3021
- /** Names this stagger in {@link AnimatorEvent}s and `animator.live()`. */
3022
- label?: string;
3023
- }
3024
- /** Produces one iteration of a loop. Must arrange for `next` to be called when
3025
- * the animation it returns finishes, or the loop stalls after one pass. */
3026
- type LoopFactory = (iteration: number, next: () => void) => AnimationHandle;
3027
- /** Per-index delay schedule. Number ⇒ `index * delay` ms. Function ⇒ caller
3028
- * decides the absolute delay for each index (e.g. `i => i * i * 30`). */
3029
- type StaggerDelay = number | ((index: number) => number);
3030
- /** Produces the animation for one staggered item. */
3031
- type StaggerFactory<TItem> = (item: TItem, index: number) => AnimationHandle;
3032
- /** A `T` value or a function that derives one from the per-item context. Used
3033
- * by the fluent builder methods (`.tween`, `.springPose`) so each item can
3034
- * vary an option (e.g. `to: (_item, i) => (i + 1) * 10`). */
3035
- type StaggerPerItem<T, TItem> = T | ((item: TItem, index: number) => T);
3036
- /** Options for the stagger builder's `.tween`: a tween per item, where
3037
- * `from`, `to` and `ms` may each vary by item. `cancelKey` and `label` belong
3038
- * to the stagger as a whole, as in {@link StaggerOptions}. */
3039
- interface StaggerTweenOptions<T, TItem> extends StaggerOptions {
3040
- from: StaggerPerItem<T, TItem>;
3041
- to: StaggerPerItem<T, TItem>;
3042
- ms: StaggerPerItem<number, TItem>;
3043
- easing?: EasingSpec;
3044
- interpolate?: Interpolate<T>;
3045
- onTick: (value: T, item: TItem, index: number) => void;
3046
- onDone?: (item: TItem, index: number) => void;
3047
- }
3048
- /** Options for the stagger builder's `.springPose`: the spring tuning, and
3049
- * whether each item's settle is recorded as an undoable op. `cancelKey` and
3050
- * `label` belong to the stagger as a whole, as in {@link StaggerOptions};
3051
- * each item's spring keeps its own per-node key. */
3052
- interface StaggerSpringPoseOptions<TPose> extends StaggerOptions {
3053
- preset?: SpringPresetName;
3054
- stiffness?: number;
3055
- damping?: number;
3056
- mass?: number;
3057
- geometry?: PoseDescriptor<TPose>;
3058
- recordOp?: boolean;
3059
- opLabel?: string;
3060
- }
3061
- /** Fluent form of `Animator.stagger`: pick what to run per item after the
3062
- * items and the delay schedule are already fixed. */
3063
- interface StaggerBuilder<TItem> {
3064
- /** Run an arbitrary per-item factory. */
3065
- each(factory: StaggerFactory<TItem>, opts?: StaggerOptions): AnimationHandle;
3066
- /** Sugar: per-item `animator.tween` with per-item-varying options. */
3067
- tween<T>(opts: StaggerTweenOptions<T, TItem>): AnimationHandle;
3068
- /** Sugar: per-item `springPose` against an adapter. `poseFn` returns the
3069
- * target pose for each item. Each item must either be a primitive
3070
- * (string/number) or expose a string `id` field — otherwise pose ids
3071
- * would collide on `"[object Object]"` and successive tweens would
3072
- * cancel each other. Throws on items that satisfy neither. */
3073
- springPose<TPose>(adapter: SceneAdapter<{
3074
- id: string;
3075
- }, TPose>, poseFn: (item: TItem, index: number) => TPose, opts?: StaggerSpringPoseOptions<TPose>): AnimationHandle;
3076
- }
3077
- /** Options for `Animator.tweenLoop` — a tween's options plus how each
3078
- * iteration relates to the last. */
3079
- interface TweenLoopOptions<T> {
3080
- from: T;
3081
- to: T;
3082
- ms: number;
3083
- easing?: EasingSpec;
3084
- /** `restart` (default): from→to every iteration.
3085
- * `reverse`: to→from every iteration.
3086
- * `alternate`: even iterations from→to, odd iterations to→from. */
3087
- direction?: 'restart' | 'reverse' | 'alternate';
3088
- count?: number;
3089
- interpolate?: Interpolate<T>;
3090
- onTick: (value: T) => void;
3091
- onDone?: () => void;
3092
- cancelKey?: string;
3093
- /** Names this animation in {@link AnimatorEvent}s and `animator.live()`. */
3094
- label?: string;
3095
- }
3096
-
3097
- /** Cancel-key prefix. Each hook instance appends its own id, so two runners
3098
- * sharing an animator do not cancel each other. */
3099
- declare const VIEW_ANIMATION_KEY = "view";
3100
- /** How the camera should move. */
3101
- interface ViewAnimationOptions {
3102
- /** Duration in ms. Default 250. */
3103
- ms?: number;
3104
- /** Easing curve. Default `easeOutCubic`. */
3105
- easing?: EasingSpec;
3106
- /** Replace the kit's log-scale / fixed-anchor curve. */
3107
- interpolator?: InterpolatorFactory<View>;
3108
- /** Fires when the target is reached. Not called on cancel. */
3109
- onDone?: () => void;
3110
- }
3111
- /** Options accepted by {@link ViewAnimationApi.animateToBounds}. */
3112
- interface AnimateToBoundsOptions extends FitViewToBoundsOptions, ViewAnimationOptions {
3113
- }
3114
- /** What the runner reads and writes. On `<SceneCanvas>` this is the same
3115
- * channel `view.set` uses, so a camera animation on an uncontrolled canvas
3116
- * costs no React render. */
3117
- interface ViewChannel {
3118
- get(): View;
3119
- set(v: View): void;
3120
- }
3121
- /** The camera animation surface. One animation at a time. */
3122
- interface ViewAnimationApi {
3123
- /** Glide from the live view to `to`. A thunk receives the pending target when
3124
- * one is in flight, so successive discrete steps compound. */
3125
- animate(to: View | ((base: View) => View), opts?: ViewAnimationOptions): void;
3126
- /** `fitViewToBounds` composed with `animate`. */
3127
- animateToBounds(bounds: Bounds, dims: ViewportDims, opts?: AnimateToBoundsOptions): void;
3128
- /** Cancel. The view stays where it is — no jump to the target. */
3129
- stop(): void;
3130
- isAnimating(): boolean;
3131
- /** Where the in-flight animation is heading, or null when none is. */
3132
- target(): View | null;
3133
- /** Cancel unless the write that prompted this came from the runner's own
3134
- * per-frame write. Feed it from every channel that can move the camera. */
3135
- stopIfExternal(): void;
3136
- }
3137
- /**
3138
- * Animate the viewport `View`. Runs on the kit's {@link Animator} — pass one to
3139
- * share a canvas's animator, or omit it and the hook makes its own. A caller
3140
- * that always has one should use {@link useViewAnimationOn}, which builds none.
3141
- *
3142
- * Every animation from one instance registers under that instance's cancel key,
3143
- * so starting one cancels whatever *it* had in flight, and each starts from the
3144
- * *live* view rather than a captured value — an interrupted camera never jumps.
3145
- * Two instances on one animator are independent.
3146
- */
3147
- declare function useViewAnimation(view: ViewChannel, animator?: Animator): ViewAnimationApi;
3148
- /** {@link useViewAnimation} on an animator the caller owns. Constructs no
3149
- * animator of its own, so the camera runs on exactly the one passed. */
3150
- declare function useViewAnimationOn(view: ViewChannel, animator: Animator): ViewAnimationApi;
3151
-
3152
- /**
3153
- * @experimental
3154
- * PointerContext — the world-space position of the canvas pointer, and the
3155
- * view it is over, refreshed on every `pointermove` and cleared on
3156
- * `pointerleave`.
3157
- *
3158
- * It is an external store rather than React state: cursor moves fire dozens of
3159
- * times per second, and a consumer that only reads on demand (paste at the
3160
- * pointer) must not re-render for each one. Readers that do need to follow it
3161
- * — a linked cursor — subscribe, or use `usePointerPosition()`.
3162
- *
3163
- * `<SceneCanvas>` publishes automatically, and defers to a provider already in
3164
- * scope, so several surfaces under one provider share one pointer: a detached
3165
- * minimap and its main canvas each see where the other's pointer is.
3166
- */
3167
-
3168
- /** @experimental World-space pointer position and the view it is over — a
3169
- * view id, or `null` for a surface's own camera. `null` as a whole when the
3170
- * pointer is over no publishing surface. */
3171
- type PointerWorldPos = {
3172
- worldX: number;
3173
- worldY: number;
3174
- viewId: string | null;
3175
- } | null;
3176
- /** @experimental */
3177
- interface PointerContextValue {
3178
- /** The latest position. */
3179
- get(): PointerWorldPos;
3180
- /** Publish a position. Subscribers hear only a changed value. */
3181
- set(next: PointerWorldPos): void;
3182
- /** Called after every change; returns the unsubscribe. */
3183
- subscribe(fn: () => void): () => void;
3184
- /** Bumps on every change. */
3185
- getVersion(): number;
3186
- /** `get()` as a stable thunk, for hooks taking a drop-point option. */
3187
- readonly getDropPoint: () => PointerWorldPos;
3188
- }
3189
- /** @experimental A pointer store with no provider around it. */
3190
- declare function createPointerStore(): PointerContextValue;
3191
- /**
3192
- * @experimental
3193
- * Wrap the part of the React tree that should share one pointer. Usually at
3194
- * the app root, alongside `<ActionsProvider>` and `<SelectionContextProvider>`.
3195
- *
3196
- * Most consumers don't need to mount this directly — `<SceneCanvas>` mounts
3197
- * one when none is in scope. Mount it yourself when two surfaces should share
3198
- * a pointer. Pass `store` to publish into one you hold elsewhere
3199
- * (`createPointerStore()`).
3200
- */
3201
- declare function PointerContextProvider({ children, store }: {
3202
- children: ReactNode;
3203
- store?: PointerContextValue;
3204
- }): ReactNode;
3205
- /** @experimental The surrounding pointer store, or `null` when no provider is
3206
- * in scope. */
3207
- declare function usePointerContext(): PointerContextValue | null;
3208
- /** @experimental The pointer, re-rendering the caller whenever it changes.
3209
- * `null` outside a provider. */
3210
- declare function usePointerPosition(): PointerWorldPos;
3211
-
3212
- /**
3213
- * Project a leaf path from its source frame into world coordinates given a
3214
- * `{x, y, width, height, rotation?}` pose. Two transforms compose, in this
3215
- * order:
3216
- *
3217
- * 1. Translate so the path's source AABB origin lands at `pose.x, pose.y`.
3218
- * 2. If `pose.rotation` is nonzero, rotate every coord about the unrotated
3219
- * AABB center — i.e. `(pose.x + pose.width/2, pose.y + pose.height/2)`,
3220
- * matching `SceneCanvas.rotateAroundAABBCenter` so the baked geometry
3221
- * lines up with what the renderer draws.
3222
- *
3223
- * RectPaths can't carry rotation as-is (they're axis-aligned by definition);
3224
- * a non-zero `pose.rotation` on a rect promotes the result to a polygon so
3225
- * the four corners are baked into world coords. Callers that need to keep
3226
- * the rect fast-path can pre-check `pose.rotation` themselves.
3227
- *
3228
- * Used by adapters that need true world geometry — most notably boolean ops,
3229
- * where operating on the unrotated source path produces wrong shapes.
3230
- */
3231
-
3232
- /** Subset of pose fields this helper consumes. Matches the kit's auto-rotate
3233
- * convention (`SceneCanvas.defaultDrawOne`): `x/y/width/height` define an
3234
- * AABB whose unrotated center is the rotation pivot. */
3235
- interface PathInWorldPose {
3236
- x: number;
3237
- y: number;
3238
- width: number;
3239
- height: number;
3240
- rotation?: number;
3241
- }
3242
- /**
3243
- * Project a stored path into the pose's **local frame** — translate (and, for
3244
- * rects, rebase to the pose's `width`/`height`) so the path's AABB origin sits
3245
- * at the pose origin. This is translate-only: it does NOT apply
3246
- * `pose.rotation`. Callers that want the fully world-positioned (rotated) path
3247
- * should use `pathInWorld`, which composes this with the rotation wrap.
3248
- *
3249
- * Rects rebase onto the pose's `width`/`height` because resize updates the pose,
3250
- * not the stored `RectPath` — so the live size lives on the pose. Honoring it
3251
- * here keeps this in lockstep with the renderer (`SceneCanvas` paints the rect
3252
- * at `pose.width/height`); otherwise world-space consumers (slice, booleans,
3253
- * release-compound) would operate on the stale pre-resize dimensions.
3254
- *
3255
- * Used by the local seams: the default painter (paint + silhouette), and as the
3256
- * building block `pathInWorld`/`findShapeSilhouette` rotate on top of.
3257
- */
3258
- declare function pathInPoseFrame(path: Path, pose: PathInWorldPose): Path;
3259
- /**
3260
- * Bake `pose` into `path`'s coordinates so the returned path is positioned
3261
- * in world space. Returns a fresh `Path` instance; never mutates inputs.
3262
- */
3263
- declare function pathInWorld(path: Path, pose: PathInWorldPose): Path;
3264
- /**
3265
- * Inverse of `pathInWorld` for a path in **world** space: the unrotated
3266
- * stored path plus the pose that draws it there. Inverse-rotates about the
3267
- * pose's AABB center, so the stored path is unrotated and the pose's
3268
- * `rotation` is preserved, then fits the pose's AABB fields to the result.
3269
- * The new box's center is placed where the old rotation carries it, since
3270
- * the pivot moves with the box: `pathInWorld(result.path, result.pose)`
3271
- * reproduces `worldPath` whether or not its bounds changed.
3272
- *
3273
- * Generic in the pose type so consumer-defined pose fields (including
3274
- * `rotation`) survive the round-trip. Used by the anchor-edit commit, its
3275
- * live preview, and the default slice dep.
3276
- */
3277
- declare function worldEditToStorage<P extends PathInWorldPose>(pose: P, worldPath: PolygonPath): {
3278
- pose: P;
3279
- path: PolygonPath;
3280
- };
3281
- declare function worldEditToStorage<P extends PathInWorldPose>(pose: P, worldPath: Path): {
3282
- pose: P;
3283
- path: Path;
3284
- };
3285
-
3286
- /**
3287
- * Text as path geometry: the glyph outlines a text node draws, placed in
3288
- * world space and composed into one `Path`. What "Create Outlines" replaces a
3289
- * text node with, and what a Boolean op reads when a text node is an operand.
3290
- *
3291
- * The walk is `layoutRuns`', so the glyphs land exactly where the renderer's
3292
- * outline tier puts them — same pen positions, same baselines, same synthetic
3293
- * italic shear — but with the tier forced on for every glyph whatever its
3294
- * size, and with paint set aside: unfilled text has outlines too.
3295
- * `textToPathsByPaint` is the same walk, split by the paint each glyph wears.
3296
- */
3297
-
3298
- /** The fields of a text node's data that decide its glyph geometry, and the
3299
- * paint `textToPathsByPaint` splits it by. Paint never changes the geometry. */
3300
- interface TextOutlineSource {
3301
- text: string;
3302
- runs?: readonly StyledRun[];
3303
- style?: TextStyle;
3304
- verticalAlign?: TextVerticalAlign;
3305
- /** The node's `data.fill`: `null` is no fill, absent the default black. */
3306
- fill?: FillStyle | null;
3307
- /** The node's `data.stroke`: absent or `null` is no stroke. */
3308
- stroke?: Stroke | null;
3309
- }
3310
- /** One paint's share of a text node's outlines — see {@link textToPathsByPaint}. */
3311
- interface TextPaintPath {
3312
- /** The resolved fill these glyphs wear: the run's own, else the node's.
3313
- * `null` for unfilled text, painted by its stroke alone. */
3314
- fill: FillStyle | null;
3315
- /** The resolved stroke — the run's own, else the node's — or absent. */
3316
- stroke?: Stroke;
3317
- /** World-space outlines, as {@link textToPath} builds them. */
3318
- path: PolygonPath;
3319
- }
3320
- /** A text node as {@link textToPath} reads it: its data and its pose. */
3321
- interface TextNodeSource {
3322
- data: TextOutlineSource;
3323
- pose: PathInWorldPose;
3324
- }
3325
- /** Options for {@link textToPath}. */
3326
- interface TextToPathOptions {
3327
- /** The view scale a `{ px }` `fontSize` or `letterSpacing` resolves
3328
- * against, as for `textCommandFromPose`. Default 1. */
3329
- scale?: number;
3330
- }
3331
- /**
3332
- * Why a text node has no outline geometry to give:
3333
- *
3334
- * - `'no-outlines'` — the face that serves it has no outlines registered
3335
- * (`registerFontOutlines`), so there is no geometry to read.
3336
- * - `'outlines-loading'` — registered but not parsed yet. `loadTextOutlines`
3337
- * waits for it; the call has already started the load.
3338
- * - `'outlines-failed'` — registered, and the bytes did not load or parse.
3339
- * - `'synthetic-bold'` — the requested weight is faked from a lighter face.
3340
- * The renderer thickens the distance field for that, and a path has no
3341
- * field to thicken; emboldening geometry needs outline offsetting, which
3342
- * the kit does not have. Converting would silently lighten the text.
3343
- */
3344
- type TextOutlinesFailure = 'no-outlines' | 'outlines-loading' | 'outlines-failed' | 'synthetic-bold';
3345
- /** Thrown by {@link textToPath} when some run of the text has no outline
3346
- * geometry. Names the run's requested face. */
3347
- declare class TextOutlinesError extends Error {
3348
- readonly reason: TextOutlinesFailure;
3349
- readonly family: string;
3350
- readonly weight: number;
3351
- readonly style: FontStyle;
3352
- constructor(reason: TextOutlinesFailure, family: string, weight: number, style: FontStyle, detail: string);
3353
- }
3354
- /**
3355
- * The outline geometry of a text node, in world space: every glyph and every
3356
- * underline, strikethrough and overline rule it draws, as one `'nonzero'`
3357
- * compound path whose filled region is their union. Glyphs keep the font's
3358
- * curves; overlapping contours are not merged — run the result through
3359
- * `pathUnion` for that, at the cost of flattening the curves.
3360
- *
3361
- * `pose` is the node's box and rotation. Synthetic italic is sheared exactly
3362
- * as the renderer shears it. Throws {@link TextOutlinesError} when any run's
3363
- * face cannot supply outlines — never returns a partial or empty path for
3364
- * that. Text with no ink (empty, or only spaces) returns an empty path.
3365
- */
3366
- declare function textToPath(data: TextOutlineSource, pose: PathInWorldPose, opts?: TextToPathOptions): PolygonPath;
3367
- /**
3368
- * {@link textToPath}, split by paint: one path per distinct resolved fill and
3369
- * stroke, each gathering the glyphs and decoration rules that wear it — a
3370
- * rule takes the fill of the run it underlines. Groups come in the order
3371
- * their paint first appears in the text; a paint whose runs put down no ink
3372
- * has none. Together the paths cover exactly what `textToPath` covers.
3373
- *
3374
- * Paint resolves as the renderer resolves it: a run's `fill` / `stroke`, else
3375
- * the node's `data.fill` / `data.stroke` from `data`, else the default black
3376
- * fill and no stroke. Throws as `textToPath` does.
3377
- */
3378
- declare function textToPathsByPaint(data: TextOutlineSource, pose: PathInWorldPose, opts?: TextToPathOptions): TextPaintPath[];
3379
- /**
3380
- * Load the outlines of every face `data` is set in — the requested faces and
3381
- * the ones they resolve to — so a following {@link textToPath} does not fail
3382
- * with `'outlines-loading'`. Resolves once each has loaded or failed.
3383
- */
3384
- declare function loadTextOutlines(data: TextOutlineSource, opts?: TextToPathOptions): Promise<void>;
3385
-
3386
- /**
3387
- * Create Outlines: replace each selected text node with path nodes carrying
3388
- * its glyph outlines, giving up editability for geometry — the destructive
3389
- * conversion every vector editor has. One path per paint the text wears; one
3390
- * undoable batch; the replacement takes the text node's slot, so stacking
3391
- * order is unchanged.
3392
- */
3393
-
3394
- /** What a path minted by Create Outlines must carry — see
3395
- * {@link CreateOutlinesAdapter.createPathNode}. */
3396
- interface OutlinePathSpec {
3397
- /** The fill these glyphs wear, resolved: their run's, else the node's,
3398
- * else the default text fill. `null` is no fill. */
3399
- fill: FillStyle | null;
3400
- /** The stroke they wear, resolved the same way; absent for none. */
3401
- stroke?: Stroke;
3402
- /** The parent the path goes under: the text's own, or the container
3403
- * minted to hold a multi-paint text's paths. */
3404
- parent: string | null;
3405
- }
3406
- /** What `applyCreateOutlines` reads and writes through. */
3407
- interface CreateOutlinesAdapter {
3408
- getSelection(): readonly NodeId[] | readonly string[];
3409
- /** The text node behind `id`, or `undefined` for anything else. */
3410
- getTextSource(id: NodeId): TextNodeSource | undefined;
3411
- /**
3412
- * Mint one path node of text node `sourceId`'s outlines. `path` is in world
3413
- * space. The node must wear `spec`'s fill and stroke — a text whose runs
3414
- * differ in paint gets one path per paint — and sit under `spec.parent`.
3415
- * The kit places it; only the consumer knows its data shape.
3416
- */
3417
- createPathNode(path: PolygonPath, sourceId: NodeId, spec: OutlinePathSpec): {
3418
- id: string;
3419
- };
3420
- /**
3421
- * Mint the container that holds a multi-paint text's paths, under `parent`.
3422
- * `bounds` is the world envelope of the paths. The kit puts the container in
3423
- * the text's slot and the paths inside it, in the text's order. Without
3424
- * this, the paths are laid flat in that slot instead.
3425
- */
3426
- createContainerNode?(sourceId: NodeId, spec: {
3427
- parent: string | null;
3428
- bounds: Rect;
3429
- }): {
3430
- id: string;
3431
- };
3432
- /** Full node for `id`, so undo restores the text node whole. Without it the
3433
- * delete captures an `{ id }` stub. */
3434
- getNode?(id: string): {
3435
- id: string;
3436
- } | undefined | null;
3437
- /** Parent and sibling order — the reorder contract. With both, each path is
3438
- * inserted at its text node's index; without, where `insertNode` defaults. */
3439
- getParent?(id: string): string | null;
3440
- getChildren?(parentId: string | null): readonly string[];
3441
- applyOps?(ops: Op[], label?: string): void;
3442
- setSelection?(ids: NodeId[]): void;
3443
- insertNode?(node: {
3444
- id: string;
3445
- }, index?: number): void;
3446
- removeNode?(id: string): void;
3447
- }
3448
- /** Outcome of {@link applyCreateOutlines}. */
3449
- type CreateOutlinesResult =
3450
- /** `resultIds` are what replaced the text nodes at their level — a path,
3451
- * a container, or a run of flat paths each — which is also what is now
3452
- * selected in the text nodes' place. */
3453
- {
3454
- kind: 'applied';
3455
- resultIds: string[];
3456
- }
3457
- /** Nothing selected is text with ink. */
3458
- | {
3459
- kind: 'noop';
3460
- reason: 'no-text';
3461
- }
3462
- /** Some selected text has no outline geometry; nothing was changed. */
3463
- | {
3464
- kind: 'failed';
3465
- reason: 'text-outlines';
3466
- error: TextOutlinesError;
3467
- };
3468
- /**
3469
- * Convert every text node in the selection to outlines, as one batch labeled
3470
- * "Create Outlines". Other selected nodes are left alone and stay selected.
3471
- * All or nothing: if any text node's face cannot supply outlines the scene is
3472
- * untouched and the error comes back in the result.
3473
- */
3474
- declare function applyCreateOutlines(adapter: CreateOutlinesAdapter): CreateOutlinesResult;
3475
-
3476
- /** Boolean op identifiers — five Pathfinder primaries plus Crop. */
3477
- type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop';
3478
- /**
3479
- * z-position descriptor for a path node. `parentId` is the direct parent
3480
- * (or `null` for a top-level node); `index` is the position within that
3481
- * parent's child order — never a global render index.
3482
- */
3483
- /** @internal */
3484
- interface BooleanZOrder {
3485
- parentId: string | null;
3486
- index: number;
3487
- }
3488
- /** Adapter the hook and the pure core both consume. */
3489
- interface BooleansAdapter {
3490
- getSelection(): readonly NodeId[] | readonly string[];
3491
- getWorldPath(id: NodeId): Path | undefined;
3492
- /**
3493
- * Optional: the text node behind `id`, for an id `getWorldPath` has no path
3494
- * for. Its glyph outlines (`textToPath`) become the operand, so text can be
3495
- * cut, united or intersected like any shape.
3496
- */
3497
- getTextSource?(id: NodeId): TextNodeSource | undefined;
3498
- compareZ(a: NodeId, b: NodeId): number;
3499
- /**
3500
- * Mint a new node from a boolean-op result `Path`. `producedBy` names the
3501
- * op that synthesized it — adapters that store provenance (e.g. for a
3502
- * layer-panel icon) record it; others ignore the arg. `sourceId` is the
3503
- * topmost source, whose slot the result takes: give the node that source's
3504
- * parent, or the kit's reorder cannot find it there.
3505
- */
3506
- createPathNode(path: Path, producedBy: BooleanOp, sourceId: NodeId): {
3507
- id: string;
3508
- };
3509
- /**
3510
- * Optional: return the full object for an id, used by the delete ops so
3511
- * their `invert` (an insert) can restore the complete object on undo.
3512
- * If omitted, a `{ id }` stub is captured — undo will reinstate the id
3513
- * but consumers reading other fields (path, fill, etc.) will see them as
3514
- * undefined. Mirrors `DeleteAdapter.getNode`; should be provided whenever
3515
- * undo over boolean ops is expected to be lossless.
3516
- */
3517
- getNode?(id: NodeId): {
3518
- id: string;
3519
- } | undefined | null;
3520
- /**
3521
- * Optional: the reorder contract. With `getChildren` (and `getParent`, or
3522
- * every node reads as top-level) the kit reads each source's slot among its
3523
- * siblings, and with `setChildOrder` as well it moves the result into the
3524
- * topmost source's slot. Without them the result lands wherever
3525
- * `insertNode` defaults to. `defaultCommitAdapter` supplies all three.
3526
- */
3527
- getParent?(id: string): string | null;
3528
- getChildren?(parentId: string | null): readonly string[];
3529
- setChildOrder?(parentId: string | null, ids: string[]): void;
3530
- /**
3531
- * Optional override for the slot the kit otherwise reads through
3532
- * `getParent` / `getChildren`: `id`'s parent and its index in that
3533
- * parent's child order.
3534
- *
3535
- * @deprecated Supply `getParent` and `getChildren` instead, and the kit
3536
- * derives this.
3537
- */
3538
- getZOrder?(id: NodeId): BooleanZOrder | undefined;
3539
- applyOps?(ops: Op[], label?: string): void;
3540
- setSelection?(ids: NodeId[]): void;
3541
- insertNode?(node: {
3542
- id: string;
3543
- }): void;
3544
- removeNode?(id: string): void;
3545
- }
3546
- /** Outcome reported back to callers (lets the hook surface no-op signals). */
3547
- type BooleanOpResult = {
3548
- kind: 'applied';
3549
- resultIds: string[];
3550
- } | {
3551
- kind: 'noop';
3552
- reason: 'no-paths' | 'too-few-for-subtract' | 'empty-result';
3553
- }
3554
- /** A text operand had no outline geometry. Nothing was changed: an op that
3555
- * quietly dropped the operand would commit a different shape than asked. */
3556
- | {
3557
- kind: 'failed';
3558
- reason: 'text-outlines';
3559
- error: TextOutlinesError;
3560
- };
3561
- /**
3562
- * Run one Boolean operation over the selected paths and commit the result as a
3563
- * single undoable batch.
3564
- *
3565
- * Operands are ordered back-to-front, which is what makes `subtract` mean
3566
- * "everything in front removed from the backmost shape". Returns without
3567
- * mutating anything when the selection holds no paths, or too few for the
3568
- * requested operation.
3569
- */
3570
- declare function applyBooleanOp(adapter: BooleansAdapter, op: BooleanOp): BooleanOpResult;
3571
-
3572
- /** Context handed to every content handler for one ingest event. */
3573
- interface IngestCtx {
3574
- /** World-space arrival point (drop / pointed imperative ingest); `null`
3575
- * for paste and point-less calls — handlers pick their own policy
3576
- * (the kit image handler centers on the viewport). */
3577
- point: {
3578
- x: number;
3579
- y: number;
3580
- } | null;
3581
- /** Visible canvas area in world coordinates. */
3582
- viewportWorldRect(): {
3583
- x: number;
3584
- y: number;
3585
- width: number;
3586
- height: number;
3587
- };
3588
- /** The kit insert dep — id/layer/undoable-op supplied; the canonical way
3589
- * for a handler to mint a node (`insert.commit(bounds, { kind, ... })`). */
3590
- insert: InsertDep;
3591
- /** Raw op commit for handlers that build their own ops. */
3592
- applyOps(ops: Op[], label?: string): void;
3593
- scene: Scene<unknown, string, unknown>;
3594
- selection: SelectionApi;
3595
- /** Consumer file→src resolver (SceneCanvas `ingestion.resolveSrc`).
3596
- * When absent, the kit image handler embeds as a `data:` URI. */
3597
- resolveSrc?: (file: File) => Promise<string>;
3598
- /** Kit SVG-handler options (SceneCanvas `ingestion.svg`) — e.g.
3599
- * `{ unpack: unpackSvgFiles }` (from `@weasel-js/svg`) to parse SVG files
3600
- * into scene nodes. */
3601
- svg?: SvgIngestOptions;
3602
- /** Clipboard-paste seam — present when the hosting `SceneCanvas` supplied
3603
- * an adapter with `commitPaste`. `reviver` comes from
3604
- * `SceneCanvasProps.ingestion.clipboard`. Absent ⇒ the kit weasel-JSON
3605
- * handler declines inert (dwarn, nothing ingested) — its matched items
3606
- * were already consumed at match time, so they do NOT fall through;
3607
- * only match-level misses flow on to other handlers. */
3608
- clipboard?: ClipboardIngestCtx;
3609
- /** Set to `true` by the kit weasel-JSON handler when it successfully
3610
- * pastes a payload in this event. The `ctx` object is shared across all
3611
- * handlers in one `runIngest` call, and higher-priority handlers' `handle`
3612
- * bodies run (synchronously) before lower ones — so `kit:svg`'s
3613
- * `text/plain` SVG fallback reads this to decline the SVG flavor of a copy
3614
- * whose canonical weasel-JSON flavor already ingested (avoids a
3615
- * double-paste when both flavors ride one clipboard event). */
3616
- consumedWeaselPayload?: boolean;
3617
- /** Full action-deps bag, for consumer handlers that need more. */
3618
- deps: ActionDeps;
3619
- }
3620
- /** A handler for content arriving by paste, drop or file picker. Handlers are
3621
- * matched by MIME glob or predicate and run highest-priority first; the kit's
3622
- * own register at a low priority so a consumer's handler wins by default. */
3623
- interface ContentHandlerEntry {
3624
- /** Stable identifier — used for unregistration and debugging
3625
- * (`'kit:image'`, `'app:csv'`). */
3626
- id: string;
3627
- /** MIME glob(s) (`'image/*'`, `'text/csv'`) or an item predicate. */
3628
- match: string | string[] | ((item: IngestItem) => boolean);
3629
- /** Higher runs earlier. Kit defaults register at -100 so any consumer
3630
- * handler (default 0) beats them. */
3631
- priority?: number;
3632
- handle(items: IngestItem[], ctx: IngestCtx): void | Promise<void>;
3633
- }
3634
- /** Register a content handler. Returns a disposer that removes it. */
3635
- declare function registerContentHandler(entry: ContentHandlerEntry): () => void;
3636
-
3637
- /**
3638
- * The lifecycle the ongoing transform actions (`move`, `resize`, `rotate`)
3639
- * share: what their committed batch is called in history, whether it is
3640
- * recorded at all, and the callbacks fired as the gesture opens and closes.
3641
- *
3642
- * Move and rotate read it from their binding's params; resize reads it from
3643
- * the `resizePolicy` dep, where the rest of its options already live.
3644
- */
3645
-
3646
- /** The lifecycle options, as a binding's params or the resize policy carry
3647
- * them. */
3648
- interface GestureLifecycleOptions {
3649
- /** History label for the committed batch. Unset, the batch takes its first
3650
- * op's label (a layout drop or a behavior names its own), else the
3651
- * action's. */
3652
- label?: string;
3653
- /** Commit through `scene.untracked`: the edit lands, but no undo entry
3654
- * records it and the consumer `applyOps` hook is bypassed. Unset, any
3655
- * behavior's `defaultTransient: true` turns it on. */
3656
- transient?: boolean;
3657
- /** Fired once the gesture starts changing poses, with the ids it moves. */
3658
- onGestureStart?(ids: string[]): void;
3659
- /** Fired once per `onGestureStart`: `true` when the gesture wrote to the
3660
- * document, `false` on cancel, abort, or a release with nothing to write. */
3661
- onGestureEnd?(committed: boolean): void;
3662
- }
3663
-
3664
- /** Optional consumer seam: given a node and the affine `m` that a pose-transform
3665
- * action applied to the node's POSE, return updated `data` with the node's
3666
- * data-held geometry transformed by `m`, or `null` if this node has no
3667
- * data-held geometry (the kit leaves `data` alone). */
3668
- interface GeometryProjection {
3669
- transform(node: {
3670
- id?: string;
3671
- data: unknown;
3672
- pose: unknown;
3673
- }, m: Mat3): unknown | null;
3674
- }
3675
-
3676
- /** Minimal view API the action layer consumes. */
3677
- interface ViewApi {
3678
- get(): View;
3679
- set(v: View): void;
3680
- /** How this camera's world maps into the world the invocation edits in,
3681
- * when that is a parallax plane — set by `inPlane` on the view it hands a
3682
- * wrapped action. Absent or null: the camera's own world. */
3683
- plane?(): PlaneMap | null;
3684
- /** How the world the invocation edits in maps into the world `layer`'s
3685
- * nodes are stored in, read now — set by `inPlane` alongside `plane`.
3686
- * Absent or null: the same world. */
3687
- planeOf?(layer: string): PlaneMap | null;
3688
- /** Optional recenter callback. When wired, `viewportZoomAction`'s `reset`
3689
- * branch (Cmd-0) calls this instead of resetting to identity — letting
3690
- * consumers re-fit the page (or other reference bounds) into the workspace.
3691
- * Return the target `View` to let the action animate there; return nothing
3692
- * to keep dispatching the view yourself. */
3693
- recenter?(): View | void;
3694
- /** Optional canvas-local host dimensions (CSS px). When wired,
3695
- * `viewportZoomAction`'s keyboard branches (Cmd+= / Cmd+-) anchor at the
3696
- * host center instead of the top-left origin. Null when the host isn't
3697
- * measurable (unmounted). */
3698
- hostSize?(): {
3699
- width: number;
3700
- height: number;
3701
- } | null;
3702
- /** Optional camera animation. `<SceneCanvas>` wires these three; a consumer
3703
- * publishing their own `view` dep need not, and actions fall back to `set`. */
3704
- animate?(to: View, opts?: ViewAnimationOptions): void;
3705
- stopAnimation?(): void;
3706
- /** Where an in-flight camera animation is heading, or null. Compute the next
3707
- * discrete step from this so repeated presses compound. */
3708
- animationTarget?(): View | null;
3709
- /** Optional momentum decay. `<SceneCanvas>` wires this from `useDecayLoop`;
3710
- * a consumer publishing their own `view` dep need not, and `viewport.dragPan`
3711
- * simply lands the pan without coasting. */
3712
- decay?(config: DecayLoopConfig): void;
3713
- stopDecay?(): void;
3714
- /** Whether a scene layer reaches the screen in this view — its own
3715
- * `layerVisibility` / `layerOrder`, on top of the scene's `visible` flag.
3716
- * Absent: this view paints every layer the scene shows. Selecting actions
3717
- * pass over what the asking view does not paint. */
3718
- layerIsPainted?(layerId: string): boolean;
3719
- }
3720
- /** The part of the asking view a region hit-test consults. `get` is its
3721
- * camera, which a parallax layer is picked through. */
3722
- type HitTestView = RegionPickView;
3723
- /**
3724
- * Adapter dep for `areaSelectAction`.
3725
- *
3726
- * Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>` via AABB
3727
- * overlap over scene nodes. Consumers with custom hit-testing override this
3728
- * dep entry in their own registrar.
3729
- */
3730
- /**
3731
- * Topmost-node-at-world-point dep, consumed by `moveAction` for
3732
- * reparent-on-drop and available to any action that needs a single-best
3733
- * pick. Mirrors the same hit-test plumbing `<SceneCanvas>` feeds to the
3734
- * tool dispatcher; consumers with custom hit-testing override here.
3735
- *
3736
- * `exclude` is iterated once per call and treated as a set membership
3737
- * test — the dep walks hits front-to-back and returns the first id not
3738
- * in the exclude set. Pass moving-node roots + their descendants when
3739
- * the caller wants to ignore the nodes it's manipulating.
3740
- */
3741
- type NodeAtPointDep = (point: {
3742
- x: number;
3743
- y: number;
3744
- }, exclude?: Iterable<NodeId>) => NodeId | null;
3745
- /** What an area-selecting action needs: a way to ask what a region covers,
3746
- * and a way to read and replace the selection. */
3747
- interface AreaSelectDep {
3748
- /** Return ids of all scene nodes whose AABB overlaps `bounds`, skipping any
3749
- * on a layer `view` — the view the gesture ran in — does not paint. */
3750
- hitTestArea(bounds: {
3751
- x: number;
3752
- y: number;
3753
- width: number;
3754
- height: number;
3755
- }, view?: HitTestView): NodeId[];
3756
- /** Return the current selection id list. */
3757
- getSelection(): NodeId[];
3758
- /** Replace the current selection. */
3759
- setSelection(ids: NodeId[]): void;
3760
- }
3761
- /**
3762
- * Adapter dep for `editAnchorsAction`.
3763
- *
3764
- * Provides narrow read/write access to the editable polygon for a single
3765
- * node. Consumers register this dep so anchor-edit actions can read/write
3766
- * the polygon WITHOUT knowing whether it lives directly on the node's
3767
- * pose (`pose.kind === 'polygon'`) or on `node.data.path` (the kit's
3768
- * built-in pen-tool default, also WeaselDraw's shape).
3769
- *
3770
- * Note on live previews: in-flight edit state is surfaced through the
3771
- * dispatcher's standard `OngoingHandle.previewIds/previewPose/previewData`
3772
- * triple (not this dep), so chrome and preview-ghost stay in lock-step
3773
- * via one source of truth.
3774
- */
3775
- interface EditAnchorsDep {
3776
- /** Id of the node currently being edited. Empty string means no node is
3777
- * currently in edit mode — the chrome and gesture both opt out. */
3778
- editingId: string;
3779
- /** Enter/exit edit mode for a specific node. Pass `null` (or an empty
3780
- * string) to exit. `enterPathEditAction` and `exitPathEditAction` call
3781
- * this; consumers can call it directly to drive edit mode programmatically. */
3782
- setEditingId(id: string | null): void;
3783
- /** Returns the COMMITTED editable polygon in world coordinates, or
3784
- * null if this node has no editable polygon. Does NOT consult in-
3785
- * flight previews — callers that need live state read the dispatcher's
3786
- * in-flight handles. */
3787
- getEditablePath(id: string): unknown;
3788
- /** Returns where the polygon is stored — `'pose'` when `node.pose`
3789
- * IS the polygon, `'data'` when it lives on `node.data.path` with a
3790
- * rect pose, or `null` when the node has no editable polygon. The
3791
- * action uses this to know which preview-ghost axis to populate
3792
- * (`previewPose` only / `previewData` + `previewPose` for data.path). */
3793
- getStorageKind(id: string): 'pose' | 'data' | null;
3794
- /** Returns the node's raw `pose` and `data` so storage-aware actions
3795
- * can capture origin state at gesture-start and synthesize a matching
3796
- * `previewPose` / `previewData` during `onMove`. Used by
3797
- * `editAnchorsAction` for the data.path branch (rect pose + data
3798
- * carrying extra fields like fill / stroke that must be preserved
3799
- * through the preview). Returns null when the node is gone. */
3800
- getNodeShape(id: string): {
3801
- pose: unknown;
3802
- data: unknown;
3803
- } | null;
3804
- /** Commit `worldPath` as the new value for `id`. Implementation routes
3805
- * to setPose (when pose IS the polygon) or batched setPose+update
3806
- * (when the polygon lives on data.path). Records one history entry
3807
- * labelled `label`. */
3808
- applyEdit(id: string, worldPath: unknown, label: string): void;
3809
- /** The ops `applyEdit` would commit for the same arguments, unapplied, so
3810
- * a caller can make the edit part of a larger batch — the pen joining
3811
- * two paths edits one node and deletes the other in one undo step.
3812
- * Optional; a caller that needs it declines the composite edit without
3813
- * it. Returns `[]` when `id` has no editable polygon. */
3814
- editOps?(id: string, worldPath: unknown, label: string): Op[];
3815
- /**
3816
- * Anchors currently selected within the edited path, as **flat anchor
3817
- * indices** — the same numbering `enumerateAnchors` produces and the
3818
- * `anchor:N` affordance kinds carry.
3819
- *
3820
- * Selection is transient UI state, deliberately not part of the scene:
3821
- * it is cleared whenever `editingId` changes, and any edit that
3822
- * renumbers anchors (insert, delete) is responsible for leaving it
3823
- * coherent. Empty means "no anchor selected" — the keyboard actions
3824
- * (nudge, delete) no-op rather than acting on all anchors, matching
3825
- * Illustrator.
3826
- */
3827
- selectedAnchors: ReadonlySet<number>;
3828
- /** Replace the anchor selection. Pass an empty iterable to clear. */
3829
- setSelectedAnchors(next: Iterable<number>): void;
3830
- /**
3831
- * In-flight anchor-marquee rect in world coords, or null when no
3832
- * marquee drag is active. Written by `marqueeAnchorsAction` and read by
3833
- * the path-editing overlay — the same "ongoing action owns the preview,
3834
- * chrome just draws it" split the move/resize ghosts use.
3835
- */
3836
- marquee: {
3837
- x: number;
3838
- y: number;
3839
- width: number;
3840
- height: number;
3841
- } | null;
3842
- /** Set or clear the in-flight marquee rect. */
3843
- setMarquee(rect: {
3844
- x: number;
3845
- y: number;
3846
- width: number;
3847
- height: number;
3848
- } | null): void;
3849
- }
3850
- /**
3851
- * Adapter dep for `lassoSelectAction`.
3852
- *
3853
- * Provides polygon-lasso hit-testing + selection read/write.
3854
- * Consumers that don't implement `hitTestLasso` can omit it; the action
3855
- * falls back to a bounding-box AABB test via `hitTestArea`.
3856
- */
3857
- interface LassoSelectDep {
3858
- /**
3859
- * Hit-test against a closed polygon (vertex order CW or CCW; last→first
3860
- * closing edge is implicit). Returns matching node ids.
3861
- * Optional — when absent, `lassoSelectAction` falls back to AABB via
3862
- * `hitTestArea`.
3863
- */
3864
- hitTestLasso?(polygon: ReadonlyArray<{
3865
- x: number;
3866
- y: number;
3867
- }>, mode: 'centers' | 'intersect' | 'enclosed', view?: HitTestView): string[];
3868
- /** Return ids of nodes whose AABB overlaps the given rect (fallback). */
3869
- hitTestArea(bounds: {
3870
- x: number;
3871
- y: number;
3872
- width: number;
3873
- height: number;
3874
- }, view?: HitTestView): string[];
3875
- /** Return the current selection id list. */
3876
- getSelection(): string[];
3877
- /** Replace the current selection. */
3878
- setSelection(ids: string[]): void;
3879
- }
3880
- /**
3881
- * Options for the kit `image/svg+xml` content handler, threaded from
3882
- * SceneCanvas's `ingestion={{ svg }}` prop.
3883
- */
3884
- interface SvgIngestOptions {
3885
- /** Parse dropped/pasted/picked SVG files into native scene nodes (path /
3886
- * text leaves under containers mirroring the source `<g>` structure)
3887
- * instead of the default single embedded-image node.
3888
- *
3889
- * Pass `unpackSvgFiles` from `@weasel-js/svg`:
3890
- *
3891
- * ```ts
3892
- * import { unpackSvgFiles } from '@weasel-js/svg';
3893
- * <SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }} />
3894
- * ```
3895
- *
3896
- * It is injected rather than flagged on with `true` because the SVG parser
3897
- * lives in `@weasel-js/svg`, which depends on this package — core importing
3898
- * it back would make the two mutually dependent and unpublishable
3899
- * separately. Passing the function keeps the parser out of core's bundle
3900
- * for consumers who never unpack. */
3901
- unpack?: SvgUnpacker;
3902
- }
3903
- /** Parses SVG files and inserts the resulting nodes into `ctx.scene`, as one
3904
- * `applyOps` batch per file. Implemented by `unpackSvgFiles` in
3905
- * `@weasel-js/svg`; see {@link SvgIngestOptions.unpack}. */
3906
- type SvgUnpacker = (files: File[], ctx: IngestCtx) => Promise<void>;
3907
- /**
3908
- * Clipboard-paste seam consumed by the kit weasel-JSON content handler
3909
- * (`IngestCtx.clipboard`). Built by `<SceneCanvas>` from its own synthesized
3910
- * adapter + the `ingestion.clipboard` prop; absent when the consumer set
3911
- * `ingestion.clipboard.enabled === false` or the adapter lacks `commitPaste`.
3912
- * Absence makes the handler decline inert (dwarn, nothing ingested) — its
3913
- * matched items were already consumed at match time and do not fall through
3914
- * to other handlers.
3915
- */
3916
- interface ClipboardIngestCtx {
3917
- /** The hosting canvas's adapter — `commitPaste` materializes the pasted
3918
- * nodes (fresh ids, offset applied); insertion still goes through ops. */
3919
- adapter: InsertAdapter<{
3920
- id: string;
3921
- }>;
3922
- /** JSON reviver for the weasel wire payload (typed arrays etc.) — from
3923
- * `SceneCanvasProps.ingestion.clipboard.reviver`. */
3924
- reviver?: (key: string, value: unknown) => unknown;
3925
- }
3926
- /**
3927
- * Dep for the `ingest` action (external-content ingestion).
3928
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
3929
- * `useIngestionDepSource` — canvas rect + current view.
3930
- */
3931
- interface IngestionDep {
3932
- /** Visible canvas area in world coordinates. */
3933
- viewportWorldRect(): {
3934
- x: number;
3935
- y: number;
3936
- width: number;
3937
- height: number;
3938
- };
3939
- /** Consumer file→src resolver (from SceneCanvas's `ingestion` prop).
3940
- * Live accessor — read it at use time. Destructuring (or copying the
3941
- * property early) snapshots the current value and won't track later
3942
- * prop changes across an `await`. */
3943
- resolveSrc?: (file: File) => Promise<string>;
3944
- /** Kit SVG-handler options (from SceneCanvas's `ingestion` prop).
3945
- * Live accessor, same caveat as `resolveSrc`. */
3946
- svg?: SvgIngestOptions;
3947
- /** Clipboard-paste seam for the kit weasel-JSON handler.
3948
- * Live accessor, same caveat as `resolveSrc`. */
3949
- clipboard?: ClipboardIngestCtx;
3950
- }
3951
- /**
3952
- * Per-kind extra geometry passed to `InsertDep.commit`.
3953
- *
3954
- * Built-in tools populate a typed variant so the kit's default factory can
3955
- * render the true tool params (line endpoints, polygon side count, star
3956
- * geometry, pencil sample list). Consumer-defined tools may pass any
3957
- * `{ kind: string; ... }` payload; the kit's factory falls back to AABB
3958
- * inscription for unknown kinds.
3959
- *
3960
- * `bounds` is still passed alongside as a useful AABB pose hint — factories
3961
- * may use it as the node's pose even when richer geometry is available.
3962
- */
3963
- type InsertExtras = {
3964
- kind: 'rect';
3965
- } | {
3966
- kind: 'ellipse';
3967
- } | {
3968
- kind: 'line';
3969
- a: {
3970
- x: number;
3971
- y: number;
3972
- };
3973
- b: {
3974
- x: number;
3975
- y: number;
3976
- };
3977
- } | {
3978
- kind: 'polygon';
3979
- sides: number;
3980
- rotation: number;
3981
- center?: {
3982
- x: number;
3983
- y: number;
3984
- };
3985
- radius?: number;
3986
- } | {
3987
- kind: 'star';
3988
- points: number;
3989
- innerRadiusRatio: number;
3990
- rotation: number;
3991
- center?: {
3992
- x: number;
3993
- y: number;
3994
- };
3995
- outerRadius?: number;
3996
- } | {
3997
- kind: 'pencil';
3998
- samples: ReadonlyArray<DragSample>;
3999
- } | {
4000
- kind: 'text';
4001
- text?: string;
4002
- } | {
4003
- kind: 'image';
4004
- src?: string;
4005
- opacity?: number;
4006
- /** Chrome-only: what the in-flight drag paints. Read by the overlay
4007
- * layer, ignored by the insert dep. */
4008
- preview?: 'bitmap' | 'outline';
4009
- } | {
4010
- kind: string;
4011
- [extra: string]: unknown;
4012
- };
4013
- /**
4014
- * World-space point snapping — grid, guides, or any consumer rule.
4015
- *
4016
- * Sourced by `<SceneCanvas>` from its `toolOptions.snapPoint`. Actions apply
4017
- * it to the coords they ingest so the live preview and the committed
4018
- * geometry agree; `insertAction` snaps the drag's start and current point.
4019
- *
4020
- * Optional: when the dep is absent, actions treat it as identity.
4021
- */
4022
- interface SnapDep {
4023
- /** Snap a world-space point. Return `p` unchanged to opt out. */
4024
- point(p: {
4025
- x: number;
4026
- y: number;
4027
- }): {
4028
- x: number;
4029
- y: number;
4030
- };
4031
- }
4032
- /**
4033
- * Adapter dep for `insertAction`.
4034
- *
4035
- * Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>`. The `extras`
4036
- * carry the active tool's kind + per-kind geometry. Callers
4037
- * that need typed data must supply a richer `insert` dep.
4038
- */
4039
- interface InsertDep {
4040
- /**
4041
- * Materialise a new node from the given drag-rect bounds and typed
4042
- * per-kind extras. Returns the new node's id, or `null` if the consumer
4043
- * rejected the insert (e.g. sub-threshold bounds, unknown kind).
4044
- */
4045
- commit(bounds: {
4046
- x: number;
4047
- y: number;
4048
- width: number;
4049
- height: number;
4050
- }, extras: InsertExtras): NodeId | null;
4051
- /** The scene layer `commit` puts the node on. An insert on a layer carrying
4052
- * `parallax` is dragged out in that plane's world, so `bounds` arrive
4053
- * there too. Omitted: the camera's world. */
4054
- layer?(): string;
4055
- }
4056
- /**
4057
- * Adapter dep for `resizeAction`.
4058
- *
4059
- * Carries the behavior-shaping options the legacy `useResize` hook
4060
- * exposed through `UseResizeOptions`: bounds-frame behaviors (e.g.
4061
- * `lockAspectWithModifier`), world-space anchor-point snap behaviors (e.g.
4062
- * `@weasel-js/guides`' `pointSnapToGrid`), and group-expansion (`expandIds`).
4063
- *
4064
- * Optional in `DepSchema`: when absent, `resizeAction` falls back to
4065
- * identity defaults (no behaviors, identity expandIds). Consumers wire the
4066
- * dep via `useDepSource('resizePolicy', ...)` from any descendant of
4067
- * `<DepRegistryProvider>` / `<SceneCanvas>`.
4068
- *
4069
- * The generic is erased to `unknown` at the schema entry; consumers cast at
4070
- * the call site (mirrors the `scene` entry's convention).
4071
- */
4072
- interface ResizePolicy<TPose> extends GestureLifecycleOptions {
4073
- /** Bounds-frame constraints. Constrained to `TPose extends Bounds` since
4074
- * constraints read/write `{x,y,width,height}`. For non-rect TPose pass `[]`. */
4075
- constraints: TPose extends Bounds ? BoundsConstraint<TPose>[] : never[];
4076
- /** World-space anchor-point snap behaviors. Same TPose constraint as
4077
- * `constraints`. */
4078
- pointSnap: TPose extends Bounds ? PointSnapBehavior<TPose>[] : never[];
4079
- /** Group-expansion at gesture start. Identity (`ids => ids`) when group
4080
- * resize isn't wanted. */
4081
- expandIds: (ids: string[]) => string[];
4082
- }
4083
- /**
4084
- * Layout-strategy lookup by container id, consumed by `moveAction` to run
4085
- * the drag-time reflow pass. Sourced by `<SceneCanvas>` from its `layouts`
4086
- * prop. Optional: `getLayout` returns null for any container when no layout
4087
- * is configured, so the reflow pass is a no-op then.
4088
- */
4089
- interface LayoutDep {
4090
- getLayout(containerId: string): LayoutStrategy<unknown> | null;
4091
- /** Which container wins when several contain the drop point. Default
4092
- * `'innermost'`. */
4093
- dropTarget?: LayoutDropTargetMode;
4094
- /** Carries displaced siblings to their slots over time. Absent, they snap. */
4095
- reflow?: ReflowTransition<unknown> | null;
4096
- }
4097
- /**
4098
- * Dep for `enterTextEditAction`.
4099
- *
4100
- * Wrap the return value of `useTextEdit` / `useSceneTextEdit` to source this
4101
- * dep. The `isTextNode` predicate is optional — when absent the action fires
4102
- * unconditionally (the binding spec acts as the gate).
4103
- *
4104
- * @example
4105
- * ```ts
4106
- * const textEdit = useSceneTextEdit({ scene, container });
4107
- * useDepSource('textEdit', () => ({
4108
- * startEdit: textEdit.startEdit,
4109
- * isTextNode: (id) => scene.get(id as NodeId)?.data?.kind === 'text',
4110
- * }));
4111
- * ```
4112
- */
4113
- interface TextEditDep {
4114
- /**
4115
- * Begin editing the node with `id`. Activates the contenteditable overlay
4116
- * managed by `useTextEdit` / `useSceneTextEdit`.
4117
- */
4118
- startEdit(id: string, opts?: {
4119
- caret?: number | 'all';
4120
- }): void;
4121
- /**
4122
- * Optional predicate: returns `true` when the node with `id` is a text node.
4123
- * When absent the action fires on any selected node (binding spec is the gate).
4124
- * When present and returning `false`, the invocation is a no-op.
4125
- */
4126
- isTextNode?(id: string): boolean;
4127
- }
4128
- /**
4129
- * Clipboard dep — the imperative surface `useClipboardOps` returns.
4130
- *
4131
- * Consumers publish their live clipboard through `useDepSource('clipboard',
4132
- * …)` from inside the `<DepRegistryProvider>` (i.e. under `<SceneCanvas>`).
4133
- * The kit deliberately does not build one for them: `useClipboardOps` needs
4134
- * an adapter and a selection reader that only the consumer can supply.
4135
- */
4136
- interface ClipboardDep {
4137
- copy(): void;
4138
- paste(): void;
4139
- isEmpty(): boolean;
4140
- }
4141
- /**
4142
- * The commit behind the Slice action. `commit` receives the cut as a polyline
4143
- * of at least two points (world coords): the drag's two ends for a straight
4144
- * cut, its whole trail for a freehand one, the placed points for a
4145
- * click-by-click one, whose last point equals its first when it closes into
4146
- * a loop. `<SceneCanvas>` publishes one over its scene (`useSliceDepSource`);
4147
- * a consumer replaces it with `useSliceDep`.
4148
- */
4149
- interface SliceDep {
4150
- commit(cut: ReadonlyArray<Point2>): void;
4151
- }
4152
- /**
4153
- * The names an action may declare in `requires`, and what each resolves to.
4154
- *
4155
- * This is the whole vocabulary of things an action can reach — selection,
4156
- * scene, view, history, and the rest. The interface itself is declared empty
4157
- * in `@weasel-js/routing`, which knows that an action names its dependencies
4158
- * but not what any of them are; these entries are the kit's, merged in from
4159
- * outside exactly the way a consumer merges its own
4160
- * (`declare module '@weasel-js/core'`).
4161
- */
4162
- declare module '@weasel-js/routing' {
4163
- interface DepSchema {
4164
- /** Kit selection state — ids of currently selected nodes. */
4165
- selection: SelectionApi;
4166
- /** Current viewport — camera position + scale. */
4167
- view: ViewApi;
4168
- /**
4169
- * Scene tree — structural reads + undoable mutations.
4170
- *
4171
- * The entry uses the fully-erased form `Scene<unknown, string, unknown>`
4172
- * because `DepSchema` must be concrete; any `Scene<MyData, MyLayer,
4173
- * MyPose>` is assignable to it. Actions that need a typed scene back
4174
- * should cast: `deps.scene as Scene<MyData, MyLayer, MyPose>`.
4175
- */
4176
- scene: Scene<unknown, string, unknown>;
4177
- /** Undo/redo history bound to the current scene. */
4178
- history: History;
4179
- /** Currently active tool id + hotkey-hold stack. */
4180
- activeTool: ActiveToolContextValue;
4181
- /**
4182
- * Area-select dep — AABB hit-test + selection read/write.
4183
- *
4184
- * Sourced from `<SceneCanvas>` via AABB overlap over all scene
4185
- * nodes. Override per-consumer for custom hit-testing (e.g. contain-mode,
4186
- * lock-aware filtering).
4187
- */
4188
- areaSelect: AreaSelectDep;
4189
- /**
4190
- * Topmost node at a world-space point. Sourced by `<SceneCanvas>` from
4191
- * the same picker that feeds the tool dispatcher's `getNodeAtPoint`.
4192
- * Optional: actions that read this (e.g. `moveAction` reparent-on-drop)
4193
- * fall back to a no-op when the dep isn't registered.
4194
- */
4195
- nodeAtPoint?: NodeAtPointDep;
4196
- /**
4197
- * Insert dep — node factory for drag-to-insert.
4198
- *
4199
- * Sourced from `<SceneCanvas>`. The `kind` param comes from
4200
- * the active binding's `opts.params.kind`. Override per-consumer to
4201
- * provide a typed node factory (e.g. with custom data payloads).
4202
- */
4203
- insert: InsertDep;
4204
- /**
4205
- * Snap dep — world-space point snapping (grid / guides).
4206
- *
4207
- * Sourced by `<SceneCanvas>` from `toolOptions.snapPoint`. Optional:
4208
- * absent means no snapping (identity).
4209
- */
4210
- snap?: SnapDep;
4211
- /**
4212
- * The canvas's debug sink — the one tools reach as `ToolCtx.debug`.
4213
- * Sourced by `<SceneCanvas>`; absent while its `debug` overlay is off.
4214
- * Actions report what they did here, and the overlay draws it.
4215
- */
4216
- debug?: DebugSink;
4217
- /**
4218
- * Lasso-select dep — polygon hit-test + selection read/write.
4219
- *
4220
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>`.
4221
- * Falls back to AABB hit-test when `hitTestLasso` is absent.
4222
- */
4223
- lassoSelect: LassoSelectDep;
4224
- /**
4225
- * Edit-anchors dep — narrow read/write of one polygon's path pose.
4226
- *
4227
- * Sourced from consumer. Wraps `getPose`/`setPose`/`applyOps`
4228
- * for the currently-being-edited polygon node.
4229
- *
4230
- * The `editAnchorsAction` requires this dep to be registered when anchor
4231
- * editing is active. If absent, `start` returns an empty handle (no-op).
4232
- */
4233
- editAnchors: EditAnchorsDep;
4234
- /**
4235
- * Text-edit dep — activates the in-place text editing overlay.
4236
- *
4237
- * Sourced from consumer via `useTextEdit` / `useSceneTextEdit`.
4238
- * The `enterTextEditAction` requires this dep to be registered by the text
4239
- * tool when text editing is available.
4240
- *
4241
- * The optional `isTextNode` predicate guards against entering edit mode on
4242
- * non-text nodes. A binding can pre-filter instead with a
4243
- * `target: 'kind:text:selected'` spec; the guard remains for consumers who
4244
- * bind the broader `'selected-body'` target or opted out of routing.
4245
- */
4246
- textEdit: TextEditDep;
4247
- /**
4248
- * Resize-policy dep — bounds constraints, point-snap behaviors and
4249
- * group expansion for `resizeAction`.
4250
- *
4251
- * Optional: when omitted, `resizeAction` falls back to identity defaults
4252
- * (no constraints, no snap, identity expandIds).
4253
- * Consumers wire via `useDepSource('resizePolicy', ...)` or the
4254
- * `useResizePolicy` helper.
4255
- */
4256
- resizePolicy?: ResizePolicy<unknown>;
4257
- /**
4258
- * How to read and rewrite a pose — bounds, translate, remap, rotation. Every
4259
- * built-in action that touches a pose reads it. Sourced by `<SceneCanvas>`
4260
- * from its `poseDescriptor` prop; `AUTO_POSE_DESCRIPTOR` when absent.
4261
- */
4262
- poseDescriptor?: PoseDescriptor<unknown>;
4263
- /**
4264
- * Booleans adapter — read selection ids, fetch world-space `Path`s,
4265
- * compare z-order, and mint result nodes for Pathfinder ops.
4266
- *
4267
- * Consumers wire via `useBooleansAdapter(adapter)` (a thin wrapper
4268
- * around `useDepSource('booleansAdapter', ...)`). The descriptor's
4269
- * `enabled` predicate reads `deps.selection` for the count check; the
4270
- * invoker reads `deps.booleansAdapter` to execute the op.
4271
- */
4272
- booleansAdapter?: BooleansAdapter;
4273
- /**
4274
- * Create Outlines adapter — read selection ids, resolve text nodes, and
4275
- * mint the path nodes that replace them. Consumers wire it with
4276
- * `useCreateOutlinesAdapter(adapter)`; the `createOutlines` action reads it
4277
- * in both its `enabled` predicate and its invoker.
4278
- */
4279
- createOutlinesAdapter?: CreateOutlinesAdapter;
4280
- /**
4281
- * Gesture dispatcher control surface — exposes `cancelAll(reason)` so
4282
- * actions that need to abort an in-flight handle (Escape cancels a
4283
- * drag, etc.) can do so. Sourced by `<SceneCanvas>` from the
4284
- * dispatcher instance it already owns.
4285
- */
4286
- dispatcher?: {
4287
- cancelAll(reason: 'commit' | 'cancel'): void;
4288
- };
4289
- /**
4290
- * Layout-strategy lookup. Sourced by `<SceneCanvas>` from `layouts`.
4291
- * Optional: absent (or all-null) → `moveAction` skips reflow.
4292
- */
4293
- layout?: LayoutDep;
4294
- /**
4295
- * Slice dep — the commit for the Slice action. `<SceneCanvas>` publishes
4296
- * `useSliceDepSource`; a consumer overrides it with `useSliceDep`.
4297
- *
4298
- * Optional: when absent, `sliceAction` is a no-op.
4299
- */
4300
- slice?: SliceDep;
4301
- /**
4302
- * Clipboard dep — the imperative surface `useClipboardOps` returns.
4303
- *
4304
- * Published by the consumer (`useDepSource('clipboard', …)` from under
4305
- * `<SceneCanvas>`), because `useClipboardOps` needs an adapter and a
4306
- * selection reader only the consumer has. Feeds `clipboard.copy` /
4307
- * `clipboard.cut`; both no-op when the dep is absent.
4308
- */
4309
- clipboard?: ClipboardDep;
4310
- /**
4311
- * Optional consumer commit hook. When present, `moveAction` (and other
4312
- * default actions) submit their committed ops through it instead of
4313
- * `scene.applyBatch`, so apps with their own history integration
4314
- * (checkpoint + push entry) capture the gesture as one undo entry.
4315
- * When absent, commits fall back to `scene.applyBatch`.
4316
- */
4317
- applyOps?: (ops: Op[], label: string) => void;
4318
- /** Optional pose-composition strategy for hierarchical (local-pose) scenes.
4319
- * When absent, defaults to IDENTITY (absolute-pose: nodes store world
4320
- * coords). Local-pose consumers supply { compose: composeRectPose,
4321
- * decompose: decomposeRectPose } (or their pose shape's equivalent). */
4322
- poseComposition?: PoseComposition<unknown>;
4323
- /**
4324
- * Ingestion dep — canvas viewport rect + consumer file→src resolver.
4325
- *
4326
- * Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
4327
- * `useIngestionDepSource`. Feeds `ingestAction` with the world-space
4328
- * viewport rect for paste-placement and image fit-clamping, and forwards
4329
- * the consumer's optional `resolveSrc` seam.
4330
- *
4331
- * Optional: when absent, the `ingest` action no-ops (there is no
4332
- * placement geometry to work with).
4333
- */
4334
- ingestion?: IngestionDep;
4335
- /**
4336
- * Optional consumer seam for the eager-sync layer: lets pose-transform
4337
- * actions (resize/move/nudge/flip — NOT rotate) ALSO rewrite a node's
4338
- * data-held geometry. Given a node and the affine `m` applied to its pose,
4339
- * `transform(node, m)` returns updated `data` (geometry mapped by `m`) or
4340
- * `null` for nodes with no data-held geometry.
4341
- *
4342
- * Strictly opt-in: when absent (or when `transform` returns null), the kit
4343
- * emits only the pose op and leaves `data` untouched. apps/draw wires this
4344
- * to mirror `data.path` through `transformPath`. Rotate intentionally never
4345
- * consults this seam (rotation lives on the pose, baked at render).
4346
- */
4347
- geometryProjection?: GeometryProjection;
4348
- /**
4349
- * The surface's own camera. Inside a `<CanvasView>`, `view` answers that
4350
- * view's camera and this one does not change: an action bound in a view
4351
- * that must move the main camera — a minimap's pan — reads this.
4352
- */
4353
- rootView: ViewApi;
4354
- /**
4355
- * The pointer store in scope: where the pointer is, in which view. Shared
4356
- * by every surface under one `<PointerContextProvider>`.
4357
- */
4358
- pointer?: PointerContextValue;
4359
- /**
4360
- * The touch or pen long-press being held, if any: where, since when, how
4361
- * far through, and whether any binding would fire on it. Sourced by
4362
- * `<SceneCanvas>` from its gesture dispatcher; press feedback reads it.
4363
- */
4364
- longPress?: LongPressState;
4365
- }
4366
- }
4367
-
4368
- /**
4369
- * The routing types that carry an overlay, bound to the kit's `RenderLayer`.
4370
- *
4371
- * `@weasel-js/routing` never reads an overlay — it collects the live ones and
4372
- * hands them back in scope order — so it leaves the element type open. What a
4373
- * weasel canvas draws is a `RenderLayer`, and every kit call site should see
4374
- * that rather than `unknown`, so core binds the parameter once, here, and
4375
- * re-exports these names under the ones they have always had.
4376
- */
4377
-
4378
- /** What a canvas overlay is, at this layer. */
4379
- type Overlay = RenderLayer<unknown>;
4380
- declare module '@weasel-js/routing' {
4381
- interface OverlaySchema {
4382
- overlay: RenderLayer<unknown>;
4383
- }
4384
- }
4385
- /** Routing's `Contribution`, with overlays typed as `RenderLayer`. */
4386
- type Contribution = Contribution$1<Overlay>;
4387
- /** Routing's `ContributionChrome`, with overlays typed as `RenderLayer`. */
4388
- type ContributionChrome = ContributionChrome$1<Overlay>;
4389
- /** Routing's `Tool`, with overlays typed as `RenderLayer`. */
4390
- type Tool<TScratch = unknown> = Tool$1<TScratch, Overlay>;
4391
- /** A `Tool` of any scratch type: the element type for a list of mixed tools. */
4392
- type AnyTool = AnyToolOf<Overlay>;
4393
- /** Routing's `ToolDef` (what `defineTool` takes), with overlays typed as `RenderLayer`. */
4394
- type ToolDef<TScratch = void> = ToolDef$1<TScratch, Overlay>;
4395
- /** Routing's `ToolsApi`, with overlays typed as `RenderLayer`. */
4396
- type ToolsApi = ToolsApi$1<Overlay>;
4397
- /** Routing's `UseToolsOptions`, with overlays typed as `RenderLayer`. */
4398
- type UseToolsOptions = UseToolsOptions$1<Overlay>;
4399
- /** Routing's `useTools`, typed for `RenderLayer` overlays. */
4400
- declare const useTools: (opts: UseToolsOptions) => ToolsApi;
4401
-
4402
- /**
4403
- * The space a screen-pixel stroke is built in.
4404
- *
4405
- * A `{ px }` stroke is exact on screen only when its ribbon is offset in a
4406
- * space that measures lengths the way the screen does. The transform's linear
4407
- * part is `scale · R · P`: a uniform scale, a rotation (or reflection) that
4408
- * moves no lengths, and `P`, the symmetric unit-determinant stretch left over.
4409
- * Offsetting the ribbon after `P` and mapping it back through `P⁻¹` gives a
4410
- * world-space mesh that the full transform turns into a ribbon of exactly
4411
- * `width · scale` pixels in every direction — and keeps the mesh drawable by
4412
- * every path that draws a world-space ribbon.
4413
- *
4414
- * `P` depends only on the lengths the transform induces (`LᵀL`), not on any
4415
- * rotation applied after it, so a view that only pans or zooms uniformly keeps
4416
- * the same metric and the ribbon cache keeps hitting.
4417
- */
4418
-
4419
- /** `P` = [[p, q], [q, r]], with `pr − q² = 1`. */
4420
- interface StrokeMetric {
4421
- readonly p: number;
4422
- readonly q: number;
4423
- readonly r: number;
4424
- }
4425
-
4426
- /**
4427
- * Trimming a stroke's ends where each inset is measured.
4428
- *
4429
- * A head stops the line by a length in the head's own space, which need not be
4430
- * the ribbon's: a world-sized head on a `{ px }` stroke stops it a world
4431
- * distance short, a `{ px }` head on a world-width stroke a screen distance.
4432
- * Under a stretching view one length means different distances in the two
4433
- * spaces depending on which way the line runs, so each end is trimmed in its
4434
- * own space rather than converted.
4435
- */
4436
-
4437
- /** A length along the line measured in `metric`'s space, or in world where
4438
- * `metric` is `null`. */
4439
- interface MeasuredLength {
4440
- readonly length: number;
4441
- readonly metric: StrokeMetric | null;
4442
- }
4443
- /** A plain number is a length in the ribbon's own space. */
4444
- type InsetLength = number | MeasuredLength;
4445
-
4446
- /** Options for stroke tessellation. */
4447
- interface StrokeOptions {
4448
- flattenTolerance?: number;
4449
- /**
4450
- * Shorten each open subpath by this much at its start / end: a length in
4451
- * the ribbon's space, or one measured in a space of its own. Stroke markers
4452
- * use this so a filled head is not speared by its own line; the caller
4453
- * resolves the distance, because this layer knows nothing about the marker
4454
- * registry.
4455
- */
4456
- startInset?: InsetLength;
4457
- endInset?: InsetLength;
4458
- /**
4459
- * Build the ribbon in this metric's space and map it back out, so the
4460
- * transform it was derived from draws it at an exact screen width — see
4461
- * `./metric`. Width and dash are then lengths in that space; `vertexWidths`
4462
- * stay world widths. The path is flattened in world first, so the ribbon
4463
- * follows the same vertices its fill does.
4464
- */
4465
- metric?: StrokeMetric;
4466
- }
4467
- /** Resolve a stroke width to world units. A number is already world units;
4468
- * `{ px }` is screen pixels divided by the accumulated scale, so it holds its
4469
- * on-screen thickness as the view zooms. */
4470
- declare function resolveStrokeWidth(width: ScreenLength, scale: number): number;
4471
- /**
4472
- * Build a triangle-mesh ribbon from a stroked Path.
4473
- *
4474
- * Supports:
4475
- * - cap: 'butt' | 'round' | 'square'
4476
- * - join: 'miter' | 'round' | 'bevel'
4477
- * - center alignment directly; RectPath inner/outer alignment via a
4478
- * pre-shifted rect; PolygonPath inner/outer alignment via stencil at the
4479
- * renderer level
4480
- * - dash splitting
4481
- */
4482
- declare function tessellateStroke(path: Path, stroke: Stroke, opts?: StrokeOptions): Mesh;
4483
-
4484
- /**
4485
- * Effects that ship with the kit.
4486
- *
4487
- * Each is a function returning `Effect[]`, not a single `Effect`, because a
4488
- * separable kernel is genuinely two passes and hiding that behind one entry
4489
- * would make the cost invisible at the callsite:
4490
- *
4491
- * effects={[...blur({ radius: 4 }), ...vignette({ amount: 0.6 })]}
4492
- */
4493
-
4494
- /**
4495
- * Gaussian blur, as a horizontal pass followed by a vertical one.
4496
- *
4497
- * `radius` is in device pixels and is the distance of the outermost tap, not a
4498
- * standard deviation — 0 is a copy, and the falloff is the same nine-tap
4499
- * kernel at every radius, so a large one is a wide blur rather than a better
4500
- * one. Two passes at O(9) beat one at O(81) and look the same.
4501
- */
4502
- declare function blur({ radius }: {
4503
- radius: number;
4504
- }): Effect[];
4505
- /**
4506
- * Darken toward the corners. `amount` is how dark the corner gets (0..1) and
4507
- * `feather` how much of the radius the falloff occupies.
4508
- */
4509
- declare function vignette({ amount, feather }: {
4510
- amount: number;
4511
- feather?: number;
4512
- }): Effect[];
4513
-
4514
- export { type SvgIngestOptions as $, type PoseComposition as A, type PoseAdapter as B, type CullRect as C, type DrawCommand as D, type Effect as E, type GestureLifecycleOptions as F, type GroupDrawCommand as G, type SliceDep as H, IDENTITY_COLOR_MATRIX as I, type ToolsApi as J, type Tool as K, type Contribution as L, type LayerHit as M, type Node as N, type ChromeState as O, type PathDrawCommand as P, type VertexColorChannel as Q, type RenderOptions as R, SPRITE_STRIDE as S, type TextDrawCommand as T, type LayerGroup as U, type ViewAnimationOptions as V, WeaselRenderer as W, type InsertExtras as X, type SerializedScene as Y, type GeometryProjection as Z, type ContentHandlerEntry as _, type ImageDrawCommand as a, type LayoutFrame as a$, type AnyTool as a0, type PanBounds as a1, type Animator as a2, ColorOverrideRegistry as a3, type UseSceneOptions as a4, type SceneRegistry as a5, type RegisteredOp as a6, type PoseOverrides as a7, type AddNodeSpec as a8, type DerivePoseFn as a9, type ColorOverride as aA, type ColorOverrideFn as aB, type ContainerNode as aC, type ContributionChrome as aD, type CreateOutlinesResult as aE, type CustomPaintContext as aF, type DecayLoopConfig as aG, type DecayOptions as aH, type EditAnchorsDep as aI, type EventBooking as aJ, type EventBookingHandle as aK, type EventTrack as aL, type FitViewToBoundsOptions as aM, type HitTestView as aN, IDENTITY_POSE_COMPOSITION as aO, type IngestCtx as aP, type IngestionDep as aQ, type InsertDep as aR, type Interpolate as aS, type InterpolatorFactory as aT, type Keyframe as aU, type LassoSelectDep as aV, type LayerCommandCache as aW, type LayerDrawFailure as aX, type LayerDrawSpan as aY, type LayerRecord as aZ, type LayoutDep as a_, type DerivePathFn as aa, type DerivedDep as ab, type ViewportDims as ac, type ViewApi as ad, type Affordance as ae, type AffordanceRegion as af, type AffordanceBinding as ag, type CommonAffordanceScratch as ah, type BooleansAdapter as ai, type CreateOutlinesAdapter as aj, type UseAnimatorOptions as ak, type AnimationHandle as al, type SampledTrack as am, type SceneArrivalHandler as an, type AddLayerSpec as ao, type AnimateToBoundsOptions as ap, type AnimationInfo as aq, type AnimationKind as ar, type AnimatorEvent as as, type AnimatorListener as at, type AreaSelectDep as au, type BooleanOp as av, type BooleanOpResult as aw, type ClipFromPoseFn as ax, type ClipboardDep as ay, type ClipboardIngestCtx as az, type ImageMinification as b, isLayerVisible as b$, type LayoutMove as b0, type LeafNode as b1, type LiveAnimation as b2, type LoopFactory as b3, type LoopOptions as b4, type NestedTimeline as b5, type NodeAtPointDep as b6, type OutlinePathSpec as b7, type Overlay as b8, type PathInWorldPose as b9, type TextOutlinesFailure as bA, type TextPaintPath as bB, type TextToPathOptions as bC, type TimelineClock as bD, type TimelineEvent as bE, type TimelineHandle as bF, type TimelineOptions as bG, type TimelineTrack as bH, type ToolDef as bI, type Track as bJ, type TweenLoopOptions as bK, type TweenOptions as bL, type UseToolsOptions as bM, type UserLayerRecord as bN, VIEW_ANIMATION_KEY as bO, type ViewAnimationApi as bP, type ViewChannel as bQ, applyBooleanOp as bR, applyCreateOutlines as bS, asNodeId as bT, composeWorldPose as bU, createPointerStore as bV, depsUnchanged as bW, drawLayers as bX, drawOneLayer as bY, fitViewToBounds as bZ, isLayerPainted as b_, type PhysicsHandle as ba, type PhysicsOptions as bb, PointerContextProvider as bc, type PointerContextValue as bd, type PointerWorldPos as be, type PoseClosure as bf, type PoseOverride as bg, type ResizePolicy as bh, type SerializedLayer as bi, type SerializedNode as bj, type SnapDep as bk, type SpringOptions as bl, type StaggerBuilder as bm, type StaggerDelay as bn, type StaggerFactory as bo, type StaggerOptions as bp, type StaggerPerItem as bq, type StaggerSpringPoseOptions as br, type StaggerTweenOptions as bs, type SvgUnpacker as bt, type SystemLayerRecord as bu, type SystemLayerSpec as bv, type TextEditDep as bw, type TextNodeSource as bx, type TextOutlineSource as by, TextOutlinesError as bz, type RenderSpan as c, loadTextOutlines as c0, pathInPoseFrame as c1, pathInWorld as c2, rebaseLocalPose as c3, registerContentHandler as c4, resolveStrokeWidth as c5, subscribeToSources as c6, textToPath as c7, textToPathsByPaint as c8, useDecayLoop as c9, usePointerContext as ca, usePointerPosition as cb, useTools as cc, useViewAnimation as cd, useViewAnimationOn as ce, worldEditToStorage as cf, type RenderSpanStats as d, type RenderStats as e, type RenderTarget as f, type ShaderDrawCommand as g, type SolidPaint as h, type SpriteSheet as i, type SpritesDrawCommand as j, type StrokeOptions as k, type WeaselRendererOptions as l, blur as m, buildGradientRamp as n, cullDrawCommands as o, frameRect as p, vignette as q, registerEffect as r, type RenderLayer as s, tessellateStroke as t, type Dims as u, viewToMat3 as v, type RedrawSource as w, type Scene as x, type RectPose as y, type InertiaConfig as z };