@diagrammo/dgmo 0.8.11 → 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.
@@ -257,7 +257,7 @@ export function renderKanban(
257
257
  const legendY = DIAGRAM_PADDING + (TITLE_FONT_SIZE - LEGEND_HEIGHT) / 2;
258
258
  const legendConfig: LegendConfig = {
259
259
  groups: parsed.tagGroups,
260
- position: { placement: 'top-center', titleRelation: 'below-title' },
260
+ position: { placement: 'top-center', titleRelation: 'inline-with-title' },
261
261
  mode: exportDims ? 'inline' : 'fixed',
262
262
  };
263
263
  const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
package/src/org/parser.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  isTagBlockHeading,
7
7
  matchTagBlockHeading,
8
8
  validateTagValues,
9
+ validateTagGroupNames,
9
10
  stripDefaultModifier,
10
11
  } from '../utils/tag-groups';
11
12
  import {
@@ -54,6 +55,7 @@ const KNOWN_OPTIONS = new Set([
54
55
  'sub-node-label',
55
56
  'hide',
56
57
  'show-sub-node-count',
58
+ 'active-tag',
57
59
  ]);
58
60
  /** Known org chart boolean options (bare keyword = on). */
59
61
  const KNOWN_BOOLEANS = new Set(['show-sub-node-count', 'direction-tb']);
@@ -344,6 +346,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
344
346
  collectAll(result.roots);
345
347
 
346
348
  validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
349
+ validateTagGroupNames(result.tagGroups, pushWarning);
347
350
  }
348
351
 
349
352
  if (
@@ -18,6 +18,7 @@ import type { TagGroup } from '../utils/tag-groups';
18
18
  import {
19
19
  matchTagBlockHeading,
20
20
  validateTagValues,
21
+ validateTagGroupNames,
21
22
  stripDefaultModifier,
22
23
  } from '../utils/tag-groups';
23
24
 
@@ -1280,6 +1281,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1280
1281
  entities.push({ metadata: g.metadata, lineNumber: g.lineNumber });
1281
1282
  }
1282
1283
  validateTagValues(entities, result.tagGroups, pushWarning, suggest);
1284
+ validateTagGroupNames(result.tagGroups, pushWarning);
1283
1285
  }
1284
1286
 
1285
1287
  return result;
