@diagrammo/dgmo 0.25.5 → 0.26.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/src/completion.ts CHANGED
@@ -648,6 +648,69 @@ export const ENTITY_TYPES = new Map<string, string[]>([
648
648
  ],
649
649
  ]);
650
650
 
651
+ // ============================================================
652
+ // Structural keywords for line-leading completion
653
+ // ============================================================
654
+
655
+ /**
656
+ * Chart-type-specific structural keywords offered on an empty/start-of-line in
657
+ * the data zone (block openers like `loop`, section headers like `containers`,
658
+ * the `tag` block declaration, etc.). This is the single source of truth for
659
+ * the editor's structural-keyword popup — every entry MUST be a token the
660
+ * corresponding parser actually recognizes (validated by the
661
+ * completion-conformance suite). Do NOT add removed/diagnostic-only tokens
662
+ * (e.g. cycle's `no-descriptions`) or tokens the parser ignores.
663
+ *
664
+ * Chart types not listed here have no structural keywords (most data charts).
665
+ */
666
+ export const STRUCTURAL_KEYWORDS = new Map<string, string[]>([
667
+ ['sequence', ['if', 'else', 'loop', 'parallel', 'note', 'tag']],
668
+ ['gantt', ['era', 'marker', 'holiday', 'workweek', 'parallel', 'tag']],
669
+ ['c4', ['containers', 'components', 'deployment', 'tag']],
670
+ ['timeline', ['era', 'marker', 'tag']],
671
+ ['org', ['tag']],
672
+ ['kanban', ['tag']],
673
+ ['sitemap', ['tag']],
674
+ ['infra', ['tag']],
675
+ ['pert', ['tag']],
676
+ ['mindmap', ['tag']],
677
+ ['boxes-and-lines', ['tag']],
678
+ ['er', ['tag']],
679
+ ['cycle', ['direction-counterclockwise', 'circle-nodes']],
680
+ ['journey-map', ['persona', 'tag']],
681
+ ['raci', ['roles']],
682
+ ['tech-radar', ['rings']],
683
+ [
684
+ 'wireframe',
685
+ [
686
+ 'nav',
687
+ 'tabs',
688
+ 'table',
689
+ 'image',
690
+ 'modal',
691
+ 'skeleton',
692
+ 'alert',
693
+ 'progress',
694
+ 'chart',
695
+ 'mobile',
696
+ 'tag',
697
+ ],
698
+ ],
699
+ ['class', ['abstract', 'interface', 'enum', 'extends', 'implements']],
700
+ ]);
701
+
702
+ /**
703
+ * Chart types that support `tag` block declarations (and thus the
704
+ * `alias`/`default` sub-keywords inside a tag block). Derived from
705
+ * STRUCTURAL_KEYWORDS so the two can never drift — a chart supports tag blocks
706
+ * iff it offers the `tag` keyword.
707
+ */
708
+ export const TAG_SUPPORTING_TYPES: ReadonlySet<string> = new Set(
709
+ [...STRUCTURAL_KEYWORDS]
710
+ .filter(([, kws]) => kws.includes('tag'))
711
+ .map(([type]) => type)
712
+ );
713
+
651
714
  // ============================================================
652
715
  // Pipe metadata for inline `| key value` on data lines
653
716
  // ============================================================
@@ -992,7 +1055,6 @@ function extractSequenceSymbols(docText: string): DiagramSymbols {
992
1055
  return {
993
1056
  kind: 'sequence',
994
1057
  entities,
995
- keywords: ['if', 'else', 'loop', 'parallel', 'note'],
996
1058
  };
997
1059
  }
998
1060
 
@@ -1031,7 +1093,7 @@ function extractStateSymbols(docText: string): DiagramSymbols {
1031
1093
  }
1032
1094
  }
1033
1095
 
1034
- return { kind: 'state', entities, keywords: [] };
1096
+ return { kind: 'state', entities };
1035
1097
  }
