@weasel-js/labkit 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/_dts/{DrawCommand-uKHt4Vul.d.ts → DrawCommand-BkZztJsW.d.ts} +3 -1
  2. package/dist/_dts/{useExperimentState-vrttakTt.d.ts → useExperimentState-D7EQnnwJ.d.ts} +4 -0
  3. package/dist/{chunk-N5KTQKQA.js → chunk-BAPZPDDA.js} +628 -588
  4. package/dist/chunk-BAPZPDDA.js.map +1 -0
  5. package/dist/{chunk-73PXCRCR.js → chunk-DEWXYFEU.js} +6 -6
  6. package/dist/chunk-DEWXYFEU.js.map +1 -0
  7. package/dist/{chunk-3WPOGKUP.js → chunk-DWV7SFKR.js} +518 -217
  8. package/dist/chunk-DWV7SFKR.js.map +1 -0
  9. package/dist/{chunk-T7OKNJTY.js → chunk-KSTEW2AF.js} +18 -2
  10. package/dist/chunk-KSTEW2AF.js.map +1 -0
  11. package/dist/index.d.ts +64 -15
  12. package/dist/index.js +133 -24
  13. package/dist/index.js.map +1 -1
  14. package/dist/passthrough/weasel-canvas.d.ts +1 -1
  15. package/dist/passthrough/weasel-canvas.js +1 -1
  16. package/dist/passthrough/weasel-ui.d.ts +1 -1
  17. package/dist/passthrough/weasel-ui.js +2 -2
  18. package/dist/state/index.d.ts +4 -4
  19. package/dist/state/index.js +2 -2
  20. package/dist/styles.css +163 -6
  21. package/dist/ui/layers/index.js +3 -3
  22. package/package.json +2 -1
  23. package/src/lab/Lab.tsx +14 -2
  24. package/src/lab/LabContext.ts +1 -0
  25. package/src/lab/WorkspaceGrid.less +37 -5
  26. package/src/lab/WorkspaceGrid.stories.tsx +16 -0
  27. package/src/lab/WorkspaceGrid.test.tsx +84 -10
  28. package/src/lab/WorkspaceGrid.tsx +188 -14
  29. package/src/lab/index.ts +0 -2
  30. package/src/state/helpers.ts +2 -2
  31. package/src/state/store.test.ts +2 -1
  32. package/src/state/store.ts +19 -0
  33. package/src/state/types.ts +3 -0
  34. package/src/test-setup.ts +19 -0
  35. package/src/workspace/Workspace.stories.tsx +1 -0
  36. package/src/workspace/Workspace.test.tsx +1 -0
  37. package/src/workspace/index.ts +1 -0
  38. package/src/workspace/workspaceOps.test.ts +34 -1
  39. package/src/workspace/workspaceOps.ts +16 -0
  40. package/dist/chunk-3WPOGKUP.js.map +0 -1
  41. package/dist/chunk-73PXCRCR.js.map +0 -1
  42. package/dist/chunk-N5KTQKQA.js.map +0 -1
  43. package/dist/chunk-T7OKNJTY.js.map +0 -1
  44. package/src/lab/gridDims.test.ts +0 -35
  45. package/src/lab/gridDims.ts +0 -13
@@ -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-TR3HZN34.js
6
+ // ../core/dist/chunk-J3YC3GVF.js
7
7
 
8
8
  // ../gestures/dist/index.js
