@weasel-js/core 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (586) hide show
  1. package/CHANGELOG.md +611 -0
  2. package/README.md +1 -1
  3. package/dist/WeaselProvider.js +1 -1
  4. package/dist/affordances/pathAnchors.js +10 -6
  5. package/dist/affordances/pathAnchors.js.map +1 -1
  6. package/dist/animation/index.js +3 -3
  7. package/dist/animation/rig/bindRig.js +1 -1
  8. package/dist/animation/rig/rigNode.js.map +1 -1
  9. package/dist/barrel/canvas.js +9 -3
  10. package/dist/barrel/interactions.js +5 -4
  11. package/dist/barrel/paint.js +4 -4
  12. package/dist/barrel/paths.js +10 -6
  13. package/dist/barrel/renderer.js +5 -3
  14. package/dist/barrel/routing.js +7 -12
  15. package/dist/barrel/scene.js +9 -5
  16. package/dist/barrel/text.js +2 -2
  17. package/dist/barrel/viewport.js +9 -4
  18. package/dist/canvas/Canvas.js +5 -2
  19. package/dist/canvas/Canvas.js.map +1 -1
  20. package/dist/canvas/CanvasView.js +23 -34
  21. package/dist/canvas/CanvasView.js.map +1 -1
  22. package/dist/canvas/ModalityHud.js +1 -1
  23. package/dist/canvas/NodeShape.js +4 -1
  24. package/dist/canvas/NodeShape.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  26. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  27. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  28. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  29. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  31. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  32. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  33. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  34. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -0
  35. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  37. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  39. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/features.js +45 -5
  41. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js +1 -11
  43. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  47. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  49. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  51. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  54. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +94 -0
  56. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  57. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  58. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useCanvasTools.js +15 -10
  60. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  62. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  64. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +23 -4
  66. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  67. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -3
  68. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  69. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  70. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  71. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +24 -7
  72. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  73. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  74. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  75. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  76. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  77. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -5
  78. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  79. package/dist/canvas/SceneCanvas.js +27 -24
  80. package/dist/canvas/SceneCanvas.js.map +1 -1
  81. package/dist/canvas/SurfaceCanvas.js +5 -22
  82. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  83. package/dist/canvas/ViewMarker.js +35 -0
  84. package/dist/canvas/ViewMarker.js.map +1 -0
  85. package/dist/canvas/canvasRenderer.js +9 -5
  86. package/dist/canvas/canvasRenderer.js.map +1 -1
  87. package/dist/canvas/contextAttributes.js +14 -0
  88. package/dist/canvas/contextAttributes.js.map +1 -0
  89. package/dist/canvas/defaultDrawOne.js +2 -0
  90. package/dist/canvas/defaultDrawOne.js.map +1 -1
  91. package/dist/canvas/deps/booleans.js +2 -1
  92. package/dist/canvas/deps/booleans.js.map +1 -1
  93. package/dist/canvas/deps/editAnchors.js +9 -113
  94. package/dist/canvas/deps/editAnchors.js.map +1 -1
  95. package/dist/canvas/deps/hitTestArea.js +2 -2
  96. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  97. package/dist/canvas/deps/insert.js +10 -4
  98. package/dist/canvas/deps/insert.js.map +1 -1
  99. package/dist/canvas/deps/pathEditCommits.js +189 -0
  100. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  101. package/dist/canvas/deps/snap.js +12 -9
  102. package/dist/canvas/deps/snap.js.map +1 -1
  103. package/dist/canvas/deps/transformFollowers.js +20 -0
  104. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  105. package/dist/canvas/derivedPath.js +20 -1
  106. package/dist/canvas/derivedPath.js.map +1 -1
  107. package/dist/canvas/drawnBounds.js +38 -4
  108. package/dist/canvas/drawnBounds.js.map +1 -1
  109. package/dist/canvas/inheritedPaints.js +44 -0
  110. package/dist/canvas/inheritedPaints.js.map +1 -0
  111. package/dist/canvas/nodeShapes/derivedPainter.js +23 -16
  112. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/nodePaint.js +13 -34
  114. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  115. package/dist/canvas/nodeShapes/pathPainter.js +12 -10
  116. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  118. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  119. package/dist/canvas/nodeShapes/shapePainter.js +12 -11
  120. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  121. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  122. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  123. package/dist/canvas/overlayRoleStrokes.js +12 -0
  124. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  125. package/dist/canvas/pathOverlay.js +27 -7
  126. package/dist/canvas/pathOverlay.js.map +1 -1
  127. package/dist/canvas/pickReach.js +3 -3
  128. package/dist/canvas/pickReach.js.map +1 -1
  129. package/dist/canvas/pickWalk.js +3 -1
  130. package/dist/canvas/pickWalk.js.map +1 -1
  131. package/dist/canvas/renderDebugSnapshot.js +1 -0
  132. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  133. package/dist/canvas/renderSceneToPixels.js +7 -6
  134. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  135. package/dist/canvas/sceneAdapter.js.map +1 -1
  136. package/dist/canvas/sceneSlotWiring.js +2 -1
  137. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  138. package/dist/canvas/sceneViewRender.js +2 -1
  139. package/dist/canvas/sceneViewRender.js.map +1 -1
  140. package/dist/canvas/surfaceContribution.js +12 -8
  141. package/dist/canvas/surfaceContribution.js.map +1 -1
  142. package/dist/canvas/useCanvasPaint.js +2 -1
  143. package/dist/canvas/useCanvasPaint.js.map +1 -1
  144. package/dist/canvas/useCanvasView.js +4 -2
  145. package/dist/canvas/useCanvasView.js.map +1 -1
  146. package/dist/canvas/useInFlightBounds.js +52 -0
  147. package/dist/canvas/useInFlightBounds.js.map +1 -0
  148. package/dist/canvas/useLayerStack.js +6 -2
  149. package/dist/canvas/useLayerStack.js.map +1 -1
  150. package/dist/canvas/useViewAim.js +121 -0
  151. package/dist/canvas/useViewAim.js.map +1 -0
  152. package/dist/canvas/useViewHelpers.js +7 -9
  153. package/dist/canvas/useViewHelpers.js.map +1 -1
  154. package/dist/canvas/useViewNesting.js +3 -2
  155. package/dist/canvas/useViewNesting.js.map +1 -1
  156. package/dist/canvas/useViewRegistrySurface.js +4 -2
  157. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  158. package/dist/canvas/viewMarkers.js +63 -0
  159. package/dist/canvas/viewMarkers.js.map +1 -0
  160. package/dist/canvas/viewRegistry.js +27 -1
  161. package/dist/canvas/viewRegistry.js.map +1 -1
  162. package/dist/clipboard.d.ts +3 -2
  163. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  164. package/dist/clone.d.ts +4 -3
  165. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  166. package/dist/core/ops/setDependsOn.js +37 -0
  167. package/dist/core/ops/setDependsOn.js.map +1 -0
  168. package/dist/core/ops/writtenBy.js +53 -0
  169. package/dist/core/ops/writtenBy.js.map +1 -0
  170. package/dist/core/pages/fitViewToPage.js +12 -1
  171. package/dist/core/pages/fitViewToPage.js.map +1 -1
  172. package/dist/core/pages/pageEdits.js +163 -0
  173. package/dist/core/pages/pageEdits.js.map +1 -0
  174. package/dist/core/pages/pages.js +30 -1
  175. package/dist/core/pages/pages.js.map +1 -1
  176. package/dist/core/paint/meshKindLazy.js +59 -2
  177. package/dist/core/paint/meshKindLazy.js.map +1 -1
  178. package/dist/core/scene/NodeRouting.js +8 -3
  179. package/dist/core/scene/NodeRouting.js.map +1 -1
  180. package/dist/core/scene/arrivalWindow.js.map +1 -1
  181. package/dist/core/scene/carryDescendants.js +51 -0
  182. package/dist/core/scene/carryDescendants.js.map +1 -0
  183. package/dist/core/scene/clipNothing.js +13 -0
  184. package/dist/core/scene/clipNothing.js.map +1 -0
  185. package/dist/core/scene/depMemo.js +11 -3
  186. package/dist/core/scene/depMemo.js.map +1 -1
  187. package/dist/core/scene/dependents.js +7 -1
  188. package/dist/core/scene/dependents.js.map +1 -1
  189. package/dist/core/scene/derivedParts.js +10 -11
  190. package/dist/core/scene/derivedParts.js.map +1 -1
  191. package/dist/core/scene/docValues.js +57 -0
  192. package/dist/core/scene/docValues.js.map +1 -0
  193. package/dist/core/scene/editPass.js +111 -0
  194. package/dist/core/scene/editPass.js.map +1 -0
  195. package/dist/core/scene/effectivePose.js +13 -2
  196. package/dist/core/scene/effectivePose.js.map +1 -1
  197. package/dist/core/scene/fieldEdits.js +4 -1
  198. package/dist/core/scene/fieldEdits.js.map +1 -1
  199. package/dist/core/scene/fontReads.js +37 -0
  200. package/dist/core/scene/fontReads.js.map +1 -0
  201. package/dist/core/scene/historyFacade.js +1 -0
  202. package/dist/core/scene/historyFacade.js.map +1 -1
  203. package/dist/core/scene/inheritedPaint.js +75 -0
  204. package/dist/core/scene/inheritedPaint.js.map +1 -0
  205. package/dist/core/scene/kitRegistry.js +5 -0
  206. package/dist/core/scene/kitRegistry.js.map +1 -1
  207. package/dist/core/scene/layoutPass.js +28 -3
  208. package/dist/core/scene/layoutPass.js.map +1 -1
  209. package/dist/core/scene/nodeMemo.js +15 -7
  210. package/dist/core/scene/nodeMemo.js.map +1 -1
  211. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  212. package/dist/core/scene/pathEffects/derive.js +7 -2
  213. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  214. package/dist/core/scene/pathEffects/effects.js +3 -1
  215. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  216. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  217. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  218. package/dist/core/scene/recorder.js +28 -19
  219. package/dist/core/scene/recorder.js.map +1 -1
  220. package/dist/core/scene/scene.js +15 -9
  221. package/dist/core/scene/scene.js.map +1 -1
  222. package/dist/core/scene/sceneChanges.js +13 -0
  223. package/dist/core/scene/sceneChanges.js.map +1 -0
  224. package/dist/core/scene/sceneEdits.js +8 -1
  225. package/dist/core/scene/sceneEdits.js.map +1 -1
  226. package/dist/core/scene/sceneMutators.js +1 -0
  227. package/dist/core/scene/sceneMutators.js.map +1 -1
  228. package/dist/core/scene/sceneRecording.js +20 -3
  229. package/dist/core/scene/sceneRecording.js.map +1 -1
  230. package/dist/core/scene/sceneSelection.js +4 -1
  231. package/dist/core/scene/sceneSelection.js.map +1 -1
  232. package/dist/core/scene/sceneState.js +11 -5
  233. package/dist/core/scene/sceneState.js.map +1 -1
  234. package/dist/core/scene/serialization.js +5 -3
  235. package/dist/core/scene/serialization.js.map +1 -1
  236. package/dist/core/scene/types.js.map +1 -1
  237. package/dist/core/scene/unitScopes.js +19 -1
  238. package/dist/core/scene/unitScopes.js.map +1 -1
  239. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  240. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  241. package/dist/core/shapeKinds.js +25 -11
  242. package/dist/core/shapeKinds.js.map +1 -1
  243. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-p8fDIAKt.d.ts} +3 -2
  244. package/dist/debug/createDebugOverlayLayer.js +16 -111
  245. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  246. package/dist/debug/createDebugSink.js +2 -1
  247. package/dist/debug/createDebugSink.js.map +1 -1
  248. package/dist/debug/defaultTheme.js +4 -2
  249. package/dist/debug/defaultTheme.js.map +1 -1
  250. package/dist/debug/features.js +17 -0
  251. package/dist/debug/features.js.map +1 -0
  252. package/dist/debug/overlayPanels.js +117 -0
  253. package/dist/debug/overlayPanels.js.map +1 -0
  254. package/dist/debug/parseDebugFlags.js +4 -13
  255. package/dist/debug/parseDebugFlags.js.map +1 -1
  256. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-mG19aEdD.d.ts} +174 -904
  257. package/dist/devtools/handlers.js +112 -0
  258. package/dist/devtools/handlers.js.map +1 -0
  259. package/dist/devtools/history.js +71 -0
  260. package/dist/devtools/history.js.map +1 -0
  261. package/dist/devtools/hook.js +74 -0
  262. package/dist/devtools/hook.js.map +1 -0
  263. package/dist/devtools/label.js +25 -0
  264. package/dist/devtools/label.js.map +1 -0
  265. package/dist/devtools/notices.js +58 -0
  266. package/dist/devtools/notices.js.map +1 -0
  267. package/dist/devtools/protocol.js +7 -0
  268. package/dist/devtools/protocol.js.map +1 -0
  269. package/dist/devtools/registry.js +90 -0
  270. package/dist/devtools/registry.js.map +1 -0
  271. package/dist/devtools/root.js +80 -0
  272. package/dist/devtools/root.js.map +1 -0
  273. package/dist/devtools/serialize.js +86 -0
  274. package/dist/devtools/serialize.js.map +1 -0
  275. package/dist/devtools/session.js +111 -0
  276. package/dist/devtools/session.js.map +1 -0
  277. package/dist/devtools/transport.js +67 -0
  278. package/dist/devtools/transport.js.map +1 -0
  279. package/dist/devtools/useDevtoolsRoot.js +81 -0
  280. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  281. package/dist/devtools-protocol.d.ts +435 -0
  282. package/dist/devtools-protocol.js +7 -0
  283. package/dist/devtools.d.ts +8 -0
  284. package/dist/devtools.js +12 -0
  285. package/dist/devtools.js.map +1 -0
  286. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  287. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  288. package/dist/features/ingestion/contentHandlers.js +25 -2
  289. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  290. package/dist/features/measure/measureCommands.js +7 -5
  291. package/dist/features/measure/measureCommands.js.map +1 -1
  292. package/dist/features/measure/measureContribution.js +4 -2
  293. package/dist/features/measure/measureContribution.js.map +1 -1
  294. package/dist/features/modes/modeTriggers.js +7 -3
  295. package/dist/features/modes/modeTriggers.js.map +1 -1
  296. package/dist/features/pathEffects/effectSources.js +60 -0
  297. package/dist/features/pathEffects/effectSources.js.map +1 -0
  298. package/dist/features/pathEffects/readSource.js +17 -1
  299. package/dist/features/pathEffects/readSource.js.map +1 -1
  300. package/dist/features/pathEffects/textItems.js +53 -0
  301. package/dist/features/pathEffects/textItems.js.map +1 -0
  302. package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
  303. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  304. package/dist/features/paths/index.js +2 -2
  305. package/dist/features/paths/pathEditExtension.js.map +1 -1
  306. package/dist/features/paths/pathEditState.js +5 -2
  307. package/dist/features/paths/pathEditState.js.map +1 -1
  308. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  309. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  310. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  311. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  312. package/dist/features/selection/selectionPose.js +4 -1
  313. package/dist/features/selection/selectionPose.js.map +1 -1
  314. package/dist/features/snap/gather.js +9 -1
  315. package/dist/features/snap/gather.js.map +1 -1
  316. package/dist/features/snap/lazySession.js +17 -0
  317. package/dist/features/snap/lazySession.js.map +1 -0
  318. package/dist/features/snap/marks.js.map +1 -1
  319. package/dist/features/snap/probes2d.js +27 -19
  320. package/dist/features/snap/probes2d.js.map +1 -1
  321. package/dist/features/snap/readoutLayer.js.map +1 -1
  322. package/dist/features/snap/roles.js +2 -1
  323. package/dist/features/snap/roles.js.map +1 -1
  324. package/dist/features/snap/rotationSource.js +25 -8
  325. package/dist/features/snap/rotationSource.js.map +1 -1
  326. package/dist/features/snap/sceneSession2d.js +6 -4
  327. package/dist/features/snap/sceneSession2d.js.map +1 -1
  328. package/dist/features/snap/session2d.js +37 -23
  329. package/dist/features/snap/session2d.js.map +1 -1
  330. package/dist/features/snap/spanSnap.js +5 -2
  331. package/dist/features/snap/spanSnap.js.map +1 -1
  332. package/dist/features/text/sceneTextDrafts.js +65 -0
  333. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  334. package/dist/features/text/textHitAt.js +31 -2
  335. package/dist/features/text/textHitAt.js.map +1 -1
  336. package/dist/features/text/useSceneTextEdit.js +22 -18
  337. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  338. package/dist/features/viewports/aim.js +105 -0
  339. package/dist/features/viewports/aim.js.map +1 -0
  340. package/dist/features/viewports/viewMarker.js +82 -0
  341. package/dist/features/viewports/viewMarker.js.map +1 -0
  342. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  343. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  344. package/dist/icons.table.js +1 -1
  345. package/dist/icons.table.js.map +1 -1
  346. package/dist/icons.table.json +0 -10
  347. package/dist/index.d.ts +1198 -894
  348. package/dist/index.js +55 -38
  349. package/dist/interactions/actions/align/action.js +6 -4
  350. package/dist/interactions/actions/align/action.js.map +1 -1
  351. package/dist/interactions/actions/align/align.js.map +1 -1
  352. package/dist/interactions/actions/booleans/action.js +7 -1
  353. package/dist/interactions/actions/booleans/action.js.map +1 -1
  354. package/dist/interactions/actions/booleans/booleans.js +2 -1
  355. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  356. package/dist/interactions/actions/booleans/operands.js +6 -5
  357. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  358. package/dist/interactions/actions/clipboard/action.js +27 -17
  359. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  360. package/dist/interactions/actions/clone/action.js +1 -1
  361. package/dist/interactions/actions/clone/behaviors.js +4 -4
  362. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  363. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  364. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  365. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  366. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  367. package/dist/interactions/actions/defaults/bendSegment.js +8 -5
  368. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  369. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  370. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  371. package/dist/interactions/actions/defaults/contextMenu.js +17 -8
  372. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  373. package/dist/interactions/actions/defaults/createPaintAction.js +2 -2
  374. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  375. package/dist/interactions/actions/defaults/delete.js +64 -41
  376. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  377. package/dist/interactions/actions/defaults/duplicate.js +12 -9
  378. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  379. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
  380. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  381. package/dist/interactions/actions/defaults/escape.js +11 -8
  382. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  383. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  384. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  385. package/dist/interactions/actions/defaults/group.js +21 -15
  386. package/dist/interactions/actions/defaults/group.js.map +1 -1
  387. package/dist/interactions/actions/defaults/ingest.js +17 -9
  388. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  389. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  390. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  391. package/dist/interactions/actions/defaults/nudge.js +21 -40
  392. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  393. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  394. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  395. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  396. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  397. package/dist/interactions/actions/defaults/reorder.js +24 -6
  398. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  399. package/dist/interactions/actions/defaults/selectAll.js +8 -5
  400. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  401. package/dist/interactions/actions/defaults/setFill.js +7 -3
  402. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  403. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  404. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  405. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  406. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  407. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  408. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  409. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  410. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  411. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  412. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  413. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  414. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  415. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  416. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  417. package/dist/interactions/actions/distribute/action.js +6 -4
  418. package/dist/interactions/actions/distribute/action.js.map +1 -1
  419. package/dist/interactions/actions/drawnBoxes.js +2 -1
  420. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  421. package/dist/interactions/actions/edit-anchors/action.js +10 -10
  422. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  423. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  424. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  425. package/dist/interactions/actions/flip/action.js +14 -6
  426. package/dist/interactions/actions/flip/action.js.map +1 -1
  427. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  428. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  429. package/dist/interactions/actions/insert/action.js +1 -2
  430. package/dist/interactions/actions/insert/action.js.map +1 -1
  431. package/dist/interactions/actions/insert/commit.js +1 -1
  432. package/dist/interactions/actions/insert/parent.js +11 -0
  433. package/dist/interactions/actions/insert/parent.js.map +1 -0
  434. package/dist/interactions/actions/insert/placeAction.js +7 -2
  435. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  436. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  437. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  438. package/dist/interactions/actions/insert/session.js +5 -1
  439. package/dist/interactions/actions/insert/session.js.map +1 -1
  440. package/dist/interactions/actions/move/action.js +15 -8
  441. package/dist/interactions/actions/move/action.js.map +1 -1
  442. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  443. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  444. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  445. package/dist/interactions/actions/move/capture.js +1 -1
  446. package/dist/interactions/actions/move/commit/index.js +0 -2
  447. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  448. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  449. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  450. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  451. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  452. package/dist/interactions/actions/move/frame.js +4 -10
  453. package/dist/interactions/actions/move/frame.js.map +1 -1
  454. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  455. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  456. package/dist/interactions/actions/move/layout/container.js +2 -1
  457. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  458. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  459. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  460. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  461. package/dist/interactions/actions/move/layout/exits.js +5 -5
  462. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  463. package/dist/interactions/actions/move/session.js +14 -3
  464. package/dist/interactions/actions/move/session.js.map +1 -1
  465. package/dist/interactions/actions/move/translate.js +8 -37
  466. package/dist/interactions/actions/move/translate.js.map +1 -1
  467. package/dist/interactions/actions/moveNodes.js +87 -0
  468. package/dist/interactions/actions/moveNodes.js.map +1 -0
  469. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  470. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  471. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  472. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  473. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  474. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  475. package/dist/interactions/actions/planeInput.js.map +1 -1
  476. package/dist/interactions/actions/resize/action.js +83 -72
  477. package/dist/interactions/actions/resize/action.js.map +1 -1
  478. package/dist/interactions/actions/resize/commit.js +2 -1
  479. package/dist/interactions/actions/resize/commit.js.map +1 -1
  480. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  481. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  482. package/dist/interactions/actions/resize/session.js +2 -0
  483. package/dist/interactions/actions/resize/session.js.map +1 -1
  484. package/dist/interactions/actions/resolvePreviews.js +3 -3
  485. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  486. package/dist/interactions/actions/rotate/action.js +7 -4
  487. package/dist/interactions/actions/rotate/action.js.map +1 -1
  488. package/dist/interactions/actions/rotate/commit.js +2 -1
  489. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  490. package/dist/interactions/actions/rotate/session.js +2 -0
  491. package/dist/interactions/actions/rotate/session.js.map +1 -1
  492. package/dist/interactions/actions/selectionSubtree.js +25 -9
  493. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  494. package/dist/interactions/actions/testUtils.js.map +1 -1
  495. package/dist/interactions/actions/transform-again/action.js +13 -11
  496. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  497. package/dist/interactions/actions/transformFinish.js +106 -0
  498. package/dist/interactions/actions/transformFinish.js.map +1 -0
  499. package/dist/json.d.ts +4 -0
  500. package/dist/json.js +3 -0
  501. package/dist/kitSettingsSchema.js +8 -5
  502. package/dist/kitSettingsSchema.js.map +1 -1
  503. package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
  504. package/dist/math.d.ts +1 -1
  505. package/dist/math.js +2 -1
  506. package/dist/move.d.ts +5 -4
  507. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  508. package/dist/resize.d.ts +4 -3
  509. package/dist/routing.d.ts +1 -1
  510. package/dist/routing.js +2 -2
  511. package/dist/test-hook/createTestHook.js +1 -1
  512. package/dist/test-hook/createTestHook.js.map +1 -1
  513. package/dist/test-hook/useTestHook.js +2 -1
  514. package/dist/test-hook/useTestHook.js.map +1 -1
  515. package/dist/test-seams.d.ts +9 -5
  516. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  517. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  518. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  519. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  520. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  521. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  522. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  523. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  524. package/dist/tools/builtin/image/useImageTool.js +1 -0
  525. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  526. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  527. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  528. package/dist/tools/builtin/line/useLineTool.js +1 -0
  529. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  530. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  531. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  532. package/dist/tools/builtin/pen/penHandle.js +3 -5
  533. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  534. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  535. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  536. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  537. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  538. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  539. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  540. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  541. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  542. package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
  543. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  544. package/dist/tools/builtin/select/pressDecision.js +32 -0
  545. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  546. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  547. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  548. package/dist/tools/builtin/select/useSelectTool.js +13 -10
  549. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  550. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  551. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  552. package/dist/tools/builtin/star/useStarTool.js +13 -4
  553. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  554. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  555. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  556. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  557. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  558. package/dist/tools/builtin/text/useTextTool.js +3 -1
  559. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  560. package/dist/tools/index.js +26 -0
  561. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  562. package/dist/types-BN9hbdMI.d.ts +184 -0
  563. package/dist/types-CniVctmk.d.ts +949 -0
  564. package/dist/types-oFeQ5VjV.d.ts +264 -0
  565. package/dist/util/paint.js +1 -3
  566. package/dist/util/paint.js.map +1 -1
  567. package/dist/version.js +1 -1
  568. package/package.json +39 -25
  569. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  570. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  571. package/dist/tools/builtin/bone/boneActions.js +0 -156
  572. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  573. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  574. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  575. package/dist/tools/builtin/bone/placedRig.js +0 -28
  576. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  577. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  578. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  579. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  580. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  581. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  582. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  583. package/dist/tools/builtin/weight/weightActions.js +0 -140
  584. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  585. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  586. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"effectivePose.js","names":[],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. A reader of `node.pose` disagrees with the render about where a\n * dragged or derived node is.\n */\nimport { deriveRecorded, emptyDepRecord, sameDeps, type DepRecord } from './depMemo';\nimport type { PoseComposition } from './composeFrame';\nimport { carryDepPath, depFrameRebase, pathAsPainted } from './depFrame';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, DerivePathFn, DerivePoseFn, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n /** Read under a composing strategy, to find the frame its pose is in. */\n parent?: NodeId | null;\n derivePose?: DerivePoseFn<TPose>;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n /** When it composes frames, a derivation reads each dependency in the\n * frame of the node deriving. */\n readonly composition?: PoseComposition<TPose>;\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = depsAsRead(source, node, reading);\n // Keyed on the authored pose, which only sees a change someone pushed, so\n // the record also carries what the derivation read and re-checks it by\n // value on a hit — see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { ...emptyDepRecord<TPose>(), value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = deriveRecorded(into, deps, (handed) => derive(node as never, handed));\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reader?: PosedNode<TPose>,\n): DerivedDep<TPose> | undefined {\n return reader === undefined ? depIn(source, id, 'effective') : depsAsRead(source, reader, 'effective')(id);\n}\n\n/** `(id) => that dependency as `reader`'s derivation is handed it`: its pose\n * in `reader`'s frame where the scene composes frames. A `'children'`\n * dependent shares its children's frame, so it reads them as they are. */\nfunction depsAsRead<TPose>(\n source: PoseSource<TPose>,\n reader: PosedNode<TPose>,\n reading: Reading,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const plain = (id: NodeId) => depIn(source, id, reading);\n if (reader.dependsOn === 'children') return plain;\n const rebase = depFrameRebase(\n source.composition,\n reader,\n (id) => source.get(id),\n (id) => poseIn(source, source.get(id)!, reading),\n (id) => {\n const n = source.get(id);\n return n === undefined || definesFrame(n);\n },\n );\n if (rebase === null) return plain;\n return (id) => {\n const dep = plain(id);\n if (dep === undefined) return undefined;\n const pose = rebase(id, dep.pose);\n return pose === dep.pose ? dep : new RebasedDep(dep, pose);\n };\n}\n\n/** A dependency read from another frame: its pose rebased, and its path\n * carried by the same frame change when asked for. */\nclass RebasedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n\n constructor(private readonly dep: DerivedDep<TPose>, readonly pose: TPose) {\n this.node = dep.node;\n }\n\n get path(): Path | null {\n const path = this.dep.path;\n return path === null ? null : carryDepPath(path, this.dep.pose, this.pose);\n }\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n private readonly shown: Path | undefined;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n const override = reading === 'effective' ? source.overrides.read(posed.id) : undefined;\n this.shown = override?.path;\n this.pose = override?.pose ?? derivedPoseIn(source, posed, reading) ?? posed.pose;\n }\n\n get path(): Path | null {\n if (this.shown !== undefined) return pathAsPainted(this.shown, this.pose, this.pose);\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n depsAsRead(source, this.posed, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture, such as an action\n * capturing the `from` of a transform op.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at. Every pose a reader acts on\n * comes from here.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !(node.dependsOn === 'children' && node.derivePose !== undefined);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"effectivePose.js","names":[],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. A reader of `node.pose` disagrees with the render about where a\n * dragged or derived node is.\n */\nimport { deriveRecorded, emptyDepRecord, sameDeps, type DepRecord } from './depMemo';\nimport type { PoseComposition } from './composeFrame';\nimport { carryDepPath, depFrameRebase, pathAsPainted } from './depFrame';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, DerivePathFn, DerivePoseFn, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n /** Read under a composing strategy, to find the frame its pose is in. */\n parent?: NodeId | null;\n derivePose?: DerivePoseFn<TPose>;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n /** When it composes frames, a derivation reads each dependency in the\n * frame of the node deriving. */\n readonly composition?: PoseComposition<TPose>;\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = depsAsRead(source, node, reading);\n // Keyed on the authored pose, which only sees a change someone pushed, so\n // the record also carries what the derivation read and re-checks it by\n // value on a hit — see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { ...emptyDepRecord<TPose>(), value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = deriveRecorded(into, deps, (handed) => derive(node as never, handed));\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reader?: PosedNode<TPose>,\n): DerivedDep<TPose> | undefined {\n return reader === undefined ? depIn(source, id, 'effective') : depsAsRead(source, reader, 'effective')(id);\n}\n\n/** `id` as {@link derivedDepOf} answers it, but at the pose and path the\n * document holds: no override anywhere down its dependencies counts. For a\n * reader that must not see an in-flight gesture, such as an edit pass. */\nexport function documentDepOf<TPose>(source: PoseSource<TPose>, id: NodeId): DerivedDep<TPose> | undefined {\n return depIn(source, id, 'document');\n}\n\n/** `(id) => that dependency as `reader`'s derivation is handed it`: its pose\n * in `reader`'s frame where the scene composes frames. A `'children'`\n * dependent shares its children's frame, so it reads them as they are. */\nfunction depsAsRead<TPose>(\n source: PoseSource<TPose>,\n reader: PosedNode<TPose>,\n reading: Reading,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const plain = (id: NodeId) => depIn(source, id, reading);\n if (reader.dependsOn === 'children') return plain;\n const rebase = depFrameRebase(\n source.composition,\n reader,\n (id) => source.get(id),\n (id) => poseIn(source, source.get(id)!, reading),\n (id) => {\n const n = source.get(id);\n return n === undefined || definesFrame(n);\n },\n );\n if (rebase === null) return plain;\n return (id) => {\n const dep = plain(id);\n if (dep === undefined) return undefined;\n const pose = rebase(id, dep.pose);\n return pose === dep.pose ? dep : new RebasedDep(dep, pose);\n };\n}\n\n/** A dependency read from another frame: its pose rebased, and its path\n * carried by the same frame change when asked for. */\nclass RebasedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n\n constructor(private readonly dep: DerivedDep<TPose>, readonly pose: TPose) {\n this.node = dep.node;\n }\n\n get path(): Path | null {\n const path = this.dep.path;\n return path === null ? null : carryDepPath(path, this.dep.pose, this.pose);\n }\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n private readonly shown: Path | undefined;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n const override = reading === 'effective' ? source.overrides.read(posed.id) : undefined;\n this.shown = override?.path;\n this.pose = override?.pose ?? derivedPoseIn(source, posed, reading) ?? posed.pose;\n }\n\n get path(): Path | null {\n if (this.shown !== undefined) return pathAsPainted(this.shown, this.pose, this.pose);\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n depsAsRead(source, this.posed, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture, such as an action\n * capturing the `from` of a transform op.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at. Every pose a reader acts on\n * comes from here.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !isEnvelope(node);\n}\n\n/** Whether `node` derives its pose from its children: a group that is its\n * members, so moving them moves it. */\nexport function isEnvelope(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return node.dependsOn === 'children' && node.derivePose !== undefined;\n}\n"],"mappings":"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"}