1036
1098
 
1037
1099
  // ============================================================
@@ -1247,7 +1309,7 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
1247
1309
  }
1248
1310
  }
1249
1311
 
1250
- return { kind: 'sitemap', entities, keywords: [] };
1312
+ return { kind: 'sitemap', entities };
1251
1313
  }
1252
1314
 
1253
1315
  // ============================================================
@@ -1326,7 +1388,6 @@ function extractC4Symbols(docText: string): DiagramSymbols {
1326
1388
  return {
1327
1389
  kind: 'c4',
1328
1390
  entities,
1329
- keywords: ['containers', 'components', 'deployment'],
1330
1391
  };
1331
1392
  }
1332
1393
 
@@ -1429,7 +1490,7 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
1429
1490
  }
1430
1491
  }
1431
1492
 
1432
- return { kind: 'gantt', entities, keywords: [] };
1493
+ return { kind: 'gantt', entities };
1433
1494
  }
1434
1495
 
1435
1496
  // ============================================================
@@ -1487,7 +1548,7 @@ function extractBoxesAndLinesSymbols(docText: string): DiagramSymbols {
1487
1548
  if (label && !entities.includes(label)) entities.push(label);
1488
1549
  }
1489
1550
 
1490
- return { kind: 'boxes-and-lines', entities, keywords: [] };
1551
+ return { kind: 'boxes-and-lines', entities };
1491
1552
  }
1492
1553
 
1493
1554
  // ============================================================
@@ -1540,7 +1601,7 @@ function extractOrgSymbols(docText: string): DiagramSymbols {
1540
1601
  if (label && !entities.includes(label)) entities.push(label);
1541
1602
  }
1542
1603
 
1543
- return { kind: 'org', entities, keywords: [] };
1604
+ return { kind: 'org', entities };
1544
1605
  }
1545
1606
 
1546
1607
  // ============================================================
@@ -1592,7 +1653,7 @@ function extractKanbanSymbols(docText: string): DiagramSymbols {
1592
1653
  }
1593
1654
  }
1594
1655
 
1595
- return { kind: 'kanban', entities, keywords: [] };
1656
+ return { kind: 'kanban', entities };
1596
1657
  }
1597
1658
 
1598
1659
  // ============================================================
@@ -1635,7 +1696,7 @@ function extractMindmapSymbols(docText: string): DiagramSymbols {
1635
1696
  if (label && !entities.includes(label)) entities.push(label);
1636
1697
  }
1637
1698
 
1638
- return { kind: 'mindmap', entities, keywords: [] };
1699
+ return { kind: 'mindmap', entities };
1639
1700
  }
1640
1701
 
1641
1702
  // ============================================================
@@ -1668,7 +1729,7 @@ function extractPyramidSymbols(docText: string): DiagramSymbols {
1668
1729
  if (label && !entities.includes(label)) entities.push(label);
1669
1730
  }
1670
1731
 
1671
- return { kind: 'pyramid', entities, keywords: ['inverted'] };
1732
+ return { kind: 'pyramid', entities };
1672
1733
  }
1673
1734
 
1674
1735
  // ============================================================
@@ -1701,7 +1762,7 @@ function extractRingSymbols(docText: string): DiagramSymbols {
1701
1762
  if (label && !entities.includes(label)) entities.push(label);
1702
1763
  }
1703
1764
 
1704
- return { kind: 'ring', entities, keywords: [] };
1765
+ return { kind: 'ring', entities };
1705
1766
  }
1706
1767
 
1707
1768
  // ============================================================
@@ -1736,7 +1797,7 @@ function extractArcSymbols(docText: string): DiagramSymbols {
1736
1797
  }
1737
1798
  }
1738
1799
 
1739
- return { kind: 'arc', entities, keywords: [] };
1800
+ return { kind: 'arc', entities };
1740
1801
  }
1741
1802
 
1742
1803
  // ============================================================
