@diagrammo/dgmo 0.8.10 → 0.8.12

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 (43) hide show
  1. package/dist/cli.cjs +245 -672
  2. package/dist/editor.cjs.map +1 -1
  3. package/dist/editor.d.cts +2 -3
  4. package/dist/editor.d.ts +2 -3
  5. package/dist/editor.js.map +1 -1
  6. package/dist/index.cjs +491 -125
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +9 -1
  9. package/dist/index.d.ts +9 -1
  10. package/dist/index.js +489 -125
  11. package/dist/index.js.map +1 -1
  12. package/gallery/fixtures/er.dgmo +36 -0
  13. package/gallery/fixtures/kanban.dgmo +27 -0
  14. package/package.json +14 -17
  15. package/src/boxes-and-lines/parser.ts +2 -0
  16. package/src/boxes-and-lines/renderer.ts +13 -5
  17. package/src/c4/layout.ts +31 -10
  18. package/src/c4/parser.ts +5 -1
  19. package/src/completion.ts +17 -2
  20. package/src/d3.ts +220 -102
  21. package/src/echarts.ts +57 -58
  22. package/src/editor/index.ts +1 -2
  23. package/src/er/parser.ts +5 -1
  24. package/src/gantt/parser.ts +8 -0
  25. package/src/gantt/renderer.ts +6 -7
  26. package/src/gantt/resolver.ts +19 -14
  27. package/src/gantt/types.ts +1 -0
  28. package/src/index.ts +2 -0
  29. package/src/infra/parser.ts +4 -0
  30. package/src/kanban/parser.ts +4 -1
  31. package/src/kanban/renderer.ts +11 -8
  32. package/src/label-layout.ts +286 -0
  33. package/src/org/parser.ts +3 -0
  34. package/src/sequence/parser.ts +6 -0
  35. package/src/sequence/renderer.ts +24 -9
  36. package/src/sharing.ts +15 -5
  37. package/src/sitemap/parser.ts +2 -0
  38. package/src/utils/arrows.ts +1 -1
  39. package/src/utils/legend-layout.ts +8 -8
  40. package/src/utils/legend-svg.ts +2 -2
  41. package/src/utils/legend-types.ts +0 -3
  42. package/src/utils/parsing.ts +1 -1
  43. package/src/utils/tag-groups.ts +65 -1
@@ -0,0 +1,36 @@
1
+ er Pirate Fleet
2
+
3
+ ships
4
+ id int pk
5
+ name varchar
6
+ ship_type varchar
7
+ cannons int
8
+ 1-aboard-* crew_members
9
+ 1-1 captains
10
+ 1-carries-* treasure
11
+
12
+ captains
13
+ id int pk
14
+ name varchar
15
+ ship_id int fk
16
+ bounty int
17
+ ?-frequents-1 ports
18
+ *-has-1 crew_members
19
+
20
+ crew_members
21
+ id int pk
22
+ name varchar
23
+ ship_id int fk
24
+ role varchar nullable
25
+
26
+ treasure
27
+ id int pk
28
+ name varchar
29
+ value int
30
+ ship_id int fk, nullable
31
+
32
+ ports
33
+ id int pk
34
+ name varchar
35
+ region varchar unique
36
+ 1-docks-* ships
@@ -0,0 +1,27 @@
1
+ kanban Plunder Sprint 7
2
+
3
+ tag Priority
4
+ Low(green)
5
+ Urgent(red)
6
+ High(orange)
7
+
8
+ tag Crew c
9
+ Blackbeard(red)
10
+ Anne Bonny(purple)
11
+ Calico Jack(teal)
12
+
13
+ [Awaiting Orders](red)
14
+ Recruit gunners at Tortuga | priority: High, c: Calico Jack
15
+ Chart new trade route | priority: Urgent, c: Anne Bonny
16
+ Scout the Windward Passage
17
+ Avoid Royal Navy patrols
18
+ Resupply rum and powder | priority: Low, c: Calico Jack
19
+
20
+ [Underway](orange) | wip: 2
21
+ Forge letters of marque | priority: High, c: Anne Bonny
22
+ Raid merchant convoy | priority: Urgent, c: Blackbeard
23
+ Three ships spotted off Nassau
24
+
25
+ [Done](green)
26
+ Bribe the harbour master | priority: High, c: Anne Bonny
27
+ Repair hull damage | priority: Low, c: Blackbeard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -74,10 +74,10 @@
74
74
  "prepare": "husky"
75
75
  },
