@weasel-js/core 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/dist/animation/poseTarget.js +46 -15
  3. package/dist/animation/poseTarget.js.map +1 -1
  4. package/dist/animation/poseTransition.js +3 -3
  5. package/dist/animation/poseTransition.js.map +1 -1
  6. package/dist/animation/reflow.js +3 -6
  7. package/dist/animation/reflow.js.map +1 -1
  8. package/dist/animation/rig/bindRig.js +2 -2
  9. package/dist/animation/rig/bindRig.js.map +1 -1
  10. package/dist/animation/rig/skinBinding.js +2 -2
  11. package/dist/animation/rig/skinBinding.js.map +1 -1
  12. package/dist/barrel/canvas.js +5 -3
  13. package/dist/barrel/chrome.js +3 -1
  14. package/dist/barrel/paths.js +3 -2
  15. package/dist/barrel/routing.js +2 -2
  16. package/dist/barrel/scene.js +6 -1
  17. package/dist/barrel/text.js +2 -2
  18. package/dist/canvas/NodeShape.js +1 -0
  19. package/dist/canvas/NodeShape.js.map +1 -1
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  21. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  23. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  25. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  27. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  29. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
  31. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  32. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  33. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  34. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  35. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  37. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  39. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  41. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  43. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  44. package/dist/canvas/SceneCanvas.js +19 -2
  45. package/dist/canvas/SceneCanvas.js.map +1 -1
  46. package/dist/canvas/accentColor.js +26 -3
  47. package/dist/canvas/accentColor.js.map +1 -1
  48. package/dist/canvas/buildSceneLayer.js +1 -1
  49. package/dist/canvas/buildSceneLayer.js.map +1 -1
  50. package/dist/canvas/buildSceneTree.js +14 -20
  51. package/dist/canvas/buildSceneTree.js.map +1 -1
  52. package/dist/canvas/canvasRenderer.js +6 -2
  53. package/dist/canvas/canvasRenderer.js.map +1 -1
  54. package/dist/canvas/copiesAbove.js +1 -1
  55. package/dist/canvas/copiesAbove.js.map +1 -1
  56. package/dist/canvas/defaultDrawOne.js +10 -7
  57. package/dist/canvas/defaultDrawOne.js.map +1 -1
  58. package/dist/canvas/deps/areaSelect.js +5 -2
  59. package/dist/canvas/deps/areaSelect.js.map +1 -1
  60. package/dist/canvas/deps/booleans.js +1 -1
  61. package/dist/canvas/deps/booleans.js.map +1 -1
  62. package/dist/canvas/deps/enteredGroup.js +81 -0
  63. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  64. package/dist/canvas/deps/hitTestArea.js +27 -15
  65. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  66. package/dist/canvas/deps/insert.js +4 -36
  67. package/dist/canvas/deps/insert.js.map +1 -1
  68. package/dist/canvas/deps/insertSite.js +39 -0
  69. package/dist/canvas/deps/insertSite.js.map +1 -0
  70. package/dist/canvas/deps/lassoSelect.js +6 -3
  71. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  72. package/dist/canvas/deps/pathLeaf.js +1 -1
  73. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  74. package/dist/canvas/deps/slice.js +2 -1
  75. package/dist/canvas/deps/slice.js.map +1 -1
  76. package/dist/canvas/derivedPath.js +14 -6
  77. package/dist/canvas/derivedPath.js.map +1 -1
  78. package/dist/canvas/doubleClickTarget.js +9 -11
  79. package/dist/canvas/doubleClickTarget.js.map +1 -1
  80. package/dist/canvas/drawnBounds.js +16 -7
  81. package/dist/canvas/drawnBounds.js.map +1 -1
  82. package/dist/canvas/inheritedPaints.js +22 -11
  83. package/dist/canvas/inheritedPaints.js.map +1 -1
  84. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  85. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  86. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  87. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  88. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  89. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  90. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  91. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  92. package/dist/canvas/ownClip.js +24 -0
  93. package/dist/canvas/ownClip.js.map +1 -0
  94. package/dist/canvas/paintCull.js.map +1 -1
  95. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  96. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  97. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  98. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  99. package/dist/canvas/pickWalk.js +14 -90
  100. package/dist/canvas/pickWalk.js.map +1 -1
  101. package/dist/canvas/poseCascade.js +1 -1
  102. package/dist/canvas/poseCascade.js.map +1 -1
  103. package/dist/canvas/publishPreviews.js +97 -0
  104. package/dist/canvas/publishPreviews.js.map +1 -0
  105. package/dist/canvas/sceneAdapter.js +7 -5
  106. package/dist/canvas/sceneAdapter.js.map +1 -1
  107. package/dist/canvas/sceneClipboard.js +17 -10
  108. package/dist/canvas/sceneClipboard.js.map +1 -1
  109. package/dist/canvas/scenePickReads.js +37 -0
  110. package/dist/canvas/scenePickReads.js.map +1 -0
  111. package/dist/canvas/scenePickSource.js +74 -0
  112. package/dist/canvas/scenePickSource.js.map +1 -0
  113. package/dist/canvas/sceneSlotWiring.js +9 -4
  114. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  115. package/dist/canvas/sceneViewRender.js +13 -8
  116. package/dist/canvas/sceneViewRender.js.map +1 -1
  117. package/dist/canvas/shownNodes.js +15 -0
  118. package/dist/canvas/shownNodes.js.map +1 -0
  119. package/dist/canvas/useCanvasPaint.js +3 -1
  120. package/dist/canvas/useCanvasPaint.js.map +1 -1
  121. package/dist/canvas/useSceneAdapter.js +5 -3
  122. package/dist/canvas/useSceneAdapter.js.map +1 -1
  123. package/dist/clipboard.d.ts +2 -2
  124. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  125. package/dist/clone.d.ts +3 -3
  126. package/dist/core/isPlainObject.js +2 -2
  127. package/dist/core/layers/layerGroup.js.map +1 -1
  128. package/dist/core/layers/render.js.map +1 -1
  129. package/dist/core/scene/NodeRouting.js.map +1 -1
  130. package/dist/core/scene/arrivalWindow.js +1 -1
  131. package/dist/core/scene/arrivalWindow.js.map +1 -1
  132. package/dist/core/scene/clipNothing.js +2 -5
  133. package/dist/core/scene/clipNothing.js.map +1 -1
  134. package/dist/core/scene/declaredFieldSetters.js +44 -0
  135. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  136. package/dist/core/scene/depMemo.js +3 -0
  137. package/dist/core/scene/depMemo.js.map +1 -1
  138. package/dist/core/scene/dependents.js +3 -1
  139. package/dist/core/scene/dependents.js.map +1 -1
  140. package/dist/core/scene/documentOf.js +43 -0
  141. package/dist/core/scene/documentOf.js.map +1 -0
  142. package/dist/core/scene/effectivePose.js +37 -13
  143. package/dist/core/scene/effectivePose.js.map +1 -1
  144. package/dist/core/scene/fieldEdits.js +17 -14
  145. package/dist/core/scene/fieldEdits.js.map +1 -1
  146. package/dist/core/scene/historyForm.js +4 -4
  147. package/dist/core/scene/historyForm.js.map +1 -1
  148. package/dist/core/scene/inheritedPaint.js +27 -5
  149. package/dist/core/scene/inheritedPaint.js.map +1 -1
  150. package/dist/core/scene/kitOps.js +3 -23
  151. package/dist/core/scene/kitOps.js.map +1 -1
  152. package/dist/core/scene/layoutPass.js +1 -2
  153. package/dist/core/scene/layoutPass.js.map +1 -1
  154. package/dist/core/scene/nodeAsShown.js +72 -0
  155. package/dist/core/scene/nodeAsShown.js.map +1 -0
  156. package/dist/core/scene/nodeFnFields.js +2 -10
  157. package/dist/core/scene/nodeFnFields.js.map +1 -1
  158. package/dist/core/scene/nodeMemo.js +6 -1
  159. package/dist/core/scene/nodeMemo.js.map +1 -1
  160. package/dist/core/scene/nodeStore.js +2 -4
  161. package/dist/core/scene/nodeStore.js.map +1 -1
  162. package/dist/core/scene/pathEffects/derive.js +3 -2
  163. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  164. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  165. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  166. package/dist/core/scene/plyStacks.js +89 -0
  167. package/dist/core/scene/plyStacks.js.map +1 -0
  168. package/dist/core/scene/poseHold.js +42 -24
  169. package/dist/core/scene/poseHold.js.map +1 -1
  170. package/dist/core/scene/poseOverrides.js +35 -38
  171. package/dist/core/scene/poseOverrides.js.map +1 -1
  172. package/dist/core/scene/posePlies.js +59 -0
  173. package/dist/core/scene/posePlies.js.map +1 -0
  174. package/dist/core/scene/reconcileSpecs.js +6 -15
  175. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  176. package/dist/core/scene/renderOrder.js +10 -4
  177. package/dist/core/scene/renderOrder.js.map +1 -1
  178. package/dist/core/scene/sameValue.js +17 -0
  179. package/dist/core/scene/sameValue.js.map +1 -0
  180. package/dist/core/scene/scene.js +3 -3
  181. package/dist/core/scene/scene.js.map +1 -1
  182. package/dist/core/scene/sceneEdits.js +8 -0
  183. package/dist/core/scene/sceneEdits.js.map +1 -1
  184. package/dist/core/scene/sceneState.js +1 -2
  185. package/dist/core/scene/sceneState.js.map +1 -1
  186. package/dist/core/scene/serialization.js +45 -9
  187. package/dist/core/scene/serialization.js.map +1 -1
  188. package/dist/core/scene/staleReplay.js +5 -2
  189. package/dist/core/scene/staleReplay.js.map +1 -1
  190. package/dist/core/scene/statedFields.js +4 -2
  191. package/dist/core/scene/statedFields.js.map +1 -1
  192. package/dist/core/scene/treeEdits.js +2 -4
  193. package/dist/core/scene/treeEdits.js.map +1 -1
  194. package/dist/core/scene/treeOrder.js +3 -1
  195. package/dist/core/scene/treeOrder.js.map +1 -1
  196. package/dist/core/scene/treeReads.js +1 -2
  197. package/dist/core/scene/treeReads.js.map +1 -1
  198. package/dist/core/scene/treeShape.js +13 -0
  199. package/dist/core/scene/treeShape.js.map +1 -0
  200. package/dist/core/scene/types.js.map +1 -1
  201. package/dist/core/scene/useScene.js +1 -1
  202. package/dist/core/scene/useScene.js.map +1 -1
  203. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  204. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-CpIQzKWQ.d.ts} +38 -59
  205. package/dist/devtools/handlers.js +1 -1
  206. package/dist/devtools/handlers.js.map +1 -1
  207. package/dist/devtools/protocol.js.map +1 -1
  208. package/dist/devtools-protocol.d.ts +4 -3
  209. package/dist/devtools.d.ts +3 -2
  210. package/dist/features/measure/measureHit.js +5 -3
  211. package/dist/features/measure/measureHit.js.map +1 -1
  212. package/dist/features/measure/measurement.js +1 -1
  213. package/dist/features/measure/measurement.js.map +1 -1
  214. package/dist/features/modes/modeLayers.js +14 -7
  215. package/dist/features/modes/modeLayers.js.map +1 -1
  216. package/dist/features/modes/modeTriggers.js +3 -3
  217. package/dist/features/modes/modeTriggers.js.map +1 -1
  218. package/dist/features/pathEffects/effectSources.js +25 -16
  219. package/dist/features/pathEffects/effectSources.js.map +1 -1
  220. package/dist/features/pathEffects/readSource.js +1 -1
  221. package/dist/features/pathEffects/readSource.js.map +1 -1
  222. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  223. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  224. package/dist/features/paths/editablePath.js +1 -1
  225. package/dist/features/paths/editablePath.js.map +1 -1
  226. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  227. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  228. package/dist/features/selection/enteredGroupFrame.js +65 -0
  229. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  230. package/dist/features/selection/enteredGroupScope.js +44 -0
  231. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  232. package/dist/features/text/sceneTextDrafts.js +26 -27
  233. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  234. package/dist/features/text/textHitAt.js +4 -2
  235. package/dist/features/text/textHitAt.js.map +1 -1
  236. package/dist/features/text/useSceneTextEdit.js +3 -3
  237. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  238. package/dist/features/viewports/viewportLayer.js +2 -1
  239. package/dist/features/viewports/viewportLayer.js.map +1 -1
  240. package/dist/index.d.ts +230 -77
  241. package/dist/index.js +16 -6
  242. package/dist/interactions/actions/admitsChild.js +11 -0
  243. package/dist/interactions/actions/admitsChild.js.map +1 -0
  244. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  245. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  246. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  247. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  248. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  249. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  250. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  251. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  252. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  253. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  254. package/dist/interactions/actions/defaults/group.js +7 -7
  255. package/dist/interactions/actions/defaults/group.js.map +1 -1
  256. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  257. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  258. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  259. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  260. package/dist/interactions/actions/drawnBoxes.js +2 -1
  261. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  262. package/dist/interactions/actions/insert/action.js +1 -0
  263. package/dist/interactions/actions/insert/action.js.map +1 -1
  264. package/dist/interactions/actions/insert/parent.js +7 -5
  265. package/dist/interactions/actions/insert/parent.js.map +1 -1
  266. package/dist/interactions/actions/insert/placeAction.js +3 -2
  267. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  268. package/dist/interactions/actions/insert/session.js +2 -2
  269. package/dist/interactions/actions/insert/session.js.map +1 -1
  270. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  271. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  272. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  273. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  274. package/dist/interactions/actions/move/layout/exits.js +1 -1
  275. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  276. package/dist/interactions/actions/move/session.js +4 -4
  277. package/dist/interactions/actions/move/session.js.map +1 -1
  278. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  279. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  280. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  281. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  282. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  283. package/dist/math.d.ts +1 -1
  284. package/dist/move.d.ts +4 -4
  285. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  286. package/dist/resize.d.ts +4 -4
  287. package/dist/test-seams.d.ts +6 -6
  288. package/dist/tools/builtin/pressTarget.js +22 -0
  289. package/dist/tools/builtin/pressTarget.js.map +1 -0
  290. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  291. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  292. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  293. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  294. package/dist/tools/builtin/select/groupEntry.js +82 -0
  295. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  296. package/dist/tools/builtin/select/pressDecision.js +1 -1
  297. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  298. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  299. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  300. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  301. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  302. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  303. package/dist/version.js +1 -1
  304. package/package.json +25 -25
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what picking and bounds read. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // Through the adapter, not `n.pose`: an ephemeral override is the pose\n // the renderer draws, so it has to be the one picking tests. World, not\n // local, for the same reason — a framed child is drawn in its parent's\n // frame, not where its own pose says.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n getPose: (id) => adapter.getWorldPose(id),\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, adapter, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what bounds read, and equal the\n * scene's painted poses, which picking reads. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // No `getPose`: the scene's painted poses are the adapter's world poses\n // (`useSceneSelectTool.index.test.tsx`), and they let the index serve the walk.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n index: { descriptor: d as PoseDescriptor<unknown> },\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { useDecayLoop } from "../../core/viewport/useDecayLoop.js";
2
2
  import { drawnBoxes } from "../../interactions/actions/drawnBoxes.js";
