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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/animation/poseTarget.js +46 -15
  3. package/dist/animation/poseTarget.js.map +1 -1
  4. package/dist/animation/poseTransition.js +3 -3
  5. package/dist/animation/poseTransition.js.map +1 -1
  6. package/dist/animation/reflow.js +3 -6
  7. package/dist/animation/reflow.js.map +1 -1
  8. package/dist/animation/rig/bindRig.js +2 -2
  9. package/dist/animation/rig/bindRig.js.map +1 -1
  10. package/dist/animation/rig/skinBinding.js +2 -2
  11. package/dist/animation/rig/skinBinding.js.map +1 -1
  12. package/dist/barrel/canvas.js +5 -3
  13. package/dist/barrel/chrome.js +3 -1
  14. package/dist/barrel/paths.js +3 -2
  15. package/dist/barrel/routing.js +2 -2
  16. package/dist/barrel/scene.js +6 -1
  17. package/dist/canvas/NodeShape.js +1 -0
  18. package/dist/canvas/NodeShape.js.map +1 -1
  19. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  29. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  30. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  32. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  34. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  35. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  36. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  37. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  38. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  39. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  40. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  41. package/dist/canvas/SceneCanvas.js +19 -2
  42. package/dist/canvas/SceneCanvas.js.map +1 -1
  43. package/dist/canvas/accentColor.js +26 -3
  44. package/dist/canvas/accentColor.js.map +1 -1
  45. package/dist/canvas/buildSceneTree.js +14 -20
  46. package/dist/canvas/buildSceneTree.js.map +1 -1
  47. package/dist/canvas/copiesAbove.js +1 -1
  48. package/dist/canvas/copiesAbove.js.map +1 -1
  49. package/dist/canvas/defaultDrawOne.js +10 -7
  50. package/dist/canvas/defaultDrawOne.js.map +1 -1
  51. package/dist/canvas/deps/areaSelect.js +5 -2
  52. package/dist/canvas/deps/areaSelect.js.map +1 -1
  53. package/dist/canvas/deps/booleans.js +1 -1
  54. package/dist/canvas/deps/booleans.js.map +1 -1
  55. package/dist/canvas/deps/enteredGroup.js +81 -0
  56. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  57. package/dist/canvas/deps/hitTestArea.js +27 -15
  58. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  59. package/dist/canvas/deps/insert.js +4 -36
  60. package/dist/canvas/deps/insert.js.map +1 -1
  61. package/dist/canvas/deps/insertSite.js +39 -0
  62. package/dist/canvas/deps/insertSite.js.map +1 -0
  63. package/dist/canvas/deps/lassoSelect.js +6 -3
  64. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  65. package/dist/canvas/deps/pathLeaf.js +1 -1
  66. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  67. package/dist/canvas/deps/slice.js +2 -1
  68. package/dist/canvas/deps/slice.js.map +1 -1
  69. package/dist/canvas/derivedPath.js +14 -6
  70. package/dist/canvas/derivedPath.js.map +1 -1
  71. package/dist/canvas/doubleClickTarget.js +9 -11
  72. package/dist/canvas/doubleClickTarget.js.map +1 -1
  73. package/dist/canvas/drawnBounds.js +16 -7
  74. package/dist/canvas/drawnBounds.js.map +1 -1
  75. package/dist/canvas/inheritedPaints.js +22 -11
  76. package/dist/canvas/inheritedPaints.js.map +1 -1
  77. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  78. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  79. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  80. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  81. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  82. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  83. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  84. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  85. package/dist/canvas/ownClip.js +24 -0
  86. package/dist/canvas/ownClip.js.map +1 -0
  87. package/dist/canvas/paintCull.js.map +1 -1
  88. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  89. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  90. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  91. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  92. package/dist/canvas/pickWalk.js +14 -90
  93. package/dist/canvas/pickWalk.js.map +1 -1
  94. package/dist/canvas/poseCascade.js +1 -1
  95. package/dist/canvas/poseCascade.js.map +1 -1
  96. package/dist/canvas/publishPreviews.js +97 -0
  97. package/dist/canvas/publishPreviews.js.map +1 -0
  98. package/dist/canvas/sceneAdapter.js +7 -5
  99. package/dist/canvas/sceneAdapter.js.map +1 -1
  100. package/dist/canvas/sceneClipboard.js +17 -10
  101. package/dist/canvas/sceneClipboard.js.map +1 -1
  102. package/dist/canvas/scenePickReads.js +37 -0
  103. package/dist/canvas/scenePickReads.js.map +1 -0
  104. package/dist/canvas/scenePickSource.js +74 -0
  105. package/dist/canvas/scenePickSource.js.map +1 -0
  106. package/dist/canvas/sceneSlotWiring.js +9 -4
  107. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  108. package/dist/canvas/sceneViewRender.js +9 -5
  109. package/dist/canvas/sceneViewRender.js.map +1 -1
  110. package/dist/canvas/shownNodes.js +15 -0
  111. package/dist/canvas/shownNodes.js.map +1 -0
  112. package/dist/canvas/useSceneAdapter.js +5 -3
  113. package/dist/canvas/useSceneAdapter.js.map +1 -1
  114. package/dist/clipboard.d.ts +2 -2
  115. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  116. package/dist/clone.d.ts +3 -3
  117. package/dist/core/isPlainObject.js +2 -2
  118. package/dist/core/layers/layerGroup.js.map +1 -1
  119. package/dist/core/scene/NodeRouting.js.map +1 -1
  120. package/dist/core/scene/arrivalWindow.js +1 -1
  121. package/dist/core/scene/arrivalWindow.js.map +1 -1
  122. package/dist/core/scene/clipNothing.js +2 -5
  123. package/dist/core/scene/clipNothing.js.map +1 -1
  124. package/dist/core/scene/declaredFieldSetters.js +44 -0
  125. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  126. package/dist/core/scene/depMemo.js +3 -0
  127. package/dist/core/scene/depMemo.js.map +1 -1
  128. package/dist/core/scene/dependents.js +3 -1
  129. package/dist/core/scene/dependents.js.map +1 -1
  130. package/dist/core/scene/documentOf.js +43 -0
  131. package/dist/core/scene/documentOf.js.map +1 -0
  132. package/dist/core/scene/effectivePose.js +37 -13
  133. package/dist/core/scene/effectivePose.js.map +1 -1
  134. package/dist/core/scene/fieldEdits.js +17 -14
  135. package/dist/core/scene/fieldEdits.js.map +1 -1
  136. package/dist/core/scene/historyForm.js +4 -4
  137. package/dist/core/scene/historyForm.js.map +1 -1
  138. package/dist/core/scene/inheritedPaint.js +27 -5
  139. package/dist/core/scene/inheritedPaint.js.map +1 -1
  140. package/dist/core/scene/kitOps.js +3 -23
  141. package/dist/core/scene/kitOps.js.map +1 -1
  142. package/dist/core/scene/layoutPass.js +1 -2
  143. package/dist/core/scene/layoutPass.js.map +1 -1
  144. package/dist/core/scene/nodeAsShown.js +72 -0
  145. package/dist/core/scene/nodeAsShown.js.map +1 -0
  146. package/dist/core/scene/nodeFnFields.js +2 -10
  147. package/dist/core/scene/nodeFnFields.js.map +1 -1
  148. package/dist/core/scene/nodeMemo.js +6 -1
  149. package/dist/core/scene/nodeMemo.js.map +1 -1
  150. package/dist/core/scene/nodeStore.js +2 -4
  151. package/dist/core/scene/nodeStore.js.map +1 -1
  152. package/dist/core/scene/pathEffects/derive.js +3 -2
  153. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  154. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  155. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  156. package/dist/core/scene/plyStacks.js +89 -0
  157. package/dist/core/scene/plyStacks.js.map +1 -0
  158. package/dist/core/scene/poseHold.js +42 -24
  159. package/dist/core/scene/poseHold.js.map +1 -1
  160. package/dist/core/scene/poseOverrides.js +35 -38
  161. package/dist/core/scene/poseOverrides.js.map +1 -1
  162. package/dist/core/scene/posePlies.js +59 -0
  163. package/dist/core/scene/posePlies.js.map +1 -0
  164. package/dist/core/scene/reconcileSpecs.js +6 -15
  165. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  166. package/dist/core/scene/renderOrder.js +10 -4
  167. package/dist/core/scene/renderOrder.js.map +1 -1
  168. package/dist/core/scene/sameValue.js +17 -0
  169. package/dist/core/scene/sameValue.js.map +1 -0
  170. package/dist/core/scene/scene.js +3 -3
  171. package/dist/core/scene/scene.js.map +1 -1
  172. package/dist/core/scene/sceneEdits.js +8 -0
  173. package/dist/core/scene/sceneEdits.js.map +1 -1
  174. package/dist/core/scene/sceneState.js +1 -2
  175. package/dist/core/scene/sceneState.js.map +1 -1
  176. package/dist/core/scene/serialization.js +45 -9
  177. package/dist/core/scene/serialization.js.map +1 -1
  178. package/dist/core/scene/staleReplay.js +5 -2
  179. package/dist/core/scene/staleReplay.js.map +1 -1
  180. package/dist/core/scene/statedFields.js +4 -2
  181. package/dist/core/scene/statedFields.js.map +1 -1
  182. package/dist/core/scene/treeEdits.js +2 -4
  183. package/dist/core/scene/treeEdits.js.map +1 -1
  184. package/dist/core/scene/treeOrder.js +3 -1
  185. package/dist/core/scene/treeOrder.js.map +1 -1
  186. package/dist/core/scene/treeReads.js +1 -2
  187. package/dist/core/scene/treeReads.js.map +1 -1
  188. package/dist/core/scene/treeShape.js +13 -0
  189. package/dist/core/scene/treeShape.js.map +1 -0
  190. package/dist/core/scene/types.js.map +1 -1
  191. package/dist/core/scene/useScene.js +1 -1
  192. package/dist/core/scene/useScene.js.map +1 -1
  193. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  194. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-B1RS-IJx.d.ts} +27 -54
  195. package/dist/devtools/handlers.js +1 -1
  196. package/dist/devtools/handlers.js.map +1 -1
  197. package/dist/devtools/protocol.js.map +1 -1
  198. package/dist/devtools-protocol.d.ts +4 -3
  199. package/dist/devtools.d.ts +3 -2
  200. package/dist/features/measure/measureHit.js +5 -3
  201. package/dist/features/measure/measureHit.js.map +1 -1
  202. package/dist/features/measure/measurement.js +1 -1
  203. package/dist/features/measure/measurement.js.map +1 -1
  204. package/dist/features/modes/modeLayers.js +14 -7
  205. package/dist/features/modes/modeLayers.js.map +1 -1
  206. package/dist/features/modes/modeTriggers.js +3 -3
  207. package/dist/features/modes/modeTriggers.js.map +1 -1
  208. package/dist/features/pathEffects/effectSources.js +25 -16
  209. package/dist/features/pathEffects/effectSources.js.map +1 -1
  210. package/dist/features/pathEffects/readSource.js +1 -1
  211. package/dist/features/pathEffects/readSource.js.map +1 -1
  212. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  213. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  214. package/dist/features/paths/editablePath.js +1 -1
  215. package/dist/features/paths/editablePath.js.map +1 -1
  216. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  217. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  218. package/dist/features/selection/enteredGroupFrame.js +65 -0
  219. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  220. package/dist/features/selection/enteredGroupScope.js +44 -0
  221. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  222. package/dist/features/text/sceneTextDrafts.js +26 -27
  223. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  224. package/dist/features/text/textHitAt.js +4 -2
  225. package/dist/features/text/textHitAt.js.map +1 -1
  226. package/dist/features/text/useSceneTextEdit.js +3 -3
  227. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  228. package/dist/index.d.ts +225 -73
  229. package/dist/index.js +15 -5
  230. package/dist/interactions/actions/admitsChild.js +11 -0
  231. package/dist/interactions/actions/admitsChild.js.map +1 -0
  232. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  233. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  234. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  235. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  236. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  237. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  238. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  239. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  240. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  241. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  242. package/dist/interactions/actions/defaults/group.js +7 -7
  243. package/dist/interactions/actions/defaults/group.js.map +1 -1
  244. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  245. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  246. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  247. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  248. package/dist/interactions/actions/drawnBoxes.js +2 -1
  249. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  250. package/dist/interactions/actions/insert/action.js +1 -0
  251. package/dist/interactions/actions/insert/action.js.map +1 -1
  252. package/dist/interactions/actions/insert/parent.js +7 -5
  253. package/dist/interactions/actions/insert/parent.js.map +1 -1
  254. package/dist/interactions/actions/insert/placeAction.js +3 -2
  255. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  256. package/dist/interactions/actions/insert/session.js +2 -2
  257. package/dist/interactions/actions/insert/session.js.map +1 -1
  258. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  259. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  260. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  261. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  262. package/dist/interactions/actions/move/layout/exits.js +1 -1
  263. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  264. package/dist/interactions/actions/move/session.js +4 -4
  265. package/dist/interactions/actions/move/session.js.map +1 -1
  266. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  267. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  268. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  269. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  270. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  271. package/dist/math.d.ts +1 -1
  272. package/dist/move.d.ts +4 -4
  273. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  274. package/dist/resize.d.ts +4 -4
  275. package/dist/test-seams.d.ts +6 -6
  276. package/dist/tools/builtin/pressTarget.js +22 -0
  277. package/dist/tools/builtin/pressTarget.js.map +1 -0
  278. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  279. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  280. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  281. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  282. package/dist/tools/builtin/select/groupEntry.js +82 -0
  283. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  284. package/dist/tools/builtin/select/pressDecision.js +1 -1
  285. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  286. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  287. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  288. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  289. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  290. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  291. package/dist/version.js +1 -1
  292. package/package.json +25 -25