9
9
  var GESTURE_DESCRIPTORS = [
@@ -60,9 +60,8 @@ function formatRoute(r) {
60
60
  const desc = getGestureDescriptor(r.gesture);
61
61
  const phaseStr = `[${r.phases.map(formatPhaseAtom).join(",")}]`;
62
62
  let out = `${phaseStr} ${r.gesture}`;
63
- if (desc.arg) {
64
- const isDefault = r.arg === void 0 || desc.arg.default !== void 0 && r.arg === desc.arg.default;
65
- if (!isDefault) out += `(${r.arg})`;
63
+ if (desc.arg && r.arg !== void 0 && r.arg !== (desc.arg.default ?? "*")) {
64
+ out += `(${r.arg})`;
66
65
  }
67
66
  if (desc.hasTarget && r.target !== void 0 && r.target !== "*") {
68
67
  out += ` => ${r.target}`;
@@ -288,7 +287,7 @@ function matchSpec(e, spec, isMac, phaseCtx) {
288
287
  }
289
288
  var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
290
289
 
291
- // ../core/dist/chunk-TR3HZN34.js
290
+ // ../core/dist/chunk-J3YC3GVF.js
292
291
  var DepRegistryContext = createContext(null);
293
292
  function DepRegistryProvider({ children }) {
294
293
  const sourcesRef = useRef(/* @__PURE__ */ new Map());
@@ -520,7 +519,8 @@ function ActionsProvider({ children }) {
520
519
  const disp = dispatcherRef.current;
521
520
  if (!disp) return null;
522
521
  const r = wiredDepRegRef.current ?? depRegRef.current;
523
- const deps = r ? {
522
+ const a = actionsRef.current.get(id);
523
+ const deps = !r ? {} : a?.requires ? buildDepsFromRequires(a, r) : {
524
524
  selection: r.get("selection"),
525
525
  scene: r.get("scene"),
526
526
  history: r.get("history"),
@@ -528,7 +528,7 @@ function ActionsProvider({ children }) {
528
528
  pointer: r.get("pointer"),
529
529
  activeTool: r.get("activeTool"),
530
530
  booleansAdapter: r.get("booleansAdapter")
531
- } : {};
531
+ };
532
532
  return disp.beginUiOngoing(id, deps, params);
533
533
  }
534
534
  };
@@ -699,7 +699,7 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
699
699
  return conflicts;
700
700
  }
701
701
 
702
- // ../geom/dist/chunk-UULRZUHO.js
702
+ // ../geom/dist/chunk-KU7FXMOA.js
703
703
  function cubicEvalAt(x0, y0, x1, y1, x2, y2, x3, y3, t) {
704
704
  const u = 1 - t;
705
705
  const a = u * u * u, b = 3 * u * u * t, c = 3 * u * t * t, d = t * t * t;
@@ -753,6 +753,12 @@ function cubicBounds(x0, y0, x1, y1, x2, y2, x3, y3) {
753
753
  }
754
754
 
755
755
  // ../geom/dist/index.js
756
+ function dot(ax, ay, bx, by) {
757
+ return ax * bx + ay * by;
758
+ }
759
+ function len2(x, y) {
760
+ return x * x + y * y;
761
+ }
756
762
  function sign(n) {
757
763
  return n > 0 ? 1 : n < 0 ? -1 : 0;
758
764
  }
@@ -804,6 +810,15 @@ function segmentsCross(ax, ay, bx, by, cx, cy, dx, dy) {
804
810
  const d4 = sign((bx - ax) * (dy - ay) - (by - ay) * (dx - ax));
805
811
  return d1 !== d2 && d3 !== d4;
806
812
  }
813
+ function pointSegmentDist2(px, py, ax, ay, bx, by) {
814
+ const vx = bx - ax, vy = by - ay;
815
+ const wx = px - ax, wy = py - ay;
816
+ const vv = len2(vx, vy);
817
+ let t = vv === 0 ? 0 : dot(wx, wy, vx, vy) / vv;
818
+ t = t < 0 ? 0 : t > 1 ? 1 : t;
819
+ const dx = px - (ax + t * vx), dy = py - (ay + t * vy);
820
+ return len2(dx, dy);
821
+ }
807
822
  function transformCoords(coords, m) {
808
823
  const out = new Float64Array(coords.length);
809
824
  const a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
@@ -815,7 +830,7 @@ function transformCoords(coords, m) {
815
830
  return out;
816
831
  }
817
832
 
818
- // ../core/dist/chunk-U45SODC2.js
833
+ // ../core/dist/chunk-4Z6J4IVH.js
819
834
  function resolveUnit(v, unitSystem) {
820
835
  if (typeof v === "number") return v;
821
836
  if (!unitSystem) {
@@ -900,19 +915,36 @@ function boundsOfPath(path) {
900
915
  return { kind: "rect", x: minX, y: minY, width: maxX - minX, height: maxY - minY };
901
916
  }
902
917
  var DEFAULT_FLATTEN_TOLERANCE = 0.5;
918
+ var MIN_FLATTEN_TOLERANCE = 1e-6;
919
+ function usableTolerance(tolerance) {
920
+ return tolerance > 0 ? tolerance : MIN_FLATTEN_TOLERANCE;
921
+ }
922
+ function finite6(a, b, c, d, e, f) {
923
+ return Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d) && Number.isFinite(e) && Number.isFinite(f);
924
+ }
925
+ function finite8(a, b, c, d, e, f, g, h) {
926
+ return finite6(a, b, c, d, e, f) && Number.isFinite(g) && Number.isFinite(h);
927
+ }
903
928
  function distPointToLine(px, py, ax, ay, bx, by) {
904
929
  const dx = bx - ax;
905
930
  const dy = by - ay;
906
- const len2 = dx * dx + dy * dy;
907
- if (len2 === 0) {
931
+ const len22 = dx * dx + dy * dy;
932
+ if (len22 === 0) {
908
933
  const ex = px - ax;
909
934
  const ey = py - ay;
910
935
  return Math.sqrt(ex * ex + ey * ey);
911
936
  }
912
937
  const cross = (px - ax) * dy - (py - ay) * dx;
913
- return Math.abs(cross) / Math.sqrt(len2);
938
+ return Math.abs(cross) / Math.sqrt(len22);
914
939
  }
915
940
  function flattenCubic2(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out) {
941
+ if (!finite8(x0, y0, x1, y1, x2, y2, x3, y3)) {
942
+ out.push(x3, y3);
943
+ return;
944
+ }
945
+ flattenCubicRec(x0, y0, x1, y1, x2, y2, x3, y3, usableTolerance(tolerance), out);
946
+ }
947
+ function flattenCubicRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out) {
916
948
  const d1 = distPointToLine(x1, y1, x0, y0, x3, y3);
917
949
  const d2 = distPointToLine(x2, y2, x0, y0, x3, y3);
918
950
  if (Math.max(d1, d2) <= tolerance) {
@@ -925,10 +957,17 @@ function flattenCubic2(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out) {
925
957
  const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
926
958
  const x123 = (x12 + x23) * 0.5, y123 = (y12 + y23) * 0.5;
927
959
  const x0123 = (x012 + x123) * 0.5, y0123 = (y012 + y123) * 0.5;
928
- flattenCubic2(x0, y0, x01, y01, x012, y012, x0123, y0123, tolerance, out);
929
- flattenCubic2(x0123, y0123, x123, y123, x23, y23, x3, y3, tolerance, out);
960
+ flattenCubicRec(x0, y0, x01, y01, x012, y012, x0123, y0123, tolerance, out);
961
+ flattenCubicRec(x0123, y0123, x123, y123, x23, y23, x3, y3, tolerance, out);
930
962
  }
931
963
  function flattenQuadratic(x0, y0, x1, y1, x2, y2, tolerance, out) {
964
+ if (!finite6(x0, y0, x1, y1, x2, y2)) {
965
+ out.push(x2, y2);
966
+ return;
967
+ }
968
+ flattenQuadraticRec(x0, y0, x1, y1, x2, y2, usableTolerance(tolerance), out);
969
+ }
970
+ function flattenQuadraticRec(x0, y0, x1, y1, x2, y2, tolerance, out) {
932
971
  const d = distPointToLine(x1, y1, x0, y0, x2, y2);
933
972
  if (d <= tolerance) {
934
973
  out.push(x2, y2);
@@ -937,11 +976,16 @@ function flattenQuadratic(x0, y0, x1, y1, x2, y2, tolerance, out) {
937
976
  const x01 = (x0 + x1) * 0.5, y01 = (y0 + y1) * 0.5;
938
977
  const x12 = (x1 + x2) * 0.5, y12 = (y1 + y2) * 0.5;
939
978
  const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
940
- flattenQuadratic(x0, y0, x01, y01, x012, y012, tolerance, out);
941
- flattenQuadratic(x012, y012, x12, y12, x2, y2, tolerance, out);
979
+ flattenQuadraticRec(x0, y0, x01, y01, x012, y012, tolerance, out);
980
+ flattenQuadraticRec(x012, y012, x12, y12, x2, y2, tolerance, out);
942
981
  }
943
982
  function flattenCubicWithArcLen(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut) {
944
- return flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut, 0);
983
+ if (!finite8(x0, y0, x1, y1, x2, y2, x3, y3)) {
984
+ out.push(x3, y3);
985
+ arcOut.push(0);
986
+ return 0;
987
+ }
988
+ return flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, usableTolerance(tolerance), out, arcOut, 0);
945
989
  }
946
990
  function flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut, accum) {
947
991
  const d1 = distPointToLine(x1, y1, x0, y0, x3, y3);
@@ -966,7 +1010,12 @@ function flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcO
966
1010
  return accum;
967
1011
  }
968
1012
  function flattenQuadraticWithArcLen(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut) {
969
- return flattenQuadraticArcRec(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut, 0);
1013
+ if (!finite6(x0, y0, x1, y1, x2, y2)) {
1014
+ out.push(x2, y2);
1015
+ arcOut.push(0);
1016
+ return 0;
1017
+ }
1018
+ return flattenQuadraticArcRec(x0, y0, x1, y1, x2, y2, usableTolerance(tolerance), out, arcOut, 0);
970
1019
  }
971
1020
  function flattenQuadraticArcRec(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut, accum) {
972
1021
  const d = distPointToLine(x1, y1, x0, y0, x2, y2);
@@ -1159,22 +1208,6 @@ function polylineWithinThreshold(path, px, py, t2, tolerance) {
1159
1208
  if (subOpen && checkSegments(false)) return true;
1160
1209
  return false;
1161
1210
  }
1162
- function pointSegmentDist2(px, py, ax, ay, bx, by) {
1163
- const dx = bx - ax;
1164
- const dy = by - ay;
1165
- const len2 = dx * dx + dy * dy;
1166
- if (len2 === 0) {
1167
- const ex2 = px - ax, ey2 = py - ay;
1168
- return ex2 * ex2 + ey2 * ey2;
1169
- }
1170
- let t = ((px - ax) * dx + (py - ay) * dy) / len2;
1171
- if (t < 0) t = 0;
1172
- else if (t > 1) t = 1;
1173
- const cx = ax + t * dx;
1174
- const cy = ay + t * dy;
1175
- const ex = px - cx, ey = py - cy;
1176
- return ex * ex + ey * ey;
1177
- }
1178
1211
  var PathBuilder = class _PathBuilder {
1179
1212
  cmds = [];
1180
1213
  xs = [];
@@ -1563,7 +1596,7 @@ function createDeleteOp(args) {
1563
1596
  }
1564
1597
  registerOpFactory("delete", (args) => createDeleteOp(args));
1565
1598
 
1566
- // ../core/dist/chunk-3KGMJMOS.js
1599
+ // ../core/dist/chunk-CSMF654J.js
1567
1600
  function createTransformOp(args) {
1568
1601
  const { id, from, to, label, coalesceKey = `transform:${id}` } = args;
1569
1602
  return {
@@ -1644,7 +1677,7 @@ function snap(strategy, opts = {}) {
1644
1677
  };
1645
1678
  }
1646
1679
 
1647
- // ../core/dist/chunk-3JYFAQIB.js
1680
+ // ../core/dist/chunk-J7LUY47N.js
1648
1681
  function lockAspectWithModifier(opts = {}) {
1649
1682
  const { key = "shift" } = opts;
1650
1683
  return {
@@ -2850,6 +2883,7 @@ function createHistory(adapter, options = {}) {
2850
2883
  const logger = options.debug ?? SILENT;
2851
2884
  let nextEntryId = 1;
2852
2885
  let version = 0;
2886
+ let coalesceAnchorVersion = -1;
2853
2887
  const listeners = /* @__PURE__ */ new Set();
2854
2888
  function bump() {
2855
2889
  version++;
@@ -2890,6 +2924,7 @@ function createHistory(adapter, options = {}) {
2890
2924
  }
2891
2925
  function canCoalesce(top, incoming) {
2892
2926
  if (coalesceWindowMs <= 0) return false;
2927
+ if (version !== coalesceAnchorVersion) return false;
2893
2928
  if (now() - top.timestamp > coalesceWindowMs) return false;
2894
2929
  if (top.forwardOps.length === 0 || incoming.length === 0) return false;
2895
2930
  if (top.forwardOps.length !== incoming.length) return false;
@@ -2927,9 +2962,9 @@ function createHistory(adapter, options = {}) {
2927
2962
  for (const id of incoming) merged.add(id);
2928
2963
  top.touchedIds = merged;
2929
2964
  }
2930
- dropRedo();
2931
2965
  logger.log(`coalesce '${label}' into entry id=${top.id} (${ops.length} ops)`);
2932
2966
  bump();
2967
+ coalesceAnchorVersion = version;
2933
2968
  return;
2934
2969
  }
2935
2970
  logger.log(`push '${label}' (${ops.length} ops)`);
@@ -2937,6 +2972,7 @@ function createHistory(adapter, options = {}) {
2937
2972
  dropRedo();
2938
2973
  enforceLimit();
2939
2974
  bump();
2975
+ coalesceAnchorVersion = version;
2940
2976
  }
2941
2977
  return {
2942
2978
  apply(op, label) {
@@ -3042,7 +3078,11 @@ function createHistory(adapter, options = {}) {
3042
3078
  return j;
3043
3079
  },
3044
3080
  resumeJournal(journal) {
3081
+ if (activeJournal !== null && activeJournal !== journal && activeJournal.isActive()) {
3082
+ throw new Error("A journal is already active \u2014 commit, cancel, or suspend it first");
3083
+ }
3045
3084
  _resumeJournalInternal(journal);
3085
+ activeJournal = journal;
3046
3086
  },
3047
3087
  restore(snapshot) {
3048
3088
  undoStack.length = 0;
@@ -3123,9 +3163,8 @@ function serialToEntry(se, custom, logger) {
3123
3163
  label: se.label,
3124
3164
  forwardOps,
3125
3165
  baseOps,
3126
- // Restored entries inherit a "now" timestamp coalesce eligibility is
3127
- // a within-session concept and a restored entry shouldn't merge with a
3128
- // freshly-typed one regardless of when it was originally pushed.
3166
+ // Coalescing is a within-session concept; a restored entry is never a
3167
+ // coalesce anchor, so its timestamp only has to be non-null.
3129
3168
  timestamp: 0,
3130
3169
  // Re-derive touchedIds from the rebuilt ops rather than trying to
3131
3170
  // round-trip the Set through the serialized form (Sets aren't JSON-safe).
@@ -3133,7 +3172,7 @@ function serialToEntry(se, custom, logger) {
3133
3172
  };
3134
3173
  }
3135
3174
 
3136
- // ../core/dist/chunk-SMCZRSYB.js
3175
+ // ../core/dist/chunk-AYGSXNY3.js
3137
3176
  function worldToScreen(worldX, worldY, view) {
3138
3177
  return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
3139
3178
  }
@@ -3570,13 +3609,20 @@ var GLTextureCache = class {
3570
3609
  }
3571
3610
  gl;
3572
3611
  map = /* @__PURE__ */ new Map();
3612
+ wraps = /* @__PURE__ */ new Map();
3573
3613
  has(id) {
3574
3614
  return this.map.has(id);
3575
3615
  }
3576
- /** Uploads once per id; `wrap` therefore applies on first upload only.
3577
- * An id is either a pattern tile or a shader texture, never both. */
3616
+ /** Uploads the pixels once per id. `wrap` is re-applied whenever it differs
3617
+ * from what the id was last uploaded under: the same registered image can
3618
+ * be a clamped shader texture in one draw and a repeating pattern tile in
3619
+ * the next, and first-upload-wins gave the second one the first one's mode. */
3578
3620
  upload(id, source, wrap = "clamp") {
3579
- if (this.map.has(id)) return id;
3621
+ const cached2 = this.map.get(id);
3622
+ if (cached2) {
3623
+ if (this.wraps.get(id) !== wrap) this.applyWrap(id, cached2, wrap);
3624
+ return id;
3625
+ }
3580
3626
  const gl = this.gl;
3581
3627
  const tex = gl.createTexture();
3582
3628
  if (!tex) throw new Error(`GLTextureCache: createTexture failed for id="${id}"`);
@@ -3589,8 +3635,18 @@ var GLTextureCache = class {
3589
3635
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
3590
3636
  gl.bindTexture(gl.TEXTURE_2D, null);
3591
3637
  this.map.set(id, tex);
3638
+ this.wraps.set(id, wrap);
3592
3639
  return id;
3593
3640
  }
3641
+ applyWrap(id, tex, wrap) {
3642
+ const gl = this.gl;
3643
+ const mode = wrap === "repeat" ? gl.REPEAT : gl.CLAMP_TO_EDGE;
3644
+ gl.bindTexture(gl.TEXTURE_2D, tex);
3645
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, mode);
3646
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, mode);
3647
+ gl.bindTexture(gl.TEXTURE_2D, null);
3648
+ this.wraps.set(id, wrap);
3649
+ }
3594
3650
  /** Create a single-channel R8 texture from raw bytes (full upload).
3595
3651
  * No-op if `id` already exists. Same LINEAR/CLAMP params as `upload`;
3596
3652
  * UNPACK_ALIGNMENT dropped to 1 for non-4-aligned row widths. */
@@ -3609,6 +3665,7 @@ var GLTextureCache = class {
3609
3665
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3610
3666
  gl.bindTexture(gl.TEXTURE_2D, null);
3611
3667
  this.map.set(id, tex);
3668
+ this.wraps.set(id, "clamp");
3612
3669
  }
3613
3670
  /** Patch a rect of an existing R8 texture with tightly-packed w×h bytes. */
3614
3671
  subImageR8(id, x, y, w, h, data) {
@@ -3640,6 +3697,7 @@ var GLTextureCache = class {
3640
3697
  gl.deleteTexture(tex);
3641
3698
  }
3642
3699
  this.map.clear();
3700
+ this.wraps.clear();
3643
3701
  }
3644
3702
  };
3645
3703
  var GLImageCache = class {
@@ -4289,6 +4347,13 @@ var GroupState = class {
4289
4347
  this.alphaStack.push(nextAlpha);
4290
4348
  this.colorMatrixStack.push(nextCM);
4291
4349
  }
4350
+ /** Drop every pushed frame, leaving the root. A frame that throws part-way
4351
+ * down the tree never pops, and the next frame would draw under leftovers. */
4352
+ reset() {
4353
+ this.transformStack.length = 1;
4354
+ this.alphaStack.length = 1;
4355
+ this.colorMatrixStack.length = 1;
4356
+ }
4292
4357
  pop() {
4293
4358
  if (this.transformStack.length <= 1) {
4294
4359
  throw new Error("GroupState.pop: cannot pop root frame");
@@ -4936,16 +5001,28 @@ function verticalAlignOffset(align, boxHeight, textHeight) {
4936
5001
  return align === "center" ? slack / 2 : slack;
4937
5002
  }
4938
5003
  var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
5004
+ var NUM_RE_AT = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/y;
5005
+ function isCommandLetter(d, j) {
5006
+ const c = d[j];
5007
+ if (!/[A-Za-z]/.test(c)) return false;
5008
+ if (c !== "e" && c !== "E") return true;
5009
+ if (!/[\d.]/.test(d[j - 1] ?? "")) return true;
5010
+ const k = d[j + 1] === "+" || d[j + 1] === "-" ? j + 2 : j + 1;
5011
+ return !/\d/.test(d[k] ?? "");
5012
+ }
4939
5013
  function tokenize(d) {
4940
5014
  const out = [];
4941
5015
  let i = 0;
4942
5016
  while (i < d.length) {
4943
- const ch = d[i];
4944
- if (/[A-Za-z]/.test(ch)) {
5017
+ if (isCommandLetter(d, i)) {
4945
5018
  let j = i + 1;
4946
- while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
5019
+ while (j < d.length && !isCommandLetter(d, j)) j++;
4947
5020
  const argSlice = d.slice(i + 1, j);
4948
- out.push({ cmd: ch, args: readNumbers(argSlice) });
5021
+ const cmd = d[i];
5022
+ out.push({
5023
+ cmd,
5024
+ args: cmd === "A" || cmd === "a" ? readArcArgs(argSlice) : readNumbers(argSlice)
5025
+ });
4949
5026
  i = j;
4950
5027
  } else {
4951
5028
  i++;
@@ -4963,6 +5040,30 @@ function readNumbers(s) {
4963
5040
  }
4964
5041
  return out;
4965
5042
  }
5043
+ function readArcArgs(s) {
5044
+ const out = [];
5045
+ let i = 0;
5046
+ while (i < s.length) {
5047
+ const c = s[i];
5048
+ if (c === "," || c === " " || c === " " || c === "\n" || c === "\r") {
5049
+ i++;
5050
+ continue;
5051
+ }
5052
+ const slot = out.length % 7;
5053
+ if (slot === 3 || slot === 4) {
5054
+ if (c !== "0" && c !== "1") return out;
5055
+ out.push(c === "1" ? 1 : 0);
5056
+ i++;
5057
+ continue;
5058
+ }
5059
+ NUM_RE_AT.lastIndex = i;
5060
+ const m = NUM_RE_AT.exec(s);
5061
+ if (!m) return out;
5062
+ out.push(Number(m[0]));
5063
+ i = NUM_RE_AT.lastIndex;
5064
+ }
5065
+ return out;
5066
+ }
4966
5067
  var ARG_COUNTS = {
4967
5068
  M: 2,
4968
5069
  L: 2,
@@ -5153,9 +5254,9 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
5153
5254
  const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
5154
5255
  const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
5155
5256
  const angle = (ux, uy, vx, vy) => {
5156
- const dot = ux * vx + uy * vy;
5257
+ const dot2 = ux * vx + uy * vy;
5157
5258
  const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
5158
- let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
5259
+ let a = Math.acos(Math.max(-1, Math.min(1, dot2 / len)));
5159
5260
  if (ux * vy - uy * vx < 0) a = -a;
5160
5261
  return a;
5161
5262
  };
@@ -5867,42 +5968,42 @@ var MAX_VERTICES_PER_BATCH = 32768;
5867
5968
  var FLOATS_PER_VERTEX = 6;
5868
5969
  var INITIAL_VERTICES = 256;
5869
5970
  var INITIAL_INDICES = 384;
5971
+ var SOLID_RING_SIZE = 64;
5972
+ var SOLID_RING_SLOT_VERTICES = 1024;
5973
+ var SOLID_RING_SLOT_INDICES = 3072;
5974
+ var SOLID_LARGE_RING_SIZE = 4;
5975
+ function doubledTo(from, need) {
5976
+ let capacity = from;
5977
+ while (capacity < need) capacity *= 2;
5978
+ return capacity;
5979
+ }
5870
5980
  var SolidBatch = class {
5871
5981
  gl;
5872
- vao;
5873
- vbo;
5874
- ibo;
5982
+ aPos;
5983
+ aColor;
5984
+ /** Cycled per flush, and created on first use so a renderer that flushes
5985
+ * rarely allocates as few slots as it flushes. */
5986
+ ring = new Array(SOLID_RING_SIZE).fill(void 0);
5987
+ next = 0;
5988
+ /** The same, for flushes past a slot's capacity; these sets grow to fit. */
5989
+ largeRing = new Array(SOLID_LARGE_RING_SIZE).fill(void 0);
5990
+ nextLarge = 0;
5875
5991
  verts = new Float32Array(INITIAL_VERTICES * FLOATS_PER_VERTEX);
5876
5992
  idx = new Uint32Array(INITIAL_INDICES);
5877
5993
  nVerts = 0;
5878
5994
  nIdx = 0;
5879
- /** What the GPU buffers are currently sized for. */
5880
- vboVerts = 0;
5881
- iboIdx = 0;
5995
+ /** Whether the staged run is rects alone, so its indices are the canonical
5996
+ * quad pattern and a slot already holding that pattern needs no upload. */
5997
+ pureRects = true;
5882
5998
  constructor(gl, prog) {
5883
5999
  const aPos = prog.attribute("a_position");
5884
6000
  const aColor = prog.attribute("a_vertexColor");
5885
6001
  if (aPos === void 0 || aColor === void 0) {
5886
6002
  throw new Error("SolidBatch: vertex-color program is missing a_position / a_vertexColor");
5887
6003
  }
5888
- const vao = gl.createVertexArray();
5889
- const vbo = gl.createBuffer();
5890
- const ibo = gl.createBuffer();
5891
- if (!vao || !vbo || !ibo) throw new Error("SolidBatch: failed to create GL objects");
5892
6004
  this.gl = gl;
5893
- this.vao = vao;
5894
- this.vbo = vbo;
5895
- this.ibo = ibo;
5896
- const stride = FLOATS_PER_VERTEX * 4;
5897
- gl.bindVertexArray(vao);
5898
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
5899
- gl.enableVertexAttribArray(aPos);
5900
- gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);
5901
- gl.enableVertexAttribArray(aColor);
5902
- gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 8);
5903
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
5904
- gl.bindVertexArray(null);
5905
- this.growGpu(INITIAL_VERTICES, INITIAL_INDICES);
6005
+ this.aPos = aPos;
6006
+ this.aColor = aColor;
5906
6007
  }
5907
6008
  get length() {
5908
6009
  return this.nIdx;
@@ -5972,6 +6073,7 @@ var SolidBatch = class {
5972
6073
  const srcIdx = mesh.indices;
5973
6074
  const n = src.length >> 1;
5974
6075
  this.reserve(n, srcIdx.length);
6076
+ this.pureRects = false;
5975
6077
  const v = this.verts;
5976
6078
  let i = this.nVerts * FLOATS_PER_VERTEX;
5977
6079
  const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
@@ -5992,67 +6094,109 @@ var SolidBatch = class {
5992
6094
  this.nVerts += n;
5993
6095
  this.nIdx += srcIdx.length;
5994
6096
  }
5995
- /** Upload the staged geometry and bind the batch VAO. Returns the index
5996
- * count for the caller's `drawElements`. */
6097
+ /** Upload the staged geometry into the next set of buffers and bind its VAO.
6098
+ * Returns the index count for the caller's `drawElements`. */
5997
6099
  uploadAndBind() {
5998
6100
  const gl = this.gl;
5999
- if (this.nVerts > this.vboVerts || this.nIdx > this.iboIdx) {
6000
- this.growGpu(this.nVerts, this.nIdx);
6001
- }
6002
- gl.bindVertexArray(this.vao);
6003
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
6101
+ const set = this.nVerts <= SOLID_RING_SLOT_VERTICES && this.nIdx <= SOLID_RING_SLOT_INDICES ? this.nextRingSlot() : this.nextLargeSlot();
6102
+ gl.bindVertexArray(set.vao);
6103
+ gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);
6004
6104
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * FLOATS_PER_VERTEX);
6005
- gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);
6105
+ const rects = this.pureRects ? this.nVerts >> 2 : -1;
6106
+ if (rects < 0 || rects !== set.indexRects) {
6107
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);
6108
+ set.indexRects = rects;
6109
+ }
6006
6110
  return this.nIdx;
6007
6111
  }
6008
6112
  reset() {
6009
6113
  this.nVerts = 0;
6010
6114
  this.nIdx = 0;
6115
+ this.pureRects = true;
6011
6116
  }
6012
6117
  dispose() {
6013
- const gl = this.gl;
6014
- gl.deleteBuffer(this.vbo);
6015
- gl.deleteBuffer(this.ibo);
6016
- gl.deleteVertexArray(this.vao);
6118
+ for (const set of [...this.ring, ...this.largeRing]) {
6119
+ if (set) this.deleteSet(set);
6120
+ }
6121
+ this.ring.fill(void 0);
6122
+ this.largeRing.fill(void 0);
6017
6123
  }
6018
6124
  /** Grow the CPU arrays so `vertices` / `indices` more fit. */
6019
6125
  reserve(vertices, indices) {
6020
6126
  const needVerts = (this.nVerts + vertices) * FLOATS_PER_VERTEX;
6021
6127
  if (needVerts > this.verts.length) {
6022
- let len = this.verts.length;
6023
- while (len < needVerts) len *= 2;
6024
- const grown = new Float32Array(len);
6128
+ const grown = new Float32Array(doubledTo(this.verts.length, needVerts));
6025
6129
  grown.set(this.verts);
6026
6130
  this.verts = grown;
6027
6131
  }
6028
6132
  const needIdx = this.nIdx + indices;
6029
6133
  if (needIdx > this.idx.length) {
6030
- let len = this.idx.length;
6031
- while (len < needIdx) len *= 2;
6032
- const grown = new Uint32Array(len);
6134
+ const grown = new Uint32Array(doubledTo(this.idx.length, needIdx));
6033
6135
  grown.set(this.idx);
6034
6136
  this.idx = grown;
6035
6137
  }
6036
6138
  }
6037
- /** Size both GPU buffers for at least what is staged, doubling to amortize. */
6038
- growGpu(vertices, indices) {
6139
+ nextRingSlot() {
6140
+ const slot = this.next;
6141
+ this.next = (slot + 1) % SOLID_RING_SIZE;
6142
+ const existing = this.ring[slot];
6143
+ if (existing) return existing;
6144
+ const set = this.createSet(SOLID_RING_SLOT_VERTICES, SOLID_RING_SLOT_INDICES);
6145
+ this.ring[slot] = set;
6146
+ return set;
6147
+ }
6148
+ nextLargeSlot() {
6039
6149
  const gl = this.gl;
6040
- if (vertices > this.vboVerts) {
6041
- let capacity = Math.max(this.vboVerts, INITIAL_VERTICES);
6042
- while (capacity < vertices) capacity *= 2;
6043
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
6044
- gl.bufferData(gl.ARRAY_BUFFER, capacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);
6045
- this.vboVerts = capacity;
6046
- }
6047
- if (indices > this.iboIdx) {
6048
- let capacity = Math.max(this.iboIdx, INITIAL_INDICES);
6049
- while (capacity < indices) capacity *= 2;
6050
- gl.bindVertexArray(this.vao);
6051
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
6052
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, capacity * 4, gl.DYNAMIC_DRAW);
6150
+ const slot = this.nextLarge;
6151
+ this.nextLarge = (slot + 1) % SOLID_LARGE_RING_SIZE;
6152
+ let set = this.largeRing[slot];
6153
+ if (!set) {
6154
+ set = this.createSet(
6155
+ doubledTo(SOLID_RING_SLOT_VERTICES, this.nVerts),
6156
+ doubledTo(SOLID_RING_SLOT_INDICES, this.nIdx)
6157
+ );
6158
+ this.largeRing[slot] = set;
6159
+ return set;
6160
+ }
6161
+ if (this.nVerts > set.vertexCapacity) {
6162
+ set.vertexCapacity = doubledTo(set.vertexCapacity, this.nVerts);
6163
+ gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);
6164
+ gl.bufferData(gl.ARRAY_BUFFER, set.vertexCapacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);
6165
+ }
6166
+ if (this.nIdx > set.indexCapacity) {
6167
+ set.indexCapacity = doubledTo(set.indexCapacity, this.nIdx);
6168
+ gl.bindVertexArray(set.vao);
6169
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, set.ibo);
6170
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, set.indexCapacity * 4, gl.DYNAMIC_DRAW);
6053
6171
  gl.bindVertexArray(null);
6054
- this.iboIdx = capacity;
6172
+ set.indexRects = -1;
6055
6173
  }
6174
+ return set;
6175
+ }
6176
+ createSet(vertices, indices) {
6177
+ const gl = this.gl;
6178
+ const vao = gl.createVertexArray();
6179
+ const vbo = gl.createBuffer();
6180
+ const ibo = gl.createBuffer();
6181
+ if (!vao || !vbo || !ibo) throw new Error("SolidBatch: failed to create GL objects");
6182
+ const stride = FLOATS_PER_VERTEX * 4;
6183
+ gl.bindVertexArray(vao);
6184
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
6185
+ gl.bufferData(gl.ARRAY_BUFFER, vertices * stride, gl.DYNAMIC_DRAW);
6186
+ gl.enableVertexAttribArray(this.aPos);
6187
+ gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);
6188
+ gl.enableVertexAttribArray(this.aColor);
6189
+ gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, stride, 8);
6190
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
6191
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices * 4, gl.DYNAMIC_DRAW);
6192
+ gl.bindVertexArray(null);
6193
+ return { vao, vbo, ibo, vertexCapacity: vertices, indexCapacity: indices, indexRects: -1 };
6194
+ }
6195
+ deleteSet(set) {
6196
+ const gl = this.gl;
6197
+ gl.deleteBuffer(set.vbo);
6198
+ gl.deleteBuffer(set.ibo);
6199
+ gl.deleteVertexArray(set.vao);
6056
6200
  }
6057
6201
  };
6058
6202
  var FRAME_UPLOADS = /* @__PURE__ */ new WeakMap();
@@ -6075,6 +6219,25 @@ function sameValues(prev, next) {
6075
6219
  for (let i = 0; i < prev.length; i++) if (prev[i] !== next[i]) return false;
6076
6220
  return true;
6077
6221
  }
6222
+ function setColorUniform(ctx, prog, r, g, b, a) {
6223
+ const loc = prog.uniform("u_color");
6224
+ if (loc === void 0) return;
6225
+ const uploaded = uploadedFor(ctx, prog);
6226
+ const prev = uploaded.color;
6227
+ if (prev !== void 0 && prev[0] === r && prev[1] === g && prev[2] === b && prev[3] === a) {
6228
+ return;
6229
+ }
6230
+ ctx.gl.uniform4f(loc, r, g, b, a);
6231
+ uploaded.color = [r, g, b, a];
6232
+ }
6233
+ function setAlphaUniform(ctx, prog, alpha) {
6234
+ const loc = prog.uniform("u_alpha");
6235
+ if (loc === void 0) return;
6236
+ const uploaded = uploadedFor(ctx, prog);
6237
+ if (uploaded.alpha === alpha) return;
6238
+ ctx.gl.uniform1f(loc, alpha);
6239
+ uploaded.alpha = alpha;
6240
+ }
6078
6241
  var BATCH_MODEL = mat3.identity();
6079
6242
  function projFor(ctx) {
6080
6243
  let proj = FRAME_PROJ.get(ctx);
@@ -6367,8 +6530,8 @@ function flushSolids(ctx) {
6367
6530
  gl.useProgram(prog.handle);
6368
6531
  const indexCount = batch.uploadAndBind();
6369
6532
  setProjAndModel(ctx, prog, BATCH_MODEL);
6370
- gl.uniform4f(prog.uniform("u_color"), 1, 1, 1, 1);
6371
- gl.uniform1f(prog.uniform("u_alpha"), staged.alpha);
6533
+ setColorUniform(ctx, prog, 1, 1, 1, 1);
6534
+ setAlphaUniform(ctx, prog, staged.alpha);
6372
6535
  setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
6373
6536
  applyClipTest(ctx, staged.clipDepth);
6374
6537
  gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_INT, 0);
@@ -6433,10 +6596,9 @@ function setProjAndModel(ctx, prog, model = ctx.state.transform) {
6433
6596
  }
6434
6597
  }
6435
6598
  function setSolidPaintUniforms(ctx, prog, color, opacity) {
6436
- const gl = ctx.gl;
6437
6599
  const [r, g, b, a] = resolveColor(color);
6438
- gl.uniform4f(prog.uniform("u_color"), r, g, b, a * (opacity ?? 1));
6439
- gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
6600
+ setColorUniform(ctx, prog, r, g, b, a * (opacity ?? 1));
6601
+ setAlphaUniform(ctx, prog, ctx.state.alpha);
6440
6602
  }
6441
6603
  function drawPathFillByKind(ctx, fill, handle) {
6442
6604
  const kind = fill.fill ?? "solid";
@@ -6482,7 +6644,7 @@ function drawPathFillPattern(ctx, fill, handle) {
6482
6644
  ctx.textureCache.bind(tex.id, 0);
6483
6645
  gl.uniform1i(ctx.patternFill.uniform("u_sampler"), 0);
6484
6646
  gl.uniform1f(ctx.patternFill.uniform("u_opacity"), fill.opacity ?? 1);
6485
- gl.uniform1f(ctx.patternFill.uniform("u_alpha"), ctx.state.alpha);
6647
+ setAlphaUniform(ctx, ctx.patternFill, ctx.state.alpha);
6486
6648
  applyClipTest(ctx);
6487
6649
  gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
6488
6650
  gl.bindVertexArray(null);
@@ -6506,7 +6668,7 @@ function drawPathFillGradient(ctx, fill, handle) {
6506
6668
  gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
6507
6669
  ctx.gradRampCache.bind(key, 0);
6508
6670
  gl.uniform1i(ctx.gradFill.uniform("u_ramp"), 0);
6509
- gl.uniform1f(ctx.gradFill.uniform("u_alpha"), ctx.state.alpha);
6671
+ setAlphaUniform(ctx, ctx.gradFill, ctx.state.alpha);
6510
6672
  gl.uniform1f(ctx.gradFill.uniform("u_opacity"), fill.opacity ?? 1);
6511
6673
  if (fill.fill === "linear-gradient") {
6512
6674
  gl.uniform1i(ctx.gradFill.uniform("u_gradKind"), 0);
@@ -6656,6 +6818,7 @@ function drawPathStrokeUnclipped(ctx, cmd) {
6656
6818
  gl.enableVertexAttribArray(aVColorLoc);
6657
6819
  gl.vertexAttribPointer(aVColorLoc, 4, gl.FLOAT, false, 0, 0);
6658
6820
  }
6821
+ applyClipTest(ctx);
6659
6822
  gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
6660
6823
  gl.bindVertexArray(null);
6661
6824
  gl.deleteBuffer(colorVbo);
@@ -6771,7 +6934,7 @@ function drawText(ctx, cmd) {
6771
6934
  preparedPrograms.add(prog);
6772
6935
  setProjAndModel(ctx, prog);
6773
6936
  setColorMatrixUniforms(ctx, prog);
6774
- gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
6937
+ setAlphaUniform(ctx, prog, ctx.state.alpha);
6775
6938
  }
6776
6939
  drawTextGroup(ctx, group, prog, dx, dy);
6777
6940
  }
@@ -6889,8 +7052,8 @@ function drawTextDecorations(ctx, decorations, dx, dy) {
6889
7052
  if (!ibo) throw new Error("drawTextDecorations: createBuffer (IBO) returned null");
6890
7053
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
6891
7054
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW);
6892
- gl.uniform4f(prog.uniform("u_color"), rgba[0], rgba[1], rgba[2], rgba[3]);
6893
- gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
7055
+ setColorUniform(ctx, prog, rgba[0], rgba[1], rgba[2], rgba[3]);
7056
+ setAlphaUniform(ctx, prog, ctx.state.alpha);
6894
7057
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_INT, 0);
6895
7058
  gl.bindVertexArray(null);
6896
7059
  gl.deleteVertexArray(vao);
@@ -6974,7 +7137,7 @@ function drawTextGroup(ctx, group, prog, dx, dy) {
6974
7137
  if ("color" in group.fill) {
6975
7138
  [r, g, b, a] = resolveColor(group.fill.color);
6976
7139
  }
6977
- gl.uniform4f(prog.uniform("u_color"), r, g, b, a);
7140
+ setColorUniform(ctx, prog, r, g, b, a);
6978
7141
  const synthBoldAmount = group.synthetic.bold ? 0.08 : 0;
6979
7142
  const uSynthBold = prog.uniform("u_synthBold");
6980
7143
  if (uSynthBold !== void 0) gl.uniform1f(uSynthBold, synthBoldAmount);
@@ -7078,7 +7241,7 @@ function drawImage(ctx, cmd) {
7078
7241
  );
7079
7242
  gl.uniform1i(prog.uniform("u_sampler"), 0);
7080
7243
  gl.uniform1f(prog.uniform("u_opacity"), cmd.opacity ?? 1);
7081
- gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
7244
+ setAlphaUniform(ctx, prog, ctx.state.alpha);
7082
7245
  applyClipTest(ctx);
7083
7246
  gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
7084
7247
  gl.bindVertexArray(null);
@@ -7293,6 +7456,8 @@ var WeaselRenderer = class {
7293
7456
  const program = new ShaderProgram(this.gl, vertSrc, fragSrc);
7294
7457
  program.lookupUniforms([...CUSTOM_KIT_UNIFORMS, ...extractUniformNames(fragSrc)]);
7295
7458
  program.lookupAttributes(CUSTOM_ATTRIBUTES);
7459
+ const previous = this.programRegistry.get(handle.id);
7460
+ if (previous) this.gl.deleteProgram(previous.handle);
7296
7461
  this.programRegistry.set(handle.id, program);
7297
7462
  }
7298
7463
  applyViewport() {
@@ -7390,7 +7555,7 @@ var WeaselRenderer = class {
7390
7555
  }
7391
7556
  this.meshCache.freeTransient();
7392
7557
  this.meshCache.drainPendingDeletes();
7393
- for (const prog of [this.pathFill, this.pathFillVColor, this.textSdf, this.textSdfR8, this.imageFill, this.gradFill]) {
7558
+ for (const prog of [this.pathFill, this.pathFillVColor, this.textSdf, this.textSdfR8, this.imageFill, this.gradFill, this.patternFill]) {
7394
7559
  gl.deleteProgram(prog.handle);
7395
7560
  }
7396
7561
  for (const prog of this.programRegistry.values()) {
@@ -7417,6 +7582,7 @@ var WeaselRenderer = class {
7417
7582
  const gl = this.gl;
7418
7583
  this.meshCache.drainPendingDeletes();
7419
7584
  resetBakeBudget(this.bakeBudget);
7585
+ this.groupState.reset();
7420
7586
  gl.stencilMask(255);
7421
7587
  gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
7422
7588
  const ctx = {
@@ -10986,7 +11152,7 @@ var reorderBackwardAction = {
10986
11152
  }
10987
11153
  ],
10988
11154
  eligible: { capability: "edits-page" },
10989
- requires: ["selection", "scene"],
11155
+ requires: ["selection", "scene", "applyOps"],
10990
11156
  invoker: {
10991
11157
  timing: "immediate",
10992
11158
  run: (deps, params) => {
@@ -11126,6 +11292,7 @@ function makeAlignAction(edge) {
11126
11292
  icon: ICON_FOR[edge],
11127
11293
  group: "align",
11128
11294
  eligible: { capability: "transforms-selection" },
11295
+ requires: ["selection", "scene"],
11129
11296
  // No default keybindings — six edges/centers don't fit a clean default
11130
11297
  // chord set. Wire bindings explicitly via the actions registry override map.
11131
11298
  invoker: {
@@ -11137,7 +11304,14 @@ function makeAlignAction(edge) {
11137
11304
  alignSelection(selection, scene, edge);
11138
11305
  }
11139
11306
  },
11140
- enabled: () => ActionDisabledReason.SelectionRequired
11307
+ // Deps-aware, matching `alignSelection`'s own `ids.length < 2` guard: a
11308
+ // constant disabled reason greys the entry out forever (see
11309
+ // `requiresSelection`).
11310
+ enabled: (deps) => {
11311
+ const selection = deps?.selection;
11312
+ const count = selection?.get().length ?? 0;
11313
+ return count >= 2 ? true : ActionDisabledReason.SelectionRequired;
11314
+ }
11141
11315
  };
11142
11316
  }
11143
11317
  var alignLeftAction = makeAlignAction("left");
@@ -11218,6 +11392,7 @@ function makeDistributeAction(axis) {
11218
11392
  icon: ICON_FOR2[axis],
11219
11393
  group: "distribute",
11220
11394
  eligible: { capability: "transforms-selection" },
11395
+ requires: ["selection", "scene"],
11221
11396
  // No default keybindings. Wire bindings explicitly via the actions registry.
11222
11397
  invoker: {
11223
11398
  timing: "immediate",
@@ -11228,7 +11403,14 @@ function makeDistributeAction(axis) {
11228
11403
  distributeSelection(selection, scene, axis);
11229
11404
  }
11230
11405
  },
11231
- enabled: () => ActionDisabledReason.SelectionRequired
11406
+ // Deps-aware, matching `distributeSelection`'s own `ids.length < 3` guard:
11407
+ // a constant disabled reason greys the entry out forever (see
11408
+ // `requiresSelection`).
11409
+ enabled: (deps) => {
11410
+ const selection = deps?.selection;
11411
+ const count = selection?.get().length ?? 0;
11412
+ return count >= 3 ? true : ActionDisabledReason.SelectionRequired;
11413
+ }
11232
11414
  };
11233
11415
  }
11234
11416
  var distributeHorizontalAction = makeDistributeAction("x");
@@ -11310,7 +11492,91 @@ function pathToMultiPolygon(path, opts = {}) {
11310
11492
  }
11311
11493
  finalizeCurrent();
11312
11494
  if (rings.length === 0) return [];
11313
- return rings.map((r) => [r]);
11495
+ return groupRings(rings, path.fillRule);
11496
+ }
11497
+ function ringSignedArea(ring) {
11498
+ let a = 0;
11499
+ for (let i = 0, n = ring.length - 1; i < n; i++) {
11500
+ a += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];
11501
+ }
11502
+ return a * 0.5;
11503
+ }
11504
+ function ringBox(ring) {
11505
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
11506
+ for (const [x, y] of ring) {
11507
+ if (x < minX) minX = x;
11508
+ if (x > maxX) maxX = x;
11509
+ if (y < minY) minY = y;
11510
+ if (y > maxY) maxY = y;
11511
+ }
11512
+ return [minX, minY, maxX, maxY];
11513
+ }
11514
+ function pointInRing(ring, px, py) {
11515
+ let inside = false;
11516
+ for (let i = 0, n = ring.length - 1; i < n; i++) {
11517
+ const [ax, ay] = ring[i];
11518
+ const [bx, by] = ring[i + 1];
11519
+ if (ay > py !== by > py && px < (bx - ax) * (py - ay) / (by - ay) + ax) {
11520
+ inside = !inside;
11521
+ }
11522
+ }
11523
+ return inside;
11524
+ }
11525
+ function ringContains(outer, outerBox, inner, innerBox) {
11526
+ if (innerBox[0] < outerBox[0] || innerBox[1] < outerBox[1] || innerBox[2] > outerBox[2] || innerBox[3] > outerBox[3]) return false;
11527
+ const n = inner.length - 1;
11528
+ let votes = 0;
11529
+ for (const k of [0, Math.floor(n / 3), Math.floor(2 * n / 3)]) {
11530
+ if (pointInRing(outer, inner[k][0], inner[k][1])) votes++;
11531
+ }
11532
+ return votes >= 2;
11533
+ }
11534
+ function groupRings(rings, fillRule) {
11535
+ const n = rings.length;
11536
+ if (n === 1) return [[rings[0]]];
11537
+ const boxes = rings.map(ringBox);
11538
+ const orient = rings.map((r) => ringSignedArea(r) >= 0 ? 1 : -1);
11539
+ const parent = new Array(n).fill(-1);
11540
+ const children = rings.map(() => []);
11541
+ for (let i = 0; i < n; i++) {
11542
+ let best = -1;
11543
+ let bestArea = Infinity;
11544
+ for (let j = 0; j < n; j++) {
11545
+ if (i === j) continue;
11546
+ if (!ringContains(rings[j], boxes[j], rings[i], boxes[i])) continue;
11547
+ const area = (boxes[j][2] - boxes[j][0]) * (boxes[j][3] - boxes[j][1]);
11548
+ if (area < bestArea) {
11549
+ bestArea = area;
11550
+ best = j;
11551
+ }
11552
+ }
11553
+ parent[i] = best;
11554
+ }
11555
+ for (let i = 0; i < n; i++) if (parent[i] >= 0) children[parent[i]].push(i);
11556
+ const filled = new Array(n);
11557
+ for (let i = 0; i < n; i++) {
11558
+ let depth = 0;
11559
+ let winding = 0;
11560
+ for (let a = i; a >= 0; a = parent[a]) {
11561
+ winding += orient[a];
11562
+ if (a !== i) depth++;
11563
+ }
11564
+ filled[i] = fillRule === "evenodd" ? depth % 2 === 0 : winding !== 0;
11565
+ }
11566
+ const out = [];
11567
+ for (let i = 0; i < n; i++) {
11568
+ if (!filled[i]) continue;
11569
+ if (parent[i] >= 0 && filled[parent[i]]) continue;
11570
+ const polygon = [rings[i]];
11571
+ const queue = [...children[i]];
11572
+ while (queue.length > 0) {
11573
+ const c = queue.pop();
11574
+ if (filled[c]) queue.push(...children[c]);
11575
+ else polygon.push(rings[c]);
11576
+ }
11577
+ out.push(polygon);
11578
+ }
11579
+ return out;
11314
11580
  }
11315
11581
  function multiPolygonToPath(mp) {
11316
11582
  let nCmds = 0;
@@ -11623,6 +11889,7 @@ function makePathfinderAction(op) {
11623
11889
  icon: ICON_FOR3[op],
11624
11890
  group: "pathfinder",
11625
11891
  eligible: { capability: "creates-shapes" },
11892
+ requires: ["selection", "booleansAdapter"],
11626
11893
  // No default keybindings — there's no industry-standard chord set for
11627
11894
  // boolean ops. Wire bindings explicitly via the actions registry override map.
11628
11895
  invoker: {
@@ -13915,10 +14182,11 @@ function createDispatcher(opts) {
13915
14182
  uiOngoingByAction.set(actionId, gestureId);
13916
14183
  notify2();
13917
14184
  let ended = false;
14185
+ const live = () => !ended && inFlightHandles.get(gestureId) === handle;
13918
14186
  return {
13919
14187
  gestureId,
13920
14188
  update(nextParams) {
13921
- if (ended) return;
14189
+ if (!live()) return;
13922
14190
  if (!handle.onMove) return;
13923
14191
  try {
13924
14192
  handle.onMove(buildUiInvocationCtx(deps, nextParams));
@@ -13928,7 +14196,10 @@ function createDispatcher(opts) {
13928
14196
  notify2();
13929
14197
  },
13930
14198
  end(reason) {
13931
- if (ended) return;
14199
+ if (!live()) {
14200
+ ended = true;
14201
+ return;
14202
+ }
13932
14203
  ended = true;
13933
14204
  if (handle.onEnd) {
13934
14205
  try {
@@ -15142,6 +15413,7 @@ function useTools(opts) {
15142
15413
  );
15143
15414
  const active = contributions.focused;
15144
15415
  const getActiveOverlays = contributions.overlays;
15416
+ const registryKey = Object.keys(slotted.registry).sort().join("|") + "#" + slotted.ambient.map((t) => t.id).join("|");
15145
15417
  return useMemo(
15146
15418
  () => ({
15147
15419
  active,
@@ -15154,7 +15426,7 @@ function useTools(opts) {
15154
15426
  has,
15155
15427
  getActiveOverlays
15156
15428
  }),
15157
- [active, setActive, hotkeyEngaged, engageHotkey, disengageHotkey, has, getActiveOverlays]
15429
+ [active, setActive, hotkeyEngaged, engageHotkey, disengageHotkey, has, getActiveOverlays, registryKey]
15158
15430
  );
15159
15431
  }
15160
15432
  var TOOL_ACTIVATE_ID = "tool.activate";
@@ -16545,8 +16817,17 @@ function composeAffordanceLayer(id, label, affordances) {
16545
16817
  // `buildAffordanceAt` share one implementation rather than two that can
16546
16818
  // drift. This wrapper lifts the region's declared cursor and claim onto
16547
16819
  // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.
16548
- hitTest: (wx, wy, state, view, _dims, isVisible) => {
16549
- const hit = hitAffordanceRegions(affordances, wx, wy, state, view, isVisible);
16820
+ hitTest: (wx, wy, data, view, _dims, isVisible) => {
16821
+ const state = asChromeState(data);
16822
+ if (!state) return null;
16823
+ const hit = hitAffordanceRegions(
16824
+ affordances,
16825
+ wx,
16826
+ wy,
16827
+ state,
16828
+ view,
16829
+ isVisible ?? asIsVisible(data) ?? void 0
16830
+ );
16550
16831
  if (!hit) return null;
16551
16832
  const { cursor, strength, claimedKinds } = hit.region;
16552
16833
  return {
@@ -17149,6 +17430,10 @@ function usePenTool(options) {
17149
17430
  onEnd: (endCtx, reason) => {
17150
17431
  const se = s();
17151
17432
  if (reason === "cancel") {
17433
+ if (se.current && se.draggingHandleAt === se.current.anchors.length - 1) {
17434
+ se.current.anchors.pop();
17435
+ if (se.current.anchors.length === 0) se.current = null;
17436
+ }
17152
17437
  se.draggingHandleAt = null;
17153
17438
  forceRenderRef.current();
17154
17439
  return;
@@ -18727,7 +19012,6 @@ function CanvasInner(props, ref) {
18727
19012
  shaders,
18728
19013
  previewIdsExtra,
18729
19014
  previewPoseExtra,
18730
- previewBoundsExtra,
18731
19015
  gestureSource,
18732
19016
  viewport,
18733
19017
  backgroundFill,
@@ -18753,6 +19037,7 @@ function CanvasInner(props, ref) {
18753
19037
  debugSinkRefForCtx.current = debugSink;
18754
19038
  const adapter = adapterProp;
18755
19039
  const canvasRef = useRef(null);
19040
+ const helpersForLayersRef = useRef(null);
18756
19041
  const dimsRef = useRef({ width, height });
18757
19042
  dimsRef.current = { width, height };
18758
19043
  const getIsVisibleRef = useRef(getIsVisible);
@@ -18775,7 +19060,7 @@ function CanvasInner(props, ref) {
18775
19060
  const isVisible = getIsVisibleRef.current?.() ?? alwaysVisible;
18776
19061
  for (const layer of layers2) {
18777
19062
  if (!layer.hitTest) continue;
18778
- const hit = layer.hitTest(worldX, worldY, void 0, view, dims, isVisible);
19063
+ const hit = layer.hitTest(worldX, worldY, helpersForLayersRef.current, view, dims, isVisible);
18779
19064
  if (hit) return { layerId: layer.id, hit };
18780
19065
  }
18781
19066
  return null;
@@ -18930,8 +19215,8 @@ function CanvasInner(props, ref) {
18930
19215
  };
18931
19216
  const selectedIdsForWiring = effectiveSelection.current;
18932
19217
  const multiActive2 = selectedIdsForWiring.length > 1;
18933
- const previewExtraRef = useRef({ previewPoseExtra, previewBoundsExtra });
18934
- previewExtraRef.current = { previewPoseExtra, previewBoundsExtra };
19218
+ const previewExtraRef = useRef({ previewPoseExtra });
19219
+ previewExtraRef.current = { previewPoseExtra };
18935
19220
  const effectiveBoundsOf = useMemo(() => {
18936
19221
  return boundsOf2 ?? baseBoundsOf;
18937
19222
  }, [boundsOf2, baseBoundsOf]);
@@ -18950,10 +19235,6 @@ function CanvasInner(props, ref) {
18950
19235
  if (p != null) return geometry.getBounds(p);
18951
19236
  }
18952
19237
  const extras = previewExtraRef.current;
18953
- if (extras.previewBoundsExtra) {
18954
- const b = extras.previewBoundsExtra(id);
18955
- if (b) return b;
18956
- }
18957
19238
  if (extras.previewPoseExtra) {
18958
19239
  const p = extras.previewPoseExtra(id);
18959
19240
  if (p != null) return geometry.getBounds(p);
@@ -18994,10 +19275,6 @@ function CanvasInner(props, ref) {
18994
19275
  const p = firstPreviewPose(tools, id);
18995
19276
  if (p != null) return geometry.getBounds(p);
18996
19277
  }
18997
- if (previewBoundsExtra) {
18998
- const b = previewBoundsExtra(id);
18999
- if (b) return b;
19000
- }
19001
19278
  if (previewPoseExtra) {
19002
19279
  const p = previewPoseExtra(id);
19003
19280
  if (p != null) return geometry.getBounds(p);
@@ -19033,6 +19310,7 @@ function CanvasInner(props, ref) {
19033
19310
  getDebug: () => debugSink,
19034
19311
  getIsVisible: () => getIsVisibleRef.current?.() ?? alwaysVisible
19035
19312
  };
19313
+ helpersForLayersRef.current = helpersForLayers;
19036
19314
  if (helpersRef) helpersRef.current = helpersForLayers;
19037
19315
  const pointerDownRef = useRef(false);
19038
19316
  const handlePointerDown = (e) => {
@@ -19214,6 +19492,11 @@ function CanvasInner(props, ref) {
19214
19492
  );
19215
19493
  renderer.render(commands, viewToMat3(effectiveView));
19216
19494
  }, [layersWithDebug, width, height, effectiveView, debugSink, redrawNonce, dprProp]);
19495
+ useEffect(() => () => {
19496
+ glRendererRef.current?.dispose();
19497
+ glRendererRef.current = null;
19498
+ lastResizeRef.current = null;
19499
+ }, []);
19217
19500
  const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
19218
19501
  useEffect(() => {
19219
19502
  const renderer = glRendererRef.current;
@@ -20642,6 +20925,89 @@ function poseContainsRotated(pose, wx, wy, tolerance) {
20642
20925
  }
20643
20926
  return poseContains(pose, wx, wy, tolerance);
20644
20927
  }
20928
+ function hiddenLayerIds(layers) {
20929
+ const out = /* @__PURE__ */ new Set();
20930
+ for (const layer of layers ?? []) {
20931
+ if (layer.visible === false) out.add(layer.id);
20932
+ }
20933
+ return out;
20934
+ }
20935
+ function hitTestArea(scene, bounds) {
20936
+ const { x, y, width: w, height: h } = bounds;
20937
+ const area = [x, y, x + w, y, x + w, y + h, x, y + h];
20938
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
20939
+ }
20940
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
20941
+ const ab = areaBounds ?? boundsOf(area);
20942
+ if (!ab) return [];
20943
+ const hits = [];
20944
+ const hidden = hiddenLayerIds(scene.layers);
20945
+ const order = scene.renderOrderNodes();
20946
+ for (let i = 0; i < order.length; i++) {
20947
+ const node = order[i];
20948
+ if (node.kind === "container") continue;
20949
+ if (hidden.size > 0 && hidden.has(node.layer)) continue;
20950
+ const pose = node.pose;
20951
+ const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
20952
+ const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose)) : aabbOfPose(pose);
20953
+ if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
20954
+ continue;
20955
+ }
20956
+ if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
20957
+ continue;
20958
+ }
20959
+ if (areaIsRect && b.x >= ab.x && b.y >= ab.y && b.x + b.width <= ab.x + ab.width && b.y + b.height <= ab.y + ab.height) {
20960
+ hits.push(node.id);
20961
+ continue;
20962
+ }
20963
+ if (silhouette) {
20964
+ if (silhouetteOverlapsArea(pose.coords, area)) {
20965
+ hits.push(node.id);
20966
+ }
20967
+ continue;
20968
+ }
20969
+ const drawn = findShapeSilhouette(node, pose);
20970
+ if (drawn?.kind === "polygon") {
20971
+ if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
20972
+ continue;
20973
+ }
20974
+ hits.push(node.id);
20975
+ }
20976
+ return hits;
20977
+ }
20978
+ function silhouetteOverlapsArea(sil, area) {
20979
+ const ns = sil.length >> 1;
20980
+ const na = area.length >> 1;
20981
+ if (ns < 1 || na < 3) return false;
20982
+ for (let i = 0; i < ns; i++) {
20983
+ if (pointInPolygon(area, sil[i * 2], sil[i * 2 + 1])) return true;
20984
+ }
20985
+ for (let i = 0; i < na; i++) {
20986
+ if (pointInPolygon(sil, area[i * 2], area[i * 2 + 1])) return true;
20987
+ }
20988
+ for (let i = 0, j = ns - 1; i < ns; j = i++) {
20989
+ const ax = sil[j * 2], ay = sil[j * 2 + 1];
20990
+ const bx = sil[i * 2], by = sil[i * 2 + 1];
20991
+ for (let k = 0, l = na - 1; k < na; l = k++) {
20992
+ const cx = area[l * 2], cy = area[l * 2 + 1];
20993
+ const dx = area[k * 2], dy = area[k * 2 + 1];
20994
+ if (segmentsCross(ax, ay, bx, by, cx, cy, dx, dy)) return true;
20995
+ }
20996
+ }
20997
+ return false;
20998
+ }
20999
+ function boundsOf(coords) {
21000
+ if (coords.length < 2) return null;
21001
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
21002
+ for (let i = 0; i + 1 < coords.length; i += 2) {
21003
+ const x = coords[i], y = coords[i + 1];
21004
+ if (x < minX) minX = x;
21005
+ if (y < minY) minY = y;
21006
+ if (x > maxX) maxX = x;
21007
+ if (y > maxY) maxY = y;
21008
+ }
21009
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
21010
+ }
20645
21011
  var DEFAULT_PICK_TOLERANCE_PX = 4;
20646
21012
  function useSceneSelectTool(args) {
20647
21013
  const { scene, selection, geometry, selectTool: opts, insertTool, layouts, getView } = args;
@@ -20739,7 +21105,9 @@ function useSceneSelectTool(args) {
20739
21105
  }
20740
21106
  const tolerance = pickTolerancePx / meanScale(getView?.()?.scale ?? { x: 1, y: 1 });
20741
21107
  const out = [];
21108
+ const hidden = hiddenLayerIds(scene.layers);
20742
21109
  for (const n of scene.renderOrderNodes()) {
21110
+ if (hidden.size > 0 && hidden.has(n.layer)) continue;
20743
21111
  if (!poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
20744
21112
  if (shapePicking && !shapeCoversPoint(n, n.pose, wx, wy, { tolerance })) continue;
20745
21113
  out.push(n.id);
@@ -21623,8 +21991,10 @@ function createPathAnchorAffordances(getAnchorState, opts = {}) {
21623
21991
  const anchors = {
21624
21992
  id: PATH_ANCHOR_CHROME_ID,
21625
21993
  regions(state) {
21994
+ const editingId = getAnchorState()?.editingId ?? null;
21626
21995
  const out = [];
21627
21996
  for (const { id, path } of editablePaths(state)) {
21997
+ if (editingId !== null && id !== editingId) continue;
21628
21998
  for (const a of enumerateAnchors(path)) {
21629
21999
  out.push(point(
21630
22000
  `${id}:anchor:${a.anchorIndex}`,
@@ -21879,80 +22249,6 @@ function DepRegistryProviderIfRoot({ children }) {
21879
22249
  if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
21880
22250
  return /* @__PURE__ */ jsx(DepRegistryProvider, { children });
21881
22251
  }
21882
- function hitTestArea(scene, bounds) {
21883
- const { x, y, width: w, height: h } = bounds;
21884
- const area = [x, y, x + w, y, x + w, y + h, x, y + h];
21885
- return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
21886
- }
21887
- function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
21888
- const ab = areaBounds ?? boundsOf(area);
21889
- if (!ab) return [];
21890
- const hits = [];
21891
- const order = scene.renderOrderNodes();
21892
- for (let i = 0; i < order.length; i++) {
21893
- const node = order[i];
21894
- if (node.kind === "container") continue;
21895
- const pose = node.pose;
21896
- const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
21897
- const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose)) : aabbOfPose(pose);
21898
- if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
21899
- continue;
21900
- }
21901
- if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
21902
- continue;
21903
- }
21904
- if (areaIsRect && b.x >= ab.x && b.y >= ab.y && b.x + b.width <= ab.x + ab.width && b.y + b.height <= ab.y + ab.height) {
21905
- hits.push(node.id);
21906
- continue;
21907
- }
21908
- if (silhouette) {
21909
- if (silhouetteOverlapsArea(pose.coords, area)) {
21910
- hits.push(node.id);
21911
- }
21912
- continue;
21913
- }
21914
- const drawn = findShapeSilhouette(node, pose);
21915
- if (drawn?.kind === "polygon") {
21916
- if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
21917
- continue;
21918
- }
21919
- hits.push(node.id);
21920
- }
21921
- return hits;
21922
- }
21923
- function silhouetteOverlapsArea(sil, area) {
21924
- const ns = sil.length >> 1;
21925
- const na = area.length >> 1;
21926
- if (ns < 1 || na < 3) return false;
21927
- for (let i = 0; i < ns; i++) {
21928
- if (pointInPolygon(area, sil[i * 2], sil[i * 2 + 1])) return true;
21929
- }
21930
- for (let i = 0; i < na; i++) {
21931
- if (pointInPolygon(sil, area[i * 2], area[i * 2 + 1])) return true;
21932
- }
21933
- for (let i = 0, j = ns - 1; i < ns; j = i++) {
21934
- const ax = sil[j * 2], ay = sil[j * 2 + 1];
21935
- const bx = sil[i * 2], by = sil[i * 2 + 1];
21936
- for (let k = 0, l = na - 1; k < na; l = k++) {
21937
- const cx = area[l * 2], cy = area[l * 2 + 1];
21938
- const dx = area[k * 2], dy = area[k * 2 + 1];
21939
- if (segmentsCross(ax, ay, bx, by, cx, cy, dx, dy)) return true;
21940
- }
21941
- }
21942
- return false;
21943
- }
21944
- function boundsOf(coords) {
21945
- if (coords.length < 2) return null;
21946
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
21947
- for (let i = 0; i + 1 < coords.length; i += 2) {
21948
- const x = coords[i], y = coords[i + 1];
21949
- if (x < minX) minX = x;
21950
- if (y < minY) minY = y;
21951
- if (x > maxX) maxX = x;
21952
- if (y > maxY) maxY = y;
21953
- }
21954
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
21955
- }
21956
22252
  function useViewDepSource(currentViewRef, onViewChange, recenter, hostSize) {
21957
22253
  const viewApiRef = useRef({
21958
22254
  get: () => currentViewRef.current,
@@ -22724,6 +23020,7 @@ function useActionsPropResolver(actions) {
22724
23020
  }
22725
23021
  if (!prop) return;
22726
23022
  const unregisters = [];
23023
+ const restores = [];
22727
23024
  const warnedIds = /* @__PURE__ */ new Set();
22728
23025
  for (const [slotId, entry] of Object.entries(prop)) {
22729
23026
  if (entry === null) {
@@ -22751,10 +23048,14 @@ function useActionsPropResolver(actions) {
22751
23048
  }
22752
23049
  const { id: _drop, ...rest } = entry;
22753
23050
  const merged = { ...existing, ...rest };
23051
+ restores.push(() => {
23052
+ reg.register(existing);
23053
+ });
22754
23054
  unregisters.push(reg.register(merged));
22755
23055
  }
22756
23056
  return () => {
22757
23057
  for (const u of unregisters) u();
23058
+ for (const r of restores) r();
22758
23059
  };
22759
23060
  }, [reg, actions]);
22760
23061
  }
@@ -24506,5 +24807,5 @@ function oklchToOklab(L, C, h) {
24506
24807
  }
24507
24808
 
24508
24809
  export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand, eligibleContribution, evaluateEnabled, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry };
24509
- //# sourceMappingURL=chunk-3WPOGKUP.js.map
24510
- //# sourceMappingURL=chunk-3WPOGKUP.js.map
24810
+ //# sourceMappingURL=chunk-DWV7SFKR.js.map
24811
+ //# sourceMappingURL=chunk-DWV7SFKR.js.map