@diagrammo/dgmo 0.8.20 → 0.8.21

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 (53) hide show
  1. package/dist/cli.cjs +92 -90
  2. package/dist/editor.cjs +13 -1
  3. package/dist/editor.cjs.map +1 -1
  4. package/dist/editor.js +13 -1
  5. package/dist/editor.js.map +1 -1
  6. package/dist/highlight.cjs +13 -1
  7. package/dist/highlight.cjs.map +1 -1
  8. package/dist/highlight.js +13 -1
  9. package/dist/highlight.js.map +1 -1
  10. package/dist/index.cjs +4144 -940
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +318 -84
  13. package/dist/index.d.ts +318 -84
  14. package/dist/index.js +4132 -938
  15. package/dist/index.js.map +1 -1
  16. package/docs/guide/chart-mindmap.md +198 -0
  17. package/docs/guide/chart-sequence.md +23 -1
  18. package/docs/guide/chart-wireframe.md +100 -0
  19. package/docs/guide/index.md +8 -0
  20. package/docs/language-reference.md +137 -2
  21. package/package.json +1 -1
  22. package/src/boxes-and-lines/collapse.ts +21 -3
  23. package/src/boxes-and-lines/layout.ts +51 -9
  24. package/src/boxes-and-lines/parser.ts +8 -1
  25. package/src/boxes-and-lines/renderer.ts +121 -23
  26. package/src/boxes-and-lines/types.ts +1 -0
  27. package/src/completion.ts +26 -0
  28. package/src/d3.ts +153 -32
  29. package/src/dgmo-router.ts +6 -0
  30. package/src/editor/keywords.ts +12 -0
  31. package/src/graph/layout.ts +73 -9
  32. package/src/graph/state-collapse.ts +78 -0
  33. package/src/graph/state-renderer.ts +139 -34
  34. package/src/index.ts +28 -0
  35. package/src/kanban/renderer.ts +303 -57
  36. package/src/mindmap/collapse.ts +88 -0
  37. package/src/mindmap/layout.ts +605 -0
  38. package/src/mindmap/parser.ts +379 -0
  39. package/src/mindmap/renderer.ts +543 -0
  40. package/src/mindmap/text-wrap.ts +207 -0
  41. package/src/mindmap/types.ts +55 -0
  42. package/src/render.ts +18 -21
  43. package/src/sequence/renderer.ts +129 -18
  44. package/src/sharing.ts +2 -0
  45. package/src/sitemap/layout.ts +35 -12
  46. package/src/utils/export-container.ts +3 -2
  47. package/src/utils/legend-d3.ts +1 -0
  48. package/src/utils/legend-layout.ts +2 -2
  49. package/src/utils/parsing.ts +2 -0
  50. package/src/wireframe/layout.ts +460 -0
  51. package/src/wireframe/parser.ts +956 -0
  52. package/src/wireframe/renderer.ts +1293 -0
  53. package/src/wireframe/types.ts +110 -0
@@ -36,6 +36,7 @@ const CHAR_WIDTH_RATIO = 0.6;
36
36
  const NODE_TEXT_PADDING = 12;
37
37
  const GROUP_RX = 8;
38
38
  const GROUP_LABEL_FONT_SIZE = 14;
39
+ const GROUP_LABEL_ZONE = 32;
39
40
 
40
41
  type D3G = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
41
42
  type D3Svg = d3Selection.Selection<SVGSVGElement, unknown, null, undefined>;
@@ -45,19 +46,13 @@ const lineGeneratorLR = d3Shape
45
46
  .line<{ x: number; y: number }>()
46
47
  .x((d) => d.x)
47
48
  .y((d) => d.y)
48
- .curve(d3Shape.curveMonotoneX);
49
+ .curve(d3Shape.curveBasis);
49
50
 
50
51
  const lineGeneratorTB = d3Shape
51
52
  .line<{ x: number; y: number }>()
52
53
  .x((d) => d.x)
53
54
  .y((d) => d.y)