@@ -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"}
@@ -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 = [];
@@ -1 +1 @@
1
- {"version":3,"file":"buildSceneTree.js","names":[],"sources":["../../src/canvas/buildSceneTree.ts"],"sourcesContent":["import type { DrawCommand, GroupDrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { findShapeSilhouette } from './NodeShape';\nimport type { Node } from 'core/scene/types';\nimport { mat3, type Affine, type Path } from '@weasel-js/geom';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport { deriveParallaxView, planeMap, type ParallaxOpts } from 'core/viewport/parallax';\nimport { scenePlaneOf } from './pickWalk';\nimport { clipCarrier, planeTransform } from './planeClips';\nimport { splitOverChildren } from './overChildren';\nimport { hidesSubtree } from 'core/scene/nodeGates';\n\n/** The scene-tree reading surface `buildSceneTree` walks. A `SceneCanvasAdapter` is one. */\nexport interface HierarchicalAdapter<TNode, TPose> {\n getLayers(): readonly { id: string; visible: boolean; parallax?: ParallaxOpts }[];\n getNode(id: string): TNode | undefined;\n getChildren(parentId: string | null): readonly string[];\n getPose(id: string): TPose;\n /** Folds a child's local pose into its parent's frame. Absent means the\n * scene stores absolute poses and a parent contributes no transform —\n * see `PoseComposition`. */\n composePose?(parent: TPose, child: TPose): TPose;\n}\n\n/** A clip on the chain, with the layer whose world it is drawn in, or the\n * transform of a copy a container draws — see `ContainerNode.copiesFromPose`. */\ntype ChainEntry = { path: Path; layer: string } | { copy: Affine };\n\n/** Wrap `cmds` in one nested group per chain entry, so the renderer intersects\n * the clips and applies each copy's transform to everything inside it.\n * Outermost group carries the ancestor-most entry; innermost holds `cmds`. */\nfunction wrapInChain(\n cmds: DrawCommand[],\n chain: readonly ChainEntry[],\n toLayer: string,\n carry: ReturnType<typeof clipCarrier>,\n): DrawCommand {\n let node: GroupDrawCommand = { kind: 'group', children: cmds };\n for (let i = chain.length - 1; i >= 0; i--) {\n const e = chain[i];\n node = 'copy' in e\n ? { kind: 'group', children: [node], transform: mat3.fromAffine(e.copy) }\n : { kind: 'group', children: [node], clip: carry(e.path, e.layer, toLayer) };\n }\n return node;\n}\n\n/**\n * Walk the adapter's scene tree once and emit each node's own paint into the bucket for\n * **that node's own `layer`** — not its parent's. A node carrying `hidden` is skipped with\n * its whole subtree; see `nodeGates`. So a child whose `layer` differs from its\n * parent draws in its own layer's pass while staying a tree child for hit-testing / move /\n * reparent. Output is one group per visible layer, in adapter (`getLayers`) order.\n *\n * A node paints before its children. What its `drawOne` wraps in `overChildren` paints after\n * them instead, in the node's own layer.\n *\n * `drawOne` returns world-coord commands. Because a node may be drawn outside its parent's\n * group, the tree nesting cannot carry geometry as renderer state; the walk folds each node's\n * pose into its parent's frame on the way down and hands the painter a **world** pose. Clips\n * are accumulated the same way — each in its container's plane, carried into the plane of the\n * node it clips. With no `composePose` on the adapter the fold is the identity.\n *\n * A layer carrying `parallax` is a plane: its painters and its cull see the view derived for\n * it, and its group carries the transform from its world to the camera's, so the caller's\n * single camera wrap still lands it right.\n */\nexport function buildSceneTree<\n TNode extends { id: string; layer: string; hidden?: boolean },\n TPose,\n>(\n adapter: HierarchicalAdapter<TNode, TPose>,\n drawOne: (obj: TNode, pose: TPose, view: View) => DrawCommand[],\n view: View,\n /** When set, only nodes on this layer paint (others are still walked for\n * ancestor clips) and only this layer's group is returned. */\n forLayer?: string,\n /** The path a container computes from its dependencies' poses: the scene's\n * lookup, or `adapterDerivedPathOf` for an adapter with no scene. Left out,\n * a derived container contributes no clip. */\n derivedPathOf?: (node: TNode, pose: TPose) => Path | null,\n /** True for a node whose paint cannot be seen — see `paintMissesView`. Its\n * painter is not called; its clip and its children are walked as usual. */\n culled?: (node: TNode, pose: TPose, view: View) => boolean,\n): DrawCommand[] {\n const layers = adapter.getLayers();\n // A hidden layer gets no bucket, and a node with no bucket is not painted.\n const buckets = new Map<string, DrawCommand[]>();\n for (const l of layers) if (l.visible) buckets.set(l.id, []);\n let planeViews: Map<string, View> | null = null;\n for (const l of layers) {\n if (l.parallax) (planeViews ??= new Map()).set(l.id, deriveParallaxView(view, l.parallax));\n }\n\n const compose = adapter.composePose?.bind(adapter);\n const carry = clipCarrier(planeViews ? scenePlaneOf(layers, view) : null);\n\n function visit(\n id: string,\n ancestorClips: readonly ChainEntry[],\n parentFrame: TPose | null,\n /** Under a copy: the pose is not where it paints, so the cull cannot judge it. */\n copied: boolean,\n ): void {\n const node = adapter.getNode(id);\n if (!node || hidesSubtree(node)) return;\n const local = adapter.getPose(id);\n // Fold once, on the way down: O(1) per node where walking the parent chain\n // per node would be O(depth).\n const pose = compose && parentFrame !== null ? compose(parentFrame, local) : local;\n // Skip the (potentially expensive) painter for nodes we won't emit; their\n // clip still extends the chain for descendants below.\n const bucket = forLayer === undefined || node.layer === forLayer\n ? buckets.get(node.layer) : undefined;\n const v = planeViews?.get(node.layer) ?? view;\n const { under: self, over } = splitOverChildren(\n bucket && (copied || !culled?.(node, pose, v)) ? drawOne(node, pose, v) : [],\n );\n\n // Extend the clip chain with this node's own clip when it is a container.\n let ownClips = ancestorClips;\n let ownClip: ChainEntry | null = null;\n const maybeContainer = node as {\n kind?: string;\n clipFromPose?: (pose: TPose) => unknown;\n copiesFromPose?: (pose: TPose, node: unknown) => readonly Affine[] | null;\n };\n if (maybeContainer.kind === 'container') {\n let clip: unknown;\n if (typeof maybeContainer.clipFromPose === 'function') {\n // Explicit per-node clip wins — no fallback to the painter silhouette.\n clip = maybeContainer.clipFromPose(pose);\n } else {\n // No explicit clip → the painter silhouette for the container's shape kind.\n clip = findShapeSilhouette(\n node as unknown as Node<unknown, string, TPose>,\n pose,\n { derivedPath: derivedPathOf?.(node, pose) },\n );\n }\n if (clip) {\n ownClip = { path: clip as Path, layer: node.layer };\n ownClips = [...ancestorClips, ownClip];\n }\n }\n\n // Every node emits a wrapper group even with empty paint, keeping the per-node tree\n // shape stable.\n if (bucket) {\n bucket.push(ownClips.length > 0 ? wrapInChain(self, ownClips, node.layer, carry) : { kind: 'group', children: self });\n }\n\n // An envelope's children share its own parent's frame.\n const childFrame = definesFrame(node as never) ? pose : parentFrame;\n const children = adapter.getChildren(id);\n for (const cid of children) visit(cid, ownClips, childFrame, copied);\n\n // A copy is a copy of the clipped content, so its transform encloses the\n // container's own clip and sits inside every clip above it.\n const copies = typeof maybeContainer.copiesFromPose === 'function'\n ? maybeContainer.copiesFromPose(pose, node) : null;\n for (const copy of copies ?? []) {\n const chain: ChainEntry[] = [...ancestorClips, { copy }, ...(ownClip ? [ownClip] : [])];\n for (const cid of children) visit(cid, chain, childFrame, true);\n }\n\n if (bucket && over.length > 0) {\n bucket.push(ownClips.length > 0 ? wrapInChain(over, ownClips, node.layer, carry) : { kind: 'group', children: over });\n }\n }\n\n for (const rootId of adapter.getChildren(null)) visit(rootId, [], null, false);\n\n const out: DrawCommand[] = [];\n for (const layer of layers) {\n if (!layer.visible) continue;\n if (forLayer !== undefined && layer.id !== forLayer) continue;\n const children = buckets.get(layer.id) ?? [];\n const transform = layer.parallax ? planeTransform(planeMap(view, layer.parallax), null) : undefined;\n out.push(transform ? { kind: 'group', transform, children } : { kind: 'group', children });\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,SAAS,YACP,MACA,OACA,SACA,OACa;CACb,IAAI,OAAyB;EAAE,MAAM;EAAS,UAAU;CAAK;CAC7D,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,IAAI,MAAM;EAChB,OAAO,UAAU,IACb;GAAE,MAAM;GAAS,UAAU,CAAC,IAAI;GAAG,WAAW,KAAK,WAAW,EAAE,IAAI;EAAE,IACtE;GAAE,MAAM;GAAS,UAAU,CAAC,IAAI;GAAG,MAAM,MAAM,EAAE,MAAM,EAAE,OAAO,OAAO;EAAE;CAC/E;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,eAId,SACA,SACA,MAGA,UAIA,eAGA,QACe;CACf,MAAM,SAAS,QAAQ,UAAU;CAEjC,MAAM,0BAAU,IAAI,IAA2B;CAC/C,KAAK,MAAM,KAAK,QAAQ,IAAI,EAAE,SAAS,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC;CAC3D,IAAI,aAAuC;CAC3C,KAAK,MAAM,KAAK,QACd,IAAI,EAAE,UAAU,CAAC,+BAAe,IAAI,IAAI,EAAA,CAAG,IAAI,EAAE,IAAI,mBAAmB,MAAM,EAAE,QAAQ,CAAC;CAG3F,MAAM,UAAU,QAAQ,aAAa,KAAK,OAAO;CACjD,MAAM,QAAQ,YAAY,aAAa,aAAa,QAAQ,IAAI,IAAI,IAAI;CAExE,SAAS,MACP,IACA,eACA,aAEA,QACM;EACN,MAAM,OAAO,QAAQ,QAAQ,EAAE;EAC/B,IAAI,CAAC,QAAQ,aAAa,IAAI,GAAG;EACjC,MAAM,QAAQ,QAAQ,QAAQ,EAAE;EAGhC,MAAM,OAAO,WAAW,gBAAgB,OAAO,QAAQ,aAAa,KAAK,IAAI;EAG7E,MAAM,SAAS,aAAa,UAAa,KAAK,UAAU,WACpD,QAAQ,IAAI,KAAK,KAAK,IAAI;EAC9B,MAAM,IAAI,YAAY,IAAI,KAAK,KAAK,KAAK;EACzC,MAAM,EAAE,OAAO,MAAM,SAAS,kBAC5B,WAAW,UAAU,CAAC,SAAS,MAAM,MAAM,CAAC,KAAK,QAAQ,MAAM,MAAM,CAAC,IAAI,CAAC,CAC7E;EAGA,IAAI,WAAW;EACf,IAAI,UAA6B;EACjC,MAAM,iBAAiB;EAKvB,IAAI,eAAe,SAAS,aAAa;GACvC,IAAI;GACJ,IAAI,OAAO,eAAe,iBAAiB,YAEzC,OAAO,eAAe,aAAa,IAAI;QAGvC,OAAO,oBACL,MACA,MACA,EAAE,aAAa,gBAAgB,MAAM,IAAI,EAAE,CAC7C;GAEF,IAAI,MAAM;IACR,UAAU;KAAE,MAAM;KAAc,OAAO,KAAK;IAAM;IAClD,WAAW,CAAC,GAAG,eAAe,OAAO;GACvC;EACF;EAIA,IAAI,QACF,OAAO,KAAK,SAAS,SAAS,IAAI,YAAY,MAAM,UAAU,KAAK,OAAO,KAAK,IAAI;GAAE,MAAM;GAAS,UAAU;EAAK,CAAC;EAItH,MAAM,aAAa,aAAa,IAAa,IAAI,OAAO;EACxD,MAAM,WAAW,QAAQ,YAAY,EAAE;EACvC,KAAK,MAAM,OAAO,UAAU,MAAM,KAAK,UAAU,YAAY,MAAM;EAInE,MAAM,SAAS,OAAO,eAAe,mBAAmB,aACpD,eAAe,eAAe,MAAM,IAAI,IAAI;EAChD,KAAK,MAAM,QAAQ,UAAU,CAAC,GAAG;GAC/B,MAAM,QAAsB;IAAC,GAAG;IAAe,EAAE,KAAK;IAAG,GAAI,UAAU,CAAC,OAAO,IAAI,CAAC;GAAE;GACtF,KAAK,MAAM,OAAO,UAAU,MAAM,KAAK,OAAO,YAAY,IAAI;EAChE;EAEA,IAAI,UAAU,KAAK,SAAS,GAC1B,OAAO,KAAK,SAAS,SAAS,IAAI,YAAY,MAAM,UAAU,KAAK,OAAO,KAAK,IAAI;GAAE,MAAM;GAAS,UAAU;EAAK,CAAC;CAExH;CAEA,KAAK,MAAM,UAAU,QAAQ,YAAY,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAK;CAE7E,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,CAAC,MAAM,SAAS;EACpB,IAAI,aAAa,UAAa,MAAM,OAAO,UAAU;EACrD,MAAM,WAAW,QAAQ,IAAI,MAAM,EAAE,KAAK,CAAC;EAC3C,MAAM,YAAY,MAAM,WAAW,eAAe,SAAS,MAAM,MAAM,QAAQ,GAAG,IAAI,IAAI;EAC1F,IAAI,KAAK,YAAY;GAAE,MAAM;GAAS;GAAW;EAAS,IAAI;GAAE,MAAM;GAAS;EAAS,CAAC;CAC3F;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"buildSceneTree.js","names":[],"sources":["../../src/canvas/buildSceneTree.ts"],"sourcesContent":["import type { DrawCommand, GroupDrawCommand } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport { ownClipOf, type ClipBearing } from './ownClip';\nimport { mat3, type Affine, type Path } from '@weasel-js/geom';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport { deriveParallaxView, planeMap, type ParallaxOpts } from 'core/viewport/parallax';\nimport { scenePlaneOf } from './pickWalk';\nimport { clipCarrier, planeTransform } from './planeClips';\nimport { splitOverChildren } from './overChildren';\nimport { hidesSubtree } from 'core/scene/nodeGates';\n\n/** The scene-tree reading surface `buildSceneTree` walks. A `SceneCanvasAdapter` is one. */\nexport interface HierarchicalAdapter<TNode, TPose> {\n getLayers(): readonly { id: string; visible: boolean; parallax?: ParallaxOpts }[];\n getNode(id: string): TNode | undefined;\n getChildren(parentId: string | null): readonly string[];\n getPose(id: string): TPose;\n /** Folds a child's local pose into its parent's frame. Absent means the\n * scene stores absolute poses and a parent contributes no transform —\n * see `PoseComposition`. */\n composePose?(parent: TPose, child: TPose): TPose;\n}\n\n/** A clip on the chain, with the layer whose world it is drawn in, or the\n * transform of a copy a container draws — see `GroupNode.copiesFromPose`. */\ntype ChainEntry = { path: Path; layer: string } | { copy: Affine };\n\n/** Wrap `cmds` in one nested group per chain entry, so the renderer intersects\n * the clips and applies each copy's transform to everything inside it.\n * Outermost group carries the ancestor-most entry; innermost holds `cmds`. */\nfunction wrapInChain(\n cmds: DrawCommand[],\n chain: readonly ChainEntry[],\n toLayer: string,\n carry: ReturnType<typeof clipCarrier>,\n): DrawCommand {\n let node: GroupDrawCommand = { kind: 'group', children: cmds };\n for (let i = chain.length - 1; i >= 0; i--) {\n const e = chain[i];\n node = 'copy' in e\n ? { kind: 'group', children: [node], transform: mat3.fromAffine(e.copy) }\n : { kind: 'group', children: [node], clip: carry(e.path, e.layer, toLayer) };\n }\n return node;\n}\n\n/**\n * Walk the adapter's scene tree once and emit each node's own paint into the bucket for\n * **that node's own `layer`** — not its parent's. A node carrying `hidden` is skipped with\n * its whole subtree; see `nodeGates`. So a child whose `layer` differs from its\n * parent draws in its own layer's pass while staying a tree child for hit-testing / move /\n * reparent. Output is one group per visible layer, in adapter (`getLayers`) order.\n *\n * A node paints before its children. What its `drawOne` wraps in `overChildren` paints after\n * them instead, in the node's own layer.\n *\n * `drawOne` returns world-coord commands. Because a node may be drawn outside its parent's\n * group, the tree nesting cannot carry geometry as renderer state; the walk folds each node's\n * pose into its parent's frame on the way down and hands the painter a **world** pose. Clips\n * are accumulated the same way — each in its container's plane, carried into the plane of the\n * node it clips. With no `composePose` on the adapter the fold is the identity.\n *\n * A layer carrying `parallax` is a plane: its painters and its cull see the view derived for\n * it, and its group carries the transform from its world to the camera's, so the caller's\n * single camera wrap still lands it right.\n */\nexport function buildSceneTree<\n TNode extends { id: string; layer: string; hidden?: boolean },\n TPose,\n>(\n adapter: HierarchicalAdapter<TNode, TPose>,\n drawOne: (obj: TNode, pose: TPose, view: View) => DrawCommand[],\n view: View,\n /** When set, only nodes on this layer paint (others are still walked for\n * ancestor clips) and only this layer's group is returned. */\n forLayer?: string,\n /** The path a container computes from its dependencies' poses: the scene's\n * lookup, or `adapterDerivedPathOf` for an adapter with no scene. Left out,\n * a derived container contributes no clip. */\n derivedPathOf?: (node: TNode, pose: TPose) => Path | null,\n /** True for a node whose paint cannot be seen — see `paintMissesView`. Its\n * painter is not called; its clip and its children are walked as usual. */\n culled?: (node: TNode, pose: TPose, view: View) => boolean,\n): DrawCommand[] {\n const layers = adapter.getLayers();\n // A hidden layer gets no bucket, and a node with no bucket is not painted.\n const buckets = new Map<string, DrawCommand[]>();\n for (const l of layers) if (l.visible) buckets.set(l.id, []);\n let planeViews: Map<string, View> | null = null;\n for (const l of layers) {\n if (l.parallax) (planeViews ??= new Map()).set(l.id, deriveParallaxView(view, l.parallax));\n }\n\n const compose = adapter.composePose?.bind(adapter);\n const carry = clipCarrier(planeViews ? scenePlaneOf(layers, view) : null);\n\n function visit(\n id: string,\n ancestorClips: readonly ChainEntry[],\n parentFrame: TPose | null,\n /** Under a copy: the pose is not where it paints, so the cull cannot judge it. */\n copied: boolean,\n ): void {\n const node = adapter.getNode(id);\n if (!node || hidesSubtree(node)) return;\n const local = adapter.getPose(id);\n // Fold once, on the way down: O(1) per node where walking the parent chain\n // per node would be O(depth).\n const pose = compose && parentFrame !== null ? compose(parentFrame, local) : local;\n // Skip the (potentially expensive) painter for nodes we won't emit; their\n // clip still extends the chain for descendants below.\n const bucket = forLayer === undefined || node.layer === forLayer\n ? buckets.get(node.layer) : undefined;\n const v = planeViews?.get(node.layer) ?? view;\n const { under: self, over } = splitOverChildren(\n bucket && (copied || !culled?.(node, pose, v)) ? drawOne(node, pose, v) : [],\n );\n\n let ownClips = ancestorClips;\n let ownClip: ChainEntry | null = null;\n const maybeContainer = node as ClipBearing<TPose> & {\n copiesFromPose?: (pose: TPose, node: unknown) => readonly Affine[] | null;\n };\n const clip = ownClipOf(maybeContainer, pose, () => derivedPathOf?.(node, pose));\n if (clip) {\n ownClip = { path: clip, layer: node.layer };\n ownClips = [...ancestorClips, ownClip];\n }\n\n // Every node emits a wrapper group even with empty paint, keeping the per-node tree\n // shape stable. A node's own clip is its children's, so its own paint, over\n // its children or under them, takes only the clips above it.\n const wrap = (cmds: DrawCommand[]): DrawCommand => (ancestorClips.length > 0\n ? wrapInChain(cmds, ancestorClips, node.layer, carry) : { kind: 'group', children: cmds });\n if (bucket) bucket.push(wrap(self));\n\n // An envelope's children share its own parent's frame.\n const childFrame = definesFrame(node as never) ? pose : parentFrame;\n const children = adapter.getChildren(id);\n for (const cid of children) visit(cid, ownClips, childFrame, copied);\n\n // A copy is a copy of the clipped content, so its transform encloses the\n // container's own clip and sits inside every clip above it.\n const copies = typeof maybeContainer.copiesFromPose === 'function'\n ? maybeContainer.copiesFromPose(pose, node) : null;\n for (const copy of copies ?? []) {\n const chain: ChainEntry[] = [...ancestorClips, { copy }, ...(ownClip ? [ownClip] : [])];\n for (const cid of children) visit(cid, chain, childFrame, true);\n }\n\n if (bucket && over.length > 0) bucket.push(wrap(over));\n }\n\n for (const rootId of adapter.getChildren(null)) visit(rootId, [], null, false);\n\n const out: DrawCommand[] = [];\n for (const layer of layers) {\n if (!layer.visible) continue;\n if (forLayer !== undefined && layer.id !== forLayer) continue;\n const children = buckets.get(layer.id) ?? [];\n const transform = layer.parallax ? planeTransform(planeMap(view, layer.parallax), null) : undefined;\n out.push(transform ? { kind: 'group', transform, children } : { kind: 'group', children });\n }\n return out;\n}\n"],"mappings":"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"}
@@ -20,7 +20,7 @@ function copiesWithin(scene, container, poseOf) {
20
20
  ]];
21
21
  for (const aid of [container, ...scene.ancestorsOf(container)].reverse()) {
22
22
  const a = scene.get(aid);
23
- if (a?.kind !== "container" || !a.copiesFromPose) continue;
23
+ if (!a?.copiesFromPose) continue;
24
24
  const copies = a.copiesFromPose(poseOf(a), a) ?? [];
25
25
  if (copies.length === 0) continue;
26
26
  all = all.flatMap((m) => [m, ...copies.map((c) => multiply(m, c))]);
@@ -1 +1 @@
1
- {"version":3,"file":"copiesAbove.js","names":[],"sources":["../../src/canvas/copiesAbove.ts"],"sourcesContent":["import { multiply, type Affine } from '@weasel-js/geom';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\n\n/**\n * Every transform content placed in `container` is drawn under besides as\n * authored: the `copiesFromPose` of the container and of each container above\n * it, composed outermost first, the identity left out. Empty for the top of\n * the tree, or when nothing there draws copies. `poseOf` is the pose a\n * container is painted at, which is what its copies are computed from.\n */\nexport function copiesWithin<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n container: NodeId | null,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n if (container === null || scene.get(container) === undefined) return [];\n let all: Affine[] = [[1, 0, 0, 1, 0, 0]];\n for (const aid of [container, ...scene.ancestorsOf(container)].reverse()) {\n const a = scene.get(aid);\n if (a?.kind !== 'container' || !a.copiesFromPose) continue;\n const copies = a.copiesFromPose(poseOf(a), a) ?? [];\n if (copies.length === 0) continue;\n // A copy at an outer level carries everything inside it, inner copies included.\n all = all.flatMap((m) => [m, ...copies.map((c) => multiply(m, c))]);\n }\n return all.slice(1);\n}\n\n/** Every transform the ancestors of `id` draw it under; see {@link copiesWithin}. */\nexport function copiesAbove<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: NodeId,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n return copiesWithin(scene, scene.get(id)?.parent ?? null, poseOf);\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,aACd,OACA,WACA,QACU;CACV,IAAI,cAAc,QAAQ,MAAM,IAAI,SAAS,MAAM,QAAW,OAAO,CAAC;CACtE,IAAI,MAAgB,CAAC;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC,CAAC;CACvC,KAAK,MAAM,OAAO,CAAC,WAAW,GAAG,MAAM,YAAY,SAAS,CAAC,CAAC,CAAC,QAAQ,GAAG;EACxE,MAAM,IAAI,MAAM,IAAI,GAAG;EACvB,IAAI,GAAG,SAAS,eAAe,CAAC,EAAE,gBAAgB;EAClD,MAAM,SAAS,EAAE,eAAe,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;EAClD,IAAI,OAAO,WAAW,GAAG;EAEzB,MAAM,IAAI,SAAS,MAAM,CAAC,GAAG,GAAG,OAAO,KAAK,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;CACpE;CACA,OAAO,IAAI,MAAM,CAAC;AACpB;;AAGA,SAAgB,YACd,OACA,IACA,QACU;CACV,OAAO,aAAa,OAAO,MAAM,IAAI,EAAE,CAAC,EAAE,UAAU,MAAM,MAAM;AAClE"}
1
+ {"version":3,"file":"copiesAbove.js","names":[],"sources":["../../src/canvas/copiesAbove.ts"],"sourcesContent":["import { multiply, type Affine } from '@weasel-js/geom';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\n\n/**\n * Every transform content placed in `container` is drawn under besides as\n * authored: the `copiesFromPose` of the container and of each container above\n * it, composed outermost first, the identity left out. Empty for the top of\n * the tree, or when nothing there draws copies. `poseOf` is the pose a\n * container is painted at, which is what its copies are computed from.\n */\nexport function copiesWithin<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n container: NodeId | null,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n if (container === null || scene.get(container) === undefined) return [];\n let all: Affine[] = [[1, 0, 0, 1, 0, 0]];\n for (const aid of [container, ...scene.ancestorsOf(container)].reverse()) {\n const a = scene.get(aid);\n if (!a?.copiesFromPose) continue;\n const copies = a.copiesFromPose(poseOf(a), a) ?? [];\n if (copies.length === 0) continue;\n // A copy at an outer level carries everything inside it, inner copies included.\n all = all.flatMap((m) => [m, ...copies.map((c) => multiply(m, c))]);\n }\n return all.slice(1);\n}\n\n/** Every transform the ancestors of `id` draw it under; see {@link copiesWithin}. */\nexport function copiesAbove<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: NodeId,\n poseOf: (node: Node<TData, TLayer, TPose>) => TPose,\n): Affine[] {\n return copiesWithin(scene, scene.get(id)?.parent ?? null, poseOf);\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,aACd,OACA,WACA,QACU;CACV,IAAI,cAAc,QAAQ,MAAM,IAAI,SAAS,MAAM,QAAW,OAAO,CAAC;CACtE,IAAI,MAAgB,CAAC;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC,CAAC;CACvC,KAAK,MAAM,OAAO,CAAC,WAAW,GAAG,MAAM,YAAY,SAAS,CAAC,CAAC,CAAC,QAAQ,GAAG;EACxE,MAAM,IAAI,MAAM,IAAI,GAAG;EACvB,IAAI,CAAC,GAAG,gBAAgB;EACxB,MAAM,SAAS,EAAE,eAAe,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;EAClD,IAAI,OAAO,WAAW,GAAG;EAEzB,MAAM,IAAI,SAAS,MAAM,CAAC,GAAG,GAAG,OAAO,KAAK,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;CACpE;CACA,OAAO,IAAI,MAAM,CAAC;AACpB;;AAGA,SAAgB,YACd,OACA,IACA,QACU;CACV,OAAO,aAAa,OAAO,MAAM,IAAI,EAAE,CAAC,EAAE,UAAU,MAAM,MAAM;AAClE"}
@@ -22,7 +22,7 @@ function viewOnlyCtx(view) {
22
22
  * registered node-shape painter that matches, then draw its `data.label` on
23
23
  * top when it has one: on top of a container's children too. Consumers replace or wrap this via `drawOne`. */
24
24
  function defaultDrawOne(node, pose, view, ctx) {
25
- const painter = findNodeShape(node);
25
+ const painter = node.kind === "group" ? void 0 : findNodeShape(node);
26
26
  const paintCtx = !view || ctx?.pixelScale !== void 0 ? ctx : ctx ? {
27
27
  ...ctx,
28
28
  pixelScale: pixelScaleOf(view)
@@ -41,24 +41,27 @@ function defaultDrawOne(node, pose, view, ctx) {
41
41
  fontFamily: "sans-serif",
42
42
  fontSize: 11
43
43
  });
44
- return [...primary, node.kind === "container" ? overChildren([label]) : label];
44
+ return [...primary, node.kind === "group" ? overChildren([label]) : label];
45
45
  }
46
46
  return primary;
47
47
  }
48
48
  /**
49
49
  * The box {@link defaultDrawOne}'s output for `node` fits in — the matched
50
50
  * painter's `bounds` — or `null` when it has none, or when a `data.label`
51
- * overlay adds text nothing bounds cheaply. The scene slot's `cull` reads it
52
- * to skip painting nodes the view cannot see.
51
+ * overlay adds text nothing bounds cheaply, or an inherited stroke with no
52
+ * `ctx.inherited` to say what it takes. The scene slot's `cull` reads it to
53
+ * skip painting nodes the view cannot see.
53
54
  */
54
- function defaultPaintBounds(node, pose, view) {
55
+ function defaultPaintBounds(node, pose, view, ctx) {
55
56
  const data = node.data;
56
57
  if (data?.label && data.text == null) return null;
57
- if (data?.stroke === INHERIT) return null;
58
+ const inherited = ctx?.inherited;
59
+ if (data?.stroke === INHERIT && inherited === void 0) return null;
58
60
  const sx = Math.abs(view.scale.x), sy = Math.abs(view.scale.y);
59
61
  return findShapeBounds(node, pose, {
60
62
  scale: Math.sqrt(sx * sy),
61
- leastScale: Math.min(sx, sy)
63
+ leastScale: Math.min(sx, sy),
64
+ inherited
62
65
  });
63
66
  }
64
67
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"defaultDrawOne.js","names":[],"sources":["../../src/canvas/defaultDrawOne.ts"],"sourcesContent":["/**\n * The kit's default per-node draw function, kept apart from `SceneCanvas.tsx` so non-React\n * callers (headless rasterization via `renderSceneToPixels`) can use it without importing the\n * React component tree.\n *\n * To teach the kit about a new kind of shape, register a painter (`registerNodeShape`) — do not\n * override `drawOne`. Override `drawOne` only for cross-cutting decoration.\n */\nimport type { Node } from 'core/scene/types';\nimport type { View } from 'core/viewport/view';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { textCommandFromRuns } from '@weasel-js/renderer';\nimport { findNodeShape, findShapeBounds, type NodePaintCtx } from './NodeShape';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { overChildren } from './overChildren';\nimport { INHERIT } from '@weasel-js/paint';\n\nfunction pixelScaleOf(view: View): number {\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n return Math.max(Math.abs(view.scale.x), Math.abs(view.scale.y)) * dpr;\n}\n\n// Shared across a frame's nodes, which all arrive with the same view.\nlet lastView: View | undefined;\nlet lastViewCtx: NodePaintCtx = {};\nfunction viewOnlyCtx(view: View): NodePaintCtx {\n const pixelScale = pixelScaleOf(view);\n if (view !== lastView || lastViewCtx.pixelScale !== pixelScale) {\n lastView = view;\n lastViewCtx = { pixelScale };\n }\n return lastViewCtx;\n}\n\n/** Paint one node the way the kit does by default: dispatch to the first\n * registered node-shape painter that matches, then draw its `data.label` on\n * top when it has one: on top of a container's children too. Consumers replace or wrap this via `drawOne`. */\nexport function defaultDrawOne<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view?: View,\n ctx?: NodePaintCtx,\n): DrawCommand[] {\n const painter = findNodeShape(node);\n const paintCtx = !view || ctx?.pixelScale !== undefined\n ? ctx\n : ctx ? { ...ctx, pixelScale: pixelScaleOf(view) } : viewOnlyCtx(view);\n const primary = painter ? painter.paint(node, pose, paintCtx) : [];\n\n // Label overlay — skipped for text nodes (their content is the label).\n const data = node.data as { label?: string; text?: string } | null;\n if (data?.label && data.text == null) {\n const p = pose as unknown as { x: number; y: number };\n // Copy rather than `push`. A painter's return value is not ours to grow:\n // one that memoizes its command list — which is the point of caching\n // `paint` — would gain another label command on every frame, forever.\n const label = textCommandFromRuns(\n p.x + 6,\n p.y + 14,\n [{ text: data.label, fill: { paint: { kind: 'solid', color: 'rgba(0,0,0,0.7)' } } }],\n { fontFamily: 'sans-serif', fontSize: 11 },\n );\n // A container's children would cover a label drawn with the container.\n return [...primary, node.kind === 'container' ? overChildren([label]) : label];\n }\n\n // Rotation and per-node alpha are applied OUTSIDE this function, by `wrapNodeOutput` in\n // the scene walks and the preview-ghost layer, so that consumer-supplied painters get\n // them too.\n return primary;\n}\n\n/**\n * The box {@link defaultDrawOne}'s output for `node` fits in — the matched\n * painter's `bounds` — or `null` when it has none, or when a `data.label`\n * overlay adds text nothing bounds cheaply. The scene slot's `cull` reads it\n * to skip painting nodes the view cannot see.\n */\nexport function defaultPaintBounds<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view: View,\n): Bounds | null {\n const data = node.data as { label?: string; text?: string; stroke?: unknown } | null;\n if (data?.label && data.text == null) return null;\n // No scene here to say what an inherited stroke reaches.\n if (data?.stroke === INHERIT) return null;\n const sx = Math.abs(view.scale.x), sy = Math.abs(view.scale.y);\n return findShapeBounds(node, pose, { scale: Math.sqrt(sx * sy), leastScale: Math.min(sx, sy) });\n}\n\n/**\n * `defaultPaintBounds` when `drawOne` is `defaultDrawOne`, and nothing\n * otherwise: a painter's box says nothing about what a replacement\n * `drawOne` paints.\n */\nexport function paintBoundsFor<TData, TLayer extends string, TPose>(\n drawOne: unknown,\n): typeof defaultPaintBounds<TData, TLayer, TPose> | undefined {\n return drawOne === defaultDrawOne ? defaultPaintBounds : undefined;\n}\n"],"mappings":";;;;;;AAiBA,SAAS,aAAa,MAAoB;CACxC,MAAM,MAAO,OAAO,WAAW,eAAe,OAAO,oBAAqB;CAC1E,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,IAAI;AACpE;AAGA,IAAI;AACJ,IAAI,cAA4B,CAAC;AACjC,SAAS,YAAY,MAA0B;CAC7C,MAAM,aAAa,aAAa,IAAI;CACpC,IAAI,SAAS,YAAY,YAAY,eAAe,YAAY;EAC9D,WAAW;EACX,cAAc,EAAE,WAAW;CAC7B;CACA,OAAO;AACT;;;;AAKA,SAAgB,eACd,MACA,MACA,MACA,KACe;CACf,MAAM,UAAU,cAAc,IAAI;CAClC,MAAM,WAAW,CAAC,QAAQ,KAAK,eAAe,SAC1C,MACA,MAAM;EAAE,GAAG;EAAK,YAAY,aAAa,IAAI;CAAE,IAAI,YAAY,IAAI;CACvE,MAAM,UAAU,UAAU,QAAQ,MAAM,MAAM,MAAM,QAAQ,IAAI,CAAC;CAGjE,MAAM,OAAO,KAAK;CAClB,IAAI,MAAM,SAAS,KAAK,QAAQ,MAAM;EACpC,MAAM,IAAI;EAIV,MAAM,QAAQ,oBACZ,EAAE,IAAI,GACN,EAAE,IAAI,IACN,CAAC;GAAE,MAAM,KAAK;GAAO,MAAM,EAAE,OAAO;IAAE,MAAM;IAAS,OAAO;GAAkB,EAAE;EAAE,CAAC,GACnF;GAAE,YAAY;GAAc,UAAU;EAAG,CAC3C;EAEA,OAAO,CAAC,GAAG,SAAS,KAAK,SAAS,cAAc,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK;CAC/E;CAKA,OAAO;AACT;;;;;;;AAQA,SAAgB,mBACd,MACA,MACA,MACe;CACf,MAAM,OAAO,KAAK;CAClB,IAAI,MAAM,SAAS,KAAK,QAAQ,MAAM,OAAO;CAE7C,IAAI,MAAM,WAAW,SAAS,OAAO;CACrC,MAAM,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;CAC7D,OAAO,gBAAgB,MAAM,MAAM;EAAE,OAAO,KAAK,KAAK,KAAK,EAAE;EAAG,YAAY,KAAK,IAAI,IAAI,EAAE;CAAE,CAAC;AAChG;;;;;;AAOA,SAAgB,eACd,SAC6D;CAC7D,OAAO,YAAY,iBAAiB,qBAAqB;AAC3D"}
1
+ {"version":3,"file":"defaultDrawOne.js","names":[],"sources":["../../src/canvas/defaultDrawOne.ts"],"sourcesContent":["/**\n * The kit's default per-node draw function, kept apart from `SceneCanvas.tsx` so non-React\n * callers (headless rasterization via `renderSceneToPixels`) can use it without importing the\n * React component tree.\n *\n * To teach the kit about a new kind of shape, register a painter (`registerNodeShape`) — do not\n * override `drawOne`. Override `drawOne` only for cross-cutting decoration.\n */\nimport type { Node } from 'core/scene/types';\nimport type { View } from 'core/viewport/view';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { textCommandFromRuns } from '@weasel-js/renderer';\nimport { findNodeShape, findShapeBounds, type NodePaintCtx } from './NodeShape';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { overChildren } from './overChildren';\nimport { INHERIT } from '@weasel-js/paint';\n\nfunction pixelScaleOf(view: View): number {\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n return Math.max(Math.abs(view.scale.x), Math.abs(view.scale.y)) * dpr;\n}\n\n// Shared across a frame's nodes, which all arrive with the same view.\nlet lastView: View | undefined;\nlet lastViewCtx: NodePaintCtx = {};\nfunction viewOnlyCtx(view: View): NodePaintCtx {\n const pixelScale = pixelScaleOf(view);\n if (view !== lastView || lastViewCtx.pixelScale !== pixelScale) {\n lastView = view;\n lastViewCtx = { pixelScale };\n }\n return lastViewCtx;\n}\n\n/** Paint one node the way the kit does by default: dispatch to the first\n * registered node-shape painter that matches, then draw its `data.label` on\n * top when it has one: on top of a container's children too. Consumers replace or wrap this via `drawOne`. */\nexport function defaultDrawOne<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view?: View,\n ctx?: NodePaintCtx,\n): DrawCommand[] {\n // A group is structure: it never paints, whatever its data holds.\n const painter = node.kind === 'group' ? undefined : findNodeShape(node);\n const paintCtx = !view || ctx?.pixelScale !== undefined\n ? ctx\n : ctx ? { ...ctx, pixelScale: pixelScaleOf(view) } : viewOnlyCtx(view);\n const primary = painter ? painter.paint(node, pose, paintCtx) : [];\n\n // Label overlay — skipped for text nodes (their content is the label).\n const data = node.data as { label?: string; text?: string } | null;\n if (data?.label && data.text == null) {\n const p = pose as unknown as { x: number; y: number };\n // Copy rather than `push`. A painter's return value is not ours to grow:\n // one that memoizes its command list — which is the point of caching\n // `paint` — would gain another label command on every frame, forever.\n const label = textCommandFromRuns(\n p.x + 6,\n p.y + 14,\n [{ text: data.label, fill: { paint: { kind: 'solid', color: 'rgba(0,0,0,0.7)' } } }],\n { fontFamily: 'sans-serif', fontSize: 11 },\n );\n // A container's children would cover a label drawn with the container.\n return [...primary, node.kind === 'group' ? overChildren([label]) : label];\n }\n\n // Rotation and per-node alpha are applied OUTSIDE this function, by `wrapNodeOutput` in\n // the scene walks and the preview-ghost layer, so that consumer-supplied painters get\n // them too.\n return primary;\n}\n\n/**\n * The box {@link defaultDrawOne}'s output for `node` fits in — the matched\n * painter's `bounds` — or `null` when it has none, or when a `data.label`\n * overlay adds text nothing bounds cheaply, or an inherited stroke with no\n * `ctx.inherited` to say what it takes. The scene slot's `cull` reads it to\n * skip painting nodes the view cannot see.\n */\nexport function defaultPaintBounds<TData, TLayer extends string, TPose>(\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n view: View,\n ctx?: Pick<NodePaintCtx, 'inherited'>,\n): Bounds | null {\n const data = node.data as { label?: string; text?: string; stroke?: unknown } | null;\n if (data?.label && data.text == null) return null;\n const inherited = ctx?.inherited;\n if (data?.stroke === INHERIT && inherited === undefined) return null;\n const sx = Math.abs(view.scale.x), sy = Math.abs(view.scale.y);\n return findShapeBounds(node, pose, { scale: Math.sqrt(sx * sy), leastScale: Math.min(sx, sy), inherited });\n}\n\n/**\n * `defaultPaintBounds` when `drawOne` is `defaultDrawOne`, and nothing\n * otherwise: a painter's box says nothing about what a replacement\n * `drawOne` paints.\n */\nexport function paintBoundsFor<TData, TLayer extends string, TPose>(\n drawOne: unknown,\n): typeof defaultPaintBounds<TData, TLayer, TPose> | undefined {\n return drawOne === defaultDrawOne ? defaultPaintBounds : undefined;\n}\n"],"mappings":";;;;;;AAiBA,SAAS,aAAa,MAAoB;CACxC,MAAM,MAAO,OAAO,WAAW,eAAe,OAAO,oBAAqB;CAC1E,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,IAAI;AACpE;AAGA,IAAI;AACJ,IAAI,cAA4B,CAAC;AACjC,SAAS,YAAY,MAA0B;CAC7C,MAAM,aAAa,aAAa,IAAI;CACpC,IAAI,SAAS,YAAY,YAAY,eAAe,YAAY;EAC9D,WAAW;EACX,cAAc,EAAE,WAAW;CAC7B;CACA,OAAO;AACT;;;;AAKA,SAAgB,eACd,MACA,MACA,MACA,KACe;CAEf,MAAM,UAAU,KAAK,SAAS,UAAU,SAAY,cAAc,IAAI;CACtE,MAAM,WAAW,CAAC,QAAQ,KAAK,eAAe,SAC1C,MACA,MAAM;EAAE,GAAG;EAAK,YAAY,aAAa,IAAI;CAAE,IAAI,YAAY,IAAI;CACvE,MAAM,UAAU,UAAU,QAAQ,MAAM,MAAM,MAAM,QAAQ,IAAI,CAAC;CAGjE,MAAM,OAAO,KAAK;CAClB,IAAI,MAAM,SAAS,KAAK,QAAQ,MAAM;EACpC,MAAM,IAAI;EAIV,MAAM,QAAQ,oBACZ,EAAE,IAAI,GACN,EAAE,IAAI,IACN,CAAC;GAAE,MAAM,KAAK;GAAO,MAAM,EAAE,OAAO;IAAE,MAAM;IAAS,OAAO;GAAkB,EAAE;EAAE,CAAC,GACnF;GAAE,YAAY;GAAc,UAAU;EAAG,CAC3C;EAEA,OAAO,CAAC,GAAG,SAAS,KAAK,SAAS,UAAU,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK;CAC3E;CAKA,OAAO;AACT;;;;;;;;AASA,SAAgB,mBACd,MACA,MACA,MACA,KACe;CACf,MAAM,OAAO,KAAK;CAClB,IAAI,MAAM,SAAS,KAAK,QAAQ,MAAM,OAAO;CAC7C,MAAM,YAAY,KAAK;CACvB,IAAI,MAAM,WAAW,WAAW,cAAc,QAAW,OAAO;CAChE,MAAM,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;CAC7D,OAAO,gBAAgB,MAAM,MAAM;EAAE,OAAO,KAAK,KAAK,KAAK,EAAE;EAAG,YAAY,KAAK,IAAI,IAAI,EAAE;EAAG;CAAU,CAAC;AAC3G;;;;;;AAOA,SAAgB,eACd,SAC6D;CAC7D,OAAO,YAAY,iBAAiB,qBAAqB;AAC3D"}
@@ -14,18 +14,21 @@ import { useDepSource } from "@weasel-js/routing/react";
14
14
  * ref internally), so callers can pass fresh selection/scene refs without
15
15
  * triggering re-registration.
16
16
  */
17
- function useAreaSelectDepSource(scene, selection, descriptor, alphaOf) {
17
+ function useAreaSelectDepSource(scene, selection, descriptor, alphaOf, reachable) {
18
18
  const sceneRef = useLatest(scene);
19
19
  const selectionRef = useLatest(selection);
20
20
  const descriptorRef = useLatest(descriptor);
21
21
  const alphaOfRef = useLatest(alphaOf);
22
+ const reachableRef = useLatest(reachable);
22
23
  useDepSource("areaSelect", () => {
23
24
  const sc = sceneRef.current;
24
25
  const s = selectionRef.current;
25
26
  const d = descriptorRef.current;
26
27
  const a = alphaOfRef.current;
28
+ const r = reachableRef.current;
29
+ const keep = (ids) => r ? ids.filter((id) => r(id)) : ids;
27
30
  return {
28
- hitTestArea: (bounds, view) => hitTestArea(sc, bounds, regionPickOptions(view, a), d),
31
+ hitTestArea: (bounds, view) => keep(hitTestArea(sc, bounds, regionPickOptions(view, a), d)),
29
32
  getSelection: () => s.current,
30
33
  setSelection: (ids) => s.set(ids)
31
34
  };
@@ -1 +1 @@
1
- {"version":3,"file":"areaSelect.js","names":["hitTestAreaShared"],"sources":["../../../src/canvas/deps/areaSelect.ts"],"sourcesContent":["/**\n * `useAreaSelectDepSource` — wires the `areaSelect` dep consumed by\n * `areaSelectAction`. The dep exposes:\n * - `hitTestArea(bounds)` — silhouette-aware scan over `scene.renderOrderNodes()`\n * (rect poses by AABB; polygon poses by the kernel polygon-overlap test).\n * - `getSelection` / `setSelection` — passthrough to the kit selection api.\n *\n * Built once per render and stabilised by `useDepSource` (which reads via a\n * ref internally), so callers can pass fresh selection/scene refs without\n * triggering re-registration.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { AreaSelectDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { hitTestArea as hitTestAreaShared, regionPickOptions } from './hitTestArea';\n\nexport function useAreaSelectDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n descriptor?: PoseDescriptor<unknown>,\n /** The painted alpha the surface's layers apply, which every view shares. */\n alphaOf?: (id: string) => number,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const descriptorRef = useLatest(descriptor);\n const alphaOfRef = useLatest(alphaOf);\n\n useDepSource('areaSelect', (): AreaSelectDep => {\n const sc = sceneRef.current;\n const s = selectionRef.current;\n const d = descriptorRef.current;\n const a = alphaOfRef.current;\n return {\n hitTestArea: (bounds, view) =>\n hitTestAreaShared(sc, bounds, regionPickOptions(view, a), d),\n getSelection: () => s.current as NodeId[],\n setSelection: (ids) => s.set(ids),\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,uBACd,OACA,WACA,YAEA,SACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,gBAAgB,UAAU,UAAU;CAC1C,MAAM,aAAa,UAAU,OAAO;CAEpC,aAAa,oBAAmC;EAC9C,MAAM,KAAK,SAAS;EACpB,MAAM,IAAI,aAAa;EACvB,MAAM,IAAI,cAAc;EACxB,MAAM,IAAI,WAAW;EACrB,OAAO;GACL,cAAc,QAAQ,SACpBA,YAAkB,IAAI,QAAQ,kBAAkB,MAAM,CAAC,GAAG,CAAC;GAC7D,oBAAoB,EAAE;GACtB,eAAe,QAAQ,EAAE,IAAI,GAAG;EAClC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"areaSelect.js","names":["hitTestAreaShared"],"sources":["../../../src/canvas/deps/areaSelect.ts"],"sourcesContent":["/**\n * `useAreaSelectDepSource` — wires the `areaSelect` dep consumed by\n * `areaSelectAction`. The dep exposes:\n * - `hitTestArea(bounds)` — silhouette-aware scan over `scene.renderOrderNodes()`\n * (rect poses by AABB; polygon poses by the kernel polygon-overlap test).\n * - `getSelection` / `setSelection` — passthrough to the kit selection api.\n *\n * Built once per render and stabilised by `useDepSource` (which reads via a\n * ref internally), so callers can pass fresh selection/scene refs without\n * triggering re-registration.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { AreaSelectDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { hitTestArea as hitTestAreaShared, regionPickOptions } from './hitTestArea';\n\nexport function useAreaSelectDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n descriptor?: PoseDescriptor<unknown>,\n /** The painted alpha the surface's layers apply, which every view shares. */\n alphaOf?: (id: string) => number,\n /** Whether a node can be reached at all: the canvas's one answer, which picking reads too. */\n reachable?: (id: string) => boolean,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const descriptorRef = useLatest(descriptor);\n const alphaOfRef = useLatest(alphaOf);\n const reachableRef = useLatest(reachable);\n\n useDepSource('areaSelect', (): AreaSelectDep => {\n const sc = sceneRef.current;\n const s = selectionRef.current;\n const d = descriptorRef.current;\n const a = alphaOfRef.current;\n const r = reachableRef.current;\n const keep = (ids: NodeId[]): NodeId[] => (r ? ids.filter((id) => r(id)) : ids);\n return {\n hitTestArea: (bounds, view) =>\n keep(hitTestAreaShared(sc, bounds, regionPickOptions(view, a), d)),\n getSelection: () => s.current as NodeId[],\n setSelection: (ids) => s.set(ids),\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,uBACd,OACA,WACA,YAEA,SAEA,WACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,gBAAgB,UAAU,UAAU;CAC1C,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,eAAe,UAAU,SAAS;CAExC,aAAa,oBAAmC;EAC9C,MAAM,KAAK,SAAS;EACpB,MAAM,IAAI,aAAa;EACvB,MAAM,IAAI,cAAc;EACxB,MAAM,IAAI,WAAW;EACrB,MAAM,IAAI,aAAa;EACvB,MAAM,QAAQ,QAA6B,IAAI,IAAI,QAAQ,OAAO,EAAE,EAAE,CAAC,IAAI;EAC3E,OAAO;GACL,cAAc,QAAQ,SACpB,KAAKA,YAAkB,IAAI,QAAQ,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC;GACnE,oBAAoB,EAAE;GACtB,eAAe,QAAQ,EAAE,IAAI,GAAG;EAClC;CACF,CAAC;AACH"}
@@ -82,7 +82,7 @@ function useBooleansAdapterSource(scene, selection, adapter, poseDescriptor) {
82
82
  };
83
83
  return {
84
84
  id: sceneRef.current.mintId(),
85
- kind: "leaf",
85
+ kind: "mark",
86
86
  layer: leaf.node.layer,
87
87
  parent: leaf.node.parent ?? null,
88
88
  pose: leaf.frame.local(sourceId, stored.pose),
@@ -1 +1 @@
1
- {"version":3,"file":"booleans.js","names":[],"sources":["../../../src/canvas/deps/booleans.ts"],"sourcesContent":["/**\n * The kit `booleansAdapter` dep: what the Pathfinder actions, and any tool\n * reading the dep, work on over a canvas's own path leaves.\n *\n * `<SceneCanvas>` publishes it over its own scene. A consumer replaces it with\n * `useBooleansAdapter` from under the canvas — for text operands, say, which\n * this one does not read; the newest registration wins, and a canvas child\n * registers after the canvas's own.\n */\nimport { sceneInheritedPaintOf } from 'canvas/inheritedPaints';\nimport { useMemo } from 'react';\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport { pathInWorld } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { sceneMutators } from 'core/scene/sceneMutators';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { scenePoseFrame } from 'interactions/actions/poseFrame';\nimport type { BooleansAdapter } from 'interactions/actions/booleans/booleans';\nimport { pathPaintOf, type PathPaint } from '../nodeShapes/nodePaint';\nimport { drawnPose, leafPath, storePiece } from './pathLeaf';\nimport type { OpsApplier } from './slice';\n\n/**\n * Publish a `booleansAdapter` over `scene`'s path leaves (`data.path` drawn\n * through the pose's box). A result is minted from the source it stands in\n * for: its layer, parent, frame, and data, with the result's path, and the\n * result's paint in place of the source's when the operation gives one.\n */\nexport function useBooleansAdapterSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n adapter: OpsApplier,\n poseDescriptor?: PoseDescriptor<unknown>,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const adapterRef = useLatest(adapter);\n const descriptorRef = useLatest(poseDescriptor);\n\n const booleans = useMemo<BooleansAdapter>(() => {\n const leafOf = (id: string) => {\n const sc = sceneRef.current;\n const node = sc.get(asNodeId(id));\n const path = node ? leafPath(sc, node) : null;\n if (!node || !path) return null;\n const frame = scenePoseFrame(sc);\n const g = poseDescriptorForNode(descriptorRef.current ?? AUTO_POSE_DESCRIPTOR, node);\n const drawn = drawnPose(g, frame.world(node.id));\n return drawn ? { node, path, drawn, frame } : null;\n };\n const tree = () => sceneMutators(sceneRef.current);\n return {\n getNode: (id) => tree().getNode(id),\n getParent: (id) => tree().getParent(id),\n getChildren: (parentId) => tree().getChildren(parentId),\n setChildOrder: (parentId, ids) => tree().setChildOrder(parentId, ids),\n getSelection: () => selectionRef.current.get(),\n getWorldPath: (id) => {\n const leaf = leafOf(id);\n return leaf ? pathInWorld(leaf.path, leaf.drawn.box) : undefined;\n },\n getPaint: (id) => {\n const leaf = leafOf(id);\n return leaf\n ? pathPaintOf(leaf.node.data as Parameters<typeof pathPaintOf>[0], sceneInheritedPaintOf(sceneRef.current)(leaf.node))\n : undefined;\n },\n compareZ: (a, b) => {\n const order = [...sceneRef.current.renderOrder()];\n return order.indexOf(asNodeId(a)) - order.indexOf(asNodeId(b));\n },\n createPathNode: (path, _op, sourceId, paint?: PathPaint) => {\n const leaf = leafOf(sourceId);\n if (!leaf) throw new Error(`[booleansAdapter] no path leaf behind source ${sourceId}`);\n const stored = storePiece(leaf.drawn, path);\n const { fill: _fill, stroke: _stroke, ...rest } = (leaf.node.data ?? {}) as Record<string, unknown>;\n const data = paint ? { ...rest, ...paint, path: stored.path } : { ...(leaf.node.data as object), path: stored.path };\n return {\n id: sceneRef.current.mintId(),\n kind: 'leaf',\n layer: leaf.node.layer,\n parent: leaf.node.parent ?? null,\n pose: leaf.frame.local(sourceId, stored.pose),\n data,\n };\n },\n applyOps: (ops, label) => adapterRef.current.applyOps(ops, label),\n };\n }, [sceneRef, selectionRef, adapterRef, descriptorRef]);\n\n useDepSource('booleansAdapter', () => booleans);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,yBACd,OACA,WACA,SACA,gBACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,gBAAgB,UAAU,cAAc;CAE9C,MAAM,WAAW,cAA+B;EAC9C,MAAM,UAAU,OAAe;GAC7B,MAAM,KAAK,SAAS;GACpB,MAAM,OAAO,GAAG,IAAI,SAAS,EAAE,CAAC;GAChC,MAAM,OAAO,OAAO,SAAS,IAAI,IAAI,IAAI;GACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO;GAC3B,MAAM,QAAQ,eAAe,EAAE;GAC/B,MAAM,IAAI,sBAAsB,cAAc,WAAW,sBAAsB,IAAI;GACnF,MAAM,QAAQ,UAAU,GAAG,MAAM,MAAM,KAAK,EAAE,CAAC;GAC/C,OAAO,QAAQ;IAAE;IAAM;IAAM;IAAO;GAAM,IAAI;EAChD;EACA,MAAM,aAAa,cAAc,SAAS,OAAO;EACjD,OAAO;GACL,UAAU,OAAO,KAAK,CAAC,CAAC,QAAQ,EAAE;GAClC,YAAY,OAAO,KAAK,CAAC,CAAC,UAAU,EAAE;GACtC,cAAc,aAAa,KAAK,CAAC,CAAC,YAAY,QAAQ;GACtD,gBAAgB,UAAU,QAAQ,KAAK,CAAC,CAAC,cAAc,UAAU,GAAG;GACpE,oBAAoB,aAAa,QAAQ,IAAI;GAC7C,eAAe,OAAO;IACpB,MAAM,OAAO,OAAO,EAAE;IACtB,OAAO,OAAO,YAAY,KAAK,MAAM,KAAK,MAAM,GAAG,IAAI;GACzD;GACA,WAAW,OAAO;IAChB,MAAM,OAAO,OAAO,EAAE;IACtB,OAAO,OACH,YAAY,KAAK,KAAK,MAA2C,sBAAsB,SAAS,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,IACnH;GACN;GACA,WAAW,GAAG,MAAM;IAClB,MAAM,QAAQ,CAAC,GAAG,SAAS,QAAQ,YAAY,CAAC;IAChD,OAAO,MAAM,QAAQ,SAAS,CAAC,CAAC,IAAI,MAAM,QAAQ,SAAS,CAAC,CAAC;GAC/D;GACA,iBAAiB,MAAM,KAAK,UAAU,UAAsB;IAC1D,MAAM,OAAO,OAAO,QAAQ;IAC5B,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,gDAAgD,UAAU;IACrF,MAAM,SAAS,WAAW,KAAK,OAAO,IAAI;IAC1C,MAAM,EAAE,MAAM,OAAO,QAAQ,SAAS,GAAG,SAAU,KAAK,KAAK,QAAQ,CAAC;IACtE,MAAM,OAAO,QAAQ;KAAE,GAAG;KAAM,GAAG;KAAO,MAAM,OAAO;IAAK,IAAI;KAAE,GAAI,KAAK,KAAK;KAAiB,MAAM,OAAO;IAAK;IACnH,OAAO;KACL,IAAI,SAAS,QAAQ,OAAO;KAC5B,MAAM;KACN,OAAO,KAAK,KAAK;KACjB,QAAQ,KAAK,KAAK,UAAU;KAC5B,MAAM,KAAK,MAAM,MAAM,UAAU,OAAO,IAAI;KAC5C;IACF;GACF;GACA,WAAW,KAAK,UAAU,WAAW,QAAQ,SAAS,KAAK,KAAK;EAClE;CACF,GAAG;EAAC;EAAU;EAAc;EAAY;CAAa,CAAC;CAEtD,aAAa,yBAAyB,QAAQ;AAChD"}
1
+ {"version":3,"file":"booleans.js","names":[],"sources":["../../../src/canvas/deps/booleans.ts"],"sourcesContent":["/**\n * The kit `booleansAdapter` dep: what the Pathfinder actions, and any tool\n * reading the dep, work on over a canvas's own path leaves.\n *\n * `<SceneCanvas>` publishes it over its own scene. A consumer replaces it with\n * `useBooleansAdapter` from under the canvas — for text operands, say, which\n * this one does not read; the newest registration wins, and a canvas child\n * registers after the canvas's own.\n */\nimport { sceneInheritedPaintOf } from 'canvas/inheritedPaints';\nimport { useMemo } from 'react';\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport { pathInWorld } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { sceneMutators } from 'core/scene/sceneMutators';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport { scenePoseFrame } from 'interactions/actions/poseFrame';\nimport type { BooleansAdapter } from 'interactions/actions/booleans/booleans';\nimport { pathPaintOf, type PathPaint } from '../nodeShapes/nodePaint';\nimport { drawnPose, leafPath, storePiece } from './pathLeaf';\nimport type { OpsApplier } from './slice';\n\n/**\n * Publish a `booleansAdapter` over `scene`'s path leaves (`data.path` drawn\n * through the pose's box). A result is minted from the source it stands in\n * for: its layer, parent, frame, and data, with the result's path, and the\n * result's paint in place of the source's when the operation gives one.\n */\nexport function useBooleansAdapterSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n adapter: OpsApplier,\n poseDescriptor?: PoseDescriptor<unknown>,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const adapterRef = useLatest(adapter);\n const descriptorRef = useLatest(poseDescriptor);\n\n const booleans = useMemo<BooleansAdapter>(() => {\n const leafOf = (id: string) => {\n const sc = sceneRef.current;\n const node = sc.get(asNodeId(id));\n const path = node ? leafPath(sc, node) : null;\n if (!node || !path) return null;\n const frame = scenePoseFrame(sc);\n const g = poseDescriptorForNode(descriptorRef.current ?? AUTO_POSE_DESCRIPTOR, node);\n const drawn = drawnPose(g, frame.world(node.id));\n return drawn ? { node, path, drawn, frame } : null;\n };\n const tree = () => sceneMutators(sceneRef.current);\n return {\n getNode: (id) => tree().getNode(id),\n getParent: (id) => tree().getParent(id),\n getChildren: (parentId) => tree().getChildren(parentId),\n setChildOrder: (parentId, ids) => tree().setChildOrder(parentId, ids),\n getSelection: () => selectionRef.current.get(),\n getWorldPath: (id) => {\n const leaf = leafOf(id);\n return leaf ? pathInWorld(leaf.path, leaf.drawn.box) : undefined;\n },\n getPaint: (id) => {\n const leaf = leafOf(id);\n return leaf\n ? pathPaintOf(leaf.node.data as Parameters<typeof pathPaintOf>[0], sceneInheritedPaintOf(sceneRef.current)(leaf.node))\n : undefined;\n },\n compareZ: (a, b) => {\n const order = [...sceneRef.current.renderOrder()];\n return order.indexOf(asNodeId(a)) - order.indexOf(asNodeId(b));\n },\n createPathNode: (path, _op, sourceId, paint?: PathPaint) => {\n const leaf = leafOf(sourceId);\n if (!leaf) throw new Error(`[booleansAdapter] no path leaf behind source ${sourceId}`);\n const stored = storePiece(leaf.drawn, path);\n const { fill: _fill, stroke: _stroke, ...rest } = (leaf.node.data ?? {}) as Record<string, unknown>;\n const data = paint ? { ...rest, ...paint, path: stored.path } : { ...(leaf.node.data as object), path: stored.path };\n return {\n id: sceneRef.current.mintId(),\n kind: 'mark',\n layer: leaf.node.layer,\n parent: leaf.node.parent ?? null,\n pose: leaf.frame.local(sourceId, stored.pose),\n data,\n };\n },\n applyOps: (ops, label) => adapterRef.current.applyOps(ops, label),\n };\n }, [sceneRef, selectionRef, adapterRef, descriptorRef]);\n\n useDepSource('booleansAdapter', () => booleans);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,yBACd,OACA,WACA,SACA,gBACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,gBAAgB,UAAU,cAAc;CAE9C,MAAM,WAAW,cAA+B;EAC9C,MAAM,UAAU,OAAe;GAC7B,MAAM,KAAK,SAAS;GACpB,MAAM,OAAO,GAAG,IAAI,SAAS,EAAE,CAAC;GAChC,MAAM,OAAO,OAAO,SAAS,IAAI,IAAI,IAAI;GACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO;GAC3B,MAAM,QAAQ,eAAe,EAAE;GAC/B,MAAM,IAAI,sBAAsB,cAAc,WAAW,sBAAsB,IAAI;GACnF,MAAM,QAAQ,UAAU,GAAG,MAAM,MAAM,KAAK,EAAE,CAAC;GAC/C,OAAO,QAAQ;IAAE;IAAM;IAAM;IAAO;GAAM,IAAI;EAChD;EACA,MAAM,aAAa,cAAc,SAAS,OAAO;EACjD,OAAO;GACL,UAAU,OAAO,KAAK,CAAC,CAAC,QAAQ,EAAE;GAClC,YAAY,OAAO,KAAK,CAAC,CAAC,UAAU,EAAE;GACtC,cAAc,aAAa,KAAK,CAAC,CAAC,YAAY,QAAQ;GACtD,gBAAgB,UAAU,QAAQ,KAAK,CAAC,CAAC,cAAc,UAAU,GAAG;GACpE,oBAAoB,aAAa,QAAQ,IAAI;GAC7C,eAAe,OAAO;IACpB,MAAM,OAAO,OAAO,EAAE;IACtB,OAAO,OAAO,YAAY,KAAK,MAAM,KAAK,MAAM,GAAG,IAAI;GACzD;GACA,WAAW,OAAO;IAChB,MAAM,OAAO,OAAO,EAAE;IACtB,OAAO,OACH,YAAY,KAAK,KAAK,MAA2C,sBAAsB,SAAS,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,IACnH;GACN;GACA,WAAW,GAAG,MAAM;IAClB,MAAM,QAAQ,CAAC,GAAG,SAAS,QAAQ,YAAY,CAAC;IAChD,OAAO,MAAM,QAAQ,SAAS,CAAC,CAAC,IAAI,MAAM,QAAQ,SAAS,CAAC,CAAC;GAC/D;GACA,iBAAiB,MAAM,KAAK,UAAU,UAAsB;IAC1D,MAAM,OAAO,OAAO,QAAQ;IAC5B,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,gDAAgD,UAAU;IACrF,MAAM,SAAS,WAAW,KAAK,OAAO,IAAI;IAC1C,MAAM,EAAE,MAAM,OAAO,QAAQ,SAAS,GAAG,SAAU,KAAK,KAAK,QAAQ,CAAC;IACtE,MAAM,OAAO,QAAQ;KAAE,GAAG;KAAM,GAAG;KAAO,MAAM,OAAO;IAAK,IAAI;KAAE,GAAI,KAAK,KAAK;KAAiB,MAAM,OAAO;IAAK;IACnH,OAAO;KACL,IAAI,SAAS,QAAQ,OAAO;KAC5B,MAAM;KACN,OAAO,KAAK,KAAK;KACjB,QAAQ,KAAK,KAAK,UAAU;KAC5B,MAAM,KAAK,MAAM,MAAM,UAAU,OAAO,IAAI;KAC5C;IACF;GACF;GACA,WAAW,KAAK,UAAU,WAAW,QAAQ,SAAS,KAAK,KAAK;EAClE;CACF,GAAG;EAAC;EAAU;EAAc;EAAY;CAAa,CAAC;CAEtD,aAAa,yBAAyB,QAAQ;AAChD"}
@@ -0,0 +1,81 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { useDepRegistry, useDepSource } from "@weasel-js/routing/react";
3
+
4
+ //#region src/canvas/deps/enteredGroup.ts
5
+ /**
6
+ * The entered group: the group a press resolves inside of. Session state beside
7
+ * the selection, not scene data, so it is never saved and never undone.
8
+ *
9
+ * It remembers the entered group and the groups above it. `get()` answers the
10
+ * innermost of those still in the scene, so deleting the group lands on its
11
+ * parent, and an undo that brings it back lands on it again.
12
+ */
13
+ /** Whether `id` is reachable while `group` is entered: inside it, and not the group itself. With
14
+ * nothing entered every node is. Picking, region selects and select-all all ask this. */
15
+ function reachableInGroup(scene, group, id) {
16
+ return group === null || scene.ancestorsOf(id).includes(group);
17
+ }
18
+ /** An entered-group store over `scene`, with nothing entered. */
19
+ function createEnteredGroup(scene) {
20
+ let chain = [];
21
+ let announced = null;
22
+ const listeners = /* @__PURE__ */ new Set();
23
+ const isGroup = (id) => scene.get(id)?.kind === "group";
24
+ const get = () => chain.find(isGroup) ?? null;
25
+ const groupsFrom = (id) => [id, ...scene.ancestorsOf(id)].filter(isGroup);
26
+ const announce = () => {
27
+ announced = get();
28
+ for (const fn of [...listeners]) fn();
29
+ };
30
+ let stopScene = null;
31
+ const watchScene = () => scene.subscribe(() => {
32
+ if (get() !== announced) announce();
33
+ });
34
+ return {
35
+ get,
36
+ enter(id) {
37
+ if (!isGroup(id)) return;
38
+ chain = groupsFrom(id);
39
+ announce();
40
+ },
41
+ leave() {
42
+ const here = get();
43
+ if (here === null) return;
44
+ chain = groupsFrom(here).slice(1);
45
+ announce();
46
+ },
47
+ leaveAll() {
48
+ if (get() === null) return;
49
+ chain = [];
50
+ announce();
51
+ },
52
+ subscribe(fn) {
53
+ if (listeners.size === 0) {
54
+ announced = get();
55
+ stopScene = watchScene();
56
+ }
57
+ listeners.add(fn);
58
+ return () => {
59
+ listeners.delete(fn);
60
+ if (listeners.size === 0) {
61
+ stopScene?.();
62
+ stopScene = null;
63
+ }
64
+ };
65
+ }
66
+ };
67
+ }
68
+ /** One entered-group store for the life of the scene. */
69
+ function useEnteredGroup(scene) {
70
+ return useMemo(() => createEnteredGroup(scene), [scene]);
71
+ }
72
+ function useEnteredGroupApiSource(dep) {
73
+ useDepSource("enteredGroup", () => dep);
74
+ useDepSource("insertInto", (below) => ({ get: () => below()?.get() ?? dep.get() }));
75
+ const registry = useDepRegistry();
76
+ useEffect(() => dep.subscribe(() => registry.changed?.()), [dep, registry]);
77
+ }
78
+
79
+ //#endregion
80
+ export { createEnteredGroup, reachableInGroup, useEnteredGroup, useEnteredGroupApiSource };
81
+ //# sourceMappingURL=enteredGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enteredGroup.js","names":[],"sources":["../../../src/canvas/deps/enteredGroup.ts"],"sourcesContent":["/**\n * The entered group: the group a press resolves inside of. Session state beside\n * the selection, not scene data, so it is never saved and never undone.\n *\n * It remembers the entered group and the groups above it. `get()` answers the\n * innermost of those still in the scene, so deleting the group lands on its\n * parent, and an undo that brings it back lands on it again.\n */\nimport { useEffect, useMemo } from 'react';\nimport { useDepRegistry, useDepSource } from '@weasel-js/routing/react';\nimport type { NodeId, Scene } from 'core/scene/types';\n\n/** The group a press resolves inside of, held beside the selection; `<SceneCanvas enteredGroup>` takes one. */\nexport interface EnteredGroupApi {\n get(): NodeId | null;\n enter(id: NodeId): void;\n /** Moves to the nearest group above the current one, or to none. */\n leave(): void;\n /** Leaves every entered group at once. */\n leaveAll(): void;\n subscribe(fn: () => void): () => void;\n}\n\n/** Whether `id` is reachable while `group` is entered: inside it, and not the group itself. With\n * nothing entered every node is. Picking, region selects and select-all all ask this. */\nexport function reachableInGroup(\n scene: Pick<Scene<unknown, string, unknown>, 'ancestorsOf'>,\n group: NodeId | null,\n id: string,\n): boolean {\n return group === null || scene.ancestorsOf(id as NodeId).includes(group);\n}\n\ntype SceneReads = Pick<Scene<unknown, string, unknown>, 'get' | 'ancestorsOf' | 'subscribe'>;\n\n/** An entered-group store over `scene`, with nothing entered. */\nexport function createEnteredGroup(scene: SceneReads): EnteredGroupApi {\n let chain: NodeId[] = [];\n let announced: NodeId | null = null;\n const listeners = new Set<() => void>();\n\n const isGroup = (id: NodeId) => scene.get(id)?.kind === 'group';\n const get = (): NodeId | null => chain.find(isGroup) ?? null;\n const groupsFrom = (id: NodeId): NodeId[] => [id, ...scene.ancestorsOf(id)].filter(isGroup);\n\n const announce = () => {\n announced = get();\n for (const fn of [...listeners]) fn();\n };\n\n let stopScene: (() => void) | null = null;\n const watchScene = () => scene.subscribe(() => {\n if (get() !== announced) announce();\n });\n\n return {\n get,\n enter(id) {\n if (!isGroup(id)) return;\n chain = groupsFrom(id);\n announce();\n },\n leave() {\n const here = get();\n if (here === null) return;\n chain = groupsFrom(here).slice(1);\n announce();\n },\n leaveAll() {\n if (get() === null) return;\n chain = [];\n announce();\n },\n subscribe(fn) {\n if (listeners.size === 0) {\n announced = get();\n stopScene = watchScene();\n }\n listeners.add(fn);\n return () => {\n listeners.delete(fn);\n if (listeners.size === 0) { stopScene?.(); stopScene = null; }\n };\n },\n };\n}\n\n/** One entered-group store for the life of the scene. */\nexport function useEnteredGroup(scene: SceneReads): EnteredGroupApi {\n return useMemo(() => createEnteredGroup(scene), [scene]);\n}\n\nexport function useEnteredGroupApiSource(dep: EnteredGroupApi): void {\n useDepSource('enteredGroup', () => dep);\n // What is drawn or pasted while a group is entered lands in it, unless a source\n // registered under this one, such as a symmetry group, names a container.\n useDepSource('insertInto', (below) => ({ get: () => below()?.get() ?? dep.get() }));\n const registry = useDepRegistry();\n useEffect(() => dep.subscribe(() => registry.changed?.()), [dep, registry]);\n}\n"],"mappings":";;;;;;;;;;;;;;AAyBA,SAAgB,iBACd,OACA,OACA,IACS;CACT,OAAO,UAAU,QAAQ,MAAM,YAAY,EAAY,CAAC,CAAC,SAAS,KAAK;AACzE;;AAKA,SAAgB,mBAAmB,OAAoC;CACrE,IAAI,QAAkB,CAAC;CACvB,IAAI,YAA2B;CAC/B,MAAM,4BAAY,IAAI,IAAgB;CAEtC,MAAM,WAAW,OAAe,MAAM,IAAI,EAAE,CAAC,EAAE,SAAS;CACxD,MAAM,YAA2B,MAAM,KAAK,OAAO,KAAK;CACxD,MAAM,cAAc,OAAyB,CAAC,IAAI,GAAG,MAAM,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,OAAO;CAE1F,MAAM,iBAAiB;EACrB,YAAY,IAAI;EAChB,KAAK,MAAM,MAAM,CAAC,GAAG,SAAS,GAAG,GAAG;CACtC;CAEA,IAAI,YAAiC;CACrC,MAAM,mBAAmB,MAAM,gBAAgB;EAC7C,IAAI,IAAI,MAAM,WAAW,SAAS;CACpC,CAAC;CAED,OAAO;EACL;EACA,MAAM,IAAI;GACR,IAAI,CAAC,QAAQ,EAAE,GAAG;GAClB,QAAQ,WAAW,EAAE;GACrB,SAAS;EACX;EACA,QAAQ;GACN,MAAM,OAAO,IAAI;GACjB,IAAI,SAAS,MAAM;GACnB,QAAQ,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC;GAChC,SAAS;EACX;EACA,WAAW;GACT,IAAI,IAAI,MAAM,MAAM;GACpB,QAAQ,CAAC;GACT,SAAS;EACX;EACA,UAAU,IAAI;GACZ,IAAI,UAAU,SAAS,GAAG;IACxB,YAAY,IAAI;IAChB,YAAY,WAAW;GACzB;GACA,UAAU,IAAI,EAAE;GAChB,aAAa;IACX,UAAU,OAAO,EAAE;IACnB,IAAI,UAAU,SAAS,GAAG;KAAE,YAAY;KAAG,YAAY;IAAM;GAC/D;EACF;CACF;AACF;;AAGA,SAAgB,gBAAgB,OAAoC;CAClE,OAAO,cAAc,mBAAmB,KAAK,GAAG,CAAC,KAAK,CAAC;AACzD;AAEA,SAAgB,yBAAyB,KAA4B;CACnE,aAAa,sBAAsB,GAAG;CAGtC,aAAa,eAAe,WAAW,EAAE,WAAW,MAAM,CAAC,EAAE,IAAI,KAAK,IAAI,IAAI,EAAE,EAAE;CAClF,MAAM,WAAW,eAAe;CAChC,gBAAgB,IAAI,gBAAgB,SAAS,UAAU,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC;AAC5E"}