3
+ import { useEnteredGroupApiSource } from "../deps/enteredGroup.js";
3
4
  import { useActionSet, useStandardDeps } from "../../interactions/actions/useActionSet.js";
4
5
  import { usePoseDescriptorDepSource } from "../deps/poseDescriptor.js";
5
6
  import { useViewDepSource } from "../deps/view.js";
@@ -34,7 +35,7 @@ import { useActionsRegistry, useDepSource } from "@weasel-js/routing/react";
34
35
  * parent's effects run after its children's, so a child's override would lose.
35
36
  */
36
37
  function useSceneStandardActions(inputs) {
37
- const { props, viewOn, selection, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
38
+ const { props, viewOn, selection, enteredGroup, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, reachable, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
38
39
  const { tune, viewport, ingestion } = props;
39
40
  const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;
40
41
  const reflowTransition = props.reflowTransition;
@@ -67,9 +68,10 @@ function useSceneStandardActions(inputs) {
67
68
  zoom: viewOn ? viewportZoom : false,
68
69
  pinchZoom: viewOn ? viewport?.pinchZoom ?? true : false
69
70
  });
70
- useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf);
71
+ useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
71
72
  useNodeAtPointDepSource(pickEvery);
72
73
  useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);
74
+ useEnteredGroupApiSource(enteredGroup);
73
75
  useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);
74
76
  usePathLeafDepSources(scene, selection, adapter, poseDescriptor);
75
77
  useSnapDepSource(currentViewRef, {
@@ -92,7 +94,7 @@ function useSceneStandardActions(inputs) {
92
94
  usePreviewPathsDepSource(canvasApiRef);
93
95
  useDepChangeNotices(scene, selection.current, editAnchorsExternalState);
94
96
  useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);
95
- useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf);
97
+ useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
96
98
  useTextEditDepSource(scene);
97
99
  useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);
98
100
  useDispatcherDepSource(dispatcher);
