@markdy/core 1.4.2 → 1.4.4
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/README.md +3 -1
- package/dist/index.js +188 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
<a href="https://markdy.com/playground/"><img src="https://img.shields.io/badge/⚡_Live_Studio-markdy.com%2Fplayground-3b82f6?style=for-the-badge" alt="Live Studio" /></a>
|
|
5
5
|
<a href="https://markdy.com/docs/"><img src="https://img.shields.io/badge/📖_Docs-Documentation-10b981?style=for-the-badge" alt="Documentation" /></a>
|
|
6
6
|
<a href="https://marketplace.visualstudio.com/items?itemName=hoangyell.markdy-vscode"><img src="https://img.shields.io/badge/🔌_VS_Code-Extension-8b5cf6?style=for-the-badge" alt="VS Code Extension" /></a>
|
|
7
|
+
<a href="https://github.com/sponsors/HoangYell"><img src="https://img.shields.io/badge/💖_Sponsor-Support_Markdy-ea4aaa?style=for-the-badge" alt="Sponsor Markdy" /></a>
|
|
7
8
|
</p>
|
|
8
9
|
|
|
9
10
|
The zero-dependency parser, dynamic port multiplexer, and AST routing engine for [MarkdyScript](https://markdy.com/docs/) — a diagram-native DSL for animated architecture diagrams that AI agents generate reliably.
|
|
10
11
|
|
|
11
12
|
> 🚀 **Try it live**: Test MarkdyScript in the browser at **[markdy.com/playground](https://markdy.com/playground/)**
|
|
12
13
|
> 📚 **Documentation**: Complete syntax guide and examples at **[markdy.com/docs](https://markdy.com/docs/)**
|
|
13
|
-
> 🌟 **Architecture Blueprints**: 30+ canonical production diagrams at **[markdy.com/examples](https://markdy.com/examples/)**
|
|
14
|
+
> 🌟 **Architecture Blueprints**: 30+ canonical production diagrams at **[markdy.com/examples](https://markdy.com/examples/)**
|
|
15
|
+
> 💼 **Enterprise & Commercial**: Free under MIT. To support development or request custom architecture blueprints, explore **[GitHub Sponsors](https://github.com/sponsors/HoangYell)**.
|
|
14
16
|
|
|
15
17
|
## Features
|
|
16
18
|
|
package/dist/index.js
CHANGED
|
@@ -521,6 +521,7 @@ var FLAT = {
|
|
|
521
521
|
next_beat: CONTROLS.next_beat,
|
|
522
522
|
keyboard: INTERACTION.keyboard,
|
|
523
523
|
shortcuts: INTERACTION.shortcuts,
|
|
524
|
+
fit: CONTROLS.fit,
|
|
524
525
|
fitView: CONTROLS.fitView,
|
|
525
526
|
fit_view: CONTROLS.fit_view,
|
|
526
527
|
fitViewButton: CONTROLS.fitViewButton,
|
|
@@ -642,6 +643,7 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
642
643
|
const chrome = config.chrome ?? {};
|
|
643
644
|
const controlsAllowed = overrides.controls !== false;
|
|
644
645
|
const hostControlDefault = overrides.controls === true;
|
|
646
|
+
const hasControlsConfig = config.controls !== void 0 || overrideControls !== void 0;
|
|
645
647
|
const resolveControl = (value, fallback = hostControlDefault) => controlsAllowed && (value ?? fallback);
|
|
646
648
|
const requestedControls = {
|
|
647
649
|
play: resolveControl(configuredControls.play),
|
|
@@ -659,11 +661,13 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
659
661
|
code: resolveControl(configuredControls.code, false),
|
|
660
662
|
theme: resolveControl(configuredControls.theme, hostControlDefault)
|
|
661
663
|
};
|
|
662
|
-
const
|
|
664
|
+
const hasExplicitInteraction = config.interaction !== void 0;
|
|
665
|
+
const fallbackGesture = overrides.interactiveViewport === true || overrides.controls === true;
|
|
666
|
+
const interactionOn = overrides.interactiveViewport === true || overrides.controls === true || overrides.interactiveViewport !== false && hasExplicitInteraction;
|
|
663
667
|
const gestures = {
|
|
664
|
-
zoom: interactionOn && (interaction.zoom ??
|
|
665
|
-
pan: interactionOn && (interaction.pan ??
|
|
666
|
-
doubleClickToReset: interactionOn && (interaction.doubleClickToReset ??
|
|
668
|
+
zoom: interactionOn && (interaction.zoom ?? fallbackGesture),
|
|
669
|
+
pan: interactionOn && (interaction.pan ?? fallbackGesture),
|
|
670
|
+
doubleClickToReset: interactionOn && (interaction.doubleClickToReset ?? fallbackGesture)
|
|
667
671
|
};
|
|
668
672
|
const interactionEnabled = gestures.zoom || gestures.pan || gestures.doubleClickToReset;
|
|
669
673
|
const configuredSpeeds = configuredControls.speeds?.length ? configuredControls.speeds : [0.25, 1];
|
|
@@ -692,7 +696,7 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
692
696
|
interaction: {
|
|
693
697
|
...gestures,
|
|
694
698
|
enabled: interactionEnabled,
|
|
695
|
-
clickToPlay: overrides.clickToPlay ?? (
|
|
699
|
+
clickToPlay: overrides.clickToPlay ?? (interaction.clickToPlay ?? true),
|
|
696
700
|
keyboard: interactionOn && (interaction.keyboard ?? false)
|
|
697
701
|
},
|
|
698
702
|
chrome: {
|
|
@@ -1023,17 +1027,32 @@ function dedupeEdges(edges) {
|
|
|
1023
1027
|
function assignRanks(nodeIds, edges, direction) {
|
|
1024
1028
|
const ranks = /* @__PURE__ */ new Map();
|
|
1025
1029
|
for (const id of nodeIds) ranks.set(id, 0);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1030
|
+
const outgoing = new Map(nodeIds.map((id) => [id, []]));
|
|
1031
|
+
const indegree = new Map(nodeIds.map((id) => [id, 0]));
|
|
1032
|
+
for (const edge of edges) {
|
|
1033
|
+
if (edge.kind === "response" || edge.selfLoop || !outgoing.has(edge.from) || !outgoing.has(edge.to)) continue;
|
|
1034
|
+
outgoing.get(edge.from).push(edge.to);
|
|
1035
|
+
indegree.set(edge.to, indegree.get(edge.to) + 1);
|
|
1036
|
+
}
|
|
1037
|
+
const queue = nodeIds.filter((id) => indegree.get(id) === 0);
|
|
1038
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1039
|
+
let cursor = 0;
|
|
1040
|
+
let cycleRoot = 0;
|
|
1041
|
+
while (processed.size < nodeIds.length) {
|
|
1042
|
+
if (cursor === queue.length) {
|
|
1043
|
+
while (processed.has(nodeIds[cycleRoot])) cycleRoot++;
|
|
1044
|
+
queue.push(nodeIds[cycleRoot]);
|
|
1045
|
+
}
|
|
1046
|
+
const id = queue[cursor++];
|
|
1047
|
+
if (processed.has(id)) continue;
|
|
1048
|
+
processed.add(id);
|
|
1049
|
+
for (const target of outgoing.get(id)) {
|
|
1050
|
+
if (processed.has(target)) continue;
|
|
1051
|
+
ranks.set(target, Math.max(ranks.get(target), ranks.get(id) + 1));
|
|
1052
|
+
const remaining = indegree.get(target) - 1;
|
|
1053
|
+
indegree.set(target, remaining);
|
|
1054
|
+
if (remaining === 0) queue.push(target);
|
|
1035
1055
|
}
|
|
1036
|
-
if (!changed) break;
|
|
1037
1056
|
}
|
|
1038
1057
|
if (direction === "RL" || direction === "BT") {
|
|
1039
1058
|
const max = Math.max(...ranks.values(), 0);
|
|
@@ -1047,7 +1066,7 @@ function snapGrid(n, step = 4) {
|
|
|
1047
1066
|
function layoutRanked(ast, edges, opts) {
|
|
1048
1067
|
const nodeIds = Object.keys(ast.nodes);
|
|
1049
1068
|
const dtype = diagramType(ast);
|
|
1050
|
-
const direction = opts.forceVertical ? "TB" : ast.meta.direction;
|
|
1069
|
+
const direction = opts.forceVertical ? ast.meta.direction === "BT" ? "BT" : "TB" : ast.meta.direction;
|
|
1051
1070
|
const ranks = assignRanks(nodeIds, edges, direction);
|
|
1052
1071
|
const byRank = /* @__PURE__ */ new Map();
|
|
1053
1072
|
for (const id of nodeIds) {
|
|
@@ -1072,6 +1091,7 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1072
1091
|
}
|
|
1073
1092
|
const rankOrder = /* @__PURE__ */ new Map();
|
|
1074
1093
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
1094
|
+
if (direction === "RL" || direction === "BT") sortedRanks.reverse();
|
|
1075
1095
|
for (const r of sortedRanks) {
|
|
1076
1096
|
const ids = byRank.get(r);
|
|
1077
1097
|
ids.sort((a, b) => {
|
|
@@ -1091,6 +1111,39 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1091
1111
|
rankOrder.set(id, idx);
|
|
1092
1112
|
});
|
|
1093
1113
|
}
|
|
1114
|
+
const neighbors = new Map(nodeIds.map((id) => [id, []]));
|
|
1115
|
+
for (const edge of edges) {
|
|
1116
|
+
if (edge.kind === "response" || edge.selfLoop || !neighbors.has(edge.from) || !neighbors.has(edge.to)) continue;
|
|
1117
|
+
neighbors.get(edge.from).push(edge.to);
|
|
1118
|
+
neighbors.get(edge.to).push(edge.from);
|
|
1119
|
+
}
|
|
1120
|
+
for (let sweep = 0; sweep < 4; sweep++) {
|
|
1121
|
+
let changed = false;
|
|
1122
|
+
const order = sweep % 2 === 0 ? [...sortedRanks].reverse() : sortedRanks;
|
|
1123
|
+
for (const rank of order) {
|
|
1124
|
+
const ids = byRank.get(rank);
|
|
1125
|
+
for (let index = 0; index < ids.length - 1; index++) {
|
|
1126
|
+
const left = ids[index];
|
|
1127
|
+
const right = ids[index + 1];
|
|
1128
|
+
if (nodeGroupIndex.get(left) !== nodeGroupIndex.get(right)) continue;
|
|
1129
|
+
let improvement = 0;
|
|
1130
|
+
for (const leftNeighbor of neighbors.get(left)) {
|
|
1131
|
+
for (const rightNeighbor of neighbors.get(right)) {
|
|
1132
|
+
const neighborRank = ranks.get(leftNeighbor);
|
|
1133
|
+
if (neighborRank === rank || neighborRank !== ranks.get(rightNeighbor)) continue;
|
|
1134
|
+
improvement += Math.sign(rankOrder.get(leftNeighbor) - rankOrder.get(rightNeighbor));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (improvement <= 0) continue;
|
|
1138
|
+
ids[index] = right;
|
|
1139
|
+
ids[index + 1] = left;
|
|
1140
|
+
rankOrder.set(right, index);
|
|
1141
|
+
rankOrder.set(left, index + 1);
|
|
1142
|
+
changed = true;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (!changed) break;
|
|
1146
|
+
}
|
|
1094
1147
|
const isVertical = direction === "TB" || direction === "BT";
|
|
1095
1148
|
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
1096
1149
|
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
@@ -1225,6 +1278,7 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1225
1278
|
function layoutTree(ast, edges) {
|
|
1226
1279
|
const nodeIds = Object.keys(ast.nodes);
|
|
1227
1280
|
if (nodeIds.length === 0) return [];
|
|
1281
|
+
edges = cycleSafeEdges(nodeIds, edges);
|
|
1228
1282
|
const nodeDims = /* @__PURE__ */ new Map();
|
|
1229
1283
|
for (const id of nodeIds) {
|
|
1230
1284
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
@@ -1395,19 +1449,21 @@ function cycleSafeEdges(nodeIds, edges) {
|
|
|
1395
1449
|
}
|
|
1396
1450
|
const visited = /* @__PURE__ */ new Set();
|
|
1397
1451
|
const safe = [];
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
visited.
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1452
|
+
const roots = nodeIds.filter((id) => !incoming.has(id));
|
|
1453
|
+
for (const root of [...roots, ...nodeIds]) {
|
|
1454
|
+
if (visited.has(root)) continue;
|
|
1455
|
+
const stack = [{ id: root }];
|
|
1456
|
+
while (stack.length > 0) {
|
|
1457
|
+
const current = stack.pop();
|
|
1458
|
+
if (visited.has(current.id)) continue;
|
|
1459
|
+
visited.add(current.id);
|
|
1460
|
+
if (current.edge) safe.push(current.edge);
|
|
1461
|
+
const children = outgoing.get(current.id);
|
|
1462
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
1463
|
+
stack.push({ id: children[index].to, edge: children[index] });
|
|
1464
|
+
}
|
|
1405
1465
|
}
|
|
1406
|
-
};
|
|
1407
|
-
for (const id of nodeIds) {
|
|
1408
|
-
if (!incoming.has(id)) visit(id);
|
|
1409
1466
|
}
|
|
1410
|
-
for (const id of nodeIds) visit(id);
|
|
1411
1467
|
return safe;
|
|
1412
1468
|
}
|
|
1413
1469
|
function layoutConstellation(ast) {
|
|
@@ -2549,7 +2605,8 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2549
2605
|
const children = /* @__PURE__ */ new Map();
|
|
2550
2606
|
const hasParent = /* @__PURE__ */ new Set();
|
|
2551
2607
|
for (const id of nodeIds) children.set(id, []);
|
|
2552
|
-
const
|
|
2608
|
+
const treeCandidates = effectiveEdges.some((e) => e.structural) ? effectiveEdges.filter((e) => e.structural) : effectiveEdges;
|
|
2609
|
+
const treeEdges = cycleSafeEdges(nodeIds, treeCandidates);
|
|
2553
2610
|
for (const e of treeEdges) {
|
|
2554
2611
|
if (ast.nodes[e.from] && ast.nodes[e.to] && e.from !== e.to) {
|
|
2555
2612
|
children.get(e.from)?.push(e.to);
|
|
@@ -5257,6 +5314,97 @@ function buildSmoothSvgPath(start, waypoints, end, cornerRadius = 0) {
|
|
|
5257
5314
|
d += ` L ${end.x} ${end.y}`;
|
|
5258
5315
|
return d;
|
|
5259
5316
|
}
|
|
5317
|
+
function routeAroundObstacles(sourceBox, targetBox, points, options) {
|
|
5318
|
+
if (!options.obstacles?.length) return points;
|
|
5319
|
+
const margin = options.margin ?? 20;
|
|
5320
|
+
const obstacles = [sourceBox, targetBox, ...options.obstacles];
|
|
5321
|
+
const intersections = (route) => {
|
|
5322
|
+
let hits = 0;
|
|
5323
|
+
for (let index = 1; index < route.length; index++) {
|
|
5324
|
+
const start2 = route[index - 1];
|
|
5325
|
+
const end2 = route[index];
|
|
5326
|
+
if (start2.x === end2.x && start2.y === end2.y) continue;
|
|
5327
|
+
for (const box of obstacles) {
|
|
5328
|
+
if (start2.y === end2.y && start2.y > box.y && start2.y < box.y + box.height && Math.max(start2.x, end2.x) > box.x && Math.min(start2.x, end2.x) < box.x + box.width) hits++;
|
|
5329
|
+
else if (start2.x === end2.x && start2.x > box.x && start2.x < box.x + box.width && Math.max(start2.y, end2.y) > box.y && Math.min(start2.y, end2.y) < box.y + box.height) hits++;
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
return hits;
|
|
5333
|
+
};
|
|
5334
|
+
if (intersections(points) === 0) return points;
|
|
5335
|
+
const start = points[0];
|
|
5336
|
+
const end = points[points.length - 1];
|
|
5337
|
+
const stub = (point, port) => {
|
|
5338
|
+
let distance = margin;
|
|
5339
|
+
for (const box of obstacles) {
|
|
5340
|
+
let clearance = Number.POSITIVE_INFINITY;
|
|
5341
|
+
if (point.y > box.y && point.y < box.y + box.height) {
|
|
5342
|
+
if (port === "right") clearance = box.x - point.x;
|
|
5343
|
+
if (port === "left") clearance = point.x - box.x - box.width;
|
|
5344
|
+
}
|
|
5345
|
+
if (point.x > box.x && point.x < box.x + box.width) {
|
|
5346
|
+
if (port === "bottom") clearance = box.y - point.y;
|
|
5347
|
+
if (port === "top") clearance = point.y - box.y - box.height;
|
|
5348
|
+
}
|
|
5349
|
+
if (clearance >= 0) distance = Math.min(distance, clearance / 2);
|
|
5350
|
+
}
|
|
5351
|
+
return {
|
|
5352
|
+
x: point.x + (port === "right" ? distance : port === "left" ? -distance : 0),
|
|
5353
|
+
y: point.y + (port === "bottom" ? distance : port === "top" ? -distance : 0)
|
|
5354
|
+
};
|
|
5355
|
+
};
|
|
5356
|
+
const sourceStub = stub(start, options.sourcePort);
|
|
5357
|
+
const targetStub = stub(end, options.targetPort);
|
|
5358
|
+
const corridorsX = /* @__PURE__ */ new Set();
|
|
5359
|
+
const corridorsY = /* @__PURE__ */ new Set();
|
|
5360
|
+
for (const box of obstacles) {
|
|
5361
|
+
corridorsX.add(box.x - margin);
|
|
5362
|
+
corridorsX.add(box.x + box.width + margin);
|
|
5363
|
+
corridorsY.add(box.y - margin);
|
|
5364
|
+
corridorsY.add(box.y + box.height + margin);
|
|
5365
|
+
}
|
|
5366
|
+
const candidates = [
|
|
5367
|
+
points,
|
|
5368
|
+
[start, sourceStub, { x: sourceStub.x, y: targetStub.y }, targetStub, end],
|
|
5369
|
+
[start, sourceStub, { x: targetStub.x, y: sourceStub.y }, targetStub, end]
|
|
5370
|
+
];
|
|
5371
|
+
for (const corridor of corridorsX) {
|
|
5372
|
+
candidates.push([
|
|
5373
|
+
start,
|
|
5374
|
+
sourceStub,
|
|
5375
|
+
{ x: corridor, y: sourceStub.y },
|
|
5376
|
+
{ x: corridor, y: targetStub.y },
|
|
5377
|
+
targetStub,
|
|
5378
|
+
end
|
|
5379
|
+
]);
|
|
5380
|
+
}
|
|
5381
|
+
for (const corridor of corridorsY) {
|
|
5382
|
+
candidates.push([
|
|
5383
|
+
start,
|
|
5384
|
+
sourceStub,
|
|
5385
|
+
{ x: sourceStub.x, y: corridor },
|
|
5386
|
+
{ x: targetStub.x, y: corridor },
|
|
5387
|
+
targetStub,
|
|
5388
|
+
end
|
|
5389
|
+
]);
|
|
5390
|
+
}
|
|
5391
|
+
let best = points;
|
|
5392
|
+
let bestHits = Number.POSITIVE_INFINITY;
|
|
5393
|
+
let bestLength = Number.POSITIVE_INFINITY;
|
|
5394
|
+
for (const candidate of candidates) {
|
|
5395
|
+
const hits = intersections(candidate);
|
|
5396
|
+
let length = 0;
|
|
5397
|
+
for (let index = 1; index < candidate.length; index++) {
|
|
5398
|
+
length += Math.abs(candidate[index].x - candidate[index - 1].x) + Math.abs(candidate[index].y - candidate[index - 1].y);
|
|
5399
|
+
}
|
|
5400
|
+
if (hits < bestHits || hits === bestHits && length < bestLength) {
|
|
5401
|
+
best = candidate;
|
|
5402
|
+
bestHits = hits;
|
|
5403
|
+
bestLength = length;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
return best.filter((point, index) => index === 0 || point.x !== best[index - 1].x || point.y !== best[index - 1].y);
|
|
5407
|
+
}
|
|
5260
5408
|
function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
5261
5409
|
const isSelfLoop = sourceBox === targetBox || sourceBox.x === targetBox.x && sourceBox.y === targetBox.y && sourceBox.width === targetBox.width && sourceBox.height === targetBox.height;
|
|
5262
5410
|
const MARGIN = options.margin ?? 20;
|
|
@@ -5299,6 +5447,11 @@ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
|
5299
5447
|
end2 = { x: rightX, y: endY };
|
|
5300
5448
|
waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
|
|
5301
5449
|
}
|
|
5450
|
+
waypoints2 = routeAroundObstacles(sourceBox, targetBox, [start2, ...waypoints2, end2], {
|
|
5451
|
+
...options,
|
|
5452
|
+
sourcePort: port,
|
|
5453
|
+
targetPort: port
|
|
5454
|
+
}).slice(1, -1);
|
|
5302
5455
|
const svgPathData2 = buildSmoothSvgPath(start2, waypoints2, end2, options.cornerRadius ?? 6);
|
|
5303
5456
|
return {
|
|
5304
5457
|
sourcePort: port,
|
|
@@ -5381,13 +5534,18 @@ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
|
5381
5534
|
}
|
|
5382
5535
|
waypoints.push(p2);
|
|
5383
5536
|
}
|
|
5384
|
-
const
|
|
5537
|
+
const routedWaypoints = routeAroundObstacles(sourceBox, targetBox, [start, ...waypoints, end], {
|
|
5538
|
+
...options,
|
|
5539
|
+
sourcePort,
|
|
5540
|
+
targetPort
|
|
5541
|
+
}).slice(1, -1);
|
|
5542
|
+
const svgPathData = buildSmoothSvgPath(start, routedWaypoints, end, options.cornerRadius ?? 6);
|
|
5385
5543
|
return {
|
|
5386
5544
|
sourcePort,
|
|
5387
5545
|
targetPort,
|
|
5388
5546
|
startPoint: start,
|
|
5389
5547
|
endPoint: end,
|
|
5390
|
-
waypoints,
|
|
5548
|
+
waypoints: routedWaypoints,
|
|
5391
5549
|
svgPathData
|
|
5392
5550
|
};
|
|
5393
5551
|
}
|
package/package.json
CHANGED