@weasel-js/core 1.9.3 → 1.9.4

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 (542) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +2 -2
  3. package/dist/WeaselProvider.js +5 -5
  4. package/dist/affordances/composeAffordanceLayer.js +10 -20
  5. package/dist/affordances/composeAffordanceLayer.js.map +1 -1
  6. package/dist/affordances/cornerResize.js +25 -17
  7. package/dist/affordances/cornerResize.js.map +1 -1
  8. package/dist/affordances/hitAffordanceRegions.js +27 -20
  9. package/dist/affordances/hitAffordanceRegions.js.map +1 -1
  10. package/dist/affordances/rotationHandle.js +18 -18
  11. package/dist/affordances/rotationHandle.js.map +1 -1
  12. package/dist/animation/behaviors/momentum.js +93 -55
  13. package/dist/animation/behaviors/momentum.js.map +1 -1
  14. package/dist/animation/loop.js +10 -12
  15. package/dist/animation/loop.js.map +1 -1
  16. package/dist/animation/reflow.js +8 -8
  17. package/dist/animation/reflow.js.map +1 -1
  18. package/dist/animation/stagger.js +64 -56
  19. package/dist/animation/stagger.js.map +1 -1
  20. package/dist/animation/timeline/booking.js +62 -54
  21. package/dist/animation/timeline/booking.js.map +1 -1
  22. package/dist/animation/timeline/createTimeline.js +85 -76
  23. package/dist/animation/timeline/createTimeline.js.map +1 -1
  24. package/dist/animation/timeline/sampleTrack.js +13 -5
  25. package/dist/animation/timeline/sampleTrack.js.map +1 -1
  26. package/dist/animation/useAnimator.js +45 -35
  27. package/dist/animation/useAnimator.js.map +1 -1
  28. package/dist/animation/wrappers/animateLifecycle.js +31 -24
  29. package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
  30. package/dist/animation/wrappers/animateOnSetPose.js +28 -26
  31. package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
  32. package/dist/animation/wrappers/instantPose.js +13 -0
  33. package/dist/animation/wrappers/instantPose.js.map +1 -0
  34. package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
  35. package/dist/barrel/chrome.js +16 -0
  36. package/dist/barrel/input.js +19 -0
  37. package/dist/barrel/interactions.js +42 -0
  38. package/dist/barrel/paint.js +18 -0
  39. package/dist/barrel/paths.js +19 -0
  40. package/dist/barrel/renderer.js +20 -0
  41. package/dist/barrel/routing.js +51 -0
  42. package/dist/barrel/text.js +18 -0
  43. package/dist/barrel/viewport.js +52 -0
  44. package/dist/builtins-DPcdghtB.d.ts +1223 -0
  45. package/dist/canvas/Canvas.js +363 -448
  46. package/dist/canvas/Canvas.js.map +1 -1
  47. package/dist/canvas/CanvasView.js +164 -165
  48. package/dist/canvas/CanvasView.js.map +1 -1
  49. package/dist/canvas/CursorCoordsHud.js +37 -32
  50. package/dist/canvas/CursorCoordsHud.js.map +1 -1
  51. package/dist/canvas/DrawCanvas.js +49 -43
  52. package/dist/canvas/DrawCanvas.js.map +1 -1
  53. package/dist/canvas/MinimapCanvas.js +26 -25
  54. package/dist/canvas/MinimapCanvas.js.map +1 -1
  55. package/dist/canvas/NodeShape.js +59 -377
  56. package/dist/canvas/NodeShape.js.map +1 -1
  57. package/dist/canvas/PickHud.js +50 -47
  58. package/dist/canvas/PickHud.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
  60. package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
  61. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
  62. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
  63. package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
  64. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
  65. package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
  66. package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
  67. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
  68. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +112 -70
  69. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  70. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
  71. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  72. package/dist/canvas/SceneCanvas/features.js +1 -0
  73. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  74. package/dist/canvas/SceneCanvas/isDev.js +12 -0
  75. package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
  76. package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
  77. package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
  78. package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
  79. package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
  80. package/dist/canvas/SceneCanvas/props.js +38 -0
  81. package/dist/canvas/SceneCanvas/props.js.map +1 -0
  82. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
  83. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  84. package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
  85. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
  86. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  87. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
  88. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  89. package/dist/canvas/SceneCanvas.js +534 -788
  90. package/dist/canvas/SceneCanvas.js.map +1 -1
  91. package/dist/canvas/SceneViewCanvas.js +45 -43
  92. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  93. package/dist/canvas/buildSceneLayer.js +91 -0
  94. package/dist/canvas/buildSceneLayer.js.map +1 -0
  95. package/dist/canvas/buildSceneTree.js +16 -19
  96. package/dist/canvas/buildSceneTree.js.map +1 -1
  97. package/dist/canvas/canvasRenderer.js +68 -52
  98. package/dist/canvas/canvasRenderer.js.map +1 -1
  99. package/dist/canvas/deps/changes.js +13 -0
  100. package/dist/canvas/deps/changes.js.map +1 -0
  101. package/dist/canvas/deps/editAnchors.js +28 -28
  102. package/dist/canvas/deps/insert.js +119 -91
  103. package/dist/canvas/deps/insert.js.map +1 -1
  104. package/dist/canvas/deps/slice.js +46 -45
  105. package/dist/canvas/deps/slice.js.map +1 -1
  106. package/dist/canvas/deps/textEdit.js +10 -9
  107. package/dist/canvas/deps/textEdit.js.map +1 -1
  108. package/dist/canvas/derivedPath.js +46 -23
  109. package/dist/canvas/derivedPath.js.map +1 -1
  110. package/dist/canvas/getNodeAtPoint.js +7 -7
  111. package/dist/canvas/getNodeAtPoint.js.map +1 -1
  112. package/dist/canvas/hud.module.js +1 -1
  113. package/dist/canvas/hud.module.js.map +1 -1
  114. package/dist/canvas/kitRequirements.js +2 -2
  115. package/dist/canvas/kitRequirements.js.map +1 -1
  116. package/dist/canvas/layerOrder.js +13 -6
  117. package/dist/canvas/layerOrder.js.map +1 -1
  118. package/dist/canvas/minimapMath.js +12 -10
  119. package/dist/canvas/minimapMath.js.map +1 -1
  120. package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
  121. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
  122. package/dist/canvas/nodeShapes/imagePainter.js +87 -0
  123. package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
  124. package/dist/canvas/nodeShapes/memo.js +9 -0
  125. package/dist/canvas/nodeShapes/memo.js.map +1 -0
  126. package/dist/canvas/nodeShapes/nodePaint.js +57 -0
  127. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
  128. package/dist/canvas/nodeShapes/pathPainter.js +38 -0
  129. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
  130. package/dist/canvas/nodeShapes/poseBox.js +20 -0
  131. package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
  132. package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
  133. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
  134. package/dist/canvas/nodeShapes/shapePainter.js +72 -0
  135. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
  136. package/dist/canvas/nodeShapes/textPainter.js +55 -0
  137. package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
  138. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  139. package/dist/canvas/paintCull.js +10 -11
  140. package/dist/canvas/paintCull.js.map +1 -1
  141. package/dist/canvas/pickWalk.js +52 -63
  142. package/dist/canvas/pickWalk.js.map +1 -1
  143. package/dist/canvas/poseRotation.js +11 -23
  144. package/dist/canvas/poseRotation.js.map +1 -1
  145. package/dist/canvas/renderDebugSnapshot.js +5 -5
  146. package/dist/canvas/renderSceneToPixels.js +3 -1
  147. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  148. package/dist/canvas/sceneAdapter.js +66 -148
  149. package/dist/canvas/sceneAdapter.js.map +1 -1
  150. package/dist/canvas/sceneClipboard.js +112 -0
  151. package/dist/canvas/sceneClipboard.js.map +1 -0
  152. package/dist/canvas/sceneSlotWiring.js +4 -4
  153. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  154. package/dist/canvas/sceneViewRender.js +39 -40
  155. package/dist/canvas/sceneViewRender.js.map +1 -1
  156. package/dist/canvas/useCanvasRenderer.js +14 -6
  157. package/dist/canvas/useCanvasRenderer.js.map +1 -1
  158. package/dist/canvas/useCanvasView.js +58 -0
  159. package/dist/canvas/useCanvasView.js.map +1 -0
  160. package/dist/canvas/useFrameLoop.js +35 -26
  161. package/dist/canvas/useFrameLoop.js.map +1 -1
  162. package/dist/canvas/useSceneLayerList.js +21 -0
  163. package/dist/canvas/useSceneLayerList.js.map +1 -0
  164. package/dist/clipboard.d.ts +10 -6
  165. package/dist/clone.d.ts +4 -3
  166. package/dist/core/adapters/arrayAdapter.js +48 -39
  167. package/dist/core/adapters/arrayAdapter.js.map +1 -1
  168. package/dist/core/device/index.js +5 -4
  169. package/dist/core/device/useDeviceProfile.js +38 -44
  170. package/dist/core/device/useDeviceProfile.js.map +1 -1
  171. package/dist/core/device/watchDevicePixelRatio.js +17 -0
  172. package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
  173. package/dist/core/fillInPoseFrame.js.map +1 -1
  174. package/dist/core/geometry/poseDescriptor.js +21 -19
  175. package/dist/core/geometry/poseDescriptor.js.map +1 -1
  176. package/dist/core/geometry/poseFrame.js +44 -0
  177. package/dist/core/geometry/poseFrame.js.map +1 -0
  178. package/dist/core/geometry/poseRotation.js.map +1 -1
  179. package/dist/core/geometry/regionHit.js +9 -10
  180. package/dist/core/geometry/regionHit.js.map +1 -1
  181. package/dist/core/geometry/unionBounds.js +31 -29
  182. package/dist/core/geometry/unionBounds.js.map +1 -1
  183. package/dist/core/isPlainObject.js +2 -11
  184. package/dist/core/ops/arrange.js +1 -0
  185. package/dist/core/ops/arrange.js.map +1 -1
  186. package/dist/core/ops/create.js +10 -5
  187. package/dist/core/ops/create.js.map +1 -1
  188. package/dist/core/ops/delete.js +12 -24
  189. package/dist/core/ops/delete.js.map +1 -1
  190. package/dist/core/ops/reorder/index.js +43 -40
  191. package/dist/core/ops/reorder/index.js.map +1 -1
  192. package/dist/core/ops/reorder/setChildOrder.js +34 -0
  193. package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
  194. package/dist/core/paintKinds.js +5 -5
  195. package/dist/core/paintKinds.js.map +1 -1
  196. package/dist/core/scene/NodeProperties.js.map +1 -1
  197. package/dist/core/scene/appliedLog.js +37 -0
  198. package/dist/core/scene/appliedLog.js.map +1 -0
  199. package/dist/core/scene/arrivalWindow.js +101 -0
  200. package/dist/core/scene/arrivalWindow.js.map +1 -0
  201. package/dist/core/scene/derivedPath.js.map +1 -1
  202. package/dist/core/scene/historyFacade.js +84 -0
  203. package/dist/core/scene/historyFacade.js.map +1 -0
  204. package/dist/core/scene/kitOps.js +173 -0
  205. package/dist/core/scene/kitOps.js.map +1 -0
  206. package/dist/core/scene/layerStack.js +38 -0
  207. package/dist/core/scene/layerStack.js.map +1 -0
  208. package/dist/core/scene/lockGuard.js +37 -0
  209. package/dist/core/scene/lockGuard.js.map +1 -0
  210. package/dist/core/scene/nodeFnFields.js +5 -6
  211. package/dist/core/scene/nodeFnFields.js.map +1 -1
  212. package/dist/core/scene/nodeMemo.js +45 -19
  213. package/dist/core/scene/nodeMemo.js.map +1 -1
  214. package/dist/core/scene/nodeStore.js +126 -0
  215. package/dist/core/scene/nodeStore.js.map +1 -0
  216. package/dist/core/scene/pickability.js +6 -0
  217. package/dist/core/scene/pickability.js.map +1 -0
  218. package/dist/core/scene/poseFeed.js +38 -38
  219. package/dist/core/scene/poseFeed.js.map +1 -1
  220. package/dist/core/scene/poseOverrides.js +34 -38
  221. package/dist/core/scene/poseOverrides.js.map +1 -1
  222. package/dist/core/scene/renderOrder.js +84 -0
  223. package/dist/core/scene/renderOrder.js.map +1 -0
  224. package/dist/core/scene/scene.js +289 -866
  225. package/dist/core/scene/scene.js.map +1 -1
  226. package/dist/core/scene/serialization.js +68 -0
  227. package/dist/core/scene/serialization.js.map +1 -0
  228. package/dist/core/scene/treeOrder.js +13 -0
  229. package/dist/core/scene/treeOrder.js.map +1 -0
  230. package/dist/core/scene/types.js.map +1 -1
  231. package/dist/core/viewport/meanScale.js +1 -1
  232. package/dist/core/viewport/meanScale.js.map +1 -1
  233. package/dist/core/viewport/view.js +12 -12
  234. package/dist/core/viewport/view.js.map +1 -1
  235. package/dist/core/viewport/zoomBounds.js +10 -8
  236. package/dist/core/viewport/zoomBounds.js.map +1 -1
  237. package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
  238. package/dist/debug/createDebugOverlayLayer.js +14 -14
  239. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  240. package/dist/debug/flag.js +2 -41
  241. package/dist/features/chrome-caps/useHoverTracking.js +22 -17
  242. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  243. package/dist/features/focus/gateLayer.js +3 -4
  244. package/dist/features/focus/gateLayer.js.map +1 -1
  245. package/dist/features/images/imageCache.js +19 -9
  246. package/dist/features/images/imageCache.js.map +1 -1
  247. package/dist/features/ingestion/contentHandlers.js +14 -11
  248. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  249. package/dist/features/ingestion/index.js +1 -1
  250. package/dist/features/ingestion/registerKitHandlers.js +7 -7
  251. package/dist/features/ingestion/weaselJsonHandler.js +17 -14
  252. package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
  253. package/dist/features/meshPaint/lazyKind.js +201 -35
  254. package/dist/features/meshPaint/lazyKind.js.map +1 -1
  255. package/dist/features/meshPaint/meshPaint.js +82 -79
  256. package/dist/features/meshPaint/meshPaint.js.map +1 -1
  257. package/dist/features/minimap/createMinimapContribution.js +42 -42
  258. package/dist/features/minimap/layers.js +44 -62
  259. package/dist/features/minimap/layers.js.map +1 -1
  260. package/dist/features/parallax/createParallaxPlane.js +10 -14
  261. package/dist/features/parallax/createParallaxPlane.js.map +1 -1
  262. package/dist/features/paths/anchorEdits.js +19 -8
  263. package/dist/features/paths/anchorEdits.js.map +1 -1
  264. package/dist/features/paths/anchors.js +31 -26
  265. package/dist/features/paths/anchors.js.map +1 -1
  266. package/dist/features/paths/booleans.js +2 -2
  267. package/dist/features/paths/index.js +13 -11
  268. package/dist/features/paths/markerCommands.js +37 -36
  269. package/dist/features/paths/markerCommands.js.map +1 -1
  270. package/dist/features/paths/pathInWorld.js +19 -19
  271. package/dist/features/paths/pathInWorld.js.map +1 -1
  272. package/dist/features/paths/pathLayer.js.map +1 -1
  273. package/dist/features/paths/strokeToPath.js +40 -0
  274. package/dist/features/paths/strokeToPath.js.map +1 -0
  275. package/dist/features/paths/tessellate/stroke.js.map +1 -1
  276. package/dist/features/patterns/commandPaint.js +18 -0
  277. package/dist/features/patterns/commandPaint.js.map +1 -0
  278. package/dist/features/patterns/index.js +5 -17
  279. package/dist/features/patterns/index.js.map +1 -1
  280. package/dist/features/patterns/rasterizeTile.js +17 -0
  281. package/dist/features/patterns/rasterizeTile.js.map +1 -0
  282. package/dist/features/patterns/resolveSpec.js +52 -44
  283. package/dist/features/patterns/resolveSpec.js.map +1 -1
  284. package/dist/features/poseRun/usePoseRun.js +8 -3
  285. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  286. package/dist/features/selection/SelectionContext.js +5 -0
  287. package/dist/features/selection/SelectionContext.js.map +1 -1
  288. package/dist/features/selection/overlay.js +18 -18
  289. package/dist/features/selection/overlay.js.map +1 -1
  290. package/dist/features/text/domOffsets.js +82 -0
  291. package/dist/features/text/domOffsets.js.map +1 -0
  292. package/dist/features/text/domRuns.js +103 -131
  293. package/dist/features/text/domRuns.js.map +1 -1
  294. package/dist/features/text/hitTest.js +5 -2
  295. package/dist/features/text/hitTest.js.map +1 -1
  296. package/dist/features/text/isTextData.js +8 -0
  297. package/dist/features/text/isTextData.js.map +1 -0
  298. package/dist/features/text/overlayDirection.js +15 -0
  299. package/dist/features/text/overlayDirection.js.map +1 -0
  300. package/dist/features/text/runs/rangeStyle.js +32 -34
  301. package/dist/features/text/runs/rangeStyle.js.map +1 -1
  302. package/dist/features/text/textCommand.js +34 -33
  303. package/dist/features/text/textCommand.js.map +1 -1
  304. package/dist/features/text/textToPath.js +40 -40
  305. package/dist/features/text/textToPath.js.map +1 -1
  306. package/dist/features/text/useSceneTextEdit.js +76 -82
  307. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  308. package/dist/features/text/useTextEdit.js +184 -160
  309. package/dist/features/text/useTextEdit.js.map +1 -1
  310. package/dist/features/tiling/createTiledLayer.js +24 -22
  311. package/dist/features/tiling/createTiledLayer.js.map +1 -1
  312. package/dist/index.d.ts +11033 -7749
  313. package/dist/index.js +316 -309
  314. package/dist/input/keyState.js +42 -43
  315. package/dist/input/keyState.js.map +1 -1
  316. package/dist/input/useKeyModifiers.js +11 -0
  317. package/dist/input/useKeyModifiers.js.map +1 -0
  318. package/dist/interactions/actions/align/align.js +8 -8
  319. package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
  320. package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
  321. package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
  322. package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
  323. package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
  324. package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
  325. package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
  326. package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
  327. package/dist/interactions/actions/defaults/align.js +9 -10
  328. package/dist/interactions/actions/defaults/align.js.map +1 -1
  329. package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
  330. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  331. package/dist/interactions/actions/defaults/areaSelect.js +35 -38
  332. package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
  333. package/dist/interactions/actions/defaults/clipboard.js +26 -10
  334. package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
  335. package/dist/interactions/actions/defaults/clone.js +44 -60
  336. package/dist/interactions/actions/defaults/clone.js.map +1 -1
  337. package/dist/interactions/actions/defaults/copySubtree.js +40 -0
  338. package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
  339. package/dist/interactions/actions/defaults/distribute.js +14 -13
  340. package/dist/interactions/actions/defaults/distribute.js.map +1 -1
  341. package/dist/interactions/actions/defaults/duplicate.js +17 -57
  342. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  343. package/dist/interactions/actions/defaults/editAnchors.js +18 -18
  344. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  345. package/dist/interactions/actions/defaults/flip.js +52 -46
  346. package/dist/interactions/actions/defaults/flip.js.map +1 -1
  347. package/dist/interactions/actions/defaults/group.js +43 -48
  348. package/dist/interactions/actions/defaults/group.js.map +1 -1
  349. package/dist/interactions/actions/defaults/ingest.js +35 -33
  350. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  351. package/dist/interactions/actions/defaults/insert.js +50 -51
  352. package/dist/interactions/actions/defaults/insert.js.map +1 -1
  353. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
  354. package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
  355. package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
  356. package/dist/interactions/actions/defaults/move.js +267 -249
  357. package/dist/interactions/actions/defaults/move.js.map +1 -1
  358. package/dist/interactions/actions/defaults/nudge.js +40 -68
  359. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  360. package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
  361. package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
  362. package/dist/interactions/actions/defaults/reorder.js +2 -0
  363. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  364. package/dist/interactions/actions/defaults/resize.js +104 -98
  365. package/dist/interactions/actions/defaults/resize.js.map +1 -1
  366. package/dist/interactions/actions/defaults/rotate.js +111 -110
  367. package/dist/interactions/actions/defaults/rotate.js.map +1 -1
  368. package/dist/interactions/actions/defaults/setFill.js +7 -7
  369. package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
  370. package/dist/interactions/actions/defaults/setStroke.js +12 -12
  371. package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
  372. package/dist/interactions/actions/defaults/sliceOps.js +19 -19
  373. package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
  374. package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
  375. package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
  376. package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
  377. package/dist/interactions/actions/flip/helpers.js +30 -17
  378. package/dist/interactions/actions/flip/helpers.js.map +1 -1
  379. package/dist/interactions/actions/hitSelection.js +8 -0
  380. package/dist/interactions/actions/hitSelection.js.map +1 -0
  381. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
  382. package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
  383. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
  384. package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
  385. package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
  386. package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
  387. package/dist/interactions/actions/move/gestureAdapter.js +32 -15
  388. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  389. package/dist/interactions/actions/move/landedDescendants.js +33 -0
  390. package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
  391. package/dist/interactions/actions/outlines/createOutlines.js +18 -18
  392. package/dist/interactions/actions/poseDescriptorDep.js +16 -4
  393. package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
  394. package/dist/interactions/actions/poseFrame.js +11 -6
  395. package/dist/interactions/actions/poseFrame.js.map +1 -1
  396. package/dist/interactions/actions/previewOverrides.js +5 -5
  397. package/dist/interactions/actions/previewOverrides.js.map +1 -1
  398. package/dist/interactions/actions/selectionSubtree.js +69 -0
  399. package/dist/interactions/actions/selectionSubtree.js.map +1 -0
  400. package/dist/interactions/actions/useStandardActions.js +66 -62
  401. package/dist/interactions/actions/useStandardActions.js.map +1 -1
  402. package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
  403. package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
  404. package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
  405. package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
  406. package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
  407. package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
  408. package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
  409. package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
  410. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
  411. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  412. package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
  413. package/dist/math.d.ts +2 -3
  414. package/dist/move.d.ts +12 -11
  415. package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
  416. package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
  417. package/dist/renderer/DrawCommand.js.map +1 -1
  418. package/dist/renderer/WeaselRenderer.js +75 -73
  419. package/dist/renderer/WeaselRenderer.js.map +1 -1
  420. package/dist/renderer/cache/GLImageCache.js +15 -5
  421. package/dist/renderer/cache/GLImageCache.js.map +1 -1
  422. package/dist/renderer/cache/GLMeshCache.js +23 -23
  423. package/dist/renderer/cache/GLMeshCache.js.map +1 -1
  424. package/dist/renderer/cache/GLTextureCache.js +18 -1
  425. package/dist/renderer/cache/GLTextureCache.js.map +1 -1
  426. package/dist/renderer/cache/cache.js.map +1 -1
  427. package/dist/renderer/cache/markerCommandCache.js.map +1 -1
  428. package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
  429. package/dist/renderer/cullDrawCommands.js +11 -11
  430. package/dist/renderer/draw/batch.js +133 -0
  431. package/dist/renderer/draw/batch.js.map +1 -0
  432. package/dist/renderer/draw/clip.js +89 -0
  433. package/dist/renderer/draw/clip.js.map +1 -0
  434. package/dist/renderer/draw/customShader.js +230 -0
  435. package/dist/renderer/draw/customShader.js.map +1 -0
  436. package/dist/renderer/draw/drawTriangles.js +8 -0
  437. package/dist/renderer/draw/drawTriangles.js.map +1 -0
  438. package/dist/renderer/draw/effects.js +28 -0
  439. package/dist/renderer/draw/effects.js.map +1 -0
  440. package/dist/renderer/draw/flushBatch.js +35 -0
  441. package/dist/renderer/draw/flushBatch.js.map +1 -0
  442. package/dist/renderer/draw/gradient.js +90 -0
  443. package/dist/renderer/draw/gradient.js.map +1 -0
  444. package/dist/renderer/draw/image.js +38 -0
  445. package/dist/renderer/draw/image.js.map +1 -0
  446. package/dist/renderer/draw/mesh.js +16 -0
  447. package/dist/renderer/draw/mesh.js.map +1 -0
  448. package/dist/renderer/draw/paintBind.js +70 -0
  449. package/dist/renderer/draw/paintBind.js.map +1 -0
  450. package/dist/renderer/draw/pathPaint.js +142 -0
  451. package/dist/renderer/draw/pathPaint.js.map +1 -0
  452. package/dist/renderer/draw/scratchPass.js +83 -0
  453. package/dist/renderer/draw/scratchPass.js.map +1 -0
  454. package/dist/renderer/draw/stencil.js +28 -0
  455. package/dist/renderer/draw/stencil.js.map +1 -0
  456. package/dist/renderer/draw/text.js +167 -0
  457. package/dist/renderer/draw/text.js.map +1 -0
  458. package/dist/renderer/draw/uniforms.js +77 -0
  459. package/dist/renderer/draw/uniforms.js.map +1 -0
  460. package/dist/renderer/draw.js +60 -1116
  461. package/dist/renderer/draw.js.map +1 -1
  462. package/dist/renderer/effects/EffectTargets.js +3 -0
  463. package/dist/renderer/effects/EffectTargets.js.map +1 -1
  464. package/dist/renderer/effects/composite.js +7 -5
  465. package/dist/renderer/effects/composite.js.map +1 -1
  466. package/dist/renderer/index.js +18 -17
  467. package/dist/renderer/math/color.js +63 -83
  468. package/dist/renderer/math/color.js.map +1 -1
  469. package/dist/renderer/math/mat3.js +15 -11
  470. package/dist/renderer/math/mat3.js.map +1 -1
  471. package/dist/renderer/math/viewToMat3.js +1 -1
  472. package/dist/renderer/math/viewToMat3.js.map +1 -1
  473. package/dist/renderer/shaders/ShaderProgram.js +13 -13
  474. package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
  475. package/dist/renderer/shaders/glyphPaint.js +3 -3
  476. package/dist/renderer/shaders/glyphPaint.js.map +1 -1
  477. package/dist/renderer/shaders/gradFill.js +2 -1
  478. package/dist/renderer/shaders/gradFill.js.map +1 -1
  479. package/dist/renderer/shaders/pathFill.js +2 -1
  480. package/dist/renderer/shaders/pathFill.js.map +1 -1
  481. package/dist/renderer/shaders/patternFill.js +2 -1
  482. package/dist/renderer/shaders/patternFill.js.map +1 -1
  483. package/dist/renderer/shaders/registerProgram.js.map +1 -1
  484. package/dist/renderer/strokeLengths.js.map +1 -1
  485. package/dist/renderer/textures/registerTexture.js +25 -5
  486. package/dist/renderer/textures/registerTexture.js.map +1 -1
  487. package/dist/renderer.d.ts +16 -15
  488. package/dist/renderer.js +17 -16
  489. package/dist/resize.d.ts +5 -6
  490. package/dist/resize.js +5 -5
  491. package/dist/style.css +1 -1
  492. package/dist/test-hook/install.js +12 -6
  493. package/dist/test-hook/install.js.map +1 -1
  494. package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
  495. package/dist/test-seams.d.ts +2 -2
  496. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  497. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  498. package/dist/tools/builtin/hand/useHandTool.js +1 -0
  499. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  500. package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
  501. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  502. package/dist/tools/builtin/line/useLineTool.js +1 -0
  503. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  504. package/dist/tools/builtin/pen/usePenTool.js +153 -103
  505. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  506. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  507. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  508. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
  509. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  510. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  511. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  512. package/dist/tools/builtin/select/selectionContributions.js +24 -23
  513. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  514. package/dist/tools/builtin/select/useSelectTool.js +51 -48
  515. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  516. package/dist/tools/builtin/shared/pointerFollower.js +17 -0
  517. package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
  518. package/dist/tools/builtin/slice/useSliceTool.js +49 -54
  519. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  520. package/dist/tools/builtin/star/useStarTool.js +22 -16
  521. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  522. package/dist/tools/builtin/text/useTextTool.js +1 -0
  523. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  524. package/dist/tools/useKeybindings.js +14 -30
  525. package/dist/tools/useKeybindings.js.map +1 -1
  526. package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
  527. package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
  528. package/dist/util/color.js +3 -3
  529. package/dist/util/color.js.map +1 -1
  530. package/dist/util/paint.js.map +1 -1
  531. package/dist/version.js +1 -1
  532. package/package.json +20 -18
  533. package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
  534. package/dist/builtins-C-VgUlWI.d.ts +0 -4514
  535. package/dist/core/isPlainObject.js.map +0 -1
  536. package/dist/debug/flag.js.map +0 -1
  537. package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
  538. package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
  539. package/dist/renderer/shaders/imageFill.js +0 -14
  540. package/dist/renderer/shaders/imageFill.js.map +0 -1
  541. package/dist/tools/prefs.js +0 -62
  542. package/dist/tools/prefs.js.map +0 -1
