@canvas-harness/core 0.1.4 → 0.1.6

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
@@ -1050,7 +1050,6 @@ var COMPOSITE = /* @__PURE__ */ new Set([
1050
1050
  var isCompositePrimitive = (type) => COMPOSITE.has(type);
1051
1051
  var isDrawablePrimitive = (type) => ATOMIC.has(type) || COMPOSITE.has(type);
1052
1052
  var PLAIN_RECT_CORNER_THRESHOLD_PX = 1.5;
1053
- var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
1054
1053
  var LAYERED_OFFSET = 12;
1055
1054
  var drawShape = (ctx, node, scale, theme, opts) => {
1056
1055
  if (!isDrawablePrimitive(node.type)) return;
@@ -1069,7 +1068,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
1069
1068
  const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
1070
1069
  const stroke = resolveColor(style, "strokeColor", DEFAULT_STYLE.strokeColor, theme);
1071
1070
  const fillVisible = !isFullyTransparent(fill);
1072
- const strokeVisible = strokeWidth > 0 && strokeWidth * scale >= STROKE_VISIBILITY_THRESHOLD_PX && !isFullyTransparent(stroke);
1071
+ const strokeVisible = strokeWidth > 0 && !isFullyTransparent(stroke);
1073
1072
  if (!fillVisible && !strokeVisible) return;
1074
1073
  const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
1075
1074
  switch (type) {
@@ -1106,7 +1105,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
1106
1105
  }
1107
1106
  if (strokeVisible && !opts?.skipStroke) {
1108
1107
  ctx.strokeStyle = stroke;
1109
- ctx.lineWidth = strokeWidth;
1108
+ ctx.lineWidth = Math.max(strokeWidth, 1 / scale);
1110
1109
  ctx.setLineDash(dashPatternFor(style?.strokeStyle, strokeWidth));
1111
1110
  ctx.stroke();
1112
1111
  }
@@ -1482,13 +1481,16 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
1482
1481
  const isDark = theme?.("mode") === "dark";
1483
1482
  const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
1484
1483
  const strokeColor = deriveRoughStrokeColor(rawStroke, fill, isDark);
1485
- const strokeWidth = resolveStrokeWidth(style, theme);
1486
- if (strokeWidth <= 0) return;
1484
+ const rawStrokeWidth = resolveStrokeWidth(style, theme);
1485
+ if (rawStrokeWidth <= 0) return;
1487
1486
  const roughness = style?.roughness ?? 0;
1488
1487
  if (roughness <= 0) return;
1488
+ const isNoBorderIntent = isFullyTransparent(rawStroke);
1489
+ const effectiveStrokeStyle = isNoBorderIntent ? "solid" : style?.strokeStyle ?? "solid";
1490
+ const strokeWidth = isNoBorderIntent ? DEFAULT_STYLE.strokeWidth : rawStrokeWidth;
1489
1491
  const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
1490
1492
  const radius = Math.max(0, Math.min(cornerRadius, w / 2, h / 2));
1491
- const dash = dashPatternFor(style?.strokeStyle, strokeWidth);
1493
+ const dash = dashPatternFor(effectiveStrokeStyle, strokeWidth);
1492
1494
  const detail = apparentDetail(Math.max(w, h), scale);
1493
1495
  const cacheKey = [
1494
1496
  type,
@@ -1497,7 +1499,7 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
1497
1499
  radius.toFixed(1),
1498
1500
  strokeColor,
1499
1501
  strokeWidth.toFixed(2),
1500
- style?.strokeStyle ?? "solid",
1502
+ effectiveStrokeStyle,
1501
1503
  roughness.toFixed(2),
1502
1504
  seed,
1503
1505
  detail.curveStepCount,
@@ -2648,7 +2650,7 @@ var DEFAULT_EDGE_STYLE = {
2648
2650
  sourceArrowhead: "none",
2649
2651
  targetArrowhead: "arrow-filled"
2650
2652
  };
2651
- var STROKE_VISIBILITY_THRESHOLD_PX2 = 0.5;
2653
+ var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
2652
2654
  var ARROWHEAD_VISIBILITY_THRESHOLD_PX = 2;
2653
2655
  var samplePaintStride = (scale) => {
2654
2656
  if (scale < 0.15) return 8;
@@ -2662,7 +2664,7 @@ var drawEdge = (ctx, edge, geom, sourceNode, targetNode, scale, theme, opts) =>
2662
2664
  if (samples.length < 2) return;
2663
2665
  const style = edge.style;
2664
2666
  const strokeWidth = typeof style?.strokeWidth === "number" ? style.strokeWidth : theme?.("strokeWidth") ?? DEFAULT_EDGE_STYLE.strokeWidth;
2665
- if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX2) return;
2667
+ if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX) return;
2666
2668
  const strokeColor = typeof style?.strokeColor === "string" ? style.strokeColor : theme?.("edge.strokeColor") ?? DEFAULT_EDGE_STYLE.strokeColor;
2667
2669
  const sourceArrowhead = style?.sourceArrowhead ?? DEFAULT_EDGE_STYLE.sourceArrowhead;
2668
2670
  const targetArrowhead = style?.targetArrowhead ?? DEFAULT_EDGE_STYLE.targetArrowhead;
@@ -3378,6 +3380,8 @@ var idleInteractionState = () => ({
3378
3380
  resizeHandle: null,
3379
3381
  resizeLockAspect: false,
3380
3382
  resizeFromCenter: false,
3383
+ resizeDraft: null,
3384
+ midpointDraft: null,
3381
3385
  marqueeRect: null,
3382
3386
  marqueeAdditive: false,
3383
3387
  draftEdge: null,
@@ -4886,7 +4890,7 @@ var drawWithNodeTransform = (ctx, node, fn) => {
4886
4890
  };
4887
4891
 
4888
4892
  // src/render/renderer.ts
4889
- var VIEWPORT_OVERSCAN_PX = 64;
4893
+ var SCENE_CACHE_MARGIN_PX = 256;
4890
4894
  var MIN_ON_SCREEN_SIZE_PX = 1.5;
4891
4895
  var MIN_READABLE_FONT_PX = 3;
4892
4896
  var createRenderer = (opts) => {
@@ -4903,6 +4907,30 @@ var createRenderer = (opts) => {
4903
4907
  let interactiveDirty = false;
4904
4908
  let overlaySet = /* @__PURE__ */ new Set();
4905
4909
  let lastDrawn = 0;
4910
+ let cacheSurface = null;
4911
+ let cacheCamX = 0;
4912
+ let cacheCamY = 0;
4913
+ let cacheCamZ = 1;
4914
+ let cacheStale = true;
4915
+ const ensureCacheSurface = () => {
4916
+ const dpr = staticSurface.dpr;
4917
+ const cssW = staticSurface.cssWidth + 2 * SCENE_CACHE_MARGIN_PX;
4918
+ const cssH = staticSurface.cssHeight + 2 * SCENE_CACHE_MARGIN_PX;
4919
+ if (!cacheSurface) {
4920
+ const canvas = document.createElement("canvas");
4921
+ const ctx = canvas.getContext("2d");
4922
+ if (!ctx) throw new Error("Canvas 2d context unavailable");
4923
+ cacheSurface = { canvas, ctx, cssWidth: 0, cssHeight: 0, dpr: 1 };
4924
+ }
4925
+ if (cacheSurface.cssWidth !== cssW || cacheSurface.cssHeight !== cssH || cacheSurface.dpr !== dpr) {
4926
+ cacheSurface.cssWidth = cssW;
4927
+ cacheSurface.cssHeight = cssH;
4928
+ cacheSurface.dpr = dpr;
4929
+ cacheSurface.canvas.width = Math.max(1, Math.round(cssW * dpr));
4930
+ cacheSurface.canvas.height = Math.max(1, Math.round(cssH * dpr));
4931
+ }
4932
+ return cacheSurface;
4933
+ };
4906
4934
  let sortedNodeIdsCache = null;
4907
4935
  let sortedEdgeIdsCache = null;
4908
4936
  const invalidateSortedCaches = () => {
@@ -4911,6 +4939,7 @@ var createRenderer = (opts) => {
4911
4939
  };
4912
4940
  const requestRepaint = () => {
4913
4941
  staticDirty = true;
4942
+ cacheStale = true;
4914
4943
  loop.requestFrame();
4915
4944
  };
4916
4945
  const assetCache = createAssetCache({ onReady: requestRepaint });
@@ -4925,16 +4954,14 @@ var createRenderer = (opts) => {
4925
4954
  interactiveDirty = false;
4926
4955
  }
4927
4956
  };
4928
- const paintStatic = () => {
4929
- const camera = store.getCamera();
4930
- clearSurface(staticSurface);
4931
- applyCameraTransform(staticSurface, camera);
4932
- const scale = camera.z * staticSurface.dpr;
4957
+ const paintSceneBody = (surface, camera, viewport, fullRender = true) => {
4958
+ const scale = camera.z * surface.dpr;
4933
4959
  const interaction = store.getInteractionState();
4934
4960
  const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
4935
- const excludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
4936
- const viewport = inflateRect(worldViewport(staticSurface, camera), VIEWPORT_OVERSCAN_PX);
4937
- paintBackground(staticSurface.ctx, { viewport, zoom: camera.z, background });
4961
+ const baseExcludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
4962
+ const midpointEdgeId = interaction.midpointDraft?.edgeId ?? null;
4963
+ const excludedEdges = midpointEdgeId !== null ? /* @__PURE__ */ new Set([...baseExcludedEdges ?? [], midpointEdgeId]) : baseExcludedEdges;
4964
+ paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
4938
4965
  const visible = visibleNodes(camera, viewport);
4939
4966
  const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
4940
4967
  const minOnScreen = MIN_ON_SCREEN_SIZE_PX;
@@ -4956,8 +4983,8 @@ var createRenderer = (opts) => {
4956
4983
  for (const node of visible) {
4957
4984
  if (node.type !== "frame") continue;
4958
4985
  if (excludedNodes?.has(node.id)) continue;
4959
- drawWithNodeTransform(staticSurface.ctx, node, () => {
4960
- paintFrameNode(staticSurface.ctx, node, scale, theme);
4986
+ drawWithNodeTransform(surface.ctx, node, () => {
4987
+ paintFrameNode(surface.ctx, node, scale, theme);
4961
4988
  });
4962
4989
  drawn++;
4963
4990
  }
@@ -4970,52 +4997,53 @@ var createRenderer = (opts) => {
4970
4997
  const useRough = roughEnabled && (node.style?.roughness ?? 0) > 0;
4971
4998
  const roughReady = useRough ? getRoughCanvasCtor() !== null : false;
4972
4999
  const composite = isCompositePrimitive(node.type);
4973
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5000
+ drawWithNodeTransform(surface.ctx, node, () => {
4974
5001
  if (useRough && roughReady) {
4975
5002
  if (composite) {
4976
- drawCompositeRough(staticSurface.ctx, node, camera.z, theme);
5003
+ drawCompositeRough(surface.ctx, node, camera.z, theme);
4977
5004
  } else {
4978
- staticSurface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
4979
- drawShape(staticSurface.ctx, node, scale, theme, { skipStroke: true });
4980
- staticSurface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
4981
- drawRoughShape(staticSurface.ctx, node, camera.z, theme);
5005
+ surface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
5006
+ drawShape(surface.ctx, node, scale, theme, { skipStroke: true });
5007
+ surface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
5008
+ drawRoughShape(surface.ctx, node, camera.z, theme);
4982
5009
  }
4983
5010
  } else {
4984
- drawShape(staticSurface.ctx, node, scale, theme);
5011
+ drawShape(surface.ctx, node, scale, theme);
4985
5012
  if (useRough && !roughReady) {
4986
5013
  onRoughReady(() => {
4987
5014
  staticDirty = true;
5015
+ cacheStale = true;
4988
5016
  loop.requestFrame();
4989
5017
  });
4990
5018
  }
4991
5019
  }
4992
- if (!isEditingThis) paintNodeContent(staticSurface.ctx, node, renderEnv);
5020
+ if (!isEditingThis) paintNodeContent(surface.ctx, node, renderEnv);
4993
5021
  });
4994
5022
  drawn++;
4995
5023
  continue;
4996
5024
  }
4997
5025
  if (node.type === "image") {
4998
- drawWithNodeTransform(staticSurface.ctx, node, () => {
4999
- paintImageNode(staticSurface.ctx, node, assetCache, theme);
5026
+ drawWithNodeTransform(surface.ctx, node, () => {
5027
+ paintImageNode(surface.ctx, node, assetCache, theme);
5000
5028
  });
5001
5029
  drawn++;
5002
5030
  continue;
5003
5031
  }
5004
5032
  if (node.type === "icon") {
5005
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5006
- paintIconNode(staticSurface.ctx, node, assetCache, scale, theme);
5033
+ drawWithNodeTransform(surface.ctx, node, () => {
5034
+ paintIconNode(surface.ctx, node, assetCache, scale, theme);
5007
5035
  });
5008
5036
  drawn++;
5009
5037
  continue;
5010
5038
  }
5011
5039
  if (node.type === "text") {
5012
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5040
+ drawWithNodeTransform(surface.ctx, node, () => {
5013
5041
  if (isEditingThis) return;
5014
5042
  const hasContent = node.content && node.content.trim().length > 0;
5015
5043
  if (hasContent) {
5016
- paintNodeContent(staticSurface.ctx, node, renderEnv);
5044
+ paintNodeContent(surface.ctx, node, renderEnv);
5017
5045
  } else {
5018
- paintEmptyTextPlaceholder(staticSurface.ctx, node, camera.z);
5046
+ paintEmptyTextPlaceholder(surface.ctx, node, camera.z);
5019
5047
  }
5020
5048
  });
5021
5049
  drawn++;
@@ -5027,7 +5055,7 @@ var createRenderer = (opts) => {
5027
5055
  if (camera.z < def.lod.minZoomForPlaceholder) continue;
5028
5056
  const preferCanvas = camera.z < def.lod.minZoomForReact || isMoving2;
5029
5057
  if (preferCanvas) {
5030
- if (paintCustomCanvasFallback(staticSurface.ctx, node, def, scale, renderEnv)) {
5058
+ if (paintCustomCanvasFallback(surface.ctx, node, def, scale, renderEnv)) {
5031
5059
  drawn++;
5032
5060
  }
5033
5061
  continue;
@@ -5037,10 +5065,10 @@ var createRenderer = (opts) => {
5037
5065
  continue;
5038
5066
  }
5039
5067
  if (def.renderCanvas) {
5040
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5041
- staticSurface.ctx.save();
5042
- def.renderCanvas(staticSurface.ctx, node, renderEnv);
5043
- staticSurface.ctx.restore();
5068
+ drawWithNodeTransform(surface.ctx, node, () => {
5069
+ surface.ctx.save();
5070
+ def.renderCanvas(surface.ctx, node, renderEnv);
5071
+ surface.ctx.restore();
5044
5072
  });
5045
5073
  drawn++;
5046
5074
  }
@@ -5049,15 +5077,120 @@ var createRenderer = (opts) => {
5049
5077
  const edgeRoughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
5050
5078
  for (const edge of visEdges) {
5051
5079
  if (excludedEdges?.has(edge.id)) continue;
5052
- paintOneEdge(staticSurface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
5080
+ paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
5053
5081
  drawn++;
5054
5082
  }
5083
+ if (!fullRender) return;
5055
5084
  lastDrawn = drawn;
5056
5085
  if (!setsEqual(nextOverlaySet, overlaySet)) {
5057
5086
  overlaySet = nextOverlaySet;
5058
5087
  onOverlayChange?.([...overlaySet]);
5059
5088
  }
5060
5089
  };
5090
+ const applyCacheTransform = (cache5, centerX, centerY, z) => {
5091
+ const s = z * cache5.dpr;
5092
+ const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
5093
+ cache5.ctx.setTransform(s, 0, 0, s, -centerX * s + m, -centerY * s + m);
5094
+ };
5095
+ const renderFullCache = (camera) => {
5096
+ const cache5 = ensureCacheSurface();
5097
+ clearSurface(cache5);
5098
+ applyCacheTransform(cache5, camera.x, camera.y, camera.z);
5099
+ const marginWorld = SCENE_CACHE_MARGIN_PX / camera.z;
5100
+ const viewport = inflateRect(worldViewport(staticSurface, camera), marginWorld);
5101
+ paintSceneBody(cache5, camera, viewport);
5102
+ cacheCamX = camera.x;
5103
+ cacheCamY = camera.y;
5104
+ cacheCamZ = camera.z;
5105
+ cacheStale = false;
5106
+ };
5107
+ const canExtend = (camera) => {
5108
+ if (!cacheSurface) return false;
5109
+ const s = camera.z * staticSurface.dpr;
5110
+ const dx = Math.abs((cacheCamX - camera.x) * s);
5111
+ const dy = Math.abs((cacheCamY - camera.y) * s);
5112
+ return dx < cacheSurface.canvas.width && dy < cacheSurface.canvas.height;
5113
+ };
5114
+ const renderCacheStrip = (cache5, centerX, centerY, z, px, py, pw, ph) => {
5115
+ const ctx = cache5.ctx;
5116
+ const s = z * cache5.dpr;
5117
+ const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
5118
+ ctx.save();
5119
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
5120
+ ctx.beginPath();
5121
+ ctx.rect(px, py, pw, ph);
5122
+ ctx.clip();
5123
+ ctx.clearRect(px, py, pw, ph);
5124
+ applyCacheTransform(cache5, centerX, centerY, z);
5125
+ const viewport = {
5126
+ x: (px - m) / s + centerX,
5127
+ y: (py - m) / s + centerY,
5128
+ w: pw / s,
5129
+ h: ph / s
5130
+ };
5131
+ paintSceneBody(cache5, { z }, viewport, false);
5132
+ ctx.restore();
5133
+ };
5134
+ const extendCache = (camera) => {
5135
+ const cache5 = ensureCacheSurface();
5136
+ const s = camera.z * cache5.dpr;
5137
+ const cacheW = cache5.canvas.width;
5138
+ const cacheH = cache5.canvas.height;
5139
+ const dx = Math.round((cacheCamX - camera.x) * s);
5140
+ const dy = Math.round((cacheCamY - camera.y) * s);
5141
+ const newCamX = cacheCamX - dx / s;
5142
+ const newCamY = cacheCamY - dy / s;
5143
+ cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
5144
+ cache5.ctx.drawImage(cache5.canvas, 0, 0, cacheW, cacheH, dx, dy, cacheW, cacheH);
5145
+ cacheCamX = newCamX;
5146
+ cacheCamY = newCamY;
5147
+ cacheCamZ = camera.z;
5148
+ const hw = Math.abs(dx);
5149
+ const vh = Math.abs(dy);
5150
+ const hx = dx > 0 ? 0 : cacheW - hw;
5151
+ const vy = dy > 0 ? 0 : cacheH - vh;
5152
+ const vx = dx > 0 ? hw : 0;
5153
+ const vw = cacheW - hw;
5154
+ if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
5155
+ if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
5156
+ };
5157
+ const cacheSourceOffset = (camera) => {
5158
+ const dpr = staticSurface.dpr;
5159
+ return {
5160
+ x: Math.round(((camera.x - cacheCamX) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr),
5161
+ y: Math.round(((camera.y - cacheCamY) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr)
5162
+ };
5163
+ };
5164
+ const viewportFitsInCache = (camera) => {
5165
+ if (!cacheSurface) return false;
5166
+ const { x, y } = cacheSourceOffset(camera);
5167
+ return x >= 0 && y >= 0 && x + staticSurface.canvas.width <= cacheSurface.canvas.width && y + staticSurface.canvas.height <= cacheSurface.canvas.height;
5168
+ };
5169
+ const presentStatic = (camera) => {
5170
+ const cache5 = ensureCacheSurface();
5171
+ const w = staticSurface.canvas.width;
5172
+ const h = staticSurface.canvas.height;
5173
+ const { x: srcX, y: srcY } = cacheSourceOffset(camera);
5174
+ staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
5175
+ staticSurface.ctx.clearRect(0, 0, w, h);
5176
+ staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
5177
+ };
5178
+ const paintStatic = () => {
5179
+ const camera = store.getCamera();
5180
+ if (!cacheStale && camera.z === cacheCamZ) {
5181
+ if (viewportFitsInCache(camera)) {
5182
+ presentStatic(camera);
5183
+ return;
5184
+ }
5185
+ if (canExtend(camera)) {
5186
+ extendCache(camera);
5187
+ presentStatic(camera);
5188
+ return;
5189
+ }
5190
+ }
5191
+ renderFullCache(camera);
5192
+ presentStatic(camera);
5193
+ };
5061
5194
  const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
5062
5195
  if (def.getSnapshot) {
5063
5196
  const snap = def.getSnapshot(node, {
@@ -5239,6 +5372,23 @@ var createRenderer = (opts) => {
5239
5372
  }
5240
5373
  }
5241
5374
  }
5375
+ if (interaction.midpointDraft) {
5376
+ const { edgeId, control } = interaction.midpointDraft;
5377
+ const edge = store.getEdge(edgeId);
5378
+ if (edge) {
5379
+ const draftEdge = { ...edge, control };
5380
+ const geom = computeEdgeGeometry(draftEdge, (id) => store.getNode(id));
5381
+ if (geom) {
5382
+ const sourceNode = geom.sourceNodeId ? store.getNode(geom.sourceNodeId) ?? null : null;
5383
+ const targetNode = geom.targetNodeId ? store.getNode(geom.targetNodeId) ?? null : null;
5384
+ drawEdge(ctx, draftEdge, geom, sourceNode, targetNode, scale, theme, {
5385
+ zoom: camera.z,
5386
+ dpr: interactiveSurface.dpr,
5387
+ isMoving: true
5388
+ });
5389
+ }
5390
+ }
5391
+ }
5242
5392
  const selection = store.getSelection();
5243
5393
  const selectedNodeIds = [];
5244
5394
  const selectedEdgeIds = [];
@@ -5311,7 +5461,12 @@ var createRenderer = (opts) => {
5311
5461
  y: orig.y + interaction.dragDelta.y
5312
5462
  });
5313
5463
  } else {
5314
- m.set(orig.id, live);
5464
+ const d = interaction.resizeDraft;
5465
+ if (d) {
5466
+ m.set(orig.id, { ...live, x: d.x, y: d.y, w: d.w, h: d.h, angle: d.angle });
5467
+ } else {
5468
+ m.set(orig.id, live);
5469
+ }
5315
5470
  }
5316
5471
  }
5317
5472
  return m;
@@ -5342,6 +5497,7 @@ var createRenderer = (opts) => {
5342
5497
  const onStoreChange = () => {
5343
5498
  invalidateSortedCaches();
5344
5499
  staticDirty = true;
5500
+ cacheStale = true;
5345
5501
  interactiveDirty = true;
5346
5502
  loop.requestFrame();
5347
5503
  };
@@ -5358,6 +5514,7 @@ var createRenderer = (opts) => {
5358
5514
  interactiveDirty = true;
5359
5515
  if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "zooming" || state.mode === "idle") {
5360
5516
  staticDirty = true;
5517
+ cacheStale = true;
5361
5518
  }
5362
5519
  loop.requestFrame();
5363
5520
  };
@@ -5367,16 +5524,19 @@ var createRenderer = (opts) => {
5367
5524
  const unsubInteraction = store.subscribe("interaction", onInteractionChange);
5368
5525
  const unsubFontEpoch = subscribeFontEpoch(() => {
5369
5526
  staticDirty = true;
5527
+ cacheStale = true;
5370
5528
  loop.requestFrame();
5371
5529
  });
5372
5530
  const unsubMathEpoch = subscribeMathEpoch(() => {
5373
5531
  staticDirty = true;
5532
+ cacheStale = true;
5374
5533
  loop.requestFrame();
5375
5534
  });
5376
5535
  return {
5377
5536
  start() {
5378
5537
  loop.start();
5379
5538
  staticDirty = true;
5539
+ cacheStale = true;
5380
5540
  interactiveDirty = isInteractive(store.getInteractionState());
5381
5541
  loop.requestFrame();
5382
5542
  },
@@ -5385,6 +5545,7 @@ var createRenderer = (opts) => {
5385
5545
  },
5386
5546
  invalidate() {
5387
5547
  staticDirty = true;
5548
+ cacheStale = true;
5388
5549
  interactiveDirty = true;
5389
5550
  loop.requestFrame();
5390
5551
  },
@@ -5393,6 +5554,7 @@ var createRenderer = (opts) => {
5393
5554
  const b = sizeSurface(interactiveSurface, cssW, cssH, maxDpr);
5394
5555
  if (a || b) {
5395
5556
  staticDirty = true;
5557
+ cacheStale = true;
5396
5558
  interactiveDirty = true;
5397
5559
  loop.requestFrame();
5398
5560
  }
@@ -5400,6 +5562,7 @@ var createRenderer = (opts) => {
5400
5562
  setBackground(bg) {
5401
5563
  background = bg;
5402
5564
  staticDirty = true;
5565
+ cacheStale = true;
5403
5566
  loop.requestFrame();
5404
5567
  },
5405
5568
  setSelectionColor(color) {
@@ -5410,6 +5573,7 @@ var createRenderer = (opts) => {
5410
5573
  setHideFrames(hidden) {
5411
5574
  hideFrames = hidden;
5412
5575
  staticDirty = true;
5576
+ cacheStale = true;
5413
5577
  loop.requestFrame();
5414
5578
  },
5415
5579
  stats: () => loop.stats(),
@@ -5424,6 +5588,11 @@ var createRenderer = (opts) => {
5424
5588
  unsubFontEpoch();
5425
5589
  unsubMathEpoch();
5426
5590
  assetCache.dispose();
5591
+ if (cacheSurface) {
5592
+ cacheSurface.canvas.width = 0;
5593
+ cacheSurface.canvas.height = 0;
5594
+ cacheSurface = null;
5595
+ }
5427
5596
  }
5428
5597
  };
5429
5598
  };