@weasel-js/core 1.12.0 → 1.13.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 (304) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/dist/animation/poseTarget.js +46 -15
  3. package/dist/animation/poseTarget.js.map +1 -1
  4. package/dist/animation/poseTransition.js +3 -3
  5. package/dist/animation/poseTransition.js.map +1 -1
  6. package/dist/animation/reflow.js +3 -6
  7. package/dist/animation/reflow.js.map +1 -1
  8. package/dist/animation/rig/bindRig.js +2 -2
  9. package/dist/animation/rig/bindRig.js.map +1 -1
  10. package/dist/animation/rig/skinBinding.js +2 -2
  11. package/dist/animation/rig/skinBinding.js.map +1 -1
  12. package/dist/barrel/canvas.js +5 -3
  13. package/dist/barrel/chrome.js +3 -1
  14. package/dist/barrel/paths.js +3 -2
  15. package/dist/barrel/routing.js +2 -2
  16. package/dist/barrel/scene.js +6 -1
  17. package/dist/barrel/text.js +2 -2
  18. package/dist/canvas/NodeShape.js +1 -0
  19. package/dist/canvas/NodeShape.js.map +1 -1
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  21. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  23. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  25. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  27. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  29. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
  31. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  32. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  33. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  34. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  35. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  37. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  39. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  41. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  43. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  44. package/dist/canvas/SceneCanvas.js +19 -2
  45. package/dist/canvas/SceneCanvas.js.map +1 -1
  46. package/dist/canvas/accentColor.js +26 -3
  47. package/dist/canvas/accentColor.js.map +1 -1
  48. package/dist/canvas/buildSceneLayer.js +1 -1
  49. package/dist/canvas/buildSceneLayer.js.map +1 -1
  50. package/dist/canvas/buildSceneTree.js +14 -20
  51. package/dist/canvas/buildSceneTree.js.map +1 -1
  52. package/dist/canvas/canvasRenderer.js +6 -2
  53. package/dist/canvas/canvasRenderer.js.map +1 -1
  54. package/dist/canvas/copiesAbove.js +1 -1
  55. package/dist/canvas/copiesAbove.js.map +1 -1
  56. package/dist/canvas/defaultDrawOne.js +10 -7
  57. package/dist/canvas/defaultDrawOne.js.map +1 -1
  58. package/dist/canvas/deps/areaSelect.js +5 -2
  59. package/dist/canvas/deps/areaSelect.js.map +1 -1
  60. package/dist/canvas/deps/booleans.js +1 -1
  61. package/dist/canvas/deps/booleans.js.map +1 -1
  62. package/dist/canvas/deps/enteredGroup.js +81 -0
  63. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  64. package/dist/canvas/deps/hitTestArea.js +27 -15
  65. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  66. package/dist/canvas/deps/insert.js +4 -36
  67. package/dist/canvas/deps/insert.js.map +1 -1
  68. package/dist/canvas/deps/insertSite.js +39 -0
  69. package/dist/canvas/deps/insertSite.js.map +1 -0
  70. package/dist/canvas/deps/lassoSelect.js +6 -3
  71. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  72. package/dist/canvas/deps/pathLeaf.js +1 -1
  73. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  74. package/dist/canvas/deps/slice.js +2 -1
  75. package/dist/canvas/deps/slice.js.map +1 -1
  76. package/dist/canvas/derivedPath.js +14 -6
  77. package/dist/canvas/derivedPath.js.map +1 -1
  78. package/dist/canvas/doubleClickTarget.js +9 -11
  79. package/dist/canvas/doubleClickTarget.js.map +1 -1
  80. package/dist/canvas/drawnBounds.js +16 -7
  81. package/dist/canvas/drawnBounds.js.map +1 -1
  82. package/dist/canvas/inheritedPaints.js +22 -11
  83. package/dist/canvas/inheritedPaints.js.map +1 -1
  84. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  85. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  86. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  87. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  88. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  89. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  90. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  91. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  92. package/dist/canvas/ownClip.js +24 -0
  93. package/dist/canvas/ownClip.js.map +1 -0
  94. package/dist/canvas/paintCull.js.map +1 -1
  95. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  96. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  97. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  98. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  99. package/dist/canvas/pickWalk.js +14 -90
  100. package/dist/canvas/pickWalk.js.map +1 -1
  101. package/dist/canvas/poseCascade.js +1 -1
  102. package/dist/canvas/poseCascade.js.map +1 -1
  103. package/dist/canvas/publishPreviews.js +97 -0
  104. package/dist/canvas/publishPreviews.js.map +1 -0
  105. package/dist/canvas/sceneAdapter.js +7 -5
  106. package/dist/canvas/sceneAdapter.js.map +1 -1
  107. package/dist/canvas/sceneClipboard.js +17 -10
  108. package/dist/canvas/sceneClipboard.js.map +1 -1
  109. package/dist/canvas/scenePickReads.js +37 -0
  110. package/dist/canvas/scenePickReads.js.map +1 -0
  111. package/dist/canvas/scenePickSource.js +74 -0
  112. package/dist/canvas/scenePickSource.js.map +1 -0
  113. package/dist/canvas/sceneSlotWiring.js +9 -4
  114. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  115. package/dist/canvas/sceneViewRender.js +13 -8
  116. package/dist/canvas/sceneViewRender.js.map +1 -1
  117. package/dist/canvas/shownNodes.js +15 -0
  118. package/dist/canvas/shownNodes.js.map +1 -0
  119. package/dist/canvas/useCanvasPaint.js +3 -1
  120. package/dist/canvas/useCanvasPaint.js.map +1 -1
  121. package/dist/canvas/useSceneAdapter.js +5 -3
  122. package/dist/canvas/useSceneAdapter.js.map +1 -1
  123. package/dist/clipboard.d.ts +2 -2
  124. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  125. package/dist/clone.d.ts +3 -3
  126. package/dist/core/isPlainObject.js +2 -2
  127. package/dist/core/layers/layerGroup.js.map +1 -1
  128. package/dist/core/layers/render.js.map +1 -1
  129. package/dist/core/scene/NodeRouting.js.map +1 -1
  130. package/dist/core/scene/arrivalWindow.js +1 -1
  131. package/dist/core/scene/arrivalWindow.js.map +1 -1
  132. package/dist/core/scene/clipNothing.js +2 -5
  133. package/dist/core/scene/clipNothing.js.map +1 -1
  134. package/dist/core/scene/declaredFieldSetters.js +44 -0
  135. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  136. package/dist/core/scene/depMemo.js +3 -0
  137. package/dist/core/scene/depMemo.js.map +1 -1
  138. package/dist/core/scene/dependents.js +3 -1
  139. package/dist/core/scene/dependents.js.map +1 -1
  140. package/dist/core/scene/documentOf.js +43 -0
  141. package/dist/core/scene/documentOf.js.map +1 -0
  142. package/dist/core/scene/effectivePose.js +37 -13
  143. package/dist/core/scene/effectivePose.js.map +1 -1
  144. package/dist/core/scene/fieldEdits.js +17 -14
  145. package/dist/core/scene/fieldEdits.js.map +1 -1
  146. package/dist/core/scene/historyForm.js +4 -4
  147. package/dist/core/scene/historyForm.js.map +1 -1
  148. package/dist/core/scene/inheritedPaint.js +27 -5
  149. package/dist/core/scene/inheritedPaint.js.map +1 -1
  150. package/dist/core/scene/kitOps.js +3 -23
  151. package/dist/core/scene/kitOps.js.map +1 -1
  152. package/dist/core/scene/layoutPass.js +1 -2
  153. package/dist/core/scene/layoutPass.js.map +1 -1
  154. package/dist/core/scene/nodeAsShown.js +72 -0
  155. package/dist/core/scene/nodeAsShown.js.map +1 -0
  156. package/dist/core/scene/nodeFnFields.js +2 -10
  157. package/dist/core/scene/nodeFnFields.js.map +1 -1
  158. package/dist/core/scene/nodeMemo.js +6 -1
  159. package/dist/core/scene/nodeMemo.js.map +1 -1
  160. package/dist/core/scene/nodeStore.js +2 -4
  161. package/dist/core/scene/nodeStore.js.map +1 -1
  162. package/dist/core/scene/pathEffects/derive.js +3 -2
  163. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  164. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  165. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  166. package/dist/core/scene/plyStacks.js +89 -0
  167. package/dist/core/scene/plyStacks.js.map +1 -0
  168. package/dist/core/scene/poseHold.js +42 -24
  169. package/dist/core/scene/poseHold.js.map +1 -1
  170. package/dist/core/scene/poseOverrides.js +35 -38
  171. package/dist/core/scene/poseOverrides.js.map +1 -1
  172. package/dist/core/scene/posePlies.js +59 -0
  173. package/dist/core/scene/posePlies.js.map +1 -0
  174. package/dist/core/scene/reconcileSpecs.js +6 -15
  175. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  176. package/dist/core/scene/renderOrder.js +10 -4
  177. package/dist/core/scene/renderOrder.js.map +1 -1
  178. package/dist/core/scene/sameValue.js +17 -0
  179. package/dist/core/scene/sameValue.js.map +1 -0
  180. package/dist/core/scene/scene.js +3 -3
  181. package/dist/core/scene/scene.js.map +1 -1
  182. package/dist/core/scene/sceneEdits.js +8 -0
  183. package/dist/core/scene/sceneEdits.js.map +1 -1
  184. package/dist/core/scene/sceneState.js +1 -2
  185. package/dist/core/scene/sceneState.js.map +1 -1
  186. package/dist/core/scene/serialization.js +45 -9
  187. package/dist/core/scene/serialization.js.map +1 -1
  188. package/dist/core/scene/staleReplay.js +5 -2
  189. package/dist/core/scene/staleReplay.js.map +1 -1
  190. package/dist/core/scene/statedFields.js +4 -2
  191. package/dist/core/scene/statedFields.js.map +1 -1
  192. package/dist/core/scene/treeEdits.js +2 -4
  193. package/dist/core/scene/treeEdits.js.map +1 -1
  194. package/dist/core/scene/treeOrder.js +3 -1
  195. package/dist/core/scene/treeOrder.js.map +1 -1
  196. package/dist/core/scene/treeReads.js +1 -2
  197. package/dist/core/scene/treeReads.js.map +1 -1
  198. package/dist/core/scene/treeShape.js +13 -0
  199. package/dist/core/scene/treeShape.js.map +1 -0
  200. package/dist/core/scene/types.js.map +1 -1
  201. package/dist/core/scene/useScene.js +1 -1
  202. package/dist/core/scene/useScene.js.map +1 -1
  203. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  204. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-CpIQzKWQ.d.ts} +38 -59
  205. package/dist/devtools/handlers.js +1 -1
  206. package/dist/devtools/handlers.js.map +1 -1
  207. package/dist/devtools/protocol.js.map +1 -1
  208. package/dist/devtools-protocol.d.ts +4 -3
  209. package/dist/devtools.d.ts +3 -2
  210. package/dist/features/measure/measureHit.js +5 -3
  211. package/dist/features/measure/measureHit.js.map +1 -1
  212. package/dist/features/measure/measurement.js +1 -1
  213. package/dist/features/measure/measurement.js.map +1 -1
  214. package/dist/features/modes/modeLayers.js +14 -7
  215. package/dist/features/modes/modeLayers.js.map +1 -1
  216. package/dist/features/modes/modeTriggers.js +3 -3
  217. package/dist/features/modes/modeTriggers.js.map +1 -1
  218. package/dist/features/pathEffects/effectSources.js +25 -16
  219. package/dist/features/pathEffects/effectSources.js.map +1 -1
  220. package/dist/features/pathEffects/readSource.js +1 -1
  221. package/dist/features/pathEffects/readSource.js.map +1 -1
  222. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  223. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  224. package/dist/features/paths/editablePath.js +1 -1
  225. package/dist/features/paths/editablePath.js.map +1 -1
  226. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  227. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  228. package/dist/features/selection/enteredGroupFrame.js +65 -0
  229. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  230. package/dist/features/selection/enteredGroupScope.js +44 -0
  231. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  232. package/dist/features/text/sceneTextDrafts.js +26 -27
  233. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  234. package/dist/features/text/textHitAt.js +4 -2
  235. package/dist/features/text/textHitAt.js.map +1 -1
  236. package/dist/features/text/useSceneTextEdit.js +3 -3
  237. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  238. package/dist/features/viewports/viewportLayer.js +2 -1
  239. package/dist/features/viewports/viewportLayer.js.map +1 -1
  240. package/dist/index.d.ts +230 -77
  241. package/dist/index.js +16 -6
  242. package/dist/interactions/actions/admitsChild.js +11 -0
  243. package/dist/interactions/actions/admitsChild.js.map +1 -0
  244. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  245. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  246. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  247. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  248. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  249. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  250. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  251. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  252. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  253. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  254. package/dist/interactions/actions/defaults/group.js +7 -7
  255. package/dist/interactions/actions/defaults/group.js.map +1 -1
  256. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  257. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  258. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  259. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  260. package/dist/interactions/actions/drawnBoxes.js +2 -1
  261. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  262. package/dist/interactions/actions/insert/action.js +1 -0
  263. package/dist/interactions/actions/insert/action.js.map +1 -1
  264. package/dist/interactions/actions/insert/parent.js +7 -5
  265. package/dist/interactions/actions/insert/parent.js.map +1 -1
  266. package/dist/interactions/actions/insert/placeAction.js +3 -2
  267. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  268. package/dist/interactions/actions/insert/session.js +2 -2
  269. package/dist/interactions/actions/insert/session.js.map +1 -1
  270. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  271. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  272. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  273. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  274. package/dist/interactions/actions/move/layout/exits.js +1 -1
  275. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  276. package/dist/interactions/actions/move/session.js +4 -4
  277. package/dist/interactions/actions/move/session.js.map +1 -1
  278. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  279. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  280. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  281. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  282. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  283. package/dist/math.d.ts +1 -1
  284. package/dist/move.d.ts +4 -4
  285. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  286. package/dist/resize.d.ts +4 -4
  287. package/dist/test-seams.d.ts +6 -6
  288. package/dist/tools/builtin/pressTarget.js +22 -0
  289. package/dist/tools/builtin/pressTarget.js.map +1 -0
  290. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  291. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  292. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  293. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  294. package/dist/tools/builtin/select/groupEntry.js +82 -0
  295. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  296. package/dist/tools/builtin/select/pressDecision.js +1 -1
  297. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  298. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  299. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  300. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  301. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  302. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  303. package/dist/version.js +1 -1
  304. package/package.json +25 -25
@@ -1,7 +1,9 @@
1
+ import { sourcesOf } from "../core/scene/dependents.js";
1
2
  import { definesFrame } from "../core/scene/effectivePose.js";
2
3
  import { asNodeId } from "../core/scene/types.js";
3
4
  import { NODE_FN_FIELDS, createNodeFnFields, fnFieldsOfNode, fnKeysOf } from "../core/scene/nodeFnFields.js";
4
5
  import { statedFields } from "../core/scene/statedFields.js";
6
+ import { hasChildren } from "../core/scene/treeShape.js";
5
7
 
6
8
  //#region src/canvas/sceneClipboard.ts
7
9
  function copyField(value) {
@@ -9,7 +11,6 @@ function copyField(value) {
9
11
  if (value !== null && typeof value === "object") return { ...value };
10
12
  return value;
11
13
  }
12
- var dependencyList = (item) => Array.isArray(item.dependsOn) ? item.dependsOn : [];
13
14
  function snapshotSceneSelection(env, ids) {
14
15
  const { scene } = env;
15
16
  const fnFields = createNodeFnFields(scene.registry);
@@ -34,13 +35,13 @@ function snapshotSceneSelection(env, ids) {
34
35
  parent: n.parent,
35
36
  pose: copyField(poseFor(n, isRoot)),
36
37
  data: copyField(n.data),
37
- ...n.kind === "container" ? { children: [...n.children] } : {},
38
+ ...n.children !== void 0 ? { children: [...n.children] } : {},
38
39
  ...statedFields(n),
39
40
  ...n.dependsOn !== void 0 ? { dependsOn: copyField(n.dependsOn) } : {},
40
41
  ...fnFieldsOfNode(n),
41
42
  ...fnKeysOf(fnFields, n, "jsonKey")
42
43
  });
43
- if (n.kind === "container") for (const cid of scene.childrenOf(asNodeId(id))) capture(cid);
44
+ if (hasChildren(n)) for (const cid of scene.childrenOf(asNodeId(id))) capture(cid);
44
45
  };
