@diagrammo/dgmo 0.15.0 → 0.16.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.
Files changed (127) hide show
  1. package/README.md +23 -10
  2. package/dist/advanced.cjs +53094 -0
  3. package/dist/advanced.d.cts +4690 -0
  4. package/dist/advanced.d.ts +4690 -0
  5. package/dist/advanced.js +52849 -0
  6. package/dist/auto.cjs +2298 -2069
  7. package/dist/auto.js +132 -109
  8. package/dist/auto.mjs +2294 -2065
  9. package/dist/cli.cjs +175 -152
  10. package/dist/editor.cjs +8 -9
  11. package/dist/editor.js +8 -9
  12. package/dist/highlight.cjs +8 -9
  13. package/dist/highlight.js +8 -9
  14. package/dist/index.cjs +2281 -2048
  15. package/dist/index.d.cts +45 -1
  16. package/dist/index.d.ts +45 -1
  17. package/dist/index.js +2276 -2044
  18. package/dist/internal.cjs +2064 -1831
  19. package/dist/internal.d.cts +113 -113
  20. package/dist/internal.d.ts +113 -113
  21. package/dist/internal.js +2059 -1826
  22. package/dist/pert.cjs +325 -0
  23. package/dist/pert.d.cts +542 -0
  24. package/dist/pert.d.ts +542 -0
  25. package/dist/pert.js +294 -0
  26. package/docs/language-reference.md +83 -66
  27. package/gallery/fixtures/area.dgmo +3 -3
  28. package/gallery/fixtures/bar-stacked.dgmo +5 -5
  29. package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
  30. package/gallery/fixtures/c4-full.dgmo +8 -8
  31. package/gallery/fixtures/class-full.dgmo +2 -2
  32. package/gallery/fixtures/doughnut.dgmo +6 -6
  33. package/gallery/fixtures/flowchart-colors.dgmo +3 -3
  34. package/gallery/fixtures/function.dgmo +3 -3
  35. package/gallery/fixtures/gantt-full.dgmo +9 -9
  36. package/gallery/fixtures/gantt.dgmo +7 -7
  37. package/gallery/fixtures/infra-full.dgmo +6 -6
  38. package/gallery/fixtures/infra.dgmo +2 -2
  39. package/gallery/fixtures/kanban.dgmo +9 -9
  40. package/gallery/fixtures/line.dgmo +2 -2
  41. package/gallery/fixtures/multi-line.dgmo +3 -3
  42. package/gallery/fixtures/org-full.dgmo +6 -6
  43. package/gallery/fixtures/quadrant.dgmo +2 -2
  44. package/gallery/fixtures/sankey.dgmo +9 -9
  45. package/gallery/fixtures/scatter.dgmo +3 -3
  46. package/gallery/fixtures/sequence-tags-protocols.dgmo +8 -8
  47. package/gallery/fixtures/sequence-tags.dgmo +7 -7
  48. package/gallery/fixtures/sitemap-full.dgmo +7 -7
  49. package/gallery/fixtures/slope.dgmo +5 -5
  50. package/gallery/fixtures/spr-eras.dgmo +9 -9
  51. package/gallery/fixtures/timeline.dgmo +3 -3
  52. package/gallery/fixtures/venn.dgmo +3 -3
  53. package/package.json +28 -3
  54. package/src/advanced.ts +730 -0
  55. package/src/auto/index.ts +14 -13
  56. package/src/boxes-and-lines/layout.ts +481 -445
  57. package/src/boxes-and-lines/renderer.ts +5 -1
  58. package/src/c4/parser.ts +8 -8
  59. package/src/c4/renderer.ts +15 -8
  60. package/src/chart-types.ts +0 -5
  61. package/src/chart.ts +18 -9
  62. package/src/class/parser.ts +8 -15
  63. package/src/class/renderer.ts +17 -6
  64. package/src/cli.ts +15 -13
  65. package/src/completion-types.ts +28 -0
  66. package/src/completion.ts +28 -21
  67. package/src/cycle/layout.ts +2 -2
  68. package/src/cycle/parser.ts +14 -0
  69. package/src/cycle/renderer.ts +6 -3
  70. package/src/d3.ts +1537 -1164
  71. package/src/echarts.ts +37 -20
  72. package/src/editor/dgmo.grammar +1 -3
  73. package/src/editor/dgmo.grammar.js +8 -8
  74. package/src/editor/dgmo.grammar.terms.js +11 -12
  75. package/src/editor/highlight-api.ts +0 -1
  76. package/src/editor/highlight.ts +0 -1
  77. package/src/er/parser.ts +19 -20
  78. package/src/er/renderer.ts +20 -8
  79. package/src/gantt/calculator.ts +1 -11
  80. package/src/gantt/parser.ts +17 -17
  81. package/src/gantt/renderer.ts +9 -6
  82. package/src/graph/flowchart-parser.ts +19 -85
  83. package/src/graph/flowchart-renderer.ts +4 -9
  84. package/src/graph/layout.ts +0 -2
  85. package/src/graph/state-parser.ts +17 -62
  86. package/src/graph/state-renderer.ts +4 -9
  87. package/src/index.ts +17 -1
  88. package/src/infra/parser.ts +40 -30
  89. package/src/infra/renderer.ts +9 -6
  90. package/src/internal.ts +9 -721
  91. package/src/journey-map/parser.ts +10 -3
  92. package/src/journey-map/renderer.ts +3 -1
  93. package/src/kanban/parser.ts +12 -8
  94. package/src/kanban/renderer.ts +3 -1
  95. package/src/mindmap/layout.ts +1 -1
  96. package/src/mindmap/parser.ts +3 -3
  97. package/src/mindmap/renderer.ts +2 -1
  98. package/src/org/parser.ts +3 -3
  99. package/src/org/renderer.ts +5 -4
  100. package/src/pert/layout.ts +1 -1
  101. package/src/pert/monte-carlo.ts +2 -2
  102. package/src/pert/parser.ts +10 -10
  103. package/src/pert/renderer.ts +7 -2
  104. package/src/pert/types.ts +1 -1
  105. package/src/pyramid/parser.ts +12 -0
  106. package/src/raci/parser.ts +44 -14
  107. package/src/raci/renderer.ts +3 -2
  108. package/src/raci/types.ts +4 -3
  109. package/src/ring/parser.ts +12 -0
  110. package/src/sequence/parser.ts +15 -9
  111. package/src/sequence/renderer.ts +2 -5
  112. package/src/sitemap/layout.ts +0 -2
  113. package/src/sitemap/parser.ts +12 -38
  114. package/src/sitemap/renderer.ts +13 -13
  115. package/src/sitemap/types.ts +0 -1
  116. package/src/tech-radar/interactive.ts +1 -1
  117. package/src/tech-radar/renderer.ts +6 -4
  118. package/src/tech-radar/types.ts +2 -0
  119. package/src/utils/arrows.ts +3 -28
  120. package/src/utils/legend-d3.ts +12 -6
  121. package/src/utils/legend-layout.ts +1 -1
  122. package/src/utils/legend-types.ts +1 -1
  123. package/src/utils/parsing.ts +64 -35
  124. package/src/utils/tag-groups.ts +109 -30
  125. package/src/wireframe/layout.ts +11 -7
  126. package/src/wireframe/parser.ts +4 -4
  127. package/src/wireframe/renderer.ts +5 -2
