@canvas-harness/core 0.1.5 → 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.d.cts CHANGED
@@ -1454,9 +1454,33 @@ type InteractionState = {
1454
1454
  resizeLockAspect: boolean;
1455
1455
  /** Whether the user is holding Alt during a resize (resize from center). */
1456
1456
  resizeFromCenter: boolean;
1457
+ /**
1458
+ * Live in-progress geometry of the resized node — written every
1459
+ * pointermove, committed to the store once on pointer-up. While
1460
+ * present, `store.getNode(id)` still returns the original geometry;
1461
+ * the renderer overlays this draft via `mapDragPositions` for the
1462
+ * interactive layer paint. Mirrors how `dragDelta` works for drag.
1463
+ */
1464
+ resizeDraft: {
1465
+ x: number;
1466
+ y: number;
1467
+ w: number;
1468
+ h: number;
1469
+ angle: number;
1470
+ } | null;
1457
1471
  marqueeRect: WorldRect | null;
1458
1472
  /** Whether the marquee should add to (true, shift held) or replace selection. */
1459
1473
  marqueeAdditive: boolean;
1474
+ /**
1475
+ * Live in-progress cubic controls of an edge being mid-point-dragged.
1476
+ * Written every pointermove, committed to the store once on
1477
+ * pointer-up. Same draft+commit model as `resizeDraft` and
1478
+ * `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
1479
+ */
1480
+ midpointDraft: {
1481
+ edgeId: EdgeId;
1482
+ control: [Vec2, Vec2];
1483
+ } | null;
1460
1484
  draftEdge: {
1461
1485
  source: EdgeEnd;
1462
1486
  target: EdgeEnd;
package/dist/index.d.ts CHANGED
@@ -1454,9 +1454,33 @@ type InteractionState = {
1454
1454
  resizeLockAspect: boolean;
1455
1455
  /** Whether the user is holding Alt during a resize (resize from center). */
1456
1456
  resizeFromCenter: boolean;
1457
+ /**
1458
+ * Live in-progress geometry of the resized node — written every
1459
+ * pointermove, committed to the store once on pointer-up. While
1460
+ * present, `store.getNode(id)` still returns the original geometry;
1461
+ * the renderer overlays this draft via `mapDragPositions` for the
1462
+ * interactive layer paint. Mirrors how `dragDelta` works for drag.
1463
+ */
1464
+ resizeDraft: {
1465
+ x: number;
1466
+ y: number;
1467
+ w: number;
1468
+ h: number;
1469
+ angle: number;
1470
+ } | null;
1457
1471
  marqueeRect: WorldRect | null;
1458
1472
  /** Whether the marquee should add to (true, shift held) or replace selection. */
1459
1473
  marqueeAdditive: boolean;
1474
+ /**
1475
+ * Live in-progress cubic controls of an edge being mid-point-dragged.
1476
+ * Written every pointermove, committed to the store once on
1477
+ * pointer-up. Same draft+commit model as `resizeDraft` and
1478
+ * `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
1479
+ */
1480
+ midpointDraft: {
1481
+ edgeId: EdgeId;
1482
+ control: [Vec2, Vec2];
1483
+ } | null;
1460
1484
  draftEdge: {
1461
1485
  source: EdgeEnd;
1462
1486
  target: EdgeEnd;
package/dist/index.js CHANGED
@@ -1048,7 +1048,6 @@ var COMPOSITE = /* @__PURE__ */ new Set([
1048
1048
  var isCompositePrimitive = (type) => COMPOSITE.has(type);
1049
1049
  var isDrawablePrimitive = (type) => ATOMIC.has(type) || COMPOSITE.has(type);
1050
1050
  var PLAIN_RECT_CORNER_THRESHOLD_PX = 1.5;
1051
- var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
1052
1051
  var LAYERED_OFFSET = 12;
1053
1052
  var drawShape = (ctx, node, scale, theme, opts) => {
1054
1053
  if (!isDrawablePrimitive(node.type)) return;
@@ -1067,7 +1066,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
1067
1066
  const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
1068
1067
  const stroke = resolveColor(style, "strokeColor", DEFAULT_STYLE.strokeColor, theme);
1069
1068
  const fillVisible = !isFullyTransparent(fill);
1070
- const strokeVisible = strokeWidth > 0 && strokeWidth * scale >= STROKE_VISIBILITY_THRESHOLD_PX && !isFullyTransparent(stroke);
1069
+ const strokeVisible = strokeWidth > 0 && !isFullyTransparent(stroke);
1071
1070
  if (!fillVisible && !strokeVisible) return;
1072
1071
  const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
1073
1072
  switch (type) {
@@ -1104,7 +1103,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
1104
1103
  }
1105
1104
  if (strokeVisible && !opts?.skipStroke) {
1106
1105
  ctx.strokeStyle = stroke;
1107
- ctx.lineWidth = strokeWidth;
1106
+ ctx.lineWidth = Math.max(strokeWidth, 1 / scale);
1108
1107
  ctx.setLineDash(dashPatternFor(style?.strokeStyle, strokeWidth));
1109
1108
  ctx.stroke();
1110
1109
  }
@@ -1480,13 +1479,16 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
1480
1479
  const isDark = theme?.("mode") === "dark";
1481
1480
  const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
1482
1481
  const strokeColor = deriveRoughStrokeColor(rawStroke, fill, isDark);
1483
- const strokeWidth = resolveStrokeWidth(style, theme);
1484
- if (strokeWidth <= 0) return;
1482
+ const rawStrokeWidth = resolveStrokeWidth(style, theme);
1483
+ if (rawStrokeWidth <= 0) return;
1485
1484
  const roughness = style?.roughness ?? 0;
1486
1485
  if (roughness <= 0) return;
1486
+ const isNoBorderIntent = isFullyTransparent(rawStroke);
1487
+ const effectiveStrokeStyle = isNoBorderIntent ? "solid" : style?.strokeStyle ?? "solid";
1488
+ const strokeWidth = isNoBorderIntent ? DEFAULT_STYLE.strokeWidth : rawStrokeWidth;
1487
1489
  const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
1488
1490
  const radius = Math.max(0, Math.min(cornerRadius, w / 2, h / 2));
1489
- const dash = dashPatternFor(style?.strokeStyle, strokeWidth);
1491
+ const dash = dashPatternFor(effectiveStrokeStyle, strokeWidth);
1490
1492
  const detail = apparentDetail(Math.max(w, h), scale);
1491
1493
  const cacheKey = [
1492
1494
  type,
@@ -1495,7 +1497,7 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
1495
1497
  radius.toFixed(1),
1496
1498
  strokeColor,
1497
1499
  strokeWidth.toFixed(2),
1498
- style?.strokeStyle ?? "solid",
1500
+ effectiveStrokeStyle,
1499
1501
  roughness.toFixed(2),
1500
1502
  seed,
1501
1503
  detail.curveStepCount,
@@ -2646,7 +2648,7 @@ var DEFAULT_EDGE_STYLE = {
2646
2648
  sourceArrowhead: "none",
2647
2649
  targetArrowhead: "arrow-filled"
2648
2650
  };
2649
- var STROKE_VISIBILITY_THRESHOLD_PX2 = 0.5;
2651
+ var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
2650
2652
  var ARROWHEAD_VISIBILITY_THRESHOLD_PX = 2;
2651
2653
  var samplePaintStride = (scale) => {
2652
2654
  if (scale < 0.15) return 8;
@@ -2660,7 +2662,7 @@ var drawEdge = (ctx, edge, geom, sourceNode, targetNode, scale, theme, opts) =>
2660
2662
  if (samples.length < 2) return;
2661
2663
  const style = edge.style;
2662
2664
  const strokeWidth = typeof style?.strokeWidth === "number" ? style.strokeWidth : theme?.("strokeWidth") ?? DEFAULT_EDGE_STYLE.strokeWidth;
2663
- if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX2) return;
2665
+ if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX) return;
2664
2666
  const strokeColor = typeof style?.strokeColor === "string" ? style.strokeColor : theme?.("edge.strokeColor") ?? DEFAULT_EDGE_STYLE.strokeColor;
2665
2667
  const sourceArrowhead = style?.sourceArrowhead ?? DEFAULT_EDGE_STYLE.sourceArrowhead;
2666
2668
  const targetArrowhead = style?.targetArrowhead ?? DEFAULT_EDGE_STYLE.targetArrowhead;
@@ -3376,6 +3378,8 @@ var idleInteractionState = () => ({
3376
3378
  resizeHandle: null,
3377
3379
  resizeLockAspect: false,
3378
3380
  resizeFromCenter: false,
3381
+ resizeDraft: null,
3382
+ midpointDraft: null,
3379
3383
  marqueeRect: null,
3380
3384
  marqueeAdditive: false,
3381
3385
  draftEdge: null,
@@ -4952,7 +4956,9 @@ var createRenderer = (opts) => {
4952
4956
  const scale = camera.z * surface.dpr;
4953
4957
  const interaction = store.getInteractionState();
4954
4958
  const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
4955
- const excludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
4959
+ const baseExcludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
4960
+ const midpointEdgeId = interaction.midpointDraft?.edgeId ?? null;
4961
+ const excludedEdges = midpointEdgeId !== null ? /* @__PURE__ */ new Set([...baseExcludedEdges ?? [], midpointEdgeId]) : baseExcludedEdges;
4956
4962
  paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
4957
4963
  const visible = visibleNodes(camera, viewport);
4958
4964
  const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
@@ -5364,6 +5370,23 @@ var createRenderer = (opts) => {
5364
5370
  }
5365
5371
  }
5366
5372
  }
5373
+ if (interaction.midpointDraft) {
5374
+ const { edgeId, control } = interaction.midpointDraft;
5375
+ const edge = store.getEdge(edgeId);
5376
+ if (edge) {
5377
+ const draftEdge = { ...edge, control };
5378
+ const geom = computeEdgeGeometry(draftEdge, (id) => store.getNode(id));
5379
+ if (geom) {
5380
+ const sourceNode = geom.sourceNodeId ? store.getNode(geom.sourceNodeId) ?? null : null;
5381
+ const targetNode = geom.targetNodeId ? store.getNode(geom.targetNodeId) ?? null : null;
5382
+ drawEdge(ctx, draftEdge, geom, sourceNode, targetNode, scale, theme, {
5383
+ zoom: camera.z,
5384
+ dpr: interactiveSurface.dpr,
5385
+ isMoving: true
5386
+ });
5387
+ }
5388
+ }
5389
+ }
5367
5390
  const selection = store.getSelection();
5368
5391
  const selectedNodeIds = [];
5369
5392
  const selectedEdgeIds = [];
@@ -5436,7 +5459,12 @@ var createRenderer = (opts) => {
5436
5459
  y: orig.y + interaction.dragDelta.y
5437
5460
  });
5438
5461
  } else {
5439
- m.set(orig.id, live);
5462
+ const d = interaction.resizeDraft;
5463
+ if (d) {
5464
+ m.set(orig.id, { ...live, x: d.x, y: d.y, w: d.w, h: d.h, angle: d.angle });
5465
+ } else {
5466
+ m.set(orig.id, live);
5467
+ }
5440
5468
  }
5441
5469
  }
5442
5470
  return m;