54
- .curve(d3Shape.curveMonotoneY);
55
-
56
- const lineGeneratorLinear = d3Shape
57
- .line<{ x: number; y: number }>()
58
- .x((d) => d.x)
59
- .y((d) => d.y)
60
- .curve(d3Shape.curveLinear);
55
+ .curve(d3Shape.curveBasis);
61
56
 
62
57
  // ── Text fitting ───────────────────────────────────────────
63
58
 
@@ -336,8 +331,18 @@ export function renderBoxesAndLines(
336
331
  // Compute diagram bounds for scaling
337
332
  const titleOffset = parsed.title ? 40 : 0;
338
333
  const legendH = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
334
+
335
+ // Account for group label zone extensions (renderer-only, not in layout.height)
336
+ const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
337
+ let labelZoneExtension = 0;
338
+ for (const group of parsed.groups) {
339
+ if (group.children.some((c) => groupLabelsSet.has(c))) {
340
+ labelZoneExtension += GROUP_LABEL_ZONE;
341
+ }
342
+ }
343
+
339
344
  const contentW = layout.width;
340
- const contentH = layout.height + titleOffset + legendH;
345
+ const contentH = layout.height + titleOffset + legendH + labelZoneExtension;
341
346
 
342
347
  const scaleX = width / (contentW + DIAGRAM_PADDING * 2);
343
348
  const scaleY = height / (contentH + DIAGRAM_PADDING * 2);
@@ -390,10 +395,29 @@ export function renderBoxesAndLines(
390
395
  }
391
396
  ensureArrowMarkers(defs, arrowColors);
392
397
 
393
- // ── Render groups (bottom layer) ───────────────────────
394
- for (const group of layout.groups) {
398
+ // ── Render groups (bottom layer, largest first for nesting) ──
399
+ const sortedGroups = [...layout.groups].sort(
400
+ (a, b) => b.width * b.height - a.width * a.height
401
+ );
402
+ // Identify groups that contain sub-groups — only those need extra label space
403
+ const groupLabels = new Set(layout.groups.map((g) => g.label));
404
+ const hasSubGroups = new Set<string>();
405
+ for (const group of parsed.groups) {
406
+ for (const child of group.children) {
407
+ if (groupLabels.has(child)) hasSubGroups.add(group.label);
408
+ }
409
+ }
410
+
411
+ for (const group of sortedGroups) {
395
412
  const gx = group.x - group.width / 2;
396
- const gy = group.y - group.height / 2;
413
+ // Only extend top for groups that contain sub-groups (dagre under-pads these)
414
+ const needsExtra = !group.collapsed && hasSubGroups.has(group.label);
415
+ const gy = needsExtra
416
+ ? group.y - group.height / 2 - GROUP_LABEL_ZONE
417
+ : group.y - group.height / 2;
418
+ const groupHeight = needsExtra
419
+ ? group.height + GROUP_LABEL_ZONE
420
+ : group.height;
397
421
 
398
422
  const groupG = diagramG
399
423
  .append('g')
@@ -464,7 +488,7 @@ export function renderBoxesAndLines(
464
488
  .attr('x', gx)
465
489
  .attr('y', gy)
466
490
  .attr('width', group.width)
467
- .attr('height', group.height)
491
+ .attr('height', groupHeight)
468
492
  .attr('rx', GROUP_RX)
469
493
  .attr('ry', GROUP_RX)
470
494
  .attr('fill', mix(palette.surface, palette.bg, 40))
@@ -516,8 +540,73 @@ export function renderBoxesAndLines(
516
540
  if (isHidden) continue;
517
541
  }
518
542
 
519
- // Apply parallel y-offset to points
520
- const points = le.points.map((p) => ({ x: p.x, y: p.y + le.yOffset }));
543
+ // Self-loop: render as a smooth circular arc below the node
544
+ if (le.source === le.target) {
545
+ const nodeLayout = layoutNodeMap.get(le.source);
546
+ if (nodeLayout) {
547
+ const edgeG = diagramG
548
+ .append('g')
549
+ .attr('class', 'bl-edge-group')
550
+ .attr('data-line-number', String(le.lineNumber));
551
+ edgeGroups.set(i, edgeG as unknown as D3G);
552
+
553
+ const markerId = `bl-arrow-${color.replace('#', '')}`;
554
+ const cx = nodeLayout.x;
555
+ const cy = nodeLayout.y;
556
+ const hw = nodeLayout.width / 2;
557
+ const hh = nodeLayout.height / 2;
558
+ const pad = 20; // clearance from node edge
559
+
560
+ // Arc exits from bottom of right side, swings wide, returns to right of bottom side
561
+ const startX = cx + hw;
562
+ const startY = cy + hh * 0.4;
563
+ const endX = cx + hw * 0.4;
564
+ const endY = cy + hh;
565
+
566
+ // Control points swing far out to create a smooth circular arc
567
+ const cp1x = startX + hw + pad;
568
+ const cp1y = startY;
569
+ const cp2x = endX;
570
+ const cp2y = endY + hh + pad;
571
+
572
+ edgeG
573
+ .append('path')
574
+ .attr('class', 'bl-edge')
575
+ .attr(
576
+ 'd',
577
+ `M ${startX} ${startY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${endX} ${endY}`
578
+ )
579
+ .attr('fill', 'none')
580
+ .attr('stroke', color)
581
+ .attr('stroke-width', EDGE_STROKE_WIDTH)
582
+ .attr('marker-end', `url(#${markerId})`);
583
+ }
584
+ continue;
585
+ }
586
+
587
+ // Parallel edge fan: construct explicit 5-point geometry so lines
588
+ // bundle at ports and visibly spread apart in the middle.
589
+ let points: { x: number; y: number }[];
590
+ if (le.yOffset !== 0 && le.parallelCount > 1) {
591
+ const srcLayout = layoutNodeMap.get(le.source);
592
+ const tgtLayout = layoutNodeMap.get(le.target);
593
+ const srcY = srcLayout?.y ?? le.points[0]?.y ?? 0;
594
+ const tgtY = tgtLayout?.y ?? le.points[le.points.length - 1]?.y ?? 0;
595
+ const srcX = le.points[0]?.x ?? 0;
596
+ const tgtX = le.points[le.points.length - 1]?.x ?? 0;
597
+ const midX = (srcX + tgtX) / 2;
598
+ const midY = (srcY + tgtY) / 2;
599
+
600
+ points = [
601
+ { x: srcX, y: srcY }, // port (bundled)
602
+ { x: srcX + (midX - srcX) * 0.3, y: srcY + le.yOffset * 0.5 }, // separate
603
+ { x: midX, y: midY + le.yOffset }, // full spread
604
+ { x: tgtX - (tgtX - midX) * 0.3, y: tgtY + le.yOffset * 0.5 }, // converge
605
+ { x: tgtX, y: tgtY }, // port (bundled)
606
+ ];
607
+ } else {
608
+ points = le.points.map((p) => ({ x: p.x, y: p.y }));
609
+ }
521
610
  if (points.length < 2) continue;
522
611
 
523
612
  const edgeG = diagramG
@@ -527,11 +616,7 @@ export function renderBoxesAndLines(
527
616
  edgeGroups.set(i, edgeG as unknown as D3G);
528
617
 
529
618
  const markerId = `bl-arrow-${color.replace('#', '')}`;
530
- const gen = le.deferred
531
- ? lineGeneratorLinear
532
- : parsed.direction === 'TB'
533
- ? lineGeneratorTB
534
- : lineGeneratorLR;
619
+ const gen = parsed.direction === 'TB' ? lineGeneratorTB : lineGeneratorLR;
535
620
  const path = edgeG
536
621
  .append('path')
537
622
  .attr('class', 'bl-edge')
@@ -546,14 +631,25 @@ export function renderBoxesAndLines(
546
631
  path.attr('marker-start', `url(#${revId})`);
547
632
  }
548
633
 
549
- // Edge label
634
+ // Edge label — for parallel edges, place relative to each line:
635
+ // negative offset (top line) → label above, zero → on line, positive → below
550
636
  if (le.label && le.labelX != null && le.labelY != null) {
551
637
  const lw = le.label.length * EDGE_LABEL_FONT_SIZE * CHAR_WIDTH_RATIO;
638
+ const labelH = EDGE_LABEL_FONT_SIZE + 6;
639
+ let ly: number;
640
+ if (le.parallelCount > 1 && le.yOffset !== 0) {
641
+ // Position label on the line at midpoint, shifted above/below based on offset sign
642
+ const lineY = le.labelY + 10 + le.yOffset; // +10 to undo the -10 in layout
643
+ const labelShift = le.yOffset < 0 ? -labelH : labelH;
644
+ ly = lineY + labelShift * 0.5;
645
+ } else {
646
+ ly = le.labelY + le.yOffset;
647
+ }
552
648
  labelPositions.push({
553
649
  x: le.labelX,
554
- y: le.labelY + le.yOffset,
650
+ y: ly,
555
651
  width: lw + 8,
556
- height: EDGE_LABEL_FONT_SIZE + 6,
652
+ height: labelH,
557
653
  idx: i,
558
654
  });
559
655
  }
@@ -743,10 +839,12 @@ export function renderBoxesAndLinesForExport(
743
839
  options?: {
744
840
  exportDims?: { width: number; height: number };
745
841
  activeTagGroup?: string | null;
842
+ hiddenTagValues?: Map<string, Set<string>>;
746
843
  }
747
844
  ): void {
748
845
  renderBoxesAndLines(container, parsed, layout, palette, isDark, {
749
846
  exportDims: options?.exportDims,
750
847
  activeTagGroup: options?.activeTagGroup,
848
+ hiddenTagValues: options?.hiddenTagValues,
751
849
  });
752
850
  }
@@ -22,6 +22,7 @@ export interface BLGroup {
22
22
  children: string[];
23
23
  lineNumber: number;
24
24
  metadata: Record<string, string>;
25
+ parentGroup?: string;
25
26
  }
26
27
 
27
28
  export interface ParsedBoxesAndLines {
package/src/completion.ts CHANGED
@@ -335,6 +335,20 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
335
335
  hide: { description: 'Hide tag:value pairs' },
336
336
  }),
337
337
  ],
338
+ [
339
+ 'mindmap',
340
+ withGlobals({
341
+ 'hide-descriptions': { description: 'Hide node descriptions' },
342
+ 'active-tag': { description: 'Active tag group name' },
343
+ }),
344
+ ],
345
+ [
346
+ 'wireframe',
347
+ withGlobals({
348
+ mobile: { description: 'Use mobile (narrow vertical) layout' },
349
+ 'active-tag': { description: 'Active tag group name' },
350
+ }),
351
+ ],
338
352
  ]);
339
353
 
340
354
  // ============================================================
@@ -378,6 +392,8 @@ const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
378
392
  infra: 'Infrastructure diagram',
379
393
  gantt: 'Gantt chart',
380
394
  'boxes-and-lines': 'Boxes and lines diagram',
395
+ mindmap: 'Mindmap diagram',
396
+ wireframe: 'UI wireframe diagram',
381
397
  };
