@weasel-js/core 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (586) hide show
  1. package/CHANGELOG.md +611 -0
  2. package/README.md +1 -1
  3. package/dist/WeaselProvider.js +1 -1
  4. package/dist/affordances/pathAnchors.js +10 -6
  5. package/dist/affordances/pathAnchors.js.map +1 -1
  6. package/dist/animation/index.js +3 -3
  7. package/dist/animation/rig/bindRig.js +1 -1
  8. package/dist/animation/rig/rigNode.js.map +1 -1
  9. package/dist/barrel/canvas.js +9 -3
  10. package/dist/barrel/interactions.js +5 -4
  11. package/dist/barrel/paint.js +4 -4
  12. package/dist/barrel/paths.js +10 -6
  13. package/dist/barrel/renderer.js +5 -3
  14. package/dist/barrel/routing.js +7 -12
  15. package/dist/barrel/scene.js +9 -5
  16. package/dist/barrel/text.js +2 -2
  17. package/dist/barrel/viewport.js +9 -4
  18. package/dist/canvas/Canvas.js +5 -2
  19. package/dist/canvas/Canvas.js.map +1 -1
  20. package/dist/canvas/CanvasView.js +23 -34
  21. package/dist/canvas/CanvasView.js.map +1 -1
  22. package/dist/canvas/ModalityHud.js +1 -1
  23. package/dist/canvas/NodeShape.js +4 -1
  24. package/dist/canvas/NodeShape.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  26. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  27. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  28. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  29. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  31. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  32. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  33. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  34. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -0
  35. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  37. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  39. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/features.js +45 -5
  41. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js +1 -11
  43. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  47. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  49. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  51. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  54. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +94 -0
  56. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  57. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  58. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useCanvasTools.js +15 -10
  60. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  62. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  64. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +23 -4
  66. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  67. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -3
  68. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  69. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  70. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  71. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +24 -7
  72. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  73. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  74. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  75. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  76. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  77. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -5
  78. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  79. package/dist/canvas/SceneCanvas.js +27 -24
  80. package/dist/canvas/SceneCanvas.js.map +1 -1
  81. package/dist/canvas/SurfaceCanvas.js +5 -22
  82. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  83. package/dist/canvas/ViewMarker.js +35 -0
  84. package/dist/canvas/ViewMarker.js.map +1 -0
  85. package/dist/canvas/canvasRenderer.js +9 -5
  86. package/dist/canvas/canvasRenderer.js.map +1 -1
  87. package/dist/canvas/contextAttributes.js +14 -0
  88. package/dist/canvas/contextAttributes.js.map +1 -0
  89. package/dist/canvas/defaultDrawOne.js +2 -0
  90. package/dist/canvas/defaultDrawOne.js.map +1 -1
  91. package/dist/canvas/deps/booleans.js +2 -1
  92. package/dist/canvas/deps/booleans.js.map +1 -1
  93. package/dist/canvas/deps/editAnchors.js +9 -113
  94. package/dist/canvas/deps/editAnchors.js.map +1 -1
  95. package/dist/canvas/deps/hitTestArea.js +2 -2
  96. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  97. package/dist/canvas/deps/insert.js +10 -4
  98. package/dist/canvas/deps/insert.js.map +1 -1
  99. package/dist/canvas/deps/pathEditCommits.js +189 -0
  100. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  101. package/dist/canvas/deps/snap.js +12 -9
  102. package/dist/canvas/deps/snap.js.map +1 -1
  103. package/dist/canvas/deps/transformFollowers.js +20 -0
  104. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  105. package/dist/canvas/derivedPath.js +20 -1
  106. package/dist/canvas/derivedPath.js.map +1 -1
  107. package/dist/canvas/drawnBounds.js +38 -4
  108. package/dist/canvas/drawnBounds.js.map +1 -1
  109. package/dist/canvas/inheritedPaints.js +44 -0
  110. package/dist/canvas/inheritedPaints.js.map +1 -0
  111. package/dist/canvas/nodeShapes/derivedPainter.js +23 -16
  112. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/nodePaint.js +13 -34
  114. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  115. package/dist/canvas/nodeShapes/pathPainter.js +12 -10
  116. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  118. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  119. package/dist/canvas/nodeShapes/shapePainter.js +12 -11
  120. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  121. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  122. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  123. package/dist/canvas/overlayRoleStrokes.js +12 -0
  124. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  125. package/dist/canvas/pathOverlay.js +27 -7
  126. package/dist/canvas/pathOverlay.js.map +1 -1
  127. package/dist/canvas/pickReach.js +3 -3
  128. package/dist/canvas/pickReach.js.map +1 -1
  129. package/dist/canvas/pickWalk.js +3 -1
  130. package/dist/canvas/pickWalk.js.map +1 -1
  131. package/dist/canvas/renderDebugSnapshot.js +1 -0
  132. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  133. package/dist/canvas/renderSceneToPixels.js +7 -6
  134. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  135. package/dist/canvas/sceneAdapter.js.map +1 -1
  136. package/dist/canvas/sceneSlotWiring.js +2 -1
  137. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  138. package/dist/canvas/sceneViewRender.js +2 -1
  139. package/dist/canvas/sceneViewRender.js.map +1 -1
  140. package/dist/canvas/surfaceContribution.js +12 -8
  141. package/dist/canvas/surfaceContribution.js.map +1 -1
  142. package/dist/canvas/useCanvasPaint.js +2 -1
  143. package/dist/canvas/useCanvasPaint.js.map +1 -1
  144. package/dist/canvas/useCanvasView.js +4 -2
  145. package/dist/canvas/useCanvasView.js.map +1 -1
  146. package/dist/canvas/useInFlightBounds.js +52 -0
  147. package/dist/canvas/useInFlightBounds.js.map +1 -0
  148. package/dist/canvas/useLayerStack.js +6 -2
  149. package/dist/canvas/useLayerStack.js.map +1 -1
  150. package/dist/canvas/useViewAim.js +121 -0
  151. package/dist/canvas/useViewAim.js.map +1 -0
  152. package/dist/canvas/useViewHelpers.js +7 -9
  153. package/dist/canvas/useViewHelpers.js.map +1 -1
  154. package/dist/canvas/useViewNesting.js +3 -2
  155. package/dist/canvas/useViewNesting.js.map +1 -1
  156. package/dist/canvas/useViewRegistrySurface.js +4 -2
  157. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  158. package/dist/canvas/viewMarkers.js +63 -0
  159. package/dist/canvas/viewMarkers.js.map +1 -0
  160. package/dist/canvas/viewRegistry.js +27 -1
  161. package/dist/canvas/viewRegistry.js.map +1 -1
  162. package/dist/clipboard.d.ts +3 -2
  163. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  164. package/dist/clone.d.ts +4 -3
  165. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  166. package/dist/core/ops/setDependsOn.js +37 -0
  167. package/dist/core/ops/setDependsOn.js.map +1 -0
  168. package/dist/core/ops/writtenBy.js +53 -0
  169. package/dist/core/ops/writtenBy.js.map +1 -0
  170. package/dist/core/pages/fitViewToPage.js +12 -1
  171. package/dist/core/pages/fitViewToPage.js.map +1 -1
  172. package/dist/core/pages/pageEdits.js +163 -0
  173. package/dist/core/pages/pageEdits.js.map +1 -0
  174. package/dist/core/pages/pages.js +30 -1
  175. package/dist/core/pages/pages.js.map +1 -1
  176. package/dist/core/paint/meshKindLazy.js +59 -2
  177. package/dist/core/paint/meshKindLazy.js.map +1 -1
  178. package/dist/core/scene/NodeRouting.js +8 -3
  179. package/dist/core/scene/NodeRouting.js.map +1 -1
  180. package/dist/core/scene/arrivalWindow.js.map +1 -1
  181. package/dist/core/scene/carryDescendants.js +51 -0
  182. package/dist/core/scene/carryDescendants.js.map +1 -0
  183. package/dist/core/scene/clipNothing.js +13 -0
  184. package/dist/core/scene/clipNothing.js.map +1 -0
  185. package/dist/core/scene/depMemo.js +11 -3
  186. package/dist/core/scene/depMemo.js.map +1 -1
  187. package/dist/core/scene/dependents.js +7 -1
  188. package/dist/core/scene/dependents.js.map +1 -1
  189. package/dist/core/scene/derivedParts.js +10 -11
  190. package/dist/core/scene/derivedParts.js.map +1 -1
  191. package/dist/core/scene/docValues.js +57 -0
  192. package/dist/core/scene/docValues.js.map +1 -0
  193. package/dist/core/scene/editPass.js +111 -0
  194. package/dist/core/scene/editPass.js.map +1 -0
  195. package/dist/core/scene/effectivePose.js +13 -2
  196. package/dist/core/scene/effectivePose.js.map +1 -1
  197. package/dist/core/scene/fieldEdits.js +4 -1
  198. package/dist/core/scene/fieldEdits.js.map +1 -1
  199. package/dist/core/scene/fontReads.js +37 -0
  200. package/dist/core/scene/fontReads.js.map +1 -0
  201. package/dist/core/scene/historyFacade.js +1 -0
  202. package/dist/core/scene/historyFacade.js.map +1 -1
  203. package/dist/core/scene/inheritedPaint.js +75 -0
  204. package/dist/core/scene/inheritedPaint.js.map +1 -0
  205. package/dist/core/scene/kitRegistry.js +5 -0
  206. package/dist/core/scene/kitRegistry.js.map +1 -1
  207. package/dist/core/scene/layoutPass.js +28 -3
  208. package/dist/core/scene/layoutPass.js.map +1 -1
  209. package/dist/core/scene/nodeMemo.js +15 -7
  210. package/dist/core/scene/nodeMemo.js.map +1 -1
  211. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  212. package/dist/core/scene/pathEffects/derive.js +7 -2
  213. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  214. package/dist/core/scene/pathEffects/effects.js +3 -1
  215. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  216. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  217. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  218. package/dist/core/scene/recorder.js +28 -19
  219. package/dist/core/scene/recorder.js.map +1 -1
  220. package/dist/core/scene/scene.js +15 -9
  221. package/dist/core/scene/scene.js.map +1 -1
  222. package/dist/core/scene/sceneChanges.js +13 -0
  223. package/dist/core/scene/sceneChanges.js.map +1 -0
  224. package/dist/core/scene/sceneEdits.js +8 -1
  225. package/dist/core/scene/sceneEdits.js.map +1 -1
  226. package/dist/core/scene/sceneMutators.js +1 -0
  227. package/dist/core/scene/sceneMutators.js.map +1 -1
  228. package/dist/core/scene/sceneRecording.js +20 -3
  229. package/dist/core/scene/sceneRecording.js.map +1 -1
  230. package/dist/core/scene/sceneSelection.js +4 -1
  231. package/dist/core/scene/sceneSelection.js.map +1 -1
  232. package/dist/core/scene/sceneState.js +11 -5
  233. package/dist/core/scene/sceneState.js.map +1 -1
  234. package/dist/core/scene/serialization.js +5 -3
  235. package/dist/core/scene/serialization.js.map +1 -1
  236. package/dist/core/scene/types.js.map +1 -1
  237. package/dist/core/scene/unitScopes.js +19 -1
  238. package/dist/core/scene/unitScopes.js.map +1 -1
  239. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  240. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  241. package/dist/core/shapeKinds.js +25 -11
  242. package/dist/core/shapeKinds.js.map +1 -1
  243. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-p8fDIAKt.d.ts} +3 -2
  244. package/dist/debug/createDebugOverlayLayer.js +16 -111
  245. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  246. package/dist/debug/createDebugSink.js +2 -1
  247. package/dist/debug/createDebugSink.js.map +1 -1
  248. package/dist/debug/defaultTheme.js +4 -2
  249. package/dist/debug/defaultTheme.js.map +1 -1
  250. package/dist/debug/features.js +17 -0
  251. package/dist/debug/features.js.map +1 -0
  252. package/dist/debug/overlayPanels.js +117 -0
  253. package/dist/debug/overlayPanels.js.map +1 -0
  254. package/dist/debug/parseDebugFlags.js +4 -13
  255. package/dist/debug/parseDebugFlags.js.map +1 -1
  256. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-mG19aEdD.d.ts} +174 -904
  257. package/dist/devtools/handlers.js +112 -0
  258. package/dist/devtools/handlers.js.map +1 -0
  259. package/dist/devtools/history.js +71 -0
  260. package/dist/devtools/history.js.map +1 -0
  261. package/dist/devtools/hook.js +74 -0
  262. package/dist/devtools/hook.js.map +1 -0
  263. package/dist/devtools/label.js +25 -0
  264. package/dist/devtools/label.js.map +1 -0
  265. package/dist/devtools/notices.js +58 -0
  266. package/dist/devtools/notices.js.map +1 -0
  267. package/dist/devtools/protocol.js +7 -0
  268. package/dist/devtools/protocol.js.map +1 -0
  269. package/dist/devtools/registry.js +90 -0
  270. package/dist/devtools/registry.js.map +1 -0
  271. package/dist/devtools/root.js +80 -0
  272. package/dist/devtools/root.js.map +1 -0
  273. package/dist/devtools/serialize.js +86 -0
  274. package/dist/devtools/serialize.js.map +1 -0
  275. package/dist/devtools/session.js +111 -0
  276. package/dist/devtools/session.js.map +1 -0
  277. package/dist/devtools/transport.js +67 -0
  278. package/dist/devtools/transport.js.map +1 -0
  279. package/dist/devtools/useDevtoolsRoot.js +81 -0
  280. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  281. package/dist/devtools-protocol.d.ts +435 -0
  282. package/dist/devtools-protocol.js +7 -0
  283. package/dist/devtools.d.ts +8 -0
  284. package/dist/devtools.js +12 -0
  285. package/dist/devtools.js.map +1 -0
  286. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  287. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  288. package/dist/features/ingestion/contentHandlers.js +25 -2
  289. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  290. package/dist/features/measure/measureCommands.js +7 -5
  291. package/dist/features/measure/measureCommands.js.map +1 -1
  292. package/dist/features/measure/measureContribution.js +4 -2
  293. package/dist/features/measure/measureContribution.js.map +1 -1
  294. package/dist/features/modes/modeTriggers.js +7 -3
  295. package/dist/features/modes/modeTriggers.js.map +1 -1
  296. package/dist/features/pathEffects/effectSources.js +60 -0
  297. package/dist/features/pathEffects/effectSources.js.map +1 -0
  298. package/dist/features/pathEffects/readSource.js +17 -1
  299. package/dist/features/pathEffects/readSource.js.map +1 -1
  300. package/dist/features/pathEffects/textItems.js +53 -0
  301. package/dist/features/pathEffects/textItems.js.map +1 -0
  302. package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
  303. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  304. package/dist/features/paths/index.js +2 -2
  305. package/dist/features/paths/pathEditExtension.js.map +1 -1
  306. package/dist/features/paths/pathEditState.js +5 -2
  307. package/dist/features/paths/pathEditState.js.map +1 -1
  308. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  309. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  310. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  311. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  312. package/dist/features/selection/selectionPose.js +4 -1
  313. package/dist/features/selection/selectionPose.js.map +1 -1
  314. package/dist/features/snap/gather.js +9 -1
  315. package/dist/features/snap/gather.js.map +1 -1
  316. package/dist/features/snap/lazySession.js +17 -0
  317. package/dist/features/snap/lazySession.js.map +1 -0
  318. package/dist/features/snap/marks.js.map +1 -1
  319. package/dist/features/snap/probes2d.js +27 -19
  320. package/dist/features/snap/probes2d.js.map +1 -1
  321. package/dist/features/snap/readoutLayer.js.map +1 -1
  322. package/dist/features/snap/roles.js +2 -1
  323. package/dist/features/snap/roles.js.map +1 -1
  324. package/dist/features/snap/rotationSource.js +25 -8
  325. package/dist/features/snap/rotationSource.js.map +1 -1
  326. package/dist/features/snap/sceneSession2d.js +6 -4
  327. package/dist/features/snap/sceneSession2d.js.map +1 -1
  328. package/dist/features/snap/session2d.js +37 -23
  329. package/dist/features/snap/session2d.js.map +1 -1
  330. package/dist/features/snap/spanSnap.js +5 -2
  331. package/dist/features/snap/spanSnap.js.map +1 -1
  332. package/dist/features/text/sceneTextDrafts.js +65 -0
  333. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  334. package/dist/features/text/textHitAt.js +31 -2
  335. package/dist/features/text/textHitAt.js.map +1 -1
  336. package/dist/features/text/useSceneTextEdit.js +22 -18
  337. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  338. package/dist/features/viewports/aim.js +105 -0
  339. package/dist/features/viewports/aim.js.map +1 -0
  340. package/dist/features/viewports/viewMarker.js +82 -0
  341. package/dist/features/viewports/viewMarker.js.map +1 -0
  342. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  343. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  344. package/dist/icons.table.js +1 -1
  345. package/dist/icons.table.js.map +1 -1
  346. package/dist/icons.table.json +0 -10
  347. package/dist/index.d.ts +1198 -894
  348. package/dist/index.js +55 -38
  349. package/dist/interactions/actions/align/action.js +6 -4
  350. package/dist/interactions/actions/align/action.js.map +1 -1
  351. package/dist/interactions/actions/align/align.js.map +1 -1
  352. package/dist/interactions/actions/booleans/action.js +7 -1
  353. package/dist/interactions/actions/booleans/action.js.map +1 -1
  354. package/dist/interactions/actions/booleans/booleans.js +2 -1
  355. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  356. package/dist/interactions/actions/booleans/operands.js +6 -5
  357. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  358. package/dist/interactions/actions/clipboard/action.js +27 -17
  359. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  360. package/dist/interactions/actions/clone/action.js +1 -1
  361. package/dist/interactions/actions/clone/behaviors.js +4 -4
  362. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  363. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  364. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  365. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  366. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  367. package/dist/interactions/actions/defaults/bendSegment.js +8 -5
  368. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  369. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  370. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  371. package/dist/interactions/actions/defaults/contextMenu.js +17 -8
  372. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  373. package/dist/interactions/actions/defaults/createPaintAction.js +2 -2
  374. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  375. package/dist/interactions/actions/defaults/delete.js +64 -41
  376. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  377. package/dist/interactions/actions/defaults/duplicate.js +12 -9
  378. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  379. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
  380. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  381. package/dist/interactions/actions/defaults/escape.js +11 -8
  382. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  383. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  384. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  385. package/dist/interactions/actions/defaults/group.js +21 -15
  386. package/dist/interactions/actions/defaults/group.js.map +1 -1
  387. package/dist/interactions/actions/defaults/ingest.js +17 -9
  388. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  389. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  390. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  391. package/dist/interactions/actions/defaults/nudge.js +21 -40
  392. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  393. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  394. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  395. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  396. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  397. package/dist/interactions/actions/defaults/reorder.js +24 -6
  398. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  399. package/dist/interactions/actions/defaults/selectAll.js +8 -5
  400. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  401. package/dist/interactions/actions/defaults/setFill.js +7 -3
  402. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  403. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  404. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  405. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  406. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  407. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  408. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  409. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  410. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  411. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  412. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  413. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  414. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  415. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  416. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  417. package/dist/interactions/actions/distribute/action.js +6 -4
  418. package/dist/interactions/actions/distribute/action.js.map +1 -1
  419. package/dist/interactions/actions/drawnBoxes.js +2 -1
  420. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  421. package/dist/interactions/actions/edit-anchors/action.js +10 -10
  422. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  423. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  424. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  425. package/dist/interactions/actions/flip/action.js +14 -6
  426. package/dist/interactions/actions/flip/action.js.map +1 -1
  427. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  428. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  429. package/dist/interactions/actions/insert/action.js +1 -2
  430. package/dist/interactions/actions/insert/action.js.map +1 -1
  431. package/dist/interactions/actions/insert/commit.js +1 -1
  432. package/dist/interactions/actions/insert/parent.js +11 -0
  433. package/dist/interactions/actions/insert/parent.js.map +1 -0
  434. package/dist/interactions/actions/insert/placeAction.js +7 -2
  435. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  436. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  437. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  438. package/dist/interactions/actions/insert/session.js +5 -1
  439. package/dist/interactions/actions/insert/session.js.map +1 -1
  440. package/dist/interactions/actions/move/action.js +15 -8
  441. package/dist/interactions/actions/move/action.js.map +1 -1
  442. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  443. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  444. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  445. package/dist/interactions/actions/move/capture.js +1 -1
  446. package/dist/interactions/actions/move/commit/index.js +0 -2
  447. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  448. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  449. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  450. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  451. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  452. package/dist/interactions/actions/move/frame.js +4 -10
  453. package/dist/interactions/actions/move/frame.js.map +1 -1
  454. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  455. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  456. package/dist/interactions/actions/move/layout/container.js +2 -1
  457. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  458. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  459. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  460. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  461. package/dist/interactions/actions/move/layout/exits.js +5 -5
  462. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  463. package/dist/interactions/actions/move/session.js +14 -3
  464. package/dist/interactions/actions/move/session.js.map +1 -1
  465. package/dist/interactions/actions/move/translate.js +8 -37
  466. package/dist/interactions/actions/move/translate.js.map +1 -1
  467. package/dist/interactions/actions/moveNodes.js +87 -0
  468. package/dist/interactions/actions/moveNodes.js.map +1 -0
  469. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  470. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  471. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  472. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  473. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  474. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  475. package/dist/interactions/actions/planeInput.js.map +1 -1
  476. package/dist/interactions/actions/resize/action.js +83 -72
  477. package/dist/interactions/actions/resize/action.js.map +1 -1
  478. package/dist/interactions/actions/resize/commit.js +2 -1
  479. package/dist/interactions/actions/resize/commit.js.map +1 -1
  480. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  481. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  482. package/dist/interactions/actions/resize/session.js +2 -0
  483. package/dist/interactions/actions/resize/session.js.map +1 -1
  484. package/dist/interactions/actions/resolvePreviews.js +3 -3
  485. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  486. package/dist/interactions/actions/rotate/action.js +7 -4
  487. package/dist/interactions/actions/rotate/action.js.map +1 -1
  488. package/dist/interactions/actions/rotate/commit.js +2 -1
  489. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  490. package/dist/interactions/actions/rotate/session.js +2 -0
  491. package/dist/interactions/actions/rotate/session.js.map +1 -1
  492. package/dist/interactions/actions/selectionSubtree.js +25 -9
  493. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  494. package/dist/interactions/actions/testUtils.js.map +1 -1
  495. package/dist/interactions/actions/transform-again/action.js +13 -11
  496. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  497. package/dist/interactions/actions/transformFinish.js +106 -0
  498. package/dist/interactions/actions/transformFinish.js.map +1 -0
  499. package/dist/json.d.ts +4 -0
  500. package/dist/json.js +3 -0
  501. package/dist/kitSettingsSchema.js +8 -5
  502. package/dist/kitSettingsSchema.js.map +1 -1
  503. package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
  504. package/dist/math.d.ts +1 -1
  505. package/dist/math.js +2 -1
  506. package/dist/move.d.ts +5 -4
  507. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  508. package/dist/resize.d.ts +4 -3
  509. package/dist/routing.d.ts +1 -1
  510. package/dist/routing.js +2 -2
  511. package/dist/test-hook/createTestHook.js +1 -1
  512. package/dist/test-hook/createTestHook.js.map +1 -1
  513. package/dist/test-hook/useTestHook.js +2 -1
  514. package/dist/test-hook/useTestHook.js.map +1 -1
  515. package/dist/test-seams.d.ts +9 -5
  516. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  517. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  518. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  519. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  520. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  521. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  522. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  523. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  524. package/dist/tools/builtin/image/useImageTool.js +1 -0
  525. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  526. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  527. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  528. package/dist/tools/builtin/line/useLineTool.js +1 -0
  529. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  530. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  531. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  532. package/dist/tools/builtin/pen/penHandle.js +3 -5
  533. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  534. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  535. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  536. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  537. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  538. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  539. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  540. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  541. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  542. package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
  543. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  544. package/dist/tools/builtin/select/pressDecision.js +32 -0
  545. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  546. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  547. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  548. package/dist/tools/builtin/select/useSelectTool.js +13 -10
  549. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  550. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  551. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  552. package/dist/tools/builtin/star/useStarTool.js +13 -4
  553. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  554. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  555. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  556. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  557. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  558. package/dist/tools/builtin/text/useTextTool.js +3 -1
  559. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  560. package/dist/tools/index.js +26 -0
  561. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  562. package/dist/types-BN9hbdMI.d.ts +184 -0
  563. package/dist/types-CniVctmk.d.ts +949 -0
  564. package/dist/types-oFeQ5VjV.d.ts +264 -0
  565. package/dist/util/paint.js +1 -3
  566. package/dist/util/paint.js.map +1 -1
  567. package/dist/version.js +1 -1
  568. package/package.json +39 -25
  569. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  570. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  571. package/dist/tools/builtin/bone/boneActions.js +0 -156
  572. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  573. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  574. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  575. package/dist/tools/builtin/bone/placedRig.js +0 -28
  576. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  577. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  578. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  579. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  580. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  581. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  582. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  583. package/dist/tools/builtin/weight/weightActions.js +0 -140
  584. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  585. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  586. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"blend.js","names":[],"sources":["../../../../src/core/scene/pathEffects/blend.ts"],"sourcesContent":["/**\n * A blend's steps: where each sits between its two sources — evenly by\n * count, or a fixed distance apart — and, with a spine, laid along it.\n */\nimport {\n boundsOfPath, measurePath, multiply, prepareBlend, rotate, rotatePathAround, transformPath, translate,\n type BlendPathsOptions, type Path, type PathMeasure,\n} from '@weasel-js/geom';\nimport { lerpFill, lerpStroke } from '@weasel-js/paint';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectSource } from './source';\n\n/** Steps between two shapes. Its sources are `[from, to]`, or\n * `[from, to, spine]` to lay the steps along a third path. */\nexport interface BlendEffect extends BlendPathsOptions {\n kind: 'blend';\n /** How many shapes stand between the two sources, spacing by count. */\n steps: number;\n /**\n * How the steps are spaced. `'count'` puts `steps` of them evenly between\n * the sources; `'distance'` puts one every `distance` along the run from\n * the first source, as many as fit short of the second, so the last gap is\n * whatever is left. Default `'count'`.\n */\n spaceBy?: 'count' | 'distance';\n /** Distance from one shape's middle to the next, spacing by distance —\n * along the spine where there is one. Spacing by count when unset. */\n distance?: number;\n /**\n * Interpolate each source's rotation as a turn about its middle, rather\n * than moving every point in a straight line — so a shape turned 90° at\n * one end turns through 45° halfway instead of shrinking there. Default\n * `true`; it applies where both sources are turned boxes.\n */\n rotate?: boolean;\n /** With a spine, turn each step by how far the spine's heading where it\n * sits differs from the straight run between the sources. Default\n * `false`, which only moves them. */\n orient?: boolean;\n}\n\nconst EPS = 1e-9;\n\n/** The fractions of the way from the first source to the second that the\n * steps sit at, over a run `length` long. */\nexport function blendStations(effect: BlendEffect, length: number): number[] {\n const out: number[] = [];\n const d = effect.distance;\n if (effect.spaceBy === 'distance' && d !== undefined && d > 0) {\n if (!(length > 0)) return out;\n for (let s = d; s < length - EPS * Math.max(length, 1); s += d) out.push(s / length);\n return out;\n }\n const steps = Math.max(Math.floor(effect.steps), 0);\n for (let i = 1; i <= steps; i++) out.push(i / (steps + 1));\n return out;\n}\n\n/** The middle a source's steps are interpolated about: its turn's center\n * where it is a turned box, else its outline's bounds' middle. */\nfunction middleOf(source: EffectSource): { x: number; y: number } {\n if (source.framed) return { x: source.framed.cx, y: source.framed.cy };\n const b = boundsOfPath(source.world);\n return { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n}\n\n/** The parts of `effect` between `a` and `b`, along `spine` when given.\n * `null` when the spine has no length to measure. */\nexport function blendParts(effect: BlendEffect, a: EffectSource, b: EffectSource, spine?: Path): DerivedPart[] | null {\n const from = middleOf(a);\n const to = middleOf(b);\n const along: PathMeasure | null = spine === undefined ? null : measurePath(spine);\n if (spine !== undefined && along === null) return null;\n const stations = blendStations(effect, along ? along.length : Math.hypot(to.x - from.x, to.y - from.y));\n if (stations.length === 0) return [];\n\n const turned = (effect.rotate ?? true) && a.framed !== undefined && b.framed !== undefined\n ? { a: a.framed, b: b.framed }\n : null;\n const blend = turned\n ? prepareBlend(turned.a.path, turned.b.path, effect)\n : prepareBlend(a.world, b.world, effect);\n // The shorter way round, so a turn of 350° reads as -10°.\n const turn = turned\n ? Math.atan2(Math.sin(turned.b.rotation - turned.a.rotation), Math.cos(turned.b.rotation - turned.a.rotation))\n : 0;\n const chord = Math.atan2(to.y - from.y, to.x - from.x);\n return stations.map((t) => {\n let path: Path = blend.at(t);\n if (turned) {\n const cx = turned.a.cx + (turned.b.cx - turned.a.cx) * t;\n const cy = turned.a.cy + (turned.b.cy - turned.a.cy) * t;\n const rotation = turned.a.rotation + turn * t;\n if (rotation !== 0) path = rotatePathAround(path, cx, cy, rotation);\n }\n if (along) {\n const { point, tangent } = along.at(t * along.length);\n const angle = effect.orient ? Math.atan2(tangent.y, tangent.x) - chord : 0;\n const middle = { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };\n path = transformPath(path, multiply(translate(point.x, point.y), multiply(rotate(angle), translate(-middle.x, -middle.y))));\n }\n return { path, fill: lerpFill(a.fill, b.fill, t) ?? null, stroke: lerpStroke(a.stroke, b.stroke, t) ?? null };\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"blend.js","names":[],"sources":["../../../../src/core/scene/pathEffects/blend.ts"],"sourcesContent":["/**\n * A blend's steps: where each sits between its two sources — evenly by\n * count, or a fixed distance apart — and, with a spine, laid along it.\n */\nimport {\n boundsOfPath, measurePath, multiply, prepareBlend, rotate, rotatePathAround, transformPath, translate,\n type BlendPathsOptions, type Path, type PathMeasure,\n} from '@weasel-js/geom';\nimport { lerpFill, lerpStroke } from '@weasel-js/paint';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectSource, PathEffectOptions } from './source';\n\n/** Steps between two shapes. Its sources are `[from, to]`, or\n * `[from, to, spine]` to lay the steps along a third path. */\nexport interface BlendEffect extends BlendPathsOptions, PathEffectOptions {\n kind: 'blend';\n /** How many shapes stand between the two sources, spacing by count. */\n steps: number;\n /**\n * How the steps are spaced. `'count'` puts `steps` of them evenly between\n * the sources; `'distance'` puts one every `distance` along the run from\n * the first source, as many as fit short of the second, so the last gap is\n * whatever is left. Default `'count'`.\n */\n spaceBy?: 'count' | 'distance';\n /** Distance from one shape's middle to the next, spacing by distance —\n * along the spine where there is one. Spacing by count when unset. */\n distance?: number;\n /**\n * Interpolate each source's rotation as a turn about its middle, rather\n * than moving every point in a straight line — so a shape turned 90° at\n * one end turns through 45° halfway instead of shrinking there. Default\n * `true`; it applies where both sources are turned boxes.\n */\n rotate?: boolean;\n /** With a spine, turn each step by how far the spine's heading where it\n * sits differs from the straight run between the sources. Default\n * `false`, which only moves them. */\n orient?: boolean;\n}\n\nconst EPS = 1e-9;\n\n/** The fractions of the way from the first source to the second that the\n * steps sit at, over a run `length` long. */\nexport function blendStations(effect: BlendEffect, length: number): number[] {\n const out: number[] = [];\n const d = effect.distance;\n if (effect.spaceBy === 'distance' && d !== undefined && d > 0) {\n if (!(length > 0)) return out;\n for (let s = d; s < length - EPS * Math.max(length, 1); s += d) out.push(s / length);\n return out;\n }\n const steps = Math.max(Math.floor(effect.steps), 0);\n for (let i = 1; i <= steps; i++) out.push(i / (steps + 1));\n return out;\n}\n\n/** The middle a source's steps are interpolated about: its turn's center\n * where it is a turned box, else its outline's bounds' middle. */\nfunction middleOf(source: EffectSource): { x: number; y: number } {\n if (source.framed) return { x: source.framed.cx, y: source.framed.cy };\n const b = boundsOfPath(source.world);\n return { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n}\n\n/** The parts of `effect` between `a` and `b`, along `spine` when given.\n * `null` when the spine has no length to measure. */\nexport function blendParts(effect: BlendEffect, a: EffectSource, b: EffectSource, spine?: Path): DerivedPart[] | null {\n const from = middleOf(a);\n const to = middleOf(b);\n const along: PathMeasure | null = spine === undefined ? null : measurePath(spine);\n if (spine !== undefined && along === null) return null;\n const stations = blendStations(effect, along ? along.length : Math.hypot(to.x - from.x, to.y - from.y));\n if (stations.length === 0) return [];\n\n const turned = (effect.rotate ?? true) && a.framed !== undefined && b.framed !== undefined\n ? { a: a.framed, b: b.framed }\n : null;\n const blend = turned\n ? prepareBlend(turned.a.path, turned.b.path, effect)\n : prepareBlend(a.world, b.world, effect);\n // The shorter way round, so a turn of 350° reads as -10°.\n const turn = turned\n ? Math.atan2(Math.sin(turned.b.rotation - turned.a.rotation), Math.cos(turned.b.rotation - turned.a.rotation))\n : 0;\n const chord = Math.atan2(to.y - from.y, to.x - from.x);\n return stations.map((t) => {\n let path: Path = blend.at(t);\n if (turned) {\n const cx = turned.a.cx + (turned.b.cx - turned.a.cx) * t;\n const cy = turned.a.cy + (turned.b.cy - turned.a.cy) * t;\n const rotation = turned.a.rotation + turn * t;\n if (rotation !== 0) path = rotatePathAround(path, cx, cy, rotation);\n }\n if (along) {\n const { point, tangent } = along.at(t * along.length);\n const angle = effect.orient ? Math.atan2(tangent.y, tangent.x) - chord : 0;\n const middle = { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };\n path = transformPath(path, multiply(translate(point.x, point.y), multiply(rotate(angle), translate(-middle.x, -middle.y))));\n }\n return { path, fill: lerpFill(a.fill, b.fill, t) ?? null, stroke: lerpStroke(a.stroke, b.stroke, t) ?? null };\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { fontGeneration, noteFontRead, withFontReads } from "../fontReads.js";
1
2
  import { samePoseValue, snapshotPose } from "../poseSnapshot.js";
2
3
  import { derivedPartsOf, pathFromParts } from "../derivedParts.js";
3
4
  import { effectParts } from "./effects.js";
@@ -33,14 +34,16 @@ function createPathEffectDerivations(read) {
33
34
  const held = results.get(node);
34
35
  if (held !== void 0 && current(held, node, deps)) return held;
35
36
  const effect = pathEffectOf(node);
36
- const parts = effect === null ? null : effectParts(effect, deps.map((d) => d === void 0 ? null : read(d)));
37
+ const generation = fontGeneration();
38
+ const { value: parts, fonts } = withFontReads(() => effect === null ? null : effectParts(effect, deps.map((d) => d === void 0 ? null : read(d))));
37
39
  const result = {
38
40
  data: node.data,
39
41
  nodes: deps.map((d) => d?.node),
40
42
  depData: deps.map((d) => d?.node.data),
41
43
  poses: deps.map((d) => d === void 0 ? void 0 : snapshotPose(d.pose)),
42
44
  paths: deps.map((d) => d?.path ?? null),
43
- path: parts === null ? null : pathFromParts(parts)
45
+ path: parts === null ? null : pathFromParts(parts),
46
+ faces: fonts ? generation : null
44
47
  };
45
48
  results.set(node, result);
46
49
  return result;
@@ -62,6 +65,7 @@ function createPathEffectDerivations(read) {
62
65
  }
63
66
  function current(held, node, deps) {
64
67
  if (held.data !== node.data || held.nodes.length !== deps.length) return false;
68
+ if (held.faces !== null && held.faces !== fontGeneration()) return false;
65
69
  for (let i = 0; i < deps.length; i++) {
66
70
  const d = deps[i];
67
71
  if (held.nodes[i] !== d?.node) return false;
@@ -69,6 +73,7 @@ function current(held, node, deps) {
69
73
  if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;
70
74
  if (held.paths[i] !== d.path) return false;
71
75
  }
76
+ if (held.faces !== null) noteFontRead();
72
77
  return true;
73
78
  }
74
79
  /** The parts a path-effect node paints, as its sources stand, or `null` when
@@ -1 +1 @@
1
- {"version":3,"file":"derive.js","names":[],"sources":["../../../../src/core/scene/pathEffects/derive.ts"],"sourcesContent":["/**\n * A path effect as a scene node: a leaf whose `dependsOn` names its sources,\n * whose `data.pathEffect` says what to make of them, and whose path and pose\n * are both derived — the path to paint and pick, the pose as that path's\n * bounds, so selection chrome and marquee see where it is.\n *\n * Reading a source's outline and paint is its painter's business, which sits\n * above the scene, so the derivations are made from a reader:\n * `createPathEffectDerivations(read)`. The canvas layer makes the kit's, and\n * a scene that holds path effects registers them under {@link PATH_EFFECT}.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport { derivedPartsOf, pathFromParts, type DerivedPart } from '../derivedParts';\nimport { samePoseValue, snapshotPose } from '../poseSnapshot';\nimport type { DerivedDep, NodeId } from '../types';\nimport { effectParts, type PathEffect } from './effects';\nimport type { EffectSourceReader } from './source';\n\n/** The registry key both derivations go by, for `derivePath` and `derivePose`. */\nexport const PATH_EFFECT = 'kit:pathEffect';\n\n/** A node's path effect, or `null` when its data holds none. */\nexport function pathEffectOf(node: { data?: unknown } | undefined): PathEffect | null {\n const effect = (node?.data as { pathEffect?: PathEffect } | null | undefined)?.pathEffect;\n return effect && typeof effect === 'object' && typeof effect.kind === 'string' ? effect : null;\n}\n\n/** A path-effect node's two derivations. */\nexport interface PathEffectDerivations {\n /** What it paints and picks by. */\n derivePath(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Path | null;\n /** The bounds of what it paints, or `null` — its authored pose — while it\n * paints nothing. */\n derivePose(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): { x: number; y: number; width: number; height: number } | null;\n}\n\ninterface Derived {\n data: unknown;\n nodes: unknown[];\n depData: unknown[];\n poses: unknown[];\n paths: (Path | null)[];\n path: Path | null;\n}\n\n/** The derivations of a path effect whose sources `read` reads. */\nexport function createPathEffectDerivations(read: EffectSourceReader): PathEffectDerivations {\n /** One result per effect node, shared by its two derivations: the pose is\n * the path's bounds, and working the effect out twice a frame would double\n * its cost. Checked against everything it read, by value for poses — a\n * drag's override mutates its pose in place. */\n const results = new WeakMap<object, Derived>();\n const derive = (node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Derived => {\n const held = results.get(node);\n if (held !== undefined && current(held, node, deps)) return held;\n const effect = pathEffectOf(node);\n const parts = effect === null\n ? null\n : effectParts(effect, deps.map((d) => (d === undefined ? null : read(d))));\n const result: Derived = {\n data: node.data,\n nodes: deps.map((d) => d?.node),\n depData: deps.map((d) => d?.node.data),\n poses: deps.map((d) => (d === undefined ? undefined : snapshotPose(d.pose))),\n paths: deps.map((d) => d?.path ?? null),\n path: parts === null ? null : pathFromParts(parts),\n };\n results.set(node, result);\n return result;\n };\n return {\n derivePath: (node, deps) => derive(node, deps).path,\n derivePose: (node, deps) => {\n const path = derive(node, deps).path;\n if (path === null) return null;\n const { x, y, width, height } = boundsOfPath(path);\n return { x, y, width, height };\n },\n };\n}\n\nfunction current(held: Derived, node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): boolean {\n if (held.data !== node.data || held.nodes.length !== deps.length) return false;\n for (let i = 0; i < deps.length; i++) {\n const d = deps[i];\n if (held.nodes[i] !== d?.node) return false;\n if (d === undefined) continue;\n if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;\n if (held.paths[i] !== d.path) return false;\n }\n return true;\n}\n\n/** The parts a path-effect node paints, as its sources stand, or `null` when\n * it has no derivation or nothing to paint. */\nexport function pathEffectParts(\n node: { data?: unknown; derivePath?: unknown },\n deps: readonly (DerivedDep<unknown> | undefined)[],\n): readonly DerivedPart[] | null {\n if (typeof node.derivePath !== 'function') return null;\n return derivedPartsOf((node.derivePath as PathEffectDerivations['derivePath'])(node, deps)) ?? null;\n}\n\n/** What to hand `Scene.add` (or an insert op) for a path-effect node over\n * `sources`, in the order its kind reads them, deriving through\n * `derivations` — the ones the scene registers under {@link PATH_EFFECT}. */\nexport function pathEffectNode<TLayer extends string>(\n effect: PathEffect,\n sources: readonly NodeId[],\n layer: TLayer,\n derivations: PathEffectDerivations,\n): {\n kind: 'leaf';\n layer: TLayer;\n pose: { x: number; y: number; width: number; height: number };\n data: { pathEffect: PathEffect };\n dependsOn: readonly NodeId[];\n derivePath: PathEffectDerivations['derivePath'];\n derivePose: PathEffectDerivations['derivePose'];\n} {\n return {\n kind: 'leaf',\n layer,\n pose: { x: 0, y: 0, width: 0, height: 0 },\n data: { pathEffect: effect },\n dependsOn: [...sources],\n derivePath: derivations.derivePath,\n derivePose: derivations.derivePose,\n };\n}\n\n/** The path-effect derivations `registry` holds under {@link PATH_EFFECT},\n * or `null` when it holds none. */\nexport function registeredPathEffects(registry: {\n derivePath?: Readonly<Record<string, unknown>>;\n derivePose?: Readonly<Record<string, unknown>>;\n}): PathEffectDerivations | null {\n const derivePath = registry.derivePath?.[PATH_EFFECT];\n const derivePose = registry.derivePose?.[PATH_EFFECT];\n return typeof derivePath === 'function' && typeof derivePose === 'function'\n ? { derivePath, derivePose } as PathEffectDerivations\n : null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,cAAc;;AAG3B,SAAgB,aAAa,MAAyD;CACpF,MAAM,UAAU,MAAM,KAAA,EAAyD;CAC/E,OAAO,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,WAAW,SAAS;AAC5F;;AAqBA,SAAgB,4BAA4B,MAAiD;;;;;CAK3F,MAAM,0BAAU,IAAI,QAAyB;CAC7C,MAAM,UAAU,MAA0B,SAAgE;EACxG,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,IAAI,SAAS,UAAa,QAAQ,MAAM,MAAM,IAAI,GAAG,OAAO;EAC5D,MAAM,SAAS,aAAa,IAAI;EAChC,MAAM,QAAQ,WAAW,OACrB,OACA,YAAY,QAAQ,KAAK,KAAK,MAAO,MAAM,SAAY,OAAO,KAAK,CAAC,CAAE,CAAC;EAC3E,MAAM,SAAkB;GACtB,MAAM,KAAK;GACX,OAAO,KAAK,KAAK,MAAM,GAAG,IAAI;GAC9B,SAAS,KAAK,KAAK,MAAM,GAAG,KAAK,IAAI;GACrC,OAAO,KAAK,KAAK,MAAO,MAAM,SAAY,SAAY,aAAa,EAAE,IAAI,CAAE;GAC3E,OAAO,KAAK,KAAK,MAAM,GAAG,QAAQ,IAAI;GACtC,MAAM,UAAU,OAAO,OAAO,cAAc,KAAK;EACnD;EACA,QAAQ,IAAI,MAAM,MAAM;EACxB,OAAO;CACT;CACA,OAAO;EACL,aAAa,MAAM,SAAS,OAAO,MAAM,IAAI,CAAC,CAAC;EAC/C,aAAa,MAAM,SAAS;GAC1B,MAAM,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC;GAChC,IAAI,SAAS,MAAM,OAAO;GAC1B,MAAM,EAAE,GAAG,GAAG,OAAO,WAAW,aAAa,IAAI;GACjD,OAAO;IAAE;IAAG;IAAG;IAAO;GAAO;EAC/B;CACF;AACF;AAEA,SAAS,QAAQ,MAAe,MAA0B,MAA6D;CACrH,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,MAAM,WAAW,KAAK,QAAQ,OAAO;CACzE,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;EACf,IAAI,KAAK,MAAM,OAAO,GAAG,MAAM,OAAO;EACtC,IAAI,MAAM,QAAW;EACrB,IAAI,KAAK,QAAQ,OAAO,EAAE,KAAK,QAAQ,CAAC,cAAc,KAAK,MAAM,IAAI,EAAE,IAAI,GAAG,OAAO;EACrF,IAAI,KAAK,MAAM,OAAO,EAAE,MAAM,OAAO;CACvC;CACA,OAAO;AACT;;;AAIA,SAAgB,gBACd,MACA,MAC+B;CAC/B,IAAI,OAAO,KAAK,eAAe,YAAY,OAAO;CAClD,OAAO,eAAgB,KAAK,WAAmD,MAAM,IAAI,CAAC,KAAK;AACjG;;;;AAKA,SAAgB,eACd,QACA,SACA,OACA,aASA;CACA,OAAO;EACL,MAAM;EACN;EACA,MAAM;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;GAAG,QAAQ;EAAE;EACxC,MAAM,EAAE,YAAY,OAAO;EAC3B,WAAW,CAAC,GAAG,OAAO;EACtB,YAAY,YAAY;EACxB,YAAY,YAAY;CAC1B;AACF;;;AAIA,SAAgB,sBAAsB,UAGL;CAC/B,MAAM,aAAa,SAAS,aAAa;CACzC,MAAM,aAAa,SAAS,aAAa;CACzC,OAAO,OAAO,eAAe,cAAc,OAAO,eAAe,aAC7D;EAAE;EAAY;CAAW,IACzB;AACN"}
1
+ {"version":3,"file":"derive.js","names":[],"sources":["../../../../src/core/scene/pathEffects/derive.ts"],"sourcesContent":["/**\n * A path effect as a scene node: a leaf whose `dependsOn` names its sources,\n * whose `data.pathEffect` says what to make of them, and whose path and pose\n * are both derived — the path to paint and pick, the pose as that path's\n * bounds, so selection chrome and marquee see where it is.\n *\n * Reading a source's outline and paint is its painter's business, which sits\n * above the scene, so the derivations are made from a reader:\n * `createPathEffectDerivations(read)`. The canvas layer makes the kit's, and\n * a scene that holds path effects registers them under {@link PATH_EFFECT}.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport { derivedPartsOf, pathFromParts, type DerivedPart } from '../derivedParts';\nimport { fontGeneration, noteFontRead, withFontReads } from '../fontReads';\nimport { samePoseValue, snapshotPose } from '../poseSnapshot';\nimport type { DerivedDep, NodeId } from '../types';\nimport { effectParts, type PathEffect } from './effects';\nimport type { EffectSourceReader } from './source';\n\n/** The registry key both derivations go by, for `derivePath` and `derivePose`. */\nexport const PATH_EFFECT = 'kit:pathEffect';\n\n/** A node's path effect, or `null` when its data holds none. */\nexport function pathEffectOf(node: { data?: unknown } | undefined): PathEffect | null {\n const effect = (node?.data as { pathEffect?: PathEffect } | null | undefined)?.pathEffect;\n return effect && typeof effect === 'object' && typeof effect.kind === 'string' ? effect : null;\n}\n\n/** A path-effect node's two derivations. */\nexport interface PathEffectDerivations {\n /** What it paints and picks by. */\n derivePath(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Path | null;\n /** The bounds of what it paints, or `null` — its authored pose — while it\n * paints nothing. */\n derivePose(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): { x: number; y: number; width: number; height: number } | null;\n}\n\ninterface Derived {\n data: unknown;\n nodes: unknown[];\n depData: unknown[];\n poses: unknown[];\n paths: (Path | null)[];\n path: Path | null;\n /** The face generation it was worked out under, or `null` when it read no\n * glyph outlines. */\n faces: number | null;\n}\n\n/** The derivations of a path effect whose sources `read` reads. */\nexport function createPathEffectDerivations(read: EffectSourceReader): PathEffectDerivations {\n /** One result per effect node, shared by its two derivations: the pose is\n * the path's bounds, and working the effect out twice a frame would double\n * its cost. Checked against everything it read, by value for poses — a\n * drag's override mutates its pose in place. */\n const results = new WeakMap<object, Derived>();\n const derive = (node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Derived => {\n const held = results.get(node);\n if (held !== undefined && current(held, node, deps)) return held;\n const effect = pathEffectOf(node);\n const generation = fontGeneration();\n const { value: parts, fonts } = withFontReads(() => (effect === null\n ? null\n : effectParts(effect, deps.map((d) => (d === undefined ? null : read(d))))));\n const result: Derived = {\n data: node.data,\n nodes: deps.map((d) => d?.node),\n depData: deps.map((d) => d?.node.data),\n poses: deps.map((d) => (d === undefined ? undefined : snapshotPose(d.pose))),\n paths: deps.map((d) => d?.path ?? null),\n path: parts === null ? null : pathFromParts(parts),\n faces: fonts ? generation : null,\n };\n results.set(node, result);\n return result;\n };\n return {\n derivePath: (node, deps) => derive(node, deps).path,\n derivePose: (node, deps) => {\n const path = derive(node, deps).path;\n if (path === null) return null;\n const { x, y, width, height } = boundsOfPath(path);\n return { x, y, width, height };\n },\n };\n}\n\nfunction current(held: Derived, node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): boolean {\n if (held.data !== node.data || held.nodes.length !== deps.length) return false;\n if (held.faces !== null && held.faces !== fontGeneration()) return false;\n for (let i = 0; i < deps.length; i++) {\n const d = deps[i];\n if (held.nodes[i] !== d?.node) return false;\n if (d === undefined) continue;\n if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;\n if (held.paths[i] !== d.path) return false;\n }\n // A hit is still a read, for the scene memo enclosing this one.\n if (held.faces !== null) noteFontRead();\n return true;\n}\n\n/** The parts a path-effect node paints, as its sources stand, or `null` when\n * it has no derivation or nothing to paint. */\nexport function pathEffectParts(\n node: { data?: unknown; derivePath?: unknown },\n deps: readonly (DerivedDep<unknown> | undefined)[],\n): readonly DerivedPart[] | null {\n if (typeof node.derivePath !== 'function') return null;\n return derivedPartsOf((node.derivePath as PathEffectDerivations['derivePath'])(node, deps)) ?? null;\n}\n\n/** What to hand `Scene.add` (or an insert op) for a path-effect node over\n * `sources`, in the order its kind reads them, deriving through\n * `derivations` — the ones the scene registers under {@link PATH_EFFECT}. */\nexport function pathEffectNode<TLayer extends string>(\n effect: PathEffect,\n sources: readonly NodeId[],\n layer: TLayer,\n derivations: PathEffectDerivations,\n): {\n kind: 'leaf';\n layer: TLayer;\n pose: { x: number; y: number; width: number; height: number };\n data: { pathEffect: PathEffect };\n dependsOn: readonly NodeId[];\n derivePath: PathEffectDerivations['derivePath'];\n derivePose: PathEffectDerivations['derivePose'];\n} {\n return {\n kind: 'leaf',\n layer,\n pose: { x: 0, y: 0, width: 0, height: 0 },\n data: { pathEffect: effect },\n dependsOn: [...sources],\n derivePath: derivations.derivePath,\n derivePose: derivations.derivePose,\n };\n}\n\n/** The path-effect derivations `registry` holds under {@link PATH_EFFECT},\n * or `null` when it holds none. */\nexport function registeredPathEffects(registry: {\n derivePath?: Readonly<Record<string, unknown>>;\n derivePose?: Readonly<Record<string, unknown>>;\n}): PathEffectDerivations | null {\n const derivePath = registry.derivePath?.[PATH_EFFECT];\n const derivePose = registry.derivePose?.[PATH_EFFECT];\n return typeof derivePath === 'function' && typeof derivePose === 'function'\n ? { derivePath, derivePose } as PathEffectDerivations\n : null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,IAAa,cAAc;;AAG3B,SAAgB,aAAa,MAAyD;CACpF,MAAM,UAAU,MAAM,KAAA,EAAyD;CAC/E,OAAO,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,WAAW,SAAS;AAC5F;;AAwBA,SAAgB,4BAA4B,MAAiD;;;;;CAK3F,MAAM,0BAAU,IAAI,QAAyB;CAC7C,MAAM,UAAU,MAA0B,SAAgE;EACxG,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,IAAI,SAAS,UAAa,QAAQ,MAAM,MAAM,IAAI,GAAG,OAAO;EAC5D,MAAM,SAAS,aAAa,IAAI;EAChC,MAAM,aAAa,eAAe;EAClC,MAAM,EAAE,OAAO,OAAO,UAAU,oBAAqB,WAAW,OAC5D,OACA,YAAY,QAAQ,KAAK,KAAK,MAAO,MAAM,SAAY,OAAO,KAAK,CAAC,CAAE,CAAC,CAAE;EAC7E,MAAM,SAAkB;GACtB,MAAM,KAAK;GACX,OAAO,KAAK,KAAK,MAAM,GAAG,IAAI;GAC9B,SAAS,KAAK,KAAK,MAAM,GAAG,KAAK,IAAI;GACrC,OAAO,KAAK,KAAK,MAAO,MAAM,SAAY,SAAY,aAAa,EAAE,IAAI,CAAE;GAC3E,OAAO,KAAK,KAAK,MAAM,GAAG,QAAQ,IAAI;GACtC,MAAM,UAAU,OAAO,OAAO,cAAc,KAAK;GACjD,OAAO,QAAQ,aAAa;EAC9B;EACA,QAAQ,IAAI,MAAM,MAAM;EACxB,OAAO;CACT;CACA,OAAO;EACL,aAAa,MAAM,SAAS,OAAO,MAAM,IAAI,CAAC,CAAC;EAC/C,aAAa,MAAM,SAAS;GAC1B,MAAM,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC;GAChC,IAAI,SAAS,MAAM,OAAO;GAC1B,MAAM,EAAE,GAAG,GAAG,OAAO,WAAW,aAAa,IAAI;GACjD,OAAO;IAAE;IAAG;IAAG;IAAO;GAAO;EAC/B;CACF;AACF;AAEA,SAAS,QAAQ,MAAe,MAA0B,MAA6D;CACrH,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,MAAM,WAAW,KAAK,QAAQ,OAAO;CACzE,IAAI,KAAK,UAAU,QAAQ,KAAK,UAAU,eAAe,GAAG,OAAO;CACnE,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;EACf,IAAI,KAAK,MAAM,OAAO,GAAG,MAAM,OAAO;EACtC,IAAI,MAAM,QAAW;EACrB,IAAI,KAAK,QAAQ,OAAO,EAAE,KAAK,QAAQ,CAAC,cAAc,KAAK,MAAM,IAAI,EAAE,IAAI,GAAG,OAAO;EACrF,IAAI,KAAK,MAAM,OAAO,EAAE,MAAM,OAAO;CACvC;CAEA,IAAI,KAAK,UAAU,MAAM,aAAa;CACtC,OAAO;AACT;;;AAIA,SAAgB,gBACd,MACA,MAC+B;CAC/B,IAAI,OAAO,KAAK,eAAe,YAAY,OAAO;CAClD,OAAO,eAAgB,KAAK,WAAmD,MAAM,IAAI,CAAC,KAAK;AACjG;;;;AAKA,SAAgB,eACd,QACA,SACA,OACA,aASA;CACA,OAAO;EACL,MAAM;EACN;EACA,MAAM;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;GAAG,QAAQ;EAAE;EACxC,MAAM,EAAE,YAAY,OAAO;EAC3B,WAAW,CAAC,GAAG,OAAO;EACtB,YAAY,YAAY;EACxB,YAAY,YAAY;CAC1B;AACF;;;AAIA,SAAgB,sBAAsB,UAGL;CAC/B,MAAM,aAAa,SAAS,aAAa;CACzC,MAAM,aAAa,SAAS,aAAa;CACzC,OAAO,OAAO,eAAe,cAAc,OAAO,eAAe,aAC7D;EAAE;EAAY;CAAW,IACzB;AACN"}
@@ -1,11 +1,12 @@
1
1
  import { blendParts } from "./blend.js";
2
+ import { textAlongParts, wholeItem } from "./textAlong.js";
2
3
  import { bendAlongPath, repeatAlongPath } from "@weasel-js/geom";
3
4
 
4
5
  //#region src/core/scene/pathEffects/effects.ts
5
6
  /**
6
7
  * The path effects, each as the parts it paints from its sources: a blend's
7
8
  * steps between two shapes, a shape bent along a spine, copies of a shape
8
- * along a path.
9
+ * along a path, text laid along a path.
9
10
  */
10
11
  /** The parts `effect` paints from `sources`, in its sources' order, or
11
12
  * `null` when a source it needs is missing — a blend's spine among them,
@@ -32,6 +33,7 @@ function effectParts(effect, sources) {
32
33
  fill: a.fill,
33
34
  stroke: a.stroke
34
35
  }));
36
+ case "textAlong": return textAlongParts(effect, a.items?.() ?? [wholeItem(a)], b.world);
35
37
  }
36
38
  }
37
39
 
@@ -1 +1 @@
1
- {"version":3,"file":"effects.js","names":[],"sources":["../../../../src/core/scene/pathEffects/effects.ts"],"sourcesContent":["/**\n * The path effects, each as the parts it paints from its sources: a blend's\n * steps between two shapes, a shape bent along a spine, copies of a shape\n * along a path.\n */\nimport {\n bendAlongPath, repeatAlongPath, type BendAlongPathOptions, type RepeatAlongPathOptions,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport { blendParts, type BlendEffect } from './blend';\nimport type { EffectSource } from './source';\n\nexport type { BlendEffect };\n\n/** A shape bent along a spine. Its sources are `[shape, spine]`. */\nexport interface BendAlongEffect extends BendAlongPathOptions {\n kind: 'bendAlong';\n}\n\n/** Copies of a shape along a path. Its sources are `[shape, path]`. */\nexport interface RepeatAlongEffect extends Omit<RepeatAlongPathOptions, 'flattenTolerance'> {\n kind: 'repeatAlong';\n}\n\n/** A path effect, as a node's `data.pathEffect` holds it. */\nexport type PathEffect = BlendEffect | BendAlongEffect | RepeatAlongEffect;\n\n/** The parts `effect` paints from `sources`, in its sources' order, or\n * `null` when a source it needs is missing — a blend's spine among them,\n * where it names one. */\nexport function effectParts(effect: PathEffect, sources: readonly (EffectSource | null)[]): DerivedPart[] | null {\n const [a, b] = sources;\n if (!a || !b) return null;\n switch (effect.kind) {\n case 'blend': {\n if (sources.length < 3) return blendParts(effect, a, b);\n const spine = sources[2];\n return spine ? blendParts(effect, a, b, spine.world) : null;\n }\n case 'bendAlong': {\n const path = bendAlongPath(a.world, b.world, effect);\n return path === null ? null : [{ path, fill: a.fill, stroke: a.stroke }];\n }\n case 'repeatAlong':\n return repeatAlongPath(a.world, b.world, effect).map((path) => ({ path, fill: a.fill, stroke: a.stroke }));\n }\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,SAAgB,YAAY,QAAoB,SAAiE;CAC/G,MAAM,CAAC,GAAG,KAAK;CACf,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,QAAQ,OAAO,MAAf;EACE,KAAK,SAAS;GACZ,IAAI,QAAQ,SAAS,GAAG,OAAO,WAAW,QAAQ,GAAG,CAAC;GACtD,MAAM,QAAQ,QAAQ;GACtB,OAAO,QAAQ,WAAW,QAAQ,GAAG,GAAG,MAAM,KAAK,IAAI;EACzD;EACA,KAAK,aAAa;GAChB,MAAM,OAAO,cAAc,EAAE,OAAO,EAAE,OAAO,MAAM;GACnD,OAAO,SAAS,OAAO,OAAO,CAAC;IAAE;IAAM,MAAM,EAAE;IAAM,QAAQ,EAAE;GAAO,CAAC;EACzE;EACA,KAAK,eACH,OAAO,gBAAgB,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAAE;GAAM,MAAM,EAAE;GAAM,QAAQ,EAAE;EAAO,EAAE;CAC7G;AACF"}
1
+ {"version":3,"file":"effects.js","names":[],"sources":["../../../../src/core/scene/pathEffects/effects.ts"],"sourcesContent":["/**\n * The path effects, each as the parts it paints from its sources: a blend's\n * steps between two shapes, a shape bent along a spine, copies of a shape\n * along a path, text laid along a path.\n */\nimport {\n bendAlongPath, repeatAlongPath, type BendAlongPathOptions, type RepeatAlongPathOptions,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport { blendParts, type BlendEffect } from './blend';\nimport type { EffectSource, PathEffectOptions } from './source';\nimport { textAlongParts, wholeItem, type TextAlongEffect } from './textAlong';\n\nexport type { BlendEffect, PathEffectOptions, TextAlongEffect };\n\n/** A shape bent along a spine. Its sources are `[shape, spine]`. */\nexport interface BendAlongEffect extends BendAlongPathOptions, PathEffectOptions {\n kind: 'bendAlong';\n}\n\n/** Copies of a shape along a path. Its sources are `[shape, path]`. */\nexport interface RepeatAlongEffect extends Omit<RepeatAlongPathOptions, 'flattenTolerance'>, PathEffectOptions {\n kind: 'repeatAlong';\n}\n\n/** A path effect, as a node's `data.pathEffect` holds it. */\nexport type PathEffect = BlendEffect | BendAlongEffect | RepeatAlongEffect | TextAlongEffect;\n\n/** The parts `effect` paints from `sources`, in its sources' order, or\n * `null` when a source it needs is missing — a blend's spine among them,\n * where it names one. */\nexport function effectParts(effect: PathEffect, sources: readonly (EffectSource | null)[]): DerivedPart[] | null {\n const [a, b] = sources;\n if (!a || !b) return null;\n switch (effect.kind) {\n case 'blend': {\n if (sources.length < 3) return blendParts(effect, a, b);\n const spine = sources[2];\n return spine ? blendParts(effect, a, b, spine.world) : null;\n }\n case 'bendAlong': {\n const path = bendAlongPath(a.world, b.world, effect);\n return path === null ? null : [{ path, fill: a.fill, stroke: a.stroke }];\n }\n case 'repeatAlong':\n return repeatAlongPath(a.world, b.world, effect).map((path) => ({ path, fill: a.fill, stroke: a.stroke }));\n case 'textAlong':\n return textAlongParts(effect, a.items?.() ?? [wholeItem(a)], b.world);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,SAAgB,YAAY,QAAoB,SAAiE;CAC/G,MAAM,CAAC,GAAG,KAAK;CACf,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,QAAQ,OAAO,MAAf;EACE,KAAK,SAAS;GACZ,IAAI,QAAQ,SAAS,GAAG,OAAO,WAAW,QAAQ,GAAG,CAAC;GACtD,MAAM,QAAQ,QAAQ;GACtB,OAAO,QAAQ,WAAW,QAAQ,GAAG,GAAG,MAAM,KAAK,IAAI;EACzD;EACA,KAAK,aAAa;GAChB,MAAM,OAAO,cAAc,EAAE,OAAO,EAAE,OAAO,MAAM;GACnD,OAAO,SAAS,OAAO,OAAO,CAAC;IAAE;IAAM,MAAM,EAAE;IAAM,QAAQ,EAAE;GAAO,CAAC;EACzE;EACA,KAAK,eACH,OAAO,gBAAgB,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAAE;GAAM,MAAM,EAAE;GAAM,QAAQ,EAAE;EAAO,EAAE;EAC3G,KAAK,aACH,OAAO,eAAe,QAAQ,EAAE,QAAQ,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,KAAK;CACxE;AACF"}
@@ -0,0 +1,95 @@
1
+ import { boundsOfPath, multiply, pathShape, placeAlongRun, rotate, runLength, trackRun, transformPath, translate } from "@weasel-js/geom";
2
+
3
+ //#region src/core/scene/pathEffects/textAlong.ts
4
+ /**
5
+ * Text along a path: a source's items — a text node's grapheme clusters, or a
6
+ * whole shape as one item — laid end to end along a path, each turned to it.
7
+ */
8
+ var LEAD = {
9
+ start: 0,
10
+ center: .5,
11
+ end: 1
12
+ };
13
+ /** The parts `items` paint laid along `path` as `effect` says; `[]` when there is nothing to lay or nowhere to lay it. */
14
+ function textAlongParts(effect, items, path) {
15
+ const shape = pathShape(path);
16
+ if (shape === null || !(shape.length > 0) || items.length === 0) return [];
17
+ const L = shape.length;
18
+ const closed = shape.closed;
19
+ const lengths = items.map((it) => it.length);
20
+ const k = LEAD[effect.align ?? "start"];
21
+ const at = clamp01(effect.start ?? 0) * L;
22
+ const gap = effect.overflow === "fit" ? fitGap(lengths, roomAt(at, L, k, closed)) : 0;
23
+ const total = runLength(lengths, gap);
24
+ const c = at + (.5 - k) * total;
25
+ const about = closed ? (c / L % 1 + 1) % 1 : clamp01(c / L);
26
+ const run = trackRun(shape, 0, 1, {
27
+ side: effect.side ?? "auto",
28
+ about
29
+ });
30
+ const places = placeAlongRun(run, lengths, {
31
+ gap,
32
+ align: "center",
33
+ at: run.reversed ? L - c : c,
34
+ shift: finiteOr(effect.shift, 0),
35
+ wrap: closed
36
+ });
37
+ const center = effect.anchor === "center";
38
+ const out = [];
39
+ const firstStart = places[0].center - lengths[0] / 2;
40
+ places.forEach((place, i) => {
41
+ if (dropped(place, closed ? firstStart : null, L)) return;
42
+ const item = items[i];
43
+ const m = itemFrame(place, item.length, center ? item.middle : 0);
44
+ for (const part of item.parts) out.push({
45
+ ...part,
46
+ path: transformPath(part.path, m)
47
+ });
48
+ });
49
+ return out;
50
+ }
51
+ /** `source`'s whole outline as one item: its bounds' left at x = 0 and their vertical middle on the anchor line. */
52
+ function wholeItem(source) {
53
+ const b = boundsOfPath(source.world);
54
+ return {
55
+ length: b.width,
56
+ middle: 0,
57
+ parts: [{
58
+ path: transformPath(source.world, translate(-b.x, -(b.y + b.height / 2))),
59
+ fill: source.fill,
60
+ stroke: source.stroke
61
+ }]
62
+ };
63
+ }
64
+ /** SVG `textPath`'s rule: an item is not drawn once its middle is past an open path's ends, or on a closed path a whole
65
+ * lap or more on from the first item's start. Judged within a hair of rounding. */
66
+ function dropped(place, firstStart, L) {
67
+ const hair = 1e-9 * Math.max(1, L);
68
+ if (firstStart !== null) return place.center - firstStart >= L - hair;
69
+ return place.center < -hair || place.center > L + hair;
70
+ }
71
+ /** How much path the text has before it runs off, from `at`, with `k` of it laid back behind `at`. */
72
+ function roomAt(at, L, k, closed) {
73
+ if (closed) return L;
74
+ return k === 0 ? L - at : k === 1 ? at : 2 * Math.min(at, L - at);
75
+ }
76
+ /** The gap that makes `lengths` fill `room`, or 0 when they fit already. Never so negative that an item's step back
77
+ * from the one before it (its length plus the gap) goes below zero, so items keep their order in too short a room. */
78
+ function fitGap(lengths, room) {
79
+ const total = runLength(lengths, 0);
80
+ if (!(total > room) || lengths.length < 2) return 0;
81
+ let floor = -Infinity;
82
+ for (let i = 1; i < lengths.length; i++) floor = Math.max(floor, -(lengths[i - 1] + lengths[i]) / 2);
83
+ return Math.max((room - total) / (lengths.length - 1), floor);
84
+ }
85
+ /** Item frame to world: its middle on `place`, turned to it, its anchor line `lift` above y = 0 brought onto the path. */
86
+ function itemFrame(place, length, lift) {
87
+ const turn = multiply(rotate(place.angle * Math.PI / 180), translate(-length / 2, lift));
88
+ return multiply(translate(place.point.x, place.point.y), turn);
89
+ }
90
+ var clamp01 = (v) => v > 1 ? 1 : v > 0 ? v : 0;
91
+ var finiteOr = (v, d) => v !== void 0 && Number.isFinite(v) ? v : d;
92
+
93
+ //#endregion
94
+ export { textAlongParts, wholeItem };
95
+ //# sourceMappingURL=textAlong.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textAlong.js","names":[],"sources":["../../../../src/core/scene/pathEffects/textAlong.ts"],"sourcesContent":["/**\n * Text along a path: a source's items — a text node's grapheme clusters, or a\n * whole shape as one item — laid end to end along a path, each turned to it.\n */\nimport {\n boundsOfPath, multiply, pathShape, placeAlongRun, rotate, runLength, trackRun, transformPath, translate,\n type Affine, type Path, type RunAlign, type RunPlace, type TrackSide,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectItem, EffectSource, PathEffectOptions } from './source';\n\n/**\n * Text (or any shape) laid along a path, item by item. Its sources are `[text, path]`. Only the text's first laid line\n * follows the path; a color glyph keeps its advance and paints nothing; underlines and other decorations are not drawn.\n * On an open path an item whose middle falls past either end is not drawn; on a closed one the text wraps past the\n * seam, and an item a whole lap on from the first item's start is not drawn.\n */\nexport interface TextAlongEffect extends PathEffectOptions {\n kind: 'textAlong';\n /** Which part of the text sits at `start`. Default `'start'`. */\n align?: RunAlign;\n /** Where the text sits, as a fraction 0..1 of the path's length from its first point. Default 0. */\n start?: number;\n /** How far the anchor line sits off the path, toward the glyphs' up, in world units. Default 0. */\n shift?: number;\n /** What sits on the path: the glyphs' baseline, or (`'center'`) their x-height middle. Default `'baseline'`. */\n anchor?: 'baseline' | 'center';\n /** Text longer than its room is cut at the path's end (`'clip'`), or has the gaps between items squeezed until it\n * fits (`'fit'`). Default `'clip'`. */\n overflow?: 'clip' | 'fit';\n /** Which side of the path the glyphs stand on; see `TrackSide`. Default `'auto'`, whichever reads upright. Turned over,\n * the text keeps the stretch of path it had, and on an open path drops whichever letters fall off it. */\n side?: TrackSide;\n}\n\nconst LEAD: Record<RunAlign, number> = { start: 0, center: 0.5, end: 1 };\n\n/** The parts `items` paint laid along `path` as `effect` says; `[]` when there is nothing to lay or nowhere to lay it. */\nexport function textAlongParts(effect: TextAlongEffect, items: readonly EffectItem[], path: Path): DerivedPart[] {\n const shape = pathShape(path);\n if (shape === null || !(shape.length > 0) || items.length === 0) return [];\n const L = shape.length;\n const closed = shape.closed;\n const lengths = items.map((it) => it.length);\n const k = LEAD[effect.align ?? 'start'];\n const at = clamp01(effect.start ?? 0) * L;\n const gap = effect.overflow === 'fit' ? fitGap(lengths, roomAt(at, L, k, closed)) : 0;\n const total = runLength(lengths, gap);\n const c = at + (0.5 - k) * total;\n const about = closed ? ((c / L) % 1 + 1) % 1 : clamp01(c / L);\n const run = trackRun(shape, 0, 1, { side: effect.side ?? 'auto', about });\n const places = placeAlongRun(run, lengths, {\n gap, align: 'center', at: run.reversed ? L - c : c, shift: finiteOr(effect.shift, 0), wrap: closed,\n });\n const center = effect.anchor === 'center';\n const out: DerivedPart[] = [];\n const firstStart = places[0]!.center - lengths[0]! / 2;\n places.forEach((place, i) => {\n // By center (SVG textPath's rule), not by RunPlace.clipped, which judges by extent.\n if (dropped(place, closed ? firstStart : null, L)) return;\n const item = items[i]!;\n const m = itemFrame(place, item.length, center ? item.middle : 0);\n for (const part of item.parts) out.push({ ...part, path: transformPath(part.path, m) });\n });\n return out;\n}\n\n/** `source`'s whole outline as one item: its bounds' left at x = 0 and their vertical middle on the anchor line. */\nexport function wholeItem(source: EffectSource): EffectItem {\n const b = boundsOfPath(source.world);\n return {\n length: b.width,\n middle: 0,\n parts: [{\n path: transformPath(source.world, translate(-b.x, -(b.y + b.height / 2))),\n fill: source.fill,\n stroke: source.stroke,\n }],\n };\n}\n\n/** SVG `textPath`'s rule: an item is not drawn once its middle is past an open path's ends, or on a closed path a whole\n * lap or more on from the first item's start. Judged within a hair of rounding. */\nfunction dropped(place: RunPlace, firstStart: number | null, L: number): boolean {\n const hair = 1e-9 * Math.max(1, L);\n if (firstStart !== null) return place.center - firstStart >= L - hair;\n return place.center < -hair || place.center > L + hair;\n}\n\n/** How much path the text has before it runs off, from `at`, with `k` of it laid back behind `at`. */\nfunction roomAt(at: number, L: number, k: number, closed: boolean): number {\n if (closed) return L;\n return k === 0 ? L - at : k === 1 ? at : 2 * Math.min(at, L - at);\n}\n\n/** The gap that makes `lengths` fill `room`, or 0 when they fit already. Never so negative that an item's step back\n * from the one before it (its length plus the gap) goes below zero, so items keep their order in too short a room. */\nfunction fitGap(lengths: readonly number[], room: number): number {\n const total = runLength(lengths, 0);\n if (!(total > room) || lengths.length < 2) return 0;\n let floor = -Infinity;\n for (let i = 1; i < lengths.length; i++) floor = Math.max(floor, -(lengths[i - 1]! + lengths[i]!) / 2);\n return Math.max((room - total) / (lengths.length - 1), floor);\n}\n\n/** Item frame to world: its middle on `place`, turned to it, its anchor line `lift` above y = 0 brought onto the path. */\nfunction itemFrame(place: RunPlace, length: number, lift: number): Affine {\n const turn = multiply(rotate((place.angle * Math.PI) / 180), translate(-length / 2, lift));\n return multiply(translate(place.point.x, place.point.y), turn);\n}\n\nconst clamp01 = (v: number): number => (v > 1 ? 1 : v > 0 ? v : 0);\nconst finiteOr = (v: number | undefined, d: number): number => (v !== undefined && Number.isFinite(v) ? v : d);\n"],"mappings":"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"}
@@ -9,30 +9,35 @@ function unwind(outer) {
9
9
  } catch {}
10
10
  }
11
11
  function createRecorder(env) {
12
- const { notifier, mode, engine, log, arrivals } = env;
12
+ const { notifier, mode, engine, log, arrivals, passes } = env;
13
13
  /** The outermost open batch, while it is recording. */
14
14
  let collecting = null;
15
15
  /** The kit ops applied since the innermost `capturing` opened. */
16
16
  let captured = null;
17
- /** `fn`, then the poses its arrivals settle to when this edit owns them. */
18
- function withArrivalsSettled(fn, owns) {
17
+ /** `fn`, then the poses its arrivals settle to and its passes' ops, for whichever this edit owns. */
18
+ function withArrivalsSettled(fn, owns, ownsPasses = false) {
19
19
  const result = fn();
20
20
  if (owns) for (const p of arrivals.settle()) env.writePose(p.id, p.to);
21
+ if (ownsPasses) passes.run();
21
22
  return result;
22
23
  }
23
- function batch(label, fn) {
24
+ function batch(label, fn, options = {}) {
24
25
  const outermost = !mode.open();
25
26
  if (outermost) collecting = {
26
27
  label,
27
28
  ops: [],
28
29
  outer: [],
29
- selectionBefore: env.selection()
30
+ entry: {
31
+ selectionBefore: env.selection(),
32
+ ...options
33
+ }
30
34
  };
31
35
  const ownsArrivals = outermost && arrivals.open(true);
36
+ const ownsPasses = outermost && passes.open();
32
37
  return env.labeled(label, "call", () => notifier.deferred(() => mode.during("batch", () => {
33
38
  if (!outermost) return fn();
34
39
  try {
35
- const result = log.atomically(() => withArrivalsSettled(fn, ownsArrivals), () => (collecting?.ops.length ?? 0) > 0);
40
+ const result = log.atomically(() => withArrivalsSettled(fn, ownsArrivals, ownsPasses), () => (collecting?.ops.length ?? 0) > 0);
36
41
  if (ownsArrivals) arrivals.emitReflow();
37
42
  return result;
38
43
  } catch (err) {
@@ -42,25 +47,29 @@ function createRecorder(env) {
42
47
  }
43
48
  }), () => {
44
49
  if (ownsArrivals) arrivals.abandon();
50
+ if (ownsPasses) passes.abandon();
45
51
  if (!outermost || !collecting) return;
46
52
  const finished = collecting;
47
53
  collecting = null;
48
- if (finished.ops.length > 0) engine.recordEntry(finished.ops, finished.label, { selectionBefore: finished.selectionBefore });
54
+ if (finished.ops.length > 0) engine.recordEntry(finished.ops, finished.label, finished.entry);
49
55
  }));
50
56
  }
57
+ function record(kind, payload, label) {
58
+ if (mode.records() && !collecting && passes.any()) return void batch(label, () => record(kind, payload, label));
59
+ passes.note(kind, payload);
60
+ env.labeled(label, "call", () => {
61
+ if (mode.records() && !collecting) mode.during("own", () => engine.applyOps([env.makeOp(kind, payload)], label));
62
+ else {
63
+ env.runOp(kind, payload);
64
+ log.logApplied(kind, payload);
65
+ if (mode.records()) collecting.ops.push(env.makeOp(kind, payload));
66
+ else if (!mode.untracked()) captured?.push(env.makeOp(kind, payload));
67
+ }
68
+ });
69
+ notifier.notify();
70
+ }
51
71
  return {
52
- record(kind, payload, label) {
53
- env.labeled(label, "call", () => {
54
- if (mode.records() && !collecting) mode.during("own", () => engine.applyOps([env.makeOp(kind, payload)], label));
55
- else {
56
- env.runOp(kind, payload);
57
- log.logApplied(kind, payload);
58
- if (mode.records()) collecting.ops.push(env.makeOp(kind, payload));
59
- else if (!mode.untracked()) captured?.push(env.makeOp(kind, payload));
60
- }
61
- });
62
- notifier.notify();
63
- },
72
+ record,
64
73
  edit: (label, touchesLayout, fn) => touchesLayout && arrivals.bareLayoutEdit() ? batch(label, fn) : fn(),
65
74
  batch,
66
75
  untracked(fn) {
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.js","names":[],"sources":["../../../src/core/scene/recorder.ts"],"sourcesContent":["/**\n * How a scene mutation becomes an undo entry: recorded on its own, collected\n * into the batch it runs in, or left to the engine or the untracked run that\n * is driving it.\n */\nimport type { History } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport type { AppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { bindOpToAdapter } from './boundOps';\nimport type { EditMode } from './editMode';\nimport type { Notifier } from './notifier';\nimport type { NodeId } from './types';\n\n/** What the recorder needs from the scene it records for. */\nexport interface RecorderEnv {\n notifier: Notifier;\n mode: EditMode;\n /** The scene's own undo/redo engine. */\n engine: History;\n /** Apply a registered op's forward half. */\n runOp(kind: string, payload: unknown): void;\n /** A registered op as the engine holds it. */\n makeOp(kind: string, payload: unknown): Op;\n log: AppliedLog;\n arrivals: Pick<ArrivalWindow<unknown>, 'bareLayoutEdit' | 'open' | 'settle' | 'emitReflow' | 'abandon'>;\n selection(): readonly NodeId[];\n /** Write one pose an arrival settle decided. */\n writePose(id: string, pose: unknown): void;\n /** Run `fn` as an edit made by a scene call labeled `label`. */\n labeled<T>(label: string, via: 'call', fn: () => T): T;\n}\n\nexport interface Recorder {\n /** Apply a registered op as part of whatever edit is running. */\n record(kind: string, payload: unknown, label: string): void;\n /** Run the recording half of a mutation. One that touches a laid-out\n * container, called bare, runs as a batch of its own: that is what puts\n * the arrangement it triggers in the same undo step. */\n edit<T>(label: string, touchesLayout: boolean, fn: () => T): T;\n batch<T>(label: string, fn: () => T): T;\n untracked<T>(fn: () => T): T;\n /** Whether an edit made now would join an open `batch`'s entry. */\n joins(): boolean;\n /** Apply ops built outside the scene as part of the open `batch`, all or\n * none. Its entry holds the kit ops they cause, and beside them, bound to\n * `adapter`, any op that wrote no scene state. */\n join(ops: Op[], adapter: unknown): void;\n /** Run `step`, and answer with the kit ops applied during it. */\n capturing<T>(step: () => T): { result: T; ops: Op[] };\n /** Run `fn` as a history engine's step: the ops it applies may re-enter\n * scene mutation methods, which apply but do not record. The caller\n * notifies once after. */\n engineDriven<T>(fn: () => T): T;\n}\n\n/** Take back ops that wrote outside the scene, newest first. The scene's own\n * writes are reverted by the applied log. */\nfunction unwind(outer: readonly Op[]): void {\n for (let i = outer.length - 1; i >= 0; i--) {\n try {\n outer[i].invert().apply(undefined);\n } catch {\n // The throw being unwound is the one the caller sees.\n }\n }\n}\n\nexport function createRecorder(env: RecorderEnv): Recorder {\n const { notifier, mode, engine, log, arrivals } = env;\n /** The outermost open batch, while it is recording. */\n let collecting: { label: string; ops: Op[]; outer: Op[]; selectionBefore: readonly NodeId[] } | null = null;\n /** The kit ops applied since the innermost `capturing` opened. */\n let captured: Op[] | null = null;\n\n /** `fn`, then the poses its arrivals settle to when this edit owns them. */\n function withArrivalsSettled<T>(fn: () => T, owns: boolean): T {\n const result = fn();\n if (owns) for (const p of arrivals.settle()) env.writePose(p.id, p.to);\n return result;\n }\n\n function batch<T>(label: string, fn: () => T): T {\n const outermost = !mode.open();\n // Captured at open, not at recordEntry: by the time the batch closes\n // the selection has already moved to wherever the batch put it.\n if (outermost) collecting = { label, ops: [], outer: [], selectionBefore: env.selection() };\n const ownsArrivals = outermost && arrivals.open(true);\n return env.labeled(label, 'call', () => notifier.deferred(() => mode.during('batch', () => {\n if (!outermost) return fn();\n try {\n const result = log.atomically(\n () => withArrivalsSettled(fn, ownsArrivals),\n () => (collecting?.ops.length ?? 0) > 0,\n );\n if (ownsArrivals) arrivals.emitReflow();\n return result;\n } catch (err) {\n // Reverted already; the half-built entry must not reach history.\n unwind(collecting?.outer ?? []);\n collecting = null;\n throw err;\n }\n }), () => {\n if (ownsArrivals) arrivals.abandon();\n if (!outermost || !collecting) return;\n const finished = collecting;\n collecting = null;\n // The ops were applied as they were issued, so the entry is pushed\n // without re-applying.\n if (finished.ops.length > 0) {\n engine.recordEntry(finished.ops, finished.label, { selectionBefore: finished.selectionBefore });\n }\n }));\n }\n\n return {\n record(kind, payload, label) {\n env.labeled(label, 'call', () => {\n if (mode.records() && !collecting) {\n // The engine applies the op itself, which is what lets it coalesce.\n mode.during('own', () => engine.applyOps([env.makeOp(kind, payload)], label));\n } else {\n env.runOp(kind, payload);\n log.logApplied(kind, payload);\n if (mode.records()) collecting!.ops.push(env.makeOp(kind, payload));\n else if (!mode.untracked()) captured?.push(env.makeOp(kind, payload));\n }\n });\n notifier.notify();\n },\n edit: (label, touchesLayout, fn) => (touchesLayout && arrivals.bareLayoutEdit() ? batch(label, fn) : fn()),\n batch,\n untracked(fn) {\n const versionBefore = notifier.version();\n const ownsArrivals = arrivals.open(true);\n return notifier.deferred(() => mode.during('untracked', () => {\n const result = log.atomically(() => withArrivalsSettled(fn, ownsArrivals), () => false);\n if (ownsArrivals) arrivals.emitReflow();\n return result;\n }), () => {\n if (ownsArrivals) arrivals.abandon();\n if (notifier.version() !== versionBefore) engine.seal();\n });\n },\n joins: () => mode.records() && collecting !== null,\n join(ops, adapter) {\n const into = collecting!;\n const mark = into.ops.length;\n const outer: Op[] = [];\n try {\n log.atomically(() => {\n for (const op of ops) {\n const before = into.ops.length;\n const result = op.apply(adapter);\n if (into.ops.length > before || result === false || result === 'noop') continue;\n outer.push(bindOpToAdapter(op, adapter));\n into.ops.push(outer[outer.length - 1]);\n }\n }, () => false);\n } catch (err) {\n unwind(outer);\n into.ops.length = mark;\n throw err;\n }\n into.outer.push(...outer);\n },\n capturing(step) {\n const enclosing = captured;\n const ops: Op[] = [];\n captured = ops;\n try {\n return { result: step(), ops };\n } finally {\n captured = enclosing;\n }\n },\n engineDriven: (fn) => mode.during('engine', () => notifier.muted(fn)),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"recorder.js","names":[],"sources":["../../../src/core/scene/recorder.ts"],"sourcesContent":["/**\n * How a scene mutation becomes an undo entry: recorded on its own, collected\n * into the batch it runs in, or left to the engine or the untracked run that\n * is driving it.\n */\nimport type { History, RecordEntryOptions } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport type { AppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { bindOpToAdapter } from './boundOps';\nimport type { EditMode } from './editMode';\nimport type { EditPasses } from './editPass';\nimport type { Notifier } from './notifier';\nimport type { NodeId } from './types';\n\n/** What the recorder needs from the scene it records for. */\nexport interface RecorderEnv {\n notifier: Notifier;\n mode: EditMode;\n /** The scene's own undo/redo engine. */\n engine: History;\n /** Apply a registered op's forward half. */\n runOp(kind: string, payload: unknown): void;\n /** A registered op as the engine holds it. */\n makeOp(kind: string, payload: unknown): Op;\n log: AppliedLog;\n arrivals: Pick<ArrivalWindow<unknown>, 'bareLayoutEdit' | 'open' | 'settle' | 'emitReflow' | 'abandon'>;\n passes: Pick<EditPasses, 'any' | 'open' | 'note' | 'run' | 'abandon'>;\n selection(): readonly NodeId[];\n /** Write one pose an arrival settle decided. */\n writePose(id: string, pose: unknown): void;\n /** Run `fn` as an edit made by a scene call labeled `label`. */\n labeled<T>(label: string, via: 'call', fn: () => T): T;\n}\n\n/** Options for `scene.batch`. */\nexport interface SceneBatchOptions {\n /** Fold this batch's entry into the one before it when that one carries the same key and\n * nothing came between: one undo entry for a run of batches, such as a color dragged. */\n coalesceKey?: string;\n}\n\nexport interface Recorder {\n /** Apply a registered op as part of whatever edit is running. */\n record(kind: string, payload: unknown, label: string): void;\n /** Run the recording half of a mutation. One that touches a laid-out\n * container, called bare, runs as a batch of its own: that is what puts\n * the arrangement it triggers in the same undo step. */\n edit<T>(label: string, touchesLayout: boolean, fn: () => T): T;\n batch<T>(label: string, fn: () => T, options?: SceneBatchOptions): T;\n untracked<T>(fn: () => T): T;\n /** Whether an edit made now would join an open `batch`'s entry. */\n joins(): boolean;\n /** Apply ops built outside the scene as part of the open `batch`, all or\n * none. Its entry holds the kit ops they cause, and beside them, bound to\n * `adapter`, any op that wrote no scene state. */\n join(ops: Op[], adapter: unknown): void;\n /** Run `step`, and answer with the kit ops applied during it. */\n capturing<T>(step: () => T): { result: T; ops: Op[] };\n /** Run `fn` as a history engine's step: the ops it applies may re-enter\n * scene mutation methods, which apply but do not record. The caller\n * notifies once after. */\n engineDriven<T>(fn: () => T): T;\n}\n\n/** Take back ops that wrote outside the scene, newest first. The scene's own\n * writes are reverted by the applied log. */\nfunction unwind(outer: readonly Op[]): void {\n for (let i = outer.length - 1; i >= 0; i--) {\n try {\n outer[i].invert().apply(undefined);\n } catch {\n // The throw being unwound is the one the caller sees.\n }\n }\n}\n\nexport function createRecorder(env: RecorderEnv): Recorder {\n const { notifier, mode, engine, log, arrivals, passes } = env;\n /** The outermost open batch, while it is recording. */\n let collecting: { label: string; ops: Op[]; outer: Op[]; entry: RecordEntryOptions } | null = null;\n /** The kit ops applied since the innermost `capturing` opened. */\n let captured: Op[] | null = null;\n\n /** `fn`, then the poses its arrivals settle to and its passes' ops, for whichever this edit owns. */\n function withArrivalsSettled<T>(fn: () => T, owns: boolean, ownsPasses = false): T {\n const result = fn();\n if (owns) for (const p of arrivals.settle()) env.writePose(p.id, p.to);\n if (ownsPasses) passes.run();\n return result;\n }\n\n function batch<T>(label: string, fn: () => T, options: SceneBatchOptions = {}): T {\n const outermost = !mode.open();\n // Captured at open, not at recordEntry: by the time the batch closes\n // the selection has already moved to wherever the batch put it.\n if (outermost) collecting = { label, ops: [], outer: [], entry: { selectionBefore: env.selection(), ...options } };\n const ownsArrivals = outermost && arrivals.open(true);\n const ownsPasses = outermost && passes.open();\n return env.labeled(label, 'call', () => notifier.deferred(() => mode.during('batch', () => {\n if (!outermost) return fn();\n try {\n const result = log.atomically(\n () => withArrivalsSettled(fn, ownsArrivals, ownsPasses),\n () => (collecting?.ops.length ?? 0) > 0,\n );\n if (ownsArrivals) arrivals.emitReflow();\n return result;\n } catch (err) {\n // Reverted already; the half-built entry must not reach history.\n unwind(collecting?.outer ?? []);\n collecting = null;\n throw err;\n }\n }), () => {\n if (ownsArrivals) arrivals.abandon();\n if (ownsPasses) passes.abandon();\n if (!outermost || !collecting) return;\n const finished = collecting;\n collecting = null;\n // The ops were applied as they were issued, so the entry is pushed\n // without re-applying.\n if (finished.ops.length > 0) {\n engine.recordEntry(finished.ops, finished.label, finished.entry);\n }\n }));\n }\n\n function record(kind: string, payload: unknown, label: string): void {\n // While passes listen, a bare edit is a batch of its own, so their ops land in its entry.\n if (mode.records() && !collecting && passes.any()) return void batch(label, () => record(kind, payload, label));\n passes.note(kind, payload);\n env.labeled(label, 'call', () => {\n if (mode.records() && !collecting) {\n // The engine applies the op itself, which is what lets it coalesce.\n mode.during('own', () => engine.applyOps([env.makeOp(kind, payload)], label));\n } else {\n env.runOp(kind, payload);\n log.logApplied(kind, payload);\n if (mode.records()) collecting!.ops.push(env.makeOp(kind, payload));\n else if (!mode.untracked()) captured?.push(env.makeOp(kind, payload));\n }\n });\n notifier.notify();\n }\n\n return {\n record,\n edit: (label, touchesLayout, fn) => (touchesLayout && arrivals.bareLayoutEdit() ? batch(label, fn) : fn()),\n batch,\n untracked(fn) {\n const versionBefore = notifier.version();\n const ownsArrivals = arrivals.open(true);\n return notifier.deferred(() => mode.during('untracked', () => {\n const result = log.atomically(() => withArrivalsSettled(fn, ownsArrivals), () => false);\n if (ownsArrivals) arrivals.emitReflow();\n return result;\n }), () => {\n if (ownsArrivals) arrivals.abandon();\n if (notifier.version() !== versionBefore) engine.seal();\n });\n },\n joins: () => mode.records() && collecting !== null,\n join(ops, adapter) {\n const into = collecting!;\n const mark = into.ops.length;\n const outer: Op[] = [];\n try {\n log.atomically(() => {\n for (const op of ops) {\n const before = into.ops.length;\n const result = op.apply(adapter);\n if (into.ops.length > before || result === false || result === 'noop') continue;\n outer.push(bindOpToAdapter(op, adapter));\n into.ops.push(outer[outer.length - 1]);\n }\n }, () => false);\n } catch (err) {\n unwind(outer);\n into.ops.length = mark;\n throw err;\n }\n into.outer.push(...outer);\n },\n capturing(step) {\n const enclosing = captured;\n const ops: Op[] = [];\n captured = ops;\n try {\n return { result: step(), ops };\n } finally {\n captured = enclosing;\n }\n },\n engineDriven: (fn) => mode.during('engine', () => notifier.muted(fn)),\n };\n}\n"],"mappings":"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"}
@@ -1,10 +1,12 @@
1
1
  import { IDENTITY_POSE_COMPOSITION } from "./composeFrame.js";
2
2
  import { effectivePose } from "./effectivePose.js";
3
3
  import { asNodeId } from "./types.js";
4
+ import { documentReads, sceneMutators } from "./sceneMutators.js";
4
5
  import { withKitRegistry } from "./kitRegistry.js";
5
6
  import { createPoseOverrides } from "./poseOverrides.js";
6
7
  import { createArrivalWindow } from "./arrivalWindow.js";
7
8
  import { createNotifier } from "./notifier.js";
9
+ import { sceneChanges } from "./sceneChanges.js";
8
10
  import { createPoseWriteLog } from "./poseWrites.js";
9
11
  import { createEditMode } from "./editMode.js";
10
12
  import { resolveLayoutFrame, runLayoutPass } from "./layoutPass.js";
@@ -30,14 +32,15 @@ var defaultGenerateId = () => asNodeId(`n${(defaultIdCounter++).toString(36)}-${
30
32
  * this in tests, in headless rendering, or wherever a scene must outlive a
31
33
  * component. Throws if no system layers are declared. */
32
34
  function createScene(options) {
33
- return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial);
35
+ return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);
34
36
  }
35
- /** A scene holding `layers`, and the nodes `initialNodes` answers with once
36
- * it is handed the registry their functions resolve against. */
37
- function buildScene(options, layers, initialNodes) {
37
+ /** A scene holding `layers`, the document values `initialDoc`, and the nodes
38
+ * `initialNodes` answers with once it is handed the registry their functions
39
+ * resolve against. */
40
+ function buildScene(options, layers, initialNodes, initialDoc) {
38
41
  if (layers.length === 0) throw new Error("Scene: systemLayers must declare at least one layer");
39
42
  const registry = withKitRegistry(options.registry ?? {});
40
- const doc = createSceneState(registry, layers, () => arrivals.layoutDeclared());
43
+ const doc = createSceneState(registry, layers, () => arrivals.layoutDeclared(), initialDoc);
41
44
  const { state, locks, reads } = doc;
42
45
  const notifier = createNotifier();
43
46
  const mode = createEditMode();
@@ -63,6 +66,10 @@ function buildScene(options, layers, initialNodes) {
63
66
  const recording = createSceneRecording({
64
67
  notifier,
65
68
  mode,
69
+ passAdapter: () => ({
70
+ ...sceneMutators(scene),
71
+ ...documentReads(scene)
72
+ }),
66
73
  stale: doc.stale,
67
74
  lifted: locks.lifted,
68
75
  selection,
@@ -117,14 +124,13 @@ function buildScene(options, layers, initialNodes) {
117
124
  const specs = specsFromSerialized(json, registry);
118
125
  if (mode.open()) throw new Error("Scene: cannot loadState inside another edit — it replaces the state that edit would revert to");
119
126
  overrides.clearAll();
120
- doc.reset(json.systemLayers.map(layerFromSerialized));
127
+ doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);
121
128
  recording.clearHistory();
122
129
  poseWrites.quiet(() => edits.construct(specs));
123
130
  selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));
124
131
  notifier.notify();
125
132
  },
126
- subscribe: notifier.subscribe,
127
- getVersion: notifier.version
133
+ ...sceneChanges(notifier, selection)
128
134
  };
129
135
  const initial = initialNodes(registry);
130
136
  if (initial) {
@@ -139,7 +145,7 @@ function buildScene(options, layers, initialNodes) {
139
145
  * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).
140
146
  * The scene starts with empty history. */
141
147
  function sceneFromJSON(json, options = {}) {
142
- return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry));
148
+ return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry), json.doc);
143
149
  }
144
150
 
145
151
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { createNotifier } from './notifier';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial'>;\n\n/** A scene holding `layers`, and the nodes `initialNodes` answers with once\n * it is handed the registry their functions resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared());\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void =>\n poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized));\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n subscribe: notifier.subscribe,\n getVersion: notifier.version,\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,SAAS,UAA+C,OAAoC,IAAY;CACtG,MAAM,OAAO,MAAM,IAAI,EAAE;CACzB,OAAO,QAAQ;EAAE;EAAM,MAAM,cAAc,OAAO,IAAI;CAAE;AAC1D;AAEA,IAAI,mBAAmB;AACvB,IAAM,0BACJ,SAAS,KAAK,mBAAA,CAAoB,SAAS,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG;;;;AAK5F,SAAgB,YACd,SAC6B;CAC7B,OAAO,WAAW,UAAU,QAAQ,gBAAgB,CAAC,EAAA,CAAG,IAAI,iBAAiB,SAAS,QAAQ,OAAO;AACvG;;;AAOA,SAAS,WACP,SACA,QACA,cAC6B;CAC7B,IAAI,OAAO,WAAW,GAAG,MAAM,IAAI,MAAM,qDAAqD;CAK9F,MAAM,WAAW,gBAAgB,QAAQ,YAAY,CAAC,CAAC;CAEvD,MAAM,MAAM,iBAAuC,UAAU,cAAc,SAAS,eAAe,CAAC;CACpG,MAAM,EAAE,OAAO,OAAO,UAAU;CAChC,MAAM,WAAW,eAAe;CAChC,MAAM,OAAO,eAAe;CAC5B,MAAM,aAAa,mBAAmB;EAAE,WAAW,KAAK;EAAW,SAAS;EAAU,QAAQ,OAAO,UAAU,OAAO,EAAE;CAAE,CAAC;CAC3H,MAAM,aAAa,IAAY,SAC7B,WAAW,aAAa,MAAM,QAAQ,SAAS,EAAE,GAAG,IAAa,CAAC;CAEpE,MAAM,cAAc,mBAAmB,QAAQ,WAAW;CAC1D,MAAM,WAAW,oBAA0C;EACzD,OAAO,MAAM;EACb,aAAa,SAAS,SAAS,aAC7B,cAAc,OAAO,MAAM,UAAU,SAAS,SAAS,aAAa,QAAQ;EAC9E,wBAAwB,CAAC,KAAK,KAAK;EACnC;CACF,CAAC;CAED,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,YAAY,qBAAqB,MAAM,SAAS;CAEtD,UAAU,UAAU,SAAS,MAAM;;;CAInC,MAAM,YAAY,qBAA4B,OAAO,MAAM,MAAM,IAAI,EAAE,GAAG,IAAI,MAAM,oBAAoB;CAExG,MAAM,YAAY,qBAAqB;EACrC;EACA;EACA,OAAO,IAAI;EACX,QAAQ,MAAM;EACd;EACA;EACA;EACA;EACA,QAAQ;CACV,CAAC;CAED,MAAM,QAAQ,iBAAuC;EACnD;EACA,OAAO,IAAI;EACX;EACA,QAAQ,IAAI;EACZ,iBAAiB,IAAI,MAAM;EAC3B,UAAU,IAAI;EACd,KAAK;GAAE,GAAG,UAAU;GAAK,UAAU,WAAW,UAAU,UAAU,IAAI,QAAQ;EAAE;EAChF,UAAU,UAAU;EACpB;EACA;EACA;EACA,iBAAiB,UAAU;EAC3B,eAAe,UAAU;CAC3B,CAAC;CACD,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,QAAQ,OAAO,CAAC,CAAC,GAAG,UAAU,IAAI,SAAS,MAAM,OAAO;CAErG,MAAM,QAAqC;EACzC,IAAI,QAAQ;GAAE,OAAO,MAAM;EAAO;EAClC,IAAI,QAAQ;GAAE,OAAO,MAAM;EAAO;EAClC,IAAI,SAAS;GAAE,OAAO,MAAM;EAAQ;EACpC;EAEA,GAAG;EAEH,QAAQ;EACR,aAAa,QAAQ,eAAgB;EAErC,GAAG,MAAM;EACT,GAAG,UAAU;EAEb,WAAW,OAAO,MAAM,OAAO,EAAE;EAEjC,mBAAmB,SAAS;EAC5B,UAAU,SAAS;EACnB,aAAa,WAAW;EAExB;EAEA,cAAc,UAAU;EACxB,eAAe,QAAQ,UAAU,IAAI,CAAC,GAAG,GAAG,CAAC;EAE7C,UAAU,MAAM;GAGd,MAAM,QAAQ,oBAAoB,MAAM,QAAQ;GAChD,IAAI,KAAK,KAAK,GACZ,MAAM,IAAI,MAAM,+FAA+F;GAGjH,UAAU,SAAS;GACnB,IAAI,MAAM,KAAK,aAAa,IAAI,mBAAmB,CAAC;GACpD,UAAU,aAAa;GACvB,WAAW,YAAY,MAAM,UAAU,KAAK,CAAC;GAC7C,UAAU,QAAQ,MAAM,UAAU,UAAU,IAAI,CAAC,CAAC,QAAQ,OAAO,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;GACtF,SAAS,OAAO;EAClB;EAEA,WAAW,SAAS;EACpB,YAAY,SAAS;CACvB;CAEA,MAAM,UAAU,aAAa,QAAQ;CACrC,IAAI,SAAS;EACX,WAAW,YAAY,MAAM,UAAU,OAAO,CAAC;EAC/C,SAAS,OAAO;CAClB;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,cACd,MACA,UAQI,CAAC,GACwB;CAC7B,OAAO,WACL,SACA,KAAK,aAAa,IAAI,mBAAmB,IACxC,aAAa,oBAAoB,MAAM,QAAQ,CAClD;AACF"}
1
+ {"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { documentReads, sceneMutators } from './sceneMutators';\nimport { createNotifier } from './notifier';\nimport type { SerializedDoc } from './docValues';\nimport { sceneChanges } from './sceneChanges';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial' | 'doc'>;\n\n/** A scene holding `layers`, the document values `initialDoc`, and the nodes\n * `initialNodes` answers with once it is handed the registry their functions\n * resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n initialDoc: SerializedDoc | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared(), initialDoc);\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void => poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n passAdapter: () => ({ ...sceneMutators(scene), ...documentReads(scene) }),\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n ...sceneChanges(notifier, selection),\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry),\n json.doc,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region src/core/scene/sceneChanges.ts
2
+ /** A scene's change members, over its notifier and its selection's own count of changes. */
3
+ function sceneChanges(notifier, selection) {
4
+ return {
5
+ subscribe: notifier.subscribe,
6
+ getVersion: notifier.version,
7
+ getContentVersion: () => notifier.version() - selection.changes()
8
+ };
9
+ }
10
+
11
+ //#endregion
12
+ export { sceneChanges };
13
+ //# sourceMappingURL=sceneChanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sceneChanges.js","names":[],"sources":["../../../src/core/scene/sceneChanges.ts"],"sourcesContent":["/** How a scene tells its readers it changed. */\nimport type { Notifier } from './notifier';\n\nexport interface SceneChanges {\n /** Hear every change, a change of selection included. Returns the unsubscribe. */\n subscribe(listener: () => void): () => void;\n /** Monotonically increasing version. Snapshot for `useSyncExternalStore`. */\n getVersion(): number;\n /** {@link SceneChanges.getVersion} less the changes that only moved the selection: unmoved\n * across a notice that changed nothing else. */\n getContentVersion(): number;\n}\n\n/** A scene's change members, over its notifier and its selection's own count of changes. */\nexport function sceneChanges(notifier: Notifier, selection: { changes(): number }): SceneChanges {\n return {\n subscribe: notifier.subscribe,\n getVersion: notifier.version,\n getContentVersion: () => notifier.version() - selection.changes(),\n };\n}\n"],"mappings":";;AAcA,SAAgB,aAAa,UAAoB,WAAgD;CAC/F,OAAO;EACL,WAAW,SAAS;EACpB,YAAY,SAAS;EACrB,yBAAyB,SAAS,QAAQ,IAAI,UAAU,QAAQ;CAClE;AACF"}
@@ -1,3 +1,4 @@
1
+ import { createSceneDoc } from "./docValues.js";
1
2
  import { createFieldEdits } from "./fieldEdits.js";
2
3
  import { registerKitOps } from "./kitOps.js";
3
4
  import { createNodeFlags } from "./nodeFlags.js";
@@ -55,13 +56,19 @@ function createSceneEdits(env) {
55
56
  record: recorder.record,
56
57
  narrowSelection: () => env.narrowSelection()
57
58
  });
59
+ const doc = createSceneDoc({
60
+ values: state.docValues,
61
+ register: env.ops.register,
62
+ record: recorder.record
63
+ });
58
64
  return {
59
65
  construct,
60
66
  methods: {
61
67
  ...treeEdits,
62
68
  ...fieldEdits,
63
69
  ...layerEdits,
64
- ...flagEdits
70
+ ...flagEdits,
71
+ doc
65
72
  }
66
73
  };
67
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, and layers — with\n * the kit ops they record.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { createFieldEdits, type FieldEdits } from './fieldEdits';\nimport { registerKitOps } from './kitOps';\nimport { createNodeFlags } from './nodeFlags';\nimport { createLayerEdits, type LayerEdits, type createLayerStack } from './layerStack';\nimport type { ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { OpTable } from './opTable';\nimport type { Recorder } from './recorder';\nimport { createTreeEdits, type TreeEdits } from './treeEdits';\nimport type { LayerRecord, Node, NodeId } from './types';\n\nexport interface SceneEditsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n layerIndex: Map<TLayer, number>;\n };\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<\n ReturnType<typeof createLayerStack<TLayer>>,\n 'requireLayerIndex' | 'rebuildLayerIndex' | 'assertSubtreeLayer'\n >;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n generateId(): NodeId;\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n clearOverride(id: NodeId): void;\n}\n\nexport interface SceneEdits<TData, TLayer extends string, TPose> {\n construct: TreeEdits<TData, TLayer, TPose>['construct'];\n methods: Omit<TreeEdits<TData, TLayer, TPose>, 'construct'>\n & FieldEdits<TData, TLayer, TPose>\n & LayerEdits<TData, TLayer, TPose>\n & ReturnType<typeof createNodeFlags<TData, TLayer, TPose>>;\n}\n\nexport function createSceneEdits<TData, TLayer extends string, TPose>(\n env: SceneEditsEnv<TData, TLayer, TPose>,\n): SceneEdits<TData, TLayer, TPose> {\n const { state, store, locks, layers, fnFields, recorder, arrivals } = env;\n\n const { layoutRefs } = registerKitOps<TData, TLayer, TPose>({\n state,\n register: env.ops.register,\n store,\n requireLayerIndex: layers.requireLayerIndex,\n rebuildLayerIndex: layers.rebuildLayerIndex,\n invalidateOrder: env.invalidateOrder,\n fnFields,\n narrowSelection: env.narrowSelection,\n clearOverride: env.clearOverride,\n });\n\n const { construct, ...treeEdits } = createTreeEdits<TData, TLayer, TPose>({\n nodes: state.nodes,\n generateId: env.generateId,\n store,\n locks,\n layers,\n fnFields,\n runOp: env.ops.run,\n recorder,\n arrivals,\n });\n\n const fieldEdits = createFieldEdits<TData, TLayer, TPose>({\n store,\n locks,\n layers,\n recorder,\n arrivals,\n layoutFrame: env.layoutFrame,\n layoutField: fnFields.find((f) => f.spec.field === 'layout')!,\n layoutRefs,\n });\n\n const layerEdits = createLayerEdits<TData, TLayer, TPose>({\n layers: state.layers,\n layerIndex: state.layerIndex,\n nodes: state.nodes,\n requireLayerIndex: layers.requireLayerIndex,\n record: recorder.record,\n lifted: locks.lifted,\n batch: recorder.batch,\n removeMany: treeEdits.removeMany,\n });\n\n const flagEdits = createNodeFlags<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n record: recorder.record,\n narrowSelection: () => env.narrowSelection(),\n });\n\n return { construct, methods: { ...treeEdits, ...fieldEdits, ...layerEdits, ...flagEdits } };\n}\n"],"mappings":";;;;;;;AAiDA,SAAgB,iBACd,KACkC;CAClC,MAAM,EAAE,OAAO,OAAO,OAAO,QAAQ,UAAU,UAAU,aAAa;CAEtE,MAAM,EAAE,eAAe,eAAqC;EAC1D;EACA,UAAU,IAAI,IAAI;EAClB;EACA,mBAAmB,OAAO;EAC1B,mBAAmB,OAAO;EAC1B,iBAAiB,IAAI;EACrB;EACA,iBAAiB,IAAI;EACrB,eAAe,IAAI;CACrB,CAAC;CAED,MAAM,EAAE,WAAW,GAAG,cAAc,gBAAsC;EACxE,OAAO,MAAM;EACb,YAAY,IAAI;EAChB;EACA;EACA;EACA;EACA,OAAO,IAAI,IAAI;EACf;EACA;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD;EACA;EACA;EACA;EACA;EACA,aAAa,IAAI;EACjB,aAAa,SAAS,MAAM,MAAM,EAAE,KAAK,UAAU,QAAQ;EAC3D;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,mBAAmB,OAAO;EAC1B,QAAQ,SAAS;EACjB,QAAQ,MAAM;EACd,OAAO,SAAS;EAChB,YAAY,UAAU;CACxB,CAAC;CAED,MAAM,YAAY,gBAAsC;EACtD,UAAU,IAAI,IAAI;EAClB,aAAa,MAAM;EACnB,QAAQ,SAAS;EACjB,uBAAuB,IAAI,gBAAgB;CAC7C,CAAC;CAED,OAAO;EAAE;EAAW,SAAS;GAAE,GAAG;GAAW,GAAG;GAAY,GAAG;GAAY,GAAG;EAAU;CAAE;AAC5F"}
1
+ {"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, layers, and document values — with\n * the kit ops they record.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { createSceneDoc, type SceneDocValues } from './docValues';\nimport { createFieldEdits, type FieldEdits } from './fieldEdits';\nimport { registerKitOps } from './kitOps';\nimport { createNodeFlags } from './nodeFlags';\nimport { createLayerEdits, type LayerEdits, type createLayerStack } from './layerStack';\nimport type { ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { OpTable } from './opTable';\nimport type { Recorder } from './recorder';\nimport { createTreeEdits, type TreeEdits } from './treeEdits';\nimport type { LayerRecord, Node, NodeId } from './types';\n\nexport interface SceneEditsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n layerIndex: Map<TLayer, number>;\n /** The scene's document values, which `doc` reads and records. */\n docValues: Map<string, unknown>;\n };\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<\n ReturnType<typeof createLayerStack<TLayer>>,\n 'requireLayerIndex' | 'rebuildLayerIndex' | 'assertSubtreeLayer'\n >;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n generateId(): NodeId;\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n clearOverride(id: NodeId): void;\n}\n\nexport interface SceneEdits<TData, TLayer extends string, TPose> {\n construct: TreeEdits<TData, TLayer, TPose>['construct'];\n methods: Omit<TreeEdits<TData, TLayer, TPose>, 'construct'>\n & FieldEdits<TData, TLayer, TPose>\n & LayerEdits<TData, TLayer, TPose>\n & ReturnType<typeof createNodeFlags<TData, TLayer, TPose>>\n & SceneDocValues;\n}\n\nexport function createSceneEdits<TData, TLayer extends string, TPose>(\n env: SceneEditsEnv<TData, TLayer, TPose>,\n): SceneEdits<TData, TLayer, TPose> {\n const { state, store, locks, layers, fnFields, recorder, arrivals } = env;\n\n const { layoutRefs } = registerKitOps<TData, TLayer, TPose>({\n state,\n register: env.ops.register,\n store,\n requireLayerIndex: layers.requireLayerIndex,\n rebuildLayerIndex: layers.rebuildLayerIndex,\n invalidateOrder: env.invalidateOrder,\n fnFields,\n narrowSelection: env.narrowSelection,\n clearOverride: env.clearOverride,\n });\n\n const { construct, ...treeEdits } = createTreeEdits<TData, TLayer, TPose>({\n nodes: state.nodes,\n generateId: env.generateId,\n store,\n locks,\n layers,\n fnFields,\n runOp: env.ops.run,\n recorder,\n arrivals,\n });\n\n const fieldEdits = createFieldEdits<TData, TLayer, TPose>({\n store,\n locks,\n layers,\n recorder,\n arrivals,\n layoutFrame: env.layoutFrame,\n layoutField: fnFields.find((f) => f.spec.field === 'layout')!,\n layoutRefs,\n });\n\n const layerEdits = createLayerEdits<TData, TLayer, TPose>({\n layers: state.layers,\n layerIndex: state.layerIndex,\n nodes: state.nodes,\n requireLayerIndex: layers.requireLayerIndex,\n record: recorder.record,\n lifted: locks.lifted,\n batch: recorder.batch,\n removeMany: treeEdits.removeMany,\n });\n\n const flagEdits = createNodeFlags<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n record: recorder.record,\n narrowSelection: () => env.narrowSelection(),\n });\n\n const doc = createSceneDoc({ values: state.docValues, register: env.ops.register, record: recorder.record });\n\n return { construct, methods: { ...treeEdits, ...fieldEdits, ...layerEdits, ...flagEdits, doc } };\n}\n"],"mappings":";;;;;;;;AAqDA,SAAgB,iBACd,KACkC;CAClC,MAAM,EAAE,OAAO,OAAO,OAAO,QAAQ,UAAU,UAAU,aAAa;CAEtE,MAAM,EAAE,eAAe,eAAqC;EAC1D;EACA,UAAU,IAAI,IAAI;EAClB;EACA,mBAAmB,OAAO;EAC1B,mBAAmB,OAAO;EAC1B,iBAAiB,IAAI;EACrB;EACA,iBAAiB,IAAI;EACrB,eAAe,IAAI;CACrB,CAAC;CAED,MAAM,EAAE,WAAW,GAAG,cAAc,gBAAsC;EACxE,OAAO,MAAM;EACb,YAAY,IAAI;EAChB;EACA;EACA;EACA;EACA,OAAO,IAAI,IAAI;EACf;EACA;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD;EACA;EACA;EACA;EACA;EACA,aAAa,IAAI;EACjB,aAAa,SAAS,MAAM,MAAM,EAAE,KAAK,UAAU,QAAQ;EAC3D;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,mBAAmB,OAAO;EAC1B,QAAQ,SAAS;EACjB,QAAQ,MAAM;EACd,OAAO,SAAS;EAChB,YAAY,UAAU;CACxB,CAAC;CAED,MAAM,YAAY,gBAAsC;EACtD,UAAU,IAAI,IAAI;EAClB,aAAa,MAAM;EACnB,QAAQ,SAAS;EACjB,uBAAuB,IAAI,gBAAgB;CAC7C,CAAC;CAED,MAAM,MAAM,eAAe;EAAE,QAAQ,MAAM;EAAW,UAAU,IAAI,IAAI;EAAU,QAAQ,SAAS;CAAO,CAAC;CAE3G,OAAO;EAAE;EAAW,SAAS;GAAE,GAAG;GAAW,GAAG;GAAY,GAAG;GAAY,GAAG;GAAW;EAAI;CAAE;AACjG"}