@markdy/core 1.1.5 → 1.2.0
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 +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +275 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The parser and AST types for [MarkdyScript](../../docs/SYNTAX.md) — a diagram-
|
|
|
7
7
|
- **Zero runtime dependencies** — pure TypeScript, no DOM or platform APIs (~14 KB minzipped)
|
|
8
8
|
- **Single-pass parser** — line-by-line state machine with strict `ParseError` line-number diagnostics
|
|
9
9
|
- **17 Specialized Layout Engines** — `architecture`, `flowchart`, `tree`, `sequence`, `state`, `layers`, `nested`, `swimlane`, `timeline`, `gantt`, `medallion`, `flywheel`, `constellation`, `quadrant`, `pyramid`, `radar`, `venn`
|
|
10
|
-
- **
|
|
10
|
+
- **10 Semantic Themes** — `paper`, `editorial`, `midnight`, `blueprint`, `graphite`, `nebula`, `terminal`, `sketchy`, `ink`, `doodle`
|
|
11
11
|
- **Content-Adaptive Canvas Sizing** — automatically calculates optimal aspect ratio and bounds based on diagram items and topology
|
|
12
12
|
- **Well-Architected Governance & AST Diffing** — layer boundaries, deadlock cycle detection, gateway isolation, and semantic AST evolution
|
|
13
13
|
- **Isomorphic** — runs in Node.js, Deno, Bun, edge runtimes, and the browser
|
|
@@ -93,7 +93,7 @@ try {
|
|
|
93
93
|
| `validateArchitectureRules` | `(ast) => Diagnostic[]` | Run Well-Architected rules: cycle detection, layer boundaries, and gateway checks |
|
|
94
94
|
| `diffAST` | `(astA, astB) => ASTDiffResult` | Compare architecture versions, calculate diff metrics, and generate migration scenes |
|
|
95
95
|
| `compressUrlState` / `decompressUrlState` | `(code, opts?) => string` | Zero-dependency URL hash state encoder for shareable playground links |
|
|
96
|
-
| `THEMES` / `resolveTheme` | tokens / function |
|
|
96
|
+
| `THEMES` / `resolveTheme` | tokens / function | 10 Semantic theme palettes (`paper`, `editorial`, `nebula`, `midnight`, `blueprint`, `graphite`, `terminal`, `sketchy`, `ink`, `doodle`) |
|
|
97
97
|
|
|
98
98
|
## Documentation
|
|
99
99
|
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ type PlayerControlsConfig = {
|
|
|
25
25
|
resetView?: boolean;
|
|
26
26
|
fullscreen?: boolean;
|
|
27
27
|
svg?: boolean;
|
|
28
|
+
gif?: boolean;
|
|
28
29
|
share?: boolean;
|
|
29
30
|
/** Show the MarkdyScript source code when clicked. */
|
|
30
31
|
code?: boolean;
|
|
@@ -82,6 +83,8 @@ type SceneMeta = {
|
|
|
82
83
|
explicitWidth?: boolean;
|
|
83
84
|
/** Whether height was explicitly specified by the author in the script. */
|
|
84
85
|
explicitHeight?: boolean;
|
|
86
|
+
/** Whether theme was explicitly specified by the author in the script. */
|
|
87
|
+
explicitTheme?: boolean;
|
|
85
88
|
/** Opt-in diagram mode; defaults to architecture. */
|
|
86
89
|
type?: DiagramType;
|
|
87
90
|
/** Playback, controls, interaction, and chrome behavior. Source of truth. */
|
package/dist/index.js
CHANGED
|
@@ -397,6 +397,7 @@ var CONTROL_KEYS = [
|
|
|
397
397
|
"resetView",
|
|
398
398
|
"fullscreen",
|
|
399
399
|
"svg",
|
|
400
|
+
"gif",
|
|
400
401
|
"share",
|
|
401
402
|
"code",
|
|
402
403
|
"theme"
|
|
@@ -448,6 +449,11 @@ var CONTROLS = {
|
|
|
448
449
|
svg: { group: "controls", key: "svg", type: "boolean" },
|
|
449
450
|
exportSvg: { group: "controls", key: "svg", type: "boolean" },
|
|
450
451
|
export_svg: { group: "controls", key: "svg", type: "boolean" },
|
|
452
|
+
gif: { group: "controls", key: "gif", type: "boolean" },
|
|
453
|
+
exportGif: { group: "controls", key: "gif", type: "boolean" },
|
|
454
|
+
export_gif: { group: "controls", key: "gif", type: "boolean" },
|
|
455
|
+
gifButton: { group: "controls", key: "gif", type: "boolean" },
|
|
456
|
+
gif_button: { group: "controls", key: "gif", type: "boolean" },
|
|
451
457
|
share: { group: "controls", key: "share", type: "boolean" },
|
|
452
458
|
shareLink: { group: "controls", key: "share", type: "boolean" },
|
|
453
459
|
share_link: { group: "controls", key: "share", type: "boolean" },
|
|
@@ -528,6 +534,11 @@ var FLAT = {
|
|
|
528
534
|
fullscreen_button: CONTROLS.fullscreen_button,
|
|
529
535
|
exportSvg: CONTROLS.exportSvg,
|
|
530
536
|
export_svg: CONTROLS.export_svg,
|
|
537
|
+
gif: CONTROLS.gif,
|
|
538
|
+
exportGif: CONTROLS.exportGif,
|
|
539
|
+
export_gif: CONTROLS.export_gif,
|
|
540
|
+
gifButton: CONTROLS.gifButton,
|
|
541
|
+
gif_button: CONTROLS.gif_button,
|
|
531
542
|
shareLink: CONTROLS.shareLink,
|
|
532
543
|
share_link: CONTROLS.share_link,
|
|
533
544
|
code: CONTROLS.code,
|
|
@@ -643,6 +654,7 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
643
654
|
resetView: resolveControl(configuredControls.resetView),
|
|
644
655
|
fullscreen: resolveControl(configuredControls.fullscreen),
|
|
645
656
|
svg: resolveControl(configuredControls.svg),
|
|
657
|
+
gif: resolveControl(configuredControls.gif, false),
|
|
646
658
|
share: resolveControl(configuredControls.share),
|
|
647
659
|
code: resolveControl(configuredControls.code, false),
|
|
648
660
|
theme: resolveControl(configuredControls.theme, hostControlDefault)
|
|
@@ -791,6 +803,38 @@ var NODE_W = 180;
|
|
|
791
803
|
var NODE_H = 76;
|
|
792
804
|
var VENN_NODE_SIZE = 220;
|
|
793
805
|
var GROUP_PAD = 28;
|
|
806
|
+
function computeNodeDimensions(decl, baseW = 180, baseH = 76) {
|
|
807
|
+
if (typeof decl.props?.width === "number") {
|
|
808
|
+
return {
|
|
809
|
+
width: decl.props.width,
|
|
810
|
+
height: typeof decl.props.height === "number" ? decl.props.height : baseH
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
const labelLen = (decl.label || "").length;
|
|
814
|
+
const tech = decl.props?.tech ?? decl.props?.sub;
|
|
815
|
+
const techLen = tech ? String(tech).length : 0;
|
|
816
|
+
const val = decl.props?.value ?? decl.props?.metric;
|
|
817
|
+
const valLen = val ? String(val).length : 0;
|
|
818
|
+
if (decl.kind === "dot") return { width: 64, height: 64 };
|
|
819
|
+
if (decl.kind === "matrix") return { width: 220, height: 96 };
|
|
820
|
+
const valueW = valLen > 0 ? Math.max(50, valLen * 9.5 + 16) : 0;
|
|
821
|
+
const neededLabelChars = labelLen > 18 ? Math.ceil(labelLen / 2) : labelLen;
|
|
822
|
+
const maxChars = Math.max(neededLabelChars, techLen);
|
|
823
|
+
const neededTextW = Math.max(88, maxChars * 7.8);
|
|
824
|
+
const calculatedW = 56 + neededTextW + (valueW > 0 ? valueW + 14 : 0) + 16;
|
|
825
|
+
const minW = Math.max(baseW, Math.min(360, calculatedW));
|
|
826
|
+
let width = Math.ceil(minW / 8) * 8;
|
|
827
|
+
let height = baseH;
|
|
828
|
+
if (labelLen > 24 && techLen > 0) {
|
|
829
|
+
height = Math.max(height, 84);
|
|
830
|
+
}
|
|
831
|
+
return { width, height };
|
|
832
|
+
}
|
|
833
|
+
function resolveNodeStyle(decl, ast) {
|
|
834
|
+
if (!decl.style) return void 0;
|
|
835
|
+
if (typeof decl.style === "string") return ast.styles[decl.style]?.props;
|
|
836
|
+
return decl.style;
|
|
837
|
+
}
|
|
794
838
|
var DEFAULTS = {
|
|
795
839
|
show: 0.35,
|
|
796
840
|
hide: 0.35,
|
|
@@ -805,7 +849,13 @@ var DEFAULTS = {
|
|
|
805
849
|
function diagramType(ast) {
|
|
806
850
|
return ast.meta.type ?? "architecture";
|
|
807
851
|
}
|
|
808
|
-
function nodeShape(kind, dtype) {
|
|
852
|
+
function nodeShape(kind, dtype, props) {
|
|
853
|
+
if (typeof props?.shape === "string") {
|
|
854
|
+
const s = props.shape.toLowerCase();
|
|
855
|
+
if (s === "diamond" || s === "circle" || s === "pill" || s === "terminal" || s === "rounded" || s === "card" || s === "container") {
|
|
856
|
+
return s;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
809
859
|
if (kind === "terminal") return "terminal";
|
|
810
860
|
if (kind === "dot" || kind === "marker") return "circle";
|
|
811
861
|
if (kind === "token_strip" || kind === "chips") return "pill";
|
|
@@ -940,22 +990,31 @@ function layoutRanked(ast, edges, opts) {
|
|
|
940
990
|
});
|
|
941
991
|
}
|
|
942
992
|
const isVertical = direction === "TB" || direction === "BT";
|
|
993
|
+
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
994
|
+
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
995
|
+
const titleBand = hasTitle ? TITLE_BAND : 0;
|
|
996
|
+
const bottomBand = hasBeatCaptions ? 44 : 0;
|
|
943
997
|
const contentW = ast.meta.width - SAFE * 2;
|
|
944
|
-
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
945
998
|
const maxRank = Math.max(...byRank.keys(), 0);
|
|
946
999
|
const rankCount = maxRank + 1;
|
|
947
1000
|
const maxInRank = Math.max(...[...byRank.values()].map((v) => v.length), 1);
|
|
948
1001
|
const nodes = [];
|
|
1002
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1003
|
+
for (const id of nodeIds) {
|
|
1004
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1005
|
+
}
|
|
949
1006
|
if (opts.columnLayout) {
|
|
950
1007
|
const count = nodeIds.length;
|
|
951
|
-
const
|
|
952
|
-
const
|
|
1008
|
+
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id)?.width ?? NODE_W));
|
|
1009
|
+
const colSpacing = Math.max(maxNodeW + 32, contentW / Math.max(count, 1));
|
|
1010
|
+
const totalW2 = (count - 1) * colSpacing + maxNodeW;
|
|
953
1011
|
const startX2 = SAFE + Math.max(0, (contentW - totalW2) / 2);
|
|
954
1012
|
nodeIds.forEach((id, idx) => {
|
|
955
1013
|
const decl = ast.nodes[id];
|
|
956
1014
|
const role = nodeRole(decl.kind);
|
|
957
|
-
const
|
|
958
|
-
const
|
|
1015
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1016
|
+
const x = startX2 + idx * colSpacing + (maxNodeW - dims.width) / 2;
|
|
1017
|
+
const y = (hasTitle ? titleBand : 20) + 32;
|
|
959
1018
|
nodes.push({
|
|
960
1019
|
id,
|
|
961
1020
|
kind: decl.kind,
|
|
@@ -963,12 +1022,12 @@ function layoutRanked(ast, edges, opts) {
|
|
|
963
1022
|
label: decl.label,
|
|
964
1023
|
x: snapGrid(x),
|
|
965
1024
|
y: snapGrid(y),
|
|
966
|
-
width:
|
|
967
|
-
height:
|
|
968
|
-
style: decl
|
|
1025
|
+
width: dims.width,
|
|
1026
|
+
height: dims.height,
|
|
1027
|
+
style: resolveNodeStyle(decl, ast),
|
|
969
1028
|
props: decl.props,
|
|
970
1029
|
opacity: 0,
|
|
971
|
-
shape: nodeShape(decl.kind, dtype),
|
|
1030
|
+
shape: nodeShape(decl.kind, dtype, decl.props),
|
|
972
1031
|
focal: decl.props.focal === true || decl.props.accent === true,
|
|
973
1032
|
column: idx
|
|
974
1033
|
});
|
|
@@ -976,18 +1035,22 @@ function layoutRanked(ast, edges, opts) {
|
|
|
976
1035
|
return nodes;
|
|
977
1036
|
}
|
|
978
1037
|
if (isVertical) {
|
|
979
|
-
const
|
|
980
|
-
const
|
|
1038
|
+
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1039
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id)?.height ?? NODE_H));
|
|
1040
|
+
const rowGap = Math.max(maxNodeH + 40, Math.min(200, contentH / Math.max(rankCount, 1)));
|
|
1041
|
+
const totalH = (rankCount - 1) * rowGap + maxNodeH;
|
|
981
1042
|
const startY = TITLE_BAND + Math.max(0, (contentH - totalH) / 2);
|
|
982
1043
|
for (const [rank, ids] of [...byRank.entries()].sort((a, b) => a[0] - b[0])) {
|
|
983
1044
|
const rowCount = ids.length;
|
|
984
|
-
const
|
|
985
|
-
const
|
|
1045
|
+
const maxRowW = Math.max(...ids.map((id) => nodeDims.get(id)?.width ?? NODE_W));
|
|
1046
|
+
const colSpacing = Math.max(maxRowW + 36, Math.min(320, contentW / Math.max(rowCount, 1)));
|
|
1047
|
+
const rankW = (rowCount - 1) * colSpacing + maxRowW;
|
|
986
1048
|
const rankStartX = SAFE + Math.max(0, (contentW - rankW) / 2);
|
|
987
1049
|
const y = startY + rank * rowGap;
|
|
988
1050
|
ids.forEach((id, idx) => {
|
|
989
1051
|
const decl = ast.nodes[id];
|
|
990
1052
|
const role = nodeRole(decl.kind);
|
|
1053
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
991
1054
|
const x = rankStartX + idx * colSpacing;
|
|
992
1055
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
993
1056
|
nodes.push({
|
|
@@ -997,32 +1060,44 @@ function layoutRanked(ast, edges, opts) {
|
|
|
997
1060
|
label: decl.label,
|
|
998
1061
|
x: snapGrid(x),
|
|
999
1062
|
y: snapGrid(y),
|
|
1000
|
-
width:
|
|
1001
|
-
height:
|
|
1002
|
-
style: decl
|
|
1063
|
+
width: dims.width,
|
|
1064
|
+
height: dims.height,
|
|
1065
|
+
style: resolveNodeStyle(decl, ast),
|
|
1003
1066
|
props: decl.props,
|
|
1004
1067
|
opacity: 0,
|
|
1005
|
-
shape: nodeShape(decl.kind, dtype),
|
|
1068
|
+
shape: nodeShape(decl.kind, dtype, decl.props),
|
|
1006
1069
|
focal
|
|
1007
1070
|
});
|
|
1008
1071
|
});
|
|
1009
1072
|
}
|
|
1010
1073
|
return nodes;
|
|
1011
1074
|
}
|
|
1012
|
-
const
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1075
|
+
const rankWidths = /* @__PURE__ */ new Map();
|
|
1076
|
+
for (const [rank, ids] of byRank.entries()) {
|
|
1077
|
+
const maxW = Math.max(...ids.map((id) => nodeDims.get(id)?.width ?? NODE_W));
|
|
1078
|
+
rankWidths.set(rank, maxW);
|
|
1079
|
+
}
|
|
1080
|
+
const totalRankWidths = [...rankWidths.values()].reduce((a, b) => a + b, 0);
|
|
1081
|
+
const minColGap = 56;
|
|
1082
|
+
const availableGapW = Math.max(0, contentW - totalRankWidths);
|
|
1083
|
+
const colGap = rankCount > 1 ? Math.max(minColGap, Math.min(160, availableGapW / (rankCount - 1))) : 0;
|
|
1084
|
+
const totalW = totalRankWidths + (rankCount - 1) * colGap;
|
|
1015
1085
|
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1016
|
-
|
|
1086
|
+
const availableH = Math.max(NODE_H, ast.meta.height - titleBand - bottomBand);
|
|
1087
|
+
let currentRankX = startX;
|
|
1088
|
+
for (let rank = 0; rank < rankCount; rank++) {
|
|
1089
|
+
const ids = byRank.get(rank) ?? [];
|
|
1090
|
+
const rankW = rankWidths.get(rank) ?? NODE_W;
|
|
1017
1091
|
const rowCount = ids.length;
|
|
1018
|
-
const
|
|
1019
|
-
const
|
|
1020
|
-
const
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1092
|
+
const maxH = Math.max(...ids.map((id) => nodeDims.get(id)?.height ?? NODE_H), NODE_H);
|
|
1093
|
+
const maxPossibleRowStep = rowCount > 1 ? (availableH - maxH) / (rowCount - 1) : 0;
|
|
1094
|
+
const rowSpacing = rowCount > 1 ? Math.min(180, Math.max(maxH + 28, maxPossibleRowStep)) : 0;
|
|
1095
|
+
const colH = (rowCount - 1) * rowSpacing + maxH;
|
|
1096
|
+
const colStartY = titleBand + Math.max(0, (availableH - colH) / 2);
|
|
1023
1097
|
ids.forEach((id, idx) => {
|
|
1024
1098
|
const decl = ast.nodes[id];
|
|
1025
1099
|
const role = nodeRole(decl.kind);
|
|
1100
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1026
1101
|
const y = colStartY + idx * rowSpacing;
|
|
1027
1102
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1028
1103
|
nodes.push({
|
|
@@ -1030,17 +1105,18 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1030
1105
|
kind: decl.kind,
|
|
1031
1106
|
role,
|
|
1032
1107
|
label: decl.label,
|
|
1033
|
-
x: snapGrid(
|
|
1108
|
+
x: snapGrid(currentRankX),
|
|
1034
1109
|
y: snapGrid(y),
|
|
1035
|
-
width:
|
|
1036
|
-
height:
|
|
1037
|
-
style: decl
|
|
1110
|
+
width: dims.width,
|
|
1111
|
+
height: dims.height,
|
|
1112
|
+
style: resolveNodeStyle(decl, ast),
|
|
1038
1113
|
props: decl.props,
|
|
1039
1114
|
opacity: 0,
|
|
1040
|
-
shape: nodeShape(decl.kind, dtype),
|
|
1115
|
+
shape: nodeShape(decl.kind, dtype, decl.props),
|
|
1041
1116
|
focal
|
|
1042
1117
|
});
|
|
1043
1118
|
});
|
|
1119
|
+
currentRankX += rankW + colGap;
|
|
1044
1120
|
}
|
|
1045
1121
|
return nodes;
|
|
1046
1122
|
}
|
|
@@ -1141,7 +1217,7 @@ function layoutTree(ast, edges) {
|
|
|
1141
1217
|
y: snapGrid(pos.y),
|
|
1142
1218
|
width: NODE_W,
|
|
1143
1219
|
height: NODE_H,
|
|
1144
|
-
style: decl
|
|
1220
|
+
style: resolveNodeStyle(decl, ast),
|
|
1145
1221
|
props: decl.props,
|
|
1146
1222
|
opacity: 0,
|
|
1147
1223
|
shape: "card",
|
|
@@ -1209,10 +1285,10 @@ function layoutConstellation(ast) {
|
|
|
1209
1285
|
y: snapGrid(y),
|
|
1210
1286
|
width: NODE_W,
|
|
1211
1287
|
height: NODE_H,
|
|
1212
|
-
style: decl
|
|
1288
|
+
style: resolveNodeStyle(decl, ast),
|
|
1213
1289
|
props: decl.props,
|
|
1214
1290
|
opacity: 0,
|
|
1215
|
-
shape: nodeShape(decl.kind, "constellation"),
|
|
1291
|
+
shape: nodeShape(decl.kind, "constellation", decl.props),
|
|
1216
1292
|
focal: isFocal || decl.props.focal === true || decl.props.accent === true
|
|
1217
1293
|
});
|
|
1218
1294
|
}
|
|
@@ -1257,7 +1333,7 @@ function layoutLoop(ast) {
|
|
|
1257
1333
|
y: snapGrid(y),
|
|
1258
1334
|
width: isHub ? snapGrid(NODE_W * 1.25) : NODE_W,
|
|
1259
1335
|
height: isHub ? snapGrid(NODE_H * 1.15) : NODE_H,
|
|
1260
|
-
style: decl
|
|
1336
|
+
style: resolveNodeStyle(decl, ast),
|
|
1261
1337
|
props: decl.props,
|
|
1262
1338
|
opacity: 0,
|
|
1263
1339
|
shape: isHub ? "pill" : "card",
|
|
@@ -1311,7 +1387,7 @@ function layoutMedallion(ast, edges) {
|
|
|
1311
1387
|
y: snapGrid(rowY),
|
|
1312
1388
|
width: NODE_W,
|
|
1313
1389
|
height: NODE_H,
|
|
1314
|
-
style: decl
|
|
1390
|
+
style: resolveNodeStyle(decl, ast),
|
|
1315
1391
|
props: decl.props,
|
|
1316
1392
|
opacity: 0,
|
|
1317
1393
|
shape: "card",
|
|
@@ -1373,7 +1449,7 @@ function layoutQuadrant(ast) {
|
|
|
1373
1449
|
y: snapGrid(y),
|
|
1374
1450
|
width: NODE_W,
|
|
1375
1451
|
height: NODE_H,
|
|
1376
|
-
style: decl
|
|
1452
|
+
style: resolveNodeStyle(decl, ast),
|
|
1377
1453
|
props: decl.props,
|
|
1378
1454
|
opacity: 0,
|
|
1379
1455
|
shape: "card",
|
|
@@ -1426,7 +1502,7 @@ function layoutSwimlane(ast, edges) {
|
|
|
1426
1502
|
y: snapGrid(laneY),
|
|
1427
1503
|
width: NODE_W,
|
|
1428
1504
|
height: NODE_H,
|
|
1429
|
-
style: decl
|
|
1505
|
+
style: resolveNodeStyle(decl, ast),
|
|
1430
1506
|
props: decl.props,
|
|
1431
1507
|
opacity: 0,
|
|
1432
1508
|
shape: "card",
|
|
@@ -1475,7 +1551,7 @@ function layoutPyramid(ast) {
|
|
|
1475
1551
|
y: snapGrid(tierY),
|
|
1476
1552
|
width: snapGrid(nodeW),
|
|
1477
1553
|
height: NODE_H,
|
|
1478
|
-
style: decl
|
|
1554
|
+
style: resolveNodeStyle(decl, ast),
|
|
1479
1555
|
props: decl.props,
|
|
1480
1556
|
opacity: 0,
|
|
1481
1557
|
shape: "card",
|
|
@@ -1508,7 +1584,7 @@ function layoutTimeline(ast) {
|
|
|
1508
1584
|
y: snapGrid(y),
|
|
1509
1585
|
width: NODE_W,
|
|
1510
1586
|
height: NODE_H,
|
|
1511
|
-
style: decl
|
|
1587
|
+
style: resolveNodeStyle(decl, ast),
|
|
1512
1588
|
props: decl.props,
|
|
1513
1589
|
opacity: 0,
|
|
1514
1590
|
shape: "pill",
|
|
@@ -1551,7 +1627,7 @@ function layoutGantt(ast) {
|
|
|
1551
1627
|
y: snapGrid(y),
|
|
1552
1628
|
width: snapGrid(w),
|
|
1553
1629
|
height: barH,
|
|
1554
|
-
style: decl
|
|
1630
|
+
style: resolveNodeStyle(decl, ast),
|
|
1555
1631
|
props: decl.props,
|
|
1556
1632
|
opacity: 0,
|
|
1557
1633
|
shape: "pill",
|
|
@@ -1591,7 +1667,7 @@ function layoutVenn(ast) {
|
|
|
1591
1667
|
y: snapGrid(y),
|
|
1592
1668
|
width: VENN_NODE_SIZE,
|
|
1593
1669
|
height: VENN_NODE_SIZE,
|
|
1594
|
-
style: decl
|
|
1670
|
+
style: resolveNodeStyle(decl, ast),
|
|
1595
1671
|
props: decl.props,
|
|
1596
1672
|
opacity: 0,
|
|
1597
1673
|
shape: "circle",
|
|
@@ -1625,7 +1701,7 @@ function layoutLayers(ast) {
|
|
|
1625
1701
|
y: snapGrid(y),
|
|
1626
1702
|
width: snapGrid(layerW),
|
|
1627
1703
|
height: snapGrid(layerH),
|
|
1628
|
-
style: decl
|
|
1704
|
+
style: resolveNodeStyle(decl, ast),
|
|
1629
1705
|
props: decl.props,
|
|
1630
1706
|
opacity: 0,
|
|
1631
1707
|
shape: "rounded",
|
|
@@ -1672,7 +1748,7 @@ function layoutNested(ast) {
|
|
|
1672
1748
|
y: snapGrid(y),
|
|
1673
1749
|
width: snapGrid(w),
|
|
1674
1750
|
height: snapGrid(h),
|
|
1675
|
-
style: decl
|
|
1751
|
+
style: resolveNodeStyle(decl, ast),
|
|
1676
1752
|
props: decl.props,
|
|
1677
1753
|
opacity: 0,
|
|
1678
1754
|
shape: isCore ? "card" : "container",
|
|
@@ -1706,7 +1782,7 @@ function layoutRadar(ast) {
|
|
|
1706
1782
|
y: snapGrid(y),
|
|
1707
1783
|
width: NODE_W,
|
|
1708
1784
|
height: NODE_H,
|
|
1709
|
-
style: decl
|
|
1785
|
+
style: resolveNodeStyle(decl, ast),
|
|
1710
1786
|
props: decl.props,
|
|
1711
1787
|
opacity: 0,
|
|
1712
1788
|
shape: "rounded",
|
|
@@ -2094,17 +2170,32 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2094
2170
|
autoH = Math.max(720, Math.min(2e3, requiredH));
|
|
2095
2171
|
}
|
|
2096
2172
|
} else {
|
|
2097
|
-
const
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2173
|
+
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
2174
|
+
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
2175
|
+
const titleBand = hasTitle ? TITLE_BAND : 0;
|
|
2176
|
+
const bottomBand = hasBeatCaptions ? 44 : 0;
|
|
2177
|
+
const maxRankW = Math.max(...nodeIds.map((id) => computeNodeDimensions(ast.nodes[id]).width));
|
|
2178
|
+
const maxRankH = Math.max(...nodeIds.map((id) => computeNodeDimensions(ast.nodes[id]).height));
|
|
2179
|
+
const requiredW = SAFE * 2 + rankCount * maxRankW + (rankCount - 1) * 56 + groupPaddingBonus;
|
|
2180
|
+
const requiredH = SAFE * 2 + titleBand + bottomBand + maxInRank * maxRankH + (maxInRank - 1) * 44 + groupPaddingBonus;
|
|
2181
|
+
if (maxInRank === 1 && !hasTitle) {
|
|
2182
|
+
autoW = Math.max(1024, Math.min(2560, requiredW));
|
|
2183
|
+
autoH = Math.max(hasBeatCaptions ? 240 : 208, Math.min(360, requiredH + 24));
|
|
2184
|
+
} else if (maxInRank === 2 && !hasTitle) {
|
|
2185
|
+
autoW = Math.max(1024, Math.min(2560, requiredW));
|
|
2186
|
+
autoH = Math.max(hasBeatCaptions ? 368 : 336, Math.min(480, requiredH + 24));
|
|
2187
|
+
} else if (maxInRank === 3 && !hasTitle) {
|
|
2188
|
+
autoW = Math.max(1152, Math.min(2560, requiredW));
|
|
2189
|
+
autoH = Math.max(hasBeatCaptions ? 480 : 448, Math.min(640, requiredH + 24));
|
|
2190
|
+
} else if (nodeCount <= 2 && rankCount <= 2) {
|
|
2100
2191
|
autoW = 1024;
|
|
2101
|
-
autoH = 576;
|
|
2192
|
+
autoH = hasTitle ? 576 : 384;
|
|
2102
2193
|
} else if (nodeCount <= 4 && rankCount <= 3 && maxInRank <= 2) {
|
|
2103
2194
|
autoW = 1152;
|
|
2104
|
-
autoH = 648;
|
|
2195
|
+
autoH = hasTitle ? 648 : 448;
|
|
2105
2196
|
} else {
|
|
2106
2197
|
autoW = Math.max(1152, Math.min(2560, requiredW));
|
|
2107
|
-
autoH = Math.max(648, Math.min(1600, requiredH));
|
|
2198
|
+
autoH = Math.max(hasTitle ? 648 : maxInRank <= 2 ? 448 : 576, Math.min(1600, requiredH));
|
|
2108
2199
|
}
|
|
2109
2200
|
}
|
|
2110
2201
|
}
|
|
@@ -2440,6 +2531,116 @@ var THEMES = {
|
|
|
2440
2531
|
},
|
|
2441
2532
|
radiusMd: 4,
|
|
2442
2533
|
spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
|
|
2534
|
+
},
|
|
2535
|
+
ink: {
|
|
2536
|
+
name: "ink",
|
|
2537
|
+
canvas: "#f8fafd",
|
|
2538
|
+
surface: "#ffffff",
|
|
2539
|
+
surfaceRaised: "#eff6ff",
|
|
2540
|
+
border: "#1d4ed8",
|
|
2541
|
+
text: "#0f172a",
|
|
2542
|
+
textMuted: "#3b629b",
|
|
2543
|
+
paper: "#f8fafd",
|
|
2544
|
+
ink: "#1e40af",
|
|
2545
|
+
muted: "#4776b2",
|
|
2546
|
+
rule: "rgba(29, 78, 216, 0.14)",
|
|
2547
|
+
soft: "#60a5fa",
|
|
2548
|
+
link: "#2563eb",
|
|
2549
|
+
gridMinor: "rgba(37, 99, 235, 0.05)",
|
|
2550
|
+
gridMajor: "rgba(37, 99, 235, 0.10)",
|
|
2551
|
+
vignette: "rgba(219, 234, 254, 0.45)",
|
|
2552
|
+
accent: "#1d4ed8",
|
|
2553
|
+
accentTint: "rgba(29, 78, 216, 0.08)",
|
|
2554
|
+
nodeSurface: "#ffffff",
|
|
2555
|
+
nodeSurfaceRaised: "#f4f8ff",
|
|
2556
|
+
hairline: "rgba(29, 78, 216, 0.20)",
|
|
2557
|
+
shadow: "rgba(15, 35, 90, 0.09)",
|
|
2558
|
+
labelPlate: "#ffffff",
|
|
2559
|
+
flatCards: false,
|
|
2560
|
+
roles: {
|
|
2561
|
+
...ROLE_COLORS,
|
|
2562
|
+
compute: "#1d4ed8",
|
|
2563
|
+
code: "#2563eb",
|
|
2564
|
+
client: "#0284c7",
|
|
2565
|
+
data: "#0369a1",
|
|
2566
|
+
messaging: "#4338ca",
|
|
2567
|
+
network: "#0284c7",
|
|
2568
|
+
security: "#1e3a8a",
|
|
2569
|
+
platform: "#3b82f6",
|
|
2570
|
+
observability: "#0891b2",
|
|
2571
|
+
delivery: "#4f46e5",
|
|
2572
|
+
flow: "#2563eb",
|
|
2573
|
+
distributed: "#475569"
|
|
2574
|
+
},
|
|
2575
|
+
edges: {
|
|
2576
|
+
request: "#1d4ed8",
|
|
2577
|
+
response: "#3b82f6",
|
|
2578
|
+
event: "#0284c7",
|
|
2579
|
+
dependency: "#64748b"
|
|
2580
|
+
},
|
|
2581
|
+
fonts: {
|
|
2582
|
+
title: 'ui-serif, "Charter", "Newsreader", "Georgia", "Songti SC", "Noto Serif", serif',
|
|
2583
|
+
nodeName: 'ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif',
|
|
2584
|
+
mono: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace'
|
|
2585
|
+
},
|
|
2586
|
+
radiusMd: 6,
|
|
2587
|
+
spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
|
|
2588
|
+
series: ["#1d4ed8", "#0284c7", "#4338ca", "#0891b2", "#1e3a8a"]
|
|
2589
|
+
},
|
|
2590
|
+
doodle: {
|
|
2591
|
+
name: "doodle",
|
|
2592
|
+
canvas: "#fffef9",
|
|
2593
|
+
surface: "#ffffff",
|
|
2594
|
+
surfaceRaised: "#fdf8ec",
|
|
2595
|
+
border: "#18181b",
|
|
2596
|
+
text: "#18181b",
|
|
2597
|
+
textMuted: "#52525b",
|
|
2598
|
+
paper: "#fffef9",
|
|
2599
|
+
ink: "#18181b",
|
|
2600
|
+
muted: "#52525b",
|
|
2601
|
+
rule: "rgba(24, 24, 27, 0.16)",
|
|
2602
|
+
soft: "#71717a",
|
|
2603
|
+
link: "#f43f5e",
|
|
2604
|
+
gridMinor: "rgba(24, 24, 27, 0.05)",
|
|
2605
|
+
gridMajor: "rgba(24, 24, 27, 0.08)",
|
|
2606
|
+
vignette: "rgba(245, 235, 210, 0.35)",
|
|
2607
|
+
accent: "#f43f5e",
|
|
2608
|
+
accentTint: "rgba(244, 63, 94, 0.12)",
|
|
2609
|
+
nodeSurface: "#ffffff",
|
|
2610
|
+
nodeSurfaceRaised: "#fffdf5",
|
|
2611
|
+
hairline: "rgba(24, 24, 27, 0.25)",
|
|
2612
|
+
shadow: "rgba(24, 24, 27, 0.15)",
|
|
2613
|
+
labelPlate: "#ffffff",
|
|
2614
|
+
flatCards: true,
|
|
2615
|
+
roles: {
|
|
2616
|
+
...ROLE_COLORS,
|
|
2617
|
+
compute: "#2563eb",
|
|
2618
|
+
code: "#2563eb",
|
|
2619
|
+
client: "#f43f5e",
|
|
2620
|
+
data: "#10b981",
|
|
2621
|
+
messaging: "#8b5cf6",
|
|
2622
|
+
network: "#06b6d4",
|
|
2623
|
+
security: "#ef4444",
|
|
2624
|
+
platform: "#f59e0b",
|
|
2625
|
+
observability: "#14b8a6",
|
|
2626
|
+
delivery: "#f59e0b",
|
|
2627
|
+
flow: "#ec4899",
|
|
2628
|
+
distributed: "#71717a"
|
|
2629
|
+
},
|
|
2630
|
+
edges: {
|
|
2631
|
+
request: "#18181b",
|
|
2632
|
+
response: "#71717a",
|
|
2633
|
+
event: "#f43f5e",
|
|
2634
|
+
dependency: "#a1a1aa"
|
|
2635
|
+
},
|
|
2636
|
+
fonts: {
|
|
2637
|
+
title: '"Caveat", "Comic Neue", "Chalkboard SE", "Patrick Hand", "Comic Sans MS", cursive, sans-serif',
|
|
2638
|
+
nodeName: 'ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif',
|
|
2639
|
+
mono: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace'
|
|
2640
|
+
},
|
|
2641
|
+
radiusMd: 12,
|
|
2642
|
+
spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
|
|
2643
|
+
series: ["#f43f5e", "#2563eb", "#10b981", "#8b5cf6", "#f59e0b"]
|
|
2443
2644
|
}
|
|
2444
2645
|
};
|
|
2445
2646
|
function resolveTheme(name) {
|
|
@@ -3005,7 +3206,7 @@ function validateReferences(ast) {
|
|
|
3005
3206
|
}
|
|
3006
3207
|
}
|
|
3007
3208
|
for (const node of Object.values(ast.nodes)) {
|
|
3008
|
-
if (node.style && !ast.styles[node.style]) {
|
|
3209
|
+
if (typeof node.style === "string" && !ast.styles[node.style]) {
|
|
3009
3210
|
pushWarning(ast.diagnostics, seen, node.line, `node '${node.id}' references unknown style '${node.style}'`);
|
|
3010
3211
|
}
|
|
3011
3212
|
}
|
|
@@ -3109,6 +3310,7 @@ function parse(source, opts = {}) {
|
|
|
3109
3310
|
height: 720,
|
|
3110
3311
|
fps: 60,
|
|
3111
3312
|
theme: "paper",
|
|
3313
|
+
explicitTheme: false,
|
|
3112
3314
|
direction: "LR"
|
|
3113
3315
|
};
|
|
3114
3316
|
const styles = {};
|
|
@@ -3207,6 +3409,7 @@ function parse(source, opts = {}) {
|
|
|
3207
3409
|
const str = parseStringToken(rest2);
|
|
3208
3410
|
if (str) {
|
|
3209
3411
|
title = str.value;
|
|
3412
|
+
meta.title = title;
|
|
3210
3413
|
remainder = str.rest;
|
|
3211
3414
|
}
|
|
3212
3415
|
const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
|
|
@@ -3229,8 +3432,11 @@ function parse(source, opts = {}) {
|
|
|
3229
3432
|
} else if (k === "fps") {
|
|
3230
3433
|
meta.fps = Number(v);
|
|
3231
3434
|
} else if (k === "duration") meta.duration = Number(v);
|
|
3232
|
-
else if (k === "theme")
|
|
3233
|
-
|
|
3435
|
+
else if (k === "theme") {
|
|
3436
|
+
const val = String(v).toLowerCase();
|
|
3437
|
+
meta.theme = val;
|
|
3438
|
+
meta.explicitTheme = val !== "auto";
|
|
3439
|
+
} else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
|
|
3234
3440
|
else if (PLAYER_FLAT_KEY_SET.has(k)) {
|
|
3235
3441
|
const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
|
|
3236
3442
|
if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
|
|
@@ -3249,6 +3455,16 @@ function parse(source, opts = {}) {
|
|
|
3249
3455
|
i++;
|
|
3250
3456
|
continue;
|
|
3251
3457
|
}
|
|
3458
|
+
if (/^theme\s*[:=]?\s*([a-zA-Z0-9_-]+)/i.test(line)) {
|
|
3459
|
+
const match = line.match(/^theme\s*[:=]?\s*([a-zA-Z0-9_-]+)/i);
|
|
3460
|
+
if (match) {
|
|
3461
|
+
const val = match[1].toLowerCase();
|
|
3462
|
+
meta.theme = val;
|
|
3463
|
+
meta.explicitTheme = val !== "auto";
|
|
3464
|
+
}
|
|
3465
|
+
i++;
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3252
3468
|
const directiveMatch = line.match(PLAYER_DIRECTIVE_RE);
|
|
3253
3469
|
if (directiveMatch) {
|
|
3254
3470
|
const error = applyPlayerSetting(meta.player ??= {}, "player", directiveMatch[1], directiveMatch[2] ?? "");
|
|
@@ -4621,7 +4837,7 @@ function diagnoseMarkdyCode(code, options = {}) {
|
|
|
4621
4837
|
snippet: trimmed,
|
|
4622
4838
|
suggestion: closeTheme ? `Use theme '${closeTheme.match}'.` : `Choose from: ${THEME_NAMES.join(", ")}.`,
|
|
4623
4839
|
didYouMean: closeTheme?.match,
|
|
4624
|
-
ruleExplanation: "Supported themes: paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal.",
|
|
4840
|
+
ruleExplanation: "Supported themes: paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal, ink, doodle.",
|
|
4625
4841
|
fix: closeTheme ? { original: themeMatch[0], replacement: `theme=${closeTheme.match}` } : void 0
|
|
4626
4842
|
});
|
|
4627
4843
|
}
|
|
@@ -5517,7 +5733,9 @@ function getIntelliCodeCompletions(docText, cursorLine, cursorCol) {
|
|
|
5517
5733
|
{ name: "editorial", desc: "High-contrast serif luxury publication layout" },
|
|
5518
5734
|
{ name: "graphite", desc: "Sleek slate monochrome engineering aesthetic" },
|
|
5519
5735
|
{ name: "terminal", desc: "Retro phosphor CRT hacker terminal green on black" },
|
|
5520
|
-
{ name: "sketchy", desc: "Hand-drawn sketchy whiteboard marker design" }
|
|
5736
|
+
{ name: "sketchy", desc: "Hand-drawn sketchy whiteboard marker design" },
|
|
5737
|
+
{ name: "ink", desc: "Monochromatic blue ink style inspired by ballpoint pens, fountain pens, cyanotypes & porcelain" },
|
|
5738
|
+
{ name: "doodle", desc: "Playful hand-drawn doodle sketchbook with felt-tip marker pens and comic block shadows" }
|
|
5521
5739
|
];
|
|
5522
5740
|
for (const t of themeEntries) {
|
|
5523
5741
|
items.push({
|
package/package.json
CHANGED