@diagrammo/dgmo 0.25.2 → 0.25.3

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/README.md CHANGED
@@ -1,952 +1,137 @@
1
- # @diagrammo/dgmo
1
+ <div align="center">
2
2
 
3
- A diagram markup language — parser, config builder, renderer, and color system.
3
+ # `@diagrammo/dgmo`
4
4
 
5
- Write plain-text `.dgmo` files and render them as charts, diagrams, and visualizations. Supports 40+ chart types — data charts, structural diagrams, sequence diagrams, RACI matrices, and more. Ships as both a library and a standalone CLI.
5
+ ### Simple text in. Brilliant diagrams out.
6
6
 
7
- ## Language Reference
7
+ Write plain-text `.dgmo` files, get clean, themeable diagrams and charts. One markup language, **45+ chart types** — flowcharts, sequence diagrams, ER, org charts, C4, gantt, maps, bar/line/pie, and a lot more.
8
8
 
9
- Full syntax documentation for every chart type, directive, and option:
9
+ [![npm](https://img.shields.io/npm/v/@diagrammo/dgmo?color=2e7d32&label=npm)](https://www.npmjs.com/package/@diagrammo/dgmo)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@diagrammo/dgmo?color=blue)](https://www.npmjs.com/package/@diagrammo/dgmo)
11
+ [![license](https://img.shields.io/npm/l/@diagrammo/dgmo)](./LICENSE)
10
12
 
11
- - **Online:** [diagrammo.app/reference](https://diagrammo.app/reference)
12
- - **Local:** [`docs/language-reference.md`](docs/language-reference.md)
13
+ **[📖 Docs](https://diagrammo.app/start) · [🧩 Language Reference](https://diagrammo.app/reference) · [🖥️ Desktop App](https://diagrammo.app/app) · [🪄 Live Editor](https://online.diagrammo.app)**
13
14
 
14
- ## Install
15
+ </div>
15
16
 
16
- ### As a library
17
+ ---
17
18
 
18
- ```bash
19
- npm install @diagrammo/dgmo
20
- # or
21
- pnpm add @diagrammo/dgmo
22
19
  ```
20
+ sequence Boarding the Marauder
23
21
 
24
- ### As a CLI
25
-
26
- ```bash
27
- # via Homebrew (macOS)
28
- brew tap diagrammo/dgmo
29
- brew install dgmo
30
-
31
- # or run directly via npx
32
- npx @diagrammo/dgmo diagram.dgmo
22
+ Quartermaster -Hoist the colors-> Crew
23
+ Crew -Aye, captain-> Bosun
24
+ Bosun -Heading 270-> Helm
25
+ Helm -On course-> Quartermaster
33
26
  ```
34
27
 
35
- ## CLI usage
28
+ That's a complete diagram. No coordinates, no XML, no drag-and-drop — just text you can diff, review, and version like any other source file.
36
29
 
37
- ```bash
38
- # First time with no args? Creates a sample.dgmo to get you started
39
- dgmo
30
+ ## Install
40
31
 
41
- # Render to PNG (default)
42
- dgmo diagram.dgmo # → diagram.png
32
+ ```bash
33
+ # Library
34
+ npm install @diagrammo/dgmo
43
35
 
44
- # Render to SVG
45
- dgmo diagram.dgmo -o output.svg
36
+ # CLI (macOS, via Homebrew)
37
+ brew install diagrammo/dgmo/dgmo
46
38
 
47
- # Explicit PNG
48
- dgmo diagram.dgmo -o output.png
39
+ # CLI (no install)
40
+ npx @diagrammo/dgmo diagram.dgmo
41
+ ```
49
42
 
50
- # Pipe from stdin
51
- cat diagram.dgmo | dgmo -o out.png
52
- cat diagram.dgmo | dgmo > out.png # PNG to stdout
43
+ ## CLI
53
44
 
54
- # With theme and palette options
45
+ ```bash
46
+ dgmo # creates a sample.dgmo to get you started
47
+ dgmo diagram.dgmo # → diagram.png
48
+ dgmo diagram.dgmo -o out.svg # → SVG (format from extension)
49
+ cat diagram.dgmo | dgmo > out.png # pipe in, PNG to stdout
55
50
  dgmo diagram.dgmo --theme dark --palette catppuccin
56
51
  ```
57
52
 
58
- **Options:**
59
-
60
53
  | Flag | Values | Default |
61
54
  |------|--------|---------|
62
55
  | `--theme` | `light`, `dark`, `transparent` | `light` |
63
- | `--palette` | `nord`, `atlas`, `blueprint`, `slate`, `tidewater`, `solarized`, `catppuccin`, `rose-pine`, `gruvbox`, `tokyo-night`, `one-dark`, `dracula`, `monokai` | `nord` |
64
- | `-o` | Output file path (`.svg` extension → SVG, otherwise PNG) | `<input>.png` |
65
-
66
- ## Supported chart types
67
-
68
- ### Data Charts
69
-
70
- | Type | Description |
71
- |------|-------------|
72
- | `bar` | Vertical/horizontal bar charts |
73
- | `line` | Line charts with crosshair |
74
- | `area` | Filled area charts |
75
- | `pie` | Pie charts with connector labels |
76
- | `doughnut` | Doughnut charts |
77
- | `radar` | Radar/spider charts |
78
- | `polar-area` | Polar area charts |
79
- | `bar-stacked` | Stacked bar charts |
80
- | `multi-line` | Multi-series line charts |
81
- | `scatter` | XY scatter with categories and sizing |
82
- | `heatmap` | Matrix heatmaps |
83
- | `funnel` | Conversion funnels |
84
- | `sankey` | Flow diagrams |
85
- | `chord` | Circular relationship diagrams |
86
-
87
- ### Visualizations
88
-
89
- | Type | Description |
90
- |------|-------------|
91
- | `slope` | Before/after comparison |
92
- | `wordcloud` | Weighted text clouds |
93
- | `arc` | Arc/network diagrams |
94
- | `timeline` | Timelines with eras and markers |
95
- | `venn` | Set intersection diagrams |
96
- | `quadrant` | 2D quadrant scatter |
97
- | `tech-radar` | Technology adoption radar (ThoughtWorks style) |
98
-
99
- ### Structural Diagrams
100
-
101
- | Type | Description |
102
- |------|-------------|
103
- | `sequence` | Sequence diagrams with type inference |
104
- | `flowchart` | Directed graph flowcharts with branching and 6 node shapes |
105
- | `class` | UML class diagrams with inheritance, composition, and visibility |
106
- | `er` | Entity-relationship diagrams with crow's foot notation |
107
- | `org` | Org charts with hierarchy, team containers, and tag group color-coding |
108
- | `c4` | C4 architecture diagrams (context, containers, components, deployment) |
109
- | `state` | State machine diagrams |
110
- | `infra` | Infrastructure capacity and latency diagrams |
111
- | `kanban` | Kanban boards |
112
- | `sitemap` | Site structure and navigation maps |
113
-
114
- ### Other
115
-
116
- | Type | Description |
117
- |------|-------------|
118
- | `function` | Mathematical function plots |
119
-
120
- ## How it works
121
-
122
- Every `.dgmo` file is plain text with a `<type>` header line (optionally followed by a title) and then metadata and data. The library parses each chart type and gives you either:
123
-
124
- - A **config object** you render yourself
125
- - A **rendered SVG** directly
126
-
127
- ```
128
- parse → build config → render
129
- ```
56
+ | `--palette` | one of 13 palettes (see below) | `nord` |
57
+ | `-o` | output path (`.svg` → SVG, else PNG) | `<input>.png` |
130
58
 
131
- All parsers are pure functions with no DOM dependency. The CLI sets up jsdom internally for headless rendering.
59
+ ## Library
132
60
 
133
- ## Usage
134
-
135
- ### Data charts — standard (bar, line, pie, radar, etc.)
61
+ Render any diagram to an SVG string — no browser, no visible DOM:
136
62
 
137
63
  ```typescript
138
- import { parseChart, buildSimpleChartOption, getPalette } from '@diagrammo/dgmo';
139
- import * as echarts from 'echarts';
140
-
141
- const colors = getPalette('nord').light;
64
+ import { render } from '@diagrammo/dgmo';
142
65
 
143
- const content = `
66
+ const { svg } = await render(`
144
67
  bar Revenue by Quarter
145
- x-label Quarter
146
- y-label Revenue ($M)
147
-
148
68
  Q1 12
149
69
  Q2 19
150
70
  Q3 15
151
71
  Q4 22
152
- `;
153
-
154
- const parsed = parseChart(content, colors);
155
- const option = buildSimpleChartOption(parsed, colors, false);
156
- echarts.init(container).setOption(option);
157
- ```
158
-
159
- ### Data charts — extended (scatter, sankey, heatmap, etc.)
160
-
161
- ```typescript
162
- import { parseExtendedChart, buildExtendedChartOption, getPalette } from '@diagrammo/dgmo';
163
- import * as echarts from 'echarts';
164
-
165
- const colors = getPalette('nord').light;
166
-
167
- const content = `
168
- sankey Energy Flow
169
-
170
- Coal orange
171
- Electricity 50
172
- Gas blue
173
- Electricity 30
174
- Electricity -> Industry 45
175
- Electricity -> Homes 35
176
- `;
177
-
178
- const parsed = parseExtendedChart(content);
179
- const option = buildExtendedChartOption(parsed, colors, false);
180
- echarts.init(container).setOption(option);
181
- ```
182
-
183
- ### Visualizations (slope, timeline, wordcloud, etc.)
184
-
185
- ```typescript
186
- import { parseVisualization, renderTimeline, getPalette } from '@diagrammo/dgmo';
187
-
188
- const colors = getPalette('nord').light;
189
-
190
- const content = `
191
- timeline Project Milestones
192
-
193
- 2024-01 Kickoff
194
- 2024-03 -> 2024-06 Development
195
- 2024-07 Launch
196
- `;
197
-
198
- const parsed = parseVisualization(content, colors);
199
- renderTimeline(container, parsed, colors, false);
200
- ```
201
-
202
- ### Sequence diagrams
203
-
204
- Sequence diagrams use a minimal DSL. Participants are inferred from messages — no declaration blocks needed. Types (service, database, actor, queue, etc.) are inferred from naming conventions.
205
-
206
- ```typescript
207
- import { parseSequenceDgmo, renderSequenceDiagram, getPalette } from '@diagrammo/dgmo';
208
-
209
- const colors = getPalette('nord').light;
210
-
211
- const content = `
212
- sequence Login Flow
213
-
214
- User -login(email, pass)-> AuthService
215
- AuthService -findByEmail(email)-> UserDB
216
- AuthService <-user- UserDB
217
- User <-token- AuthService
218
- `;
219
-
220
- const parsed = parseSequenceDgmo(content);
221
- renderSequenceDiagram(container, parsed, colors, false, (lineNum) => {
222
- // clicked a message — jump to that line in the editor
223
- });
224
- ```
225
-
226
- **Sequence syntax:**
227
-
228
- - `A -message-> B` — synchronous call
229
- - `A -> B` — unlabeled synchronous call
230
- - `A ~message~> B` — async/fire-and-forget call
231
- - `A ~> B` — unlabeled async call
232
- - `A <-message- B` — synchronous return (dashed arrow, from B to A)
233
- - `A <- B` — unlabeled return
234
- - `A <~message~ B` — async return
235
- - `if condition` / `else` / `end` — conditional blocks
236
- - `loop condition` / `end` — loop blocks
237
- - `parallel` / `else` / `end` — concurrent branches
238
- - `== Section ==` — horizontal dividers (collapsible in the desktop app)
239
- - `[GroupName]` — participant grouping
240
- - `Name is a database` — explicit type declaration
241
- - `Name position 0` — explicit ordering
242
- - `activations off` — disable activation bars
243
- - `tag Name` + `Value(color)` entries — color-coded metadata dimensions with interactive legend
244
- - `| key: value` — attach tag metadata to participants, messages, or groups
245
-
246
- **Participant type inference** — 104 rules map names to shapes automatically:
247
-
248
- | Pattern | Inferred type | Shape |
249
- |---------|--------------|-------|
250
- | User, Admin, Alice, Bob | actor | stick figure |
251
- | DB, Postgres, Mongo, Redis (store) | database | cylinder |
252
- | Redis, Memcache (cache) | cache | dashed cylinder |
253
- | Queue, Kafka, SQS, EventBus | queue | horizontal cylinder |
254
- | Gateway, Proxy, LB, CDN | networking | shield |
255
- | App, Browser, Dashboard, CLI | frontend | rounded rect |
256
- | Service, API, Lambda, Fn | service | pill shape |
257
- | External, ThirdParty, Vendor | external | dashed square |
258
-
259
- ### Flowcharts
260
-
261
- Flowcharts use a concise syntax with 6 node shapes: `(terminal)`, `[process]`, `<decision>`, `/io/`, `{preparation}`, and `[[subroutine]]`. Edges support labels and branching.
262
-
263
- ```typescript
264
- import { parseFlowchart, layoutGraph, renderFlowchart, getPalette } from '@diagrammo/dgmo';
265
-
266
- const colors = getPalette('nord').dark;
267
-
268
- const content = `
269
- flowchart Decision Flow
270
-
271
- (Start) -> /Get Input/ -> <Valid?>
272
- -yes-> [Process Data] -> (Done)
273
- -no-> [Show Error] -> /Get Input/
274
- `;
275
-
276
- const parsed = parseFlowchart(content, colors);
277
- const layout = layoutGraph(parsed);
278
- renderFlowchart(container, parsed, layout, colors, true);
279
- ```
280
-
281
- ### ER diagrams
282
-
283
- ER diagrams use a table-and-column syntax with constraint annotations and cardinality relationships. Crow's foot notation is the default, with an optional `notation: labels` mode.
284
-
285
- ```typescript
286
- import { parseERDiagram, layoutERDiagram, renderERDiagram, getPalette } from '@diagrammo/dgmo';
287
-
288
- const colors = getPalette('nord').light;
289
-
290
- const content = `
291
- er Blog Platform
292
-
293
- users
294
- id: int [pk]
295
- name: varchar
296
- email: varchar [unique]
297
-
298
- posts
299
- id: int [pk]
300
- author_id: int [fk]
301
- title: varchar
302
-
303
- users 1--* posts: writes
304
- `;
305
-
306
- const parsed = parseERDiagram(content, colors);
307
- const layout = layoutERDiagram(parsed);
308
- renderERDiagram(container, parsed, layout, colors, false);
309
- ```
310
-
311
- **ER diagram syntax:**
312
-
313
- - `er` — chart type (first line), optionally followed by title: `er Blog Platform`
314
- - `notation labels` — use text labels instead of crow's foot markers
315
- - Table declaration: unindented name (e.g. `users`, `order_items`)
316
- - Column: indented `name: type [constraints]`
317
- - Constraints: `[pk]`, `[fk]`, `[unique]`, `[nullable]`, or combined `[pk, unique]`
318
- - Relationships with cardinality: `table1 1--* table2: label`
319
- - Symbolic: `1--*`, `1-*`, `?--1`, `*--*`
320
- - Keyword: `one-to-many`, `many-to-one`, `one-to-one`
321
- - Natural: `one to many`, `1 to many`
322
- - Colors: `table_name (color)` for explicit color
323
-
324
- ### Org charts
325
-
326
- Org charts use indentation to define hierarchy, with metadata on nodes, team containers, and tag groups for color-coding.
327
-
328
- ```typescript
329
- import { parseOrg, renderOrg, getPalette } from '@diagrammo/dgmo';
330
-
331
- const colors = getPalette('nord').light;
332
-
333
- const content = `
334
- org Engineering
335
-
336
- tag Location
337
- NY blue
338
- SF green
339
-
340
- Alex Chen
341
- role: CTO
342
- location: NY
343
-
344
- [Platform Team]
345
- goal: Core infrastructure
346
-
347
- Alice Park role: Senior Engineer, location: NY
348
- Bob Torres role: Junior Engineer, location: SF
349
- `;
350
-
351
- const parsed = parseOrg(content, colors);
352
- renderOrg(container, parsed, colors, false);
353
- ```
354
-
355
- **Org chart syntax:**
356
-
357
- - `org` — chart type (first line), optionally followed by title: `org Engineering`
358
- - Indentation defines parent-child hierarchy (2 or 4 spaces, consistent within file)
359
- - Multiple root nodes supported (e.g., co-CEOs at top level)
360
-
361
- **Node metadata:**
362
-
363
- ```
364
- Jane Smith
365
- role: CEO
366
- location: NY
367
- ```
368
-
369
- Or single-line with same-line metadata:
370
-
371
- ```
372
- Jane Smith role: CEO, location: NY
373
- ```
374
-
375
- **Team containers** — grouping constructs rendered as labeled boxes:
376
-
377
- ```
378
- [Platform Team]
379
- goal: Core infrastructure
380
- charter: Developer experience
381
-
382
- Alice Park
383
- role: Senior Engineer
384
- ```
385
-
386
- Containers can nest and carry their own metadata (key: value pairs). Children are bare labels (no colon).
387
-
388
- **Tag groups** — define color coding for metadata values. Must appear before org content:
389
-
390
- ```
391
- tag Location as l
392
- NY blue
393
- SF green
394
- Remote purple default
395
- ```
396
-
397
- - `tag GroupName` starts a tag group; `as <alias>` provides a shorthand for metadata keys
398
- - `Value color` maps a metadata value to a color (trailing-token form per spec §1.5)
399
- - `default` marks the fallback value for nodes without that metadata
400
- - Nodes whose metadata matches a tag group value get color-coded automatically
401
- - `##` syntax is deprecated but still accepted — use `tag` for new diagrams
402
-
403
- **Options:**
404
-
405
- | Option | Description |
406
- |--------|-------------|
407
- | `org Title Text` | Chart title (on first line after chart type) |
408
- | `sub-node-label Text` | Label for child count badges (e.g., "Crew", "Reports") |
409
- | `show-sub-node-count` | Show descendant count on nodes |
410
-
411
- **Comments:**
412
-
413
- ```
414
- // This is a comment
415
- ```
416
-
417
- ### Routing
418
-
419
- If you don't know the chart type ahead of time, use the router:
420
-
421
- ```typescript
422
- import { parseDgmoChartType, getRenderCategory, isExtendedChartType, parseChart, parseExtendedChart } from '@diagrammo/dgmo';
423
-
424
- const chartType = parseDgmoChartType(content); // e.g. 'bar'
425
- const category = getRenderCategory(chartType); // 'data-chart' | 'visualization' | 'diagram' | null
426
-
427
- // Dispatch within data-chart: standard vs extended parser
428
- if (isExtendedChartType(chartType)) {
429
- const parsed = parseExtendedChart(content); // scatter, sankey, heatmap, funnel, chord, function
430
- } else {
431
- const parsed = parseChart(content); // bar, line, pie, etc.
432
- }
433
- ```
434
-
435
- Content with `->` arrows and no chart type header is automatically detected as a sequence diagram.
436
-
437
- ## .dgmo file format
438
-
439
- Plain text. Lines starting with `#` or `//` are comments. Empty lines are ignored.
440
-
441
- ```
442
- <type> Optional Title
443
- x-label X Axis
444
- y-label Y Axis
445
- series Series1, Series2
446
- orientation horizontal
447
-
448
- # Multi-line values (alternative to comma-separated)
449
- series
450
- Series1
451
- Series2
452
-
453
- # Data section
454
- Label value
455
- Label (color) value
456
- Label value1, value2
457
-
458
- # Connections (sankey, chord, arc)
459
- Source -> Target weight
460
- Source (color) -> Target (color) weight (linkcolor)
461
-
462
- # Indentation syntax (sankey)
463
- Source (color)
464
- Target (color) weight (linkcolor)
465
-
466
- # Groups
467
- ## Category Name
468
- Item1 value
469
- Item2 value
470
- ```
471
-
472
- Colors can be specified inline as named colors (`red`, `blue`, `teal`, etc.) or hex values (`#ff6b6b`). They resolve against the active palette.
473
-
474
- ## Palettes
475
-
476
- Thirteen built-in palettes, each with light and dark variants:
477
-
478
- - `nordPalette` — cool, muted Scandinavian tones (default)
479
- - `atlasPalette` — vintage classroom-map pastels on warm manila paper
480
- - `blueprintPalette` — cyanotype engineering drawing (chalk on blueprint blue)
481
- - `slatePalette` — restrained corporate/BI neutrals with one corporate blue
482
- - `tidewaterPalette` — nautical maritime-chart (sea-mist, navy ink, brass)
483
- - `solarizedPalette` — warm/cool Solarized
484
- - `catppuccinPalette` — modern pastels
485
- - `rosePinePalette` — soft mauve and rose
486
- - `gruvboxPalette` — retro groove
487
- - `tokyoNightPalette` — Tokyo night
488
- - `oneDarkPalette` — Atom One Dark inspired
489
- - `draculaPalette` — Dracula
490
- - `monokaiPalette` — Monokai
491
-
492
- ```typescript
493
- import { getPalette, getAvailablePalettes, registerPalette } from '@diagrammo/dgmo';
494
-
495
- // Use a built-in palette
496
- const palette = getPalette('nord');
497
- const colors = palette.light; // or palette.dark
498
-
499
- // List available palettes
500
- const all = getAvailablePalettes(); // [{ id, name }, ...]
501
-
502
- // Register a custom palette
503
- registerPalette({
504
- id: 'custom',
505
- name: 'My Theme',
506
- light: { bg: '#fff', surface: '#f5f5f5', /* ... */ },
507
- dark: { bg: '#1a1a1a', surface: '#2a2a2a', /* ... */ },
508
- });
509
- ```
510
-
511
- ### Color utilities
512
-
513
- ```typescript
514
- import { hexToHSL, hslToHex, mute, tint, shade, contrastText } from '@diagrammo/dgmo';
515
-
516
- hexToHSL('#5e81ac') // { h: 213, s: 32, l: 52 }
517
- mute('#5e81ac') // desaturated + darkened hex
518
- tint('#5e81ac', 0.3) // blended toward white
519
- contrastText('#2e3440', '#eceff4', '#2e3440') // WCAG-compliant pick
520
- ```
521
-
522
- ### Mermaid theming
523
-
524
- Generate Mermaid-compatible CSS variables from any palette:
525
-
526
- ```typescript
527
- import { buildMermaidThemeVars, buildThemeCSS } from '@diagrammo/dgmo';
528
-
529
- const vars = buildMermaidThemeVars(palette.light); // ~121 CSS custom properties
530
- const css = buildThemeCSS(palette.light); // complete CSS string
531
- ```
532
-
533
- ## HTML embed (auto-render)
534
-
535
- Drop a `<script>` tag on any static HTML page and any `<pre class="dgmo">` block becomes a rendered diagram on load.
536
-
537
- ### 60-second quickstart
538
-
539
- ```html
540
- <!doctype html>
541
- <html>
542
- <head>
543
- <!-- Add `integrity="sha384-…"` for SRI; the published value for each
544
- release is in the GitHub release notes (or run `pnpm sri` after
545
- building from source). -->
546
- <script
547
- src="https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js"
548
- crossorigin="anonymous"></script>
549
- </head>
550
- <body>
551
- <pre class="dgmo">sequence Boarding the Marauder
552
-
553
- Quartermaster -Hoist colors-> Crew
554
- Crew -Aye, captain-> Bosun
555
- Bosun -Heading 270-> Helm
556
- Helm -On course-> Quartermaster
557
- </pre>
558
- </body>
559
- </html>
72
+ `, { theme: 'light', palette: 'nord' });
560
73
  ```
561
74
 
562
- The bundle exposes `window.dgmo` and self-runs on `DOMContentLoaded`. Each match is replaced with a `<div class="dgmo-rendered">` containing the SVG plus a collapsible source panel with **Copy** and **Open in editor** buttons.
563
-
564
- Selectors matched: `.dgmo`, `.language-dgmo` (covers Prism/highlight.js fenced ` ```dgmo ` blocks). Already-rendered nodes are tagged `data-dgmo-processed="true"` so re-runs are idempotent.
75
+ `render()` auto-detects the chart type and dispatches to the right engine. Need the lower-level parsers, config builders, and per-type renderers? They live under the [`@diagrammo/dgmo/advanced`](https://diagrammo.app/dev) subpath see the docs for the full surface and stability contract.
565
76
 
566
- ### Configuration
77
+ ## Drop into any web page
567
78
 
568
- Configure via JSON on the bundle's own `<script>` tag no inline JS, CSP-friendly:
79
+ Add one `<script>` tag and every `<pre class="dgmo">` block renders on load:
569
80
 
570
81
  ```html
571
- <script
572
- src="https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js"
573
- data-config='{"theme":"auto","palette":"nord","showSource":true,"showEditorLink":true}'
574
- ></script>
575
- ```
576
-
577
- | Key | Type | Default | Description |
578
- |-----|------|---------|-------------|
579
- | `theme` | `'auto' \| 'light' \| 'dark' \| 'transparent'` | `'auto'` | `'auto'` reads `prefers-color-scheme`, `<html data-theme>`, and `<html class="dark">`; re-renders live when the system preference flips |
580
- | `palette` | palette id (string) | `'nord'` | Any registered palette: `atlas`, `blueprint`, `catppuccin`, `dracula`, `gruvbox`, `monokai`, `nord`, `one-dark`, `rose-pine`, `slate`, `solarized`, `tidewater`, `tokyo-night` |
581
- | `showSource` | `boolean` | `true` | Show the collapsible "DGMO source" panel under each diagram |
582
- | `showEditorLink` | `boolean` | `true` | Include the "Open in editor" button (set `false` for air-gapped intranets) |
583
-
584
- Per-element override: `<pre class="dgmo" data-show-source="false">` hides only that diagram's source panel.
585
-
586
- Opt out of auto-bootstrap with `data-auto="false"` and call `dgmo.run()` manually after framework hydration:
82
+ <script src="https://cdn.jsdelivr.net/npm/@diagrammo/dgmo/dist/auto.js"></script>
587
83
 
588
- ```js
589
- window.dgmo.initialize({ theme: 'dark' });
590
- window.dgmo.run(); // or: window.dgmo.run({ nodes: [el1, el2] });
84
+ <pre class="dgmo">pie Crew Rations
85
+ Grog: 58
86
+ Hardtack: 21
87
+ Limes: 21</pre>
591
88
  ```
592
89
 
593
- ### Framework recipes
90
+ Theme detection, copy button, and "open in editor" come for free. Full config, framework recipes (Astro, Docusaurus, Hugo, MkDocs), self-hosting, and CSP guidance: **[diagrammo.app/embed](https://diagrammo.app/embed)**.
594
91
 
595
- <details>
596
- <summary><strong>Astro</strong></summary>
92
+ Using a docs framework? First-class plugins wrap all of this:
93
+ [`remark-dgmo`](https://www.npmjs.com/package/remark-dgmo) · [`astro-dgmo`](https://www.npmjs.com/package/astro-dgmo) · [`docusaurus-plugin-dgmo`](https://www.npmjs.com/package/docusaurus-plugin-dgmo) · [`fumadocs-dgmo`](https://www.npmjs.com/package/fumadocs-dgmo)
597
94
 
598
- Use `client:load` only if you need SPA hydration. For static pages the script tag is enough.
95
+ ## Use it from your AI tool
599
96
 
600
- ```astro
601
- ---
602
- // src/pages/index.astro
603
- ---
604
- <script src="https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js" is:inline></script>
605
- <pre class="dgmo">pie Languages
606
- TypeScript: 58
607
- Rust: 21</pre>
608
- ```
97
+ There's an MCP server ([`@diagrammo/dgmo-mcp`](https://www.npmjs.com/package/@diagrammo/dgmo-mcp)) so Claude, Cursor, and other agents can author and render DGMO directly. Setup: **[diagrammo.app/ai](https://diagrammo.app/ai)**.
609
98
 
610
- For islands/dynamic content set `data-auto="false"` and run from `onMount`.
611
- </details>
99
+ ## Chart types
612
100
 
613
- <details>
614
- <summary><strong>Docusaurus / MDX</strong></summary>
101
+ **Data charts** — bar · line · area · pie · doughnut · radar · polar-area · bar-stacked · multi-line · scatter · heatmap · funnel · sankey · chord
615
102
 
616
- Add the script via the `scripts` field in `docusaurus.config.js`:
103
+ **Visualizations** slope · wordcloud · arc · timeline · venn · quadrant · tech-radar · cycle · pyramid · ring · function · map
617
104
 
618
- ```js
619
- module.exports = {
620
- scripts: [{
621
- src: 'https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js',
622
- 'data-config': '{"theme":"auto"}',
623
- async: false,
624
- }],
625
- };
626
- ```
627
-
628
- If you use Prism, ensure it loads **after** the dgmo bundle, or set `data-auto="false"` and trigger `dgmo.run()` from a Docusaurus client module.
629
- </details>
630
-
631
- <details>
632
- <summary><strong>MkDocs</strong></summary>
633
-
634
- In `mkdocs.yml`:
635
-
636
- ```yaml
637
- extra_javascript:
638
- - https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js
639
- ```
640
-
641
- Markdown fences `` ```dgmo `` rendered by Pygments produce `<pre><code class="language-dgmo">…</code></pre>` — the auto bundle replaces the entire `<pre>`, leaving no empty shell.
642
- </details>
105
+ **Diagrams** — sequence · flowchart · class · er · org · c4 · state · infra · kanban · sitemap · mindmap · gantt · pert · journey-map · boxes-and-lines · wireframe · raci
643
106
 
644
- <details>
645
- <summary><strong>Hugo</strong></summary>
107
+ Each type's full syntax, directives, and options live in the **[Language Reference](https://diagrammo.app/reference)** — the authoritative spec.
646
108
 
647
- Add a partial at `layouts/partials/dgmo.html`:
109
+ ## Palettes & themes
648
110
 
649
- ```html
650
- <script src="https://cdn.jsdelivr.net/npm/@diagrammo/dgmo@^0.8/dist/auto.js"></script>
651
- ```
652
-
653
- Include it from your base template's `<head>`. Hugo's chroma highlighter emits `<pre><code class="language-dgmo">…</code></pre>` which the bundle picks up automatically.
654
- </details>
655
-
656
- ### Self-hosting
111
+ Thirteen built-in palettes, each with light, dark, and transparent variants:
657
112
 
658
- Drop `dist/auto.js` (and optionally `dist/auto.css`) on any web server or intranet CDN there are zero outbound runtime fetches. The only outbound link is the **Open in editor** button to `online.diagrammo.app`, suppressible with:
659
-
660
- ```html
661
- <script src="/static/auto.js" data-config='{"showEditorLink":false}'></script>
662
- ```
113
+ `nord` (default) · `atlas` · `blueprint` · `slate` · `tidewater` · `solarized` · `catppuccin` · `rose-pine` · `gruvbox` · `tokyo-night` · `one-dark` · `dracula` · `monokai`
663
114
 
664
- Air-gapped and outbound-blocked environments work without modification.
115
+ Register your own with `registerPalette()`. Color helpers (`getPalette`, `tint`, `mute`, `contrastText`, …) and Mermaid theme-variable generation ship from the package too — see the [docs](https://diagrammo.app/dev).
665
116
 
666
- ### Bundle size
117
+ ## Editor support
667
118
 
668
- The IIFE bundle ships every chart-type renderer for one-tag-and-done convenience, so the artifact is currently ~1.6 MB gzipped. If size matters more than coverage, two options:
119
+ Syntax highlighting and autocomplete for `.dgmo` files ship as ready-to-use exports:
669
120
 
670
- 1. **Use the npm-direct ESM/CJS exports** (`@diagrammo/dgmo/auto`) and tree-shake your bundler drops chart types you don't reference.
671
- 2. **Subset import** — if you only need a handful of chart types, import the parser/renderer pieces directly from `@diagrammo/dgmo` and skip the auto facade.
121
+ - `@diagrammo/dgmo/highlight` — standalone highlighter
122
+ - `@diagrammo/dgmo/editor` CodeMirror 6 language support
672
123
 
673
- Per-chart-type lazy-loading inside the IIFE is on the roadmap and tracked in the spec under ADR-2 plan B.
124
+ Both back the [desktop app](https://diagrammo.app/app) and the [live editor](https://online.diagrammo.app).
674
125
 
675
- ### Security & CSP
676
-
677
- Recommended Content-Security-Policy snippet:
678
-
679
- ```
680
- script-src 'self' https://cdn.jsdelivr.net;
681
- style-src 'self' 'unsafe-inline';
682
- connect-src 'self';
683
- ```
684
-
685
- The bundle makes no `fetch`/XHR calls. The injected `<style>` block requires `'unsafe-inline'` in `style-src`; for strict CSP, link the parallel `dist/auto.css` artifact and the bundle skips inline injection.
686
-
687
- `window.dgmo` and `window.diagrammo` are defined with `Object.defineProperty(..., { writable: false, configurable: false })` so a later-loaded script cannot intercept the API.
688
-
689
- ### SemVer policy
690
-
691
- Within a major version, the following surface is stable for embedders:
692
-
693
- - `window.dgmo` API (`initialize`, `run`, `version`)
694
- - DOM contract (selector, wrapper class names, `data-dgmo-processed` flag)
695
- - CSS class prefix `dgmo-`
696
- - Error-banner shape (HTML class names + text format — locked so AI tools can screen-scrape it)
697
- - Default values (`theme: 'auto'`, `palette: 'nord'`, `showSource: true`, `showEditorLink: true`)
698
- - UTM parameter shape on the editor link
699
-
700
- Any breaking change is a major-version bump. Pin `^0.8` (or whatever the current major is) in your CDN URL to opt out of breaking changes.
701
-
702
- ## Server-side / headless export
703
-
704
- Render any chart to an SVG string without a visible DOM:
705
-
706
- ```typescript
707
- import { renderForExport, renderExtendedChartForExport } from '@diagrammo/dgmo';
708
-
709
- // Diagrams, visualizations, and sequence charts
710
- const svg = await renderForExport(content, 'light');
711
-
712
- // Data charts (bar, line, scatter, sankey, etc.)
713
- const svg = await renderExtendedChartForExport(content, 'light');
714
- ```
715
-
716
- Both accept an optional third argument for a custom `PaletteColors` object (defaults to Nord).
717
-
718
- ## API tiers
719
-
720
- `@diagrammo/dgmo` ships three import paths with different stability contracts:
721
-
722
- | Path | Surface | SemVer contract |
723
- |------|---------|-----------------|
724
- | `@diagrammo/dgmo` | `render`, `validate`, `encodeDiagramUrl`, `decodeDiagramUrl`, `palettes`, `getPalette`, `themes`, plus the `RenderOptions`, `RenderResult`, `CompactViewState`, `PaletteConfig`, `PaletteColors`, `Theme`, `DgmoError` types | **Stable.** Breaking changes only in major versions. Pin `^0.x` (or `^1.x` once we hit 1.0). |
725
- | `@diagrammo/dgmo/highlight` | `highlightDgmo`, `NORD_ROLE_STYLES` and other role-style sets | **Stable.** Same policy as root. |
726
- | `@diagrammo/dgmo/editor` | CodeMirror grammar + language support | **Stable.** Same policy as root. |
727
- | `@diagrammo/dgmo/advanced` | Parsers (`parseChart`, `parseVisualization`, `parseSequenceDgmo`, …), layout helpers, config builders, low-level renderers, palette-color utilities, sequence internals — see the table below | **Reduced semver.** Symbols may be renamed, signatures may change, or exports may be removed in **minor** versions. Patch versions only fix bugs. Pin `~0.x.y` if you depend on `/advanced`. |
728
-
729
- The `@diagrammo/dgmo/internal` subpath was renamed to `/advanced` in 0.15.x and exists as a re-export alias for one minor version. It will be removed in 0.17.x — update your imports to `/advanced` before then.
730
-
731
- ## /advanced exports
732
-
733
- ### Router
734
-
735
- | Export | Description |
736
- |--------|-------------|
737
- | `parseDgmoChartType(content)` | Extract chart type from content (infers `sequence` from arrow syntax) |
738
- | `getRenderCategory(type)` | Map chart type → `'data-chart'` \| `'visualization'` \| `'diagram'` \| `null` |
739
- | `isExtendedChartType(type)` | Returns `true` for extended data-chart types (scatter, sankey, chord, function, heatmap, funnel) |
740
- | `RenderCategory` | Type alias for `'data-chart' \| 'visualization' \| 'diagram'` |
741
-
742
- ### Parsers
743
-
744
- | Export | Description |
745
- |--------|-------------|
746
- | `parseChart(content, colors)` | Parse standard data-chart types (bar, line, pie, radar, etc.) |
747
- | `parseExtendedChart(content)` | Parse extended data-chart types (scatter, sankey, heatmap, etc.) |
748
- | `parseVisualization(content, colors)` | Parse visualization types (slope, arc, timeline, etc.) |
749
- | `parseSequenceDgmo(content)` | Parse sequence diagrams |
750
- | `parseFlowchart(content, colors)` | Parse flowchart diagrams |
751
- | `parseClassDiagram(content, colors)` | Parse class diagrams |
752
- | `parseERDiagram(content, colors)` | Parse ER diagrams |
753
- | `parseOrg(content, colors)` | Parse org chart diagrams |
754
- | `parseQuadrant(content)` | Parse quadrant charts |
755
-
756
- ### Config builders
757
-
758
- | Export | Description |
759
- |--------|-------------|
760
- | `buildSimpleChartOption(parsed, colors, dark)` | ECharts option from `parseChart` result (bar, line, pie, etc.) |
761
- | `buildExtendedChartOption(parsed, colors, dark)` | ECharts option from `parseExtendedChart` result (scatter, sankey, etc.) |
762
- | `buildMermaidQuadrant(parsed, colors)` | Mermaid quadrantChart syntax string |
763
-
764
- ### Renderers
765
-
766
- | Export | Description |
767
- |--------|-------------|
768
- | `renderSlopeChart(el, parsed, colors, dark)` | Slope chart SVG |
769
- | `renderArcDiagram(el, parsed, colors, dark)` | Arc diagram SVG |
770
- | `renderTimeline(el, parsed, colors, dark)` | Timeline SVG |
771
- | `renderWordCloud(el, parsed, colors, dark)` | Word cloud SVG |
772
- | `renderVenn(el, parsed, colors, dark)` | Venn diagram SVG |
773
- | `renderQuadrant(el, parsed, colors, dark)` | Quadrant chart SVG |
774
- | `renderFlowchart(el, parsed, layout, colors, dark)` | Flowchart SVG |
775
- | `renderClassDiagram(el, parsed, layout, colors, dark)` | Class diagram SVG |
776
- | `renderERDiagram(el, parsed, layout, colors, dark)` | ER diagram SVG |
777
- | `renderOrg(el, parsed, colors, dark)` | Org chart SVG |
778
- | `layoutClassDiagram(parsed)` | Compute class diagram node positions |
779
- | `layoutERDiagram(parsed)` | Compute ER diagram node positions |
780
- | `layoutGraph(parsed)` | Compute flowchart node positions |
781
- | `renderSequenceDiagram(el, parsed, colors, dark, onClick)` | Sequence diagram SVG |
782
- | `renderForExport(content, theme, palette?)` | Any diagram or visualization → SVG string |
783
- | `renderExtendedChartForExport(content, theme, palette?)` | Any data-chart → SVG string |
784
-
785
- ### Sequence internals
786
-
787
- | Export | Description |
788
- |--------|-------------|
789
- | `buildRenderSequence(parsed)` | Ordered render steps from parsed diagram |
790
- | `computeActivations(steps, participants)` | Activation bar positions |
791
- | `applyPositionOverrides(participants, parsed)` | Apply `Name position N` overrides |
792
- | `applyGroupOrdering(participants, groups)` | Reorder participants by group |
793
- | `groupMessagesBySection(elements)` | Group elements into collapsible sections |
794
- | `inferParticipantType(name)` | Infer participant type from name |
795
-
796
- ### Palette & color
797
-
798
- | Export | Description |
799
- |--------|-------------|
800
- | `getPalette(id)` | Get palette by ID (falls back to Nord) |
801
- | `getAvailablePalettes()` | List registered palettes `[{ id, name }]` |
802
- | `registerPalette(config)` | Register a custom palette |
803
- | `resolveColor(name, colors)` | Resolve color name or hex against a palette |
804
- | `hexToHSL(hex)` / `hslToHex(h,s,l)` | Color conversion |
805
- | `mute(hex)` / `tint(hex, amount)` / `shade(hex, base, amount)` | Color manipulation |
806
- | `contrastText(bg, light, dark)` | WCAG contrast text picker |
807
- | `buildMermaidThemeVars(colors)` | Mermaid CSS variables |
808
- | `buildThemeCSS(colors)` | Complete Mermaid theme CSS |
809
-
810
- ## Development
811
-
812
- ### Prerequisites
813
-
814
- - Node.js 18+
815
- - pnpm (`npm install -g pnpm`)
816
-
817
- ### Setup
126
+ ## Develop
818
127
 
819
128
  ```bash
820
129
  pnpm install
821
130
  pnpm build # tsup → dist/ (ESM + CJS + CLI)
131
+ pnpm test # vitest
132
+ pnpm typecheck
822
133
  ```
823
134
 
824
- ### Commands
825
-
826
- ```bash
827
- pnpm build # Production build (lib + CLI)
828
- pnpm dev # Watch mode (rebuild on save)
829
- pnpm test # Run tests (Vitest)
830
- pnpm test:watch # Tests in watch mode
831
- pnpm typecheck # TypeScript type checking
832
- ```
833
-
834
- ### Quick CLI testing
835
-
836
- ```bash
837
- ./test-cli.sh input.dgmo [args...] # Builds and runs in one step
838
- ```
839
-
840
- ### Project structure
841
-
842
- ```
843
- src/
844
- ├── index.ts # Public API exports
845
- ├── cli.ts # CLI entry point → dist/cli.cjs
846
- ├── dgmo-router.ts # Chart type → framework dispatcher
847
- ├── chart.ts # Standard chart parser (bar, line, pie, etc.)
848
- ├── echarts.ts # ECharts parser, config builder, SSR export
849
- ├── d3.ts # D3 parsers + renderers (slope, arc, timeline, wordcloud, venn, quadrant)
850
- ├── class/ # Class diagram parser, layout engine, and renderer
851
- ├── er/ # ER diagram parser, layout engine, and renderer
852
- ├── graph/ # Flowchart parser, layout engine, and renderer
853
- ├── org/ # Org chart parser, layout engine, and renderer
854
- ├── dgmo-mermaid.ts # Quadrant parser + Mermaid syntax builder
855
- ├── colors.ts # Named color map, resolve helper
856
- ├── fonts.ts # Font family constants (Helvetica for resvg)
857
- ├── sequence/
858
- │ ├── parser.ts # Sequence diagram DSL parser
859
- │ ├── renderer.ts # SVG renderer (D3-based)
860
- │ └── participant-inference.ts # 104-rule name → type engine
861
- └── palettes/
862
- ├── types.ts # PaletteConfig, PaletteColors types
863
- ├── registry.ts # getPalette, registerPalette
864
- ├── color-utils.ts # HSL conversions, mix(), mute(), tint()
865
- ├── mermaid-bridge.ts # Mermaid CSS variable builder
866
- ├── nord.ts # Nord palette (default)
867
- ├── atlas.ts # Atlas palette
868
- ├── blueprint.ts # Blueprint palette
869
- ├── slate.ts # Slate palette
870
- ├── tidewater.ts # Tidewater palette
871
- ├── solarized.ts # Solarized palette
872
- ├── catppuccin.ts # Catppuccin palette
873
- ├── rose-pine.ts # Rose Pine palette
874
- ├── gruvbox.ts # Gruvbox palette
875
- ├── tokyo-night.ts # Tokyo Night palette
876
- ├── one-dark.ts # One Dark palette
877
- ├── dracula.ts # Dracula palette
878
- └── monokai.ts # Monokai palette
879
- ```
880
-
881
- ### Build output
882
-
883
- tsup produces:
884
- - `dist/index.js` + `dist/index.d.ts` (ESM)
885
- - `dist/index.cjs` + `dist/index.d.cts` (CJS)
886
- - `dist/cli.cjs` (CLI binary — bundles everything except `@resvg/resvg-js`)
887
-
888
- ### Testing
889
-
890
- Tests live in `tests/` and use Vitest with jsdom:
891
-
892
- ```bash
893
- pnpm test # Run all tests
894
- pnpm test -- --reporter verbose # Verbose output
895
- ```
896
-
897
- ## Releasing
898
-
899
- ### npm publish
900
-
901
- 1. Bump version in `package.json`
902
- 2. Build and test:
903
- ```bash
904
- pnpm build && pnpm test
905
- ```
906
- 3. Publish:
907
- ```bash
908
- npm publish
909
- ```
910
- 4. After publishing, update downstream consumers:
911
- - **homebrew-dgmo**: Update `Formula/dgmo.rb` with new tarball URL and sha256
912
- - **obsidian-dgmo**: Update `@diagrammo/dgmo` version in `package.json`
913
- - **diagrammo-app**: Update submodule ref (`git submodule update --remote`)
914
-
915
- ### Generating the sha256 for Homebrew
916
-
917
- ```bash
918
- VERSION=0.2.7 # new version
919
- curl -sL "https://registry.npmjs.org/@diagrammo/dgmo/-/dgmo-${VERSION}.tgz" | shasum -a 256
920
- ```
921
-
922
- ## Gallery
923
-
924
- The gallery renders every fixture in `gallery/fixtures/` across all palettes, themes, and formats, producing a filterable HTML page.
925
-
926
- ```bash
927
- pnpm gallery # Build CLI + render all combinations
928
- ```
929
-
930
- Output lands in `gallery/output/` (gitignored):
931
-
932
- - `gallery/output/renders/` — individual SVG and PNG files
933
- - `gallery/output/index.html` — filterable gallery page (open in a browser)
934
-
935
- ### Filter options
936
-
937
- ```bash
938
- pnpm gallery -- --chart bar
939
- pnpm gallery -- --palette nord
940
- pnpm gallery -- --theme dark
941
- pnpm gallery -- --format svg
942
- pnpm gallery -- --chart sequence --palette catppuccin --theme light --format png
943
- pnpm gallery -- --concurrency 4 # defaults to CPU count
944
- ```
945
-
946
- ### Adding fixtures
947
-
948
- Drop a new `.dgmo` file into `gallery/fixtures/` and re-run `pnpm gallery`.
949
-
950
135
  ## License
951
136
 
952
- MIT
137
+ MIT © Demian Neidetcher
package/dist/auto.cjs CHANGED
@@ -59158,7 +59158,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
59158
59158
 
59159
59159
  // src/auto/index.ts
59160
59160
  init_safe_href();
59161
- var VERSION = "0.25.2";
59161
+ var VERSION = "0.25.3";
59162
59162
  var DEFAULTS = {
59163
59163
  theme: "auto",
59164
59164
  palette: "nord",
package/dist/auto.js CHANGED
@@ -400,7 +400,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
400
400
  .dgmo-rendered.dgmo-theme-dark .dgmo-source-pre .dgmo-tok-comment,
401
401
  .dgmo-rendered.dgmo-theme-dark .dgmo-source-pre .dgmo-tok-noteContent,
402
402
  .dgmo-rendered.dgmo-theme-dark .dgmo-source-pre .dgmo-tok-punctuation { color: #616e88; }
403
- `;wtt();var rQt="0.25.2",hLr={theme:"auto",palette:"nord",showSource:!0,showEditorLink:!0},Qxt={...hLr},Cet=new Set,pLr="https://online.diagrammo.app",gLr=8192,Xxn=256*1024,mLr=200,bLr=1200,vLr="[dgmo:auto]",wLr=/[\x00-\x1F‪-‮⁦-⁩]/g,eQt="dgmoAutoStyles";function R1(...r){typeof console<"u"&&console.warn&&console.warn(vLr,...r)}function Jxn(r){return typeof r=="object"&&r!==null&&!Array.isArray(r)}function yLr(r){r.querySelectorAll("script, foreignObject").forEach(a=>a.remove());let o=[r,...Array.from(r.querySelectorAll("*"))];for(let a of o){for(let c of Array.from(a.attributes))c.name.toLowerCase().startsWith("on")&&a.removeAttribute(c.name);if(a.hasAttribute("href")&&AC(a.getAttribute("href"))===null&&a.removeAttribute("href"),a.hasAttributeNS("http://www.w3.org/1999/xlink","href")){let c=a.getAttributeNS("http://www.w3.org/1999/xlink","href");AC(c)===null&&a.removeAttributeNS("http://www.w3.org/1999/xlink","href")}}}function Zxn(r){try{return Pgt().some(n=>n.id===r)}catch{return!1}}function Qxn(){if(typeof document>"u")return null;let r=document.currentScript;if(r&&r instanceof HTMLScriptElement)return r;let n=document.querySelectorAll('script[src*="auto.js"], script[src*="auto.min.js"]');return n.length===0?null:n[n.length-1]}var xLr=["theme","palette","showSource","showEditorLink"],t2n=["__proto__","constructor","prototype"],SLr=["auto","light","dark","transparent"];function e2n(r){return typeof r=="string"&&SLr.includes(r)}function n2n(r){if(!r)return{};let n;try{n=JSON.parse(r)}catch(a){return R1("data-config: invalid JSON",a),{}}if(!Jxn(n))return R1("data-config: not an object"),{};for(let a of t2n)if(Object.prototype.hasOwnProperty.call(n,a))return R1("data-config: rejected (prototype-pollution key:",a,")"),{};let o={};for(let[a,c]of Object.entries(n)){if(!xLr.includes(a)){R1("data-config: dropping unknown key",a);continue}if(a==="theme"){if(!e2n(c)){R1("data-config: rejected theme",c);continue}o.theme=c}else if(a==="palette"){if(typeof c!="string"||!Zxn(c)){R1("data-config: rejected palette",c);continue}o.palette=c}else if(a==="showSource"){if(typeof c!="boolean"){R1("data-config: rejected showSource",c);continue}o.showSource=c}else if(a==="showEditorLink"){if(typeof c!="boolean"){R1("data-config: rejected showEditorLink",c);continue}o.showEditorLink=c}}return o}function r2n(r=document){let n=r.querySelectorAll(".dgmo:not([data-dgmo-processed]), .language-dgmo:not([data-dgmo-processed])");return Array.from(n).filter(o=>!o.closest(".dgmo-rendered"))}function i2n(r){if(r==="light"||r==="dark"||r==="transparent")return r;if(typeof document>"u")return"light";let n=document.documentElement,o=n.getAttribute("data-theme");return o==="dark"?"dark":o==="light"?"light":n.classList.contains("dark")?"dark":n.classList.contains("light")?"light":typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function o2n(){if(typeof document>"u")return;let r=document.documentElement;if(r?.dataset?.[eQt]==="1")return;if(document.querySelector('link[rel="stylesheet"][href*="auto.css"]')){r&&r.dataset&&(r.dataset[eQt]="1");return}let o=document.createElement("style");o.setAttribute("data-dgmo-auto",""),o.textContent=qxn,document.head.appendChild(o),r&&r.dataset&&(r.dataset[eQt]="1")}function ELr(r){let n=document.createDocumentFragment(),o;try{o=Yxn(r)}catch{return n.appendChild(document.createTextNode(r)),n}for(let a of o){if(!a.text)continue;let c=document.createElement("span");c.className=`dgmo-tok-${a.role}`,c.textContent=a.text,n.appendChild(c)}return n}var a2n='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5"/></svg>',_Lr='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 8.5 6.5 12 13 4.5"/></svg>',kLr='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 2h5v5"/><path d="M14 2L7 9"/><path d="M13 9v4a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4"/></svg>';function t2t(r,n){r.innerHTML=n}function TLr(r,n,o){let a=document.createElement("div");a.className="dgmo-source-panel";let c=document.createElement("button");c.type="button",c.className="dgmo-source-toggle",c.setAttribute("aria-expanded","false");let u=document.createElement("span");u.className="dgmo-chevron",u.setAttribute("aria-hidden","true"),u.textContent="\u25B8",c.appendChild(u);let f=document.createElement("span");f.textContent="DGMO source",c.appendChild(f);let d=document.createElement("div");d.className="dgmo-source-body";let b=document.createElement("pre");b.className="dgmo-source-pre",b.appendChild(ELr(r)),d.appendChild(b);let v=document.createElement("div");v.className="dgmo-source-actions";let y=document.createElement("button");if(y.type="button",y.className="dgmo-btn dgmo-btn-copy",y.setAttribute("aria-label","Copy DGMO source"),y.title="Copy source",t2t(y,a2n),y.addEventListener("click",()=>{CLr(r,y)}),v.appendChild(y),o){let x=document.createElement("a");x.className="dgmo-btn dgmo-btn-editor",x.target="_blank",x.rel="noopener noreferrer",x.setAttribute("aria-label","Open in editor"),t2t(x,kLr),n?(x.href=n,x.title="Open in editor"):(x.setAttribute("aria-disabled","true"),x.title="Diagram too large for share link; copy source and paste into editor",x.addEventListener("click",S=>S.preventDefault())),v.appendChild(x)}return d.appendChild(v),a.appendChild(c),a.appendChild(d),c.addEventListener("click",()=>{let x=d.classList.toggle("dgmo-open");c.setAttribute("aria-expanded",x?"true":"false")}),a}async function CLr(r,n){let o=!1;try{navigator?.clipboard?.writeText&&(await navigator.clipboard.writeText(r),o=!0)}catch{}if(!o&&typeof document<"u")try{let a=document.createElement("textarea");a.value=r,a.setAttribute("readonly",""),a.style.position="absolute",a.style.left="-9999px",document.body.appendChild(a),a.select(),o=document.execCommand("copy"),document.body.removeChild(a)}catch{o=!1}o?(t2t(n,_Lr),n.classList.add("dgmo-btn-copied"),setTimeout(()=>{t2t(n,a2n),n.classList.remove("dgmo-btn-copied")},bLr)):R1("clipboard write failed")}function Tet(r){let n=document.createElement("div");n.className="dgmo-error-banner",n.setAttribute("role","alert");let o=document.createElement("div");if(o.className="dgmo-error-banner-title",o.textContent=`${r.severity??"error"}: ${r.message}`,n.appendChild(o),r.line!==void 0&&r.line>0){let a=document.createElement("div");a.className="dgmo-error-banner-loc",a.textContent=r.column!==void 0?`at ${r.line}:${r.column}`:`at line ${r.line}`,n.appendChild(a)}return n}function MLr(r){let n=r.split(`
403
+ `;wtt();var rQt="0.25.3",hLr={theme:"auto",palette:"nord",showSource:!0,showEditorLink:!0},Qxt={...hLr},Cet=new Set,pLr="https://online.diagrammo.app",gLr=8192,Xxn=256*1024,mLr=200,bLr=1200,vLr="[dgmo:auto]",wLr=/[\x00-\x1F‪-‮⁦-⁩]/g,eQt="dgmoAutoStyles";function R1(...r){typeof console<"u"&&console.warn&&console.warn(vLr,...r)}function Jxn(r){return typeof r=="object"&&r!==null&&!Array.isArray(r)}function yLr(r){r.querySelectorAll("script, foreignObject").forEach(a=>a.remove());let o=[r,...Array.from(r.querySelectorAll("*"))];for(let a of o){for(let c of Array.from(a.attributes))c.name.toLowerCase().startsWith("on")&&a.removeAttribute(c.name);if(a.hasAttribute("href")&&AC(a.getAttribute("href"))===null&&a.removeAttribute("href"),a.hasAttributeNS("http://www.w3.org/1999/xlink","href")){let c=a.getAttributeNS("http://www.w3.org/1999/xlink","href");AC(c)===null&&a.removeAttributeNS("http://www.w3.org/1999/xlink","href")}}}function Zxn(r){try{return Pgt().some(n=>n.id===r)}catch{return!1}}function Qxn(){if(typeof document>"u")return null;let r=document.currentScript;if(r&&r instanceof HTMLScriptElement)return r;let n=document.querySelectorAll('script[src*="auto.js"], script[src*="auto.min.js"]');return n.length===0?null:n[n.length-1]}var xLr=["theme","palette","showSource","showEditorLink"],t2n=["__proto__","constructor","prototype"],SLr=["auto","light","dark","transparent"];function e2n(r){return typeof r=="string"&&SLr.includes(r)}function n2n(r){if(!r)return{};let n;try{n=JSON.parse(r)}catch(a){return R1("data-config: invalid JSON",a),{}}if(!Jxn(n))return R1("data-config: not an object"),{};for(let a of t2n)if(Object.prototype.hasOwnProperty.call(n,a))return R1("data-config: rejected (prototype-pollution key:",a,")"),{};let o={};for(let[a,c]of Object.entries(n)){if(!xLr.includes(a)){R1("data-config: dropping unknown key",a);continue}if(a==="theme"){if(!e2n(c)){R1("data-config: rejected theme",c);continue}o.theme=c}else if(a==="palette"){if(typeof c!="string"||!Zxn(c)){R1("data-config: rejected palette",c);continue}o.palette=c}else if(a==="showSource"){if(typeof c!="boolean"){R1("data-config: rejected showSource",c);continue}o.showSource=c}else if(a==="showEditorLink"){if(typeof c!="boolean"){R1("data-config: rejected showEditorLink",c);continue}o.showEditorLink=c}}return o}function r2n(r=document){let n=r.querySelectorAll(".dgmo:not([data-dgmo-processed]), .language-dgmo:not([data-dgmo-processed])");return Array.from(n).filter(o=>!o.closest(".dgmo-rendered"))}function i2n(r){if(r==="light"||r==="dark"||r==="transparent")return r;if(typeof document>"u")return"light";let n=document.documentElement,o=n.getAttribute("data-theme");return o==="dark"?"dark":o==="light"?"light":n.classList.contains("dark")?"dark":n.classList.contains("light")?"light":typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function o2n(){if(typeof document>"u")return;let r=document.documentElement;if(r?.dataset?.[eQt]==="1")return;if(document.querySelector('link[rel="stylesheet"][href*="auto.css"]')){r&&r.dataset&&(r.dataset[eQt]="1");return}let o=document.createElement("style");o.setAttribute("data-dgmo-auto",""),o.textContent=qxn,document.head.appendChild(o),r&&r.dataset&&(r.dataset[eQt]="1")}function ELr(r){let n=document.createDocumentFragment(),o;try{o=Yxn(r)}catch{return n.appendChild(document.createTextNode(r)),n}for(let a of o){if(!a.text)continue;let c=document.createElement("span");c.className=`dgmo-tok-${a.role}`,c.textContent=a.text,n.appendChild(c)}return n}var a2n='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5"/></svg>',_Lr='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 8.5 6.5 12 13 4.5"/></svg>',kLr='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 2h5v5"/><path d="M14 2L7 9"/><path d="M13 9v4a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4"/></svg>';function t2t(r,n){r.innerHTML=n}function TLr(r,n,o){let a=document.createElement("div");a.className="dgmo-source-panel";let c=document.createElement("button");c.type="button",c.className="dgmo-source-toggle",c.setAttribute("aria-expanded","false");let u=document.createElement("span");u.className="dgmo-chevron",u.setAttribute("aria-hidden","true"),u.textContent="\u25B8",c.appendChild(u);let f=document.createElement("span");f.textContent="DGMO source",c.appendChild(f);let d=document.createElement("div");d.className="dgmo-source-body";let b=document.createElement("pre");b.className="dgmo-source-pre",b.appendChild(ELr(r)),d.appendChild(b);let v=document.createElement("div");v.className="dgmo-source-actions";let y=document.createElement("button");if(y.type="button",y.className="dgmo-btn dgmo-btn-copy",y.setAttribute("aria-label","Copy DGMO source"),y.title="Copy source",t2t(y,a2n),y.addEventListener("click",()=>{CLr(r,y)}),v.appendChild(y),o){let x=document.createElement("a");x.className="dgmo-btn dgmo-btn-editor",x.target="_blank",x.rel="noopener noreferrer",x.setAttribute("aria-label","Open in editor"),t2t(x,kLr),n?(x.href=n,x.title="Open in editor"):(x.setAttribute("aria-disabled","true"),x.title="Diagram too large for share link; copy source and paste into editor",x.addEventListener("click",S=>S.preventDefault())),v.appendChild(x)}return d.appendChild(v),a.appendChild(c),a.appendChild(d),c.addEventListener("click",()=>{let x=d.classList.toggle("dgmo-open");c.setAttribute("aria-expanded",x?"true":"false")}),a}async function CLr(r,n){let o=!1;try{navigator?.clipboard?.writeText&&(await navigator.clipboard.writeText(r),o=!0)}catch{}if(!o&&typeof document<"u")try{let a=document.createElement("textarea");a.value=r,a.setAttribute("readonly",""),a.style.position="absolute",a.style.left="-9999px",document.body.appendChild(a),a.select(),o=document.execCommand("copy"),document.body.removeChild(a)}catch{o=!1}o?(t2t(n,_Lr),n.classList.add("dgmo-btn-copied"),setTimeout(()=>{t2t(n,a2n),n.classList.remove("dgmo-btn-copied")},bLr)):R1("clipboard write failed")}function Tet(r){let n=document.createElement("div");n.className="dgmo-error-banner",n.setAttribute("role","alert");let o=document.createElement("div");if(o.className="dgmo-error-banner-title",o.textContent=`${r.severity??"error"}: ${r.message}`,n.appendChild(o),r.line!==void 0&&r.line>0){let a=document.createElement("div");a.className="dgmo-error-banner-loc",a.textContent=r.column!==void 0?`at ${r.line}:${r.column}`:`at line ${r.line}`,n.appendChild(a)}return n}function MLr(r){let n=r.split(`
404
404
  `).map(o=>o.trim()).find(o=>o.length>0);return n?n.replace(wLr,"").slice(0,mLr):"DGMO diagram"}function LLr(r){if(r.tagName==="CODE"){let n=r.parentElement;if(n?.tagName==="PRE"){let o=Array.from(n.childNodes).filter(a=>a.nodeType===1||a.nodeType===3&&(a.textContent||"").trim().length>0);if(o.length===1&&o[0]===r)return n}}return r}async function s2n(r){if(!(r instanceof HTMLElement))return{};if(r.dataset.dgmoProcessed==="true")return{};r.dataset.dgmoProcessed="true";let n=r.textContent||"";if(new TextEncoder().encode(n).byteLength>Xxn){r.style.visibility="";let A=Tet({message:`DGMO source too large to render \u2014 ${Xxn/1024} KB max`,severity:"error"});return r.parentElement?.insertBefore(A,r),R1("source exceeds 256 KB cap"),{}}let a=Qxt,c=a.theme==="transparent"?"transparent":i2n(a.theme),u=c==="transparent"?"transparent":c,f=MLr(n),d=r.dataset.showSource,b=a.showSource;d==="true"?b=!0:d==="false"?b=!1:d!==void 0&&R1("data-show-source: invalid value",d,'\u2014 expected "true" or "false"');let v;try{v=await kxn(n,{theme:u,palette:a.palette})}catch(A){r.style.visibility="";let I=A instanceof Error?A.message:"Render failed unexpectedly",N=Tet({message:I,severity:"error"});return r.parentElement?.insertBefore(N,r),R1("render() rejected:",A),{}}if(v.diagnostics&&v.diagnostics.length>0){r.style.visibility="";let A=v.diagnostics[0],I=Tet({message:A.message,severity:A.severity,line:A.line,...A.column!==void 0&&{column:A.column}});return r.parentElement?.insertBefore(I,r),R1("diagnostic:",A.message,A.line,A.column),{}}if(!v.svg){r.style.visibility="";let A=Tet({message:"Empty SVG returned from renderer",severity:"error"});return r.parentElement?.insertBefore(A,r),{}}let y=document.createElement("div"),x=c==="dark"?"dgmo-theme-dark":c==="transparent"?"dgmo-theme-transparent":"dgmo-theme-light";y.className=`dgmo-rendered ${x}`,y.dataset.dgmoProcessed="true";let S=document.createElement("div");S.innerHTML=v.svg;let _=S.querySelector("svg");if(!_){r.style.visibility="";let A=Tet({message:"Empty SVG returned from renderer",severity:"error"});return r.parentElement?.insertBefore(A,r),{}}if(yLr(_),_.setAttribute("role","img"),_.setAttribute("aria-label",f),y.appendChild(_),b){let A=null;if(a.showEditorLink){let N=c==="dark"?"dark":"light",D=Mxn(n,{baseUrl:pLr,palette:a.palette,theme:N});if(D.url){let G=`utm_source=auto-embed&utm_medium=html&utm_campaign=${encodeURIComponent(rQt)}`,B=D.url.indexOf("#"),O;if(B===-1){let W=D.url.includes("?")?"&":"?";O=D.url+W+G}else{let W=D.url.slice(0,B),Y=D.url.slice(B+1),H=W.includes("?")?"&":"?",j=Y.length>0?"&":"";O=W+H+G+"#"+Y+j+G}A=O,AC(A)||(A=null)}else D.error==="too-large"&&D.compressedSize>gLr&&(A=null)}let I=TLr(n,A,a.showEditorLink);y.appendChild(I)}let C={wrapper:y,source:n,perElementShowSource:d==="true"?!0:d==="false"?!1:null};return Cet.add(C),LLr(r).replaceWith(y),{wrapper:y}}function iQt(r={}){if(!Jxn(r))return;for(let o of t2n)if(Object.prototype.hasOwnProperty.call(r,o)){R1("initialize: rejected (prototype-pollution key:",o,")");return}let n={...Qxt};e2n(r.theme)&&(n.theme=r.theme),typeof r.palette=="string"&&Zxn(r.palette)&&(n.palette=r.palette),typeof r.showSource=="boolean"&&(n.showSource=r.showSource),typeof r.showEditorLink=="boolean"&&(n.showEditorLink=r.showEditorLink),Qxt=n}async function oQt(r={}){o2n();for(let o of Array.from(Cet))document.contains(o.wrapper)||Cet.delete(o);let n;if(r.nodes){n=("length"in r.nodes&&typeof r.nodes!="string"?Array.from(r.nodes):[]).filter(c=>c instanceof Element&&(c.matches(".dgmo, .language-dgmo")||c.querySelector(".dgmo, .language-dgmo")!==null));let a=[];for(let c of n)c.matches(".dgmo, .language-dgmo")&&a.push(c),c.querySelectorAll(".dgmo:not([data-dgmo-processed]), .language-dgmo:not([data-dgmo-processed])").forEach(f=>a.push(f));n=Array.from(new Set(a)).filter(c=>!c.closest(".dgmo-rendered"))}else n=r2n();await Promise.all(n.map(o=>s2n(o).catch(()=>({}))))}function ALr(){if(Qxt.theme==="auto")for(let r of Array.from(Cet)){let n=document.createElement("pre");n.className="dgmo",n.textContent=r.source,r.perElementShowSource!==null&&(n.dataset.showSource=String(r.perElementShowSource)),r.wrapper.replaceWith(n),Cet.delete(r),s2n(n)}}function ILr(){if(typeof window>"u"||!window.matchMedia)return;let r=window.matchMedia("(prefers-color-scheme: dark)"),n=()=>ALr();r.addEventListener?r.addEventListener("change",n):"addListener"in r&&typeof r.addListener=="function"&&r.addListener(n)}function nQt(){if(typeof document>"u")return;document.querySelectorAll(".dgmo:not(.dgmo-rendered), .language-dgmo").forEach(n=>{n.closest(".dgmo-rendered")||(n.style.visibility="visible")}),document.documentElement&&document.documentElement.dataset&&(document.documentElement.dataset.dgmoAutoFailed="1")}function NLr(){try{o2n();let r=Qxn(),n=r?.getAttribute("data-auto"),o=r?.getAttribute("data-config");if(o){let c=n2n(o);iQt(c)}if(ILr(),typeof window<"u"&&window.setTimeout(()=>{document.querySelectorAll(".dgmo:not(.dgmo-rendered):not([data-dgmo-processed])").length>0&&(R1("safety timeout: un-hiding unrendered sources"),nQt())},3e4),n==="false")return;let a=()=>{oQt().catch(c=>{R1("run() crashed:",c),nQt()})};typeof document<"u"&&document.readyState!=="loading"?a():typeof document<"u"&&document.addEventListener("DOMContentLoaded",a,{once:!0})}catch(r){R1("bootstrap failed:",r),nQt()}}var c2n=Object.freeze({initialize:iQt,run:oQt,version:rQt});function Kxn(r,n){try{Object.defineProperty(r,n,{value:c2n,writable:!1,configurable:!1,enumerable:!0})}catch{}}typeof window<"u"&&(Kxn(window,"dgmo"),Kxn(window,"diagrammo"),NLr());var PLr=c2n;return qze(DLr);})();
405
405
  /*! Bundled license information:
406
406
 
package/dist/auto.mjs CHANGED
@@ -59168,7 +59168,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
59168
59168
 
59169
59169
  // src/auto/index.ts
59170
59170
  init_safe_href();
59171
- var VERSION = "0.25.2";
59171
+ var VERSION = "0.25.3";
59172
59172
  var DEFAULTS = {
59173
59173
  theme: "auto",
59174
59174
  palette: "nord",
package/dist/index.cjs CHANGED
@@ -58782,8 +58782,11 @@ function computeBBox(svg) {
58782
58782
  const y = attr(tag, "y");
58783
58783
  if (x !== null && y !== null) {
58784
58784
  const w = text.length * 7;
58785
- push(x - w / 2, y - 14);
58786
- push(x + w / 2, y + 4);
58785
+ const anchor = tag.match(/\btext-anchor="([^"]*)"/)?.[1] ?? "start";
58786
+ const left = anchor === "middle" ? x - w / 2 : anchor === "end" ? x - w : x;
58787
+ const right = anchor === "middle" ? x + w / 2 : anchor === "end" ? x : x + w;
58788
+ push(left, y - 14);
58789
+ push(right, y + 4);
58787
58790
  }
58788
58791
  }
58789
58792
  for (const m of svg.matchAll(/<path\b[^>]*?\bd="([^"]+)"/g)) {
package/dist/index.js CHANGED
@@ -58787,8 +58787,11 @@ function computeBBox(svg) {
58787
58787
  const y = attr(tag, "y");
58788
58788
  if (x !== null && y !== null) {
58789
58789
  const w = text.length * 7;
58790
- push(x - w / 2, y - 14);
58791
- push(x + w / 2, y + 4);
58790
+ const anchor = tag.match(/\btext-anchor="([^"]*)"/)?.[1] ?? "start";
58791
+ const left = anchor === "middle" ? x - w / 2 : anchor === "end" ? x - w : x;
58792
+ const right = anchor === "middle" ? x + w / 2 : anchor === "end" ? x : x + w;
58793
+ push(left, y - 14);
58794
+ push(right, y + 4);
58792
58795
  }
58793
58796
  }
58794
58797
  for (const m of svg.matchAll(/<path\b[^>]*?\bd="([^"]+)"/g)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.25.2",
3
+ "version": "0.25.3",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -204,9 +204,18 @@ function computeBBox(
204
204
  const y = attr(tag, 'y');
205
205
  if (x !== null && y !== null) {
206
206
  const w = text.length * 7;
207
- // text-anchor may be start/middle/end; assume worst case (middle).
208
- push(x - w / 2, y - 14);
209
- push(x + w / 2, y + 4);
207
+ // Honor text-anchor so the horizontal extent points the right way:
208
+ // `start` (SVG default) grows rightward from x, `end` grows leftward,
209
+ // `middle` straddles x. Assuming middle for everything under-measures
210
+ // start-anchored text (e.g. pyramid right-column descriptions), which
211
+ // collapses the tight viewBox and clips that text in embeds.
212
+ const anchor = tag.match(/\btext-anchor="([^"]*)"/)?.[1] ?? 'start';
213
+ const left =
214
+ anchor === 'middle' ? x - w / 2 : anchor === 'end' ? x - w : x;
215
+ const right =
216
+ anchor === 'middle' ? x + w / 2 : anchor === 'end' ? x : x + w;
217
+ push(left, y - 14);
218
+ push(right, y + 4);
210
219
  }
211
220
  }
212
221