package/src/echarts.ts CHANGED
@@ -367,12 +367,15 @@ export function parseExtendedChart(
367
367
  }
368
368
 
369
369
  // [Category] container header with optional color: [Category Name] or [Category Name](color)
370
- const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
370
+ // Category brackets with optional trailing-token color (§1.5):
371
+ // `[Name]` or `[Name] color`. Per universal rule, color is a bare token.
372
+ const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s+(\S+))?\s*$/);
371
373
  if (categoryMatch) {
372
374
  const catName = categoryMatch[1].trim();
373
- const catColor = categoryMatch[2]
375
+ const rawCatColor = categoryMatch[2]?.trim();
376
+ const catColor = rawCatColor
374
377
  ? (resolveColorWithDiagnostic(
375
- categoryMatch[2].trim(),
378
+ rawCatColor,
376
379
  lineNumber,
377
380
  result.diagnostics,
378
381
  palette
@@ -388,9 +391,14 @@ export function parseExtendedChart(
388
391
  continue;
389
392
  }
390
393
 
391
- // Sankey/chord link syntax: Source -> Target Value (directed) or Source -- Target Value (undirected)
394
+ // Sankey/chord link syntax (§1.5 universal trailing-token):
395
+ // `Source -> Target value` (directed, no link color)
396
+ // `Source -> Target value linkColor` (directed, trailing-token link color)
397
+ // `Source -- Target value` (undirected)
398
+ // Link color (if present) must be a recognized lowercase palette word.
399
+ // Source/target labels still accept trailing-token color via extractColor.
392
400
  const arrowMatch = trimmed.match(
393
- /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d,_]+(?:\.[\d]+)?)\s*(?:\(([^)]+)\))?\s*$/
401
+ /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d,_]+(?:\.[\d]+)?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
394
402
  );
395
403
  if (arrowMatch) {
396
404
  const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
@@ -441,13 +449,22 @@ export function parseExtendedChart(
441
449
  sankeyStack.pop();
442
450
  }
443
451
  if (sankeyStack.length > 0) {
444
- // Parse "TargetName value (linkColor)" or "TargetName(nodeColor) value (linkColor)"
445
- // Strip trailing (color) annotation before parseDataRowValues it can't handle it
452
+ // Indented sankey child (§1.5 trailing-token):
453
+ // `TargetName value` link, no link color
454
+ // `TargetName value linkColor` — link with link color
455
+ // `TargetName nodeColor value` — node-colored child
456
+ // `TargetName nodeColor value linkColor` — both
457
+ // Strategy: peel a trailing recognized color word (after the value)
458
+ // first, then run parseDataRowValues on the remainder. Trailing
459
+ // tokens that aren't recognized colors stay in the data row.
446
460
  const valColorMatch = trimmed.match(
447
- /(-?[\d,_]+(?:\.[\d]+)?)\s*\(([^)]+)\)\s*$/
461
+ /(-?[\d,_]+(?:\.[\d]+)?)\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/
448
462
  );
449
463
  const strippedLine = valColorMatch
450
- ? trimmed.replace(/\s*\([^)]+\)\s*$/, '')
464
+ ? trimmed.replace(
465
+ /\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/,
466
+ ''
467
+ )
451
468
  : trimmed;
452
469
  const dataRow = parseDataRowValues(strippedLine);
