@weasel-js/core 1.11.1 → 1.13.0-rc.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 (488) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/dist/WeaselProvider.js +1 -1
  3. package/dist/affordances/pathAnchors.js +10 -6
  4. package/dist/affordances/pathAnchors.js.map +1 -1
  5. package/dist/animation/poseTarget.js +46 -15
  6. package/dist/animation/poseTarget.js.map +1 -1
  7. package/dist/animation/poseTransition.js +3 -3
  8. package/dist/animation/poseTransition.js.map +1 -1
  9. package/dist/animation/reflow.js +3 -6
  10. package/dist/animation/reflow.js.map +1 -1
  11. package/dist/animation/rig/bindRig.js +2 -2
  12. package/dist/animation/rig/bindRig.js.map +1 -1
  13. package/dist/animation/rig/skinBinding.js +2 -2
  14. package/dist/animation/rig/skinBinding.js.map +1 -1
  15. package/dist/barrel/canvas.js +5 -1
  16. package/dist/barrel/chrome.js +3 -1
  17. package/dist/barrel/paint.js +4 -4
  18. package/dist/barrel/paths.js +9 -4
  19. package/dist/barrel/renderer.js +3 -2
  20. package/dist/barrel/routing.js +4 -4
  21. package/dist/barrel/scene.js +7 -1
  22. package/dist/barrel/text.js +2 -2
  23. package/dist/barrel/viewport.js +6 -2
  24. package/dist/canvas/Canvas.js +2 -1
  25. package/dist/canvas/Canvas.js.map +1 -1
  26. package/dist/canvas/CanvasView.js +23 -34
  27. package/dist/canvas/CanvasView.js.map +1 -1
  28. package/dist/canvas/ModalityHud.js +1 -1
  29. package/dist/canvas/NodeShape.js +5 -1
  30. package/dist/canvas/NodeShape.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  32. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  33. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  34. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  35. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  37. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  38. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
  39. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  41. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  43. package/dist/canvas/SceneCanvas/scenePointQuery.js +27 -5
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  45. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  46. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  47. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
  48. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
  49. package/dist/canvas/SceneCanvas/useCanvasTools.js +10 -2
  50. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  51. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  52. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  54. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +17 -2
  56. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  57. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -0
  58. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
  60. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  62. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  64. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +7 -4
  66. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  67. package/dist/canvas/SceneCanvas.js +30 -12
  68. package/dist/canvas/SceneCanvas.js.map +1 -1
  69. package/dist/canvas/ViewMarker.js +35 -0
  70. package/dist/canvas/ViewMarker.js.map +1 -0
  71. package/dist/canvas/accentColor.js +26 -3
  72. package/dist/canvas/accentColor.js.map +1 -1
  73. package/dist/canvas/buildSceneTree.js +14 -20
  74. package/dist/canvas/buildSceneTree.js.map +1 -1
  75. package/dist/canvas/copiesAbove.js +1 -1
  76. package/dist/canvas/copiesAbove.js.map +1 -1
  77. package/dist/canvas/defaultDrawOne.js +11 -6
  78. package/dist/canvas/defaultDrawOne.js.map +1 -1
  79. package/dist/canvas/deps/areaSelect.js +5 -2
  80. package/dist/canvas/deps/areaSelect.js.map +1 -1
  81. package/dist/canvas/deps/booleans.js +3 -2
  82. package/dist/canvas/deps/booleans.js.map +1 -1
  83. package/dist/canvas/deps/enteredGroup.js +81 -0
  84. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  85. package/dist/canvas/deps/hitTestArea.js +28 -16
  86. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  87. package/dist/canvas/deps/insert.js +10 -38
  88. package/dist/canvas/deps/insert.js.map +1 -1
  89. package/dist/canvas/deps/insertSite.js +39 -0
  90. package/dist/canvas/deps/insertSite.js.map +1 -0
  91. package/dist/canvas/deps/lassoSelect.js +6 -3
  92. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  93. package/dist/canvas/deps/pathEditCommits.js +3 -7
  94. package/dist/canvas/deps/pathEditCommits.js.map +1 -1
  95. package/dist/canvas/deps/pathLeaf.js +1 -1
  96. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  97. package/dist/canvas/deps/slice.js +2 -1
  98. package/dist/canvas/deps/slice.js.map +1 -1
  99. package/dist/canvas/derivedPath.js +14 -6
  100. package/dist/canvas/derivedPath.js.map +1 -1
  101. package/dist/canvas/doubleClickTarget.js +9 -11
  102. package/dist/canvas/doubleClickTarget.js.map +1 -1
  103. package/dist/canvas/drawnBounds.js +26 -9
  104. package/dist/canvas/drawnBounds.js.map +1 -1
  105. package/dist/canvas/inheritedPaints.js +55 -0
  106. package/dist/canvas/inheritedPaints.js.map +1 -0
  107. package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
  108. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  109. package/dist/canvas/nodeShapes/nodePaint.js +7 -34
  110. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  111. package/dist/canvas/nodeShapes/pathPainter.js +7 -7
  112. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  114. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  115. package/dist/canvas/nodeShapes/shapePainter.js +9 -9
  116. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  118. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  119. package/dist/canvas/ownClip.js +24 -0
  120. package/dist/canvas/ownClip.js.map +1 -0
  121. package/dist/canvas/paintCull.js.map +1 -1
  122. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  123. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  124. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  125. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  126. package/dist/canvas/pickReach.js +3 -3
  127. package/dist/canvas/pickReach.js.map +1 -1
  128. package/dist/canvas/pickWalk.js +17 -91
  129. package/dist/canvas/pickWalk.js.map +1 -1
  130. package/dist/canvas/poseCascade.js +1 -1
  131. package/dist/canvas/poseCascade.js.map +1 -1
  132. package/dist/canvas/publishPreviews.js +97 -0
  133. package/dist/canvas/publishPreviews.js.map +1 -0
  134. package/dist/canvas/renderSceneToPixels.js +1 -1
  135. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  136. package/dist/canvas/sceneAdapter.js +7 -5
  137. package/dist/canvas/sceneAdapter.js.map +1 -1
  138. package/dist/canvas/sceneClipboard.js +17 -10
  139. package/dist/canvas/sceneClipboard.js.map +1 -1
  140. package/dist/canvas/scenePickReads.js +37 -0
  141. package/dist/canvas/scenePickReads.js.map +1 -0
  142. package/dist/canvas/scenePickSource.js +74 -0
  143. package/dist/canvas/scenePickSource.js.map +1 -0
  144. package/dist/canvas/sceneSlotWiring.js +9 -3
  145. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  146. package/dist/canvas/sceneViewRender.js +9 -4
  147. package/dist/canvas/sceneViewRender.js.map +1 -1
  148. package/dist/canvas/shownNodes.js +15 -0
  149. package/dist/canvas/shownNodes.js.map +1 -0
  150. package/dist/canvas/surfaceContribution.js +12 -8
  151. package/dist/canvas/surfaceContribution.js.map +1 -1
  152. package/dist/canvas/useCanvasView.js +4 -2
  153. package/dist/canvas/useCanvasView.js.map +1 -1
  154. package/dist/canvas/useLayerStack.js +6 -2
  155. package/dist/canvas/useLayerStack.js.map +1 -1
  156. package/dist/canvas/useSceneAdapter.js +5 -3
  157. package/dist/canvas/useSceneAdapter.js.map +1 -1
  158. package/dist/canvas/useViewAim.js +121 -0
  159. package/dist/canvas/useViewAim.js.map +1 -0
  160. package/dist/canvas/useViewNesting.js +3 -2
  161. package/dist/canvas/useViewNesting.js.map +1 -1
  162. package/dist/canvas/useViewRegistrySurface.js +4 -2
  163. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  164. package/dist/canvas/viewMarkers.js +63 -0
  165. package/dist/canvas/viewMarkers.js.map +1 -0
  166. package/dist/canvas/viewRegistry.js +27 -1
  167. package/dist/canvas/viewRegistry.js.map +1 -1
  168. package/dist/clipboard.d.ts +2 -2
  169. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  170. package/dist/clone.d.ts +3 -3
  171. package/dist/core/isPlainObject.js +2 -2
  172. package/dist/core/layers/layerGroup.js.map +1 -1
  173. package/dist/core/pages/pageEdits.js +2 -3
  174. package/dist/core/pages/pageEdits.js.map +1 -1
  175. package/dist/core/paint/meshKindLazy.js +59 -2
  176. package/dist/core/paint/meshKindLazy.js.map +1 -1
  177. package/dist/core/scene/NodeRouting.js.map +1 -1
  178. package/dist/core/scene/arrivalWindow.js +1 -1
  179. package/dist/core/scene/arrivalWindow.js.map +1 -1
  180. package/dist/core/scene/clipNothing.js +10 -0
  181. package/dist/core/scene/clipNothing.js.map +1 -0
  182. package/dist/core/scene/declaredFieldSetters.js +44 -0
  183. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  184. package/dist/core/scene/depMemo.js +14 -3
  185. package/dist/core/scene/depMemo.js.map +1 -1
  186. package/dist/core/scene/dependents.js +9 -1
  187. package/dist/core/scene/dependents.js.map +1 -1
  188. package/dist/core/scene/derivedParts.js +10 -11
  189. package/dist/core/scene/derivedParts.js.map +1 -1
  190. package/dist/core/scene/documentOf.js +43 -0
  191. package/dist/core/scene/documentOf.js.map +1 -0
  192. package/dist/core/scene/effectivePose.js +44 -15
  193. package/dist/core/scene/effectivePose.js.map +1 -1
  194. package/dist/core/scene/fieldEdits.js +17 -14
  195. package/dist/core/scene/fieldEdits.js.map +1 -1
  196. package/dist/core/scene/fontReads.js +37 -0
  197. package/dist/core/scene/fontReads.js.map +1 -0
  198. package/dist/core/scene/historyForm.js +4 -4
  199. package/dist/core/scene/historyForm.js.map +1 -1
  200. package/dist/core/scene/inheritedPaint.js +97 -0
  201. package/dist/core/scene/inheritedPaint.js.map +1 -0
  202. package/dist/core/scene/kitOps.js +3 -23
  203. package/dist/core/scene/kitOps.js.map +1 -1
  204. package/dist/core/scene/kitRegistry.js +5 -0
  205. package/dist/core/scene/kitRegistry.js.map +1 -1
  206. package/dist/core/scene/layoutPass.js +1 -2
  207. package/dist/core/scene/layoutPass.js.map +1 -1
  208. package/dist/core/scene/nodeAsShown.js +72 -0
  209. package/dist/core/scene/nodeAsShown.js.map +1 -0
  210. package/dist/core/scene/nodeFnFields.js +2 -10
  211. package/dist/core/scene/nodeFnFields.js.map +1 -1
  212. package/dist/core/scene/nodeMemo.js +21 -8
  213. package/dist/core/scene/nodeMemo.js.map +1 -1
  214. package/dist/core/scene/nodeStore.js +2 -4
  215. package/dist/core/scene/nodeStore.js.map +1 -1
  216. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  217. package/dist/core/scene/pathEffects/derive.js +10 -4
  218. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  219. package/dist/core/scene/pathEffects/effects.js +3 -1
  220. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  221. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  222. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  223. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  224. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  225. package/dist/core/scene/plyStacks.js +89 -0
  226. package/dist/core/scene/plyStacks.js.map +1 -0
  227. package/dist/core/scene/poseHold.js +42 -24
  228. package/dist/core/scene/poseHold.js.map +1 -1
  229. package/dist/core/scene/poseOverrides.js +35 -38
  230. package/dist/core/scene/poseOverrides.js.map +1 -1
  231. package/dist/core/scene/posePlies.js +59 -0
  232. package/dist/core/scene/posePlies.js.map +1 -0
  233. package/dist/core/scene/reconcileSpecs.js +6 -15
  234. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  235. package/dist/core/scene/renderOrder.js +10 -4
  236. package/dist/core/scene/renderOrder.js.map +1 -1
  237. package/dist/core/scene/sameValue.js +17 -0
  238. package/dist/core/scene/sameValue.js.map +1 -0
  239. package/dist/core/scene/scene.js +3 -3
  240. package/dist/core/scene/scene.js.map +1 -1
  241. package/dist/core/scene/sceneEdits.js +8 -0
  242. package/dist/core/scene/sceneEdits.js.map +1 -1
  243. package/dist/core/scene/sceneState.js +1 -2
  244. package/dist/core/scene/sceneState.js.map +1 -1
  245. package/dist/core/scene/serialization.js +45 -9
  246. package/dist/core/scene/serialization.js.map +1 -1
  247. package/dist/core/scene/staleReplay.js +5 -2
  248. package/dist/core/scene/staleReplay.js.map +1 -1
  249. package/dist/core/scene/statedFields.js +4 -2
  250. package/dist/core/scene/statedFields.js.map +1 -1
  251. package/dist/core/scene/treeEdits.js +2 -4
  252. package/dist/core/scene/treeEdits.js.map +1 -1
  253. package/dist/core/scene/treeOrder.js +3 -1
  254. package/dist/core/scene/treeOrder.js.map +1 -1
  255. package/dist/core/scene/treeReads.js +1 -2
  256. package/dist/core/scene/treeReads.js.map +1 -1
  257. package/dist/core/scene/treeShape.js +13 -0
  258. package/dist/core/scene/treeShape.js.map +1 -0
  259. package/dist/core/scene/types.js.map +1 -1
  260. package/dist/core/scene/useScene.js +1 -1
  261. package/dist/core/scene/useScene.js.map +1 -1
  262. package/dist/{cornerHandles-BHuoMs1o.d.ts → cornerHandles-CNCb7aTY.d.ts} +3 -2
  263. package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-B1RS-IJx.d.ts} +39 -18
  264. package/dist/devtools/handlers.js +1 -1
  265. package/dist/devtools/handlers.js.map +1 -1
  266. package/dist/devtools/protocol.js.map +1 -1
  267. package/dist/devtools/registry.js +4 -4
  268. package/dist/devtools/registry.js.map +1 -1
  269. package/dist/devtools-protocol.d.ts +4 -3
  270. package/dist/devtools.d.ts +3 -2
  271. package/dist/features/measure/measureContribution.js +4 -2
  272. package/dist/features/measure/measureContribution.js.map +1 -1
  273. package/dist/features/measure/measureHit.js +5 -3
  274. package/dist/features/measure/measureHit.js.map +1 -1
  275. package/dist/features/measure/measurement.js +1 -1
  276. package/dist/features/measure/measurement.js.map +1 -1
  277. package/dist/features/modes/modeLayers.js +14 -7
  278. package/dist/features/modes/modeLayers.js.map +1 -1
  279. package/dist/features/modes/modeTriggers.js +10 -6
  280. package/dist/features/modes/modeTriggers.js.map +1 -1
  281. package/dist/features/pathEffects/effectSources.js +69 -0
  282. package/dist/features/pathEffects/effectSources.js.map +1 -0
  283. package/dist/features/pathEffects/readSource.js +18 -2
  284. package/dist/features/pathEffects/readSource.js.map +1 -1
  285. package/dist/features/pathEffects/textItems.js +53 -0
  286. package/dist/features/pathEffects/textItems.js.map +1 -0
  287. package/dist/features/pathEffects/useEffectSourcesLayer.js +53 -0
  288. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  289. package/dist/features/paths/editablePath.js +1 -1
  290. package/dist/features/paths/editablePath.js.map +1 -1
  291. package/dist/features/paths/pathEditState.js +5 -2
  292. package/dist/features/paths/pathEditState.js.map +1 -1
  293. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  294. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  295. package/dist/features/selection/enteredGroupFrame.js +65 -0
  296. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  297. package/dist/features/selection/enteredGroupScope.js +44 -0
  298. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  299. package/dist/features/text/sceneTextDrafts.js +64 -0
  300. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  301. package/dist/features/text/textHitAt.js +34 -3
  302. package/dist/features/text/textHitAt.js.map +1 -1
  303. package/dist/features/text/useSceneTextEdit.js +23 -19
  304. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  305. package/dist/features/viewports/aim.js +105 -0
  306. package/dist/features/viewports/aim.js.map +1 -0
  307. package/dist/features/viewports/viewMarker.js +82 -0
  308. package/dist/features/viewports/viewMarker.js.map +1 -0
  309. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  310. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  311. package/dist/index.d.ts +709 -258
  312. package/dist/index.js +33 -12
  313. package/dist/interactions/actions/admitsChild.js +11 -0
  314. package/dist/interactions/actions/admitsChild.js.map +1 -0
  315. package/dist/interactions/actions/booleans/booleans.js +2 -1
  316. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  317. package/dist/interactions/actions/booleans/operands.js +6 -5
  318. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  319. package/dist/interactions/actions/clipboard/action.js +27 -17
  320. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  321. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  322. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  323. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  324. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  325. package/dist/interactions/actions/defaults/bendSegment.js +7 -4
  326. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  327. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  328. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  329. package/dist/interactions/actions/defaults/contextMenu.js +33 -11
  330. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  331. package/dist/interactions/actions/defaults/createPaintAction.js +5 -2
  332. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  333. package/dist/interactions/actions/defaults/delete.js +28 -14
  334. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  335. package/dist/interactions/actions/defaults/duplicate.js +11 -8
  336. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  337. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  338. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  339. package/dist/interactions/actions/defaults/enterPathEdit.js +20 -10
  340. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  341. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  342. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  343. package/dist/interactions/actions/defaults/escape.js +11 -8
  344. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  345. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  346. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  347. package/dist/interactions/actions/defaults/group.js +26 -20
  348. package/dist/interactions/actions/defaults/group.js.map +1 -1
  349. package/dist/interactions/actions/defaults/ingest.js +10 -4
  350. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  351. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  352. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  353. package/dist/interactions/actions/defaults/nudge.js +8 -2
  354. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  355. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  356. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  357. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  358. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  359. package/dist/interactions/actions/defaults/reorder.js +24 -6
  360. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  361. package/dist/interactions/actions/defaults/selectAll.js +15 -8
  362. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  363. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  364. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  365. package/dist/interactions/actions/defaults/setFill.js +7 -3
  366. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  367. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  368. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  369. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  370. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  371. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  372. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  373. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  374. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  375. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  376. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  377. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  378. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  379. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  380. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  381. package/dist/interactions/actions/drawnBoxes.js +2 -1
  382. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  383. package/dist/interactions/actions/edit-anchors/action.js +7 -4
  384. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  385. package/dist/interactions/actions/flip/action.js +8 -2
  386. package/dist/interactions/actions/flip/action.js.map +1 -1
  387. package/dist/interactions/actions/insert/action.js +1 -0
  388. package/dist/interactions/actions/insert/action.js.map +1 -1
  389. package/dist/interactions/actions/insert/parent.js +7 -5
  390. package/dist/interactions/actions/insert/parent.js.map +1 -1
  391. package/dist/interactions/actions/insert/placeAction.js +3 -2
  392. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  393. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  394. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  395. package/dist/interactions/actions/insert/session.js +2 -2
  396. package/dist/interactions/actions/insert/session.js.map +1 -1
  397. package/dist/interactions/actions/move/action.js +7 -4
  398. package/dist/interactions/actions/move/action.js.map +1 -1
  399. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  400. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  401. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  402. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  403. package/dist/interactions/actions/move/layout/exits.js +1 -1
  404. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  405. package/dist/interactions/actions/move/session.js +4 -4
  406. package/dist/interactions/actions/move/session.js.map +1 -1
  407. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  408. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  409. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  410. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  411. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  412. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  413. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  414. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  415. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  416. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  417. package/dist/interactions/actions/selectionSubtree.js +12 -7
  418. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  419. package/dist/interactions/actions/transform-again/action.js +8 -5
  420. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  421. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  422. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  423. package/dist/json.d.ts +4 -0
  424. package/dist/json.js +3 -0
  425. package/dist/kitSettingsSchema.js +8 -1
  426. package/dist/kitSettingsSchema.js.map +1 -1
  427. package/dist/{math-CrJULaZI.d.ts → math-C_elShP_.d.ts} +7 -1
  428. package/dist/math.d.ts +1 -1
  429. package/dist/math.js +2 -1
  430. package/dist/move.d.ts +4 -4
  431. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  432. package/dist/resize.d.ts +4 -4
  433. package/dist/test-seams.d.ts +6 -6
  434. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  435. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  436. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  437. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  438. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  439. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  440. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  441. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  442. package/dist/tools/builtin/image/useImageTool.js +1 -0
  443. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  444. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  445. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  446. package/dist/tools/builtin/line/useLineTool.js +1 -0
  447. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  448. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  449. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  450. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  451. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  452. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  453. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  454. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  455. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  456. package/dist/tools/builtin/pressTarget.js +22 -0
  457. package/dist/tools/builtin/pressTarget.js.map +1 -0
  458. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  459. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  460. package/dist/tools/builtin/select/adapterPointQuery.js +6 -5
  461. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  462. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  463. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  464. package/dist/tools/builtin/select/groupEntry.js +82 -0
  465. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  466. package/dist/tools/builtin/select/pressDecision.js +32 -0
  467. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  468. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  469. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  470. package/dist/tools/builtin/select/useSelectTool.js +74 -55
  471. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  472. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  473. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  474. package/dist/tools/builtin/star/useStarTool.js +13 -4
  475. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  476. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  477. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  478. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  479. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  480. package/dist/tools/builtin/text/useTextTool.js +3 -1
  481. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  482. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  483. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  484. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  485. package/dist/util/paint.js +1 -3
  486. package/dist/util/paint.js.map +1 -1
  487. package/dist/version.js +1 -1
  488. package/package.json +29 -25
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeAsShown.js","names":[],"sources":["../../../src/core/scene/nodeAsShown.ts"],"sourcesContent":["/**\n * `nodeAsShown` — a node with the data its override shows it with.\n *\n * The one read of `PoseOverride.data`. Painting, picking, drawn bounds and the\n * effective reading of a derivation go through it; the document reading\n * (`documentOf`), history and `scene.get` never do.\n */\nimport type { NodeId } from './types';\n\n/** What `nodeAsShown` reads of the overrides: a `Scene`'s satisfy it. */\nexport interface ShownDataSource {\n read(id: NodeId): { data?(stored: never): unknown } | undefined;\n}\n\n/** Marks a node whose data is already shown. An own enumerable key, so a spread\n * of the node (`{ ...node, pose }`) carries it, and applying an override to a\n * copy of a shown node is a no-op however the copy was made. */\nconst SHOWN: unique symbol = Symbol('weasel.shownData');\n\ninterface Held {\n stored: unknown;\n data: (stored: never) => unknown;\n shown: object;\n}\n\n/** Per stored node, the last shown copy and what it was made from: keeping it\n * is what lets a memo keyed on the node, or on its data, hit frame to frame\n * while neither changes. */\nconst shownCopies = new WeakMap<object, Held>();\n\n/**\n * `node` with its `data` replaced by what its override's `data` answers from\n * it, or `node` itself when nothing overrides its data or its data is already\n * shown. The same object while the stored node, its data and the override's\n * function are unchanged.\n */\nexport function nodeAsShown<TNode extends { id: NodeId; data?: unknown }>(\n overrides: ShownDataSource,\n node: TNode,\n): TNode {\n if (SHOWN in node) return node;\n const data = overrides.read(node.id)?.data;\n if (data === undefined) return node;\n const held = shownCopies.get(node);\n if (held !== undefined && held.data === data && held.stored === node.data) return held.shown as TNode;\n const shown = withShownData(node, data(node.data as never));\n shownCopies.set(node, { stored: node.data, data, shown });\n return shown;\n}\n\n/** `node` showing `data`, marked so that no override applies to it again: for\n * a reader that already holds the data to show, such as a gesture's ghost. */\nexport function withShownData<TNode extends object>(node: TNode, data: unknown): TNode {\n return { ...node, data, [SHOWN]: true };\n}\n\n/** The last copy `nodeAsShown` made of `node`, if any — what a reader handed\n * the shown node memoized against, for invalidating with the stored one. */\nexport function shownCopyOf(node: object): object | undefined {\n return shownCopies.get(node)?.shown;\n}\n\n/** `source`'s `get`, answering each node as it shows. For a reader that walks\n * up from a node, such as inherited paint looking through its containers. */\nexport function shownLookup<TNode extends { id: NodeId; data?: unknown }>(\n source: { overrides: ShownDataSource; get(id: NodeId): TNode | undefined },\n): { get(id: NodeId): TNode | undefined } {\n return {\n get: (id) => {\n const node = source.get(id);\n return node === undefined ? undefined : nodeAsShown(source.overrides, node);\n },\n };\n}\n\ntype CountedSource = ShownDataSource & { ids(): readonly NodeId[]; getGeneration(): number };\n\n/** Per overrides table, whether any entry shows data, as of a generation. */\nconst anyData = new WeakMap<object, { at: number; any: boolean }>();\n\n/** `nodes`, each as it shows; the same array when nothing overrides any node's\n * data. Answers that from the table's generation, so a pick with nothing\n * overridden allocates nothing. */\nexport function nodesAsShown<TNode extends { id: NodeId; data?: unknown }>(\n overrides: CountedSource,\n nodes: readonly TNode[],\n): readonly TNode[] {\n const at = overrides.getGeneration();\n let held = anyData.get(overrides);\n if (held === undefined || held.at !== at) {\n held = { at, any: overrides.ids().some((id) => overrides.read(id)?.data !== undefined) };\n anyData.set(overrides, held);\n }\n return held.any ? nodes.map((node) => nodeAsShown(overrides, node)) : nodes;\n}\n"],"mappings":";;;;AAiBA,IAAM,QAAuB,OAAO,kBAAkB;;;;AAWtD,IAAM,8BAAc,IAAI,QAAsB;;;;;;;AAQ9C,SAAgB,YACd,WACA,MACO;CACP,IAAI,SAAS,MAAM,OAAO;CAC1B,MAAM,OAAO,UAAU,KAAK,KAAK,EAAE,CAAC,EAAE;CACtC,IAAI,SAAS,QAAW,OAAO;CAC/B,MAAM,OAAO,YAAY,IAAI,IAAI;CACjC,IAAI,SAAS,UAAa,KAAK,SAAS,QAAQ,KAAK,WAAW,KAAK,MAAM,OAAO,KAAK;CACvF,MAAM,QAAQ,cAAc,MAAM,KAAK,KAAK,IAAa,CAAC;CAC1D,YAAY,IAAI,MAAM;EAAE,QAAQ,KAAK;EAAM;EAAM;CAAM,CAAC;CACxD,OAAO;AACT;;;AAIA,SAAgB,cAAoC,MAAa,MAAsB;CACrF,OAAO;EAAE,GAAG;EAAM;GAAO,QAAQ;CAAK;AACxC;;;AAIA,SAAgB,YAAY,MAAkC;CAC5D,OAAO,YAAY,IAAI,IAAI,CAAC,EAAE;AAChC;;;AAIA,SAAgB,YACd,QACwC;CACxC,OAAO,EACL,MAAM,OAAO;EACX,MAAM,OAAO,OAAO,IAAI,EAAE;EAC1B,OAAO,SAAS,SAAY,SAAY,YAAY,OAAO,WAAW,IAAI;CAC5E,EACF;AACF;;AAKA,IAAM,0BAAU,IAAI,QAA8C;;;;AAKlE,SAAgB,aACd,WACA,OACkB;CAClB,MAAM,KAAK,UAAU,cAAc;CACnC,IAAI,OAAO,QAAQ,IAAI,SAAS;CAChC,IAAI,SAAS,UAAa,KAAK,OAAO,IAAI;EACxC,OAAO;GAAE;GAAI,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,OAAO,UAAU,KAAK,EAAE,CAAC,EAAE,SAAS,MAAS;EAAE;EACvF,QAAQ,IAAI,WAAW,IAAI;CAC7B;CACA,OAAO,KAAK,MAAM,MAAM,KAAK,SAAS,YAAY,WAAW,IAAI,CAAC,IAAI;AACxE"}
@@ -4,35 +4,30 @@ var NODE_FN_FIELDS = [
4
4
  field: "clipFromPose",
5
5
  opKey: "clipKey",
6
6
  jsonKey: "clipFromPoseKey",
7
- containersOnly: true,
8
7
  lost: "clip"
9
8
  },