@@ -1 +1 @@
1
- {"version":3,"file":"useSceneStandardActions.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneStandardActions.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepSource } from '@weasel-js/routing/react';\nimport type { Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { useDecayLoop } from 'core/viewport/useDecayLoop';\nimport type { ViewAnimationApi } from 'core/viewport/useViewAnimation';\nimport type { View } from 'core/viewport/view';\nimport type { ClipboardIngestCtx } from 'interactions/actions/depSchema';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { useActionSet, useStandardDeps } from 'interactions/actions/useActionSet';\nimport { drawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport type { ReflowTransition } from '../../layout/types';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport type { SnapSource } from 'features/snap/types';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport {\n useAreaSelectDepSource,\n useDebugDepSource,\n usePreviewPathsDepSource,\n useDepChangeNotices,\n useDispatcherDepSource,\n useEditAnchorsDepSource,\n useIngestionDepSource,\n useInsertDepSource,\n useLassoSelectDepSource,\n useLayoutDepSource,\n useNodeAtPointDepSource,\n usePoseDescriptorDepSource,\n usePathLeafDepSources,\n useSnapDepSource,\n useSurfaceDepSources,\n useTextEditDepSource,\n useViewDepSource,\n} from '../deps';\nimport type { BridgeAdapter, SceneCanvasProps } from './props';\nimport { useActionsPropResolver } from './useActionsPropResolver';\nimport { useActionTunings, useBindingOverrides } from './useActionTunings';\nimport { useViewportActions, viewportZoomOption } from './useViewportActions';\n\n/** The `<SceneCanvas>` props the standard actions and their deps read. */\nexport type StandardActionsProps = Pick<\n SceneCanvasProps<unknown, string, unknown>,\n | 'actions' | 'tune' | 'bindingOverrides' | 'geometry' | 'viewport' | 'layoutDropTarget'\n | 'reflowTransition' | 'insertNodeFactories' | 'insertPaint' | 'ingestion' | 'snap'\n>;\n\n/** What `<SceneCanvas>` hands {@link useSceneStandardActions}. */\nexport interface SceneStandardActionsInputs {\n props: StandardActionsProps;\n /** Whether the `view` preset is on; without it the viewport actions are off. */\n viewOn: boolean;\n selection: SelectionApi;\n scene: Scene<unknown, string, unknown>;\n adapter: BridgeAdapter;\n /** The standard actions this canvas registers: its presets' and the ones\n * its tools bind, in registration order. */\n standardActions: readonly Action[];\n currentViewRef: React.RefObject<View>;\n onViewChange: (v: View) => void;\n /** Wires the `poseDescriptor` dep every built-in pose-touching action reads. */\n poseDescriptor: PoseDescriptor<unknown>;\n /** Forwarded so `cancelGestureAction` and other actions that need to\n * abort in-flight handles can read the dispatcher's control surface. */\n dispatcher: Dispatcher;\n /** World-space picker forwarded so the `nodeAtPoint` dep source can\n * reuse the same hit-test plumbing the tool dispatcher uses. */\n pickEvery: (worldX: number, worldY: number) => string[];\n /** This canvas's own view gate for scene layers, published on the `view`\n * dep and read by the selecting actions. Absent when it hides nothing. */\n layerIsPainted?: (layerId: string) => boolean;\n /** The composed paint alpha, when the consumer fades anything, so a region\n * select passes over what is painted at zero. */\n alphaOf?: (id: string) => number;\n /** The canvas's camera runner, published on the `view` dep so\n * `viewport.zoom`'s discrete branches can animate. */\n viewAnimation: ViewAnimationApi;\n /** Lifted edit-mode state so the `pathEditingOverlay` chrome (rendered\n * outside this subtree) can read the same `editingId` the dep does. */\n editAnchorsExternalState: import('../deps/editAnchors').EditAnchorsStateSource;\n /** The snap sources a session asks: the prop's and the presets', then the registry's. */\n snap: { sources: readonly SnapSource[]; registry: SnapSourceRegistry | null; store: SnapOutcomeStore };\n /** The canvas element ref, so `useIngestionDepSource` can compute the\n * visible world rect from the client rect + current view. */\n canvasRef: React.RefObject<HTMLElement | null>;\n /** The canvas handle, whose debug sink backs the `debug` dep. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n /** Clipboard-paste ctx (this canvas's adapter + the consumer's reviver)\n * for the kit weasel-JSON handler, from `useCanvasIngestion`; absent\n * when disabled. */\n ingestionClipboard?: ClipboardIngestCtx;\n}\n\n/**\n * Registers this canvas's standard actions, and the deps they read, into its\n * input scope, then applies the `actions` prop and `tune` over them.\n *\n * Called from a component ahead of `<SceneCanvas>`'s children, never from the\n * canvas's own body: registrations stack newest-live in effect order, and a\n * parent's effects run after its children's, so a child's override would lose.\n */\nexport function useSceneStandardActions(inputs: SceneStandardActionsInputs): void {\n const {\n props, viewOn, selection, scene, adapter, standardActions, currentViewRef,\n onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, viewAnimation,\n editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard,\n } = inputs;\n const { tune, viewport, ingestion } = props;\n const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;\n const reflowTransition = props.reflowTransition as ReflowTransition<unknown> | null | undefined;\n const viewportZoom = useMemo(\n () => viewportZoomOption(viewport?.zoom, viewport?.animatedZoom),\n [viewport?.zoom, viewport?.animatedZoom],\n );\n const registry = useActionsRegistry();\n\n // Wire the dispatcher into the registry so registry.begin() can delegate\n // to dispatcher.beginUiOngoing() for UI-driven ongoing actions (color,\n // opacity).\n useEffect(() => {\n if (!registry) return;\n return registry.setDispatcher(dispatcher);\n }, [registry, dispatcher]);\n\n // Momentum for `viewport.dragPan`. The loop must be a hook (it owns a\n // `useVisibleRaf`), so it is built here and republished on the view dep.\n const decayLoop = useDecayLoop();\n\n // Build the ViewApi (stable identity, refreshed closures) and hand it to\n // useStandardDeps (which publishes the `view` dep along with selection,\n // scene, history, activeTool). `hostSize` reads the live canvas\n // element so keyboard zoom (Cmd+=/-) can anchor at the visible center.\n const view = useViewDepSource(\n currentViewRef,\n onViewChange,\n viewport?.recenter,\n () => {\n const el = canvasRef.current;\n return el ? { width: el.clientWidth, height: el.clientHeight } : null;\n },\n viewAnimation,\n decayLoop,\n layerIsPainted,\n );\n useStandardDeps({ selection, scene, view, history: scene.history });\n useActionSet(standardActions);\n // A view overlays `view` with its own camera; this name it leaves alone.\n useDepSource('rootView', () => view);\n useSurfaceDepSources(canvasRef, currentViewRef);\n useDepSource('actionParams', () => registry?.paramsOf);\n\n // viewport.pan / viewport.zoom are SceneCanvas-coupled (need the `view` dep\n // published just above), so they're registered here rather than in\n // KIT_STANDARD_DESCRIPTORS. Both default ON; consumer opts out via\n // `viewport={{ pan: false }}` / `viewport={{ zoom: false }}`.\n useViewportActions({\n pan: viewOn ? (viewport?.pan ?? true) : false,\n zoom: viewOn ? viewportZoom : false,\n pinchZoom: viewOn ? (viewport?.pinchZoom ?? true) : false,\n });\n\n // Per-dep wiring modules under `src/canvas/deps/`. See each file for the\n // dep's contract and trade-offs.\n useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf);\n useNodeAtPointDepSource(pickEvery);\n useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);\n useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);\n usePathLeafDepSources(scene, selection, adapter, poseDescriptor);\n useSnapDepSource(\n currentViewRef,\n {\n scene, boxes: () => drawnBoxes({ scene, poseDescriptor, view, boundsIncludeStroke }), sources: snap.sources,\n ...(snap.registry ? { registry: snap.registry } : {}),\n ...(props.snap?.reachPx !== undefined ? { reachPx: props.snap.reachPx } : {}),\n ...(props.snap?.omit ? { omit: props.snap.omit } : {}),\n ...(props.snap?.originProjection ? { originProjection: props.snap.originProjection } : {}),\n poseDescriptor,\n debug: () => canvasApiRef.current?.getDebug(),\n },\n snap.store,\n );\n useDebugDepSource(canvasApiRef);\n usePreviewPathsDepSource(canvasApiRef);\n useDepChangeNotices(scene, selection.current, editAnchorsExternalState);\n useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);\n useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf);\n useTextEditDepSource(scene);\n useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);\n useDispatcherDepSource(dispatcher);\n usePoseDescriptorDepSource(poseDescriptor);\n useDepSource('boundsIncludeStroke', () => boundsIncludeStroke);\n\n // Keyed on the set too: a re-registration of it would otherwise land over\n // the overrides applied on top of it.\n useActionsPropResolver(props.actions, standardActions.map((a) => a.id).join('|'));\n useActionTunings(tune);\n useBindingOverrides(props.bindingOverrides);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA,SAAgB,wBAAwB,QAA0C;CAChF,MAAM,EACJ,OAAO,QAAQ,WAAW,OAAO,SAAS,iBAAiB,gBAC3D,cAAc,gBAAgB,YAAY,WAAW,gBAAgB,SAAS,eAC9E,0BAA0B,MAAM,WAAW,cAAc,uBACvD;CACJ,MAAM,EAAE,MAAM,UAAU,cAAc;CACtC,MAAM,sBAAsB,MAAM,UAAU,uBAAuB;CACnE,MAAM,mBAAmB,MAAM;CAC/B,MAAM,eAAe,cACb,mBAAmB,UAAU,MAAM,UAAU,YAAY,GAC/D,CAAC,UAAU,MAAM,UAAU,YAAY,CACzC;CACA,MAAM,WAAW,mBAAmB;CAKpC,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,OAAO,SAAS,cAAc,UAAU;CAC1C,GAAG,CAAC,UAAU,UAAU,CAAC;CAIzB,MAAM,YAAY,aAAa;CAM/B,MAAM,OAAO,iBACX,gBACA,cACA,UAAU,gBACJ;EACJ,MAAM,KAAK,UAAU;EACrB,OAAO,KAAK;GAAE,OAAO,GAAG;GAAa,QAAQ,GAAG;EAAa,IAAI;CACnE,GACA,eACA,WACA,cACF;CACA,gBAAgB;EAAE;EAAW;EAAO;EAAM,SAAS,MAAM;CAAQ,CAAC;CAClE,aAAa,eAAe;CAE5B,aAAa,kBAAkB,IAAI;CACnC,qBAAqB,WAAW,cAAc;CAC9C,aAAa,sBAAsB,UAAU,QAAQ;CAMrD,mBAAmB;EACjB,KAAK,SAAU,UAAU,OAAO,OAAQ;EACxC,MAAM,SAAS,eAAe;EAC9B,WAAW,SAAU,UAAU,aAAa,OAAQ;CACtD,CAAC;CAID,uBAAuB,OAAO,WAAW,gBAAgB,OAAO;CAChE,wBAAwB,SAAS;CACjC,mBAAmB,OAAO,MAAM,kBAAkB,gBAAgB;CAClE,mBAAmB,OAAO,SAAS,MAAM,qBAAqB,MAAM,WAAW;CAC/E,sBAAsB,OAAO,WAAW,SAAS,cAAc;CAC/D,iBACE,gBACA;EACE;EAAO,aAAa,WAAW;GAAE;GAAO;GAAgB;GAAM;EAAoB,CAAC;EAAG,SAAS,KAAK;EACpG,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACnD,GAAI,MAAM,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,KAAK,QAAQ,IAAI,CAAC;EAC3E,GAAI,MAAM,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK,IAAI,CAAC;EACpD,GAAI,MAAM,MAAM,mBAAmB,EAAE,kBAAkB,MAAM,KAAK,iBAAiB,IAAI,CAAC;EACxF;EACA,aAAa,aAAa,SAAS,SAAS;CAC9C,GACA,KAAK,KACP;CACA,kBAAkB,YAAY;CAC9B,yBAAyB,YAAY;CACrC,oBAAoB,OAAO,UAAU,SAAS,wBAAwB;CACtE,sBAAsB,iBAAiB,eAAe,SAAS,WAAW,YAAY,WAAW,KAAK,kBAAkB;CACxH,wBAAwB,OAAO,WAAW,gBAAgB,OAAO;CACjE,qBAAqB,KAAK;CAC1B,wBAAwB,OAAO,WAAW,SAAS,0BAA0B,IAAI;CACjF,uBAAuB,UAAU;CACjC,2BAA2B,cAAc;CACzC,aAAa,6BAA6B,mBAAmB;CAI7D,uBAAuB,MAAM,SAAS,gBAAgB,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;CAChF,iBAAiB,IAAI;CACrB,oBAAoB,MAAM,gBAAgB;AAC5C"}
1
+ {"version":3,"file":"useSceneStandardActions.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneStandardActions.ts"],"sourcesContent":["import type { Action } from '@weasel-js/routing';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepSource } from '@weasel-js/routing/react';\nimport type { Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { useDecayLoop } from 'core/viewport/useDecayLoop';\nimport type { ViewAnimationApi } from 'core/viewport/useViewAnimation';\nimport type { View } from 'core/viewport/view';\nimport type { ClipboardIngestCtx } from 'interactions/actions/depSchema';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { useActionSet, useStandardDeps } from 'interactions/actions/useActionSet';\nimport { drawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport type { ReflowTransition } from '../../layout/types';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport type { SnapSource } from 'features/snap/types';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport {\n useAreaSelectDepSource,\n useDebugDepSource,\n usePreviewPathsDepSource,\n useDepChangeNotices,\n useDispatcherDepSource,\n useEditAnchorsDepSource,\n useEnteredGroupApiSource,\n useIngestionDepSource,\n useInsertDepSource,\n useLassoSelectDepSource,\n useLayoutDepSource,\n useNodeAtPointDepSource,\n usePoseDescriptorDepSource,\n usePathLeafDepSources,\n useSnapDepSource,\n useSurfaceDepSources,\n useTextEditDepSource,\n useViewDepSource,\n} from '../deps';\nimport type { EnteredGroupApi } from '../deps/enteredGroup';\nimport type { BridgeAdapter, SceneCanvasProps } from './props';\nimport { useActionsPropResolver } from './useActionsPropResolver';\nimport { useActionTunings, useBindingOverrides } from './useActionTunings';\nimport { useViewportActions, viewportZoomOption } from './useViewportActions';\n\n/** The `<SceneCanvas>` props the standard actions and their deps read. */\nexport type StandardActionsProps = Pick<\n SceneCanvasProps<unknown, string, unknown>,\n | 'actions' | 'tune' | 'bindingOverrides' | 'geometry' | 'viewport' | 'layoutDropTarget'\n | 'reflowTransition' | 'insertNodeFactories' | 'insertPaint' | 'ingestion' | 'snap'\n>;\n\n/** What `<SceneCanvas>` hands {@link useSceneStandardActions}. */\nexport interface SceneStandardActionsInputs {\n props: StandardActionsProps;\n /** Whether the `view` preset is on; without it the viewport actions are off. */\n viewOn: boolean;\n selection: SelectionApi;\n /** The group a press resolves inside of, shared with the picking that names double-clicks. */\n enteredGroup: EnteredGroupApi;\n scene: Scene<unknown, string, unknown>;\n adapter: BridgeAdapter;\n /** The standard actions this canvas registers: its presets' and the ones\n * its tools bind, in registration order. */\n standardActions: readonly Action[];\n currentViewRef: React.RefObject<View>;\n onViewChange: (v: View) => void;\n /** Wires the `poseDescriptor` dep every built-in pose-touching action reads. */\n poseDescriptor: PoseDescriptor<unknown>;\n /** Forwarded so `cancelGestureAction` and other actions that need to\n * abort in-flight handles can read the dispatcher's control surface. */\n dispatcher: Dispatcher;\n /** World-space picker forwarded so the `nodeAtPoint` dep source can\n * reuse the same hit-test plumbing the tool dispatcher uses. */\n pickEvery: (worldX: number, worldY: number) => string[];\n /** This canvas's own view gate for scene layers, published on the `view`\n * dep and read by the selecting actions. Absent when it hides nothing. */\n layerIsPainted?: (layerId: string) => boolean;\n /** The composed paint alpha, when the consumer fades anything, so a region\n * select passes over what is painted at zero. */\n alphaOf?: (id: string) => number;\n /** Whether a node takes hits: what a region select may take, as a press may. */\n reachable?: (id: string) => boolean;\n /** The canvas's camera runner, published on the `view` dep so\n * `viewport.zoom`'s discrete branches can animate. */\n viewAnimation: ViewAnimationApi;\n /** Lifted edit-mode state so the `pathEditingOverlay` chrome (rendered\n * outside this subtree) can read the same `editingId` the dep does. */\n editAnchorsExternalState: import('../deps/editAnchors').EditAnchorsStateSource;\n /** The snap sources a session asks: the prop's and the presets', then the registry's. */\n snap: { sources: readonly SnapSource[]; registry: SnapSourceRegistry | null; store: SnapOutcomeStore };\n /** The canvas element ref, so `useIngestionDepSource` can compute the\n * visible world rect from the client rect + current view. */\n canvasRef: React.RefObject<HTMLElement | null>;\n /** The canvas handle, whose debug sink backs the `debug` dep. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n /** Clipboard-paste ctx (this canvas's adapter + the consumer's reviver)\n * for the kit weasel-JSON handler, from `useCanvasIngestion`; absent\n * when disabled. */\n ingestionClipboard?: ClipboardIngestCtx;\n}\n\n/**\n * Registers this canvas's standard actions, and the deps they read, into its\n * input scope, then applies the `actions` prop and `tune` over them.\n *\n * Called from a component ahead of `<SceneCanvas>`'s children, never from the\n * canvas's own body: registrations stack newest-live in effect order, and a\n * parent's effects run after its children's, so a child's override would lose.\n */\nexport function useSceneStandardActions(inputs: SceneStandardActionsInputs): void {\n const {\n props, viewOn, selection, enteredGroup, scene, adapter, standardActions, currentViewRef,\n onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, reachable, viewAnimation,\n editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard,\n } = inputs;\n const { tune, viewport, ingestion } = props;\n const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;\n const reflowTransition = props.reflowTransition as ReflowTransition<unknown> | null | undefined;\n const viewportZoom = useMemo(\n () => viewportZoomOption(viewport?.zoom, viewport?.animatedZoom),\n [viewport?.zoom, viewport?.animatedZoom],\n );\n const registry = useActionsRegistry();\n\n // Wire the dispatcher into the registry so registry.begin() can delegate\n // to dispatcher.beginUiOngoing() for UI-driven ongoing actions (color,\n // opacity).\n useEffect(() => {\n if (!registry) return;\n return registry.setDispatcher(dispatcher);\n }, [registry, dispatcher]);\n\n // Momentum for `viewport.dragPan`. The loop must be a hook (it owns a\n // `useVisibleRaf`), so it is built here and republished on the view dep.\n const decayLoop = useDecayLoop();\n\n // Build the ViewApi (stable identity, refreshed closures) and hand it to\n // useStandardDeps (which publishes the `view` dep along with selection,\n // scene, history, activeTool). `hostSize` reads the live canvas\n // element so keyboard zoom (Cmd+=/-) can anchor at the visible center.\n const view = useViewDepSource(\n currentViewRef,\n onViewChange,\n viewport?.recenter,\n () => {\n const el = canvasRef.current;\n return el ? { width: el.clientWidth, height: el.clientHeight } : null;\n },\n viewAnimation,\n decayLoop,\n layerIsPainted,\n );\n useStandardDeps({ selection, scene, view, history: scene.history });\n useActionSet(standardActions);\n // A view overlays `view` with its own camera; this name it leaves alone.\n useDepSource('rootView', () => view);\n useSurfaceDepSources(canvasRef, currentViewRef);\n useDepSource('actionParams', () => registry?.paramsOf);\n\n // viewport.pan / viewport.zoom are SceneCanvas-coupled (need the `view` dep\n // published just above), so they're registered here rather than in\n // KIT_STANDARD_DESCRIPTORS. Both default ON; consumer opts out via\n // `viewport={{ pan: false }}` / `viewport={{ zoom: false }}`.\n useViewportActions({\n pan: viewOn ? (viewport?.pan ?? true) : false,\n zoom: viewOn ? viewportZoom : false,\n pinchZoom: viewOn ? (viewport?.pinchZoom ?? true) : false,\n });\n\n // Per-dep wiring modules under `src/canvas/deps/`. See each file for the\n // dep's contract and trade-offs.\n useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);\n useNodeAtPointDepSource(pickEvery);\n useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);\n useEnteredGroupApiSource(enteredGroup);\n useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);\n usePathLeafDepSources(scene, selection, adapter, poseDescriptor);\n useSnapDepSource(\n currentViewRef,\n {\n scene, boxes: () => drawnBoxes({ scene, poseDescriptor, view, boundsIncludeStroke }), sources: snap.sources,\n ...(snap.registry ? { registry: snap.registry } : {}),\n ...(props.snap?.reachPx !== undefined ? { reachPx: props.snap.reachPx } : {}),\n ...(props.snap?.omit ? { omit: props.snap.omit } : {}),\n ...(props.snap?.originProjection ? { originProjection: props.snap.originProjection } : {}),\n poseDescriptor,\n debug: () => canvasApiRef.current?.getDebug(),\n },\n snap.store,\n );\n useDebugDepSource(canvasApiRef);\n usePreviewPathsDepSource(canvasApiRef);\n useDepChangeNotices(scene, selection.current, editAnchorsExternalState);\n useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);\n useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);\n useTextEditDepSource(scene);\n useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);\n useDispatcherDepSource(dispatcher);\n usePoseDescriptorDepSource(poseDescriptor);\n useDepSource('boundsIncludeStroke', () => boundsIncludeStroke);\n\n // Keyed on the set too: a re-registration of it would otherwise land over\n // the overrides applied on top of it.\n useActionsPropResolver(props.actions, standardActions.map((a) => a.id).join('|'));\n useActionTunings(tune);\n useBindingOverrides(props.bindingOverrides);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,SAAgB,wBAAwB,QAA0C;CAChF,MAAM,EACJ,OAAO,QAAQ,WAAW,cAAc,OAAO,SAAS,iBAAiB,gBACzE,cAAc,gBAAgB,YAAY,WAAW,gBAAgB,SAAS,WAAW,eACzF,0BAA0B,MAAM,WAAW,cAAc,uBACvD;CACJ,MAAM,EAAE,MAAM,UAAU,cAAc;CACtC,MAAM,sBAAsB,MAAM,UAAU,uBAAuB;CACnE,MAAM,mBAAmB,MAAM;CAC/B,MAAM,eAAe,cACb,mBAAmB,UAAU,MAAM,UAAU,YAAY,GAC/D,CAAC,UAAU,MAAM,UAAU,YAAY,CACzC;CACA,MAAM,WAAW,mBAAmB;CAKpC,gBAAgB;EACd,IAAI,CAAC,UAAU;EACf,OAAO,SAAS,cAAc,UAAU;CAC1C,GAAG,CAAC,UAAU,UAAU,CAAC;CAIzB,MAAM,YAAY,aAAa;CAM/B,MAAM,OAAO,iBACX,gBACA,cACA,UAAU,gBACJ;EACJ,MAAM,KAAK,UAAU;EACrB,OAAO,KAAK;GAAE,OAAO,GAAG;GAAa,QAAQ,GAAG;EAAa,IAAI;CACnE,GACA,eACA,WACA,cACF;CACA,gBAAgB;EAAE;EAAW;EAAO;EAAM,SAAS,MAAM;CAAQ,CAAC;CAClE,aAAa,eAAe;CAE5B,aAAa,kBAAkB,IAAI;CACnC,qBAAqB,WAAW,cAAc;CAC9C,aAAa,sBAAsB,UAAU,QAAQ;CAMrD,mBAAmB;EACjB,KAAK,SAAU,UAAU,OAAO,OAAQ;EACxC,MAAM,SAAS,eAAe;EAC9B,WAAW,SAAU,UAAU,aAAa,OAAQ;CACtD,CAAC;CAID,uBAAuB,OAAO,WAAW,gBAAgB,SAAS,SAAS;CAC3E,wBAAwB,SAAS;CACjC,mBAAmB,OAAO,MAAM,kBAAkB,gBAAgB;CAClE,yBAAyB,YAAY;CACrC,mBAAmB,OAAO,SAAS,MAAM,qBAAqB,MAAM,WAAW;CAC/E,sBAAsB,OAAO,WAAW,SAAS,cAAc;CAC/D,iBACE,gBACA;EACE;EAAO,aAAa,WAAW;GAAE;GAAO;GAAgB;GAAM;EAAoB,CAAC;EAAG,SAAS,KAAK;EACpG,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACnD,GAAI,MAAM,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,KAAK,QAAQ,IAAI,CAAC;EAC3E,GAAI,MAAM,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,KAAK,IAAI,CAAC;EACpD,GAAI,MAAM,MAAM,mBAAmB,EAAE,kBAAkB,MAAM,KAAK,iBAAiB,IAAI,CAAC;EACxF;EACA,aAAa,aAAa,SAAS,SAAS;CAC9C,GACA,KAAK,KACP;CACA,kBAAkB,YAAY;CAC9B,yBAAyB,YAAY;CACrC,oBAAoB,OAAO,UAAU,SAAS,wBAAwB;CACtE,sBAAsB,iBAAiB,eAAe,SAAS,WAAW,YAAY,WAAW,KAAK,kBAAkB;CACxH,wBAAwB,OAAO,WAAW,gBAAgB,SAAS,SAAS;CAC5E,qBAAqB,KAAK;CAC1B,wBAAwB,OAAO,WAAW,SAAS,0BAA0B,IAAI;CACjF,uBAAuB,UAAU;CACjC,2BAA2B,cAAc;CACzC,aAAa,6BAA6B,mBAAmB;CAI7D,uBAAuB,MAAM,SAAS,gBAAgB,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;CAChF,iBAAiB,IAAI;CACrB,oBAAoB,MAAM,gBAAgB;AAC5C"}
@@ -3,7 +3,9 @@ import { AUTO_POSE_DESCRIPTOR } from "../core/geometry/autoPoseDescriptor.js";
3
3
  import { ViewRegistryProvider } from "./viewRegistry.js";