382
398
 
383
399
  /** All chart types with descriptions, for chart type autocomplete. Excludes `multi-line` alias. */
@@ -511,6 +527,16 @@ export const PIPE_METADATA = new Map<
511
527
  edge: {},
512
528
  },
513
529
  ],
530
+ [
531
+ 'mindmap',
532
+ {
533
+ node: {
534
+ description: { description: 'Node description text' },
535
+ collapsed: { description: 'Collapse node subtree by default' },
536
+ },
537
+ edge: {},
538
+ },
539
+ ],
514
540
  ]);
515
541
 
516
542
  // ============================================================
package/src/d3.ts CHANGED
@@ -6492,12 +6492,7 @@ export async function renderForExport(
6492
6492
  content: string,
6493
6493
  theme: 'light' | 'dark' | 'transparent',
6494
6494
  palette?: PaletteColors,
6495
- orgExportState?: {
6496
- collapsedNodes?: Set<string>;
6497
- activeTagGroup?: string | null;
6498
- hiddenAttributes?: Set<string>;
6499
- swimlaneTagGroup?: string | null;
6500
- },
6495
+ viewState?: import('./sharing').CompactViewState,
6501
6496
  options?: {
6502
6497
  c4Level?: 'context' | 'containers' | 'components' | 'deployment';
6503
6498
  c4System?: string;
@@ -6521,14 +6516,14 @@ export async function renderForExport(
6521
6516
  const orgParsed = parseOrg(content, effectivePalette);
6522
6517
  if (orgParsed.error) return '';
6523
6518
 
6524
- // Apply interactive collapse state when provided
6525
- const collapsedNodes = orgExportState?.collapsedNodes;
6519
+ // Apply interactive collapse state when provided (read from unified viewState)
6520
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6526
6521
  const activeTagGroup = resolveActiveTagGroup(
6527
6522
  orgParsed.tagGroups,
6528
6523
  orgParsed.options['active-tag'],
6529
- orgExportState?.activeTagGroup ?? options?.tagGroup
6524
+ viewState?.tag ?? options?.tagGroup
6530
6525
  );
6531
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6526
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6532
6527
 
6533
6528
  const { parsed: effectiveParsed, hiddenCounts } =
6534
6529
  collapsedNodes && collapsedNodes.size > 0
@@ -6575,14 +6570,14 @@ export async function renderForExport(
6575
6570
  const sitemapParsed = parseSitemap(content, effectivePalette);
6576
6571
  if (sitemapParsed.error || sitemapParsed.roots.length === 0) return '';
6577
6572
 
6578
- // Apply interactive collapse state when provided
6579
- const collapsedNodes = orgExportState?.collapsedNodes;
6573
+ // Apply interactive collapse state when provided (read from unified viewState)
6574
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6580
6575
  const activeTagGroup = resolveActiveTagGroup(
6581
6576
  sitemapParsed.tagGroups,
6582
6577
  sitemapParsed.options['active-tag'],
6583
- orgExportState?.activeTagGroup ?? options?.tagGroup
6578
+ viewState?.tag ?? options?.tagGroup
6584
6579
  );
6585
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6580
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6586
6581
 
6587
6582
  const { parsed: effectiveParsed, hiddenCounts } =
6588
6583
  collapsedNodes && collapsedNodes.size > 0
@@ -6635,8 +6630,12 @@ export async function renderForExport(
6635
6630
  activeTagGroup: resolveActiveTagGroup(
6636
6631
  kanbanParsed.tagGroups,
6637
6632
  kanbanParsed.options['active-tag'],
6638
- options?.tagGroup
6633
+ viewState?.tag ?? options?.tagGroup
6639
6634
  ),
6635
+ currentSwimlaneGroup: viewState?.swim ?? null,
6636
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
6637
+ collapsedColumns: viewState?.cc ? new Set(viewState.cc) : undefined,
6638
+ compactMeta: viewState?.cm,
6640
6639
  });
6641
6640
  return finalizeSvgExport(container, theme, effectivePalette);
6642
6641
  }
@@ -6696,22 +6695,32 @@ export async function renderForExport(
6696
6695
  resolveActiveTagGroup(
6697
6696
  erParsed.tagGroups,
6698
6697
  erParsed.options['active-tag'],
6699
- options?.tagGroup
6700
- )
6698
+ viewState?.tag ?? options?.tagGroup
6699
+ ),
6700
+ viewState?.sem
6701
6701
  );
6702
6702
  return finalizeSvgExport(container, theme, effectivePalette);
6703
6703
  }