45
46
  for (const id of ids) {
46
47
  if (scene.ancestorsOf(asNodeId(id)).some((a) => idSet.has(a))) continue;
@@ -57,7 +58,6 @@ function restoredFnFields(scene, item) {
57
58
  const out = {};
58
59
  const registry = scene.registry;
59
60
  for (const spec of NODE_FN_FIELDS) {
60
- if (spec.containersOnly && item.kind !== "container") continue;
61
61
  const key = item[spec.jsonKey];
62
62
  const fn = (typeof key === "string" ? registry[spec.field]?.[key] : void 0) ?? (isLive(item[spec.field]) ? item[spec.field] : void 0);
63
63
  if (fn !== void 0) out[spec.field] = fn;
@@ -79,7 +79,7 @@ function omissions(src, fns) {
79
79
  for (const item of src) {
80
80
  if (out.has(item.id)) continue;
81
81
  let omission;
82
- const deps = dependencyList(item);
82
+ const deps = sourcesOf(item);
83
83
  const missing = deps.filter((dep) => !present.has(dep));
84
84
  const fn = fns.get(item.id);
85
85
  if (item.parent !== null && out.has(item.parent)) omission = {
@@ -142,16 +142,23 @@ function pasteSceneSnapshot(env, clipboard, offset, ctx) {
142
142
  dy = ctx.dropPoint.worldY - minY;
143
143
  }
144
144
  }
145
+ const site = env.landing?.();
146
+ const into = site?.parent != null ? site : null;
147
+ const placed = (item) => {
148
+ if (ridesPastedFrame(item)) return copyField(item.pose);
149
+ const moved = env.translate(copyField(item.pose), dx, dy, item);
150
+ return into && isRoot(item) ? into.local(moved) : moved;
151
+ };
145
152
  return src.map((item) => ({
146
153
  id: idMap.get(item.id),
147
154
  kind: item.kind,
148
- layer: item.layer,
149
- parent: isRoot(item) ? null : idMap.get(item.parent),
150
- pose: ridesPastedFrame(item) ? copyField(item.pose) : env.translate(copyField(item.pose), dx, dy, item),
155
+ layer: into && isRoot(item) ? into.layer : item.layer,
156
+ parent: isRoot(item) ? into?.parent ?? null : idMap.get(item.parent),
157
+ pose: placed(item),
151
158
  data: copyField(item.data),
152
159
  ...statedFields(item),
153
- ...item.kind === "container" ? { children: item.children.filter((c) => idMap.has(c)).map((c) => idMap.get(c)) } : {},
154
- ...item.dependsOn === "children" ? { dependsOn: "children" } : dependencyList(item).length > 0 ? { dependsOn: dependencyList(item).map((dep) => idMap.get(dep)) } : {},
160
+ ...item.children !== void 0 ? { children: item.children.filter((c) => idMap.has(c)).map((c) => idMap.get(c)) } : {},
161
+ ...item.dependsOn === "children" ? { dependsOn: "children" } : sourcesOf(item).length > 0 ? { dependsOn: sourcesOf(item).map((dep) => idMap.get(dep)) } : {},
155
162
  ...fns.get(item.id)
156
163
  }));
157
164
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sceneClipboard.js","names":[],"sources":["../../src/canvas/sceneClipboard.ts"],"sourcesContent":["/**\n * The scene adapter's clipboard seam: what a copy captures and what a paste\n * mints from it.\n *\n * `pasteSceneSnapshot` is a pure factory — it mints fresh nodes and never\n * touches the scene. Insertion happens through the caller's InsertOps (one per\n * created node, parents before children).\n *\n * A snapshot item carries its node's function fields twice: live, for a paste\n * in the same session, and as the scene's registry keys, for a snapshot that\n * crossed JSON on the way back.\n */\nimport type { ClipboardSnapshot, PasteCtx, PasteOmission } from 'core/adapters/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport {\n NODE_FN_FIELDS, createNodeFnFields, fnFieldsOfNode, fnKeysOf, type FnBearingNode,\n} from 'core/scene/nodeFnFields';\nimport { statedFields } from 'core/scene/statedFields';\nimport type { Bounds } from '../core/viewport/fitViewToBounds';\n\n/** What the seam needs from the adapter it serves. */\nexport interface SceneClipboardEnv<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** Whether a stored pose is relative to its container's frame. */\n composes: boolean;\n worldPoseOf(id: string): TPose;\n /** The nearest frame-defining container at or above `id`. */\n frameAtOrAbove(id: string | null): string | null;\n poseBounds(pose: TPose, node: unknown): Bounds;\n translate(pose: TPose, dx: number, dy: number, node?: unknown): TPose;\n}\n\n// Spread-level copy per the clipboard contract: mutating a snapshot's\n// pose/data/children must not touch the scene.\nfunction copyField<T>(value: T): T {\n if (Array.isArray(value)) return [...value] as T;\n if (value !== null && typeof value === 'object') return { ...(value as object) } as T;\n return value;\n}\n\ntype Item<TData, TLayer extends string, TPose> =\n Node<TData, TLayer, TPose> & Record<string, unknown>;\n\nconst dependencyList = (item: { dependsOn?: readonly NodeId[] | 'children' }): readonly NodeId[] =>\n (Array.isArray(item.dependsOn) ? item.dependsOn : []);\n\nexport function snapshotSceneSelection<TData, TLayer extends string, TPose>(\n env: SceneClipboardEnv<TData, TLayer, TPose>,\n ids: string[],\n): ClipboardSnapshot {\n const { scene } = env;\n const fnFields = createNodeFnFields<TPose>(scene.registry as never);\n const idSet = new Set(ids);\n const taken = new Set<string>();\n const items: Node<TData, TLayer, TPose>[] = [];\n // A stored pose is relative to its frame, and a node whose frame is not\n // copied with it lands somewhere else entirely on paste. Capture those in\n // world coordinates: every root, and under an envelope root, the nodes\n // sharing the frame the root left behind.\n const poseFor = (n: Node<TData, TLayer, TPose>, isRoot: boolean): TPose => {\n if (!env.composes) return n.pose;\n if (isRoot) return env.worldPoseOf(n.id);\n const frame = env.frameAtOrAbove(n.parent);\n return frame !== null && taken.has(frame) ? n.pose : env.worldPoseOf(n.id);\n };\n\n // DFS from one snapshot root: push the node, then its subtree —\n // parents-before-children, so paste can re-insert in array order.\n const capture = (id: string, isRoot = false): void => {\n if (taken.has(id)) return;\n const n = scene.get(asNodeId(id));\n if (!n) return;\n taken.add(id);\n items.push({\n kind: n.kind,\n id: n.id,\n layer: n.layer,\n parent: n.parent,\n pose: copyField(poseFor(n, isRoot)),\n data: copyField(n.data),\n ...(n.kind === 'container' ? { children: [...n.children] } : {}),\n ...statedFields(n),\n ...(n.dependsOn !== undefined ? { dependsOn: copyField(n.dependsOn) } : {}),\n ...fnFieldsOfNode(n),\n ...fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey'),\n } as Node<TData, TLayer, TPose>);\n if (n.kind === 'container') {\n for (const cid of scene.childrenOf(asNodeId(id))) capture(cid);\n }\n };\n for (const id of ids) {\n // Dedupe: an id whose ancestor is also selected is already covered\n // by that ancestor's subtree walk.\n if (scene.ancestorsOf(asNodeId(id)).some((a) => idSet.has(a))) continue;\n capture(id, true);\n }\n return { items };\n}\n\n// JSON drops a function, and turns a layout strategy into an object with no\n// methods on it: neither is something a pasted node can run.\nconst isLive = (value: unknown): boolean =>\n typeof value === 'function'\n || (typeof value === 'object' && value !== null\n && Object.values(value).some((member) => typeof member === 'function'));\n\n/**\n * The item's function fields: for each, what its registry key names in this\n * scene, else the live one it carries, else nothing.\n */\nfunction restoredFnFields<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n item: Item<TData, TLayer, TPose>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n const registry = scene.registry as Record<string, Record<string, unknown> | undefined>;\n for (const spec of NODE_FN_FIELDS) {\n if (spec.containersOnly && item.kind !== 'container') continue;\n const key = item[spec.jsonKey];\n const registered = typeof key === 'string' ? registry[spec.field]?.[key] : undefined;\n const fn = registered ?? (isLive(item[spec.field]) ? item[spec.field] : undefined);\n if (fn !== undefined) out[spec.field] = fn;\n }\n return out;\n}\n\n/**\n * Which items a paste leaves out, and why. A node deriving from an id list is\n * pasted only with everything it depends on, since a copy still naming the\n * originals would follow them rather than the copies beside it; and only with\n * a derivation to run, since without one it is a placeholder that paints\n * nothing. Leaving one out can strand another, so this runs to a fixed point.\n */\nfunction omissions<TData, TLayer extends string, TPose>(\n src: readonly Item<TData, TLayer, TPose>[],\n fns: ReadonlyMap<string, Record<string, unknown>>,\n): Map<string, PasteOmission> {\n const out = new Map<string, PasteOmission>();\n const present = new Set(src.map((item) => item.id as string));\n for (let changed = true; changed;) {\n changed = false;\n for (const item of src) {\n if (out.has(item.id)) continue;\n let omission: PasteOmission | undefined;\n const deps = dependencyList(item);\n const missing = deps.filter((dep) => !present.has(dep));\n const fn = fns.get(item.id)!;\n if (item.parent !== null && out.has(item.parent)) {\n omission = { id: item.id, reason: 'container-not-pasted' };\n } else if (missing.length > 0) {\n omission = { id: item.id, reason: 'dependency-not-copied', missing };\n } else if (deps.length > 0 && fn.derivePath === undefined && fn.derivePose === undefined) {\n omission = { id: item.id, reason: 'derivation-not-registered' };\n }\n if (omission === undefined) continue;\n out.set(item.id, omission);\n present.delete(item.id);\n changed = true;\n }\n }\n return out;\n}\n\nexport function pasteSceneSnapshot<TData, TLayer extends string, TPose>(\n env: SceneClipboardEnv<TData, TLayer, TPose>,\n clipboard: ClipboardSnapshot,\n offset: { dx: number; dy: number },\n ctx?: PasteCtx,\n): Node<TData, TLayer, TPose>[] {\n const all = clipboard.items as Item<TData, TLayer, TPose>[];\n if (all.length === 0) return [];\n const fns = new Map(all.map((item) => [item.id as string, restoredFnFields(env.scene, item)]));\n const omitted = omissions(all, fns);\n if (ctx?.onOmit) for (const item of all) {\n const omission = omitted.get(item.id);\n if (omission) ctx.onOmit(omission);\n }\n const src = omitted.size === 0 ? all : all.filter((item) => !omitted.has(item.id));\n\n const idMap = new Map<string, NodeId>();\n for (const item of src) idMap.set(item.id, env.scene.mintId());\n // Items whose captured parent isn't part of the snapshot become roots\n // of the pasted cluster.\n const isRoot = (item: Node<TData, TLayer, TPose>): boolean =>\n item.parent === null || !idMap.has(item.parent);\n // Under a composing strategy a node inside a pasted frame rides it, and\n // offsetting it as well would move it twice.\n const byId = new Map(src.map((item) => [item.id as string, item]));\n const ridesPastedFrame = (item: Node<TData, TLayer, TPose>): boolean => {\n if (!env.composes) return false;\n let p = item.parent === null ? undefined : byId.get(item.parent);\n while (p !== undefined) {\n if (definesFrame(p)) return true;\n p = p.parent === null ? undefined : byId.get(p.parent);\n }\n return false;\n };\n // Without composition poses are absolute, so the delta applies to EVERY\n // pasted node.\n let dx = offset.dx;\n let dy = offset.dy;\n if (ctx?.dropPoint) {\n // Drop-point policy: land the cluster origin (min corner of the root\n // items' bounds) on the drop point; `offset` is ignored.\n let minX = Infinity;\n let minY = Infinity;\n for (const item of src) {\n if (!isRoot(item)) continue;\n const b = env.poseBounds(item.pose, item);\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n }\n if (Number.isFinite(minX) && Number.isFinite(minY)) {\n dx = ctx.dropPoint.worldX - minX;\n dy = ctx.dropPoint.worldY - minY;\n }\n }\n // `src` is parents-before-children and this keeps it, so children re-attach\n // to freshly inserted parents when the caller's InsertOps apply in order.\n return src.map((item) => ({\n id: idMap.get(item.id)!,\n kind: item.kind,\n layer: item.layer,\n parent: isRoot(item) ? null : idMap.get(item.parent as string)!,\n pose: ridesPastedFrame(item)\n ? copyField(item.pose)\n : env.translate(copyField(item.pose), dx, dy, item),\n data: copyField(item.data),\n ...statedFields(item),\n ...(item.kind === 'container'\n ? { children: item.children.filter((c) => idMap.has(c)).map((c) => idMap.get(c)!) }\n : {}),\n ...(item.dependsOn === 'children'\n ? { dependsOn: 'children' as const }\n : dependencyList(item).length > 0\n ? { dependsOn: dependencyList(item).map((dep) => idMap.get(dep)!) }\n : {}),\n ...fns.get(item.id),\n } as Node<TData, TLayer, TPose>));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sceneClipboard.js","names":[],"sources":["../../src/canvas/sceneClipboard.ts"],"sourcesContent":["/**\n * The scene adapter's clipboard seam: what a copy captures and what a paste\n * mints from it.\n *\n * `pasteSceneSnapshot` is a pure factory — it mints fresh nodes and never\n * touches the scene. Insertion happens through the caller's InsertOps (one per\n * created node, parents before children).\n *\n * A snapshot item carries its node's function fields twice: live, for a paste\n * in the same session, and as the scene's registry keys, for a snapshot that\n * crossed JSON on the way back.\n */\nimport type { ClipboardSnapshot, PasteCtx, PasteOmission } from 'core/adapters/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport { hasChildren } from 'core/scene/treeShape';\nimport { sourcesOf } from 'core/scene/dependents';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport {\n NODE_FN_FIELDS, createNodeFnFields, fnFieldsOfNode, fnKeysOf, type FnBearingNode,\n} from 'core/scene/nodeFnFields';\nimport { statedFields } from 'core/scene/statedFields';\nimport type { Bounds } from '../core/viewport/fitViewToBounds';\nimport type { InsertSite } from './deps/insertSite';\n\n/** What the seam needs from the adapter it serves. */\nexport interface SceneClipboardEnv<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** Whether a stored pose is relative to its container's frame. */\n composes: boolean;\n worldPoseOf(id: string): TPose;\n /** The nearest frame-defining container at or above `id`. */\n frameAtOrAbove(id: string | null): string | null;\n poseBounds(pose: TPose, node: unknown): Bounds;\n translate(pose: TPose, dx: number, dy: number, node?: unknown): TPose;\n /** Where the pasted cluster's roots land; absent, or naming no parent, they keep their layer at the top of the tree. */\n landing?: () => InsertSite;\n}\n\n// Spread-level copy per the clipboard contract: mutating a snapshot's\n// pose/data/children must not touch the scene.\nfunction copyField<T>(value: T): T {\n if (Array.isArray(value)) return [...value] as T;\n if (value !== null && typeof value === 'object') return { ...(value as object) } as T;\n return value;\n}\n\ntype Item<TData, TLayer extends string, TPose> =\n Node<TData, TLayer, TPose> & Record<string, unknown>;\n\nexport function snapshotSceneSelection<TData, TLayer extends string, TPose>(\n env: SceneClipboardEnv<TData, TLayer, TPose>,\n ids: string[],\n): ClipboardSnapshot {\n const { scene } = env;\n const fnFields = createNodeFnFields<TPose>(scene.registry as never);\n const idSet = new Set(ids);\n const taken = new Set<string>();\n const items: Node<TData, TLayer, TPose>[] = [];\n // A stored pose is relative to its frame, and a node whose frame is not\n // copied with it lands somewhere else entirely on paste. Capture those in\n // world coordinates: every root, and under an envelope root, the nodes\n // sharing the frame the root left behind.\n const poseFor = (n: Node<TData, TLayer, TPose>, isRoot: boolean): TPose => {\n if (!env.composes) return n.pose;\n if (isRoot) return env.worldPoseOf(n.id);\n const frame = env.frameAtOrAbove(n.parent);\n return frame !== null && taken.has(frame) ? n.pose : env.worldPoseOf(n.id);\n };\n\n // DFS from one snapshot root: push the node, then its subtree —\n // parents-before-children, so paste can re-insert in array order.\n const capture = (id: string, isRoot = false): void => {\n if (taken.has(id)) return;\n const n = scene.get(asNodeId(id));\n if (!n) return;\n taken.add(id);\n items.push({\n kind: n.kind,\n id: n.id,\n layer: n.layer,\n parent: n.parent,\n pose: copyField(poseFor(n, isRoot)),\n data: copyField(n.data),\n ...(n.children !== undefined ? { children: [...n.children] } : {}),\n ...statedFields(n),\n ...(n.dependsOn !== undefined ? { dependsOn: copyField(n.dependsOn) } : {}),\n ...fnFieldsOfNode(n),\n ...fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey'),\n } as Node<TData, TLayer, TPose>);\n if (hasChildren(n)) {\n for (const cid of scene.childrenOf(asNodeId(id))) capture(cid);\n }\n };\n for (const id of ids) {\n // Dedupe: an id whose ancestor is also selected is already covered\n // by that ancestor's subtree walk.\n if (scene.ancestorsOf(asNodeId(id)).some((a) => idSet.has(a))) continue;\n capture(id, true);\n }\n return { items };\n}\n\n// JSON drops a function, and turns a layout strategy into an object with no\n// methods on it: neither is something a pasted node can run.\nconst isLive = (value: unknown): boolean =>\n typeof value === 'function'\n || (typeof value === 'object' && value !== null\n && Object.values(value).some((member) => typeof member === 'function'));\n\n/**\n * The item's function fields: for each, what its registry key names in this\n * scene, else the live one it carries, else nothing.\n */\nfunction restoredFnFields<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n item: Item<TData, TLayer, TPose>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n const registry = scene.registry as Record<string, Record<string, unknown> | undefined>;\n for (const spec of NODE_FN_FIELDS) {\n const key = item[spec.jsonKey];\n const registered = typeof key === 'string' ? registry[spec.field]?.[key] : undefined;\n const fn = registered ?? (isLive(item[spec.field]) ? item[spec.field] : undefined);\n if (fn !== undefined) out[spec.field] = fn;\n }\n return out;\n}\n\n/**\n * Which items a paste leaves out, and why. A node deriving from an id list is\n * pasted only with everything it depends on, since a copy still naming the\n * originals would follow them rather than the copies beside it; and only with\n * a derivation to run, since without one it is a placeholder that paints\n * nothing. Leaving one out can strand another, so this runs to a fixed point.\n */\nfunction omissions<TData, TLayer extends string, TPose>(\n src: readonly Item<TData, TLayer, TPose>[],\n fns: ReadonlyMap<string, Record<string, unknown>>,\n): Map<string, PasteOmission> {\n const out = new Map<string, PasteOmission>();\n const present = new Set(src.map((item) => item.id as string));\n for (let changed = true; changed;) {\n changed = false;\n for (const item of src) {\n if (out.has(item.id)) continue;\n let omission: PasteOmission | undefined;\n const deps = sourcesOf(item);\n const missing = deps.filter((dep) => !present.has(dep));\n const fn = fns.get(item.id)!;\n if (item.parent !== null && out.has(item.parent)) {\n omission = { id: item.id, reason: 'container-not-pasted' };\n } else if (missing.length > 0) {\n omission = { id: item.id, reason: 'dependency-not-copied', missing };\n } else if (deps.length > 0 && fn.derivePath === undefined && fn.derivePose === undefined) {\n omission = { id: item.id, reason: 'derivation-not-registered' };\n }\n if (omission === undefined) continue;\n out.set(item.id, omission);\n present.delete(item.id);\n changed = true;\n }\n }\n return out;\n}\n\nexport function pasteSceneSnapshot<TData, TLayer extends string, TPose>(\n env: SceneClipboardEnv<TData, TLayer, TPose>,\n clipboard: ClipboardSnapshot,\n offset: { dx: number; dy: number },\n ctx?: PasteCtx,\n): Node<TData, TLayer, TPose>[] {\n const all = clipboard.items as Item<TData, TLayer, TPose>[];\n if (all.length === 0) return [];\n const fns = new Map(all.map((item) => [item.id as string, restoredFnFields(env.scene, item)]));\n const omitted = omissions(all, fns);\n if (ctx?.onOmit) for (const item of all) {\n const omission = omitted.get(item.id);\n if (omission) ctx.onOmit(omission);\n }\n const src = omitted.size === 0 ? all : all.filter((item) => !omitted.has(item.id));\n\n const idMap = new Map<string, NodeId>();\n for (const item of src) idMap.set(item.id, env.scene.mintId());\n // Items whose captured parent isn't part of the snapshot become roots\n // of the pasted cluster.\n const isRoot = (item: Node<TData, TLayer, TPose>): boolean =>\n item.parent === null || !idMap.has(item.parent);\n // Under a composing strategy a node inside a pasted frame rides it, and\n // offsetting it as well would move it twice.\n const byId = new Map(src.map((item) => [item.id as string, item]));\n const ridesPastedFrame = (item: Node<TData, TLayer, TPose>): boolean => {\n if (!env.composes) return false;\n let p = item.parent === null ? undefined : byId.get(item.parent);\n while (p !== undefined) {\n if (definesFrame(p)) return true;\n p = p.parent === null ? undefined : byId.get(p.parent);\n }\n return false;\n };\n // Without composition poses are absolute, so the delta applies to EVERY\n // pasted node.\n let dx = offset.dx;\n let dy = offset.dy;\n if (ctx?.dropPoint) {\n // Drop-point policy: land the cluster origin (min corner of the root\n // items' bounds) on the drop point; `offset` is ignored.\n let minX = Infinity;\n let minY = Infinity;\n for (const item of src) {\n if (!isRoot(item)) continue;\n const b = env.poseBounds(item.pose, item);\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n }\n if (Number.isFinite(minX) && Number.isFinite(minY)) {\n dx = ctx.dropPoint.worldX - minX;\n dy = ctx.dropPoint.worldY - minY;\n }\n }\n const site = env.landing?.();\n const into = site?.parent != null ? site : null;\n const placed = (item: Item<TData, TLayer, TPose>): TPose => {\n if (ridesPastedFrame(item)) return copyField(item.pose);\n const moved = env.translate(copyField(item.pose), dx, dy, item);\n return into && isRoot(item) ? into.local(moved) as TPose : moved;\n };\n // `src` is parents-before-children and this keeps it, so children re-attach\n // to freshly inserted parents when the caller's InsertOps apply in order.\n return src.map((item) => ({\n id: idMap.get(item.id)!,\n kind: item.kind,\n layer: into && isRoot(item) ? into.layer as TLayer : item.layer,\n parent: isRoot(item) ? into?.parent ?? null : idMap.get(item.parent as string)!,\n pose: placed(item),\n data: copyField(item.data),\n ...statedFields(item),\n ...(item.children !== undefined\n ? { children: item.children.filter((c) => idMap.has(c)).map((c) => idMap.get(c)!) }\n : {}),\n ...(item.dependsOn === 'children'\n ? { dependsOn: 'children' as const }\n : sourcesOf(item).length > 0\n ? { dependsOn: sourcesOf(item).map((dep) => idMap.get(dep)!) }\n : {}),\n ...fns.get(item.id),\n } as Node<TData, TLayer, TPose>));\n}\n"],"mappings":";;;;;;;;AAyCA,SAAS,UAAa,OAAa;CACjC,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC,GAAG,KAAK;CAC1C,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,EAAE,GAAI,MAAiB;CAC/E,OAAO;AACT;AAKA,SAAgB,uBACd,KACA,KACmB;CACnB,MAAM,EAAE,UAAU;CAClB,MAAM,WAAW,mBAA0B,MAAM,QAAiB;CAClE,MAAM,QAAQ,IAAI,IAAI,GAAG;CACzB,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,QAAsC,CAAC;CAK7C,MAAM,WAAW,GAA+B,WAA2B;EACzE,IAAI,CAAC,IAAI,UAAU,OAAO,EAAE;EAC5B,IAAI,QAAQ,OAAO,IAAI,YAAY,EAAE,EAAE;EACvC,MAAM,QAAQ,IAAI,eAAe,EAAE,MAAM;EACzC,OAAO,UAAU,QAAQ,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,IAAI,YAAY,EAAE,EAAE;CAC3E;CAIA,MAAM,WAAW,IAAY,SAAS,UAAgB;EACpD,IAAI,MAAM,IAAI,EAAE,GAAG;EACnB,MAAM,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;EAChC,IAAI,CAAC,GAAG;EACR,MAAM,IAAI,EAAE;EACZ,MAAM,KAAK;GACT,MAAM,EAAE;GACR,IAAI,EAAE;GACN,OAAO,EAAE;GACT,QAAQ,EAAE;GACV,MAAM,UAAU,QAAQ,GAAG,MAAM,CAAC;GAClC,MAAM,UAAU,EAAE,IAAI;GACtB,GAAI,EAAE,aAAa,SAAY,EAAE,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC;GAChE,GAAG,aAAa,CAAC;GACjB,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC;GACzE,GAAG,eAAe,CAAC;GACnB,GAAG,SAAS,UAAU,GAA+B,SAAS;EAChE,CAA+B;EAC/B,IAAI,YAAY,CAAC,GACf,KAAK,MAAM,OAAO,MAAM,WAAW,SAAS,EAAE,CAAC,GAAG,QAAQ,GAAG;CAEjE;CACA,KAAK,MAAM,MAAM,KAAK;EAGpB,IAAI,MAAM,YAAY,SAAS,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC,GAAG;EAC/D,QAAQ,IAAI,IAAI;CAClB;CACA,OAAO,EAAE,MAAM;AACjB;AAIA,IAAM,UAAU,UACd,OAAO,UAAU,cACb,OAAO,UAAU,YAAY,UAAU,QACtC,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,WAAW,UAAU;;;;;AAMzE,SAAS,iBACP,OACA,MACyB;CACzB,MAAM,MAA+B,CAAC;CACtC,MAAM,WAAW,MAAM;CACvB,KAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,MAAM,KAAK,KAAK;EAEtB,MAAM,MADa,OAAO,QAAQ,WAAW,SAAS,KAAK,MAAM,GAAG,OAAO,YACjD,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS;EACxE,IAAI,OAAO,QAAW,IAAI,KAAK,SAAS;CAC1C;CACA,OAAO;AACT;;;;;;;;AASA,SAAS,UACP,KACA,KAC4B;CAC5B,MAAM,sBAAM,IAAI,IAA2B;CAC3C,MAAM,UAAU,IAAI,IAAI,IAAI,KAAK,SAAS,KAAK,EAAY,CAAC;CAC5D,KAAK,IAAI,UAAU,MAAM,UAAU;EACjC,UAAU;EACV,KAAK,MAAM,QAAQ,KAAK;GACtB,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG;GACtB,IAAI;GACJ,MAAM,OAAO,UAAU,IAAI;GAC3B,MAAM,UAAU,KAAK,QAAQ,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC;GACtD,MAAM,KAAK,IAAI,IAAI,KAAK,EAAE;GAC1B,IAAI,KAAK,WAAW,QAAQ,IAAI,IAAI,KAAK,MAAM,GAC7C,WAAW;IAAE,IAAI,KAAK;IAAI,QAAQ;GAAuB;QACpD,IAAI,QAAQ,SAAS,GAC1B,WAAW;IAAE,IAAI,KAAK;IAAI,QAAQ;IAAyB;GAAQ;QAC9D,IAAI,KAAK,SAAS,KAAK,GAAG,eAAe,UAAa,GAAG,eAAe,QAC7E,WAAW;IAAE,IAAI,KAAK;IAAI,QAAQ;GAA4B;GAEhE,IAAI,aAAa,QAAW;GAC5B,IAAI,IAAI,KAAK,IAAI,QAAQ;GACzB,QAAQ,OAAO,KAAK,EAAE;GACtB,UAAU;EACZ;CACF;CACA,OAAO;AACT;AAEA,SAAgB,mBACd,KACA,WACA,QACA,KAC8B;CAC9B,MAAM,MAAM,UAAU;CACtB,IAAI,IAAI,WAAW,GAAG,OAAO,CAAC;CAC9B,MAAM,MAAM,IAAI,IAAI,IAAI,KAAK,SAAS,CAAC,KAAK,IAAc,iBAAiB,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC;CAC7F,MAAM,UAAU,UAAU,KAAK,GAAG;CAClC,IAAI,KAAK,QAAQ,KAAK,MAAM,QAAQ,KAAK;EACvC,MAAM,WAAW,QAAQ,IAAI,KAAK,EAAE;EACpC,IAAI,UAAU,IAAI,OAAO,QAAQ;CACnC;CACA,MAAM,MAAM,QAAQ,SAAS,IAAI,MAAM,IAAI,QAAQ,SAAS,CAAC,QAAQ,IAAI,KAAK,EAAE,CAAC;CAEjF,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,IAAI,IAAI,MAAM,OAAO,CAAC;CAG7D,MAAM,UAAU,SACd,KAAK,WAAW,QAAQ,CAAC,MAAM,IAAI,KAAK,MAAM;CAGhD,MAAM,OAAO,IAAI,IAAI,IAAI,KAAK,SAAS,CAAC,KAAK,IAAc,IAAI,CAAC,CAAC;CACjE,MAAM,oBAAoB,SAA8C;EACtE,IAAI,CAAC,IAAI,UAAU,OAAO;EAC1B,IAAI,IAAI,KAAK,WAAW,OAAO,SAAY,KAAK,IAAI,KAAK,MAAM;EAC/D,OAAO,MAAM,QAAW;GACtB,IAAI,aAAa,CAAC,GAAG,OAAO;GAC5B,IAAI,EAAE,WAAW,OAAO,SAAY,KAAK,IAAI,EAAE,MAAM;EACvD;EACA,OAAO;CACT;CAGA,IAAI,KAAK,OAAO;CAChB,IAAI,KAAK,OAAO;CAChB,IAAI,KAAK,WAAW;EAGlB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,KAAK,MAAM,QAAQ,KAAK;GACtB,IAAI,CAAC,OAAO,IAAI,GAAG;GACnB,MAAM,IAAI,IAAI,WAAW,KAAK,MAAM,IAAI;GACxC,IAAI,EAAE,IAAI,MAAM,OAAO,EAAE;GACzB,IAAI,EAAE,IAAI,MAAM,OAAO,EAAE;EAC3B;EACA,IAAI,OAAO,SAAS,IAAI,KAAK,OAAO,SAAS,IAAI,GAAG;GAClD,KAAK,IAAI,UAAU,SAAS;GAC5B,KAAK,IAAI,UAAU,SAAS;EAC9B;CACF;CACA,MAAM,OAAO,IAAI,UAAU;CAC3B,MAAM,OAAO,MAAM,UAAU,OAAO,OAAO;CAC3C,MAAM,UAAU,SAA4C;EAC1D,IAAI,iBAAiB,IAAI,GAAG,OAAO,UAAU,KAAK,IAAI;EACtD,MAAM,QAAQ,IAAI,UAAU,UAAU,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI;EAC9D,OAAO,QAAQ,OAAO,IAAI,IAAI,KAAK,MAAM,KAAK,IAAa;CAC7D;CAGA,OAAO,IAAI,KAAK,UAAU;EACxB,IAAI,MAAM,IAAI,KAAK,EAAE;EACrB,MAAM,KAAK;EACX,OAAO,QAAQ,OAAO,IAAI,IAAI,KAAK,QAAkB,KAAK;EAC1D,QAAQ,OAAO,IAAI,IAAI,MAAM,UAAU,OAAO,MAAM,IAAI,KAAK,MAAgB;EAC7E,MAAM,OAAO,IAAI;EACjB,MAAM,UAAU,KAAK,IAAI;EACzB,GAAG,aAAa,IAAI;EACpB,GAAI,KAAK,aAAa,SAClB,EAAE,UAAU,KAAK,SAAS,QAAQ,MAAM,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,MAAM,IAAI,CAAC,CAAE,EAAE,IAChF,CAAC;EACL,GAAI,KAAK,cAAc,aACnB,EAAE,WAAW,WAAoB,IACjC,UAAU,IAAI,CAAC,CAAC,SAAS,IACvB,EAAE,WAAW,UAAU,IAAI,CAAC,CAAC,KAAK,QAAQ,MAAM,IAAI,GAAG,CAAE,EAAE,IAC3D,CAAC;EACP,GAAG,IAAI,IAAI,KAAK,EAAE;CACpB,EAAgC;AAClC"}
@@ -0,0 +1,37 @@
1
+ import { asNodeId } from "../core/scene/types.js";
2
+ import { sceneDerivedPathOf, showsComputedPath } from "./derivedPath.js";
3
+
4
+ //#region src/canvas/scenePickReads.ts
5
+ /** Shared, never mutated: most scenes are flat and every node returns it. */
6
+ var EMPTY_PARENTS = [];
7
+ /** `(node) => its ancestors, innermost first`, stopping at a cycle or a missing parent. */
8
+ function sceneParentsOf(scene) {
9
+ return (node) => {
10
+ let parentId = node.parent ?? null;
11
+ if (parentId === null) return EMPTY_PARENTS;
12
+ const chain = [];
13
+ const seen = /* @__PURE__ */ new Set();
14
+ while (parentId !== null) {
15
+ if (seen.has(parentId)) break;
16
+ seen.add(parentId);
17
+ const parent = scene.get(asNodeId(parentId));
18
+ if (!parent) break;
19
+ chain.push(parent);
20
+ parentId = parent.parent ?? null;
21
+ }
22
+ return chain;
23
+ };
24
+ }
25
+ /** `(node) => the path it shows in place of its pose`, or null: an override's
26
+ * path or the one it derives, at the poses the scene paints its dependencies. */
27
+ function sceneShownPathOf(scene) {
28
+ const derived = sceneDerivedPathOf(scene);
29
+ return (node) => {
30
+ const n = node;
31
+ return showsComputedPath(scene, n) ? derived(n) : null;
32
+ };
33
+ }
34
+
35
+ //#endregion
36
+ export { sceneParentsOf, sceneShownPathOf };
37
+ //# sourceMappingURL=scenePickReads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenePickReads.js","names":[],"sources":["../../src/canvas/scenePickReads.ts"],"sourcesContent":["/** What a scene-backed pick reads per node, shared by `scenePickSource` and the\n * pick index so the two cannot disagree about a node's ancestors or path. */\nimport type { Path } from '@weasel-js/geom';\nimport { asNodeId, type Node, type Scene } from 'core/scene/types';\nimport { sceneDerivedPathOf, showsComputedPath } from './derivedPath';\nimport type { PickCandidate } from './pickWalk';\n\n/** Shared, never mutated: most scenes are flat and every node returns it. */\nconst EMPTY_PARENTS: readonly never[] = [];\n\n/** `(node) => its ancestors, innermost first`, stopping at a cycle or a missing parent. */\nexport function sceneParentsOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): (node: PickCandidate<TPose>) => readonly PickCandidate<TPose>[] {\n return (node) => {\n let parentId = (node as { parent?: string | null }).parent ?? null;\n if (parentId === null) return EMPTY_PARENTS;\n const chain: PickCandidate<TPose>[] = [];\n const seen = new Set<string>();\n while (parentId !== null) {\n if (seen.has(parentId)) break;\n seen.add(parentId);\n const parent = scene.get(asNodeId(parentId));\n if (!parent) break;\n chain.push(parent as unknown as PickCandidate<TPose>);\n parentId = parent.parent ?? null;\n }\n return chain;\n };\n}\n\n/** `(node) => the path it shows in place of its pose`, or null: an override's\n * path or the one it derives, at the poses the scene paints its dependencies. */\nexport function sceneShownPathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): (node: PickCandidate<TPose>) => Path | null {\n const derived = sceneDerivedPathOf(scene);\n return (node) => {\n const n = node as unknown as Node<TData, TLayer, TPose>;\n return showsComputedPath(scene, n) ? derived(n) : null;\n };\n}\n"],"mappings":";;;;;AAQA,IAAM,gBAAkC,CAAC;;AAGzC,SAAgB,eACd,OACiE;CACjE,QAAQ,SAAS;EACf,IAAI,WAAY,KAAoC,UAAU;EAC9D,IAAI,aAAa,MAAM,OAAO;EAC9B,MAAM,QAAgC,CAAC;EACvC,MAAM,uBAAO,IAAI,IAAY;EAC7B,OAAO,aAAa,MAAM;GACxB,IAAI,KAAK,IAAI,QAAQ,GAAG;GACxB,KAAK,IAAI,QAAQ;GACjB,MAAM,SAAS,MAAM,IAAI,SAAS,QAAQ,CAAC;GAC3C,IAAI,CAAC,QAAQ;GACb,MAAM,KAAK,MAAyC;GACpD,WAAW,OAAO,UAAU;EAC9B;EACA,OAAO;CACT;AACF;;;AAIA,SAAgB,iBACd,OAC6C;CAC7C,MAAM,UAAU,mBAAmB,KAAK;CACxC,QAAQ,SAAS;EACf,MAAM,IAAI;EACV,OAAO,kBAAkB,OAAO,CAAC,IAAI,QAAQ,CAAC,IAAI;CACpD;AACF"}
@@ -0,0 +1,74 @@
1
+ import { planeMap } from "../core/viewport/parallax.js";
2
+ import { resolveDerivedPath } from "../core/scene/derivedPath.js";
3
+ import { nodeAsShown, nodesAsShown } from "../core/scene/nodeAsShown.js";
4
+ import { asNodeId } from "../core/scene/types.js";
5
+ import { annotationLayerIds } from "../core/scene/annotation.js";
6
+ import { nodeGates } from "../core/scene/nodeGates.js";
7
+ import { scenePaintedPoseOf } from "./derivedPath.js";
8
+ import { sceneParentsOf, sceneShownPathOf } from "./scenePickReads.js";
9
+ import { indexedCandidates } from "./pickIndex/scenePickIndex.js";
10
+
11
+ //#region src/canvas/scenePickSource.ts
12
+ function scenePickSource(scene, opts = {}) {
13
+ const gates = nodeGates(scene);
14
+ const enumerable = typeof scene.overrides?.ids === "function";
15
+ const asShown = (node) => scene.overrides ? nodeAsShown(scene.overrides, node) : node;
16
+ const allAsShown = (nodes) => enumerable ? nodesAsShown(scene.overrides, nodes) : nodes.map(asShown);
17
+ const annotations = annotationLayerIds(scene.layers);
18
+ const { getPose, alphaOf, layerIsPainted } = opts;
19
+ let faded = null;
20
+ if (!alphaOf && enumerable) for (const id of scene.overrides.ids()) {
21
+ const a = scene.overrides.read(id)?.alpha;
22
+ if (a !== void 0 && a <= 0) (faded ??= /* @__PURE__ */ new Set()).add(id);
23
+ }
24
+ const readAlpha = alphaOf ?? (enumerable ? faded ? (id) => faded.has(id) ? 0 : 1 : void 0 : (id) => scene.overrides.read(asNodeId(id))?.alpha ?? 1);
25
+ const paintedPoseOf = scenePaintedPoseOf(scene);
26
+ const poseOf = getPose ? (node) => getPose(node.id) : (node) => paintedPoseOf(node);
27
+ const childrenOf = (id) => scene.childrenOf(id);
28
+ const depOf = (id) => {
29
+ const stored = scene.get(id);
30
+ if (stored === void 0) return void 0;
31
+ const dep = asShown(stored);
32
+ return {
33
+ node: dep,
34
+ pose: poseOf(dep),
35
+ get path() {
36
+ return resolveDerivedPath(dep, depOf, childrenOf);
37
+ }
38
+ };
39
+ };
40
+ const derivedPathOf = getPose ? (node) => {
41
+ const n = node;
42
+ return n.dependsOn === void 0 ? null : resolveDerivedPath(n, depOf, childrenOf);
43
+ } : sceneShownPathOf(scene);
44
+ const planeOf = opts.camera ? scenePlaneOf(scene.layers, opts.camera) : null;
45
+ const index = getPose || typeof scene.getVersion !== "function" ? void 0 : opts.index;
46
+ return {
47
+ ...planeOf ? { planeOf } : {},
48
+ ...index ? { candidates: (broadFor) => {
49
+ const found = indexedCandidates(scene, index.descriptor, broadFor);
50
+ return found === null ? null : allAsShown(found);
51
+ } } : {},
52
+ order: () => allAsShown(scene.renderOrderNodes()),
53
+ derivedPathOf,
54
+ poseOf,
55
+ parentsOf: sceneParentsOf(scene),
56
+ ...readAlpha ? { alphaOf: readAlpha } : {},
57
+ isLocked: (node) => gates.locked(node.id),
58
+ isHidden: (node) => gates.hidden(node.id),
59
+ ...annotations.size > 0 || layerIsPainted ? { layerIsPainted: (layer) => !annotations.has(layer) && (layerIsPainted === void 0 || layerIsPainted(layer)) } : {}
60
+ };
61
+ }
62
+ /** Each parallax layer's plane map under `camera`, or null when no layer is a
63
+ * plane — which keeps the per-layer lookup off the walk entirely. */
64
+ function scenePlaneOf(layers, camera) {
65
+ let maps = null;
66
+ for (const l of layers ?? []) if (l.parallax) (maps ??= /* @__PURE__ */ new Map()).set(l.id, planeMap(camera, l.parallax));
67
+ if (maps === null) return null;
68
+ const found = maps;
69
+ return (layer) => found.get(layer) ?? null;
70
+ }
71
+
72
+ //#endregion
73
+ export { scenePickSource, scenePlaneOf };
74
+ //# sourceMappingURL=scenePickSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenePickSource.js","names":[],"sources":["../../src/canvas/scenePickSource.ts"],"sourcesContent":["/** The pick source over a `Scene`: its render order, painted poses and gates,\n * and, given a descriptor, the pick index that narrows the walk. */\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport { annotationLayerIds } from 'core/scene/annotation';\nimport { nodeGates } from 'core/scene/nodeGates';\nimport { planeMap, type ParallaxOpts, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { resolveDerivedPath, scenePaintedPoseOf } from './derivedPath';\nimport { indexedCandidates } from './pickIndex/scenePickIndex';\nimport type { PickBroad, PickCandidate, PickSource, ViewPickGates } from './pickWalk';\nimport { sceneParentsOf, sceneShownPathOf } from './scenePickReads';\nimport { nodeAsShown, nodesAsShown } from 'core/scene/nodeAsShown';\n\nexport interface ScenePickSourceOptions<TPose> extends ViewPickGates {\n /** Pose lookup, when the caller holds an adapter that already resolves\n * overrides. Defaults to `effectivePose` against the scene's own. */\n getPose?: (id: string) => TPose;\n /** The asking view's composed alpha — `alphaFor(id)` times override alpha.\n * Defaults to the override alpha alone, which is all a bare scene knows. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\",\n * which is `layerVisibility` and `layerOrder` on top of the scene's own\n * `visible` flag. Consulted after the scene's flag, never instead of it. */\n layerIsPainted?: (layer: string) => boolean;\n /** The asking view's camera. With it, a layer carrying `parallax` is picked\n * through its plane; without it, every layer is taken to move with the\n * camera. */\n camera?: View;\n /** Box every node once per scene change, so a query carrying a `broad` box\n * visits only the nodes it can hit. `descriptor` must be the one the\n * query reads poses through. */\n index?: { descriptor: PoseDescriptor<unknown> };\n}\n\nexport function scenePickSource<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n opts: ScenePickSourceOptions<TPose> = {},\n): PickSource<TPose> {\n // Partial scene stand-ins carry only `layers` and `get`, which is all the gates read.\n const gates = nodeGates(scene as never);\n const enumerable = typeof scene.overrides?.ids === 'function';\n // Pick candidates carry a plain string id; the scene answers them all the same.\n const asShown = <N extends { id: string; data?: unknown }>(node: N): N =>\n (scene.overrides ? nodeAsShown(scene.overrides, node as N & { id: NodeId }) : node);\n const allAsShown = <N extends { id: string; data?: unknown }>(nodes: readonly N[]): readonly N[] =>\n (enumerable ? nodesAsShown(scene.overrides, nodes as readonly (N & { id: NodeId })[]) : nodes.map(asShown));\n const annotations = annotationLayerIds(scene.layers);\n const { getPose, alphaOf, layerIsPainted } = opts;\n\n // Resolved once per walk rather than once per candidate. Needs enumeration — a\n // partial scene stand-in carrying only `read` falls back to reading per node.\n let faded: Set<string> | null = null;\n if (!alphaOf && enumerable) {\n for (const id of scene.overrides.ids()) {\n const a = scene.overrides.read(id)?.alpha;\n if (a !== undefined && a <= 0) (faded ??= new Set()).add(id);\n }\n }\n const readAlpha = alphaOf\n ?? (enumerable\n ? (faded ? (id: string) => (faded.has(id) ? 0 : 1) : undefined)\n : (id: string) => scene.overrides.read(asNodeId(id))?.alpha ?? 1);\n\n // A composing strategy makes a node's own pose local, so picking folds the\n // parent chain the same way the render walk does: one definition of both.\n const paintedPoseOf = scenePaintedPoseOf(scene);\n const poseOf = getPose\n ? (node: PickCandidate<TPose>) => getPose(node.id)\n : (node: PickCandidate<TPose>) => paintedPoseOf(node as never);\n\n // Through `poseOf`, not the document pose: a derived node has to be tested\n // where the renderer draws its dependencies, which is what an override says.\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const depOf = (id: NodeId): DerivedDep<TPose> | undefined => {\n const stored = scene.get(id);\n if (stored === undefined) return undefined;\n const dep = asShown(stored);\n return {\n node: dep as Node<unknown, string, TPose>,\n pose: poseOf(dep as never),\n get path(): Path | null {\n return resolveDerivedPath(dep as never, depOf, childrenOf);\n },\n };\n };\n\n const derivedPathOf = getPose\n ? (node: PickCandidate<TPose>): Path | null => {\n const n = node as unknown as Node<TData, TLayer, TPose>;\n return n.dependsOn === undefined ? null : resolveDerivedPath(n, depOf, childrenOf);\n }\n : sceneShownPathOf(scene);\n\n const planeOf = opts.camera ? scenePlaneOf(scene.layers, opts.camera) : null;\n\n // The index boxes nodes at the scene's painted poses; a custom `getPose` must be shown to agree first.\n // A partial stand-in has no versions for it to key on.\n const index = getPose || typeof scene.getVersion !== 'function' ? undefined : opts.index;\n return {\n ...(planeOf ? { planeOf } : {}),\n ...(index ? {\n candidates: (broadFor: (layer: string | undefined) => PickBroad | null | undefined) => {\n const found = indexedCandidates(scene as never, index.descriptor, broadFor) as readonly PickCandidate<TPose>[] | null;\n return found === null ? null : allAsShown(found);\n },\n } : {}),\n order: () => allAsShown(scene.renderOrderNodes() as unknown as readonly PickCandidate<TPose>[]),\n derivedPathOf,\n poseOf,\n parentsOf: sceneParentsOf(scene),\n // Absent entirely when nothing is faded, so the walk skips the gate.\n ...(readAlpha ? { alphaOf: readAlpha } : {}),\n isLocked: (node: PickCandidate<TPose>) => gates.locked(node.id),\n isHidden: (node: PickCandidate<TPose>) => gates.hidden(node.id),\n ...(annotations.size > 0 || layerIsPainted\n ? {\n layerIsPainted: (layer: string) =>\n !annotations.has(layer) && (layerIsPainted === undefined || layerIsPainted(layer)),\n }\n : {}),\n };\n}\n\n/** Each parallax layer's plane map under `camera`, or null when no layer is a\n * plane — which keeps the per-layer lookup off the walk entirely. */\nexport function scenePlaneOf(\n layers: readonly { id: string; parallax?: ParallaxOpts }[] | undefined,\n camera: View,\n): ((layer: string) => PlaneMap | null) | null {\n let maps: Map<string, PlaneMap> | null = null;\n for (const l of layers ?? []) {\n if (l.parallax) (maps ??= new Map()).set(l.id, planeMap(camera, l.parallax));\n }\n if (maps === null) return null;\n const found = maps;\n return (layer) => found.get(layer) ?? null;\n}\n"],"mappings":";;;;;;;;;;;AAqCA,SAAgB,gBACd,OACA,OAAsC,CAAC,GACpB;CAEnB,MAAM,QAAQ,UAAU,KAAc;CACtC,MAAM,aAAa,OAAO,MAAM,WAAW,QAAQ;CAEnD,MAAM,WAAqD,SACxD,MAAM,YAAY,YAAY,MAAM,WAAW,IAA0B,IAAI;CAChF,MAAM,cAAwD,UAC3D,aAAa,aAAa,MAAM,WAAW,KAAwC,IAAI,MAAM,IAAI,OAAO;CAC3G,MAAM,cAAc,mBAAmB,MAAM,MAAM;CACnD,MAAM,EAAE,SAAS,SAAS,mBAAmB;CAI7C,IAAI,QAA4B;CAChC,IAAI,CAAC,WAAW,YACd,KAAK,MAAM,MAAM,MAAM,UAAU,IAAI,GAAG;EACtC,MAAM,IAAI,MAAM,UAAU,KAAK,EAAE,CAAC,EAAE;EACpC,IAAI,MAAM,UAAa,KAAK,GAAG,CAAC,0BAAU,IAAI,IAAI,EAAA,CAAG,IAAI,EAAE;CAC7D;CAEF,MAAM,YAAY,YACZ,aACC,SAAS,OAAgB,MAAM,IAAI,EAAE,IAAI,IAAI,IAAK,UAClD,OAAe,MAAM,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC,EAAE,SAAS;CAInE,MAAM,gBAAgB,mBAAmB,KAAK;CAC9C,MAAM,SAAS,WACV,SAA+B,QAAQ,KAAK,EAAE,KAC9C,SAA+B,cAAc,IAAa;CAI/D,MAAM,cAAc,OAAkC,MAAM,WAAW,EAAE;CACzE,MAAM,SAAS,OAA8C;EAC3D,MAAM,SAAS,MAAM,IAAI,EAAE;EAC3B,IAAI,WAAW,QAAW,OAAO;EACjC,MAAM,MAAM,QAAQ,MAAM;EAC1B,OAAO;GACL,MAAM;GACN,MAAM,OAAO,GAAY;GACzB,IAAI,OAAoB;IACtB,OAAO,mBAAmB,KAAc,OAAO,UAAU;GAC3D;EACF;CACF;CAEA,MAAM,gBAAgB,WACjB,SAA4C;EAC7C,MAAM,IAAI;EACV,OAAO,EAAE,cAAc,SAAY,OAAO,mBAAmB,GAAG,OAAO,UAAU;CACnF,IACE,iBAAiB,KAAK;CAE1B,MAAM,UAAU,KAAK,SAAS,aAAa,MAAM,QAAQ,KAAK,MAAM,IAAI;CAIxE,MAAM,QAAQ,WAAW,OAAO,MAAM,eAAe,aAAa,SAAY,KAAK;CACnF,OAAO;EACL,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAI,QAAQ,EACV,aAAa,aAA0E;GACrF,MAAM,QAAQ,kBAAkB,OAAgB,MAAM,YAAY,QAAQ;GAC1E,OAAO,UAAU,OAAO,OAAO,WAAW,KAAK;EACjD,EACF,IAAI,CAAC;EACL,aAAa,WAAW,MAAM,iBAAiB,CAA+C;EAC9F;EACA;EACA,WAAW,eAAe,KAAK;EAE/B,GAAI,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC;EAC1C,WAAW,SAA+B,MAAM,OAAO,KAAK,EAAE;EAC9D,WAAW,SAA+B,MAAM,OAAO,KAAK,EAAE;EAC9D,GAAI,YAAY,OAAO,KAAK,iBACxB,EACA,iBAAiB,UACf,CAAC,YAAY,IAAI,KAAK,MAAM,mBAAmB,UAAa,eAAe,KAAK,GACpF,IACE,CAAC;CACP;AACF;;;AAIA,SAAgB,aACd,QACA,QAC6C;CAC7C,IAAI,OAAqC;CACzC,KAAK,MAAM,KAAK,UAAU,CAAC,GACzB,IAAI,EAAE,UAAU,CAAC,yBAAS,IAAI,IAAI,EAAA,CAAG,IAAI,EAAE,IAAI,SAAS,QAAQ,EAAE,QAAQ,CAAC;CAE7E,IAAI,SAAS,MAAM,OAAO;CAC1B,MAAM,QAAQ;CACd,QAAQ,UAAU,MAAM,IAAI,KAAK,KAAK;AACxC"}
@@ -1,6 +1,8 @@
1
- import { withInheritedPaints } from "./inheritedPaints.js";
1
+ import { nodeAsShown, shownLookup } from "../core/scene/nodeAsShown.js";
2
+ import { withInheritedPaintBounds, withInheritedPaints } from "./inheritedPaints.js";
2
3
  import { sceneDerivedPathOf, sceneShownToPose, withDerivedPaths } from "./derivedPath.js";
3
4
  import { withColorOverrides } from "./colorOverrides.js";
5
+ import { withShownNode } from "./shownNodes.js";
4
6
 
5
7
  //#region src/canvas/sceneSlotWiring.ts
6
8
  /**
@@ -16,13 +18,16 @@ function composeAlphaFor(scene, alphaFor) {
16
18
  return alphaFor ? (id) => alphaFor(id) * overrideAlphaFor(id) : overrideAlphaFor;
17
19
  }
18
20
  function wireSceneSlotToScene(slot, scene, alphaFor, colorOverrides) {
19
- const derived = withDerivedPaths(scene, withInheritedPaints(scene, slot.drawOne), slot.toPose);
21
+ const shown = shownLookup(scene);
22
+ const derived = withDerivedPaths(scene, withInheritedPaints(shown, slot.drawOne), slot.toPose);
23
+ const painted = colorOverrides ? withColorOverrides(colorOverrides, derived) : derived;
20
24
  const derivedPathOf = sceneDerivedPathOf(scene, slot.toPose);
21
25
  return {
22
26
  ...slot,
23
27
  ...slot.toPose ? { toPose: sceneShownToPose(scene, slot.toPose) } : {},
24
- drawOne: colorOverrides ? withColorOverrides(colorOverrides, derived) : derived,
25
- derivedPathOf,
28
+ ...slot.paintBounds ? { paintBounds: withShownNode(scene.overrides, withInheritedPaintBounds(shown, slot.paintBounds)) } : {},
29
+ drawOne: withShownNode(scene.overrides, painted),
30
+ derivedPathOf: (node) => derivedPathOf(nodeAsShown(scene.overrides, node)),
26
31
  alphaFor: composeAlphaFor(scene, alphaFor)
27
32
  };
28
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sceneSlotWiring.js","names":[],"sources":["../../src/canvas/sceneSlotWiring.ts"],"sourcesContent":["/**\n * What the scene slot needs from the `Scene` that `<Canvas>` cannot see.\n *\n * `<Canvas>` is scene-agnostic: it walks an adapter. Four things a node is\n * painted with are therefore invisible to it — an ephemeral override's alpha, a\n * derived path, which needs other nodes' poses to resolve, the paint an\n * `'inherit'` field takes from its containers, and animated vertex colors,\n * which live in an animator. `<SceneCanvas>` folds them into the slot\n * here; `buildSceneViewCommands` does the same for the headless walk.\n */\nimport type { SceneSlotConfig } from './buildSceneLayer';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf, sceneShownToPose } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\n\n/**\n * The alpha a view paints a node at: the view's own `alphaFor` times any\n * ephemeral override alpha.\n *\n * The hit path asks this too — a node painted at alpha 0 is not on screen and\n * must not claim clicks — so it is one function rather than the painter's and\n * picking's separate readings of the same two inputs.\n */\nexport function composeAlphaFor<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n alphaFor?: (id: string) => number,\n): (id: string) => number {\n const overrideAlphaFor = (id: string) => scene.overrides.read(id as NodeId)?.alpha ?? 1;\n return alphaFor ? (id: string) => alphaFor(id) * overrideAlphaFor(id) : overrideAlphaFor;\n}\n\nexport function wireSceneSlotToScene<TData, TLayer extends string, TPose>(\n slot: SceneSlotConfig<Node<TData, TLayer, TPose>, TPose>,\n scene: Scene<TData, TLayer, TPose>,\n alphaFor?: (id: string) => number,\n colorOverrides?: ColorOverrideRegistry,\n): SceneSlotConfig<Node<TData, TLayer, TPose>, TPose> {\n const derived = withDerivedPaths(scene, withInheritedPaints(scene, slot.drawOne), slot.toPose);\n const derivedPathOf = sceneDerivedPathOf(scene, slot.toPose);\n return {\n ...slot,\n ...(slot.toPose ? { toPose: sceneShownToPose(scene, slot.toPose) } : {}),\n drawOne: colorOverrides ? withColorOverrides(colorOverrides, derived) : derived,\n derivedPathOf,\n alphaFor: composeAlphaFor(scene, alphaFor),\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAyBA,SAAgB,gBACd,OACA,UACwB;CACxB,MAAM,oBAAoB,OAAe,MAAM,UAAU,KAAK,EAAY,CAAC,EAAE,SAAS;CACtF,OAAO,YAAY,OAAe,SAAS,EAAE,IAAI,iBAAiB,EAAE,IAAI;AAC1E;AAEA,SAAgB,qBACd,MACA,OACA,UACA,gBACoD;CACpD,MAAM,UAAU,iBAAiB,OAAO,oBAAoB,OAAO,KAAK,OAAO,GAAG,KAAK,MAAM;CAC7F,MAAM,gBAAgB,mBAAmB,OAAO,KAAK,MAAM;CAC3D,OAAO;EACL,GAAG;EACH,GAAI,KAAK,SAAS,EAAE,QAAQ,iBAAiB,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC;EACtE,SAAS,iBAAiB,mBAAmB,gBAAgB,OAAO,IAAI;EACxE;EACA,UAAU,gBAAgB,OAAO,QAAQ;CAC3C;AACF"}
1
+ {"version":3,"file":"sceneSlotWiring.js","names":[],"sources":["../../src/canvas/sceneSlotWiring.ts"],"sourcesContent":["/**\n * What the scene slot needs from the `Scene` that `<Canvas>` cannot see.\n *\n * `<Canvas>` is scene-agnostic: it walks an adapter. Five things a node is\n * painted with are therefore invisible to it — an ephemeral override's alpha\n * and data, a derived path, which needs other nodes' poses to resolve, the\n * paint an `'inherit'` field takes from its containers, and animated vertex\n * colors, which live in an animator. `<SceneCanvas>` folds them into the slot\n * here; `buildSceneViewCommands` does the same for the headless walk.\n */\nimport type { SceneSlotConfig } from './buildSceneLayer';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport { withInheritedPaintBounds, withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf, sceneShownToPose } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport { nodeAsShown, shownLookup } from 'core/scene/nodeAsShown';\nimport { withShownNode } from './shownNodes';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\n\n/**\n * The alpha a view paints a node at: the view's own `alphaFor` times any\n * ephemeral override alpha.\n *\n * The hit path asks this too — a node painted at alpha 0 is not on screen and\n * must not claim clicks — so it is one function rather than the painter's and\n * picking's separate readings of the same two inputs.\n */\nexport function composeAlphaFor<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n alphaFor?: (id: string) => number,\n): (id: string) => number {\n const overrideAlphaFor = (id: string) => scene.overrides.read(id as NodeId)?.alpha ?? 1;\n return alphaFor ? (id: string) => alphaFor(id) * overrideAlphaFor(id) : overrideAlphaFor;\n}\n\nexport function wireSceneSlotToScene<TData, TLayer extends string, TPose>(\n slot: SceneSlotConfig<Node<TData, TLayer, TPose>, TPose>,\n scene: Scene<TData, TLayer, TPose>,\n alphaFor?: (id: string) => number,\n colorOverrides?: ColorOverrideRegistry,\n): SceneSlotConfig<Node<TData, TLayer, TPose>, TPose> {\n const shown = shownLookup(scene);\n const derived = withDerivedPaths(scene, withInheritedPaints(shown, slot.drawOne), slot.toPose);\n const painted = colorOverrides ? withColorOverrides(colorOverrides, derived) : derived;\n const derivedPathOf = sceneDerivedPathOf(scene, slot.toPose);\n return {\n ...slot,\n ...(slot.toPose ? { toPose: sceneShownToPose(scene, slot.toPose) } : {}),\n ...(slot.paintBounds\n ? { paintBounds: withShownNode(scene.overrides, withInheritedPaintBounds(shown, slot.paintBounds)) }\n : {}),\n drawOne: withShownNode(scene.overrides, painted),\n derivedPathOf: (node) => derivedPathOf(nodeAsShown(scene.overrides, node)),\n alphaFor: composeAlphaFor(scene, alphaFor),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,SAAgB,gBACd,OACA,UACwB;CACxB,MAAM,oBAAoB,OAAe,MAAM,UAAU,KAAK,EAAY,CAAC,EAAE,SAAS;CACtF,OAAO,YAAY,OAAe,SAAS,EAAE,IAAI,iBAAiB,EAAE,IAAI;AAC1E;AAEA,SAAgB,qBACd,MACA,OACA,UACA,gBACoD;CACpD,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,UAAU,iBAAiB,OAAO,oBAAoB,OAAO,KAAK,OAAO,GAAG,KAAK,MAAM;CAC7F,MAAM,UAAU,iBAAiB,mBAAmB,gBAAgB,OAAO,IAAI;CAC/E,MAAM,gBAAgB,mBAAmB,OAAO,KAAK,MAAM;CAC3D,OAAO;EACL,GAAG;EACH,GAAI,KAAK,SAAS,EAAE,QAAQ,iBAAiB,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC;EACtE,GAAI,KAAK,cACL,EAAE,aAAa,cAAc,MAAM,WAAW,yBAAyB,OAAO,KAAK,WAAW,CAAC,EAAE,IACjG,CAAC;EACL,SAAS,cAAc,MAAM,WAAW,OAAO;EAC/C,gBAAgB,SAAS,cAAc,YAAY,MAAM,WAAW,IAAI,CAAC;EACzE,UAAU,gBAAgB,OAAO,QAAQ;CAC3C;AACF"}
@@ -1,6 +1,7 @@
1
- import { withInheritedPaints } from "./inheritedPaints.js";
1
+ import { nodeAsShown, shownLookup } from "../core/scene/nodeAsShown.js";
2
+ import { withInheritedPaintBounds, withInheritedPaints } from "./inheritedPaints.js";
2
3
  import { paintedSceneLayers } from "./sceneLayerPaint.js";
3
- import { paintCanvas } from "./canvasRenderer.js";
4
+ import { paintCanvas, resolveDpr } from "./canvasRenderer.js";
4
5
  import { wrapNodeOutput } from "./wrapNodeOutput.js";
5
6
  import { sceneDerivedPathOf, withDerivedPaths } from "./derivedPath.js";
6
7
  import { buildSceneTree } from "./buildSceneTree.js";
@@ -8,6 +9,7 @@ import { sceneToAdapter } from "./sceneAdapter.js";
8
9
  import { withColorOverrides } from "./colorOverrides.js";
9
10
  import { paintMissesView } from "./paintCull.js";
10
11
  import { paintBoundsFor } from "./defaultDrawOne.js";
12
+ import { withShownNode } from "./shownNodes.js";
11
13
  import { cullDrawCommands } from "@weasel-js/renderer";
12
14
  import { viewToMat3 as viewToMat3$1 } from "@weasel-js/geom";
13
15
 
@@ -44,12 +46,14 @@ function sceneAsHierarchy(scene, layers = {}) {
44
46
  * `layers.layerOrder`), each holding one group per node on that layer.
45
47
  */
46
48
  function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, layers, colorOverrides, cull) {
47
- const derivedOnly = withDerivedPaths(scene, withInheritedPaints(scene, drawOne));
49
+ const shown = shownLookup(scene);
50
+ const derivedOnly = withDerivedPaths(scene, withInheritedPaints(shown, drawOne));
48
51
  const derived = colorOverrides ? withColorOverrides(colorOverrides, derivedOnly) : derivedOnly;
49
- const derivedPathOf = sceneDerivedPathOf(scene);
52
+ const derivedPathAt = sceneDerivedPathOf(scene);
53
+ const derivedPathOf = (node) => derivedPathAt(nodeAsShown(scene.overrides, node));
50
54
  const wrappedDrawOne = (node, pose, v) => {
51
55
  const overrideAlpha = scene.overrides.read(node.id)?.alpha ?? 1;
52
- return wrapNodeOutput(derived(node, pose, v), pose, (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha);
56
+ return wrapNodeOutput(derived(nodeAsShown(scene.overrides, node), pose, v), pose, (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha);
53
57
  };
54
58
  const hierarchy = sceneAsHierarchy(scene, layers);
55
59
  const cullRect = cull ? {
@@ -58,9 +62,9 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, l
58
62
  width: cull.width,
59
63
  height: cull.height
60
64
  } : void 0;
61
- const culled = cullRect && cull?.paintBounds ? paintMissesView(cull.paintBounds, view, cullRect) : void 0;
65
+ const culled = cullRect && cull?.paintBounds ? paintMissesView(withShownNode(scene.overrides, withInheritedPaintBounds(shown, cull.paintBounds)), view, cullRect) : void 0;
62
66
  const tree = buildSceneTree(hierarchy, wrappedDrawOne, view, void 0, derivedPathOf, culled);
63
- const children = cullRect ? cullDrawCommands(tree, viewToMat3$1(view), cullRect) : tree;
67
+ const children = cull && cullRect ? cullDrawCommands(tree, viewToMat3$1(view), cullRect, cull.dpr) : tree;
64
68
  if (extraCommands && extraCommands.length > 0) for (const cmd of extraCommands) children.push(cmd);
65
69
  return [{
66
70
  kind: "group",
@@ -74,13 +78,14 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, l
74
78
  * renderer lease.
75
79
  */
76
80
  function sceneViewFrame(args) {
77
- const { scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder, colorOverrides, cull } = args;
81
+ const { scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder, colorOverrides, cull, dpr } = args;
78
82
  return buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, {
79
83
  layerVisibility,
80
84
  layerOrder
81
85
  }, colorOverrides, cull ? {
82
86
  width,
83
87
  height,
88
+ dpr: resolveDpr(dpr),
84
89
  paintBounds: args.paintBounds ?? paintBoundsFor(drawOne)
85
90
  } : void 0);
86
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sceneViewRender.js","names":["viewToMat3"],"sources":["../../src/canvas/sceneViewRender.ts"],"sourcesContent":["/**\n * `renderSceneToCanvas` — substrate for detached, read-only scene views\n * (`<SceneViewCanvas>` / `<MinimapCanvas>`). Given a `<canvas>`, a `Scene`,\n * a `View`, and a per-node `drawOne`, render one frame.\n */\n\nimport type { WeaselRenderer } from '@weasel-js/renderer';\nimport { cachedCanvasRenderer, paintCanvas } from './canvasRenderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport type { Node, NodeId, Scene } from '../core/scene/types';\nimport { wrapNodeOutput } from './wrapNodeOutput';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { sceneToAdapter } from './sceneAdapter';\nimport { withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport { paintedSceneLayers } from './sceneLayerPaint';\nimport { cullDrawCommands } from '@weasel-js/renderer';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { paintBoundsFor } from './defaultDrawOne';\n\nexport type { SceneViewDrawOne } from './NodeShape';\n\n/**\n * Which scene layers a detached view paints, on top of each layer's own\n * `visible` flag. Keyed the way `<SceneCanvas>` keys its scene layers —\n * `scene:<layerId>` — so the map a main canvas takes serves its minimap too.\n */\nexport interface SceneViewLayers {\n /** `false` hides that layer in this view. It cannot show one the scene hides. */\n layerVisibility?: Record<string, boolean>;\n /** Paint order, bottom first. A listed order is the whole list: a scene\n * layer left out of it is not painted. */\n layerOrder?: readonly string[];\n}\n\n/** The view size `buildSceneViewCommands` culls to, and how it bounds a\n * node's paint before painting it. */\nexport interface SceneViewCull<TData, TLayer extends string, TPose> {\n width: number;\n height: number;\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n}\n\n/** What to draw into an existing canvas: the scene, the view, and the same\n * painting hooks `<SceneCanvas>` takes. */\nexport interface RenderSceneToCanvasArgs<TData, TLayer extends string, TPose> {\n /** The DOM canvas to paint into. The first call on a given canvas creates\n * its renderer and later calls reuse it;\n * `releaseCanvasRenderer(canvas)` frees it. */\n canvas: HTMLCanvasElement;\n scene: Scene<TData, TLayer, TPose>;\n /** Camera view applied to scene and `extraCommands` alike. */\n view: View;\n /** CSS-pixel size. The drawing buffer is set to `width × dpr` and\n * `height × dpr`; the element's CSS size is the caller's to set. */\n width: number;\n height: number;\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>;\n /** Commands appended after the scene-node output, wrapped in the same view\n * transform as scene commands. */\n extraCommands?: DrawCommand[];\n /** Per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot\n * `alphaFor`. Defaults to `() => 1`. */\n alphaFor?: (id: string) => number;\n /** Per-view layer visibility. See {@link SceneViewLayers}. */\n layerVisibility?: SceneViewLayers['layerVisibility'];\n /** Per-view layer order. See {@link SceneViewLayers}. */\n layerOrder?: SceneViewLayers['layerOrder'];\n /** Animated vertex colors to paint, typically an animator's\n * `colorOverrides` — see `NodePaintCtx.vertexColors`. */\n colorOverrides?: ColorOverrideRegistry;\n /** Skip what cannot reach the `width × height` view: nodes `paintBounds`\n * places outside it are not painted, and scene commands that cannot reach\n * it are dropped before the renderer — the scene slot's `cull`. Off by\n * default. `extraCommands` are never culled. */\n cull?: boolean;\n /** Where `drawOne`'s output for a node can reach, as on the scene slot.\n * Defaults to `defaultPaintBounds` when `drawOne` is `defaultDrawOne`. */\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio || 1` when\n * available, otherwise 1. */\n dpr?: number;\n}\n\n/** Test seam: the renderer `renderSceneToCanvas` created for a canvas. Not\n * re-exported from the barrel. */\nexport function __getCachedRendererForTest(\n canvas: HTMLCanvasElement,\n): WeaselRenderer | undefined {\n return cachedCanvasRenderer(canvas);\n}\n\n/**\n * Present a `Scene` as the reading surface `buildSceneTree` walks: the scene\n * adapter's own reads, under this view's layer list.\n */\nfunction sceneAsHierarchy<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n layers: SceneViewLayers = {},\n): HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose> {\n const { getNode, getChildren, getPose, composePose } = sceneToAdapter(scene);\n return {\n getLayers: () =>\n paintedSceneLayers(scene.layers, layers)\n .map((l) => ({ id: l.id, visible: l.visible, parallax: l.parallax })),\n getNode,\n getChildren,\n getPose,\n ...(composePose ? { composePose } : {}),\n };\n}\n\n/**\n * Build the DrawCommand list that `renderSceneToCanvas` would dispatch, in\n * screen space. Pure — no GL, no DOM. Walks the scene through `buildSceneTree`,\n * the same walk `<SceneCanvas>` uses, which is what keeps hidden layers\n * unpainted and container clips applied here.\n *\n * Each node's commands go through `wrapNodeOutput` — pose rotation, then the\n * `alphaFor` multiplier — so a `drawOne` that rotates its own output rotates\n * twice; emit unrotated geometry and let the pose drive it.\n *\n * Output is one group per **visible** layer, in scene-layer order (or\n * `layers.layerOrder`), each holding one group per node on that layer.\n */\nexport function buildSceneViewCommands<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n view: View,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n extraCommands?: ReadonlyArray<DrawCommand>,\n alphaFor?: (id: string) => number,\n /** This view's own layer visibility and order. */\n layers?: SceneViewLayers,\n /** Animated vertex colors to paint, typically an animator's `colorOverrides`. */\n colorOverrides?: ColorOverrideRegistry,\n /** Cull the scene to this CSS-pixel view size — see\n * `RenderSceneToCanvasArgs.cull`. Omit to paint every node. */\n cull?: SceneViewCull<TData, TLayer, TPose>,\n): DrawCommand[] {\n const derivedOnly = withDerivedPaths(scene, withInheritedPaints(scene, drawOne));\n const derived = colorOverrides ? withColorOverrides(colorOverrides, derivedOnly) : derivedOnly;\n const derivedPathOf = sceneDerivedPathOf(scene);\n const wrappedDrawOne = (\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n v: View,\n ): DrawCommand[] => {\n const overrideAlpha = scene.overrides.read(node.id as NodeId)?.alpha ?? 1;\n return wrapNodeOutput(\n derived(node, pose, v),\n pose,\n (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha,\n );\n };\n\n const hierarchy = sceneAsHierarchy(scene, layers);\n\n const cullRect = cull ? { x: 0, y: 0, width: cull.width, height: cull.height } : undefined;\n const culled = cullRect && cull?.paintBounds ? paintMissesView(cull.paintBounds, view, cullRect) : undefined;\n const tree: DrawCommand[] = buildSceneTree(\n hierarchy as Parameters<typeof buildSceneTree>[0],\n wrappedDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n undefined,\n derivedPathOf as unknown as Parameters<typeof buildSceneTree>[4],\n culled as Parameters<typeof buildSceneTree>[5],\n );\n const children = cullRect ? cullDrawCommands(tree, viewToMat3(view), cullRect) : tree;\n if (extraCommands && extraCommands.length > 0) {\n for (const cmd of extraCommands) children.push(cmd);\n }\n return [{\n kind: 'group',\n transform: viewToMat3(view),\n children,\n }];\n}\n\n/**\n * The commands `renderSceneToCanvas` paints for one frame — everything in\n * `args` but the canvas. `<SceneViewCanvas>` paints these through its own\n * renderer lease.\n */\nexport function sceneViewFrame<TData, TLayer extends string, TPose>(\n args: Omit<RenderSceneToCanvasArgs<TData, TLayer, TPose>, 'canvas'>,\n): DrawCommand[] {\n const {\n scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder,\n colorOverrides, cull,\n } = args;\n return buildSceneViewCommands(\n scene, view, drawOne, extraCommands, alphaFor, { layerVisibility, layerOrder }, colorOverrides,\n cull ? { width, height, paintBounds: args.paintBounds ?? paintBoundsFor(drawOne) } : undefined,\n );\n}\n\n/**\n * Render one frame of `scene` at `view` into `canvas`. Where no WebGL2\n * context is available (jsdom) the call paints nothing. The canvas's renderer\n * lives until `releaseCanvasRenderer(canvas)`.\n */\nexport function renderSceneToCanvas<TData, TLayer extends string, TPose>(\n args: RenderSceneToCanvasArgs<TData, TLayer, TPose>,\n): void {\n const { canvas, view, width, height, dpr } = args;\n paintCanvas(canvas, sceneViewFrame(args), view, { width, height, dpr });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqGA,SAAS,iBACP,OACA,SAA0B,CAAC,GAC6B;CACxD,MAAM,EAAE,SAAS,aAAa,SAAS,gBAAgB,eAAe,KAAK;CAC3E,OAAO;EACL,iBACE,mBAAmB,MAAM,QAAQ,MAAM,CAAC,CACrC,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE;GAAS,UAAU,EAAE;EAAS,EAAE;EACxE;EACA;EACA;EACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;CACvC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,OACA,MACA,SACA,eACA,UAEA,QAEA,gBAGA,MACe;CACf,MAAM,cAAc,iBAAiB,OAAO,oBAAoB,OAAO,OAAO,CAAC;CAC/E,MAAM,UAAU,iBAAiB,mBAAmB,gBAAgB,WAAW,IAAI;CACnF,MAAM,gBAAgB,mBAAmB,KAAK;CAC9C,MAAM,kBACJ,MACA,MACA,MACkB;EAClB,MAAM,gBAAgB,MAAM,UAAU,KAAK,KAAK,EAAY,CAAC,EAAE,SAAS;EACxE,OAAO,eACL,QAAQ,MAAM,MAAM,CAAC,GACrB,OACC,WAAW,SAAS,KAAK,EAAE,IAAI,KAAK,aACvC;CACF;CAEA,MAAM,YAAY,iBAAiB,OAAO,MAAM;CAEhD,MAAM,WAAW,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,KAAK;EAAO,QAAQ,KAAK;CAAO,IAAI;CACjF,MAAM,SAAS,YAAY,MAAM,cAAc,gBAAgB,KAAK,aAAa,MAAM,QAAQ,IAAI;CACnG,MAAM,OAAsB,eAC1B,WACA,gBACA,MACA,QACA,eACA,MACF;CACA,MAAM,WAAW,WAAW,iBAAiB,MAAMA,aAAW,IAAI,GAAG,QAAQ,IAAI;CACjF,IAAI,iBAAiB,cAAc,SAAS,GAC1C,KAAK,MAAM,OAAO,eAAe,SAAS,KAAK,GAAG;CAEpD,OAAO,CAAC;EACN,MAAM;EACN,WAAWA,aAAW,IAAI;EAC1B;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,eACd,MACe;CACf,MAAM,EACJ,OAAO,MAAM,OAAO,QAAQ,SAAS,eAAe,UAAU,iBAAiB,YAC/E,gBAAgB,SACd;CACJ,OAAO,uBACL,OAAO,MAAM,SAAS,eAAe,UAAU;EAAE;EAAiB;CAAW,GAAG,gBAChF,OAAO;EAAE;EAAO;EAAQ,aAAa,KAAK,eAAe,eAAe,OAAO;CAAE,IAAI,MACvF;AACF;;;;;;AAOA,SAAgB,oBACd,MACM;CACN,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ,QAAQ;CAC7C,YAAY,QAAQ,eAAe,IAAI,GAAG,MAAM;EAAE;EAAO;EAAQ;CAAI,CAAC;AACxE"}
1
+ {"version":3,"file":"sceneViewRender.js","names":["viewToMat3"],"sources":["../../src/canvas/sceneViewRender.ts"],"sourcesContent":["/**\n * `renderSceneToCanvas` — substrate for detached, read-only scene views\n * (`<SceneViewCanvas>` / `<MinimapCanvas>`). Given a `<canvas>`, a `Scene`,\n * a `View`, and a per-node `drawOne`, render one frame.\n */\n\nimport type { WeaselRenderer } from '@weasel-js/renderer';\nimport { cachedCanvasRenderer, paintCanvas, resolveDpr } from './canvasRenderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport type { Node, NodeId, Scene } from '../core/scene/types';\nimport { wrapNodeOutput } from './wrapNodeOutput';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { sceneToAdapter } from './sceneAdapter';\nimport { withInheritedPaintBounds, withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport { paintedSceneLayers } from './sceneLayerPaint';\nimport { cullDrawCommands } from '@weasel-js/renderer';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { paintBoundsFor } from './defaultDrawOne';\nimport { nodeAsShown, shownLookup } from 'core/scene/nodeAsShown';\nimport { withShownNode } from './shownNodes';\n\nexport type { SceneViewDrawOne } from './NodeShape';\n\n/**\n * Which scene layers a detached view paints, on top of each layer's own\n * `visible` flag. Keyed the way `<SceneCanvas>` keys its scene layers —\n * `scene:<layerId>` — so the map a main canvas takes serves its minimap too.\n */\nexport interface SceneViewLayers {\n /** `false` hides that layer in this view. It cannot show one the scene hides. */\n layerVisibility?: Record<string, boolean>;\n /** Paint order, bottom first. A listed order is the whole list: a scene\n * layer left out of it is not painted. */\n layerOrder?: readonly string[];\n}\n\n/** The view size `buildSceneViewCommands` culls to, the density it will be\n * painted at, and how it bounds a node's paint before painting it. */\nexport interface SceneViewCull<TData, TLayer extends string, TPose> {\n width: number;\n height: number;\n dpr: number;\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n}\n\n/** What to draw into an existing canvas: the scene, the view, and the same\n * painting hooks `<SceneCanvas>` takes. */\nexport interface RenderSceneToCanvasArgs<TData, TLayer extends string, TPose> {\n /** The DOM canvas to paint into. The first call on a given canvas creates\n * its renderer and later calls reuse it;\n * `releaseCanvasRenderer(canvas)` frees it. */\n canvas: HTMLCanvasElement;\n scene: Scene<TData, TLayer, TPose>;\n /** Camera view applied to scene and `extraCommands` alike. */\n view: View;\n /** CSS-pixel size. The drawing buffer is set to `width × dpr` and\n * `height × dpr`; the element's CSS size is the caller's to set. */\n width: number;\n height: number;\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>;\n /** Commands appended after the scene-node output, wrapped in the same view\n * transform as scene commands. */\n extraCommands?: DrawCommand[];\n /** Per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot\n * `alphaFor`. Defaults to `() => 1`. */\n alphaFor?: (id: string) => number;\n /** Per-view layer visibility. See {@link SceneViewLayers}. */\n layerVisibility?: SceneViewLayers['layerVisibility'];\n /** Per-view layer order. See {@link SceneViewLayers}. */\n layerOrder?: SceneViewLayers['layerOrder'];\n /** Animated vertex colors to paint, typically an animator's\n * `colorOverrides` — see `NodePaintCtx.vertexColors`. */\n colorOverrides?: ColorOverrideRegistry;\n /** Skip what cannot reach the `width × height` view: nodes `paintBounds`\n * places outside it are not painted, and scene commands that cannot reach\n * it are dropped before the renderer — the scene slot's `cull`. Off by\n * default. `extraCommands` are never culled. */\n cull?: boolean;\n /** Where `drawOne`'s output for a node can reach, as on the scene slot.\n * Defaults to `defaultPaintBounds` when `drawOne` is `defaultDrawOne`. */\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio || 1` when\n * available, otherwise 1. */\n dpr?: number;\n}\n\n/** Test seam: the renderer `renderSceneToCanvas` created for a canvas. Not\n * re-exported from the barrel. */\nexport function __getCachedRendererForTest(\n canvas: HTMLCanvasElement,\n): WeaselRenderer | undefined {\n return cachedCanvasRenderer(canvas);\n}\n\n/**\n * Present a `Scene` as the reading surface `buildSceneTree` walks: the scene\n * adapter's own reads, under this view's layer list.\n */\nfunction sceneAsHierarchy<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n layers: SceneViewLayers = {},\n): HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose> {\n const { getNode, getChildren, getPose, composePose } = sceneToAdapter(scene);\n return {\n getLayers: () =>\n paintedSceneLayers(scene.layers, layers)\n .map((l) => ({ id: l.id, visible: l.visible, parallax: l.parallax })),\n getNode,\n getChildren,\n getPose,\n ...(composePose ? { composePose } : {}),\n };\n}\n\n/**\n * Build the DrawCommand list that `renderSceneToCanvas` would dispatch, in\n * screen space. Pure — no GL, no DOM. Walks the scene through `buildSceneTree`,\n * the same walk `<SceneCanvas>` uses, which is what keeps hidden layers\n * unpainted and container clips applied here.\n *\n * Each node's commands go through `wrapNodeOutput` — pose rotation, then the\n * `alphaFor` multiplier — so a `drawOne` that rotates its own output rotates\n * twice; emit unrotated geometry and let the pose drive it.\n *\n * Output is one group per **visible** layer, in scene-layer order (or\n * `layers.layerOrder`), each holding one group per node on that layer.\n */\nexport function buildSceneViewCommands<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n view: View,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n extraCommands?: ReadonlyArray<DrawCommand>,\n alphaFor?: (id: string) => number,\n /** This view's own layer visibility and order. */\n layers?: SceneViewLayers,\n /** Animated vertex colors to paint, typically an animator's `colorOverrides`. */\n colorOverrides?: ColorOverrideRegistry,\n /** Cull the scene to this CSS-pixel view size — see\n * `RenderSceneToCanvasArgs.cull`. Omit to paint every node. */\n cull?: SceneViewCull<TData, TLayer, TPose>,\n): DrawCommand[] {\n const shown = shownLookup(scene);\n const derivedOnly = withDerivedPaths(scene, withInheritedPaints(shown, drawOne));\n const derived = colorOverrides ? withColorOverrides(colorOverrides, derivedOnly) : derivedOnly;\n const derivedPathAt = sceneDerivedPathOf(scene);\n const derivedPathOf = (node: Node<TData, TLayer, TPose>) => derivedPathAt(nodeAsShown(scene.overrides, node));\n const wrappedDrawOne = (\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n v: View,\n ): DrawCommand[] => {\n const overrideAlpha = scene.overrides.read(node.id as NodeId)?.alpha ?? 1;\n return wrapNodeOutput(\n derived(nodeAsShown(scene.overrides, node), pose, v),\n pose,\n (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha,\n );\n };\n\n const hierarchy = sceneAsHierarchy(scene, layers);\n\n const cullRect = cull ? { x: 0, y: 0, width: cull.width, height: cull.height } : undefined;\n const culled = cullRect && cull?.paintBounds\n ? paintMissesView(withShownNode(scene.overrides, withInheritedPaintBounds(shown, cull.paintBounds)), view, cullRect)\n : undefined;\n const tree: DrawCommand[] = buildSceneTree(\n hierarchy as Parameters<typeof buildSceneTree>[0],\n wrappedDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n undefined,\n derivedPathOf as unknown as Parameters<typeof buildSceneTree>[4],\n culled as Parameters<typeof buildSceneTree>[5],\n );\n const children = cull && cullRect ? cullDrawCommands(tree, viewToMat3(view), cullRect, cull.dpr) : tree;\n if (extraCommands && extraCommands.length > 0) {\n for (const cmd of extraCommands) children.push(cmd);\n }\n return [{\n kind: 'group',\n transform: viewToMat3(view),\n children,\n }];\n}\n\n/**\n * The commands `renderSceneToCanvas` paints for one frame — everything in\n * `args` but the canvas. `<SceneViewCanvas>` paints these through its own\n * renderer lease.\n */\nexport function sceneViewFrame<TData, TLayer extends string, TPose>(\n args: Omit<RenderSceneToCanvasArgs<TData, TLayer, TPose>, 'canvas'>,\n): DrawCommand[] {\n const {\n scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder,\n colorOverrides, cull, dpr,\n } = args;\n return buildSceneViewCommands(\n scene, view, drawOne, extraCommands, alphaFor, { layerVisibility, layerOrder }, colorOverrides,\n cull ? { width, height, dpr: resolveDpr(dpr), paintBounds: args.paintBounds ?? paintBoundsFor(drawOne) } : undefined,\n );\n}\n\n/**\n * Render one frame of `scene` at `view` into `canvas`. Where no WebGL2\n * context is available (jsdom) the call paints nothing. The canvas's renderer\n * lives until `releaseCanvasRenderer(canvas)`.\n */\nexport function renderSceneToCanvas<TData, TLayer extends string, TPose>(\n args: RenderSceneToCanvasArgs<TData, TLayer, TPose>,\n): void {\n const { canvas, view, width, height, dpr } = args;\n paintCanvas(canvas, sceneViewFrame(args), view, { width, height, dpr });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwGA,SAAS,iBACP,OACA,SAA0B,CAAC,GAC6B;CACxD,MAAM,EAAE,SAAS,aAAa,SAAS,gBAAgB,eAAe,KAAK;CAC3E,OAAO;EACL,iBACE,mBAAmB,MAAM,QAAQ,MAAM,CAAC,CACrC,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE;GAAS,UAAU,EAAE;EAAS,EAAE;EACxE;EACA;EACA;EACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;CACvC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,OACA,MACA,SACA,eACA,UAEA,QAEA,gBAGA,MACe;CACf,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,cAAc,iBAAiB,OAAO,oBAAoB,OAAO,OAAO,CAAC;CAC/E,MAAM,UAAU,iBAAiB,mBAAmB,gBAAgB,WAAW,IAAI;CACnF,MAAM,gBAAgB,mBAAmB,KAAK;CAC9C,MAAM,iBAAiB,SAAqC,cAAc,YAAY,MAAM,WAAW,IAAI,CAAC;CAC5G,MAAM,kBACJ,MACA,MACA,MACkB;EAClB,MAAM,gBAAgB,MAAM,UAAU,KAAK,KAAK,EAAY,CAAC,EAAE,SAAS;EACxE,OAAO,eACL,QAAQ,YAAY,MAAM,WAAW,IAAI,GAAG,MAAM,CAAC,GACnD,OACC,WAAW,SAAS,KAAK,EAAE,IAAI,KAAK,aACvC;CACF;CAEA,MAAM,YAAY,iBAAiB,OAAO,MAAM;CAEhD,MAAM,WAAW,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,KAAK;EAAO,QAAQ,KAAK;CAAO,IAAI;CACjF,MAAM,SAAS,YAAY,MAAM,cAC7B,gBAAgB,cAAc,MAAM,WAAW,yBAAyB,OAAO,KAAK,WAAW,CAAC,GAAG,MAAM,QAAQ,IACjH;CACJ,MAAM,OAAsB,eAC1B,WACA,gBACA,MACA,QACA,eACA,MACF;CACA,MAAM,WAAW,QAAQ,WAAW,iBAAiB,MAAMA,aAAW,IAAI,GAAG,UAAU,KAAK,GAAG,IAAI;CACnG,IAAI,iBAAiB,cAAc,SAAS,GAC1C,KAAK,MAAM,OAAO,eAAe,SAAS,KAAK,GAAG;CAEpD,OAAO,CAAC;EACN,MAAM;EACN,WAAWA,aAAW,IAAI;EAC1B;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,eACd,MACe;CACf,MAAM,EACJ,OAAO,MAAM,OAAO,QAAQ,SAAS,eAAe,UAAU,iBAAiB,YAC/E,gBAAgB,MAAM,QACpB;CACJ,OAAO,uBACL,OAAO,MAAM,SAAS,eAAe,UAAU;EAAE;EAAiB;CAAW,GAAG,gBAChF,OAAO;EAAE;EAAO;EAAQ,KAAK,WAAW,GAAG;EAAG,aAAa,KAAK,eAAe,eAAe,OAAO;CAAE,IAAI,MAC7G;AACF;;;;;;AAOA,SAAgB,oBACd,MACM;CACN,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ,QAAQ;CAC7C,YAAY,QAAQ,eAAe,IAAI,GAAG,MAAM;EAAE;EAAO;EAAQ;CAAI,CAAC;AACxE"}
@@ -0,0 +1,15 @@
1
+ import { nodeAsShown } from "../core/scene/nodeAsShown.js";
2
+
3
+ //#region src/canvas/shownNodes.ts
4
+ /**
5
+ * The scene walks' half of `PoseOverride.data`: handing painters and cull
6
+ * bounds each node as the scene shows it, through `nodeAsShown`.
7
+ */
8
+ /** `fn`, a painter or a cull's bounds, handed each node as it shows. */
9
+ function withShownNode(overrides, fn) {
10
+ return (node, a, b, c) => fn(nodeAsShown(overrides, node), a, b, c);
11
+ }
12
+
13
+ //#endregion
14
+ export { withShownNode };
15
+ //# sourceMappingURL=shownNodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shownNodes.js","names":[],"sources":["../../src/canvas/shownNodes.ts"],"sourcesContent":["/**\n * The scene walks' half of `PoseOverride.data`: handing painters and cull\n * bounds each node as the scene shows it, through `nodeAsShown`.\n */\nimport { nodeAsShown, type ShownDataSource } from 'core/scene/nodeAsShown';\nimport type { NodeId } from 'core/scene/types';\n\n/** `fn`, a painter or a cull's bounds, handed each node as it shows. */\nexport function withShownNode<TNode extends { id: NodeId; data?: unknown }, A, B, C, TOut>(\n overrides: ShownDataSource,\n fn: (node: TNode, a: A, b: B, c: C) => TOut,\n): (node: TNode, a: A, b: B, c: C) => TOut {\n return (node, a, b, c) => fn(nodeAsShown(overrides, node), a, b, c);\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,cACd,WACA,IACyC;CACzC,QAAQ,MAAM,GAAG,GAAG,MAAM,GAAG,YAAY,WAAW,IAAI,GAAG,GAAG,GAAG,CAAC;AACpE"}
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_LAYER_RETAIN } from "../core/layers/retain.js";
2
2
  import { drawLayers } from "../core/layers/render.js";
3
+ import { resolveDpr } from "./canvasRenderer.js";
3
4
  import { beginDebugFrame } from "../debug/useDebugSink.js";
4
5
  import { NO_LAYER_VISIBILITY } from "./useExtraLayers.js";
5
6
  import { viewToMat3 } from "@weasel-js/renderer";
@@ -59,7 +60,8 @@ function useCanvasPaint(paintRef, lease, target, frame, markPainted) {
59
60
  const spans = debugFrame?.time();
60
61
  const commands = drawLayers(paintLayers, helpersRef.current, vis ?? NO_LAYER_VISIBILITY, order, view, {
61
62
  width: w,
62
- height: h
63
+ height: h,
64
+ dpr: resolveDpr(dprIn)
63
65
  }, layerCacheRef.current, void 0, groups, spans, retainPolicy(retain));
64
66
  renderer.render(commands, viewToMat3(view), spans ? { spans } : void 0);
65
67
  debugFrame?.end(renderer.lastFrameStats());
@@ -1 +1 @@
1
- {"version":3,"file":"useCanvasPaint.js","names":[],"sources":["../../src/canvas/useCanvasPaint.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { viewToMat3 } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport {\n DEFAULT_LAYER_RETAIN, drawLayers, type LayerCommandCache, type LayerRetainPolicy, type RenderLayer,\n} from 'core/layers/render';\nimport type { CanvasDebugSink } from '../debug/types';\nimport { beginDebugFrame } from '../debug/useDebugSink';\nimport type { CanvasRendererLease } from './canvasRenderer';\nimport type { CanvasProps } from './canvasProps';\nimport type { CanvasElements } from './useCanvasElements';\nimport { NO_LAYER_VISIBILITY, type LayerGate } from './useExtraLayers';\n\n/** What a frame's paint reads that a React render owns. */\nexport interface PaintInputs extends Pick<CanvasProps<unknown>, 'dpr' | 'layerGroups' | 'shaders' | 'flattenTolerance' | 'imageMinification' | 'retain'> {\n layers: RenderLayer<unknown>[];\n width: number;\n height: number;\n debugSink: CanvasDebugSink | null;\n frameStats: boolean;\n}\n\n/** What a frame's paint reads that changes outside a render. */\nexport interface PaintFrame {\n inputsRef: { readonly current: PaintInputs };\n viewRef: { readonly current: View };\n gateRef: { readonly current: LayerGate };\n helpersRef: { readonly current: unknown };\n}\n\n/**\n * Which content version the last paint showed. `markPainted` records the current one, read\n * from `contentVersion`'s latest committed value.\n */\nexport function usePaintedVersion(contentVersion: (() => number) | undefined): {\n getPaintedVersion: () => number;\n markPainted: () => void;\n} {\n const contentVersionRef = useLatest(contentVersion);\n const paintedVersionRef = useRef(0);\n const getPaintedVersion = useCallback(() => paintedVersionRef.current, []);\n const markPainted = useCallback(() => {\n paintedVersionRef.current = contentVersionRef.current?.() ?? 0;\n }, [contentVersionRef]);\n return { getPaintedVersion, markPainted };\n}\n\nfunction retainPolicy(retain: PaintInputs['retain']): LayerRetainPolicy | false {\n if (retain === false) return false;\n const minCommands = retain?.minCommands;\n return minCommands === undefined ? DEFAULT_LAYER_RETAIN : { minCommands };\n}\n\n/**\n * Publish the canvas's paint into `paintRef`, which the frame loop calls. The paint binds the\n * leased renderer to whichever canvas pixels go to and draws the layer stack through the\n * current view. The layer command cache lives as long as the canvas.\n */\nexport function useCanvasPaint(\n paintRef: { current: () => boolean },\n lease: CanvasRendererLease,\n target: Pick<CanvasElements, 'paintTargetRef' | 'ownCanvasRef' | 'paintRectRef'>,\n frame: PaintFrame,\n markPainted: () => void,\n): void {\n const { paintTargetRef, ownCanvasRef, paintRectRef } = target;\n const { inputsRef, viewRef, gateRef, helpersRef } = frame;\n const layerCacheRef = useRef<LayerCommandCache>(new Map());\n\n const paint = useCallback((): boolean => {\n const c = paintTargetRef.current ?? ownCanvasRef.current;\n if (!c) return false;\n const rect = paintRectRef.current;\n const {\n layers: paintLayers, width: w, height: h, debugSink: sink, frameStats: timed,\n dpr: dprIn,\n flattenTolerance: paintFlattenTolerance,\n layerGroups: groups, shaders: paintShaders, retain, imageMinification,\n } = inputsRef.current;\n if (w === 0 || h === 0) return false;\n\n const debugFrame = beginDebugFrame(sink, timed, paintLayers);\n\n const renderer = lease.bind(c, { width: w, height: h, dpr: dprIn }, {\n tile: rect,\n flattenTolerance: paintFlattenTolerance,\n retainBudget: retain ? retain.budget : undefined,\n imageMinification,\n shaders: paintShaders,\n });\n // No WebGL2 (jsdom), or a context that is lost for now: nothing to paint.\n if (!renderer) return false;\n\n const view = viewRef.current;\n const { layerVisibility: vis, layerOrder: order } = gateRef.current;\n const spans = debugFrame?.time();\n const commands = drawLayers(\n paintLayers,\n helpersRef.current,\n vis ?? NO_LAYER_VISIBILITY,\n order,\n view,\n { width: w, height: h },\n layerCacheRef.current,\n undefined,\n groups,\n spans,\n retainPolicy(retain),\n );\n renderer.render(commands, viewToMat3(view), spans ? { spans } : undefined);\n debugFrame?.end(renderer.lastFrameStats());\n markPainted();\n return true;\n }, [gateRef, helpersRef, inputsRef, lease, markPainted, ownCanvasRef, paintRectRef, paintTargetRef, viewRef]);\n useInsertionEffect(() => {\n paintRef.current = paint;\n });\n\n useEffect(() => {\n const layerCache = layerCacheRef.current;\n return () => { layerCache.clear(); };\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;AAmCA,SAAgB,kBAAkB,gBAGhC;CACA,MAAM,oBAAoB,UAAU,cAAc;CAClD,MAAM,oBAAoB,OAAO,CAAC;CAKlC,OAAO;EAAE,mBAJiB,kBAAkB,kBAAkB,SAAS,CAAC,CAI/D;EAAmB,aAHR,kBAAkB;GACpC,kBAAkB,UAAU,kBAAkB,UAAU,KAAK;EAC/D,GAAG,CAAC,iBAAiB,CACO;CAAY;AAC1C;AAEA,SAAS,aAAa,QAA0D;CAC9E,IAAI,WAAW,OAAO,OAAO;CAC7B,MAAM,cAAc,QAAQ;CAC5B,OAAO,gBAAgB,SAAY,uBAAuB,EAAE,YAAY;AAC1E;;;;;;AAOA,SAAgB,eACd,UACA,OACA,QACA,OACA,aACM;CACN,MAAM,EAAE,gBAAgB,cAAc,iBAAiB;CACvD,MAAM,EAAE,WAAW,SAAS,SAAS,eAAe;CACpD,MAAM,gBAAgB,uBAA0B,IAAI,IAAI,CAAC;CAEzD,MAAM,QAAQ,kBAA2B;EACvC,MAAM,IAAI,eAAe,WAAW,aAAa;EACjD,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,OAAO,aAAa;EAC1B,MAAM,EACJ,QAAQ,aAAa,OAAO,GAAG,QAAQ,GAAG,WAAW,MAAM,YAAY,OACvE,KAAK,OACL,kBAAkB,uBAClB,aAAa,QAAQ,SAAS,cAAc,QAAQ,sBAClD,UAAU;EACd,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;EAE/B,MAAM,aAAa,gBAAgB,MAAM,OAAO,WAAW;EAE3D,MAAM,WAAW,MAAM,KAAK,GAAG;GAAE,OAAO;GAAG,QAAQ;GAAG,KAAK;EAAM,GAAG;GAClE,MAAM;GACN,kBAAkB;GAClB,cAAc,SAAS,OAAO,SAAS;GACvC;GACA,SAAS;EACX,CAAC;EAED,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,OAAO,QAAQ;EACrB,MAAM,EAAE,iBAAiB,KAAK,YAAY,UAAU,QAAQ;EAC5D,MAAM,QAAQ,YAAY,KAAK;EAC/B,MAAM,WAAW,WACf,aACA,WAAW,SACX,OAAO,qBACP,OACA,MACA;GAAE,OAAO;GAAG,QAAQ;EAAE,GACtB,cAAc,SACd,QACA,QACA,OACA,aAAa,MAAM,CACrB;EACA,SAAS,OAAO,UAAU,WAAW,IAAI,GAAG,QAAQ,EAAE,MAAM,IAAI,MAAS;EACzE,YAAY,IAAI,SAAS,eAAe,CAAC;EACzC,YAAY;EACZ,OAAO;CACT,GAAG;EAAC;EAAS;EAAY;EAAW;EAAO;EAAa;EAAc;EAAc;EAAgB;CAAO,CAAC;CAC5G,yBAAyB;EACvB,SAAS,UAAU;CACrB,CAAC;CAED,gBAAgB;EACd,MAAM,aAAa,cAAc;EACjC,aAAa;GAAE,WAAW,MAAM;EAAG;CACrC,GAAG,CAAC,CAAC;AACP"}
1
+ {"version":3,"file":"useCanvasPaint.js","names":[],"sources":["../../src/canvas/useCanvasPaint.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { viewToMat3 } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport {\n DEFAULT_LAYER_RETAIN, drawLayers, type LayerCommandCache, type LayerRetainPolicy, type RenderLayer,\n} from 'core/layers/render';\nimport type { CanvasDebugSink } from '../debug/types';\nimport { beginDebugFrame } from '../debug/useDebugSink';\nimport { resolveDpr, type CanvasRendererLease } from './canvasRenderer';\nimport type { CanvasProps } from './canvasProps';\nimport type { CanvasElements } from './useCanvasElements';\nimport { NO_LAYER_VISIBILITY, type LayerGate } from './useExtraLayers';\n\n/** What a frame's paint reads that a React render owns. */\nexport interface PaintInputs extends Pick<CanvasProps<unknown>, 'dpr' | 'layerGroups' | 'shaders' | 'flattenTolerance' | 'imageMinification' | 'retain'> {\n layers: RenderLayer<unknown>[];\n width: number;\n height: number;\n debugSink: CanvasDebugSink | null;\n frameStats: boolean;\n}\n\n/** What a frame's paint reads that changes outside a render. */\nexport interface PaintFrame {\n inputsRef: { readonly current: PaintInputs };\n viewRef: { readonly current: View };\n gateRef: { readonly current: LayerGate };\n helpersRef: { readonly current: unknown };\n}\n\n/**\n * Which content version the last paint showed. `markPainted` records the current one, read\n * from `contentVersion`'s latest committed value.\n */\nexport function usePaintedVersion(contentVersion: (() => number) | undefined): {\n getPaintedVersion: () => number;\n markPainted: () => void;\n} {\n const contentVersionRef = useLatest(contentVersion);\n const paintedVersionRef = useRef(0);\n const getPaintedVersion = useCallback(() => paintedVersionRef.current, []);\n const markPainted = useCallback(() => {\n paintedVersionRef.current = contentVersionRef.current?.() ?? 0;\n }, [contentVersionRef]);\n return { getPaintedVersion, markPainted };\n}\n\nfunction retainPolicy(retain: PaintInputs['retain']): LayerRetainPolicy | false {\n if (retain === false) return false;\n const minCommands = retain?.minCommands;\n return minCommands === undefined ? DEFAULT_LAYER_RETAIN : { minCommands };\n}\n\n/**\n * Publish the canvas's paint into `paintRef`, which the frame loop calls. The paint binds the\n * leased renderer to whichever canvas pixels go to and draws the layer stack through the\n * current view. The layer command cache lives as long as the canvas.\n */\nexport function useCanvasPaint(\n paintRef: { current: () => boolean },\n lease: CanvasRendererLease,\n target: Pick<CanvasElements, 'paintTargetRef' | 'ownCanvasRef' | 'paintRectRef'>,\n frame: PaintFrame,\n markPainted: () => void,\n): void {\n const { paintTargetRef, ownCanvasRef, paintRectRef } = target;\n const { inputsRef, viewRef, gateRef, helpersRef } = frame;\n const layerCacheRef = useRef<LayerCommandCache>(new Map());\n\n const paint = useCallback((): boolean => {\n const c = paintTargetRef.current ?? ownCanvasRef.current;\n if (!c) return false;\n const rect = paintRectRef.current;\n const {\n layers: paintLayers, width: w, height: h, debugSink: sink, frameStats: timed,\n dpr: dprIn,\n flattenTolerance: paintFlattenTolerance,\n layerGroups: groups, shaders: paintShaders, retain, imageMinification,\n } = inputsRef.current;\n if (w === 0 || h === 0) return false;\n\n const debugFrame = beginDebugFrame(sink, timed, paintLayers);\n\n const renderer = lease.bind(c, { width: w, height: h, dpr: dprIn }, {\n tile: rect,\n flattenTolerance: paintFlattenTolerance,\n retainBudget: retain ? retain.budget : undefined,\n imageMinification,\n shaders: paintShaders,\n });\n // No WebGL2 (jsdom), or a context that is lost for now: nothing to paint.\n if (!renderer) return false;\n\n const view = viewRef.current;\n const { layerVisibility: vis, layerOrder: order } = gateRef.current;\n const spans = debugFrame?.time();\n const commands = drawLayers(\n paintLayers,\n helpersRef.current,\n vis ?? NO_LAYER_VISIBILITY,\n order,\n view,\n { width: w, height: h, dpr: resolveDpr(dprIn) },\n layerCacheRef.current,\n undefined,\n groups,\n spans,\n retainPolicy(retain),\n );\n renderer.render(commands, viewToMat3(view), spans ? { spans } : undefined);\n debugFrame?.end(renderer.lastFrameStats());\n markPainted();\n return true;\n }, [gateRef, helpersRef, inputsRef, lease, markPainted, ownCanvasRef, paintRectRef, paintTargetRef, viewRef]);\n useInsertionEffect(() => {\n paintRef.current = paint;\n });\n\n useEffect(() => {\n const layerCache = layerCacheRef.current;\n return () => { layerCache.clear(); };\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;AAmCA,SAAgB,kBAAkB,gBAGhC;CACA,MAAM,oBAAoB,UAAU,cAAc;CAClD,MAAM,oBAAoB,OAAO,CAAC;CAKlC,OAAO;EAAE,mBAJiB,kBAAkB,kBAAkB,SAAS,CAAC,CAI/D;EAAmB,aAHR,kBAAkB;GACpC,kBAAkB,UAAU,kBAAkB,UAAU,KAAK;EAC/D,GAAG,CAAC,iBAAiB,CACO;CAAY;AAC1C;AAEA,SAAS,aAAa,QAA0D;CAC9E,IAAI,WAAW,OAAO,OAAO;CAC7B,MAAM,cAAc,QAAQ;CAC5B,OAAO,gBAAgB,SAAY,uBAAuB,EAAE,YAAY;AAC1E;;;;;;AAOA,SAAgB,eACd,UACA,OACA,QACA,OACA,aACM;CACN,MAAM,EAAE,gBAAgB,cAAc,iBAAiB;CACvD,MAAM,EAAE,WAAW,SAAS,SAAS,eAAe;CACpD,MAAM,gBAAgB,uBAA0B,IAAI,IAAI,CAAC;CAEzD,MAAM,QAAQ,kBAA2B;EACvC,MAAM,IAAI,eAAe,WAAW,aAAa;EACjD,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,OAAO,aAAa;EAC1B,MAAM,EACJ,QAAQ,aAAa,OAAO,GAAG,QAAQ,GAAG,WAAW,MAAM,YAAY,OACvE,KAAK,OACL,kBAAkB,uBAClB,aAAa,QAAQ,SAAS,cAAc,QAAQ,sBAClD,UAAU;EACd,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;EAE/B,MAAM,aAAa,gBAAgB,MAAM,OAAO,WAAW;EAE3D,MAAM,WAAW,MAAM,KAAK,GAAG;GAAE,OAAO;GAAG,QAAQ;GAAG,KAAK;EAAM,GAAG;GAClE,MAAM;GACN,kBAAkB;GAClB,cAAc,SAAS,OAAO,SAAS;GACvC;GACA,SAAS;EACX,CAAC;EAED,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,OAAO,QAAQ;EACrB,MAAM,EAAE,iBAAiB,KAAK,YAAY,UAAU,QAAQ;EAC5D,MAAM,QAAQ,YAAY,KAAK;EAC/B,MAAM,WAAW,WACf,aACA,WAAW,SACX,OAAO,qBACP,OACA,MACA;GAAE,OAAO;GAAG,QAAQ;GAAG,KAAK,WAAW,KAAK;EAAE,GAC9C,cAAc,SACd,QACA,QACA,OACA,aAAa,MAAM,CACrB;EACA,SAAS,OAAO,UAAU,WAAW,IAAI,GAAG,QAAQ,EAAE,MAAM,IAAI,MAAS;EACzE,YAAY,IAAI,SAAS,eAAe,CAAC;EACzC,YAAY;EACZ,OAAO;CACT,GAAG;EAAC;EAAS;EAAY;EAAW;EAAO;EAAa;EAAc;EAAc;EAAgB;CAAO,CAAC;CAC5G,yBAAyB;EACvB,SAAS,UAAU;CACrB,CAAC;CAED,gBAAgB;EACd,MAAM,aAAa,cAAc;EACjC,aAAa;GAAE,WAAW,MAAM;EAAG;CACrC,GAAG,CAAC,CAAC;AACP"}
@@ -7,18 +7,20 @@ import { useMemo } from "react";
7
7
  * the adapter keeps its identity across renders unless one of them changes.
8
8
  */
9
9
  function useSceneAdapter(scene, options = {}) {
10
- const { selection, commitInsert, insertLayer, poseDescriptor } = options;
10
+ const { selection, commitInsert, insertLayer, poseDescriptor, pasteInto } = options;
11
11
  return useMemo(() => sceneToAdapter(scene, {
12
12
  selection,
13
13
  commitInsert,
14
14
  insertLayer,
15
- poseDescriptor
15
+ poseDescriptor,
16
+ ...pasteInto ? { pasteInto } : {}
16
17
  }), [
17
18
  scene,
18
19
  selection,
19
20
  commitInsert,
20
21
  insertLayer,
21
- poseDescriptor
22
+ poseDescriptor,
23
+ pasteInto
22
24
  ]);
23
25
  }
24
26
 
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneAdapter.js","names":[],"sources":["../../src/canvas/useSceneAdapter.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { Scene } from 'core/scene/types';\nimport {\n sceneToAdapter,\n type SceneCanvasAdapter,\n type SceneToAdapterOptions,\n} from './sceneAdapter';\n\n/**\n * React-hook wrapper around `sceneToAdapter`. Memoizes on every option, so\n * the adapter keeps its identity across renders unless one of them changes.\n */\nexport function useSceneAdapter<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n options: SceneToAdapterOptions<TData, TLayer, TPose> = {},\n): SceneCanvasAdapter<TData, TLayer, TPose> {\n const {\n selection, commitInsert, insertLayer, poseDescriptor,\n } = options;\n return useMemo(\n () => sceneToAdapter(scene, {\n selection, commitInsert, insertLayer, poseDescriptor,\n }),\n [scene, selection, commitInsert, insertLayer, poseDescriptor],\n );\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,gBACd,OACA,UAAuD,CAAC,GACd;CAC1C,MAAM,EACJ,WAAW,cAAc,aAAa,mBACpC;CACJ,OAAO,cACC,eAAe,OAAO;EAC1B;EAAW;EAAc;EAAa;CACxC,CAAC,GACD;EAAC;EAAO;EAAW;EAAc;EAAa;CAAc,CAC9D;AACF"}
1
+ {"version":3,"file":"useSceneAdapter.js","names":[],"sources":["../../src/canvas/useSceneAdapter.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { Scene } from 'core/scene/types';\nimport {\n sceneToAdapter,\n type SceneCanvasAdapter,\n type SceneToAdapterOptions,\n} from './sceneAdapter';\n\n/**\n * React-hook wrapper around `sceneToAdapter`. Memoizes on every option, so\n * the adapter keeps its identity across renders unless one of them changes.\n */\nexport function useSceneAdapter<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n options: SceneToAdapterOptions<TData, TLayer, TPose> = {},\n): SceneCanvasAdapter<TData, TLayer, TPose> {\n const {\n selection, commitInsert, insertLayer, poseDescriptor, pasteInto,\n } = options;\n return useMemo(\n () => sceneToAdapter(scene, {\n selection, commitInsert, insertLayer, poseDescriptor, ...(pasteInto ? { pasteInto } : {}),\n }),\n [scene, selection, commitInsert, insertLayer, poseDescriptor, pasteInto],\n );\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,gBACd,OACA,UAAuD,CAAC,GACd;CAC1C,MAAM,EACJ,WAAW,cAAc,aAAa,gBAAgB,cACpD;CACJ,OAAO,cACC,eAAe,OAAO;EAC1B;EAAW;EAAc;EAAa;EAAgB,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CACzF,CAAC,GACD;EAAC;EAAO;EAAW;EAAc;EAAa;EAAgB;CAAS,CACzE;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { NodeId } from '@weasel-js/routing';
2
- import { P as PasteOmission, C as ClipboardSnapshot, I as InsertAdapter } from './types-oFeQ5VjV.js';
3
- import './types-7puAuVD2.js';
2
+ import { P as PasteOmission, C as ClipboardSnapshot, I as InsertAdapter } from './types-p2alu-R1.js';
3
+ import './types-49uYJN3j.js';
4
4
  import '@weasel-js/geom';
5
5
  import '@weasel-js/history';
6
6
  import './poseDescriptor-DN2yfnq_.js';
@@ -1,6 +1,6 @@
1
- import { a as PlaneMap } from './types-7puAuVD2.js';
1
+ import { a as PlaneMap } from './types-49uYJN3j.js';
2
2
  import { Op } from '@weasel-js/history';
3
- import { S as SnapTarget, M as MoveAdapter } from './types-oFeQ5VjV.js';
3
+ import { S as SnapTarget, M as MoveAdapter } from './types-p2alu-R1.js';
4
4
  import { FramedBox, View } from '@weasel-js/geom';
5
5
  import { KitSettingsStore } from '@weasel-js/react';
6
6
  import { ModifierState, ResizeAnchor } from '@weasel-js/routing';
package/dist/clone.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { C as CloneBehavior, d as CloneBehaviorResult } from './clone-C3e0OTuF.js';
2
- import './types-7puAuVD2.js';
1
+ export { C as CloneBehavior, d as CloneBehaviorResult } from './clone-qb2e1F5p.js';
2
+ import './types-49uYJN3j.js';
3
3
  import '@weasel-js/geom';
4
4
  import '@weasel-js/history';
5
- import './types-oFeQ5VjV.js';
5
+ import './types-p2alu-R1.js';
6
6
  import './poseDescriptor-DN2yfnq_.js';
7
7
  import '@weasel-js/animator';
8
8
  import '@weasel-js/react';