4
4
  import { rotateAroundAABBCenter } from "./poseRotation.js";
5
5
  import { defaultDrawOne } from "./defaultDrawOne.js";
6
+ import { chromeColorOf, watchThemeOf } from "./accentColor.js";
6
7
  import { CORE_PRESET_SOURCE } from "./SceneCanvas/namedPresets.js";
8
+ import { useGroupEntryContribution } from "../tools/builtin/select/groupEntry.js";
7
9
  import { useCanvasCamera } from "./SceneCanvas/useCanvasCamera.js";
8
10
  import { useHeldModifiers } from "./SceneCanvas/useHeldModifiers.js";
9
11
  import { useCanvasIngestion } from "../features/ingestion/useCanvasIngestion.js";
@@ -16,6 +18,7 @@ import { contributionEntries } from "./SceneCanvas/useContributionRoles.js";
16
18
  import { SceneCanvasInput } from "./SceneCanvas/SceneCanvasInput.js";
17
19
  import { useBakedScene } from "./SceneCanvas/useBakedScene.js";
18
20
  import { useCanvasDispatcher } from "./SceneCanvas/useCanvasDispatcher.js";
21
+ import { publishPreviews } from "./publishPreviews.js";
19
22
  import { useCanvasSelection } from "./SceneCanvas/useCanvasSelection.js";