@@ -1775,7 +1836,7 @@ function extractSankeySymbols(docText: string): DiagramSymbols {
1775
1836
  }
1776
1837
  }
1777
1838
 
1778
- return { kind: 'sankey', entities, keywords: [] };
1839
+ return { kind: 'sankey', entities };
1779
1840
  }
1780
1841
 
1781
1842
  // ============================================================
@@ -1835,7 +1896,7 @@ function extractTimelineSymbols(docText: string): DiagramSymbols {
1835
1896
  if (label && !entities.includes(label)) entities.push(label);
1836
1897
  }
1837
1898
 
1838
- return { kind: 'timeline', entities, keywords: ['era', 'marker'] };
1899
+ return { kind: 'timeline', entities };
1839
1900
  }
1840
1901
 
1841
1902
  // ============================================================
@@ -1867,7 +1928,7 @@ function extractVennSymbols(docText: string): DiagramSymbols {
1867
1928
  if (label && !entities.includes(label)) entities.push(label);
1868
1929
  }
1869
1930
 
1870
- return { kind: 'venn', entities, keywords: [] };
1931
+ return { kind: 'venn', entities };
1871
1932
  }
1872
1933
 
1873
1934
  // ============================================================
@@ -1901,7 +1962,7 @@ function extractQuadrantSymbols(docText: string): DiagramSymbols {
1901
1962
  if (label && !entities.includes(label)) entities.push(label);
1902
1963
  }
1903
1964
 
1904
- return { kind: 'quadrant', entities, keywords: [] };
1965
+ return { kind: 'quadrant', entities };
1905
1966
  }
1906
1967
 
1907
1968
  // ============================================================
@@ -1936,7 +1997,7 @@ function extractSlopeSymbols(docText: string): DiagramSymbols {
1936
1997
  if (label && !entities.includes(label)) entities.push(label);
1937
1998
  }
1938
1999
 
1939
- return { kind: 'slope', entities, keywords: ['period'] };
2000
+ return { kind: 'slope', entities };
1940
2001
  }
1941
2002
 
1942
2003
  // ============================================================
@@ -1983,7 +2044,7 @@ function extractDataChartSymbols(docText: string): DiagramSymbols {
1983
2044
  }
1984
2045
  }
1985
2046
 
1986
- return { kind: chartType, entities, keywords: [] };
2047
+ return { kind: chartType, entities };
1987
2048
  }
1988
2049
 
1989
2050
  // ============================================================
@@ -2034,23 +2095,6 @@ registerExtractor('chord', extractDataChartSymbols);
2034
2095
 
2035
2096
  function extractTechRadarSymbols(docText: string): DiagramSymbols {
2036
2097
  const entities: string[] = [];
2037
- const keywords: string[] = [
2038
- 'rings',
2039
- 'quadrant',
2040
- 'ring',
2041
- 'trend',
2042
- 'new',
2043
- 'up',
2044
- 'down',
2045
- 'stable',
2046
- 'top-left',
2047
- 'top-right',
2048
- 'bottom-left',
2049
- 'bottom-right',
2050
- 'alias',
2051
- 'aka',
2052
- 'color',
2053
- ];
2054
2098
 
2055
2099
  // Extract ring names and aliases from the rings block
2056
2100
  const lines = docText.split('\n');
@@ -2078,7 +2122,7 @@ function extractTechRadarSymbols(docText: string): DiagramSymbols {
2078
2122
  }
2079
2123
  }
2080
2124
 
2081
- return { kind: 'tech-radar', entities, keywords };
2125
+ return { kind: 'tech-radar', entities };
2082
2126
  }
2083
2127
 
2084
2128
  // ============================================================
@@ -2121,7 +2165,6 @@ function extractCycleSymbols(docText: string): DiagramSymbols {
2121
2165
  return {
2122
2166
  kind: 'cycle',
2123
2167
  entities,
2124
- keywords: ['direction-counterclockwise', 'circle-nodes'],
2125
2168
  };
2126
2169
  }