6704
6704
 
6705
6705
  if (detectedType === 'boxes-and-lines') {
6706
6706
  const { parseBoxesAndLines } = await import('./boxes-and-lines/parser');
6707
- const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6708
- const { renderBoxesAndLinesForExport } =
6709
- await import('./boxes-and-lines/renderer');
6710
-
6711
6707
  const effectivePalette = await resolveExportPalette(theme, palette);
6712
6708
  const blParsed = parseBoxesAndLines(content);
6713
6709
  if (blParsed.error || blParsed.nodes.length === 0) return '';
6714
6710
 
6711
+ // Convert viewState.htv (Record<string, string[]>) to Map<string, Set<string>>
6712
+ let blHiddenTagValues: Map<string, Set<string>> | undefined;
6713
+ if (viewState?.htv) {
6714
+ blHiddenTagValues = new Map();
6715
+ for (const [k, v] of Object.entries(viewState.htv)) {
6716
+ blHiddenTagValues.set(k, new Set(v));
6717
+ }
6718
+ }
6719
+
6720
+ const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6721
+ const { renderBoxesAndLinesForExport } =
6722
+ await import('./boxes-and-lines/renderer');
6723
+
6715
6724
  const blLayout = layoutBoxesAndLines(blParsed);
6716
6725
  const PADDING = 20;
6717
6726
  const titleOffset = blParsed.title ? 40 : 0;
@@ -6727,12 +6736,106 @@ export async function renderForExport(
6727
6736
  theme === 'dark',
6728
6737
  {
6729
6738
  exportDims: { width: exportWidth, height: exportHeight },
6730
- activeTagGroup: options?.tagGroup,
6739
+ activeTagGroup: viewState?.tag ?? options?.tagGroup,
6740
+ hiddenTagValues: blHiddenTagValues,
6731
6741
  }
6732
6742
  );
6733
6743
  return finalizeSvgExport(container, theme, effectivePalette);
6734
6744
  }