@@ -1,56 +1,65 @@
1
1
  import { RECT_POSE_DESCRIPTOR as e, poseDescriptorForNode as t, translatePoseViaDescriptor as n } from "../geometry/poseDescriptor.js";
2
- import { applyOpsTo as r } from "../applyOps.js";
3
- import { polygonRegion as i, poseOutline as a, rectRegion as o, regionBoundsOf as s, regionTakes as c } from "../geometry/regionHit.js";
2
+ import { parentOf as r } from "../ops/slot.js";
3
+ import { applyOpsTo as i } from "../applyOps.js";
4
+ import { polygonRegion as a, poseOutline as o, rectRegion as s, regionBoundsOf as c, regionTakes as l } from "../geometry/regionHit.js";
4
5
  //#region src/core/adapters/arrayAdapter.ts
5
- function l(e, t) {
6
+ function u(e, t) {
6
7
  return {
7
8
  ...e,
8
9
  ...t
9
10
  };
10
11
  }
11
- var u = 0;
12
- function d() {
12
+ var d = 0;
13
+ function f() {
13
14
  let e = globalThis.crypto;
14
- return e && typeof e.randomUUID == "function" ? e.randomUUID() : `paste-${u++}`;
15
+ return e && typeof e.randomUUID == "function" ? e.randomUUID() : `paste-${d++}`;
15
16
  }
16
- function f(u) {
17
- let { ref: f, setItems: p, toPose: m, fromPose: h = l, getParent: g = () => null, setParent: _ = () => {}, getChildren: v, selectionRef: y, setSelection: b = () => {}, createDefault: x, nextId: S = d, silhouette: C } = u, w = u.poseDescriptor ?? e, T = y ? () => y.current : () => [], E = (e, n) => {
17
+ function p(d) {
18
+ let { ref: p, setItems: m, toPose: h, fromPose: g = u, getParent: _ = () => null, setParent: v = () => {}, getChildren: y, selectionRef: b, setSelection: x = () => {}, createDefault: S, nextId: C = f, silhouette: w } = d, T = d.poseDescriptor ?? e, E = b ? () => b.current : () => [], D = (e, n) => {
18
19
  let r = [];
19
- for (let i of f.current) {
20
- let o = m(i), l = s(i, o, t(w, i));
21
- c(e, n, o, l, () => C?.(i, o) ?? a(o, l)) && r.push(i.id);
20
+ for (let i of p.current) {
21
+ let a = h(i), s = c(i, a, t(T, i));
22
+ l(e, n, a, s, () => w?.(i, a) ?? o(a, s)) && r.push(i.id);
22
23
  }
23
24
  return r;
24
- }, D = {
25
- getNode: (e) => f.current.find((t) => t.id === e),
26
- getNodes: () => f.current,
25
+ }, O = (e, t) => t === null ? e.filter((e) => _(e.id) === null).map((e) => e.id) : y?.(t) ?? [], k = (e, t, n) => {
26
+ if (n === void 0 || n < 0) return;
27
+ if (!y) return n < e.length ? n : void 0;
28
+ let i = O(e, r(t)).filter((e) => e !== t.id)[n], a = i === void 0 ? -1 : e.findIndex((e) => e.id === i);
29
+ return a < 0 ? void 0 : a;
30
+ }, A = {
31
+ getNode: (e) => p.current.find((t) => t.id === e),
32
+ getNodes: () => p.current,
27
33
  getPose: (e) => {
28
- let t = f.current.find((t) => t.id === e);
34
+ let t = p.current.find((t) => t.id === e);
29
35
  if (!t) throw Error(`arrayAdapter.getPose: id "${e}" not found`);
30
- return m(t);
36
+ return h(t);
31
37
  },
32
38
  setPose: (e, t) => {
33
- p((n) => n.map((n) => n.id === e ? h(n, t) : n));
39
+ m((n) => n.map((n) => n.id === e ? g(n, t) : n));
34
40
  },
35
- getParent: g,
36
- setParent: _,
37
- insertNode: (e, t) => p((n) => t === void 0 || t < 0 || t >= n.length ? [...n, e] : [
38
- ...n.slice(0, t),
39
- e,
40
- ...n.slice(t)
41
- ]),
42
- removeNode: (e) => p((t) => t.filter((t) => t.id !== e)),
43
- getSelection: T,
44
- setSelection: b,
45
- hitTestArea: (e) => E(o(e), "intersect"),
41
+ getParent: _,
42
+ setParent: v,
43
+ insertNode: (e, t) => m((n) => {
44
+ let r = k(n, e, t);
45
+ return r === void 0 ? [...n, e] : [
46
+ ...n.slice(0, r),
47
+ e,
48
+ ...n.slice(r)
49
+ ];
50
+ }),
51
+ removeNode: (e) => m((t) => t.filter((t) => t.id !== e)),
52
+ getSelection: E,
53
+ setSelection: x,
54
+ hitTestArea: (e) => D(s(e), "intersect"),
46
55
  hitTestLasso: (e, t) => {
47
- let n = i(e);
48
- return n ? E(n, t) : [];
56
+ let n = a(e);
57
+ return n ? D(n, t) : [];
49
58
  },
50
59
  applyOps(e) {
51
- r(this, e);
60
+ i(this, e);
52
61
  },
53
- commitInsert: x ? (e) => x(e) : () => null,
62
+ commitInsert: S ? (e) => S(e) : () => null,
54
63
  commitPaste: (e, r, i) => {
55
64
  let a = e.items;
56
65
  if (a.length === 0) return [];
@@ -58,7 +67,7 @@ function f(u) {
58
67
  if (i?.dropPoint) {
59
68
  let e = Infinity, n = Infinity, r = -Infinity, c = -Infinity;
60
69
  for (let i of a) {
61
- let a = t(w, i).getBounds(m(i));
70
+ let a = t(T, i).getBounds(h(i));
62
71
  a.x < e && (e = a.x), a.y < n && (n = a.y), a.x + a.width > r && (r = a.x + a.width), a.y + a.height > c && (c = a.y + a.height);
63
72
  }
64
73
  if (Number.isFinite(e) && Number.isFinite(n)) {
@@ -69,17 +78,17 @@ function f(u) {
69
78
  let c = [];
70
79
  for (let e of a) {
71
80
  let r = JSON.parse(JSON.stringify(e));
72
- r.id = S();
73
- let i = n(m(r), o, s, t(w, r));
74
- c.push(h(r, i));
81
+ r.id = C();
82
+ let i = n(h(r), o, s, t(T, r));
83
+ c.push(g(r, i));
75
84
  }
76
85
  return c;
77
86
  },
78
- snapshotSelection: (e) => ({ items: e.map((e) => f.current.find((t) => t.id === e)).filter((e) => !!e) })
87
+ snapshotSelection: (e) => ({ items: e.map((e) => p.current.find((t) => t.id === e)).filter((e) => !!e) })
79
88
  };
80
- return v && (D.getChildren = (e) => e === null ? f.current.filter((e) => g(e.id) === null).map((e) => e.id) : v(e) ?? []), D;
89
+ return y && (A.getChildren = (e) => O(p.current, e)), A;
81
90
  }
82
91
  //#endregion
83
- export { f as arrayAdapter };
92
+ export { p as arrayAdapter };
84
93
 
85
94
  //# sourceMappingURL=arrayAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"arrayAdapter.js","names":["defaultFromPose","obj","pose","monotonicPasteCounter","defaultNextId","c","arrayAdapter","config","d","RECT_POSE_DESCRIPTOR","getSelection","selectionRef","takenBy","out","o","ref","toPose","b","regionBoundsOf","poseDescriptorForNode","regionTakes","region","mode","silhouette","poseOutline","adapter","id","items","fromPose","setItems","index","rectRegion","rect","polygonRegion","polygon","ops","createDefault","src","clip","dx","offset","dy","ctx","minX","minY","maxX","maxY","n","cx","cy","cloned","nextId","translated","translatePoseViaDescriptor","ids","getChildren","parentId","getParent"],"sources":["../../../src/core/adapters/arrayAdapter.ts"],"sourcesContent":["import type { MutableRefObject } from 'react';\nimport type {\n AreaSelectAdapter,\n InsertAdapter,\n LassoHitMode,\n LassoSelectAdapter,\n MoveAdapter,\n ResizeAdapter,\n} from './types';\nimport type { Op } from '../ops/types';\nimport { applyOpsTo } from '../applyOps';\nimport type { Bounds } from '../viewport/fitViewToBounds';\nimport type { ClipboardSnapshot } from './types';\nimport {\n poseDescriptorForNode,\n translatePoseViaDescriptor,\n RECT_POSE_DESCRIPTOR,\n type PoseDescriptor,\n} from 'core/geometry/poseDescriptor';\nimport type { Path } from '@weasel-js/geom';\nimport {\n polygonRegion,\n poseOutline,\n rectRegion,\n regionBoundsOf,\n regionTakes,\n type SelectRegion,\n} from 'core/geometry/regionHit';\n\n/** Configuration for `arrayAdapter`. */\nexport interface ArrayAdapterConfig<TNode extends { id: string }, TPose> {\n /** Live ref to the current array. */\n ref: MutableRefObject<TNode[]>;\n /** Functional setter (typically the second value from `useState`). */\n setItems: (updater: (items: TNode[]) => TNode[]) => void;\n /** Project an object to its pose. Required. */\n toPose: (obj: TNode) => TPose;\n /** Merge a new pose back into an object. Default: shallow spread. */\n fromPose?: (obj: TNode, pose: TPose) => TNode;\n /** Optional parent lookup. Default returns `null`. */\n getParent?: (id: string) => string | null;\n /** Optional reparent mutator. Default is a noop. */\n setParent?: (id: string, parentId: string | null) => void;\n /** Optional children lookup for a node id. The adapter answers the root\n * itself, from `ref` and `getParent` in array order, so this is never\n * called with `null`. Default omits the method entirely. */\n getChildren?: (id: string) => string[] | undefined;\n\n /** Live ref to the current selection. Default: empty array. */\n selectionRef?: MutableRefObject<string[]>;\n /** Selection setter. Default: noop. */\n setSelection?: (ids: string[]) => void;\n\n /** Factory for `commitInsert` — invoked at the end of an insert drag.\n * Consumers own id generation and any palette/payload fields. Returning\n * `null` aborts the insert. When omitted, `commitInsert` returns `null`. */\n createDefault?: (bounds: Bounds) => TNode | null;\n\n /** How to read and rewrite item poses. Default `RECT_POSE_DESCRIPTOR`,\n * which reads the pose as its own AABB — the canonical axis-aligned rect\n * pose. `AUTO_POSE_DESCRIPTOR` lives above `core/` and so cannot be the\n * default here; pass it for a scene whose poses may be Paths. */\n poseDescriptor?: PoseDescriptor<TPose>;\n\n /** The outline a node draws, for marquee and lasso, in world coordinates.\n * Return `null` where the pose says it all. Default: none — a polygon pose\n * is its own outline, and anything else is its pose rect, rotated by\n * `rotation`. The kit's painters live above `core/`, so a scene whose\n * nodes they draw passes `findShapeSilhouette` to be tested as drawn. */\n silhouette?: (node: TNode, pose: TPose) => Path | null;\n\n /** Factory for fresh node ids in `commitPaste`. Default: `crypto.randomUUID()`\n * when available, otherwise a monotonic `paste-<n>` counter. Override\n * when consumers want stable id schemes (e.g. `clip-${n++}`). */\n nextId?: () => string;\n}\n\n/**\n * Combined adapter satisfying every narrow adapter the kit ships. Methods\n * with no default implementation (e.g. `commitInsert` without\n * `createDefault`) return null/[]/noop — sufficient to typecheck against\n * `InsertAdapter`/`AreaSelectAdapter`, and the corresponding gesture hook\n * simply produces no commit.\n */\nexport interface ArrayAdapter<TNode extends { id: string }, TPose>\n extends MoveAdapter<TNode, TPose>,\n ResizeAdapter<TNode, TPose>,\n InsertAdapter<TNode>,\n AreaSelectAdapter,\n LassoSelectAdapter {\n getNodes(): TNode[];\n removeNode(id: string): void;\n // ArrayAdapter satisfies the union of all narrow adapters; redeclare the\n // methods that are optional on AreaSelectAdapter (post-relaxation) but\n // required on InsertAdapter so TS sees a single non-conflicting signature.\n getSelection(): string[];\n setSelection(ids: string[]): void;\n // Redeclare applyOps with an optional label so it's compatible with both\n // MoveAdapter (label required) and AreaSelectAdapter (label absent = transient).\n applyOps?(ops: Op[], label?: string): void;\n}\n\nfunction defaultFromPose<TNode extends { id: string }, TPose>(\n obj: TNode,\n pose: TPose,\n): TNode {\n return { ...obj, ...(pose as object) } as TNode;\n}\n\nlet monotonicPasteCounter = 0;\nfunction defaultNextId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n return `paste-${monotonicPasteCounter++}`;\n}\n\n/**\n * Synthesize a many-faceted adapter from a `useState`-array scene. Always\n * satisfies `MoveAdapter`, `ResizeAdapter`, `InsertAdapter`, and\n * `AreaSelectAdapter` via structural typing. Override individual methods by\n * spreading the result.\n *\n * `applyOps` is intentionally omitted — hooks fall back to the built-in\n * dispatcher (see `dispatchApplyBatch`). Apps with custom history\n * integration supply their own via spread.\n */\nexport function arrayAdapter<TNode extends { id: string }, TPose>(\n config: ArrayAdapterConfig<TNode, TPose>,\n): ArrayAdapter<TNode, TPose> {\n const {\n ref,\n setItems,\n toPose,\n fromPose = defaultFromPose,\n getParent = () => null,\n setParent = () => {},\n getChildren,\n selectionRef,\n setSelection = () => {},\n createDefault,\n nextId = defaultNextId,\n silhouette,\n } = config;\n\n const d = (config.poseDescriptor ?? RECT_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const getSelection = selectionRef ? () => selectionRef.current : () => [];\n\n const takenBy = (region: SelectRegion, mode: LassoHitMode): string[] => {\n const out: string[] = [];\n for (const o of ref.current) {\n const pose = toPose(o);\n const b = regionBoundsOf(o, pose, poseDescriptorForNode(d, o) as PoseDescriptor<unknown>);\n const outline = (): Path => silhouette?.(o, pose) ?? poseOutline(pose, b);\n if (regionTakes(region, mode, pose, b, outline)) out.push(o.id);\n }\n return out;\n };\n\n const adapter: ArrayAdapter<TNode, TPose> = {\n getNode: (id) => ref.current.find((o) => o.id === id),\n getNodes: () => ref.current,\n getPose: (id) => {\n const obj = ref.current.find((o) => o.id === id);\n if (!obj) throw new Error(`arrayAdapter.getPose: id \"${id}\" not found`);\n return toPose(obj);\n },\n setPose: (id, pose) => {\n setItems((items) => items.map((o) => (o.id === id ? fromPose(o, pose) : o)));\n },\n getParent,\n setParent,\n\n insertNode: (obj, index) => setItems((items) => {\n if (index === undefined || index < 0 || index >= items.length) return [...items, obj];\n return [...items.slice(0, index), obj, ...items.slice(index)];\n }),\n removeNode: (id) => setItems((items) => items.filter((o) => o.id !== id)),\n\n getSelection,\n setSelection,\n\n hitTestArea: (rect) => takenBy(rectRegion(rect), 'intersect'),\n\n hitTestLasso: (polygon, mode: LassoHitMode) => {\n const region = polygonRegion(polygon);\n return region ? takenBy(region, mode) : [];\n },\n\n // Use a method shorthand so `this` is the call-site receiver, not the\n // adapter we're building here. That way consumers can spread additional\n // methods on top (e.g. `{ ...arrayAdapter(...), ...selection.adapterMethods }`)\n // and `applyOps` will dispatch ops against the merged object.\n applyOps(ops: Op[]) {\n applyOpsTo(this, ops);\n },\n\n commitInsert: createDefault ? (b) => createDefault(b) : () => null,\n commitPaste: (\n clip: ClipboardSnapshot,\n offset: { dx: number; dy: number },\n ctx?: { dropPoint?: { worldX: number; worldY: number } },\n ): TNode[] => {\n const src = clip.items as TNode[];\n if (src.length === 0) return [];\n\n // Default translation = `offset`. When `dropPoint` is supplied, center\n // the cluster's union AABB at the drop point — matches the\n // `ClipboardDemo` reference wiring.\n let dx = offset.dx;\n let dy = offset.dy;\n if (ctx?.dropPoint) {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const n of src) {\n const b = poseDescriptorForNode(d, n).getBounds(toPose(n));\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n if (b.x + b.width > maxX) maxX = b.x + b.width;\n if (b.y + b.height > maxY) maxY = b.y + b.height;\n }\n // Defensive: if the descriptor returned nothing usable, fall back to offset.\n if (Number.isFinite(minX) && Number.isFinite(minY)) {\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n dx = ctx.dropPoint.worldX - cx;\n dy = ctx.dropPoint.worldY - cy;\n }\n }\n\n // Deep-clone via JSON round-trip so the snapshot isn't mutated by\n // downstream pose edits.\n const out: TNode[] = [];\n for (const n of src) {\n const cloned = JSON.parse(JSON.stringify(n)) as TNode;\n (cloned as { id: string }).id = nextId();\n const translated = translatePoseViaDescriptor(\n toPose(cloned), dx, dy, poseDescriptorForNode(d, cloned),\n );\n out.push(fromPose(cloned, translated));\n }\n return out;\n },\n snapshotSelection: (ids) => ({\n items: ids\n .map((id) => ref.current.find((o) => o.id === id))\n .filter((o): o is TNode => !!o),\n }),\n };\n\n if (getChildren) {\n (adapter as ArrayAdapter<TNode, TPose> & {\n getChildren: (parentId: string | null) => string[];\n }).getChildren = (parentId) => {\n // Root order is the item array's order, which this adapter owns — asking\n // the consumer would get a callback written for node ids, and its `[]`\n // is indistinguishable from \"the root has no children\".\n if (parentId === null) {\n return ref.current.filter((o) => getParent(o.id) === null).map((o) => o.id);\n }\n return getChildren(parentId) ?? [];\n };\n }\n\n return adapter;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"arrayAdapter.js","names":["defaultFromPose","obj","pose","monotonicPasteCounter","defaultNextId","c","arrayAdapter","config","d","RECT_POSE_DESCRIPTOR","getSelection","selectionRef","takenBy","out","o","ref","toPose","b","regionBoundsOf","poseDescriptorForNode","regionTakes","region","mode","silhouette","poseOutline","childrenIn","parentId","items","getParent","getChildren","arrayIndexFor","index","before","parentOf","id","at","adapter","fromPose","setItems","rectRegion","rect","polygonRegion","polygon","ops","createDefault","src","clip","dx","offset","dy","ctx","minX","minY","maxX","maxY","n","cx","cy","cloned","nextId","translated","translatePoseViaDescriptor","ids"],"sources":["../../../src/core/adapters/arrayAdapter.ts"],"sourcesContent":["import type { MutableRefObject } from 'react';\nimport type {\n AreaSelectAdapter,\n InsertAdapter,\n LassoHitMode,\n LassoSelectAdapter,\n MoveAdapter,\n ResizeAdapter,\n} from './types';\nimport type { Op } from '../ops/types';\nimport { applyOpsTo } from '../applyOps';\nimport { parentOf } from '../ops/slot';\nimport type { Bounds } from '../viewport/fitViewToBounds';\nimport type { ClipboardSnapshot } from './types';\nimport {\n poseDescriptorForNode,\n translatePoseViaDescriptor,\n RECT_POSE_DESCRIPTOR,\n type PoseDescriptor,\n} from 'core/geometry/poseDescriptor';\nimport type { Path } from '@weasel-js/geom';\nimport {\n polygonRegion,\n poseOutline,\n rectRegion,\n regionBoundsOf,\n regionTakes,\n type SelectRegion,\n} from 'core/geometry/regionHit';\n\n/** Configuration for `arrayAdapter`. */\nexport interface ArrayAdapterConfig<TNode extends { id: string }, TPose> {\n /** Live ref to the current array. */\n ref: MutableRefObject<TNode[]>;\n /** Functional setter (typically the second value from `useState`). */\n setItems: (updater: (items: TNode[]) => TNode[]) => void;\n /** Project an object to its pose. Required. */\n toPose: (obj: TNode) => TPose;\n /** Merge a new pose back into an object. Default: shallow spread. */\n fromPose?: (obj: TNode, pose: TPose) => TNode;\n /** Optional parent lookup. Default returns `null`. */\n getParent?: (id: string) => string | null;\n /** Optional reparent mutator. Default is a noop. */\n setParent?: (id: string, parentId: string | null) => void;\n /** Optional children lookup for a node id. The adapter answers the root\n * itself, from `ref` and `getParent` in array order, so this is never\n * called with `null`. Default omits the method entirely. */\n getChildren?: (id: string) => string[] | undefined;\n\n /** Live ref to the current selection. Default: empty array. */\n selectionRef?: MutableRefObject<string[]>;\n /** Selection setter. Default: noop. */\n setSelection?: (ids: string[]) => void;\n\n /** Factory for `commitInsert` — invoked at the end of an insert drag.\n * Consumers own id generation and any palette/payload fields. Returning\n * `null` aborts the insert. When omitted, `commitInsert` returns `null`. */\n createDefault?: (bounds: Bounds) => TNode | null;\n\n /** How to read and rewrite item poses. Default `RECT_POSE_DESCRIPTOR`,\n * which reads the pose as its own AABB — the canonical axis-aligned rect\n * pose. `AUTO_POSE_DESCRIPTOR` lives above `core/` and so cannot be the\n * default here; pass it for a scene whose poses may be Paths. */\n poseDescriptor?: PoseDescriptor<TPose>;\n\n /** The outline a node draws, for marquee and lasso, in world coordinates.\n * Return `null` where the pose says it all. Default: none — a polygon pose\n * is its own outline, and anything else is its pose rect, rotated by\n * `rotation`. The kit's painters live above `core/`, so a scene whose\n * nodes they draw passes `findShapeSilhouette` to be tested as drawn. */\n silhouette?: (node: TNode, pose: TPose) => Path | null;\n\n /** Factory for fresh node ids in `commitPaste`. Default: `crypto.randomUUID()`\n * when available, otherwise a monotonic `paste-<n>` counter. Override\n * when consumers want stable id schemes (e.g. `clip-${n++}`). */\n nextId?: () => string;\n}\n\n/**\n * Combined adapter satisfying every narrow adapter the kit ships. Methods\n * with no default implementation (e.g. `commitInsert` without\n * `createDefault`) return null/[]/noop — sufficient to typecheck against\n * `InsertAdapter`/`AreaSelectAdapter`, and the corresponding gesture hook\n * simply produces no commit.\n */\nexport interface ArrayAdapter<TNode extends { id: string }, TPose>\n extends MoveAdapter<TNode, TPose>,\n ResizeAdapter<TNode, TPose>,\n InsertAdapter<TNode>,\n AreaSelectAdapter,\n LassoSelectAdapter {\n getNodes(): TNode[];\n removeNode(id: string): void;\n // ArrayAdapter satisfies the union of all narrow adapters; redeclare the\n // methods that are optional on AreaSelectAdapter (post-relaxation) but\n // required on InsertAdapter so TS sees a single non-conflicting signature.\n getSelection(): string[];\n setSelection(ids: string[]): void;\n // Redeclare applyOps with an optional label so it's compatible with both\n // MoveAdapter (label required) and AreaSelectAdapter (label absent = transient).\n applyOps?(ops: Op[], label?: string): void;\n}\n\nfunction defaultFromPose<TNode extends { id: string }, TPose>(\n obj: TNode,\n pose: TPose,\n): TNode {\n return { ...obj, ...(pose as object) } as TNode;\n}\n\nlet monotonicPasteCounter = 0;\nfunction defaultNextId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n return `paste-${monotonicPasteCounter++}`;\n}\n\n/**\n * Synthesize a many-faceted adapter from a `useState`-array scene. Always\n * satisfies `MoveAdapter`, `ResizeAdapter`, `InsertAdapter`, and\n * `AreaSelectAdapter` via structural typing. Override individual methods by\n * spreading the result.\n *\n * `applyOps` is intentionally omitted — hooks fall back to the built-in\n * dispatcher (see `dispatchApplyBatch`). Apps with custom history\n * integration supply their own via spread.\n */\nexport function arrayAdapter<TNode extends { id: string }, TPose>(\n config: ArrayAdapterConfig<TNode, TPose>,\n): ArrayAdapter<TNode, TPose> {\n const {\n ref,\n setItems,\n toPose,\n fromPose = defaultFromPose,\n getParent = () => null,\n setParent = () => {},\n getChildren,\n selectionRef,\n setSelection = () => {},\n createDefault,\n nextId = defaultNextId,\n silhouette,\n } = config;\n\n const d = (config.poseDescriptor ?? RECT_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const getSelection = selectionRef ? () => selectionRef.current : () => [];\n\n const takenBy = (region: SelectRegion, mode: LassoHitMode): string[] => {\n const out: string[] = [];\n for (const o of ref.current) {\n const pose = toPose(o);\n const b = regionBoundsOf(o, pose, poseDescriptorForNode(d, o) as PoseDescriptor<unknown>);\n const outline = (): Path => silhouette?.(o, pose) ?? poseOutline(pose, b);\n if (regionTakes(region, mode, pose, b, outline)) out.push(o.id);\n }\n return out;\n };\n\n // Root order is the item array's order, which this adapter owns — asking\n // the consumer would get a callback written for node ids, and its `[]`\n // is indistinguishable from \"the root has no children\".\n const childrenIn = (items: readonly TNode[], parentId: string | null): string[] =>\n parentId === null\n ? items.filter((o) => getParent(o.id) === null).map((o) => o.id)\n : getChildren?.(parentId) ?? [];\n\n /** Where in `items` an insert at `index` lands, `undefined` to append. With\n * `getChildren` configured the ops hand over an ordinal among the node's\n * siblings, which is an array index only when nothing else sits between. */\n const arrayIndexFor = (items: readonly TNode[], obj: TNode, index: number | undefined): number | undefined => {\n if (index === undefined || index < 0) return undefined;\n if (!getChildren) return index < items.length ? index : undefined;\n const before = childrenIn(items, parentOf(obj)).filter((id) => id !== obj.id)[index];\n const at = before === undefined ? -1 : items.findIndex((o) => o.id === before);\n return at < 0 ? undefined : at;\n };\n\n const adapter: ArrayAdapter<TNode, TPose> = {\n getNode: (id) => ref.current.find((o) => o.id === id),\n getNodes: () => ref.current,\n getPose: (id) => {\n const obj = ref.current.find((o) => o.id === id);\n if (!obj) throw new Error(`arrayAdapter.getPose: id \"${id}\" not found`);\n return toPose(obj);\n },\n setPose: (id, pose) => {\n setItems((items) => items.map((o) => (o.id === id ? fromPose(o, pose) : o)));\n },\n getParent,\n setParent,\n\n insertNode: (obj, index) => setItems((items) => {\n const at = arrayIndexFor(items, obj, index);\n if (at === undefined) return [...items, obj];\n return [...items.slice(0, at), obj, ...items.slice(at)];\n }),\n removeNode: (id) => setItems((items) => items.filter((o) => o.id !== id)),\n\n getSelection,\n setSelection,\n\n hitTestArea: (rect) => takenBy(rectRegion(rect), 'intersect'),\n\n hitTestLasso: (polygon, mode: LassoHitMode) => {\n const region = polygonRegion(polygon);\n return region ? takenBy(region, mode) : [];\n },\n\n // Use a method shorthand so `this` is the call-site receiver, not the\n // adapter we're building here. That way consumers can spread additional\n // methods on top (e.g. `{ ...arrayAdapter(...), ...selection.adapterMethods }`)\n // and `applyOps` will dispatch ops against the merged object.\n applyOps(ops: Op[]) {\n applyOpsTo(this, ops);\n },\n\n commitInsert: createDefault ? (b) => createDefault(b) : () => null,\n commitPaste: (\n clip: ClipboardSnapshot,\n offset: { dx: number; dy: number },\n ctx?: { dropPoint?: { worldX: number; worldY: number } },\n ): TNode[] => {\n const src = clip.items as TNode[];\n if (src.length === 0) return [];\n\n // Default translation = `offset`. When `dropPoint` is supplied, center\n // the cluster's union AABB at the drop point — matches the\n // `ClipboardDemo` reference wiring.\n let dx = offset.dx;\n let dy = offset.dy;\n if (ctx?.dropPoint) {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const n of src) {\n const b = poseDescriptorForNode(d, n).getBounds(toPose(n));\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n if (b.x + b.width > maxX) maxX = b.x + b.width;\n if (b.y + b.height > maxY) maxY = b.y + b.height;\n }\n // Defensive: if the descriptor returned nothing usable, fall back to offset.\n if (Number.isFinite(minX) && Number.isFinite(minY)) {\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n dx = ctx.dropPoint.worldX - cx;\n dy = ctx.dropPoint.worldY - cy;\n }\n }\n\n // Deep-clone via JSON round-trip so the snapshot isn't mutated by\n // downstream pose edits.\n const out: TNode[] = [];\n for (const n of src) {\n const cloned = JSON.parse(JSON.stringify(n)) as TNode;\n (cloned as { id: string }).id = nextId();\n const translated = translatePoseViaDescriptor(\n toPose(cloned), dx, dy, poseDescriptorForNode(d, cloned),\n );\n out.push(fromPose(cloned, translated));\n }\n return out;\n },\n snapshotSelection: (ids) => ({\n items: ids\n .map((id) => ref.current.find((o) => o.id === id))\n .filter((o): o is TNode => !!o),\n }),\n };\n\n if (getChildren) {\n (adapter as ArrayAdapter<TNode, TPose> & {\n getChildren: (parentId: string | null) => string[];\n }).getChildren = (parentId) => childrenIn(ref.current, parentId);\n }\n\n return adapter;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { DeviceProfileProvider as e, useDeviceProfile as t } from "./useDeviceProfile.js";
2
- import { ANCHOR_HIT_BASE_PX as n, HANDLE_BASE_PX as r, ROTATION_HANDLE_BASE_PX as i } from "./targets.js";
3
- import { COARSE_TARGET_SCALE as a, DEFAULT_DEVICE_PROFILE as o, resolveDeviceProfile as s } from "@weasel-js/routing";
4
- export { n as ANCHOR_HIT_BASE_PX, a as COARSE_TARGET_SCALE, o as DEFAULT_DEVICE_PROFILE, e as DeviceProfileProvider, r as HANDLE_BASE_PX, i as ROTATION_HANDLE_BASE_PX, s as resolveDeviceProfile, t as useDeviceProfile };
1
+ import { watchDevicePixelRatio as e } from "./watchDevicePixelRatio.js";
2
+ import { DeviceProfileProvider as t, useDeviceProfile as n } from "./useDeviceProfile.js";
3
+ import { ANCHOR_HIT_BASE_PX as r, HANDLE_BASE_PX as i, ROTATION_HANDLE_BASE_PX as a } from "./targets.js";
4
+ import { COARSE_TARGET_SCALE as o, DEFAULT_DEVICE_PROFILE as s, resolveDeviceProfile as c } from "@weasel-js/routing";
5
+ export { r as ANCHOR_HIT_BASE_PX, o as COARSE_TARGET_SCALE, s as DEFAULT_DEVICE_PROFILE, t as DeviceProfileProvider, i as HANDLE_BASE_PX, a as ROTATION_HANDLE_BASE_PX, c as resolveDeviceProfile, n as useDeviceProfile, e as watchDevicePixelRatio };
@@ -1,75 +1,69 @@
1
- import { createContext as e, createElement as t, useContext as n, useEffect as r, useMemo as i, useState as a } from "react";
2
- import { DEFAULT_DEVICE_PROFILE as o, resolveDeviceProfile as s } from "@weasel-js/routing";
1
+ import { watchDevicePixelRatio as e } from "./watchDevicePixelRatio.js";
2
+ import { createContext as t, createElement as n, useContext as r, useEffect as i, useMemo as a, useState as o } from "react";
3
+ import { DEFAULT_DEVICE_PROFILE as s, resolveDeviceProfile as c } from "@weasel-js/routing";
3
4
  //#region src/core/device/useDeviceProfile.ts
4
- var c = "(pointer: coarse)", l = "(hover: hover)";
5
- function u() {
5
+ var l = "(pointer: coarse)", u = "(hover: hover)";
6
+ function d() {
6
7
  return typeof window < "u" && typeof window.matchMedia == "function";
7
8
  }
8
- function d() {
9
+ function f() {
9
10
  let e = typeof window < "u" && window.devicePixelRatio || 1;
10
- return u() ? {
11
- coarsePointer: window.matchMedia(c).matches,
12
- canHover: window.matchMedia(l).matches,
11
+ return d() ? {
12
+ coarsePointer: window.matchMedia(l).matches,
13
+ canHover: window.matchMedia(u).matches,
13
14
  dpr: e
14
15
  } : {
15
- coarsePointer: o.coarsePointer,
16
- canHover: o.canHover,
16
+ coarsePointer: s.coarsePointer,
17
+ canHover: s.canHover,
17
18
  dpr: e
18
19
  };
19
20
  }
20
- function f() {
21
- let [e, t] = a(d);
22
- return r(() => {
23
- if (!u()) return;
24
- let e = window.matchMedia(c), n = window.matchMedia(l), r = (e) => t((t) => ({
21
+ function p() {
22
+ let [t, n] = o(f);
23
+ return i(() => {
24
+ if (!d()) return;
25
+ let t = window.matchMedia(l), r = window.matchMedia(u), i = (e) => n((t) => ({
25
26
  ...t,
26
27
  coarsePointer: e.matches
27
- })), i = (e) => t((t) => ({
28
+ })), a = (e) => n((t) => ({
28
29
  ...t,
29
30
  canHover: e.matches
30
31
  }));
31
- e.addEventListener("change", r), n.addEventListener("change", i);
32
- let a = !1, o = null, s = () => {
33
- if (a) return;
34
- let e = window.devicePixelRatio || 1;
35
- t((t) => t.dpr === e ? t : {
36
- ...t,
37
- dpr: e
38
- }), d();
39
- };
40
- function d() {
41
- a || (o?.removeEventListener("change", s), o = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`), o.addEventListener("change", s));
42
- }
43
- return d(), t({
44
- coarsePointer: e.matches,
45
- canHover: n.matches,
32
+ t.addEventListener("change", i), r.addEventListener("change", a);
33
+ let o = e((e) => n((t) => t.dpr === e ? t : {
34
+ ...t,
35
+ dpr: e
36
+ }));
37
+ return n({
38
+ coarsePointer: t.matches,
39
+ canHover: r.matches,
46
40
  dpr: window.devicePixelRatio || 1
47
41
  }), () => {
48
- a = !0, e.removeEventListener("change", r), n.removeEventListener("change", i), o?.removeEventListener("change", s);
42
+ o(), t.removeEventListener("change", i), r.removeEventListener("change", a);
49
43
  };
50
- }, []), e;
44
+ }, []), t;
51
45
  }
52
- var p = e(null);
53
- function m(e) {
54
- let t = n(p), r = f(), a = t ?? r, { coarsePointer: o, canHover: c, dpr: l, targetScale: u } = e ?? {};
55
- return i(() => s(a, {
46
+ var m = t(null);
47
+ function h(e) {
48
+ let t = r(m), n = p(), i = t ?? n, { coarsePointer: o, canHover: s, dpr: l, targetScale: u } = e ?? {};
49
+ return a(() => c(i, {
56
50
  ...o === void 0 ? {} : { coarsePointer: o },
57
- ...c === void 0 ? {} : { canHover: c },
51
+ ...s === void 0 ? {} : { canHover: s },
58
52
  ...l === void 0 ? {} : { dpr: l },
59
53
  ...u === void 0 ? {} : { targetScale: u }
60
54
  }), [
61
- a,
55
+ i,
62
56
  o,
63
- c,
57
+ s,
64
58
  l,
65
59
  u
66
60
  ]);
67
61
  }
68
- function h({ value: e, children: n }) {
69
- let r = m(e);
70
- return t(p.Provider, { value: r }, n);
62
+ function g({ value: e, children: t }) {
63
+ let r = h(e);
64
+ return n(m.Provider, { value: r }, t);
71
65
  }
72
66
  //#endregion
73
- export { h as DeviceProfileProvider, m as useDeviceProfile };
67
+ export { g as DeviceProfileProvider, h as useDeviceProfile };
74
68
 
75
69
  //# sourceMappingURL=useDeviceProfile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDeviceProfile.js","names":["COARSE_QUERY","HOVER_QUERY","hasMatchMedia","detectOnce","dpr","DEFAULT_DEVICE_PROFILE","useDetectedFacts","facts","setFacts","useState","useEffect","coarseMq","hoverMq","onCoarse","f","onHover","disposed","densityMq","onDensity","next","arm","DeviceProfileContext","createContext","useDeviceProfile","provided","useContext","detected","base","overrides","useMemo","resolveDeviceProfile","coarsePointer","canHover","targetScale","DeviceProfileProvider","profile","value","createElement","children"],"sources":["../../../src/core/device/useDeviceProfile.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport type { DetectedDeviceFacts, DeviceProfile } from './types';\nimport { DEFAULT_DEVICE_PROFILE, resolveDeviceProfile } from '@weasel-js/routing';\n\nconst COARSE_QUERY = '(pointer: coarse)';\nconst HOVER_QUERY = '(hover: hover)';\n\nfunction hasMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\nfunction detectOnce(): DetectedDeviceFacts {\n // Density is read unconditionally: `devicePixelRatio` does not depend on\n // `matchMedia` existing. Only *watching* density needs a media query.\n // Gating this read behind `hasMatchMedia()` would report density 1 under\n // any environment without matchMedia — including jsdom, which would\n // silently break `useCanvasSize`'s existing DPR assertions.\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n if (!hasMatchMedia()) {\n return {\n coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,\n canHover: DEFAULT_DEVICE_PROFILE.canHover,\n dpr,\n };\n }\n return {\n coarsePointer: window.matchMedia(COARSE_QUERY).matches,\n canHover: window.matchMedia(HOVER_QUERY).matches,\n dpr,\n };\n}\n\n/**\n * Detect device facts and keep them live.\n *\n * Three subscriptions: pointer coarseness, hover capability, and pixel\n * density. The density one is special — `(resolution: Ndppx)` stops matching\n * as soon as density changes, so a single listener fires once and goes dead.\n * We re-arm a fresh query at the new density on every change.\n */\nfunction useDetectedFacts(): DetectedDeviceFacts {\n const [facts, setFacts] = useState<DetectedDeviceFacts>(detectOnce);\n\n useEffect(() => {\n if (!hasMatchMedia()) return;\n\n const coarseMq = window.matchMedia(COARSE_QUERY);\n const hoverMq = window.matchMedia(HOVER_QUERY);\n\n const onCoarse = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, coarsePointer: e.matches }));\n const onHover = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, canHover: e.matches }));\n\n coarseMq.addEventListener('change', onCoarse as EventListener);\n hoverMq.addEventListener('change', onHover as EventListener);\n\n // Re-arming density watcher.\n let disposed = false;\n let densityMq: MediaQueryList | null = null;\n const onDensity = () => {\n if (disposed) return;\n const next = window.devicePixelRatio || 1;\n setFacts((f) => (f.dpr === next ? f : { ...f, dpr: next }));\n arm();\n };\n function arm(): void {\n if (disposed) return;\n densityMq?.removeEventListener('change', onDensity as EventListener);\n densityMq = window.matchMedia(\n `(resolution: ${window.devicePixelRatio || 1}dppx)`,\n );\n densityMq.addEventListener('change', onDensity as EventListener);\n }\n arm();\n\n // Re-sync once on mount: a query could have changed between the\n // useState initializer and the effect running.\n // eslint-disable-next-line react-hooks/set-state-in-effect -- re-syncs media queries that may have changed before subscribing\n setFacts({\n coarsePointer: coarseMq.matches,\n canHover: hoverMq.matches,\n dpr: window.devicePixelRatio || 1,\n });\n\n return () => {\n disposed = true;\n coarseMq.removeEventListener('change', onCoarse as EventListener);\n hoverMq.removeEventListener('change', onHover as EventListener);\n densityMq?.removeEventListener('change', onDensity as EventListener);\n };\n }, []);\n\n return facts;\n}\n\nconst DeviceProfileContext = createContext<DeviceProfile | null>(null);\n\n/**\n * Read the ambient device profile.\n *\n * Uses the nearest {@link DeviceProfileProvider} when one exists, and\n * otherwise detects for itself — so a standalone overlay or a labkit panel\n * rendered outside a `<SceneCanvas>` still gets correct facts.\n *\n * `overrides` always wins over both, which is what makes tests and\n * force-touch-chrome demos possible without stubbing `matchMedia`.\n */\nexport function useDeviceProfile(overrides?: Partial<DeviceProfile>): DeviceProfile {\n const provided = useContext(DeviceProfileContext);\n const detected = useDetectedFacts();\n const base: DetectedDeviceFacts = provided ?? detected;\n const { coarsePointer, canHover, dpr, targetScale } = overrides ?? {};\n return useMemo(\n () =>\n resolveDeviceProfile(base, {\n ...(coarsePointer !== undefined ? { coarsePointer } : {}),\n ...(canHover !== undefined ? { canHover } : {}),\n ...(dpr !== undefined ? { dpr } : {}),\n ...(targetScale !== undefined ? { targetScale } : {}),\n }),\n [base, coarsePointer, canHover, dpr, targetScale],\n );\n}\n\n/** Props for `<DeviceProfileProvider>`. */\nexport interface DeviceProfileProviderProps {\n /** Partial override folded over detected facts. */\n value?: Partial<DeviceProfile>;\n children: ReactNode;\n}\n\n/**\n * Publish one resolved profile to a subtree. `<SceneCanvas>` renders this so\n * overlays, affordances, and consumer chrome all read the same object.\n */\nexport function DeviceProfileProvider({ value, children }: DeviceProfileProviderProps) {\n const profile = useDeviceProfile(value);\n return createElement(DeviceProfileContext.Provider, { value: profile }, children);\n}\n"],"mappings":";;;AAYA,IAAMA,IAAe,qBACfC,IAAc;AAEpB,SAASC,IAAyB;CAChC,OAAO,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe;AACvE;AAEA,SAASC,IAAkC;CAMzC,IAAMC,IAAO,OAAO,SAAW,OAAe,OAAO,oBAAqB;CAQ1E,OAPKF,EAAc,IAOZ;EACL,eAAe,OAAO,WAAWF,CAAY,CAAC,CAAC;EAC/C,UAAU,OAAO,WAAWC,CAAW,CAAC,CAAC;EACzC;CACF,IAVS;EACL,eAAeI,EAAuB;EACtC,UAAUA,EAAuB;EACjC;CACF;AAOJ;AAUA,SAASC,IAAwC;CAC/C,IAAM,CAACC,GAAOC,KAAYC,EAA8BN,CAAU;CAoDlE,OAlDAO,QAAgB;EACd,IAAI,CAACR,EAAc,GAAG;EAEtB,IAAMS,IAAW,OAAO,WAAWX,CAAY,GACzCY,IAAU,OAAO,WAAWX,CAAW,GAEvCY,KAAY,MAChBL,GAAU,OAAO;GAAE,GAAGM;GAAG,eAAe,EAAE;EAAQ,EAAE,GAChDC,KAAW,MACfP,GAAU,OAAO;GAAE,GAAGM;GAAG,UAAU,EAAE;EAAQ,EAAE;EAGjD,AADAH,EAAS,iBAAiB,UAAUE,CAAyB,GAC7DD,EAAQ,iBAAiB,UAAUG,CAAwB;EAG3D,IAAIC,IAAW,IACXC,IAAmC,MACjCC,UAAkB;GACtB,IAAIF,GAAU;GACd,IAAMG,IAAO,OAAO,oBAAoB;GAExC,AADAX,GAAU,MAAOM,EAAE,QAAQK,IAAOL,IAAI;IAAE,GAAGA;IAAG,KAAKK;GAAK,CAAE,GAC1DC,EAAI;EACN;EACA,SAASA,IAAY;GACnB,AAAIJ,MACJC,GAAW,oBAAoB,UAAUC,CAA0B,GACnE,IAAY,OAAO,WACjB,gBAAgB,OAAO,oBAAoB,EAAE,MAC/C,GACAD,EAAU,iBAAiB,UAAUC,CAA0B;EACjE;EAYA,OAXAE,EAAI,GAKJZ,EAAS;GACP,eAAeG,EAAS;GACxB,UAAUC,EAAQ;GAClB,KAAK,OAAO,oBAAoB;EAClC,CAAC,SAEY;GAIX,AAHA,IAAW,IACXD,EAAS,oBAAoB,UAAUE,CAAyB,GAChED,EAAQ,oBAAoB,UAAUG,CAAwB,GAC9DE,GAAW,oBAAoB,UAAUC,CAA0B;EACrE;CACF,GAAG,CAAC,CAAC,GAEEX;AACT;AAEA,IAAMc,IAAuBC,EAAoC,IAAI;AAYrE,SAAgBC,EAAiB,GAAmD;CAClF,IAAMC,IAAWC,EAAWJ,CAAoB,GAC1CK,IAAWpB,EAAiB,GAC5BqB,IAA4BH,KAAYE,GACxC,EAAE,kBAAe,aAAU,QAAK,mBAAgBE,KAAa,CAAC;CACpE,OAAOC,QAEHC,EAAqBH,GAAM;EACzB,GAAII,MAAkB,KAAA,IAAgC,CAAC,IAArB,EAAE,iBAAc;EAClD,GAAIC,MAAa,KAAA,IAA2B,CAAC,IAAhB,EAAE,YAAS;EACxC,GAAI5B,MAAQ,KAAA,IAAsB,CAAC,IAAX,EAAE,OAAI;EAC9B,GAAI6B,MAAgB,KAAA,IAA8B,CAAC,IAAnB,EAAE,eAAY;CAChD,CAAC,GACH;EAACN;EAAMI;EAAeC;EAAU5B;EAAK6B;CAAW,CAClD;AACF;AAaA,SAAgBC,EAAsB,EAAE,UAAO,eAAwC;CACrF,IAAMC,IAAUZ,EAAiBa,CAAK;CACtC,OAAOC,EAAchB,EAAqB,UAAU,EAAE,OAAOc,EAAQ,GAAGG,CAAQ;AAClF"}
1
+ {"version":3,"file":"useDeviceProfile.js","names":["COARSE_QUERY","HOVER_QUERY","hasMatchMedia","detectOnce","dpr","DEFAULT_DEVICE_PROFILE","useDetectedFacts","facts","setFacts","useState","useEffect","coarseMq","hoverMq","onCoarse","f","onHover","unwatchDensity","watchDevicePixelRatio","next","DeviceProfileContext","createContext","useDeviceProfile","provided","useContext","detected","base","overrides","useMemo","resolveDeviceProfile","coarsePointer","canHover","targetScale","DeviceProfileProvider","profile","value","createElement","children"],"sources":["../../../src/core/device/useDeviceProfile.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport type { DetectedDeviceFacts, DeviceProfile } from './types';\nimport { DEFAULT_DEVICE_PROFILE, resolveDeviceProfile } from '@weasel-js/routing';\nimport { watchDevicePixelRatio } from './watchDevicePixelRatio';\n\nconst COARSE_QUERY = '(pointer: coarse)';\nconst HOVER_QUERY = '(hover: hover)';\n\nfunction hasMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\nfunction detectOnce(): DetectedDeviceFacts {\n // Density is read unconditionally: `devicePixelRatio` does not depend on\n // `matchMedia` existing. Only *watching* density needs a media query.\n // Gating this read behind `hasMatchMedia()` would report density 1 under\n // any environment without matchMedia — including jsdom, which would\n // silently break `useCanvasSize`'s existing DPR assertions.\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n if (!hasMatchMedia()) {\n return {\n coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,\n canHover: DEFAULT_DEVICE_PROFILE.canHover,\n dpr,\n };\n }\n return {\n coarsePointer: window.matchMedia(COARSE_QUERY).matches,\n canHover: window.matchMedia(HOVER_QUERY).matches,\n dpr,\n };\n}\n\n/**\n * Detect device facts and keep them live.\n *\n * Three subscriptions: pointer coarseness, hover capability, and pixel\n * density.\n */\nfunction useDetectedFacts(): DetectedDeviceFacts {\n const [facts, setFacts] = useState<DetectedDeviceFacts>(detectOnce);\n\n useEffect(() => {\n if (!hasMatchMedia()) return;\n\n const coarseMq = window.matchMedia(COARSE_QUERY);\n const hoverMq = window.matchMedia(HOVER_QUERY);\n\n const onCoarse = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, coarsePointer: e.matches }));\n const onHover = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, canHover: e.matches }));\n\n coarseMq.addEventListener('change', onCoarse as EventListener);\n hoverMq.addEventListener('change', onHover as EventListener);\n\n const unwatchDensity = watchDevicePixelRatio((next) =>\n setFacts((f) => (f.dpr === next ? f : { ...f, dpr: next })));\n\n // Re-sync once on mount: a query could have changed between the\n // useState initializer and the effect running.\n // eslint-disable-next-line react-hooks/set-state-in-effect -- re-syncs media queries that may have changed before subscribing\n setFacts({\n coarsePointer: coarseMq.matches,\n canHover: hoverMq.matches,\n dpr: window.devicePixelRatio || 1,\n });\n\n return () => {\n unwatchDensity();\n coarseMq.removeEventListener('change', onCoarse as EventListener);\n hoverMq.removeEventListener('change', onHover as EventListener);\n };\n }, []);\n\n return facts;\n}\n\nconst DeviceProfileContext = createContext<DeviceProfile | null>(null);\n\n/**\n * Read the ambient device profile.\n *\n * Uses the nearest {@link DeviceProfileProvider} when one exists, and\n * otherwise detects for itself — so a standalone overlay or a labkit panel\n * rendered outside a `<SceneCanvas>` still gets correct facts.\n *\n * `overrides` always wins over both, which is what makes tests and\n * force-touch-chrome demos possible without stubbing `matchMedia`.\n */\nexport function useDeviceProfile(overrides?: Partial<DeviceProfile>): DeviceProfile {\n const provided = useContext(DeviceProfileContext);\n const detected = useDetectedFacts();\n const base: DetectedDeviceFacts = provided ?? detected;\n const { coarsePointer, canHover, dpr, targetScale } = overrides ?? {};\n return useMemo(\n () =>\n resolveDeviceProfile(base, {\n ...(coarsePointer !== undefined ? { coarsePointer } : {}),\n ...(canHover !== undefined ? { canHover } : {}),\n ...(dpr !== undefined ? { dpr } : {}),\n ...(targetScale !== undefined ? { targetScale } : {}),\n }),\n [base, coarsePointer, canHover, dpr, targetScale],\n );\n}\n\n/** Props for `<DeviceProfileProvider>`. */\nexport interface DeviceProfileProviderProps {\n /** Partial override folded over detected facts. */\n value?: Partial<DeviceProfile>;\n children: ReactNode;\n}\n\n/**\n * Publish one resolved profile to a subtree. `<SceneCanvas>` renders this so\n * overlays, affordances, and consumer chrome all read the same object.\n */\nexport function DeviceProfileProvider({ value, children }: DeviceProfileProviderProps) {\n const profile = useDeviceProfile(value);\n return createElement(DeviceProfileContext.Provider, { value: profile }, children);\n}\n"],"mappings":";;;;AAaA,IAAMA,IAAe,qBACfC,IAAc;AAEpB,SAASC,IAAyB;CAChC,OAAO,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe;AACvE;AAEA,SAASC,IAAkC;CAMzC,IAAMC,IAAO,OAAO,SAAW,OAAe,OAAO,oBAAqB;CAQ1E,OAPKF,EAAc,IAOZ;EACL,eAAe,OAAO,WAAWF,CAAY,CAAC,CAAC;EAC/C,UAAU,OAAO,WAAWC,CAAW,CAAC,CAAC;EACzC;CACF,IAVS;EACL,eAAeI,EAAuB;EACtC,UAAUA,EAAuB;EACjC;CACF;AAOJ;AAQA,SAASC,IAAwC;CAC/C,IAAM,CAACC,GAAOC,KAAYC,EAA8BN,CAAU;CAmClE,OAjCAO,QAAgB;EACd,IAAI,CAACR,EAAc,GAAG;EAEtB,IAAMS,IAAW,OAAO,WAAWX,CAAY,GACzCY,IAAU,OAAO,WAAWX,CAAW,GAEvCY,KAAY,MAChBL,GAAU,OAAO;GAAE,GAAGM;GAAG,eAAe,EAAE;EAAQ,EAAE,GAChDC,KAAW,MACfP,GAAU,OAAO;GAAE,GAAGM;GAAG,UAAU,EAAE;EAAQ,EAAE;EAGjD,AADAH,EAAS,iBAAiB,UAAUE,CAAyB,GAC7DD,EAAQ,iBAAiB,UAAUG,CAAwB;EAE3D,IAAMC,IAAiBC,GAAuB,MAC5CT,GAAU,MAAOM,EAAE,QAAQI,IAAOJ,IAAI;GAAE,GAAGA;GAAG,KAAKI;EAAK,CAAE,CAAC;EAW7D,OANAV,EAAS;GACP,eAAeG,EAAS;GACxB,UAAUC,EAAQ;GAClB,KAAK,OAAO,oBAAoB;EAClC,CAAC,SAEY;GAGX,AAFAI,EAAe,GACfL,EAAS,oBAAoB,UAAUE,CAAyB,GAChED,EAAQ,oBAAoB,UAAUG,CAAwB;EAChE;CACF,GAAG,CAAC,CAAC,GAEER;AACT;AAEA,IAAMY,IAAuBC,EAAoC,IAAI;AAYrE,SAAgBC,EAAiB,GAAmD;CAClF,IAAMC,IAAWC,EAAWJ,CAAoB,GAC1CK,IAAWlB,EAAiB,GAC5BmB,IAA4BH,KAAYE,GACxC,EAAE,kBAAe,aAAU,QAAK,mBAAgBE,KAAa,CAAC;CACpE,OAAOC,QAEHC,EAAqBH,GAAM;EACzB,GAAII,MAAkB,KAAA,IAAgC,CAAC,IAArB,EAAE,iBAAc;EAClD,GAAIC,MAAa,KAAA,IAA2B,CAAC,IAAhB,EAAE,YAAS;EACxC,GAAI1B,MAAQ,KAAA,IAAsB,CAAC,IAAX,EAAE,OAAI;EAC9B,GAAI2B,MAAgB,KAAA,IAA8B,CAAC,IAAnB,EAAE,eAAY;CAChD,CAAC,GACH;EAACN;EAAMI;EAAeC;EAAU1B;EAAK2B;CAAW,CAClD;AACF;AAaA,SAAgBC,EAAsB,EAAE,UAAO,eAAwC;CACrF,IAAMC,IAAUZ,EAAiBa,CAAK;CACtC,OAAOC,EAAchB,EAAqB,UAAU,EAAE,OAAOc,EAAQ,GAAGG,CAAQ;AAClF"}
@@ -0,0 +1,17 @@
1
+ //#region src/core/device/watchDevicePixelRatio.ts
2
+ function e(e) {
3
+ if (typeof window > "u" || typeof window.matchMedia != "function") return () => {};
4
+ let t = !1, n = null, r = () => {
5
+ t || (i(), e(window.devicePixelRatio || 1));
6
+ };
7
+ function i() {
8
+ n?.removeEventListener("change", r), n = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`), n.addEventListener("change", r);
9
+ }
10
+ return i(), () => {
11
+ t = !0, n?.removeEventListener("change", r);
12
+ };
13
+ }
14
+ //#endregion
15
+ export { e as watchDevicePixelRatio };
16
+
17
+ //# sourceMappingURL=watchDevicePixelRatio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchDevicePixelRatio.js","names":["watchDevicePixelRatio","disposed","query","onDensity","arm","onChange"],"sources":["../../../src/core/device/watchDevicePixelRatio.ts"],"sourcesContent":["/**\n * Call `onChange` each time `window.devicePixelRatio` changes: the window\n * moved to a display of another density, or the page zoomed. Returns the\n * unsubscribe. A no-op where `matchMedia` does not exist.\n *\n * `(resolution: Ndppx)` stops matching as soon as density changes, so one\n * listener fires once and goes dead; this arms a fresh query at the new\n * density on every change.\n */\nexport function watchDevicePixelRatio(onChange: (dpr: number) => void): () => void {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {};\n let disposed = false;\n let query: MediaQueryList | null = null;\n const onDensity = (): void => {\n if (disposed) return;\n arm();\n onChange(window.devicePixelRatio || 1);\n };\n function arm(): void {\n query?.removeEventListener('change', onDensity);\n query = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);\n query.addEventListener('change', onDensity);\n }\n arm();\n return () => {\n disposed = true;\n query?.removeEventListener('change', onDensity);\n };\n}\n"],"mappings":";AASA,SAAgBA,EAAsB,GAA6C;CACjF,IAAI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,YAAY,aAAa,CAAC;CAC5F,IAAIC,IAAW,IACXC,IAA+B,MAC7BC,UAAwB;EAC5B,AAAIF,MACJG,EAAI,GACJC,EAAS,OAAO,oBAAoB,CAAC;CACvC;CACA,SAASD,IAAY;EAGnB,AAFAF,GAAO,oBAAoB,UAAUC,CAAS,GAC9C,IAAQ,OAAO,WAAW,gBAAgB,OAAO,oBAAoB,EAAE,MAAM,GAC7ED,EAAM,iBAAiB,UAAUC,CAAS;CAC5C;CAEA,OADAC,EAAI,SACS;EAEX,AADA,IAAW,IACXF,GAAO,oBAAoB,UAAUC,CAAS;CAChD;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"fillInPoseFrame.js","names":["fillInPoseFrame","isBuiltinKind","fill","getPaintKind","box","isBoundsPattern","o","isBoundsGradient","toBox","p","scale","fillToBoundsFrame","fromBox","kind"],"sources":["../../src/core/fillInPoseFrame.ts"],"sourcesContent":["/**\n * Resolve a `units: 'bounds'` gradient into the frame its node is painted in.\n *\n * Node painters bake the pose into their geometry (`pathInPoseFrame`) rather\n * than emitting a per-node transform, so by the time the renderer sees a\n * scene node there is no node-local space left for a gradient to refer to.\n * This is the paint-side counterpart: it maps box-relative gradient geometry\n * onto the same box the path was just projected into, so the two arrive in\n * the renderer in one frame.\n */\n\nimport type { FillStyle, GradientFill } from '@weasel-js/paint';\nimport { getPaintKind } from './paintKinds';\n\n/** The box a `'bounds'` gradient's normalized geometry is mapped onto —\n * usually a node's bounds. */\nexport interface FillPoseBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Map a `'bounds'` gradient's `0..1` geometry onto `box`, returning it in the\n * box's own frame (`units: 'local'`). Any other fill — solid, pattern, or a\n * gradient in another space — is returned untouched.\n *\n * Radii scale by the box's normalized diagonal, SVG's rule for a percentage\n * radius against `objectBoundingBox`: `sqrt(w² + h²) / sqrt(2)`, which is\n * exactly `w` for a square box. The renderer's radial gradient is circular,\n * so a non-square box cannot make it the ellipse SVG would — this keeps the\n * radius proportionate instead.\n */\nexport function fillInPoseFrame(fill: FillStyle, box: FillPoseBox): FillStyle {\n if (!isBuiltinKind(fill)) {\n return getPaintKind(fill.fill)?.inPoseFrame?.(fill, box) ?? fill;\n }\n if (isBoundsPattern(fill)) {\n const o = fill.origin ?? { x: 0, y: 0 };\n return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: 'local' as const };\n }\n if (!isBoundsGradient(fill)) return fill;\n\n const toBox = (p: { x: number; y: number }): { x: number; y: number } => ({\n x: box.x + p.x * box.width,\n y: box.y + p.y * box.height,\n });\n\n if (fill.fill === 'linear-gradient') {\n return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: 'local' as const };\n }\n if (fill.fill === 'radial-gradient') {\n const scale = Math.hypot(box.width, box.height) / Math.SQRT2;\n return { ...fill, center: toBox(fill.center), radius: fill.radius * scale, units: 'local' as const };\n }\n return { ...fill, center: toBox(fill.center), units: 'local' as const };\n}\n\n/**\n * Inverse of {@link fillInPoseFrame}: express a gradient already positioned\n * in `box`'s frame as `0..1` fractions of that box (`units: 'bounds'`), so\n * it follows the node when moved or resized.\n *\n * The importer's counterpart — SVG `userSpaceOnUse` coordinates arrive in\n * page space, and normalizing them here makes an imported gradient behave\n * exactly like one drawn in the app.\n *\n * A zero-width or zero-height box has no frame to normalize against; the\n * fill is returned untouched rather than divided by zero.\n */\nexport function fillToBoundsFrame(fill: FillStyle, box: FillPoseBox): FillStyle {\n if (!isBuiltinKind(fill)) {\n return getPaintKind(fill.fill)?.toBoundsFrame?.(fill, box) ?? fill;\n }\n if (fill.fill === 'pattern') {\n const o = fill.origin ?? { x: 0, y: 0 };\n return { ...fill, origin: { x: o.x - box.x, y: o.y - box.y }, units: 'bounds' as const };\n }\n const isGradient = fill.fill === 'linear-gradient'\n || fill.fill === 'radial-gradient'\n || fill.fill === 'conic-gradient';\n if (!isGradient) return fill;\n if (box.width === 0 || box.height === 0) return fill;\n\n const fromBox = (p: { x: number; y: number }): { x: number; y: number } => ({\n x: (p.x - box.x) / box.width,\n y: (p.y - box.y) / box.height,\n });\n\n if (fill.fill === 'linear-gradient') {\n return { ...fill, from: fromBox(fill.from), to: fromBox(fill.to), units: 'bounds' as const };\n }\n if (fill.fill === 'radial-gradient') {\n const scale = Math.hypot(box.width, box.height) / Math.SQRT2;\n return { ...fill, center: fromBox(fill.center), radius: fill.radius / scale, units: 'bounds' as const };\n }\n return { ...fill, center: fromBox(fill.center), units: 'bounds' as const };\n}\n\n/** True when this fill is a pattern anchored to the painted node's box.\n * Rebasing it is a translation only — a resize must reveal more tiles, not\n * stretch them, which is the whole reason patterns get their own branch. */\nfunction isBoundsPattern(\n fill: FillStyle,\n): fill is Extract<FillStyle, { fill: 'pattern' }> {\n return fill.fill === 'pattern' && fill.units === 'bounds';\n}\n\n/** True when this fill is a gradient declaring box-relative geometry. */\nfunction isBoundsGradient(fill: FillStyle | undefined): fill is GradientFill {\n if (fill === undefined) return false;\n const kind = fill.fill;\n if (kind !== 'linear-gradient' && kind !== 'radial-gradient' && kind !== 'conic-gradient') {\n return false;\n }\n return fill.units === 'bounds';\n}\n\n/** The five kinds whose frame conversion is written above. Anything else is a\n * registered kind and converts through its own entry. */\nfunction isBuiltinKind(fill: FillStyle): boolean {\n const kind = fill.fill ?? 'solid';\n return kind === 'solid' || kind === 'pattern'\n || kind === 'linear-gradient' || kind === 'radial-gradient' || kind === 'conic-gradient';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fillInPoseFrame.js","names":["fillInPoseFrame","isBuiltinKind","fill","getPaintKind","box","isBoundsPattern","o","isBoundsGradient","toBox","p","scale","fillToBoundsFrame","fromBox","kind"],"sources":["../../src/core/fillInPoseFrame.ts"],"sourcesContent":["/**\n * Resolve a `units: 'bounds'` gradient into the frame its node is painted in.\n *\n * Node painters bake the pose into their geometry (`pathInPoseFrame`) rather\n * than emitting a per-node transform, so by the time the renderer sees a\n * scene node there is no node-local space left for a gradient to refer to.\n * This is the paint-side counterpart: it maps box-relative gradient geometry\n * onto the same box the path was just projected into, so the two arrive in\n * the renderer in one frame.\n */\n\nimport type { FillStyle, GradientFill, PosedFill } from '@weasel-js/paint';\nimport { getPaintKind } from './paintKinds';\n\n/** The box a `'bounds'` gradient's normalized geometry is mapped onto —\n * usually a node's bounds. */\nexport interface FillPoseBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Map a `'bounds'` gradient's `0..1` geometry onto `box`, returning it in the\n * box's own frame (`units: 'local'`). Any other fill — solid, pattern, or a\n * gradient in another space — is returned untouched.\n *\n * Radii scale by the box's normalized diagonal, SVG's rule for a percentage\n * radius against `objectBoundingBox`: `sqrt(w² + h²) / sqrt(2)`, which is\n * exactly `w` for a square box. The renderer's radial gradient is circular,\n * so a non-square box cannot make it the ellipse SVG would — this keeps the\n * radius proportionate instead.\n */\nexport function fillInPoseFrame(fill: FillStyle, box: FillPoseBox): PosedFill {\n if (!isBuiltinKind(fill)) {\n return (getPaintKind(fill.fill)?.inPoseFrame?.(fill, box) ?? fill) as PosedFill;\n }\n if (isBoundsPattern(fill)) {\n const o = fill.origin ?? { x: 0, y: 0 };\n return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: 'local' as const };\n }\n if (!isBoundsGradient(fill)) return fill as PosedFill;\n\n const toBox = (p: { x: number; y: number }): { x: number; y: number } => ({\n x: box.x + p.x * box.width,\n y: box.y + p.y * box.height,\n });\n\n if (fill.fill === 'linear-gradient') {\n return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: 'local' as const };\n }\n if (fill.fill === 'radial-gradient') {\n const scale = Math.hypot(box.width, box.height) / Math.SQRT2;\n return { ...fill, center: toBox(fill.center), radius: fill.radius * scale, units: 'local' as const };\n }\n return { ...fill, center: toBox(fill.center), units: 'local' as const };\n}\n\n/**\n * Inverse of {@link fillInPoseFrame}: express a gradient already positioned\n * in `box`'s frame as `0..1` fractions of that box (`units: 'bounds'`), so\n * it follows the node when moved or resized.\n *\n * The importer's counterpart — SVG `userSpaceOnUse` coordinates arrive in\n * page space, and normalizing them here makes an imported gradient behave\n * exactly like one drawn in the app.\n *\n * A zero-width or zero-height box has no frame to normalize against; the\n * fill is returned untouched rather than divided by zero.\n */\nexport function fillToBoundsFrame(fill: FillStyle, box: FillPoseBox): FillStyle {\n if (!isBuiltinKind(fill)) {\n return getPaintKind(fill.fill)?.toBoundsFrame?.(fill, box) ?? fill;\n }\n if (fill.fill === 'pattern') {\n const o = fill.origin ?? { x: 0, y: 0 };\n return { ...fill, origin: { x: o.x - box.x, y: o.y - box.y }, units: 'bounds' as const };\n }\n const isGradient = fill.fill === 'linear-gradient'\n || fill.fill === 'radial-gradient'\n || fill.fill === 'conic-gradient';\n if (!isGradient) return fill;\n if (box.width === 0 || box.height === 0) return fill;\n\n const fromBox = (p: { x: number; y: number }): { x: number; y: number } => ({\n x: (p.x - box.x) / box.width,\n y: (p.y - box.y) / box.height,\n });\n\n if (fill.fill === 'linear-gradient') {\n return { ...fill, from: fromBox(fill.from), to: fromBox(fill.to), units: 'bounds' as const };\n }\n if (fill.fill === 'radial-gradient') {\n const scale = Math.hypot(box.width, box.height) / Math.SQRT2;\n return { ...fill, center: fromBox(fill.center), radius: fill.radius / scale, units: 'bounds' as const };\n }\n return { ...fill, center: fromBox(fill.center), units: 'bounds' as const };\n}\n\n/** True when this fill is a pattern anchored to the painted node's box.\n * Rebasing it is a translation only — a resize must reveal more tiles, not\n * stretch them, which is the whole reason patterns get their own branch. */\nfunction isBoundsPattern(\n fill: FillStyle,\n): fill is Extract<FillStyle, { fill: 'pattern' }> {\n return fill.fill === 'pattern' && fill.units === 'bounds';\n}\n\n/** True when this fill is a gradient declaring box-relative geometry. */\nfunction isBoundsGradient(fill: FillStyle | undefined): fill is GradientFill {\n if (fill === undefined) return false;\n const kind = fill.fill;\n if (kind !== 'linear-gradient' && kind !== 'radial-gradient' && kind !== 'conic-gradient') {\n return false;\n }\n return fill.units === 'bounds';\n}\n\n/** The five kinds whose frame conversion is written above. Anything else is a\n * registered kind and converts through its own entry. */\nfunction isBuiltinKind(fill: FillStyle): boolean {\n const kind = fill.fill ?? 'solid';\n return kind === 'solid' || kind === 'pattern'\n || kind === 'linear-gradient' || kind === 'radial-gradient' || kind === 'conic-gradient';\n}\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
- import { axisAlignedBounds as e } from "./unionBounds.js";
1
+ import { poseLinearOf as e } from "./poseFrame.js";
2
+ import { axisAlignedBounds as t } from "./unionBounds.js";
2
3
  //#region src/core/geometry/poseDescriptor.ts
3
- function t(e, t) {
4
+ function n(e, t) {
4
5
  return e.forNode?.(t) ?? e;
5
6
  }
6
- var n = {
7
+ var r = {
7
8
  getBounds: (e) => e,
8
9
  remapBounds: (e, t, n) => {
9
10
  let r = t.width === 0 ? 1 : n.width / t.width, i = t.height === 0 ? 1 : n.height / t.height;
@@ -38,11 +39,11 @@ var n = {
38
39
  ...e,
39
40
  rotation: t
40
41
  })
41
- }, r = {
42
- getBounds: n.getBounds,
43
- remapBounds: n.remapBounds,
44
- translate: n.translate,
45
- lerp: n.lerp,
42
+ }, i = {
43
+ getBounds: r.getBounds,
44
+ remapBounds: r.remapBounds,
45
+ translate: r.translate,
46
+ lerp: r.lerp,
46
47
  fromBounds: (e) => ({
47
48
  x: e.x,
48
49
  y: e.y,
@@ -56,7 +57,7 @@ var n = {
56
57
  rotation: t
57
58
  })
58
59
  };
59
- function i(e, t, n) {
60
+ function a(e, t, n) {
60
61
  let r = t.width === 0 ? 1 : n.width / t.width, i = t.height === 0 ? 1 : n.height / t.height, a = e.rotation ?? 0, o = Math.cos(a), s = Math.sin(a), c = r * o, l = i * s, u = -r * s, d = i * o, f = e.width * Math.hypot(c, l), p = e.height * Math.hypot(u, d), m = Math.atan2(l, c), h = n.x + (e.x + e.width / 2 - t.x) * r, g = n.y + (e.y + e.height / 2 - t.y) * i;
61
62
  return {
62
63
  ...e,
@@ -67,17 +68,18 @@ function i(e, t, n) {
67
68
  rotation: m
68
69
  };
69
70
  }
70
- function a(t, n) {
71
- let r = n.getBounds(t), i = n.getRotation?.(t) ?? r.rotation ?? 0;
72
- return e({
73
- x: r.x,
74
- y: r.y,
75
- width: r.width,
76
- height: r.height,
77
- rotation: i
71
+ function o(n, r) {
72
+ let i = r.getBounds(n), a = r.getRotation?.(n) ?? i.rotation ?? 0, o = e(n) ?? void 0;
73
+ return t({
74
+ x: i.x,
75
+ y: i.y,
76
+ width: i.width,
77
+ height: i.height,
78
+ rotation: a,
79
+ transform: o
78
80
  });
79
81
  }
80
- function o(e, t, n, r) {
82
+ function s(e, t, n, r) {
81
83
  if (r.translate) return r.translate(e, t, n);
82
84
  let i = r.getBounds(e), a = {
83
85
  x: i.x + t,
@@ -88,6 +90,6 @@ function o(e, t, n, r) {
88
90
  return r.remapBounds(e, i, a);
89
91
  }
90
92
  //#endregion
91
- export { n as RECT_POSE_DESCRIPTOR, r as ROTATED_POSE_DESCRIPTOR, t as poseDescriptorForNode, i as remapRotatedLeaf, o as translatePoseViaDescriptor, a as visualBoundsViaDescriptor };
93
+ export { r as RECT_POSE_DESCRIPTOR, i as ROTATED_POSE_DESCRIPTOR, n as poseDescriptorForNode, a as remapRotatedLeaf, s as translatePoseViaDescriptor, o as visualBoundsViaDescriptor };
92
94
 
93
95
  //# sourceMappingURL=poseDescriptor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"poseDescriptor.js","names":["poseDescriptorForNode","descriptor","node","RECT_POSE_DESCRIPTOR","p","sx","src","dst","sy","b","dx","dy","a","t","ROTATED_POSE_DESCRIPTOR","remapRotatedLeaf","theta","pose","cos","sin","ux","uy","vx","vy","width","height","rotation","cxNew","cyNew","visualBoundsViaDescriptor","geometry","axisAlignedBounds","translatePoseViaDescriptor"],"sources":["../../../src/core/geometry/poseDescriptor.ts"],"sourcesContent":["import type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { axisAlignedBounds } from 'core/geometry/unionBounds';\n\n/** `Bounds` with a required rotation angle (radians). Pivot is the AABB\n * center of the unrotated `{x, y, width, height}`. */\nexport interface RotatedPose extends Bounds {\n rotation: number;\n}\n\n/**\n * Bridges arbitrary `TPose` shapes into the resize hook's bounds-driven math.\n * The hook reads bounds via `getBounds`, runs anchor-relative math on those\n * bounds, then asks `remapBounds` to project the result back into TPose.\n *\n * `remapBounds(pose, src, dst)` is a single operation that subsumes both\n * \"set my own AABB to dst\" (single-leaf resize) and \"scale me as a leaf\n * inside parent's src→dst rect\" (group resize) — they're the same affine\n * map. For rect-shaped poses the default geometry interprets the pose as\n * its own bounds; for Path or polygon poses the consumer supplies a\n * projection that knows how to read and rewrite the underlying geometry.\n */\nexport interface PoseDescriptor<TPose, TNode = unknown> {\n getBounds(pose: TPose): Bounds;\n remapBounds(pose: TPose, src: Bounds, dst: Bounds): TPose;\n /** A pose occupying `bounds`, of the same kind as `template`. Unlike\n * `remapBounds`, the result carries none of the template's shape. */\n fromBounds(bounds: Bounds, template: TPose): TPose;\n /** Translate the pose by (dx, dy). Optional — when omitted, callers fall\n * back to a translation derived from `remapBounds` (origin shifted, no\n * scale). Path-shaped poses should provide this for performance. */\n translate?(pose: TPose, dx: number, dy: number): TPose;\n /** Interpolate between two poses. Optional — animation helpers fall back to\n * rect-shape lerp when omitted (which fails for non-rect poses). */\n lerp?(a: TPose, b: TPose, t: number): TPose;\n /** Read the pose's rotation in radians. Pivot is the AABB center\n * (`getBounds(pose)` center). Default 0 when omitted — descriptor\n * declares \"this pose has no rotation.\" When supplied and non-zero,\n * `useResize` projects the drag delta into the leaf's local frame,\n * runs anchor math there, and translates the resulting pose so the\n * diagonally opposite world-space corner is pinned. */\n getRotation?(pose: TPose): number;\n /** True iff this pose shape can carry a rotation. Consulted by the\n * rotation affordance to decide whether to render the rotate cursor /\n * drag-band over a selection. When omitted, the kit assumes `true` for\n * back-compat — descriptors whose poses lack `x/y/width/height/rotation`\n * fields (e.g. polygon Paths) should return `false` so the affordance\n * hides instead of exposing a non-functional rotate cursor. */\n supportsRotation?(pose: TPose): boolean;\n /** Write a rotation (radians) into the pose, bounds unchanged. Absent means\n * the pose cannot carry one, and the rotate action leaves it alone. */\n withRotation?(pose: TPose, rotation: number): TPose;\n /**\n * A descriptor specialized to one node. Every other method here sees only\n * the pose, so shapes sharing a pose type — a sphere and a box both posed by\n * position/rotation/scale — are indistinguishable to them. Implement this to\n * read `node.data` once and hand back a descriptor that knows which shape it\n * is describing.\n *\n * Callers reach it through {@link poseDescriptorForNode}, at whichever site\n * holds the node; the specialized descriptor then travels into the bounds and\n * hit-test helpers in place of this one. Sites with only an id keep using the\n * unspecialized descriptor, which is why this is optional on both ends.\n */\n forNode?(node: TNode): PoseDescriptor<TPose, TNode>;\n}\n\n/** The descriptor `node` should be read through: its own specialization when\n * it declares one, and otherwise the descriptor unchanged. */\nexport function poseDescriptorForNode<TPose, TNode>(\n descriptor: PoseDescriptor<TPose, TNode>,\n node: TNode,\n): PoseDescriptor<TPose, TNode> {\n return descriptor.forNode?.(node) ?? descriptor;\n}\n\n/** Identity geometry for `TPose extends Bounds`. Treats the pose as its\n * own bounds and remaps via affine scale against `src`/`dst`. */\nexport const RECT_POSE_DESCRIPTOR: PoseDescriptor<Bounds> = {\n getBounds: (p) => p,\n remapBounds: (p, src, dst) => {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n return {\n ...p,\n x: dst.x + (p.x - src.x) * sx,\n y: dst.y + (p.y - src.y) * sy,\n width: p.width * sx,\n height: p.height * sy,\n };\n },\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),\n lerp: (a, b, t) => ({\n ...a,\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n }),\n // `Bounds` carries an optional `rotation`, and `withRotation` writes into\n // it — so reading it back has to be the same channel, or a pose reports\n // itself unrotated to `useResize` and to port placement while painting\n // rotated.\n getRotation: (p) => p.rotation ?? 0,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/** Identity geometry for `RotatedPose`. Inherits rect-shape projection\n * from `RECT_POSE_DESCRIPTOR` (the `RotatedPose extends Bounds`\n * subtype lets the rect descriptor's methods apply directly; `remapBounds`\n * preserves the `rotation` field via `...p` spread). Adds `getRotation` so\n * `useResize` knows to take the rotation-aware math path. */\nexport const ROTATED_POSE_DESCRIPTOR: PoseDescriptor<RotatedPose> = {\n getBounds: RECT_POSE_DESCRIPTOR.getBounds as PoseDescriptor<RotatedPose>['getBounds'],\n remapBounds: RECT_POSE_DESCRIPTOR.remapBounds as PoseDescriptor<RotatedPose>['remapBounds'],\n translate: RECT_POSE_DESCRIPTOR.translate as PoseDescriptor<RotatedPose>['translate'],\n lerp: RECT_POSE_DESCRIPTOR.lerp as PoseDescriptor<RotatedPose>['lerp'],\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height, rotation: 0 }),\n getRotation: (p) => p.rotation,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/**\n * Remap a **rotated** leaf inside a group resize.\n *\n * The naive `remapBounds` scales a leaf's axis-aligned `width`/`height` by the\n * group's per-axis factors and carries `rotation` through untouched. That is\n * right when `src`/`dst` are already expressed in the leaf's own frame — the\n * single-leaf resize path, where the drag delta was projected into that frame\n * before the anchor math ran. In a group resize they are world-frame, so the\n * leaf's local axes do not line up with the axes being scaled: at 90° a\n * horizontal stretch should grow the leaf's `height`, and the naive map grows\n * its `width`.\n *\n * What this computes is the group affine applied to the leaf's local frame,\n * with the shear dropped — the pose model is `{x, y, width, height, rotation}`\n * and cannot represent the parallelogram a non-uniform scale of a rotated box\n * actually produces. The center moves exactly; each local axis takes the\n * length of its own image; the rotation follows the image of the local x-axis.\n *\n * Degenerates to the naive map at `rotation === 0`, and to a plain uniform\n * scale when `sx === sy`, both exactly.\n */\nexport function remapRotatedLeaf<TPose extends RotatedPose>(\n pose: TPose,\n src: Bounds,\n dst: Bounds,\n): TPose {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n const theta = pose.rotation ?? 0;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n\n // Images of the leaf's local unit axes under the group's diagonal scale.\n const ux = sx * cos, uy = sy * sin;\n const vx = -sx * sin, vy = sy * cos;\n\n const width = pose.width * Math.hypot(ux, uy);\n const height = pose.height * Math.hypot(vx, vy);\n const rotation = Math.atan2(uy, ux);\n\n // The center is a point, so it takes the group affine directly.\n const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;\n const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;\n\n return {\n ...pose,\n x: cxNew - width / 2,\n y: cyNew - height / 2,\n width,\n height,\n rotation,\n };\n}\n\n/** The pose's visual bounds: its frame expanded to cover the rotated\n * rectangle, so a turned shape reports the extent of its ink. */\nexport function visualBoundsViaDescriptor<TPose>(\n pose: TPose,\n geometry: PoseDescriptor<TPose>,\n): Bounds {\n const b = geometry.getBounds(pose);\n const rotation =\n geometry.getRotation?.(pose) ?? (b as { rotation?: number }).rotation ?? 0;\n return axisAlignedBounds({ x: b.x, y: b.y, width: b.width, height: b.height, rotation });\n}\n\n/** Translate through `geometry.translate`, or via `remapBounds` onto a shifted\n * copy of the pose's own bounds when the descriptor has none. */\nexport function translatePoseViaDescriptor<TPose>(\n pose: TPose,\n dx: number,\n dy: number,\n geometry: PoseDescriptor<TPose>,\n): TPose {\n if (geometry.translate) return geometry.translate(pose, dx, dy);\n const src = geometry.getBounds(pose);\n const dst = { x: src.x + dx, y: src.y + dy, width: src.width, height: src.height };\n return geometry.remapBounds(pose, src, dst);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"poseDescriptor.js","names":["poseDescriptorForNode","descriptor","node","RECT_POSE_DESCRIPTOR","p","sx","src","dst","sy","b","dx","dy","a","t","ROTATED_POSE_DESCRIPTOR","remapRotatedLeaf","theta","pose","cos","sin","ux","uy","vx","vy","width","height","rotation","cxNew","cyNew","visualBoundsViaDescriptor","geometry","transform","poseLinearOf","axisAlignedBounds","translatePoseViaDescriptor"],"sources":["../../../src/core/geometry/poseDescriptor.ts"],"sourcesContent":["import type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { axisAlignedBounds } from 'core/geometry/unionBounds';\nimport { poseLinearOf } from 'core/geometry/poseFrame';\n\n/** `Bounds` with a required rotation angle (radians). Pivot is the AABB\n * center of the unrotated `{x, y, width, height}`. */\nexport interface RotatedPose extends Bounds {\n rotation: number;\n}\n\n/**\n * Bridges arbitrary `TPose` shapes into the resize hook's bounds-driven math.\n * The hook reads bounds via `getBounds`, runs anchor-relative math on those\n * bounds, then asks `remapBounds` to project the result back into TPose.\n *\n * `remapBounds(pose, src, dst)` is a single operation that subsumes both\n * \"set my own AABB to dst\" (single-leaf resize) and \"scale me as a leaf\n * inside parent's src→dst rect\" (group resize) — they're the same affine\n * map. For rect-shaped poses the default geometry interprets the pose as\n * its own bounds; for Path or polygon poses the consumer supplies a\n * projection that knows how to read and rewrite the underlying geometry.\n */\nexport interface PoseDescriptor<TPose, TNode = unknown> {\n getBounds(pose: TPose): Bounds;\n remapBounds(pose: TPose, src: Bounds, dst: Bounds): TPose;\n /** A pose occupying `bounds`, of the same kind as `template`. Unlike\n * `remapBounds`, the result carries none of the template's shape. */\n fromBounds(bounds: Bounds, template: TPose): TPose;\n /** Translate the pose by (dx, dy). Optional — when omitted, callers fall\n * back to a translation derived from `remapBounds` (origin shifted, no\n * scale). Path-shaped poses should provide this for performance. */\n translate?(pose: TPose, dx: number, dy: number): TPose;\n /** Interpolate between two poses. Optional — animation helpers fall back to\n * rect-shape lerp when omitted (which fails for non-rect poses). */\n lerp?(a: TPose, b: TPose, t: number): TPose;\n /** Read the pose's rotation in radians. Pivot is the AABB center\n * (`getBounds(pose)` center). Default 0 when omitted — descriptor\n * declares \"this pose has no rotation.\" When supplied and non-zero,\n * `useResize` projects the drag delta into the leaf's local frame,\n * runs anchor math there, and translates the resulting pose so the\n * diagonally opposite world-space corner is pinned. */\n getRotation?(pose: TPose): number;\n /** True iff this pose shape can carry a rotation. Consulted by the\n * rotation affordance to decide whether to render the rotate cursor /\n * drag-band over a selection. When omitted, the kit assumes `true` for\n * back-compat — descriptors whose poses lack `x/y/width/height/rotation`\n * fields (e.g. polygon Paths) should return `false` so the affordance\n * hides instead of exposing a non-functional rotate cursor. */\n supportsRotation?(pose: TPose): boolean;\n /** Write a rotation (radians) into the pose, bounds unchanged. Absent means\n * the pose cannot carry one, and the rotate action leaves it alone. */\n withRotation?(pose: TPose, rotation: number): TPose;\n /**\n * A descriptor specialized to one node. Every other method here sees only\n * the pose, so shapes sharing a pose type — a sphere and a box both posed by\n * position/rotation/scale — are indistinguishable to them. Implement this to\n * read `node.data` once and hand back a descriptor that knows which shape it\n * is describing.\n *\n * Callers reach it through {@link poseDescriptorForNode}, at whichever site\n * holds the node; the specialized descriptor then travels into the bounds and\n * hit-test helpers in place of this one. Sites with only an id keep using the\n * unspecialized descriptor, which is why this is optional on both ends.\n */\n forNode?(node: TNode): PoseDescriptor<TPose, TNode>;\n}\n\n/** The descriptor `node` should be read through: its own specialization when\n * it declares one, and otherwise the descriptor unchanged. */\nexport function poseDescriptorForNode<TPose, TNode>(\n descriptor: PoseDescriptor<TPose, TNode>,\n node: TNode,\n): PoseDescriptor<TPose, TNode> {\n return descriptor.forNode?.(node) ?? descriptor;\n}\n\n/** Identity geometry for `TPose extends Bounds`. Treats the pose as its\n * own bounds and remaps via affine scale against `src`/`dst`. */\nexport const RECT_POSE_DESCRIPTOR: PoseDescriptor<Bounds> = {\n getBounds: (p) => p,\n remapBounds: (p, src, dst) => {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n return {\n ...p,\n x: dst.x + (p.x - src.x) * sx,\n y: dst.y + (p.y - src.y) * sy,\n width: p.width * sx,\n height: p.height * sy,\n };\n },\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height }),\n translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),\n lerp: (a, b, t) => ({\n ...a,\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n width: a.width + (b.width - a.width) * t,\n height: a.height + (b.height - a.height) * t,\n }),\n // `Bounds` carries an optional `rotation`, and `withRotation` writes into\n // it — so reading it back has to be the same channel, or a pose reports\n // itself unrotated to `useResize` and to port placement while painting\n // rotated.\n getRotation: (p) => p.rotation ?? 0,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/** Identity geometry for `RotatedPose`. Inherits rect-shape projection\n * from `RECT_POSE_DESCRIPTOR` (the `RotatedPose extends Bounds`\n * subtype lets the rect descriptor's methods apply directly; `remapBounds`\n * preserves the `rotation` field via `...p` spread). Adds `getRotation` so\n * `useResize` knows to take the rotation-aware math path. */\nexport const ROTATED_POSE_DESCRIPTOR: PoseDescriptor<RotatedPose> = {\n getBounds: RECT_POSE_DESCRIPTOR.getBounds as PoseDescriptor<RotatedPose>['getBounds'],\n remapBounds: RECT_POSE_DESCRIPTOR.remapBounds as PoseDescriptor<RotatedPose>['remapBounds'],\n translate: RECT_POSE_DESCRIPTOR.translate as PoseDescriptor<RotatedPose>['translate'],\n lerp: RECT_POSE_DESCRIPTOR.lerp as PoseDescriptor<RotatedPose>['lerp'],\n fromBounds: (b) => ({ x: b.x, y: b.y, width: b.width, height: b.height, rotation: 0 }),\n getRotation: (p) => p.rotation,\n withRotation: (p, rotation) => ({ ...p, rotation }),\n};\n\n/**\n * Remap a **rotated** leaf inside a group resize.\n *\n * The naive `remapBounds` scales a leaf's axis-aligned `width`/`height` by the\n * group's per-axis factors and carries `rotation` through untouched. That is\n * right when `src`/`dst` are already expressed in the leaf's own frame — the\n * single-leaf resize path, where the drag delta was projected into that frame\n * before the anchor math ran. In a group resize they are world-frame, so the\n * leaf's local axes do not line up with the axes being scaled: at 90° a\n * horizontal stretch should grow the leaf's `height`, and the naive map grows\n * its `width`.\n *\n * What this computes is the group affine applied to the leaf's local frame,\n * with the shear dropped — the pose model is `{x, y, width, height, rotation}`\n * and cannot represent the parallelogram a non-uniform scale of a rotated box\n * actually produces. The center moves exactly; each local axis takes the\n * length of its own image; the rotation follows the image of the local x-axis.\n *\n * Degenerates to the naive map at `rotation === 0`, and to a plain uniform\n * scale when `sx === sy`, both exactly.\n */\nexport function remapRotatedLeaf<TPose extends RotatedPose>(\n pose: TPose,\n src: Bounds,\n dst: Bounds,\n): TPose {\n const sx = src.width === 0 ? 1 : dst.width / src.width;\n const sy = src.height === 0 ? 1 : dst.height / src.height;\n const theta = pose.rotation ?? 0;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n\n // Images of the leaf's local unit axes under the group's diagonal scale.\n const ux = sx * cos, uy = sy * sin;\n const vx = -sx * sin, vy = sy * cos;\n\n const width = pose.width * Math.hypot(ux, uy);\n const height = pose.height * Math.hypot(vx, vy);\n const rotation = Math.atan2(uy, ux);\n\n // The center is a point, so it takes the group affine directly.\n const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;\n const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;\n\n return {\n ...pose,\n x: cxNew - width / 2,\n y: cyNew - height / 2,\n width,\n height,\n rotation,\n };\n}\n\n/** The pose's visual bounds: its box expanded to cover the box as drawn\n * under its rotation and `transform`, so a turned or skewed shape reports\n * the extent of its ink. */\nexport function visualBoundsViaDescriptor<TPose>(\n pose: TPose,\n geometry: PoseDescriptor<TPose>,\n): Bounds {\n const b = geometry.getBounds(pose);\n const rotation =\n geometry.getRotation?.(pose) ?? (b as { rotation?: number }).rotation ?? 0;\n const transform = poseLinearOf(pose) ?? undefined;\n return axisAlignedBounds({ x: b.x, y: b.y, width: b.width, height: b.height, rotation, transform });\n}\n\n/** Translate through `geometry.translate`, or via `remapBounds` onto a shifted\n * copy of the pose's own bounds when the descriptor has none. */\nexport function translatePoseViaDescriptor<TPose>(\n pose: TPose,\n dx: number,\n dy: number,\n geometry: PoseDescriptor<TPose>,\n): TPose {\n if (geometry.translate) return geometry.translate(pose, dx, dy);\n const src = geometry.getBounds(pose);\n const dst = { x: src.x + dx, y: src.y + dy, width: src.width, height: src.height };\n return geometry.remapBounds(pose, src, dst);\n}\n"],"mappings":"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"}