2127
2170
 
@@ -2218,7 +2261,6 @@ function extractRaciSymbols(docText: string): DiagramSymbols {
2218
2261
  return {
2219
2262
  kind: chartType,
2220
2263
  entities,
2221
- keywords: ['variant', 'roles'],
2222
2264
  };
2223
2265
  }
2224
2266
 
@@ -2271,6 +2313,5 @@ function extractJourneyMapSymbols(docText: string): DiagramSymbols {
2271
2313
  return {
2272
2314
  kind: 'journey-map',
2273
2315
  entities,
2274
- keywords: ['persona', 'pain', 'opportunity', 'thought', 'description'],
2275
2316
  };
2276
2317
  }
package/src/er/parser.ts CHANGED
@@ -719,6 +719,5 @@ export function extractSymbols(docText: string): DiagramSymbols {
719
719
  return {
720
720
  kind: 'er',
721
721
  entities,
722
- keywords: ['pk', 'fk', 'unique', 'nullable', '1', '*', '?'],
723
722
  };
724
723
  }
@@ -647,5 +647,5 @@ export function extractSymbols(docText: string): DiagramSymbols {
647
647
  const m = NODE_ID_RE.exec(line);
648
648
  if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
649
649
  }
650
- return { kind: 'flowchart', entities, keywords: [] };
650
+ return { kind: 'flowchart', entities };
651
651
  }
