@weasel-js/labkit 1.3.0-pre.0 → 1.4.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/_dts/CanvasStackContext-DtAn_3O-.d.ts +42 -0
  2. package/dist/_dts/DrawCommand-B3bskUsC.d.ts +564 -0
  3. package/dist/_dts/{PrefsForm-BYa6cWnO.d.ts → PrefsForm-BkUJZx0A.d.ts} +4 -1
  4. package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +21 -0
  5. package/dist/_dts/frac-Cp3NivlC.d.ts +728 -0
  6. package/dist/_dts/index-BDVzvRzQ.d.ts +236 -0
  7. package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +87 -0
  8. package/dist/_dts/{DrawCommand-DBB45NfN.d.ts → types-C-gh9Ap-.d.ts} +1 -582
  9. package/dist/_dts/{types-AHlQxBNN.d.ts → types-D6s4b7if.d.ts} +2 -2
  10. package/dist/_dts/{types-1Sdxy_Pv.d.ts → types-DYMaEvM5.d.ts} +26 -1
  11. package/dist/_dts/{useTrialState-CsGMjhu9.d.ts → useTrialState-gmMvZqPc.d.ts} +7 -2
  12. package/dist/canvas/index.d.ts +43 -25
  13. package/dist/canvas/index.js +5 -3
  14. package/dist/chrome/index.d.ts +9 -6
  15. package/dist/chrome/index.js +7 -6
  16. package/dist/chunk-2AYGEN57.js +32 -0
  17. package/dist/chunk-2AYGEN57.js.map +1 -0
  18. package/dist/{chunk-RVNN4CHQ.js → chunk-3P5TTTA7.js} +16 -8
  19. package/dist/chunk-3P5TTTA7.js.map +1 -0
  20. package/dist/{chunk-NS54R52R.js → chunk-3ZELRRBV.js} +45 -40
  21. package/dist/chunk-3ZELRRBV.js.map +1 -0
  22. package/dist/{chunk-HFDVGF4X.js → chunk-4EY67BKJ.js} +32 -7
  23. package/dist/chunk-4EY67BKJ.js.map +1 -0
  24. package/dist/chunk-5TCEVMPI.js +140 -0
  25. package/dist/chunk-5TCEVMPI.js.map +1 -0
  26. package/dist/{chunk-O7NVSCLN.js → chunk-6JMU56CU.js} +133 -134
  27. package/dist/chunk-6JMU56CU.js.map +1 -0
  28. package/dist/{chunk-CPUJ3QXL.js → chunk-CQLQPQ4P.js} +2 -2
  29. package/dist/chunk-CQLQPQ4P.js.map +1 -0
  30. package/dist/chunk-CTRKTLYZ.js +33 -0
  31. package/dist/chunk-CTRKTLYZ.js.map +1 -0
  32. package/dist/chunk-EQIB2PGC.js +458 -0
  33. package/dist/chunk-EQIB2PGC.js.map +1 -0
  34. package/dist/chunk-HEORBLVT.js +7128 -0
  35. package/dist/chunk-HEORBLVT.js.map +1 -0
  36. package/dist/{chunk-V7PRSIRQ.js → chunk-JDDIZSLL.js} +4 -4
  37. package/dist/chunk-JDDIZSLL.js.map +1 -0
  38. package/dist/chunk-LITE5YFB.js +669 -0
  39. package/dist/chunk-LITE5YFB.js.map +1 -0
  40. package/dist/{chunk-Z6HJ5FEM.js → chunk-ROQDRXKW.js} +732 -315
  41. package/dist/chunk-ROQDRXKW.js.map +1 -0
  42. package/dist/chunk-SBXNQW4G.js +3 -0
  43. package/dist/chunk-SBXNQW4G.js.map +1 -0
  44. package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
  45. package/dist/chunk-XJ6N32QP.js.map +1 -0
  46. package/dist/chunk-YQJNT4XQ.js +293 -0
  47. package/dist/chunk-YQJNT4XQ.js.map +1 -0
  48. package/dist/controls/index.d.ts +4 -28
  49. package/dist/controls/index.js +3 -3
  50. package/dist/dragdrop/index.d.ts +50 -5
  51. package/dist/dragdrop/index.js +3 -1
  52. package/dist/index.d.ts +660 -302
  53. package/dist/index.js +1793 -587
  54. package/dist/index.js.map +1 -1
  55. package/dist/layers/index.d.ts +6 -5
  56. package/dist/layers/index.js +4 -2
  57. package/dist/loupe/index.d.ts +187 -0
  58. package/dist/loupe/index.js +7 -0
  59. package/dist/loupe/index.js.map +1 -0
  60. package/dist/passthrough/weasel-canvas.d.ts +3 -1
  61. package/dist/passthrough/weasel-canvas.js +1 -1
  62. package/dist/passthrough/weasel-ui.d.ts +151 -91
  63. package/dist/passthrough/weasel-ui.js +3 -2
  64. package/dist/primitives/index.d.ts +3 -1
  65. package/dist/primitives/index.js +6 -5
  66. package/dist/state/index.d.ts +3 -3
  67. package/dist/state/index.js +2 -2
  68. package/dist/styles.css +229 -703
  69. package/dist/surface/index.d.ts +18 -2
  70. package/dist/surface/index.js +2 -2
  71. package/dist/ui/layers/index.d.ts +8 -2
  72. package/dist/ui/layers/index.js +2 -4
  73. package/dist/undo/index.d.ts +5 -4
  74. package/package.json +14 -7
  75. package/src/annotations/AnnotationOverlay.tsx +205 -0
  76. package/src/annotations/AnnotationTargets.tsx +37 -0
  77. package/src/annotations/Annotations.less +129 -0
  78. package/src/annotations/Annotations.meaning.test.tsx +111 -0
  79. package/src/annotations/Annotations.overlay.test.tsx +142 -0
  80. package/src/annotations/AnnotationsContext.ts +49 -0
  81. package/src/annotations/ExportMenu.test.tsx +86 -0
  82. package/src/annotations/ExportMenu.tsx +182 -0
  83. package/src/annotations/MarkList.tsx +75 -0
  84. package/src/annotations/capture.test.ts +140 -0
  85. package/src/annotations/capture.ts +237 -0
  86. package/src/annotations/drawOne.test.ts +56 -0
  87. package/src/annotations/drawOne.ts +47 -0
  88. package/src/annotations/frac.test.ts +59 -0
  89. package/src/annotations/frac.ts +66 -0
  90. package/src/annotations/history.test.ts +82 -0
  91. package/src/annotations/history.ts +118 -0
  92. package/src/annotations/index.ts +37 -0
  93. package/src/annotations/paint.test.ts +125 -0
  94. package/src/annotations/paint.ts +130 -0
  95. package/src/annotations/staleness.test.ts +56 -0
  96. package/src/annotations/staleness.ts +41 -0
  97. package/src/annotations/store.test.ts +175 -0
  98. package/src/annotations/store.ts +317 -0
  99. package/src/annotations/svgNodes.test.ts +77 -0
  100. package/src/annotations/svgNodes.ts +74 -0
  101. package/src/annotations/toolMap.test.ts +36 -0
  102. package/src/annotations/toolMap.ts +54 -0
  103. package/src/annotations/types.ts +222 -0
  104. package/src/annotations/view.test.ts +37 -0
  105. package/src/annotations/view.ts +40 -0
  106. package/src/canvas/AGENTS.md +55 -5
  107. package/src/canvas/CanvasStack.test.tsx +27 -1
  108. package/src/canvas/CanvasStack.tsx +29 -4
  109. package/src/canvas/CanvasStackContext.ts +23 -4
  110. package/src/canvas/camera.test.ts +78 -0
  111. package/src/canvas/camera.ts +62 -0
  112. package/src/canvas/canvasCoords.test.ts +26 -0
  113. package/src/canvas/canvasCoords.ts +20 -7
  114. package/src/canvas/index.ts +5 -1
  115. package/src/canvas/useLayerScheduler.ts +7 -3
  116. package/src/canvas/usePanZoom.test.ts +36 -5
  117. package/src/canvas/usePanZoom.ts +20 -15
  118. package/src/canvas/worldSpec.test.ts +63 -0
  119. package/src/canvas/worldSpec.ts +48 -0
  120. package/src/chrome/builtins.test.ts +31 -1
  121. package/src/chrome/builtins.tsx +63 -19
  122. package/src/chrome/regions/PaletteRegion.test.tsx +22 -1
  123. package/src/chrome/regions/PaletteRegion.tsx +4 -0
  124. package/src/chrome/regions/SidebarRegion.tsx +37 -13
  125. package/src/chrome/regions/ToolbarRegion.tsx +2 -1
  126. package/src/chrome/regions/ViewportRegion.tsx +9 -1
  127. package/src/chrome/regions/regions.test.tsx +6 -1
  128. package/src/chrome/types.ts +20 -0
  129. package/src/config/builder.test.ts +7 -1
  130. package/src/config/builder.ts +1 -5
  131. package/src/config/resolve.test.ts +1 -2
  132. package/src/controls/ControlPanel.stories.tsx +41 -2
  133. package/src/controls/ControlPanel.test.tsx +69 -9
  134. package/src/controls/ControlPanel.tsx +106 -25
  135. package/src/dragdrop/DragDropRuntime.tsx +8 -2
  136. package/src/dragdrop/dragDrop.test.tsx +29 -2
  137. package/src/dragdrop/index.ts +11 -0
  138. package/src/index.test.ts +22 -0
  139. package/src/index.ts +136 -4
  140. package/src/instrument/SineWave.smoke.test.tsx +1 -0
  141. package/src/instrument/types.ts +24 -1
  142. package/src/lab/Lab.surface.test.tsx +162 -0
  143. package/src/lab/Lab.tsx +102 -17
  144. package/src/lab/Lab.undock.test.tsx +85 -0
  145. package/src/lab/LabFullChrome.stories.tsx +20 -0
  146. package/src/lab/LabShell.less +11 -0
  147. package/src/lab/Workspace.less +73 -0
  148. package/src/lab/Workspace.surface.test.tsx +2 -0
  149. package/src/lab/Workspace.tsx +97 -8
  150. package/src/lab/index.ts +1 -1
  151. package/src/lab/panelHost.ts +37 -0
  152. package/src/layers/AGENTS.md +1 -1
  153. package/src/layers/LayerList.tsx +1 -1
  154. package/src/loupe/AGENTS.md +49 -0
  155. package/src/loupe/CanvasLoupe.tsx +83 -0
  156. package/src/loupe/DomLoupe.tsx +62 -0
  157. package/src/loupe/Loupe.less +38 -0
  158. package/src/loupe/LoupeBubble.tsx +32 -0
  159. package/src/loupe/TrialLoupe.tsx +93 -0
  160. package/src/loupe/canvasLens.test.ts +205 -0
  161. package/src/loupe/canvasLens.ts +141 -0
  162. package/src/loupe/index.ts +19 -0
  163. package/src/loupe/types.test.ts +29 -0
  164. package/src/loupe/types.ts +90 -0
  165. package/src/loupe/useHostSize.ts +30 -0
  166. package/src/loupe/useLoupe.test.tsx +197 -0
  167. package/src/loupe/useLoupe.ts +189 -0
  168. package/src/passthrough/weasel-ui.ts +25 -17
  169. package/src/primitives/FloatingPanel.test.tsx +76 -1
  170. package/src/primitives/FloatingPanel.tsx +19 -3
  171. package/src/primitives/ScaleIndicator.test.tsx +7 -2
  172. package/src/primitives/Sidebar.less +18 -0
  173. package/src/primitives/Toolbar.less +8 -0
  174. package/src/primitives/Toolbar.tsx +5 -0
  175. package/src/primitives/useRovingTabIndex.test.ts +21 -0
  176. package/src/primitives/useRovingTabIndex.ts +26 -17
  177. package/src/state/document.test.ts +23 -0
  178. package/src/state/document.ts +14 -2
  179. package/src/state/index.ts +2 -0
  180. package/src/state/store.test.ts +24 -0
  181. package/src/state/store.ts +35 -1
  182. package/src/state/types.ts +11 -0
  183. package/src/state/undock.test.ts +40 -0
  184. package/src/state/undock.ts +39 -0
  185. package/src/styles.less +2 -4
  186. package/src/surface/SurfaceContext.ts +4 -0
  187. package/src/surface/index.ts +8 -3
  188. package/src/surface/useSurfaceTile.test.tsx +2 -0
  189. package/src/surface/useSurfaceTile.ts +7 -1
  190. package/src/surface/useTiledSurface.test.tsx +58 -0
  191. package/src/surface/useTiledSurface.ts +38 -3
  192. package/src/theme/Interstellar.stories.tsx +1 -1
  193. package/src/trial/Trial.annotations.persist.test.tsx +166 -0
  194. package/src/trial/Trial.annotations.test.tsx +121 -0
  195. package/src/trial/Trial.canvas.test.tsx +76 -1
  196. package/src/trial/Trial.config.test.tsx +3 -8
  197. package/src/trial/Trial.less +19 -0
  198. package/src/trial/Trial.loupe.test.tsx +128 -0
  199. package/src/trial/Trial.stories.tsx +1 -0
  200. package/src/trial/Trial.test.tsx +12 -0
  201. package/src/trial/Trial.trialId.test.tsx +36 -0
  202. package/src/trial/Trial.tsx +215 -40
  203. package/src/trial/TrialChrome.tsx +35 -0
  204. package/src/trial/UndockedSections.tsx +65 -0
  205. package/src/trial/trialOps.test.ts +21 -0
  206. package/src/trial/trialOps.ts +5 -3
  207. package/src/ui/layers/index.ts +5 -1
  208. package/dist/_dts/index-DgcNdEdh.d.ts +0 -314
  209. package/dist/chunk-4TMMVIDM.js.map +0 -1
  210. package/dist/chunk-CPUJ3QXL.js.map +0 -1
  211. package/dist/chunk-HFDVGF4X.js.map +0 -1
  212. package/dist/chunk-NS54R52R.js.map +0 -1
  213. package/dist/chunk-O2BULFHX.js +0 -152
  214. package/dist/chunk-O2BULFHX.js.map +0 -1
  215. package/dist/chunk-O7NVSCLN.js.map +0 -1
  216. package/dist/chunk-PO6L3NSH.js +0 -366
  217. package/dist/chunk-PO6L3NSH.js.map +0 -1
  218. package/dist/chunk-PQJ5B2U2.js +0 -27
  219. package/dist/chunk-PQJ5B2U2.js.map +0 -1
  220. package/dist/chunk-RVNN4CHQ.js.map +0 -1
  221. package/dist/chunk-TLGRYALP.js +0 -6151
  222. package/dist/chunk-TLGRYALP.js.map +0 -1
  223. package/dist/chunk-V7PRSIRQ.js.map +0 -1
  224. package/dist/chunk-Z6HJ5FEM.js.map +0 -1
  225. package/dist/chunk-ZZAYVX4X.js +0 -505
  226. package/dist/chunk-ZZAYVX4X.js.map +0 -1
  227. package/src/primitives/DragHandleGlyph.tsx +0 -29
  228. package/src/ui/format.test.ts +0 -33
  229. package/src/ui/format.ts +0 -30
  230. package/src/ui/layers/LayerStack.less +0 -143
  231. package/src/ui/layers/LayerStack.stories.tsx +0 -45
  232. package/src/ui/layers/LayerStack.test.tsx +0 -170
  233. package/src/ui/layers/LayerStack.tsx +0 -208
  234. package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
  235. package/src/ui/properties/ColorRow.stories.tsx +0 -106
  236. package/src/ui/properties/CurveField.less +0 -48
  237. package/src/ui/properties/CurveField.stories.tsx +0 -20
  238. package/src/ui/properties/CurveField.test.tsx +0 -127
  239. package/src/ui/properties/CurveField.tsx +0 -184
  240. package/src/ui/properties/EffectCard.tsx +0 -324
  241. package/src/ui/properties/Gallery.stories.tsx +0 -178
  242. package/src/ui/properties/NumberRow.stories.tsx +0 -65
  243. package/src/ui/properties/PropertyGroup.less +0 -50
  244. package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
  245. package/src/ui/properties/PropertyGroup.test.tsx +0 -33
  246. package/src/ui/properties/PropertyGroup.tsx +0 -45
  247. package/src/ui/properties/PropertyList.stories.tsx +0 -83
  248. package/src/ui/properties/PropertyPanel.less +0 -606
  249. package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
  250. package/src/ui/properties/PropertyPanel.test.tsx +0 -193
  251. package/src/ui/properties/PropertyPanel.tsx +0 -469
  252. package/src/ui/properties/PropertyRow.stories.tsx +0 -53
  253. package/src/ui/properties/SelectRow.stories.tsx +0 -58
  254. package/src/ui/properties/SliderRow.stories.tsx +0 -74
  255. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -398
  256. package/src/ui/properties/TextRow.stories.tsx +0 -62
  257. package/src/ui/properties/ToggleRow.stories.tsx +0 -57
  258. package/src/ui/properties/index.ts +0 -39
  259. package/src/ui/properties/storyLayouts.tsx +0 -36
