@weasel-js/core 1.12.0 → 1.13.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/CHANGELOG.md +91 -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/canvas/NodeShape.js +1 -0
  18. package/dist/canvas/NodeShape.js.map +1 -1
  19. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  29. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  30. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  32. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  34. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  35. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  36. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  37. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  38. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  39. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  40. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  41. package/dist/canvas/SceneCanvas.js +19 -2
  42. package/dist/canvas/SceneCanvas.js.map +1 -1
  43. package/dist/canvas/accentColor.js +26 -3
  44. package/dist/canvas/accentColor.js.map +1 -1
  45. package/dist/canvas/buildSceneTree.js +14 -20
  46. package/dist/canvas/buildSceneTree.js.map +1 -1
  47. package/dist/canvas/copiesAbove.js +1 -1
  48. package/dist/canvas/copiesAbove.js.map +1 -1
  49. package/dist/canvas/defaultDrawOne.js +10 -7
  50. package/dist/canvas/defaultDrawOne.js.map +1 -1
  51. package/dist/canvas/deps/areaSelect.js +5 -2
  52. package/dist/canvas/deps/areaSelect.js.map +1 -1
  53. package/dist/canvas/deps/booleans.js +1 -1
  54. package/dist/canvas/deps/booleans.js.map +1 -1
  55. package/dist/canvas/deps/enteredGroup.js +81 -0
  56. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  57. package/dist/canvas/deps/hitTestArea.js +27 -15
  58. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  59. package/dist/canvas/deps/insert.js +4 -36
  60. package/dist/canvas/deps/insert.js.map +1 -1
  61. package/dist/canvas/deps/insertSite.js +39 -0
  62. package/dist/canvas/deps/insertSite.js.map +1 -0
  63. package/dist/canvas/deps/lassoSelect.js +6 -3
  64. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  65. package/dist/canvas/deps/pathLeaf.js +1 -1
  66. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  67. package/dist/canvas/deps/slice.js +2 -1
  68. package/dist/canvas/deps/slice.js.map +1 -1
  69. package/dist/canvas/derivedPath.js +14 -6
  70. package/dist/canvas/derivedPath.js.map +1 -1
  71. package/dist/canvas/doubleClickTarget.js +9 -11
  72. package/dist/canvas/doubleClickTarget.js.map +1 -1
  73. package/dist/canvas/drawnBounds.js +16 -7
  74. package/dist/canvas/drawnBounds.js.map +1 -1
  75. package/dist/canvas/inheritedPaints.js +22 -11
  76. package/dist/canvas/inheritedPaints.js.map +1 -1
  77. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  78. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  79. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  80. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  81. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  82. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  83. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  84. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  85. package/dist/canvas/ownClip.js +24 -0
  86. package/dist/canvas/ownClip.js.map +1 -0
  87. package/dist/canvas/paintCull.js.map +1 -1
  88. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  89. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  90. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  91. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  92. package/dist/canvas/pickWalk.js +14 -90
  93. package/dist/canvas/pickWalk.js.map +1 -1
  94. package/dist/canvas/poseCascade.js +1 -1
  95. package/dist/canvas/poseCascade.js.map +1 -1
  96. package/dist/canvas/publishPreviews.js +97 -0
  97. package/dist/canvas/publishPreviews.js.map +1 -0
  98. package/dist/canvas/sceneAdapter.js +7 -5
  99. package/dist/canvas/sceneAdapter.js.map +1 -1
  100. package/dist/canvas/sceneClipboard.js +17 -10
  101. package/dist/canvas/sceneClipboard.js.map +1 -1
  102. package/dist/canvas/scenePickReads.js +37 -0
  103. package/dist/canvas/scenePickReads.js.map +1 -0
  104. package/dist/canvas/scenePickSource.js +74 -0
  105. package/dist/canvas/scenePickSource.js.map +1 -0
  106. package/dist/canvas/sceneSlotWiring.js +9 -4
  107. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  108. package/dist/canvas/sceneViewRender.js +9 -5
  109. package/dist/canvas/sceneViewRender.js.map +1 -1
  110. package/dist/canvas/shownNodes.js +15 -0
  111. package/dist/canvas/shownNodes.js.map +1 -0
  112. package/dist/canvas/useSceneAdapter.js +5 -3
  113. package/dist/canvas/useSceneAdapter.js.map +1 -1
  114. package/dist/clipboard.d.ts +2 -2
  115. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  116. package/dist/clone.d.ts +3 -3
  117. package/dist/core/isPlainObject.js +2 -2
  118. package/dist/core/layers/layerGroup.js.map +1 -1
  119. package/dist/core/scene/NodeRouting.js.map +1 -1
  120. package/dist/core/scene/arrivalWindow.js +1 -1
  121. package/dist/core/scene/arrivalWindow.js.map +1 -1
  122. package/dist/core/scene/clipNothing.js +2 -5
  123. package/dist/core/scene/clipNothing.js.map +1 -1
  124. package/dist/core/scene/declaredFieldSetters.js +44 -0
  125. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  126. package/dist/core/scene/depMemo.js +3 -0
  127. package/dist/core/scene/depMemo.js.map +1 -1
  128. package/dist/core/scene/dependents.js +3 -1
  129. package/dist/core/scene/dependents.js.map +1 -1
  130. package/dist/core/scene/documentOf.js +43 -0
  131. package/dist/core/scene/documentOf.js.map +1 -0
  132. package/dist/core/scene/effectivePose.js +37 -13
  133. package/dist/core/scene/effectivePose.js.map +1 -1
  134. package/dist/core/scene/fieldEdits.js +17 -14
  135. package/dist/core/scene/fieldEdits.js.map +1 -1
  136. package/dist/core/scene/historyForm.js +4 -4
  137. package/dist/core/scene/historyForm.js.map +1 -1
  138. package/dist/core/scene/inheritedPaint.js +27 -5
  139. package/dist/core/scene/inheritedPaint.js.map +1 -1
  140. package/dist/core/scene/kitOps.js +3 -23
  141. package/dist/core/scene/kitOps.js.map +1 -1
  142. package/dist/core/scene/layoutPass.js +1 -2
  143. package/dist/core/scene/layoutPass.js.map +1 -1
  144. package/dist/core/scene/nodeAsShown.js +72 -0
  145. package/dist/core/scene/nodeAsShown.js.map +1 -0
  146. package/dist/core/scene/nodeFnFields.js +2 -10
  147. package/dist/core/scene/nodeFnFields.js.map +1 -1
  148. package/dist/core/scene/nodeMemo.js +6 -1
  149. package/dist/core/scene/nodeMemo.js.map +1 -1
  150. package/dist/core/scene/nodeStore.js +2 -4
  151. package/dist/core/scene/nodeStore.js.map +1 -1
  152. package/dist/core/scene/pathEffects/derive.js +3 -2
  153. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  154. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  155. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  156. package/dist/core/scene/plyStacks.js +89 -0
  157. package/dist/core/scene/plyStacks.js.map +1 -0
  158. package/dist/core/scene/poseHold.js +42 -24
  159. package/dist/core/scene/poseHold.js.map +1 -1
  160. package/dist/core/scene/poseOverrides.js +35 -38
  161. package/dist/core/scene/poseOverrides.js.map +1 -1
  162. package/dist/core/scene/posePlies.js +59 -0
  163. package/dist/core/scene/posePlies.js.map +1 -0
  164. package/dist/core/scene/reconcileSpecs.js +6 -15
  165. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  166. package/dist/core/scene/renderOrder.js +10 -4
  167. package/dist/core/scene/renderOrder.js.map +1 -1
  168. package/dist/core/scene/sameValue.js +17 -0
  169. package/dist/core/scene/sameValue.js.map +1 -0
  170. package/dist/core/scene/scene.js +3 -3
  171. package/dist/core/scene/scene.js.map +1 -1
  172. package/dist/core/scene/sceneEdits.js +8 -0
  173. package/dist/core/scene/sceneEdits.js.map +1 -1
  174. package/dist/core/scene/sceneState.js +1 -2
  175. package/dist/core/scene/sceneState.js.map +1 -1
  176. package/dist/core/scene/serialization.js +45 -9
  177. package/dist/core/scene/serialization.js.map +1 -1
  178. package/dist/core/scene/staleReplay.js +5 -2
  179. package/dist/core/scene/staleReplay.js.map +1 -1
  180. package/dist/core/scene/statedFields.js +4 -2
  181. package/dist/core/scene/statedFields.js.map +1 -1
  182. package/dist/core/scene/treeEdits.js +2 -4
  183. package/dist/core/scene/treeEdits.js.map +1 -1
  184. package/dist/core/scene/treeOrder.js +3 -1
  185. package/dist/core/scene/treeOrder.js.map +1 -1
  186. package/dist/core/scene/treeReads.js +1 -2
  187. package/dist/core/scene/treeReads.js.map +1 -1
  188. package/dist/core/scene/treeShape.js +13 -0
  189. package/dist/core/scene/treeShape.js.map +1 -0
  190. package/dist/core/scene/types.js.map +1 -1
  191. package/dist/core/scene/useScene.js +1 -1
  192. package/dist/core/scene/useScene.js.map +1 -1
  193. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  194. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-B1RS-IJx.d.ts} +27 -54
  195. package/dist/devtools/handlers.js +1 -1
  196. package/dist/devtools/handlers.js.map +1 -1
  197. package/dist/devtools/protocol.js.map +1 -1
  198. package/dist/devtools-protocol.d.ts +4 -3
  199. package/dist/devtools.d.ts +3 -2
  200. package/dist/features/measure/measureHit.js +5 -3
  201. package/dist/features/measure/measureHit.js.map +1 -1
  202. package/dist/features/measure/measurement.js +1 -1
  203. package/dist/features/measure/measurement.js.map +1 -1
  204. package/dist/features/modes/modeLayers.js +14 -7
  205. package/dist/features/modes/modeLayers.js.map +1 -1
  206. package/dist/features/modes/modeTriggers.js +3 -3
  207. package/dist/features/modes/modeTriggers.js.map +1 -1
  208. package/dist/features/pathEffects/effectSources.js +25 -16
  209. package/dist/features/pathEffects/effectSources.js.map +1 -1
  210. package/dist/features/pathEffects/readSource.js +1 -1
  211. package/dist/features/pathEffects/readSource.js.map +1 -1
  212. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  213. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  214. package/dist/features/paths/editablePath.js +1 -1
  215. package/dist/features/paths/editablePath.js.map +1 -1
  216. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  217. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  218. package/dist/features/selection/enteredGroupFrame.js +65 -0
  219. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  220. package/dist/features/selection/enteredGroupScope.js +44 -0
  221. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  222. package/dist/features/text/sceneTextDrafts.js +26 -27
  223. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  224. package/dist/features/text/textHitAt.js +4 -2
  225. package/dist/features/text/textHitAt.js.map +1 -1
  226. package/dist/features/text/useSceneTextEdit.js +3 -3
  227. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  228. package/dist/index.d.ts +225 -73
  229. package/dist/index.js +15 -5
  230. package/dist/interactions/actions/admitsChild.js +11 -0
  231. package/dist/interactions/actions/admitsChild.js.map +1 -0
  232. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  233. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  234. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  235. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  236. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  237. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  238. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  239. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  240. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  241. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  242. package/dist/interactions/actions/defaults/group.js +7 -7
  243. package/dist/interactions/actions/defaults/group.js.map +1 -1
  244. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  245. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  246. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  247. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  248. package/dist/interactions/actions/drawnBoxes.js +2 -1
  249. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  250. package/dist/interactions/actions/insert/action.js +1 -0
  251. package/dist/interactions/actions/insert/action.js.map +1 -1
  252. package/dist/interactions/actions/insert/parent.js +7 -5
  253. package/dist/interactions/actions/insert/parent.js.map +1 -1
  254. package/dist/interactions/actions/insert/placeAction.js +3 -2
  255. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  256. package/dist/interactions/actions/insert/session.js +2 -2
  257. package/dist/interactions/actions/insert/session.js.map +1 -1
  258. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  259. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  260. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  261. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  262. package/dist/interactions/actions/move/layout/exits.js +1 -1
  263. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  264. package/dist/interactions/actions/move/session.js +4 -4
  265. package/dist/interactions/actions/move/session.js.map +1 -1
  266. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  267. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  268. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  269. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  270. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  271. package/dist/math.d.ts +1 -1
  272. package/dist/move.d.ts +4 -4
  273. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  274. package/dist/resize.d.ts +4 -4
  275. package/dist/test-seams.d.ts +6 -6
  276. package/dist/tools/builtin/pressTarget.js +22 -0
  277. package/dist/tools/builtin/pressTarget.js.map +1 -0
  278. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  279. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  280. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  281. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  282. package/dist/tools/builtin/select/groupEntry.js +82 -0
  283. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  284. package/dist/tools/builtin/select/pressDecision.js +1 -1
  285. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  286. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  287. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  288. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  289. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  290. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  291. package/dist/version.js +1 -1
  292. package/package.json +25 -25
@@ -1,13 +1,15 @@
1
1
  import { deriveParallaxView } from "../../core/viewport/parallax.js";
2
- import { findShapeSilhouette } from "../NodeShape.js";
2
+ import { inheritedPaintOf, inheritsPaint, sceneAncestors } from "../../core/scene/inheritedPaint.js";
3
+ import { withShownData } from "../../core/scene/nodeAsShown.js";
3
4
  import { planeTransform } from "../planeClips.js";
4
5
  import { wrapWithPoseRotation } from "../poseRotation.js";
6
+ import { ownClipOf } from "../ownClip.js";
5
7
  import { scenePaintedPoseOf } from "../derivedPath.js";
6
- import { scenePlaneOf } from "../pickWalk.js";
8
+ import { scenePlaneOf } from "../scenePickSource.js";
7
9
  import { copiesAbove } from "../copiesAbove.js";
8
10
  import { isCustomEntry } from "../layerSlots.js";
9
11
  import { previewSourcesFrom } from "../drawEnvelope.js";
