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

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 (211) hide show
  1. package/README.md +226 -55
  2. package/dist/_dts/CanvasStackContext-LnCfqNBA.d.ts +42 -0
  3. package/dist/_dts/DrawCommand-B3bskUsC.d.ts +564 -0
  4. package/dist/_dts/{PrefsForm-BYa6cWnO.d.ts → PrefsForm-BkUJZx0A.d.ts} +4 -1
  5. package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +21 -0
  6. package/dist/_dts/frac-C-2c72Ij.d.ts +735 -0
  7. package/dist/_dts/index-BDVzvRzQ.d.ts +236 -0
  8. package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +87 -0
  9. package/dist/_dts/{DrawCommand-DBB45NfN.d.ts → types-C-gh9Ap-.d.ts} +1 -582
  10. package/dist/_dts/{types-DCGEcMO_.d.ts → types-D6s4b7if.d.ts} +1 -1
  11. package/dist/_dts/{types-1Sdxy_Pv.d.ts → types-DYMaEvM5.d.ts} +26 -1
  12. package/dist/_dts/{useTrialState-CsGMjhu9.d.ts → useTrialState-gmMvZqPc.d.ts} +7 -2
  13. package/dist/canvas/index.d.ts +110 -26
  14. package/dist/canvas/index.js +5 -4
  15. package/dist/chrome/index.d.ts +9 -6
  16. package/dist/chrome/index.js +6 -6
  17. package/dist/chunk-2AYGEN57.js +32 -0
  18. package/dist/chunk-2AYGEN57.js.map +1 -0
  19. package/dist/chunk-6OEYTYML.js +669 -0
  20. package/dist/chunk-6OEYTYML.js.map +1 -0
  21. package/dist/{chunk-BMW4TDP5.js → chunk-AE5CNVRU.js} +3 -3
  22. package/dist/{chunk-BMW4TDP5.js.map → chunk-AE5CNVRU.js.map} +1 -1
  23. package/dist/{chunk-CPUJ3QXL.js → chunk-CQLQPQ4P.js} +2 -2
  24. package/dist/chunk-CQLQPQ4P.js.map +1 -0
  25. package/dist/chunk-CTRKTLYZ.js +33 -0
  26. package/dist/chunk-CTRKTLYZ.js.map +1 -0
  27. package/dist/chunk-E2UQVZ44.js +458 -0
  28. package/dist/chunk-E2UQVZ44.js.map +1 -0
  29. package/dist/{chunk-LGKRIUSW.js → chunk-ESIQNQ6K.js} +32 -7
  30. package/dist/chunk-ESIQNQ6K.js.map +1 -0
  31. package/dist/{chunk-JDROYQM3.js → chunk-HHGVISVZ.js} +130 -131
  32. package/dist/chunk-HHGVISVZ.js.map +1 -0
  33. package/dist/{chunk-BDJXIBRZ.js → chunk-HMFODCOX.js} +126 -29
  34. package/dist/chunk-HMFODCOX.js.map +1 -0
  35. package/dist/{chunk-QCICIHEO.js → chunk-L5QJOOLV.js} +839 -320
  36. package/dist/chunk-L5QJOOLV.js.map +1 -0
  37. package/dist/{chunk-3XXPU73K.js → chunk-M75ZU6ZZ.js} +9 -7
  38. package/dist/chunk-M75ZU6ZZ.js.map +1 -0
  39. package/dist/{chunk-QRYPXSGP.js → chunk-QB5SM4BM.js} +16 -8
  40. package/dist/chunk-QB5SM4BM.js.map +1 -0
  41. package/dist/chunk-RBGKL7NF.js +7128 -0
  42. package/dist/chunk-RBGKL7NF.js.map +1 -0
  43. package/dist/{chunk-WTI26YTM.js → chunk-TO2FUOKF.js} +40 -13
  44. package/dist/chunk-TO2FUOKF.js.map +1 -0
  45. package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
  46. package/dist/chunk-XJ6N32QP.js.map +1 -0
  47. package/dist/controls/index.d.ts +4 -28
  48. package/dist/controls/index.js +3 -3
  49. package/dist/dragdrop/index.d.ts +9 -5
  50. package/dist/dragdrop/index.js +3 -3
  51. package/dist/index.d.ts +517 -234
  52. package/dist/index.js +1769 -143
  53. package/dist/index.js.map +1 -1
  54. package/dist/layers/index.d.ts +6 -5
  55. package/dist/layers/index.js +3 -3
  56. package/dist/loupe/index.d.ts +187 -0
  57. package/dist/loupe/index.js +7 -0
  58. package/dist/loupe/index.js.map +1 -0
  59. package/dist/passthrough/weasel-canvas.d.ts +3 -1
  60. package/dist/passthrough/weasel-canvas.js +1 -1
  61. package/dist/passthrough/weasel-ui.d.ts +138 -91
  62. package/dist/passthrough/weasel-ui.js +2 -2
  63. package/dist/primitives/index.d.ts +3 -1
  64. package/dist/primitives/index.js +5 -5
  65. package/dist/state/index.d.ts +3 -3
  66. package/dist/state/index.js +2 -2
  67. package/dist/styles.css +229 -1
  68. package/dist/surface/index.d.ts +18 -2
  69. package/dist/surface/index.js +2 -2
  70. package/dist/ui/layers/index.js +2 -2
  71. package/dist/undo/index.d.ts +5 -4
  72. package/package.json +14 -7
  73. package/src/annotations/AnnotationOverlay.tsx +205 -0
  74. package/src/annotations/AnnotationTargets.tsx +37 -0
  75. package/src/annotations/Annotations.less +129 -0
  76. package/src/annotations/Annotations.meaning.test.tsx +111 -0
  77. package/src/annotations/Annotations.overlay.test.tsx +142 -0
  78. package/src/annotations/AnnotationsContext.ts +49 -0
  79. package/src/annotations/ExportMenu.test.tsx +86 -0
  80. package/src/annotations/ExportMenu.tsx +182 -0
  81. package/src/annotations/MarkList.tsx +75 -0
  82. package/src/annotations/capture.test.ts +140 -0
  83. package/src/annotations/capture.ts +237 -0
  84. package/src/annotations/drawOne.test.ts +56 -0
  85. package/src/annotations/drawOne.ts +47 -0
  86. package/src/annotations/frac.test.ts +59 -0
  87. package/src/annotations/frac.ts +66 -0
  88. package/src/annotations/history.test.ts +82 -0
  89. package/src/annotations/history.ts +118 -0
  90. package/src/annotations/index.ts +37 -0
  91. package/src/annotations/paint.test.ts +125 -0
  92. package/src/annotations/paint.ts +130 -0
  93. package/src/annotations/staleness.test.ts +56 -0
  94. package/src/annotations/staleness.ts +41 -0
  95. package/src/annotations/store.test.ts +247 -0
  96. package/src/annotations/store.ts +347 -0
  97. package/src/annotations/svgNodes.test.ts +77 -0
  98. package/src/annotations/svgNodes.ts +74 -0
  99. package/src/annotations/toolMap.test.ts +36 -0
  100. package/src/annotations/toolMap.ts +54 -0
  101. package/src/annotations/types.ts +229 -0
  102. package/src/annotations/view.test.ts +37 -0
  103. package/src/annotations/view.ts +40 -0
  104. package/src/canvas/AGENTS.md +55 -5
  105. package/src/canvas/CanvasStack.test.tsx +27 -1
  106. package/src/canvas/CanvasStack.tsx +29 -4
  107. package/src/canvas/CanvasStackContext.ts +23 -4
  108. package/src/canvas/camera.test.ts +78 -0
  109. package/src/canvas/camera.ts +62 -0
  110. package/src/canvas/canvasCoords.test.ts +26 -0
  111. package/src/canvas/canvasCoords.ts +20 -7
  112. package/src/canvas/index.ts +16 -1
  113. package/src/canvas/useLayerScheduler.ts +7 -3
  114. package/src/canvas/usePanZoom.test.ts +36 -5
  115. package/src/canvas/usePanZoom.ts +20 -15
  116. package/src/canvas/worldSpec.test.ts +63 -0
  117. package/src/canvas/worldSpec.ts +48 -0
  118. package/src/chrome/builtins.test.ts +31 -1
  119. package/src/chrome/builtins.tsx +63 -19
  120. package/src/chrome/regions/PaletteRegion.test.tsx +22 -1
  121. package/src/chrome/regions/PaletteRegion.tsx +4 -0
  122. package/src/chrome/regions/SidebarRegion.tsx +37 -13
  123. package/src/chrome/regions/ToolbarRegion.tsx +2 -1
  124. package/src/chrome/regions/ViewportRegion.tsx +9 -1
  125. package/src/chrome/regions/regions.test.tsx +6 -1
  126. package/src/chrome/types.ts +20 -0
  127. package/src/controls/ControlPanel.test.tsx +38 -0
  128. package/src/controls/ControlPanel.tsx +50 -3
  129. package/src/dragdrop/DragDropRuntime.tsx +8 -2
  130. package/src/dragdrop/dragDrop.test.tsx +29 -2
  131. package/src/index.test.ts +23 -0
  132. package/src/index.ts +93 -4
  133. package/src/instrument/SineWave.smoke.test.tsx +1 -0
  134. package/src/instrument/types.ts +24 -1
  135. package/src/lab/Lab.surface.test.tsx +162 -0
  136. package/src/lab/Lab.tsx +102 -17
  137. package/src/lab/Lab.undock.test.tsx +85 -0
  138. package/src/lab/LabFullChrome.stories.tsx +20 -0
  139. package/src/lab/LabShell.less +11 -0
  140. package/src/lab/Workspace.less +73 -0
  141. package/src/lab/Workspace.surface.test.tsx +2 -0
  142. package/src/lab/Workspace.tsx +97 -8
  143. package/src/lab/index.ts +1 -1
  144. package/src/lab/panelHost.ts +37 -0
  145. package/src/loupe/AGENTS.md +49 -0
  146. package/src/loupe/CanvasLoupe.tsx +83 -0
  147. package/src/loupe/DomLoupe.tsx +62 -0
  148. package/src/loupe/Loupe.less +38 -0
  149. package/src/loupe/LoupeBubble.tsx +32 -0
  150. package/src/loupe/TrialLoupe.tsx +93 -0
  151. package/src/loupe/canvasLens.test.ts +205 -0
  152. package/src/loupe/canvasLens.ts +141 -0
  153. package/src/loupe/index.ts +19 -0
  154. package/src/loupe/types.test.ts +29 -0
  155. package/src/loupe/types.ts +90 -0
  156. package/src/loupe/useHostSize.ts +30 -0
  157. package/src/loupe/useLoupe.test.tsx +197 -0
  158. package/src/loupe/useLoupe.ts +189 -0
  159. package/src/passthrough/weasel-ui.ts +3 -0
  160. package/src/primitives/FloatingPanel.test.tsx +76 -1
  161. package/src/primitives/FloatingPanel.tsx +19 -3
  162. package/src/primitives/ScaleIndicator.test.tsx +7 -2
  163. package/src/primitives/Sidebar.less +18 -0
  164. package/src/primitives/Toolbar.less +8 -0
  165. package/src/primitives/Toolbar.tsx +5 -0
  166. package/src/primitives/useRovingTabIndex.test.ts +21 -0
  167. package/src/primitives/useRovingTabIndex.ts +26 -17
  168. package/src/state/document.test.ts +23 -0
  169. package/src/state/document.ts +14 -2
  170. package/src/state/index.ts +2 -0
  171. package/src/state/store.test.ts +24 -0
  172. package/src/state/store.ts +35 -1
  173. package/src/state/types.ts +11 -0
  174. package/src/state/undock.test.ts +40 -0
  175. package/src/state/undock.ts +39 -0
  176. package/src/styles.less +2 -0
  177. package/src/surface/SurfaceContext.ts +4 -0
  178. package/src/surface/index.ts +8 -3
  179. package/src/surface/useSurfaceTile.test.tsx +2 -0
  180. package/src/surface/useSurfaceTile.ts +7 -1
  181. package/src/surface/useTiledSurface.test.tsx +58 -0
  182. package/src/surface/useTiledSurface.ts +38 -3
  183. package/src/trial/Trial.annotations.persist.test.tsx +166 -0
  184. package/src/trial/Trial.annotations.test.tsx +121 -0
  185. package/src/trial/Trial.canvas.test.tsx +76 -1
  186. package/src/trial/Trial.less +19 -0
  187. package/src/trial/Trial.loupe.test.tsx +128 -0
  188. package/src/trial/Trial.stories.tsx +1 -0
  189. package/src/trial/Trial.test.tsx +12 -0
  190. package/src/trial/Trial.trialId.test.tsx +36 -0
  191. package/src/trial/Trial.tsx +215 -40
  192. package/src/trial/TrialChrome.tsx +35 -0
  193. package/src/trial/UndockedSections.tsx +65 -0
  194. package/src/trial/trialOps.test.ts +21 -0
  195. package/src/trial/trialOps.ts +5 -3
  196. package/dist/_dts/types-BttTIent.d.ts +0 -314
  197. package/dist/chunk-3XXPU73K.js.map +0 -1
  198. package/dist/chunk-4TMMVIDM.js.map +0 -1
  199. package/dist/chunk-6X5RBC6G.js +0 -366
  200. package/dist/chunk-6X5RBC6G.js.map +0 -1
  201. package/dist/chunk-BDJXIBRZ.js.map +0 -1
  202. package/dist/chunk-CPUJ3QXL.js.map +0 -1
  203. package/dist/chunk-JDROYQM3.js.map +0 -1
  204. package/dist/chunk-LGKRIUSW.js.map +0 -1
  205. package/dist/chunk-QCICIHEO.js.map +0 -1
  206. package/dist/chunk-QRYPXSGP.js.map +0 -1
  207. package/dist/chunk-RGT4EJIB.js +0 -6961
  208. package/dist/chunk-RGT4EJIB.js.map +0 -1
  209. package/dist/chunk-T66HU3HX.js +0 -17
  210. package/dist/chunk-T66HU3HX.js.map +0 -1
  211. package/dist/chunk-WTI26YTM.js.map +0 -1