453
470
  if (dataRow && dataRow.values.length === 1) {
@@ -1026,7 +1043,7 @@ function buildChordOption(
1026
1043
  isDark: boolean,
1027
1044
  textColor: string,
1028
1045
  colors: string[],
1029
- bg: string,
1046
+ _bg: string,
1030
1047
  titleConfig: EChartsOption['title']
1031
1048
  ): EChartsOption {
1032
1049
  // Extract unique nodes from links
@@ -1185,7 +1202,7 @@ function evaluateExpression(expr: string, x: number): number {
1185
1202
  function buildFunctionOption(
1186
1203
  parsed: ParsedExtendedChart,
1187
1204
  palette: PaletteColors,
1188
- isDark: boolean,
1205
+ _isDark: boolean,
1189
1206
  textColor: string,
1190
1207
  axisLineColor: string,
1191
1208
  gridOpacity: number,
@@ -2092,7 +2109,7 @@ function buildFunnelOption(
2092
2109
  isDark: boolean,
2093
2110
  textColor: string,
2094
2111
  colors: string[],
2095
- bg: string,
2112
+ _bg: string,
2096
2113
  titleConfig: EChartsOption['title']
2097
2114
  ): EChartsOption {
2098
2115
  // Sort data descending by value for funnel ordering
@@ -2464,7 +2481,7 @@ function buildBarOption(
2464
2481
  splitLineColor: string,
2465
2482
  gridOpacity: number,
2466
2483
  colors: string[],
2467
- bg: string,
2484
+ _bg: string,
2468
2485
  titleConfig: EChartsOption['title'],
2469
2486
  chartWidth?: number
2470
2487
  ): EChartsOption {
@@ -2893,7 +2910,7 @@ function buildPieOption(
2893
2910
  isDark: boolean,
2894
2911
  textColor: string,
2895
2912
  colors: string[],
2896
- bg: string,
2913
+ _bg: string,
2897
2914
  titleConfig: EChartsOption['title'],
2898
2915
  isDoughnut: boolean
2899
2916
  ): EChartsOption {
@@ -3021,7 +3038,7 @@ function buildPolarAreaOption(
3021
3038
  isDark: boolean,
3022
3039
  textColor: string,
3023
3040
  colors: string[],
3024
- bg: string,
3041
+ _bg: string,
3025
3042
  titleConfig: EChartsOption['title']
3026
3043
  ): EChartsOption {
3027
3044
  const data = parsed.data.map((d, i) => {
@@ -3079,7 +3096,7 @@ function buildBarStackedOption(
3079
3096
  splitLineColor: string,
3080
3097
  gridOpacity: number,
3081
3098
  colors: string[],
3082
- bg: string,
3099
+ _bg: string,
3083
3100
  titleConfig: EChartsOption['title'],
3084
3101
  chartWidth?: number
3085
3102
  ): EChartsOption {
@@ -3282,11 +3299,11 @@ export async function renderExtendedChartForExport(
3282
3299
  // In static export, expand the first group so entries are visible
3283
3300
  // Extract grid offsets for plot-area-centered legend
3284
3301
  const grid = option.grid as Record<string, unknown> | undefined;
3285
- const gridLeftPct = grid?.left
3286
- ? parseFloat(String(grid.left))
3302
+ const gridLeftPct = grid?.['left']
3303
+ ? parseFloat(String(grid['left']))
3287
3304
  : undefined;
3288
- const gridRightPct = grid?.right
3289
- ? parseFloat(String(grid.right))
3305
+ const gridRightPct = grid?.['right']
3306
+ ? parseFloat(String(grid['right']))
3290
3307
  : undefined;
3291
3308
  const { svg: legendSvgStr } = renderLegendSvg(legendGroups, {
3292
3309
  palette: effectivePalette,
@@ -8,7 +8,6 @@ contentPart {
8
8
  SectionMarker |
9
9
  Url |
10
10
  OpenBracket | CloseBracket | OpenParen | CloseParen | OpenAngle | CloseAngle |
11
- ColorAnnotation |
12
11
  Pipe | Colon | Comma | Plus | Dash | Tilde | Star | Question |
13
12
  ChartType |
14
13
  TagKeyword | DirectiveKeyword | ControlKeyword | ModifierKeyword |
@@ -38,7 +37,6 @@ contentPart {
38
37
 
39
38
  SectionMarker { "==" }
40
39
  Url { "http" "s"? "://" ![ \t\n|,)\]>]+ }
41
- ColorAnnotation { "(" $[a-z\-]+ ")" }
42
40
 
43
41
  Pipe { "|" }
44
42
  Colon { ":" }
@@ -66,7 +64,7 @@ contentPart {
66
64
 
67
65
  @precedence {
68
66
  Comment, SyncArrow, AsyncArrow,
69
- Duration, DateLiteral, Percentage, Number, SectionMarker, Url, ColorAnnotation,
67
+ Duration, DateLiteral, Percentage, Number, SectionMarker, Url,
70
68
  Pipe, Colon, Comma, Plus, Dash, Tilde, Star, Question,
71
69
  OpenBracket, CloseBracket, OpenParen, CloseParen, OpenAngle, CloseAngle,
72
70
  QuotedString, Identifier, Punct
@@ -3,16 +3,16 @@ import {LRParser} from "@lezer/lr"
3
3
  import {specializeKeyword} from "./tokens"
4
4
  export const parser = LRParser.deserialize({
5
5
  version: 14,
6
- states: "!WQVQPOOOOQO'#DV'#DVOOQO'#DQ'#DQO%cQPO'#CdOOQO'#DP'#DPQVQPOOOOQO-E7O-E7OOOQO,59O,59OOOQO-E6}-E6}",
7
- stateData: "&X~OwOS~OPPOQPORPOSPOTPOVSOXPOYPOZPO[PO]PO^PO_PO`POaPObPOcPOdPOePOfPOgPOhPOiPOjPOkPOlPOmPOnPOoPOpPOqPOrPOxSO~OPPOQPORPOSPOTPOXPOYPOZPO[PO]PO^PO_PO`POaPObPOcPOdPOePOfPOgPOhPOiPOjPOkPOlPOmPOnPOoPOpPOqPOrPO~OxVO~P#`OVXYZ[]^_`ghijklmnoabcdefpqrk~",
8
- goto: "!czPPPPPPPP{PPPPPPPPPPPPPPPPPPPPPPPPPP!P!VPPPP!^TSOTQTORWTSROTRURVQORT",
9
- nodeNames: "⚠ ChartType TagKeyword DirectiveKeyword ControlKeyword ModifierKeyword Document Comment ContentLine SyncArrow AsyncArrow Duration DateLiteral Percentage Number SectionMarker Url OpenBracket CloseBracket OpenParen CloseParen OpenAngle CloseAngle ColorAnnotation Pipe Colon Comma Plus Dash Tilde Star Question QuotedString Identifier Punct",
10
- maxTerm: 41,
6
+ states: "!WQVQPOOOOQO'#DU'#DUOOQO'#DP'#DPO%]QPO'#CdOOQO'#DO'#DOQVQPOOOOQO-E6}-E6}OOQO,59O,59OOOQO-E6|-E6|",
7
+ stateData: "&Q~OvOS~OPPOQPORPOSPOTPOVSOXPOYPOZPO[PO]PO^PO_PO`POaPObPOcPOdPOePOfPOgPOhPOiPOjPOkPOlPOmPOnPOoPOpPOqPOwSO~OPPOQPORPOSPOTPOXPOYPOZPO[PO]PO^PO_PO`POaPObPOcPOdPOePOfPOgPOhPOiPOjPOkPOlPOmPOnPOoPOpPOqPO~OwVO~P#]OVXYZ[]^_`ghijklmnabcdefopqk~",
8
+ goto: "!byPPPPPPPPzPPPPPPPPPPPPPPPPPPPPPPPPP!O!UPPPP!]TSOTQTORWTSROTRURVQORT",
9
+ nodeNames: "⚠ ChartType TagKeyword DirectiveKeyword ControlKeyword ModifierKeyword Document Comment ContentLine SyncArrow AsyncArrow Duration DateLiteral Percentage Number SectionMarker Url OpenBracket CloseBracket OpenParen CloseParen OpenAngle CloseAngle Pipe Colon Comma Plus Dash Tilde Star Question QuotedString Identifier Punct",
10
+ maxTerm: 40,
11
11
  skippedNodes: [0],
12
12
  repeatNodeCount: 2,
13
- tokenData: "=_~RzOX#uXY#zYZ$VZp#upq#zqr#urs$[st#uux#uxy%eyz&Tz{&[{|&c|}&j}!O&q!O!P#u!P!Q'Q!Q!['q![!]0d!]!^#u!^!_0k!_!`0r!`!a1P!a!b1W!b!c#u!c!}1_!}#O4e#O#P#u#P#Q4j#Q#R#u#R#S1_#S#T#u#T#[1_#[#]4q#]#o1_#o#p#u#p#q<q#q#r#u#r#s<x#s;'S#u;'S;=`=X<%lO#u~#zOr~~$PQw~XY#zpq#z~$[Ox~~$aUr~OY$sZr$srs%Ys;'S$s;'S;=`%_<%lO$s~$vUOY$sZr$srs%Ys;'S$s;'S;=`%_<%lO$s~%_Op~~%bP;=`<%l$s~%lQc~r~}!O%r#T#o%r~%uRyz&O}!O%r#T#o%r~&TOg~~&[Od~r~~&cOn~r~~&jOk~r~~&qOj~r~~&xPl~r~!`!a&{~'QOX~~'VPr~!P!Q'Y~'_SV~OY'YZ;'S'Y;'S;=`'k<%lO'Y~'nP;=`<%l'Y~'x]^~r~uv(q|}(v!O!P)s!Q![+d#R#S0O#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~(vO]~~(yP!Q![(|~)PP!Q![)S~)VP!Q![)Y~)_Q^~|}(v!O!P)e~)hP!Q![)k~)pP^~!Q![)k~)vP!Q![)y~*OY^~uv(q!Q![)y#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~*qP#W#X*t~*yPZ~!a!b*|~+ROZ~~+WQZ~!a!b*|#]#^+^~+aP#b#c*t~+i]^~uv(q|}(v!O!P)s!Q![,b#R#S0O#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~,g]^~uv(q|}(v!O!P)s!Q![-`#R#S0O#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~-e]^~uv(q}!O.^!O!P)s!Q![/T#R#S0O#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~.aP!Q![.d~.gP!Q![.j~.oP[~}!O.r~.uP!Q![.x~.{P!Q![/O~/TO[~~/Y[^~uv(q!O!P)s!Q![/T#R#S0O#U#V*n#W#X*t#[#]*t#a#b+R#e#f*t#g#h*t#k#l*t#m#n*t~0RP!Q![0U~0ZR^~!O!P)e!Q![0U#R#S0O~0kOi~r~~0rOe~r~~0wPr~!_!`0z~1PO_~~1WOf~r~~1_Oo~r~~1f_q~r~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#o2e~2j_q~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#o2e~3l]qr2est2evw2ewx2e{|2e!O!P2e!P!Q2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#o2e~4jOa~~4qOb~r~~4xaq~r~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#h2e#h#i5}#i#o2e~6Saq~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#h2e#h#i7X#i#o2e~7^aq~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#d2e#d#e8c#e#o2e~8hbq~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e![!]9p!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#g2e#g#h;j#h#o2e~9sP!P!Q9v~9yP!P!Q9|~:PYOX:oZp:oqy:oz|:o}!`:o!a#P:o#Q#p:o#q;'S:o;'S;=`;d<%lO:o~:tY`~OX:oZp:oqy:oz|:o}!`:o!a#P:o#Q#p:o#q;'S:o;'S;=`;d<%lO:o~;gP;=`<%l:o~;o`q~qr2est2evw2ewx2e{|2e}!O3i!O!P2e!P!Q2e!Q![2e![!]9p!_!`2e!a!b2e!b!c2e!c!}2e#R#S2e#T#o2e~<xOh~r~~=PPm~r~!`!a=S~=XOY~~=[P;=`<%l#u",
13
+ tokenData: "<v~RzOX#uXY#zYZ$VZp#upq#zqr#urs$[st#uux#uxy%eyz%lz{%s{|%z|}&R}!O&Y!O!P#u!P!Q&i!Q!['Y![!]/{!]!^#u!^!_0S!_!`0Z!`!a0h!a!b0o!b!c#u!c!}0v!}#O3|#O#P#u#P#Q4R#Q#R#u#R#S0v#S#T#u#T#[0v#[#]4Y#]#o0v#o#p#u#p#q<Y#q#r#u#r#s<a#s;'S#u;'S;=`<p<%lO#u~#zOq~~$PQv~XY#zpq#z~$[Ow~~$aUq~OY$sZr$srs%Ys;'S$s;'S;=`%_<%lO$s~$vUOY$sZr$srs%Ys;'S$s;'S;=`%_<%lO$s~%_Oo~~%bP;=`<%l$s~%lOc~q~~%sOd~q~~%zOm~q~~&ROj~q~~&YOi~q~~&aPk~q~!`!a&d~&iOX~~&nPq~!P!Q&q~&vSV~OY&qZ;'S&q;'S;=`'S<%lO&q~'VP;=`<%l&q~'a]^~q~uv(Y|}(_!O!P)[!Q![*{#R#S/g#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~(_O]~~(bP!Q![(e~(hP!Q![(k~(nP!Q![(q~(vQ^~|}(_!O!P(|~)PP!Q![)S~)XP^~!Q![)S~)_P!Q![)b~)gY^~uv(Y!Q![)b#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~*YP#W#X*]~*bPZ~!a!b*e~*jOZ~~*oQZ~!a!b*e#]#^*u~*xP#b#c*]~+Q]^~uv(Y|}(_!O!P)[!Q![+y#R#S/g#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~,O]^~uv(Y|}(_!O!P)[!Q![,w#R#S/g#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~,|]^~uv(Y}!O-u!O!P)[!Q![.l#R#S/g#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~-xP!Q![-{~.OP!Q![.R~.WP[~}!O.Z~.^P!Q![.a~.dP!Q![.g~.lO[~~.q[^~uv(Y!O!P)[!Q![.l#R#S/g#U#V*V#W#X*]#[#]*]#a#b*j#e#f*]#g#h*]#k#l*]#m#n*]~/jP!Q![/m~/rR^~!O!P(|!Q![/m#R#S/g~0SOh~q~~0ZOe~q~~0`Pq~!_!`0c~0hO_~~0oOf~q~~0vOn~q~~0}_p~q~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#o1|~2R_p~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#o1|~3T]qr1|st1|vw1|wx1|{|1|!O!P1|!P!Q1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#o1|~4ROa~~4YOb~q~~4aap~q~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#h1|#h#i5f#i#o1|~5kap~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#h1|#h#i6p#i#o1|~6uap~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#d1|#d#e7z#e#o1|~8Pbp~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|![!]9X!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#g1|#g#h;R#h#o1|~9[P!P!Q9_~9bP!P!Q9e~9hYOX:WZp:Wqy:Wz|:W}!`:W!a#P:W#Q#p:W#q;'S:W;'S;=`:{<%lO:W~:]Y`~OX:WZp:Wqy:Wz|:W}!`:W!a#P:W#Q#p:W#q;'S:W;'S;=`:{<%lO:W~;OP;=`<%l:W~;W`p~qr1|st1|vw1|wx1|{|1|}!O3Q!O!P1|!P!Q1|!Q![1|![!]9X!_!`1|!a!b1|!b!c1|!c!}1|#R#S1|#T#o1|~<aOg~q~~<hPl~q~!`!a<k~<pOY~~<sP;=`<%l#u",
14
14
  tokenizers: [0],
15
15
  topRules: {"Document":[0,6]},
16
- specialized: [{term: 33, get: (value, stack) => (specializeKeyword(value, stack) << 1), external: specializeKeyword}],
17
- tokenPrec: 210
16
+ specialized: [{term: 32, get: (value, stack) => (specializeKeyword(value, stack) << 1), external: specializeKeyword}],
17
+ tokenPrec: 204
18
18
  })
@@ -22,15 +22,14 @@ export const
22
22
  CloseParen = 20,
23
23
  OpenAngle = 21,
24
24
  CloseAngle = 22,
25
- ColorAnnotation = 23,
26
- Pipe = 24,
27
- Colon = 25,
28
- Comma = 26,
29
- Plus = 27,
30
- Dash = 28,
31
- Tilde = 29,
32
- Star = 30,
33
- Question = 31,
34
- QuotedString = 32,
35
- Identifier = 33,
36
- Punct = 34
25
+ Pipe = 23,
26
+ Colon = 24,
27
+ Comma = 25,
28
+ Plus = 26,
29
+ Dash = 27,
30
+ Tilde = 28,
31
+ Star = 29,
32
+ Question = 30,
33
+ QuotedString = 31,
34
+ Identifier = 32,
35
+ Punct = 33
@@ -46,7 +46,6 @@ const NODE_TO_ROLE: Record<string, string> = {
46
46
  Percentage: 'number',
47
47
  SectionMarker: 'heading',
48
48
  Url: 'url',
49
- ColorAnnotation: 'colorAnnotation',
50
49
  OpenBracket: 'bracket',
51
50
  CloseBracket: 'bracket',
52
51
  OpenParen: 'bracket',
@@ -23,7 +23,6 @@ export const dgmoHighlighting: NodePropSource = styleTags({
23
23
  OpenAngle: t.angleBracket,
24
24
  CloseAngle: t.angleBracket,
25
25
  Url: t.url,
26
- ColorAnnotation: t.atom,
27
26
  Pipe: t.separator,
28
27
  Colon: t.separator,
29
28
  Plus: t.separator,
package/src/er/parser.ts CHANGED
@@ -46,15 +46,15 @@ function tableId(name: string): string {
46
46
  // Regex patterns
47
47
  // ============================================================
48
48
 
49
- // Table declaration: name or name (color) or name | key: value
50
- // Multi-word names allowed; quote `"name with reserved chars"` if the name
51
- // contains pipe / paren / colon. Captures:
49
+ // Table declaration: `name`, `name color` (trailing-token §1.5), or
50
+ // `name | key: value`. Multi-word names allowed; quote `"name with reserved
51
+ // chars"` if the name contains pipe / paren / colon. Captures:
52
52
  // 1: quoted-name content (without surrounding quotes), or undefined
53
53
  // 2: bare-name (trimmed at call site), or undefined
54
- // 3: color (inside parens), or undefined
54
+ // 3: trailing-token color (recognized palette word), or undefined
55
55
  // 4: pipe metadata (without leading `|`), or undefined
56
56
  const TABLE_DECL_RE =
57
- /^(?:"([^"]+)"|([a-zA-Z_][^|":(]*?))(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
57
+ /^(?:"([^"]+)"|([a-zA-Z_][^|":(]*?))(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?(?:\s*\|(.+))?$/;
58
58
 
59
59
  // Column: name [type] [constraints...] — space-separated, no colon, no brackets
60
60
  // First token is always the name. Second token is the type if it's not a constraint keyword.
@@ -229,14 +229,6 @@ export function parseERDiagram(
229
229
  error: null,
230
230
  };
231
231
 
232
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
233
- const _fail = (line: number, message: string): ParsedERDiagram => {
234
- const diag = makeDgmoError(line, message);
235
- result.diagnostics.push(diag);
236
- result.error = formatDgmoError(diag);
237
- return result;
238
- };
239
-
240
232
  const pushError = (line: number, message: string): void => {
241
233
  const diag = makeDgmoError(line, message);
242
234
  result.diagnostics.push(diag);
@@ -358,7 +350,7 @@ export function parseERDiagram(
358
350
  result.diagnostics.push(
359
351
  makeDgmoError(
360
352
  lineNumber,
361
- `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`,
353
+ `Expected 'Value color' in tag group '${currentTagGroup.name}'`,
362
354
  'warning'
363
355
  )
364
356
  );
@@ -502,7 +494,7 @@ export function parseERDiagram(
502
494
  if (result.tables.length === 0 && !result.error) {
503
495
  const diag = makeDgmoError(
504
496
  1,
505
- 'No tables found. Add table declarations like "users" or "orders (blue)".'
497
+ 'No tables found. Add table declarations like "users" or "orders blue".'
506
498
  );
507
499
  result.diagnostics.push(diag);
508
500
  result.error = formatDgmoError(diag);
@@ -628,7 +620,7 @@ export function looksLikeERDiagram(content: string): boolean {
628
620
  // Symbol extraction (for completion API)
629
621
  // ============================================================
630
622
 
631
- import type { DiagramSymbols } from '../completion';
623
+ import type { DiagramSymbols } from '../completion-types';
632
624
 
633
625
  /**
634
626
  * Extract table names (entities) and ER keywords from document text.
@@ -640,15 +632,22 @@ export function extractSymbols(docText: string): DiagramSymbols {
640
632
  for (const rawLine of docText.split('\n')) {
641
633
  const line = rawLine.trim();
642
634
  if (inMetadata && /^er(\s|$)/i.test(line)) continue;
643
- if (inMetadata && OPTION_NOCOLON_RE.test(line)) continue; // option line
644
- inMetadata = false;
645
- if (line.length === 0) continue;
635
+ // Under §1.5 trailing-token, `Users blue` matches OPTION_NOCOLON_RE
636
+ // (key=Users, value=blue) but is actually a table with a color.
637
+ // Detect tables FIRST so they aren't swallowed by the option fallback.
646
638
  if (/^\s/.test(rawLine)) continue; // indented = column definition, not table
639
+ if (line.length === 0) continue;
647
640
  const m = TABLE_DECL_RE.exec(line);
648
641
  if (m) {
649
642
  const name = (m[1] ?? m[2] ?? '').trim();
650
- if (name) entities.push(name);
643
+ if (name) {
644
+ inMetadata = false;
645
+ entities.push(name);
646
+ continue;
647
+ }
651
648
  }
649
+ if (inMetadata && OPTION_NOCOLON_RE.test(line)) continue; // option line
650
+ inMetadata = false;
652
651
  }
653
652
  return {
654
653
  kind: 'er',
@@ -225,7 +225,8 @@ export function renderERDiagram(
225
225
  exportDims?: { width?: number; height?: number },
226
226
  activeTagGroup?: string | null,
227
227
  /** When false, semantic role colors are suppressed and entities use a neutral color. */
228
- semanticColorsActive?: boolean
228
+ semanticColorsActive?: boolean,
229
+ exportMode?: boolean
229
230
  ): void {
230
231
  d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
231
232
 
@@ -340,7 +341,7 @@ export function renderERDiagram(
340
341
  semanticRoles !== null && (semanticColorsActive ?? true);
341
342
 
342
343
  // ── Edges (behind nodes) ──
343
- const useLabels = parsed.options.notation === 'labels';
344
+ const useLabels = parsed.options['notation'] === 'labels';
344
345
 
345
346
  for (const edge of layout.edges) {
346
347
  if (edge.points.length < 2) continue;
@@ -560,7 +561,7 @@ export function renderERDiagram(
560
561
  const legendConfig: LegendConfig = {
561
562
  groups: parsed.tagGroups,
562
563
  position: { placement: 'top-center', titleRelation: 'below-title' },
563
- mode: 'fixed',
564
+ mode: exportMode ? 'export' : 'preview',
564
565
  };
565
566
  const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
566
567
  const legendG = svg
@@ -602,7 +603,7 @@ export function renderERDiagram(
602
603
  const legendConfig: LegendConfig = {
603
604
  groups: semanticGroups,
604
605
  position: { placement: 'top-center', titleRelation: 'below-title' },
605
- mode: 'fixed',
606
+ mode: exportMode ? 'export' : 'preview',
606
607
  };
607
608
  const legendState: LegendState = {
608
609
  activeGroup: semanticActive ? 'Role' : null,
@@ -653,10 +654,21 @@ export function renderERDiagramForExport(
653
654
  document.body.appendChild(container);
654
655
 
655
656
  try {
656
- renderERDiagram(container, parsed, layout, palette, isDark, undefined, {
657
- width: exportWidth,
658
- height: exportHeight,
659
- });
657
+ renderERDiagram(
658
+ container,
659
+ parsed,
660
+ layout,
661
+ palette,
662
+ isDark,
663
+ undefined,
664
+ {
665
+ width: exportWidth,
666
+ height: exportHeight,
667
+ },
668
+ undefined,
669
+ undefined,
670
+ true
671
+ );
660
672
 
661
673
  const svgEl = container.querySelector('svg');
662
674
  if (!svgEl) return '';
@@ -9,7 +9,7 @@
9
9
  // 3. Forward pass: resolve dates using universal max rule
10
10
  // 4. (Optional) Critical path: backward pass to find zero-slack chain
11
11
 
12
- import { makeDgmoError, formatDgmoError } from '../diagnostics';
12
+ import { makeDgmoError } from '../diagnostics';
13
13
  import type { DgmoError } from '../diagnostics';
14
14
  import type {
15
15
  ParsedGantt,
@@ -67,14 +67,6 @@ export function calculateSchedule(parsed: ParsedGantt): ResolvedSchedule {
67
67
  diagnostics.push(makeDgmoError(line, message, 'warning'));
68
68
  };
69
69
 
70
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
- const _fail = (line: number, message: string): ResolvedSchedule => {
72
- const diag = makeDgmoError(line, message);
73
- diagnostics.push(diag);
74
- result.error = formatDgmoError(diag);
75
- return result;
76
- };
77
-
78
70
  // ── Build holiday set ───────────────────────────────────
79
71
 
80
72
  const holidaySet = buildHolidaySet(parsed.holidays);
@@ -124,8 +116,6 @@ export function calculateSchedule(parsed: ParsedGantt): ResolvedSchedule {
124
116
  // ── Resolve explicit -> dependencies ────────────────────
125
117
 
126
118
  for (const task of allTasks) {
127
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
128
- const _node = taskMap.get(task.id)!;
129
119
  for (const dep of task.dependencies) {
130
120
  const resolved = resolveTaskName(dep.targetName, allTasks);
131
121
  if (isResolverError(resolved)) {
@@ -380,7 +380,7 @@ export function parseGantt(
380
380
  currentTagGroup = null;
381
381
  // fall through to process this line normally
382
382
  } else {
383
- // Parse tag entry: `Value(color)` or `Value`
383
+ // Parse tag entry: `Value color` or `Value`
384
384
  // First entry is the default unless another is marked `default`
385
385
  if (COMMENT_RE.test(line)) continue;
386
386
  const { text: cleanEntry, isDefault } = stripDefaultModifier(line);
@@ -435,15 +435,15 @@ export function parseGantt(
435
435
  metaAliasMap,
436
436
  () => warn(lineNumber, MULTIPLE_PIPE_ERROR)
437
437
  );
438
- if (meta.lag || meta.lead) {
439
- const key = meta.lag ? 'lag' : 'lead';
438
+ if (meta['lag'] || meta['lead']) {
439
+ const key = meta['lag'] ? 'lag' : 'lead';
440
440
  softError(
441
441
  lineNumber,
442
442
  `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
443
443
  );
444
444
  }
445
- if (meta.offset) {
446
- const raw = meta.offset;
445
+ if (meta['offset']) {
446
+ const raw = meta['offset'];
447
447
  if (raw.trim().startsWith('+')) {
448
448
  warn(
449
449
  lineNumber,
@@ -903,15 +903,15 @@ export function parseGantt(
903
903
  metaAliasMap,
904
904
  () => warn(lineNumber, MULTIPLE_PIPE_ERROR)
905
905
  );
906
- if (meta.lag || meta.lead) {
907
- const key = meta.lag ? 'lag' : 'lead';
906
+ if (meta['lag'] || meta['lead']) {
907
+ const key = meta['lag'] ? 'lag' : 'lead';
908
908
  softError(
909
909
  lineNumber,
910
910
  `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
911
911
  );
912
912
  }
913
- if (meta.offset) {
914
- const raw = meta.offset;
913
+ if (meta['offset']) {
914
+ const raw = meta['offset'];
915
915
  if (raw.trim().startsWith('+')) {
916
916
  warn(
917
917
  lineNumber,
@@ -1023,9 +1023,9 @@ export function parseGantt(
1023
1023
 
1024
1024
  // Extract progress from metadata or shorthand
1025
1025
  let progress: number | null = null;
1026
- if (metadata.progress) {
1027
- progress = parseFloat(metadata.progress);
1028
- delete metadata.progress;
1026
+ if (metadata['progress']) {
1027
+ progress = parseFloat(metadata['progress']);
1028
+ delete metadata['progress'];
1029
1029
  }
1030
1030
  // Check for progress shorthand: `| 80%` or `| t:X, 80%`
1031
1031
  for (const part of segments.slice(1).join(',').split(',')) {
@@ -1037,8 +1037,8 @@ export function parseGantt(
1037
1037
  }
1038
1038
 
1039
1039
  // Reject lag/lead — use offset instead
1040
- if (metadata.lag || metadata.lead) {
1041
- const key = metadata.lag ? 'lag' : 'lead';
1040
+ if (metadata['lag'] || metadata['lead']) {
1041
+ const key = metadata['lag'] ? 'lag' : 'lead';
1042
1042
  softError(
1043
1043
  ln,
1044
1044
  `"${key}" is no longer supported — use "offset: ${metadata[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
@@ -1047,8 +1047,8 @@ export function parseGantt(
1047
1047
 
1048
1048
  // Extract task-level offset from metadata
1049
1049
  let taskOffset: Offset | undefined;
1050
- if (metadata.offset) {
1051
- const raw = metadata.offset;
1050
+ if (metadata['offset']) {
1051
+ const raw = metadata['offset'];
1052
1052
  if (raw.trim().startsWith('+')) {
1053
1053
  warn(
1054
1054
  ln,
@@ -1063,7 +1063,7 @@ export function parseGantt(
1063
1063
  );
1064
1064
  }
1065
1065
  }
1066
- delete metadata.offset;
1066
+ delete metadata['offset'];
1067
1067
  }
1068
1068
 
1069
1069
  // Inherit metadata from parent groups (tag inheritance)
@@ -208,6 +208,7 @@ export interface GanttInteractiveOptions {
208
208
  collapsedLanes?: Set<string>;
209
209
  onToggleLane?: (laneName: string) => void;
210
210
  viewMode?: boolean;
211
+ exportMode?: boolean;
211
212
  }
212
213
 
213
214
  // ── Main Renderer ───────────────────────────────────────────
@@ -439,7 +440,8 @@ export function renderGantt(
439
440
  ).attr('display', active ? null : 'none');
440
441
  }
441
442
  drawLegend();
442
- }
443
+ },
444
+ options?.exportMode ?? false
443
445
  );
444
446
  }
445
447
  }
@@ -1973,7 +1975,7 @@ function renderTagLegend(
1973
1975
  isDark: boolean,
1974
1976
  hasCriticalPath: boolean,
1975
1977
  criticalPathActive: boolean,
1976
- optionLineNumbers: Record<string, number>,
1978
+ _optionLineNumbers: Record<string, number>,
1977
1979
  onToggle?: (groupName: string) => void,
1978
1980
  onToggleControlsExpand?: () => void,
1979
1981
  currentSwimlaneGroup?: string | null,
@@ -1983,7 +1985,8 @@ function renderTagLegend(
1983
1985
  controlsExpanded = false,
1984
1986
  hasDependencies = false,
1985
1987
  dependenciesActive = false,
1986
- onControlsToggle?: (toggleId: string, active: boolean) => void
1988
+ onControlsToggle?: (toggleId: string, active: boolean) => void,
1989
+ exportMode = false
1987
1990
  ): void {
1988
1991
  // Build visible groups: active group expanded + swimlane group as compact pill
1989
1992
  let visibleGroups: TagGroup[];
@@ -2117,7 +2120,7 @@ function renderTagLegend(
2117
2120
  placement: 'top-center' as const,
2118
2121
  titleRelation: 'below-title' as const,
2119
2122
  },
2120
- mode: 'fixed' as const,
2123
+ mode: exportMode ? 'export' : 'preview',
2121
2124
  capsulePillAddonWidth: iconReserve,
2122
2125
  controlsGroup:
2123
2126
  controlsToggles.length > 0 ? { toggles: controlsToggles } : undefined,
@@ -2263,7 +2266,7 @@ function renderTagLegend(
2263
2266
  placement: 'top-center' as const,
2264
2267
  titleRelation: 'below-title' as const,
2265
2268
  },
2266
- mode: 'fixed' as const,
2269
+ mode: exportMode ? 'export' : 'preview',
2267
2270
  controlsGroup: { toggles: controlsToggles },
2268
2271
  };
2269
2272
 
@@ -3633,7 +3636,7 @@ function resolveTaskColor(
3633
3636
  function renderTimeScaleHorizontal(
3634
3637
  g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
3635
3638
  scale: d3Scale.ScaleLinear<number, number>,
3636
- innerWidth: number,
3639
+ _innerWidth: number,
3637
3640
  innerHeight: number,
3638
3641
  textColor: string
3639
3642
  ): void {