20
23
  import { SCENE_CANVAS_FEATURES } from "./SceneCanvas/features.js";
21
24
  import { BUILTIN_TOOLS, BUILTIN_TOOL_IDS, DRAW_TOOLS, PAGE_TOOLS, SELECT_TOOLS, SHAPE_TOOLS, TYPE_TOOLS, VIEW_TOOLS } from "./SceneCanvas/builtinToolSets.js";
@@ -24,6 +27,7 @@ import { useEnabledFeatures } from "./SceneCanvas/useCanvasTools.js";
24
27
  import { SurfaceDeclarations } from "./SceneCanvas/SurfaceDeclarations.js";
25
28
  import { useAddedViews, useSceneCanvasHandle } from "./SceneCanvas/useSceneCanvasHandle.js";
26
29
  import { mergeLayersWithDefaults } from "./SceneCanvas/mergeLayers.js";
30
+ import { ENTERED_FRAME_COLOR, ENTERED_FRAME_TOKEN } from "../features/selection/enteredGroupFrame.js";
27
31
  import { useSceneCanvasLayers } from "./SceneCanvas/useSceneCanvasLayers.js";
28
32
  import { useSceneCanvasPathEdit } from "./SceneCanvas/useSceneCanvasPathEdit.js";
29
33
  import { useSceneCanvasTools } from "./SceneCanvas/useSceneCanvasTools.js";
@@ -32,7 +36,7 @@ import { useSceneRedrawSources } from "./SceneCanvas/useSceneRedrawSources.js";
32
36
  import { useScenePicking } from "./SceneCanvas/useScenePicking.js";
33
37
  import { useNodeKinds } from "./SceneCanvas/useNodeKinds.js";
34
38
  import { useSurfaceViewInputs } from "./SceneCanvas/useSurfaceViewInputs.js";
35
- import { forwardRef, useMemo, useRef } from "react";
39
+ import { forwardRef, useEffect, useMemo, useRef } from "react";
36
40
  import { useLatest } from "@weasel-js/react";
37
41
  import { jsx, jsxs } from "react/jsx-runtime";
38
42
  import { InputScope, useActionsRegistry } from "@weasel-js/routing/react";
@@ -100,21 +104,34 @@ function SceneCanvasInner(props, ref) {
100
104
  });
101
105
  const { adapter, boundsOf, planeOfNode } = picking;
102
106
  const getNodeAtPointRef = useLatest(picking.getNodeAtPoint);
107
+ const groupEntry = useGroupEntryContribution({
108
+ pickHit: picking.pickBest,
109
+ tree: adapter,
110
+ boxOf: boundsOf
111
+ });
103
112
  const tools = useSceneCanvasTools({
104
113
  props,
105
114
  select: picking.selectTool,
106
115
  move: picking.moveOptions,
107
116
  enabled,
108
- presetSource: props.presetSource
117
+ presetSource: props.presetSource,
118
+ groupEntry
109
119
  });
110
120
  const actionsRegistry = useActionsRegistry();
111
121
  const { dispatcher, gestureSource, previewIdsExtra, previewPoseExtra } = useCanvasDispatcher(actionsRegistry);