@@ -3,7 +3,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import earcut from 'earcut';
4
4
  import polygonClipping from 'polygon-clipping';
5
5
 
6
- // ../core/dist/chunk-3ZV53ZW2.js
6
+ // ../core/dist/chunk-H6E6KPTU.js
7
7
 
8
8
  // ../gestures/dist/index.js
9
9
  var GESTURE_DESCRIPTORS = [
@@ -287,7 +287,7 @@ function matchSpec(e, spec, isMac, phaseCtx) {
287
287
  }
288
288
  var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
289
289
 
290
- // ../core/dist/chunk-3ZV53ZW2.js
290
+ // ../core/dist/chunk-H6E6KPTU.js
291
291
  var DepRegistryContext = createContext(null);
292
292
  function DepRegistryProvider({ children }) {
293
293
  const sourcesRef = useRef(/* @__PURE__ */ new Map());
@@ -428,6 +428,12 @@ function validateActionDefaultBinding(action) {
428
428
  }
429
429
  }
430
430
  }
431
+ function warnSharedScope() {
432
+ if (!IS_DEV) return;
433
+ console.warn(
434
+ "weasel: a second dispatcher claimed this <ActionsProvider>, so only the newest <SceneCanvas> under it will respond to input. Give each canvas its own scope with <WeaselProvider isolate>."
435
+ );
436
+ }
431
437
  var ActionsContext = createContext(null);
432
438
  function ActionsProvider({ children }) {
433
439
  const actionsRef = useRef(/* @__PURE__ */ new Map());
@@ -438,6 +444,7 @@ function ActionsProvider({ children }) {
438
444
  const depReg = useOptionalDepRegistry();
439
445
  const depRegRef = useRef(depReg);
440
446
  depRegRef.current = depReg;
447
+ const warnedRef = useRef(false);
441
448
  const wiredDepRegRef = useRef(null);
442
449
  const dispatcherRef = useRef(null);
443
450
  const registry4 = useMemo(() => {
@@ -510,10 +517,20 @@ function ActionsProvider({ children }) {
510
517
  };
511
518
  },
512
519
  setDispatcher: (d) => {
520
+ if (d && dispatcherRef.current && dispatcherRef.current !== d && !warnedRef.current) {
521
+ warnedRef.current = true;
522
+ warnSharedScope();
523
+ }
513
524
  dispatcherRef.current = d;
525
+ return () => {
526
+ if (dispatcherRef.current === d) dispatcherRef.current = null;
527
+ };
514
528
  },
515
529
  setDepRegistry: (r) => {
516
530
  wiredDepRegRef.current = r;
531
+ return () => {
532
+ if (wiredDepRegRef.current === r) wiredDepRegRef.current = null;
533
+ };
517
534
  },
518
535
  begin: (id, params) => {
519
536
  const disp = dispatcherRef.current;
@@ -535,7 +552,7 @@ function ActionsProvider({ children }) {
535
552
  }, []);
536
553
  return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry4, children });
537
554
  }
