@canvas-harness/core 0.1.5 → 0.1.7
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 +156 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +156 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -945,10 +945,11 @@ declare const defineNode: (opts: NodeTypeDefOptions) => NodeTypeDef;
|
|
|
945
945
|
*/
|
|
946
946
|
type InlineType = 'text' | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'link'
|
|
947
947
|
/**
|
|
948
|
-
* LaTeX math expression, content is the source between
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
948
|
+
* LaTeX math expression, content is the source between `$$...$$` (no
|
|
949
|
+
* line breaks). Rendered via MathJax SVG output and rasterized to an
|
|
950
|
+
* inline bitmap at paint time. See `text/math/`. Double-dollar
|
|
951
|
+
* delimiters avoid the false-positives of single `$` in prose that
|
|
952
|
+
* mentions currency (e.g. `$5 to $10`).
|
|
952
953
|
*/
|
|
953
954
|
| 'math';
|
|
954
955
|
type Token = {
|
|
@@ -1075,7 +1076,7 @@ declare const getFontEpoch: () => number;
|
|
|
1075
1076
|
* Lazy loader for MathJax — same pattern as `render/rough/loader.ts`.
|
|
1076
1077
|
*
|
|
1077
1078
|
* MathJax is ~600KB and only useful for scenes with LaTeX math. We
|
|
1078
|
-
* defer loading until the first
|
|
1079
|
+
* defer loading until the first `$$...$$` token requests a compile,
|
|
1079
1080
|
* then convert LaTeX → SVG strings off the main rAF path.
|
|
1080
1081
|
*
|
|
1081
1082
|
* Loaded from jsDelivr CDN rather than bundled because the v4
|
|
@@ -1454,9 +1455,33 @@ type InteractionState = {
|
|
|
1454
1455
|
resizeLockAspect: boolean;
|
|
1455
1456
|
/** Whether the user is holding Alt during a resize (resize from center). */
|
|
1456
1457
|
resizeFromCenter: boolean;
|
|
1458
|
+
/**
|
|
1459
|
+
* Live in-progress geometry of the resized node — written every
|
|
1460
|
+
* pointermove, committed to the store once on pointer-up. While
|
|
1461
|
+
* present, `store.getNode(id)` still returns the original geometry;
|
|
1462
|
+
* the renderer overlays this draft via `mapDragPositions` for the
|
|
1463
|
+
* interactive layer paint. Mirrors how `dragDelta` works for drag.
|
|
1464
|
+
*/
|
|
1465
|
+
resizeDraft: {
|
|
1466
|
+
x: number;
|
|
1467
|
+
y: number;
|
|
1468
|
+
w: number;
|
|
1469
|
+
h: number;
|
|
1470
|
+
angle: number;
|
|
1471
|
+
} | null;
|
|
1457
1472
|
marqueeRect: WorldRect | null;
|
|
1458
1473
|
/** Whether the marquee should add to (true, shift held) or replace selection. */
|
|
1459
1474
|
marqueeAdditive: boolean;
|
|
1475
|
+
/**
|
|
1476
|
+
* Live in-progress cubic controls of an edge being mid-point-dragged.
|
|
1477
|
+
* Written every pointermove, committed to the store once on
|
|
1478
|
+
* pointer-up. Same draft+commit model as `resizeDraft` and
|
|
1479
|
+
* `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
|
|
1480
|
+
*/
|
|
1481
|
+
midpointDraft: {
|
|
1482
|
+
edgeId: EdgeId;
|
|
1483
|
+
control: [Vec2, Vec2];
|
|
1484
|
+
} | null;
|
|
1460
1485
|
draftEdge: {
|
|
1461
1486
|
source: EdgeEnd;
|
|
1462
1487
|
target: EdgeEnd;
|
package/dist/index.d.ts
CHANGED
|
@@ -945,10 +945,11 @@ declare const defineNode: (opts: NodeTypeDefOptions) => NodeTypeDef;
|
|
|
945
945
|
*/
|
|
946
946
|
type InlineType = 'text' | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'link'
|
|
947
947
|
/**
|
|
948
|
-
* LaTeX math expression, content is the source between
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
948
|
+
* LaTeX math expression, content is the source between `$$...$$` (no
|
|
949
|
+
* line breaks). Rendered via MathJax SVG output and rasterized to an
|
|
950
|
+
* inline bitmap at paint time. See `text/math/`. Double-dollar
|
|
951
|
+
* delimiters avoid the false-positives of single `$` in prose that
|
|
952
|
+
* mentions currency (e.g. `$5 to $10`).
|
|
952
953
|
*/
|
|
953
954
|
| 'math';
|
|
954
955
|
type Token = {
|
|
@@ -1075,7 +1076,7 @@ declare const getFontEpoch: () => number;
|
|
|
1075
1076
|
* Lazy loader for MathJax — same pattern as `render/rough/loader.ts`.
|
|
1076
1077
|
*
|
|
1077
1078
|
* MathJax is ~600KB and only useful for scenes with LaTeX math. We
|
|
1078
|
-
* defer loading until the first
|
|
1079
|
+
* defer loading until the first `$$...$$` token requests a compile,
|
|
1079
1080
|
* then convert LaTeX → SVG strings off the main rAF path.
|
|
1080
1081
|
*
|
|
1081
1082
|
* Loaded from jsDelivr CDN rather than bundled because the v4
|
|
@@ -1454,9 +1455,33 @@ type InteractionState = {
|
|
|
1454
1455
|
resizeLockAspect: boolean;
|
|
1455
1456
|
/** Whether the user is holding Alt during a resize (resize from center). */
|
|
1456
1457
|
resizeFromCenter: boolean;
|
|
1458
|
+
/**
|
|
1459
|
+
* Live in-progress geometry of the resized node — written every
|
|
1460
|
+
* pointermove, committed to the store once on pointer-up. While
|
|
1461
|
+
* present, `store.getNode(id)` still returns the original geometry;
|
|
1462
|
+
* the renderer overlays this draft via `mapDragPositions` for the
|
|
1463
|
+
* interactive layer paint. Mirrors how `dragDelta` works for drag.
|
|
1464
|
+
*/
|
|
1465
|
+
resizeDraft: {
|
|
1466
|
+
x: number;
|
|
1467
|
+
y: number;
|
|
1468
|
+
w: number;
|
|
1469
|
+
h: number;
|
|
1470
|
+
angle: number;
|
|
1471
|
+
} | null;
|
|
1457
1472
|
marqueeRect: WorldRect | null;
|
|
1458
1473
|
/** Whether the marquee should add to (true, shift held) or replace selection. */
|
|
1459
1474
|
marqueeAdditive: boolean;
|
|
1475
|
+
/**
|
|
1476
|
+
* Live in-progress cubic controls of an edge being mid-point-dragged.
|
|
1477
|
+
* Written every pointermove, committed to the store once on
|
|
1478
|
+
* pointer-up. Same draft+commit model as `resizeDraft` and
|
|
1479
|
+
* `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
|
|
1480
|
+
*/
|
|
1481
|
+
midpointDraft: {
|
|
1482
|
+
edgeId: EdgeId;
|
|
1483
|
+
control: [Vec2, Vec2];
|
|
1484
|
+
} | null;
|
|
1460
1485
|
draftEdge: {
|
|
1461
1486
|
source: EdgeEnd;
|
|
1462
1487
|
target: EdgeEnd;
|
package/dist/index.js
CHANGED
|
@@ -231,53 +231,6 @@ var rotatePoint = (px, py, cx, cy, cos, sin) => {
|
|
|
231
231
|
return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
-
// src/edges/auto-route.ts
|
|
235
|
-
var CONTROL_MAX = 200;
|
|
236
|
-
var CONTROL_FRACTION = 0.4;
|
|
237
|
-
var sideOf = (node, localX, localY) => {
|
|
238
|
-
const distLeft = localX;
|
|
239
|
-
const distRight = node.w - localX;
|
|
240
|
-
const distTop = localY;
|
|
241
|
-
const distBottom = node.h - localY;
|
|
242
|
-
const minDist = Math.min(distLeft, distRight, distTop, distBottom);
|
|
243
|
-
if (minDist === distLeft) return "w";
|
|
244
|
-
if (minDist === distRight) return "e";
|
|
245
|
-
if (minDist === distTop) return "n";
|
|
246
|
-
return "s";
|
|
247
|
-
};
|
|
248
|
-
var sideNormalLocal = (side) => {
|
|
249
|
-
switch (side) {
|
|
250
|
-
case "n":
|
|
251
|
-
return { x: 0, y: -1 };
|
|
252
|
-
case "s":
|
|
253
|
-
return { x: 0, y: 1 };
|
|
254
|
-
case "e":
|
|
255
|
-
return { x: 1, y: 0 };
|
|
256
|
-
case "w":
|
|
257
|
-
return { x: -1, y: 0 };
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
var rotateVecByAngle = (v, angle) => {
|
|
261
|
-
if (angle === 0) return v;
|
|
262
|
-
const cos = Math.cos(angle);
|
|
263
|
-
const sin = Math.sin(angle);
|
|
264
|
-
return { x: v.x * cos - v.y * sin, y: v.x * sin + v.y * cos };
|
|
265
|
-
};
|
|
266
|
-
var autoRouteControls = (sourceWorld, targetWorld, sourceNormalWorld, targetNormalWorld) => {
|
|
267
|
-
const dx = targetWorld.x - sourceWorld.x;
|
|
268
|
-
const dy = targetWorld.y - sourceWorld.y;
|
|
269
|
-
const dist = Math.hypot(dx, dy);
|
|
270
|
-
const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * dist);
|
|
271
|
-
const fallbackSource = dist > 0 ? { x: dx / dist, y: dy / dist } : { x: 1, y: 0 };
|
|
272
|
-
const fallbackTarget = dist > 0 ? { x: -dx / dist, y: -dy / dist } : { x: -1, y: 0 };
|
|
273
|
-
const ns = sourceNormalWorld ?? fallbackSource;
|
|
274
|
-
const nt = targetNormalWorld ?? fallbackTarget;
|
|
275
|
-
return {
|
|
276
|
-
c1: { x: sourceWorld.x + ns.x * offset, y: sourceWorld.y + ns.y * offset },
|
|
277
|
-
c2: { x: targetWorld.x + nt.x * offset, y: targetWorld.y + nt.y * offset }
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
|
|
281
234
|
// src/edges/project.ts
|
|
282
235
|
var projectEndToWorld = (end, getNode) => {
|
|
283
236
|
if (!isAttached(end)) return end.worldPoint;
|
|
@@ -326,6 +279,112 @@ var projectToNodeBoundary = (world, node) => {
|
|
|
326
279
|
return { x: clampedX, y: node.h };
|
|
327
280
|
};
|
|
328
281
|
|
|
282
|
+
// src/edges/auto-route.ts
|
|
283
|
+
var CONTROL_MAX = 200;
|
|
284
|
+
var CONTROL_FRACTION = 0.4;
|
|
285
|
+
var BOUNDARY_EPS = 0.5;
|
|
286
|
+
var isLocalOffsetInsideBody = (localOffset, node) => {
|
|
287
|
+
const onLeft = Math.abs(localOffset.x) <= BOUNDARY_EPS;
|
|
288
|
+
const onRight = Math.abs(localOffset.x - node.w) <= BOUNDARY_EPS;
|
|
289
|
+
const onTop = Math.abs(localOffset.y) <= BOUNDARY_EPS;
|
|
290
|
+
const onBottom = Math.abs(localOffset.y - node.h) <= BOUNDARY_EPS;
|
|
291
|
+
const inside = localOffset.x > -BOUNDARY_EPS && localOffset.x < node.w + BOUNDARY_EPS && localOffset.y > -BOUNDARY_EPS && localOffset.y < node.h + BOUNDARY_EPS;
|
|
292
|
+
return inside && !onLeft && !onRight && !onTop && !onBottom;
|
|
293
|
+
};
|
|
294
|
+
var sideOf = (node, localX, localY) => {
|
|
295
|
+
const distLeft = localX;
|
|
296
|
+
const distRight = node.w - localX;
|
|
297
|
+
const distTop = localY;
|
|
298
|
+
const distBottom = node.h - localY;
|
|
299
|
+
const minDist = Math.min(distLeft, distRight, distTop, distBottom);
|
|
300
|
+
if (minDist === distLeft) return "w";
|
|
301
|
+
if (minDist === distRight) return "e";
|
|
302
|
+
if (minDist === distTop) return "n";
|
|
303
|
+
return "s";
|
|
304
|
+
};
|
|
305
|
+
var sideNormalLocal = (side) => {
|
|
306
|
+
switch (side) {
|
|
307
|
+
case "n":
|
|
308
|
+
return { x: 0, y: -1 };
|
|
309
|
+
case "s":
|
|
310
|
+
return { x: 0, y: 1 };
|
|
311
|
+
case "e":
|
|
312
|
+
return { x: 1, y: 0 };
|
|
313
|
+
case "w":
|
|
314
|
+
return { x: -1, y: 0 };
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var rotateVecByAngle = (v, angle) => {
|
|
318
|
+
if (angle === 0) return v;
|
|
319
|
+
const cos = Math.cos(angle);
|
|
320
|
+
const sin = Math.sin(angle);
|
|
321
|
+
return { x: v.x * cos - v.y * sin, y: v.x * sin + v.y * cos };
|
|
322
|
+
};
|
|
323
|
+
var autoRouteControls = (sourceWorld, targetWorld, sourceNormalWorld, targetNormalWorld) => {
|
|
324
|
+
const dx = targetWorld.x - sourceWorld.x;
|
|
325
|
+
const dy = targetWorld.y - sourceWorld.y;
|
|
326
|
+
const dist = Math.hypot(dx, dy);
|
|
327
|
+
const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * dist);
|
|
328
|
+
const fallbackSource = dist > 0 ? { x: dx / dist, y: dy / dist } : { x: 1, y: 0 };
|
|
329
|
+
const fallbackTarget = dist > 0 ? { x: -dx / dist, y: -dy / dist } : { x: -1, y: 0 };
|
|
330
|
+
const ns = sourceNormalWorld ?? fallbackSource;
|
|
331
|
+
const nt = targetNormalWorld ?? fallbackTarget;
|
|
332
|
+
return {
|
|
333
|
+
c1: { x: sourceWorld.x + ns.x * offset, y: sourceWorld.y + ns.y * offset },
|
|
334
|
+
c2: { x: targetWorld.x + nt.x * offset, y: targetWorld.y + nt.y * offset }
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
var computeAsymmetricRoute = (sourceNode, targetNode) => {
|
|
338
|
+
const srcCenterWorld = {
|
|
339
|
+
x: sourceNode.x + sourceNode.w / 2,
|
|
340
|
+
y: sourceNode.y + sourceNode.h / 2
|
|
341
|
+
};
|
|
342
|
+
const srcInTgtLocal = worldToNodeLocal(srcCenterWorld, targetNode);
|
|
343
|
+
const tgtHalfW = targetNode.w / 2;
|
|
344
|
+
const tgtHalfH = targetNode.h / 2;
|
|
345
|
+
const dxNorm = (srcInTgtLocal.x - tgtHalfW) / Math.max(1, tgtHalfW);
|
|
346
|
+
const dyNorm = (srcInTgtLocal.y - tgtHalfH) / Math.max(1, tgtHalfH);
|
|
347
|
+
const targetSide = Math.abs(dxNorm) >= Math.abs(dyNorm) ? dxNorm > 0 ? "e" : "w" : dyNorm > 0 ? "s" : "n";
|
|
348
|
+
let tgtEntryLocal;
|
|
349
|
+
if (targetSide === "n" || targetSide === "s") {
|
|
350
|
+
const sideY = targetSide === "n" ? 0 : targetNode.h;
|
|
351
|
+
const clampX = Math.max(0, Math.min(targetNode.w, srcInTgtLocal.x));
|
|
352
|
+
tgtEntryLocal = { x: clampX, y: sideY };
|
|
353
|
+
} else {
|
|
354
|
+
const sideX = targetSide === "w" ? 0 : targetNode.w;
|
|
355
|
+
const clampY = Math.max(0, Math.min(targetNode.h, srcInTgtLocal.y));
|
|
356
|
+
tgtEntryLocal = { x: sideX, y: clampY };
|
|
357
|
+
}
|
|
358
|
+
const targetEntryWorld = nodeLocalToWorld(tgtEntryLocal, targetNode);
|
|
359
|
+
const tgtEntryInSrcLocal = worldToNodeLocal(targetEntryWorld, sourceNode);
|
|
360
|
+
const srcHalfW = sourceNode.w / 2;
|
|
361
|
+
const srcHalfH = sourceNode.h / 2;
|
|
362
|
+
const rayDx = tgtEntryInSrcLocal.x - srcHalfW;
|
|
363
|
+
const rayDy = tgtEntryInSrcLocal.y - srcHalfH;
|
|
364
|
+
const tx = rayDx === 0 ? Number.POSITIVE_INFINITY : (rayDx > 0 ? srcHalfW : -srcHalfW) / rayDx;
|
|
365
|
+
const ty = rayDy === 0 ? Number.POSITIVE_INFINITY : (rayDy > 0 ? srcHalfH : -srcHalfH) / rayDy;
|
|
366
|
+
const t = Math.min(tx, ty);
|
|
367
|
+
const srcExitLocal = {
|
|
368
|
+
x: srcHalfW + rayDx * t,
|
|
369
|
+
y: srcHalfH + rayDy * t
|
|
370
|
+
};
|
|
371
|
+
const sourceExitWorld = nodeLocalToWorld(srcExitLocal, sourceNode);
|
|
372
|
+
const dxWorld = targetEntryWorld.x - sourceExitWorld.x;
|
|
373
|
+
const dyWorld = targetEntryWorld.y - sourceExitWorld.y;
|
|
374
|
+
const distance2 = Math.hypot(dxWorld, dyWorld);
|
|
375
|
+
const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * distance2);
|
|
376
|
+
const c1 = distance2 > 0 ? {
|
|
377
|
+
x: sourceExitWorld.x + dxWorld / distance2 * offset,
|
|
378
|
+
y: sourceExitWorld.y + dyWorld / distance2 * offset
|
|
379
|
+
} : { ...sourceExitWorld };
|
|
380
|
+
const tgtNormalWorld = rotateVecByAngle(sideNormalLocal(targetSide), targetNode.angle);
|
|
381
|
+
const c2 = {
|
|
382
|
+
x: targetEntryWorld.x + tgtNormalWorld.x * offset,
|
|
383
|
+
y: targetEntryWorld.y + tgtNormalWorld.y * offset
|
|
384
|
+
};
|
|
385
|
+
return { source: sourceExitWorld, target: targetEntryWorld, c1, c2 };
|
|
386
|
+
};
|
|
387
|
+
|
|
329
388
|
// src/edges/clip.ts
|
|
330
389
|
var fullVisibleClipResult = (samples) => ({
|
|
331
390
|
startIndex: 0,
|
|
@@ -586,8 +645,8 @@ var computeEdgeGeometry = (edge, getNode) => {
|
|
|
586
645
|
targetNodeId
|
|
587
646
|
};
|
|
588
647
|
}
|
|
589
|
-
|
|
590
|
-
|
|
648
|
+
let sourceWorld = projectEndToWorld(edge.source, getNode);
|
|
649
|
+
let targetWorld = projectEndToWorld(edge.target, getNode);
|
|
591
650
|
if (!sourceWorld || !targetWorld) return null;
|
|
592
651
|
let samples;
|
|
593
652
|
if (edge.pathStyle === "bezier") {
|
|
@@ -596,6 +655,12 @@ var computeEdgeGeometry = (edge, getNode) => {
|
|
|
596
655
|
if (edge.control && edge.control.length >= 2) {
|
|
597
656
|
c1 = edge.control[0];
|
|
598
657
|
c2 = edge.control[1];
|
|
658
|
+
} else if (sourceNode && targetNode && isAttached(edge.source) && isAttached(edge.target) && isLocalOffsetInsideBody(edge.source.localOffset, sourceNode) && isLocalOffsetInsideBody(edge.target.localOffset, targetNode)) {
|
|
659
|
+
const r = computeAsymmetricRoute(sourceNode, targetNode);
|
|
660
|
+
sourceWorld = r.source;
|
|
661
|
+
targetWorld = r.target;
|
|
662
|
+
c1 = r.c1;
|
|
663
|
+
c2 = r.c2;
|
|
599
664
|
} else {
|
|
600
665
|
const sourceNormal = sourceNode && isAttached(edge.source) ? rotateVecByAngle(
|
|
601
666
|
sideNormalLocal(
|
|
@@ -1048,7 +1113,6 @@ var COMPOSITE = /* @__PURE__ */ new Set([
|
|
|
1048
1113
|
var isCompositePrimitive = (type) => COMPOSITE.has(type);
|
|
1049
1114
|
var isDrawablePrimitive = (type) => ATOMIC.has(type) || COMPOSITE.has(type);
|
|
1050
1115
|
var PLAIN_RECT_CORNER_THRESHOLD_PX = 1.5;
|
|
1051
|
-
var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
|
|
1052
1116
|
var LAYERED_OFFSET = 12;
|
|
1053
1117
|
var drawShape = (ctx, node, scale, theme, opts) => {
|
|
1054
1118
|
if (!isDrawablePrimitive(node.type)) return;
|
|
@@ -1067,7 +1131,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
|
|
|
1067
1131
|
const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
|
|
1068
1132
|
const stroke = resolveColor(style, "strokeColor", DEFAULT_STYLE.strokeColor, theme);
|
|
1069
1133
|
const fillVisible = !isFullyTransparent(fill);
|
|
1070
|
-
const strokeVisible = strokeWidth > 0 &&
|
|
1134
|
+
const strokeVisible = strokeWidth > 0 && !isFullyTransparent(stroke);
|
|
1071
1135
|
if (!fillVisible && !strokeVisible) return;
|
|
1072
1136
|
const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
|
|
1073
1137
|
switch (type) {
|
|
@@ -1104,7 +1168,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
|
|
|
1104
1168
|
}
|
|
1105
1169
|
if (strokeVisible && !opts?.skipStroke) {
|
|
1106
1170
|
ctx.strokeStyle = stroke;
|
|
1107
|
-
ctx.lineWidth = strokeWidth;
|
|
1171
|
+
ctx.lineWidth = Math.max(strokeWidth, 1 / scale);
|
|
1108
1172
|
ctx.setLineDash(dashPatternFor(style?.strokeStyle, strokeWidth));
|
|
1109
1173
|
ctx.stroke();
|
|
1110
1174
|
}
|
|
@@ -1480,13 +1544,16 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
|
|
|
1480
1544
|
const isDark = theme?.("mode") === "dark";
|
|
1481
1545
|
const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
|
|
1482
1546
|
const strokeColor = deriveRoughStrokeColor(rawStroke, fill, isDark);
|
|
1483
|
-
const
|
|
1484
|
-
if (
|
|
1547
|
+
const rawStrokeWidth = resolveStrokeWidth(style, theme);
|
|
1548
|
+
if (rawStrokeWidth <= 0) return;
|
|
1485
1549
|
const roughness = style?.roughness ?? 0;
|
|
1486
1550
|
if (roughness <= 0) return;
|
|
1551
|
+
const isNoBorderIntent = isFullyTransparent(rawStroke);
|
|
1552
|
+
const effectiveStrokeStyle = isNoBorderIntent ? "solid" : style?.strokeStyle ?? "solid";
|
|
1553
|
+
const strokeWidth = isNoBorderIntent ? DEFAULT_STYLE.strokeWidth : rawStrokeWidth;
|
|
1487
1554
|
const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
|
|
1488
1555
|
const radius = Math.max(0, Math.min(cornerRadius, w / 2, h / 2));
|
|
1489
|
-
const dash = dashPatternFor(
|
|
1556
|
+
const dash = dashPatternFor(effectiveStrokeStyle, strokeWidth);
|
|
1490
1557
|
const detail = apparentDetail(Math.max(w, h), scale);
|
|
1491
1558
|
const cacheKey = [
|
|
1492
1559
|
type,
|
|
@@ -1495,7 +1562,7 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
|
|
|
1495
1562
|
radius.toFixed(1),
|
|
1496
1563
|
strokeColor,
|
|
1497
1564
|
strokeWidth.toFixed(2),
|
|
1498
|
-
|
|
1565
|
+
effectiveStrokeStyle,
|
|
1499
1566
|
roughness.toFixed(2),
|
|
1500
1567
|
seed,
|
|
1501
1568
|
detail.curveStepCount,
|
|
@@ -1603,7 +1670,7 @@ var buildPath = (type, x, y, w, h, radius) => {
|
|
|
1603
1670
|
};
|
|
1604
1671
|
|
|
1605
1672
|
// src/text/tokens.ts
|
|
1606
|
-
var INLINE_PATTERN = /(
|
|
1673
|
+
var INLINE_PATTERN = /(\$\$[^\n]+?\$\$|\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
|
|
1607
1674
|
var HR_LINE_PATTERN = /^[ \t]*---[ \t]*$/;
|
|
1608
1675
|
var DOUBLE_HR_LINE_PATTERN = /^[ \t]*===[ \t]*$/;
|
|
1609
1676
|
var transformSymbols = (value) => value.replace(/<=>|<->|<-|->|\[\]|\[[vx]\]/gi, (match) => {
|
|
@@ -1641,8 +1708,8 @@ var tokenizeInline = (segment) => {
|
|
|
1641
1708
|
tokens.push({ type: "link", content: transformSymbols(match.slice(1, splitIndex)) });
|
|
1642
1709
|
} else if (match.startsWith("`") && match.endsWith("`")) {
|
|
1643
1710
|
tokens.push({ type: "code", content: match.slice(1, -1) });
|
|
1644
|
-
} else if (match.startsWith("
|
|
1645
|
-
tokens.push({ type: "math", content: match.slice(
|
|
1711
|
+
} else if (match.startsWith("$$") && match.endsWith("$$")) {
|
|
1712
|
+
tokens.push({ type: "math", content: match.slice(2, -2) });
|
|
1646
1713
|
} else {
|
|
1647
1714
|
tokens.push({ type: "text", content: transformSymbols(match) });
|
|
1648
1715
|
}
|
|
@@ -2646,7 +2713,7 @@ var DEFAULT_EDGE_STYLE = {
|
|
|
2646
2713
|
sourceArrowhead: "none",
|
|
2647
2714
|
targetArrowhead: "arrow-filled"
|
|
2648
2715
|
};
|
|
2649
|
-
var
|
|
2716
|
+
var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
|
|
2650
2717
|
var ARROWHEAD_VISIBILITY_THRESHOLD_PX = 2;
|
|
2651
2718
|
var samplePaintStride = (scale) => {
|
|
2652
2719
|
if (scale < 0.15) return 8;
|
|
@@ -2660,7 +2727,7 @@ var drawEdge = (ctx, edge, geom, sourceNode, targetNode, scale, theme, opts) =>
|
|
|
2660
2727
|
if (samples.length < 2) return;
|
|
2661
2728
|
const style = edge.style;
|
|
2662
2729
|
const strokeWidth = typeof style?.strokeWidth === "number" ? style.strokeWidth : theme?.("strokeWidth") ?? DEFAULT_EDGE_STYLE.strokeWidth;
|
|
2663
|
-
if (strokeWidth * scale <
|
|
2730
|
+
if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX) return;
|
|
2664
2731
|
const strokeColor = typeof style?.strokeColor === "string" ? style.strokeColor : theme?.("edge.strokeColor") ?? DEFAULT_EDGE_STYLE.strokeColor;
|
|
2665
2732
|
const sourceArrowhead = style?.sourceArrowhead ?? DEFAULT_EDGE_STYLE.sourceArrowhead;
|
|
2666
2733
|
const targetArrowhead = style?.targetArrowhead ?? DEFAULT_EDGE_STYLE.targetArrowhead;
|
|
@@ -3376,6 +3443,8 @@ var idleInteractionState = () => ({
|
|
|
3376
3443
|
resizeHandle: null,
|
|
3377
3444
|
resizeLockAspect: false,
|
|
3378
3445
|
resizeFromCenter: false,
|
|
3446
|
+
resizeDraft: null,
|
|
3447
|
+
midpointDraft: null,
|
|
3379
3448
|
marqueeRect: null,
|
|
3380
3449
|
marqueeAdditive: false,
|
|
3381
3450
|
draftEdge: null,
|
|
@@ -4952,7 +5021,9 @@ var createRenderer = (opts) => {
|
|
|
4952
5021
|
const scale = camera.z * surface.dpr;
|
|
4953
5022
|
const interaction = store.getInteractionState();
|
|
4954
5023
|
const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
|
|
4955
|
-
const
|
|
5024
|
+
const baseExcludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
|
|
5025
|
+
const midpointEdgeId = interaction.midpointDraft?.edgeId ?? null;
|
|
5026
|
+
const excludedEdges = midpointEdgeId !== null ? /* @__PURE__ */ new Set([...baseExcludedEdges ?? [], midpointEdgeId]) : baseExcludedEdges;
|
|
4956
5027
|
paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
|
|
4957
5028
|
const visible = visibleNodes(camera, viewport);
|
|
4958
5029
|
const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
|
|
@@ -5364,6 +5435,23 @@ var createRenderer = (opts) => {
|
|
|
5364
5435
|
}
|
|
5365
5436
|
}
|
|
5366
5437
|
}
|
|
5438
|
+
if (interaction.midpointDraft) {
|
|
5439
|
+
const { edgeId, control } = interaction.midpointDraft;
|
|
5440
|
+
const edge = store.getEdge(edgeId);
|
|
5441
|
+
if (edge) {
|
|
5442
|
+
const draftEdge = { ...edge, control };
|
|
5443
|
+
const geom = computeEdgeGeometry(draftEdge, (id) => store.getNode(id));
|
|
5444
|
+
if (geom) {
|
|
5445
|
+
const sourceNode = geom.sourceNodeId ? store.getNode(geom.sourceNodeId) ?? null : null;
|
|
5446
|
+
const targetNode = geom.targetNodeId ? store.getNode(geom.targetNodeId) ?? null : null;
|
|
5447
|
+
drawEdge(ctx, draftEdge, geom, sourceNode, targetNode, scale, theme, {
|
|
5448
|
+
zoom: camera.z,
|
|
5449
|
+
dpr: interactiveSurface.dpr,
|
|
5450
|
+
isMoving: true
|
|
5451
|
+
});
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5367
5455
|
const selection = store.getSelection();
|
|
5368
5456
|
const selectedNodeIds = [];
|
|
5369
5457
|
const selectedEdgeIds = [];
|
|
@@ -5436,7 +5524,12 @@ var createRenderer = (opts) => {
|
|
|
5436
5524
|
y: orig.y + interaction.dragDelta.y
|
|
5437
5525
|
});
|
|
5438
5526
|
} else {
|
|
5439
|
-
|
|
5527
|
+
const d = interaction.resizeDraft;
|
|
5528
|
+
if (d) {
|
|
5529
|
+
m.set(orig.id, { ...live, x: d.x, y: d.y, w: d.w, h: d.h, angle: d.angle });
|
|
5530
|
+
} else {
|
|
5531
|
+
m.set(orig.id, live);
|
|
5532
|
+
}
|
|
5440
5533
|
}
|
|
5441
5534
|
}
|
|
5442
5535
|
return m;
|