@@ -23,8 +23,11 @@ function createFieldEdits(env) {
23
23
  const node = requireNode(id);
24
24
  refuse(id, "set the pose of");
25
25
  const resizes = node.kind === "container" && resized(layoutFrame, id, node.pose, pose);
26
- edit("setPose", resizes, () => {
26
+ const parent = node.parent === null ? void 0 : requireNode(node.parent);
27
+ const arranger = parent?.kind === "container" && parent.layout !== void 0 ? parent.id : null;
28
+ edit("setPose", resizes || arranger !== null, () => {
27
29
  if (resizes) noteChange(id);
30
+ noteChange(arranger);
28
31
  record("kit:setPose", {
29
32
  id,
30
33
  from: node.pose,
@@ -1 +1 @@
1
- {"version":3,"file":"fieldEdits.js","names":[],"sources":["../../../src/core/scene/fieldEdits.ts"],"sourcesContent":["/**\n * The scene's edits to one node's fields: its data, pose, layer, dependency\n * list, layout, and unit scope. Each checks its arguments, then records.\n */\nimport type { LayoutStrategy } from '../../layout/types';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { sameDependsOn } from './dependents';\nimport type { SetLayoutPayload } from './kitOps';\nimport type { createLayerStack } from './layerStack';\nimport { resized, type ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { Recorder } from './recorder';\nimport type { NodeId, Scene } from './types';\n\nexport interface FieldEditsEnv<TData, TLayer extends string, TPose> {\n store: Pick<ReturnType<typeof createNodeStore<TData, TLayer, TPose>>, 'requireNode' | 'descendants'>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer'>;\n layers: Pick<ReturnType<typeof createLayerStack<TLayer>>, 'requireLayerIndex' | 'assertSubtreeLayer'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n /** The registry's layouts, by key and back. */\n layoutField: NodeFnField;\n /** Where a live `kit:setLayout` payload's strategies are kept. */\n layoutRefs: WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>;\n}\n\nexport type FieldEdits<TData, TLayer extends string, TPose> = Pick<\n Scene<TData, TLayer, TPose>,\n 'update' | 'setPose' | 'setLayer' | 'setDependsOn' | 'setLayout' | 'setUnits'\n>;\n\nexport function createFieldEdits<TData, TLayer extends string, TPose>(\n env: FieldEditsEnv<TData, TLayer, TPose>,\n): FieldEdits<TData, TLayer, TPose> {\n const { layoutFrame, layoutField, layoutRefs } = env;\n const { requireNode, descendants } = env.store;\n const { refuse, refuseLayer } = env.locks;\n const { requireLayerIndex, assertSubtreeLayer } = env.layers;\n const { record, edit, batch } = env.recorder;\n const { noteChange, layoutDeclared } = env.arrivals;\n\n return {\n update(id, patch) {\n const node = requireNode(id);\n refuse(id, 'update');\n record('kit:setData', { id, from: node.data, to: patch.data }, 'update');\n },\n\n setPose(id, pose) {\n const node = requireNode(id);\n refuse(id, 'set the pose of');\n const resizes = node.kind === 'container' && resized(layoutFrame, id, node.pose, pose);\n edit('setPose', resizes, () => {\n if (resizes) noteChange(id);\n record('kit:setPose', { id, from: node.pose, to: pose }, 'setPose');\n });\n },\n\n setLayer(id, layer) {\n requireLayerIndex(layer);\n const node = requireNode(id);\n refuse(id, 'relayer');\n if (node.layer !== layer) refuseLayer(layer, 'move nodes onto');\n if (node.parent !== null) {\n const parentNode = requireNode(node.parent);\n assertSubtreeLayer(id, layer, node.parent, parentNode.layer, 'relayer');\n }\n if (node.layer === layer) return;\n const subtree: NodeId[] = [id];\n descendants(id, subtree);\n for (const sid of subtree) refuse(sid, 'relayer');\n if (subtree.length === 1) {\n record('kit:setLayer', { id, from: node.layer, to: layer }, 'setLayer');\n } else {\n batch('setLayer', () => {\n for (const sid of subtree) {\n const cur = requireNode(sid);\n if (cur.layer === layer) continue;\n record('kit:setLayer', { id: sid, from: cur.layer, to: layer }, 'setLayer');\n }\n });\n }\n },\n\n setDependsOn(id, dependsOn) {\n const node = requireNode(id);\n refuse(id, 'retarget');\n if (sameDependsOn(node.dependsOn, dependsOn)) return;\n record('kit:setDependsOn', { id, from: node.dependsOn, to: dependsOn }, 'setDependsOn');\n },\n\n setLayout(id, layout) {\n const node = requireNode(id);\n if (node.kind !== 'container') throw new Error(`Scene: \"${id}\" is not a container`);\n refuse(id, 'set the layout of');\n let to: LayoutStrategy<TPose> | null;\n if (typeof layout === 'string') {\n to = (layoutField.fnOf(layout) as LayoutStrategy<TPose> | undefined) ?? null;\n if (to === null) throw new Error(`Scene: no layout \"${layout}\" in this scene's registry`);\n } else {\n to = layout;\n }\n const from = node.layout ?? null;\n if (from === to) return;\n if (to !== null) layoutDeclared();\n const fromKey = from === null ? undefined : layoutField.keyOf(from);\n const toKey = to === null ? undefined : layoutField.keyOf(to);\n const payload: SetLayoutPayload = {\n id,\n ...(fromKey !== undefined ? { fromKey } : {}),\n ...(toKey !== undefined ? { toKey } : {}),\n };\n layoutRefs.set(payload, { from, to });\n edit('setLayout', true, () => {\n noteChange(id);\n record('kit:setLayout', payload, 'setLayout');\n });\n },\n\n setUnits(id, units) {\n const node = requireNode(id);\n if (node.kind !== 'container') throw new Error(`Scene: \"${id}\" is not a container`);\n refuse(id, 'set the units of');\n record('kit:setUnits', { id, from: node.units, to: units ?? undefined }, 'setUnits');\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fieldEdits.js","names":[],"sources":["../../../src/core/scene/fieldEdits.ts"],"sourcesContent":["/**\n * The scene's edits to one node's fields: its data, pose, layer, dependency\n * list, layout, and unit scope. Each checks its arguments, then records.\n */\nimport type { LayoutStrategy } from '../../layout/types';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { sameDependsOn } from './dependents';\nimport type { SetLayoutPayload } from './kitOps';\nimport type { createLayerStack } from './layerStack';\nimport { resized, type ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { Recorder } from './recorder';\nimport type { NodeId, Scene } from './types';\n\nexport interface FieldEditsEnv<TData, TLayer extends string, TPose> {\n store: Pick<ReturnType<typeof createNodeStore<TData, TLayer, TPose>>, 'requireNode' | 'descendants'>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer'>;\n layers: Pick<ReturnType<typeof createLayerStack<TLayer>>, 'requireLayerIndex' | 'assertSubtreeLayer'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n /** The registry's layouts, by key and back. */\n layoutField: NodeFnField;\n /** Where a live `kit:setLayout` payload's strategies are kept. */\n layoutRefs: WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>;\n}\n\nexport type FieldEdits<TData, TLayer extends string, TPose> = Pick<\n Scene<TData, TLayer, TPose>,\n 'update' | 'setPose' | 'setLayer' | 'setDependsOn' | 'setLayout' | 'setUnits'\n>;\n\nexport function createFieldEdits<TData, TLayer extends string, TPose>(\n env: FieldEditsEnv<TData, TLayer, TPose>,\n): FieldEdits<TData, TLayer, TPose> {\n const { layoutFrame, layoutField, layoutRefs } = env;\n const { requireNode, descendants } = env.store;\n const { refuse, refuseLayer } = env.locks;\n const { requireLayerIndex, assertSubtreeLayer } = env.layers;\n const { record, edit, batch } = env.recorder;\n const { noteChange, layoutDeclared } = env.arrivals;\n\n return {\n update(id, patch) {\n const node = requireNode(id);\n refuse(id, 'update');\n record('kit:setData', { id, from: node.data, to: patch.data }, 'update');\n },\n\n setPose(id, pose) {\n const node = requireNode(id);\n refuse(id, 'set the pose of');\n const resizes = node.kind === 'container' && resized(layoutFrame, id, node.pose, pose);\n // A container with a layout owns where its children go, however one is moved.\n const parent = node.parent === null ? undefined : requireNode(node.parent);\n const arranger = parent?.kind === 'container' && parent.layout !== undefined ? parent.id : null;\n edit('setPose', resizes || arranger !== null, () => {\n if (resizes) noteChange(id);\n noteChange(arranger);\n record('kit:setPose', { id, from: node.pose, to: pose }, 'setPose');\n });\n },\n\n setLayer(id, layer) {\n requireLayerIndex(layer);\n const node = requireNode(id);\n refuse(id, 'relayer');\n if (node.layer !== layer) refuseLayer(layer, 'move nodes onto');\n if (node.parent !== null) {\n const parentNode = requireNode(node.parent);\n assertSubtreeLayer(id, layer, node.parent, parentNode.layer, 'relayer');\n }\n if (node.layer === layer) return;\n const subtree: NodeId[] = [id];\n descendants(id, subtree);\n for (const sid of subtree) refuse(sid, 'relayer');\n if (subtree.length === 1) {\n record('kit:setLayer', { id, from: node.layer, to: layer }, 'setLayer');\n } else {\n batch('setLayer', () => {\n for (const sid of subtree) {\n const cur = requireNode(sid);\n if (cur.layer === layer) continue;\n record('kit:setLayer', { id: sid, from: cur.layer, to: layer }, 'setLayer');\n }\n });\n }\n },\n\n setDependsOn(id, dependsOn) {\n const node = requireNode(id);\n refuse(id, 'retarget');\n if (sameDependsOn(node.dependsOn, dependsOn)) return;\n record('kit:setDependsOn', { id, from: node.dependsOn, to: dependsOn }, 'setDependsOn');\n },\n\n setLayout(id, layout) {\n const node = requireNode(id);\n if (node.kind !== 'container') throw new Error(`Scene: \"${id}\" is not a container`);\n refuse(id, 'set the layout of');\n let to: LayoutStrategy<TPose> | null;\n if (typeof layout === 'string') {\n to = (layoutField.fnOf(layout) as LayoutStrategy<TPose> | undefined) ?? null;\n if (to === null) throw new Error(`Scene: no layout \"${layout}\" in this scene's registry`);\n } else {\n to = layout;\n }\n const from = node.layout ?? null;\n if (from === to) return;\n if (to !== null) layoutDeclared();\n const fromKey = from === null ? undefined : layoutField.keyOf(from);\n const toKey = to === null ? undefined : layoutField.keyOf(to);\n const payload: SetLayoutPayload = {\n id,\n ...(fromKey !== undefined ? { fromKey } : {}),\n ...(toKey !== undefined ? { toKey } : {}),\n };\n layoutRefs.set(payload, { from, to });\n edit('setLayout', true, () => {\n noteChange(id);\n record('kit:setLayout', payload, 'setLayout');\n });\n },\n\n setUnits(id, units) {\n const node = requireNode(id);\n if (node.kind !== 'container') throw new Error(`Scene: \"${id}\" is not a container`);\n refuse(id, 'set the units of');\n record('kit:setUnits', { id, from: node.units, to: units ?? undefined }, 'setUnits');\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { faceGeneration } from "@weasel-js/font";
2
+
3
+ //#region src/core/scene/fontReads.ts
4
+ /**
5
+ * Whether a derivation read glyph outlines. Outlines land asynchronously, so a
6
+ * derivation that read them while a face was loading is stale once it lands,
7
+ * with nothing in the scene changed. A derivation that noted a read records
8
+ * {@link fontGeneration} and re-derives when it moves.
9
+ */
10
+ var noted = false;
11
+ /** Note that the derivation running now read glyph outlines. */
12
+ function noteFontRead() {
13
+ noted = true;
14
+ }
15
+ /** Run `fn`, reporting whether it — or anything it ran — noted a font read.
16
+ * A read inside counts toward any enclosing `withFontReads` too. */
17
+ function withFontReads(fn) {
18
+ const outer = noted;
19
+ noted = false;
20
+ try {
21
+ return {
22
+ value: fn(),
23
+ fonts: noted
24
+ };
25
+ } finally {
26
+ noted = outer || noted;
27
+ }
28
+ }
29
+ /** The face generation a font-reading derivation is checked against: it
30
+ * moves when a face lands, not on every glyph bake. */
31
+ function fontGeneration() {
32
+ return faceGeneration();
33
+ }
34
+
35
+ //#endregion
36
+ export { fontGeneration, noteFontRead, withFontReads };
37
+ //# sourceMappingURL=fontReads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontReads.js","names":[],"sources":["../../../src/core/scene/fontReads.ts"],"sourcesContent":["/**\n * Whether a derivation read glyph outlines. Outlines land asynchronously, so a\n * derivation that read them while a face was loading is stale once it lands,\n * with nothing in the scene changed. A derivation that noted a read records\n * {@link fontGeneration} and re-derives when it moves.\n */\nimport { faceGeneration } from '@weasel-js/font';\n\nlet noted = false;\n\n/** Note that the derivation running now read glyph outlines. */\nexport function noteFontRead(): void {\n noted = true;\n}\n\n/** Run `fn`, reporting whether it — or anything it ran — noted a font read.\n * A read inside counts toward any enclosing `withFontReads` too. */\nexport function withFontReads<T>(fn: () => T): { value: T; fonts: boolean } {\n const outer = noted;\n noted = false;\n try {\n const value = fn();\n return { value, fonts: noted };\n } finally {\n noted = outer || noted;\n }\n}\n\n/** The face generation a font-reading derivation is checked against: it\n * moves when a face lands, not on every glyph bake. */\nexport function fontGeneration(): number {\n return faceGeneration();\n}\n"],"mappings":";;;;;;;;;AAQA,IAAI,QAAQ;;AAGZ,SAAgB,eAAqB;CACnC,QAAQ;AACV;;;AAIA,SAAgB,cAAiB,IAA2C;CAC1E,MAAM,QAAQ;CACd,QAAQ;CACR,IAAI;EAEF,OAAO;GAAE,OADK,GACL;GAAO,OAAO;EAAM;CAC/B,UAAU;EACR,QAAQ,SAAS;CACnB;AACF;;;AAIA,SAAgB,iBAAyB;CACvC,OAAO,eAAe;AACxB"}
@@ -154,6 +154,7 @@ function createSceneHistory(env) {
154
154
  branches: () => engine.branches(),
155
155
  getVersion: () => engine.getVersion(),
156
156
  subscribe: (listener) => engine.subscribe(listener),
157
+ observe: (listener) => engine.observe(listener),
157
158
  serialize: methods.serializeHistory,
158
159
  allForwardOps: () => engine.allForwardOps(),
159
160
  seal: () => engine.seal(),
@@ -1 +1 @@
1
- {"version":3,"file":"historyFacade.js","names":[],"sources":["../../../src/core/scene/historyFacade.ts"],"sourcesContent":["/**\n * `scene.history`: the `History` the kit's `undo`/`redo` actions resolve\n * their `history` dep to, and the handle a mode machine opens journals\n * against.\n *\n * Handing out the engine directly would let an action-driven undo replay ops\n * with scene-side recording still armed and without bumping the scene's\n * version, so every member that mutates routes through the scene's own\n * wrappers and one `notify()`; reads forward to the engine.\n */\nimport type { History, Journal } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport { dwarn } from 'debug/flag';\nimport { SCENE_HISTORY_FORM, type SerializedSceneHistory } from './historyForm';\nimport type { Scene } from './types';\n\n/** What the facade needs from the scene it fronts. */\nexport interface SceneHistoryEnv {\n /** The scene's own undo/redo engine. */\n engine: History;\n /** Run a step that applies ops through an engine: all-or-nothing, with\n * scene-side recording suppressed. */\n applying(step: () => void): void;\n /** An op as the engine applies it: its entry records the kit ops it\n * caused. */\n causing(op: Op): Op;\n /** Run a step that replays recorded ops: an undo, a redo, a cancel. */\n replay(step: () => void): void;\n notify(): void;\n refuseUntracked(verb: string): void;\n /** Run an op-driven edit with its arrivals settled into the same batch. */\n runArrivalEdit(ops: Op[], run: (ops: Op[]) => void): void;\n /** Run an edit applying ops, labeled `label` for the pose writes it reports. */\n labeled<T>(label: string, fn: () => T): T;\n /** The journal `applyBatch` writes to now, or null. Undo and redo step it\n * instead of the engine, so they take back the edits that went there. */\n activeJournal(): Journal | null;\n}\n\n/** The scene's own spellings of its history, which answer what a caller\n * outside the engine's vocabulary asks: whether a step moved anything. */\nexport type SceneHistoryMethods = Pick<\n Scene<unknown, string, unknown>,\n | 'undo' | 'redo' | 'canUndo' | 'canRedo' | 'historyEntries' | 'historyIndex'\n | 'jumpToHistoryIndex' | 'serializeHistory' | 'restoreHistory'\n>;\n\n/** A journal drives adapter mutation itself, so its applies and undos\n * re-enter scene mutation methods the way the facade's do. */\nfunction wrapJournal(raw: Journal, env: SceneHistoryEnv): Journal {\n const { replay, notify } = env;\n return {\n targetId: raw.targetId,\n forkedAtEntryId: raw.forkedAtEntryId,\n applyBatch(ops, label) {\n env.labeled(label, () => env.runArrivalEdit(ops, (all) => {\n env.applying(() => raw.applyBatch(all.map(env.causing), label));\n notify();\n }));\n },\n undo() {\n replay(() => raw.undo());\n notify();\n },\n redo() {\n replay(() => raw.redo());\n notify();\n },\n canUndo: () => raw.canUndo(),\n canRedo: () => raw.canRedo(),\n entries: () => raw.entries(),\n seal: () => raw.seal(),\n getVersion: () => raw.getVersion(),\n subscribe: (listener) => raw.subscribe(listener),\n commit(label) {\n // Flushes the session's net ops to the parent stack — no apply, but\n // the parent's undo depth changes and listeners read that.\n raw.commit(label);\n notify();\n },\n cancel() {\n replay(() => raw.cancel());\n notify();\n },\n suspend: () => raw.suspend(),\n isActive: () => raw.isActive(),\n };\n}\n\nexport function createSceneHistory(env: SceneHistoryEnv): { history: History; methods: SceneHistoryMethods } {\n const { engine, replay, notify, refuseUntracked, runArrivalEdit } = env;\n\n /** Wrapped journal → the engine's own, for `resumeJournal`: the engine keys\n * resumability off journal identity, which a wrapper would not match. */\n const rawJournals = new WeakMap<Journal, Journal>();\n\n function step(can: () => boolean, run: () => void): boolean {\n if (!can()) return false;\n replay(run);\n notify();\n return true;\n }\n\n /** What undo and redo step: the open journal, else the engine. */\n const stack = (): Pick<History, 'canUndo' | 'canRedo' | 'undo' | 'redo'> => env.activeJournal() ?? engine;\n\n const methods: SceneHistoryMethods = {\n undo() {\n const j = env.activeJournal();\n if (j) { if (!j.canUndo()) return false; j.undo(); return true; }\n return step(() => engine.canUndo(), () => engine.undo());\n },\n redo() {\n const j = env.activeJournal();\n if (j) { if (!j.canRedo()) return false; j.redo(); return true; }\n return step(() => engine.canRedo(), () => engine.redo());\n },\n canUndo: () => stack().canUndo(),\n canRedo: () => stack().canRedo(),\n historyEntries() {\n const { undo, redo } = engine.entries();\n // entries().redo is already chronological: the next redo comes first.\n return [...undo, ...redo].map((e) => ({ id: String(e.id), label: e.label }));\n },\n historyIndex: () => engine.undoDepth(),\n jumpToHistoryIndex(index) {\n const total = engine.undoDepth() + engine.redoDepth();\n const target = Math.max(0, Math.min(total, index));\n return step(() => target !== engine.undoDepth(), () => engine.goto(target));\n },\n serializeHistory: () => ({ ...engine.serialize(), form: SCENE_HISTORY_FORM }),\n restoreHistory(snapshot) {\n if ((snapshot as Partial<SerializedSceneHistory>).form !== SCENE_HISTORY_FORM) {\n dwarn('scene', 'restoreHistory: snapshot is not in the current recorded form — dropped');\n return;\n }\n // Only the stacks are rebuilt: no op applies.\n engine.restore(snapshot);\n notify();\n },\n };\n\n const history: History = {\n // Mutating: applied ops re-enter the scene's own mutation methods.\n apply(op, label) {\n refuseUntracked('history.apply');\n env.labeled(label ?? op.label ?? '', () => runArrivalEdit([op], (all) => {\n const run = () => (all.length === 1\n ? engine.apply(env.causing(op), label)\n : engine.applyOps(all.map(env.causing), label ?? op.label ?? ''));\n env.applying(run);\n notify();\n }));\n },\n applyOps(ops, label) {\n refuseUntracked('history.applyOps');\n env.labeled(label, () => runArrivalEdit(ops, (all) => {\n env.applying(() => engine.applyOps(all.map(env.causing), label));\n notify();\n }));\n },\n undo: () => { methods.undo(); },\n redo: () => { methods.redo(); },\n goto: (n) => { methods.jumpToHistoryIndex(n); },\n // Stack-only: nothing applies, but what `canUndo()` answers changes.\n clear() {\n engine.clear();\n notify();\n },\n recordEntry(ops, label, opts) {\n engine.recordEntry(ops, label, opts);\n notify();\n },\n prune(t) {\n engine.prune(t);\n notify();\n },\n switchBranch(id) {\n engine.switchBranch(id);\n notify();\n },\n restore: methods.restoreHistory,\n // Reads forward to the engine, `version` included: it counts history\n // operations, where the scene's counts every mutation.\n canUndo: methods.canUndo,\n canRedo: methods.canRedo,\n undoDepth: () => engine.undoDepth(),\n redoDepth: () => engine.redoDepth(),\n entries: () => engine.entries(),\n timestampAt: (i) => engine.timestampAt(i),\n depthAt: (t) => engine.depthAt(t),\n branches: () => engine.branches(),\n getVersion: () => engine.getVersion(),\n subscribe: (listener) => engine.subscribe(listener),\n serialize: methods.serializeHistory,\n allForwardOps: () => engine.allForwardOps(),\n seal: () => engine.seal(),\n currentEntryId: () => engine.currentEntryId(),\n beginJournal(opts) {\n const raw = engine.beginJournal(opts);\n const wrapped = wrapJournal(raw, env);\n rawJournals.set(wrapped, raw);\n return wrapped;\n },\n resumeJournal: (journal) => engine.resumeJournal(rawJournals.get(journal) ?? journal),\n };\n return { history, methods };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"historyFacade.js","names":[],"sources":["../../../src/core/scene/historyFacade.ts"],"sourcesContent":["/**\n * `scene.history`: the `History` the kit's `undo`/`redo` actions resolve\n * their `history` dep to, and the handle a mode machine opens journals\n * against.\n *\n * Handing out the engine directly would let an action-driven undo replay ops\n * with scene-side recording still armed and without bumping the scene's\n * version, so every member that mutates routes through the scene's own\n * wrappers and one `notify()`; reads forward to the engine.\n */\nimport type { History, Journal } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport { dwarn } from 'debug/flag';\nimport { SCENE_HISTORY_FORM, type SerializedSceneHistory } from './historyForm';\nimport type { Scene } from './types';\n\n/** What the facade needs from the scene it fronts. */\nexport interface SceneHistoryEnv {\n /** The scene's own undo/redo engine. */\n engine: History;\n /** Run a step that applies ops through an engine: all-or-nothing, with\n * scene-side recording suppressed. */\n applying(step: () => void): void;\n /** An op as the engine applies it: its entry records the kit ops it\n * caused. */\n causing(op: Op): Op;\n /** Run a step that replays recorded ops: an undo, a redo, a cancel. */\n replay(step: () => void): void;\n notify(): void;\n refuseUntracked(verb: string): void;\n /** Run an op-driven edit with its arrivals settled into the same batch. */\n runArrivalEdit(ops: Op[], run: (ops: Op[]) => void): void;\n /** Run an edit applying ops, labeled `label` for the pose writes it reports. */\n labeled<T>(label: string, fn: () => T): T;\n /** The journal `applyBatch` writes to now, or null. Undo and redo step it\n * instead of the engine, so they take back the edits that went there. */\n activeJournal(): Journal | null;\n}\n\n/** The scene's own spellings of its history, which answer what a caller\n * outside the engine's vocabulary asks: whether a step moved anything. */\nexport type SceneHistoryMethods = Pick<\n Scene<unknown, string, unknown>,\n | 'undo' | 'redo' | 'canUndo' | 'canRedo' | 'historyEntries' | 'historyIndex'\n | 'jumpToHistoryIndex' | 'serializeHistory' | 'restoreHistory'\n>;\n\n/** A journal drives adapter mutation itself, so its applies and undos\n * re-enter scene mutation methods the way the facade's do. */\nfunction wrapJournal(raw: Journal, env: SceneHistoryEnv): Journal {\n const { replay, notify } = env;\n return {\n targetId: raw.targetId,\n forkedAtEntryId: raw.forkedAtEntryId,\n applyBatch(ops, label) {\n env.labeled(label, () => env.runArrivalEdit(ops, (all) => {\n env.applying(() => raw.applyBatch(all.map(env.causing), label));\n notify();\n }));\n },\n undo() {\n replay(() => raw.undo());\n notify();\n },\n redo() {\n replay(() => raw.redo());\n notify();\n },\n canUndo: () => raw.canUndo(),\n canRedo: () => raw.canRedo(),\n entries: () => raw.entries(),\n seal: () => raw.seal(),\n getVersion: () => raw.getVersion(),\n subscribe: (listener) => raw.subscribe(listener),\n commit(label) {\n // Flushes the session's net ops to the parent stack — no apply, but\n // the parent's undo depth changes and listeners read that.\n raw.commit(label);\n notify();\n },\n cancel() {\n replay(() => raw.cancel());\n notify();\n },\n suspend: () => raw.suspend(),\n isActive: () => raw.isActive(),\n };\n}\n\nexport function createSceneHistory(env: SceneHistoryEnv): { history: History; methods: SceneHistoryMethods } {\n const { engine, replay, notify, refuseUntracked, runArrivalEdit } = env;\n\n /** Wrapped journal → the engine's own, for `resumeJournal`: the engine keys\n * resumability off journal identity, which a wrapper would not match. */\n const rawJournals = new WeakMap<Journal, Journal>();\n\n function step(can: () => boolean, run: () => void): boolean {\n if (!can()) return false;\n replay(run);\n notify();\n return true;\n }\n\n /** What undo and redo step: the open journal, else the engine. */\n const stack = (): Pick<History, 'canUndo' | 'canRedo' | 'undo' | 'redo'> => env.activeJournal() ?? engine;\n\n const methods: SceneHistoryMethods = {\n undo() {\n const j = env.activeJournal();\n if (j) { if (!j.canUndo()) return false; j.undo(); return true; }\n return step(() => engine.canUndo(), () => engine.undo());\n },\n redo() {\n const j = env.activeJournal();\n if (j) { if (!j.canRedo()) return false; j.redo(); return true; }\n return step(() => engine.canRedo(), () => engine.redo());\n },\n canUndo: () => stack().canUndo(),\n canRedo: () => stack().canRedo(),\n historyEntries() {\n const { undo, redo } = engine.entries();\n // entries().redo is already chronological: the next redo comes first.\n return [...undo, ...redo].map((e) => ({ id: String(e.id), label: e.label }));\n },\n historyIndex: () => engine.undoDepth(),\n jumpToHistoryIndex(index) {\n const total = engine.undoDepth() + engine.redoDepth();\n const target = Math.max(0, Math.min(total, index));\n return step(() => target !== engine.undoDepth(), () => engine.goto(target));\n },\n serializeHistory: () => ({ ...engine.serialize(), form: SCENE_HISTORY_FORM }),\n restoreHistory(snapshot) {\n if ((snapshot as Partial<SerializedSceneHistory>).form !== SCENE_HISTORY_FORM) {\n dwarn('scene', 'restoreHistory: snapshot is not in the current recorded form — dropped');\n return;\n }\n // Only the stacks are rebuilt: no op applies.\n engine.restore(snapshot);\n notify();\n },\n };\n\n const history: History = {\n // Mutating: applied ops re-enter the scene's own mutation methods.\n apply(op, label) {\n refuseUntracked('history.apply');\n env.labeled(label ?? op.label ?? '', () => runArrivalEdit([op], (all) => {\n const run = () => (all.length === 1\n ? engine.apply(env.causing(op), label)\n : engine.applyOps(all.map(env.causing), label ?? op.label ?? ''));\n env.applying(run);\n notify();\n }));\n },\n applyOps(ops, label) {\n refuseUntracked('history.applyOps');\n env.labeled(label, () => runArrivalEdit(ops, (all) => {\n env.applying(() => engine.applyOps(all.map(env.causing), label));\n notify();\n }));\n },\n undo: () => { methods.undo(); },\n redo: () => { methods.redo(); },\n goto: (n) => { methods.jumpToHistoryIndex(n); },\n // Stack-only: nothing applies, but what `canUndo()` answers changes.\n clear() {\n engine.clear();\n notify();\n },\n recordEntry(ops, label, opts) {\n engine.recordEntry(ops, label, opts);\n notify();\n },\n prune(t) {\n engine.prune(t);\n notify();\n },\n switchBranch(id) {\n engine.switchBranch(id);\n notify();\n },\n restore: methods.restoreHistory,\n // Reads forward to the engine, `version` included: it counts history\n // operations, where the scene's counts every mutation.\n canUndo: methods.canUndo,\n canRedo: methods.canRedo,\n undoDepth: () => engine.undoDepth(),\n redoDepth: () => engine.redoDepth(),\n entries: () => engine.entries(),\n timestampAt: (i) => engine.timestampAt(i),\n depthAt: (t) => engine.depthAt(t),\n branches: () => engine.branches(),\n getVersion: () => engine.getVersion(),\n subscribe: (listener) => engine.subscribe(listener),\n observe: (listener) => engine.observe(listener),\n serialize: methods.serializeHistory,\n allForwardOps: () => engine.allForwardOps(),\n seal: () => engine.seal(),\n currentEntryId: () => engine.currentEntryId(),\n beginJournal(opts) {\n const raw = engine.beginJournal(opts);\n const wrapped = wrapJournal(raw, env);\n rawJournals.set(wrapped, raw);\n return wrapped;\n },\n resumeJournal: (journal) => engine.resumeJournal(rawJournals.get(journal) ?? journal),\n };\n return { history, methods };\n}\n"],"mappings":";;;;;;AAiDA,SAAS,YAAY,KAAc,KAA+B;CAChE,MAAM,EAAE,QAAQ,WAAW;CAC3B,OAAO;EACL,UAAU,IAAI;EACd,iBAAiB,IAAI;EACrB,WAAW,KAAK,OAAO;GACrB,IAAI,QAAQ,aAAa,IAAI,eAAe,MAAM,QAAQ;IACxD,IAAI,eAAe,IAAI,WAAW,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,CAAC;IAC9D,OAAO;GACT,CAAC,CAAC;EACJ;EACA,OAAO;GACL,aAAa,IAAI,KAAK,CAAC;GACvB,OAAO;EACT;EACA,OAAO;GACL,aAAa,IAAI,KAAK,CAAC;GACvB,OAAO;EACT;EACA,eAAe,IAAI,QAAQ;EAC3B,eAAe,IAAI,QAAQ;EAC3B,eAAe,IAAI,QAAQ;EAC3B,YAAY,IAAI,KAAK;EACrB,kBAAkB,IAAI,WAAW;EACjC,YAAY,aAAa,IAAI,UAAU,QAAQ;EAC/C,OAAO,OAAO;GAGZ,IAAI,OAAO,KAAK;GAChB,OAAO;EACT;EACA,SAAS;GACP,aAAa,IAAI,OAAO,CAAC;GACzB,OAAO;EACT;EACA,eAAe,IAAI,QAAQ;EAC3B,gBAAgB,IAAI,SAAS;CAC/B;AACF;AAEA,SAAgB,mBAAmB,KAA0E;CAC3G,MAAM,EAAE,QAAQ,QAAQ,QAAQ,iBAAiB,mBAAmB;;;CAIpE,MAAM,8BAAc,IAAI,QAA0B;CAElD,SAAS,KAAK,KAAoB,KAA0B;EAC1D,IAAI,CAAC,IAAI,GAAG,OAAO;EACnB,OAAO,GAAG;EACV,OAAO;EACP,OAAO;CACT;;CAGA,MAAM,cAAsE,IAAI,cAAc,KAAK;CAEnG,MAAM,UAA+B;EACnC,OAAO;GACL,MAAM,IAAI,IAAI,cAAc;GAC5B,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO;IAAO,EAAE,KAAK;IAAG,OAAO;GAAM;GAChE,OAAO,WAAW,OAAO,QAAQ,SAAS,OAAO,KAAK,CAAC;EACzD;EACA,OAAO;GACL,MAAM,IAAI,IAAI,cAAc;GAC5B,IAAI,GAAG;IAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO;IAAO,EAAE,KAAK;IAAG,OAAO;GAAM;GAChE,OAAO,WAAW,OAAO,QAAQ,SAAS,OAAO,KAAK,CAAC;EACzD;EACA,eAAe,MAAM,CAAC,CAAC,QAAQ;EAC/B,eAAe,MAAM,CAAC,CAAC,QAAQ;EAC/B,iBAAiB;GACf,MAAM,EAAE,MAAM,SAAS,OAAO,QAAQ;GAEtC,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,OAAO;IAAE,IAAI,OAAO,EAAE,EAAE;IAAG,OAAO,EAAE;GAAM,EAAE;EAC7E;EACA,oBAAoB,OAAO,UAAU;EACrC,mBAAmB,OAAO;GACxB,MAAM,QAAQ,OAAO,UAAU,IAAI,OAAO,UAAU;GACpD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,KAAK,CAAC;GACjD,OAAO,WAAW,WAAW,OAAO,UAAU,SAAS,OAAO,KAAK,MAAM,CAAC;EAC5E;EACA,yBAAyB;GAAE,GAAG,OAAO,UAAU;GAAG,MAAM;EAAmB;EAC3E,eAAe,UAAU;GACvB,IAAK,SAA6C,SAAS,oBAAoB;IAC7E,MAAM,SAAS,wEAAwE;IACvF;GACF;GAEA,OAAO,QAAQ,QAAQ;GACvB,OAAO;EACT;CACF;CAmEA,OAAO;EAAE;GA/DP,MAAM,IAAI,OAAO;IACf,gBAAgB,eAAe;IAC/B,IAAI,QAAQ,SAAS,GAAG,SAAS,UAAU,eAAe,CAAC,EAAE,IAAI,QAAQ;KACvE,MAAM,YAAa,IAAI,WAAW,IAC9B,OAAO,MAAM,IAAI,QAAQ,EAAE,GAAG,KAAK,IACnC,OAAO,SAAS,IAAI,IAAI,IAAI,OAAO,GAAG,SAAS,GAAG,SAAS,EAAE;KACjE,IAAI,SAAS,GAAG;KAChB,OAAO;IACT,CAAC,CAAC;GACJ;GACA,SAAS,KAAK,OAAO;IACnB,gBAAgB,kBAAkB;IAClC,IAAI,QAAQ,aAAa,eAAe,MAAM,QAAQ;KACpD,IAAI,eAAe,OAAO,SAAS,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,CAAC;KAC/D,OAAO;IACT,CAAC,CAAC;GACJ;GACA,YAAY;IAAE,QAAQ,KAAK;GAAG;GAC9B,YAAY;IAAE,QAAQ,KAAK;GAAG;GAC9B,OAAO,MAAM;IAAE,QAAQ,mBAAmB,CAAC;GAAG;GAE9C,QAAQ;IACN,OAAO,MAAM;IACb,OAAO;GACT;GACA,YAAY,KAAK,OAAO,MAAM;IAC5B,OAAO,YAAY,KAAK,OAAO,IAAI;IACnC,OAAO;GACT;GACA,MAAM,GAAG;IACP,OAAO,MAAM,CAAC;IACd,OAAO;GACT;GACA,aAAa,IAAI;IACf,OAAO,aAAa,EAAE;IACtB,OAAO;GACT;GACA,SAAS,QAAQ;GAGjB,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,iBAAiB,OAAO,UAAU;GAClC,iBAAiB,OAAO,UAAU;GAClC,eAAe,OAAO,QAAQ;GAC9B,cAAc,MAAM,OAAO,YAAY,CAAC;GACxC,UAAU,MAAM,OAAO,QAAQ,CAAC;GAChC,gBAAgB,OAAO,SAAS;GAChC,kBAAkB,OAAO,WAAW;GACpC,YAAY,aAAa,OAAO,UAAU,QAAQ;GAClD,UAAU,aAAa,OAAO,QAAQ,QAAQ;GAC9C,WAAW,QAAQ;GACnB,qBAAqB,OAAO,cAAc;GAC1C,YAAY,OAAO,KAAK;GACxB,sBAAsB,OAAO,eAAe;GAC5C,aAAa,MAAM;IACjB,MAAM,MAAM,OAAO,aAAa,IAAI;IACpC,MAAM,UAAU,YAAY,KAAK,GAAG;IACpC,YAAY,IAAI,SAAS,GAAG;IAC5B,OAAO;GACT;GACA,gBAAgB,YAAY,OAAO,cAAc,YAAY,IAAI,OAAO,KAAK,OAAO;EAE7E;EAAS;CAAQ;AAC5B"}
@@ -0,0 +1,75 @@
1
+ import { INHERIT, resolveFill, resolveStroke } from "@weasel-js/paint";
2
+
3
+ //#region src/core/scene/inheritedPaint.ts
4
+ /** Whether `node`'s fill or stroke is {@link INHERIT}, so painting it needs
5
+ * its containers' paint. */
6
+ function inheritsPaint(node) {
7
+ const d = node.data;
8
+ return d?.fill === INHERIT || d?.stroke === INHERIT;
9
+ }
10
+ var LAST = /* @__PURE__ */ new WeakMap();
11
+ /**
12
+ * {@link InheritedPaint} for `node`, given its ancestors innermost first. Only
13
+ * containers are sources; one whose own field is `'inherit'` passes the
14
+ * question on to the next, and running out of containers is none.
15
+ *
16
+ * Hands back the object it returned last time for `node` while both values
17
+ * are the same references, so a memo can key on it.
18
+ */
19
+ function inheritedPaintOf(node, ancestors) {
20
+ const d = node.data;
21
+ let wantFill = d?.fill === INHERIT;
22
+ let wantStroke = d?.stroke === INHERIT;
23
+ let fill = null;
24
+ let stroke = null;
25
+ for (const a of ancestors) {
26
+ if (!wantFill && !wantStroke) break;
27
+ if (a.kind !== "container") continue;
28
+ const ad = a.data;
29
+ if (wantFill && ad?.fill !== INHERIT) {
30
+ fill = resolveFill(ad?.fill);
31
+ wantFill = false;
32
+ }
33
+ if (wantStroke && ad?.stroke !== INHERIT) {
34
+ stroke = resolveStroke(ad?.stroke);
35
+ wantStroke = false;
36
+ }
37
+ }
38
+ const last = LAST.get(node);
39
+ if (last !== void 0 && last.fill === fill && last.stroke === stroke) return last;
40
+ const next = {
41
+ fill,
42
+ stroke
43
+ };
44
+ LAST.set(node, next);
45
+ return next;
46
+ }
47
+ /** `node`'s ancestors in `scene`, innermost first, read lazily off `parent`. */
48
+ function* sceneAncestors(get, node) {
49
+ const seen = /* @__PURE__ */ new Set();
50
+ let id = node.parent ?? null;
51
+ while (id !== null && !seen.has(id)) {
52
+ seen.add(id);
53
+ const p = get(id);
54
+ if (p === void 0) return;
55
+ yield p;
56
+ id = p.parent ?? null;
57
+ }
58
+ }
59
+ /**
60
+ * `data` with an `'inherit'` fill or stroke replaced by what `inherited` says
61
+ * it resolves to, for a reader that wants the paint a node shows rather than
62
+ * the one it declares. Hands `data` back as is when nothing inherits.
63
+ */
64
+ function withInheritedPaint(data, inherited) {
65
+ if (data.fill !== INHERIT && data.stroke !== INHERIT) return data;
66
+ return {
67
+ ...data,
68
+ ...data.fill === INHERIT ? { fill: inherited?.fill ?? null } : {},
69
+ ...data.stroke === INHERIT ? { stroke: inherited?.stroke ?? null } : {}
70
+ };
71
+ }
72
+
73
+ //#endregion
74
+ export { inheritedPaintOf, inheritsPaint, sceneAncestors, withInheritedPaint };
75
+ //# sourceMappingURL=inheritedPaint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inheritedPaint.js","names":[],"sources":["../../../src/core/scene/inheritedPaint.ts"],"sourcesContent":["import {\n INHERIT, resolveFill, resolveStroke,\n type Fill, type Inherit, type PaintedFill, type PaintedStroke, type Stroke,\n} from '@weasel-js/paint';\n\n/**\n * What a node's `'inherit'` fill and stroke resolve to: its nearest enclosing\n * container's, `null` for none. A field the node does not inherit is `null`\n * here whatever its containers hold.\n */\nexport interface InheritedPaint {\n readonly fill: PaintedFill | null;\n readonly stroke: PaintedStroke | null;\n}\n\ntype PaintData = { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null } | null | undefined;\n\n/** Whether `node`'s fill or stroke is {@link INHERIT}, so painting it needs\n * its containers' paint. */\nexport function inheritsPaint(node: { data?: unknown }): boolean {\n const d = node.data as PaintData;\n return d?.fill === INHERIT || d?.stroke === INHERIT;\n}\n\nconst LAST = new WeakMap<object, InheritedPaint>();\n\n/**\n * {@link InheritedPaint} for `node`, given its ancestors innermost first. Only\n * containers are sources; one whose own field is `'inherit'` passes the\n * question on to the next, and running out of containers is none.\n *\n * Hands back the object it returned last time for `node` while both values\n * are the same references, so a memo can key on it.\n */\nexport function inheritedPaintOf(\n node: { data?: unknown },\n ancestors: Iterable<{ kind?: string; data?: unknown }>,\n): InheritedPaint {\n const d = node.data as PaintData;\n let wantFill = d?.fill === INHERIT;\n let wantStroke = d?.stroke === INHERIT;\n let fill: PaintedFill | null = null;\n let stroke: PaintedStroke | null = null;\n for (const a of ancestors) {\n if (!wantFill && !wantStroke) break;\n if (a.kind !== 'container') continue;\n const ad = a.data as PaintData;\n if (wantFill && ad?.fill !== INHERIT) {\n fill = resolveFill(ad?.fill);\n wantFill = false;\n }\n if (wantStroke && ad?.stroke !== INHERIT) {\n stroke = resolveStroke(ad?.stroke);\n wantStroke = false;\n }\n }\n const last = LAST.get(node);\n if (last !== undefined && last.fill === fill && last.stroke === stroke) return last;\n const next: InheritedPaint = { fill, stroke };\n LAST.set(node, next);\n return next;\n}\n\n/** `node`'s ancestors in `scene`, innermost first, read lazily off `parent`. */\nexport function* sceneAncestors<TNode extends { parent?: string | null }>(\n get: (id: never) => TNode | undefined,\n node: TNode,\n): Generator<TNode> {\n const seen = new Set<string>();\n let id = node.parent ?? null;\n while (id !== null && !seen.has(id)) {\n seen.add(id);\n const p = get(id as never);\n if (p === undefined) return;\n yield p;\n id = p.parent ?? null;\n }\n}\n\n/** The fields {@link withInheritedPaint} resolves. */\ntype ResolvedPaintFields<T> = Omit<T, 'fill' | 'stroke'> & {\n fill?: Exclude<T extends { fill?: infer F } ? F : never, Inherit>;\n stroke?: Exclude<T extends { stroke?: infer S } ? S : never, Inherit>;\n};\n\n/**\n * `data` with an `'inherit'` fill or stroke replaced by what `inherited` says\n * it resolves to, for a reader that wants the paint a node shows rather than\n * the one it declares. Hands `data` back as is when nothing inherits.\n */\nexport function withInheritedPaint<T extends { fill?: unknown; stroke?: unknown }>(\n data: T,\n inherited: InheritedPaint | undefined,\n): ResolvedPaintFields<T> {\n if (data.fill !== INHERIT && data.stroke !== INHERIT) return data as ResolvedPaintFields<T>;\n return {\n ...data,\n ...(data.fill === INHERIT ? { fill: inherited?.fill ?? null } : {}),\n ...(data.stroke === INHERIT ? { stroke: inherited?.stroke ?? null } : {}),\n } as ResolvedPaintFields<T>;\n}\n"],"mappings":";;;;;AAmBA,SAAgB,cAAc,MAAmC;CAC/D,MAAM,IAAI,KAAK;CACf,OAAO,GAAG,SAAS,WAAW,GAAG,WAAW;AAC9C;AAEA,IAAM,uBAAO,IAAI,QAAgC;;;;;;;;;AAUjD,SAAgB,iBACd,MACA,WACgB;CAChB,MAAM,IAAI,KAAK;CACf,IAAI,WAAW,GAAG,SAAS;CAC3B,IAAI,aAAa,GAAG,WAAW;CAC/B,IAAI,OAA2B;CAC/B,IAAI,SAA+B;CACnC,KAAK,MAAM,KAAK,WAAW;EACzB,IAAI,CAAC,YAAY,CAAC,YAAY;EAC9B,IAAI,EAAE,SAAS,aAAa;EAC5B,MAAM,KAAK,EAAE;EACb,IAAI,YAAY,IAAI,SAAS,SAAS;GACpC,OAAO,YAAY,IAAI,IAAI;GAC3B,WAAW;EACb;EACA,IAAI,cAAc,IAAI,WAAW,SAAS;GACxC,SAAS,cAAc,IAAI,MAAM;GACjC,aAAa;EACf;CACF;CACA,MAAM,OAAO,KAAK,IAAI,IAAI;CAC1B,IAAI,SAAS,UAAa,KAAK,SAAS,QAAQ,KAAK,WAAW,QAAQ,OAAO;CAC/E,MAAM,OAAuB;EAAE;EAAM;CAAO;CAC5C,KAAK,IAAI,MAAM,IAAI;CACnB,OAAO;AACT;;AAGA,UAAiB,eACf,KACA,MACkB;CAClB,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,KAAK,KAAK,UAAU;CACxB,OAAO,OAAO,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG;EACnC,KAAK,IAAI,EAAE;EACX,MAAM,IAAI,IAAI,EAAW;EACzB,IAAI,MAAM,QAAW;EACrB,MAAM;EACN,KAAK,EAAE,UAAU;CACnB;AACF;;;;;;AAaA,SAAgB,mBACd,MACA,WACwB;CACxB,IAAI,KAAK,SAAS,WAAW,KAAK,WAAW,SAAS,OAAO;CAC7D,OAAO;EACL,GAAG;EACH,GAAI,KAAK,SAAS,UAAU,EAAE,MAAM,WAAW,QAAQ,KAAK,IAAI,CAAC;EACjE,GAAI,KAAK,WAAW,UAAU,EAAE,QAAQ,WAAW,UAAU,KAAK,IAAI,CAAC;CACzE;AACF"}
@@ -1,4 +1,5 @@
1
1
  import { unionAABB } from "../geometry/unionBounds.js";
2
+ import { CLIP_NOTHING, clipNothing } from "./clipNothing.js";
2
3
 
3
4
  //#region src/core/scene/kitRegistry.ts
4
5
  /**
@@ -48,6 +49,10 @@ function asRectPose(pose) {
48
49
  function withKitRegistry(registry) {
49
50
  return {
50
51
  ...registry,
52
+ clipFromPose: {
53
+ [CLIP_NOTHING]: clipNothing,
54
+ ...registry.clipFromPose
55
+ },
51
56
  derivePose: {
52
57
  [UNION_OF_CHILDREN]: unionOfChildren,
53
58
  ...registry.derivePose
@@ -1 +1 @@
1
- {"version":3,"file":"kitRegistry.js","names":[],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose.\n *\n * Reads members as rect poses only: a scene whose poses are shaped otherwise\n * gets `null` here and its container keeps its authored pose. To make such a\n * container track its members, register `unionOfChildrenVia(itsDescriptor)`\n * under {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n): SceneRegistry<TPose, TData, TLayer> {\n return {\n ...registry,\n derivePose: {\n [UNION_OF_CHILDREN]: unionOfChildren as NonNullable<\n SceneRegistry<TPose, TData, TLayer>['derivePose']\n >[string],\n ...registry.derivePose,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,oBAAoB;;;;;;;;;;;;;;;AAgBjC,SAAgB,gBACd,OACA,MACc;CACd,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,KAAK,MAAM;EACpB,IAAI,MAAM,QAAW;EACrB,MAAM,OAAO,WAAW,EAAE,IAAI;EAC9B,IAAI,SAAS,MAAM,OAAO;EAC1B,MAAM,KAAK,IAAI;CACjB;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,QAAQ,UAAU,KAAK;CAG7B,OAAO,UAAU,OAAO,OAAQ;AAClC;;AAGA,SAAgB,WAAW,MAAgC;CACzD,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,MAAM,IAAI;CAGV,OAFa,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,YAClD,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,WAAW,WACzC,OAAoB;AACrC;;AAGA,SAAgB,gBACd,UACqC;CACrC,OAAO;EACL,GAAG;EACH,YAAY;IACT,oBAAoB;GAGrB,GAAG,SAAS;EACd;CACF;AACF"}
1
+ {"version":3,"file":"kitRegistry.js","names":[],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\nimport { CLIP_NOTHING, clipNothing } from './clipNothing';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose.\n *\n * Reads members as rect poses only: a scene whose poses are shaped otherwise\n * gets `null` here and its container keeps its authored pose. To make such a\n * container track its members, register `unionOfChildrenVia(itsDescriptor)`\n * under {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n): SceneRegistry<TPose, TData, TLayer> {\n return {\n ...registry,\n clipFromPose: {\n [CLIP_NOTHING]: clipNothing,\n ...registry.clipFromPose,\n },\n derivePose: {\n [UNION_OF_CHILDREN]: unionOfChildren as NonNullable<\n SceneRegistry<TPose, TData, TLayer>['derivePose']\n >[string],\n ...registry.derivePose,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,IAAa,oBAAoB;;;;;;;;;;;;;;;AAgBjC,SAAgB,gBACd,OACA,MACc;CACd,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,KAAK,MAAM;EACpB,IAAI,MAAM,QAAW;EACrB,MAAM,OAAO,WAAW,EAAE,IAAI;EAC9B,IAAI,SAAS,MAAM,OAAO;EAC1B,MAAM,KAAK,IAAI;CACjB;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,QAAQ,UAAU,KAAK;CAG7B,OAAO,UAAU,OAAO,OAAQ;AAClC;;AAGA,SAAgB,WAAW,MAAgC;CACzD,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,MAAM,IAAI;CAGV,OAFa,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,YAClD,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,WAAW,WACzC,OAAoB;AACrC;;AAGA,SAAgB,gBACd,UACqC;CACrC,OAAO;EACL,GAAG;EACH,cAAc;IACX,eAAe;GAChB,GAAG,SAAS;EACd;EACA,YAAY;IACT,oBAAoB;GAGrB,GAAG,SAAS;EACd;CACF;AACF"}
@@ -1,6 +1,7 @@
1
1
  import { composeWorldPose, rebaseLocalPose } from "./composeFrame.js";
2
2
  import { samePoseValue } from "./poseSnapshot.js";
3
3
  import { definesFrame, documentPose } from "./effectivePose.js";
4
+ import { carriedDescendants } from "./carryDescendants.js";
4
5
  import { asRectPose } from "./kitRegistry.js";
5
6
 
6
7
  //#region src/core/scene/layoutPass.ts
@@ -12,13 +13,14 @@ import { asRectPose } from "./kitRegistry.js";
12
13
  * the rest through it (and may shrink). One that gained children and whose
13
14
  * strategy has `arrive` then places them through it (and may refuse, or
14
15
  * grow). Any other changed container — one that reordered children, was
15
- * resized, or gained or lost children with neither hook — is put back in its
16
- * resting arrangement, `childPoses`.
16
+ * resized, had a child moved, or gained or lost children with neither hook —
17
+ * is put back in its resting arrangement, `childPoses`.
17
18
  *
18
19
  * `LayoutStrategy` is world-framed, so each pose is composed to world before
19
20
  * a strategy sees it and each result is rebased into its node's parent frame
20
21
  * after. A child already where the strategy puts it is left out, so a pass
21
- * that changes nothing writes nothing. A container this pass resizes is
22
+ * that changes nothing writes nothing. A child the pass moves takes its own
23
+ * descendants along (`carriedDescendants`). A container this pass resizes is
22
24
  * arranged in turn, so nested layouts settle in the same edit.
23
25
  */
24
26
  function resolveLayoutFrame(frame) {
@@ -99,9 +101,32 @@ function runLayoutPass(source, layoutOf, arrivals, changed, frame, departures =
99
101
  for (const [cid, pose] of result.poses) {
100
102
  const id = cid;
101
103
  if (source.get(id)?.parent !== containerId) continue;
104
+ const before = frame.bounds(world(id), id);
102
105
  write(id, rebaseLocalPose(pa, pose, containerId, compose, decompose));
106
+ carry(id, before);
103
107
  }
104
108
  };
109
+ /** Moves `id`'s descendants by however far its write just moved it from `before`. */
110
+ const carry = (id, before) => {
111
+ const after = frame.bounds(world(id), id);
112
+ if (before === null || after === null) return;
113
+ const dx = after.x - before.x;
114
+ const dy = after.y - before.y;
115
+ if (dx === 0 && dy === 0) return;
116
+ const carried = carriedDescendants({
117
+ childrenOf: (cid) => source.childrenOf(cid),
118
+ poses: pa,
119
+ composition: source.composition
120
+ }, id, (pose, cid) => {
121
+ const b = frame.bounds(pose, cid);
122
+ return b === null ? null : frame.remap(pose, b, {
123
+ ...b,
124
+ x: b.x + dx,
125
+ y: b.y + dy
126
+ }, cid);
127
+ });
128
+ for (const [cid, pose] of carried) write(cid, pose);
129
+ };
105
130
  while (queue.length > 0) {
106
131
  const containerId = queue.shift();
107
132
  current = containerId;
@@ -1 +1 @@
1
- {"version":3,"file":"layoutPass.js","names":[],"sources":["../../../src/core/scene/layoutPass.ts"],"sourcesContent":["/**\n * The one computation behind a scene's container layouts: given the\n * containers one edit changed, the pose writes their layouts ask for.\n *\n * A container that lost children and whose strategy has `depart` rearranges\n * the rest through it (and may shrink). One that gained children and whose\n * strategy has `arrive` then places them through it (and may refuse, or\n * grow). Any other changed container — one that reordered children, was\n * resized, or gained or lost children with neither hook — is put back in its\n * resting arrangement, `childPoses`.\n *\n * `LayoutStrategy` is world-framed, so each pose is composed to world before\n * a strategy sees it and each result is rebased into its node's parent frame\n * after. A child already where the strategy puts it is left out, so a pass\n * that changes nothing writes nothing. A container this pass resizes is\n * arranged in turn, so nested layouts settle in the same edit.\n */\nimport {\n composeWorldPose,\n rebaseLocalPose,\n type PoseAdapter,\n type PoseComposition,\n} from './composeFrame';\nimport type { ContainerBounds, LayoutArrival, LayoutChild, LayoutStrategy } from '../../layout/types';\nimport { definesFrame, documentPose, type PoseSource, type PosedNode } from './effectivePose';\nimport { asRectPose } from './kitRegistry';\nimport { samePoseValue } from './poseSnapshot';\nimport type { LayoutFrame, NodeId } from './types';\n\n/** What the pass reads a scene through. */\nexport interface LayoutPassSource<TPose> extends PoseSource<TPose> {\n get(id: NodeId): (PosedNode<TPose> & { parent: NodeId | null }) | undefined;\n readonly composition: PoseComposition<TPose>;\n}\n\n/** How the pass measures and resizes poses, with every default filled. */\nexport interface ResolvedLayoutFrame<TPose> {\n bounds(pose: TPose, id: NodeId): ContainerBounds | null;\n remap(pose: TPose, from: ContainerBounds, to: ContainerBounds, id: NodeId): TPose;\n}\n\n/** The pose writes one edit's layouts ask for, or `null` when a strategy\n * refused the children that joined it. */\nexport type LayoutPassResult<TPose> = Map<NodeId, TPose> | null;\n\nexport function resolveLayoutFrame<TPose>(frame: LayoutFrame<TPose> | undefined): ResolvedLayoutFrame<TPose> {\n return {\n bounds: frame?.bounds ?? ((pose) => {\n const r = asRectPose(pose);\n return r === null ? null : { x: r.x, y: r.y, width: r.width, height: r.height };\n }),\n remap: frame?.remap ?? ((pose, _from, to) => ({ ...(pose as object), ...to }) as TPose),\n };\n}\n\n/** Whether a node going from `from` to `to` changed size. */\nexport function resized<TPose>(frame: ResolvedLayoutFrame<TPose>, id: NodeId, from: TPose, to: TPose): boolean {\n const a = frame.bounds(from, id);\n const b = frame.bounds(to, id);\n if (a === null || b === null) return a !== b;\n return a.width !== b.width || a.height !== b.height;\n}\n\n/** How many times one pass may arrange a container before giving up on it —\n * only nested layouts that keep resizing each other get close. */\nconst MAX_PASSES = 16;\n\nexport function runLayoutPass<TPose>(\n source: LayoutPassSource<TPose>,\n layoutOf: (id: NodeId) => LayoutStrategy<TPose> | null,\n arrivals: ReadonlyMap<NodeId, readonly NodeId[]>,\n changed: ReadonlySet<NodeId>,\n frame: ResolvedLayoutFrame<TPose>,\n departures: ReadonlyMap<NodeId, readonly NodeId[]> = new Map(),\n): LayoutPassResult<TPose> {\n // Poses already decided in this pass, so a container resized here is the\n // frame its children are rebased into.\n const out = new Map<NodeId, TPose>();\n const poseOf = (id: NodeId): TPose => out.get(id) ?? documentPose(source, source.get(id)!);\n const pa: PoseAdapter<TPose> = {\n getPose: (id) => poseOf(id as NodeId),\n getParent: (id) => source.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => {\n const node = source.get(id as NodeId);\n return node === undefined || definesFrame(node);\n },\n };\n const { compose, decompose } = source.composition;\n const world = (id: NodeId): TPose => composeWorldPose(pa, id, compose);\n\n const queue: NodeId[] = [...new Set<NodeId>([...arrivals.keys(), ...changed])];\n const passes = new Map<NodeId, number>();\n const write = (id: NodeId, pose: TPose): void => {\n const node = source.get(id)!;\n const before = poseOf(id);\n if (samePoseValue(before, pose)) return;\n out.set(id, pose);\n // A container its own `depart` or `arrive` resized is arranged already.\n if (id !== current && node.kind === 'container' && layoutOf(id) !== null\n && resized(frame, id, before, pose)) queue.push(id);\n };\n let current: NodeId | null = null;\n\n /** The container and its children as the strategy sees them, in world,\n * with every write this pass has made so far folded in. */\n const frameOf = (containerId: NodeId) => {\n const pose = world(containerId);\n const bounds = frame.bounds(pose, containerId);\n if (bounds === null) return null;\n const children: LayoutChild<TPose>[] = source.childrenOf(containerId).map((cid) => ({\n id: cid as string,\n pose: world(cid),\n }));\n return { world: pose, container: { id: containerId as string, bounds }, children };\n };\n const apply = (\n containerId: NodeId,\n at: NonNullable<ReturnType<typeof frameOf>>,\n result: LayoutArrival<TPose>,\n ): void => {\n if (result.bounds) {\n const node = source.get(containerId)!;\n const sized = frame.remap(at.world, at.container.bounds, result.bounds, containerId);\n write(containerId, rebaseLocalPose(pa, sized, node.parent, compose, decompose));\n }\n for (const [cid, pose] of result.poses) {\n const id = cid as NodeId;\n if (source.get(id)?.parent !== containerId) continue;\n write(id, rebaseLocalPose(pa, pose, containerId, compose, decompose));\n }\n };\n\n while (queue.length > 0) {\n const containerId = queue.shift()!;\n current = containerId;\n const n = (passes.get(containerId) ?? 0) + 1;\n passes.set(containerId, n);\n if (n > MAX_PASSES) continue;\n const layout = layoutOf(containerId);\n if (layout === null || source.get(containerId) === undefined) continue;\n const joined = n === 1 ? arrivals.get(containerId) : undefined;\n const left = n === 1 ? departures.get(containerId) : undefined;\n let arranged = false;\n if (left !== undefined && layout.depart) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const joinedSet = new Set<string>(joined ?? []);\n const staying = at.children.filter((c) => !joinedSet.has(c.id));\n apply(containerId, at, layout.depart(at.container, staying, new Set<string>(left)));\n arranged = true;\n }\n if (joined !== undefined && layout.arrive) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const result = layout.arrive(at.container, at.children, new Set<string>(joined));\n if (result === null) return null;\n apply(containerId, at, result);\n } else if (!arranged || joined !== undefined) {\n const at = frameOf(containerId);\n if (at === null) continue;\n apply(containerId, at, { poses: layout.childPoses(at.container, at.children) });\n }\n }\n return out;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"layoutPass.js","names":[],"sources":["../../../src/core/scene/layoutPass.ts"],"sourcesContent":["/**\n * The one computation behind a scene's container layouts: given the\n * containers one edit changed, the pose writes their layouts ask for.\n *\n * A container that lost children and whose strategy has `depart` rearranges\n * the rest through it (and may shrink). One that gained children and whose\n * strategy has `arrive` then places them through it (and may refuse, or\n * grow). Any other changed container — one that reordered children, was\n * resized, had a child moved, or gained or lost children with neither hook —\n * is put back in its resting arrangement, `childPoses`.\n *\n * `LayoutStrategy` is world-framed, so each pose is composed to world before\n * a strategy sees it and each result is rebased into its node's parent frame\n * after. A child already where the strategy puts it is left out, so a pass\n * that changes nothing writes nothing. A child the pass moves takes its own\n * descendants along (`carriedDescendants`). A container this pass resizes is\n * arranged in turn, so nested layouts settle in the same edit.\n */\nimport {\n composeWorldPose,\n rebaseLocalPose,\n type PoseAdapter,\n type PoseComposition,\n} from './composeFrame';\nimport type { ContainerBounds, LayoutArrival, LayoutChild, LayoutStrategy } from '../../layout/types';\nimport { carriedDescendants } from './carryDescendants';\nimport { definesFrame, documentPose, type PoseSource, type PosedNode } from './effectivePose';\nimport { asRectPose } from './kitRegistry';\nimport { samePoseValue } from './poseSnapshot';\nimport type { LayoutFrame, NodeId } from './types';\n\n/** What the pass reads a scene through. */\nexport interface LayoutPassSource<TPose> extends PoseSource<TPose> {\n get(id: NodeId): (PosedNode<TPose> & { parent: NodeId | null }) | undefined;\n readonly composition: PoseComposition<TPose>;\n}\n\n/** How the pass measures and resizes poses, with every default filled. */\nexport interface ResolvedLayoutFrame<TPose> {\n bounds(pose: TPose, id: NodeId): ContainerBounds | null;\n remap(pose: TPose, from: ContainerBounds, to: ContainerBounds, id: NodeId): TPose;\n}\n\n/** The pose writes one edit's layouts ask for, or `null` when a strategy\n * refused the children that joined it. */\nexport type LayoutPassResult<TPose> = Map<NodeId, TPose> | null;\n\nexport function resolveLayoutFrame<TPose>(frame: LayoutFrame<TPose> | undefined): ResolvedLayoutFrame<TPose> {\n return {\n bounds: frame?.bounds ?? ((pose) => {\n const r = asRectPose(pose);\n return r === null ? null : { x: r.x, y: r.y, width: r.width, height: r.height };\n }),\n remap: frame?.remap ?? ((pose, _from, to) => ({ ...(pose as object), ...to }) as TPose),\n };\n}\n\n/** Whether a node going from `from` to `to` changed size. */\nexport function resized<TPose>(frame: ResolvedLayoutFrame<TPose>, id: NodeId, from: TPose, to: TPose): boolean {\n const a = frame.bounds(from, id);\n const b = frame.bounds(to, id);\n if (a === null || b === null) return a !== b;\n return a.width !== b.width || a.height !== b.height;\n}\n\n/** How many times one pass may arrange a container before giving up on it —\n * only nested layouts that keep resizing each other get close. */\nconst MAX_PASSES = 16;\n\nexport function runLayoutPass<TPose>(\n source: LayoutPassSource<TPose>,\n layoutOf: (id: NodeId) => LayoutStrategy<TPose> | null,\n arrivals: ReadonlyMap<NodeId, readonly NodeId[]>,\n changed: ReadonlySet<NodeId>,\n frame: ResolvedLayoutFrame<TPose>,\n departures: ReadonlyMap<NodeId, readonly NodeId[]> = new Map(),\n): LayoutPassResult<TPose> {\n // Poses already decided in this pass, so a container resized here is the\n // frame its children are rebased into.\n const out = new Map<NodeId, TPose>();\n const poseOf = (id: NodeId): TPose => out.get(id) ?? documentPose(source, source.get(id)!);\n const pa: PoseAdapter<TPose> = {\n getPose: (id) => poseOf(id as NodeId),\n getParent: (id) => source.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => {\n const node = source.get(id as NodeId);\n return node === undefined || definesFrame(node);\n },\n };\n const { compose, decompose } = source.composition;\n const world = (id: NodeId): TPose => composeWorldPose(pa, id, compose);\n\n const queue: NodeId[] = [...new Set<NodeId>([...arrivals.keys(), ...changed])];\n const passes = new Map<NodeId, number>();\n const write = (id: NodeId, pose: TPose): void => {\n const node = source.get(id)!;\n const before = poseOf(id);\n if (samePoseValue(before, pose)) return;\n out.set(id, pose);\n // A container its own `depart` or `arrive` resized is arranged already.\n if (id !== current && node.kind === 'container' && layoutOf(id) !== null\n && resized(frame, id, before, pose)) queue.push(id);\n };\n let current: NodeId | null = null;\n\n /** The container and its children as the strategy sees them, in world,\n * with every write this pass has made so far folded in. */\n const frameOf = (containerId: NodeId) => {\n const pose = world(containerId);\n const bounds = frame.bounds(pose, containerId);\n if (bounds === null) return null;\n const children: LayoutChild<TPose>[] = source.childrenOf(containerId).map((cid) => ({\n id: cid as string,\n pose: world(cid),\n }));\n return { world: pose, container: { id: containerId as string, bounds }, children };\n };\n const apply = (\n containerId: NodeId,\n at: NonNullable<ReturnType<typeof frameOf>>,\n result: LayoutArrival<TPose>,\n ): void => {\n if (result.bounds) {\n const node = source.get(containerId)!;\n const sized = frame.remap(at.world, at.container.bounds, result.bounds, containerId);\n write(containerId, rebaseLocalPose(pa, sized, node.parent, compose, decompose));\n }\n for (const [cid, pose] of result.poses) {\n const id = cid as NodeId;\n if (source.get(id)?.parent !== containerId) continue;\n const before = frame.bounds(world(id), id);\n write(id, rebaseLocalPose(pa, pose, containerId, compose, decompose));\n carry(id, before);\n }\n };\n /** Moves `id`'s descendants by however far its write just moved it from `before`. */\n const carry = (id: NodeId, before: ContainerBounds | null): void => {\n const after = frame.bounds(world(id), id);\n if (before === null || after === null) return;\n const dx = after.x - before.x;\n const dy = after.y - before.y;\n if (dx === 0 && dy === 0) return;\n const carried = carriedDescendants(\n { childrenOf: (cid) => source.childrenOf(cid), poses: pa, composition: source.composition },\n id,\n (pose, cid) => {\n const b = frame.bounds(pose, cid);\n return b === null ? null : frame.remap(pose, b, { ...b, x: b.x + dx, y: b.y + dy }, cid);\n },\n );\n for (const [cid, pose] of carried) write(cid, pose);\n };\n\n while (queue.length > 0) {\n const containerId = queue.shift()!;\n current = containerId;\n const n = (passes.get(containerId) ?? 0) + 1;\n passes.set(containerId, n);\n if (n > MAX_PASSES) continue;\n const layout = layoutOf(containerId);\n if (layout === null || source.get(containerId) === undefined) continue;\n const joined = n === 1 ? arrivals.get(containerId) : undefined;\n const left = n === 1 ? departures.get(containerId) : undefined;\n let arranged = false;\n if (left !== undefined && layout.depart) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const joinedSet = new Set<string>(joined ?? []);\n const staying = at.children.filter((c) => !joinedSet.has(c.id));\n apply(containerId, at, layout.depart(at.container, staying, new Set<string>(left)));\n arranged = true;\n }\n if (joined !== undefined && layout.arrive) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const result = layout.arrive(at.container, at.children, new Set<string>(joined));\n if (result === null) return null;\n apply(containerId, at, result);\n } else if (!arranged || joined !== undefined) {\n const at = frameOf(containerId);\n if (at === null) continue;\n apply(containerId, at, { poses: layout.childPoses(at.container, at.children) });\n }\n }\n return out;\n}\n"],"mappings":"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"}
@@ -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
@@ -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/**\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;;;;;;;;;;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"}