6735
6745
 
6746
+ if (detectedType === 'mindmap') {
6747
+ const { parseMindmap } = await import('./mindmap/parser');
6748
+ const { layoutMindmap } = await import('./mindmap/layout');
6749
+ const { collapseMindmapTree } = await import('./mindmap/collapse');
6750
+ const { renderMindmap } = await import('./mindmap/renderer');
6751
+
6752
+ const isDark = theme === 'dark';
6753
+ const effectivePalette = await resolveExportPalette(theme, palette);
6754
+
6755
+ const mmParsed = parseMindmap(content, effectivePalette);
6756
+ if (mmParsed.error) return '';
6757
+
6758
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6759
+ const activeTagGroup = resolveActiveTagGroup(
6760
+ mmParsed.tagGroups,
6761
+ mmParsed.options['active-tag'],
6762
+ viewState?.tag ?? options?.tagGroup
6763
+ );
6764
+ const hideDescriptions =
6765
+ mmParsed.options['hide-descriptions'] === 'true' ||
6766
+ viewState?.hd === true;
6767
+
6768
+ const { roots: effectiveRoots, hiddenCounts } =
6769
+ collapsedNodes && collapsedNodes.size > 0
6770
+ ? collapseMindmapTree(mmParsed.roots, collapsedNodes)
6771
+ : { roots: mmParsed.roots, hiddenCounts: new Map<string, number>() };
6772
+
6773
+ const effectiveParsed = { ...mmParsed, roots: effectiveRoots };
6774
+
6775
+ const mmLayout = layoutMindmap(effectiveParsed, effectivePalette, {
6776
+ interactive: false,
6777
+ hiddenCounts: hiddenCounts.size > 0 ? hiddenCounts : undefined,
6778
+ activeTagGroup,
6779
+ hideDescriptions,
6780
+ });
6781
+
6782
+ const PADDING = 20;
6783
+ const titleOffset = effectiveParsed.title ? 30 : 0;
6784
+ const exportWidth = mmLayout.width + PADDING * 2;
6785
+ const exportHeight = mmLayout.height + PADDING * 2 + titleOffset;
6786
+ const container = createExportContainer(exportWidth, exportHeight);
6787
+
6788
+ const colorByDepth = viewState?.cbd === true;
6789
+
6790
+ renderMindmap(
6791
+ container,
6792
+ effectiveParsed,
6793
+ mmLayout,
6794
+ effectivePalette,
6795
+ isDark,
6796
+ undefined,
6797
+ { width: exportWidth, height: exportHeight },
6798
+ undefined,
6799
+ hideDescriptions,
6800
+ colorByDepth ? null : activeTagGroup,
6801
+ colorByDepth ? { colorByDepth: true } : undefined
6802
+ );
6803
+ return finalizeSvgExport(container, theme, effectivePalette);
6804
+ }
6805
+
6806
+ if (detectedType === 'wireframe') {
6807
+ const { parseWireframe } = await import('./wireframe/parser');
6808
+ const { layoutWireframe } = await import('./wireframe/layout');
6809
+ const { renderWireframe } = await import('./wireframe/renderer');
6810
+
6811
+ const effectivePalette = await resolveExportPalette(theme, palette);
6812
+ const wireframeParsed = parseWireframe(content);
6813
+ if (
6814
+ wireframeParsed.error ||
6815
+ (wireframeParsed.roots.length === 0 &&
6816
+ wireframeParsed.modals.length === 0)
6817
+ )
6818
+ return '';
6819
+
6820
+ const wireframeLayout = layoutWireframe(wireframeParsed);
6821
+
6822
+ const exportWidth = wireframeLayout.width;
6823
+ const exportHeight = wireframeLayout.height;
6824
+ const container = createExportContainer(exportWidth, exportHeight);
6825
+
6826
+ renderWireframe(
6827
+ container,
6828
+ wireframeParsed,
6829
+ wireframeLayout,
6830
+ effectivePalette,
6831
+ theme === 'dark',
6832
+ undefined,
6833
+ { width: exportWidth, height: exportHeight },
6834
+ theme
6835
+ );
6836
+ return finalizeSvgExport(container, theme, effectivePalette);
6837
+ }
6838
+
6736
6839
  if (detectedType === 'c4') {
6737
6840
  const { parseC4 } = await import('./c4/parser');
6738
6841
  const {
@@ -6748,10 +6851,18 @@ export async function renderForExport(
6748
6851
  const c4Parsed = parseC4(content, effectivePalette);
6749
6852
  if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
6750
6853
 
6751
- // Container/component-level rendering
6752
- const c4Level = options?.c4Level ?? 'context';
6753
- const c4System = options?.c4System;
6754
- const c4Container = options?.c4Container;
6854
+ // Container/component-level rendering (viewState fallback for share links)
6855
+ const c4Level =
6856
+ options?.c4Level ??
6857
+ (viewState?.c4l as
6858
+ | 'context'
6859
+ | 'containers'
6860
+ | 'components'
6861
+ | 'deployment'
6862
+ | undefined) ??
6863
+ 'context';
6864
+ const c4System = options?.c4System ?? viewState?.c4s;
6865
+ const c4Container = options?.c4Container ?? viewState?.c4c;
6755
6866
 
6756
6867
  const c4Layout =
6757
6868
  c4Level === 'deployment'
@@ -6788,7 +6899,7 @@ export async function renderForExport(
6788
6899
  resolveActiveTagGroup(
6789
6900
  c4Parsed.tagGroups,
6790
6901
  c4Parsed.options['active-tag'],
6791
- options?.tagGroup
6902
+ viewState?.tag ?? options?.tagGroup
6792
6903
  )
6793
6904
  );
6794
6905
  return finalizeSvgExport(container, theme, effectivePalette);
@@ -6834,7 +6945,7 @@ export async function renderForExport(
6834
6945
  const activeTagGroup = resolveActiveTagGroup(
6835
6946
  infraParsed.tagGroups,
6836
6947
  infraParsed.options['active-tag'],
6837
- options?.tagGroup
6948
+ viewState?.tag ?? options?.tagGroup
6838
6949
  );
6839
6950
 
6840
6951
  const titleOffset = infraParsed.title ? 40 : 0;
@@ -6860,7 +6971,8 @@ export async function renderForExport(
6860
6971
  false,
6861
6972
  null,
6862
6973
  null,
6863
- true
6974
+ true,
6975
+ viewState?.cg ? new Set(viewState.cg) : null
6864
6976
  );
6865
6977
  // Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
6866
6978
  const infraSvg = container.querySelector('svg');
@@ -6890,7 +7002,16 @@ export async function renderForExport(
6890
7002
  resolved,
6891
7003
  effectivePalette,
6892
7004
  theme === 'dark',
6893
- undefined,
7005
+ {
7006
+ collapsedGroups: viewState?.cg ? new Set(viewState.cg) : undefined,
7007
+ currentSwimlaneGroup: viewState?.swim ?? undefined,
7008
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
7009
+ currentActiveGroup: resolveActiveTagGroup(
7010
+ resolved.tagGroups,
7011
+ resolved.options.activeTag ?? undefined,
7012
+ viewState?.tag ?? options?.tagGroup
7013
+ ),
7014
+ },
6894
7015
  { width: EXPORT_W, height: EXPORT_H }
6895
7016
  );
6896
7017
  return finalizeSvgExport(container, theme, effectivePalette);
@@ -6991,9 +7112,9 @@ export async function renderForExport(
6991
7112
  resolveActiveTagGroup(
6992
7113
  parsed.timelineTagGroups,
6993
7114
  undefined,
6994
- orgExportState?.activeTagGroup ?? options?.tagGroup
7115
+ viewState?.tag ?? options?.tagGroup
6995
7116
  ),
6996
- orgExportState?.swimlaneTagGroup
7117
+ viewState?.swim
6997
7118
  );
6998
7119
  } else if (parsed.type === 'venn') {
6999
7120
  renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
@@ -17,6 +17,8 @@ import { looksLikeSitemap, parseSitemap } from './sitemap/parser';
17
17
  import { parseInfra } from './infra/parser';
18
18
  import { parseGantt } from './gantt/parser';
19
19
  import { parseBoxesAndLines } from './boxes-and-lines/parser';
20
+ import { parseMindmap } from './mindmap/parser';
21
+ import { parseWireframe } from './wireframe/parser';
20
22
  import { parseFirstLine } from './utils/parsing';
21
23
  import { makeDgmoError, suggest } from './diagnostics';
22
24
  import type { DgmoError } from './diagnostics';
@@ -149,6 +151,8 @@ const DIAGRAM_TYPES = new Set([
149
151
  'infra',
150
152
  'gantt',
151
153
  'boxes-and-lines',
154
+ 'mindmap',
155
+ 'wireframe',
152
156
  ]);
153
157
  const EXTENDED_CHART_TYPES = new Set([
154
158
  'scatter',
@@ -228,6 +232,8 @@ const PARSE_DISPATCH = new Map<
228
232
  ['infra', (c) => parseInfra(c)],
229
233
  ['gantt', (c) => parseGantt(c)],
230
234
  ['boxes-and-lines', (c) => parseBoxesAndLines(c)],
235
+ ['mindmap', (c) => parseMindmap(c)],
236
+ ['wireframe', (c) => parseWireframe(c)],
231
237
  ]);
232
238
 
233
239
  /**
@@ -13,6 +13,7 @@ export const CHART_TYPES = new Set([
13
13
  'infra',
14
14
  'gantt',
15
15
  'boxes-and-lines',
16
+ 'wireframe',
16
17
  // Data chart types
17
18
  'bar',
18
19
  'line',
@@ -170,6 +171,17 @@ export const CONTROL_KEYWORDS = new Set([
170
171
  'loop',
171
172
  'parallel',
172
173
  'note',
174
+ // Wireframe elements
175
+ 'nav',
176
+ 'tabs',
177
+ 'table',
178
+ 'image',
179
+ 'modal',
180
+ 'skeleton',
181
+ 'alert',
182
+ 'progress',
183
+ 'chart',
184
+ 'mobile',
173
185
  ]);
174
186
 
175
187
  /** Status keywords — kanban. */