122
+ useEffect(() => publishPreviews(scene, dispatcher), [scene, dispatcher]);
123
+ const frameColor = useMemo(() => ({
124
+ read: () => chromeColorOf(internalCanvasRef.current, ENTERED_FRAME_TOKEN, ENTERED_FRAME_COLOR),
125
+ changed: (fn) => watchThemeOf(() => internalCanvasRef.current, fn)
126
+ }), []);
112
127
  const layered = useSceneCanvasLayers({
113
128
  layers: props.layers,
114
129
  targetScale: deviceProfile.targetScale,
115
130
  scene,
116
131
  adapter,
117
132
  poseDescriptor: descriptor,
133
+ enteredGroup: picking.enteredGroup,
134
+ frameColor,
118
135
  alphaFor: props.alphaFor,
119
136
  colorOverrides: props.animator?.colorOverrides,
120
137
  boundsOf,
@@ -1 +1 @@
1
- {"version":3,"file":"SceneCanvas.js","names":[],"sources":["../../src/canvas/SceneCanvas.tsx"],"sourcesContent":["/**\n * `<SceneCanvas>` — `<Canvas>` wired to a `Scene` primitive. Bare, it only renders: behavior\n * comes from the `features` presets (`canvas/SceneCanvas/features.ts`), each of which turns on\n * its own actions, tools, ambient bindings, and chrome.\n *\n * A consumer needing custom tools can pass `tools={useTools(...)}` and SceneCanvas forwards it\n * as-is — the internal tool registry is ignored in that case.\n */\nimport { getActiveModeFor } from '@weasel-js/modes';\nimport { useLatest } from '@weasel-js/react';\nimport { InputScope, useActionsRegistry } from '@weasel-js/routing/react';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { useCanvasCamera } from './SceneCanvas/useCanvasCamera';\nimport { useHeldModifiers } from './SceneCanvas/useHeldModifiers';\nimport { useCanvasIngestion } from 'features/ingestion/useCanvasIngestion';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type React from 'react';\nimport { forwardRef, useMemo, useRef } from 'react';\nimport { DeviceProfileProvider, useDeviceProfile } from '../core/device/useDeviceProfile';\nimport { Canvas } from './Canvas';\nimport { canvasDebug, DevHuds } from './SceneCanvas/DevHuds';\nimport { CanvasKitProviders } from './SceneCanvas/CanvasKitProviders';\nimport { canvasPassThrough } from './SceneCanvas/canvasPassThrough';\nimport { SceneCanvasInput, type SceneCanvasInputOptions } from './SceneCanvas/SceneCanvasInput';\nimport type { PresetSource } from './SceneCanvas/features';\nimport { CORE_PRESET_SOURCE } from './SceneCanvas/namedPresets';\nimport type { BridgeAdapter, SceneCanvasProps } from './SceneCanvas/props';\nimport { useBakedScene } from './SceneCanvas/useBakedScene';\nimport { useCanvasDispatcher } from './SceneCanvas/useCanvasDispatcher';\nimport { useCanvasSelection } from './SceneCanvas/useCanvasSelection';\nimport { useEnabledFeatures } from './SceneCanvas/useCanvasTools';\nimport { contributionEntries } from './SceneCanvas/useContributionRoles';\nimport { SurfaceDeclarations } from './SceneCanvas/SurfaceDeclarations';\nimport { useAddedViews, useSceneCanvasHandle } from './SceneCanvas/useSceneCanvasHandle';\nimport { useSceneCanvasLayers } from './SceneCanvas/useSceneCanvasLayers';\nimport { useSceneCanvasPathEdit } from './SceneCanvas/useSceneCanvasPathEdit';\nimport { useSceneCanvasTools, type SceneCanvasToolProps } from './SceneCanvas/useSceneCanvasTools';\nimport { useSceneCanvasChrome } from './SceneCanvas/useSceneChrome';\nimport { useDevtoolsOverride } from '../devtools/useDevtoolsRoot';\nimport { useSceneRedrawSources } from './SceneCanvas/useSceneRedrawSources';\nimport { useScenePicking } from './SceneCanvas/useScenePicking';\nimport { useNodeKinds } from './SceneCanvas/useNodeKinds';\nimport { useSurfaceViewInputs } from './SceneCanvas/useSurfaceViewInputs';\nimport type { CanvasExtensionApi, SceneCanvasApi } from './canvasExtension';\nimport { ViewRegistryProvider } from './viewRegistry';\nexport { SCENE_CANVAS_FEATURES, type Feature } from './SceneCanvas/features';\nexport type { BuiltinToolOptions } from './SceneCanvas/useBuiltinShapeTools';\nexport { defaultDrawOne } from './defaultDrawOne';\nexport { rotateAroundAABBCenter } from './poseRotation';\n\nfunction SceneCanvasInner<TData, TLayer extends string, TPose>(\n props: SceneCanvasProps<TData, TLayer, TPose> & { presetSource: PresetSource },\n ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n const { selectTool: selectToolOpts, selectable = true, viewport, modes } = props;\n const devtools = useDevtoolsOverride(props.debug), { debug } = devtools;\n const descriptor = (props.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const deviceProfile = useDeviceProfile();\n const scene = useBakedScene(props.scene);\n const getActiveMode = useMemo(() => (modes ? getActiveModeFor(modes) : undefined), [modes]);\n const heldModifiers = useHeldModifiers();\n\n // Our own refs, so input listeners attach even when the consumer forwards its own ref.\n const internalCanvasRef = useRef<HTMLElement | null>(null);\n const canvasApiRef = useRef<CanvasExtensionApi | null>(null);\n const declaredSize = useLatest({ width: props.width ?? 0, height: props.height ?? 0 });\n const { viewState, viewAnimation, camera } = useCanvasCamera({\n view: props.view, defaultView: props.defaultView, onViewChange: props.onViewChange, viewBounds: props.viewBounds,\n dims: () => canvasApiRef.current?.getSurfaceRect() ?? declaredSize.current,\n requestRedraw: () => { canvasApiRef.current?.requestRedraw(); },\n });\n const { viewRef } = viewState;\n const kinds = useNodeKinds(scene, props.routing);\n const selection = useCanvasSelection({ scene, props, selectable, kindOf: kinds.kindOf });\n const enabled = useEnabledFeatures(\n props.features, props.presetSource.named, viewport !== undefined, selectToolOpts?.rotate !== false,\n );\n const { pathEditState, selectToolWithDefaults } = useSceneCanvasPathEdit({\n host: props.pathEdit, opening: props.defaultPathEdit, getActiveMode, scene, selection,\n extensions: enabled.pathEdit, selectTool: selectToolOpts,\n });\n const picking = useScenePicking({ scene, selection, descriptor, props, viewRef, selectTool: selectToolWithDefaults, kinds });\n const { adapter, boundsOf, planeOfNode } = picking;\n const getNodeAtPointRef = useLatest(picking.getNodeAtPoint);\n const tools = useSceneCanvasTools({\n props: props as SceneCanvasToolProps, select: picking.selectTool, move: picking.moveOptions,\n enabled, presetSource: props.presetSource,\n });\n\n // The wrapper's `<InputScope>` provides it.\n const actionsRegistry = useActionsRegistry();\n const { dispatcher, gestureSource, previewIdsExtra, previewPoseExtra } = useCanvasDispatcher(actionsRegistry);\n\n const layered = useSceneCanvasLayers({\n layers: props.layers, targetScale: deviceProfile.targetScale, scene, adapter, poseDescriptor: descriptor,\n alphaFor: props.alphaFor, colorOverrides: props.animator?.colorOverrides, boundsOf, planeOfNode,\n poseTransition: props.poseTransition, previewIds: previewIdsExtra, dispatcher, snapReadout: props.snap?.readout !== false, snapReadoutStyle: props.snap?.readoutStyle, pathEditState,\n slops: debug !== undefined && debug !== false && debug.slops === true,\n boundsIncludeStroke: props.geometry?.boundsIncludeStroke ?? true, selectTool: selectToolOpts,\n });\n const redrawOn = useSceneRedrawSources({ props, scene, heldModifiers, departures: layered.departures });\n\n const chrome = useSceneCanvasChrome({\n source: {\n getMode: getActiveMode, getFocused: props.getFocused, getModifiers: heldModifiers.get,\n resizable: selectToolOpts?.resize?.resizable,\n editingAnchors: () => pathEditState.getEditingId() !== '',\n device: deviceProfile,\n },\n rules: { enabled, selectable, chromeVisibility: props.chromeVisibility },\n hover: { canvasApiRef, nodeAtPointRef: getNodeAtPointRef, ...(props.onHover ? { onChange: props.onHover } : {}) },\n selection: selection.current as readonly NodeId[], viewRef, dispatcher,\n });\n\n const { ingest, clipboard: ingestionClipboard } = useCanvasIngestion(adapter, () => actionsRegistry, props.ingestion);\n const { views: addedViews, addView, viewAt, aimOf, setAim } = useAddedViews();\n // The consumer's handle extends the primitive's with `ingest`, which needs the action stack.\n const { handleRef, ready: canvasReady } = useSceneCanvasHandle({\n ref, elementRef: internalCanvasRef, apiRef: canvasApiRef, onElement: chrome.trackHoverOn,\n ingest, viewAnimation, camera, addView, viewAt, aimOf, setAim,\n });\n const viewInputs = useSurfaceViewInputs({ picking, selection, resizeHandles: layered.resizeHandles,\n descriptor: descriptor as PoseDescriptor<unknown>, chromeCaps: chrome.chromeCaps, rotationBadge: layered.rotationBadge,\n });\n\n return (\n <CanvasKitProviders viewInputs={viewInputs}>\n <Canvas<TPose>\n ref={handleRef} adapter={adapter} selection={selection} poseDescriptor={descriptor}\n tools={tools.tools} layers={layered.layers}\n // The chrome's bounds are the picker's: a node on a parallax plane is\n // boxed where the plane draws it, and a `geometry.boundsOf` reaches both.\n boundsOf={boundsOf} boundsOfPose={picking.boundsOfPose}\n getIsVisible={chrome.getIsVisible}\n // The gesture surface behind `helpersRef.getGestureBounds()` /\n // `subscribeGestures()` — Canvas has no dispatcher of its own.\n gestureSource={gestureSource}\n previewPoseExtra={previewPoseExtra}\n {...(debug !== undefined ? { debug: canvasDebug(debug) } : {})}\n contentVersion={scene.getVersion} layerVisibility={props.layerVisibility} layerOrder={props.layerOrder}\n viewState={viewState} redrawOn={redrawOn}\n {...canvasPassThrough(props)}\n />\n <DevHuds\n cursorCoords={props.cursorCoordsHud} pick={props.pickHud} modality={props.modalityHud}\n canvasRef={internalCanvasRef} canvasApiRef={canvasApiRef}\n detachedInput={props.paintInto ? props.inputElement ?? null : undefined}\n viewRef={viewRef} pickEvery={picking.pickEvery} pickBest={picking.pickBest}\n />\n <SceneCanvasInput\n props={props as SceneCanvasInputOptions} scene={scene as Scene<unknown, string, unknown>}\n selection={selection} descriptor={descriptor as PoseDescriptor<unknown>}\n picking={{ ...picking, adapter: adapter as unknown as BridgeAdapter }} nodeAtPointRef={getNodeAtPointRef}\n tools={tools} viewOn={enabled.has('view')} camera={{ viewState, viewAnimation }}\n dispatcher={dispatcher} pathEdit={{ state: pathEditState }}\n chrome={chrome} overlay={layered} ingestionClipboard={ingestionClipboard} devtools={devtools}\n canvas={{ elementRef: internalCanvasRef, apiRef: canvasApiRef, ready: canvasReady }} snapSources={enabled.snapSources} snapStore={layered.snapStore}\n />\n <SurfaceDeclarations views={props.views} entries={contributionEntries(tools.tools)} added={addedViews} />\n {props.children}\n </CanvasKitProviders>\n );\n}\n\nconst SceneCanvasInnerForwardRef = forwardRef(SceneCanvasInner);\n\n// The canvas's input scope sits above `SceneCanvasInner`, whose own hooks\n// (`useTools` among them) must already read this canvas's tool and registries.\n// Its device profile too, so the canvas and everything under it share one.\n/** A scene canvas resolving its `features` through `source`. */\nexport function renderSceneCanvas<TData, TLayer extends string, TPose>(\n source: PresetSource,\n { device, ...props }: SceneCanvasProps<TData, TLayer, TPose>,\n ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n return (\n <InputScope yoke={props.yoke}>\n <DeviceProfileProvider value={device}>\n <ViewRegistryProvider>\n <SceneCanvasInnerForwardRef {...(props as SceneCanvasProps<unknown, string, unknown>)} presetSource={source} ref={ref} />\n </ViewRegistryProvider>\n </DeviceProfileProvider>\n </InputScope>\n );\n}\n\nfunction SceneCanvasWrapper<TData, TLayer extends string, TPose>(\n props: SceneCanvasProps<TData, TLayer, TPose>, ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n return renderSceneCanvas(CORE_PRESET_SOURCE, props, ref);\n}\n\n// Marked pure so a bundle using only `LeanSceneCanvas` drops it, and core's named presets with it.\n/**\n * The canvas component: renders a `Scene` and wires the interaction stack\n * around it.\n *\n * Mounted bare it renders the scene, keeps a selection no input sets, and\n * runs a gesture dispatcher for whatever bindings the consumer adds. The\n * `features` presets turn the kit's behavior on — `features={['draw']}` is the\n * whole editor: tools, standard actions and their keys, selection chrome,\n * undo. This is the intended entry point; the lower-level primitives it\n * composes are not part of the public surface.\n *\n * Its ref exposes a `SceneCanvasApi` for the imperative operations that do not\n * fit a prop (view control, hit queries, redraw requests).\n */\nexport const SceneCanvas = /* @__PURE__ */ forwardRef(SceneCanvasWrapper) as <\n TData, TLayer extends string, TPose,\n>(\n props: SceneCanvasProps<TData, TLayer, TPose> & { ref?: React.Ref<SceneCanvasApi> },\n) => ReturnType<typeof SceneCanvasInner>;\n\nexport { DEFAULT_HANDLE_SIZE, BUILTIN_TOOL_IDS } from './SceneCanvas/props';\nexport {\n BUILTIN_TOOLS, SELECT_TOOLS, VIEW_TOOLS, SHAPE_TOOLS, DRAW_TOOLS, TYPE_TOOLS, PAGE_TOOLS,\n} from './SceneCanvas/builtinToolSets';\nexport { mergeLayersWithDefaults } from './SceneCanvas/mergeLayers';\nexport type {\n SceneCanvasProps, SceneCanvasHit, SceneCanvasLayers, SceneCanvasLongPress, BuiltinToolId,\n} from './SceneCanvas/props';\nexport type { CoordTraceEntry } from './SceneCanvas/coordTrace';\n"],"mappings":"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"}
1
+ {"version":3,"file":"SceneCanvas.js","names":[],"sources":["../../src/canvas/SceneCanvas.tsx"],"sourcesContent":["/**\n * `<SceneCanvas>` — `<Canvas>` wired to a `Scene` primitive. Bare, it only renders: behavior\n * comes from the `features` presets (`canvas/SceneCanvas/features.ts`), each of which turns on\n * its own actions, tools, ambient bindings, and chrome.\n *\n * A consumer needing custom tools can pass `tools={useTools(...)}` and SceneCanvas forwards it\n * as-is — the internal tool registry is ignored in that case.\n */\nimport { getActiveModeFor } from '@weasel-js/modes';\nimport { useLatest } from '@weasel-js/react';\nimport { InputScope, useActionsRegistry } from '@weasel-js/routing/react';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { useCanvasCamera } from './SceneCanvas/useCanvasCamera';\nimport { useHeldModifiers } from './SceneCanvas/useHeldModifiers';\nimport { useCanvasIngestion } from 'features/ingestion/useCanvasIngestion';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type React from 'react';\nimport { forwardRef, useEffect, useMemo, useRef } from 'react';\nimport { DeviceProfileProvider, useDeviceProfile } from '../core/device/useDeviceProfile';\nimport { Canvas } from './Canvas';\nimport { canvasDebug, DevHuds } from './SceneCanvas/DevHuds';\nimport { CanvasKitProviders } from './SceneCanvas/CanvasKitProviders';\nimport { canvasPassThrough } from './SceneCanvas/canvasPassThrough';\nimport { SceneCanvasInput, type SceneCanvasInputOptions } from './SceneCanvas/SceneCanvasInput';\nimport type { PresetSource } from './SceneCanvas/features';\nimport { CORE_PRESET_SOURCE } from './SceneCanvas/namedPresets';\nimport type { BridgeAdapter, SceneCanvasProps } from './SceneCanvas/props';\nimport { useBakedScene } from './SceneCanvas/useBakedScene';\nimport { useCanvasDispatcher } from './SceneCanvas/useCanvasDispatcher';\nimport { publishPreviews } from './publishPreviews';\nimport { useCanvasSelection } from './SceneCanvas/useCanvasSelection';\nimport { useEnabledFeatures } from './SceneCanvas/useCanvasTools';\nimport { contributionEntries } from './SceneCanvas/useContributionRoles';\nimport { SurfaceDeclarations } from './SceneCanvas/SurfaceDeclarations';\nimport { useAddedViews, useSceneCanvasHandle } from './SceneCanvas/useSceneCanvasHandle';\nimport { useSceneCanvasLayers } from './SceneCanvas/useSceneCanvasLayers';\nimport { useSceneCanvasPathEdit } from './SceneCanvas/useSceneCanvasPathEdit';\nimport { useSceneCanvasTools, type SceneCanvasToolProps } from './SceneCanvas/useSceneCanvasTools';\nimport { useSceneCanvasChrome } from './SceneCanvas/useSceneChrome';\nimport { useDevtoolsOverride } from '../devtools/useDevtoolsRoot';\nimport { useSceneRedrawSources } from './SceneCanvas/useSceneRedrawSources';\nimport { useScenePicking } from './SceneCanvas/useScenePicking';\nimport { useGroupEntryContribution } from 'tools/builtin/select/groupEntry';\nimport { useNodeKinds } from './SceneCanvas/useNodeKinds';\nimport { useSurfaceViewInputs } from './SceneCanvas/useSurfaceViewInputs';\nimport type { CanvasExtensionApi, SceneCanvasApi } from './canvasExtension';\nimport { ViewRegistryProvider } from './viewRegistry';\nimport { chromeColorOf, watchThemeOf } from './accentColor';\nimport { ENTERED_FRAME_COLOR, ENTERED_FRAME_TOKEN } from 'features/selection/enteredGroupFrame';\nexport { SCENE_CANVAS_FEATURES, type Feature } from './SceneCanvas/features';\nexport type { BuiltinToolOptions } from './SceneCanvas/useBuiltinShapeTools';\nexport { defaultDrawOne } from './defaultDrawOne';\nexport { rotateAroundAABBCenter } from './poseRotation';\n\nfunction SceneCanvasInner<TData, TLayer extends string, TPose>(\n props: SceneCanvasProps<TData, TLayer, TPose> & { presetSource: PresetSource },\n ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n const { selectTool: selectToolOpts, selectable = true, viewport, modes } = props;\n const devtools = useDevtoolsOverride(props.debug), { debug } = devtools;\n const descriptor = (props.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const deviceProfile = useDeviceProfile();\n const scene = useBakedScene(props.scene);\n const getActiveMode = useMemo(() => (modes ? getActiveModeFor(modes) : undefined), [modes]);\n const heldModifiers = useHeldModifiers();\n\n // Our own refs, so input listeners attach even when the consumer forwards its own ref.\n const internalCanvasRef = useRef<HTMLElement | null>(null);\n const canvasApiRef = useRef<CanvasExtensionApi | null>(null);\n const declaredSize = useLatest({ width: props.width ?? 0, height: props.height ?? 0 });\n const { viewState, viewAnimation, camera } = useCanvasCamera({\n view: props.view, defaultView: props.defaultView, onViewChange: props.onViewChange, viewBounds: props.viewBounds,\n dims: () => canvasApiRef.current?.getSurfaceRect() ?? declaredSize.current,\n requestRedraw: () => { canvasApiRef.current?.requestRedraw(); },\n });\n const { viewRef } = viewState;\n const kinds = useNodeKinds(scene, props.routing);\n const selection = useCanvasSelection({ scene, props, selectable, kindOf: kinds.kindOf });\n const enabled = useEnabledFeatures(\n props.features, props.presetSource.named, viewport !== undefined, selectToolOpts?.rotate !== false,\n );\n const { pathEditState, selectToolWithDefaults } = useSceneCanvasPathEdit({\n host: props.pathEdit, opening: props.defaultPathEdit, getActiveMode, scene, selection,\n extensions: enabled.pathEdit, selectTool: selectToolOpts,\n });\n const picking = useScenePicking({ scene, selection, descriptor, props, viewRef, selectTool: selectToolWithDefaults, kinds });\n const { adapter, boundsOf, planeOfNode } = picking;\n const getNodeAtPointRef = useLatest(picking.getNodeAtPoint);\n const groupEntry = useGroupEntryContribution({ pickHit: picking.pickBest, tree: adapter, boxOf: boundsOf });\n const tools = useSceneCanvasTools({\n props: props as SceneCanvasToolProps, select: picking.selectTool, move: picking.moveOptions,\n enabled, presetSource: props.presetSource, groupEntry,\n });\n\n // The wrapper's `<InputScope>` provides it.\n const actionsRegistry = useActionsRegistry();\n const { dispatcher, gestureSource, previewIdsExtra, previewPoseExtra } = useCanvasDispatcher(actionsRegistry);\n useEffect(() => publishPreviews(scene, dispatcher), [scene, dispatcher]);\n\n const frameColor = useMemo(() => ({\n read: () => chromeColorOf(internalCanvasRef.current, ENTERED_FRAME_TOKEN, ENTERED_FRAME_COLOR),\n changed: (fn: () => void) => watchThemeOf(() => internalCanvasRef.current, fn),\n }), []);\n const layered = useSceneCanvasLayers({\n layers: props.layers, targetScale: deviceProfile.targetScale, scene, adapter, poseDescriptor: descriptor,\n enteredGroup: picking.enteredGroup, frameColor, alphaFor: props.alphaFor, colorOverrides: props.animator?.colorOverrides, boundsOf, planeOfNode,\n poseTransition: props.poseTransition, previewIds: previewIdsExtra, dispatcher, snapReadout: props.snap?.readout !== false, snapReadoutStyle: props.snap?.readoutStyle, pathEditState,\n slops: debug !== undefined && debug !== false && debug.slops === true,\n boundsIncludeStroke: props.geometry?.boundsIncludeStroke ?? true, selectTool: selectToolOpts,\n });\n const redrawOn = useSceneRedrawSources({ props, scene, heldModifiers, departures: layered.departures });\n\n const chrome = useSceneCanvasChrome({\n source: {\n getMode: getActiveMode, getFocused: props.getFocused, getModifiers: heldModifiers.get,\n resizable: selectToolOpts?.resize?.resizable,\n editingAnchors: () => pathEditState.getEditingId() !== '',\n device: deviceProfile,\n },\n rules: { enabled, selectable, chromeVisibility: props.chromeVisibility },\n hover: { canvasApiRef, nodeAtPointRef: getNodeAtPointRef, ...(props.onHover ? { onChange: props.onHover } : {}) },\n selection: selection.current as readonly NodeId[], viewRef, dispatcher,\n });\n\n const { ingest, clipboard: ingestionClipboard } = useCanvasIngestion(adapter, () => actionsRegistry, props.ingestion);\n const { views: addedViews, addView, viewAt, aimOf, setAim } = useAddedViews();\n // The consumer's handle extends the primitive's with `ingest`, which needs the action stack.\n const { handleRef, ready: canvasReady } = useSceneCanvasHandle({\n ref, elementRef: internalCanvasRef, apiRef: canvasApiRef, onElement: chrome.trackHoverOn,\n ingest, viewAnimation, camera, addView, viewAt, aimOf, setAim,\n });\n const viewInputs = useSurfaceViewInputs({ picking, selection, resizeHandles: layered.resizeHandles,\n descriptor: descriptor as PoseDescriptor<unknown>, chromeCaps: chrome.chromeCaps, rotationBadge: layered.rotationBadge,\n });\n\n return (\n <CanvasKitProviders viewInputs={viewInputs}>\n <Canvas<TPose>\n ref={handleRef} adapter={adapter} selection={selection} poseDescriptor={descriptor}\n tools={tools.tools} layers={layered.layers}\n // The chrome's bounds are the picker's: a node on a parallax plane is\n // boxed where the plane draws it, and a `geometry.boundsOf` reaches both.\n boundsOf={boundsOf} boundsOfPose={picking.boundsOfPose}\n getIsVisible={chrome.getIsVisible}\n // The gesture surface behind `helpersRef.getGestureBounds()` /\n // `subscribeGestures()` — Canvas has no dispatcher of its own.\n gestureSource={gestureSource}\n previewPoseExtra={previewPoseExtra}\n {...(debug !== undefined ? { debug: canvasDebug(debug) } : {})}\n contentVersion={scene.getVersion} layerVisibility={props.layerVisibility} layerOrder={props.layerOrder}\n viewState={viewState} redrawOn={redrawOn}\n {...canvasPassThrough(props)}\n />\n <DevHuds\n cursorCoords={props.cursorCoordsHud} pick={props.pickHud} modality={props.modalityHud}\n canvasRef={internalCanvasRef} canvasApiRef={canvasApiRef}\n detachedInput={props.paintInto ? props.inputElement ?? null : undefined}\n viewRef={viewRef} pickEvery={picking.pickEvery} pickBest={picking.pickBest}\n />\n <SceneCanvasInput\n props={props as SceneCanvasInputOptions} scene={scene as Scene<unknown, string, unknown>}\n selection={selection} descriptor={descriptor as PoseDescriptor<unknown>}\n picking={{ ...picking, adapter: adapter as unknown as BridgeAdapter }} nodeAtPointRef={getNodeAtPointRef}\n tools={tools} viewOn={enabled.has('view')} camera={{ viewState, viewAnimation }}\n dispatcher={dispatcher} pathEdit={{ state: pathEditState }}\n chrome={chrome} overlay={layered} ingestionClipboard={ingestionClipboard} devtools={devtools}\n canvas={{ elementRef: internalCanvasRef, apiRef: canvasApiRef, ready: canvasReady }} snapSources={enabled.snapSources} snapStore={layered.snapStore}\n />\n <SurfaceDeclarations views={props.views} entries={contributionEntries(tools.tools)} added={addedViews} />\n {props.children}\n </CanvasKitProviders>\n );\n}\n\nconst SceneCanvasInnerForwardRef = forwardRef(SceneCanvasInner);\n\n// The canvas's input scope sits above `SceneCanvasInner`, whose own hooks\n// (`useTools` among them) must already read this canvas's tool and registries.\n// Its device profile too, so the canvas and everything under it share one.\n/** A scene canvas resolving its `features` through `source`. */\nexport function renderSceneCanvas<TData, TLayer extends string, TPose>(\n source: PresetSource,\n { device, ...props }: SceneCanvasProps<TData, TLayer, TPose>,\n ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n return (\n <InputScope yoke={props.yoke}>\n <DeviceProfileProvider value={device}>\n <ViewRegistryProvider>\n <SceneCanvasInnerForwardRef {...(props as SceneCanvasProps<unknown, string, unknown>)} presetSource={source} ref={ref} />\n </ViewRegistryProvider>\n </DeviceProfileProvider>\n </InputScope>\n );\n}\n\nfunction SceneCanvasWrapper<TData, TLayer extends string, TPose>(\n props: SceneCanvasProps<TData, TLayer, TPose>, ref: React.ForwardedRef<SceneCanvasApi>,\n) {\n return renderSceneCanvas(CORE_PRESET_SOURCE, props, ref);\n}\n\n// Marked pure so a bundle using only `LeanSceneCanvas` drops it, and core's named presets with it.\n/**\n * The canvas component: renders a `Scene` and wires the interaction stack\n * around it.\n *\n * Mounted bare it renders the scene, keeps a selection no input sets, and\n * runs a gesture dispatcher for whatever bindings the consumer adds. The\n * `features` presets turn the kit's behavior on — `features={['draw']}` is the\n * whole editor: tools, standard actions and their keys, selection chrome,\n * undo. This is the intended entry point; the lower-level primitives it\n * composes are not part of the public surface.\n *\n * Its ref exposes a `SceneCanvasApi` for the imperative operations that do not\n * fit a prop (view control, hit queries, redraw requests).\n */\nexport const SceneCanvas = /* @__PURE__ */ forwardRef(SceneCanvasWrapper) as <\n TData, TLayer extends string, TPose,\n>(\n props: SceneCanvasProps<TData, TLayer, TPose> & { ref?: React.Ref<SceneCanvasApi> },\n) => ReturnType<typeof SceneCanvasInner>;\n\nexport { DEFAULT_HANDLE_SIZE, BUILTIN_TOOL_IDS } from './SceneCanvas/props';\nexport {\n BUILTIN_TOOLS, SELECT_TOOLS, VIEW_TOOLS, SHAPE_TOOLS, DRAW_TOOLS, TYPE_TOOLS, PAGE_TOOLS,\n} from './SceneCanvas/builtinToolSets';\nexport { mergeLayersWithDefaults } from './SceneCanvas/mergeLayers';\nexport type {\n SceneCanvasProps, SceneCanvasHit, SceneCanvasLayers, SceneCanvasLongPress, BuiltinToolId,\n} from './SceneCanvas/props';\nexport type { CoordTraceEntry } from './SceneCanvas/coordTrace';\n"],"mappings":"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"}
@@ -1,12 +1,35 @@
1
1
  //#region src/canvas/accentColor.ts
2
2
  /** The accent canvas chrome paints in when the element sits under no theme. */
3
3
  var FALLBACK_ACCENT = "#4c8dff";
4
+ /** Read the color custom property `name` off `el`, where the theme cascade lands on a canvas. */
5
+ function chromeColorOf(el, name, fallback) {
6
+ if (!el || typeof getComputedStyle !== "function") return fallback;
7
+ return getComputedStyle(el).getPropertyValue(name).trim() || fallback;
8
+ }
4
9
  /** Read `--wzl-accent` off `el`, where the theme cascade lands on a canvas. */
5
10
  function accentColorOf(el) {
6
- if (!el || typeof getComputedStyle !== "function") return FALLBACK_ACCENT;
7
- return getComputedStyle(el).getPropertyValue("--wzl-accent").trim() || FALLBACK_ACCENT;
11
+ return chromeColorOf(el, "--wzl-accent", FALLBACK_ACCENT);
12
+ }
13
+ var THEME_ATTRIBUTE = /^(data-wzl-|class$)/;
14
+ /**
15
+ * Hear that the colors read off an element may have changed: a theme, mode or class attribute
16
+ * changing on it or anything above it, or the system color scheme flipping. `el` is read when
17
+ * this is called; with none yet, the document root is watched. Returns the stop.
18
+ */
19
+ function watchThemeOf(el, fn) {
20
+ if (typeof MutationObserver !== "function" || typeof document === "undefined") return () => {};
21
+ const observer = new MutationObserver((records) => {
22
+ if (records.some((r) => r.attributeName !== null && THEME_ATTRIBUTE.test(r.attributeName))) fn();
23
+ });
24
+ for (let node = el() ?? document.documentElement; node; node = node.parentElement) observer.observe(node, { attributes: true });
25
+ const scheme = typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
26
+ scheme?.addEventListener?.("change", fn);
27
+ return () => {
28
+ observer.disconnect();
29
+ scheme?.removeEventListener?.("change", fn);
30
+ };
8
31
  }
9
32
 
10
33
  //#endregion
11
- export { FALLBACK_ACCENT, accentColorOf };
34
+ export { FALLBACK_ACCENT, accentColorOf, chromeColorOf, watchThemeOf };
12
35
  //# sourceMappingURL=accentColor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"accentColor.js","names":[],"sources":["../../src/canvas/accentColor.ts"],"sourcesContent":["/** The accent canvas chrome paints in when the element sits under no theme. */\nexport const FALLBACK_ACCENT = '#4c8dff';\n\n/** Read `--wzl-accent` off `el`, where the theme cascade lands on a canvas. */\nexport function accentColorOf(el: Element | null | undefined): string {\n if (!el || typeof getComputedStyle !== 'function') return FALLBACK_ACCENT;\n return getComputedStyle(el).getPropertyValue('--wzl-accent').trim() || FALLBACK_ACCENT;\n}\n"],"mappings":";;AACA,IAAa,kBAAkB;;AAG/B,SAAgB,cAAc,IAAwC;CACpE,IAAI,CAAC,MAAM,OAAO,qBAAqB,YAAY,OAAO;CAC1D,OAAO,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,cAAc,CAAC,CAAC,KAAK,KAAK;AACzE"}
1
+ {"version":3,"file":"accentColor.js","names":[],"sources":["../../src/canvas/accentColor.ts"],"sourcesContent":["/** The accent canvas chrome paints in when the element sits under no theme. */\nexport const FALLBACK_ACCENT = '#4c8dff';\n\n/** Read the color custom property `name` off `el`, where the theme cascade lands on a canvas. */\nexport function chromeColorOf(el: Element | null | undefined, name: `--${string}`, fallback: string): string {\n if (!el || typeof getComputedStyle !== 'function') return fallback;\n return getComputedStyle(el).getPropertyValue(name).trim() || fallback;\n}\n\n/** Read `--wzl-accent` off `el`, where the theme cascade lands on a canvas. */\nexport function accentColorOf(el: Element | null | undefined): string {\n return chromeColorOf(el, '--wzl-accent', FALLBACK_ACCENT);\n}\n\nconst THEME_ATTRIBUTE = /^(data-wzl-|class$)/;\n\n/**\n * Hear that the colors read off an element may have changed: a theme, mode or class attribute\n * changing on it or anything above it, or the system color scheme flipping. `el` is read when\n * this is called; with none yet, the document root is watched. Returns the stop.\n */\nexport function watchThemeOf(el: () => Element | null | undefined, fn: () => void): () => void {\n if (typeof MutationObserver !== 'function' || typeof document === 'undefined') return () => {};\n const observer = new MutationObserver((records) => {\n if (records.some((r) => r.attributeName !== null && THEME_ATTRIBUTE.test(r.attributeName))) fn();\n });\n for (let node: Element | null = el() ?? document.documentElement; node; node = node.parentElement) {\n observer.observe(node, { attributes: true });\n }\n const scheme = typeof matchMedia === 'function' ? matchMedia('(prefers-color-scheme: dark)') : null;\n scheme?.addEventListener?.('change', fn);\n return () => {\n observer.disconnect();\n scheme?.removeEventListener?.('change', fn);\n };\n}\n"],"mappings":";;AACA,IAAa,kBAAkB;;AAG/B,SAAgB,cAAc,IAAgC,MAAqB,UAA0B;CAC3G,IAAI,CAAC,MAAM,OAAO,qBAAqB,YAAY,OAAO;CAC1D,OAAO,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,KAAK,KAAK;AAC/D;;AAGA,SAAgB,cAAc,IAAwC;CACpE,OAAO,cAAc,IAAI,gBAAgB,eAAe;AAC1D;AAEA,IAAM,kBAAkB;;;;;;AAOxB,SAAgB,aAAa,IAAsC,IAA4B;CAC7F,IAAI,OAAO,qBAAqB,cAAc,OAAO,aAAa,aAAa,aAAa,CAAC;CAC7F,MAAM,WAAW,IAAI,kBAAkB,YAAY;EACjD,IAAI,QAAQ,MAAM,MAAM,EAAE,kBAAkB,QAAQ,gBAAgB,KAAK,EAAE,aAAa,CAAC,GAAG,GAAG;CACjG,CAAC;CACD,KAAK,IAAI,OAAuB,GAAG,KAAK,SAAS,iBAAiB,MAAM,OAAO,KAAK,eAClF,SAAS,QAAQ,MAAM,EAAE,YAAY,KAAK,CAAC;CAE7C,MAAM,SAAS,OAAO,eAAe,aAAa,WAAW,8BAA8B,IAAI;CAC/F,QAAQ,mBAAmB,UAAU,EAAE;CACvC,aAAa;EACX,SAAS,WAAW;EACpB,QAAQ,sBAAsB,UAAU,EAAE;CAC5C;AACF"}
@@ -44,7 +44,7 @@ function buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, slot) {
44
44
  y: 0,
45
45
  width: dims.width,
46
46
  height: dims.height
47
- }) : (cmds) => cmds;
47
+ }, dims.dpr) : (cmds) => cmds;
48
48
  const paintBounds = cfg.cull ? cfg.paintBounds : void 0;