76
76
  "dependencies": {
77
- "@dagrejs/dagre": "^2.0.4",
77
+ "@dagrejs/dagre": "^3.0.0",
78
78
  "@resvg/resvg-js": "^2.6.2",
79
79
  "d3-array": "^3.2.4",
80
- "d3-cloud": "^1.2.7",
80
+ "d3-cloud": "^1.2.9",
81
81
  "d3-hierarchy": "^3.1.2",
82
82
  "d3-scale": "^4.0.2",
83
83
  "d3-selection": "^3.0.0",
@@ -85,32 +85,30 @@
85
85
  "echarts": "^6.0.0",
86
86
  "lz-string": "^1.5.0"
87
87
  },
88
- "optionalDependencies": {
89
- "jsdom": "^28.1.0"
90
- },
91
88
  "devDependencies": {
92
89
  "@codemirror/language": "^6.12.3",
93
- "@codemirror/state": "^6.6.0",
94
90
  "@eslint/js": "^10.0.1",
95
91
  "@lezer/generator": "^1.8.0",
96
- "@types/d3-array": "^3.2.1",
92
+ "@types/d3-array": "^3.2.2",
97
93
  "@types/d3-cloud": "^1.2.9",
98
94
  "@types/d3-hierarchy": "^3.1.7",
99
- "@types/d3-scale": "^4.0.8",
95
+ "@types/d3-scale": "^4.0.9",
100
96
  "@types/d3-selection": "^3.0.11",
101
- "@types/d3-shape": "^3.1.7",
102
- "@types/jsdom": "^28.0.0",
97
+ "@types/d3-shape": "^3.1.8",
98
+ "@types/jsdom": "^28.0.1",
103
99
  "cspell": "^9.7.0",
104
- "eslint": "^10.1.0",
100
+ "esbuild": "^0.28.0",
101
+ "eslint": "^10.2.0",
105
102
  "husky": "^9.1.7",
106
103
  "jscpd": "^4.0.8",
107
- "knip": "^6.0.1",
104
+ "jsdom": "^29.0.1",
105
+ "knip": "^6.3.0",
108
106
  "lint-staged": "^16.4.0",
109
107
  "prettier": "^3.8.1",
110
108
  "tsup": "^8.5.1",
111
- "typescript": "^5.7.3",
112
- "typescript-eslint": "^8.57.2",
113
- "vitest": "^4.0.18"
109
+ "typescript": "^6.0.2",
110
+ "typescript-eslint": "^8.58.0",
111
+ "vitest": "^4.1.2"
114
112
  },