@@ -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 {
@@ -3802,6 +3841,120 @@ function eligibleContribution(reg, entry) {
3802
3841
  return eligibleForMode(reg.current(), entry.eligibility?.capabilities ?? []);
3803
3842
  }
3804
3843
 
3844
+ // ../cursor/dist/index.js
3845
+ var CURSOR_INK = "#141418";
3846
+ var CURSOR_HALO = "#ffffff";
3847
+ var CURSOR_HALO_WIDTH = 2.6;
3848
+ var CURSOR_MAX_CSS_PX = 128;
3849
+ function renderHalo(p) {
3850
+ switch (p.role) {
3851
+ case "ink":
3852
+ case "accent":
3853
+ return `<path d="${p.d}" fill="${CURSOR_HALO}" stroke="${CURSOR_HALO}" stroke-width="${CURSOR_HALO_WIDTH}" stroke-linejoin="round"/>`;
3854
+ case "stroke":
3855
+ return `<path d="${p.d}" fill="none" stroke="${CURSOR_HALO}" stroke-width="${p.width + CURSOR_HALO_WIDTH}" stroke-linecap="round" stroke-linejoin="round"/>`;
3856
+ // A detail IS halo-coloured and sits on top of the ink; it has no halo.
3857
+ case "detail":
3858
+ return "";
3859
+ }
3860
+ }
3861
+ function renderInk(p) {
3862
+ switch (p.role) {
3863
+ case "ink":
3864
+ return `<path d="${p.d}" fill="${CURSOR_INK}"/>`;
3865
+ case "stroke":
3866
+ return `<path d="${p.d}" fill="none" stroke="${CURSOR_INK}" stroke-width="${p.width}" stroke-linecap="round" stroke-linejoin="round"/>`;
3867
+ case "detail":
3868
+ return `<path d="${p.d}" fill="none" stroke="${CURSOR_HALO}" stroke-width="${p.width}" stroke-linecap="round"/>`;
3869
+ case "accent":
3870
+ return `<path d="${p.d}" fill="${p.fill}"/>`;
3871
+ }
3872
+ }
3873
+ function bakeCursor(glyph, opts = {}) {
3874
+ const size = opts.size ?? 24;
3875
+ if (size > CURSOR_MAX_CSS_PX) {
3876
+ throw new RangeError(
3877
+ `cursor size ${size} exceeds the ${CURSOR_MAX_CSS_PX}px cap: the browser would drop the image and silently fall back. Use the painted tier.`
3878
+ );
3879
+ }
3880
+ const body = glyph.paths.map(renderHalo).join("") + glyph.paths.map(renderInk).join("");
3881
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${glyph.box} ${glyph.box}">${body}</svg>`;
3882
+ const hx = Math.round(glyph.hotspot[0] / glyph.box * size);
3883
+ const hy = Math.round(glyph.hotspot[1] / glyph.box * size);
3884
+ const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;
3885
+ return `url("${uri}") ${hx} ${hy}, ${opts.fallback ?? "default"}`;
3886
+ }
3887
+ var GLYPHS = {
3888
+ "pencil": {
3889
+ "box": 24,
3890
+ "hotspot": [
3891
+ 5,
3892
+ 19
3893
+ ],
3894
+ "paths": [
3895
+ {
3896
+ "role": "ink",
3897
+ "d": "M 5 19 L 7.5 16.5 L 16 8 L 19 11 L 10.5 19.5 L 8 19 Z"
3898
+ },
3899
+ {
3900
+ "role": "detail",
3901
+ "d": "M 14 6 L 19 11",
3902
+ "width": 1.2
3903
+ }
3904
+ ]
3905
+ },
3906
+ "pen": {
3907
+ "box": 24,
3908
+ "hotspot": [
3909
+ 5,
3910
+ 19
3911
+ ],
3912
+ "paths": [
3913
+ {
3914
+ "role": "ink",
3915
+ "d": "M 5 19 L 8.5 9.5 L 13 5 L 18 10 L 13.5 14.5 Z"
3916
+ },
3917
+ {
3918
+ "role": "detail",
3919
+ "d": "M 8.4 13.9 L 12.2 10.1",
3920
+ "width": 0.9
3921
+ }
3922
+ ]
3923
+ },
3924
+ "eyedropper": {
3925
+ "box": 24,
3926
+ "hotspot": [
3927
+ 5,
3928
+ 19
3929
+ ],
3930
+ "paths": [
3931
+ {
3932
+ "role": "ink",
3933
+ "d": "M 5 19 L 6.8 14.8 L 14.4 7.2 L 16.8 9.6 L 9.2 17.2 Z"
3934
+ },
3935
+ {
3936
+ "role": "ink",
3937
+ "d": "M 18.2 2.6 A 3.2 3.2 0 1 0 18.2 9 A 3.2 3.2 0 1 0 18.2 2.6 Z"
3938
+ }
3939
+ ]
3940
+ }
3941
+ };
3942
+ var cache2 = /* @__PURE__ */ new Map();
3943
+ function cursorFor(name, opts = {}) {
3944
+ const size = opts.size ?? 24;
3945
+ const fallback = opts.fallback ?? "default";
3946
+ const key = `${name}|${size}|${fallback}`;
3947
+ const hit = cache2.get(key);
3948
+ if (hit !== void 0) return hit;
3949
+ const glyph = GLYPHS[name];
3950
+ if (glyph === void 0) {
3951
+ throw new Error(`unknown cursor glyph: ${String(name)}`);
3952
+ }
3953
+ const css = bakeCursor(glyph, { size, fallback });
3954
+ cache2.set(key, css);
3955
+ return css;
3956
+ }
3957
+
3805
3958
  // ../history/dist/index.js
3806
3959
  var RESUMERS = /* @__PURE__ */ new WeakMap();
3807
3960
  function _resumeJournalInternal(j) {
@@ -4224,7 +4377,7 @@ function serialToEntry(se, custom, logger) {
4224
4377
  };
4225
4378
  }
4226
4379
 
4227
- // ../core/dist/chunk-N3T7D6AS.js
4380
+ // ../core/dist/chunk-FUQWXDG3.js
4228
4381
  function worldToScreen(worldX, worldY, view) {
4229
4382
  return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
4230
4383
  }
@@ -5551,6 +5704,9 @@ function flattenPolygon(p, tolerance) {
5551
5704
  let coordIdx = 0;
5552
5705
  let prevX = 0;
5553
5706
  let prevY = 0;
5707
+ let startX = 0;
5708
+ let startY = 0;
5709
+ let startAnchor = -1;
5554
5710
  let prevAnchor = -1;
5555
5711
  let anchorCounter = 0;
5556
5712
  for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
@@ -5560,6 +5716,9 @@ function flattenPolygon(p, tolerance) {
5560
5716
  contourStarts.push(out.length / 2);
5561
5717
  prevX = coords[coordIdx];
5562
5718
  prevY = coords[coordIdx + 1];
5719
+ startX = prevX;
5720
+ startY = prevY;
5721
+ startAnchor = anchorCounter;
5563
5722
  out.push(prevX, prevY);
5564
5723
  anchorA.push(anchorCounter);
5565
5724
  anchorB.push(anchorCounter);
@@ -5629,6 +5788,9 @@ function flattenPolygon(p, tolerance) {
5629
5788
  break;
5630
5789
  }
5631
5790
  case PATH_Z: {
5791
+ prevX = startX;
5792
+ prevY = startY;
5793
+ prevAnchor = startAnchor;
5632
5794
  break;
5633
5795
  }
5634
5796
  default:
@@ -5773,12 +5935,12 @@ function pointInContour(coords, start, end, x, y) {
5773
5935
  }
5774
5936
  return inside;
5775
5937
  }
5776
- var cache2 = /* @__PURE__ */ new WeakMap();
5938
+ var cache3 = /* @__PURE__ */ new WeakMap();
5777
5939
  function getMesh(path) {
5778
- const cached2 = cache2.get(path);
5940
+ const cached2 = cache3.get(path);
5779
5941
  if (cached2 !== void 0) return cached2;
5780
5942
  const mesh = tessellate(path);
5781
- cache2.set(path, mesh);
5943
+ cache3.set(path, mesh);
5782
5944
  return mesh;
5783
5945
  }
5784
5946
  function extractPolylines(path, opts = {}) {
@@ -6798,7 +6960,7 @@ function outlineMesh(key, d) {
6798
6960
  }
6799
6961
  var OUTLINE_STROKE_MESH_CACHE_LIMIT = 2048;
6800
6962
  var WIDTH_QUANTUM = 1 / 1024;
6801
- var cache3 = /* @__PURE__ */ new Map();
6963
+ var cache32 = /* @__PURE__ */ new Map();
6802
6964
  function quantizeEmWidth(emWidth) {
6803
6965
  return Math.round(emWidth / WIDTH_QUANTUM) * WIDTH_QUANTUM;
6804
6966
  }
@@ -6813,15 +6975,15 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
6813
6975
  stroke.align ?? "center",
6814
6976
  (stroke.dash ?? []).join(",")
6815
6977
  ].join("|");
6816
- const cached2 = cache3.get(key);
6978
+ const cached2 = cache32.get(key);
6817
6979
  if (cached2 !== void 0) return cached2.indices.length === 0 ? null : cached2;
6818
- if (cache3.size >= OUTLINE_STROKE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
6980
+ if (cache32.size >= OUTLINE_STROKE_MESH_CACHE_LIMIT) cache32 = /* @__PURE__ */ new Map();
6819
6981
  const mesh = tessellateStroke(
6820
6982
  pathFromD(d),
6821
6983
  { ...stroke, width: emWidth },
6822
6984
  { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE }
6823
6985
  );
6824
- cache3.set(key, mesh);
6986
+ cache32.set(key, mesh);
6825
6987
  return mesh.indices.length === 0 ? null : mesh;
6826
6988
  }
6827
6989
  function poly(pts, size, close = true) {
@@ -8444,6 +8606,7 @@ var WeaselRenderer = class {
8444
8606
  heightCss;
8445
8607
  dpr;
8446
8608
  canvas = null;
8609
+ target = null;
8447
8610
  imageMinification;
8448
8611
  flattenTolerance;
8449
8612
  bakeBudget;
@@ -8461,6 +8624,12 @@ var WeaselRenderer = class {
8461
8624
  const gl = opts.gl ?? opts.canvas.getContext("webgl2", { stencil: true });
8462
8625
  if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
8463
8626
  this.gl = gl;
8627
+ const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
8628
+ if (attrs && attrs.stencil === false) {
8629
+ throw new Error(
8630
+ "WeaselRenderer: the supplied WebGL2 context has no stencil buffer. Create it with { stencil: true } \u2014 clips and even-odd fills need one."
8631
+ );
8632
+ }
8464
8633
  this.widthCss = opts.width;
8465
8634
  this.heightCss = opts.height;
8466
8635
  this.dpr = opts.dpr;
@@ -8479,12 +8648,8 @@ var WeaselRenderer = class {
8479
8648
  this.canvas.addEventListener("webglcontextlost", this.boundOnLost);
8480
8649
  this.canvas.addEventListener("webglcontextrestored", this.boundOnRestored);
8481
8650
  }
8482
- this.gl.enable(this.gl.BLEND);
8483
- this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
8484
- this.gl.disable(this.gl.DEPTH_TEST);
8485
- this.gl.disable(this.gl.CULL_FACE);
8486
- this.gl.clearColor(0, 0, 0, 0);
8487
- this.applyViewport();
8651
+ this.applyGlState();
8652
+ this.applyTarget();
8488
8653
  this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
8489
8654
  this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
8490
8655
  this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
@@ -8574,8 +8739,42 @@ var WeaselRenderer = class {
8574
8739
  }
8575
8740
  return this.programRegistry.get(id) ?? null;
8576
8741
  }
8577
- applyViewport() {
8578
- this.gl.viewport(0, 0, this.widthCss * this.dpr, this.heightCss * this.dpr);
8742
+ /** The GL state every frame assumes. Applied per `render()` rather than once
8743
+ * at construction because a co-tenant sharing this context moves all of it
8744
+ * between our frames. */
8745
+ applyGlState() {
8746
+ const gl = this.gl;
8747
+ gl.enable(gl.BLEND);
8748
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
8749
+ gl.disable(gl.DEPTH_TEST);
8750
+ gl.disable(gl.CULL_FACE);
8751
+ gl.clearColor(0, 0, 0, 0);
8752
+ }
8753
+ /** Viewport and scissor for this frame. Re-applied inside `render()` because
8754
+ * a co-tenant on the same context moves both between our frames. */
8755
+ applyTarget() {
8756
+ const gl = this.gl;
8757
+ const w = Math.round(this.widthCss * this.dpr);
8758
+ const h = Math.round(this.heightCss * this.dpr);
8759
+ if (!this.target) {
8760
+ gl.disable(gl.SCISSOR_TEST);
8761
+ gl.viewport(0, 0, w, h);
8762
+ return;
8763
+ }
8764
+ const x = Math.round(this.target.origin.x * this.dpr);
8765
+ const y = gl.drawingBufferHeight - Math.round(this.target.origin.y * this.dpr) - h;
8766
+ gl.viewport(x, y, w, h);
8767
+ gl.scissor(x, y, w, h);
8768
+ gl.enable(gl.SCISSOR_TEST);
8769
+ }
8770
+ /** Confine this renderer to a rect of its drawing buffer, or pass null to
8771
+ * give it the whole buffer back. Takes effect from the next `render()`. */
8772
+ setTarget(target) {
8773
+ this.target = target;
8774
+ this.applyTarget();
8775
+ }
8776
+ getTarget() {
8777
+ return this.target;
8579
8778
  }
8580
8779
  isContextLost() {
8581
8780
  return this.contextLost;
@@ -8586,12 +8785,8 @@ var WeaselRenderer = class {
8586
8785
  }
8587
8786
  onContextRestored() {
8588
8787
  this.contextLost = false;
8589
- this.gl.enable(this.gl.BLEND);
8590
- this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
8591
- this.gl.disable(this.gl.DEPTH_TEST);
8592
- this.gl.disable(this.gl.CULL_FACE);
8593
- this.gl.clearColor(0, 0, 0, 0);
8594
- this.applyViewport();
8788
+ this.applyGlState();
8789
+ this.applyTarget();
8595
8790
  this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
8596
8791
  this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
8597
8792
  this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
@@ -8698,8 +8893,12 @@ var WeaselRenderer = class {
8698
8893
  this.meshCache.drainPendingDeletes();
8699
8894
  resetBakeBudget(this.bakeBudget);
8700
8895
  this.groupState.reset();
8701
- gl.stencilMask(255);
8702
- gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
8896
+ this.applyGlState();
8897
+ this.applyTarget();
8898
+ if (this.target?.clear !== false) {
8899
+ gl.stencilMask(255);
8900
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
8901
+ }
8703
8902
  const ctx = {
8704
8903
  gl,
8705
8904
  pathFill: this.pathFill,
@@ -8742,7 +8941,7 @@ var WeaselRenderer = class {
8742
8941
  this.canvas.style.height = `${dims.height}px`;
8743
8942
  }
8744
8943
  }
8745
- this.applyViewport();
8944
+ this.applyTarget();
8746
8945
  }
8747
8946
  /** @internal */
8748
8947
  _gl() {
@@ -9382,10 +9581,10 @@ function targetSizesPx(targetScale = 1) {
9382
9581
  rotationDistance: BASE_PX.rotationDistance * targetScale
9383
9582
  };
9384
9583
  }
9385
- var SYSTEM_MAX_SCALE = 10;
9386
- var SYSTEM_MIN_SCALE = 0.1;
9584
+ var DEFAULT_MIN_ZOOM = 0.1;
9585
+ var DEFAULT_MAX_ZOOM = 8;
9387
9586
  function fitViewToBounds(bounds, viewportDims, currentView, opts = {}) {
9388
- const { padding = 16, maxScale = SYSTEM_MAX_SCALE, minScale = SYSTEM_MIN_SCALE } = opts;
9587
+ const { padding = 16, maxScale = DEFAULT_MAX_ZOOM, minScale = DEFAULT_MIN_ZOOM } = opts;
9389
9588
  if (bounds.width <= 0 || bounds.height <= 0) {
9390
9589
  console.warn("[weasel] fitViewToBounds: bounds has zero or negative area; returning currentView unchanged.", bounds);
9391
9590
  return currentView;
@@ -9429,15 +9628,19 @@ function fitViewToBounds(bounds, viewportDims, currentView, opts = {}) {
9429
9628
  scale: { x: scaleX, y: scaleY }
9430
9629
  };
9431
9630
  }
9631
+ function clampScale(scale2, min, max) {
9632
+ const magnitude = Math.min(max, Math.max(min, Math.abs(scale2)));
9633
+ return scale2 < 0 ? -magnitude : magnitude;
9634
+ }
9432
9635
  function zoomAt(view, anchor, factor, opts) {
9433
9636
  const fx = typeof factor === "number" ? factor : factor.x;
9434
9637
  const fy = typeof factor === "number" ? factor : factor.y;
9435
- const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? 0.1;
9436
- const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? 0.1;
9437
- const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? 8;
9438
- const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? 8;
9439
- const nextX = Math.min(maxX, Math.max(minX, view.scale.x * fx));
9440
- const nextY = Math.min(maxY, Math.max(minY, view.scale.y * fy));
9638
+ const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? DEFAULT_MIN_ZOOM;
9639
+ const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? DEFAULT_MIN_ZOOM;
9640
+ const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? DEFAULT_MAX_ZOOM;
9641
+ const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? DEFAULT_MAX_ZOOM;
9642
+ const nextX = clampScale(view.scale.x * fx, minX, maxX);
9643
+ const nextY = clampScale(view.scale.y * fy, minY, maxY);
9441
9644
  const worldX = anchor.x / view.scale.x + view.x;
9442
9645
  const worldY = anchor.y / view.scale.y + view.y;
9443
9646
  return {
@@ -9447,22 +9650,24 @@ function zoomAt(view, anchor, factor, opts) {
9447
9650
  };
9448
9651
  }
9449
9652
  function clampView(view, bounds, canvas) {
9450
- const visW = canvas.width / view.scale.x;
9451
- const visH = canvas.height / view.scale.y;
9653
+ const visW = Math.abs(canvas.width / view.scale.x);
9654
+ const visH = Math.abs(canvas.height / view.scale.y);
9655
+ const flipX = view.scale.x < 0;
9656
+ const flipY = view.scale.y < 0;
9452
9657
  let x;
9453
9658
  if (visW >= bounds.width) {
9454
- x = bounds.x + (bounds.width - visW) / 2;
9659
+ x = bounds.x + (bounds.width - visW) / 2 + (flipX ? visW : 0);
9455
9660
  } else {
9456
- const minX = bounds.x;
9457
- const maxX = bounds.x + bounds.width - visW;
9661
+ const minX = flipX ? bounds.x + visW : bounds.x;
9662
+ const maxX = flipX ? bounds.x + bounds.width : bounds.x + bounds.width - visW;
9458
9663
  x = view.x < minX ? minX : view.x > maxX ? maxX : view.x;
9459
9664
  }
9460
9665
  let y;
9461
9666
  if (visH >= bounds.height) {
9462
- y = bounds.y + (bounds.height - visH) / 2;
9667
+ y = bounds.y + (bounds.height - visH) / 2 + (flipY ? visH : 0);
9463
9668
  } else {
9464
- const minY = bounds.y;
9465
- const maxY = bounds.y + bounds.height - visH;
9669
+ const minY = flipY ? bounds.y + visH : bounds.y;
9670
+ const maxY = flipY ? bounds.y + bounds.height : bounds.y + bounds.height - visH;
9466
9671
  y = view.y < minY ? minY : view.y > maxY ? maxY : view.y;
9467
9672
  }
9468
9673
  return x === view.x && y === view.y ? view : { x, y, scale: view.scale };
@@ -11165,7 +11370,7 @@ function keyAnchor(view) {
11165
11370
  }
11166
11371
  var WHEEL_STEP = 1.1;
11167
11372
  function makeViewportZoomAction(opts = {}) {
11168
- const clamp2 = { min: opts.min ?? 0.1, max: opts.max ?? 8 };
11373
+ const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
11169
11374
  const wheelSpec = opts.wheel === "plain" ? { kind: "wheel" } : { kind: "wheel", mods: { mod: true } };
11170
11375
  const rawAnimate = opts.animate === true ? {} : opts.animate || null;
11171
11376
  const { resetMs, ...animateRest } = rawAnimate ?? {};
@@ -11814,7 +12019,7 @@ var lassoSelectAction = {
11814
12019
  enabled: () => true
11815
12020
  };
11816
12021
  function makePinchZoomAction(opts = {}) {
11817
- const clamp2 = { min: opts.min ?? 0.1, max: opts.max ?? 8 };
12022
+ const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
11818
12023
  return {
11819
12024
  id: "viewport.pinchZoom",
11820
12025
  label: "Pinch Zoom",
@@ -16349,154 +16554,6 @@ function useGestureDispatcher(opts) {
16349
16554
  };
16350
16555
  }, [enabled, keyboard, canvasRef]);
16351
16556
  }
16352
- function useVelocityTracker() {
16353
- const samplesRef = useRef([]);
16354
- return useMemo(() => ({
16355
- record(dx, dy, t) {
16356
- samplesRef.current.push({ dx, dy, t });
16357
- const cutoff = t - 100;
16358
- samplesRef.current = samplesRef.current.filter((s) => s.t >= cutoff);
16359
- },
16360
- getVelocity() {
16361
- const s = samplesRef.current;
16362
- if (s.length < 2) return { vx: 0, vy: 0 };
16363
- const dt = s[s.length - 1].t - s[0].t;
16364
- if (dt === 0) return { vx: 0, vy: 0 };
16365
- const totalDx = s.slice(1).reduce((acc, p) => acc + p.dx, 0);
16366
- const totalDy = s.slice(1).reduce((acc, p) => acc + p.dy, 0);
16367
- return { vx: totalDx / dt, vy: totalDy / dt };
16368
- },
16369
- reset() {
16370
- samplesRef.current = [];
16371
- }
16372
- }), []);
16373
- }
16374
- var SPRING_DAMPING = 0.5;
16375
- function useDecayLoop() {
16376
- const stateRef = useRef(null);
16377
- const frameLoop = useVisibleRaf(
16378
- (now) => {
16379
- tick(now);
16380
- },
16381
- {
16382
- // Coasting does not continue while suspended: dropping `lastTime` makes
16383
- // the resuming frame seed a fresh interval instead of applying an hour of
16384
- // friction at once, which would end the decay before it was seen.
16385
- onResume: useCallback(() => {
16386
- if (stateRef.current) stateRef.current.lastTime = null;
16387
- }, [])
16388
- }
16389
- );
16390
- const cancel = useCallback(() => {
16391
- frameLoop.cancel();
16392
- stateRef.current = null;
16393
- }, [frameLoop]);
16394
- const tick = useCallback((now) => {
16395
- const s = stateRef.current;
16396
- if (!s) return;
16397
- if (s.lastTime === null) {
16398
- s.lastTime = now;
16399
- frameLoop.request();
16400
- return;
16401
- }
16402
- const dt = Math.min(now - s.lastTime, 64);
16403
- s.lastTime = now;
16404
- const f = Math.pow(s.friction, dt / 16.67);
16405
- s.vx *= f;
16406
- s.vy *= f;
16407
- const speed = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
16408
- if (speed < s.minSpeed) {
16409
- stateRef.current = null;
16410
- frameLoop.cancel();
16411
- s.onEnd?.();
16412
- return;
16413
- }
16414
- let dx = s.vx * dt;
16415
- let dy = s.vy * dt;
16416
- if (s.viewBounds && s.boundary) {
16417
- const { minX, maxX, minY, maxY } = s.viewBounds;
16418
- const newX = s.posX + dx;
16419
- const newY = s.posY + dy;
16420
- if (s.boundary === "stop") {
16421
- if (minX !== void 0 && newX < minX) {
16422
- dx = minX - s.posX;
16423
- s.vx = 0;
16424
- } else if (maxX !== void 0 && newX > maxX) {
16425
- dx = maxX - s.posX;
16426
- s.vx = 0;
16427
- }
16428
- if (minY !== void 0 && newY < minY) {
16429
- dy = minY - s.posY;
16430
- s.vy = 0;
16431
- } else if (maxY !== void 0 && newY > maxY) {
16432
- dy = maxY - s.posY;
16433
- s.vy = 0;
16434
- }
16435
- } else if (s.boundary === "spring") {
16436
- if (minX !== void 0 && newX < minX) {
16437
- dx = minX - s.posX;
16438
- s.vx = Math.abs(s.vx) * SPRING_DAMPING;
16439
- } else if (maxX !== void 0 && newX > maxX) {
16440
- dx = maxX - s.posX;
16441
- s.vx = -Math.abs(s.vx) * SPRING_DAMPING;
16442
- }
16443
- if (minY !== void 0 && newY < minY) {
16444
- dy = minY - s.posY;
16445
- s.vy = Math.abs(s.vy) * SPRING_DAMPING;
16446
- } else if (maxY !== void 0 && newY > maxY) {
16447
- dy = maxY - s.posY;
16448
- s.vy = -Math.abs(s.vy) * SPRING_DAMPING;
16449
- }
16450
- } else {
16451
- if (minX !== void 0 && newX < minX) {
16452
- dx = minX - s.posX;
16453
- s.vx = Math.abs(s.vx);
16454
- } else if (maxX !== void 0 && newX > maxX) {
16455
- dx = maxX - s.posX;
16456
- s.vx = -Math.abs(s.vx);
16457
- }
16458
- if (minY !== void 0 && newY < minY) {
16459
- dy = minY - s.posY;
16460
- s.vy = Math.abs(s.vy);
16461
- } else if (maxY !== void 0 && newY > maxY) {
16462
- dy = maxY - s.posY;
16463
- s.vy = -Math.abs(s.vy);
16464
- }
16465
- }
16466
- s.posX += dx;
16467
- s.posY += dy;
16468
- }
16469
- s.onTick(dx, dy);
16470
- frameLoop.request();
16471
- }, [frameLoop]);
16472
- const start = useCallback((config) => {
16473
- cancel();
16474
- const { velocity, friction = 0.92, minSpeed = 0.01, viewBounds, boundary, initialPosition, onTick, onEnd } = config;
16475
- const speed = Math.sqrt(velocity.vx ** 2 + velocity.vy ** 2);
16476
- if (speed < minSpeed) {
16477
- onEnd?.();
16478
- return;
16479
- }
16480
- stateRef.current = {
16481
- vx: velocity.vx,
16482
- vy: velocity.vy,
16483
- friction,
16484
- minSpeed,
16485
- lastTime: null,
16486
- posX: initialPosition?.x ?? 0,
16487
- posY: initialPosition?.y ?? 0,
16488
- viewBounds,
16489
- boundary,
16490
- onTick,
16491
- onEnd
16492
- };
16493
- frameLoop.request();
16494
- }, [cancel, frameLoop]);
16495
- useEffect(() => () => {
16496
- cancel();
16497
- }, [cancel]);
16498
- return { start, cancel };
16499
- }
16500
16557
  function dist(a, b) {
16501
16558
  return Math.hypot(b.x - a.x, b.y - a.y);
16502
16559
  }
@@ -16575,15 +16632,180 @@ function interpolateView(from, to) {
16575
16632
  return { x: a.x, y: b.x, scale: { x: a.s, y: b.s } };
16576
16633
  };
16577
16634
  }
16635
+ var linear = (t) => t;
16636
+ var easeInQuad = (t) => t * t;
16578
16637
  var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
16638
+ var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
16639
+ var easeInCubic = (t) => t * t * t;
16579
16640
  var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
16641
+ var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
16642
+ var easeInQuart = (t) => t * t * t * t;
16643
+ var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
16644
+ var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
16645
+ var easeInQuint = (t) => t * t * t * t * t;
16646
+ var easeOutQuint = (t) => 1 - Math.pow(1 - t, 5);
16647
+ var easeInOutQuint = (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
16648
+ var easeInSine = (t) => 1 - Math.cos(t * Math.PI / 2);
16649
+ var easeOutSine = (t) => Math.sin(t * Math.PI / 2);
16650
+ var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
16651
+ var easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
16652
+ var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
16653
+ var easeInOutExpo = (t) => {
16654
+ if (t === 0) return 0;
16655
+ if (t === 1) return 1;
16656
+ return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
16657
+ };
16658
+ var easeInCirc = (t) => 1 - Math.sqrt(1 - t * t);
16659
+ var easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
16660
+ 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;
16661
+ var C1 = 1.70158;
16662
+ var C2 = C1 * 1.525;
16663
+ var C3 = C1 + 1;
16664
+ var C4 = 2 * Math.PI / 3;
16665
+ var C5 = 2 * Math.PI / 4.5;
16666
+ var easeInBack = (t) => C3 * t * t * t - C1 * t * t;
16667
+ var easeOutBack = (t) => 1 + C3 * Math.pow(t - 1, 3) + C1 * Math.pow(t - 1, 2);
16668
+ 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;
16669
+ var easeInElastic = (t) => {
16670
+ if (t === 0) return 0;
16671
+ if (t === 1) return 1;
16672
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * C4);
16673
+ };
16674
+ var easeOutElastic = (t) => {
16675
+ if (t === 0) return 0;
16676
+ if (t === 1) return 1;
16677
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * C4) + 1;
16678
+ };
16679
+ var easeInOutElastic = (t) => {
16680
+ if (t === 0) return 0;
16681
+ if (t === 1) return 1;
16682
+ 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;
16683
+ };
16684
+ var easeOutBounce = (t) => {
16685
+ const n1 = 7.5625;
16686
+ const d1 = 2.75;
16687
+ if (t < 1 / d1) return n1 * t * t;
16688
+ if (t < 2 / d1) {
16689
+ t -= 1.5 / d1;
16690
+ return n1 * t * t + 0.75;
16691
+ }
16692
+ if (t < 2.5 / d1) {
16693
+ t -= 2.25 / d1;
16694
+ return n1 * t * t + 0.9375;
16695
+ }
16696
+ t -= 2.625 / d1;
16697
+ return n1 * t * t + 0.984375;
16698
+ };
16699
+ var easeInBounce = (t) => 1 - easeOutBounce(1 - t);
16700
+ var easeInOutBounce = (t) => t < 0.5 ? (1 - easeOutBounce(1 - 2 * t)) / 2 : (1 + easeOutBounce(2 * t - 1)) / 2;
16580
16701
  var easeOut = easeOutQuad;
16702
+ var EASINGS = {
16703
+ linear,
16704
+ easeInQuad,
16705
+ easeOutQuad,
16706
+ easeInOutQuad,
16707
+ easeInCubic,
16708
+ easeOutCubic,
16709
+ easeInOutCubic,
16710
+ easeInQuart,
16711
+ easeOutQuart,
16712
+ easeInOutQuart,
16713
+ easeInQuint,
16714
+ easeOutQuint,
16715
+ easeInOutQuint,
16716
+ easeInSine,
16717
+ easeOutSine,
16718
+ easeInOutSine,
16719
+ easeInExpo,
16720
+ easeOutExpo,
16721
+ easeInOutExpo,
16722
+ easeInCirc,
16723
+ easeOutCirc,
16724
+ easeInOutCirc,
16725
+ easeInBack,
16726
+ easeOutBack,
16727
+ easeInOutBack,
16728
+ easeInElastic,
16729
+ easeOutElastic,
16730
+ easeInOutElastic,
16731
+ easeInBounce,
16732
+ easeOutBounce,
16733
+ easeInOutBounce
16734
+ };
16581
16735
  var SPRING_PRESETS = {
16582
16736
  gentle: { stiffness: 120, damping: 14, mass: 1 },
16583
16737
  wobbly: { stiffness: 180, damping: 12, mass: 1 },
16584
16738
  stiff: { stiffness: 210, damping: 20, mass: 1 },
16585
16739
  slow: { stiffness: 80, damping: 20, mass: 1 }
16586
16740
  };
16741
+ var NEWTON_ITERATIONS = 8;
16742
+ var NEWTON_MIN_SLOPE = 1e-3;
16743
+ var SUBDIVISION_EPSILON = 1e-7;
16744
+ var SUBDIVISION_MAX = 12;
16745
+ function bezierAt(t, a1, a2) {
16746
+ const c = 3 * a1;
16747
+ const b = 3 * (a2 - a1) - c;
16748
+ const a = 1 - c - b;
16749
+ return ((a * t + b) * t + c) * t;
16750
+ }
16751
+ function bezierSlope(t, a1, a2) {
16752
+ const c = 3 * a1;
16753
+ const b = 3 * (a2 - a1) - c;
16754
+ const a = 1 - c - b;
16755
+ return (3 * a * t + 2 * b) * t + c;
16756
+ }
16757
+ function solveForX(x, x1, x2) {
16758
+ let t = x;
16759
+ for (let i = 0; i < NEWTON_ITERATIONS; i++) {
16760
+ const slope = bezierSlope(t, x1, x2);
16761
+ if (slope < NEWTON_MIN_SLOPE) break;
16762
+ t -= (bezierAt(t, x1, x2) - x) / slope;
16763
+ }
16764
+ let lo = 0;
16765
+ let hi = 1;
16766
+ t = Math.min(1, Math.max(0, t));
16767
+ for (let i = 0; i < SUBDIVISION_MAX; i++) {
16768
+ const err = bezierAt(t, x1, x2) - x;
16769
+ if (Math.abs(err) < SUBDIVISION_EPSILON) return t;
16770
+ if (err > 0) hi = t;
16771
+ else lo = t;
16772
+ t = (lo + hi) / 2;
16773
+ }
16774
+ return t;
16775
+ }
16776
+ function cubicBezierEasing(x1, y1, x2, y2) {
16777
+ const cx1 = Math.min(1, Math.max(0, x1));
16778
+ const cx2 = Math.min(1, Math.max(0, x2));
16779
+ return (t) => {
16780
+ if (t <= 0) return 0;
16781
+ if (t >= 1) return 1;
16782
+ return bezierAt(solveForX(t, cx1, cx2), y1, y2);
16783
+ };
16784
+ }
16785
+ var bezierCache = /* @__PURE__ */ new Map();
16786
+ function isBezier(spec) {
16787
+ return typeof spec === "object" && spec !== null && "bezier" in spec;
16788
+ }
16789
+ function resolveEasing(spec) {
16790
+ if (spec === void 0) return EASINGS.linear;
16791
+ if (typeof spec === "function") return spec;
16792
+ if (typeof spec === "string") {
16793
+ const fn = EASINGS[spec];
16794
+ if (!fn) throw new Error(`resolveEasing: unknown easing name "${spec}"`);
16795
+ return fn;
16796
+ }
16797
+ if (isBezier(spec)) {
16798
+ const [x1, y1, x2, y2] = spec.bezier;
16799
+ const key = `${x1},${y1},${x2},${y2}`;
16800
+ let fn = bezierCache.get(key);
16801
+ if (!fn) {
16802
+ fn = cubicBezierEasing(x1, y1, x2, y2);
16803
+ bezierCache.set(key, fn);
16804
+ }
16805
+ return fn;
16806
+ }
16807
+ throw new Error("resolveEasing: unrecognized easing spec");
16808
+ }
16587
16809
  function createLoop(createSupervisor, factory, opts = {}) {
16588
16810
  const max = opts.count ?? Infinity;
16589
16811
  let iteration = 0;
@@ -16623,6 +16845,7 @@ function createLoop(createSupervisor, factory, opts = {}) {
16623
16845
  supervisor.setTimeScale(s);
16624
16846
  current?.setTimeScale(s);
16625
16847
  },
16848
+ timeScale: () => supervisor.timeScale(),
16626
16849
  isPaused: () => supervisor.isPaused()
16627
16850
  };
16628
16851
  }
@@ -16782,6 +17005,9 @@ var CompositeHandle = class {
16782
17005
  this.timeScale_ = s;
16783
17006
  for (const c of this.children) c.setTimeScale(s);
16784
17007
  }
17008
+ timeScale() {
17009
+ return this.timeScale_;
17010
+ }
16785
17011
  isPaused() {
16786
17012
  return this.paused_;
16787
17013
  }
@@ -16889,7 +17115,7 @@ function sampleTrack(track, t, segmentCache) {
16889
17115
  const b = keys[i + 1];
16890
17116
  const span = b.t - a.t;
16891
17117
  const raw = span <= 0 ? 1 : (t - a.t) / span;
16892
- const u = b.easing ? b.easing(raw) : raw;
17118
+ const u = b.easing ? resolveEasing(b.easing)(raw) : raw;
16893
17119
  if (track.interpolator) {
16894
17120
  let fn = segmentCache?.get(i + 1);
16895
17121
  if (!fn) {
@@ -16968,7 +17194,10 @@ function createTimeline(register, id, opts) {
16968
17194
  for (const track of tracks) {
16969
17195
  if (track.kind === "event") {
16970
17196
  const end = firstAfter(track.events, to);
16971
- for (let i = firstAfter(track.events, from); i < end; i += 1) track.events[i].fire();
17197
+ for (let i = firstAfter(track.events, from); i < end; i += 1) {
17198
+ const ev = track.events[i];
17199
+ ev.fire(to - ev.t);
17200
+ }
16972
17201
  } else if (track.kind === "timeline") {
16973
17202
  fireEvents(track.timeline.tracks, from - track.at, to - track.at);
16974
17203
  }
@@ -17048,6 +17277,7 @@ function createTimeline(register, id, opts) {
17048
17277
  wantScale = s;
17049
17278
  base.setTimeScale(s);
17050
17279
  },
17280
+ timeScale: () => live ? base.timeScale() : wantScale,
17051
17281
  isPaused: () => live ? base.isPaused() : wantPaused,
17052
17282
  seek(t) {
17053
17283
  offset = t - lastVirtual;
@@ -17058,6 +17288,10 @@ function createTimeline(register, id, opts) {
17058
17288
  time: () => playhead,
17059
17289
  duration: () => duration,
17060
17290
  tracks: () => opts.tracks,
17291
+ setLoop(loop) {
17292
+ loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;
17293
+ },
17294
+ loop: () => loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft,
17061
17295
  edit(fn) {
17062
17296
  fn();
17063
17297
  caches = /* @__PURE__ */ new WeakMap();
@@ -17272,6 +17506,7 @@ function useAnimator(opts = {}) {
17272
17506
  const a = animations.current.get(anim.id);
17273
17507
  if (a) a.timeScale = s;
17274
17508
  },
17509
+ timeScale: () => animations.current.get(anim.id)?.timeScale ?? 1,
17275
17510
  isPaused: () => animations.current.get(anim.id)?.paused ?? false
17276
17511
  };
17277
17512
  };
@@ -17293,6 +17528,7 @@ function useAnimator(opts = {}) {
17293
17528
  pause: base.pause,
17294
17529
  resume: base.resume,
17295
17530
  setTimeScale: base.setTimeScale,
17531
+ timeScale: base.timeScale,
17296
17532
  isPaused: base.isPaused,
17297
17533
  setOnCancel: (cb) => {
17298
17534
  onCancelCb = cb;
@@ -17303,7 +17539,7 @@ function useAnimator(opts = {}) {
17303
17539
  const tween = (o) => {
17304
17540
  const id = nextId.current++;
17305
17541
  const start = 0;
17306
- const easing = o.easing ?? easeOut;
17542
+ const easing = resolveEasing(o.easing ?? easeOut);
17307
17543
  const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;
17308
17544
  const perTickInterp = factoryFn ? null : o.interpolate ?? ((a, b, t) => {
17309
17545
  if (typeof a === "number" && typeof b === "number") {
@@ -17458,6 +17694,7 @@ function useAnimator(opts = {}) {
17458
17694
  setTimeScale: (s) => {
17459
17695
  globalTimeScale.current = s;
17460
17696
  },
17697
+ timeScale: () => globalTimeScale.current,
17461
17698
  pauseKey: (key) => {
17462
17699
  for (const a of animations.current.values()) if (a.cancelKey === key) a.paused = true;
17463
17700
  },
@@ -18055,6 +18292,21 @@ function LineIcon({ className, size = 20 }) {
18055
18292
  }
18056
18293
  );
18057
18294
  }
18295
+ function ArrowIcon({ className, size = 20 }) {
18296
+ return /* @__PURE__ */ jsxs(
18297
+ "svg",
18298
+ {
18299
+ ...ICON_SVG_BASE,
18300
+ className,
18301
+ width: size,
18302
+ height: size,
18303
+ children: [
18304
+ /* @__PURE__ */ jsx("path", { d: "M3.5 16.5 12.9 7.1", strokeLinecap: "round" }),
18305
+ /* @__PURE__ */ jsx("path", { d: "M16.5 3.5 11.06 5.98 14.03 8.95Z", fill: "currentColor", strokeLinejoin: "round" })
18306
+ ]
18307
+ }
18308
+ );
18309
+ }
18058
18310
  function PolygonIcon({ className, size = 20 }) {
18059
18311
  return /* @__PURE__ */ jsx(
18060
18312
  "svg",
@@ -18632,6 +18884,14 @@ var TEXT_PAINTER = {
18632
18884
  );
18633
18885
  if (boxes.length === 0) return null;
18634
18886
  return rectsToPath(boxes);
18887
+ },
18888
+ // `filled` is unconditional: a text node's `data.fill: null` still resolves
18889
+ // to the default in `ResolvedRun`, so there is no outline-only text to
18890
+ // report. The reach is the point — a heavily outlined glyph was unpickable
18891
+ // across the width of its own outline.
18892
+ ink: (node, _pose, ctx) => {
18893
+ const d = node.data;
18894
+ return { filled: true, ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale) };
18635
18895
  }
18636
18896
  };
18637
18897
  function rectsToPath(rects) {
@@ -18897,10 +19157,10 @@ var RECT_FALLBACK_PAINTER = {
18897
19157
  };
18898
19158
  function registerBuiltInShapePainters() {
18899
19159
  registerNodeShape(TEXT_PAINTER);
19160
+ registerNodeShape(DERIVED_PAINTER);
18900
19161
  registerNodeShape(PATH_PAINTER);
18901
19162
  registerNodeShape(SHAPE_PAINTER);
18902
19163
  registerNodeShape(IMAGE_PAINTER);
18903
- registerNodeShape(DERIVED_PAINTER);
18904
19164
  registerNodeShape(RECT_FALLBACK_PAINTER);
18905
19165
  }
18906
19166
  registerBuiltInShapePainters();
@@ -19909,11 +20169,7 @@ function useRotateTool(adapter, options) {
19909
20169
  [overlay]
19910
20170
  );
19911
20171
  }
19912
- function useHandTool(opts = {}) {
19913
- const inertia = opts.inertia === false ? false : opts.inertia;
19914
- const axis = opts.axis ?? "both";
19915
- const tracker = useVelocityTracker();
19916
- const decay = useDecayLoop();
20172
+ function useHandTool(_opts = {}) {
19917
20173
  return useMemo(
19918
20174
  () => Object.assign(defineViewportTool({
19919
20175
  id: "hand",
@@ -19933,8 +20189,7 @@ function useHandTool(opts = {}) {
19933
20189
  // phase entry.
19934
20190
  bindings: [{ spec: { kind: "drag" }, actionId: "viewport.dragPan" }]
19935
20191
  }),
19936
- // eslint-disable-next-line react-hooks/exhaustive-deps
19937
- [inertia, axis, tracker, decay]
20192
+ []
19938
20193
  );
19939
20194
  }
19940
20195
  var PRESENTATION = {
@@ -19963,7 +20218,7 @@ function usePinchZoomTool(canvasRef, getView, setView, opts = {}) {
19963
20218
  getViewRef.current = getView;
19964
20219
  const setViewRef = useRef(setView);
19965
20220
  setViewRef.current = setView;
19966
- const { min = 0.1, max = 8, enabled = true } = opts;
20221
+ const { min = DEFAULT_MIN_ZOOM, max = DEFAULT_MAX_ZOOM, enabled = true } = opts;
19967
20222
  const resolveTargetRef = useRef(opts.resolveTarget);
19968
20223
  resolveTargetRef.current = opts.resolveTarget;
19969
20224
  usePinchGesture(canvasRef, (clientAnchor, scaleFactor) => {
@@ -20227,7 +20482,7 @@ function usePenTool(options) {
20227
20482
  hookName: "usePenTool",
20228
20483
  // Reads the tool's own ref rather than a scratch handed in by a
20229
20484
  // dispatcher — the pen owns this state and nothing else needs it.
20230
- cursor: () => scratchRef.current?.closeHintActive ? "pointer" : "crosshair",
20485
+ cursor: () => scratchRef.current?.closeHintActive ? "pointer" : cursorFor("pen", { fallback: "crosshair" }),
20231
20486
  presentation: {
20232
20487
  label: "Pen",
20233
20488
  icon: createElement(PenIcon),
@@ -20612,7 +20867,7 @@ function usePencilTool() {
20612
20867
  id: "pencil",
20613
20868
  capabilities: ["creates-paths"],
20614
20869
  hookName: "usePencilTool",
20615
- cursor: "crosshair",
20870
+ cursor: cursorFor("pencil", { fallback: "crosshair" }),
20616
20871
  presentation: {
20617
20872
  label: "Pencil",
20618
20873
  group: "draw",
@@ -21616,14 +21871,89 @@ function wrapNodeOutput(cmds, pose, alpha) {
21616
21871
  }
21617
21872
  return rotated;
21618
21873
  }
21619
- function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21874
+ function insetAlong(start, extent, size, align, by) {
21875
+ if (align === "start") return start + by;
21876
+ if (align === "end") return start + extent - size - by;
21877
+ return start + (extent - size) / 2 + by;
21878
+ }
21879
+ function hostAnchorRect(input) {
21880
+ const { host, panel, viewport, align, offset, padding = 0 } = input;
21881
+ const desired = {
21882
+ x: insetAlong(host.x, host.width, panel.width, align.x, offset.x),
21883
+ y: insetAlong(host.y, host.height, panel.height, align.y, offset.y),
21884
+ width: panel.width,
21885
+ height: panel.height
21886
+ };
21887
+ return clampRectWithin(desired, { x: 0, y: 0, width: viewport.width, height: viewport.height }, padding);
21888
+ }
21889
+ function hostAnchorCss(rect, align, viewport) {
21890
+ return {
21891
+ ...align.y === "end" ? { bottom: viewport.height - rect.y - rect.height } : { top: rect.y },
21892
+ ...align.x === "end" ? { right: viewport.width - rect.x - rect.width } : { left: rect.x }
21893
+ };
21894
+ }
21895
+ function useHostAnchor(resolveHost, options) {
21896
+ const [panel, setPanel] = useState(null);
21897
+ const [style, setStyle] = useState(null);
21898
+ const ref = useCallback((el) => setPanel(el), []);
21899
+ const resolve = useRef(resolveHost);
21900
+ resolve.current = resolveHost;
21901
+ const { align, offset, padding = 0 } = options;
21902
+ const { x: alignX, y: alignY } = align;
21903
+ const { x: offsetX, y: offsetY } = offset;
21904
+ const recompute = useCallback(() => {
21905
+ const hostEl = resolve.current();
21906
+ if (!hostEl) {
21907
+ setStyle(null);
21908
+ return;
21909
+ }
21910
+ const h = hostEl.getBoundingClientRect();
21911
+ const p = panel?.getBoundingClientRect();
21912
+ const viewport = { width: window.innerWidth, height: window.innerHeight };
21913
+ const align2 = { x: alignX, y: alignY };
21914
+ const rect = hostAnchorRect({
21915
+ host: { x: h.left, y: h.top, width: h.width, height: h.height },
21916
+ panel: { width: p?.width ?? 0, height: p?.height ?? 0 },
21917
+ viewport,
21918
+ align: align2,
21919
+ offset: { x: offsetX, y: offsetY },
21920
+ padding
21921
+ });
21922
+ setStyle(hostAnchorCss(rect, align2, viewport));
21923
+ }, [panel, alignX, alignY, offsetX, offsetY, padding]);
21924
+ const raf = useVisibleRaf(recompute);
21925
+ useEffect(() => {
21926
+ recompute();
21927
+ const schedule = () => raf.request();
21928
+ window.addEventListener("scroll", schedule, true);
21929
+ window.addEventListener("resize", schedule);
21930
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(schedule) : null;
21931
+ if (panel) observer?.observe(panel);
21932
+ const hostEl = resolve.current();
21933
+ if (hostEl) observer?.observe(hostEl);
21934
+ return () => {
21935
+ window.removeEventListener("scroll", schedule, true);
21936
+ window.removeEventListener("resize", schedule);
21937
+ raf.cancel();
21938
+ observer?.disconnect();
21939
+ };
21940
+ }, [recompute, raf, panel]);
21941
+ return { ref, style };
21942
+ }
21943
+ function CursorCoordsHud({ canvasRef, anchorRef, viewRef, offset }) {
21620
21944
  const [state, setState] = useState({
21621
21945
  client: { x: 0, y: 0 },
21622
21946
  world: null,
21623
- inCanvas: false,
21624
- anchor: null
21947
+ inCanvas: false
21625
21948
  });
21626
21949
  const [fps, setFps] = useState(0);
21950
+ const { ref, style: anchorStyle } = useHostAnchor(
21951
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
21952
+ {
21953
+ align: { x: "end", y: "start" },
21954
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 8 }
21955
+ }
21956
+ );
21627
21957
  const framesRef = useRef(0);
21628
21958
  const lastFlushRef = useRef(0);
21629
21959
  const fpsLoop = useVisibleRaf(
@@ -21653,49 +21983,33 @@ function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21653
21983
  return () => fpsLoop.cancel();
21654
21984
  }, [fpsLoop]);
21655
21985
  useEffect(() => {
21656
- const readAnchor = () => {
21657
- const canvas = canvasRef.current;
21658
- if (!canvas) return null;
21659
- const host = canvas.parentElement ?? canvas;
21660
- const rect = host.getBoundingClientRect();
21661
- return { top: rect.top, right: window.innerWidth - rect.right };
21662
- };
21663
21986
  const onMove = (e) => {
21664
21987
  const canvas = canvasRef.current;
21665
21988
  const view = viewRef.current;
21666
21989
  const client = { x: e.clientX, y: e.clientY };
21667
- const anchor = readAnchor();
21668
21990
  if (!canvas || !view) {
21669
- setState({ client, world: null, inCanvas: false, anchor });
21991
+ setState({ client, world: null, inCanvas: false });
21670
21992
  return;
21671
21993
  }
21672
21994
  const rect = canvas.getBoundingClientRect();
21673
21995
  const inCanvas = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
21674
21996
  const [wx, wy] = clientToWorld(e.clientX, e.clientY, rect, view);
21675
21997
  const world = { x: wx, y: wy };
21676
- setState({ client, world, inCanvas, anchor });
21998
+ setState({ client, world, inCanvas });
21677
21999
  };
21678
- const onLayout = () => setState((s) => ({ ...s, anchor: readAnchor() }));
21679
22000
  document.addEventListener("pointermove", onMove);
21680
- window.addEventListener("scroll", onLayout, true);
21681
- window.addEventListener("resize", onLayout);
21682
- setState((s) => ({ ...s, anchor: readAnchor() }));
21683
22001
  return () => {
21684
22002
  document.removeEventListener("pointermove", onMove);
21685
- window.removeEventListener("scroll", onLayout, true);
21686
- window.removeEventListener("resize", onLayout);
21687
22003
  };
21688
22004
  }, [canvasRef, viewRef]);
21689
- if (!state.anchor) return null;
21690
- const top = state.anchor.top + (offset?.top ?? 8);
21691
- const right = state.anchor.right + (offset?.right ?? 8);
22005
+ if (!anchorStyle) return null;
21692
22006
  return /* @__PURE__ */ jsxs(
21693
22007
  "div",
21694
22008
  {
22009
+ ref,
21695
22010
  style: {
21696
22011
  position: "fixed",
21697
- top,
21698
- right,
22012
+ ...anchorStyle,
21699
22013
  zIndex: 1e4,
21700
22014
  background: "rgba(0,0,0,0.7)",
21701
22015
  color: "#e8e8e8",
@@ -21718,33 +22032,31 @@ function CursorCoordsHud({ canvasRef, viewRef, offset }) {
21718
22032
  );
21719
22033
  }
21720
22034
  var PickHud_default = {};
21721
- function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
22035
+ function PickHud({ canvasRef, anchorRef, viewRef, pickEvery, pickBest, offset }) {
21722
22036
  const [state, setState] = useState({
21723
22037
  ids: [],
21724
22038
  best: null,
21725
- inCanvas: false,
21726
- anchor: null
22039
+ inCanvas: false
21727
22040
  });
22041
+ const { ref, style } = useHostAnchor(
22042
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
22043
+ {
22044
+ align: { x: "end", y: "start" },
22045
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 76 }
22046
+ }
22047
+ );
21728
22048
  useEffect(() => {
21729
- const readAnchor = () => {
21730
- const canvas = canvasRef.current;
21731
- if (!canvas) return null;
21732
- const host = canvas.parentElement ?? canvas;
21733
- const rect = host.getBoundingClientRect();
21734
- return { top: rect.top, right: window.innerWidth - rect.right };
21735
- };
21736
22049
  const onMove = (e) => {
21737
22050
  const canvas = canvasRef.current;
21738
22051
  const view = viewRef.current;
21739
- const anchor = readAnchor();
21740
22052
  if (!canvas || !view) {
21741
- setState({ ids: [], best: null, inCanvas: false, anchor });
22053
+ setState({ ids: [], best: null, inCanvas: false });
21742
22054
  return;
21743
22055
  }
21744
22056
  const rect = canvas.getBoundingClientRect();
21745
22057
  const inCanvas = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
21746
22058
  if (!inCanvas) {
21747
- setState({ ids: [], best: null, inCanvas: false, anchor });
22059
+ setState({ ids: [], best: null, inCanvas: false });
21748
22060
  return;
21749
22061
  }
21750
22062
  const worldX = (e.clientX - rect.left) / view.scale.x + view.x;
@@ -21763,24 +22075,15 @@ function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
21763
22075
  best = null;
21764
22076
  }
21765
22077
  }
21766
- setState({ ids, best, inCanvas: true, anchor });
22078
+ setState({ ids, best, inCanvas: true });
21767
22079
  };
21768
- const onLayout = () => setState((s) => ({ ...s, anchor: readAnchor() }));
21769
22080
  document.addEventListener("pointermove", onMove);
21770
- window.addEventListener("scroll", onLayout, true);
21771
- window.addEventListener("resize", onLayout);
21772
- setState((s) => ({ ...s, anchor: readAnchor() }));
21773
22081
  return () => {
21774
22082
  document.removeEventListener("pointermove", onMove);
21775
- window.removeEventListener("scroll", onLayout, true);
21776
- window.removeEventListener("resize", onLayout);
21777
22083
  };
21778
22084
  }, [canvasRef, viewRef, pickEvery, pickBest]);
21779
- if (!state.anchor) return null;
21780
- const top = state.anchor.top + (offset?.top ?? 76);
21781
- const right = state.anchor.right + (offset?.right ?? 8);
21782
- const style = { top, right };
21783
- return /* @__PURE__ */ jsxs("div", { className: PickHud_default.hud, style, children: [
22085
+ if (!style) return null;
22086
+ return /* @__PURE__ */ jsxs("div", { ref, className: PickHud_default.hud, style, children: [
21784
22087
  /* @__PURE__ */ jsxs("div", { className: PickHud_default.header, children: [
21785
22088
  "pickEvery (",
21786
22089
  state.ids.length,
@@ -21797,34 +22100,20 @@ function PickHud({ canvasRef, viewRef, pickEvery, pickBest, offset }) {
21797
22100
  ] });
21798
22101
  }
21799
22102
  var ModalityHud_default = {};
21800
- function ModalityHud({ canvasRef, modeId, offset }) {
22103
+ function ModalityHud({ canvasRef, anchorRef, modeId, offset }) {
21801
22104
  const toolCtx = useOptionalActiveToolContext();
21802
- const [state, setState] = useState({ anchor: null });
21803
- useEffect(() => {
21804
- const readAnchor = () => {
21805
- const canvas = canvasRef.current;
21806
- if (!canvas) return null;
21807
- const host = canvas.parentElement ?? canvas;
21808
- const rect = host.getBoundingClientRect();
21809
- return { top: rect.top, right: window.innerWidth - rect.right };
21810
- };
21811
- const onLayout = () => setState({ anchor: readAnchor() });
21812
- onLayout();
21813
- window.addEventListener("scroll", onLayout, true);
21814
- window.addEventListener("resize", onLayout);
21815
- return () => {
21816
- window.removeEventListener("scroll", onLayout, true);
21817
- window.removeEventListener("resize", onLayout);
21818
- };
21819
- }, [canvasRef]);
21820
- if (!state.anchor) return null;
21821
- const top = state.anchor.top + (offset?.top ?? 220);
21822
- const right = state.anchor.right + (offset?.right ?? 8);
21823
- const style = { top, right };
22105
+ const { ref, style } = useHostAnchor(
22106
+ () => anchorRef?.current ?? canvasRef.current?.parentElement ?? canvasRef.current,
22107
+ {
22108
+ align: { x: "end", y: "start" },
22109
+ offset: { x: offset?.right ?? 8, y: offset?.top ?? 220 }
22110
+ }
22111
+ );
22112
+ if (!style) return null;
21824
22113
  const active = toolCtx?.active ?? null;
21825
22114
  const hotkeys = toolCtx?.hotkeyStack ?? [];
21826
22115
  const hotkeyTop = hotkeys.length > 0 ? hotkeys[hotkeys.length - 1] : null;
21827
- return /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.hud, style, children: [
22116
+ return /* @__PURE__ */ jsxs("div", { ref, className: ModalityHud_default.hud, style, children: [
21828
22117
  /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.row, children: [
21829
22118
  /* @__PURE__ */ jsx("span", { className: ModalityHud_default.label, children: "mode" }),
21830
22119
  /* @__PURE__ */ jsx("span", { className: modeId ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: modeId ?? "\u2014" })
@@ -21955,6 +22244,8 @@ function CanvasInner(props, ref) {
21955
22244
  boundsOf: boundsOf2,
21956
22245
  pickEvery,
21957
22246
  clientToWorld: clientToWorld2,
22247
+ paintInto,
22248
+ inputElement,
21958
22249
  geometry = AUTO_POSE_DESCRIPTOR,
21959
22250
  className,
21960
22251
  style,
@@ -22000,6 +22291,15 @@ function CanvasInner(props, ref) {
22000
22291
  debugSinkRefForCtx.current = debugSink;
22001
22292
  const adapter = adapterProp;
22002
22293
  const canvasRef = useRef(null);
22294
+ const ownCanvasRef = useRef(null);
22295
+ const detached = !!paintInto;
22296
+ if (detached) {
22297
+ canvasRef.current = inputElement ?? null;
22298
+ }
22299
+ const paintTargetRef = useRef(null);
22300
+ paintTargetRef.current = detached ? paintInto.canvas ?? null : null;
22301
+ const paintRectRef = useRef(null);
22302
+ paintRectRef.current = detached ? { x: paintInto.x, y: paintInto.y } : null;
22003
22303
  const helpersForLayersRef = useRef(null);
22004
22304
  const dimsRef = useRef({ width, height });
22005
22305
  dimsRef.current = { width, height };
@@ -22087,7 +22387,10 @@ function CanvasInner(props, ref) {
22087
22387
  };
22088
22388
  }, []);
22089
22389
  useImperativeHandle(ref, () => ({
22090
- element: canvasRef.current,
22390
+ // Named rather than read off `canvasRef` so the handle rebuilds when a
22391
+ // detached surface's input element arrives, which is a render later.
22392
+ element: detached ? inputElement ?? null : canvasRef.current,
22393
+ surface: detached ? paintInto?.canvas ?? null : ownCanvasRef.current,
22091
22394
  requestRedraw,
22092
22395
  subscribeFrame,
22093
22396
  registerLayer,
@@ -22098,6 +22401,10 @@ function CanvasInner(props, ref) {
22098
22401
  getPaintedVersion
22099
22402
  }), [
22100
22403
  canvasRef,
22404
+ ownCanvasRef,
22405
+ detached,
22406
+ inputElement,
22407
+ paintInto?.canvas,
22101
22408
  requestRedraw,
22102
22409
  subscribeFrame,
22103
22410
  registerLayer,
@@ -22393,8 +22700,9 @@ function CanvasInner(props, ref) {
22393
22700
  shaders
22394
22701
  };
22395
22702
  const paint = useCallback(() => {
22396
- const c = canvasRef.current;
22703
+ const c = paintTargetRef.current ?? ownCanvasRef.current;
22397
22704
  if (!c) return false;
22705
+ const rect = paintRectRef.current;
22398
22706
  const {
22399
22707
  layers: paintLayers,
22400
22708
  width: w,
@@ -22423,7 +22731,9 @@ function CanvasInner(props, ref) {
22423
22731
  try {
22424
22732
  renderer = new WeaselRenderer({
22425
22733
  gl,
22426
- canvas: c,
22734
+ // A shared buffer is the caller's to size. Handing the renderer the
22735
+ // element would have every co-tenant resize it to its own pane.
22736
+ ...rect ? {} : { canvas: c },
22427
22737
  width: w,
22428
22738
  height: h,
22429
22739
  dpr
@@ -22442,6 +22752,7 @@ function CanvasInner(props, ref) {
22442
22752
  lastResizeRef.current = { w, h, dpr };
22443
22753
  }
22444
22754
  }
22755
+ renderer.setTarget(rect ? { origin: { x: rect.x, y: rect.y } } : null);
22445
22756
  const view = viewRef.current;
22446
22757
  const commands = drawLayers(
22447
22758
  paintLayers,
@@ -22490,31 +22801,69 @@ function CanvasInner(props, ref) {
22490
22801
  }, [shaderIdKey]);
22491
22802
  const toolsCursor = tools ? resolveToolsCursor(tools, toolsCtxBase) : void 0;
22492
22803
  const effectiveStyle = toolsCursor ? { ...style, cursor: toolsCursor } : style;
22493
- return /* @__PURE__ */ jsxs(Fragment, { children: [
22494
- /* @__PURE__ */ jsx(
22495
- "canvas",
22804
+ const layersForMoveRef = useRef(layersWithDebug);
22805
+ layersForMoveRef.current = layersWithDebug;
22806
+ useEffect(() => {
22807
+ if (!detached || !inputElement) return;
22808
+ const el = inputElement;
22809
+ const onDown = () => {
22810
+ if (autoFocusOnPointerDown) el.focus();
22811
+ pointerDownRef.current = true;
22812
+ };
22813
+ const onMove = (e) => {
22814
+ if (pointerDownRef.current) return;
22815
+ const view = viewRef.current;
22816
+ const [worldX, worldY] = toWorld(
22817
+ el,
22818
+ e.clientX,
22819
+ e.clientY,
22820
+ view,
22821
+ clientToWorldRef.current
22822
+ );
22823
+ const dims = dimsRef.current;
22824
+ for (const layer of layersForMoveRef.current) {
22825
+ layer.onUncapturedMove?.(worldX, worldY, e, view, dims);
22826
+ }
22827
+ };
22828
+ const onUp = () => {
22829
+ pointerDownRef.current = false;
22830
+ };
22831
+ const onLeave = () => {
22832
+ pointerDownRef.current = false;
22833
+ for (const layer of layersForMoveRef.current) layer.onUncapturedLeave?.();
22834
+ };
22835
+ const onMenu = (e) => e.preventDefault();
22836
+ el.addEventListener("pointerdown", onDown);
22837
+ el.addEventListener("pointermove", onMove);
22838
+ el.addEventListener("pointerup", onUp);
22839
+ el.addEventListener("pointerleave", onLeave);
22840
+ el.addEventListener("contextmenu", onMenu);
22841
+ return () => {
22842
+ el.removeEventListener("pointerdown", onDown);
22843
+ el.removeEventListener("pointermove", onMove);
22844
+ el.removeEventListener("pointerup", onUp);
22845
+ el.removeEventListener("pointerleave", onLeave);
22846
+ el.removeEventListener("contextmenu", onMenu);
22847
+ };
22848
+ }, [detached, inputElement, autoFocusOnPointerDown]);
22849
+ useEffect(() => {
22850
+ if (!detached || !inputElement) return;
22851
+ inputElement.style.cursor = toolsCursor ?? "";
22852
+ }, [detached, inputElement, toolsCursor]);
22853
+ const huds = /* @__PURE__ */ jsxs(Fragment, { children: [
22854
+ cursorCoordsHud && /* @__PURE__ */ jsx(
22855
+ CursorCoordsHud,
22496
22856
  {
22497
- ref: canvasRef,
22498
- width,
22499
- height,
22500
- tabIndex,
22501
- className,
22502
- style: effectiveStyle,
22503
- onFocus,
22504
- onBlur,
22505
- onPointerDown: handlePointerDown,
22506
- onPointerMove: handlePointerMove,
22507
- onPointerUp: handlePointerUp,
22508
- onPointerCancel: handlePointerCancel,
22509
- onPointerLeave: handlePointerLeave,
22510
- onContextMenu: (e) => e.preventDefault()
22857
+ canvasRef,
22858
+ ...detached ? { anchorRef: canvasRef } : {},
22859
+ viewRef
22511
22860
  }
22512
22861
  ),
22513
- cursorCoordsHud && /* @__PURE__ */ jsx(CursorCoordsHud, { canvasRef, viewRef }),
22514
22862
  pickHud && /* @__PURE__ */ jsx(
22515
22863
  PickHud,
22516
22864
  {
22517
22865
  canvasRef,
22866
+ ...detached ? { anchorRef: canvasRef } : {},
22518
22867
  viewRef,
22519
22868
  pickEvery: stablePickEveryForHud,
22520
22869
  pickBest: stablePickBestForHud
@@ -22524,10 +22873,37 @@ function CanvasInner(props, ref) {
22524
22873
  ModalityHud,
22525
22874
  {
22526
22875
  canvasRef,
22876
+ ...detached ? { anchorRef: canvasRef } : {},
22527
22877
  modeId: typeof modalityHud === "object" ? modalityHud.modeId : void 0
22528
22878
  }
22529
22879
  )
22530
22880
  ] });
22881
+ if (detached) return inputElement ? huds : null;
22882
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22883
+ /* @__PURE__ */ jsx(
22884
+ "canvas",
22885
+ {
22886
+ ref: (el) => {
22887
+ ownCanvasRef.current = el;
22888
+ canvasRef.current = el;
22889
+ },
22890
+ width,
22891
+ height,
22892
+ tabIndex,
22893
+ className,
22894
+ style: effectiveStyle,
22895
+ onFocus,
22896
+ onBlur,
22897
+ onPointerDown: handlePointerDown,
22898
+ onPointerMove: handlePointerMove,
22899
+ onPointerUp: handlePointerUp,
22900
+ onPointerCancel: handlePointerCancel,
22901
+ onPointerLeave: handlePointerLeave,
22902
+ onContextMenu: (e) => e.preventDefault()
22903
+ }
22904
+ ),
22905
+ huds
22906
+ ] });
22531
22907
  }
22532
22908
  var Canvas = forwardRef(CanvasInner);
22533
22909
  function composeAlphaFor(scene, alphaFor) {
@@ -23656,6 +24032,32 @@ function sceneFromJSON(json, options) {
23656
24032
  });
23657
24033
  }
23658
24034
  var SelectionContext = createContext(null);
24035
+ function SelectionContextProvider({ children }) {
24036
+ const [state, setState] = useState({ selection: [], kinds: void 0 });
24037
+ const lastSerializedRef = useRef("");
24038
+ const publishSelection = useCallback((ids, kinds) => {
24039
+ const next = JSON.stringify([ids, kinds]);
24040
+ if (next === lastSerializedRef.current) return;
24041
+ lastSerializedRef.current = next;
24042
+ setState({ selection: ids, kinds });
24043
+ }, []);
24044
+ const value = useMemo(
24045
+ () => ({
24046
+ selection: state.selection,
24047
+ ...state.kinds !== void 0 ? { kinds: state.kinds } : {},
24048
+ publishSelection
24049
+ }),
24050
+ [state, publishSelection]
24051
+ );
24052
+ return /* @__PURE__ */ jsx(SelectionContext.Provider, { value, children });
24053
+ }
24054
+ function SelectionContextProviderIfRoot({
24055
+ children
24056
+ }) {
24057
+ const parent = useContext(SelectionContext);
24058
+ if (parent !== null) return /* @__PURE__ */ jsx(Fragment, { children });
24059
+ return /* @__PURE__ */ jsx(SelectionContextProvider, { children });
24060
+ }
23659
24061
  function usePublishSelection(ids, kinds) {
23660
24062
  const ctx = useContext(SelectionContext);
23661
24063
  const idsRef = useRef(ids);
@@ -25113,7 +25515,7 @@ function useDispatcherOverlayLayer(args) {
25113
25515
  break;
25114
25516
  case "pencil": {
25115
25517
  if (geom.samples.length < 2) break;
25116
- pathCmd = polygonFromPoints(projectPoints(geom.samples));
25518
+ pathCmd = polylineFromPoints(projectPoints(geom.samples));
25117
25519
  break;
25118
25520
  }
25119
25521
  }
@@ -25125,7 +25527,7 @@ function useDispatcherOverlayLayer(args) {
25125
25527
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
25126
25528
  };
25127
25529
  out.push(cmd);
25128
- if (ov.anchorPoint) {
25530
+ if (ov.anchorPoint && ov.shape !== "pencil") {
25129
25531
  const anchorScreen = projectPoint(ov.anchorPoint.x, ov.anchorPoint.y);
25130
25532
  out.push({
25131
25533
  kind: "path",
@@ -26994,7 +27396,7 @@ function SceneCanvasInner(props, ref) {
26994
27396
  boundary: inertiaObj.boundary,
26995
27397
  bounds: inertiaObj.bounds
26996
27398
  } : void 0;
26997
- const handTool = useHandTool(handToolInertia ? { inertia: handToolInertia } : {});
27399
+ const handTool = useHandTool(handToolInertia ? {} : {});
26998
27400
  const viewportAmbient = [];
26999
27401
  const baseRequestedTools = defaultTools ?? (toolBundle ? BUNDLE_TOOLS[toolBundle] : null) ?? (viewportRegistered ? ["select", "rotate", "hand"] : ["select", "rotate"]);
27000
27402
  const KNOWN_BUILTIN_IDS = /* @__PURE__ */ new Set([
@@ -27719,14 +28121,12 @@ function StandardActionsRegistrar({
27719
28121
  }, [registry22, actionsRegistryRef]);
27720
28122
  useEffect(() => {
27721
28123
  if (!registry22) return;
27722
- registry22.setDispatcher(dispatcher);
27723
- return () => registry22.setDispatcher(null);
28124
+ return registry22.setDispatcher(dispatcher);
27724
28125
  }, [registry22, dispatcher]);
27725
28126
  const wiredDepRegistry = useDepRegistry();
27726
28127
  useEffect(() => {
27727
28128
  if (!registry22) return;
27728
- registry22.setDepRegistry(wiredDepRegistry);
27729
- return () => registry22.setDepRegistry(null);
28129
+ return registry22.setDepRegistry(wiredDepRegistry);
27730
28130
  }, [registry22, wiredDepRegistry]);
27731
28131
  useEffect(() => {
27732
28132
  if (!registry22) return;
@@ -27787,7 +28187,7 @@ var SceneCanvasInnerForwardRef = forwardRef(SceneCanvasInner);
27787
28187
  function SceneCanvasWrapper(props, ref) {
27788
28188
  return /* @__PURE__ */ jsx(ActiveToolContextProviderIfRoot, { children: /* @__PURE__ */ jsx(ViewRegistryProvider, { children: /* @__PURE__ */ jsx(SceneCanvasInnerForwardRef, { ...props, ref }) }) });
27789
28189
  }
27790
- forwardRef(SceneCanvasWrapper);
28190
+ var SceneCanvas = forwardRef(SceneCanvasWrapper);
27791
28191
  var RAD_TO_DEG = 180 / Math.PI;
27792
28192
  var rotationDegreesUnit = {
27793
28193
  toDisplay: (rad) => Math.round(rad * RAD_TO_DEG * 10) / 10,
@@ -27919,9 +28319,9 @@ function shapeSchema(opts = {}) {
27919
28319
  fontWeight: { kind: "number", name: "Weight", description: "Font weight, 100\u2013900.", default: 400, min: 100, max: 900, step: 100, pair: "Size / weight" },
27920
28320
  fontStyle: { kind: "enum", name: "Style", description: "Upright or italic.", default: "normal", options: [{ value: "normal", label: "Normal" }, { value: "italic", label: "Italic" }] },
27921
28321
  letterSpacing: { kind: "number", name: "Tracking", description: "Extra advance per glyph, world units.", default: 0, step: 0.1 },
27922
- underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false },
27923
- strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false },
27924
- overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false }
28322
+ underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false, control: "toggle", short: "U", pair: "Decoration" },
28323
+ strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false, control: "toggle", short: "S", pair: "Decoration" },
28324
+ overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false, control: "toggle", short: "O", pair: "Decoration" }
27925
28325
  }
27926
28326
  },
27927
28327
  paragraph: {
@@ -28274,6 +28674,125 @@ function MinimapCanvasInner(props) {
28274
28674
  );
28275
28675
  }
28276
28676
  var MinimapCanvas = MinimapCanvasInner;
28677
+ function planPixelRender(args) {
28678
+ const { sourceRect, scale: scale2 } = args;
28679
+ for (const [label, v] of [
28680
+ ["sourceRect.width", sourceRect.width],
28681
+ ["sourceRect.height", sourceRect.height],
28682
+ ["scale.x", scale2.x],
28683
+ ["scale.y", scale2.y]
28684
+ ]) {
28685
+ if (!Number.isFinite(v) || v <= 0) {
28686
+ throw new Error(`renderSceneToPixels: ${label} must be a positive finite number, got ${v}`);
28687
+ }
28688
+ }
28689
+ for (const [label, v] of [
28690
+ ["sourceRect.x", sourceRect.x],
28691
+ ["sourceRect.y", sourceRect.y]
28692
+ ]) {
28693
+ if (!Number.isFinite(v)) {
28694
+ throw new Error(`renderSceneToPixels: ${label} must be a finite number, got ${v}`);
28695
+ }
28696
+ }
28697
+ const width = Math.max(1, Math.round(sourceRect.width * scale2.x));
28698
+ const height = Math.max(1, Math.round(sourceRect.height * scale2.y));
28699
+ const view = { x: sourceRect.x, y: sourceRect.y, scale: { x: scale2.x, y: scale2.y } };
28700
+ const resolveImage = args.resolveImage;
28701
+ const drawOne = args.drawOne ?? ((node, pose, view2, ctx) => defaultDrawOne(node, pose, view2, { ...ctx, resolveImage }));
28702
+ const commands = [];
28703
+ if (args.background !== void 0) {
28704
+ commands.push({
28705
+ kind: "path",
28706
+ path: { kind: "rect", x: 0, y: 0, width, height },
28707
+ fill: { fill: "solid", color: args.background }
28708
+ });
28709
+ }
28710
+ commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor));
28711
+ return { width, height, view, commands };
28712
+ }
28713
+ function defaultCreateCanvas(width, height) {
28714
+ if (typeof OffscreenCanvas !== "undefined") {
28715
+ return new OffscreenCanvas(width, height);
28716
+ }
28717
+ if (typeof document !== "undefined") {
28718
+ const c = document.createElement("canvas");
28719
+ c.width = width;
28720
+ c.height = height;
28721
+ return c;
28722
+ }
28723
+ throw new Error("renderSceneToPixels: no canvas source in this environment \u2014 supply `gl` or `createCanvas`");
28724
+ }
28725
+ function flipRows(raw, width, height) {
28726
+ const rowBytes = width * 4;
28727
+ const out = new Uint8ClampedArray(raw.length);
28728
+ for (let y = 0; y < height; y++) {
28729
+ out.set(raw.subarray((height - 1 - y) * rowBytes, (height - y) * rowBytes), y * rowBytes);
28730
+ }
28731
+ return out;
28732
+ }
28733
+ function unpremultiply(data) {
28734
+ for (let i = 0; i < data.length; i += 4) {
28735
+ const a = data[i + 3];
28736
+ if (a === 0 || a === 255) continue;
28737
+ const inv = 255 / a;
28738
+ data[i] = data[i] * inv;
28739
+ data[i + 1] = data[i + 1] * inv;
28740
+ data[i + 2] = data[i + 2] * inv;
28741
+ }
28742
+ }
28743
+ function renderSceneToPixels(args) {
28744
+ if (args.gl && args.createCanvas) {
28745
+ throw new Error("renderSceneToPixels: `gl` and `createCanvas` are mutually exclusive");
28746
+ }
28747
+ const plan = planPixelRender(args);
28748
+ const { width, height } = plan;
28749
+ let gl = args.gl;
28750
+ if (!gl) {
28751
+ const canvas = (args.createCanvas ?? defaultCreateCanvas)(width, height);
28752
+ canvas.width = width;
28753
+ canvas.height = height;
28754
+ gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true, stencil: true }) ?? void 0;
28755
+ }
28756
+ if (!gl || typeof gl.enable !== "function") {
28757
+ throw new Error("renderSceneToPixels: WebGL2 is unavailable \u2014 supply `gl` or a WebGL2-capable `createCanvas`");
28758
+ }
28759
+ if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
28760
+ throw new Error("renderSceneToPixels: the supplied WebGL2 context is lost");
28761
+ }
28762
+ const flattenTolerancePx = args.flattenTolerancePx ?? 0.25;
28763
+ const renderer = new WeaselRenderer({
28764
+ gl,
28765
+ width,
28766
+ height,
28767
+ dpr: 1,
28768
+ imageMinification: "mipmap",
28769
+ flattenTolerance: flattenTolerancePx / Math.max(args.scale.x, args.scale.y),
28770
+ // Dynamic canvas-SDF glyphs must all bake inline — this path is
28771
+ // synchronous with no notify-and-redraw, and print must be complete.
28772
+ bakeBudget: Infinity
28773
+ });
28774
+ try {
28775
+ renderer.render(plan.commands, viewToMat3(plan.view));
28776
+ if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
28777
+ throw new Error("renderSceneToPixels: WebGL2 context was lost during render");
28778
+ }
28779
+ const raw = new Uint8Array(width * height * 4);
28780
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, raw);
28781
+ const data = flipRows(raw, width, height);
28782
+ unpremultiply(data);
28783
+ return { width, height, data };
28784
+ } finally {
28785
+ renderer.dispose();
28786
+ }
28787
+ }
28788
+ function WeaselProvider({ children, isolate = false }) {
28789
+ const Deps = isolate ? DepRegistryProvider : DepRegistryProviderIfRoot;
28790
+ const Actions = isolate ? ActionsProvider : ActionsProviderIfRoot;
28791
+ const ActiveTool = isolate ? ActiveToolContextProvider : ActiveToolContextProviderIfRoot;
28792
+ const Selection = isolate ? SelectionContextProvider : SelectionContextProviderIfRoot;
28793
+ const Pointer = isolate ? PointerContextProvider : PointerProviderIfRoot;
28794
+ return /* @__PURE__ */ jsx(Deps, { children: /* @__PURE__ */ jsx(Actions, { children: /* @__PURE__ */ jsx(ActiveTool, { children: /* @__PURE__ */ jsx(Selection, { children: /* @__PURE__ */ jsx(Pointer, { children }) }) }) }) });
28795
+ }
28277
28796
  function createSetTextOp(args) {
28278
28797
  const { id, from, to, label, coalesceKey = `setText:${id}` } = args;
28279
28798
  return {
@@ -28410,6 +28929,6 @@ function oklchToOklab(L, C, h) {
28410
28929
  return [L, C * Math.cos(h), C * Math.sin(h)];
28411
28930
  }
28412
28931
 
28413
- export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView, computeIndicatorCommand, eligibleContribution, evaluateEnabled, isBuiltinToolPref, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry, useVisibleRaf };
28414
- //# sourceMappingURL=chunk-QCICIHEO.js.map
28415
- //# sourceMappingURL=chunk-QCICIHEO.js.map
28932
+ 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 };
28933
+ //# sourceMappingURL=chunk-L5QJOOLV.js.map
28934
+ //# sourceMappingURL=chunk-L5QJOOLV.js.map