49
49
  const culledIn = (view, dims) => paintBounds ? paintMissesView(paintBounds, view, {
50
50
  x: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"buildSceneLayer.js","names":[],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** The pose an object is painted at. Defaults to `adapter.getPose(obj.id)`. `<SceneCanvas>`\n * hands it each node as the scene shows it, its `pose` an override's or a derived one,\n * and carries an override's `path` into the box it answers; return a pose placed from\n * `obj.pose`, or a drag in flight and a rig's motion do not show in this view. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: Dims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height })\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"buildSceneLayer.js","names":[],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, LayerDims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** The pose an object is painted at. Defaults to `adapter.getPose(obj.id)`. `<SceneCanvas>`\n * hands it each node as the scene shows it, its `pose` an override's or a derived one,\n * and carries an override's `path` into the box it answers; return a pose placed from\n * `obj.pose`, or a drag in flight and a rig's motion do not show in this view. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: LayerDims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height }, dims.dpr)\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { deriveParallaxView, planeMap } from "../core/viewport/parallax.js";
2
2
  import { definesFrame } from "../core/scene/effectivePose.js";
3
- import { findShapeSilhouette } from "./NodeShape.js";
4
3
  import { clipCarrier, planeTransform } from "./planeClips.js";
5
4
  import { splitOverChildren } from "./overChildren.js";