538
- (() => {
555
+ var IS_DEV = (() => {
539
556
  try {
540
557
  return Boolean(import.meta.env?.DEV);
541
558
  } catch {
@@ -879,8 +896,19 @@ function transformCoords(coords, m) {
879
896
  }
880
897
  return out;
881
898
  }
899
+ function clampAxis(start, size, low, extent, padding) {
900
+ const min = low + padding;
901
+ return Math.max(min, Math.min(start, min + extent - padding * 2 - size));
902
+ }
903
+ function clampRectWithin(rect, boundary, padding = 0) {
904
+ return {
905
+ ...rect,
906
+ x: clampAxis(rect.x, rect.width, boundary.x, boundary.width, padding),
907
+ y: clampAxis(rect.y, rect.height, boundary.y, boundary.height, padding)
908
+ };
909
+ }
882
910
 
883
- // ../core/dist/chunk-F74NVEHT.js
911
+ // ../core/dist/chunk-SBFC6J3C.js
884
912
  function resolveUnit(v, unitSystem) {
885
913
  if (typeof v === "number") return v;
886
914
  if (!unitSystem) {
@@ -1315,6 +1343,17 @@ var PathBuilder = class _PathBuilder {
1315
1343
  function rectPath(x, y, width, height) {
1316
1344
  return { kind: "rect", x, y, width, height };
1317
1345
  }
1346
+ function polylineFromPoints(points) {
1347
+ const b = new PathBuilder();
1348
+ const first = points[0];
1349
+ if (!first) return rectPath(0, 0, 0, 0);
1350
+ b.moveTo(first.x, first.y);
1351
+ for (let i = 1; i < points.length; i++) {
1352
+ const p = points[i];
1353
+ if (p) b.lineTo(p.x, p.y);
1354
+ }
1355
+ return b.build();
1356
+ }
1318
1357
  function polygonFromPoints(points, opts = {}) {
1319
1358
  if (points.length === 0) {
1320
1359
  return {
@@ -1690,7 +1729,7 @@ function createDeleteOp(args) {
1690
1729
  }
1691
1730
  registerOpFactory("delete", (args) => createDeleteOp(args));
1692
1731
 
1693
- // ../core/dist/chunk-AQS4JGRA.js
1732
+ // ../core/dist/chunk-Y5KS66G7.js
1694
1733
  function createTransformOp(args) {
1695
1734
  const { id, from, to, label, coalesceKey = `transform:${id}` } = args;
1696
1735
  return {
@@ -1814,7 +1853,7 @@ function snap(strategy, opts = {}) {
1814
1853
  };
1815
1854
  }
1816
1855
 
1817
- // ../core/dist/chunk-3FB44DBC.js
1856
+ // ../core/dist/chunk-BLJNRMKB.js
1818
1857
  function lockAspectWithModifier(opts = {}) {
1819
1858
  const { key = "shift" } = opts;
1820
1859
  return {
@@ -2168,7 +2207,7 @@ function chunks(params) {
2168
2207
  return build({ tile: "chunks", ...params });
2169
2208
  }
2170
2209
 
2171
- // ../font/dist/chunk-XK73IOFB.js
2210
+ // ../font/dist/chunk-NRVRDX3F.js
2172
2211
  var subscribers = /* @__PURE__ */ new Set();
2173
2212
  var generation = 0;
2174
2213
  function glyphGeneration() {
@@ -2929,7 +2968,7 @@ var TEXT_SDF_UNIFORMS = [
2929
2968
  ];
2930
2969
  var TEXT_SDF_ATTRIBUTES = ["a_position", "a_uv", "a_baselineY"];
2931
2970
 
2932
- // ../text/dist/chunk-FCHCIQYT.js
2971
+ // ../text/dist/chunk-BYL6KOD4.js
2933
2972
  function resolveAlign(align, direction) {
2934
2973
  if (align === "start") return direction === "rtl" ? "right" : "left";
2935
2974
  if (align === "end") return direction === "rtl" ? "left" : "right";
@@ -3108,7 +3147,9 @@ function warnLogicalRtlOnce() {
3108
3147
  if (warnedLogicalRtl) return;
3109
3148
  warnedLogicalRtl = true;
3110
3149
  console.warn(
3111
- 'weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine was supplied, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Pass `bidi` from "@weasel-js/bidi": `import { bidi } from "@weasel-js/bidi"` and add it to the layout options.'
3150
+ // Naming the import in `from "<pkg>"` form would trip labkit's
3151
+ // consumer smoke test, which greps dist for that exact shape.
3152
+ "weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine was supplied, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and add it to the layout options."
3112
3153
  );
3113
3154
  }
3114
3155
  var warnedMissingGlyphs = /* @__PURE__ */ new Set();
@@ -3121,6 +3162,15 @@ function warnMissingGlyphOnce(family, cp) {
3121
3162
  `weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
3122
3163
  );
3123
3164
  }
3165
+ var warnedNoMetrics = /* @__PURE__ */ new Set();
3166
+ function warnNoMetricsOnce(family, weight, style) {
3167
+ const key = `${family}|${weight}|${style}`;
3168
+ if (warnedNoMetrics.has(key)) return;
3169
+ warnedNoMetrics.add(key);
3170
+ console.warn(
3171
+ `weasel layoutRuns: no metrics for "${family}" ${weight}/${style} \u2014 neither an atlas nor an outline face resolved, so every run set in it lays out as nothing. Register it with registerFont / registerCanvasFont / registerFontOutlines. If you did, check for a duplicated @weasel-js/font in node_modules \u2014 its registry is module state, so a second copy is a second, empty one.`
3172
+ );
3173
+ }
3124
3174
  function layoutRuns(runs, opts) {
3125
3175
  const ctx = { groups: /* @__PURE__ */ new Map() };
3126
3176
  const align = resolveAlign(opts.align, opts.direction ?? "ltr");
@@ -3135,6 +3185,7 @@ function layoutRuns(runs, opts) {
3135
3185
  const font = resolved.entry?.font ?? resolved.dynamicFace?.font ?? null;
3136
3186
  const metrics = outlineFace ? { size: 1, base: outlineFace.ascender, advanceOf: (cp) => outlineFace.advanceOf(cp), kernOf: (l, r) => outlineFace.kernOf(l, r) } : font ? atlasMetrics(font) : void 0;
3137
3187
  if (!metrics) {
3188
+ warnNoMetricsOnce(run.fontFamily, run.fontWeight, run.fontStyle);
3138
3189
  srcIndex += run.text.length;
3139
3190
  prevCp = void 0;
3140
3191
  prevMetrics = void 0;
@@ -3551,7 +3602,7 @@ function layoutRuns(runs, opts) {
3551
3602
  cells,
3552
3603
  srcEnd
3553
3604
  });
3554
- maxLineWidth = Math.max(maxLineWidth, line.width);
3605
+ maxLineWidth = Math.max(maxLineWidth, inkWidth);
3555
3606
  penY += line.height;
3556
3607
  }
3557
3608
  return {
@@ -4212,7 +4263,7 @@ function serialToEntry(se, custom, logger) {
4212
4263
  };
4213
4264
  }
4214
4265
 
4215
- // ../core/dist/chunk-IFOVTYAK.js
4266
+ // ../core/dist/chunk-IMFXEINV.js
4216
4267
  function worldToScreen(worldX, worldY, view) {
4217
4268
  return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
4218
4269
  }
@@ -5539,6 +5590,9 @@ function flattenPolygon(p, tolerance) {
5539
5590
  let coordIdx = 0;
5540
5591
  let prevX = 0;
5541
5592
  let prevY = 0;
5593
+ let startX = 0;
5594
+ let startY = 0;
5595
+ let startAnchor = -1;
5542
5596
  let prevAnchor = -1;
5543
5597
  let anchorCounter = 0;
5544
5598
  for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
@@ -5548,6 +5602,9 @@ function flattenPolygon(p, tolerance) {
5548
5602
  contourStarts.push(out.length / 2);
5549
5603
  prevX = coords[coordIdx];
5550
5604
  prevY = coords[coordIdx + 1];
5605
+ startX = prevX;
5606
+ startY = prevY;
5607
+ startAnchor = anchorCounter;
5551
5608
  out.push(prevX, prevY);
5552
5609
  anchorA.push(anchorCounter);
5553
5610
  anchorB.push(anchorCounter);
@@ -5617,6 +5674,9 @@ function flattenPolygon(p, tolerance) {
5617
5674
  break;
5618
5675
  }
5619
5676
  case PATH_Z: {
5677
+ prevX = startX;
5678
+ prevY = startY;
5679
+ prevAnchor = startAnchor;
5620
5680
  break;
5621
5681
  }
5622
5682
  default:
@@ -8432,6 +8492,7 @@ var WeaselRenderer = class {
8432
8492
  heightCss;
8433
8493
  dpr;
8434
8494
  canvas = null;
8495
+ target = null;
8435
8496
  imageMinification;
8436
8497
  flattenTolerance;
8437
8498
  bakeBudget;
@@ -8449,6 +8510,12 @@ var WeaselRenderer = class {
8449
8510
  const gl = opts.gl ?? opts.canvas.getContext("webgl2", { stencil: true });
8450
8511
  if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
8451
8512
  this.gl = gl;
8513
+ const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
8514
+ if (attrs && attrs.stencil === false) {
8515
+ throw new Error(
8516
+ "WeaselRenderer: the supplied WebGL2 context has no stencil buffer. Create it with { stencil: true } \u2014 clips and even-odd fills need one."
8517
+ );
8518
+ }
8452
8519
  this.widthCss = opts.width;
8453
8520
  this.heightCss = opts.height;
8454
8521
  this.dpr = opts.dpr;
@@ -8467,12 +8534,8 @@ var WeaselRenderer = class {
8467
8534
  this.canvas.addEventListener("webglcontextlost", this.boundOnLost);
8468
8535
  this.canvas.addEventListener("webglcontextrestored", this.boundOnRestored);
8469
8536
  }
8470
- this.gl.enable(this.gl.BLEND);
8471
- this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
8472
- this.gl.disable(this.gl.DEPTH_TEST);
8473
- this.gl.disable(this.gl.CULL_FACE);
8474
- this.gl.clearColor(0, 0, 0, 0);
8475
- this.applyViewport();
8537
+ this.applyGlState();
8538
+ this.applyTarget();
8476
8539
  this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
8477
8540
  this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
8478
8541
  this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
@@ -8562,8 +8625,42 @@ var WeaselRenderer = class {
8562
8625
  }
8563
8626
  return this.programRegistry.get(id) ?? null;
8564
8627
  }
8565
- applyViewport() {
8566
- this.gl.viewport(0, 0, this.widthCss * this.dpr, this.heightCss * this.dpr);
8628
+ /** The GL state every frame assumes. Applied per `render()` rather than once
8629
+ * at construction because a co-tenant sharing this context moves all of it
8630
+ * between our frames. */
8631
+ applyGlState() {
8632
+ const gl = this.gl;
8633
+ gl.enable(gl.BLEND);
8634
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
8635
+ gl.disable(gl.DEPTH_TEST);
8636
+ gl.disable(gl.CULL_FACE);
8637
+ gl.clearColor(0, 0, 0, 0);
8638
+ }
8639
+ /** Viewport and scissor for this frame. Re-applied inside `render()` because
8640
+ * a co-tenant on the same context moves both between our frames. */
8641
+ applyTarget() {
8642
+ const gl = this.gl;
8643
+ const w = Math.round(this.widthCss * this.dpr);
8644
+ const h = Math.round(this.heightCss * this.dpr);
8645
+ if (!this.target) {
8646
+ gl.disable(gl.SCISSOR_TEST);
8647
+ gl.viewport(0, 0, w, h);
8648
+ return;
8649
+ }
8650
+ const x = Math.round(this.target.origin.x * this.dpr);
8651
+ const y = gl.drawingBufferHeight - Math.round(this.target.origin.y * this.dpr) - h;
8652
+ gl.viewport(x, y, w, h);
8653
+ gl.scissor(x, y, w, h);
8654
+ gl.enable(gl.SCISSOR_TEST);
8655
+ }
8656
+ /** Confine this renderer to a rect of its drawing buffer, or pass null to
8657
+ * give it the whole buffer back. Takes effect from the next `render()`. */
8658
+ setTarget(target) {
8659
+ this.target = target;
8660
+ this.applyTarget();
8661
+ }
8662
+ getTarget() {
8663
+ return this.target;
8567
8664
  }
8568
8665
  isContextLost() {
8569
8666
  return this.contextLost;
@@ -8574,12 +8671,8 @@ var WeaselRenderer = class {
8574
8671
  }
8575
8672
  onContextRestored() {
8576
8673
  this.contextLost = false;
8577
- this.gl.enable(this.gl.BLEND);
8578
- this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
8579
- this.gl.disable(this.gl.DEPTH_TEST);
8580
- this.gl.disable(this.gl.CULL_FACE);
8581
- this.gl.clearColor(0, 0, 0, 0);
8582
- this.applyViewport();
8674
+ this.applyGlState();
8675
+ this.applyTarget();
8583
8676
  this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
8584
8677
  this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
8585
8678
  this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
@@ -8686,8 +8779,12 @@ var WeaselRenderer = class {
8686
8779
  this.meshCache.drainPendingDeletes();
8687
8780
  resetBakeBudget(this.bakeBudget);
8688
8781
  this.groupState.reset();
8689
- gl.stencilMask(255);
8690
- gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
8782
+ this.applyGlState();
8783
+ this.applyTarget();
8784
+ if (this.target?.clear !== false) {
8785
+ gl.stencilMask(255);
8786
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
8787
+ }
8691
8788
  const ctx = {
8692
8789
  gl,
8693
8790
  pathFill: this.pathFill,
@@ -8730,7 +8827,7 @@ var WeaselRenderer = class {
8730
8827
  this.canvas.style.height = `${dims.height}px`;
8731
8828
  }
8732
8829
  }
8733
- this.applyViewport();
8830
+ this.applyTarget();
8734
8831
  }
8735
8832
  /** @internal */
8736
8833
  _gl() {
@@ -9370,10 +9467,10 @@ function targetSizesPx(targetScale = 1) {
9370
9467
  rotationDistance: BASE_PX.rotationDistance * targetScale
9371
9468
  };
9372
9469
  }
9373
- var SYSTEM_MAX_SCALE = 10;
9374
- var SYSTEM_MIN_SCALE = 0.1;
9470
+ var DEFAULT_MIN_ZOOM = 0.1;
9471
+ var DEFAULT_MAX_ZOOM = 8;
9375
9472
  function fitViewToBounds(bounds, viewportDims, currentView, opts = {}) {
9376
- const { padding = 16, maxScale = SYSTEM_MAX_SCALE, minScale = SYSTEM_MIN_SCALE } = opts;
9473
+ const { padding = 16, maxScale = DEFAULT_MAX_ZOOM, minScale = DEFAULT_MIN_ZOOM } = opts;
9377
9474
  if (bounds.width <= 0 || bounds.height <= 0) {
9378
9475
  console.warn("[weasel] fitViewToBounds: bounds has zero or negative area; returning currentView unchanged.", bounds);
9379
9476
  return currentView;
@@ -9417,15 +9514,19 @@ function fitViewToBounds(bounds, viewportDims, currentView, opts = {}) {
9417
9514
  scale: { x: scaleX, y: scaleY }
9418
9515
  };
9419
9516
  }
9517
+ function clampScale(scale2, min, max) {
9518
+ const magnitude = Math.min(max, Math.max(min, Math.abs(scale2)));
9519
+ return scale2 < 0 ? -magnitude : magnitude;
9520
+ }
9420
9521
  function zoomAt(view, anchor, factor, opts) {
9421
9522
  const fx = typeof factor === "number" ? factor : factor.x;
9422
9523
  const fy = typeof factor === "number" ? factor : factor.y;
9423
- const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? 0.1;
9424
- const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? 0.1;
9425
- const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? 8;
9426
- const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? 8;
9427
- const nextX = Math.min(maxX, Math.max(minX, view.scale.x * fx));
9428
- const nextY = Math.min(maxY, Math.max(minY, view.scale.y * fy));
9524
+ const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? DEFAULT_MIN_ZOOM;
9525
+ const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? DEFAULT_MIN_ZOOM;
9526
+ const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? DEFAULT_MAX_ZOOM;
9527
+ const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? DEFAULT_MAX_ZOOM;
9528
+ const nextX = clampScale(view.scale.x * fx, minX, maxX);
9529
+ const nextY = clampScale(view.scale.y * fy, minY, maxY);
9429
9530
  const worldX = anchor.x / view.scale.x + view.x;
9430
9531
  const worldY = anchor.y / view.scale.y + view.y;
9431
9532
  return {
@@ -9435,22 +9536,24 @@ function zoomAt(view, anchor, factor, opts) {
9435
9536
  };
9436
9537
  }
9437
9538
  function clampView(view, bounds, canvas) {
9438
- const visW = canvas.width / view.scale.x;
9439
- const visH = canvas.height / view.scale.y;
9539
+ const visW = Math.abs(canvas.width / view.scale.x);
9540
+ const visH = Math.abs(canvas.height / view.scale.y);
9541
+ const flipX = view.scale.x < 0;
9542
+ const flipY = view.scale.y < 0;
9440
9543
  let x;
9441
9544
  if (visW >= bounds.width) {
9442
- x = bounds.x + (bounds.width - visW) / 2;
9545
+ x = bounds.x + (bounds.width - visW) / 2 + (flipX ? visW : 0);
9443
9546
  } else {
9444
- const minX = bounds.x;
9445
- const maxX = bounds.x + bounds.width - visW;
9547
+ const minX = flipX ? bounds.x + visW : bounds.x;
9548
+ const maxX = flipX ? bounds.x + bounds.width : bounds.x + bounds.width - visW;
9446
9549
  x = view.x < minX ? minX : view.x > maxX ? maxX : view.x;
9447
9550
  }
9448
9551
  let y;
9449
9552
  if (visH >= bounds.height) {
9450
- y = bounds.y + (bounds.height - visH) / 2;
9553
+ y = bounds.y + (bounds.height - visH) / 2 + (flipY ? visH : 0);
9451
9554
  } else {
9452
- const minY = bounds.y;
9453
- const maxY = bounds.y + bounds.height - visH;
9555
+ const minY = flipY ? bounds.y + visH : bounds.y;
9556
+ const maxY = flipY ? bounds.y + bounds.height : bounds.y + bounds.height - visH;
9454
9557
  y = view.y < minY ? minY : view.y > maxY ? maxY : view.y;
9455
9558
  }
9456
9559
  return x === view.x && y === view.y ? view : { x, y, scale: view.scale };
@@ -11153,7 +11256,7 @@ function keyAnchor(view) {
11153
11256
  }
11154
11257
  var WHEEL_STEP = 1.1;
11155
11258
  function makeViewportZoomAction(opts = {}) {
11156
- const clamp2 = { min: opts.min ?? 0.1, max: opts.max ?? 8 };
11259
+ const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
11157
11260
  const wheelSpec = opts.wheel === "plain" ? { kind: "wheel" } : { kind: "wheel", mods: { mod: true } };
11158
11261
  const rawAnimate = opts.animate === true ? {} : opts.animate || null;
11159
11262
  const { resetMs, ...animateRest } = rawAnimate ?? {};
@@ -11802,7 +11905,7 @@ var lassoSelectAction = {
11802
11905
  enabled: () => true
11803
11906
  };
11804
11907
  function makePinchZoomAction(opts = {}) {
11805
- const clamp2 = { min: opts.min ?? 0.1, max: opts.max ?? 8 };
11908
+ const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
11806
11909
  return {
11807
11910
  id: "viewport.pinchZoom",
11808
11911
  label: "Pinch Zoom",
@@ -16337,154 +16440,6 @@ function useGestureDispatcher(opts) {
16337
16440
  };
16338
16441
  }, [enabled, keyboard, canvasRef]);
16339
16442
  }
16340
- function useVelocityTracker() {
16341
- const samplesRef = useRef([]);
16342
- return useMemo(() => ({
16343
- record(dx, dy, t) {
16344
- samplesRef.current.push({ dx, dy, t });
16345
- const cutoff = t - 100;
16346
- samplesRef.current = samplesRef.current.filter((s) => s.t >= cutoff);
16347
- },
16348
- getVelocity() {
16349
- const s = samplesRef.current;
16350
- if (s.length < 2) return { vx: 0, vy: 0 };
16351
- const dt = s[s.length - 1].t - s[0].t;
16352
- if (dt === 0) return { vx: 0, vy: 0 };
16353
- const totalDx = s.slice(1).reduce((acc, p) => acc + p.dx, 0);
16354
- const totalDy = s.slice(1).reduce((acc, p) => acc + p.dy, 0);
16355
- return { vx: totalDx / dt, vy: totalDy / dt };
16356
- },
16357
- reset() {
16358
- samplesRef.current = [];
16359
- }
16360
- }), []);
16361
- }
16362
- var SPRING_DAMPING = 0.5;
16363
- function useDecayLoop() {
16364
- const stateRef = useRef(null);
16365
- const frameLoop = useVisibleRaf(
16366
- (now) => {
16367
- tick(now);
16368
- },
16369
- {
16370
- // Coasting does not continue while suspended: dropping `lastTime` makes
16371
- // the resuming frame seed a fresh interval instead of applying an hour of
16372
- // friction at once, which would end the decay before it was seen.
16373
- onResume: useCallback(() => {
16374
- if (stateRef.current) stateRef.current.lastTime = null;
16375
- }, [])
16376
- }
16377
- );
16378
- const cancel = useCallback(() => {
16379
- frameLoop.cancel();
16380
- stateRef.current = null;
16381
- }, [frameLoop]);
16382
- const tick = useCallback((now) => {
16383
- const s = stateRef.current;
16384
- if (!s) return;
16385
- if (s.lastTime === null) {
16386
- s.lastTime = now;
16387
- frameLoop.request();
16388
- return;
16389
- }
16390
- const dt = Math.min(now - s.lastTime, 64);
16391
- s.lastTime = now;
16392
- const f = Math.pow(s.friction, dt / 16.67);
16393
- s.vx *= f;
16394
- s.vy *= f;
16395
- const speed = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
16396
- if (speed < s.minSpeed) {
16397
- stateRef.current = null;
16398
- frameLoop.cancel();
16399
- s.onEnd?.();
16400
- return;
16401
- }
16402
- let dx = s.vx * dt;
16403
- let dy = s.vy * dt;
16404
- if (s.viewBounds && s.boundary) {
16405
- const { minX, maxX, minY, maxY } = s.viewBounds;
16406
- const newX = s.posX + dx;
16407
- const newY = s.posY + dy;
16408
- if (s.boundary === "stop") {
16409
- if (minX !== void 0 && newX < minX) {
16410
- dx = minX - s.posX;
16411
- s.vx = 0;
16412
- } else if (maxX !== void 0 && newX > maxX) {
16413
- dx = maxX - s.posX;
16414
- s.vx = 0;
16415
- }
16416
- if (minY !== void 0 && newY < minY) {
16417
- dy = minY - s.posY;
16418
- s.vy = 0;
16419
- } else if (maxY !== void 0 && newY > maxY) {
16420
- dy = maxY - s.posY;
16421
- s.vy = 0;
16422
- }
16423
- } else if (s.boundary === "spring") {
16424
- if (minX !== void 0 && newX < minX) {
16425
- dx = minX - s.posX;
16426
- s.vx = Math.abs(s.vx) * SPRING_DAMPING;
16427
- } else if (maxX !== void 0 && newX > maxX) {
16428
- dx = maxX - s.posX;
16429
- s.vx = -Math.abs(s.vx) * SPRING_DAMPING;
16430
- }
16431
- if (minY !== void 0 && newY < minY) {
16432
- dy = minY - s.posY;
16433
- s.vy = Math.abs(s.vy) * SPRING_DAMPING;
16434
- } else if (maxY !== void 0 && newY > maxY) {
16435
- dy = maxY - s.posY;
16436
- s.vy = -Math.abs(s.vy) * SPRING_DAMPING;
16437
- }
16438
- } else {
16439
- if (minX !== void 0 && newX < minX) {
16440
- dx = minX - s.posX;
16441
- s.vx = Math.abs(s.vx);
16442
- } else if (maxX !== void 0 && newX > maxX) {
16443
- dx = maxX - s.posX;
16444
- s.vx = -Math.abs(s.vx);
16445
- }
16446
- if (minY !== void 0 && newY < minY) {
16447
- dy = minY - s.posY;
16448
- s.vy = Math.abs(s.vy);
16449
- } else if (maxY !== void 0 && newY > maxY) {
16450
- dy = maxY - s.posY;
16451
- s.vy = -Math.abs(s.vy);
16452
- }
16453
- }
16454
- s.posX += dx;
16455
- s.posY += dy;
16456
- }
16457
- s.onTick(dx, dy);
16458
- frameLoop.request();
16459
- }, [frameLoop]);
16460
- const start = useCallback((config) => {
16461
- cancel();
16462
- const { velocity, friction = 0.92, minSpeed = 0.01, viewBounds, boundary, initialPosition, onTick, onEnd } = config;
16463
- const speed = Math.sqrt(velocity.vx ** 2 + velocity.vy ** 2);
16464
- if (speed < minSpeed) {
16465
- onEnd?.();
16466
- return;
16467
- }
16468
- stateRef.current = {
16469
- vx: velocity.vx,
16470
- vy: velocity.vy,
16471
- friction,
16472
- minSpeed,
16473
- lastTime: null,
16474
- posX: initialPosition?.x ?? 0,
16475
- posY: initialPosition?.y ?? 0,
16476
- viewBounds,
16477
- boundary,
16478
- onTick,
16479
- onEnd
16480
- };
16481
- frameLoop.request();
16482
- }, [cancel, frameLoop]);
16483
- useEffect(() => () => {
16484
- cancel();
16485
- }, [cancel]);
16486
- return { start, cancel };
16487
- }
16488
16443
  function dist(a, b) {
16489
16444
  return Math.hypot(b.x - a.x, b.y - a.y);
16490
16445
  }
@@ -16563,15 +16518,180 @@ function interpolateView(from, to) {
16563
16518
  return { x: a.x, y: b.x, scale: { x: a.s, y: b.s } };
16564
16519
  };
16565
16520
  }
16521
+ var linear = (t) => t;
16522
+ var easeInQuad = (t) => t * t;
16566
16523
  var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
16524
+ var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
16525
+ var easeInCubic = (t) => t * t * t;
16567
16526
  var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
16527
+ var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
16528
+ var easeInQuart = (t) => t * t * t * t;
16529
+ var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
16530
+ var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
16531
+ var easeInQuint = (t) => t * t * t * t * t;
16532
+ var easeOutQuint = (t) => 1 - Math.pow(1 - t, 5);
16533
+ var easeInOutQuint = (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
16534
+ var easeInSine = (t) => 1 - Math.cos(t * Math.PI / 2);
16535
+ var easeOutSine = (t) => Math.sin(t * Math.PI / 2);
16536
+ var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
16537
+ var easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
16538
+ var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
16539
+ var easeInOutExpo = (t) => {
16540
+ if (t === 0) return 0;
16541
+ if (t === 1) return 1;
16542
+ return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
16543
+ };
16544
+ var easeInCirc = (t) => 1 - Math.sqrt(1 - t * t);
16545
+ var easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
16546
+ var easeInOutCirc = (t) => t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
16547
+ var C1 = 1.70158;
16548
+ var C2 = C1 * 1.525;
16549
+ var C3 = C1 + 1;
16550
+ var C4 = 2 * Math.PI / 3;
16551
+ var C5 = 2 * Math.PI / 4.5;
16552
+ var easeInBack = (t) => C3 * t * t * t - C1 * t * t;
16553
+ var easeOutBack = (t) => 1 + C3 * Math.pow(t - 1, 3) + C1 * Math.pow(t - 1, 2);
16554
+ var easeInOutBack = (t) => t < 0.5 ? Math.pow(2 * t, 2) * ((C2 + 1) * 2 * t - C2) / 2 : (Math.pow(2 * t - 2, 2) * ((C2 + 1) * (2 * t - 2) + C2) + 2) / 2;
16555
+ var easeInElastic = (t) => {
16556
+ if (t === 0) return 0;
16557
+ if (t === 1) return 1;
16558
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * C4);
16559
+ };
16560
+ var easeOutElastic = (t) => {
16561
+ if (t === 0) return 0;
16562
+ if (t === 1) return 1;
16563
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * C4) + 1;
16564
+ };
16565
+ var easeInOutElastic = (t) => {
16566
+ if (t === 0) return 0;
16567
+ if (t === 1) return 1;
16568
+ return t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * C5)) / 2 : Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * C5) / 2 + 1;
16569
+ };
16570
+ var easeOutBounce = (t) => {
16571
+ const n1 = 7.5625;
16572
+ const d1 = 2.75;
16573
+ if (t < 1 / d1) return n1 * t * t;
16574
+ if (t < 2 / d1) {
16575
+ t -= 1.5 / d1;
16576
+ return n1 * t * t + 0.75;
16577
+ }
16578
+ if (t < 2.5 / d1) {
16579
+ t -= 2.25 / d1;
16580
+ return n1 * t * t + 0.9375;
16581
+ }
16582
+ t -= 2.625 / d1;
16583
+ return n1 * t * t + 0.984375;
16584
+ };
16585
+ var easeInBounce = (t) => 1 - easeOutBounce(1 - t);
16586
+ var easeInOutBounce = (t) => t < 0.5 ? (1 - easeOutBounce(1 - 2 * t)) / 2 : (1 + easeOutBounce(2 * t - 1)) / 2;
16568
16587
  var easeOut = easeOutQuad;
16588
+ var EASINGS = {
16589
+ linear,
16590
+ easeInQuad,
16591
+ easeOutQuad,
16592
+ easeInOutQuad,
16593
+ easeInCubic,
16594
+ easeOutCubic,
16595
+ easeInOutCubic,
16596
+ easeInQuart,
16597
+ easeOutQuart,
16598
+ easeInOutQuart,
16599
+ easeInQuint,
16600
+ easeOutQuint,
16601
+ easeInOutQuint,
16602
+ easeInSine,
16603
+ easeOutSine,
16604
+ easeInOutSine,
16605
+ easeInExpo,
16606
+ easeOutExpo,
16607
+ easeInOutExpo,
16608
+ easeInCirc,
16609
+ easeOutCirc,
16610
+ easeInOutCirc,
16611
+ easeInBack,
16612
+ easeOutBack,
16613
+ easeInOutBack,
16614
+ easeInElastic,
16615
+ easeOutElastic,
16616
+ easeInOutElastic,
16617
+ easeInBounce,
16618
+ easeOutBounce,
16619
+ easeInOutBounce
16620
+ };
16569
16621
  var SPRING_PRESETS = {
16570
16622
  gentle: { stiffness: 120, damping: 14, mass: 1 },
16571
16623
  wobbly: { stiffness: 180, damping: 12, mass: 1 },
16572
16624
  stiff: { stiffness: 210, damping: 20, mass: 1 },
16573
16625
  slow: { stiffness: 80, damping: 20, mass: 1 }
16574
16626
  };
16627
+ var NEWTON_ITERATIONS = 8;
16628
+ var NEWTON_MIN_SLOPE = 1e-3;
16629
+ var SUBDIVISION_EPSILON = 1e-7;
16630
+ var SUBDIVISION_MAX = 12;
16631
+ function bezierAt(t, a1, a2) {
16632
+ const c = 3 * a1;
16633
+ const b = 3 * (a2 - a1) - c;
16634
+ const a = 1 - c - b;
16635
+ return ((a * t + b) * t + c) * t;
16636
+ }
16637
+ function bezierSlope(t, a1, a2) {
16638
+ const c = 3 * a1;
16639
+ const b = 3 * (a2 - a1) - c;
16640
+ const a = 1 - c - b;
16641
+ return (3 * a * t + 2 * b) * t + c;
16642
+ }
16643
+ function solveForX(x, x1, x2) {
16644
+ let t = x;
16645
+ for (let i = 0; i < NEWTON_ITERATIONS; i++) {
16646
+ const slope = bezierSlope(t, x1, x2);
16647
+ if (slope < NEWTON_MIN_SLOPE) break;
16648
+ t -= (bezierAt(t, x1, x2) - x) / slope;
16649
+ }
16650
+ let lo = 0;
16651
+ let hi = 1;
16652
+ t = Math.min(1, Math.max(0, t));
16653
+ for (let i = 0; i < SUBDIVISION_MAX; i++) {
16654
+ const err = bezierAt(t, x1, x2) - x;
16655
+ if (Math.abs(err) < SUBDIVISION_EPSILON) return t;
16656
+ if (err > 0) hi = t;
16657
+ else lo = t;
16658
+ t = (lo + hi) / 2;
16659
+ }
16660
+ return t;
16661
+ }
16662
+ function cubicBezierEasing(x1, y1, x2, y2) {
16663
+ const cx1 = Math.min(1, Math.max(0, x1));
16664
+ const cx2 = Math.min(1, Math.max(0, x2));
16665
+ return (t) => {
16666
+ if (t <= 0) return 0;
16667
+ if (t >= 1) return 1;
16668
+ return bezierAt(solveForX(t, cx1, cx2), y1, y2);
16669
+ };
16670
+ }
16671
+ var bezierCache = /* @__PURE__ */ new Map();
16672
+ function isBezier(spec) {
16673
+ return typeof spec === "object" && spec !== null && "bezier" in spec;
16674
+ }
16675
+ function resolveEasing(spec) {
16676
+ if (spec === void 0) return EASINGS.linear;
16677
+ if (typeof spec === "function") return spec;
16678
+ if (typeof spec === "string") {
16679
+ const fn = EASINGS[spec];
16680
+ if (!fn) throw new Error(`resolveEasing: unknown easing name "${spec}"`);
16681
+ return fn;
16682
+ }
16683
+ if (isBezier(spec)) {
16684
+ const [x1, y1, x2, y2] = spec.bezier;
16685
+ const key = `${x1},${y1},${x2},${y2}`;
16686
+ let fn = bezierCache.get(key);
16687
+ if (!fn) {
16688
+ fn = cubicBezierEasing(x1, y1, x2, y2);
16689
+ bezierCache.set(key, fn);
16690
+ }
16691
+ return fn;
16692
+ }
16693
+ throw new Error("resolveEasing: unrecognized easing spec");
16694
+ }
16575
16695
  function createLoop(createSupervisor, factory, opts = {}) {
16576
16696
  const max = opts.count ?? Infinity;
16577
16697
  let iteration = 0;
@@ -16611,6 +16731,7 @@ function createLoop(createSupervisor, factory, opts = {}) {
16611
16731
  supervisor.setTimeScale(s);
16612
16732
  current?.setTimeScale(s);
16613
16733
  },
16734
+ timeScale: () => supervisor.timeScale(),
16614
16735
  isPaused: () => supervisor.isPaused()
16615
16736
  };
16616
16737
  }
@@ -16770,6 +16891,9 @@ var CompositeHandle = class {
16770
16891
  this.timeScale_ = s;
16771
16892
  for (const c of this.children) c.setTimeScale(s);
16772
16893
  }
16894
+ timeScale() {
16895
+ return this.timeScale_;
16896
+ }
16773
16897
  isPaused() {
16774
16898
  return this.paused_;
16775
16899
  }
@@ -16877,7 +17001,7 @@ function sampleTrack(track, t, segmentCache) {
16877
17001
  const b = keys[i + 1];
16878
17002
  const span = b.t - a.t;
16879
17003
  const raw = span <= 0 ? 1 : (t - a.t) / span;
16880
- const u = b.easing ? b.easing(raw) : raw;
17004
+ const u = b.easing ? resolveEasing(b.easing)(raw) : raw;
16881
17005
  if (track.interpolator) {
16882
17006
  let fn = segmentCache?.get(i + 1);
16883
17007
  if (!fn) {
@@ -16956,7 +17080,10 @@ function createTimeline(register, id, opts) {
16956
17080
  for (const track of tracks) {
16957
17081
  if (track.kind === "event") {
16958
17082
  const end = firstAfter(track.events, to);
16959
- for (let i = firstAfter(track.events, from); i < end; i += 1) track.events[i].fire();
17083
+ for (let i = firstAfter(track.events, from); i < end; i += 1) {
17084
+ const ev = track.events[i];
17085
+ ev.fire(to - ev.t);
17086
+ }
16960
17087
  } else if (track.kind === "timeline") {
16961
17088
  fireEvents(track.timeline.tracks, from - track.at, to - track.at);
16962
17089
  }
@@ -17036,6 +17163,7 @@ function createTimeline(register, id, opts) {
17036
17163
  wantScale = s;
17037
17164
  base.setTimeScale(s);
17038
17165
  },
17166
+ timeScale: () => live ? base.timeScale() : wantScale,
17039
17167
  isPaused: () => live ? base.isPaused() : wantPaused,
17040
17168
  seek(t) {
17041
17169
  offset = t - lastVirtual;
@@ -17046,6 +17174,10 @@ function createTimeline(register, id, opts) {
17046
17174
  time: () => playhead,
17047
17175
  duration: () => duration,
17048
17176
  tracks: () => opts.tracks,
17177
+ setLoop(loop) {
17178
+ loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;
17179
+ },
17180
+ loop: () => loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft,
17049
17181
  edit(fn) {
17050
17182
  fn();
17051
17183
  caches = /* @__PURE__ */ new WeakMap();
@@ -17260,6 +17392,7 @@ function useAnimator(opts = {}) {
17260
17392
  const a = animations.current.get(anim.id);
17261
17393
  if (a) a.timeScale = s;
17262
17394
  },
17395
+ timeScale: () => animations.current.get(anim.id)?.timeScale ?? 1,
17263
17396
  isPaused: () => animations.current.get(anim.id)?.paused ?? false
17264
17397
  };
17265
17398
  };
@@ -17281,6 +17414,7 @@ function useAnimator(opts = {}) {
17281
17414
  pause: base.pause,
17282
17415
  resume: base.resume,
17283
17416
  setTimeScale: base.setTimeScale,
17417
+ timeScale: base.timeScale,
17284
17418
  isPaused: base.isPaused,
17285
17419
  setOnCancel: (cb) => {
17286
17420
  onCancelCb = cb;
@@ -17291,7 +17425,7 @@ function useAnimator(opts = {}) {
17291
17425
  const tween = (o) => {
17292
17426
  const id = nextId.current++;
17293
17427
  const start = 0;
17294
- const easing = o.easing ?? easeOut;
17428
+ const easing = resolveEasing(o.easing ?? easeOut);
17295
17429
  const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;
17296
17430
  const perTickInterp = factoryFn ? null : o.interpolate ?? ((a, b, t) => {
17297
17431
  if (typeof a === "number" && typeof b === "number") {
@@ -17446,6 +17580,7 @@ function useAnimator(opts = {}) {
17446
17580
  setTimeScale: (s) => {
17447
17581
  globalTimeScale.current = s;
17448
17582
  },
17583
+ timeScale: () => globalTimeScale.current,
17449
17584
  pauseKey: (key) => {
17450
17585
  for (const a of animations.current.values()) if (a.cancelKey === key) a.paused = true;
17451
17586
  },
@@ -18043,6 +18178,21 @@ function LineIcon({ className, size = 20 }) {
18043
18178
  }
18044
18179
  );
18045
18180
  }
18181
+ function ArrowIcon({ className, size = 20 }) {
18182
+ return /* @__PURE__ */ jsxs(
18183
+ "svg",
18184
+ {
18185
+ ...ICON_SVG_BASE,
18186
+ className,
18187
+ width: size,
18188
+ height: size,
18189
+ children: [
18190
+ /* @__PURE__ */ jsx("path", { d: "M3.5 16.5 12.9 7.1", strokeLinecap: "round" }),
18191
+ /* @__PURE__ */ jsx("path", { d: "M16.5 3.5 11.06 5.98 14.03 8.95Z", fill: "currentColor", strokeLinejoin: "round" })
18192
+ ]
18193
+ }
18194
+ );
18195
+ }
18046
18196
  function PolygonIcon({ className, size = 20 }) {
18047
18197
  return /* @__PURE__ */ jsx(
18048
18198
  "svg",
@@ -18620,6 +18770,14 @@ var TEXT_PAINTER = {
18620
18770
  );
18621
18771
  if (boxes.length === 0) return null;
18622
18772
  return rectsToPath(boxes);
18773
+ },
18774
+ // `filled` is unconditional: a text node's `data.fill: null` still resolves
18775
+ // to the default in `ResolvedRun`, so there is no outline-only text to
18776
+ // report. The reach is the point — a heavily outlined glyph was unpickable
18777
+ // across the width of its own outline.
18778
+ ink: (node, _pose, ctx) => {
18779
+ const d = node.data;
18780
+ return { filled: true, ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale) };
18623
18781
  }
18624
18782
  };
18625
18783
  function rectsToPath(rects) {
@@ -18885,10 +19043,10 @@ var RECT_FALLBACK_PAINTER = {
18885
19043
  };
18886
19044
  function registerBuiltInShapePainters() {
18887
19045
  registerNodeShape(TEXT_PAINTER);
19046
+ registerNodeShape(DERIVED_PAINTER);
18888
19047
  registerNodeShape(PATH_PAINTER);
18889
19048
  registerNodeShape(SHAPE_PAINTER);
18890
19049
  registerNodeShape(IMAGE_PAINTER);
18891
- registerNodeShape(DERIVED_PAINTER);
18892
19050
  registerNodeShape(RECT_FALLBACK_PAINTER);
18893
19051
  }
18894
19052
  registerBuiltInShapePainters();
@@ -19897,11 +20055,7 @@ function useRotateTool(adapter, options) {
19897
20055
  [overlay]
19898
20056
  );
19899
20057
  }
19900
- function useHandTool(opts = {}) {
19901
- const inertia = opts.inertia === false ? false : opts.inertia;
19902
- const axis = opts.axis ?? "both";
19903
- const tracker = useVelocityTracker();
19904
- const decay = useDecayLoop();
20058
+ function useHandTool(_opts = {}) {
19905
20059
  return useMemo(
19906
20060
  () => Object.assign(defineViewportTool({
19907
20061
  id: "hand",
@@ -19921,8 +20075,7 @@ function useHandTool(opts = {}) {
19921
20075
  // phase entry.
19922
20076
  bindings: [{ spec: { kind: "drag" }, actionId: "viewport.dragPan" }]
19923
20077
  }),
19924
- // eslint-disable-next-line react-hooks/exhaustive-deps
19925
- [inertia, axis, tracker, decay]
20078
+ []
19926
20079
  );
19927
20080
  }
19928
20081
  var PRESENTATION = {
@@ -19951,7 +20104,7 @@ function usePinchZoomTool(canvasRef, getView, setView, opts = {}) {
19951
20104
  getViewRef.current = getView;
19952
20105
  const setViewRef = useRef(setView);
19953
20106
  setViewRef.current = setView;
19954
- const { min = 0.1, max = 8, enabled = true } = opts;
20107
+ const { min = DEFAULT_MIN_ZOOM, max = DEFAULT_MAX_ZOOM, enabled = true } = opts;
19955
20108
  const resolveTargetRef = useRef(opts.resolveTarget);
19956
20109
  resolveTargetRef.current = opts.resolveTarget;
19957
20110
  usePinchGesture(canvasRef, (clientAnchor, scaleFactor) => {
@@ -21604,14 +21757,89 @@ function wrapNodeOutput(cmds, pose, alpha) {
21604
21757
  }
21605
21758
  return rotated;
21606
21759
  }
21607
- function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21760
+ function insetAlong(start, extent, size, align, by) {
21761
+ if (align === "start") return start + by;
21762
+ if (align === "end") return start + extent - size - by;
21763
+ return start + (extent - size) / 2 + by;
21764
+ }
21765
+ function hostAnchorRect(input) {
21766
+ const { host, panel, viewport, align, offset, padding = 0 } = input;
21767
+ const desired = {
21768
+ x: insetAlong(host.x, host.width, panel.width, align.x, offset.x),
21769
+ y: insetAlong(host.y, host.height, panel.height, align.y, offset.y),
21770
+ width: panel.width,
21771
+ height: panel.height
21772
+ };
21773
+ return clampRectWithin(desired, { x: 0, y: 0, width: viewport.width, height: viewport.height }, padding);
21774
+ }
21775
+ function hostAnchorCss(rect, align, viewport) {
21776
+ return {
21777
+ ...align.y === "end" ? { bottom: viewport.height - rect.y - rect.height } : { top: rect.y },
21778
+ ...align.x === "end" ? { right: viewport.width - rect.x - rect.width } : { left: rect.x }
21779
+ };
21780
+ }
21781
+ function useHostAnchor(resolveHost, options) {
21782
+ const [panel, setPanel] = useState(null);
21783
+ const [style, setStyle] = useState(null);
21784
+ const ref = useCallback((el) => setPanel(el), []);
21785
+ const resolve = useRef(resolveHost);
21786
+ resolve.current = resolveHost;
21787
+ const { align, offset, padding = 0 } = options;
21788
+ const { x: alignX, y: alignY } = align;
21789
+ const { x: offsetX, y: offsetY } = offset;
21790
+ const recompute = useCallback(() => {
21791
+ const hostEl = resolve.current();
21792
+ if (!hostEl) {
21793
+ setStyle(null);
21794
+ return;
21795
+ }
21796
+ const h = hostEl.getBoundingClientRect();
21797
+ const p = panel?.getBoundingClientRect();
21798
+ const viewport = { width: window.innerWidth, height: window.innerHeight };
21799
+ const align2 = { x: alignX, y: alignY };
21800
+ const rect = hostAnchorRect({
21801
+ host: { x: h.left, y: h.top, width: h.width, height: h.height },
21802
+ panel: { width: p?.width ?? 0, height: p?.height ?? 0 },
21803
+ viewport,
21804
+ align: align2,
21805
+ offset: { x: offsetX, y: offsetY },
21806
+ padding
21807
+ });
21808
+ setStyle(hostAnchorCss(rect, align2, viewport));
21809
+ }, [panel, alignX, alignY, offsetX, offsetY, padding]);
21810
+ const raf = useVisibleRaf(recompute);
21811
+ useEffect(() => {
21812
+ recompute();
21813
+ const schedule = () => raf.request();
21814
+ window.addEventListener("scroll", schedule, true);
21815
+ window.addEventListener("resize", schedule);
21816
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(schedule) : null;
21817
+ if (panel) observer?.observe(panel);
21818
+ const hostEl = resolve.current();
21819
+ if (hostEl) observer?.observe(hostEl);
21820
+ return () => {
21821
+ window.removeEventListener("scroll", schedule, true);
21822
+ window.removeEventListener("resize", schedule);
21823
+ raf.cancel();
21824
+ observer?.disconnect();
21825
+ };
21826
+ }, [recompute, raf, panel]);
21827
+ return { ref, style };
21828
+ }
21829
+ function CursorCoordsHud({ canvasRef, anchorRef, viewRef, offset }) {
21608
21830
  const [state, setState] = useState({
21609
21831
  client: { x: 0, y: 0 },
21610
21832
  world: null,
21611
- inCanvas: false,
21612
- anchor: null
21833
+ inCanvas: false
21613
21834
  });
21614
21835
  const [fps, setFps] = useState(0);
21836
+ const { ref, style: anchorStyle } = useHostAnchor(
21837
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
21838
+ {
21839
+ align: { x: "end", y: "start" },
21840
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 8 }
21841
+ }
21842
+ );
21615
21843
  const framesRef = useRef(0);
21616
21844
  const lastFlushRef = useRef(0);
21617
21845
  const fpsLoop = useVisibleRaf(
@@ -21641,49 +21869,33 @@ function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21641
21869
  return () => fpsLoop.cancel();
21642
21870
  }, [fpsLoop]);
21643
21871
  useEffect(() => {
21644
- const readAnchor = () => {
21645
- const canvas = canvasRef.current;
21646
- if (!canvas) return null;
21647
- const host = canvas.parentElement ?? canvas;
21648
- const rect = host.getBoundingClientRect();
21649
- return { top: rect.top, right: window.innerWidth - rect.right };
21650
- };
21651
21872
  const onMove = (e) => {
21652
21873
  const canvas = canvasRef.current;
21653
21874
  const view = viewRef.current;
21654
21875
  const client = { x: e.clientX, y: e.clientY };
21655
- const anchor = readAnchor();
21656
21876
  if (!canvas || !view) {
21657
- setState({ client, world: null, inCanvas: false, anchor });
21877
+ setState({ client, world: null, inCanvas: false });
21658
21878
  return;
21659
21879
  }
21660
21880
  const rect = canvas.getBoundingClientRect();
21661
21881
  const inCanvas = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
21662
21882
  const [wx, wy] = clientToWorld(e.clientX, e.clientY, rect, view);
21663
21883
  const world = { x: wx, y: wy };
21664
- setState({ client, world, inCanvas, anchor });
21884
+ setState({ client, world, inCanvas });
21665
21885
  };
21666
- const onLayout = () => setState((s) => ({ ...s, anchor: readAnchor() }));
21667
21886
  document.addEventListener("pointermove", onMove);
21668
- window.addEventListener("scroll", onLayout, true);
21669
- window.addEventListener("resize", onLayout);
21670
- setState((s) => ({ ...s, anchor: readAnchor() }));
21671
21887
  return () => {
21672
21888
  document.removeEventListener("pointermove", onMove);
21673
- window.removeEventListener("scroll", onLayout, true);
21674
- window.removeEventListener("resize", onLayout);
21675
21889
  };
21676
21890
  }, [canvasRef, viewRef]);
21677
- if (!state.anchor) return null;
21678
- const top = state.anchor.top + (offset?.top ?? 8);
21679
- const right = state.anchor.right + (offset?.right ?? 8);
21891
+ if (!anchorStyle) return null;
21680
21892
  return /* @__PURE__ */ jsxs(
21681
21893
  "div",
21682
21894
  {
21895
+ ref,
21683
21896
  style: {
21684
21897
  position: "fixed",
21685
- top,
21686
- right,
21898
+ ...anchorStyle,
21687
21899
  zIndex: 1e4,
21688
21900
  background: "rgba(0,0,0,0.7)",
21689
21901
  color: "#e8e8e8",
@@ -21706,33 +21918,31 @@ function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21706
21918
  );
21707
21919
  }
21708
21920
  var PickHud_default = {};
21709
- function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
21921
+ function PickHud({ canvasRef, anchorRef, viewRef, pickEvery, pickBest, offset }) {
21710
21922
  const [state, setState] = useState({
21711
21923
  ids: [],
21712
21924
  best: null,
21713
- inCanvas: false,
21714
- anchor: null
21925
+ inCanvas: false
21715
21926
  });
21927
+ const { ref, style } = useHostAnchor(
21928
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
21929
+ {
21930
+ align: { x: "end", y: "start" },
21931
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 76 }
21932
+ }
21933
+ );
21716
21934
  useEffect(() => {
21717
- const readAnchor = () => {
21718
- const canvas = canvasRef.current;
21719
- if (!canvas) return null;
21720
- const host = canvas.parentElement ?? canvas;
21721
- const rect = host.getBoundingClientRect();
21722
- return { top: rect.top, right: window.innerWidth - rect.right };
21723
- };
21724
21935
  const onMove = (e) => {
21725
21936
  const canvas = canvasRef.current;
21726
21937
  const view = viewRef.current;
21727
- const anchor = readAnchor();
21728
21938
  if (!canvas || !view) {
21729
- setState({ ids: [], best: null, inCanvas: false, anchor });
21939
+ setState({ ids: [], best: null, inCanvas: false });
21730
21940
  return;
21731
21941
  }
21732
21942
  const rect = canvas.getBoundingClientRect();
21733
21943
  const inCanvas = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
21734
21944
  if (!inCanvas) {
21735
- setState({ ids: [], best: null, inCanvas: false, anchor });
21945
+ setState({ ids: [], best: null, inCanvas: false });
21736
21946
  return;
21737
21947
  }
21738
21948
  const worldX = (e.clientX - rect.left) / view.scale.x + view.x;
@@ -21751,24 +21961,15 @@ function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
21751
21961
  best = null;
21752
21962
  }
21753
21963
  }
21754
- setState({ ids, best, inCanvas: true, anchor });
21964
+ setState({ ids, best, inCanvas: true });
21755
21965
  };
21756
- const onLayout = () => setState((s) => ({ ...s, anchor: readAnchor() }));
21757
21966
  document.addEventListener("pointermove", onMove);
21758
- window.addEventListener("scroll", onLayout, true);
21759
- window.addEventListener("resize", onLayout);
21760
- setState((s) => ({ ...s, anchor: readAnchor() }));
21761
21967
  return () => {
21762
21968
  document.removeEventListener("pointermove", onMove);
21763
- window.removeEventListener("scroll", onLayout, true);
21764
- window.removeEventListener("resize", onLayout);
21765
21969
  };
21766
21970
  }, [canvasRef, viewRef, pickEvery, pickBest]);
21767
- if (!state.anchor) return null;
21768
- const top = state.anchor.top + (offset?.top ?? 76);
21769
- const right = state.anchor.right + (offset?.right ?? 8);
21770
- const style = { top, right };
21771
- return /* @__PURE__ */ jsxs("div", { className: PickHud_default.hud, style, children: [
21971
+ if (!style) return null;
21972
+ return /* @__PURE__ */ jsxs("div", { ref, className: PickHud_default.hud, style, children: [
21772
21973
  /* @__PURE__ */ jsxs("div", { className: PickHud_default.header, children: [
21773
21974
  "pickEvery (",
21774
21975
  state.ids.length,
@@ -21785,34 +21986,20 @@ function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
21785
21986
  ] });
21786
21987
  }
21787
21988
  var ModalityHud_default = {};
21788
- function ModalityHud({ canvasRef, modeId, offset }) {
21989
+ function ModalityHud({ canvasRef, anchorRef, modeId, offset }) {
21789
21990
  const toolCtx = useOptionalActiveToolContext();
21790
- const [state, setState] = useState({ anchor: null });
21791
- useEffect(() => {
21792
- const readAnchor = () => {
21793
- const canvas = canvasRef.current;
21794
- if (!canvas) return null;
21795
- const host = canvas.parentElement ?? canvas;
21796
- const rect = host.getBoundingClientRect();
21797
- return { top: rect.top, right: window.innerWidth - rect.right };
21798
- };
21799
- const onLayout = () => setState({ anchor: readAnchor() });
21800
- onLayout();
21801
- window.addEventListener("scroll", onLayout, true);
21802
- window.addEventListener("resize", onLayout);
21803
- return () => {
21804
- window.removeEventListener("scroll", onLayout, true);
21805
- window.removeEventListener("resize", onLayout);
21806
- };
21807
- }, [canvasRef]);
21808
- if (!state.anchor) return null;
21809
- const top = state.anchor.top + (offset?.top ?? 220);
21810
- const right = state.anchor.right + (offset?.right ?? 8);
21811
- const style = { top, right };
21991
+ const { ref, style } = useHostAnchor(
21992
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
21993
+ {
21994
+ align: { x: "end", y: "start" },
21995
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 220 }
21996
+ }
21997
+ );
21998
+ if (!style) return null;
21812
21999
  const active = toolCtx?.active ?? null;
21813
22000
  const hotkeys = toolCtx?.hotkeyStack ?? [];
21814
22001
  const hotkeyTop = hotkeys.length > 0 ? hotkeys[hotkeys.length - 1] : null;
21815
- return /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.hud, style, children: [
22002
+ return /* @__PURE__ */ jsxs("div", { ref, className: ModalityHud_default.hud, style, children: [
21816
22003
  /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.row, children: [
21817
22004
  /* @__PURE__ */ jsx("span", { className: ModalityHud_default.label, children: "mode" }),
21818
22005
  /* @__PURE__ */ jsx("span", { className: modeId ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: modeId ?? "\u2014" })
@@ -21943,6 +22130,8 @@ function CanvasInner(props, ref) {
21943
22130
  boundsOf: boundsOf2,
21944
22131
  pickEvery,
21945
22132
  clientToWorld: clientToWorld2,
22133
+ paintInto,
22134
+ inputElement,
21946
22135
  geometry = AUTO_POSE_DESCRIPTOR,
21947
22136
  className,
21948
22137
  style,
@@ -21988,6 +22177,15 @@ function CanvasInner(props, ref) {
21988
22177
  debugSinkRefForCtx.current = debugSink;
21989
22178
  const adapter = adapterProp;
21990
22179
  const canvasRef = useRef(null);
22180
+ const ownCanvasRef = useRef(null);
22181
+ const detached = !!paintInto;
22182
+ if (detached) {
22183
+ canvasRef.current = inputElement ?? null;
22184
+ }
22185
+ const paintTargetRef = useRef(null);
22186
+ paintTargetRef.current = detached ? paintInto.canvas ?? null : null;
22187
+ const paintRectRef = useRef(null);
22188
+ paintRectRef.current = detached ? { x: paintInto.x, y: paintInto.y } : null;
21991
22189
  const helpersForLayersRef = useRef(null);
21992
22190
  const dimsRef = useRef({ width, height });
21993
22191
  dimsRef.current = { width, height };
@@ -22075,7 +22273,10 @@ function CanvasInner(props, ref) {
22075
22273
  };
22076
22274
  }, []);
22077
22275
  useImperativeHandle(ref, () => ({
22078
- element: canvasRef.current,
22276
+ // Named rather than read off `canvasRef` so the handle rebuilds when a
22277
+ // detached surface's input element arrives, which is a render later.
22278
+ element: detached ? inputElement ?? null : canvasRef.current,
22279
+ surface: detached ? paintInto?.canvas ?? null : ownCanvasRef.current,
22079
22280
  requestRedraw,
22080
22281
  subscribeFrame,
22081
22282
  registerLayer,
@@ -22086,6 +22287,10 @@ function CanvasInner(props, ref) {
22086
22287
  getPaintedVersion
22087
22288
  }), [
22088
22289
  canvasRef,
22290
+ ownCanvasRef,
22291
+ detached,
22292
+ inputElement,
22293
+ paintInto?.canvas,
22089
22294
  requestRedraw,
22090
22295
  subscribeFrame,
22091
22296
  registerLayer,
@@ -22381,8 +22586,9 @@ function CanvasInner(props, ref) {
22381
22586
  shaders
22382
22587
  };
22383
22588
  const paint = useCallback(() => {
22384
- const c = canvasRef.current;
22589
+ const c = paintTargetRef.current ?? ownCanvasRef.current;
22385
22590
  if (!c) return false;
22591
+ const rect = paintRectRef.current;
22386
22592
  const {
22387
22593
  layers: paintLayers,
22388
22594
  width: w,
@@ -22411,7 +22617,9 @@ function CanvasInner(props, ref) {
22411
22617
  try {
22412
22618
  renderer = new WeaselRenderer({
22413
22619
  gl,
22414
- canvas: c,
22620
+ // A shared buffer is the caller's to size. Handing the renderer the
22621
+ // element would have every co-tenant resize it to its own pane.
22622
+ ...rect ? {} : { canvas: c },
22415
22623
  width: w,
22416
22624
  height: h,
22417
22625
  dpr
@@ -22430,6 +22638,7 @@ function CanvasInner(props, ref) {
22430
22638
  lastResizeRef.current = { w, h, dpr };
22431
22639
  }
22432
22640
  }
22641
+ renderer.setTarget(rect ? { origin: { x: rect.x, y: rect.y } } : null);
22433
22642
  const view = viewRef.current;
22434
22643
  const commands = drawLayers(
22435
22644
  paintLayers,
@@ -22478,31 +22687,69 @@ function CanvasInner(props, ref) {
22478
22687
  }, [shaderIdKey]);
22479
22688
  const toolsCursor = tools ? resolveToolsCursor(tools, toolsCtxBase) : void 0;
22480
22689
  const effectiveStyle = toolsCursor ? { ...style, cursor: toolsCursor } : style;
22481
- return /* @__PURE__ */ jsxs(Fragment, { children: [
22482
- /* @__PURE__ */ jsx(
22483
- "canvas",
22690
+ const layersForMoveRef = useRef(layersWithDebug);
22691
+ layersForMoveRef.current = layersWithDebug;
22692
+ useEffect(() => {
22693
+ if (!detached || !inputElement) return;
22694
+ const el = inputElement;
22695
+ const onDown = () => {
22696
+ if (autoFocusOnPointerDown) el.focus();
22697
+ pointerDownRef.current = true;
22698
+ };
22699
+ const onMove = (e) => {
22700
+ if (pointerDownRef.current) return;
22701
+ const view = viewRef.current;
22702
+ const [worldX, worldY] = toWorld(
22703
+ el,
22704
+ e.clientX,
22705
+ e.clientY,
22706
+ view,
22707
+ clientToWorldRef.current
22708
+ );
22709
+ const dims = dimsRef.current;
22710
+ for (const layer of layersForMoveRef.current) {
22711
+ layer.onUncapturedMove?.(worldX, worldY, e, view, dims);
22712
+ }
22713
+ };
22714
+ const onUp = () => {
22715
+ pointerDownRef.current = false;
22716
+ };
22717
+ const onLeave = () => {
22718
+ pointerDownRef.current = false;
22719
+ for (const layer of layersForMoveRef.current) layer.onUncapturedLeave?.();
22720
+ };
22721
+ const onMenu = (e) => e.preventDefault();
22722
+ el.addEventListener("pointerdown", onDown);
22723
+ el.addEventListener("pointermove", onMove);
22724
+ el.addEventListener("pointerup", onUp);
22725
+ el.addEventListener("pointerleave", onLeave);
22726
+ el.addEventListener("contextmenu", onMenu);
22727
+ return () => {
22728
+ el.removeEventListener("pointerdown", onDown);
22729
+ el.removeEventListener("pointermove", onMove);
22730
+ el.removeEventListener("pointerup", onUp);
22731
+ el.removeEventListener("pointerleave", onLeave);
22732
+ el.removeEventListener("contextmenu", onMenu);
22733
+ };
22734
+ }, [detached, inputElement, autoFocusOnPointerDown]);
22735
+ useEffect(() => {
22736
+ if (!detached || !inputElement) return;
22737
+ inputElement.style.cursor = toolsCursor ?? "";
22738
+ }, [detached, inputElement, toolsCursor]);
22739
+ const huds = /* @__PURE__ */ jsxs(Fragment, { children: [
22740
+ cursorCoordsHud && /* @__PURE__ */ jsx(
22741
+ CursorCoordsHud,
22484
22742
  {
22485
- ref: canvasRef,
22486
- width,
22487
- height,
22488
- tabIndex,
22489
- className,
22490
- style: effectiveStyle,
22491
- onFocus,
22492
- onBlur,
22493
- onPointerDown: handlePointerDown,
22494
- onPointerMove: handlePointerMove,
22495
- onPointerUp: handlePointerUp,
22496
- onPointerCancel: handlePointerCancel,
22497
- onPointerLeave: handlePointerLeave,
22498
- onContextMenu: (e) => e.preventDefault()
22743
+ canvasRef,
22744
+ ...detached ? { anchorRef: canvasRef } : {},
22745
+ viewRef
22499
22746
  }
22500
22747
  ),
22501
- cursorCoordsHud && /* @__PURE__ */ jsx(CursorCoordsHud, { canvasRef, viewRef }),
22502
22748
  pickHud && /* @__PURE__ */ jsx(
22503
22749
  PickHud,
22504
22750
  {
22505
22751
  canvasRef,
22752
+ ...detached ? { anchorRef: canvasRef } : {},
22506
22753
  viewRef,
22507
22754
  pickEvery: stablePickEveryForHud,
22508
22755
  pickBest: stablePickBestForHud
@@ -22512,10 +22759,37 @@ function CanvasInner(props, ref) {
22512
22759
  ModalityHud,
22513
22760
  {
22514
22761
  canvasRef,
22762
+ ...detached ? { anchorRef: canvasRef } : {},
22515
22763
  modeId: typeof modalityHud === "object" ? modalityHud.modeId : void 0
22516
22764
  }
22517
22765
  )
22518
22766
  ] });
22767
+ if (detached) return inputElement ? huds : null;
22768
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22769
+ /* @__PURE__ */ jsx(
22770
+ "canvas",
22771
+ {
22772
+ ref: (el) => {
22773
+ ownCanvasRef.current = el;
22774
+ canvasRef.current = el;
22775
+ },
22776
+ width,
22777
+ height,
22778
+ tabIndex,
22779
+ className,
22780
+ style: effectiveStyle,
22781
+ onFocus,
22782
+ onBlur,
22783
+ onPointerDown: handlePointerDown,
22784
+ onPointerMove: handlePointerMove,
22785
+ onPointerUp: handlePointerUp,
22786
+ onPointerCancel: handlePointerCancel,
22787
+ onPointerLeave: handlePointerLeave,
22788
+ onContextMenu: (e) => e.preventDefault()
22789
+ }
22790
+ ),
22791
+ huds
22792
+ ] });
22519
22793
  }
22520
22794
  var Canvas = forwardRef(CanvasInner);
22521
22795
  function composeAlphaFor(scene, alphaFor) {
@@ -23644,6 +23918,32 @@ function sceneFromJSON(json, options) {
23644
23918
  });
23645
23919
  }
23646
23920
  var SelectionContext = createContext(null);
23921
+ function SelectionContextProvider({ children }) {
23922
+ const [state, setState] = useState({ selection: [], kinds: void 0 });
23923
+ const lastSerializedRef = useRef("");
23924
+ const publishSelection = useCallback((ids, kinds) => {
23925
+ const next = JSON.stringify([ids, kinds]);
23926
+ if (next === lastSerializedRef.current) return;
23927
+ lastSerializedRef.current = next;
23928
+ setState({ selection: ids, kinds });
23929
+ }, []);
23930
+ const value = useMemo(
23931
+ () => ({
23932
+ selection: state.selection,
23933
+ ...state.kinds !== void 0 ? { kinds: state.kinds } : {},
23934
+ publishSelection
23935
+ }),
23936
+ [state, publishSelection]
23937
+ );
23938
+ return /* @__PURE__ */ jsx(SelectionContext.Provider, { value, children });
23939
+ }
23940
+ function SelectionContextProviderIfRoot({
23941
+ children
23942
+ }) {
23943
+ const parent = useContext(SelectionContext);
23944
+ if (parent !== null) return /* @__PURE__ */ jsx(Fragment, { children });
23945
+ return /* @__PURE__ */ jsx(SelectionContextProvider, { children });
23946
+ }
23647
23947
  function usePublishSelection(ids, kinds) {
23648
23948
  const ctx = useContext(SelectionContext);
23649
23949
  const idsRef = useRef(ids);
@@ -25101,7 +25401,7 @@ function useDispatcherOverlayLayer(args) {
25101
25401
  break;
25102
25402
  case "pencil": {
25103
25403
  if (geom.samples.length < 2) break;
25104
- pathCmd = polygonFromPoints(projectPoints(geom.samples));
25404
+ pathCmd = polylineFromPoints(projectPoints(geom.samples));
25105
25405
  break;
25106
25406
  }
25107
25407
  }
@@ -25113,7 +25413,7 @@ function useDispatcherOverlayLayer(args) {
25113
25413
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
25114
25414
  };
25115
25415
  out.push(cmd);
25116
- if (ov.anchorPoint) {
25416
+ if (ov.anchorPoint && ov.shape !== "pencil") {
25117
25417
  const anchorScreen = projectPoint(ov.anchorPoint.x, ov.anchorPoint.y);
25118
25418
  out.push({
25119
25419
  kind: "path",
@@ -26982,7 +27282,7 @@ function SceneCanvasInner(props, ref) {
26982
27282
  boundary: inertiaObj.boundary,
26983
27283
  bounds: inertiaObj.bounds
26984
27284
  } : void 0;
26985
- const handTool = useHandTool(handToolInertia ? { inertia: handToolInertia } : {});
27285
+ const handTool = useHandTool(handToolInertia ? {} : {});
26986
27286
  const viewportAmbient = [];
26987
27287
  const baseRequestedTools = defaultTools ?? (toolBundle ? BUNDLE_TOOLS[toolBundle] : null) ?? (viewportRegistered ? ["select", "rotate", "hand"] : ["select", "rotate"]);
26988
27288
  const KNOWN_BUILTIN_IDS = /* @__PURE__ */ new Set([
@@ -27707,14 +28007,12 @@ function StandardActionsRegistrar({
27707
28007
  }, [registry22, actionsRegistryRef]);
27708
28008
  useEffect(() => {
27709
28009
  if (!registry22) return;
27710
- registry22.setDispatcher(dispatcher);
27711
- return () => registry22.setDispatcher(null);
28010
+ return registry22.setDispatcher(dispatcher);
27712
28011
  }, [registry22, dispatcher]);
27713
28012
  const wiredDepRegistry = useDepRegistry();
27714
28013
  useEffect(() => {
27715
28014
  if (!registry22) return;
27716
- registry22.setDepRegistry(wiredDepRegistry);
27717
- return () => registry22.setDepRegistry(null);
28015
+ return registry22.setDepRegistry(wiredDepRegistry);
27718
28016
  }, [registry22, wiredDepRegistry]);
27719
28017
  useEffect(() => {
27720
28018
  if (!registry22) return;
@@ -27775,7 +28073,7 @@ var SceneCanvasInnerForwardRef = forwardRef(SceneCanvasInner);
27775
28073
  function SceneCanvasWrapper(props, ref) {
27776
28074
  return /* @__PURE__ */ jsx(ActiveToolContextProviderIfRoot, { children: /* @__PURE__ */ jsx(ViewRegistryProvider, { children: /* @__PURE__ */ jsx(SceneCanvasInnerForwardRef, { ...props, ref }) }) });
27777
28075
  }
27778
- forwardRef(SceneCanvasWrapper);
28076
+ var SceneCanvas = forwardRef(SceneCanvasWrapper);
27779
28077
  var RAD_TO_DEG = 180 / Math.PI;
27780
28078
  var rotationDegreesUnit = {
27781
28079
  toDisplay: (rad) => Math.round(rad * RAD_TO_DEG * 10) / 10,
@@ -27907,9 +28205,9 @@ function shapeSchema(opts = {}) {
27907
28205
  fontWeight: { kind: "number", name: "Weight", description: "Font weight, 100\u2013900.", default: 400, min: 100, max: 900, step: 100, pair: "Size / weight" },
27908
28206
  fontStyle: { kind: "enum", name: "Style", description: "Upright or italic.", default: "normal", options: [{ value: "normal", label: "Normal" }, { value: "italic", label: "Italic" }] },
27909
28207
  letterSpacing: { kind: "number", name: "Tracking", description: "Extra advance per glyph, world units.", default: 0, step: 0.1 },
27910
- underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false },
27911
- strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false },
27912
- overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false }
28208
+ underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false, control: "toggle", short: "U", pair: "Decoration" },
28209
+ strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false, control: "toggle", short: "S", pair: "Decoration" },
28210
+ overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false, control: "toggle", short: "O", pair: "Decoration" }
27913
28211
  }
27914
28212
  },
27915
28213
  paragraph: {
@@ -28262,6 +28560,125 @@ function MinimapCanvasInner(props) {
28262
28560
  );
28263
28561
  }
28264
28562
  var MinimapCanvas = MinimapCanvasInner;
28563
+ function planPixelRender(args) {
28564
+ const { sourceRect, scale: scale2 } = args;
28565
+ for (const [label, v] of [
28566
+ ["sourceRect.width", sourceRect.width],
28567
+ ["sourceRect.height", sourceRect.height],
28568
+ ["scale.x", scale2.x],
28569
+ ["scale.y", scale2.y]
28570
+ ]) {
28571
+ if (!Number.isFinite(v) || v <= 0) {
28572
+ throw new Error(`renderSceneToPixels: ${label} must be a positive finite number, got ${v}`);
28573
+ }
28574
+ }
28575
+ for (const [label, v] of [
28576
+ ["sourceRect.x", sourceRect.x],
28577
+ ["sourceRect.y", sourceRect.y]
28578
+ ]) {
28579
+ if (!Number.isFinite(v)) {
28580
+ throw new Error(`renderSceneToPixels: ${label} must be a finite number, got ${v}`);
28581
+ }
28582
+ }
28583
+ const width = Math.max(1, Math.round(sourceRect.width * scale2.x));
28584
+ const height = Math.max(1, Math.round(sourceRect.height * scale2.y));
28585
+ const view = { x: sourceRect.x, y: sourceRect.y, scale: { x: scale2.x, y: scale2.y } };
28586
+ const resolveImage = args.resolveImage;
28587
+ const drawOne = args.drawOne ?? ((node, pose, view2, ctx) => defaultDrawOne(node, pose, view2, { ...ctx, resolveImage }));
28588
+ const commands = [];
28589
+ if (args.background !== void 0) {
28590
+ commands.push({
28591
+ kind: "path",
28592
+ path: { kind: "rect", x: 0, y: 0, width, height },
28593
+ fill: { fill: "solid", color: args.background }
28594
+ });
28595
+ }
28596
+ commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor));
28597
+ return { width, height, view, commands };
28598
+ }
28599
+ function defaultCreateCanvas(width, height) {
28600
+ if (typeof OffscreenCanvas !== "undefined") {
28601
+ return new OffscreenCanvas(width, height);
28602
+ }
28603
+ if (typeof document !== "undefined") {
28604
+ const c = document.createElement("canvas");
28605
+ c.width = width;
28606
+ c.height = height;
28607
+ return c;
28608
+ }
28609
+ throw new Error("renderSceneToPixels: no canvas source in this environment \u2014 supply `gl` or `createCanvas`");
28610
+ }
28611
+ function flipRows(raw, width, height) {
28612
+ const rowBytes = width * 4;
28613
+ const out = new Uint8ClampedArray(raw.length);
28614
+ for (let y = 0; y < height; y++) {
28615
+ out.set(raw.subarray((height - 1 - y) * rowBytes, (height - y) * rowBytes), y * rowBytes);
28616
+ }
28617
+ return out;
28618
+ }
28619
+ function unpremultiply(data) {
28620
+ for (let i = 0; i < data.length; i += 4) {
28621
+ const a = data[i + 3];
28622
+ if (a === 0 || a === 255) continue;
28623
+ const inv = 255 / a;
28624
+ data[i] = data[i] * inv;
28625
+ data[i + 1] = data[i + 1] * inv;
28626
+ data[i + 2] = data[i + 2] * inv;
28627
+ }
28628
+ }
28629
+ function renderSceneToPixels(args) {
28630
+ if (args.gl && args.createCanvas) {
28631
+ throw new Error("renderSceneToPixels: `gl` and `createCanvas` are mutually exclusive");
28632
+ }
28633
+ const plan = planPixelRender(args);
28634
+ const { width, height } = plan;
28635
+ let gl = args.gl;
28636
+ if (!gl) {
28637
+ const canvas = (args.createCanvas ?? defaultCreateCanvas)(width, height);
28638
+ canvas.width = width;
28639
+ canvas.height = height;
28640
+ gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true, stencil: true }) ?? void 0;
28641
+ }
28642
+ if (!gl || typeof gl.enable !== "function") {
28643
+ throw new Error("renderSceneToPixels: WebGL2 is unavailable \u2014 supply `gl` or a WebGL2-capable `createCanvas`");
28644
+ }
28645
+ if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
28646
+ throw new Error("renderSceneToPixels: the supplied WebGL2 context is lost");
28647
+ }
28648
+ const flattenTolerancePx = args.flattenTolerancePx ?? 0.25;
28649
+ const renderer = new WeaselRenderer({
28650
+ gl,
28651
+ width,
28652
+ height,
28653
+ dpr: 1,
28654
+ imageMinification: "mipmap",
28655
+ flattenTolerance: flattenTolerancePx / Math.max(args.scale.x, args.scale.y),
28656
+ // Dynamic canvas-SDF glyphs must all bake inline — this path is
28657
+ // synchronous with no notify-and-redraw, and print must be complete.
28658
+ bakeBudget: Infinity
28659
+ });
28660
+ try {
28661
+ renderer.render(plan.commands, viewToMat3(plan.view));
28662
+ if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
28663
+ throw new Error("renderSceneToPixels: WebGL2 context was lost during render");
28664
+ }
28665
+ const raw = new Uint8Array(width * height * 4);
28666
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, raw);
28667
+ const data = flipRows(raw, width, height);
28668
+ unpremultiply(data);
28669
+ return { width, height, data };
28670
+ } finally {
28671
+ renderer.dispose();
28672
+ }
28673
+ }
28674
+ function WeaselProvider({ children, isolate = false }) {
28675
+ const Deps = isolate ? DepRegistryProvider : DepRegistryProviderIfRoot;
28676
+ const Actions = isolate ? ActionsProvider : ActionsProviderIfRoot;
28677
+ const ActiveTool = isolate ? ActiveToolContextProvider : ActiveToolContextProviderIfRoot;
28678
+ const Selection = isolate ? SelectionContextProvider : SelectionContextProviderIfRoot;
28679
+ const Pointer = isolate ? PointerContextProvider : PointerProviderIfRoot;
28680
+ return /* @__PURE__ */ jsx(Deps, { children: /* @__PURE__ */ jsx(Actions, { children: /* @__PURE__ */ jsx(ActiveTool, { children: /* @__PURE__ */ jsx(Selection, { children: /* @__PURE__ */ jsx(Pointer, { children }) }) }) }) });
28681
+ }
28265
28682
  function createSetTextOp(args) {
28266
28683
  const { id, from, to, label, coalesceKey = `setText:${id}` } = args;
28267
28684
  return {
@@ -28398,6 +28815,6 @@ function oklchToOklab(L, C, h) {
28398
28815
  return [L, C * Math.cos(h), C * Math.sin(h)];
28399
28816
  }
28400
28817
 
28401
- export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView, computeIndicatorCommand, eligibleContribution, evaluateEnabled, isBuiltinToolPref, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry, useVisibleRaf };
28402
- //# sourceMappingURL=chunk-Z6HJ5FEM.js.map
28403
- //# sourceMappingURL=chunk-Z6HJ5FEM.js.map
28818
+ export { ArrowIcon, EllipseIcon, FALLBACK_FIT_VIEW, LineIcon, MinimapCanvas, PATH_C, PATH_L, PATH_M, PATH_Q, PATH_Z, PathBuilder, PencilIcon, RectIcon, SCRIPT_METRICS, SceneCanvas, SceneViewCanvas, SelectIcon, TextIcon, UnknownIcon, WeaselProvider, asNodeId, boundsOfPath, buildSceneViewCommands, computeFitView, computeIndicatorCommand, createScene, eligibleContribution, ellipsePath, evaluateEnabled, getMarker, getPaintKind, isBuiltinToolPref, linePath, markerDrawCommands, oklabToSrgbU8, oklchToOklab, rectPath, renderSceneToCanvas, renderSceneToPixels, resolveStrokeWidth, rgbaToHex, sceneFromJSON, textCommand, tileGeometry, useActionsRegistry, useActiveToolContext, useOptionalDepRegistry, useVisibleRaf };
28819
+ //# sourceMappingURL=chunk-ROQDRXKW.js.map
28820
+ //# sourceMappingURL=chunk-ROQDRXKW.js.map