115
113
  "lint-staged": {
116
114
  "*.ts": [
@@ -120,7 +118,6 @@
120
118
  },
121
119
  "peerDependencies": {
122
120
  "@codemirror/language": "^6.12.3",
123
- "@codemirror/state": "^6.6.0",
124
121
  "@lezer/common": "^1.5.1",
125
122
  "@lezer/highlight": "^1.2.3",
126
123
  "@lezer/lr": "^1.4.8"
@@ -9,6 +9,7 @@ import {
9
9
  matchTagBlockHeading,
10
10
  injectDefaultTagMetadata,
11
11
  validateTagValues,
12
+ validateTagGroupNames,
12
13
  stripDefaultModifier,
13
14
  } from '../utils/tag-groups';
14
15
  import type { TagGroup } from '../utils/tag-groups';
@@ -531,6 +532,7 @@ export function parseBoxesAndLines(content: string): ParsedBoxesAndLines {
531
532
  if (result.tagGroups.length > 0) {
532
533
  injectDefaultTagMetadata(result.nodes, result.tagGroups);
533
534
  validateTagValues(result.nodes, result.tagGroups, pushWarning, suggest);
535
+ validateTagGroupNames(result.tagGroups, pushWarning);
534
536
  }
535
537
 
536
538
  return result;
@@ -14,7 +14,7 @@ import {
14
14
  TITLE_Y,
15
15
  } from '../utils/title-constants';
16
16
  import { contrastText, mix } from '../palettes/color-utils';
17
- import { resolveTagColor } from '../utils/tag-groups';
17
+ import { resolveTagColor, resolveActiveTagGroup } from '../utils/tag-groups';
18
18
  import type { TagGroup } from '../utils/tag-groups';
19
19
  import type { PaletteColors } from '../palettes';
20
20
  import type { ParsedBoxesAndLines, BLNode } from './types';
@@ -292,7 +292,7 @@ function resolveEdgeLabelOverlaps(
292
292
 
293
293
  // ── Main render function ───────────────────────────────────
294
294
 
295
- export interface BLRenderOptions {
295
+ interface BLRenderOptions {
296
296
  onClickItem?: (lineNumber: number) => void;
297
297
  exportDims?: { width?: number; height?: number };
298
298
  activeTagGroup?: string | null;
@@ -315,8 +315,12 @@ export function renderBoxesAndLines(
315
315
  const height = exportDims?.height ?? container.clientHeight;
316
316
  if (width <= 0 || height <= 0) return;
317
317
 
318
- // Determine active tag group
319
- const activeGroup = activeTagGroup ?? parsed.options['active-tag'] ?? null;
318
+ // Determine active tag group — shared utility handles priority chain
319
+ const activeGroup = resolveActiveTagGroup(
320
+ parsed.tagGroups,
321
+ parsed.options['active-tag'],
322
+ activeTagGroup
323
+ );
320
324
 
321
325
  // Build hidden set
322
326
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
@@ -736,9 +740,13 @@ export function renderBoxesAndLinesForExport(
736
740
  layout: BLLayoutResult,
737
741
  palette: PaletteColors,
738
742
  isDark: boolean,
739
- options?: { exportDims?: { width: number; height: number } }
743
+ options?: {
744
+ exportDims?: { width: number; height: number };
745
+ activeTagGroup?: string | null;
746
+ }
740
747
  ): void {
741
748
  renderBoxesAndLines(container, parsed, layout, palette, isDark, {
742
749
  exportDims: options?.exportDims,
750
+ activeTagGroup: options?.activeTagGroup,
743
751
  });
744
752
  }
package/src/c4/layout.ts CHANGED
@@ -18,6 +18,27 @@ import {
18
18
  measureLegendText,
19
19
  } from '../utils/legend-constants';
20
20
 
21
+ /** dagre node label shape after layout(). */
22
+ interface DagreNodeLabel {
23
+ x: number;
24
+ y: number;
25
+ width: number;
26
+ height: number;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ /** dagre edge label shape after layout(). */
31
+ interface DagreEdgeLabel {
32
+ points: { x: number; y: number }[];
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ const gNode = (g: any, name: string): DagreNodeLabel => g.node(name);
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const gEdge = (g: any, v: string, w: string): DagreEdgeLabel | undefined =>
40
+ g.edge(v, w);
41
+
21
42
  // ============================================================
22
43
  // Types
23
44
  // ============================================================
@@ -213,7 +234,7 @@ function computeEdgePenalty(
213
234
  * closer to their neighbors, producing cleaner visual layouts.
214
235
  */
215
236
  function reduceCrossings(
216
- g: dagre.graphlib.Graph,
237
+ g: InstanceType<typeof dagre.graphlib.Graph>,
217
238
  edgeList: { source: string; target: string }[],
218
239
  nodeGroupMap?: Map<string, string>
219
240
  ): void {
@@ -229,7 +250,7 @@ function reduceCrossings(
229
250
  // Build geometry map for edge-node collision scoring
230
251
  const nodeGeometry = new Map<string, NodeGeometry>();
231
252
  for (const name of g.nodes()) {
232
- const pos = g.node(name);
253
+ const pos = gNode(g, name);
233
254
  if (pos)
234
255
  nodeGeometry.set(name, {
235
256
  y: pos.y,
@@ -241,7 +262,7 @@ function reduceCrossings(
241
262
  // Group nodes by rank
242
263
  const rankMap = new Map<number, string[]>();
243
264
  for (const name of g.nodes()) {
244
- const pos = g.node(name);
265
+ const pos = gNode(g, name);
245
266
  if (!pos) continue;
246
267
  const rankY = Math.round(pos.y);
247
268
  if (!rankMap.has(rankY)) rankMap.set(rankY, []);
@@ -250,7 +271,7 @@ function reduceCrossings(
250
271
 
251
272
  // Sort each rank by current x position
252
273
  for (const [, rankNodes] of rankMap) {
253
- rankNodes.sort((a, b) => g.node(a).x - g.node(b).x);
274
+ rankNodes.sort((a, b) => gNode(g, a).x - gNode(g, b).x);
254
275
  }
255
276
 
256
277
  let anyMoved = false;
@@ -285,13 +306,13 @@ function reduceCrossings(
285
306
 
286
307
  // Collect the x-slots for this partition (sorted)
287
308
  const xSlots = partition
288
- .map((name) => g.node(name).x)
309
+ .map((name) => gNode(g, name).x)
289
310
  .sort((a, b) => a - b);
290
311
 
291
312
  // Build position map snapshot
292
313
  const basePositions = new Map<string, number>();
293
314
  for (const name of g.nodes()) {
294
- const pos = g.node(name);
315
+ const pos = gNode(g, name);
295
316
  if (pos) basePositions.set(name, pos.x);
296
317
  }
297
318
 
@@ -379,7 +400,7 @@ function reduceCrossings(
379
400
  // Apply best permutation if it differs from current
380
401
  if (bestPerm.some((name, i) => name !== partition[i])) {
381
402
  for (let i = 0; i < bestPerm.length; i++) {
382
- g.node(bestPerm[i]!).x = xSlots[i]!;
403
+ gNode(g, bestPerm[i]!).x = xSlots[i]!;
383
404
  // Update in the original rankNodes too
384
405
  const rankIdx = rankNodes.indexOf(partition[i]!);
385
406
  if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i]!;
@@ -392,10 +413,10 @@ function reduceCrossings(
392
413
  // Recompute edge waypoints if any positions changed
393
414
  if (anyMoved) {
394
415
  for (const edge of edgeList) {
395
- const edgeData = g.edge(edge.source, edge.target);
416
+ const edgeData = gEdge(g, edge.source, edge.target);
396
417
  if (!edgeData) continue;
397
- const srcPos = g.node(edge.source);
398
- const tgtPos = g.node(edge.target);
418
+ const srcPos = gNode(g, edge.source);
419
+ const tgtPos = gNode(g, edge.target);
399
420
  if (!srcPos || !tgtPos) continue;
400
421
 
401
422
  const srcBottom = { x: srcPos.x, y: srcPos.y + srcPos.height / 2 };
package/src/c4/parser.ts CHANGED
@@ -8,6 +8,7 @@ import type { TagGroup } from '../utils/tag-groups';
8
8
  import {
9
9
  matchTagBlockHeading,
10
10
  stripDefaultModifier,
11
+ validateTagGroupNames,
11
12
  } from '../utils/tag-groups';
12
13
  import { inferParticipantType } from '../sequence/participant-inference';
13
14
  import {
@@ -84,7 +85,7 @@ const VALID_SHAPES = new Set<string>([
84
85
  ]);
85
86
 
86
87
  /** Known top-level option keys for C4 diagrams. */
87
- const KNOWN_C4_OPTIONS = new Set<string>(['layout']);
88
+ const KNOWN_C4_OPTIONS = new Set<string>(['layout', 'active-tag']);
88
89
 
89
90
  /** Known C4 boolean options (bare keyword = on). */
90
91
  const KNOWN_C4_BOOLEANS = new Set<string>(['direction-tb']);
@@ -829,6 +830,9 @@ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
829
830
  // ── Post-parse validation ───────────────────────────────
830
831
  validateRelationshipTargets(result, knownNames, pushError);
831
832
  validateDeploymentRefs(result, knownNames, pushError);
833
+ validateTagGroupNames(result.tagGroups, (line, msg) =>
834
+ pushError(line, msg, 'warning')
835
+ );
832
836
 
833
837
  return result;
834
838
  }
package/src/completion.ts CHANGED
@@ -258,21 +258,33 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
258
258
  },
259
259
  }),
260
260
  ],
261
- ['er', withGlobals()],
261
+ [
262
+ 'er',
263
+ withGlobals({
264
+ 'active-tag': { description: 'Active tag group name' },
265
+ }),
266
+ ],
262
267
  [
263
268
  'org',
264
269
  withGlobals({
265
270
  'sub-node-label': { description: 'Label for sub-nodes' },
266
271
  'show-sub-node-count': { description: 'Show sub-node counts' },
272
+ 'active-tag': { description: 'Active tag group name' },
267
273
  }),
268
274
  ],
269
275
  [
270
276
  'kanban',
271
277
  withGlobals({
272
278
  'no-auto-color': { description: 'Disable automatic card coloring' },
279
+ 'active-tag': { description: 'Active tag group name' },
280
+ }),
281
+ ],
282
+ [
283
+ 'c4',
284
+ withGlobals({
285
+ 'active-tag': { description: 'Active tag group name' },
273
286
  }),
274
287
  ],
275
- ['c4', withGlobals()],
276
288
  [
277
289
  'state',
278
290
  withGlobals({
@@ -284,6 +296,7 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
284
296
  'sitemap',
285
297
  withGlobals({
286
298
  'direction-tb': { description: 'Switch to top-to-bottom layout' },
299
+ 'active-tag': { description: 'Active tag group name' },
287
300
  }),
288
301
  ],
289
302
  [
@@ -298,6 +311,7 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
298
311
  'slo-availability': { description: 'SLO availability target (0-1)' },
299
312
  'slo-p90-latency-ms': { description: 'SLO p90 latency target in ms' },
300
313
  'slo-warning-margin': { description: 'SLO warning margin percentage' },
314
+ 'active-tag': { description: 'Active tag group name' },
301
315
  }),
302
316
  ],
303
317
  [
@@ -310,6 +324,7 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
310
324
  sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
311
325
  'critical-path': { description: 'Show critical path' },
312
326
  dependencies: { description: 'Show dependencies' },
327
+ 'active-tag': { description: 'Active tag group name' },
313
328
  }),
314
329
  ],
315
330
  [