10
9
  {
11
10
  field: "copiesFromPose",
12
11
  opKey: "copiesKey",
13
12
  jsonKey: "copiesFromPoseKey",
14
- containersOnly: true,
15
13
  lost: "copies"
16
14
  },
17
15
  {
18
16
  field: "derivePath",
19
17
  opKey: "derivePathKey",
20
18
  jsonKey: "derivePathKey",
21
- containersOnly: false,
22
19
  lost: "derived path"
23
20
  },
24
21
  {
25
22
  field: "derivePose",
26
23
  opKey: "derivePoseKey",
27
24
  jsonKey: "derivePoseKey",
28
- containersOnly: false,
29
25
  lost: "derived pose"
30
26
  },
31
27
  {
32
28
  field: "layout",
33
29
  opKey: "layoutKey",
34
30
  jsonKey: "layoutKey",
35
- containersOnly: true,
36
31
  lost: "layout"
37
32
  }
38
33
  ];
@@ -41,14 +36,12 @@ function createNodeFnFields(registry, onWrite) {
41
36
  const table = registry[spec.field];
42
37
  const reverse = /* @__PURE__ */ new Map();
43
38
  if (table) for (const [key, fn] of Object.entries(table)) reverse.set(fn, key);
44
- const applies = (node) => !spec.containersOnly || node.kind === "container";
45
39
  return {
46
40
  spec,
47
41
  keyOf: (fn) => reverse.get(fn),
48
42
  fnOf: (key) => table?.[key],
49
- read: (node) => applies(node) ? node[spec.field] : void 0,
43
+ read: (node) => node[spec.field],
50
44
  write: (node, fn) => {
51
- if (!applies(node)) return;
52
45
  node[spec.field] = fn;
53
46
  onWrite?.(spec.field, node);
54
47
  }
@@ -87,14 +80,13 @@ function fnKeysOf(fields, node, which, onUnregistered) {
87
80
  /**
88
81
  * The function fields to carry from a live node into an `AddNodeSpec`. An
89
82
  * insert that drops them brings an edge back as a static path that never
90
- * follows its endpoints again, and a clipped container back with nothing
83
+ * follows its endpoints again, and a clipped group back with nothing
91
84
  * clipped.
92
85
  */
93
86
  function fnFieldsOfNode(node) {
94
87
  const src = node;
95
88
  const out = {};
96
89
  for (const spec of NODE_FN_FIELDS) {
97
- if (spec.containersOnly && node.kind !== "container") continue;
98
90
  const fn = src[spec.field];
99
91
  if (fn !== void 0) out[spec.field] = fn;
100
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nodeFnFields.js","names":[],"sources":["../../../src/core/scene/nodeFnFields.ts"],"sourcesContent":["/**\n * The registry-keyed function fields a node can carry, as a table.\n *\n * These fields are live functions on a node, and a function travels through\n * neither a serializable op payload nor a JSON snapshot. Each one therefore\n * needs the same things: a function→key reverse map, a name in the op\n * payload, a name in the snapshot, and a registry lookup on restore.\n */\nimport type { NodeId, SceneRegistry } from './types';\n\n/** A node's function field, opaque here: the table moves these around by\n * reference and never calls one. A container's `layout` is an object rather\n * than a function, and rides the same table for the same reason. */\nexport type NodeFn = ((...args: never[]) => unknown) | object;\n\n/** A node as this module reads it — a bag with the function fields on it. */\nexport type FnBearingNode = { id: NodeId; kind: 'leaf' | 'container' } & {\n [K in NodeFnFieldName]?: NodeFn;\n};\n\nexport type NodeFnFieldName = 'clipFromPose' | 'copiesFromPose' | 'derivePath' | 'derivePose' | 'layout';\n\nexport interface NodeFnFieldSpec {\n /** The field's name on `Node` — and, deliberately, its name in\n * `SceneRegistry` too, so one string indexes both. */\n readonly field: NodeFnFieldName;\n /** Its key in the `kit:add` and `kit:remove` op payloads. `clipFromPose`'s\n * is the short one because a persisted history already carries that name. */\n readonly opKey: string;\n /** Its key in a `SerializedNode`. */\n readonly jsonKey: string;\n /** True for a field only a `ContainerNode` can carry. */\n readonly containersOnly: boolean;\n /** What a node loses when its key is not in the registry, for the warning. */\n readonly lost: string;\n}\n\nexport const NODE_FN_FIELDS: readonly NodeFnFieldSpec[] = [\n {\n field: 'clipFromPose',\n opKey: 'clipKey',\n jsonKey: 'clipFromPoseKey',\n containersOnly: true,\n lost: 'clip',\n },\n {\n field: 'copiesFromPose',\n opKey: 'copiesKey',\n jsonKey: 'copiesFromPoseKey',\n containersOnly: true,\n lost: 'copies',\n },\n {\n field: 'derivePath',\n opKey: 'derivePathKey',\n jsonKey: 'derivePathKey',\n containersOnly: false,\n lost: 'derived path',\n },\n {\n field: 'derivePose',\n opKey: 'derivePoseKey',\n jsonKey: 'derivePoseKey',\n containersOnly: false,\n lost: 'derived pose',\n },\n {\n field: 'layout',\n opKey: 'layoutKey',\n jsonKey: 'layoutKey',\n containersOnly: true,\n lost: 'layout',\n },\n];\n\n/** One field's per-scene state. */\nexport interface NodeFnField {\n readonly spec: NodeFnFieldSpec;\n /** `fn → registry key`, for writing a payload or a snapshot. */\n keyOf(fn: NodeFn): string | undefined;\n /** `registry key → fn`, for restoring one. */\n fnOf(key: string): NodeFn | undefined;\n /** The function on `node`, or `undefined` — including when the field is\n * container-only and `node` is a leaf. */\n read(node: FnBearingNode): NodeFn | undefined;\n /** Attach `fn` to `node`, ignoring a container-only field on a leaf. */\n write(node: FnBearingNode, fn: NodeFn): void;\n}\n\nexport function createNodeFnFields<TPose>(\n registry: SceneRegistry<TPose>,\n /** Called after a field is written onto a node it applies to. */\n onWrite?: (field: NodeFnFieldName, node: FnBearingNode) => void,\n): readonly NodeFnField[] {\n return NODE_FN_FIELDS.map((spec) => {\n const table = registry[spec.field] as Readonly<Record<string, NodeFn>> | undefined;\n const reverse = new Map<NodeFn, string>();\n if (table) for (const [key, fn] of Object.entries(table)) reverse.set(fn, key);\n const applies = (node: FnBearingNode): boolean =>\n !spec.containersOnly || node.kind === 'container';\n return {\n spec,\n keyOf: (fn) => reverse.get(fn),\n fnOf: (key) => table?.[key],\n read: (node) => (applies(node) ? node[spec.field] : undefined),\n write: (node, fn) => {\n if (!applies(node)) return;\n node[spec.field] = fn;\n onWrite?.(spec.field, node);\n },\n };\n });\n}\n\n/**\n * Attach the function `key` names to `node`. Warns and leaves the field\n * unset when the key is not in this scene's registry — a snapshot outliving\n * the code that registered its functions must still load.\n */\nexport function restoreFn(\n field: NodeFnField,\n node: FnBearingNode,\n key: string,\n site: string,\n warn: (channel: string, message: string) => void,\n): void {\n const fn = field.fnOf(key);\n if (fn === undefined) {\n warn(\n 'scene',\n `${site}: ${field.spec.opKey} \"${key}\" not in this scene's registry — node \"${node.id}\" ` +\n `restored without its ${field.spec.lost}. Register a function with this key in the ` +\n `registry option to restore it.`,\n );\n return;\n }\n field.write(node, fn);\n}\n\n/** The `{ clipKey?, derivePathKey?, … }` an op payload or a snapshot carries\n * for `node`. Empty when the node has no keyed function on it. */\nexport function fnKeysOf(\n fields: readonly NodeFnField[],\n node: FnBearingNode,\n which: 'opKey' | 'jsonKey',\n onUnregistered?: (field: NodeFnField) => never,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const field of fields) {\n const fn = field.read(node);\n if (fn === undefined) continue;\n const key = field.keyOf(fn);\n if (key === undefined) {\n onUnregistered?.(field);\n continue;\n }\n out[field.spec[which]] = key;\n }\n return out;\n}\n\n/**\n * The function fields to carry from a live node into an `AddNodeSpec`. An\n * insert that drops them brings an edge back as a static path that never\n * follows its endpoints again, and a clipped container back with nothing\n * clipped.\n */\nexport function fnFieldsOfNode<T extends { kind: 'leaf' | 'container' }>(\n node: T,\n): Partial<Pick<T, Extract<NodeFnFieldName, keyof T>>> {\n const src = node as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const spec of NODE_FN_FIELDS) {\n if (spec.containersOnly && node.kind !== 'container') continue;\n const fn = src[spec.field];\n if (fn !== undefined) out[spec.field] = fn;\n }\n return out as Partial<Pick<T, Extract<NodeFnFieldName, keyof T>>>;\n}\n"],"mappings":";AAqCA,IAAa,iBAA6C;CACxD;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,MAAM;CACR;AACF;AAgBA,SAAgB,mBACd,UAEA,SACwB;CACxB,OAAO,eAAe,KAAK,SAAS;EAClC,MAAM,QAAQ,SAAS,KAAK;EAC5B,MAAM,0BAAU,IAAI,IAAoB;EACxC,IAAI,OAAO,KAAK,MAAM,CAAC,KAAK,OAAO,OAAO,QAAQ,KAAK,GAAG,QAAQ,IAAI,IAAI,GAAG;EAC7E,MAAM,WAAW,SACf,CAAC,KAAK,kBAAkB,KAAK,SAAS;EACxC,OAAO;GACL;GACA,QAAQ,OAAO,QAAQ,IAAI,EAAE;GAC7B,OAAO,QAAQ,QAAQ;GACvB,OAAO,SAAU,QAAQ,IAAI,IAAI,KAAK,KAAK,SAAS;GACpD,QAAQ,MAAM,OAAO;IACnB,IAAI,CAAC,QAAQ,IAAI,GAAG;IACpB,KAAK,KAAK,SAAS;IACnB,UAAU,KAAK,OAAO,IAAI;GAC5B;EACF;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,UACd,OACA,MACA,KACA,MACA,MACM;CACN,MAAM,KAAK,MAAM,KAAK,GAAG;CACzB,IAAI,OAAO,QAAW;EACpB,KACE,SACA,GAAG,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,yCAAyC,KAAK,GAAG,yBAC5D,MAAM,KAAK,KAAK,0EAE5C;EACA;CACF;CACA,MAAM,MAAM,MAAM,EAAE;AACtB;;;AAIA,SAAgB,SACd,QACA,MACA,OACA,gBACwB;CACxB,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,KAAK,MAAM,KAAK,IAAI;EAC1B,IAAI,OAAO,QAAW;EACtB,MAAM,MAAM,MAAM,MAAM,EAAE;EAC1B,IAAI,QAAQ,QAAW;GACrB,iBAAiB,KAAK;GACtB;EACF;EACA,IAAI,MAAM,KAAK,UAAU;CAC3B;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,eACd,MACqD;CACrD,MAAM,MAAM;CACZ,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,QAAQ,gBAAgB;EACjC,IAAI,KAAK,kBAAkB,KAAK,SAAS,aAAa;EACtD,MAAM,KAAK,IAAI,KAAK;EACpB,IAAI,OAAO,QAAW,IAAI,KAAK,SAAS;CAC1C;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"nodeFnFields.js","names":[],"sources":["../../../src/core/scene/nodeFnFields.ts"],"sourcesContent":["/**\n * The registry-keyed function fields a node can carry, as a table.\n *\n * These fields are live functions on a node, and a function travels through\n * neither a serializable op payload nor a JSON snapshot. Each one therefore\n * needs the same things: a function→key reverse map, a name in the op\n * payload, a name in the snapshot, and a registry lookup on restore.\n */\nimport type { NodeId, SceneRegistry } from './types';\n\n/** A node's function field, opaque here: the table moves these around by\n * reference and never calls one. A node's `layout` is an object rather\n * than a function, and rides the same table for the same reason. */\nexport type NodeFn = ((...args: never[]) => unknown) | object;\n\n/** A node as this module reads it — a bag with the function fields on it. */\nexport type FnBearingNode = { id: NodeId; kind: 'mark' | 'group' } & {\n [K in NodeFnFieldName]?: NodeFn;\n};\n\nexport type NodeFnFieldName = 'clipFromPose' | 'copiesFromPose' | 'derivePath' | 'derivePose' | 'layout';\n\nexport interface NodeFnFieldSpec {\n /** The field's name on `Node` — and, deliberately, its name in\n * `SceneRegistry` too, so one string indexes both. */\n readonly field: NodeFnFieldName;\n /** Its key in the `kit:add` and `kit:remove` op payloads. `clipFromPose`'s\n * is the short one because a persisted history already carries that name. */\n readonly opKey: string;\n /** Its key in a `SerializedNode`. */\n readonly jsonKey: string;\n /** What a node loses when its key is not in the registry, for the warning. */\n readonly lost: string;\n}\n\nexport const NODE_FN_FIELDS: readonly NodeFnFieldSpec[] = [\n {\n field: 'clipFromPose',\n opKey: 'clipKey',\n jsonKey: 'clipFromPoseKey',\n lost: 'clip',\n },\n {\n field: 'copiesFromPose',\n opKey: 'copiesKey',\n jsonKey: 'copiesFromPoseKey',\n lost: 'copies',\n },\n {\n field: 'derivePath',\n opKey: 'derivePathKey',\n jsonKey: 'derivePathKey',\n lost: 'derived path',\n },\n {\n field: 'derivePose',\n opKey: 'derivePoseKey',\n jsonKey: 'derivePoseKey',\n lost: 'derived pose',\n },\n {\n field: 'layout',\n opKey: 'layoutKey',\n jsonKey: 'layoutKey',\n lost: 'layout',\n },\n];\n\n/** One field's per-scene state. */\nexport interface NodeFnField {\n readonly spec: NodeFnFieldSpec;\n /** `fn → registry key`, for writing a payload or a snapshot. */\n keyOf(fn: NodeFn): string | undefined;\n /** `registry key → fn`, for restoring one. */\n fnOf(key: string): NodeFn | undefined;\n /** The function on `node`, or `undefined`. */\n read(node: FnBearingNode): NodeFn | undefined;\n /** Attach `fn` to `node`. */\n write(node: FnBearingNode, fn: NodeFn): void;\n}\n\nexport function createNodeFnFields<TPose>(\n registry: SceneRegistry<TPose>,\n /** Called after a field is written onto a node it applies to. */\n onWrite?: (field: NodeFnFieldName, node: FnBearingNode) => void,\n): readonly NodeFnField[] {\n return NODE_FN_FIELDS.map((spec) => {\n const table = registry[spec.field] as Readonly<Record<string, NodeFn>> | undefined;\n const reverse = new Map<NodeFn, string>();\n if (table) for (const [key, fn] of Object.entries(table)) reverse.set(fn, key);\n return {\n spec,\n keyOf: (fn) => reverse.get(fn),\n fnOf: (key) => table?.[key],\n read: (node) => node[spec.field],\n write: (node, fn) => {\n node[spec.field] = fn;\n onWrite?.(spec.field, node);\n },\n };\n });\n}\n\n/**\n * Attach the function `key` names to `node`. Warns and leaves the field\n * unset when the key is not in this scene's registry — a snapshot outliving\n * the code that registered its functions must still load.\n */\nexport function restoreFn(\n field: NodeFnField,\n node: FnBearingNode,\n key: string,\n site: string,\n warn: (channel: string, message: string) => void,\n): void {\n const fn = field.fnOf(key);\n if (fn === undefined) {\n warn(\n 'scene',\n `${site}: ${field.spec.opKey} \"${key}\" not in this scene's registry — node \"${node.id}\" ` +\n `restored without its ${field.spec.lost}. Register a function with this key in the ` +\n `registry option to restore it.`,\n );\n return;\n }\n field.write(node, fn);\n}\n\n/** The `{ clipKey?, derivePathKey?, … }` an op payload or a snapshot carries\n * for `node`. Empty when the node has no keyed function on it. */\nexport function fnKeysOf(\n fields: readonly NodeFnField[],\n node: FnBearingNode,\n which: 'opKey' | 'jsonKey',\n onUnregistered?: (field: NodeFnField) => never,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const field of fields) {\n const fn = field.read(node);\n if (fn === undefined) continue;\n const key = field.keyOf(fn);\n if (key === undefined) {\n onUnregistered?.(field);\n continue;\n }\n out[field.spec[which]] = key;\n }\n return out;\n}\n\n/**\n * The function fields to carry from a live node into an `AddNodeSpec`. An\n * insert that drops them brings an edge back as a static path that never\n * follows its endpoints again, and a clipped group back with nothing\n * clipped.\n */\nexport function fnFieldsOfNode<T extends { kind: 'mark' | 'group' }>(\n node: T,\n): Partial<Pick<T, Extract<NodeFnFieldName, keyof T>>> {\n const src = node as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const spec of NODE_FN_FIELDS) {\n const fn = src[spec.field];\n if (fn !== undefined) out[spec.field] = fn;\n }\n return out as Partial<Pick<T, Extract<NodeFnFieldName, keyof T>>>;\n}\n"],"mappings":";AAmCA,IAAa,iBAA6C;CACxD;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,MAAM;CACR;AACF;AAeA,SAAgB,mBACd,UAEA,SACwB;CACxB,OAAO,eAAe,KAAK,SAAS;EAClC,MAAM,QAAQ,SAAS,KAAK;EAC5B,MAAM,0BAAU,IAAI,IAAoB;EACxC,IAAI,OAAO,KAAK,MAAM,CAAC,KAAK,OAAO,OAAO,QAAQ,KAAK,GAAG,QAAQ,IAAI,IAAI,GAAG;EAC7E,OAAO;GACL;GACA,QAAQ,OAAO,QAAQ,IAAI,EAAE;GAC7B,OAAO,QAAQ,QAAQ;GACvB,OAAO,SAAS,KAAK,KAAK;GAC1B,QAAQ,MAAM,OAAO;IACnB,KAAK,KAAK,SAAS;IACnB,UAAU,KAAK,OAAO,IAAI;GAC5B;EACF;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,UACd,OACA,MACA,KACA,MACA,MACM;CACN,MAAM,KAAK,MAAM,KAAK,GAAG;CACzB,IAAI,OAAO,QAAW;EACpB,KACE,SACA,GAAG,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,yCAAyC,KAAK,GAAG,yBAC5D,MAAM,KAAK,KAAK,0EAE5C;EACA;CACF;CACA,MAAM,MAAM,MAAM,EAAE;AACtB;;;AAIA,SAAgB,SACd,QACA,MACA,OACA,gBACwB;CACxB,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,KAAK,MAAM,KAAK,IAAI;EAC1B,IAAI,OAAO,QAAW;EACtB,MAAM,MAAM,MAAM,MAAM,EAAE;EAC1B,IAAI,QAAQ,QAAW;GACrB,iBAAiB,KAAK;GACtB;EACF;EACA,IAAI,MAAM,KAAK,UAAU;CAC3B;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,eACd,MACqD;CACrD,MAAM,MAAM;CACZ,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,KAAK,IAAI,KAAK;EACpB,IAAI,OAAO,QAAW,IAAI,KAAK,SAAS;CAC1C;CACA,OAAO;AACT"}
@@ -8,8 +8,8 @@ var MEMO = /* @__PURE__ */ new WeakMap();
8
8
  var generation = 0;
9
9
  /**
10
10
  * Return the value `derive` produces for `node`, recomputing only when the
11
- * node's `data` reference, the given `pose` reference, or the global
12
- * generation has changed since the last call for this `slot`.
11
+ * node's `data` reference, the given `pose` reference, the `also` reference,
12
+ * or the global generation has changed since the last call for this `slot`.
13
13
  *
14
14
  * `slot` names the derivation, so a node's several derived values do not
15
15
  * overwrite each other.
@@ -18,9 +18,13 @@ var generation = 0;
18
18
  * legitimately derive against a pose that isn't `node.pose`: a preview or
19
19
  * ghost pose mid-drag. Pass `undefined` for derivations that don't depend on
20
20
  * the pose at all, leaving `data` as the only key.
21
+ *
22
+ * `also` is for an input that lives outside the node, such as the paint a
23
+ * node inherits from its container. It must be a reference its owner keeps
24
+ * while the input is unchanged.
21
25
  */
22
- function nodeMemo(node, slot, pose, derive) {
23
- if (typeof slot !== "string") return scopedNodeMemo(node, slot, pose, derive);
26
+ function nodeMemo(node, slot, pose, derive, also) {
27
+ if (typeof slot !== "string") return scopedNodeMemo(node, slot, pose, derive, also);
24
28
  let record = MEMO.get(node);
25
29
  if (record === void 0) {
26
30
  record = {
@@ -35,36 +39,40 @@ function nodeMemo(node, slot, pose, derive) {
35
39
  }
36
40
  const data = node.data;
37
41
  const hit = record.slots.get(slot);
38
- if (hit !== void 0 && hit.pose === pose && hit.data === data) return hit.value;
42
+ if (hit !== void 0 && hit.pose === pose && hit.data === data && hit.also === also) return hit.value;
39
43
  const value = derive();
40
44
  if (hit !== void 0) {
41
45
  hit.pose = pose;
42
46
  hit.data = data;
47
+ hit.also = also;
43
48
  hit.value = value;
44
49
  } else record.slots.set(slot, {
45
50
  pose,
46
51
  data,
52
+ also,
47
53
  value
48
54
  });
49
55
  return value;
50
56
  }
51
- function scopedNodeMemo(node, scope, pose, derive) {
57
+ function scopedNodeMemo(node, scope, pose, derive, also) {
52
58
  const entries = scope;
53
59
  const data = node.data;
54
60
  const drops = MEMO.get(node)?.drops ?? 0;
55
61
  const hit = entries.get(node);
56
- if (hit !== void 0 && hit.generation === generation && hit.pose === pose && hit.data === data && (pose === void 0 || hit.drops === drops)) return hit.value;
62
+ if (hit !== void 0 && hit.generation === generation && hit.pose === pose && hit.data === data && hit.also === also && (pose === void 0 || hit.drops === drops)) return hit.value;
57
63
  const value = derive();
58
64
  const dropsNow = MEMO.get(node)?.drops ?? 0;
59
65
  if (hit !== void 0) {
60
66
  hit.pose = pose;
61
67
  hit.data = data;
68
+ hit.also = also;
62
69
  hit.value = value;
63
70
  hit.generation = generation;
64
71
  hit.drops = dropsNow;
65
72
  } else entries.set(node, {
66
73
  pose,
67
74
  data,
75
+ also,
68
76
  value,
69
77
  generation,
70
78
  drops: dropsNow
@@ -78,6 +86,11 @@ function scopedNodeMemo(node, scope, pose, derive) {
78
86
  function bumpNodeMemoGeneration() {
79
87
  generation++;
80
88
  }
89
+ /** The generation {@link bumpNodeMemoGeneration} last moved to: a freshness
90
+ * key for a cache outside this memo that reads the same ambient state. */
91
+ function nodeMemoGeneration() {
92
+ return generation;
93
+ }
81
94
  /**
82
95
  * Drop the slots of `node` that were keyed on a pose, keeping the ones that
83
96
  * weren't.
@@ -102,5 +115,5 @@ function dropPoseKeyedMemoSlots(node) {
102
115
  }
103
116
 
104
117
  //#endregion
105
- export { bumpNodeMemoGeneration, createNodeMemoScope, dropPoseKeyedMemoSlots, nodeMemo };
118
+ export { bumpNodeMemoGeneration, createNodeMemoScope, dropPoseKeyedMemoSlots, nodeMemo, nodeMemoGeneration };
106
119
  //# sourceMappingURL=nodeMemo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nodeMemo.js","names":[],"sources":["../../../src/core/scene/nodeMemo.ts"],"sourcesContent":["/**\n * `nodeMemo` — memoize anything derived from a node, keyed on the node's\n * identity plus the `pose` / `data` references the scene swaps on every edit.\n *\n * The scene mutates node objects in place but *replaces* `pose` and `data`\n * with new references, so those two references are an exact freshness signal.\n * The assumption it rests on: nobody mutates `node.data` (or `node.pose`) in\n * place without replacing the reference. A consumer who violates it sees a\n * stale derivation until the next real edit.\n *\n * **What the key does not cover** is ambient state that `derive` reads — a\n * registry, a font set, a decode cache. Whoever owns the ambient state calls\n * {@link bumpNodeMemoGeneration}. Derivations that read ambient state with\n * *no* such signal (an async image decode landing, say) must not be memoized\n * here at all.\n */\n\n/** One memoized derivation: the references it was derived against, plus the\n * value. Presence in the slot map is what \"computed\" means — a `null` or\n * `undefined` value is a real answer and stays cached. */\ninterface MemoSlot {\n /** Reference-compared against the caller's `pose` argument. */\n pose: unknown;\n /** Reference-compared against `node.data` at call time. */\n data: unknown;\n value: unknown;\n}\n\ninterface NodeMemoRecord {\n /** `generation` at the time the slots were filled. */\n generation: number;\n slots: Map<string, MemoSlot>;\n /** How many times {@link dropPoseKeyedMemoSlots} has run for this node,\n * which is how a drop reaches the scopes it cannot enumerate. */\n drops: number;\n}\n\n/** A scoped entry also carries what a named slot's record holds for it. */\ninterface ScopedSlot extends MemoSlot {\n generation: number;\n drops: number;\n}\n\ndeclare const scopeBrand: unique symbol;\n\n/**\n * A private place to memoize one derivation, in place of a slot name.\n *\n * A named slot lives on the node, so it lasts as long as the node does. That\n * is wrong for a derivation that belongs to some shorter-lived reader — one\n * `toPose` function among many over the life of a scene. A scope is held by\n * that reader, and its entries go when the reader does.\n */\nexport type NodeMemoScope = { readonly [scopeBrand]: true };\n\nexport function createNodeMemoScope(): NodeMemoScope {\n return new WeakMap<object, ScopedSlot>() as unknown as NodeMemoScope;\n}\n\nconst MEMO = new WeakMap<object, NodeMemoRecord>();\n\n/** Bumped whenever ambient state a derivation reads has changed. Entries\n * stamped with an older value are dropped rather than served stale. */\nlet generation = 0;\n\n/** A node, as far as the memo is concerned: an object identity that may carry\n * a `data` reference. */\ntype MemoizableNode = { data?: unknown };\n\n/**\n * Return the value `derive` produces for `node`, recomputing only when the\n * node's `data` reference, the given `pose` reference, or the global\n * generation has changed since the last call for this `slot`.\n *\n * `slot` names the derivation, so a node's several derived values do not\n * overwrite each other.\n *\n * `pose` is a **parameter rather than a read off the node**, because callers\n * legitimately derive against a pose that isn't `node.pose`: a preview or\n * ghost pose mid-drag. Pass `undefined` for derivations that don't depend on\n * the pose at all, leaving `data` as the only key.\n */\nexport function nodeMemo<T>(\n node: MemoizableNode,\n slot: string | NodeMemoScope,\n pose: unknown,\n derive: () => T,\n): T {\n if (typeof slot !== 'string') return scopedNodeMemo(node, slot, pose, derive);\n let record = MEMO.get(node);\n if (record === undefined) {\n record = { generation, slots: new Map(), drops: 0 };\n MEMO.set(node, record);\n } else if (record.generation !== generation) {\n record.generation = generation;\n record.slots.clear();\n }\n\n const data = node.data;\n const hit = record.slots.get(slot);\n if (hit !== undefined && hit.pose === pose && hit.data === data) {\n return hit.value as T;\n }\n\n const value = derive();\n // Reuse the slot object when there is one: this runs per node per frame, and\n // the entry churn is the only allocation the memo would otherwise add.\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.value = value;\n } else {\n record.slots.set(slot, { pose, data, value });\n }\n return value;\n}\n\nfunction scopedNodeMemo<T>(\n node: MemoizableNode,\n scope: NodeMemoScope,\n pose: unknown,\n derive: () => T,\n): T {\n const entries = scope as unknown as WeakMap<object, ScopedSlot>;\n const data = node.data;\n const drops = MEMO.get(node)?.drops ?? 0;\n const hit = entries.get(node);\n if (\n hit !== undefined && hit.generation === generation && hit.pose === pose && hit.data === data\n && (pose === undefined || hit.drops === drops)\n ) {\n return hit.value as T;\n }\n const value = derive();\n // Read again: a derivation that hit a cycle drops this node's slots itself.\n const dropsNow = MEMO.get(node)?.drops ?? 0;\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.value = value;\n hit.generation = generation;\n hit.drops = dropsNow;\n } else {\n entries.set(node, { pose, data, value, generation, drops: dropsNow });\n }\n return value;\n}\n\n/** How many named slots `node` holds. For tests of what is kept on a node. */\nexport function nodeMemoSlotCount(node: MemoizableNode): number {\n const record = MEMO.get(node);\n return record === undefined || record.generation !== generation ? 0 : record.slots.size;\n}\n\n/**\n * Drop every memoized slot on every node. For owners of ambient state that\n * changes what a `derive` would return. Cheap — it bumps a counter.\n */\nexport function bumpNodeMemoGeneration(): void {\n generation++;\n}\n\n/**\n * Drop the slots of `node` that were keyed on a pose, keeping the ones that\n * weren't.\n *\n * For per-frame pose overrides, which mutate one buffer in place rather than\n * replacing the reference, which the `(pose, data)` key cannot see.\n * {@link bumpNodeMemoGeneration} is the wrong tool there — it would drop every\n * static node's slots too, every frame.\n */\nexport function dropPoseKeyedMemoSlots(node: MemoizableNode): void {\n const record = MEMO.get(node);\n if (record === undefined) {\n MEMO.set(node, { generation, slots: new Map(), drops: 1 });\n return;\n }\n record.drops++;\n for (const [slot, entry] of record.slots) {\n if (entry.pose !== undefined) record.slots.delete(slot);\n }\n}\n"],"mappings":";AAuDA,SAAgB,sBAAqC;CACnD,uBAAO,IAAI,QAA4B;AACzC;AAEA,IAAM,uBAAO,IAAI,QAAgC;;;AAIjD,IAAI,aAAa;;;;;;;;;;;;;;AAmBjB,SAAgB,SACd,MACA,MACA,MACA,QACG;CACH,IAAI,OAAO,SAAS,UAAU,OAAO,eAAe,MAAM,MAAM,MAAM,MAAM;CAC5E,IAAI,SAAS,KAAK,IAAI,IAAI;CAC1B,IAAI,WAAW,QAAW;EACxB,SAAS;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE;EAClD,KAAK,IAAI,MAAM,MAAM;CACvB,OAAO,IAAI,OAAO,eAAe,YAAY;EAC3C,OAAO,aAAa;EACpB,OAAO,MAAM,MAAM;CACrB;CAEA,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,OAAO,MAAM,IAAI,IAAI;CACjC,IAAI,QAAQ,UAAa,IAAI,SAAS,QAAQ,IAAI,SAAS,MACzD,OAAO,IAAI;CAGb,MAAM,QAAQ,OAAO;CAGrB,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;CACd,OACE,OAAO,MAAM,IAAI,MAAM;EAAE;EAAM;EAAM;CAAM,CAAC;CAE9C,OAAO;AACT;AAEA,SAAS,eACP,MACA,OACA,MACA,QACG;CACH,MAAM,UAAU;CAChB,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CACvC,MAAM,MAAM,QAAQ,IAAI,IAAI;CAC5B,IACE,QAAQ,UAAa,IAAI,eAAe,cAAc,IAAI,SAAS,QAAQ,IAAI,SAAS,SACpF,SAAS,UAAa,IAAI,UAAU,QAExC,OAAO,IAAI;CAEb,MAAM,QAAQ,OAAO;CAErB,MAAM,WAAW,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CAC1C,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;EACZ,IAAI,aAAa;EACjB,IAAI,QAAQ;CACd,OACE,QAAQ,IAAI,MAAM;EAAE;EAAM;EAAM;EAAO;EAAY,OAAO;CAAS,CAAC;CAEtE,OAAO;AACT;;;;;AAYA,SAAgB,yBAA+B;CAC7C;AACF;;;;;;;;;;AAWA,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,SAAS,KAAK,IAAI,IAAI;CAC5B,IAAI,WAAW,QAAW;EACxB,KAAK,IAAI,MAAM;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE,CAAC;EACzD;CACF;CACA,OAAO;CACP,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,OACjC,IAAI,MAAM,SAAS,QAAW,OAAO,MAAM,OAAO,IAAI;AAE1D"}
1
+ {"version":3,"file":"nodeMemo.js","names":[],"sources":["../../../src/core/scene/nodeMemo.ts"],"sourcesContent":["/**\n * `nodeMemo` — memoize anything derived from a node, keyed on the node's\n * identity plus the `pose` / `data` references the scene swaps on every edit.\n *\n * The scene mutates node objects in place but *replaces* `pose` and `data`\n * with new references, so those two references are an exact freshness signal.\n * The assumption it rests on: nobody mutates `node.data` (or `node.pose`) in\n * place without replacing the reference. A consumer who violates it sees a\n * stale derivation until the next real edit.\n *\n * **What the key does not cover** is ambient state that `derive` reads — a\n * registry, a font set, a decode cache. Whoever owns the ambient state calls\n * {@link bumpNodeMemoGeneration}. Derivations that read ambient state with\n * *no* such signal (an async image decode landing, say) must not be memoized\n * here at all.\n */\n\n/** One memoized derivation: the references it was derived against, plus the\n * value. Presence in the slot map is what \"computed\" means — a `null` or\n * `undefined` value is a real answer and stays cached. */\ninterface MemoSlot {\n /** Reference-compared against the caller's `pose` argument. */\n pose: unknown;\n /** Reference-compared against `node.data` at call time. */\n data: unknown;\n /** Reference-compared against the caller's `also` argument. */\n also: unknown;\n value: unknown;\n}\n\ninterface NodeMemoRecord {\n /** `generation` at the time the slots were filled. */\n generation: number;\n slots: Map<string, MemoSlot>;\n /** How many times {@link dropPoseKeyedMemoSlots} has run for this node,\n * which is how a drop reaches the scopes it cannot enumerate. */\n drops: number;\n}\n\n/** A scoped entry also carries what a named slot's record holds for it. */\ninterface ScopedSlot extends MemoSlot {\n generation: number;\n drops: number;\n}\n\ndeclare const scopeBrand: unique symbol;\n\n/**\n * A private place to memoize one derivation, in place of a slot name.\n *\n * A named slot lives on the node, so it lasts as long as the node does. That\n * is wrong for a derivation that belongs to some shorter-lived reader — one\n * `toPose` function among many over the life of a scene. A scope is held by\n * that reader, and its entries go when the reader does.\n */\nexport type NodeMemoScope = { readonly [scopeBrand]: true };\n\nexport function createNodeMemoScope(): NodeMemoScope {\n return new WeakMap<object, ScopedSlot>() as unknown as NodeMemoScope;\n}\n\nconst MEMO = new WeakMap<object, NodeMemoRecord>();\n\n/** Bumped whenever ambient state a derivation reads has changed. Entries\n * stamped with an older value are dropped rather than served stale. */\nlet generation = 0;\n\n/** A node, as far as the memo is concerned: an object identity that may carry\n * a `data` reference. */\ntype MemoizableNode = { data?: unknown };\n\n/**\n * Return the value `derive` produces for `node`, recomputing only when the\n * node's `data` reference, the given `pose` reference, the `also` reference,\n * or the global generation has changed since the last call for this `slot`.\n *\n * `slot` names the derivation, so a node's several derived values do not\n * overwrite each other.\n *\n * `pose` is a **parameter rather than a read off the node**, because callers\n * legitimately derive against a pose that isn't `node.pose`: a preview or\n * ghost pose mid-drag. Pass `undefined` for derivations that don't depend on\n * the pose at all, leaving `data` as the only key.\n *\n * `also` is for an input that lives outside the node, such as the paint a\n * node inherits from its container. It must be a reference its owner keeps\n * while the input is unchanged.\n */\nexport function nodeMemo<T>(\n node: MemoizableNode,\n slot: string | NodeMemoScope,\n pose: unknown,\n derive: () => T,\n also?: unknown,\n): T {\n if (typeof slot !== 'string') return scopedNodeMemo(node, slot, pose, derive, also);\n let record = MEMO.get(node);\n if (record === undefined) {\n record = { generation, slots: new Map(), drops: 0 };\n MEMO.set(node, record);\n } else if (record.generation !== generation) {\n record.generation = generation;\n record.slots.clear();\n }\n\n const data = node.data;\n const hit = record.slots.get(slot);\n if (hit !== undefined && hit.pose === pose && hit.data === data && hit.also === also) {\n return hit.value as T;\n }\n\n const value = derive();\n // Reuse the slot object when there is one: this runs per node per frame, and\n // the entry churn is the only allocation the memo would otherwise add.\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.also = also;\n hit.value = value;\n } else {\n record.slots.set(slot, { pose, data, also, value });\n }\n return value;\n}\n\nfunction scopedNodeMemo<T>(\n node: MemoizableNode,\n scope: NodeMemoScope,\n pose: unknown,\n derive: () => T,\n also: unknown,\n): T {\n const entries = scope as unknown as WeakMap<object, ScopedSlot>;\n const data = node.data;\n const drops = MEMO.get(node)?.drops ?? 0;\n const hit = entries.get(node);\n if (\n hit !== undefined && hit.generation === generation && hit.pose === pose && hit.data === data\n && hit.also === also && (pose === undefined || hit.drops === drops)\n ) {\n return hit.value as T;\n }\n const value = derive();\n // Read again: a derivation that hit a cycle drops this node's slots itself.\n const dropsNow = MEMO.get(node)?.drops ?? 0;\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.also = also;\n hit.value = value;\n hit.generation = generation;\n hit.drops = dropsNow;\n } else {\n entries.set(node, { pose, data, also, value, generation, drops: dropsNow });\n }\n return value;\n}\n\n/** How many named slots `node` holds. For tests of what is kept on a node. */\nexport function nodeMemoSlotCount(node: MemoizableNode): number {\n const record = MEMO.get(node);\n return record === undefined || record.generation !== generation ? 0 : record.slots.size;\n}\n\n/**\n * Drop every memoized slot on every node. For owners of ambient state that\n * changes what a `derive` would return. Cheap — it bumps a counter.\n */\nexport function bumpNodeMemoGeneration(): void {\n generation++;\n}\n\n/** The generation {@link bumpNodeMemoGeneration} last moved to: a freshness\n * key for a cache outside this memo that reads the same ambient state. */\nexport function nodeMemoGeneration(): number {\n return generation;\n}\n\n/**\n * Drop the slots of `node` that were keyed on a pose, keeping the ones that\n * weren't.\n *\n * For per-frame pose overrides, which mutate one buffer in place rather than\n * replacing the reference, which the `(pose, data)` key cannot see.\n * {@link bumpNodeMemoGeneration} is the wrong tool there — it would drop every\n * static node's slots too, every frame.\n */\nexport function dropPoseKeyedMemoSlots(node: MemoizableNode): void {\n const record = MEMO.get(node);\n if (record === undefined) {\n MEMO.set(node, { generation, slots: new Map(), drops: 1 });\n return;\n }\n record.drops++;\n for (const [slot, entry] of record.slots) {\n if (entry.pose !== undefined) record.slots.delete(slot);\n }\n}\n"],"mappings":";AAyDA,SAAgB,sBAAqC;CACnD,uBAAO,IAAI,QAA4B;AACzC;AAEA,IAAM,uBAAO,IAAI,QAAgC;;;AAIjD,IAAI,aAAa;;;;;;;;;;;;;;;;;;AAuBjB,SAAgB,SACd,MACA,MACA,MACA,QACA,MACG;CACH,IAAI,OAAO,SAAS,UAAU,OAAO,eAAe,MAAM,MAAM,MAAM,QAAQ,IAAI;CAClF,IAAI,SAAS,KAAK,IAAI,IAAI;CAC1B,IAAI,WAAW,QAAW;EACxB,SAAS;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE;EAClD,KAAK,IAAI,MAAM,MAAM;CACvB,OAAO,IAAI,OAAO,eAAe,YAAY;EAC3C,OAAO,aAAa;EACpB,OAAO,MAAM,MAAM;CACrB;CAEA,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,OAAO,MAAM,IAAI,IAAI;CACjC,IAAI,QAAQ,UAAa,IAAI,SAAS,QAAQ,IAAI,SAAS,QAAQ,IAAI,SAAS,MAC9E,OAAO,IAAI;CAGb,MAAM,QAAQ,OAAO;CAGrB,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;CACd,OACE,OAAO,MAAM,IAAI,MAAM;EAAE;EAAM;EAAM;EAAM;CAAM,CAAC;CAEpD,OAAO;AACT;AAEA,SAAS,eACP,MACA,OACA,MACA,QACA,MACG;CACH,MAAM,UAAU;CAChB,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CACvC,MAAM,MAAM,QAAQ,IAAI,IAAI;CAC5B,IACE,QAAQ,UAAa,IAAI,eAAe,cAAc,IAAI,SAAS,QAAQ,IAAI,SAAS,QACrF,IAAI,SAAS,SAAY,SAAS,UAAa,IAAI,UAAU,QAEhE,OAAO,IAAI;CAEb,MAAM,QAAQ,OAAO;CAErB,MAAM,WAAW,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CAC1C,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;EACZ,IAAI,aAAa;EACjB,IAAI,QAAQ;CACd,OACE,QAAQ,IAAI,MAAM;EAAE;EAAM;EAAM;EAAM;EAAO;EAAY,OAAO;CAAS,CAAC;CAE5E,OAAO;AACT;;;;;AAYA,SAAgB,yBAA+B;CAC7C;AACF;;;AAIA,SAAgB,qBAA6B;CAC3C,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,SAAS,KAAK,IAAI,IAAI;CAC5B,IAAI,WAAW,QAAW;EACxB,KAAK,IAAI,MAAM;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE,CAAC;EACzD;CACF;CACA,OAAO;CACP,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,OACjC,IAAI,MAAM,SAAS,QAAW,OAAO,MAAM,OAAO,IAAI;AAE1D"}
@@ -85,8 +85,7 @@ function createNodeStore(state, invalidateOrder, fnFields) {
85
85
  function siblingsOf(parent) {
86
86
  if (parent === null) return state.roots;
87
87
  const p = requireNode(parent);
88
- if (p.kind !== "container") throw new Error(`Scene: parent "${parent}" is not a container`);
89
- return p.children;
88
+ return p.children ??= [];
90
89
  }
91
90
  function detach(id) {
92
91
  const node = requireNode(id);
@@ -110,7 +109,7 @@ function createNodeStore(state, invalidateOrder, fnFields) {
110
109
  }
111
110
  function descendants(id, out) {
112
111
  const n = state.nodes.get(id);
113
- if (!n || n.kind !== "container") return;
112
+ if (!n?.children) return;
114
113
  for (const c of n.children) {
115
114
  out.push(c);
116
115
  descendants(c, out);
@@ -164,7 +163,6 @@ function createNodeStore(state, invalidateOrder, fnFields) {
164
163
  for (const field of fnFields) {
165
164
  const fn = spec[field.spec.field];
166
165
  if (fn === void 0) continue;
167
- if (field.spec.containersOnly && spec.kind !== "container") continue;
168
166
  field.write(node, fn);
169
167
  attached.set(field, fn);
170
168
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nodeStore.js","names":[],"sources":["../../../src/core/scene/nodeStore.ts"],"sourcesContent":["/**\n * The scene's node store: the tree links, the reverse index of who derives\n * from whom, and the function fields a node carries beside its data.\n *\n * Everything here reads and writes `state.nodes` and `state.roots` directly;\n * none of it records. The kit ops call it from both of their directions.\n */\nimport { dwarn } from 'debug/flag';\nimport { createDependentsIndex } from './dependents';\nimport { dropPoseKeyedMemoSlots } from './nodeMemo';\nimport { restoreFn, type FnBearingNode, type NodeFn, type NodeFnField } from './nodeFnFields';\nimport { SceneMembershipError } from './staleReplay';\nimport type { AddNodeSpec, Node, NodeId } from './types';\n\n/** What the store keeps of a scene's state. */\nexport interface NodeStoreState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n}\n\nexport function createNodeStore<TData, TLayer extends string, TPose>(\n state: NodeStoreState<TData, TLayer, TPose>,\n /** Called on every edit to the tree's links. */\n invalidateOrder: () => void,\n fnFields: readonly NodeFnField[],\n) {\n /** Maintained from `kit:add` / `kit:remove` — the only two places a node is\n * created or destroyed, and unlike `add`/`remove` they replay on undo/redo.\n * Only explicit `dependsOn` id lists are registered here; `'children'` is\n * the ancestor walk in `invalidateDependents` instead. */\n const dependents = createDependentsIndex();\n\n /** Nodes carrying `dependsOn: 'children'`. Empty in every scene that has no\n * container hugging its contents, which is what keeps the ancestor walk off\n * the hot path. */\n const childDerived = new Set<NodeId>();\n\n /** Walks `id`'s whole subtree: under a composing pose strategy a container's\n * move carries every descendant, and whatever derives from one. */\n function invalidateDependents(id: NodeId): void {\n invalidateChildDerivedFrom(state.nodes.get(id)?.parent ?? null);\n if (dependents.isEmpty()) return;\n const subtree: NodeId[] = [id];\n descendants(id, subtree);\n for (const src of subtree) {\n for (const dep of dependents.transitiveDependentsOf(src)) {\n const node = state.nodes.get(dep);\n if (node !== undefined) dropPoseKeyedMemoSlots(node);\n }\n }\n }\n\n /** A `dependsOn: 'children'` container derives from nodes the reverse index\n * never sees, so its invalidation runs the other way: up the parent chain\n * from `start` inclusive, and on to each such ancestor's own dependents.\n *\n * Callers that removed or reparented a node pass the parent explicitly —\n * a gone node cannot say who its parent was. */\n function invalidateChildDerivedFrom(start: NodeId | null): void {\n if (childDerived.size === 0) return;\n let cursor = start;\n while (cursor !== null) {\n const node = state.nodes.get(cursor);\n if (node === undefined) return;\n if (childDerived.has(cursor)) {\n dropPoseKeyedMemoSlots(node);\n for (const dep of dependents.transitiveDependentsOf(cursor)) {\n const d = state.nodes.get(dep);\n if (d !== undefined) dropPoseKeyedMemoSlots(d);\n }\n }\n cursor = node.parent;\n }\n }\n\n /** Track (or forget) a node whose `dependsOn` is `'children'`. Paired with\n * `dependents.add` / `.remove` at every site a node is created or destroyed. */\n function trackChildDerived(id: NodeId, dependsOn: Node<TData, TLayer, TPose>['dependsOn']): void {\n if (dependsOn === 'children') childDerived.add(id);\n else childDerived.delete(id);\n }\n\n /** Point `id` at a different set of dependencies, keeping both indices and\n * every memo that reads them in step. Shared by `kit:setDependsOn`'s two\n * directions, which differ only in which value they install.\n *\n * `id`'s own memo is dropped as well as its dependents': its derivation now\n * reads different sources, and nothing else would clear it. */\n function retarget(id: NodeId, dependsOn: Node<TData, TLayer, TPose>['dependsOn']): void {\n const node = requireNode(id) as { dependsOn?: readonly NodeId[] | 'children' };\n if (dependsOn === undefined) delete node.dependsOn;\n else node.dependsOn = dependsOn;\n dependents.add(id, dependsOn === undefined || dependsOn === 'children' ? [] : dependsOn);\n trackChildDerived(id, dependsOn);\n dropPoseKeyedMemoSlots(requireNode(id));\n invalidateDependents(id);\n }\n\n function requireNode(id: NodeId): Node<TData, TLayer, TPose> {\n const n = state.nodes.get(id);\n if (!n) throw new SceneMembershipError(`Scene: unknown node id \"${id}\"`);\n return n;\n }\n\n function siblingsOf(parent: NodeId | null): NodeId[] {\n if (parent === null) return state.roots;\n const p = requireNode(parent);\n if (p.kind !== 'container') {\n throw new Error(`Scene: parent \"${parent}\" is not a container`);\n }\n return p.children;\n }\n\n function detach(id: NodeId): { parent: NodeId | null; index: number } {\n const node = requireNode(id);\n const sibs = siblingsOf(node.parent);\n const idx = sibs.indexOf(id);\n if (idx < 0) throw new Error(`Scene: node \"${id}\" not found in its parent's children`);\n sibs.splice(idx, 1);\n invalidateOrder();\n return { parent: node.parent, index: idx };\n }\n\n function attach(id: NodeId, parent: NodeId | null, index: number): void {\n const sibs = siblingsOf(parent);\n const i = Math.max(0, Math.min(index, sibs.length));\n sibs.splice(i, 0, id);\n const node = requireNode(id);\n (node as { parent: NodeId | null }).parent = parent;\n invalidateOrder();\n }\n\n function descendants(id: NodeId, out: NodeId[]): void {\n const n = state.nodes.get(id);\n if (!n || n.kind !== 'container') return;\n for (const c of n.children) {\n out.push(c);\n descendants(c, out);\n }\n }\n\n /** Everything removing `rootIds` takes with it: each root's subtree, then\n * every node deriving from any of those, then those nodes' subtrees in turn.\n * A dependent is not a descendant, so the second pass is a separate\n * reachability walk; `ids` grows as it runs, which is what carries the\n * cascade transitively and upward through a dependent's own ancestors.\n *\n * Published as {@link Scene.removalClosure}, which is what the delete op and\n * `buildDeleteOps` ask instead of rebuilding the walk from `dependsOn` and\n * `children` — a third cascade relation is then added here alone. */\n function removalClosure(rootIds: readonly NodeId[]): {\n ids: NodeId[];\n removing: Set<NodeId>;\n } {\n const ids: NodeId[] = [];\n const removing = new Set<NodeId>();\n function take(id: NodeId): void {\n if (removing.has(id)) return;\n removing.add(id);\n ids.push(id);\n const sub: NodeId[] = [];\n descendants(id, sub);\n for (const s of sub) {\n if (removing.has(s)) continue;\n removing.add(s);\n ids.push(s);\n }\n }\n for (const root of rootIds) take(root);\n for (let i = 0; i < ids.length; i++) {\n for (const dep of dependents.transitiveDependentsOf(ids[i])) take(dep);\n }\n return { ids, removing };\n }\n\n /** The functions a live `scene.add` attached, by the `kit:add` payload it\n * recorded: redo replays that payload without the spec. Keyed on the\n * payload rather than the node id because an id is reusable, and each\n * add of it owns its own functions. */\n const addedFns = new WeakMap<object, Map<NodeFnField, NodeFn>>();\n\n /** Post-patch the registry-keyed function fields onto a node after its\n * `kit:add` op runs. A function cannot travel through the serializable op\n * payload, so it is attached to the live node here, and kept against\n * `payload` for redo when there is one. `dependsOn` is plain data and\n * rides the payload instead. */\n function patchNodeFunctions(\n spec: AddNodeSpec<TData, TLayer, TPose>,\n id: NodeId,\n payload: object | null,\n ): void {\n const node = state.nodes.get(id) as FnBearingNode | undefined;\n if (node === undefined) return;\n const attached = new Map<NodeFnField, NodeFn>();\n for (const field of fnFields) {\n const fn = (spec as unknown as Record<string, unknown>)[field.spec.field] as NodeFn | undefined;\n if (fn === undefined) continue;\n if (field.spec.containersOnly && spec.kind !== 'container') continue;\n field.write(node, fn);\n attached.set(field, fn);\n }\n if (payload && attached.size > 0) addedFns.set(payload, attached);\n }\n\n /** Re-attach those fields to a node restored from a `kit:remove` snapshot.\n * In-session the clone already carries them; a persisted history does not,\n * so the registry keys are what is left. */\n function restoreNodeFunctions(\n node: Node<TData, TLayer, TPose>,\n keys: Record<string, string | undefined> | undefined,\n ): void {\n if (!keys) return;\n const target = node as unknown as FnBearingNode;\n for (const field of fnFields) {\n if (field.read(target) !== undefined) continue;\n const key = keys[field.spec.opKey];\n if (key === undefined) continue;\n restoreFn(field, target, key, 'kit:remove', dwarn);\n }\n }\n\n return {\n dependents, childDerived, invalidateDependents, invalidateChildDerivedFrom, trackChildDerived, retarget,\n requireNode, siblingsOf, detach, attach, descendants, removalClosure, addedFns, patchNodeFunctions,\n restoreNodeFunctions,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nodeStore.js","names":[],"sources":["../../../src/core/scene/nodeStore.ts"],"sourcesContent":["/**\n * The scene's node store: the tree links, the reverse index of who derives\n * from whom, and the function fields a node carries beside its data.\n *\n * Everything here reads and writes `state.nodes` and `state.roots` directly;\n * none of it records. The kit ops call it from both of their directions.\n */\nimport { dwarn } from 'debug/flag';\nimport { createDependentsIndex } from './dependents';\nimport { dropPoseKeyedMemoSlots } from './nodeMemo';\nimport { restoreFn, type FnBearingNode, type NodeFn, type NodeFnField } from './nodeFnFields';\nimport { SceneMembershipError } from './staleReplay';\nimport type { AddNodeSpec, Node, NodeId } from './types';\n\n/** What the store keeps of a scene's state. */\nexport interface NodeStoreState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n}\n\nexport function createNodeStore<TData, TLayer extends string, TPose>(\n state: NodeStoreState<TData, TLayer, TPose>,\n /** Called on every edit to the tree's links. */\n invalidateOrder: () => void,\n fnFields: readonly NodeFnField[],\n) {\n /** Maintained from `kit:add` / `kit:remove` — the only two places a node is\n * created or destroyed, and unlike `add`/`remove` they replay on undo/redo.\n * Only explicit `dependsOn` id lists are registered here; `'children'` is\n * the ancestor walk in `invalidateDependents` instead. */\n const dependents = createDependentsIndex();\n\n /** Nodes carrying `dependsOn: 'children'`. Empty in every scene that has no\n * container hugging its contents, which is what keeps the ancestor walk off\n * the hot path. */\n const childDerived = new Set<NodeId>();\n\n /** Walks `id`'s whole subtree: under a composing pose strategy a container's\n * move carries every descendant, and whatever derives from one. */\n function invalidateDependents(id: NodeId): void {\n invalidateChildDerivedFrom(state.nodes.get(id)?.parent ?? null);\n if (dependents.isEmpty()) return;\n const subtree: NodeId[] = [id];\n descendants(id, subtree);\n for (const src of subtree) {\n for (const dep of dependents.transitiveDependentsOf(src)) {\n const node = state.nodes.get(dep);\n if (node !== undefined) dropPoseKeyedMemoSlots(node);\n }\n }\n }\n\n /** A `dependsOn: 'children'` container derives from nodes the reverse index\n * never sees, so its invalidation runs the other way: up the parent chain\n * from `start` inclusive, and on to each such ancestor's own dependents.\n *\n * Callers that removed or reparented a node pass the parent explicitly —\n * a gone node cannot say who its parent was. */\n function invalidateChildDerivedFrom(start: NodeId | null): void {\n if (childDerived.size === 0) return;\n let cursor = start;\n while (cursor !== null) {\n const node = state.nodes.get(cursor);\n if (node === undefined) return;\n if (childDerived.has(cursor)) {\n dropPoseKeyedMemoSlots(node);\n for (const dep of dependents.transitiveDependentsOf(cursor)) {\n const d = state.nodes.get(dep);\n if (d !== undefined) dropPoseKeyedMemoSlots(d);\n }\n }\n cursor = node.parent;\n }\n }\n\n /** Track (or forget) a node whose `dependsOn` is `'children'`. Paired with\n * `dependents.add` / `.remove` at every site a node is created or destroyed. */\n function trackChildDerived(id: NodeId, dependsOn: Node<TData, TLayer, TPose>['dependsOn']): void {\n if (dependsOn === 'children') childDerived.add(id);\n else childDerived.delete(id);\n }\n\n /** Point `id` at a different set of dependencies, keeping both indices and\n * every memo that reads them in step. Shared by `kit:setDependsOn`'s two\n * directions, which differ only in which value they install.\n *\n * `id`'s own memo is dropped as well as its dependents': its derivation now\n * reads different sources, and nothing else would clear it. */\n function retarget(id: NodeId, dependsOn: Node<TData, TLayer, TPose>['dependsOn']): void {\n const node = requireNode(id) as { dependsOn?: readonly NodeId[] | 'children' };\n if (dependsOn === undefined) delete node.dependsOn;\n else node.dependsOn = dependsOn;\n dependents.add(id, dependsOn === undefined || dependsOn === 'children' ? [] : dependsOn);\n trackChildDerived(id, dependsOn);\n dropPoseKeyedMemoSlots(requireNode(id));\n invalidateDependents(id);\n }\n\n function requireNode(id: NodeId): Node<TData, TLayer, TPose> {\n const n = state.nodes.get(id);\n if (!n) throw new SceneMembershipError(`Scene: unknown node id \"${id}\"`);\n return n;\n }\n\n function siblingsOf(parent: NodeId | null): NodeId[] {\n if (parent === null) return state.roots;\n const p = requireNode(parent);\n return (p.children ??= []);\n }\n\n function detach(id: NodeId): { parent: NodeId | null; index: number } {\n const node = requireNode(id);\n const sibs = siblingsOf(node.parent);\n const idx = sibs.indexOf(id);\n if (idx < 0) throw new Error(`Scene: node \"${id}\" not found in its parent's children`);\n sibs.splice(idx, 1);\n invalidateOrder();\n return { parent: node.parent, index: idx };\n }\n\n function attach(id: NodeId, parent: NodeId | null, index: number): void {\n const sibs = siblingsOf(parent);\n const i = Math.max(0, Math.min(index, sibs.length));\n sibs.splice(i, 0, id);\n const node = requireNode(id);\n (node as { parent: NodeId | null }).parent = parent;\n invalidateOrder();\n }\n\n function descendants(id: NodeId, out: NodeId[]): void {\n const n = state.nodes.get(id);\n if (!n?.children) return;\n for (const c of n.children) {\n out.push(c);\n descendants(c, out);\n }\n }\n\n /** Everything removing `rootIds` takes with it: each root's subtree, then\n * every node deriving from any of those, then those nodes' subtrees in turn.\n * A dependent is not a descendant, so the second pass is a separate\n * reachability walk; `ids` grows as it runs, which is what carries the\n * cascade transitively and upward through a dependent's own ancestors.\n *\n * Published as {@link Scene.removalClosure}, which is what the delete op and\n * `buildDeleteOps` ask instead of rebuilding the walk from `dependsOn` and\n * `children` — a third cascade relation is then added here alone. */\n function removalClosure(rootIds: readonly NodeId[]): {\n ids: NodeId[];\n removing: Set<NodeId>;\n } {\n const ids: NodeId[] = [];\n const removing = new Set<NodeId>();\n function take(id: NodeId): void {\n if (removing.has(id)) return;\n removing.add(id);\n ids.push(id);\n const sub: NodeId[] = [];\n descendants(id, sub);\n for (const s of sub) {\n if (removing.has(s)) continue;\n removing.add(s);\n ids.push(s);\n }\n }\n for (const root of rootIds) take(root);\n for (let i = 0; i < ids.length; i++) {\n for (const dep of dependents.transitiveDependentsOf(ids[i])) take(dep);\n }\n return { ids, removing };\n }\n\n /** The functions a live `scene.add` attached, by the `kit:add` payload it\n * recorded: redo replays that payload without the spec. Keyed on the\n * payload rather than the node id because an id is reusable, and each\n * add of it owns its own functions. */\n const addedFns = new WeakMap<object, Map<NodeFnField, NodeFn>>();\n\n /** Post-patch the registry-keyed function fields onto a node after its\n * `kit:add` op runs. A function cannot travel through the serializable op\n * payload, so it is attached to the live node here, and kept against\n * `payload` for redo when there is one. `dependsOn` is plain data and\n * rides the payload instead. */\n function patchNodeFunctions(\n spec: AddNodeSpec<TData, TLayer, TPose>,\n id: NodeId,\n payload: object | null,\n ): void {\n const node = state.nodes.get(id) as FnBearingNode | undefined;\n if (node === undefined) return;\n const attached = new Map<NodeFnField, NodeFn>();\n for (const field of fnFields) {\n const fn = (spec as unknown as Record<string, unknown>)[field.spec.field] as NodeFn | undefined;\n if (fn === undefined) continue;\n field.write(node, fn);\n attached.set(field, fn);\n }\n if (payload && attached.size > 0) addedFns.set(payload, attached);\n }\n\n /** Re-attach those fields to a node restored from a `kit:remove` snapshot.\n * In-session the clone already carries them; a persisted history does not,\n * so the registry keys are what is left. */\n function restoreNodeFunctions(\n node: Node<TData, TLayer, TPose>,\n keys: Record<string, string | undefined> | undefined,\n ): void {\n if (!keys) return;\n const target = node as unknown as FnBearingNode;\n for (const field of fnFields) {\n if (field.read(target) !== undefined) continue;\n const key = keys[field.spec.opKey];\n if (key === undefined) continue;\n restoreFn(field, target, key, 'kit:remove', dwarn);\n }\n }\n\n return {\n dependents, childDerived, invalidateDependents, invalidateChildDerivedFrom, trackChildDerived, retarget,\n requireNode, siblingsOf, detach, attach, descendants, removalClosure, addedFns, patchNodeFunctions,\n restoreNodeFunctions,\n };\n}\n"],"mappings":"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"}
@@ -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,17 @@ 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
+ inherited: deps.map((d) => d?.inherited),
46
+ path: parts === null ? null : pathFromParts(parts),
47
+ faces: fonts ? generation : null
44
48
  };
45
49
  results.set(node, result);
46
50
  return result;
@@ -62,13 +66,15 @@ function createPathEffectDerivations(read) {
62
66
  }
63
67
  function current(held, node, deps) {
64
68
  if (held.data !== node.data || held.nodes.length !== deps.length) return false;
69
+ if (held.faces !== null && held.faces !== fontGeneration()) return false;
65
70
  for (let i = 0; i < deps.length; i++) {
66
71
  const d = deps[i];
67
72
  if (held.nodes[i] !== d?.node) return false;
68
73
  if (d === void 0) continue;
69
74
  if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;
70
- if (held.paths[i] !== d.path) return false;
75
+ if (held.paths[i] !== d.path || held.inherited[i] !== d.inherited) return false;
71
76
  }
77
+ if (held.faces !== null) noteFontRead();
72
78
  return true;
73
79
  }
74
80
  /** The parts a path-effect node paints, as its sources stand, or `null` when
@@ -82,7 +88,7 @@ function pathEffectParts(node, deps) {
82
88
  * `derivations` — the ones the scene registers under {@link PATH_EFFECT}. */
83
89
  function pathEffectNode(effect, sources, layer, derivations) {
84
90
  return {
85
- kind: "leaf",
91
+ kind: "mark",
86
92
  layer,
87
93
  pose: {
88
94
  x: 0,
@@ -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 inherited: unknown[];\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 inherited: deps.map((d) => d?.inherited),\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 || held.inherited[i] !== d.inherited) 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: 'mark';\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: 'mark',\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":"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"}
@@ -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,35 @@
1
+ import { pathEffectOf } from "./derive.js";
2
+
3
+ //#region src/core/scene/pathEffects/foundHidden.ts
4
+ /**
5
+ * A path effect's `foundHidden` names its sources by position, so a retarget
6
+ * of its `dependsOn` carries the record along: every writer of `dependsOn`
7
+ * goes through `scene.setDependsOn`, which asks this.
8
+ */
9
+ /**
10
+ * `data` with its path effect's `foundHidden` moved from the positions the
11
+ * sources held in `from` to the ones they hold in `to`, dropping a source
12
+ * `to` no longer names; `null` where nothing moves.
13
+ */
14
+ function foundHiddenRetargeted(data, from, to) {
15
+ const effect = pathEffectOf({ data });
16
+ const found = effect?.foundHidden;
17
+ if (!effect || !found || found.length === 0) return null;
18
+ const was = Array.isArray(from) ? from : [];
19
+ const ids = new Set(found.map((at) => was[at]));
20
+ const next = (Array.isArray(to) ? to : []).flatMap((id, at) => ids.has(id) ? [at] : []);
21
+ if (next.length === found.length && next.every((at, k) => at === found[k])) return null;
22
+ const moved = {
23
+ ...effect,
24
+ foundHidden: next
25
+ };
26
+ if (next.length === 0) delete moved.foundHidden;
27
+ return {
28
+ ...data,
29
+ pathEffect: moved
30
+ };
31
+ }
32
+
33
+ //#endregion
34
+ export { foundHiddenRetargeted };
35
+ //# sourceMappingURL=foundHidden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundHidden.js","names":[],"sources":["../../../../src/core/scene/pathEffects/foundHidden.ts"],"sourcesContent":["/**\n * A path effect's `foundHidden` names its sources by position, so a retarget\n * of its `dependsOn` carries the record along: every writer of `dependsOn`\n * goes through `scene.setDependsOn`, which asks this.\n */\nimport { pathEffectOf } from './derive';\nimport type { PathEffect } from './effects';\n\n/**\n * `data` with its path effect's `foundHidden` moved from the positions the\n * sources held in `from` to the ones they hold in `to`, dropping a source\n * `to` no longer names; `null` where nothing moves.\n */\nexport function foundHiddenRetargeted(data: unknown, from: unknown, to: unknown): unknown {\n const effect = pathEffectOf({ data });\n const found = effect?.foundHidden;\n if (!effect || !found || found.length === 0) return null;\n const was = Array.isArray(from) ? from : [];\n const ids = new Set(found.map((at) => was[at]));\n const next = (Array.isArray(to) ? to : []).flatMap((id, at) => (ids.has(id) ? [at] : []));\n if (next.length === found.length && next.every((at, k) => at === found[k])) return null;\n const moved: PathEffect = { ...effect, foundHidden: next };\n if (next.length === 0) delete moved.foundHidden;\n return { ...(data as object), pathEffect: moved };\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,sBAAsB,MAAe,MAAe,IAAsB;CACxF,MAAM,SAAS,aAAa,EAAE,KAAK,CAAC;CACpC,MAAM,QAAQ,QAAQ;CACtB,IAAI,CAAC,UAAU,CAAC,SAAS,MAAM,WAAW,GAAG,OAAO;CACpD,MAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;CAC1C,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,OAAO,IAAI,GAAG,CAAC;CAC9C,MAAM,QAAQ,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAA,CAAG,SAAS,IAAI,OAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAE;CACxF,IAAI,KAAK,WAAW,MAAM,UAAU,KAAK,OAAO,IAAI,MAAM,OAAO,MAAM,EAAE,GAAG,OAAO;CACnF,MAAM,QAAoB;EAAE,GAAG;EAAQ,aAAa;CAAK;CACzD,IAAI,KAAK,WAAW,GAAG,OAAO,MAAM;CACpC,OAAO;EAAE,GAAI;EAAiB,YAAY;CAAM;AAClD"}