@fieldnotes/core 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1933,10 +1933,134 @@ function getTemplateBounds(el) {
1933
1933
  }
1934
1934
  }
1935
1935
  }
1936
+ function transferStrokeBounds(prev, next) {
1937
+ if (prev.type !== "stroke" || next.type !== "stroke") return;
1938
+ if (prev.points !== next.points) return;
1939
+ if (prev.position.x !== next.position.x || prev.position.y !== next.position.y) return;
1940
+ const bounds = strokeBoundsCache.get(prev);
1941
+ if (bounds) strokeBoundsCache.set(next, bounds);
1942
+ }
1936
1943
  function boundsIntersect(a, b) {
1937
1944
  return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
1938
1945
  }
1939
1946
 
1947
+ // src/elements/stroke-smoothing.ts
1948
+ var MIN_PRESSURE_SCALE = 0.2;
1949
+ function pressureToWidth(pressure, baseWidth) {
1950
+ return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
1951
+ }
1952
+ function simplifyPoints(points, tolerance) {
1953
+ if (points.length <= 2) return points.slice();
1954
+ return rdp(points, 0, points.length - 1, tolerance);
1955
+ }
1956
+ function rdp(points, start, end, tolerance) {
1957
+ const first = points[start];
1958
+ const last = points[end];
1959
+ if (!first || !last) return [];
1960
+ if (end - start <= 1) return [first, last];
1961
+ let maxDist = 0;
1962
+ let maxIndex = start;
1963
+ for (let i = start + 1; i < end; i++) {
1964
+ const pt = points[i];
1965
+ if (!pt) continue;
1966
+ const dist = perpendicularDistance(pt, first, last);
1967
+ if (dist > maxDist) {
1968
+ maxDist = dist;
1969
+ maxIndex = i;
1970
+ }
1971
+ }
1972
+ if (maxDist <= tolerance) return [first, last];
1973
+ const left = rdp(points, start, maxIndex, tolerance);
1974
+ const right = rdp(points, maxIndex, end, tolerance);
1975
+ return left.concat(right.slice(1));
1976
+ }
1977
+ function perpendicularDistance(pt, lineStart, lineEnd) {
1978
+ const dx = lineEnd.x - lineStart.x;
1979
+ const dy = lineEnd.y - lineStart.y;
1980
+ const lenSq = dx * dx + dy * dy;
1981
+ if (lenSq === 0) {
1982
+ const ex = pt.x - lineStart.x;
1983
+ const ey = pt.y - lineStart.y;
1984
+ return Math.sqrt(ex * ex + ey * ey);
1985
+ }
1986
+ const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
1987
+ return num / Math.sqrt(lenSq);
1988
+ }
1989
+ function smoothToSegments(points) {
1990
+ if (points.length < 2) return [];
1991
+ if (points.length === 2) {
1992
+ const p0 = points[0];
1993
+ const p1 = points[1];
1994
+ if (!p0 || !p1) return [];
1995
+ const mx = (p0.x + p1.x) / 2;
1996
+ const my = (p0.y + p1.y) / 2;
1997
+ return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
1998
+ }
1999
+ const segments = [];
2000
+ const n = points.length;
2001
+ for (let i = 0; i < n - 1; i++) {
2002
+ const p0 = points[Math.max(0, i - 1)];
2003
+ const p1 = points[i];
2004
+ const p2 = points[i + 1];
2005
+ const p3 = points[Math.min(n - 1, i + 2)];
2006
+ if (!p0 || !p1 || !p2 || !p3) continue;
2007
+ const cp1 = {
2008
+ x: p1.x + (p2.x - p0.x) / 6,
2009
+ y: p1.y + (p2.y - p0.y) / 6
2010
+ };
2011
+ const cp2 = {
2012
+ x: p2.x - (p3.x - p1.x) / 6,
2013
+ y: p2.y - (p3.y - p1.y) / 6
2014
+ };
2015
+ segments.push({ start: p1, cp1, cp2, end: p2 });
2016
+ }
2017
+ return segments;
2018
+ }
2019
+
2020
+ // src/elements/stroke-cache.ts
2021
+ var cache = /* @__PURE__ */ new WeakMap();
2022
+ var WIDTH_QUANTUM = 0.25;
2023
+ function buildWidthBuckets(segments, widths) {
2024
+ if (typeof Path2D === "undefined") return null;
2025
+ const byWidth = /* @__PURE__ */ new Map();
2026
+ for (let i = 0; i < segments.length; i++) {
2027
+ const seg = segments[i];
2028
+ const w = widths[i];
2029
+ if (!seg || w === void 0) continue;
2030
+ const q = Math.max(WIDTH_QUANTUM, Math.round(w / WIDTH_QUANTUM) * WIDTH_QUANTUM);
2031
+ let path = byWidth.get(q);
2032
+ if (!path) {
2033
+ path = new Path2D();
2034
+ byWidth.set(q, path);
2035
+ }
2036
+ path.moveTo(seg.start.x, seg.start.y);
2037
+ path.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
2038
+ }
2039
+ return [...byWidth.entries()].map(([width, path]) => ({ width, path }));
2040
+ }
2041
+ function computeStrokeSegments(stroke) {
2042
+ const segments = smoothToSegments(stroke.points);
2043
+ const widths = [];
2044
+ for (const seg of segments) {
2045
+ const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
2046
+ widths.push(w);
2047
+ }
2048
+ const data = { segments, widths, buckets: buildWidthBuckets(segments, widths) };
2049
+ cache.set(stroke, data);
2050
+ return data;
2051
+ }
2052
+ function getStrokeRenderData(stroke) {
2053
+ const cached = cache.get(stroke);
2054
+ if (cached) return cached;
2055
+ return computeStrokeSegments(stroke);
2056
+ }
2057
+ function transferStrokeRenderData(prev, next) {
2058
+ if (prev.type !== "stroke" || next.type !== "stroke") return;
2059
+ if (prev.points !== next.points) return;
2060
+ const data = cache.get(prev);
2061
+ if (data) cache.set(next, data);
2062
+ }
2063
+
1940
2064
  // src/elements/element-store.ts
