@weasel-js/core 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/dist/animation/poseTarget.js +46 -15
  3. package/dist/animation/poseTarget.js.map +1 -1
  4. package/dist/animation/poseTransition.js +3 -3
  5. package/dist/animation/poseTransition.js.map +1 -1
  6. package/dist/animation/reflow.js +3 -6
  7. package/dist/animation/reflow.js.map +1 -1
  8. package/dist/animation/rig/bindRig.js +2 -2
  9. package/dist/animation/rig/bindRig.js.map +1 -1
  10. package/dist/animation/rig/skinBinding.js +2 -2
  11. package/dist/animation/rig/skinBinding.js.map +1 -1
  12. package/dist/barrel/canvas.js +5 -3
  13. package/dist/barrel/chrome.js +3 -1
  14. package/dist/barrel/paths.js +3 -2
  15. package/dist/barrel/routing.js +2 -2
  16. package/dist/barrel/scene.js +6 -1
  17. package/dist/barrel/text.js +2 -2
  18. package/dist/canvas/NodeShape.js +1 -0
  19. package/dist/canvas/NodeShape.js.map +1 -1
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  21. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  23. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  25. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  27. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  29. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
  31. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  32. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  33. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  34. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  35. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  37. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  39. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  41. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  43. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  44. package/dist/canvas/SceneCanvas.js +19 -2
  45. package/dist/canvas/SceneCanvas.js.map +1 -1
  46. package/dist/canvas/accentColor.js +26 -3
  47. package/dist/canvas/accentColor.js.map +1 -1
  48. package/dist/canvas/buildSceneLayer.js +1 -1
  49. package/dist/canvas/buildSceneLayer.js.map +1 -1
  50. package/dist/canvas/buildSceneTree.js +14 -20
  51. package/dist/canvas/buildSceneTree.js.map +1 -1
  52. package/dist/canvas/canvasRenderer.js +6 -2
  53. package/dist/canvas/canvasRenderer.js.map +1 -1
  54. package/dist/canvas/copiesAbove.js +1 -1
  55. package/dist/canvas/copiesAbove.js.map +1 -1
  56. package/dist/canvas/defaultDrawOne.js +10 -7
  57. package/dist/canvas/defaultDrawOne.js.map +1 -1
  58. package/dist/canvas/deps/areaSelect.js +5 -2
  59. package/dist/canvas/deps/areaSelect.js.map +1 -1
  60. package/dist/canvas/deps/booleans.js +1 -1
  61. package/dist/canvas/deps/booleans.js.map +1 -1
  62. package/dist/canvas/deps/enteredGroup.js +81 -0
  63. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  64. package/dist/canvas/deps/hitTestArea.js +27 -15
  65. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  66. package/dist/canvas/deps/insert.js +4 -36
  67. package/dist/canvas/deps/insert.js.map +1 -1
  68. package/dist/canvas/deps/insertSite.js +39 -0
  69. package/dist/canvas/deps/insertSite.js.map +1 -0
  70. package/dist/canvas/deps/lassoSelect.js +6 -3
  71. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  72. package/dist/canvas/deps/pathLeaf.js +1 -1
  73. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  74. package/dist/canvas/deps/slice.js +2 -1
  75. package/dist/canvas/deps/slice.js.map +1 -1
  76. package/dist/canvas/derivedPath.js +14 -6
  77. package/dist/canvas/derivedPath.js.map +1 -1
  78. package/dist/canvas/doubleClickTarget.js +9 -11
  79. package/dist/canvas/doubleClickTarget.js.map +1 -1
  80. package/dist/canvas/drawnBounds.js +16 -7
  81. package/dist/canvas/drawnBounds.js.map +1 -1
  82. package/dist/canvas/inheritedPaints.js +22 -11
  83. package/dist/canvas/inheritedPaints.js.map +1 -1
  84. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  85. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  86. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  87. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  88. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  89. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  90. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  91. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  92. package/dist/canvas/ownClip.js +24 -0
  93. package/dist/canvas/ownClip.js.map +1 -0
  94. package/dist/canvas/paintCull.js.map +1 -1
  95. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  96. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  97. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  98. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  99. package/dist/canvas/pickWalk.js +14 -90
  100. package/dist/canvas/pickWalk.js.map +1 -1
  101. package/dist/canvas/poseCascade.js +1 -1
  102. package/dist/canvas/poseCascade.js.map +1 -1
  103. package/dist/canvas/publishPreviews.js +97 -0
  104. package/dist/canvas/publishPreviews.js.map +1 -0
  105. package/dist/canvas/sceneAdapter.js +7 -5
  106. package/dist/canvas/sceneAdapter.js.map +1 -1
  107. package/dist/canvas/sceneClipboard.js +17 -10
  108. package/dist/canvas/sceneClipboard.js.map +1 -1
  109. package/dist/canvas/scenePickReads.js +37 -0
  110. package/dist/canvas/scenePickReads.js.map +1 -0
  111. package/dist/canvas/scenePickSource.js +74 -0
  112. package/dist/canvas/scenePickSource.js.map +1 -0
  113. package/dist/canvas/sceneSlotWiring.js +9 -4
  114. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  115. package/dist/canvas/sceneViewRender.js +13 -8
  116. package/dist/canvas/sceneViewRender.js.map +1 -1
  117. package/dist/canvas/shownNodes.js +15 -0
  118. package/dist/canvas/shownNodes.js.map +1 -0
  119. package/dist/canvas/useCanvasPaint.js +3 -1
  120. package/dist/canvas/useCanvasPaint.js.map +1 -1
  121. package/dist/canvas/useSceneAdapter.js +5 -3
  122. package/dist/canvas/useSceneAdapter.js.map +1 -1
  123. package/dist/clipboard.d.ts +2 -2
  124. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  125. package/dist/clone.d.ts +3 -3
  126. package/dist/core/isPlainObject.js +2 -2
  127. package/dist/core/layers/layerGroup.js.map +1 -1
  128. package/dist/core/layers/render.js.map +1 -1
  129. package/dist/core/scene/NodeRouting.js.map +1 -1
  130. package/dist/core/scene/arrivalWindow.js +1 -1
  131. package/dist/core/scene/arrivalWindow.js.map +1 -1
  132. package/dist/core/scene/clipNothing.js +2 -5
  133. package/dist/core/scene/clipNothing.js.map +1 -1
  134. package/dist/core/scene/declaredFieldSetters.js +44 -0
  135. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  136. package/dist/core/scene/depMemo.js +3 -0
  137. package/dist/core/scene/depMemo.js.map +1 -1
  138. package/dist/core/scene/dependents.js +3 -1
  139. package/dist/core/scene/dependents.js.map +1 -1
  140. package/dist/core/scene/documentOf.js +43 -0
  141. package/dist/core/scene/documentOf.js.map +1 -0
  142. package/dist/core/scene/effectivePose.js +37 -13
  143. package/dist/core/scene/effectivePose.js.map +1 -1
  144. package/dist/core/scene/fieldEdits.js +17 -14
  145. package/dist/core/scene/fieldEdits.js.map +1 -1
  146. package/dist/core/scene/historyForm.js +4 -4
  147. package/dist/core/scene/historyForm.js.map +1 -1
  148. package/dist/core/scene/inheritedPaint.js +27 -5
  149. package/dist/core/scene/inheritedPaint.js.map +1 -1
  150. package/dist/core/scene/kitOps.js +3 -23
  151. package/dist/core/scene/kitOps.js.map +1 -1
  152. package/dist/core/scene/layoutPass.js +1 -2
  153. package/dist/core/scene/layoutPass.js.map +1 -1
  154. package/dist/core/scene/nodeAsShown.js +72 -0
  155. package/dist/core/scene/nodeAsShown.js.map +1 -0
  156. package/dist/core/scene/nodeFnFields.js +2 -10
  157. package/dist/core/scene/nodeFnFields.js.map +1 -1
  158. package/dist/core/scene/nodeMemo.js +6 -1
  159. package/dist/core/scene/nodeMemo.js.map +1 -1
  160. package/dist/core/scene/nodeStore.js +2 -4
  161. package/dist/core/scene/nodeStore.js.map +1 -1
  162. package/dist/core/scene/pathEffects/derive.js +3 -2
  163. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  164. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  165. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  166. package/dist/core/scene/plyStacks.js +89 -0
  167. package/dist/core/scene/plyStacks.js.map +1 -0
  168. package/dist/core/scene/poseHold.js +42 -24
  169. package/dist/core/scene/poseHold.js.map +1 -1
  170. package/dist/core/scene/poseOverrides.js +35 -38
  171. package/dist/core/scene/poseOverrides.js.map +1 -1
  172. package/dist/core/scene/posePlies.js +59 -0
  173. package/dist/core/scene/posePlies.js.map +1 -0
  174. package/dist/core/scene/reconcileSpecs.js +6 -15
  175. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  176. package/dist/core/scene/renderOrder.js +10 -4
  177. package/dist/core/scene/renderOrder.js.map +1 -1
  178. package/dist/core/scene/sameValue.js +17 -0
  179. package/dist/core/scene/sameValue.js.map +1 -0
  180. package/dist/core/scene/scene.js +3 -3
  181. package/dist/core/scene/scene.js.map +1 -1
  182. package/dist/core/scene/sceneEdits.js +8 -0
  183. package/dist/core/scene/sceneEdits.js.map +1 -1
  184. package/dist/core/scene/sceneState.js +1 -2
  185. package/dist/core/scene/sceneState.js.map +1 -1
  186. package/dist/core/scene/serialization.js +45 -9
  187. package/dist/core/scene/serialization.js.map +1 -1
  188. package/dist/core/scene/staleReplay.js +5 -2
  189. package/dist/core/scene/staleReplay.js.map +1 -1
  190. package/dist/core/scene/statedFields.js +4 -2
  191. package/dist/core/scene/statedFields.js.map +1 -1
  192. package/dist/core/scene/treeEdits.js +2 -4
  193. package/dist/core/scene/treeEdits.js.map +1 -1
  194. package/dist/core/scene/treeOrder.js +3 -1
  195. package/dist/core/scene/treeOrder.js.map +1 -1
  196. package/dist/core/scene/treeReads.js +1 -2
  197. package/dist/core/scene/treeReads.js.map +1 -1
  198. package/dist/core/scene/treeShape.js +13 -0
  199. package/dist/core/scene/treeShape.js.map +1 -0
  200. package/dist/core/scene/types.js.map +1 -1
  201. package/dist/core/scene/useScene.js +1 -1
  202. package/dist/core/scene/useScene.js.map +1 -1
  203. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  204. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-CpIQzKWQ.d.ts} +38 -59
  205. package/dist/devtools/handlers.js +1 -1
  206. package/dist/devtools/handlers.js.map +1 -1
  207. package/dist/devtools/protocol.js.map +1 -1
  208. package/dist/devtools-protocol.d.ts +4 -3
  209. package/dist/devtools.d.ts +3 -2
  210. package/dist/features/measure/measureHit.js +5 -3
  211. package/dist/features/measure/measureHit.js.map +1 -1
  212. package/dist/features/measure/measurement.js +1 -1
  213. package/dist/features/measure/measurement.js.map +1 -1
  214. package/dist/features/modes/modeLayers.js +14 -7
  215. package/dist/features/modes/modeLayers.js.map +1 -1
  216. package/dist/features/modes/modeTriggers.js +3 -3
  217. package/dist/features/modes/modeTriggers.js.map +1 -1
  218. package/dist/features/pathEffects/effectSources.js +25 -16
  219. package/dist/features/pathEffects/effectSources.js.map +1 -1
  220. package/dist/features/pathEffects/readSource.js +1 -1
  221. package/dist/features/pathEffects/readSource.js.map +1 -1
  222. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  223. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  224. package/dist/features/paths/editablePath.js +1 -1
  225. package/dist/features/paths/editablePath.js.map +1 -1
  226. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  227. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  228. package/dist/features/selection/enteredGroupFrame.js +65 -0
  229. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  230. package/dist/features/selection/enteredGroupScope.js +44 -0
  231. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  232. package/dist/features/text/sceneTextDrafts.js +26 -27
  233. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  234. package/dist/features/text/textHitAt.js +4 -2
  235. package/dist/features/text/textHitAt.js.map +1 -1
  236. package/dist/features/text/useSceneTextEdit.js +3 -3
  237. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  238. package/dist/features/viewports/viewportLayer.js +2 -1
  239. package/dist/features/viewports/viewportLayer.js.map +1 -1
  240. package/dist/index.d.ts +230 -77
  241. package/dist/index.js +16 -6
  242. package/dist/interactions/actions/admitsChild.js +11 -0
  243. package/dist/interactions/actions/admitsChild.js.map +1 -0
  244. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  245. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  246. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  247. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  248. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  249. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  250. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  251. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  252. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  253. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  254. package/dist/interactions/actions/defaults/group.js +7 -7
  255. package/dist/interactions/actions/defaults/group.js.map +1 -1
  256. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  257. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  258. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  259. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  260. package/dist/interactions/actions/drawnBoxes.js +2 -1
  261. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  262. package/dist/interactions/actions/insert/action.js +1 -0
  263. package/dist/interactions/actions/insert/action.js.map +1 -1
  264. package/dist/interactions/actions/insert/parent.js +7 -5
  265. package/dist/interactions/actions/insert/parent.js.map +1 -1
  266. package/dist/interactions/actions/insert/placeAction.js +3 -2
  267. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  268. package/dist/interactions/actions/insert/session.js +2 -2
  269. package/dist/interactions/actions/insert/session.js.map +1 -1
  270. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  271. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  272. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  273. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  274. package/dist/interactions/actions/move/layout/exits.js +1 -1
  275. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  276. package/dist/interactions/actions/move/session.js +4 -4
  277. package/dist/interactions/actions/move/session.js.map +1 -1
  278. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  279. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  280. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  281. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  282. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  283. package/dist/math.d.ts +1 -1
  284. package/dist/move.d.ts +4 -4
  285. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  286. package/dist/resize.d.ts +4 -4
  287. package/dist/test-seams.d.ts +6 -6
  288. package/dist/tools/builtin/pressTarget.js +22 -0
  289. package/dist/tools/builtin/pressTarget.js.map +1 -0
  290. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  291. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  292. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  293. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  294. package/dist/tools/builtin/select/groupEntry.js +82 -0
  295. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  296. package/dist/tools/builtin/select/pressDecision.js +1 -1
  297. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  298. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  299. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  300. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  301. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  302. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  303. package/dist/version.js +1 -1
  304. package/package.json +25 -25