@@ -476,10 +476,24 @@ export function renderFlowchart(
476
476
 
477
477
  const diagramW = layout.width;
478
478
  const diagramH = layout.height;
479
- const availH = height - titleHeight;
480
479
  const scaleX = (width - sDiagramPadding * 2) / diagramW;
481
- const scaleY = (availH - sDiagramPadding * 2) / diagramH;
482
- const scale = Math.min(MAX_SCALE, scaleX, scaleY);
480
+
481
+ // Export renders a fixed canvas (e.g. 1200×800). Fitting a small graph into
482
+ // it and top-anchoring leaves a tall band of dead space below. In export
483
+ // mode, scale to width (capped by MAX_SCALE) and size the canvas to the
484
+ // scaled content height. The interactive preview keeps the fit-to-pane
485
+ // behaviour so a small graph still fills its pane.
486
+ let scale: number;
487
+ let canvasHeight: number;
488
+ if (exportDims) {
489
+ scale = Math.min(MAX_SCALE, scaleX);
490
+ canvasHeight = titleHeight + diagramH * scale + sDiagramPadding * 2;
491
+ } else {
492
+ const availH = height - titleHeight;
493
+ const scaleY = (availH - sDiagramPadding * 2) / diagramH;
494
+ scale = Math.min(MAX_SCALE, scaleX, scaleY);
495
+ canvasHeight = height;
496
+ }
483
497
 
484
498
  const scaledW = diagramW * scale;
485
499
  const offsetX = (width - scaledW) / 2;
@@ -489,8 +503,8 @@ export function renderFlowchart(
489
503
  .select(container)
490
504
  .append('svg')
491
505
  .attr('width', width)
492
- .attr('height', height)
493
- .attr('viewBox', `0 0 ${width} ${height}`)
506
+ .attr('height', canvasHeight)
507
+ .attr('viewBox', `0 0 ${width} ${canvasHeight}`)
494
508
  .attr('preserveAspectRatio', 'xMidYMin meet')
495
509
  .style('font-family', FONT_FAMILY);
496
510
 
@@ -130,10 +130,24 @@ export function renderState(
130
130
 
131
131
  const diagramW = layout.width;
132
132
  const diagramH = layout.height;
133
- const availH = height - titleHeight;
134
133
  const scaleX = (width - sDiagramPadding * 2) / diagramW;
135
- const scaleY = (availH - sDiagramPadding * 2) / diagramH;
136
- const scale = Math.min(MAX_SCALE, scaleX, scaleY);
134
+
135
+ // Export renders a fixed canvas (e.g. 1200×800). Fitting a small graph into
136
+ // it and top-anchoring leaves a tall band of dead space below. In export
137
+ // mode, scale to width (capped by MAX_SCALE) and size the canvas to the
138
+ // scaled content height. The interactive preview keeps the fit-to-pane
139
+ // behaviour so a small graph still fills its pane.
140
+ let scale: number;
141
+ let canvasHeight: number;
142
+ if (exportDims) {
143
+ scale = Math.min(MAX_SCALE, scaleX);
144
+ canvasHeight = titleHeight + diagramH * scale + sDiagramPadding * 2;
145
+ } else {
146
+ const availH = height - titleHeight;
147
+ const scaleY = (availH - sDiagramPadding * 2) / diagramH;
148
+ scale = Math.min(MAX_SCALE, scaleX, scaleY);
149
+ canvasHeight = height;
150
+ }
137
151
 
138
152
  const scaledW = diagramW * scale;
139
153
  const offsetX = (width - scaledW) / 2;
@@ -143,8 +157,8 @@ export function renderState(
143
157
  .select(container)
144
158
  .append('svg')
145
159
  .attr('width', width)
146
- .attr('height', height)
147
- .attr('viewBox', `0 0 ${width} ${height}`)
160
+ .attr('height', canvasHeight)
161
+ .attr('viewBox', `0 0 ${width} ${canvasHeight}`)
148
162
  .attr('preserveAspectRatio', 'xMidYMin meet')
149
163
  .style('font-family', FONT_FAMILY);
150
164
 
@@ -1159,5 +1159,5 @@ export function extractSymbols(docText: string): DiagramSymbols {
1159
1159
  }
1160
1160
  }
1161
1161
  }
1162
- return { kind: 'infra', entities, keywords: [] };
1162
+ return { kind: 'infra', entities };
1163
1163
  }
@@ -1639,20 +1639,6 @@ export function extractPertSymbols(docText: string): DiagramSymbols {
1639
1639
  return {
1640
1640
  kind: 'pert',
1641
1641
  entities,
1642
- keywords: [
1643
- 'time-unit',
1644
- 'default-confidence',
1645
- 'direction',
1646
- 'node-detail',
1647
- 'trials',
1648
- 'seed',
1649
- 'scrubber-trials',
1650
- 'start-date',
1651
- 'end-date',
1652
- 'active-tag',
1653
- 'tag',
1654
- 'as',
1655
- ],
1656
1642
  };
1657
1643
  }
1658
1644
 
@@ -515,6 +515,12 @@ export function renderRaci(
515
515
  const colBottomY = cursorY + sColumnBottomPad;
516
516
  const totalHeight = colBottomY + sVMargin;
517
517
 
518
+ // Export renders a fixed canvas (e.g. 1200×800); a short matrix would
519
+ // otherwise reserve a tall band of dead space below the last row. Size the
520
+ // export canvas to the content height. The interactive preview keeps the
521
+ // `max(pane, content)` behaviour so a short matrix still fills the pane.
522
+ const svgHeight = exportDims ? totalHeight : Math.max(height, totalHeight);
523
+
518
524
  // ── SVG root ───────────────────────────────────────────────
519
525
 
520
526
  const svg = d3Selection
@@ -522,8 +528,8 @@ export function renderRaci(
522
528
  .append('svg')
523
529
  .attr('xmlns', 'http://www.w3.org/2000/svg')
524
530
  .attr('width', width)
525
- .attr('height', Math.max(height, totalHeight))
526
- .attr('viewBox', `0 0 ${width} ${Math.max(height, totalHeight)}`)
531
+ .attr('height', svgHeight)
532
+ .attr('viewBox', `0 0 ${width} ${svgHeight}`)
527
533
  .attr('preserveAspectRatio', 'xMidYMin meet')
528
534
  .attr('font-family', FONT_FAMILY)
529
535
  .style('background', 'transparent');