1941
2065
  var ElementStore = class {
1942
2066
  elements = /* @__PURE__ */ new Map();
@@ -1987,6 +2111,10 @@ var ElementStore = class {
1987
2111
  this.sortedCache = null;
1988
2112
  this._versions.set(id, (this._versions.get(id) ?? 0) + 1);
1989
2113
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
2114
+ if (updated.type === "stroke" && existing.type === "stroke") {
2115
+ transferStrokeRenderData(existing, updated);
2116
+ transferStrokeBounds(existing, updated);
2117
+ }
1990
2118
  if (updated.type === "arrow") {
1991
2119
  const arrow = updated;
1992
2120
  arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
@@ -2030,6 +2158,12 @@ var ElementStore = class {
2030
2158
  this._versions.set(el.id, 0);
2031
2159
  const bounds = getElementBounds(el);
2032
2160
  if (bounds) this.spatialIndex.insert(el.id, bounds);
2161
+ if (el.type === "stroke") {
2162
+ computeStrokeSegments(el);
2163
+ }
2164
+ if (el.type === "arrow" && el.bend !== 0 && !el.cachedControlPoint) {
2165
+ el.cachedControlPoint = getArrowControlPoint(el.from, el.to, el.bend);
2166
+ }
2033
2167
  }
2034
2168
  this.bus.emit("clear", null);
2035
2169
  for (const el of elements) {
@@ -2115,6 +2249,20 @@ var ElementStore = class {
2115
2249
  }
2116
2250
  };
2117
2251
 
2252
+ // src/elements/arrow-render-cache.ts
2253
+ var cache2 = /* @__PURE__ */ new WeakMap();
2254
+ function getArrowRenderGeometry(arrow) {
2255
+ const hit = cache2.get(arrow);
2256
+ if (hit) return hit;
2257
+ const geometry = {
2258
+ controlPoint: arrow.bend !== 0 ? arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend) : null,
2259
+ tangentStart: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0),
2260
+ tangentEnd: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1)
2261
+ };
2262
+ cache2.set(arrow, geometry);
2263
+ return geometry;
2264
+ }
2265
+
2118
2266
  // src/elements/arrow-binding.ts
2119
2267
  var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