@@ -24,6 +24,7 @@ import type {
24
24
  import { isSequenceBlock, isSequenceSection, isSequenceNote } from './parser';
25
25
  import { resolveSequenceTags } from './tag-resolution';
26
26
  import type { ResolvedTagMap } from './tag-resolution';
27
+ import { resolveActiveTagGroup } from '../utils/tag-groups';
27
28
  import { LEGEND_HEIGHT } from '../utils/legend-constants';
28
29
  import { renderLegendD3 } from '../utils/legend-d3';
29
30
  import type { LegendConfig, LegendState } from '../utils/legend-types';
@@ -918,13 +919,14 @@ export function renderSequenceDiagram(
918
919
 
919
920
  const activationsOff = parsedOptions.activations?.toLowerCase() === 'off';
920
921
 
921
- // Tag resolution — compute resolved tag values and build color lookup
922
- // Explicit render option wins (including null = "no active group"),
923
- // then fall back to diagram-level `active-tag: Name` option for CLI/export
922
+ // Tag resolution — shared utility handles priority chain:
923
+ // programmatic override diagram-level active-tag auto-activate first group
924
924
  const activeTagGroup =
925
- options?.activeTagGroup !== undefined
926
- ? options.activeTagGroup || undefined
927
- : parsedOptions['active-tag'] || undefined;
925
+ resolveActiveTagGroup(
926
+ parsed.tagGroups,
927
+ parsedOptions['active-tag'],
928
+ options?.activeTagGroup
929
+ ) ?? undefined;
928
930
  let tagMap: ResolvedTagMap | undefined;
929
931
  const tagValueToColor = new Map<string, string>();
930
932
  if (activeTagGroup) {
package/src/sharing.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  decompressFromEncodedURIComponent,
4
4
  } from 'lz-string';
5
5
 
6
- const DEFAULT_BASE_URL = 'https://diagrammo.app/view';
6
+ const DEFAULT_BASE_URL = 'https://online.diagrammo.app';
7
7
  const COMPRESSED_SIZE_LIMIT = 8192; // 8 KB
8
8
 
9
9
  export interface DiagramViewState {
@@ -27,7 +27,12 @@ export interface EncodeDiagramUrlOptions {
27
27
 
28
28
  export type EncodeDiagramUrlResult =
29
29
  | { url: string; error?: undefined }
30
- | { url?: undefined; error: 'too-large'; compressedSize: number; limit: number };
30
+ | {
31
+ url?: undefined;
32
+ error: 'too-large';
33
+ compressedSize: number;
34
+ limit: number;
35
+ };
31
36
 
32
37
  /**
33
38
  * Compress a DGMO DSL string into a shareable URL.
@@ -36,14 +41,18 @@ export type EncodeDiagramUrlResult =
36
41
  */
37
42
  export function encodeDiagramUrl(
38
43
  dsl: string,
39
- options?: EncodeDiagramUrlOptions,
44
+ options?: EncodeDiagramUrlOptions
40
45
  ): EncodeDiagramUrlResult {
41
46
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
42
47
  const compressed = compressToEncodedURIComponent(dsl);
43
48
  const byteSize = new TextEncoder().encode(compressed).byteLength;
44
49
 
45
50
  if (byteSize > COMPRESSED_SIZE_LIMIT) {
46
- return { error: 'too-large', compressedSize: byteSize, limit: COMPRESSED_SIZE_LIMIT };
51
+ return {
52
+ error: 'too-large',
53
+ compressedSize: byteSize,
54
+ limit: COMPRESSED_SIZE_LIMIT,
55
+ };
47
56
  }
48
57
 
49
58
  let hash = `dgmo=${compressed}`;
@@ -124,7 +133,8 @@ export function decodeDiagramUrl(hash: string): DecodedDiagramUrl {
124
133
  viewState.collapsedLanes = val.split(',').filter(Boolean);
125
134
  }
126
135
  if (key === 'pal' && val) viewState.palette = val;
127
- if (key === 'th' && (val === 'light' || val === 'dark')) viewState.theme = val;
136
+ if (key === 'th' && (val === 'light' || val === 'dark'))
137
+ viewState.theme = val;
128
138
  }
129
139
 
130
140
  // Strip 'dgmo=' prefix
@@ -10,6 +10,7 @@ import {
10
10
  isTagBlockHeading,
11
11
  matchTagBlockHeading,
12
12
  validateTagValues,
13
+ validateTagGroupNames,
13
14
  stripDefaultModifier,
14
15
  } from '../utils/tag-groups';
15
16
  import {
@@ -476,6 +477,7 @@ export function parseSitemap(
476
477
  };
477
478
  collectAll(result.roots);
478
479
  validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
480
+ validateTagGroupNames(result.tagGroups, pushWarning);
479
481
  }
480
482
 
481
483
  if (
@@ -271,13 +271,15 @@ export function computeLegendLayout(
271
271
  }
272
272
 
273
273
  // Position elements in rows
274
+ const alignLeft = config.position.titleRelation === 'inline-with-title';
274
275
  const rows = layoutRows(
275
276
  activeCapsule,
276
277
  pills,
277
278
  controlLayouts,
278
279
  groupAvailW,
279
280
  containerWidth,
280
- totalControlsW
281
+ totalControlsW,
282
+ alignLeft
281
283
  );
282
284
 
283
285
  const height = rows.length * LEGEND_HEIGHT;
@@ -379,7 +381,8 @@ function layoutRows(
379
381
  controls: LegendControlLayout[],
380
382
  groupAvailW: number,
381
383
  containerWidth: number,
382
- totalControlsW: number
384
+ totalControlsW: number,
385
+ alignLeft = false
383
386
  ): Array<{
384
387
  y: number;
385
388
  items: Array<LegendPillLayout | LegendCapsuleLayout | LegendControlLayout>;
@@ -405,7 +408,8 @@ function layoutRows(
405
408
  const itemW = item.width + LEGEND_GROUP_GAP;
406
409
  if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
407
410
  // Commit current row
408
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
411
+ if (!alignLeft)
412
+ centerRowItems(currentRowItems, containerWidth, totalControlsW);
409
413
  rows.push({ y: rowY, items: currentRowItems });
410
414
  rowY += LEGEND_HEIGHT;
411
415
  currentRowItems = [];
@@ -304,6 +304,27 @@ export function validateTagValues(
304
304
  }
305
305
  }
306
306
 
307
+ // ── Tag Group Name Validation ────────────────────────────
308
+
309
+ /**
310
+ * Warn when a tag group uses the reserved name "none" (case-insensitive).
311
+ * Should be called alongside `validateTagValues()` in each parser's
312
+ * post-parse validation.
313
+ */
314
+ export function validateTagGroupNames(
315
+ tagGroups: ReadonlyArray<{ name: string; lineNumber: number }>,
316
+ pushWarning: (lineNumber: number, message: string) => void
317
+ ): void {
318
+ for (const group of tagGroups) {
319
+ if (group.name.toLowerCase() === 'none') {
320
+ pushWarning(
321
+ group.lineNumber,
322
+ `'none' is a reserved keyword and cannot be used as a tag group name`
323
+ );
324
+ }
325
+ }
326
+ }
327
+
307
328
  // ── Default Metadata Injection ────────────────────────────
308
329
 
309
330
  /**
@@ -340,6 +361,49 @@ export function injectDefaultTagMetadata(
340
361
  }
341
362
  }
342
363
 
364
+ // ── Active Tag Group Resolution ──────────────────────────────
365
+
366
+ /**
367
+ * Determine which tag group should be active, using a priority chain:
368
+ *
369
+ * 1. Programmatic override (from render API / CLI flag) — highest priority
370
+ * 2. Diagram-level `active-tag` option (from parsed source)
371
+ * 3. Auto-activate first declared tag group
372
+ * 4. No coloring (null)
373
+ *
374
+ * The sentinel value `"none"` (case-insensitive) at any level means
375
+ * "suppress tag coloring."
376
+ *
377
+ * @param tagGroups Declared tag groups (only `.name` is used)
378
+ * @param explicitActiveTag Value of `active-tag` option from parsed diagram, if any
379
+ * @param programmaticOverride Value from render API / CLI; `undefined` = not set,
380
+ * `null` or `''` = explicitly no coloring
381
+ */
382
+ export function resolveActiveTagGroup(
383
+ tagGroups: ReadonlyArray<{ name: string }>,
384
+ explicitActiveTag: string | undefined,
385
+ programmaticOverride?: string | null
386
+ ): string | null {
387
+ // 1. Programmatic override (highest priority)
388
+ if (programmaticOverride !== undefined) {
389
+ if (!programmaticOverride) return null; // null or ''
390
+ if (programmaticOverride.toLowerCase() === 'none') return null;
391
+ return programmaticOverride;
392
+ }
393
+
394
+ // 2. Diagram-level active-tag option
395
+ if (explicitActiveTag) {
396
+ if (explicitActiveTag.toLowerCase() === 'none') return null;
397
+ return explicitActiveTag;
398
+ }
399
+
400
+ // 3. Auto-activate first declared group
401
+ if (tagGroups.length > 0) return tagGroups[0].name;
402
+
403
+ // 4. No tag groups → no coloring
404
+ return null;
405
+ }
406
+
343
407
  // ── Matchers ────────────────────────────────────────────────
344
408
 
345
409
  export function matchTagBlockHeading(trimmed: string): TagBlockMatch | null {