@markdy/renderer-dom 1.0.8 → 1.0.9
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.ts +11 -0
- package/dist/index.js +414 -165
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -97,6 +97,17 @@ interface GifDiagramExportOptions extends SvgExportOptions {
|
|
|
97
97
|
}
|
|
98
98
|
declare function exportDiagramAsGif(container: HTMLElement, timeline: TimelineController, options?: GifDiagramExportOptions): Promise<Blob>;
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* packages/renderer-dom/src/export/png-exporter.ts
|
|
102
|
+
* High-DPI raster PNG export with 2x retina scaling.
|
|
103
|
+
* Zero external dependencies.
|
|
104
|
+
*
|
|
105
|
+
* Fix: Inline all external resources (images, fonts) as base64 data URIs
|
|
106
|
+
* before drawing the SVG to canvas. A foreignObject-wrapped SVG taints the
|
|
107
|
+
* canvas whenever it references any external URL, so every resource must be
|
|
108
|
+
* inlined first.
|
|
109
|
+
*/
|
|
110
|
+
|
|
100
111
|
interface PngExportOptions extends SvgExportOptions {
|
|
101
112
|
pixelRatio?: number;
|
|
102
113
|
}
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ function countPathIntersections(points, obstacles) {
|
|
|
49
49
|
function round1(n) {
|
|
50
50
|
return Math.round(n * 10) / 10;
|
|
51
51
|
}
|
|
52
|
-
function toPathD(points, cornerRadius =
|
|
52
|
+
function toPathD(points, cornerRadius = 14) {
|
|
53
53
|
if (points.length < 2) return "";
|
|
54
54
|
if (points.length === 2) {
|
|
55
55
|
const [a, b] = points;
|
|
@@ -73,7 +73,7 @@ function toPathD(points, cornerRadius = 8) {
|
|
|
73
73
|
const dy2 = next.y - cur.y;
|
|
74
74
|
const len1 = Math.hypot(dx1, dy1);
|
|
75
75
|
const len2 = Math.hypot(dx2, dy2);
|
|
76
|
-
if (len1 < 0.
|
|
76
|
+
if (len1 < 0.5 || len2 < 0.5) {
|
|
77
77
|
parts.push(`L ${round1(cur.x)} ${round1(cur.y)}`);
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
@@ -89,14 +89,14 @@ function toPathD(points, cornerRadius = 8) {
|
|
|
89
89
|
}
|
|
90
90
|
function selfLoopPath(rect) {
|
|
91
91
|
const top = rect.y;
|
|
92
|
-
const left = rect.x + rect.width * 0.
|
|
93
|
-
const right = rect.x + rect.width * 0.
|
|
94
|
-
const apex = top -
|
|
92
|
+
const left = rect.x + rect.width * 0.3;
|
|
93
|
+
const right = rect.x + rect.width * 0.7;
|
|
94
|
+
const apex = top - 40;
|
|
95
95
|
return [
|
|
96
|
-
{ x: left, y: top },
|
|
97
|
-
{ x: left, y: apex },
|
|
98
|
-
{ x: right, y: apex },
|
|
99
|
-
{ x: right, y: top }
|
|
96
|
+
{ x: round1(left), y: round1(top) },
|
|
97
|
+
{ x: round1(left), y: round1(apex) },
|
|
98
|
+
{ x: round1(right), y: round1(apex) },
|
|
99
|
+
{ x: round1(right), y: round1(top) }
|
|
100
100
|
];
|
|
101
101
|
}
|
|
102
102
|
function polylineLength(points) {
|
|
@@ -109,7 +109,7 @@ function polylineLength(points) {
|
|
|
109
109
|
function segmentLength(a, b) {
|
|
110
110
|
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
111
111
|
}
|
|
112
|
-
var LABEL_BOX_HEIGHT =
|
|
112
|
+
var LABEL_BOX_HEIGHT = 20;
|
|
113
113
|
function rectsOverlap(a, b) {
|
|
114
114
|
return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
|
|
115
115
|
}
|
|
@@ -134,9 +134,9 @@ function placeFlowLabel(points, textWidth, obstacles, bounds) {
|
|
|
134
134
|
const horizontal = Math.abs(a.x - b.x) >= Math.abs(a.y - b.y);
|
|
135
135
|
const half = textWidth / 2;
|
|
136
136
|
const halfH = LABEL_BOX_HEIGHT / 2;
|
|
137
|
-
const pad =
|
|
138
|
-
const base = horizontal ?
|
|
139
|
-
const step = horizontal ? LABEL_BOX_HEIGHT +
|
|
137
|
+
const pad = 10;
|
|
138
|
+
const base = horizontal ? 16 : half + 14;
|
|
139
|
+
const step = horizontal ? LABEL_BOX_HEIGHT + 6 : textWidth + 14;
|
|
140
140
|
const order = horizontal ? [-1, 1] : [1, -1];
|
|
141
141
|
const offsets = [];
|
|
142
142
|
for (let k = 0; k < 8; k++) {
|
|
@@ -166,7 +166,7 @@ function clamp(n, min, max) {
|
|
|
166
166
|
return Math.min(max, Math.max(min, n));
|
|
167
167
|
}
|
|
168
168
|
function clampPointToScene(point, bounds) {
|
|
169
|
-
const pad =
|
|
169
|
+
const pad = 16;
|
|
170
170
|
return {
|
|
171
171
|
x: round1(clamp(point.x, pad, bounds.width - pad)),
|
|
172
172
|
y: round1(clamp(point.y, pad, bounds.height - pad))
|
|
@@ -175,7 +175,7 @@ function clampPointToScene(point, bounds) {
|
|
|
175
175
|
function laneOffset(lane) {
|
|
176
176
|
if (lane <= 0) return 0;
|
|
177
177
|
const step = Math.ceil(lane / 2);
|
|
178
|
-
return (lane % 2 === 1 ? 1 : -1) * step *
|
|
178
|
+
return (lane % 2 === 1 ? 1 : -1) * step * 16;
|
|
179
179
|
}
|
|
180
180
|
function routeLength(points) {
|
|
181
181
|
let total = 0;
|
|
@@ -196,26 +196,50 @@ function routeBends(points) {
|
|
|
196
196
|
}
|
|
197
197
|
return bends;
|
|
198
198
|
}
|
|
199
|
+
function cleanCollinearPoints(points) {
|
|
200
|
+
if (points.length <= 2) return points;
|
|
201
|
+
const result = [points[0]];
|
|
202
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
203
|
+
const prev = result[result.length - 1];
|
|
204
|
+
const cur = points[i];
|
|
205
|
+
const next = points[i + 1];
|
|
206
|
+
const isCollinearX = Math.abs(prev.x - cur.x) < 0.01 && Math.abs(cur.x - next.x) < 0.01;
|
|
207
|
+
const isCollinearY = Math.abs(prev.y - cur.y) < 0.01 && Math.abs(cur.y - next.y) < 0.01;
|
|
208
|
+
const isDuplicate = Math.abs(prev.x - cur.x) < 0.01 && Math.abs(prev.y - cur.y) < 0.01;
|
|
209
|
+
if (!isCollinearX && !isCollinearY && !isDuplicate) {
|
|
210
|
+
result.push(cur);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
result.push(points[points.length - 1]);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
199
216
|
function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
200
217
|
const laneShift = laneOffset(lane);
|
|
201
218
|
const sourceCenter = rectCenter(sourceRect);
|
|
202
219
|
const targetCenter = rectCenter(targetRect);
|
|
203
220
|
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
221
|
+
const sourceRight = targetCenter.x >= sourceCenter.x;
|
|
222
|
+
const targetLeft = targetCenter.x >= sourceCenter.x;
|
|
223
|
+
const sourceDown = targetCenter.y >= sourceCenter.y;
|
|
224
|
+
const targetUp = targetCenter.y >= sourceCenter.y;
|
|
204
225
|
const source = horizontalPrimary ? {
|
|
205
|
-
x:
|
|
206
|
-
y: clamp(sourceCenter.y + laneShift, sourceRect.y1 +
|
|
226
|
+
x: sourceRight ? sourceRect.x2 : sourceRect.x1,
|
|
227
|
+
y: clamp(sourceCenter.y + laneShift, sourceRect.y1 + 16, sourceRect.y2 - 16)
|
|
207
228
|
} : {
|
|
208
|
-
x: clamp(sourceCenter.x + laneShift, sourceRect.x1 +
|
|
209
|
-
y:
|
|
229
|
+
x: clamp(sourceCenter.x + laneShift, sourceRect.x1 + 20, sourceRect.x2 - 20),
|
|
230
|
+
y: sourceDown ? sourceRect.y2 : sourceRect.y1
|
|
210
231
|
};
|
|
211
232
|
const target = horizontalPrimary ? {
|
|
212
|
-
x:
|
|
213
|
-
y: clamp(targetCenter.y + laneShift, targetRect.y1 +
|
|
233
|
+
x: targetLeft ? targetRect.x1 : targetRect.x2,
|
|
234
|
+
y: clamp(targetCenter.y + laneShift, targetRect.y1 + 16, targetRect.y2 - 16)
|
|
214
235
|
} : {
|
|
215
|
-
x: clamp(targetCenter.x + laneShift, targetRect.x1 +
|
|
216
|
-
y:
|
|
236
|
+
x: clamp(targetCenter.x + laneShift, targetRect.x1 + 20, targetRect.x2 - 20),
|
|
237
|
+
y: targetUp ? targetRect.y1 : targetRect.y2
|
|
217
238
|
};
|
|
218
|
-
const
|
|
239
|
+
const stubLen = 18;
|
|
240
|
+
const sStub = horizontalPrimary ? { x: source.x + (sourceRight ? stubLen : -stubLen), y: source.y } : { x: source.x, y: source.y + (sourceDown ? stubLen : -stubLen) };
|
|
241
|
+
const tStub = horizontalPrimary ? { x: target.x + (targetLeft ? -stubLen : stubLen), y: target.y } : { x: target.x, y: target.y + (targetUp ? -stubLen : stubLen) };
|
|
242
|
+
const infl = obstacles.map((o) => inflateRect(o, 10));
|
|
219
243
|
const candidates = [];
|
|
220
244
|
if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
|
|
221
245
|
candidates.push([source, target]);
|
|
@@ -223,36 +247,39 @@ function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
|
223
247
|
const midX = round1((source.x + target.x) / 2 + laneShift);
|
|
224
248
|
const midY = round1((source.y + target.y) / 2 + laneShift);
|
|
225
249
|
candidates.push(
|
|
250
|
+
[source, sStub, { x: midX, y: source.y }, { x: midX, y: target.y }, tStub, target],
|
|
251
|
+
[source, sStub, { x: source.x, y: midY }, { x: target.x, y: midY }, tStub, target],
|
|
226
252
|
[source, { x: midX, y: source.y }, { x: midX, y: target.y }, target],
|
|
227
253
|
[source, { x: source.x, y: midY }, { x: target.x, y: midY }, target]
|
|
228
254
|
);
|
|
229
255
|
const minObstacleY = infl.length ? Math.min(...infl.map((o) => o.y1)) : Math.min(source.y, target.y);
|
|
230
256
|
const maxObstacleY = infl.length ? Math.max(...infl.map((o) => o.y2)) : Math.max(source.y, target.y);
|
|
231
|
-
const topLane = Math.max(
|
|
232
|
-
const bottomLane = Math.min(bounds.height -
|
|
257
|
+
const topLane = Math.max(20, minObstacleY - 28 - lane * 14);
|
|
258
|
+
const bottomLane = Math.min(bounds.height - 20, maxObstacleY + 28 + lane * 14);
|
|
233
259
|
candidates.push(
|
|
234
|
-
[source, { x:
|
|
235
|
-
[source, { x:
|
|
260
|
+
[source, sStub, { x: sStub.x, y: topLane }, { x: tStub.x, y: topLane }, tStub, target],
|
|
261
|
+
[source, sStub, { x: sStub.x, y: bottomLane }, { x: tStub.x, y: bottomLane }, tStub, target]
|
|
236
262
|
);
|
|
237
263
|
const minObstacleX = infl.length ? Math.min(...infl.map((o) => o.x1)) : Math.min(source.x, target.x);
|
|
238
264
|
const maxObstacleX = infl.length ? Math.max(...infl.map((o) => o.x2)) : Math.max(source.x, target.x);
|
|
239
|
-
const leftLane = Math.max(
|
|
240
|
-
const rightLane = Math.min(bounds.width -
|
|
265
|
+
const leftLane = Math.max(20, minObstacleX - 28 - lane * 14);
|
|
266
|
+
const rightLane = Math.min(bounds.width - 20, maxObstacleX + 28 + lane * 14);
|
|
241
267
|
candidates.push(
|
|
242
|
-
[source, { x: leftLane, y:
|
|
243
|
-
[source, { x: rightLane, y:
|
|
268
|
+
[source, sStub, { x: leftLane, y: sStub.y }, { x: leftLane, y: tStub.y }, tStub, target],
|
|
269
|
+
[source, sStub, { x: rightLane, y: sStub.y }, { x: rightLane, y: tStub.y }, tStub, target]
|
|
244
270
|
);
|
|
245
271
|
let best = candidates[0];
|
|
246
272
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
247
273
|
for (const candidate of candidates) {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
274
|
+
const cleaned = cleanCollinearPoints(candidate);
|
|
275
|
+
const hits = countPathIntersections(cleaned, infl);
|
|
276
|
+
const score = hits * 1e5 + routeBends(cleaned) * 800 + routeLength(cleaned);
|
|
250
277
|
if (score < bestScore) {
|
|
251
|
-
best =
|
|
278
|
+
best = cleaned;
|
|
252
279
|
bestScore = score;
|
|
253
280
|
}
|
|
254
281
|
}
|
|
255
|
-
return best.map((point) => clampPointToScene(point, bounds));
|
|
282
|
+
return cleanCollinearPoints(best.map((point) => clampPointToScene(point, bounds)));
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
// src/edges.ts
|
|
@@ -324,23 +351,25 @@ function ensureDefs(svg, theme, id) {
|
|
|
324
351
|
for (const [kind, color] of Object.entries(theme.edges)) {
|
|
325
352
|
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
326
353
|
arrow.setAttribute("id", `${id}-arrow-${kind}`);
|
|
327
|
-
arrow.setAttribute("viewBox", "0 0
|
|
328
|
-
arrow.setAttribute("refX", "
|
|
329
|
-
arrow.setAttribute("refY", "
|
|
330
|
-
arrow.setAttribute("markerWidth", "
|
|
331
|
-
arrow.setAttribute("markerHeight", "
|
|
354
|
+
arrow.setAttribute("viewBox", "0 0 12 12");
|
|
355
|
+
arrow.setAttribute("refX", "9.5");
|
|
356
|
+
arrow.setAttribute("refY", "6");
|
|
357
|
+
arrow.setAttribute("markerWidth", "8");
|
|
358
|
+
arrow.setAttribute("markerHeight", "8");
|
|
332
359
|
arrow.setAttribute("orient", "auto-start-reverse");
|
|
333
360
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
334
361
|
if (kind === "response") {
|
|
335
|
-
path.setAttribute("d", "M
|
|
362
|
+
path.setAttribute("d", "M 2.5 2.5 L 9.5 6 L 2.5 9.5");
|
|
336
363
|
path.setAttribute("fill", "none");
|
|
337
364
|
path.setAttribute("stroke", color);
|
|
338
|
-
path.setAttribute("stroke-width", "1.
|
|
365
|
+
path.setAttribute("stroke-width", "1.6");
|
|
366
|
+
path.setAttribute("stroke-linecap", "round");
|
|
367
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
339
368
|
} else if (kind === "event") {
|
|
340
|
-
path.setAttribute("d", "M
|
|
369
|
+
path.setAttribute("d", "M 6 2.5 A 3.5 3.5 0 1 1 6 9.5 A 3.5 3.5 0 1 1 6 2.5");
|
|
341
370
|
path.setAttribute("fill", color);
|
|
342
371
|
} else {
|
|
343
|
-
path.setAttribute("d", "M
|
|
372
|
+
path.setAttribute("d", "M 2 2.5 L 10 6 L 2 9.5 L 4 6 Z");
|
|
344
373
|
path.setAttribute("fill", color);
|
|
345
374
|
}
|
|
346
375
|
arrow.appendChild(path);
|
|
@@ -390,40 +419,43 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
390
419
|
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
391
420
|
}
|
|
392
421
|
if (kind !== "dependency") {
|
|
393
|
-
path.style.filter = `drop-shadow(0 0
|
|
422
|
+
path.style.filter = `drop-shadow(0 0 4px ${translucentColor(color, "44")})`;
|
|
394
423
|
}
|
|
395
424
|
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
396
|
-
dot.setAttribute("r", "
|
|
425
|
+
dot.setAttribute("r", "4");
|
|
397
426
|
dot.setAttribute("fill", color);
|
|
398
427
|
dot.style.opacity = "0";
|
|
399
|
-
dot.style.filter = `drop-shadow(0 0
|
|
428
|
+
dot.style.filter = `drop-shadow(0 0 6px ${color}) drop-shadow(0 0 12px ${color}88)`;
|
|
400
429
|
group.append(path, dot);
|
|
401
430
|
let labelEl;
|
|
402
431
|
let labelRect;
|
|
403
432
|
if (label) {
|
|
404
|
-
const textWidth = label.length * 6.
|
|
433
|
+
const textWidth = label.length * 6.8 + 14;
|
|
405
434
|
const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
|
|
406
435
|
labelRect = placement.rect;
|
|
407
436
|
const plate = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
408
|
-
const padX =
|
|
437
|
+
const padX = 8;
|
|
409
438
|
const halfW = textWidth / 2;
|
|
410
439
|
plate.setAttribute("x", String(placement.x - halfW - padX));
|
|
411
|
-
plate.setAttribute("y", String(placement.y -
|
|
440
|
+
plate.setAttribute("y", String(placement.y - 10));
|
|
412
441
|
plate.setAttribute("width", String(textWidth + padX * 2));
|
|
413
|
-
plate.setAttribute("height", "
|
|
442
|
+
plate.setAttribute("height", "20");
|
|
414
443
|
plate.setAttribute("rx", "6");
|
|
415
444
|
plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
|
|
416
|
-
plate.setAttribute("fill-opacity", "0.
|
|
417
|
-
plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border}
|
|
445
|
+
plate.setAttribute("fill-opacity", "0.96");
|
|
446
|
+
plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border} 70%, transparent)`);
|
|
418
447
|
plate.setAttribute("stroke-width", "1");
|
|
419
448
|
plate.style.opacity = "0";
|
|
449
|
+
plate.style.filter = "drop-shadow(0 1px 3px rgba(0,0,0,0.12))";
|
|
420
450
|
group.appendChild(plate);
|
|
421
451
|
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
422
452
|
labelEl.setAttribute("x", String(placement.x));
|
|
423
|
-
labelEl.setAttribute("y", String(placement.y));
|
|
453
|
+
labelEl.setAttribute("y", String(placement.y + 0.5));
|
|
424
454
|
labelEl.setAttribute("text-anchor", "middle");
|
|
425
455
|
labelEl.setAttribute("dominant-baseline", "middle");
|
|
426
456
|
labelEl.setAttribute("font-size", "11");
|
|
457
|
+
labelEl.setAttribute("font-weight", "500");
|
|
458
|
+
labelEl.setAttribute("letter-spacing", "0.02em");
|
|
427
459
|
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
428
460
|
labelEl.setAttribute("fill", theme.text);
|
|
429
461
|
labelEl.textContent = label;
|
|
@@ -492,20 +524,35 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
492
524
|
path.style.strokeDasharray = String(pathLen);
|
|
493
525
|
path.style.strokeDashoffset = String(pathLen);
|
|
494
526
|
}
|
|
495
|
-
const drawMs = Math.min(
|
|
496
|
-
anims.push(
|
|
527
|
+
const drawMs = Math.min(260, durMs * 0.65);
|
|
528
|
+
anims.push(
|
|
529
|
+
group.animate(
|
|
530
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
531
|
+
{ duration: 140, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
532
|
+
)
|
|
533
|
+
);
|
|
497
534
|
if (runtime.drawReveal) {
|
|
498
535
|
anims.push(
|
|
499
536
|
path.animate(
|
|
500
537
|
[{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
|
|
501
|
-
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "
|
|
538
|
+
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
502
539
|
)
|
|
503
540
|
);
|
|
504
541
|
}
|
|
505
542
|
if (label) {
|
|
506
|
-
anims.push(
|
|
543
|
+
anims.push(
|
|
544
|
+
label.animate(
|
|
545
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
546
|
+
{ duration: 200, delay: startMs + 90, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
547
|
+
)
|
|
548
|
+
);
|
|
507
549
|
if (runtime.labelPlate) {
|
|
508
|
-
anims.push(
|
|
550
|
+
anims.push(
|
|
551
|
+
runtime.labelPlate.animate(
|
|
552
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
553
|
+
{ duration: 200, delay: startMs + 90, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
554
|
+
)
|
|
555
|
+
);
|
|
509
556
|
}
|
|
510
557
|
}
|
|
511
558
|
if (points.length >= 2) {
|
|
@@ -514,7 +561,7 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
514
561
|
duration: Math.max(drawMs, durMs),
|
|
515
562
|
delay: startMs,
|
|
516
563
|
fill: "forwards",
|
|
517
|
-
easing: "
|
|
564
|
+
easing: "cubic-bezier(0.2, 0.85, 0.4, 1)"
|
|
518
565
|
})
|
|
519
566
|
);
|
|
520
567
|
}
|
|
@@ -606,14 +653,14 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
606
653
|
if (el) {
|
|
607
654
|
anims.push(
|
|
608
655
|
el.animate(
|
|
609
|
-
[{ opacity: 0, transform: "translateY(
|
|
610
|
-
{ duration: durMs, delay, fill: "forwards", easing: "
|
|
656
|
+
[{ opacity: 0, transform: "translateY(10px) scale(0.98)" }, { opacity: 1, transform: "translateY(0) scale(1)" }],
|
|
657
|
+
{ duration: durMs, delay, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
611
658
|
)
|
|
612
659
|
);
|
|
613
660
|
return;
|
|
614
661
|
}
|
|
615
662
|
const runtime = edgeRuntimes.get(id);
|
|
616
|
-
if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "
|
|
663
|
+
if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }));
|
|
617
664
|
});
|
|
618
665
|
continue;
|
|
619
666
|
}
|
|
@@ -644,7 +691,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
644
691
|
{ filter: `drop-shadow(0 0 ${Math.max(4, 4 + strength * 5)}px ${glowColor}) brightness(${peak})` },
|
|
645
692
|
{ filter: "brightness(1)" }
|
|
646
693
|
],
|
|
647
|
-
{ duration: durMs, delay: startMs, fill: "none", easing: "
|
|
694
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
648
695
|
)
|
|
649
696
|
);
|
|
650
697
|
continue;
|
|
@@ -666,7 +713,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
666
713
|
{ transform: `scale(${zoom})`, filter: "drop-shadow(0 0 7px var(--md-accent))" },
|
|
667
714
|
{ transform: "scale(1)", filter: "none" }
|
|
668
715
|
],
|
|
669
|
-
{ duration: durMs, delay: startMs, fill: "none", easing: "
|
|
716
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
670
717
|
)
|
|
671
718
|
);
|
|
672
719
|
continue;
|
|
@@ -691,7 +738,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
691
738
|
anims.push(
|
|
692
739
|
scene.animate(
|
|
693
740
|
[{ transform: cameraTransform }, { transform: nextTransform }],
|
|
694
|
-
{ duration: durMs, delay: startMs, fill: "forwards", easing: "
|
|
741
|
+
{ duration: durMs, delay: startMs, fill: "forwards", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
695
742
|
)
|
|
696
743
|
);
|
|
697
744
|
cameraTransform = nextTransform;
|
|
@@ -718,6 +765,19 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds,
|
|
|
718
765
|
if (edgeId) edgeRuntimes.set(edgeId, runtime);
|
|
719
766
|
}
|
|
720
767
|
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
768
|
+
const toEl = nodeEls.get(seg.to);
|
|
769
|
+
if (toEl) {
|
|
770
|
+
anims.push(
|
|
771
|
+
toEl.animate(
|
|
772
|
+
[
|
|
773
|
+
{ transform: "scale(1)", filter: "none" },
|
|
774
|
+
{ transform: "scale(1.02)", filter: `drop-shadow(0 0 8px ${translucentColor(runtime.color, "66")})` },
|
|
775
|
+
{ transform: "scale(1)", filter: "none" }
|
|
776
|
+
],
|
|
777
|
+
{ duration: 280, delay: startMs + durMs * 0.75, fill: "none", easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
778
|
+
)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
721
781
|
}
|
|
722
782
|
}
|
|
723
783
|
for (const a of anims) a.pause();
|
|
@@ -830,6 +890,8 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
830
890
|
const pos = positionForAnnotation(ann.position, bounds, index);
|
|
831
891
|
const textEl = doc.createElement("div");
|
|
832
892
|
textEl.className = "markdy-annotation";
|
|
893
|
+
textEl.dataset.visible = "1";
|
|
894
|
+
if (ann.intent) textEl.dataset.intent = ann.intent;
|
|
833
895
|
textEl.textContent = ann.text;
|
|
834
896
|
textEl.style.left = `${pos.x}px`;
|
|
835
897
|
textEl.style.top = `${pos.y}px`;
|
|
@@ -837,11 +899,13 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
837
899
|
const target = ann.target ? nodeById.get(ann.target) : void 0;
|
|
838
900
|
if (!target) return;
|
|
839
901
|
const tx = target.x + target.width / 2;
|
|
840
|
-
const ty = target.y
|
|
841
|
-
const ax = pos.x +
|
|
842
|
-
const ay = pos.y +
|
|
902
|
+
const ty = target.y - 2;
|
|
903
|
+
const ax = pos.x > tx ? pos.x : pos.x + 160;
|
|
904
|
+
const ay = pos.y + 12;
|
|
843
905
|
const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
844
|
-
|
|
906
|
+
const midX = (ax + tx) / 2;
|
|
907
|
+
const midY = Math.min(ay, ty) - 16;
|
|
908
|
+
path.setAttribute("d", `M ${ax} ${ay} Q ${midX} ${midY} ${tx} ${ty}`);
|
|
845
909
|
path.setAttribute("fill", "none");
|
|
846
910
|
const intent = typeof ann.intent === "string" ? ann.intent : "neutral";
|
|
847
911
|
const leaderColor = intent === "accent" ? theme.accent : intent === "muted" ? theme.soft ?? theme.textMuted : theme.textMuted;
|
|
@@ -854,8 +918,8 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
854
918
|
const dot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
855
919
|
dot.setAttribute("cx", String(tx));
|
|
856
920
|
dot.setAttribute("cy", String(ty));
|
|
857
|
-
dot.setAttribute("r", "2");
|
|
858
|
-
dot.setAttribute("fill",
|
|
921
|
+
dot.setAttribute("r", "2.5");
|
|
922
|
+
dot.setAttribute("fill", leaderColor);
|
|
859
923
|
svg.appendChild(dot);
|
|
860
924
|
});
|
|
861
925
|
}
|
|
@@ -956,6 +1020,141 @@ function mountConstellationLayer(layer, nodes, theme, bounds) {
|
|
|
956
1020
|
}
|
|
957
1021
|
}
|
|
958
1022
|
|
|
1023
|
+
// src/radar.ts
|
|
1024
|
+
function mountRadarLayer(layer, nodes, theme, bounds) {
|
|
1025
|
+
if (nodes.length < 3) return;
|
|
1026
|
+
const doc = layer.ownerDocument;
|
|
1027
|
+
Object.assign(layer.style, {
|
|
1028
|
+
position: "absolute",
|
|
1029
|
+
inset: "0",
|
|
1030
|
+
zIndex: "38",
|
|
1031
|
+
pointerEvents: "none"
|
|
1032
|
+
});
|
|
1033
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1034
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
1035
|
+
Object.assign(svg.style, {
|
|
1036
|
+
position: "absolute",
|
|
1037
|
+
inset: "0",
|
|
1038
|
+
width: "100%",
|
|
1039
|
+
height: "100%",
|
|
1040
|
+
overflow: "visible"
|
|
1041
|
+
});
|
|
1042
|
+
layer.appendChild(svg);
|
|
1043
|
+
const centerX = bounds.width / 2;
|
|
1044
|
+
const centerY = (bounds.height + 40) / 2;
|
|
1045
|
+
const nodeCenters = nodes.map((n) => ({
|
|
1046
|
+
x: n.x + n.width / 2,
|
|
1047
|
+
y: n.y + n.height / 2
|
|
1048
|
+
}));
|
|
1049
|
+
const strokeBorder = theme?.border ?? "#cbd5e1";
|
|
1050
|
+
const strokeHairline = theme?.hairline ?? theme?.border ?? "#e2e8f0";
|
|
1051
|
+
const accentColor = theme?.accent ?? "#38bdf8";
|
|
1052
|
+
for (const nc of nodeCenters) {
|
|
1053
|
+
const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1054
|
+
line.setAttribute("x1", String(centerX));
|
|
1055
|
+
line.setAttribute("y1", String(centerY));
|
|
1056
|
+
line.setAttribute("x2", String(nc.x));
|
|
1057
|
+
line.setAttribute("y2", String(nc.y));
|
|
1058
|
+
line.setAttribute("stroke", strokeBorder);
|
|
1059
|
+
line.setAttribute("stroke-width", "1");
|
|
1060
|
+
line.setAttribute("stroke-dasharray", "4 4");
|
|
1061
|
+
line.setAttribute("opacity", "0.45");
|
|
1062
|
+
svg.appendChild(line);
|
|
1063
|
+
}
|
|
1064
|
+
const fractions = [0.33, 0.66, 1];
|
|
1065
|
+
for (const f of fractions) {
|
|
1066
|
+
const points = nodeCenters.map((nc) => {
|
|
1067
|
+
const px = centerX + (nc.x - centerX) * f;
|
|
1068
|
+
const py = centerY + (nc.y - centerY) * f;
|
|
1069
|
+
return `${px.toFixed(1)},${py.toFixed(1)}`;
|
|
1070
|
+
}).join(" ");
|
|
1071
|
+
const poly = doc.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
1072
|
+
poly.setAttribute("points", points);
|
|
1073
|
+
poly.setAttribute("fill", "none");
|
|
1074
|
+
poly.setAttribute("stroke", strokeHairline);
|
|
1075
|
+
poly.setAttribute("stroke-width", "1");
|
|
1076
|
+
poly.setAttribute("stroke-dasharray", f === 1 ? "none" : "3 3");
|
|
1077
|
+
poly.setAttribute("opacity", String(0.3 + f * 0.25));
|
|
1078
|
+
svg.appendChild(poly);
|
|
1079
|
+
}
|
|
1080
|
+
const areaPoints = nodeCenters.map((nc, idx) => {
|
|
1081
|
+
const f = 0.75 + idx % 3 * 0.12;
|
|
1082
|
+
const px = centerX + (nc.x - centerX) * f;
|
|
1083
|
+
const py = centerY + (nc.y - centerY) * f;
|
|
1084
|
+
return `${px.toFixed(1)},${py.toFixed(1)}`;
|
|
1085
|
+
}).join(" ");
|
|
1086
|
+
const area = doc.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
1087
|
+
area.setAttribute("points", areaPoints);
|
|
1088
|
+
area.setAttribute("fill", accentColor);
|
|
1089
|
+
area.setAttribute("fill-opacity", "0.08");
|
|
1090
|
+
area.setAttribute("stroke", accentColor);
|
|
1091
|
+
area.setAttribute("stroke-width", "1.5");
|
|
1092
|
+
area.setAttribute("stroke-dasharray", "4 4");
|
|
1093
|
+
area.setAttribute("opacity", "0.6");
|
|
1094
|
+
svg.appendChild(area);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/timeline.ts
|
|
1098
|
+
function mountTimelineLayer(layer, nodes, theme, bounds) {
|
|
1099
|
+
if (nodes.length === 0) return;
|
|
1100
|
+
const doc = layer.ownerDocument;
|
|
1101
|
+
Object.assign(layer.style, {
|
|
1102
|
+
position: "absolute",
|
|
1103
|
+
inset: "0",
|
|
1104
|
+
zIndex: "38",
|
|
1105
|
+
pointerEvents: "none"
|
|
1106
|
+
});
|
|
1107
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1108
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
1109
|
+
Object.assign(svg.style, {
|
|
1110
|
+
position: "absolute",
|
|
1111
|
+
inset: "0",
|
|
1112
|
+
width: "100%",
|
|
1113
|
+
height: "100%",
|
|
1114
|
+
overflow: "visible"
|
|
1115
|
+
});
|
|
1116
|
+
layer.appendChild(svg);
|
|
1117
|
+
const baselineY = (bounds.height + 40) / 2;
|
|
1118
|
+
const minX = Math.min(...nodes.map((n) => n.x)) - 20;
|
|
1119
|
+
const maxX = Math.max(...nodes.map((n) => n.x + n.width)) + 20;
|
|
1120
|
+
const strokeAxis = theme?.hairline ?? theme?.border ?? "#cbd5e1";
|
|
1121
|
+
const strokeBorder = theme?.border ?? "#cbd5e1";
|
|
1122
|
+
const accentColor = theme?.accent ?? "#38bdf8";
|
|
1123
|
+
const surfaceFill = theme?.surfaceRaised ?? theme?.surface ?? "#ffffff";
|
|
1124
|
+
const axis = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1125
|
+
axis.setAttribute("x1", String(Math.max(40, minX)));
|
|
1126
|
+
axis.setAttribute("y1", String(baselineY));
|
|
1127
|
+
axis.setAttribute("x2", String(Math.min(bounds.width - 40, maxX)));
|
|
1128
|
+
axis.setAttribute("y2", String(baselineY));
|
|
1129
|
+
axis.setAttribute("stroke", strokeAxis);
|
|
1130
|
+
axis.setAttribute("stroke-width", "2");
|
|
1131
|
+
axis.setAttribute("opacity", "0.6");
|
|
1132
|
+
svg.appendChild(axis);
|
|
1133
|
+
for (const node of nodes) {
|
|
1134
|
+
const nodeCenterX = node.x + node.width / 2;
|
|
1135
|
+
const isAbove = node.y + node.height <= baselineY + 10;
|
|
1136
|
+
const targetY = isAbove ? node.y + node.height : node.y;
|
|
1137
|
+
const stem = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1138
|
+
stem.setAttribute("x1", String(nodeCenterX));
|
|
1139
|
+
stem.setAttribute("y1", String(baselineY));
|
|
1140
|
+
stem.setAttribute("x2", String(nodeCenterX));
|
|
1141
|
+
stem.setAttribute("y2", String(targetY));
|
|
1142
|
+
stem.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
|
|
1143
|
+
stem.setAttribute("stroke-width", node.focal ? "1.5" : "1");
|
|
1144
|
+
stem.setAttribute("stroke-dasharray", node.focal ? "none" : "3 3");
|
|
1145
|
+
stem.setAttribute("opacity", node.focal ? "0.9" : "0.5");
|
|
1146
|
+
svg.appendChild(stem);
|
|
1147
|
+
const pip = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
1148
|
+
pip.setAttribute("cx", String(nodeCenterX));
|
|
1149
|
+
pip.setAttribute("cy", String(baselineY));
|
|
1150
|
+
pip.setAttribute("r", node.focal ? "5" : "3.5");
|
|
1151
|
+
pip.setAttribute("fill", node.focal ? accentColor : surfaceFill);
|
|
1152
|
+
pip.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
|
|
1153
|
+
pip.setAttribute("stroke-width", "1.5");
|
|
1154
|
+
svg.appendChild(pip);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
959
1158
|
// src/groups.ts
|
|
960
1159
|
var STYLE_ID2 = "markdy-group-boundary-styles";
|
|
961
1160
|
function ensureGroupStyles(doc) {
|
|
@@ -966,26 +1165,27 @@ function ensureGroupStyles(doc) {
|
|
|
966
1165
|
.markdy-group-boundary {
|
|
967
1166
|
position: absolute;
|
|
968
1167
|
box-sizing: border-box;
|
|
969
|
-
border: 1px dashed var(--md-group-border, color-mix(in srgb, var(--md-border)
|
|
970
|
-
border-radius:
|
|
971
|
-
background: color-mix(in srgb, var(--md-surface-raised)
|
|
1168
|
+
border: 1px dashed var(--md-group-border, color-mix(in srgb, var(--md-border) 60%, transparent));
|
|
1169
|
+
border-radius: 14px;
|
|
1170
|
+
background: color-mix(in srgb, var(--md-surface-raised) 25%, transparent);
|
|
972
1171
|
pointer-events: none;
|
|
973
1172
|
z-index: 40;
|
|
974
1173
|
}
|
|
975
1174
|
.markdy-group-boundary__label {
|
|
976
1175
|
position: absolute;
|
|
977
|
-
left:
|
|
978
|
-
top:
|
|
979
|
-
padding:
|
|
1176
|
+
left: 14px;
|
|
1177
|
+
top: 10px;
|
|
1178
|
+
padding: 3px 8px;
|
|
980
1179
|
font-size: 10px;
|
|
981
1180
|
font-weight: 600;
|
|
982
1181
|
letter-spacing: 0.08em;
|
|
983
1182
|
text-transform: uppercase;
|
|
984
1183
|
color: var(--md-text-muted);
|
|
985
|
-
background: var(--md-
|
|
986
|
-
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border)
|
|
987
|
-
border-radius:
|
|
988
|
-
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1184
|
+
background: color-mix(in srgb, var(--md-surface-raised) 80%, transparent);
|
|
1185
|
+
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
1186
|
+
border-radius: 5px;
|
|
1187
|
+
font-family: var(--md-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1188
|
+
backdrop-filter: blur(4px);
|
|
989
1189
|
}
|
|
990
1190
|
`;
|
|
991
1191
|
doc.head.appendChild(style);
|
|
@@ -1024,20 +1224,20 @@ function ensureNodeStyles(doc) {
|
|
|
1024
1224
|
.markdy-node {
|
|
1025
1225
|
position: absolute;
|
|
1026
1226
|
box-sizing: border-box;
|
|
1027
|
-
width: var(--md-node-w,
|
|
1028
|
-
height: var(--md-node-h,
|
|
1227
|
+
width: var(--md-node-w, 180px);
|
|
1228
|
+
height: var(--md-node-h, 76px);
|
|
1029
1229
|
border-radius: 12px;
|
|
1030
1230
|
background:
|
|
1031
1231
|
linear-gradient(180deg,
|
|
1032
|
-
var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised)
|
|
1232
|
+
var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised) 92%, #ffffff 8%)),
|
|
1033
1233
|
var(--md-node-surface, var(--md-surface)));
|
|
1034
1234
|
color: var(--md-text);
|
|
1035
1235
|
box-shadow:
|
|
1036
|
-
0 1px
|
|
1037
|
-
0
|
|
1038
|
-
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border)
|
|
1039
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
1040
|
-
font-family: var(--md-font-node,
|
|
1236
|
+
0 1px 2px color-mix(in srgb, var(--md-shadow, rgba(2, 6, 23, 0.4)) 40%, transparent),
|
|
1237
|
+
0 8px 20px -8px var(--md-shadow, rgba(2, 6, 23, 0.45)),
|
|
1238
|
+
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 45%, transparent)),
|
|
1239
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
1240
|
+
font-family: var(--md-font-node, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif);
|
|
1041
1241
|
overflow: hidden;
|
|
1042
1242
|
opacity: 0;
|
|
1043
1243
|
transform: translateY(8px);
|
|
@@ -1049,23 +1249,23 @@ function ensureNodeStyles(doc) {
|
|
|
1049
1249
|
}
|
|
1050
1250
|
.markdy-node[data-focused="1"] {
|
|
1051
1251
|
box-shadow:
|
|
1052
|
-
0 2px
|
|
1053
|
-
0 16px
|
|
1054
|
-
inset 0 0 0
|
|
1055
|
-
0 0 0 3px color-mix(in srgb, var(--md-accent)
|
|
1252
|
+
0 2px 6px rgba(2, 6, 23, 0.35),
|
|
1253
|
+
0 16px 36px -12px rgba(2, 6, 23, 0.65),
|
|
1254
|
+
inset 0 0 0 1.5px color-mix(in srgb, var(--md-accent) 75%, transparent),
|
|
1255
|
+
0 0 0 3px color-mix(in srgb, var(--md-accent) 22%, transparent);
|
|
1056
1256
|
}
|
|
1057
1257
|
.markdy-node[data-glow="1"] {
|
|
1058
1258
|
box-shadow:
|
|
1059
|
-
0 2px
|
|
1060
|
-
0 0 0
|
|
1061
|
-
0 0
|
|
1259
|
+
0 2px 6px rgba(2, 6, 23, 0.35),
|
|
1260
|
+
0 0 0 1.5px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 65%, transparent),
|
|
1261
|
+
0 0 24px -2px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 48%, transparent),
|
|
1062
1262
|
inset 0 0 18px -8px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 45%, transparent);
|
|
1063
1263
|
}
|
|
1064
1264
|
.markdy-node__rail { display: none; }
|
|
1065
1265
|
.markdy-node__type { display: none; }
|
|
1066
1266
|
.markdy-node__body {
|
|
1067
1267
|
height: 100%;
|
|
1068
|
-
padding: 0
|
|
1268
|
+
padding: 0 14px;
|
|
1069
1269
|
display: flex;
|
|
1070
1270
|
align-items: center;
|
|
1071
1271
|
gap: 10px;
|
|
@@ -1073,20 +1273,20 @@ function ensureNodeStyles(doc) {
|
|
|
1073
1273
|
}
|
|
1074
1274
|
.markdy-node__icon {
|
|
1075
1275
|
flex: 0 0 auto;
|
|
1076
|
-
width:
|
|
1077
|
-
height:
|
|
1078
|
-
border-radius:
|
|
1276
|
+
width: 32px;
|
|
1277
|
+
height: 32px;
|
|
1278
|
+
border-radius: 9px;
|
|
1079
1279
|
display: flex;
|
|
1080
1280
|
align-items: center;
|
|
1081
1281
|
justify-content: center;
|
|
1082
1282
|
color: var(--md-role-color, var(--md-accent));
|
|
1083
1283
|
background:
|
|
1084
1284
|
linear-gradient(180deg,
|
|
1085
|
-
color-mix(in srgb, var(--md-role-color, var(--md-accent))
|
|
1086
|
-
color-mix(in srgb, var(--md-role-color, var(--md-accent))
|
|
1285
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 22%, transparent),
|
|
1286
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 10%, transparent));
|
|
1087
1287
|
box-shadow:
|
|
1088
|
-
inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent))
|
|
1089
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
1288
|
+
inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 35%, transparent),
|
|
1289
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
|
1090
1290
|
}
|
|
1091
1291
|
.markdy-node__icon svg {
|
|
1092
1292
|
width: 17px;
|
|
@@ -1118,10 +1318,10 @@ function ensureNodeStyles(doc) {
|
|
|
1118
1318
|
flex: 1 1 auto;
|
|
1119
1319
|
min-width: 0;
|
|
1120
1320
|
padding: 0;
|
|
1121
|
-
font-size:
|
|
1321
|
+
font-size: 13.5px;
|
|
1122
1322
|
font-weight: 600;
|
|
1123
1323
|
letter-spacing: -0.01em;
|
|
1124
|
-
line-height: 1.
|
|
1324
|
+
line-height: 1.22;
|
|
1125
1325
|
display: -webkit-box;
|
|
1126
1326
|
-webkit-box-orient: vertical;
|
|
1127
1327
|
-webkit-line-clamp: 3;
|
|
@@ -1129,30 +1329,30 @@ function ensureNodeStyles(doc) {
|
|
|
1129
1329
|
overflow: hidden;
|
|
1130
1330
|
overflow-wrap: anywhere;
|
|
1131
1331
|
word-break: break-word;
|
|
1132
|
-
text-wrap:
|
|
1332
|
+
text-wrap: pretty;
|
|
1133
1333
|
}
|
|
1134
1334
|
.markdy-node__value {
|
|
1135
1335
|
flex: 0 0 auto;
|
|
1136
|
-
font-size:
|
|
1336
|
+
font-size: 17px;
|
|
1137
1337
|
font-weight: 700;
|
|
1138
1338
|
color: var(--md-ink, var(--md-text));
|
|
1139
1339
|
font-variant-numeric: tabular-nums;
|
|
1140
1340
|
}
|
|
1141
|
-
.markdy-node[data-role="client"] { border-radius:
|
|
1142
|
-
.markdy-node[data-role="data"] { border-radius: 12px 12px
|
|
1341
|
+
.markdy-node[data-role="client"] { border-radius: 14px 14px 10px 10px; }
|
|
1342
|
+
.markdy-node[data-role="data"] { border-radius: 12px 12px 18px 18px; }
|
|
1143
1343
|
.markdy-scene-title {
|
|
1144
1344
|
position: absolute;
|
|
1145
|
-
left:
|
|
1146
|
-
top:
|
|
1147
|
-
right:
|
|
1345
|
+
left: 48px;
|
|
1346
|
+
top: 28px;
|
|
1347
|
+
right: 48px;
|
|
1148
1348
|
z-index: 130;
|
|
1149
|
-
font-size:
|
|
1349
|
+
font-size: 30px;
|
|
1150
1350
|
font-weight: 700;
|
|
1151
1351
|
line-height: 1.2;
|
|
1152
1352
|
color: var(--md-text);
|
|
1153
1353
|
opacity: 0;
|
|
1154
1354
|
transform: translateY(-6px);
|
|
1155
|
-
font-family: var(--md-font-title,
|
|
1355
|
+
font-family: var(--md-font-title, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif);
|
|
1156
1356
|
}
|
|
1157
1357
|
.markdy-scene-title[data-visible="1"] {
|
|
1158
1358
|
opacity: 1;
|
|
@@ -1165,12 +1365,35 @@ function ensureNodeStyles(doc) {
|
|
|
1165
1365
|
}
|
|
1166
1366
|
.markdy-node[data-shape="pill"] {
|
|
1167
1367
|
border-radius: 999px;
|
|
1168
|
-
min-height:
|
|
1368
|
+
min-height: 54px;
|
|
1169
1369
|
}
|
|
1170
1370
|
.markdy-node[data-shape="circle"],
|
|
1171
1371
|
.markdy-node[data-kind="dot"] {
|
|
1172
1372
|
border-radius: 50%;
|
|
1173
1373
|
}
|
|
1374
|
+
.markdy-node[data-shape="circle"] {
|
|
1375
|
+
display: flex;
|
|
1376
|
+
align-items: center;
|
|
1377
|
+
justify-content: center;
|
|
1378
|
+
text-align: center;
|
|
1379
|
+
background:
|
|
1380
|
+
radial-gradient(circle at 35% 35%,
|
|
1381
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 16%, var(--md-surface) 84%),
|
|
1382
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 6%, var(--md-surface) 94%));
|
|
1383
|
+
border: 1.5px solid color-mix(in srgb, var(--md-role-color, var(--md-accent)) 50%, transparent);
|
|
1384
|
+
}
|
|
1385
|
+
.markdy-node[data-shape="circle"] .markdy-node__body {
|
|
1386
|
+
flex-direction: column;
|
|
1387
|
+
justify-content: center;
|
|
1388
|
+
padding: 16px;
|
|
1389
|
+
gap: 8px;
|
|
1390
|
+
text-align: center;
|
|
1391
|
+
}
|
|
1392
|
+
.markdy-node[data-shape="circle"] .markdy-node__label {
|
|
1393
|
+
text-align: center;
|
|
1394
|
+
-webkit-line-clamp: 4;
|
|
1395
|
+
line-clamp: 4;
|
|
1396
|
+
}
|
|
1174
1397
|
.markdy-node[data-kind="dot"] {
|
|
1175
1398
|
width: 64px;
|
|
1176
1399
|
height: 64px;
|
|
@@ -1188,11 +1411,24 @@ function ensureNodeStyles(doc) {
|
|
|
1188
1411
|
.markdy-node[data-kind="token_strip"] {
|
|
1189
1412
|
border-radius: 999px;
|
|
1190
1413
|
}
|
|
1414
|
+
.markdy-node[data-is-container="1"] {
|
|
1415
|
+
background: color-mix(in srgb, var(--md-surface-raised) 25%, transparent);
|
|
1416
|
+
border: 1.5px dashed color-mix(in srgb, var(--md-role-color, var(--md-accent)) 45%, var(--md-border) 55%);
|
|
1417
|
+
box-shadow: inset 0 0 0 1px var(--md-hairline);
|
|
1418
|
+
}
|
|
1419
|
+
.markdy-node[data-is-container="1"] .markdy-node__body {
|
|
1420
|
+
align-items: flex-start;
|
|
1421
|
+
padding: 12px 16px;
|
|
1422
|
+
}
|
|
1423
|
+
.markdy-node[data-is-container="1"][data-focal="1"] {
|
|
1424
|
+
background: color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 14%, transparent);
|
|
1425
|
+
border: 1.5px solid color-mix(in srgb, var(--md-accent) 65%, transparent);
|
|
1426
|
+
}
|
|
1191
1427
|
.markdy-node[data-shape="rounded"] {
|
|
1192
1428
|
border-radius: 16px;
|
|
1193
1429
|
}
|
|
1194
1430
|
.markdy-node[data-shape="terminal"] {
|
|
1195
|
-
border-radius:
|
|
1431
|
+
border-radius: 8px;
|
|
1196
1432
|
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1197
1433
|
box-shadow: none;
|
|
1198
1434
|
background: var(--md-node-surface, var(--md-surface));
|
|
@@ -1584,6 +1820,7 @@ function createNodeEl(node, theme, assets) {
|
|
|
1584
1820
|
el.dataset.icon = iconKeyForNode(node);
|
|
1585
1821
|
if (node.shape) el.dataset.shape = node.shape;
|
|
1586
1822
|
if (node.focal) el.dataset.focal = "1";
|
|
1823
|
+
if (node.shape === "container") el.dataset.isContainer = "1";
|
|
1587
1824
|
el.title = `${node.label} (${typeText})`;
|
|
1588
1825
|
el.setAttribute("aria-label", el.title);
|
|
1589
1826
|
const body = document.createElement("div");
|
|
@@ -1690,25 +1927,26 @@ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds)
|
|
|
1690
1927
|
lifeline.classList.add("markdy-sequence-lifeline");
|
|
1691
1928
|
lifeline.setAttribute("x1", String(x));
|
|
1692
1929
|
lifeline.setAttribute("x2", String(x));
|
|
1693
|
-
lifeline.setAttribute("y1", String(node.y + node.height +
|
|
1694
|
-
lifeline.setAttribute("y2", String(bounds.height -
|
|
1695
|
-
lifeline.setAttribute("stroke", theme.
|
|
1696
|
-
lifeline.setAttribute("stroke-width", "1");
|
|
1697
|
-
lifeline.setAttribute("stroke-dasharray", "4
|
|
1698
|
-
lifeline.setAttribute("opacity", "0.
|
|
1930
|
+
lifeline.setAttribute("y1", String(node.y + node.height + 8));
|
|
1931
|
+
lifeline.setAttribute("y2", String(bounds.height - 32));
|
|
1932
|
+
lifeline.setAttribute("stroke", theme.hairline ?? theme.border);
|
|
1933
|
+
lifeline.setAttribute("stroke-width", "1.2");
|
|
1934
|
+
lifeline.setAttribute("stroke-dasharray", "4 6");
|
|
1935
|
+
lifeline.setAttribute("opacity", "0.85");
|
|
1699
1936
|
svg.appendChild(lifeline);
|
|
1700
1937
|
}
|
|
1701
1938
|
for (const activation of activations) {
|
|
1702
1939
|
const x = centerX(activation.participant);
|
|
1703
1940
|
const bar = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1704
1941
|
bar.classList.add("markdy-sequence-activation");
|
|
1705
|
-
bar.setAttribute("x", String(x -
|
|
1942
|
+
bar.setAttribute("x", String(x - 6));
|
|
1706
1943
|
bar.setAttribute("y", String(activation.y));
|
|
1707
|
-
bar.setAttribute("width", "
|
|
1944
|
+
bar.setAttribute("width", "12");
|
|
1708
1945
|
bar.setAttribute("height", String(activation.height));
|
|
1709
|
-
bar.setAttribute("rx", "
|
|
1946
|
+
bar.setAttribute("rx", "4");
|
|
1710
1947
|
bar.setAttribute("fill", theme.accent);
|
|
1711
1948
|
bar.setAttribute("opacity", "0");
|
|
1949
|
+
bar.style.filter = `drop-shadow(0 0 6px ${theme.accent}66)`;
|
|
1712
1950
|
svg.appendChild(bar);
|
|
1713
1951
|
}
|
|
1714
1952
|
const animations = [];
|
|
@@ -1737,17 +1975,19 @@ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds)
|
|
|
1737
1975
|
if (message.label) {
|
|
1738
1976
|
const midX = (fromX + toX) / 2;
|
|
1739
1977
|
const plate = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1740
|
-
const width = message.label.length * 6.
|
|
1978
|
+
const width = message.label.length * 6.8 + 16;
|
|
1741
1979
|
plate.setAttribute("x", String(midX - width / 2));
|
|
1742
|
-
plate.setAttribute("y", String(message.y -
|
|
1980
|
+
plate.setAttribute("y", String(message.y - 25));
|
|
1743
1981
|
plate.setAttribute("width", String(width));
|
|
1744
|
-
plate.setAttribute("height", "
|
|
1745
|
-
plate.setAttribute("rx", "
|
|
1982
|
+
plate.setAttribute("height", "20");
|
|
1983
|
+
plate.setAttribute("rx", "6");
|
|
1746
1984
|
plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
|
|
1747
|
-
plate.setAttribute("
|
|
1985
|
+
plate.setAttribute("fill-opacity", "0.96");
|
|
1986
|
+
plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border} 70%, transparent)`);
|
|
1748
1987
|
plate.setAttribute("stroke-width", "1");
|
|
1988
|
+
plate.style.filter = "drop-shadow(0 1px 3px rgba(0,0,0,0.12))";
|
|
1749
1989
|
group.appendChild(plate);
|
|
1750
|
-
group.appendChild(createText(doc, midX, message.y -
|
|
1990
|
+
group.appendChild(createText(doc, midX, message.y - 14.5, message.label, theme));
|
|
1751
1991
|
}
|
|
1752
1992
|
svg.appendChild(group);
|
|
1753
1993
|
animations.push(
|
|
@@ -1805,25 +2045,29 @@ function mountTreeBuses(layer, buses, theme) {
|
|
|
1805
2045
|
for (const bus of buses) {
|
|
1806
2046
|
const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1807
2047
|
group.setAttribute("data-tree-bus", bus.id);
|
|
1808
|
-
const parentLeg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1809
|
-
parentLeg.setAttribute("d", `M ${bus.parentX} ${bus.parentY} L ${bus.parentX} ${bus.branchY}`);
|
|
1810
|
-
const childXs = [...bus.childXs].sort((a, b) => a - b);
|
|
1811
|
-
const branch = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1812
|
-
const branchStart = childXs[0] ?? bus.parentX;
|
|
1813
|
-
const branchEnd = childXs[childXs.length - 1] ?? bus.parentX;
|
|
1814
|
-
branch.setAttribute("d", `M ${branchStart} ${bus.branchY} L ${branchEnd} ${bus.branchY}`);
|
|
1815
|
-
group.append(parentLeg, branch);
|
|
1816
2048
|
for (const childX of bus.childXs) {
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2049
|
+
const pathEl = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2050
|
+
let d;
|
|
2051
|
+
if (Math.abs(childX - bus.parentX) < 1) {
|
|
2052
|
+
d = toPathD([
|
|
2053
|
+
{ x: bus.parentX, y: bus.parentY },
|
|
2054
|
+
{ x: childX, y: bus.childY }
|
|
2055
|
+
], 12);
|
|
2056
|
+
} else {
|
|
2057
|
+
d = toPathD([
|
|
2058
|
+
{ x: bus.parentX, y: bus.parentY },
|
|
2059
|
+
{ x: bus.parentX, y: bus.branchY },
|
|
2060
|
+
{ x: childX, y: bus.branchY },
|
|
2061
|
+
{ x: childX, y: bus.childY }
|
|
2062
|
+
], 12);
|
|
2063
|
+
}
|
|
2064
|
+
pathEl.setAttribute("d", d);
|
|
2065
|
+
pathEl.setAttribute("fill", "none");
|
|
2066
|
+
pathEl.setAttribute("stroke", stroke);
|
|
2067
|
+
pathEl.setAttribute("stroke-width", "1.6");
|
|
2068
|
+
pathEl.setAttribute("stroke-linecap", "round");
|
|
2069
|
+
pathEl.setAttribute("stroke-linejoin", "round");
|
|
2070
|
+
group.appendChild(pathEl);
|
|
1827
2071
|
}
|
|
1828
2072
|
svg.appendChild(group);
|
|
1829
2073
|
}
|
|
@@ -2250,6 +2494,20 @@ function createDiagram(opts) {
|
|
|
2250
2494
|
plan.theme,
|
|
2251
2495
|
{ width: plan.meta.width, height: plan.meta.height }
|
|
2252
2496
|
);
|
|
2497
|
+
} else if (plan.diagramType === "radar") {
|
|
2498
|
+
mountRadarLayer(
|
|
2499
|
+
constellationLayer,
|
|
2500
|
+
plan.nodes,
|
|
2501
|
+
plan.theme,
|
|
2502
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
2503
|
+
);
|
|
2504
|
+
} else if (plan.diagramType === "timeline") {
|
|
2505
|
+
mountTimelineLayer(
|
|
2506
|
+
constellationLayer,
|
|
2507
|
+
plan.nodes,
|
|
2508
|
+
plan.theme,
|
|
2509
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
2510
|
+
);
|
|
2253
2511
|
}
|
|
2254
2512
|
const groupLayer = document.createElement("div");
|
|
2255
2513
|
groupLayer.className = "markdy-group-layer";
|
|
@@ -2336,16 +2594,8 @@ function createDiagram(opts) {
|
|
|
2336
2594
|
const vHeight = viewport.clientHeight || container.clientHeight || vWidth * plan.meta.height / plan.meta.width;
|
|
2337
2595
|
const canvasScaleX = vWidth / plan.meta.width;
|
|
2338
2596
|
const canvasScaleY = vHeight / plan.meta.height;
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const contentScaleX = vWidth * 0.94 / bounds.width;
|
|
2342
|
-
const contentScaleY = vHeight * 0.94 / bounds.height;
|
|
2343
|
-
const optimalContentScale = Math.min(contentScaleX, contentScaleY);
|
|
2344
|
-
const chosenScale = Math.max(
|
|
2345
|
-
baseCanvasScale,
|
|
2346
|
-
Math.min(optimalContentScale, baseCanvasScale * 1.45)
|
|
2347
|
-
);
|
|
2348
|
-
fitScale = Number.isFinite(chosenScale) && chosenScale > 0 ? chosenScale : 1;
|
|
2597
|
+
fitScale = Math.min(canvasScaleX, canvasScaleY);
|
|
2598
|
+
if (!Number.isFinite(fitScale) || fitScale <= 0) fitScale = 1;
|
|
2349
2599
|
const scaledWidth = plan.meta.width * fitScale;
|
|
2350
2600
|
const scaledHeight = plan.meta.height * fitScale;
|
|
2351
2601
|
sceneOffsetX = (vWidth - scaledWidth) / 2;
|
|
@@ -2866,9 +3116,6 @@ function encodeGifSequence(frames, options = {}) {
|
|
|
2866
3116
|
return new Uint8Array(buffer);
|
|
2867
3117
|
}
|
|
2868
3118
|
|
|
2869
|
-
// src/export/png-exporter.ts
|
|
2870
|
-
import html2canvas from "html2canvas";
|
|
2871
|
-
|
|
2872
3119
|
// src/export/svg-exporter.ts
|
|
2873
3120
|
function copyRenderedStyles(source, clone) {
|
|
2874
3121
|
if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
|
|
@@ -3118,6 +3365,8 @@ async function rasterizeDiagramToCanvas(containerEl, options = {}, pixelRatio =
|
|
|
3118
3365
|
document.body.appendChild(host);
|
|
3119
3366
|
try {
|
|
3120
3367
|
await document.fonts?.ready;
|
|
3368
|
+
const html2canvasModule = await import("html2canvas");
|
|
3369
|
+
const html2canvas = html2canvasModule.default ?? html2canvasModule;
|
|
3121
3370
|
return await html2canvas(clonedScene, {
|
|
3122
3371
|
allowTaint: false,
|
|
3123
3372
|
backgroundColor: options.transparentBackground ? null : void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"html2canvas": "^1.4.1",
|
|
48
|
-
"@markdy/core": "1.0.
|
|
48
|
+
"@markdy/core": "1.0.9"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"jsdom": "^29.1.1",
|
|
52
52
|
"tsup": "^8.5.1",
|
|
53
53
|
"typescript": "^5.9.3",
|
|
54
54
|
"vitest": "^4.1.7",
|
|
55
|
-
"@markdy/stdlib-systems": "1.0.
|
|
55
|
+
"@markdy/stdlib-systems": "1.0.9"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsup",
|