2120
2268
  function isBindable(element) {
@@ -2235,98 +2383,6 @@ function unbindArrow(arrow, store) {
2235
2383
  return updates;
2236
2384
  }
2237
2385
 
2238
- // src/elements/stroke-smoothing.ts
2239
- var MIN_PRESSURE_SCALE = 0.2;
2240
- function pressureToWidth(pressure, baseWidth) {
2241
- return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
2242
- }
2243
- function simplifyPoints(points, tolerance) {
2244
- if (points.length <= 2) return points.slice();
2245
- return rdp(points, 0, points.length - 1, tolerance);
2246
- }
2247
- function rdp(points, start, end, tolerance) {
2248
- const first = points[start];
2249
- const last = points[end];
2250
- if (!first || !last) return [];
2251
- if (end - start <= 1) return [first, last];
2252
- let maxDist = 0;
2253
- let maxIndex = start;
2254
- for (let i = start + 1; i < end; i++) {
2255
- const pt = points[i];
2256
- if (!pt) continue;
2257
- const dist = perpendicularDistance(pt, first, last);
2258
- if (dist > maxDist) {
2259
- maxDist = dist;
2260
- maxIndex = i;
2261
- }
2262
- }
2263
- if (maxDist <= tolerance) return [first, last];
2264
- const left = rdp(points, start, maxIndex, tolerance);
2265
- const right = rdp(points, maxIndex, end, tolerance);
2266
- return left.concat(right.slice(1));
2267
- }
2268
- function perpendicularDistance(pt, lineStart, lineEnd) {
2269
- const dx = lineEnd.x - lineStart.x;
2270
- const dy = lineEnd.y - lineStart.y;
2271
- const lenSq = dx * dx + dy * dy;
2272
- if (lenSq === 0) {
2273
- const ex = pt.x - lineStart.x;
2274
- const ey = pt.y - lineStart.y;
2275
- return Math.sqrt(ex * ex + ey * ey);
2276
- }
2277
- const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
2278
- return num / Math.sqrt(lenSq);
2279
- }
2280
- function smoothToSegments(points) {
2281
- if (points.length < 2) return [];
2282
- if (points.length === 2) {
2283
- const p0 = points[0];
2284
- const p1 = points[1];
2285
- if (!p0 || !p1) return [];
2286
- const mx = (p0.x + p1.x) / 2;
2287
- const my = (p0.y + p1.y) / 2;
2288
- return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
2289
- }
2290
- const segments = [];
2291
- const n = points.length;
2292
- for (let i = 0; i < n - 1; i++) {
2293
- const p0 = points[Math.max(0, i - 1)];
2294
- const p1 = points[i];
2295
- const p2 = points[i + 1];
2296
- const p3 = points[Math.min(n - 1, i + 2)];
2297
- if (!p0 || !p1 || !p2 || !p3) continue;
2298
- const cp1 = {
2299
- x: p1.x + (p2.x - p0.x) / 6,
2300
- y: p1.y + (p2.y - p0.y) / 6
2301
- };
2302
- const cp2 = {
2303
- x: p2.x - (p3.x - p1.x) / 6,
2304
- y: p2.y - (p3.y - p1.y) / 6
2305
- };
2306
- segments.push({ start: p1, cp1, cp2, end: p2 });
2307
- }
2308
- return segments;
2309
- }
2310
-
2311
- // src/elements/stroke-cache.ts
2312
- var cache = /* @__PURE__ */ new WeakMap();
2313
- function computeStrokeSegments(stroke) {
2314
- const segments = smoothToSegments(stroke.points);
2315
- const widths = [];
2316
- for (const seg of segments) {
2317
- const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
2318
- widths.push(w);
2319
- }
2320
- const data = { segments, widths };
2321
- cache.set(stroke, data);
2322
- return data;
2323
- }
2324
- function getStrokeRenderData(stroke) {
2325
- const cached = cache.get(stroke);
2326
- if (cached) return cached;
2327
- return computeStrokeSegments(stroke);
2328
- }
2329
-
2330
2386
  // src/elements/grid-renderer.ts
2331
2387
  function getSquareGridLines(bounds, cellSize) {
2332
2388
  if (cellSize <= 0) return { verticals: [], horizontals: [] };
@@ -2724,6 +2780,7 @@ var ElementRenderer = class {
2724
2780
  canvasSize = null;
2725
2781
  hexTileCache = null;
2726
2782
  hexTileCacheKey = "";
2783
+ gridBoundsOverride = null;
2727
2784
  setStore(store) {
2728
2785
  this.store = store;
2729
2786
  }
@@ -2739,6 +2796,9 @@ var ElementRenderer = class {
2739
2796
  setCanvasSize(w, h) {
2740
2797
  this.canvasSize = { w, h };
2741
2798
  }
2799
+ setGridBoundsOverride(bounds) {
2800
+ this.gridBoundsOverride = bounds;
2801
+ }
2742
2802
  isDomElement(element) {
2743
2803
  return DOM_ELEMENT_TYPES.has(element.type);
2744
2804
  }
@@ -2772,21 +2832,29 @@ var ElementRenderer = class {
2772
2832
  ctx.lineCap = "round";
2773
2833
  ctx.lineJoin = "round";
2774
2834
  ctx.globalAlpha = stroke.opacity;
2775
- const { segments, widths } = getStrokeRenderData(stroke);
2776
- for (let i = 0; i < segments.length; i++) {
2777
- const seg = segments[i];
2778
- const w = widths[i];
2779
- if (!seg || w === void 0) continue;
2780
- ctx.lineWidth = w;
2781
- ctx.beginPath();
2782
- ctx.moveTo(seg.start.x, seg.start.y);
2783
- ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
2784
- ctx.stroke();
2835
+ const data = getStrokeRenderData(stroke);
2836
+ if (data.buckets) {
2837
+ for (const bucket of data.buckets) {
2838
+ ctx.lineWidth = bucket.width;
2839
+ ctx.stroke(bucket.path);
2840
+ }
2841
+ } else {
2842
+ for (let i = 0; i < data.segments.length; i++) {
2843
+ const seg = data.segments[i];
2844
+ const w = data.widths[i];
2845
+ if (!seg || w === void 0) continue;
2846
+ ctx.lineWidth = w;
2847
+ ctx.beginPath();
2848
+ ctx.moveTo(seg.start.x, seg.start.y);
2849
+ ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
2850
+ ctx.stroke();
2851
+ }
2785
2852
  }
2786
2853
  ctx.restore();
2787
2854
  }
2788
2855
  renderArrow(ctx, arrow) {
2789
- const { visualFrom, visualTo } = this.getVisualEndpoints(arrow);
2856
+ const geometry = getArrowRenderGeometry(arrow);
2857
+ const { visualFrom, visualTo } = this.getVisualEndpoints(arrow, geometry);
2790
2858
  ctx.save();
2791
2859
  ctx.strokeStyle = arrow.color;
2792
2860
  ctx.lineWidth = arrow.width;
@@ -2797,17 +2865,18 @@ var ElementRenderer = class {
2797
2865
  ctx.beginPath();
2798
2866
  ctx.moveTo(visualFrom.x, visualFrom.y);
2799
2867
  if (arrow.bend !== 0) {
2800
- const cp = arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
2801
- ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
2868
+ const cp = geometry.controlPoint;
2869
+ if (cp) {
2870
+ ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
2871
+ }
2802
2872
  } else {
2803
2873
  ctx.lineTo(visualTo.x, visualTo.y);
2804
2874
  }
2805
2875
  ctx.stroke();
2806
- this.renderArrowhead(ctx, arrow, visualTo);
2876
+ this.renderArrowhead(ctx, arrow, visualTo, geometry.tangentEnd);
2807
2877
  ctx.restore();
2808
2878
  }
2809
- renderArrowhead(ctx, arrow, tip) {
2810
- const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
2879
+ renderArrowhead(ctx, arrow, tip, angle) {
2811
2880
  ctx.beginPath();
2812
2881
  ctx.moveTo(tip.x, tip.y);
2813
2882
  ctx.lineTo(
@@ -2822,7 +2891,7 @@ var ElementRenderer = class {
2822
2891
  ctx.fillStyle = arrow.color;
2823
2892
  ctx.fill();
2824
2893
  }
2825
- getVisualEndpoints(arrow) {
2894
+ getVisualEndpoints(arrow, geometry) {
2826
2895
  let visualFrom = arrow.from;
2827
2896
  let visualTo = arrow.to;
2828
2897
  if (!this.store) return { visualFrom, visualTo };
@@ -2831,7 +2900,7 @@ var ElementRenderer = class {
2831
2900
  if (el) {
2832
2901
  const bounds = getElementBounds(el);
2833
2902
  if (bounds) {
2834
- const tangentAngle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
2903
+ const tangentAngle = geometry.tangentStart;
2835
2904
  const rayTarget = {
2836
2905
  x: arrow.from.x + Math.cos(tangentAngle) * 1e3,
2837
2906
  y: arrow.from.y + Math.sin(tangentAngle) * 1e3
@@ -2845,7 +2914,7 @@ var ElementRenderer = class {
2845
2914
  if (el) {
2846
2915
  const bounds = getElementBounds(el);
2847
2916
  if (bounds) {
2848
- const tangentAngle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
2917
+ const tangentAngle = geometry.tangentEnd;
2849
2918
  const rayTarget = {
2850
2919
  x: arrow.to.x - Math.cos(tangentAngle) * 1e3,
2851
2920
  y: arrow.to.y - Math.sin(tangentAngle) * 1e3
@@ -2900,20 +2969,20 @@ var ElementRenderer = class {
2900
2969
  }
2901
2970
  }
2902
2971
  renderGrid(ctx, grid) {
2903
- if (!this.canvasSize) return;
2972
+ const canvasSize = this.canvasSize;
2973
+ if (!canvasSize) return;
2904
2974
  const cam = this.camera;
2905
2975
  if (!cam) return;
2906
- const topLeft = cam.screenToWorld({ x: 0, y: 0 });
2907
- const bottomRight = cam.screenToWorld({
2908
- x: this.canvasSize.w,
2909
- y: this.canvasSize.h
2910
- });
2911
- const bounds = {
2912
- minX: topLeft.x,
2913
- minY: topLeft.y,
2914
- maxX: bottomRight.x,
2915
- maxY: bottomRight.y
2916
- };
2976
+ const bounds = this.gridBoundsOverride ?? (() => {
2977
+ const topLeft = cam.screenToWorld({ x: 0, y: 0 });
2978
+ const bottomRight = cam.screenToWorld({ x: canvasSize.w, y: canvasSize.h });
2979
+ return {
2980
+ minX: topLeft.x,
2981
+ minY: topLeft.y,
2982
+ maxX: bottomRight.x,
2983
+ maxY: bottomRight.y
2984
+ };
2985
+ })();
2917
2986
  if (grid.gridType === "hex") {
2918
2987
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
2919
2988
  const scale = cam.zoom * dpr;
@@ -4209,19 +4278,19 @@ function loadImages(elements) {
4209
4278
  const imageElements = elements.filter(
4210
4279
  (el) => el.type === "image" && "src" in el
4211
4280
  );
4212
- const cache2 = /* @__PURE__ */ new Map();
4213
- if (imageElements.length === 0) return Promise.resolve(cache2);
4281
+ const cache3 = /* @__PURE__ */ new Map();
4282
+ if (imageElements.length === 0) return Promise.resolve(cache3);
4214
4283
  return new Promise((resolve) => {
4215
4284
  let remaining = imageElements.length;
4216
4285
  const done = () => {
4217
4286
  remaining--;
4218
- if (remaining <= 0) resolve(cache2);
4287
+ if (remaining <= 0) resolve(cache3);
4219
4288
  };
4220
4289
  for (const el of imageElements) {
4221
4290
  const img = new Image();
4222
4291
  img.crossOrigin = "anonymous";
4223
4292
  img.onload = () => {
4224
- cache2.set(el.id, img);
4293
+ cache3.set(el.id, img);
4225
4294
  done();
4226
4295
  };
4227
4296
  img.onerror = done;
@@ -4702,18 +4771,39 @@ var RenderStats = class {
4702
4771
  frameTimes = [];
4703
4772
  frameCount = 0;
4704
4773
  _lastGridMs = 0;
4705
- recordFrame(durationMs, gridMs) {
4774
+ _lastLayersMs = 0;
4775
+ _lastBackgroundMs = 0;
4776
+ _lastCompositeMs = 0;
4777
+ _lastOverlayMs = 0;
4778
+ recordFrame(durationMs, breakdown) {
4706
4779
  this.frameCount++;
4707
4780
  this.frameTimes.push(durationMs);
4708
4781
  if (this.frameTimes.length > SAMPLE_SIZE) {
4709
4782
  this.frameTimes.shift();
4710
4783
  }
4711
- if (gridMs !== void 0) this._lastGridMs = gridMs;
4784
+ if (breakdown !== void 0) {
4785
+ if (breakdown.gridMs !== void 0) this._lastGridMs = breakdown.gridMs;
4786
+ if (breakdown.layersMs !== void 0) this._lastLayersMs = breakdown.layersMs;
4787
+ if (breakdown.backgroundMs !== void 0) this._lastBackgroundMs = breakdown.backgroundMs;
4788
+ if (breakdown.compositeMs !== void 0) this._lastCompositeMs = breakdown.compositeMs;
4789
+ if (breakdown.overlayMs !== void 0) this._lastOverlayMs = breakdown.overlayMs;
4790
+ }
4712
4791
  }
4713
4792
  getSnapshot() {
4714
4793
  const times = this.frameTimes;
4715
4794
  if (times.length === 0) {
4716
- return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, lastGridMs: 0, frameCount: 0 };
4795
+ return {
4796
+ fps: 0,
4797
+ avgFrameMs: 0,
4798
+ p95FrameMs: 0,
4799
+ lastFrameMs: 0,
4800
+ lastGridMs: 0,
4801
+ layersMs: 0,
4802
+ backgroundMs: 0,
4803
+ compositeMs: 0,
4804
+ overlayMs: 0,
4805
+ frameCount: 0
4806
+ };
4717
4807
  }
4718
4808
  const avg = times.reduce((a, b) => a + b, 0) / times.length;
4719
4809
  const sorted = [...times].sort((a, b) => a - b);
@@ -4725,6 +4815,10 @@ var RenderStats = class {
4725
4815
  p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
4726
4816
  lastFrameMs: Math.round(lastFrame * 100) / 100,
4727
4817
  lastGridMs: Math.round(this._lastGridMs * 100) / 100,
4818
+ layersMs: Math.round(this._lastLayersMs * 100) / 100,
4819
+ backgroundMs: Math.round(this._lastBackgroundMs * 100) / 100,
4820
+ compositeMs: Math.round(this._lastCompositeMs * 100) / 100,
4821
+ overlayMs: Math.round(this._lastOverlayMs * 100) / 100,
4728
4822
  frameCount: this.frameCount
4729
4823
  };
4730
4824
  }
@@ -4747,19 +4841,14 @@ var RenderLoop = class {
4747
4841
  layerManager;
4748
4842
  domNodeManager;
4749
4843
  layerCache;
4844
+ marginViewport;
4750
4845
  activeDrawingLayerId = null;
4751
- lastZoom;
4752
- lastCamX;
4753
- lastCamY;
4846
+ gridCacheDirty = true;
4847
+ // set on recenter/viewport-change; consumed by the grid block
4754
4848
  stats = new RenderStats();
4755
- lastGridMs = 0;
4849
+ layerGroups = /* @__PURE__ */ new Map();
4756
4850
  gridCacheCanvas = null;
4757
4851
  gridCacheCtx = null;
4758
- gridCacheZoom = -1;
4759
- gridCacheCamX = -Infinity;
4760
- gridCacheCamY = -Infinity;
4761
- gridCacheWidth = 0;
4762
- gridCacheHeight = 0;
4763
4852
  lastGridRef = null;
4764
4853
  constructor(deps) {
4765
4854
  this.canvasEl = deps.canvasEl;
@@ -4771,9 +4860,7 @@ var RenderLoop = class {
4771
4860
  this.layerManager = deps.layerManager;
4772
4861
  this.domNodeManager = deps.domNodeManager;
4773
4862
  this.layerCache = deps.layerCache;
4774
- this.lastZoom = deps.camera.zoom;
4775
- this.lastCamX = deps.camera.position.x;
4776
- this.lastCamY = deps.camera.position.y;
4863
+ this.marginViewport = deps.marginViewport;
4777
4864
  }
4778
4865
  requestRender() {
4779
4866
  this.needsRender = true;
@@ -4800,7 +4887,9 @@ var RenderLoop = class {
4800
4887
  setCanvasSize(width, height) {
4801
4888
  this.canvasEl.width = width;
4802
4889
  this.canvasEl.height = height;
4803
- this.layerCache.resize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
4890
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
4891
+ this.marginViewport.setViewport(width / dpr, height / dpr, dpr);
4892
+ this.layerCache.resize();
4804
4893
  }
4805
4894
  setActiveDrawingLayer(layerId) {
4806
4895
  this.activeDrawingLayerId = layerId;
@@ -4814,30 +4903,29 @@ var RenderLoop = class {
4814
4903
  getStats() {
4815
4904
  return this.stats.getSnapshot();
4816
4905
  }
4817
- compositeLayerCache(ctx, layerId, dpr) {
4906
+ compositeLayerCache(ctx, layerId) {
4818
4907
  const cached = this.layerCache.getCanvas(layerId);
4908
+ const offset = this.marginViewport.compositeOffset(
4909
+ this.camera.position.x,
4910
+ this.camera.position.y
4911
+ );
4819
4912
  ctx.save();
4820
- ctx.scale(1 / this.camera.zoom, 1 / this.camera.zoom);
4821
- ctx.translate(-this.camera.position.x, -this.camera.position.y);
4822
- ctx.scale(1 / dpr, 1 / dpr);
4823
- ctx.drawImage(cached, 0, 0);
4913
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
4914
+ ctx.drawImage(cached, offset.x, offset.y);
4824
4915
  ctx.restore();
4825
4916
  }
4826
- ensureGridCache(cssWidth, cssHeight, dpr) {
4827
- if (this.gridCacheCanvas !== null && this.gridCacheWidth === cssWidth && this.gridCacheHeight === cssHeight) {
4917
+ ensureGridCache() {
4918
+ const w = this.marginViewport.physicalWidth();
4919
+ const h = this.marginViewport.physicalHeight();
4920
+ if (this.gridCacheCanvas !== null && this.gridCacheCanvas.width === w && this.gridCacheCanvas.height === h) {
4828
4921
  return;
4829
4922
  }
4830
- const physWidth = Math.round(cssWidth * dpr);
4831
- const physHeight = Math.round(cssHeight * dpr);
4832
4923
  if (typeof OffscreenCanvas !== "undefined") {
4833
- this.gridCacheCanvas = new OffscreenCanvas(
4834
- physWidth,
4835
- physHeight
4836
- );
4924
+ this.gridCacheCanvas = new OffscreenCanvas(w, h);
4837
4925
  } else if (typeof document !== "undefined") {
4838
4926
  const el = document.createElement("canvas");
4839
- el.width = physWidth;
4840
- el.height = physHeight;
4927
+ el.width = w;
4928
+ el.height = h;
4841
4929
  this.gridCacheCanvas = el;
4842
4930
  } else {
4843
4931
  this.gridCacheCanvas = null;
@@ -4850,22 +4938,26 @@ var RenderLoop = class {
4850
4938
  const t0 = performance.now();
4851
4939
  const ctx = this.canvasEl.getContext("2d");
4852
4940
  if (!ctx) return;
4941
+ let layersMs = 0;
4942
+ let compositeMs = 0;
4943
+ let gridMs = 0;
4853
4944
  const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
4854
4945
  const cssWidth = this.canvasEl.clientWidth;
4855
4946
  const cssHeight = this.canvasEl.clientHeight;
4947
+ this.marginViewport.setViewport(cssWidth, cssHeight, dpr);
4856
4948
  const currentZoom = this.camera.zoom;
4857
4949
  const currentCamX = this.camera.position.x;
4858
4950
  const currentCamY = this.camera.position.y;
4859
- if (currentZoom !== this.lastZoom || currentCamX !== this.lastCamX || currentCamY !== this.lastCamY) {
4951
+ if (this.marginViewport.needsRecenter(currentCamX, currentCamY, currentZoom)) {
4952
+ this.marginViewport.recenter(currentCamX, currentCamY, currentZoom);
4860
4953
  this.layerCache.markAllDirty();
4861
- this.lastZoom = currentZoom;
4862
- this.lastCamX = currentCamX;
4863
- this.lastCamY = currentCamY;
4954
+ this.gridCacheDirty = true;
4864
4955
  }
4865
4956
  ctx.save();
4866
4957
  ctx.scale(dpr, dpr);
4867
4958
  this.renderer.setCanvasSize(cssWidth, cssHeight);
4868
4959
  const hasGridElement = this.store.getElementsByType("grid").length > 0;
4960
+ const bgT0 = performance.now();
4869
4961
  if (hasGridElement) {
4870
4962
  ctx.save();
4871
4963
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
@@ -4874,19 +4966,20 @@ var RenderLoop = class {
4874
4966
  } else {
4875
4967
  this.background.render(ctx, this.camera);
4876
4968
  }
4969
+ const backgroundMs = performance.now() - bgT0;
4877
4970
  ctx.save();
4878
4971
  ctx.translate(this.camera.position.x, this.camera.position.y);
4879
4972
  ctx.scale(this.camera.zoom, this.camera.zoom);
4880
- const visibleRect = this.camera.getVisibleRect(cssWidth, cssHeight);
4881
- const margin = Math.max(visibleRect.w, visibleRect.h) * 0.1;
4973
+ const cullBounds = this.marginViewport.cachedWorldBounds();
4974
+ const cullPad = Math.max(cullBounds.w, cullBounds.h) * 0.05;
4882
4975
  const cullingRect = {
4883
- x: visibleRect.x - margin,
4884
- y: visibleRect.y - margin,
4885
- w: visibleRect.w + margin * 2,
4886
- h: visibleRect.h + margin * 2
4976
+ x: cullBounds.x - cullPad,
4977
+ y: cullBounds.y - cullPad,
4978
+ w: cullBounds.w + cullPad * 2,
4979
+ h: cullBounds.h + cullPad * 2
4887
4980
  };
4888
4981
  const allElements = this.store.getAll();
4889
- const layerElements = /* @__PURE__ */ new Map();
4982
+ this.layerGroups.clear();
4890
4983
  const gridElements = [];
4891
4984
  let domZIndex = 0;
4892
4985
  for (const element of allElements) {
@@ -4909,31 +5002,34 @@ var RenderLoop = class {
4909
5002
  gridElements.push(element);
4910
5003
  continue;
4911
5004
  }
4912
- let group = layerElements.get(element.layerId);
5005
+ let group = this.layerGroups.get(element.layerId);
4913
5006
  if (!group) {
4914
5007
  group = [];
4915
- layerElements.set(element.layerId, group);
5008
+ this.layerGroups.set(element.layerId, group);
4916
5009
  }
4917
5010
  group.push(element);
4918
5011
  }
4919
- for (const [layerId, elements] of layerElements) {
5012
+ for (const [layerId, elements] of this.layerGroups) {
4920
5013
  const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
4921
5014
  if (!this.layerCache.isDirty(layerId)) {
4922
- this.compositeLayerCache(ctx, layerId, dpr);
5015
+ const compT0 = performance.now();
5016
+ this.compositeLayerCache(ctx, layerId);
5017
+ compositeMs += performance.now() - compT0;
4923
5018
  continue;
4924
5019
  }
4925
5020
  if (isActiveDrawingLayer) {
4926
- this.compositeLayerCache(ctx, layerId, dpr);
5021
+ const compT0 = performance.now();
5022
+ this.compositeLayerCache(ctx, layerId);
5023
+ compositeMs += performance.now() - compT0;
4927
5024
  continue;
4928
5025
  }
4929
5026
  const offCtx = this.layerCache.getContext(layerId);
4930
5027
  if (offCtx) {
5028
+ const layerT0 = performance.now();
4931
5029
  const offCanvas = this.layerCache.getCanvas(layerId);
4932
5030
  offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
4933
5031
  offCtx.save();
4934
- offCtx.scale(dpr, dpr);
4935
- offCtx.translate(this.camera.position.x, this.camera.position.y);
4936
- offCtx.scale(this.camera.zoom, this.camera.zoom);
5032
+ this.marginViewport.applyRenderTransform(offCtx);
4937
5033
  for (const element of elements) {
4938
5034
  const elBounds = getElementBounds(element);
4939
5035
  if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
@@ -4941,56 +5037,73 @@ var RenderLoop = class {
4941
5037
  }
4942
5038
  offCtx.restore();
4943
5039
  this.layerCache.markClean(layerId);
4944
- this.compositeLayerCache(ctx, layerId, dpr);
5040
+ layersMs += performance.now() - layerT0;
5041
+ const compT0 = performance.now();
5042
+ this.compositeLayerCache(ctx, layerId);
5043
+ compositeMs += performance.now() - compT0;
4945
5044
  }
4946
5045
  }
4947
5046
  if (gridElements.length > 0) {
4948
5047
  const gridT0 = performance.now();
4949
5048
  const gridRef = gridElements[0];
4950
- const gridCacheHit = this.gridCacheCanvas !== null && currentZoom === this.gridCacheZoom && currentCamX === this.gridCacheCamX && currentCamY === this.gridCacheCamY && cssWidth === this.gridCacheWidth && cssHeight === this.gridCacheHeight && gridRef === this.lastGridRef;
4951
- if (gridCacheHit) {
4952
- ctx.save();
4953
- ctx.setTransform(1, 0, 0, 1, 0, 0);
4954
- ctx.drawImage(this.gridCacheCanvas, 0, 0);
4955
- ctx.restore();
4956
- } else {
4957
- this.ensureGridCache(cssWidth, cssHeight, dpr);
5049
+ const gridDirty = this.gridCacheDirty || gridRef !== this.lastGridRef;
5050
+ if (gridDirty) {
5051
+ this.ensureGridCache();
4958
5052
  if (this.gridCacheCtx && this.gridCacheCanvas) {
5053
+ const cb = this.marginViewport.cachedWorldBounds();
5054
+ this.renderer.setGridBoundsOverride({
5055
+ minX: cb.x,
5056
+ minY: cb.y,
5057
+ maxX: cb.x + cb.w,
5058
+ maxY: cb.y + cb.h
5059
+ });
4959
5060
  const gc = this.gridCacheCtx;
4960
5061
  gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
4961
5062
  gc.save();
4962
- gc.scale(dpr, dpr);
4963
- gc.translate(currentCamX, currentCamY);
4964
- gc.scale(currentZoom, currentZoom);
4965
- for (const grid of gridElements) {
4966
- this.renderer.renderCanvasElement(gc, grid);
4967
- }
4968
- gc.restore();
4969
- ctx.save();
4970
- ctx.setTransform(1, 0, 0, 1, 0, 0);
4971
- ctx.drawImage(this.gridCacheCanvas, 0, 0);
4972
- ctx.restore();
4973
- } else {
4974
- for (const grid of gridElements) {
4975
- this.renderer.renderCanvasElement(ctx, grid);
5063
+ this.marginViewport.applyRenderTransform(gc);
5064
+ try {
5065
+ for (const grid of gridElements) {
5066
+ this.renderer.renderCanvasElement(gc, grid);
5067
+ }
5068
+ } finally {
5069
+ gc.restore();
5070
+ this.renderer.setGridBoundsOverride(null);
4976
5071
  }
4977
5072
  }
4978
- this.gridCacheZoom = currentZoom;
4979
- this.gridCacheCamX = currentCamX;
4980
- this.gridCacheCamY = currentCamY;
4981
- this.gridCacheWidth = cssWidth;
4982
- this.gridCacheHeight = cssHeight;
5073
+ this.gridCacheDirty = false;
4983
5074
  this.lastGridRef = gridRef;
4984
5075
  }
4985
- this.lastGridMs = performance.now() - gridT0;
5076
+ if (this.gridCacheCanvas) {
5077
+ const offset = this.marginViewport.compositeOffset(
5078
+ this.camera.position.x,
5079
+ this.camera.position.y
5080
+ );
5081
+ ctx.save();
5082
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
5083
+ ctx.drawImage(this.gridCacheCanvas, offset.x, offset.y);
5084
+ ctx.restore();
5085
+ } else {
5086
+ for (const grid of gridElements) {
5087
+ this.renderer.renderCanvasElement(ctx, grid);
5088
+ }
5089
+ }
5090
+ gridMs = performance.now() - gridT0;
4986
5091
  }
5092
+ const overlayT0 = performance.now();
4987
5093
  const activeTool = this.toolManager.activeTool;
4988
5094
  if (activeTool?.renderOverlay) {
4989
5095
  activeTool.renderOverlay(ctx);
4990
5096
  }
5097
+ const overlayMs = performance.now() - overlayT0;
4991
5098
  ctx.restore();
4992
5099
  ctx.restore();
4993
- this.stats.recordFrame(performance.now() - t0, this.lastGridMs);
5100
+ this.stats.recordFrame(performance.now() - t0, {
5101
+ gridMs,
5102
+ layersMs,
5103
+ backgroundMs,
5104
+ compositeMs,
5105
+ overlayMs
5106
+ });
4994
5107
  }
4995
5108
  };
4996
5109
 
@@ -5005,15 +5118,11 @@ function createOffscreenCanvas(width, height) {
5005
5118
  return canvas;
5006
5119
  }
5007
5120
  var LayerCache = class {
5121
+ constructor(viewport) {
5122
+ this.viewport = viewport;
5123
+ }
5008
5124
  canvases = /* @__PURE__ */ new Map();
5009
5125
  dirtyFlags = /* @__PURE__ */ new Map();
5010
- width;
5011
- height;
5012
- constructor(width, height) {
5013
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
5014
- this.width = Math.round(width * dpr);
5015
- this.height = Math.round(height * dpr);
5016
- }
5017
5126
  isDirty(layerId) {
5018
5127
  return this.dirtyFlags.get(layerId) !== false;
5019
5128
  }
@@ -5031,7 +5140,7 @@ var LayerCache = class {
5031
5140
  getCanvas(layerId) {
5032
5141
  let canvas = this.canvases.get(layerId);
5033
5142
  if (!canvas) {
5034
- canvas = createOffscreenCanvas(this.width, this.height);
5143
+ canvas = createOffscreenCanvas(this.viewport.physicalWidth(), this.viewport.physicalHeight());
5035
5144
  this.canvases.set(layerId, canvas);
5036
5145
  this.dirtyFlags.set(layerId, true);
5037
5146
  }
@@ -5041,13 +5150,12 @@ var LayerCache = class {
5041
5150
  const canvas = this.getCanvas(layerId);
5042
5151
  return canvas.getContext("2d");
5043
5152
  }
5044
- resize(width, height) {
5045
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
5046
- this.width = Math.round(width * dpr);
5047
- this.height = Math.round(height * dpr);
5153
+ resize() {
5154
+ const w = this.viewport.physicalWidth();
5155
+ const h = this.viewport.physicalHeight();
5048
5156
  for (const [id, canvas] of this.canvases) {
5049
- canvas.width = this.width;
5050
- canvas.height = this.height;
5157
+ canvas.width = w;
5158
+ canvas.height = h;
5051
5159
  this.dirtyFlags.set(id, true);
5052
5160
  }
5053
5161
  }
@@ -5057,6 +5165,75 @@ var LayerCache = class {
5057
5165
  }
5058
5166
  };
5059
5167
 
5168
+ // src/canvas/margin-viewport.ts
5169
+ var MarginViewport = class {
5170
+ constructor(marginPx) {
5171
+ this.marginPx = marginPx;
5172
+ }
5173
+ cssW = 0;
5174
+ cssH = 0;
5175
+ dpr = 1;
5176
+ anchorCamX = 0;
5177
+ anchorCamY = 0;
5178
+ anchorZoom = Number.NaN;
5179
+ // sentinel → first needsRecenter is true
5180
+ viewportDirty = true;
5181
+ setMargin(marginPx) {
5182
+ if (marginPx !== this.marginPx) {
5183
+ this.marginPx = marginPx;
5184
+ this.viewportDirty = true;
5185
+ }
5186
+ }
5187
+ setViewport(cssW, cssH, dpr) {
5188
+ if (cssW !== this.cssW || cssH !== this.cssH || dpr !== this.dpr) {
5189
+ this.cssW = cssW;
5190
+ this.cssH = cssH;
5191
+ this.dpr = dpr;
5192
+ this.viewportDirty = true;
5193
+ }
5194
+ }
5195
+ physicalWidth() {
5196
+ return Math.round((this.cssW + 2 * this.marginPx) * this.dpr);
5197
+ }
5198
+ physicalHeight() {
5199
+ return Math.round((this.cssH + 2 * this.marginPx) * this.dpr);
5200
+ }
5201
+ needsRecenter(camX, camY, zoom) {
5202
+ return this.viewportDirty || zoom !== this.anchorZoom || Math.abs(camX - this.anchorCamX) > this.marginPx || Math.abs(camY - this.anchorCamY) > this.marginPx;
5203
+ }
5204
+ recenter(camX, camY, zoom) {
5205
+ this.anchorCamX = camX;
5206
+ this.anchorCamY = camY;
5207
+ this.anchorZoom = zoom;
5208
+ this.viewportDirty = false;
5209
+ }
5210
+ /** Applies dpr scale + anchor-relative world transform. setViewport must have been called first. */
5211
+ applyRenderTransform(ctx) {
5212
+ ctx.scale(this.dpr, this.dpr);
5213
+ ctx.translate(this.marginPx + this.anchorCamX, this.marginPx + this.anchorCamY);
5214
+ ctx.scale(this.anchorZoom, this.anchorZoom);
5215
+ }
5216
+ // Device-px destination for drawImage(cache, x, y).
5217
+ // A world point P sits in the cache at CSS x `margin + anchorCamX + P*zoom`; it must land on
5218
+ // screen at `camX + P*zoom`; so the blit offset is `camX - anchorCamX - margin` (CSS) * dpr.
5219
+ compositeOffset(camX, camY) {
5220
+ return {
5221
+ x: (camX - this.anchorCamX - this.marginPx) * this.dpr,
5222
+ y: (camY - this.anchorCamY - this.marginPx) * this.dpr
5223
+ };
5224
+ }
5225
+ // World-space bounds of the whole cached region at the anchor (cull rect for re-renders).
5226
+ cachedWorldBounds() {
5227
+ const z = this.anchorZoom;
5228
+ return {
5229
+ x: (-this.marginPx - this.anchorCamX) / z,
5230
+ y: (-this.marginPx - this.anchorCamY) / z,
5231
+ w: (this.cssW + 2 * this.marginPx) / z,
5232
+ h: (this.cssH + 2 * this.marginPx) / z
5233
+ };
5234
+ }
5235
+ };
5236
+
5060
5237
  // src/canvas/viewport.ts
5061
5238
  var Viewport = class {
5062
5239
  constructor(container, options = {}) {
@@ -5133,10 +5310,13 @@ var Viewport = class {
5133
5310
  this.interactMode = new InteractMode({
5134
5311
  getNode: (id) => this.domNodeManager.getNode(id)
5135
5312
  });
5136
- const layerCache = new LayerCache(
5313
+ this.marginViewport = new MarginViewport(options.panBufferMargin ?? 256);
5314
+ this.marginViewport.setViewport(
5137
5315
  this.canvasEl.clientWidth || 800,
5138
- this.canvasEl.clientHeight || 600
5316
+ this.canvasEl.clientHeight || 600,
5317
+ typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1
5139
5318
  );
5319
+ const layerCache = new LayerCache(this.marginViewport);
5140
5320
  this.renderLoop = new RenderLoop({
5141
5321
  canvasEl: this.canvasEl,
5142
5322
  camera: this.camera,
@@ -5146,7 +5326,8 @@ var Viewport = class {
5146
5326
  toolManager: this.toolManager,
5147
5327
  layerManager: this.layerManager,
5148
5328
  domNodeManager: this.domNodeManager,
5149
- layerCache
5329
+ layerCache,
5330
+ marginViewport: this.marginViewport
5150
5331
  });
5151
5332
  this.unsubCamera = this.camera.onChange(() => {
5152
5333
  this.applyCameraTransform();
@@ -5210,6 +5391,7 @@ var Viewport = class {
5210
5391
  noteEditor;
5211
5392
  historyRecorder;
5212
5393
  toolContext;
5394
+ marginViewport;
5213
5395
  resizeObserver = null;
5214
5396
  _snapToGrid = false;
5215
5397
  _gridSize;
@@ -5402,7 +5584,7 @@ var Viewport = class {
5402
5584
  const id = setInterval(() => {
5403
5585
  const s = this.getRenderStats();
5404
5586
  console.log(
5405
- `[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
5587
+ `[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms layers=${s.layersMs}ms comp=${s.compositeMs}ms bg=${s.backgroundMs}ms overlay=${s.overlayMs}ms`
5406
5588
  );
5407
5589
  }, intervalMs);
5408
5590
  return () => clearInterval(id);
@@ -7400,7 +7582,7 @@ var TemplateTool = class {
7400
7582
  };
7401
7583
 
7402
7584
  // src/index.ts
7403
- var VERSION = "0.21.0";
7585
+ var VERSION = "0.23.0";
7404
7586
  // Annotate the CommonJS export names for ESM import in node:
7405
7587
  0 && (module.exports = {
7406
7588
  AddElementCommand,