@@ -8,7 +8,11 @@ import { applyToPoint, meanScale, pathContainsPoint } from "@weasel-js/geom";
8
8
  * `pickTolerancePx` of screen slop, refined to the painter's ink when
9
9
  * `shapePicking` is on. */
10
10
  function scenePointQuery(d, shapePicking, pickTolerancePx) {
11
- const pointQuery = (px, py, s) => {
11
+ const pointQuery = (px, py, s) => ({
12
+ ...bareQuery(px, py, s),
13
+ broad: pointBroad(px, py, s, pickTolerancePx)
14
+ });
15
+ const bareQuery = (px, py, s) => {
12
16
  const scale = meanScale(s);
13
17
  const tolerance = {
14
18
  px: pickTolerancePx,
@@ -47,7 +51,7 @@ function scenePointQuery(d, shapePicking, pickTolerancePx) {
47
51
  inCopy: (m) => {
48
52
  const [x, y] = applyToPoint(m, px, py);
49
53
  const k = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
50
- return pointQuery(x, y, {
54
+ return bareQuery(x, y, {
51
55
  x: s.x / k,
52
56
  y: s.y / k
53
57
  });
@@ -56,6 +60,20 @@ function scenePointQuery(d, shapePicking, pickTolerancePx) {
56
60
  };
57
61
  return pointQuery;
58
62
  }
63
+ /** The world box the point query's slop reaches, on the least-scaled axis. */
64
+ function pointBroad(px, py, s, tolerancePx) {
65
+ const least = Math.min(Math.abs(s.x), Math.abs(s.y));
66
+ const r = tolerancePx / least;
67
+ return {
68
+ box: {
69
+ x: px - r,
70
+ y: py - r,
71
+ width: 2 * r,
72
+ height: 2 * r
73
+ },
74
+ pxPerWorld: least
75
+ };
76
+ }
59
77
 
60
78
  //#endregion
61
79
  export { scenePointQuery };
@@ -1 +1 @@
1
- {"version":3,"file":"scenePointQuery.js","names":[],"sources":["../../../src/canvas/SceneCanvas/scenePointQuery.ts"],"sourcesContent":["import { applyToPoint, meanScale, pathContainsPoint } from '@weasel-js/geom';\nimport type { PickQuery } from 'canvas/pickWalk';\nimport { findShapeInk, shapeCoversPoint } from 'canvas/NodeShape';\nimport { toPlane } from 'core/viewport/parallax';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { poseContains, poseContainsRotated } from './poseGeometry';\n\n/** The point query `<SceneCanvas>`'s default pick asks: a world point with\n * `pickTolerancePx` of screen slop, refined to the painter's ink when\n * `shapePicking` is on. */\nexport function scenePointQuery<TPose>(\n d: PoseDescriptor<unknown>,\n shapePicking: boolean,\n pickTolerancePx: number,\n): (px: number, py: number, s: { x: number; y: number }) => PickQuery<TPose> {\n // `s` makes the slop a screen distance, so the grab zone stays the same\n // apparent thickness at any zoom and on every axis. A `{ px }` stroke's\n // reach is on screen too, and joins the slop there.\n const pointQuery = (px: number, py: number, s: { x: number; y: number }): PickQuery<TPose> => {\n const scale = meanScale(s);\n const tolerance = { px: pickTolerancePx, scale: s };\n return {\n hits: (n, pose, derived, inherited) => {\n // The pre-filter has to be at least as generous as the refinement\n // that follows it, or it rejects points the refinement would have\n // claimed. A stroke reaches past the pose box by `outset` — a whole\n // stroke width for an outer align — on top of the pointer slop.\n const ink = shapePicking ? findShapeInk(n as never, pose, { scale, inherited }) : null;\n // A derived node's pose is a placeholder — typically zero-sized at\n // the origin — so its own box rejects every point the path covers.\n // The path is the region to test instead, and `poseContains` already\n // reads a path-like pose as one.\n const slop = { ...tolerance, px: tolerance.px + (ink?.outsetPx ?? 0), reach: ink?.outset ?? 0 };\n const admitted = derived\n ? poseContains(derived as never, px, py, slop, d as PoseDescriptor<unknown>)\n : poseContainsRotated(pose, px, py, slop, d as PoseDescriptor<unknown>);\n if (!admitted) return false;\n // `shapeCoversPoint` narrows the rect to the ink the painter actually\n // lays down (and answers `true` for painters that have no silhouette,\n // so nothing becomes unpickable).\n return !shapePicking\n || shapeCoversPoint(n as never, pose, px, py, {\n tolerance, scale, derivedPath: derived, inherited,\n });\n },\n clipAdmits: (clip) => pathContainsPoint(clip, px, py),\n inPlane: (m) => {\n const p = toPlane(m, { x: px, y: py });\n return pointQuery(p.x, p.y, { x: s.x / m.scale.x, y: s.y / m.scale.y });\n },\n inCopy: (m) => {\n const [x, y] = applyToPoint(m, px, py);\n const k = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));\n return pointQuery(x, y, { x: s.x / k, y: s.y / k });\n },\n };\n };\n return pointQuery;\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,gBACd,GACA,cACA,iBAC2E;CAI3E,MAAM,cAAc,IAAY,IAAY,MAAkD;EAC5F,MAAM,QAAQ,UAAU,CAAC;EACzB,MAAM,YAAY;GAAE,IAAI;GAAiB,OAAO;EAAE;EAClD,OAAO;GACL,OAAO,GAAG,MAAM,SAAS,cAAc;IAKrC,MAAM,MAAM,eAAe,aAAa,GAAY,MAAM;KAAE;KAAO;IAAU,CAAC,IAAI;IAKlF,MAAM,OAAO;KAAE,GAAG;KAAW,IAAI,UAAU,MAAM,KAAK,YAAY;KAAI,OAAO,KAAK,UAAU;IAAE;IAI9F,IAAI,EAHa,UACb,aAAa,SAAkB,IAAI,IAAI,MAAM,CAA4B,IACzE,oBAAoB,MAAM,IAAI,IAAI,MAAM,CAA4B,IACzD,OAAO;IAItB,OAAO,CAAC,gBACH,iBAAiB,GAAY,MAAM,IAAI,IAAI;KAC5C;KAAW;KAAO,aAAa;KAAS;IAC1C,CAAC;GACL;GACA,aAAa,SAAS,kBAAkB,MAAM,IAAI,EAAE;GACpD,UAAU,MAAM;IACd,MAAM,IAAI,QAAQ,GAAG;KAAE,GAAG;KAAI,GAAG;IAAG,CAAC;IACrC,OAAO,WAAW,EAAE,GAAG,EAAE,GAAG;KAAE,GAAG,EAAE,IAAI,EAAE,MAAM;KAAG,GAAG,EAAE,IAAI,EAAE,MAAM;IAAE,CAAC;GACxE;GACA,SAAS,MAAM;IACb,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,IAAI,EAAE;IACrC,MAAM,IAAI,KAAK,KAAK,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;IACvD,OAAO,WAAW,GAAG,GAAG;KAAE,GAAG,EAAE,IAAI;KAAG,GAAG,EAAE,IAAI;IAAE,CAAC;GACpD;EACF;CACF;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"scenePointQuery.js","names":[],"sources":["../../../src/canvas/SceneCanvas/scenePointQuery.ts"],"sourcesContent":["import { applyToPoint, meanScale, pathContainsPoint } from '@weasel-js/geom';\nimport type { PickBroad, PickQuery } from 'canvas/pickWalk';\nimport { findShapeInk, shapeCoversPoint } from 'canvas/NodeShape';\nimport { toPlane } from 'core/viewport/parallax';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { poseContains, poseContainsRotated } from './poseGeometry';\n\n/** The point query `<SceneCanvas>`'s default pick asks: a world point with\n * `pickTolerancePx` of screen slop, refined to the painter's ink when\n * `shapePicking` is on. */\nexport function scenePointQuery<TPose>(\n d: PoseDescriptor<unknown>,\n shapePicking: boolean,\n pickTolerancePx: number,\n): (px: number, py: number, s: { x: number; y: number }) => PickQuery<TPose> {\n // `s` makes the slop a screen distance, so the grab zone stays the same\n // apparent thickness at any zoom and on every axis. A `{ px }` stroke's\n // reach is on screen too, and joins the slop there.\n const pointQuery = (px: number, py: number, s: { x: number; y: number }): PickQuery<TPose> =>\n ({ ...bareQuery(px, py, s), broad: pointBroad(px, py, s, pickTolerancePx) });\n // A copy is tested by every walk, so the query carried into one asks no index.\n const bareQuery = (px: number, py: number, s: { x: number; y: number }): PickQuery<TPose> => {\n const scale = meanScale(s);\n const tolerance = { px: pickTolerancePx, scale: s };\n return {\n hits: (n, pose, derived, inherited) => {\n // The pre-filter has to be at least as generous as the refinement\n // that follows it, or it rejects points the refinement would have\n // claimed. A stroke reaches past the pose box by `outset` — a whole\n // stroke width for an outer align — on top of the pointer slop.\n const ink = shapePicking ? findShapeInk(n as never, pose, { scale, inherited }) : null;\n // A derived node's pose is a placeholder — typically zero-sized at\n // the origin — so its own box rejects every point the path covers.\n // The path is the region to test instead, and `poseContains` already\n // reads a path-like pose as one.\n const slop = { ...tolerance, px: tolerance.px + (ink?.outsetPx ?? 0), reach: ink?.outset ?? 0 };\n const admitted = derived\n ? poseContains(derived as never, px, py, slop, d as PoseDescriptor<unknown>)\n : poseContainsRotated(pose, px, py, slop, d as PoseDescriptor<unknown>);\n if (!admitted) return false;\n // `shapeCoversPoint` narrows the rect to the ink the painter actually\n // lays down (and answers `true` for painters that have no silhouette,\n // so nothing becomes unpickable).\n return !shapePicking\n || shapeCoversPoint(n as never, pose, px, py, {\n tolerance, scale, derivedPath: derived, inherited,\n });\n },\n clipAdmits: (clip) => pathContainsPoint(clip, px, py),\n inPlane: (m) => {\n const p = toPlane(m, { x: px, y: py });\n return pointQuery(p.x, p.y, { x: s.x / m.scale.x, y: s.y / m.scale.y });\n },\n inCopy: (m) => {\n const [x, y] = applyToPoint(m, px, py);\n const k = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));\n return bareQuery(x, y, { x: s.x / k, y: s.y / k });\n },\n };\n };\n return pointQuery;\n}\n\n/** The world box the point query's slop reaches, on the least-scaled axis. */\nfunction pointBroad(px: number, py: number, s: { x: number; y: number }, tolerancePx: number): PickBroad {\n const least = Math.min(Math.abs(s.x), Math.abs(s.y));\n const r = tolerancePx / least;\n return { box: { x: px - r, y: py - r, width: 2 * r, height: 2 * r }, pxPerWorld: least };\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,gBACd,GACA,cACA,iBAC2E;CAI3E,MAAM,cAAc,IAAY,IAAY,OACzC;EAAE,GAAG,UAAU,IAAI,IAAI,CAAC;EAAG,OAAO,WAAW,IAAI,IAAI,GAAG,eAAe;CAAE;CAE5E,MAAM,aAAa,IAAY,IAAY,MAAkD;EAC3F,MAAM,QAAQ,UAAU,CAAC;EACzB,MAAM,YAAY;GAAE,IAAI;GAAiB,OAAO;EAAE;EAClD,OAAO;GACL,OAAO,GAAG,MAAM,SAAS,cAAc;IAKrC,MAAM,MAAM,eAAe,aAAa,GAAY,MAAM;KAAE;KAAO;IAAU,CAAC,IAAI;IAKlF,MAAM,OAAO;KAAE,GAAG;KAAW,IAAI,UAAU,MAAM,KAAK,YAAY;KAAI,OAAO,KAAK,UAAU;IAAE;IAI9F,IAAI,EAHa,UACb,aAAa,SAAkB,IAAI,IAAI,MAAM,CAA4B,IACzE,oBAAoB,MAAM,IAAI,IAAI,MAAM,CAA4B,IACzD,OAAO;IAItB,OAAO,CAAC,gBACH,iBAAiB,GAAY,MAAM,IAAI,IAAI;KAC5C;KAAW;KAAO,aAAa;KAAS;IAC1C,CAAC;GACL;GACA,aAAa,SAAS,kBAAkB,MAAM,IAAI,EAAE;GACpD,UAAU,MAAM;IACd,MAAM,IAAI,QAAQ,GAAG;KAAE,GAAG;KAAI,GAAG;IAAG,CAAC;IACrC,OAAO,WAAW,EAAE,GAAG,EAAE,GAAG;KAAE,GAAG,EAAE,IAAI,EAAE,MAAM;KAAG,GAAG,EAAE,IAAI,EAAE,MAAM;IAAE,CAAC;GACxE;GACA,SAAS,MAAM;IACb,MAAM,CAAC,GAAG,KAAK,aAAa,GAAG,IAAI,EAAE;IACrC,MAAM,IAAI,KAAK,KAAK,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;IACvD,OAAO,UAAU,GAAG,GAAG;KAAE,GAAG,EAAE,IAAI;KAAG,GAAG,EAAE,IAAI;IAAE,CAAC;GACnD;EACF;CACF;CACA,OAAO;AACT;;AAGA,SAAS,WAAW,IAAY,IAAY,GAA6B,aAAgC;CACvG,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,EAAE,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC;CACnD,MAAM,IAAI,cAAc;CACxB,OAAO;EAAE,KAAK;GAAE,GAAG,KAAK;GAAG,GAAG,KAAK;GAAG,OAAO,IAAI;GAAG,QAAQ,IAAI;EAAE;EAAG,YAAY;CAAM;AACzF"}
@@ -55,13 +55,16 @@ function useCanvasTools(args) {
55
55
  const pressFeedback = feedbackOpt === true ? defaultPressFeedback : feedbackOpt || null;
56
56
  const defaultMarkerDrag = useMemo(() => createViewMarkerContribution(), []);
57
57
  const markerDrag = args.actions === null ? null : defaultMarkerDrag;
58
+ const groupEntry = args.actions === null ? null : args.groupEntry;
58
59
  const mergedAmbient = useMemo(() => [
59
60
  ...presetContributions(enabled),
61
+ ...groupEntry ? [groupEntry] : [],
60
62
  ...ambient ?? [],
61
63
  ...pressFeedback ? [pressFeedback] : [],
62
64
  ...markerDrag ? [markerDrag] : []
63
65
  ], [
64
66
  enabled,
67
+ groupEntry,
65
68
  ambient,
66
69
  pressFeedback,
67
70
  markerDrag
@@ -1 +1 @@
1
- {"version":3,"file":"useCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useCanvasTools.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport type { Action } from '@weasel-js/routing';\nimport { createLongPressFeedbackContribution } from '../../features/longPress/longPressFeedback';\nimport { createViewMarkerContribution } from '../../features/viewports/viewMarkerDrag';\nimport { useTools, type Contribution, type Tool, type ToolsApi } from '../../tools/overlayBinding';\nimport {\n presetActions,\n presetAmbient,\n presetContributions,\n resolveFeatures,\n type EnabledPresets,\n type FeatureBindingOptions,\n type FeatureEntry,\n type NamedPresets,\n} from './features';\nimport type { SceneCanvasProps } from './props';\nimport type { ToolsPatch } from './toolRegistry';\nimport { useBuiltinTools } from './useBuiltinTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** Discriminate the polymorphic `tools` prop: `ToolsApi` has `setActive`\n * / `active` / `registry`; the patch record shape has none of those. */\nfunction isToolsApi(tools: ToolsApi | ToolsPatch): tools is ToolsApi {\n return typeof (tools as ToolsApi).setActive === 'function';\n}\n\nconst presetSerials = new WeakMap<object, number>();\nlet nextSerial = 0;\nconst entryKey = (e: FeatureEntry): string => {\n if (typeof e === 'string') return e;\n let n = presetSerials.get(e);\n if (n === undefined) presetSerials.set(e, (n = nextSerial++));\n return `#${n}`;\n};\n\n/** The presets a canvas runs with. Keyed on the entries, not the array, so\n * an inline `features={['draw', pathTools]}` does not re-derive every render. */\nexport function useEnabledFeatures(\n features: readonly FeatureEntry[] | undefined,\n named: NamedPresets | null,\n hasViewport: boolean,\n selectRotates: boolean,\n): EnabledPresets {\n const featuresKey = (features ?? []).map(entryKey).join('|');\n return useMemo(\n () => resolveFeatures(features, named, { viewport: hasViewport, selectRotates }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [featuresKey, named, hasViewport, selectRotates],\n );\n}\n\n/**\n * The canvas's tools: a caller's own `ToolsApi` when the `tools` prop is one\n * (the takeover form), and otherwise the built-ins the presets and\n * `defaultTools` name, patched by a `tools` record.\n */\nexport function useCanvasTools(args: {\n /** The canvas's select tool, which is built from its scene and pickers. */\n select: Tool;\n enabled: EnabledPresets;\n tools: Props['tools'];\n defaultTools: Props['defaultTools'];\n toolOptions: Props['toolOptions'];\n /** `viewport.inertia`, which is the hand tool's. */\n inertia: NonNullable<Props['viewport']>['inertia'];\n ambient: Props['ambient'];\n /** `null` leaves out the kit's default actions, the marker drag among them. */\n actions: Props['actions'];\n /** `longPress.feedback`. Default true, which installs the kit's own. */\n pressFeedback: NonNullable<Props['longPress']>['feedback'];\n initialActiveTool: Props['initialActiveTool'];\n modes: Props['modes'];\n onToolsCreated: Props['onToolsCreated'];\n}): {\n /** What the canvas routes input through: the takeover, or `internal`. */\n tools: ToolsApi;\n /** The canvas's own api, built whether or not a takeover replaces it. */\n internal: ToolsApi;\n takeover: ToolsApi | null;\n} {\n const { select, enabled, tools: toolsProp, defaultTools, toolOptions, inertia, ambient, initialActiveTool, modes, onToolsCreated } = args;\n\n const takeover = toolsProp && isToolsApi(toolsProp) ? toolsProp : null;\n const registry = useBuiltinTools({\n select,\n enabled,\n defaultTools,\n patch: toolsProp && !isToolsApi(toolsProp) ? toolsProp : null,\n toolOptions,\n inertia,\n });\n\n const defaultPressFeedback = useMemo(() => createLongPressFeedbackContribution(), []);\n const feedbackOpt = args.pressFeedback ?? true;\n const pressFeedback = feedbackOpt === true ? defaultPressFeedback : feedbackOpt || null;\n const defaultMarkerDrag = useMemo(() => createViewMarkerContribution(), []);\n const markerDrag = args.actions === null ? null : defaultMarkerDrag;\n const mergedAmbient = useMemo(\n () => [\n ...presetContributions(enabled), ...(ambient ?? []),\n ...(pressFeedback ? [pressFeedback] : []), ...(markerDrag ? [markerDrag] : []),\n ],\n [enabled, ambient, pressFeedback, markerDrag],\n );\n\n const internal = useTools({\n active: initialActiveTool ?? ('select' in registry ? 'select' : null),\n registry,\n ...(mergedAmbient.length ? { ambient: mergedAmbient } : {}),\n ...(modes ? { modes } : {}),\n });\n const tools = takeover ?? internal;\n\n const onToolsCreatedRef = useLatest(onToolsCreated);\n useEffect(() => { onToolsCreatedRef.current?.(tools); }, [tools, onToolsCreatedRef]);\n\n return { tools, internal, takeover };\n}\n\n/**\n * The presets' ambient bindings, and the standard actions the canvas\n * registers. The bindings are not tools and sit outside `tools`, so they apply\n * under a takeover too. An action is registered when an enabled preset brings\n * it, or when it is in `pool` and a tool or contribution on this canvas binds\n * it: a tool brings the actions it routes to, so `defaultTools={['rect']}`\n * works under any preset.\n */\nexport function useFeatureBindings(args: {\n enabled: EnabledPresets;\n /** Actions registered only when something binds them. */\n pool: readonly Action[];\n tools: Pick<ToolsApi, 'registry' | 'ambient'>;\n move: FeatureBindingOptions['move'];\n rotate: FeatureBindingOptions['rotate'];\n}): { contributions: Contribution[]; standardActions: Action[] } {\n const { enabled, pool, tools, move, rotate } = args;\n const contributions = useMemo<Contribution[]>(\n () => presetAmbient(enabled, { move, rotate }),\n [enabled, move, rotate],\n );\n const standardActions = useMemo(() => {\n const out = presetActions(enabled);\n const have = new Set(out.map((a) => a.id));\n const bound = new Set<string>();\n for (const entry of [...Object.values(tools.registry), ...tools.ambient, ...contributions]) {\n for (const b of entry.bindings ?? []) bound.add(b.actionId);\n }\n for (const a of pool) {\n if (bound.has(a.id) && !have.has(a.id)) {\n have.add(a.id);\n out.push(a);\n }\n }\n return out;\n }, [enabled, pool, tools, contributions]);\n return { contributions, standardActions };\n}\n\n"],"mappings":";;;;;;;;;;;AAwBA,SAAS,WAAW,OAAiD;CACnE,OAAO,OAAQ,MAAmB,cAAc;AAClD;AAEA,IAAM,gCAAgB,IAAI,QAAwB;AAClD,IAAI,aAAa;AACjB,IAAM,YAAY,MAA4B;CAC5C,IAAI,OAAO,MAAM,UAAU,OAAO;CAClC,IAAI,IAAI,cAAc,IAAI,CAAC;CAC3B,IAAI,MAAM,QAAW,cAAc,IAAI,GAAI,IAAI,YAAa;CAC5D,OAAO,IAAI;AACb;;;AAIA,SAAgB,mBACd,UACA,OACA,aACA,eACgB;CAChB,MAAM,eAAe,YAAY,CAAC,EAAA,CAAG,IAAI,QAAQ,CAAC,CAAC,KAAK,GAAG;CAC3D,OAAO,cACC,gBAAgB,UAAU,OAAO;EAAE,UAAU;EAAa;CAAc,CAAC,GAE/E;EAAC;EAAa;EAAO;EAAa;CAAa,CACjD;AACF;;;;;;AAOA,SAAgB,eAAe,MAuB7B;CACA,MAAM,EAAE,QAAQ,SAAS,OAAO,WAAW,cAAc,aAAa,SAAS,SAAS,mBAAmB,OAAO,mBAAmB;CAErI,MAAM,WAAW,aAAa,WAAW,SAAS,IAAI,YAAY;CAClE,MAAM,WAAW,gBAAgB;EAC/B;EACA;EACA;EACA,OAAO,aAAa,CAAC,WAAW,SAAS,IAAI,YAAY;EACzD;EACA;CACF,CAAC;CAED,MAAM,uBAAuB,cAAc,oCAAoC,GAAG,CAAC,CAAC;CACpF,MAAM,cAAc,KAAK,iBAAiB;CAC1C,MAAM,gBAAgB,gBAAgB,OAAO,uBAAuB,eAAe;CACnF,MAAM,oBAAoB,cAAc,6BAA6B,GAAG,CAAC,CAAC;CAC1E,MAAM,aAAa,KAAK,YAAY,OAAO,OAAO;CAClD,MAAM,gBAAgB,cACd;EACJ,GAAG,oBAAoB,OAAO;EAAG,GAAI,WAAW,CAAC;EACjD,GAAI,gBAAgB,CAAC,aAAa,IAAI,CAAC;EAAI,GAAI,aAAa,CAAC,UAAU,IAAI,CAAC;CAC9E,GACA;EAAC;EAAS;EAAS;EAAe;CAAU,CAC9C;CAEA,MAAM,WAAW,SAAS;EACxB,QAAQ,sBAAsB,YAAY,WAAW,WAAW;EAChE;EACA,GAAI,cAAc,SAAS,EAAE,SAAS,cAAc,IAAI,CAAC;EACzD,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;CACD,MAAM,QAAQ,YAAY;CAE1B,MAAM,oBAAoB,UAAU,cAAc;CAClD,gBAAgB;EAAE,kBAAkB,UAAU,KAAK;CAAG,GAAG,CAAC,OAAO,iBAAiB,CAAC;CAEnF,OAAO;EAAE;EAAO;EAAU;CAAS;AACrC;;;;;;;;;AAUA,SAAgB,mBAAmB,MAO8B;CAC/D,MAAM,EAAE,SAAS,MAAM,OAAO,MAAM,WAAW;CAC/C,MAAM,gBAAgB,cACd,cAAc,SAAS;EAAE;EAAM;CAAO,CAAC,GAC7C;EAAC;EAAS;EAAM;CAAM,CACxB;CAgBA,OAAO;EAAE;EAAe,iBAfA,cAAc;GACpC,MAAM,MAAM,cAAc,OAAO;GACjC,MAAM,OAAO,IAAI,IAAI,IAAI,KAAK,MAAM,EAAE,EAAE,CAAC;GACzC,MAAM,wBAAQ,IAAI,IAAY;GAC9B,KAAK,MAAM,SAAS;IAAC,GAAG,OAAO,OAAO,MAAM,QAAQ;IAAG,GAAG,MAAM;IAAS,GAAG;GAAa,GACvF,KAAK,MAAM,KAAK,MAAM,YAAY,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ;GAE5D,KAAK,MAAM,KAAK,MACd,IAAI,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG;IACtC,KAAK,IAAI,EAAE,EAAE;IACb,IAAI,KAAK,CAAC;GACZ;GAEF,OAAO;EACT,GAAG;GAAC;GAAS;GAAM;GAAO;EAAa,CACf;CAAgB;AAC1C"}
1
+ {"version":3,"file":"useCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useCanvasTools.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport type { Action } from '@weasel-js/routing';\nimport { createLongPressFeedbackContribution } from '../../features/longPress/longPressFeedback';\nimport { createViewMarkerContribution } from '../../features/viewports/viewMarkerDrag';\nimport { useTools, type Contribution, type Tool, type ToolsApi } from '../../tools/overlayBinding';\nimport {\n presetActions,\n presetAmbient,\n presetContributions,\n resolveFeatures,\n type EnabledPresets,\n type FeatureBindingOptions,\n type FeatureEntry,\n type NamedPresets,\n} from './features';\nimport type { SceneCanvasProps } from './props';\nimport type { ToolsPatch } from './toolRegistry';\nimport { useBuiltinTools } from './useBuiltinTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** Discriminate the polymorphic `tools` prop: `ToolsApi` has `setActive`\n * / `active` / `registry`; the patch record shape has none of those. */\nfunction isToolsApi(tools: ToolsApi | ToolsPatch): tools is ToolsApi {\n return typeof (tools as ToolsApi).setActive === 'function';\n}\n\nconst presetSerials = new WeakMap<object, number>();\nlet nextSerial = 0;\nconst entryKey = (e: FeatureEntry): string => {\n if (typeof e === 'string') return e;\n let n = presetSerials.get(e);\n if (n === undefined) presetSerials.set(e, (n = nextSerial++));\n return `#${n}`;\n};\n\n/** The presets a canvas runs with. Keyed on the entries, not the array, so\n * an inline `features={['draw', pathTools]}` does not re-derive every render. */\nexport function useEnabledFeatures(\n features: readonly FeatureEntry[] | undefined,\n named: NamedPresets | null,\n hasViewport: boolean,\n selectRotates: boolean,\n): EnabledPresets {\n const featuresKey = (features ?? []).map(entryKey).join('|');\n return useMemo(\n () => resolveFeatures(features, named, { viewport: hasViewport, selectRotates }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [featuresKey, named, hasViewport, selectRotates],\n );\n}\n\n/**\n * The canvas's tools: a caller's own `ToolsApi` when the `tools` prop is one\n * (the takeover form), and otherwise the built-ins the presets and\n * `defaultTools` name, patched by a `tools` record.\n */\nexport function useCanvasTools(args: {\n /** The canvas's select tool, which is built from its scene and pickers. */\n select: Tool;\n /** The canvas's group entry and leave, or null where the select preset is off. */\n groupEntry: Contribution | null;\n enabled: EnabledPresets;\n tools: Props['tools'];\n defaultTools: Props['defaultTools'];\n toolOptions: Props['toolOptions'];\n /** `viewport.inertia`, which is the hand tool's. */\n inertia: NonNullable<Props['viewport']>['inertia'];\n ambient: Props['ambient'];\n /** `null` leaves out the kit's default actions, the marker drag among them. */\n actions: Props['actions'];\n /** `longPress.feedback`. Default true, which installs the kit's own. */\n pressFeedback: NonNullable<Props['longPress']>['feedback'];\n initialActiveTool: Props['initialActiveTool'];\n modes: Props['modes'];\n onToolsCreated: Props['onToolsCreated'];\n}): {\n /** What the canvas routes input through: the takeover, or `internal`. */\n tools: ToolsApi;\n /** The canvas's own api, built whether or not a takeover replaces it. */\n internal: ToolsApi;\n takeover: ToolsApi | null;\n} {\n const { select, enabled, tools: toolsProp, defaultTools, toolOptions, inertia, ambient, initialActiveTool, modes, onToolsCreated } = args;\n\n const takeover = toolsProp && isToolsApi(toolsProp) ? toolsProp : null;\n const registry = useBuiltinTools({\n select,\n enabled,\n defaultTools,\n patch: toolsProp && !isToolsApi(toolsProp) ? toolsProp : null,\n toolOptions,\n inertia,\n });\n\n const defaultPressFeedback = useMemo(() => createLongPressFeedbackContribution(), []);\n const feedbackOpt = args.pressFeedback ?? true;\n const pressFeedback = feedbackOpt === true ? defaultPressFeedback : feedbackOpt || null;\n const defaultMarkerDrag = useMemo(() => createViewMarkerContribution(), []);\n const markerDrag = args.actions === null ? null : defaultMarkerDrag;\n const groupEntry = args.actions === null ? null : args.groupEntry;\n const mergedAmbient = useMemo(\n () => [\n ...presetContributions(enabled), ...(groupEntry ? [groupEntry] : []), ...(ambient ?? []),\n ...(pressFeedback ? [pressFeedback] : []), ...(markerDrag ? [markerDrag] : []),\n ],\n [enabled, groupEntry, ambient, pressFeedback, markerDrag],\n );\n\n const internal = useTools({\n active: initialActiveTool ?? ('select' in registry ? 'select' : null),\n registry,\n ...(mergedAmbient.length ? { ambient: mergedAmbient } : {}),\n ...(modes ? { modes } : {}),\n });\n const tools = takeover ?? internal;\n\n const onToolsCreatedRef = useLatest(onToolsCreated);\n useEffect(() => { onToolsCreatedRef.current?.(tools); }, [tools, onToolsCreatedRef]);\n\n return { tools, internal, takeover };\n}\n\n/**\n * The presets' ambient bindings, and the standard actions the canvas\n * registers. The bindings are not tools and sit outside `tools`, so they apply\n * under a takeover too. An action is registered when an enabled preset brings\n * it, or when it is in `pool` and a tool or contribution on this canvas binds\n * it: a tool brings the actions it routes to, so `defaultTools={['rect']}`\n * works under any preset.\n */\nexport function useFeatureBindings(args: {\n enabled: EnabledPresets;\n /** Actions registered only when something binds them. */\n pool: readonly Action[];\n tools: Pick<ToolsApi, 'registry' | 'ambient'>;\n move: FeatureBindingOptions['move'];\n rotate: FeatureBindingOptions['rotate'];\n}): { contributions: Contribution[]; standardActions: Action[] } {\n const { enabled, pool, tools, move, rotate } = args;\n const contributions = useMemo<Contribution[]>(\n () => presetAmbient(enabled, { move, rotate }),\n [enabled, move, rotate],\n );\n const standardActions = useMemo(() => {\n const out = presetActions(enabled);\n const have = new Set(out.map((a) => a.id));\n const bound = new Set<string>();\n for (const entry of [...Object.values(tools.registry), ...tools.ambient, ...contributions]) {\n for (const b of entry.bindings ?? []) bound.add(b.actionId);\n }\n for (const a of pool) {\n if (bound.has(a.id) && !have.has(a.id)) {\n have.add(a.id);\n out.push(a);\n }\n }\n return out;\n }, [enabled, pool, tools, contributions]);\n return { contributions, standardActions };\n}\n\n"],"mappings":"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"}
@@ -107,6 +107,8 @@ function useContributionRoles(tools, api, versionCheck = "warn") {
107
107
  for (const entry of entries) {
108
108
  const deps = entry.deps;
109
109
  if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));
110
+ const watch = entry.watch;
111
+ if (watch && depRegistry) installs.set(watch, () => watch(() => depRegistry.changed?.()));
110
112
  const attach = entry.attach;
111
113
  if (attach && api) installs.set(attach, () => attach(api, reader));
112
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useContributionRoles.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useContributionRoles.ts"],"sourcesContent":["/**\n * `useContributionRoles` — installs the roles of every entry in the tools\n * registry that a surface owns rather than the dispatcher: its actions into the\n * `<ActionsProvider>`, its deps into the dep registry, and its `attach` against\n * the mounted canvas. Views are the fourth such role; `<SceneCanvas>` renders\n * those itself, because a view is a component.\n *\n * Tool hooks run wherever the consumer calls them, which may be outside any\n * registry. Declaring roles on the entry and installing them from in here keeps\n * the tool hook callable from anywhere.\n */\nimport { useEffect, useRef } from 'react';\nimport { useActionsRegistry, useMenuHost, useOffhandAction, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport type { DepRegistry } from '@weasel-js/routing/react';\nimport type { DepName } from 'interactions/actions/depSchema';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport type { SurfaceContribution } from '../surfaceContribution';\nimport { checkRequirements, type RequirementMismatch } from '../kitRequirements';\nimport { useContributionFrameHooks } from './contributionFrameHooks';\nimport { isDev } from '@weasel-js/react';\n\n/** How a surface treats an entry whose `requires` the running kit does not\n * meet: warn in development (the default), throw, or not check. */\nexport type VersionCheck = 'warn' | 'strict' | 'off';\n\nconst NO_DEPS = { get: () => undefined };\nconst warnedRequirements = new WeakSet<object>();\n\nfunction describeMismatch(m: RequirementMismatch): string {\n switch (m.reason) {\n case 'unsatisfied': return `${m.package} ${m.range} (running ${m.running})`;\n case 'invalid-range': return `${m.package} \"${m.range}\", which is not a range this check reads`;\n case 'unknown-package': return `${m.package} ${m.range}, but ${m.package} reports no version`;\n }\n}\n\nfunction checkEntryRequirements(entries: readonly SurfaceContribution[], mode: VersionCheck): void {\n if (mode === 'off') return;\n for (const entry of entries) {\n const requires = entry.requires;\n if (!requires) continue;\n if (mode === 'warn' && warnedRequirements.has(requires)) continue;\n const mismatches = checkRequirements(requires);\n if (mismatches.length === 0) continue;\n const message = `[weasel] contribution \"${entry.id}\" requires ${mismatches.map(describeMismatch).join('; ')}.`;\n if (mode === 'strict') throw new Error(message);\n warnedRequirements.add(requires);\n console.warn(message);\n }\n}\n\n/** Every entry the registry holds, registry and ambient alike. */\nexport function contributionEntries(tools: Pick<ToolsApi, 'registry' | 'ambient'>): SurfaceContribution[] {\n return [...Object.values(tools.registry), ...tools.ambient] as SurfaceContribution[];\n}\n\n/**\n * Keep one installation per key alive: install keys that appeared, tear down\n * keys that went. Keys are role objects the consumer owns, so an entry the\n * host re-wraps every render — `useTools` restates eligibility on a copy —\n * still installs once.\n */\nfunction useKeyedInstall(\n items: ReadonlyMap<object, () => () => void>,\n): void {\n const live = useRef(new Map<object, () => void>());\n useEffect(() => {\n for (const [key, teardown] of live.current) {\n if (items.has(key)) continue;\n teardown();\n live.current.delete(key);\n }\n for (const [key, install] of items) {\n if (live.current.has(key)) continue;\n live.current.set(key, install());\n }\n });\n useEffect(() => () => {\n for (const teardown of live.current.values()) teardown();\n live.current.clear();\n }, []);\n}\n\nexport function useContributionRoles(\n tools: ToolsApi,\n api: CanvasExtensionApi | null,\n versionCheck: VersionCheck = 'warn',\n): void {\n const registry = useActionsRegistry();\n const depRegistry = useOptionalDepRegistry();\n const { registry: toolRegistry, ambient } = tools;\n\n useEffect(() => {\n if (!registry) return;\n const unregisters: Array<() => void> = [];\n // Ambient entries included: an always-on entry owns actions its own\n // bindings reference exactly as a registry tool does, and leaving them out\n // gives the same silent failure this hook exists to prevent — a binding\n // pointing at an id nothing registered.\n for (const entry of contributionEntries({ registry: toolRegistry, ambient })) {\n for (const action of entry.actions ?? []) {\n unregisters.push(registry.register(action));\n }\n }\n return () => { for (const u of unregisters) u(); };\n }, [registry, toolRegistry, ambient]);\n\n const menuHost = useMenuHost();\n useEffect(() => {\n if (!menuHost) return;\n const releases: Array<() => void> = [];\n for (const [id, entry] of Object.entries(toolRegistry)) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, id));\n }\n for (const entry of ambient) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, null));\n }\n return () => { for (const r of releases) r(); };\n }, [menuHost, toolRegistry, ambient]);\n\n const entries = contributionEntries(tools);\n // Development only for a warning; a strict surface asked to be stopped, so\n // it throws in every build.\n if (versionCheck === 'strict' || (versionCheck === 'warn' && isDev())) {\n checkEntryRequirements(entries, versionCheck);\n }\n\n const reader = depRegistry ?? NO_DEPS;\n const installs = new Map<object, () => () => void>();\n for (const entry of entries) {\n const deps = entry.deps;\n if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));\n const attach = entry.attach;\n if (attach && api) installs.set(attach, () => attach(api, reader));\n }\n useKeyedInstall(installs);\n useContributionFrameHooks(entries, api, reader);\n\n // Tool assembly runs above the provider too, so its own registration of\n // held-key engagement finds no registry unless the consumer mounted one.\n useOffhandAction(contributionEntries(tools));\n}\n\nfunction registerDeps(\n depRegistry: DepRegistry,\n deps: NonNullable<SurfaceContribution['deps']>,\n): () => void {\n const unregisters = (Object.keys(deps) as DepName[]).map((name) =>\n depRegistry.register(name, deps[name] as never));\n return () => { for (const u of unregisters) u(); };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useContributionRoles.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useContributionRoles.ts"],"sourcesContent":["/**\n * `useContributionRoles` — installs the roles of every entry in the tools\n * registry that a surface owns rather than the dispatcher: its actions into the\n * `<ActionsProvider>`, its deps into the dep registry, and its `attach` against\n * the mounted canvas. Views are the fourth such role; `<SceneCanvas>` renders\n * those itself, because a view is a component.\n *\n * Tool hooks run wherever the consumer calls them, which may be outside any\n * registry. Declaring roles on the entry and installing them from in here keeps\n * the tool hook callable from anywhere.\n */\nimport { useEffect, useRef } from 'react';\nimport { useActionsRegistry, useMenuHost, useOffhandAction, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport type { DepRegistry } from '@weasel-js/routing/react';\nimport type { DepName } from 'interactions/actions/depSchema';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport type { SurfaceContribution } from '../surfaceContribution';\nimport { checkRequirements, type RequirementMismatch } from '../kitRequirements';\nimport { useContributionFrameHooks } from './contributionFrameHooks';\nimport { isDev } from '@weasel-js/react';\n\n/** How a surface treats an entry whose `requires` the running kit does not\n * meet: warn in development (the default), throw, or not check. */\nexport type VersionCheck = 'warn' | 'strict' | 'off';\n\nconst NO_DEPS = { get: () => undefined };\nconst warnedRequirements = new WeakSet<object>();\n\nfunction describeMismatch(m: RequirementMismatch): string {\n switch (m.reason) {\n case 'unsatisfied': return `${m.package} ${m.range} (running ${m.running})`;\n case 'invalid-range': return `${m.package} \"${m.range}\", which is not a range this check reads`;\n case 'unknown-package': return `${m.package} ${m.range}, but ${m.package} reports no version`;\n }\n}\n\nfunction checkEntryRequirements(entries: readonly SurfaceContribution[], mode: VersionCheck): void {\n if (mode === 'off') return;\n for (const entry of entries) {\n const requires = entry.requires;\n if (!requires) continue;\n if (mode === 'warn' && warnedRequirements.has(requires)) continue;\n const mismatches = checkRequirements(requires);\n if (mismatches.length === 0) continue;\n const message = `[weasel] contribution \"${entry.id}\" requires ${mismatches.map(describeMismatch).join('; ')}.`;\n if (mode === 'strict') throw new Error(message);\n warnedRequirements.add(requires);\n console.warn(message);\n }\n}\n\n/** Every entry the registry holds, registry and ambient alike. */\nexport function contributionEntries(tools: Pick<ToolsApi, 'registry' | 'ambient'>): SurfaceContribution[] {\n return [...Object.values(tools.registry), ...tools.ambient] as SurfaceContribution[];\n}\n\n/**\n * Keep one installation per key alive: install keys that appeared, tear down\n * keys that went. Keys are role objects the consumer owns, so an entry the\n * host re-wraps every render — `useTools` restates eligibility on a copy —\n * still installs once.\n */\nfunction useKeyedInstall(\n items: ReadonlyMap<object, () => () => void>,\n): void {\n const live = useRef(new Map<object, () => void>());\n useEffect(() => {\n for (const [key, teardown] of live.current) {\n if (items.has(key)) continue;\n teardown();\n live.current.delete(key);\n }\n for (const [key, install] of items) {\n if (live.current.has(key)) continue;\n live.current.set(key, install());\n }\n });\n useEffect(() => () => {\n for (const teardown of live.current.values()) teardown();\n live.current.clear();\n }, []);\n}\n\nexport function useContributionRoles(\n tools: ToolsApi,\n api: CanvasExtensionApi | null,\n versionCheck: VersionCheck = 'warn',\n): void {\n const registry = useActionsRegistry();\n const depRegistry = useOptionalDepRegistry();\n const { registry: toolRegistry, ambient } = tools;\n\n useEffect(() => {\n if (!registry) return;\n const unregisters: Array<() => void> = [];\n // Ambient entries included: an always-on entry owns actions its own\n // bindings reference exactly as a registry tool does, and leaving them out\n // gives the same silent failure this hook exists to prevent — a binding\n // pointing at an id nothing registered.\n for (const entry of contributionEntries({ registry: toolRegistry, ambient })) {\n for (const action of entry.actions ?? []) {\n unregisters.push(registry.register(action));\n }\n }\n return () => { for (const u of unregisters) u(); };\n }, [registry, toolRegistry, ambient]);\n\n const menuHost = useMenuHost();\n useEffect(() => {\n if (!menuHost) return;\n const releases: Array<() => void> = [];\n for (const [id, entry] of Object.entries(toolRegistry)) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, id));\n }\n for (const entry of ambient) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, null));\n }\n return () => { for (const r of releases) r(); };\n }, [menuHost, toolRegistry, ambient]);\n\n const entries = contributionEntries(tools);\n // Development only for a warning; a strict surface asked to be stopped, so\n // it throws in every build.\n if (versionCheck === 'strict' || (versionCheck === 'warn' && isDev())) {\n checkEntryRequirements(entries, versionCheck);\n }\n\n const reader = depRegistry ?? NO_DEPS;\n const installs = new Map<object, () => () => void>();\n for (const entry of entries) {\n const deps = entry.deps;\n if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));\n const watch = entry.watch;\n if (watch && depRegistry) installs.set(watch, () => watch(() => depRegistry.changed?.()));\n const attach = entry.attach;\n if (attach && api) installs.set(attach, () => attach(api, reader));\n }\n useKeyedInstall(installs);\n useContributionFrameHooks(entries, api, reader);\n\n // Tool assembly runs above the provider too, so its own registration of\n // held-key engagement finds no registry unless the consumer mounted one.\n useOffhandAction(contributionEntries(tools));\n}\n\nfunction registerDeps(\n depRegistry: DepRegistry,\n deps: NonNullable<SurfaceContribution['deps']>,\n): () => void {\n const unregisters = (Object.keys(deps) as DepName[]).map((name) =>\n depRegistry.register(name, deps[name] as never));\n return () => { for (const u of unregisters) u(); };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0BA,IAAM,UAAU,EAAE,WAAW,OAAU;AACvC,IAAM,qCAAqB,IAAI,QAAgB;AAE/C,SAAS,iBAAiB,GAAgC;CACxD,QAAQ,EAAE,QAAV;EACE,KAAK,eAAe,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,MAAM,YAAY,EAAE,QAAQ;EACzE,KAAK,iBAAiB,OAAO,GAAG,EAAE,QAAQ,IAAI,EAAE,MAAM;EACtD,KAAK,mBAAmB,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,MAAM,QAAQ,EAAE,QAAQ;CAC3E;AACF;AAEA,SAAS,uBAAuB,SAAyC,MAA0B;CACjG,IAAI,SAAS,OAAO;CACpB,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,MAAM;EACvB,IAAI,CAAC,UAAU;EACf,IAAI,SAAS,UAAU,mBAAmB,IAAI,QAAQ,GAAG;EACzD,MAAM,aAAa,kBAAkB,QAAQ;EAC7C,IAAI,WAAW,WAAW,GAAG;EAC7B,MAAM,UAAU,0BAA0B,MAAM,GAAG,aAAa,WAAW,IAAI,gBAAgB,CAAC,CAAC,KAAK,IAAI,EAAE;EAC5G,IAAI,SAAS,UAAU,MAAM,IAAI,MAAM,OAAO;EAC9C,mBAAmB,IAAI,QAAQ;EAC/B,QAAQ,KAAK,OAAO;CACtB;AACF;;AAGA,SAAgB,oBAAoB,OAAsE;CACxG,OAAO,CAAC,GAAG,OAAO,OAAO,MAAM,QAAQ,GAAG,GAAG,MAAM,OAAO;AAC5D;;;;;;;AAQA,SAAS,gBACP,OACM;CACN,MAAM,OAAO,uBAAO,IAAI,IAAwB,CAAC;CACjD,gBAAgB;EACd,KAAK,MAAM,CAAC,KAAK,aAAa,KAAK,SAAS;GAC1C,IAAI,MAAM,IAAI,GAAG,GAAG;GACpB,SAAS;GACT,KAAK,QAAQ,OAAO,GAAG;EACzB;EACA,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO;GAClC,IAAI,KAAK,QAAQ,IAAI,GAAG,GAAG;GAC3B,KAAK,QAAQ,IAAI,KAAK,QAAQ,CAAC;EACjC;CACF,CAAC;CACD,sBAAsB;EACpB,KAAK,MAAM,YAAY,KAAK,QAAQ,OAAO,GAAG,SAAS;EACvD,KAAK,QAAQ,MAAM;CACrB,GAAG,CAAC,CAAC;AACP;AAEA,SAAgB,qBACd,OACA,KACA,eAA6B,QACvB;CACN,MAAM,WAAW,mBAAmB;CACpC,MAAM,cAAc,uBAAuB;CAC3C,MAAM,EAAE,UAAU,cAAc,YAAY;CAE5C,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,MAAM,cAAiC,CAAC;EAKxC,KAAK,MAAM,SAAS,oBAAoB;GAAE,UAAU;GAAc;EAAQ,CAAC,GACzE,KAAK,MAAM,UAAU,MAAM,WAAW,CAAC,GACrC,YAAY,KAAK,SAAS,SAAS,MAAM,CAAC;EAG9C,aAAa;GAAE,KAAK,MAAM,KAAK,aAAa,EAAE;EAAG;CACnD,GAAG;EAAC;EAAU;EAAc;CAAO,CAAC;CAEpC,MAAM,WAAW,YAAY;CAC7B,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,MAAM,WAA8B,CAAC;EACrC,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,YAAY,GACnD,KAAK,MAAM,OAAQ,MAA8B,SAAS,CAAC,GAAG,SAAS,KAAK,SAAS,OAAO,KAAK,EAAE,CAAC;EAEtG,KAAK,MAAM,SAAS,SAClB,KAAK,MAAM,OAAQ,MAA8B,SAAS,CAAC,GAAG,SAAS,KAAK,SAAS,OAAO,KAAK,IAAI,CAAC;EAExG,aAAa;GAAE,KAAK,MAAM,KAAK,UAAU,EAAE;EAAG;CAChD,GAAG;EAAC;EAAU;EAAc;CAAO,CAAC;CAEpC,MAAM,UAAU,oBAAoB,KAAK;CAGzC,IAAI,iBAAiB,YAAa,iBAAiB,UAAU,MAAM,GACjE,uBAAuB,SAAS,YAAY;CAG9C,MAAM,SAAS,eAAe;CAC9B,MAAM,2BAAW,IAAI,IAA8B;CACnD,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,OAAO,MAAM;EACnB,IAAI,QAAQ,aAAa,SAAS,IAAI,YAAY,aAAa,aAAa,IAAI,CAAC;EACjF,MAAM,QAAQ,MAAM;EACpB,IAAI,SAAS,aAAa,SAAS,IAAI,aAAa,YAAY,YAAY,UAAU,CAAC,CAAC;EACxF,MAAM,SAAS,MAAM;EACrB,IAAI,UAAU,KAAK,SAAS,IAAI,cAAc,OAAO,KAAK,MAAM,CAAC;CACnE;CACA,gBAAgB,QAAQ;CACxB,0BAA0B,SAAS,KAAK,MAAM;CAI9C,iBAAiB,oBAAoB,KAAK,CAAC;AAC7C;AAEA,SAAS,aACP,aACA,MACY;CACZ,MAAM,cAAe,OAAO,KAAK,IAAI,CAAC,CAAe,KAAK,SACxD,YAAY,SAAS,MAAM,KAAK,KAAc,CAAC;CACjD,aAAa;EAAE,KAAK,MAAM,KAAK,aAAa,EAAE;CAAG;AACnD"}
@@ -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,