10
- import { resolvePreviews } from "../../interactions/actions/resolvePreviews.js";
12
+ import { flattenPreviews, resolvePreviews } from "../../interactions/actions/resolvePreviews.js";
11
13
  import "@weasel-js/renderer";
12
14
  import { mat3 as mat3$1 } from "@weasel-js/geom";
13
15
  import { useEffect, useMemo, useReducer } from "react";
@@ -51,36 +53,40 @@ function usePreviewGhostLayer(args) {
51
53
  const parallax = planeOf ? sc.layers.find((l) => l.id === layer)?.parallax : void 0;
52
54
  return parallax ? deriveParallaxView(view, parallax) : view;
53
55
  };
54
- const drawEntry = (entry, parentPlane) => {
55
- const effNode = entry.data === entry.node.data ? entry.node : {
56
- ...entry.node,
57
- data: entry.data
58
- };
56
+ const drawEntry = (entry, parentPlane, above) => {
57
+ const effNode = entry.data === entry.node.data ? entry.node : withShownData(entry.node, entry.data);
59
58
  const plane = mapOf(entry.node.layer);
60
- const self = drawOne(effNode, entry.pose, viewOf(entry.node.layer));
61
- const children = [...wrapWithPoseRotation(self, entry.pose)];
62
- for (const child of entry.children) for (const cmd of drawEntry(child, plane)) children.push(cmd);
59
+ const ctx = inheritsPaint(effNode) ? { inherited: inheritedPaintOf(effNode, above()) } : void 0;
60
+ const self = drawOne(effNode, entry.pose, viewOf(entry.node.layer), ctx);
61
+ const kids = [];
62
+ for (const child of entry.children) for (const cmd of drawEntry(child, plane, () => [effNode, ...above()])) kids.push(cmd);
63
+ const clip = kids.length > 0 ? ownClipOf(effNode, entry.pose, () => slot.derivedPathOf?.(effNode, entry.pose)) : null;
64
+ const children = [...wrapWithPoseRotation(self, entry.pose), ...clip ? [{
65
+ kind: "group",
66
+ clip,
67
+ children: kids
68
+ }] : kids];
63
69
  const transform = planeTransform(plane, parentPlane);
64
- const group = transform ? {
70
+ return [transform ? {
65
71
  kind: "group",
66
72
  transform,
67
73
  children
68
74
  } : {
69
75
  kind: "group",
70
76
  children
71
- };
72
- if (effNode.kind === "container") {
73
- const clip = findShapeSilhouette(effNode, entry.pose, { derivedPath: slot.derivedPathOf?.(effNode, entry.pose) ?? null });
74
- if (clip) group.clip = clip;
75
- }
76
- return [group];
77
+ }];
77
78
  };
78
79
  const ghosted = [];
79
80
  const settled = [];
80
81
  const paintedPose = scenePaintedPoseOf(sc);
82
+ const previewed = new Map(flattenPreviews(roots).map((e) => [e.id, e.data]));
83
+ const asPreviewed = (n) => previewed.has(n.id) ? {
84
+ kind: n.kind,
85
+ data: previewed.get(n.id)
86
+ } : n;
81
87
  for (const root of roots) {
82
88
  const sink = root.opaque ? settled : ghosted;
83
- const cmds = drawEntry(root, null);
89
+ const cmds = drawEntry(root, null, () => [...sceneAncestors((id) => sc.get(id), root.node)].map(asPreviewed));
84
90
  for (const cmd of cmds) sink.push(cmd);
85
91
  for (const m of copiesAbove(sc, root.node.id, paintedPose)) sink.push({
86
92
  kind: "group",
@@ -1 +1 @@
1
- {"version":3,"file":"usePreviewGhostLayer.js","names":["mat3"],"sources":["../../../src/canvas/SceneCanvas/usePreviewGhostLayer.ts"],"sourcesContent":["/**\n * Preview-ghost render layer for `<SceneCanvas>`. Renders in-flight gesture\n * poses on top of the committed scene by reusing the scene slot's `drawOne`.\n * Which ids are displaced (`previewIds`) and where (`previewPose`) comes from\n * the drawing view's preview sources on the draw envelope: that view's\n * in-flight handles.\n *\n * This replaces the per-tool `drawGhost` fold-in — a single SceneCanvas\n * concern instead of every consumer wiring it.\n */\nimport { useEffect, useMemo, useReducer } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { type DrawCommand, type GroupDrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { isCustomEntry, type LayersMap } from '../layerSlots';\nimport type { Node, Scene } from 'core/scene/types';\nimport { findShapeSilhouette } from '../NodeShape';\nimport { wrapWithPoseRotation } from '../poseRotation';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { previewSourcesFrom } from '../drawEnvelope';\nimport { resolvePreviews, type PreviewNode } from 'interactions/actions/resolvePreviews';\nimport { scenePlaneOf } from '../pickWalk';\nimport { planeTransform } from '../planeClips';\nimport { deriveParallaxView } from 'core/viewport/parallax';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport { mat3 } from '@weasel-js/geom';\nimport { copiesAbove } from '../copiesAbove';\nimport { scenePaintedPoseOf } from '../derivedPath';\n\nconst GHOST_ALPHA = 0.85;\n\nexport function usePreviewGhostLayer<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n sceneSlot: LayersMap<Node<TData, TLayer, TPose>, TPose>['scene'];\n /**\n * The surface's dispatcher, subscribed to only so a pump repaints — preview\n * poses mutate silently inside handles otherwise. What gets *painted* is the\n * drawing view's own `getPreviewSources()`, off the draw envelope.\n */\n dispatcher?: Dispatcher | null;\n}): RenderLayer<unknown> {\n const { scene, sceneSlot, dispatcher } = args;\n\n // Refs let the layer body read the last committed scene/slot without\n // re-creating the layer on every host render.\n const sceneRef = useLatest(scene);\n const sceneSlotRef = useLatest(sceneSlot);\n\n // Subscribe to dispatcher state changes so the canvas re-renders on every\n // ongoing-action pump (preview poses mutate silently inside handles\n // otherwise — single-frame ghost stuck on the start pose).\n const [, forceRerender] = useReducer((n: number) => n + 1, 0);\n useEffect(() => {\n if (!dispatcher) return;\n const unsub = dispatcher.subscribe(forceRerender);\n return unsub;\n }, [dispatcher]);\n\n return useMemo<RenderLayer<unknown>>(() => ({\n id: 'preview-ghost',\n label: 'Preview ghost',\n draw: (data, view) => {\n const slot = sceneSlotRef.current;\n if (!slot || isCustomEntry(slot) || !slot.drawOne) return [];\n const drawOne = slot.drawOne;\n // Whose gesture this frame is showing belongs to the view being drawn,\n // not to the surface that built the layer.\n const sources = previewSourcesFrom(data);\n const sc = sceneRef.current;\n const roots = resolvePreviews(sources, sc);\n if (roots.length === 0) return [];\n\n // A node on a parallax layer ghosts where its plane draws it: through\n // the plane's view, inside a group carrying it into its parent's world.\n const planeOf = scenePlaneOf(sc.layers, view);\n const mapOf = (layer: string): PlaneMap | null => planeOf?.(layer) ?? null;\n const viewOf = (layer: string) => {\n const parallax = planeOf ? sc.layers.find((l) => l.id === layer)?.parallax : undefined;\n return parallax ? deriveParallaxView(view, parallax) : view;\n };\n\n // Mirrors buildSceneTree's structure — a container group carries a clip\n // from its painter's silhouette — but drawn from the previewed pose and\n // data, so children are clipped to the shape the container is taking.\n const drawEntry = (\n entry: PreviewNode<TData, TLayer, TPose>,\n parentPlane: PlaneMap | null,\n ): DrawCommand[] => {\n const effNode = entry.data === entry.node.data\n ? entry.node\n : ({ ...entry.node, data: entry.data } as typeof entry.node);\n const plane = mapOf(entry.node.layer);\n const self = drawOne(effNode, entry.pose, viewOf(entry.node.layer));\n const children: DrawCommand[] = [...wrapWithPoseRotation(self, entry.pose as unknown)];\n for (const child of entry.children) {\n for (const cmd of drawEntry(child, plane)) children.push(cmd);\n }\n const transform = planeTransform(plane, parentPlane);\n const group: GroupDrawCommand = transform ? { kind: 'group', transform, children } : { kind: 'group', children };\n if (effNode.kind === 'container') {\n const clip = findShapeSilhouette(\n effNode as unknown as Node<unknown, string, TPose>,\n entry.pose,\n { derivedPath: slot.derivedPathOf?.(effNode, entry.pose) ?? null },\n );\n if (clip) group.clip = clip;\n }\n return [group];\n };\n\n // Opaque is honored at subtree-root granularity, as it always was: a\n // sibling reflowing into place brings its own contents with it.\n const ghosted: DrawCommand[] = [];\n const settled: DrawCommand[] = [];\n // A node an ancestor draws copies of is ghosted in each copy too, or\n // the copies vanish for the length of the gesture.\n const paintedPose = scenePaintedPoseOf(sc);\n for (const root of roots) {\n const sink = root.opaque ? settled : ghosted;\n const cmds = drawEntry(root, null);\n for (const cmd of cmds) sink.push(cmd);\n for (const m of copiesAbove(sc, root.node.id, paintedPose)) {\n sink.push({ kind: 'group', transform: mat3.fromAffine(m), children: cmds });\n }\n }\n if (ghosted.length === 0 && settled.length === 0) return [];\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const out: DrawCommand[] = [];\n if (settled.length > 0) out.push({ kind: 'group', children: settled });\n if (ghosted.length > 0) out.push({ kind: 'group', alpha: GHOST_ALPHA, children: ghosted });\n return out;\n },\n }), [sceneRef, sceneSlotRef]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,cAAc;AAEpB,SAAgB,qBAA0D,MASjD;CACvB,MAAM,EAAE,OAAO,WAAW,eAAe;CAIzC,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CAKxC,MAAM,GAAG,iBAAiB,YAAY,MAAc,IAAI,GAAG,CAAC;CAC5D,gBAAgB;EACd,IAAI,CAAC,YAAY;EAEjB,OADc,WAAW,UAAU,aAC5B;CACT,GAAG,CAAC,UAAU,CAAC;CAEf,OAAO,eAAqC;EAC1C,IAAI;EACJ,OAAO;EACP,OAAO,MAAM,SAAS;GACpB,MAAM,OAAO,aAAa;GAC1B,IAAI,CAAC,QAAQ,cAAc,IAAI,KAAK,CAAC,KAAK,SAAS,OAAO,CAAC;GAC3D,MAAM,UAAU,KAAK;GAGrB,MAAM,UAAU,mBAAmB,IAAI;GACvC,MAAM,KAAK,SAAS;GACpB,MAAM,QAAQ,gBAAgB,SAAS,EAAE;GACzC,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;GAIhC,MAAM,UAAU,aAAa,GAAG,QAAQ,IAAI;GAC5C,MAAM,SAAS,UAAmC,UAAU,KAAK,KAAK;GACtE,MAAM,UAAU,UAAkB;IAChC,MAAM,WAAW,UAAU,GAAG,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK,CAAC,EAAE,WAAW;IAC7E,OAAO,WAAW,mBAAmB,MAAM,QAAQ,IAAI;GACzD;GAKA,MAAM,aACJ,OACA,gBACkB;IAClB,MAAM,UAAU,MAAM,SAAS,MAAM,KAAK,OACtC,MAAM,OACL;KAAE,GAAG,MAAM;KAAM,MAAM,MAAM;IAAK;IACvC,MAAM,QAAQ,MAAM,MAAM,KAAK,KAAK;IACpC,MAAM,OAAO,QAAQ,SAAS,MAAM,MAAM,OAAO,MAAM,KAAK,KAAK,CAAC;IAClE,MAAM,WAA0B,CAAC,GAAG,qBAAqB,MAAM,MAAM,IAAe,CAAC;IACrF,KAAK,MAAM,SAAS,MAAM,UACxB,KAAK,MAAM,OAAO,UAAU,OAAO,KAAK,GAAG,SAAS,KAAK,GAAG;IAE9D,MAAM,YAAY,eAAe,OAAO,WAAW;IACnD,MAAM,QAA0B,YAAY;KAAE,MAAM;KAAS;KAAW;IAAS,IAAI;KAAE,MAAM;KAAS;IAAS;IAC/G,IAAI,QAAQ,SAAS,aAAa;KAChC,MAAM,OAAO,oBACX,SACA,MAAM,MACN,EAAE,aAAa,KAAK,gBAAgB,SAAS,MAAM,IAAI,KAAK,KAAK,CACnE;KACA,IAAI,MAAM,MAAM,OAAO;IACzB;IACA,OAAO,CAAC,KAAK;GACf;GAIA,MAAM,UAAyB,CAAC;GAChC,MAAM,UAAyB,CAAC;GAGhC,MAAM,cAAc,mBAAmB,EAAE;GACzC,KAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,OAAO,KAAK,SAAS,UAAU;IACrC,MAAM,OAAO,UAAU,MAAM,IAAI;IACjC,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG;IACrC,KAAK,MAAM,KAAK,YAAY,IAAI,KAAK,KAAK,IAAI,WAAW,GACvD,KAAK,KAAK;KAAE,MAAM;KAAS,WAAWA,OAAK,WAAW,CAAC;KAAG,UAAU;IAAK,CAAC;GAE9E;GACA,IAAI,QAAQ,WAAW,KAAK,QAAQ,WAAW,GAAG,OAAO,CAAC;GAE1D,MAAM,MAAqB,CAAC;GAC5B,IAAI,QAAQ,SAAS,GAAG,IAAI,KAAK;IAAE,MAAM;IAAS,UAAU;GAAQ,CAAC;GACrE,IAAI,QAAQ,SAAS,GAAG,IAAI,KAAK;IAAE,MAAM;IAAS,OAAO;IAAa,UAAU;GAAQ,CAAC;GACzF,OAAO;EACT;CACF,IAAI,CAAC,UAAU,YAAY,CAAC;AAC9B"}
1
+ {"version":3,"file":"usePreviewGhostLayer.js","names":["mat3"],"sources":["../../../src/canvas/SceneCanvas/usePreviewGhostLayer.ts"],"sourcesContent":["/**\n * Preview-ghost render layer for `<SceneCanvas>`. Renders in-flight gesture\n * poses on top of the committed scene by reusing the scene slot's `drawOne`.\n * Which ids are displaced (`previewIds`) and where (`previewPose`) comes from\n * the drawing view's preview sources on the draw envelope: that view's\n * in-flight handles.\n *\n * This replaces the per-tool `drawGhost` fold-in — a single SceneCanvas\n * concern instead of every consumer wiring it.\n */\nimport { useEffect, useMemo, useReducer } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { type DrawCommand, type GroupDrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { isCustomEntry, type LayersMap } from '../layerSlots';\nimport type { Node, Scene } from 'core/scene/types';\nimport { ownClipOf } from '../ownClip';\nimport { wrapWithPoseRotation } from '../poseRotation';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { previewSourcesFrom } from '../drawEnvelope';\nimport { flattenPreviews, resolvePreviews, type PreviewNode } from 'interactions/actions/resolvePreviews';\nimport { scenePlaneOf } from '../pickWalk';\nimport { planeTransform } from '../planeClips';\nimport { deriveParallaxView } from 'core/viewport/parallax';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport { mat3 } from '@weasel-js/geom';\nimport { copiesAbove } from '../copiesAbove';\nimport { scenePaintedPoseOf } from '../derivedPath';\nimport { inheritedPaintOf, inheritsPaint, sceneAncestors } from 'core/scene/inheritedPaint';\nimport { withShownData } from 'core/scene/nodeAsShown';\n\nconst GHOST_ALPHA = 0.85;\n\nexport function usePreviewGhostLayer<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n sceneSlot: LayersMap<Node<TData, TLayer, TPose>, TPose>['scene'];\n /**\n * The surface's dispatcher, subscribed to only so a pump repaints — preview\n * poses mutate silently inside handles otherwise. What gets *painted* is the\n * drawing view's own `getPreviewSources()`, off the draw envelope.\n */\n dispatcher?: Dispatcher | null;\n}): RenderLayer<unknown> {\n const { scene, sceneSlot, dispatcher } = args;\n\n // Refs let the layer body read the last committed scene/slot without\n // re-creating the layer on every host render.\n const sceneRef = useLatest(scene);\n const sceneSlotRef = useLatest(sceneSlot);\n\n // Subscribe to dispatcher state changes so the canvas re-renders on every\n // ongoing-action pump (preview poses mutate silently inside handles\n // otherwise — single-frame ghost stuck on the start pose).\n const [, forceRerender] = useReducer((n: number) => n + 1, 0);\n useEffect(() => {\n if (!dispatcher) return;\n const unsub = dispatcher.subscribe(forceRerender);\n return unsub;\n }, [dispatcher]);\n\n return useMemo<RenderLayer<unknown>>(() => ({\n id: 'preview-ghost',\n label: 'Preview ghost',\n draw: (data, view) => {\n const slot = sceneSlotRef.current;\n if (!slot || isCustomEntry(slot) || !slot.drawOne) return [];\n const drawOne = slot.drawOne;\n // Whose gesture this frame is showing belongs to the view being drawn,\n // not to the surface that built the layer.\n const sources = previewSourcesFrom(data);\n const sc = sceneRef.current;\n const roots = resolvePreviews(sources, sc);\n if (roots.length === 0) return [];\n\n // A node on a parallax layer ghosts where its plane draws it: through\n // the plane's view, inside a group carrying it into its parent's world.\n const planeOf = scenePlaneOf(sc.layers, view);\n const mapOf = (layer: string): PlaneMap | null => planeOf?.(layer) ?? null;\n const viewOf = (layer: string) => {\n const parallax = planeOf ? sc.layers.find((l) => l.id === layer)?.parallax : undefined;\n return parallax ? deriveParallaxView(view, parallax) : view;\n };\n\n // Mirrors buildSceneTree's structure, but drawn from the previewed pose\n // and data, so children are clipped to the shape the node is taking and\n // inherit the paint it is taking. `above` is its containers, innermost first.\n const drawEntry = (\n entry: PreviewNode<TData, TLayer, TPose>,\n parentPlane: PlaneMap | null,\n above: () => Iterable<{ kind?: string; data?: unknown }>,\n ): DrawCommand[] => {\n const effNode = entry.data === entry.node.data ? entry.node : withShownData(entry.node, entry.data);\n const plane = mapOf(entry.node.layer);\n const ctx = inheritsPaint(effNode) ? { inherited: inheritedPaintOf(effNode, above()) } : undefined;\n const self = drawOne(effNode, entry.pose, viewOf(entry.node.layer), ctx);\n const kids: DrawCommand[] = [];\n for (const child of entry.children) {\n for (const cmd of drawEntry(child, plane, () => [effNode, ...above()])) kids.push(cmd);\n }\n // The node's clip is its children's, not its own paint's.\n const clip = kids.length > 0\n ? ownClipOf(effNode, entry.pose, () => slot.derivedPathOf?.(effNode, entry.pose)) : null;\n const children: DrawCommand[] = [\n ...wrapWithPoseRotation(self, entry.pose as unknown),\n ...(clip ? [{ kind: 'group', clip, children: kids } as GroupDrawCommand] : kids),\n ];\n const transform = planeTransform(plane, parentPlane);\n return [transform ? { kind: 'group', transform, children } : { kind: 'group', children }];\n };\n\n // Opaque is honored at subtree-root granularity, as it always was: a\n // sibling reflowing into place brings its own contents with it.\n const ghosted: DrawCommand[] = [];\n const settled: DrawCommand[] = [];\n // A node an ancestor draws copies of is ghosted in each copy too, or\n // the copies vanish for the length of the gesture.\n const paintedPose = scenePaintedPoseOf(sc);\n // A root's containers are read as previewed too, where one is.\n const previewed = new Map(flattenPreviews(roots).map((e) => [e.id as string, e.data]));\n const asPreviewed = (n: Node<TData, TLayer, TPose>) => (previewed.has(n.id) ? { kind: n.kind, data: previewed.get(n.id) } : n);\n for (const root of roots) {\n const sink = root.opaque ? settled : ghosted;\n const cmds = drawEntry(root, null, () => [...sceneAncestors((id: never) => sc.get(id), root.node)].map(asPreviewed));\n for (const cmd of cmds) sink.push(cmd);\n for (const m of copiesAbove(sc, root.node.id, paintedPose)) {\n sink.push({ kind: 'group', transform: mat3.fromAffine(m), children: cmds });\n }\n }\n if (ghosted.length === 0 && settled.length === 0) return [];\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const out: DrawCommand[] = [];\n if (settled.length > 0) out.push({ kind: 'group', children: settled });\n if (ghosted.length > 0) out.push({ kind: 'group', alpha: GHOST_ALPHA, children: ghosted });\n return out;\n },\n }), [sceneRef, sceneSlotRef]);\n}\n"],"mappings":"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"}
@@ -13,6 +13,7 @@ import { useEffectSourcesLayer } from "../../features/pathEffects/useEffectSourc
13
13
  import { usePoseTransition } from "../../animation/poseTransition.js";
14
14
  import { useSceneSlotLayers } from "./useSceneSlotLayers.js";
15
15
  import { useSelectionOverlayLayer } from "./useSelectionOverlayLayer.js";
16
+ import { useEnteredGroupFrameLayer } from "../../features/selection/enteredGroupFrame.js";
16
17
  import { createSnapReadoutLayer } from "../../features/snap/readoutLayer.js";
17
18
  import { useMemo, useRef, useState } from "react";
18
19
 
@@ -83,11 +84,16 @@ function useSceneCanvasLayers(args) {
83
84
  planeOfNode: args.planeOfNode
84
85
  }, overlayConfig);
85
86
  const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer(merged.selectionOverlay, pathEditState.getEditingIds, poseDescriptor, args.boundsIncludeStroke ? drawnBoundsScene(scene) : void 0, resizeHandles);
87
+ const enteredFrameLayer = useEnteredGroupFrameLayer(args.enteredGroup, args.boundsOf, args.frameColor);
86
88
  return {
87
89
  layers: useMemo(() => ({
88
90
  ...builtLayersOf(merged),
89
91
  scene: sceneLayers,
90
92
  selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,
93
+ enteredGroupFrame: {
94
+ layer: enteredFrameLayer,
95
+ before: "selectionOverlay"
96
+ },
91
97
  departureGhost: {
92
98
  layer: departureLayer,
93
99
  after: "scene"
@@ -120,6 +126,7 @@ function useSceneCanvasLayers(args) {
120
126
  merged,
121
127
  sceneLayers,
122
128
  selectionOverlayLayer,
129
+ enteredFrameLayer,
123
130
  departureLayer,
124
131
  previewLayer,
125
132
  effectSourcesLayer,
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneCanvasLayers.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasLayers.ts"],"sourcesContent":["import { useMemo, useRef, useState, type MutableRefObject } from 'react';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport type { Affordance } from 'affordances/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PathEditState } from 'features/paths/pathEditState';\nimport { usePathEditOverlayLayer } from 'features/paths/usePathEdit';\nimport type { rotationBadgeOf } from 'features/selection/overlay';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { HierarchicalAdapter } from '../buildSceneTree';\nimport { builtLayersOf, isCustomEntry, type CanvasLayers } from '../layerSlots';\nimport { createSlopsDebugLayer } from '../slopsDebugLayer';\nimport { useTargetBasePx } from 'core/device/useTargetBasePx';\nimport { targetSizesPx } from 'core/device/targets';\nimport type { ResizeHandleLayout } from 'interactions/actions/resize/cornerHandles';\nimport { mergeLayersWithDefaults } from './mergeLayers';\nimport type { SceneCanvasLayers } from './props';\nimport { useDispatcherOverlayLayer } from './useDispatcherOverlayLayer';\nimport { usePreviewGhostLayer } from './usePreviewGhostLayer';\nimport { useDepartureGhostLayer } from './useDepartureGhostLayer';\nimport { useEffectSourcesLayer } from 'features/pathEffects/useEffectSourcesLayer';\nimport type { Departures } from '../../animation/departures';\nimport { usePoseTransition, type PoseTransitionSeat } from '../../animation/poseTransition';\nimport { useSceneSlotLayers } from './useSceneSlotLayers';\nimport { useSelectionOverlayLayer } from './useSelectionOverlayLayer';\nimport { drawnBoundsScene } from '../derivedPath';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport { createSnapReadoutLayer } from 'features/snap/readoutLayer';\nimport type { SnapReadoutStyle } from 'features/snap/types';\n\n/**\n * The layer map `<SceneCanvas>` hands `<Canvas>`: the caller's `layers` over\n * the kit defaults, with the scene and selection-overlay slots built, and the\n * canvas's own layers anchored around them. Removed nodes leave just above\n * the scene, the preview ghost above them, a selected path effect's hidden sources above that, the dispatcher's\n * overlay (marquee, lasso) above those, and the\n * anchor overlay above the selection chrome.\n */\nexport function useSceneCanvasLayers<TData, TLayer extends string, TPose>(args: {\n /** `<SceneCanvas layers>`. Omitted, the result is the full default set. */\n layers: SceneCanvasLayers<Node<TData, TLayer, TPose>, TPose> | undefined;\n /** The device profile's, which sizes the default handles. */\n targetScale: number;\n scene: Scene<TData, TLayer, TPose>;\n adapter: HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose>;\n poseDescriptor: PoseDescriptor<TPose>;\n alphaFor: ((id: string) => number) | undefined;\n colorOverrides: ColorOverrideRegistry | undefined;\n boundsOf: (id: string) => Bounds | null;\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n /** `<SceneCanvas poseTransition>`, whose ghosts of removed nodes this draws. */\n poseTransition: PoseTransitionSeat<TPose> | null | undefined;\n /** Ids an in-flight gesture is ghosting. */\n previewIds: () => Iterable<string> | null;\n dispatcher: Dispatcher;\n /** Whether to mount the snap readout layer. */\n snapReadout: boolean;\n snapReadoutStyle?: SnapReadoutStyle;\n pathEditState: PathEditState;\n /** `debug.slops`: paint the hit regions of the chrome affordances. */\n slops: boolean;\n /** `geometry.boundsIncludeStroke`: whether chrome sits on the stroke. */\n boundsIncludeStroke: boolean;\n /** `selectTool`'s `handleHitRadius` and `resize.edgeHandles`, resolved here into\n * the one {@link ResizeHandleLayout} the overlay paints and the hit-test reads. */\n selectTool?: { handleHitRadius?: number; resize?: { edgeHandles?: boolean } };\n}): {\n layers: CanvasLayers;\n /** Where the selection overlay paints its rotate badge, or null. */\n rotationBadge: ReturnType<typeof rotationBadgeOf>;\n /** Which resize handles exist and their hit radius, for every hit-test of this canvas. */\n resizeHandles: ResizeHandleLayout;\n /** The affordances the gesture mounter hit-tests, which it publishes here\n * so the slops halos are the hit regions rather than a copy of them. */\n chromeAffordancesRef: MutableRefObject<readonly Affordance[]>;\n /** The marks the readout layer draws; the canvas's snap dep writes them. */\n snapStore: SnapOutcomeStore;\n /** The removed nodes the seat is seeing off. */\n departures: Departures<TPose, Node<TData, TLayer, TPose>>;\n} {\n const { scene, adapter, poseDescriptor, alphaFor, dispatcher, pathEditState, slops, targetScale } = args;\n\n const baseSizes = useTargetBasePx();\n const merged = useMemo(\n () => mergeLayersWithDefaults(args.layers, targetScale, baseSizes),\n [args.layers, targetScale, baseSizes],\n );\n\n const { config: sceneSlot, layers: sceneLayers } = useSceneSlotLayers({\n slot: merged.scene,\n scene,\n adapter,\n alphaFor,\n colorOverrides: args.colorOverrides,\n boundsOf: args.boundsOf,\n previewIds: args.previewIds,\n });\n // Takes the scene-wired slot, so a ghosted derived node still resolves its path.\n const previewLayer = usePreviewGhostLayer<TData, TLayer, TPose>({ scene, sceneSlot, dispatcher });\n const departures = usePoseTransition(scene, args.poseTransition);\n const departureLayer = useDepartureGhostLayer({ scene, sceneSlot, departures });\n const effectSourcesLayer = useEffectSourcesLayer(scene);\n const dispatcherOverlay = useDispatcherOverlayLayer({ dispatcher });\n\n const hitRadiusPx = args.selectTool?.handleHitRadius ?? targetSizesPx(targetScale, baseSizes).handle;\n const edges = args.selectTool?.resize?.edgeHandles ?? true;\n const resizeHandles = useMemo<ResizeHandleLayout>(() => ({ edges, hitRadiusPx }), [edges, hitRadiusPx]);\n\n const chromeAffordancesRef = useRef<readonly Affordance[]>([]);\n const slopsLayer = useMemo(\n () => createSlopsDebugLayer({ getAffordances: () => chromeAffordancesRef.current }),\n [],\n );\n\n const [snapStore] = useState(() => createSnapOutcomeStore());\n // Keyed on the style's values, so an inline `readoutStyle` literal keeps the layer.\n const { color, gapColor, lineWidth } = args.snapReadoutStyle ?? {};\n const snapReadoutLayer = useMemo(() => createSnapReadoutLayer(snapStore, {\n ...(color !== undefined ? { color } : {}),\n ...(gapColor !== undefined ? { gapColor } : {}),\n ...(lineWidth !== undefined ? { lineWidth } : {}),\n }), [snapStore, color, gapColor, lineWidth]);\n const overlaySlot = merged.pathEditingOverlay;\n const overlayConfig = overlaySlot && !isCustomEntry(overlaySlot) ? overlaySlot : undefined;\n const overlayAnchor = overlaySlot === null ? 'dispatcherOverlay' : 'pathEditingOverlay';\n const pathEditingOverlayLayer = usePathEditOverlayLayer(pathEditState, {\n nodeOf: (id) => scene.get(id as NodeId),\n planeOfNode: args.planeOfNode,\n }, overlayConfig);\n\n const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer<TPose>(\n merged.selectionOverlay,\n pathEditState.getEditingIds,\n poseDescriptor,\n args.boundsIncludeStroke ? drawnBoundsScene(scene) : undefined,\n resizeHandles,\n );\n\n const layers = useMemo<CanvasLayers>(() => ({\n ...builtLayersOf(merged),\n scene: sceneLayers,\n selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,\n departureGhost: { layer: departureLayer, after: 'scene' },\n previewGhost: { layer: previewLayer, after: 'departureGhost' },\n effectSources: { layer: effectSourcesLayer, after: 'previewGhost' },\n dispatcherOverlay: { layer: dispatcherOverlay, after: 'effectSources' },\n pathEditingOverlay: overlaySlot === null || isCustomEntry(overlaySlot)\n ? overlaySlot\n : { layer: pathEditingOverlayLayer, after: 'selectionOverlay' },\n ...(args.snapReadout ? { snapReadout: { layer: snapReadoutLayer, after: overlayAnchor } } : {}),\n ...(slops ? { slopsDebug: { layer: slopsLayer, after: args.snapReadout ? 'snapReadout' : overlayAnchor } } : {}),\n }), [\n merged, sceneLayers, selectionOverlayLayer, departureLayer, previewLayer, effectSourcesLayer, dispatcherOverlay, pathEditingOverlayLayer, overlaySlot, overlayAnchor,\n snapReadoutLayer, args.snapReadout, slops, slopsLayer,\n ]);\n\n return { layers, rotationBadge, resizeHandles, chromeAffordancesRef, snapStore, departures };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSceneCanvasLayers.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasLayers.ts"],"sourcesContent":["import { useMemo, useRef, useState, type MutableRefObject } from 'react';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport type { Affordance } from 'affordances/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PathEditState } from 'features/paths/pathEditState';\nimport { usePathEditOverlayLayer } from 'features/paths/usePathEdit';\nimport type { rotationBadgeOf } from 'features/selection/overlay';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { HierarchicalAdapter } from '../buildSceneTree';\nimport { builtLayersOf, isCustomEntry, type CanvasLayers } from '../layerSlots';\nimport { createSlopsDebugLayer } from '../slopsDebugLayer';\nimport { useTargetBasePx } from 'core/device/useTargetBasePx';\nimport { targetSizesPx } from 'core/device/targets';\nimport type { ResizeHandleLayout } from 'interactions/actions/resize/cornerHandles';\nimport { mergeLayersWithDefaults } from './mergeLayers';\nimport type { SceneCanvasLayers } from './props';\nimport { useDispatcherOverlayLayer } from './useDispatcherOverlayLayer';\nimport { usePreviewGhostLayer } from './usePreviewGhostLayer';\nimport { useDepartureGhostLayer } from './useDepartureGhostLayer';\nimport { useEffectSourcesLayer } from 'features/pathEffects/useEffectSourcesLayer';\nimport type { Departures } from '../../animation/departures';\nimport { usePoseTransition, type PoseTransitionSeat } from '../../animation/poseTransition';\nimport { useSceneSlotLayers } from './useSceneSlotLayers';\nimport { useSelectionOverlayLayer } from './useSelectionOverlayLayer';\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useEnteredGroupFrameLayer } from 'features/selection/enteredGroupFrame';\nimport type { EnteredGroupApi } from '../deps/enteredGroup';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport { createSnapReadoutLayer } from 'features/snap/readoutLayer';\nimport type { SnapReadoutStyle } from 'features/snap/types';\n\n/**\n * The layer map `<SceneCanvas>` hands `<Canvas>`: the caller's `layers` over\n * the kit defaults, with the scene and selection-overlay slots built, and the\n * canvas's own layers anchored around them. Removed nodes leave just above\n * the scene, the preview ghost above them, a selected path effect's hidden sources above that, the dispatcher's\n * overlay (marquee, lasso) above those, and the\n * anchor overlay above the selection chrome.\n */\nexport function useSceneCanvasLayers<TData, TLayer extends string, TPose>(args: {\n /** `<SceneCanvas layers>`. Omitted, the result is the full default set. */\n layers: SceneCanvasLayers<Node<TData, TLayer, TPose>, TPose> | undefined;\n /** The device profile's, which sizes the default handles. */\n targetScale: number;\n scene: Scene<TData, TLayer, TPose>;\n adapter: HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose>;\n poseDescriptor: PoseDescriptor<TPose>;\n alphaFor: ((id: string) => number) | undefined;\n colorOverrides: ColorOverrideRegistry | undefined;\n boundsOf: (id: string) => Bounds | null; enteredGroup: EnteredGroupApi; frameColor: { read: () => string; changed: (fn: () => void) => () => void };\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n /** `<SceneCanvas poseTransition>`, whose ghosts of removed nodes this draws. */\n poseTransition: PoseTransitionSeat<TPose> | null | undefined;\n /** Ids an in-flight gesture is ghosting. */\n previewIds: () => Iterable<string> | null;\n dispatcher: Dispatcher;\n /** Whether to mount the snap readout layer. */\n snapReadout: boolean;\n snapReadoutStyle?: SnapReadoutStyle;\n pathEditState: PathEditState;\n /** `debug.slops`: paint the hit regions of the chrome affordances. */\n slops: boolean;\n /** `geometry.boundsIncludeStroke`: whether chrome sits on the stroke. */\n boundsIncludeStroke: boolean;\n /** `selectTool`'s `handleHitRadius` and `resize.edgeHandles`, resolved here into\n * the one {@link ResizeHandleLayout} the overlay paints and the hit-test reads. */\n selectTool?: { handleHitRadius?: number; resize?: { edgeHandles?: boolean } };\n}): {\n layers: CanvasLayers;\n /** Where the selection overlay paints its rotate badge, or null. */\n rotationBadge: ReturnType<typeof rotationBadgeOf>;\n /** Which resize handles exist and their hit radius, for every hit-test of this canvas. */\n resizeHandles: ResizeHandleLayout;\n /** The affordances the gesture mounter hit-tests, which it publishes here\n * so the slops halos are the hit regions rather than a copy of them. */\n chromeAffordancesRef: MutableRefObject<readonly Affordance[]>;\n /** The marks the readout layer draws; the canvas's snap dep writes them. */\n snapStore: SnapOutcomeStore;\n /** The removed nodes the seat is seeing off. */\n departures: Departures<TPose, Node<TData, TLayer, TPose>>;\n} {\n const { scene, adapter, poseDescriptor, alphaFor, dispatcher, pathEditState, slops, targetScale } = args;\n\n const baseSizes = useTargetBasePx();\n const merged = useMemo(\n () => mergeLayersWithDefaults(args.layers, targetScale, baseSizes),\n [args.layers, targetScale, baseSizes],\n );\n\n const { config: sceneSlot, layers: sceneLayers } = useSceneSlotLayers({\n slot: merged.scene,\n scene,\n adapter,\n alphaFor,\n colorOverrides: args.colorOverrides,\n boundsOf: args.boundsOf,\n previewIds: args.previewIds,\n });\n // Takes the scene-wired slot, so a ghosted derived node still resolves its path.\n const previewLayer = usePreviewGhostLayer<TData, TLayer, TPose>({ scene, sceneSlot, dispatcher });\n const departures = usePoseTransition(scene, args.poseTransition);\n const departureLayer = useDepartureGhostLayer({ scene, sceneSlot, departures });\n const effectSourcesLayer = useEffectSourcesLayer(scene);\n const dispatcherOverlay = useDispatcherOverlayLayer({ dispatcher });\n\n const hitRadiusPx = args.selectTool?.handleHitRadius ?? targetSizesPx(targetScale, baseSizes).handle;\n const edges = args.selectTool?.resize?.edgeHandles ?? true;\n const resizeHandles = useMemo<ResizeHandleLayout>(() => ({ edges, hitRadiusPx }), [edges, hitRadiusPx]);\n\n const chromeAffordancesRef = useRef<readonly Affordance[]>([]);\n const slopsLayer = useMemo(\n () => createSlopsDebugLayer({ getAffordances: () => chromeAffordancesRef.current }),\n [],\n );\n\n const [snapStore] = useState(() => createSnapOutcomeStore());\n // Keyed on the style's values, so an inline `readoutStyle` literal keeps the layer.\n const { color, gapColor, lineWidth } = args.snapReadoutStyle ?? {};\n const snapReadoutLayer = useMemo(() => createSnapReadoutLayer(snapStore, {\n ...(color !== undefined ? { color } : {}),\n ...(gapColor !== undefined ? { gapColor } : {}),\n ...(lineWidth !== undefined ? { lineWidth } : {}),\n }), [snapStore, color, gapColor, lineWidth]);\n const overlaySlot = merged.pathEditingOverlay;\n const overlayConfig = overlaySlot && !isCustomEntry(overlaySlot) ? overlaySlot : undefined;\n const overlayAnchor = overlaySlot === null ? 'dispatcherOverlay' : 'pathEditingOverlay';\n const pathEditingOverlayLayer = usePathEditOverlayLayer(pathEditState, {\n nodeOf: (id) => scene.get(id as NodeId),\n planeOfNode: args.planeOfNode,\n }, overlayConfig);\n\n const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer<TPose>(\n merged.selectionOverlay,\n pathEditState.getEditingIds,\n poseDescriptor,\n args.boundsIncludeStroke ? drawnBoundsScene(scene) : undefined,\n resizeHandles,\n );\n\n const enteredFrameLayer = useEnteredGroupFrameLayer(args.enteredGroup, args.boundsOf, args.frameColor);\n const layers = useMemo<CanvasLayers>(() => ({\n ...builtLayersOf(merged),\n scene: sceneLayers,\n selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,\n enteredGroupFrame: { layer: enteredFrameLayer, before: 'selectionOverlay' },\n departureGhost: { layer: departureLayer, after: 'scene' },\n previewGhost: { layer: previewLayer, after: 'departureGhost' },\n effectSources: { layer: effectSourcesLayer, after: 'previewGhost' },\n dispatcherOverlay: { layer: dispatcherOverlay, after: 'effectSources' },\n pathEditingOverlay: overlaySlot === null || isCustomEntry(overlaySlot)\n ? overlaySlot\n : { layer: pathEditingOverlayLayer, after: 'selectionOverlay' },\n ...(args.snapReadout ? { snapReadout: { layer: snapReadoutLayer, after: overlayAnchor } } : {}),\n ...(slops ? { slopsDebug: { layer: slopsLayer, after: args.snapReadout ? 'snapReadout' : overlayAnchor } } : {}),\n }), [merged, sceneLayers, selectionOverlayLayer, enteredFrameLayer, departureLayer, previewLayer, effectSourcesLayer, dispatcherOverlay, pathEditingOverlayLayer, overlaySlot, overlayAnchor, snapReadoutLayer, args.snapReadout, slops, slopsLayer,\n ]);\n\n return { layers, rotationBadge, resizeHandles, chromeAffordancesRef, snapStore, departures };\n}\n"],"mappings":"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"}
@@ -6,6 +6,7 @@ function useSceneCanvasTools(args) {
6
6
  const { props, select, enabled } = args;
7
7
  const { tools, internal, takeover } = useCanvasTools({
8
8
  select,
9
+ groupEntry: enabled.has("select") ? args.groupEntry : null,
9
10
  enabled,
10
11
  tools: props.tools,
11
12
  defaultTools: props.defaultTools,
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasTools.ts"],"sourcesContent":["import type { Tool } from '../../tools/overlayBinding';\nimport type { EnabledPresets, FeatureBindingOptions, PresetSource } from './features';\nimport type { SceneCanvasProps } from './props';\nimport { useCanvasTools, useFeatureBindings } from './useCanvasTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** The `<SceneCanvas>` props its tools and preset bindings read. */\nexport type SceneCanvasToolProps = Pick<\n Props,\n | 'tools' | 'defaultTools' | 'toolOptions' | 'viewport' | 'ambient' | 'longPress'\n | 'initialActiveTool' | 'modes' | 'onToolsCreated' | 'selectTool' | 'actions'\n>;\n\n/** The canvas's tools, and the contributions and standard actions its presets bring. */\nexport function useSceneCanvasTools(args: {\n props: SceneCanvasToolProps;\n select: Tool;\n /** The select tool's move options, which the selection's `move` binding takes. */\n move: unknown;\n enabled: EnabledPresets;\n presetSource: PresetSource;\n}) {\n const { props, select, enabled } = args;\n const { tools, internal, takeover } = useCanvasTools({\n select,\n enabled,\n tools: props.tools,\n defaultTools: props.defaultTools,\n toolOptions: props.toolOptions,\n inertia: props.viewport?.inertia,\n ambient: props.ambient,\n actions: props.actions,\n pressFeedback: props.longPress?.feedback,\n initialActiveTool: props.initialActiveTool,\n modes: props.modes,\n onToolsCreated: props.onToolsCreated,\n });\n const { contributions, standardActions } = useFeatureBindings({\n enabled, pool: args.presetSource.pool,\n tools,\n move: args.move as FeatureBindingOptions['move'],\n rotate: (props.selectTool?.rotate || undefined) as FeatureBindingOptions['rotate'],\n });\n return { tools, internal, takeover, contributions, standardActions };\n}\n"],"mappings":";;;;AAeA,SAAgB,oBAAoB,MAOjC;CACD,MAAM,EAAE,OAAO,QAAQ,YAAY;CACnC,MAAM,EAAE,OAAO,UAAU,aAAa,eAAe;EACnD;EACA;EACA,OAAO,MAAM;EACb,cAAc,MAAM;EACpB,aAAa,MAAM;EACnB,SAAS,MAAM,UAAU;EACzB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,eAAe,MAAM,WAAW;EAChC,mBAAmB,MAAM;EACzB,OAAO,MAAM;EACb,gBAAgB,MAAM;CACxB,CAAC;CACD,MAAM,EAAE,eAAe,oBAAoB,mBAAmB;EAC5D;EAAS,MAAM,KAAK,aAAa;EACjC;EACA,MAAM,KAAK;EACX,QAAS,MAAM,YAAY,UAAU;CACvC,CAAC;CACD,OAAO;EAAE;EAAO;EAAU;EAAU;EAAe;CAAgB;AACrE"}
1
+ {"version":3,"file":"useSceneCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasTools.ts"],"sourcesContent":["import type { Contribution, Tool } from '../../tools/overlayBinding';\nimport type { EnabledPresets, FeatureBindingOptions, PresetSource } from './features';\nimport type { SceneCanvasProps } from './props';\nimport { useCanvasTools, useFeatureBindings } from './useCanvasTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** The `<SceneCanvas>` props its tools and preset bindings read. */\nexport type SceneCanvasToolProps = Pick<\n Props,\n | 'tools' | 'defaultTools' | 'toolOptions' | 'viewport' | 'ambient' | 'longPress'\n | 'initialActiveTool' | 'modes' | 'onToolsCreated' | 'selectTool' | 'actions'\n>;\n\n/** The canvas's tools, and the contributions and standard actions its presets bring. */\nexport function useSceneCanvasTools(args: {\n props: SceneCanvasToolProps;\n select: Tool;\n /** The select tool's move options, which the selection's `move` binding takes. */\n move: unknown;\n enabled: EnabledPresets;\n presetSource: PresetSource;\n /** Entering and leaving groups, installed with the `select` preset. */\n groupEntry: Contribution;\n}) {\n const { props, select, enabled } = args;\n const { tools, internal, takeover } = useCanvasTools({\n select,\n groupEntry: enabled.has('select') ? args.groupEntry : null,\n enabled,\n tools: props.tools,\n defaultTools: props.defaultTools,\n toolOptions: props.toolOptions,\n inertia: props.viewport?.inertia,\n ambient: props.ambient,\n actions: props.actions,\n pressFeedback: props.longPress?.feedback,\n initialActiveTool: props.initialActiveTool,\n modes: props.modes,\n onToolsCreated: props.onToolsCreated,\n });\n const { contributions, standardActions } = useFeatureBindings({\n enabled, pool: args.presetSource.pool,\n tools,\n move: args.move as FeatureBindingOptions['move'],\n rotate: (props.selectTool?.rotate || undefined) as FeatureBindingOptions['rotate'],\n });\n return { tools, internal, takeover, contributions, standardActions };\n}\n"],"mappings":";;;;AAeA,SAAgB,oBAAoB,MASjC;CACD,MAAM,EAAE,OAAO,QAAQ,YAAY;CACnC,MAAM,EAAE,OAAO,UAAU,aAAa,eAAe;EACnD;EACA,YAAY,QAAQ,IAAI,QAAQ,IAAI,KAAK,aAAa;EACtD;EACA,OAAO,MAAM;EACb,cAAc,MAAM;EACpB,aAAa,MAAM;EACnB,SAAS,MAAM,UAAU;EACzB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,eAAe,MAAM,WAAW;EAChC,mBAAmB,MAAM;EACzB,OAAO,MAAM;EACb,gBAAgB,MAAM;CACxB,CAAC;CACD,MAAM,EAAE,eAAe,oBAAoB,mBAAmB;EAC5D;EAAS,MAAM,KAAK,aAAa;EACjC;EACA,MAAM,KAAK;EACX,QAAS,MAAM,YAAY,UAAU;CACvC,CAAC;CACD,OAAO;EAAE;EAAO;EAAU;EAAU;EAAe;CAAgB;AACrE"}
@@ -1,13 +1,16 @@
1
1
  import { useOptionalViewRegistry } from "../viewRegistry.js";
2
2
  import { asNodeId } from "../../core/scene/types.js";
3
+ import { useEnteredGroup } from "../deps/enteredGroup.js";
3
4
  import { useViewLayerGate } from "../useViewLayerGate.js";
4
5
  import { composeAlphaFor } from "../sceneSlotWiring.js";
5
6
  import { doubleClickTarget } from "../doubleClickTarget.js";
7
+ import { createEnteredGroupScope } from "../../features/selection/enteredGroupScope.js";
6
8
  import { useSceneAdapter } from "../useSceneAdapter.js";
7
9
  import { useNodeAtPoint } from "./useNodeAtPoint.js";
8
10
  import { useSceneSelectTool } from "./useSceneSelectTool.js";
9
- import { useMemo } from "react";
11
+ import { useCallback, useMemo, useSyncExternalStore } from "react";
10
12
  import { useLatest } from "@weasel-js/react";
13
+ import { useOptionalDepRegistry } from "@weasel-js/routing/react";
11
14
 
12
15
  //#region src/canvas/SceneCanvas/useScenePicking.ts
13
16
  /**
@@ -19,11 +22,31 @@ function useScenePicking(args) {
19
22
  const { geometry, alphaFor, layerVisibility, layerOrder, isPointerInteractive } = args.props;
20
23
  const { kindOf, kindOfNode } = args.kinds;
21
24
  const composedAlphaFor = useMemo(() => composeAlphaFor(scene, alphaFor), [scene, alphaFor]);
25
+ const ownEntered = useEnteredGroup(scene);
26
+ const enteredGroup = args.props.enteredGroup ?? ownEntered;
27
+ const consumerInteractive = useLatest(isPointerInteractive);
28
+ const filtering = useSyncExternalStore(enteredGroup.subscribe, () => enteredGroup.get() !== null) || isPointerInteractive !== void 0;
29
+ const reachable = useMemo(() => {
30
+ if (!filtering) return void 0;
31
+ const scope = createEnteredGroupScope({
32
+ entered: enteredGroup,
33
+ scene
34
+ });
35
+ return (id) => scope.isPointerInteractive(id) && consumerInteractive.current?.(id) !== false;
36
+ }, [
37
+ filtering,
38
+ enteredGroup,
39
+ scene,
40
+ consumerInteractive
41
+ ]);
22
42
  const surfaceViewRegistry = useOptionalViewRegistry();
23
43
  const viewLayerGate = useViewLayerGate(layerVisibility, layerOrder, () => surfaceViewRegistry?.surface()?.layers() ?? []);
44
+ const depRegistry = useOptionalDepRegistry();
45
+ const pasteInto = useCallback(() => depRegistry?.get("insertInto")?.get() ?? null, [depRegistry]);
24
46
  const adapter = useSceneAdapter(scene, {
25
47
  selection: selection.adapterMethods,
26
- poseDescriptor: descriptor
48
+ poseDescriptor: descriptor,
49
+ pasteInto
27
50
  });
28
51
  const selectTool = useSceneSelectTool({
29
52
  scene,
@@ -33,7 +56,7 @@ function useScenePicking(args) {
33
56
  getView: () => viewRef.current,
34
57
  ...alphaFor ? { alphaOf: composedAlphaFor } : {},
35
58
  ...viewLayerGate ? { layerIsPainted: viewLayerGate } : {},
36
- ...isPointerInteractive ? { isPointerInteractive } : {},
59
+ ...reachable ? { isPointerInteractive: reachable } : {},
37
60
  selectTool: args.selectTool
38
61
  });
39
62
  const getNodeAtPoint = useNodeAtPoint({
@@ -41,15 +64,14 @@ function useScenePicking(args) {
41
64
  nodes: adapter,
42
65
  kindOf
43
66
  });
44
- const isolatedGroup = useLatest(args.props.isolatedGroup);
45
67
  const doubleClickNames = useMemo(() => kindOfNode && ((hitId) => doubleClickTarget(hitId, {
46
68
  parentOf: (id) => scene.get(asNodeId(id))?.parent ?? null,
47
69
  kindOf: kindOfNode,
48
- scope: isolatedGroup.current?.() ?? null
70
+ scope: enteredGroup.get()
49
71
  })), [
50
72
  scene,
51
73
  kindOfNode,
52
- isolatedGroup
74
+ enteredGroup
53
75
  ]);
54
76
  return {
55
77
  ...selectTool,
@@ -58,6 +80,9 @@ function useScenePicking(args) {
58
80
  doubleClickNames,
59
81
  getNodeAtPoint,
60
82
  viewLayerGate,
83
+ enteredGroup,
84
+ /** What a press or a region select can reach, or undefined when that is every node. */
85
+ reachable,
61
86
  /** The composed paint alpha, only when the consumer fades anything. */
62
87
  alphaOf: alphaFor ? composedAlphaFor : void 0
63
88
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useScenePicking.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useScenePicking.ts"],"sourcesContent":["import { useMemo, type RefObject } from 'react';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport { useLatest } from '@weasel-js/react';\nimport { doubleClickTarget } from '../doubleClickTarget';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { composeAlphaFor } from '../sceneSlotWiring';\nimport { useSceneAdapter } from '../useSceneAdapter';\nimport type { SceneCanvasProps } from './props';\nimport { useNodeAtPoint } from './useNodeAtPoint';\nimport type { NodeKinds } from './useNodeKinds';\nimport { useSceneSelectTool, type UseSceneSelectToolArgs } from './useSceneSelectTool';\nimport { useOptionalViewRegistry } from '../viewRegistry';\nimport { useViewLayerGate } from '../useViewLayerGate';\n\ntype PickingProps<TData, TLayer extends string, TPose> = Pick<\n SceneCanvasProps<TData, TLayer, TPose>,\n 'geometry' | 'alphaFor' | 'layerVisibility' | 'layerOrder' | 'isPointerInteractive' | 'isolatedGroup'\n>;\n\n/**\n * The canvas's adapter and everything that answers \"what is under this point\": the select\n * tool's pickers and bounds, the node kinds, and the paint gates picking shares with the painter.\n */\nexport function useScenePicking<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n descriptor: PoseDescriptor<TPose>;\n props: PickingProps<TData, TLayer, TPose>;\n viewRef: RefObject<View>;\n selectTool: UseSceneSelectToolArgs<TData, TLayer, TPose>['selectTool'];\n kinds: NodeKinds<TData>;\n}) {\n const { scene, selection, descriptor, viewRef } = args;\n const { geometry, alphaFor, layerVisibility, layerOrder, isPointerInteractive } = args.props;\n const { kindOf, kindOfNode } = args.kinds;\n\n // The painted alpha of a node in this view: the caller's `alphaFor` times any per-node\n // override alpha. The pick path reads the same number the painter does, or a node faded\n // to nothing stays clickable.\n const composedAlphaFor = useMemo(\n () => composeAlphaFor(scene, alphaFor),\n [scene, alphaFor],\n );\n const surfaceViewRegistry = useOptionalViewRegistry();\n const viewLayerGate = useViewLayerGate(\n layerVisibility, layerOrder, () => surfaceViewRegistry?.surface()?.layers() ?? [],\n );\n\n const adapter = useSceneAdapter(scene, {\n // `adapterMethods` is memoized; `selection` itself is a fresh object each render.\n selection: selection.adapterMethods,\n poseDescriptor: descriptor,\n });\n\n const selectTool = useSceneSelectTool({\n scene,\n adapter,\n poseDescriptor: descriptor,\n geometry,\n // The pick tolerance is declared in screen pixels; this is what converts it.\n getView: () => viewRef.current,\n // Only when the consumer actually fades something. Without a prop the\n // composed function is just the override lookup, and the source resolves\n // that once per walk instead of once per candidate.\n ...(alphaFor ? { alphaOf: composedAlphaFor } : {}),\n ...(viewLayerGate ? { layerIsPainted: viewLayerGate } : {}),\n ...(isPointerInteractive ? { isPointerInteractive } : {}),\n selectTool: args.selectTool,\n });\n\n const getNodeAtPoint = useNodeAtPoint({\n pickBest: selectTool.pickBest, nodes: adapter, kindOf,\n });\n\n const isolatedGroup = useLatest(args.props.isolatedGroup);\n const doubleClickNames = useMemo(() => kindOfNode && ((hitId: string) => doubleClickTarget(hitId, {\n parentOf: (id) => scene.get(asNodeId(id))?.parent ?? null,\n kindOf: kindOfNode,\n scope: isolatedGroup.current?.() ?? null,\n })), [scene, kindOfNode, isolatedGroup]);\n\n return {\n ...selectTool, adapter, kindOfNode, doubleClickNames, getNodeAtPoint, viewLayerGate,\n /** The composed paint alpha, only when the consumer fades anything. */\n alphaOf: alphaFor ? composedAlphaFor : undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,gBAAqD,MAQlE;CACD,MAAM,EAAE,OAAO,WAAW,YAAY,YAAY;CAClD,MAAM,EAAE,UAAU,UAAU,iBAAiB,YAAY,yBAAyB,KAAK;CACvF,MAAM,EAAE,QAAQ,eAAe,KAAK;CAKpC,MAAM,mBAAmB,cACjB,gBAAgB,OAAO,QAAQ,GACrC,CAAC,OAAO,QAAQ,CAClB;CACA,MAAM,sBAAsB,wBAAwB;CACpD,MAAM,gBAAgB,iBACpB,iBAAiB,kBAAkB,qBAAqB,QAAQ,CAAC,EAAE,OAAO,KAAK,CAAC,CAClF;CAEA,MAAM,UAAU,gBAAgB,OAAO;EAErC,WAAW,UAAU;EACrB,gBAAgB;CAClB,CAAC;CAED,MAAM,aAAa,mBAAmB;EACpC;EACA;EACA,gBAAgB;EAChB;EAEA,eAAe,QAAQ;EAIvB,GAAI,WAAW,EAAE,SAAS,iBAAiB,IAAI,CAAC;EAChD,GAAI,gBAAgB,EAAE,gBAAgB,cAAc,IAAI,CAAC;EACzD,GAAI,uBAAuB,EAAE,qBAAqB,IAAI,CAAC;EACvD,YAAY,KAAK;CACnB,CAAC;CAED,MAAM,iBAAiB,eAAe;EACpC,UAAU,WAAW;EAAU,OAAO;EAAS;CACjD,CAAC;CAED,MAAM,gBAAgB,UAAU,KAAK,MAAM,aAAa;CACxD,MAAM,mBAAmB,cAAc,gBAAgB,UAAkB,kBAAkB,OAAO;EAChG,WAAW,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE,UAAU;EACrD,QAAQ;EACR,OAAO,cAAc,UAAU,KAAK;CACtC,CAAC,IAAI;EAAC;EAAO;EAAY;CAAa,CAAC;CAEvC,OAAO;EACL,GAAG;EAAY;EAAS;EAAY;EAAkB;EAAgB;;EAEtE,SAAS,WAAW,mBAAmB;CACzC;AACF"}
1
+ {"version":3,"file":"useScenePicking.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useScenePicking.ts"],"sourcesContent":["import { useCallback, useMemo, useSyncExternalStore, type RefObject } from 'react';\nimport { useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport { useLatest } from '@weasel-js/react';\nimport { doubleClickTarget } from '../doubleClickTarget';\nimport { useEnteredGroup } from '../deps/enteredGroup';\nimport { createEnteredGroupScope } from 'features/selection/enteredGroupScope';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { composeAlphaFor } from '../sceneSlotWiring';\nimport { useSceneAdapter } from '../useSceneAdapter';\nimport type { SceneCanvasProps } from './props';\nimport { useNodeAtPoint } from './useNodeAtPoint';\nimport type { NodeKinds } from './useNodeKinds';\nimport { useSceneSelectTool, type UseSceneSelectToolArgs } from './useSceneSelectTool';\nimport { useOptionalViewRegistry } from '../viewRegistry';\nimport { useViewLayerGate } from '../useViewLayerGate';\n\ntype PickingProps<TData, TLayer extends string, TPose> = Pick<\n SceneCanvasProps<TData, TLayer, TPose>,\n 'geometry' | 'alphaFor' | 'layerVisibility' | 'layerOrder' | 'isPointerInteractive' | 'enteredGroup'\n>;\n\n/**\n * The canvas's adapter and everything that answers \"what is under this point\": the select\n * tool's pickers and bounds, the node kinds, and the paint gates picking shares with the painter.\n */\nexport function useScenePicking<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n descriptor: PoseDescriptor<TPose>;\n props: PickingProps<TData, TLayer, TPose>;\n viewRef: RefObject<View>;\n selectTool: UseSceneSelectToolArgs<TData, TLayer, TPose>['selectTool'];\n kinds: NodeKinds<TData>;\n}) {\n const { scene, selection, descriptor, viewRef } = args;\n const { geometry, alphaFor, layerVisibility, layerOrder, isPointerInteractive } = args.props;\n const { kindOf, kindOfNode } = args.kinds;\n\n // The painted alpha of a node in this view: the caller's `alphaFor` times any per-node\n // override alpha. The pick path reads the same number the painter does, or a node faded\n // to nothing stays clickable.\n const composedAlphaFor = useMemo(\n () => composeAlphaFor(scene, alphaFor),\n [scene, alphaFor],\n );\n const ownEntered = useEnteredGroup(scene);\n const enteredGroup = args.props.enteredGroup ?? ownEntered;\n // A press resolves inside the entered group, so nothing outside it takes a hit. With nothing\n // entered and no consumer predicate there is nothing to filter, and the walk runs bare.\n const consumerInteractive = useLatest(isPointerInteractive);\n const anyEntered = useSyncExternalStore(enteredGroup.subscribe, () => enteredGroup.get() !== null);\n const filtering = anyEntered || isPointerInteractive !== undefined;\n const reachable = useMemo(() => {\n if (!filtering) return undefined;\n const scope = createEnteredGroupScope({ entered: enteredGroup, scene });\n return (id: string) => scope.isPointerInteractive(id) && consumerInteractive.current?.(id) !== false;\n }, [filtering, enteredGroup, scene, consumerInteractive]);\n\n const surfaceViewRegistry = useOptionalViewRegistry();\n const viewLayerGate = useViewLayerGate(\n layerVisibility, layerOrder, () => surfaceViewRegistry?.surface()?.layers() ?? [],\n );\n\n const depRegistry = useOptionalDepRegistry();\n const pasteInto = useCallback(() => depRegistry?.get('insertInto')?.get() ?? null, [depRegistry]);\n const adapter = useSceneAdapter(scene, {\n // `adapterMethods` is memoized; `selection` itself is a fresh object each render.\n selection: selection.adapterMethods,\n poseDescriptor: descriptor,\n pasteInto,\n });\n\n const selectTool = useSceneSelectTool({\n scene,\n adapter,\n poseDescriptor: descriptor,\n geometry,\n // The pick tolerance is declared in screen pixels; this is what converts it.\n getView: () => viewRef.current,\n // Only when the consumer actually fades something. Without a prop the\n // composed function is just the override lookup, and the source resolves\n // that once per walk instead of once per candidate.\n ...(alphaFor ? { alphaOf: composedAlphaFor } : {}),\n ...(viewLayerGate ? { layerIsPainted: viewLayerGate } : {}),\n ...(reachable ? { isPointerInteractive: reachable } : {}),\n selectTool: args.selectTool,\n });\n\n const getNodeAtPoint = useNodeAtPoint({\n pickBest: selectTool.pickBest, nodes: adapter, kindOf,\n });\n\n const doubleClickNames = useMemo(() => kindOfNode && ((hitId: string) => doubleClickTarget(hitId, {\n parentOf: (id) => scene.get(asNodeId(id))?.parent ?? null,\n kindOf: kindOfNode,\n scope: enteredGroup.get(),\n })), [scene, kindOfNode, enteredGroup]);\n\n return {\n ...selectTool, adapter, kindOfNode, doubleClickNames, getNodeAtPoint, viewLayerGate, enteredGroup,\n /** What a press or a region select can reach, or undefined when that is every node. */\n reachable,\n /** The composed paint alpha, only when the consumer fades anything. */\n alphaOf: alphaFor ? composedAlphaFor : undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,gBAAqD,MAQlE;CACD,MAAM,EAAE,OAAO,WAAW,YAAY,YAAY;CAClD,MAAM,EAAE,UAAU,UAAU,iBAAiB,YAAY,yBAAyB,KAAK;CACvF,MAAM,EAAE,QAAQ,eAAe,KAAK;CAKpC,MAAM,mBAAmB,cACjB,gBAAgB,OAAO,QAAQ,GACrC,CAAC,OAAO,QAAQ,CAClB;CACA,MAAM,aAAa,gBAAgB,KAAK;CACxC,MAAM,eAAe,KAAK,MAAM,gBAAgB;CAGhD,MAAM,sBAAsB,UAAU,oBAAoB;CAE1D,MAAM,YADa,qBAAqB,aAAa,iBAAiB,aAAa,IAAI,MAAM,IAC3E,KAAc,yBAAyB;CACzD,MAAM,YAAY,cAAc;EAC9B,IAAI,CAAC,WAAW,OAAO;EACvB,MAAM,QAAQ,wBAAwB;GAAE,SAAS;GAAc;EAAM,CAAC;EACtE,QAAQ,OAAe,MAAM,qBAAqB,EAAE,KAAK,oBAAoB,UAAU,EAAE,MAAM;CACjG,GAAG;EAAC;EAAW;EAAc;EAAO;CAAmB,CAAC;CAExD,MAAM,sBAAsB,wBAAwB;CACpD,MAAM,gBAAgB,iBACpB,iBAAiB,kBAAkB,qBAAqB,QAAQ,CAAC,EAAE,OAAO,KAAK,CAAC,CAClF;CAEA,MAAM,cAAc,uBAAuB;CAC3C,MAAM,YAAY,kBAAkB,aAAa,IAAI,YAAY,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,WAAW,CAAC;CAChG,MAAM,UAAU,gBAAgB,OAAO;EAErC,WAAW,UAAU;EACrB,gBAAgB;EAChB;CACF,CAAC;CAED,MAAM,aAAa,mBAAmB;EACpC;EACA;EACA,gBAAgB;EAChB;EAEA,eAAe,QAAQ;EAIvB,GAAI,WAAW,EAAE,SAAS,iBAAiB,IAAI,CAAC;EAChD,GAAI,gBAAgB,EAAE,gBAAgB,cAAc,IAAI,CAAC;EACzD,GAAI,YAAY,EAAE,sBAAsB,UAAU,IAAI,CAAC;EACvD,YAAY,KAAK;CACnB,CAAC;CAED,MAAM,iBAAiB,eAAe;EACpC,UAAU,WAAW;EAAU,OAAO;EAAS;CACjD,CAAC;CAED,MAAM,mBAAmB,cAAc,gBAAgB,UAAkB,kBAAkB,OAAO;EAChG,WAAW,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE,UAAU;EACrD,QAAQ;EACR,OAAO,aAAa,IAAI;CAC1B,CAAC,IAAI;EAAC;EAAO;EAAY;CAAY,CAAC;CAEtC,OAAO;EACL,GAAG;EAAY;EAAS;EAAY;EAAkB;EAAgB;EAAe;;EAErF;;EAEA,SAAS,WAAW,mBAAmB;CACzC;AACF"}
@@ -3,7 +3,8 @@ import { AUTO_POSE_DESCRIPTOR } from "../../core/geometry/autoPoseDescriptor.js"
3
3
  import { drawnBoundsOf } from "../drawnBounds.js";
4
4
  import { asNodeId } from "../../core/scene/types.js";
5
5
  import { drawnBoundsScene } from "../derivedPath.js";
6
- import { pickWalk, scenePickSource, scenePlaneOf } from "../pickWalk.js";
6
+ import { scenePickSource, scenePlaneOf } from "../scenePickSource.js";
7
+ import { pickWalk } from "../pickWalk.js";
7
8
  import { pickTopMostHit } from "../../tools/builtin/pickTopMostHit.js";
8
9
  import { useSelectTool } from "../../tools/builtin/select/useSelectTool.js";
9
10
  import { scenePointQuery } from "./scenePointQuery.js";
@@ -57,7 +58,6 @@ function useSceneSelectTool(args) {
57
58
  const wiredMoveOptions = useMemo(() => moveOptions ?? {}, [moveOptions]);
58
59
  const wiredHitBody = usePointPick({
59
60
  scene,
60
- adapter,
61
61
  d,
62
62
  pickEveryProp,
63
63
  shapePicking,
@@ -126,7 +126,7 @@ function useSceneSelectTool(args) {
126
126
  /** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose
127
127
  * walk, less whatever `isPointerInteractive` rejects. */
128
128
  function usePointPick(args) {
129
- const { scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;
129
+ const { scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;
130
130
  const walk = useMemo(() => {
131
131
  return (wx, wy, view) => {
132
132
  if (pickEveryProp) {
@@ -139,7 +139,7 @@ function usePointPick(args) {
139
139
  const viewLayers = view?.layerIsPainted ?? layerIsPainted;
140
140
  const camera = cameraOf(asked);
141
141
  const src = scenePickSource(scene, {
142
- getPose: (id) => adapter.getWorldPose(id),
142
+ index: { descriptor: d },
143
143
  ...viewAlpha ? { alphaOf: viewAlpha } : {},
144
144
  ...viewLayers ? { layerIsPainted: viewLayers } : {},
145
145
  ...camera ? { camera } : {}
@@ -152,7 +152,6 @@ function usePointPick(args) {
152
152
  };
153
153
  }, [
154
154
  scene,
155
- adapter,
156
155
  pickEveryProp,
157
156
  shapePicking,
158
157
  pickTolerancePx,
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what picking and bounds read. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // Through the adapter, not `n.pose`: an ephemeral override is the pose\n // the renderer draws, so it has to be the one picking tests. World, not\n // local, for the same reason — a framed child is drawn in its parent's\n // frame, not where its own pose says.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n getPose: (id) => adapter.getWorldPose(id),\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, adapter, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what bounds read, and equal the\n * scene's painted poses, which picking reads. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // No `getPose`: the scene's painted poses are the adapter's world poses\n // (`useSceneSelectTool.index.test.tsx`), and they let the index serve the walk.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n index: { descriptor: d as PoseDescriptor<unknown> },\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { useDecayLoop } from "../../core/viewport/useDecayLoop.js";
2
2
  import { drawnBoxes } from "../../interactions/actions/drawnBoxes.js";
3
+ import { useEnteredGroupApiSource } from "../deps/enteredGroup.js";
3
4
  import { useActionSet, useStandardDeps } from "../../interactions/actions/useActionSet.js";
4
5
  import { usePoseDescriptorDepSource } from "../deps/poseDescriptor.js";
5
6
  import { useViewDepSource } from "../deps/view.js";
@@ -34,7 +35,7 @@ import { useActionsRegistry, useDepSource } from "@weasel-js/routing/react";
34
35
  * parent's effects run after its children's, so a child's override would lose.
35
36
  */
36
37
  function useSceneStandardActions(inputs) {
37
- const { props, viewOn, selection, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
38
+ const { props, viewOn, selection, enteredGroup, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, reachable, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
38
39
  const { tune, viewport, ingestion } = props;
39
40
  const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;
40
41
  const reflowTransition = props.reflowTransition;
@@ -67,9 +68,10 @@ function useSceneStandardActions(inputs) {
67
68
  zoom: viewOn ? viewportZoom : false,
68
69
  pinchZoom: viewOn ? viewport?.pinchZoom ?? true : false
69
70
  });
70
- useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf);
71
+ useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
71
72
  useNodeAtPointDepSource(pickEvery);
72
73
  useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);
74
+ useEnteredGroupApiSource(enteredGroup);
73
75
  useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);
74
76
  usePathLeafDepSources(scene, selection, adapter, poseDescriptor);
75
77
  useSnapDepSource(currentViewRef, {
@@ -92,7 +94,7 @@ function useSceneStandardActions(inputs) {
92
94
  usePreviewPathsDepSource(canvasApiRef);
93
95
  useDepChangeNotices(scene, selection.current, editAnchorsExternalState);
94
96
  useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);
95
- useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf);
97
+ useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
96
98
  useTextEditDepSource(scene);
97
99
  useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);
98
100
  useDispatcherDepSource(dispatcher);
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneStandardActions.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneStandardActions.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepSource } from '@weasel-js/routing/react';\nimport type { Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { useDecayLoop } from 'core/viewport/useDecayLoop';\nimport type { ViewAnimationApi } from 'core/viewport/useViewAnimation';\nimport type { View } from 'core/viewport/view';\nimport type { ClipboardIngestCtx } from 'interactions/actions/depSchema';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { useActionSet, useStandardDeps } from 'interactions/actions/useActionSet';\nimport { drawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport type { ReflowTransition } from '../../layout/types';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport type { SnapSource } from 'features/snap/types';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport {\n useAreaSelectDepSource,\n useDebugDepSource,\n usePreviewPathsDepSource,\n useDepChangeNotices,\n useDispatcherDepSource,\n useEditAnchorsDepSource,\n useIngestionDepSource,\n useInsertDepSource,\n useLassoSelectDepSource,\n useLayoutDepSource,\n useNodeAtPointDepSource,\n usePoseDescriptorDepSource,\n usePathLeafDepSources,\n useSnapDepSource,\n useSurfaceDepSources,\n useTextEditDepSource,\n useViewDepSource,\n} from '../deps';\nimport type { BridgeAdapter, SceneCanvasProps } from './props';\nimport { useActionsPropResolver } from './useActionsPropResolver';\nimport { useActionTunings, useBindingOverrides } from './useActionTunings';\nimport { useViewportActions, viewportZoomOption } from './useViewportActions';\n\n/** The `<SceneCanvas>` props the standard actions and their deps read. */\nexport type StandardActionsProps = Pick<\n SceneCanvasProps<unknown, string, unknown>,\n | 'actions' | 'tune' | 'bindingOverrides' | 'geometry' | 'viewport' | 'layoutDropTarget'\n | 'reflowTransition' | 'insertNodeFactories' | 'insertPaint' | 'ingestion' | 'snap'\n>;\n\n/** What `<SceneCanvas>` hands {@link useSceneStandardActions}. */\nexport interface SceneStandardActionsInputs {\n props: StandardActionsProps;\n /** Whether the `view` preset is on; without it the viewport actions are off. */\n viewOn: boolean;\n selection: SelectionApi;\n scene: Scene<unknown, string, unknown>;\n adapter: BridgeAdapter;\n /** The standard actions this canvas registers: its presets' and the ones\n * its tools bind, in registration order. */\n standardActions: readonly Action[];\n currentViewRef: React.RefObject<View>;\n onViewChange: (v: View) => void;\n /** Wires the `poseDescriptor` dep every built-in pose-touching action reads. */\n poseDescriptor: PoseDescriptor<unknown>;\n /** Forwarded so `cancelGestureAction` and other actions that need to\n * abort in-flight handles can read the dispatcher's control surface. */\n dispatcher: Dispatcher;\n /** World-space picker forwarded so the `nodeAtPoint` dep source can\n * reuse the same hit-test plumbing the tool dispatcher uses. */\n pickEvery: (worldX: number, worldY: number) => string[];\n /** This canvas's own view gate for scene layers, published on the `view`\n * dep and read by the selecting actions. Absent when it hides nothing. */\n layerIsPainted?: (layerId: string) => boolean;\n /** The composed paint alpha, when the consumer fades anything, so a region\n * select passes over what is painted at zero. */\n alphaOf?: (id: string) => number;\n /** The canvas's camera runner, published on the `view` dep so\n * `viewport.zoom`'s discrete branches can animate. */\n viewAnimation: ViewAnimationApi;\n /** Lifted edit-mode state so the `pathEditingOverlay` chrome (rendered\n * outside this subtree) can read the same `editingId` the dep does. */\n editAnchorsExternalState: import('../deps/editAnchors').EditAnchorsStateSource;\n /** The snap sources a session asks: the prop's and the presets', then the registry's. */\n snap: { sources: readonly SnapSource[]; registry: SnapSourceRegistry | null; store: SnapOutcomeStore };\n /** The canvas element ref, so `useIngestionDepSource` can compute the\n * visible world rect from the client rect + current view. */\n canvasRef: React.RefObject<HTMLElement | null>;\n /** The canvas handle, whose debug sink backs the `debug` dep. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n /** Clipboard-paste ctx (this canvas's adapter + the consumer's reviver)\n * for the kit weasel-JSON handler, from `useCanvasIngestion`; absent\n * when disabled. */\n ingestionClipboard?: ClipboardIngestCtx;\n}\n\n/**\n * Registers this canvas's standard actions, and the deps they read, into its\n * input scope, then applies the `actions` prop and `tune` over them.\n *\n * Called from a component ahead of `<SceneCanvas>`'s children, never from the\n * canvas's own body: registrations stack newest-live in effect order, and a\n * parent's effects run after its children's, so a child's override would lose.\n */\nexport function useSceneStandardActions(inputs: SceneStandardActionsInputs): void {\n const {\n props, viewOn, selection, scene, adapter, standardActions, currentViewRef,\n onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, viewAnimation,\n editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard,\n } = inputs;\n const { tune, viewport, ingestion } = props;\n const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;\n const reflowTransition = props.reflowTransition as ReflowTransition<unknown> | null | undefined;\n const viewportZoom = useMemo(\n () => viewportZoomOption(viewport?.zoom, viewport?.animatedZoom),\n [viewport?.zoom, viewport?.animatedZoom],\n );\n const registry = useActionsRegistry();\n\n // Wire the dispatcher into the registry so registry.begin() can delegate\n // to dispatcher.beginUiOngoing() for UI-driven ongoing actions (color,\n // opacity).\n useEffect(() => {\n if (!registry) return;\n return registry.setDispatcher(dispatcher);\n }, [registry, dispatcher]);\n\n // Momentum for `viewport.dragPan`. The loop must be a hook (it owns a\n // `useVisibleRaf`), so it is built here and republished on the view dep.\n const decayLoop = useDecayLoop();\n\n // Build the ViewApi (stable identity, refreshed closures) and hand it to\n // useStandardDeps (which publishes the `view` dep along with selection,\n // scene, history, activeTool). `hostSize` reads the live canvas\n // element so keyboard zoom (Cmd+=/-) can anchor at the visible center.\n const view = useViewDepSource(\n currentViewRef,\n onViewChange,\n viewport?.recenter,\n () => {\n const el = canvasRef.current;\n return el ? { width: el.clientWidth, height: el.clientHeight } : null;\n },\n viewAnimation,\n decayLoop,\n layerIsPainted,\n );\n useStandardDeps({ selection, scene, view, history: scene.history });\n useActionSet(standardActions);\n // A view overlays `view` with its own camera; this name it leaves alone.\n useDepSource('rootView', () => view);\n useSurfaceDepSources(canvasRef, currentViewRef);\n useDepSource('actionParams', () => registry?.paramsOf);\n\n // viewport.pan / viewport.zoom are SceneCanvas-coupled (need the `view` dep\n // published just above), so they're registered here rather than in\n // KIT_STANDARD_DESCRIPTORS. Both default ON; consumer opts out via\n // `viewport={{ pan: false }}` / `viewport={{ zoom: false }}`.\n useViewportActions({\n pan: viewOn ? (viewport?.pan ?? true) : false,\n zoom: viewOn ? viewportZoom : false,\n pinchZoom: viewOn ? (viewport?.pinchZoom ?? true) : false,\n });\n\n // Per-dep wiring modules under `src/canvas/deps/`. See each file for the\n // dep's contract and trade-offs.\n useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf);\n useNodeAtPointDepSource(pickEvery);\n useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);\n useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);\n usePathLeafDepSources(scene, selection, adapter, poseDescriptor);\n useSnapDepSource(\n currentViewRef,\n {\n scene, boxes: () => drawnBoxes({ scene, poseDescriptor, view, boundsIncludeStroke }), sources: snap.sources,\n ...(snap.registry ? { registry: snap.registry } : {}),\n ...(props.snap?.reachPx !== undefined ? { reachPx: props.snap.reachPx } : {}),\n ...(props.snap?.omit ? { omit: props.snap.omit } : {}),\n ...(props.snap?.originProjection ? { originProjection: props.snap.originProjection } : {}),\n poseDescriptor,\n debug: () => canvasApiRef.current?.getDebug(),\n },\n snap.store,\n );\n useDebugDepSource(canvasApiRef);\n usePreviewPathsDepSource(canvasApiRef);\n useDepChangeNotices(scene, selection.current, editAnchorsExternalState);\n useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);\n useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf);\n useTextEditDepSource(scene);\n useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);\n useDispatcherDepSource(dispatcher);\n usePoseDescriptorDepSource(poseDescriptor);\n useDepSource('boundsIncludeStroke', () => boundsIncludeStroke);\n\n // Keyed on the set too: a re-registration of it would otherwise land over\n // the overrides applied on top of it.\n useActionsPropResolver(props.actions, standardActions.map((a) => a.id).join('|'));\n useActionTunings(tune);\n useBindingOverrides(props.bindingOverrides);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA,SAAgB,wBAAwB,QAA0C;CAChF,MAAM,EACJ,OAAO,QAAQ,WAAW,OAAO,SAAS,iBAAiB,gBAC3D,cAAc,gBAAgB,YAAY,WAAW,gBAAgB,SAAS,eAC9E,0BAA0B,MAAM,WAAW,cAAc,uBACvD;CACJ,MAAM,EAAE,MAAM,UAAU,cAAc;CACtC,MAAM,sBAAsB,MAAM,UAAU,uBAAuB;CACnE,MAAM,mBAAmB,MAAM;CAC/B,MAAM,eAAe,cACb,mBAAmB,UAAU,MAAM,UAAU,YAAY,GAC/D,CAAC,UAAU,MAAM,UAAU,YAAY,CACzC;CACA,MAAM,WAAW,mBAAmB;CAKpC,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,OAAO,SAAS,cAAc,UAAU;CAC1C,GAAG,CAAC,UAAU,UAAU,CAAC;CAIzB,MAAM,YAAY,aAAa;CAM/B,MAAM,OAAO,iBACX,gBACA,cACA,UAAU,gBACJ;EACJ,MAAM,KAAK,UAAU;EACrB,OAAO,KAAK;GAAE,OAAO,GAAG;GAAa,QAAQ,GAAG;EAAa,IAAI;CACnE,GACA,eACA,WACA,cACF;CACA,gBAAgB;EAAE;EAAW;EAAO;EAAM,SAAS,MAAM;CAAQ,CAAC;CAClE,aAAa,eAAe;CAE5B,aAAa,kBAAkB,IAAI;CACnC,qBAAqB,WAAW,cAAc;CAC9C,aAAa,sBAAsB,UAAU,QAAQ;CAMrD,mBAAmB;EACjB,KAAK,SAAU,UAAU,OAAO,OAAQ;EACxC,MAAM,SAAS,eAAe;EAC9B,WAAW,SAAU,UAAU,aAAa,OAAQ;CACtD,CAAC;CAID,uBAAuB,OAAO,WAAW,gBAAgB,OAAO;CAChE,wBAAwB,SAAS;CACjC,mBAAmB,OAAO,MAAM,kBAAkB,gBAAgB;CAClE,mBAAmB,OAAO,SAAS,MAAM,qBAAqB,MAAM,WAAW;CAC/E,sBAAsB,OAAO,WAAW,SAAS,cAAc;CAC/D,iBACE,gBACA;EACE;EAAO,aAAa,WAAW;GAAE;GAAO;GAAgB;GAAM;EAAoB,CAAC;EAAG,SAAS,KAAK;EACpG,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACnD,GAAI,MAAM,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,KAAK,QAAQ,IAAI,CAAC;EAC3E,GAAI,MAAM,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK,IAAI,CAAC;EACpD,GAAI,MAAM,MAAM,mBAAmB,EAAE,kBAAkB,MAAM,KAAK,iBAAiB,IAAI,CAAC;EACxF;EACA,aAAa,aAAa,SAAS,SAAS;CAC9C,GACA,KAAK,KACP;CACA,kBAAkB,YAAY;CAC9B,yBAAyB,YAAY;CACrC,oBAAoB,OAAO,UAAU,SAAS,wBAAwB;CACtE,sBAAsB,iBAAiB,eAAe,SAAS,WAAW,YAAY,WAAW,KAAK,kBAAkB;CACxH,wBAAwB,OAAO,WAAW,gBAAgB,OAAO;CACjE,qBAAqB,KAAK;CAC1B,wBAAwB,OAAO,WAAW,SAAS,0BAA0B,IAAI;CACjF,uBAAuB,UAAU;CACjC,2BAA2B,cAAc;CACzC,aAAa,6BAA6B,mBAAmB;CAI7D,uBAAuB,MAAM,SAAS,gBAAgB,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;CAChF,iBAAiB,IAAI;CACrB,oBAAoB,MAAM,gBAAgB;AAC5C"}
1
+ {"version":3,"file":"useSceneStandardActions.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneStandardActions.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepSource } from '@weasel-js/routing/react';\nimport type { Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { useDecayLoop } from 'core/viewport/useDecayLoop';\nimport type { ViewAnimationApi } from 'core/viewport/useViewAnimation';\nimport type { View } from 'core/viewport/view';\nimport type { ClipboardIngestCtx } from 'interactions/actions/depSchema';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { useActionSet, useStandardDeps } from 'interactions/actions/useActionSet';\nimport { drawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport type { ReflowTransition } from '../../layout/types';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport type { SnapSource } from 'features/snap/types';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport {\n useAreaSelectDepSource,\n useDebugDepSource,\n usePreviewPathsDepSource,\n useDepChangeNotices,\n useDispatcherDepSource,\n useEditAnchorsDepSource,\n useEnteredGroupApiSource,\n useIngestionDepSource,\n useInsertDepSource,\n useLassoSelectDepSource,\n useLayoutDepSource,\n useNodeAtPointDepSource,\n usePoseDescriptorDepSource,\n usePathLeafDepSources,\n useSnapDepSource,\n useSurfaceDepSources,\n useTextEditDepSource,\n useViewDepSource,\n} from '../deps';\nimport type { EnteredGroupApi } from '../deps/enteredGroup';\nimport type { BridgeAdapter, SceneCanvasProps } from './props';\nimport { useActionsPropResolver } from './useActionsPropResolver';\nimport { useActionTunings, useBindingOverrides } from './useActionTunings';\nimport { useViewportActions, viewportZoomOption } from './useViewportActions';\n\n/** The `<SceneCanvas>` props the standard actions and their deps read. */\nexport type StandardActionsProps = Pick<\n SceneCanvasProps<unknown, string, unknown>,\n | 'actions' | 'tune' | 'bindingOverrides' | 'geometry' | 'viewport' | 'layoutDropTarget'\n | 'reflowTransition' | 'insertNodeFactories' | 'insertPaint' | 'ingestion' | 'snap'\n>;\n\n/** What `<SceneCanvas>` hands {@link useSceneStandardActions}. */\nexport interface SceneStandardActionsInputs {\n props: StandardActionsProps;\n /** Whether the `view` preset is on; without it the viewport actions are off. */\n viewOn: boolean;\n selection: SelectionApi;\n /** The group a press resolves inside of, shared with the picking that names double-clicks. */\n enteredGroup: EnteredGroupApi;\n scene: Scene<unknown, string, unknown>;\n adapter: BridgeAdapter;\n /** The standard actions this canvas registers: its presets' and the ones\n * its tools bind, in registration order. */\n standardActions: readonly Action[];\n currentViewRef: React.RefObject<View>;\n onViewChange: (v: View) => void;\n /** Wires the `poseDescriptor` dep every built-in pose-touching action reads. */\n poseDescriptor: PoseDescriptor<unknown>;\n /** Forwarded so `cancelGestureAction` and other actions that need to\n * abort in-flight handles can read the dispatcher's control surface. */\n dispatcher: Dispatcher;\n /** World-space picker forwarded so the `nodeAtPoint` dep source can\n * reuse the same hit-test plumbing the tool dispatcher uses. */\n pickEvery: (worldX: number, worldY: number) => string[];\n /** This canvas's own view gate for scene layers, published on the `view`\n * dep and read by the selecting actions. Absent when it hides nothing. */\n layerIsPainted?: (layerId: string) => boolean;\n /** The composed paint alpha, when the consumer fades anything, so a region\n * select passes over what is painted at zero. */\n alphaOf?: (id: string) => number;\n /** Whether a node takes hits: what a region select may take, as a press may. */\n reachable?: (id: string) => boolean;\n /** The canvas's camera runner, published on the `view` dep so\n * `viewport.zoom`'s discrete branches can animate. */\n viewAnimation: ViewAnimationApi;\n /** Lifted edit-mode state so the `pathEditingOverlay` chrome (rendered\n * outside this subtree) can read the same `editingId` the dep does. */\n editAnchorsExternalState: import('../deps/editAnchors').EditAnchorsStateSource;\n /** The snap sources a session asks: the prop's and the presets', then the registry's. */\n snap: { sources: readonly SnapSource[]; registry: SnapSourceRegistry | null; store: SnapOutcomeStore };\n /** The canvas element ref, so `useIngestionDepSource` can compute the\n * visible world rect from the client rect + current view. */\n canvasRef: React.RefObject<HTMLElement | null>;\n /** The canvas handle, whose debug sink backs the `debug` dep. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n /** Clipboard-paste ctx (this canvas's adapter + the consumer's reviver)\n * for the kit weasel-JSON handler, from `useCanvasIngestion`; absent\n * when disabled. */\n ingestionClipboard?: ClipboardIngestCtx;\n}\n\n/**\n * Registers this canvas's standard actions, and the deps they read, into its\n * input scope, then applies the `actions` prop and `tune` over them.\n *\n * Called from a component ahead of `<SceneCanvas>`'s children, never from the\n * canvas's own body: registrations stack newest-live in effect order, and a\n * parent's effects run after its children's, so a child's override would lose.\n */\nexport function useSceneStandardActions(inputs: SceneStandardActionsInputs): void {\n const {\n props, viewOn, selection, enteredGroup, scene, adapter, standardActions, currentViewRef,\n onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, reachable, viewAnimation,\n editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard,\n } = inputs;\n const { tune, viewport, ingestion } = props;\n const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;\n const reflowTransition = props.reflowTransition as ReflowTransition<unknown> | null | undefined;\n const viewportZoom = useMemo(\n () => viewportZoomOption(viewport?.zoom, viewport?.animatedZoom),\n [viewport?.zoom, viewport?.animatedZoom],\n );\n const registry = useActionsRegistry();\n\n // Wire the dispatcher into the registry so registry.begin() can delegate\n // to dispatcher.beginUiOngoing() for UI-driven ongoing actions (color,\n // opacity).\n useEffect(() => {\n if (!registry) return;\n return registry.setDispatcher(dispatcher);\n }, [registry, dispatcher]);\n\n // Momentum for `viewport.dragPan`. The loop must be a hook (it owns a\n // `useVisibleRaf`), so it is built here and republished on the view dep.\n const decayLoop = useDecayLoop();\n\n // Build the ViewApi (stable identity, refreshed closures) and hand it to\n // useStandardDeps (which publishes the `view` dep along with selection,\n // scene, history, activeTool). `hostSize` reads the live canvas\n // element so keyboard zoom (Cmd+=/-) can anchor at the visible center.\n const view = useViewDepSource(\n currentViewRef,\n onViewChange,\n viewport?.recenter,\n () => {\n const el = canvasRef.current;\n return el ? { width: el.clientWidth, height: el.clientHeight } : null;\n },\n viewAnimation,\n decayLoop,\n layerIsPainted,\n );\n useStandardDeps({ selection, scene, view, history: scene.history });\n useActionSet(standardActions);\n // A view overlays `view` with its own camera; this name it leaves alone.\n useDepSource('rootView', () => view);\n useSurfaceDepSources(canvasRef, currentViewRef);\n useDepSource('actionParams', () => registry?.paramsOf);\n\n // viewport.pan / viewport.zoom are SceneCanvas-coupled (need the `view` dep\n // published just above), so they're registered here rather than in\n // KIT_STANDARD_DESCRIPTORS. Both default ON; consumer opts out via\n // `viewport={{ pan: false }}` / `viewport={{ zoom: false }}`.\n useViewportActions({\n pan: viewOn ? (viewport?.pan ?? true) : false,\n zoom: viewOn ? viewportZoom : false,\n pinchZoom: viewOn ? (viewport?.pinchZoom ?? true) : false,\n });\n\n // Per-dep wiring modules under `src/canvas/deps/`. See each file for the\n // dep's contract and trade-offs.\n useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);\n useNodeAtPointDepSource(pickEvery);\n useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);\n useEnteredGroupApiSource(enteredGroup);\n useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);\n usePathLeafDepSources(scene, selection, adapter, poseDescriptor);\n useSnapDepSource(\n currentViewRef,\n {\n scene, boxes: () => drawnBoxes({ scene, poseDescriptor, view, boundsIncludeStroke }), sources: snap.sources,\n ...(snap.registry ? { registry: snap.registry } : {}),\n ...(props.snap?.reachPx !== undefined ? { reachPx: props.snap.reachPx } : {}),\n ...(props.snap?.omit ? { omit: props.snap.omit } : {}),\n ...(props.snap?.originProjection ? { originProjection: props.snap.originProjection } : {}),\n poseDescriptor,\n debug: () => canvasApiRef.current?.getDebug(),\n },\n snap.store,\n );\n useDebugDepSource(canvasApiRef);\n usePreviewPathsDepSource(canvasApiRef);\n useDepChangeNotices(scene, selection.current, editAnchorsExternalState);\n useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);\n useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);\n useTextEditDepSource(scene);\n useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);\n useDispatcherDepSource(dispatcher);\n usePoseDescriptorDepSource(poseDescriptor);\n useDepSource('boundsIncludeStroke', () => boundsIncludeStroke);\n\n // Keyed on the set too: a re-registration of it would otherwise land over\n // the overrides applied on top of it.\n useActionsPropResolver(props.actions, standardActions.map((a) => a.id).join('|'));\n useActionTunings(tune);\n useBindingOverrides(props.bindingOverrides);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,SAAgB,wBAAwB,QAA0C;CAChF,MAAM,EACJ,OAAO,QAAQ,WAAW,cAAc,OAAO,SAAS,iBAAiB,gBACzE,cAAc,gBAAgB,YAAY,WAAW,gBAAgB,SAAS,WAAW,eACzF,0BAA0B,MAAM,WAAW,cAAc,uBACvD;CACJ,MAAM,EAAE,MAAM,UAAU,cAAc;CACtC,MAAM,sBAAsB,MAAM,UAAU,uBAAuB;CACnE,MAAM,mBAAmB,MAAM;CAC/B,MAAM,eAAe,cACb,mBAAmB,UAAU,MAAM,UAAU,YAAY,GAC/D,CAAC,UAAU,MAAM,UAAU,YAAY,CACzC;CACA,MAAM,WAAW,mBAAmB;CAKpC,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,OAAO,SAAS,cAAc,UAAU;CAC1C,GAAG,CAAC,UAAU,UAAU,CAAC;CAIzB,MAAM,YAAY,aAAa;CAM/B,MAAM,OAAO,iBACX,gBACA,cACA,UAAU,gBACJ;EACJ,MAAM,KAAK,UAAU;EACrB,OAAO,KAAK;GAAE,OAAO,GAAG;GAAa,QAAQ,GAAG;EAAa,IAAI;CACnE,GACA,eACA,WACA,cACF;CACA,gBAAgB;EAAE;EAAW;EAAO;EAAM,SAAS,MAAM;CAAQ,CAAC;CAClE,aAAa,eAAe;CAE5B,aAAa,kBAAkB,IAAI;CACnC,qBAAqB,WAAW,cAAc;CAC9C,aAAa,sBAAsB,UAAU,QAAQ;CAMrD,mBAAmB;EACjB,KAAK,SAAU,UAAU,OAAO,OAAQ;EACxC,MAAM,SAAS,eAAe;EAC9B,WAAW,SAAU,UAAU,aAAa,OAAQ;CACtD,CAAC;CAID,uBAAuB,OAAO,WAAW,gBAAgB,SAAS,SAAS;CAC3E,wBAAwB,SAAS;CACjC,mBAAmB,OAAO,MAAM,kBAAkB,gBAAgB;CAClE,yBAAyB,YAAY;CACrC,mBAAmB,OAAO,SAAS,MAAM,qBAAqB,MAAM,WAAW;CAC/E,sBAAsB,OAAO,WAAW,SAAS,cAAc;CAC/D,iBACE,gBACA;EACE;EAAO,aAAa,WAAW;GAAE;GAAO;GAAgB;GAAM;EAAoB,CAAC;EAAG,SAAS,KAAK;EACpG,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACnD,GAAI,MAAM,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,KAAK,QAAQ,IAAI,CAAC;EAC3E,GAAI,MAAM,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK,IAAI,CAAC;EACpD,GAAI,MAAM,MAAM,mBAAmB,EAAE,kBAAkB,MAAM,KAAK,iBAAiB,IAAI,CAAC;EACxF;EACA,aAAa,aAAa,SAAS,SAAS;CAC9C,GACA,KAAK,KACP;CACA,kBAAkB,YAAY;CAC9B,yBAAyB,YAAY;CACrC,oBAAoB,OAAO,UAAU,SAAS,wBAAwB;CACtE,sBAAsB,iBAAiB,eAAe,SAAS,WAAW,YAAY,WAAW,KAAK,kBAAkB;CACxH,wBAAwB,OAAO,WAAW,gBAAgB,SAAS,SAAS;CAC5E,qBAAqB,KAAK;CAC1B,wBAAwB,OAAO,WAAW,SAAS,0BAA0B,IAAI;CACjF,uBAAuB,UAAU;CACjC,2BAA2B,cAAc;CACzC,aAAa,6BAA6B,mBAAmB;CAI7D,uBAAuB,MAAM,SAAS,gBAAgB,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;CAChF,iBAAiB,IAAI;CACrB,oBAAoB,MAAM,gBAAgB;AAC5C"}