5
+ import { ownClipOf } from "./ownClip.js";
6
6
  import { hidesSubtree } from "../core/scene/nodeGates.js";
7
- import { scenePlaneOf } from "./pickWalk.js";
7
+ import { scenePlaneOf } from "./scenePickSource.js";
8
8
  import { mat3 } from "@weasel-js/geom";
9
9
 
10
10
  //#region src/canvas/buildSceneTree.ts
@@ -69,22 +69,19 @@ function buildSceneTree(adapter, drawOne, view, forLayer, derivedPathOf, culled)
69
69
  let ownClips = ancestorClips;
70
70
  let ownClip = null;
71
71
  const maybeContainer = node;
72
- if (maybeContainer.kind === "container") {
73
- let clip;
74
- if (typeof maybeContainer.clipFromPose === "function") clip = maybeContainer.clipFromPose(pose);
75
- else clip = findShapeSilhouette(node, pose, { derivedPath: derivedPathOf?.(node, pose) });
76
- if (clip) {
77
- ownClip = {
78
- path: clip,
79
- layer: node.layer
80
- };
81
- ownClips = [...ancestorClips, ownClip];
82
- }
72
+ const clip = ownClipOf(maybeContainer, pose, () => derivedPathOf?.(node, pose));
73
+ if (clip) {
74
+ ownClip = {
75
+ path: clip,
76
+ layer: node.layer
77
+ };
78
+ ownClips = [...ancestorClips, ownClip];
83
79
  }
84
- if (bucket) bucket.push(ownClips.length > 0 ? wrapInChain(self, ownClips, node.layer, carry) : {
80
+ const wrap = (cmds) => ancestorClips.length > 0 ? wrapInChain(cmds, ancestorClips, node.layer, carry) : {
85
81
  kind: "group",
86
- children: self
87
- });
82
+ children: cmds
83
+ };
84
+ if (bucket) bucket.push(wrap(self));
88
85
  const childFrame = definesFrame(node) ? pose : parentFrame;
89
86
  const children = adapter.getChildren(id);
90
87
  for (const cid of children) visit(cid, ownClips, childFrame, copied);
@@ -97,10 +94,7 @@ function buildSceneTree(adapter, drawOne, view, forLayer, derivedPathOf, culled)
97
94
  ];
98
95
  for (const cid of children) visit(cid, chain, childFrame, true);
99
96
  }
100
- if (bucket && over.length > 0) bucket.push(ownClips.length > 0 ? wrapInChain(over, ownClips, node.layer, carry) : {
101
- kind: "group",
102
- children: over
103
- });
97
+ if (bucket && over.length > 0) bucket.push(wrap(over));
104
98
  }
105
99
  for (const rootId of adapter.getChildren(null)) visit(rootId, [], null, false);
106
100
  const out = [];