@diagrammo/dgmo 0.6.3 → 0.7.1
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/dist/cli.cjs +180 -178
- package/dist/index.cjs +5447 -2229
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -16
- package/dist/index.d.ts +236 -16
- package/dist/index.js +5439 -2228
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/parser.ts +3 -2
- package/src/c4/renderer.ts +6 -6
- package/src/class/renderer.ts +183 -7
- package/src/cli.ts +3 -11
- package/src/colors.ts +3 -3
- package/src/d3.ts +132 -29
- package/src/dgmo-router.ts +3 -1
- package/src/er/parser.ts +5 -3
- package/src/er/renderer.ts +11 -5
- package/src/gantt/calculator.ts +717 -0
- package/src/gantt/parser.ts +767 -0
- package/src/gantt/renderer.ts +2251 -0
- package/src/gantt/resolver.ts +144 -0
- package/src/gantt/types.ts +168 -0
- package/src/index.ts +27 -0
- package/src/infra/renderer.ts +48 -12
- package/src/initiative-status/filter.ts +63 -0
- package/src/initiative-status/layout.ts +319 -67
- package/src/initiative-status/parser.ts +200 -25
- package/src/initiative-status/renderer.ts +293 -10
- package/src/initiative-status/types.ts +6 -0
- package/src/org/layout.ts +22 -55
- package/src/org/parser.ts +7 -5
- package/src/org/renderer.ts +4 -8
- package/src/palettes/dracula.ts +60 -0
- package/src/palettes/index.ts +8 -6
- package/src/palettes/monokai.ts +60 -0
- package/src/palettes/registry.ts +4 -2
- package/src/sequence/parser.ts +10 -9
- package/src/sequence/renderer.ts +5 -4
- package/src/sharing.ts +8 -0
- package/src/sitemap/parser.ts +5 -3
- package/src/sitemap/renderer.ts +4 -4
- package/src/utils/duration.ts +212 -0
- package/src/utils/legend-constants.ts +1 -0
- package/src/utils/parsing.ts +23 -12
package/package.json
CHANGED
package/src/c4/parser.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
measureIndent,
|
|
12
12
|
extractColor,
|
|
13
13
|
parsePipeMetadata,
|
|
14
|
+
MULTIPLE_PIPE_WARNING,
|
|
14
15
|
CHART_TYPE_RE,
|
|
15
16
|
TITLE_RE,
|
|
16
17
|
OPTION_RE,
|
|
@@ -411,7 +412,7 @@ export function parseC4(
|
|
|
411
412
|
// Otherwise it's a deployment node (possibly with pipe metadata)
|
|
412
413
|
const segments = trimmed.split('|').map((s) => s.trim());
|
|
413
414
|
const nodeName = segments[0];
|
|
414
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
415
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, 'warning'));
|
|
415
416
|
const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
|
|
416
417
|
|
|
417
418
|
const dNode: C4DeploymentNode = {
|
|
@@ -598,7 +599,7 @@ export function parseC4(
|
|
|
598
599
|
namePart = namePart.substring(0, isAMatch.index!).trim();
|
|
599
600
|
}
|
|
600
601
|
|
|
601
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
602
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, 'warning'));
|
|
602
603
|
|
|
603
604
|
// Determine shape: explicit > inference
|
|
604
605
|
const shape =
|
package/src/c4/renderer.ts
CHANGED
|
@@ -256,7 +256,7 @@ export function renderC4Context(
|
|
|
256
256
|
|
|
257
257
|
const scaledW = diagramW * scale;
|
|
258
258
|
const offsetX = (width - scaledW) / 2;
|
|
259
|
-
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
259
|
+
const offsetY = titleHeight + DIAGRAM_PADDING + legendReserveH;
|
|
260
260
|
|
|
261
261
|
const svg = d3Selection
|
|
262
262
|
.select(container)
|
|
@@ -592,12 +592,12 @@ export function renderC4Context(
|
|
|
592
592
|
|
|
593
593
|
// ── Legend ──
|
|
594
594
|
if (hasLegend) {
|
|
595
|
-
// App mode: fixed overlay at SVG
|
|
595
|
+
// App mode: fixed overlay at SVG top so it's always readable regardless of scale.
|
|
596
596
|
// Export mode: render inside scaled contentG at layout coordinates.
|
|
597
597
|
const legendParent = fixedLegend
|
|
598
598
|
? svg.append('g')
|
|
599
599
|
.attr('class', 'c4-legend-fixed')
|
|
600
|
-
.attr('transform', `translate(0, ${
|
|
600
|
+
.attr('transform', `translate(0, ${DIAGRAM_PADDING + titleHeight})`)
|
|
601
601
|
: contentG.append('g').attr('class', 'c4-legend');
|
|
602
602
|
if (activeTagGroup) {
|
|
603
603
|
legendParent.attr('data-legend-active', activeTagGroup.toLowerCase());
|
|
@@ -1298,7 +1298,7 @@ export function renderC4Containers(
|
|
|
1298
1298
|
|
|
1299
1299
|
const scaledW = diagramW * scale;
|
|
1300
1300
|
const offsetX = (width - scaledW) / 2;
|
|
1301
|
-
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
1301
|
+
const offsetY = titleHeight + DIAGRAM_PADDING + legendReserveH;
|
|
1302
1302
|
|
|
1303
1303
|
const svg = d3Selection
|
|
1304
1304
|
.select(container)
|
|
@@ -1707,12 +1707,12 @@ export function renderC4Containers(
|
|
|
1707
1707
|
|
|
1708
1708
|
// ── Legend ──
|
|
1709
1709
|
if (hasLegend) {
|
|
1710
|
-
// App mode: fixed overlay at SVG
|
|
1710
|
+
// App mode: fixed overlay at SVG top so it's always readable regardless of scale.
|
|
1711
1711
|
// Export mode: render inside scaled contentG at layout coordinates.
|
|
1712
1712
|
const legendParent = fixedLegend
|
|
1713
1713
|
? svg.append('g')
|
|
1714
1714
|
.attr('class', 'c4-legend-fixed')
|
|
1715
|
-
.attr('transform', `translate(0, ${
|
|
1715
|
+
.attr('transform', `translate(0, ${DIAGRAM_PADDING + titleHeight})`)
|
|
1716
1716
|
: contentG.append('g').attr('class', 'c4-legend');
|
|
1717
1717
|
if (activeTagGroup) {
|
|
1718
1718
|
legendParent.attr('data-legend-active', activeTagGroup.toLowerCase());
|
package/src/class/renderer.ts
CHANGED
|
@@ -6,6 +6,18 @@ import * as d3Selection from 'd3-selection';
|
|
|
6
6
|
import * as d3Shape from 'd3-shape';
|
|
7
7
|
import { FONT_FAMILY } from '../fonts';
|
|
8
8
|
import { runInExportContainer, extractExportSvg } from '../utils/export-container';
|
|
9
|
+
import {
|
|
10
|
+
LEGEND_HEIGHT,
|
|
11
|
+
LEGEND_PILL_PAD,
|
|
12
|
+
LEGEND_PILL_FONT_SIZE,
|
|
13
|
+
LEGEND_PILL_FONT_W,
|
|
14
|
+
LEGEND_CAPSULE_PAD,
|
|
15
|
+
LEGEND_DOT_R,
|
|
16
|
+
LEGEND_ENTRY_FONT_SIZE,
|
|
17
|
+
LEGEND_ENTRY_FONT_W,
|
|
18
|
+
LEGEND_ENTRY_DOT_GAP,
|
|
19
|
+
LEGEND_ENTRY_TRAIL,
|
|
20
|
+
} from '../utils/legend-constants';
|
|
9
21
|
import type { PaletteColors } from '../palettes';
|
|
10
22
|
import { mix } from '../palettes/color-utils';
|
|
11
23
|
import type { ParsedClassDiagram, ClassModifier, RelationshipType } from './types';
|
|
@@ -51,6 +63,49 @@ function nodeStroke(palette: PaletteColors, modifier: ClassModifier | undefined,
|
|
|
51
63
|
return nodeColor ?? modifierColor(modifier, palette, colorOff);
|
|
52
64
|
}
|
|
53
65
|
|
|
66
|
+
// ============================================================
|
|
67
|
+
// Legend helpers
|
|
68
|
+
// ============================================================
|
|
69
|
+
|
|
70
|
+
interface ClassLegendEntry {
|
|
71
|
+
label: string;
|
|
72
|
+
colorKey: keyof PaletteColors['colors'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const CLASS_TYPE_MAP: Record<string, ClassLegendEntry> = {
|
|
76
|
+
class: { label: 'Class', colorKey: 'teal' },
|
|
77
|
+
abstract: { label: 'Abstract', colorKey: 'purple' },
|
|
78
|
+
interface: { label: 'Interface', colorKey: 'blue' },
|
|
79
|
+
enum: { label: 'Enum', colorKey: 'yellow' },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const CLASS_TYPE_ORDER = ['class', 'abstract', 'interface', 'enum'];
|
|
83
|
+
|
|
84
|
+
function collectClassTypes(parsed: ParsedClassDiagram): ClassLegendEntry[] {
|
|
85
|
+
if (parsed.options?.color === 'off') return [];
|
|
86
|
+
|
|
87
|
+
const present = new Set<string>();
|
|
88
|
+
for (const c of parsed.classes) {
|
|
89
|
+
if (c.color) continue; // explicit color override — skip
|
|
90
|
+
present.add(c.modifier ?? 'class');
|
|
91
|
+
}
|
|
92
|
+
return CLASS_TYPE_ORDER.filter((k) => present.has(k)).map((k) => CLASS_TYPE_MAP[k]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const LEGEND_GROUP_NAME = 'Type';
|
|
96
|
+
|
|
97
|
+
function legendEntriesWidth(entries: ClassLegendEntry[]): number {
|
|
98
|
+
let w = 0;
|
|
99
|
+
for (const e of entries) {
|
|
100
|
+
w += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + e.label.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
101
|
+
}
|
|
102
|
+
return w;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function classTypeKey(modifier: ClassModifier | undefined): string {
|
|
106
|
+
return modifier ?? 'class';
|
|
107
|
+
}
|
|
108
|
+
|
|
54
109
|
// ============================================================
|
|
55
110
|
// Visibility symbols
|
|
56
111
|
// ============================================================
|
|
@@ -107,7 +162,8 @@ export function renderClassDiagram(
|
|
|
107
162
|
palette: PaletteColors,
|
|
108
163
|
isDark: boolean,
|
|
109
164
|
onClickItem?: (lineNumber: number) => void,
|
|
110
|
-
exportDims?: { width?: number; height?: number }
|
|
165
|
+
exportDims?: { width?: number; height?: number },
|
|
166
|
+
legendActive?: boolean | null
|
|
111
167
|
): void {
|
|
112
168
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
113
169
|
|
|
@@ -115,17 +171,22 @@ export function renderClassDiagram(
|
|
|
115
171
|
const height = exportDims?.height ?? container.clientHeight;
|
|
116
172
|
if (width <= 0 || height <= 0) return;
|
|
117
173
|
|
|
174
|
+
const legendEntries = collectClassTypes(parsed);
|
|
175
|
+
const hasLegend = legendEntries.length > 1; // only show when multiple types present
|
|
176
|
+
|
|
118
177
|
const titleHeight = parsed.title ? 40 : 0;
|
|
178
|
+
const LEGEND_FIXED_GAP = 8;
|
|
179
|
+
const legendReserve = hasLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP : 0;
|
|
119
180
|
const diagramW = layout.width;
|
|
120
181
|
const diagramH = layout.height;
|
|
121
|
-
const availH = height - titleHeight;
|
|
182
|
+
const availH = height - titleHeight - legendReserve;
|
|
122
183
|
const scaleX = (width - DIAGRAM_PADDING * 2) / diagramW;
|
|
123
184
|
const scaleY = (availH - DIAGRAM_PADDING * 2) / diagramH;
|
|
124
185
|
const scale = Math.min(MAX_SCALE, scaleX, scaleY);
|
|
125
186
|
|
|
126
187
|
const scaledW = diagramW * scale;
|
|
127
188
|
const offsetX = (width - scaledW) / 2;
|
|
128
|
-
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
189
|
+
const offsetY = titleHeight + legendReserve + DIAGRAM_PADDING;
|
|
129
190
|
|
|
130
191
|
const svg = d3Selection
|
|
131
192
|
.select(container)
|
|
@@ -244,6 +305,114 @@ export function renderClassDiagram(
|
|
|
244
305
|
}
|
|
245
306
|
}
|
|
246
307
|
|
|
308
|
+
// ── Legend ──
|
|
309
|
+
// legendActive: true = expanded (default), false = collapsed pill only
|
|
310
|
+
const isLegendExpanded = legendActive !== false;
|
|
311
|
+
if (hasLegend) {
|
|
312
|
+
const groupBg = isDark
|
|
313
|
+
? mix(palette.surface, palette.bg, 50)
|
|
314
|
+
: mix(palette.surface, palette.bg, 30);
|
|
315
|
+
|
|
316
|
+
const pillWidth = LEGEND_GROUP_NAME.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
|
|
317
|
+
const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
|
|
318
|
+
const entriesW = legendEntriesWidth(legendEntries);
|
|
319
|
+
|
|
320
|
+
const totalW = isLegendExpanded
|
|
321
|
+
? LEGEND_CAPSULE_PAD * 2 + pillWidth + LEGEND_ENTRY_TRAIL + entriesW
|
|
322
|
+
: pillWidth;
|
|
323
|
+
|
|
324
|
+
const legendX = (width - totalW) / 2;
|
|
325
|
+
const legendY = titleHeight;
|
|
326
|
+
|
|
327
|
+
const legendG = svg
|
|
328
|
+
.append('g')
|
|
329
|
+
.attr('class', 'cd-legend')
|
|
330
|
+
.attr('data-legend-group', 'type')
|
|
331
|
+
.attr('transform', `translate(${legendX}, ${legendY})`)
|
|
332
|
+
.style('cursor', 'pointer');
|
|
333
|
+
|
|
334
|
+
if (isLegendExpanded) {
|
|
335
|
+
// Outer capsule
|
|
336
|
+
legendG.append('rect')
|
|
337
|
+
.attr('width', totalW)
|
|
338
|
+
.attr('height', LEGEND_HEIGHT)
|
|
339
|
+
.attr('rx', LEGEND_HEIGHT / 2)
|
|
340
|
+
.attr('fill', groupBg);
|
|
341
|
+
|
|
342
|
+
// Inner pill
|
|
343
|
+
legendG.append('rect')
|
|
344
|
+
.attr('x', LEGEND_CAPSULE_PAD)
|
|
345
|
+
.attr('y', LEGEND_CAPSULE_PAD)
|
|
346
|
+
.attr('width', pillWidth)
|
|
347
|
+
.attr('height', pillH)
|
|
348
|
+
.attr('rx', pillH / 2)
|
|
349
|
+
.attr('fill', palette.bg);
|
|
350
|
+
|
|
351
|
+
legendG.append('rect')
|
|
352
|
+
.attr('x', LEGEND_CAPSULE_PAD)
|
|
353
|
+
.attr('y', LEGEND_CAPSULE_PAD)
|
|
354
|
+
.attr('width', pillWidth)
|
|
355
|
+
.attr('height', pillH)
|
|
356
|
+
.attr('rx', pillH / 2)
|
|
357
|
+
.attr('fill', 'none')
|
|
358
|
+
.attr('stroke', mix(palette.textMuted, palette.bg, 50))
|
|
359
|
+
.attr('stroke-width', 0.75);
|
|
360
|
+
|
|
361
|
+
legendG.append('text')
|
|
362
|
+
.attr('x', LEGEND_CAPSULE_PAD + pillWidth / 2)
|
|
363
|
+
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2)
|
|
364
|
+
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
365
|
+
.attr('font-weight', '500')
|
|
366
|
+
.attr('fill', palette.text)
|
|
367
|
+
.attr('text-anchor', 'middle')
|
|
368
|
+
.attr('font-family', FONT_FAMILY)
|
|
369
|
+
.text(LEGEND_GROUP_NAME);
|
|
370
|
+
|
|
371
|
+
// Entries
|
|
372
|
+
let entryX = LEGEND_CAPSULE_PAD + pillWidth + LEGEND_ENTRY_TRAIL;
|
|
373
|
+
for (const entry of legendEntries) {
|
|
374
|
+
const color = palette.colors[entry.colorKey];
|
|
375
|
+
const typeKey = CLASS_TYPE_ORDER.find((k) => CLASS_TYPE_MAP[k] === entry)!;
|
|
376
|
+
|
|
377
|
+
const entryG = legendG.append('g')
|
|
378
|
+
.attr('data-legend-entry', typeKey);
|
|
379
|
+
|
|
380
|
+
entryG.append('circle')
|
|
381
|
+
.attr('cx', entryX + LEGEND_DOT_R)
|
|
382
|
+
.attr('cy', LEGEND_HEIGHT / 2)
|
|
383
|
+
.attr('r', LEGEND_DOT_R)
|
|
384
|
+
.attr('fill', color);
|
|
385
|
+
|
|
386
|
+
entryG.append('text')
|
|
387
|
+
.attr('x', entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP)
|
|
388
|
+
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
|
|
389
|
+
.attr('font-size', LEGEND_ENTRY_FONT_SIZE)
|
|
390
|
+
.attr('fill', palette.textMuted)
|
|
391
|
+
.attr('font-family', FONT_FAMILY)
|
|
392
|
+
.text(entry.label);
|
|
393
|
+
|
|
394
|
+
entryX += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.label.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
// Collapsed: single muted pill
|
|
398
|
+
legendG.append('rect')
|
|
399
|
+
.attr('width', pillWidth)
|
|
400
|
+
.attr('height', LEGEND_HEIGHT)
|
|
401
|
+
.attr('rx', LEGEND_HEIGHT / 2)
|
|
402
|
+
.attr('fill', groupBg);
|
|
403
|
+
|
|
404
|
+
legendG.append('text')
|
|
405
|
+
.attr('x', pillWidth / 2)
|
|
406
|
+
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2)
|
|
407
|
+
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
408
|
+
.attr('font-weight', '500')
|
|
409
|
+
.attr('fill', palette.textMuted)
|
|
410
|
+
.attr('text-anchor', 'middle')
|
|
411
|
+
.attr('font-family', FONT_FAMILY)
|
|
412
|
+
.text(LEGEND_GROUP_NAME);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
247
416
|
// ── Content group ──
|
|
248
417
|
const contentG = svg
|
|
249
418
|
.append('g')
|
|
@@ -320,7 +489,8 @@ export function renderClassDiagram(
|
|
|
320
489
|
.attr('transform', `translate(${node.x}, ${node.y})`)
|
|
321
490
|
.attr('class', 'cd-class')
|
|
322
491
|
.attr('data-line-number', String(node.lineNumber))
|
|
323
|
-
.attr('data-node-id', node.id)
|
|
492
|
+
.attr('data-node-id', node.id)
|
|
493
|
+
.attr('data-cd-type', classTypeKey(node.modifier));
|
|
324
494
|
|
|
325
495
|
if (onClickItem) {
|
|
326
496
|
nodeG.style('cursor', 'pointer').on('click', () => {
|
|
@@ -331,8 +501,11 @@ export function renderClassDiagram(
|
|
|
331
501
|
const w = node.width;
|
|
332
502
|
const h = node.height;
|
|
333
503
|
const colorOff = parsed.options?.color === 'off';
|
|
334
|
-
|
|
335
|
-
const
|
|
504
|
+
// When legend is collapsed, use neutral color for nodes without explicit color
|
|
505
|
+
const neutralize = hasLegend && !isLegendExpanded && !node.color;
|
|
506
|
+
const effectiveColor = neutralize ? palette.primary : node.color;
|
|
507
|
+
const fill = nodeFill(palette, isDark, node.modifier, effectiveColor, colorOff);
|
|
508
|
+
const stroke = nodeStroke(palette, node.modifier, effectiveColor, colorOff);
|
|
336
509
|
|
|
337
510
|
// Outer rectangle
|
|
338
511
|
nodeG.append('rect')
|
|
@@ -504,8 +677,11 @@ export function renderClassDiagramForExport(
|
|
|
504
677
|
const layout = layoutClassDiagram(parsed);
|
|
505
678
|
const isDark = theme === 'dark';
|
|
506
679
|
|
|
680
|
+
const legendEntries = collectClassTypes(parsed);
|
|
681
|
+
const EXPORT_LEGEND_GAP = 8;
|
|
682
|
+
const legendReserve = legendEntries.length > 1 ? LEGEND_HEIGHT + EXPORT_LEGEND_GAP : 0;
|
|
507
683
|
const exportWidth = layout.width + DIAGRAM_PADDING * 2;
|
|
508
|
-
const exportHeight = layout.height + DIAGRAM_PADDING * 2 + (parsed.title ? 40 : 0);
|
|
684
|
+
const exportHeight = layout.height + DIAGRAM_PADDING * 2 + (parsed.title ? 40 : 0) + legendReserve;
|
|
509
685
|
|
|
510
686
|
return runInExportContainer(exportWidth, exportHeight, (container) => {
|
|
511
687
|
renderClassDiagram(
|
package/src/cli.ts
CHANGED
|
@@ -8,21 +8,13 @@ import { render } from './render';
|
|
|
8
8
|
import { parseDgmo, getAllChartTypes } from './dgmo-router';
|
|
9
9
|
import { parseDgmoChartType } from './dgmo-router';
|
|
10
10
|
import { formatDgmoError } from './diagnostics';
|
|
11
|
-
import { getPalette } from './palettes
|
|
11
|
+
import { getPalette, getAvailablePalettes } from './palettes';
|
|
12
12
|
import { DEFAULT_FONT_NAME } from './fonts';
|
|
13
13
|
import { encodeDiagramUrl } from './sharing';
|
|
14
14
|
import { resolveOrgImports } from './org/resolver';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'solarized',
|
|
19
|
-
'catppuccin',
|
|
20
|
-
'rose-pine',
|
|
21
|
-
'gruvbox',
|
|
22
|
-
'tokyo-night',
|
|
23
|
-
'one-dark',
|
|
24
|
-
'bold',
|
|
25
|
-
];
|
|
16
|
+
// Derived from the palette registry so new palettes are auto-included.
|
|
17
|
+
const PALETTES = getAvailablePalettes().map((p) => p.id);
|
|
26
18
|
|
|
27
19
|
const THEMES = ['light', 'dark', 'transparent'] as const;
|
|
28
20
|
|
package/src/colors.ts
CHANGED
|
@@ -41,9 +41,9 @@ export const colorNames: Record<string, string> = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Resolves a color name
|
|
44
|
+
* Resolves a color name to a valid CSS color.
|
|
45
45
|
* When a palette is provided, named colors resolve against its color map first.
|
|
46
|
-
* Hex codes
|
|
46
|
+
* Hex codes are NOT supported — use named colors only.
|
|
47
47
|
* Unknown names are returned as-is.
|
|
48
48
|
*/
|
|
49
49
|
export function resolveColor(
|
|
@@ -51,7 +51,6 @@ export function resolveColor(
|
|
|
51
51
|
palette?: { colors: Record<string, string> }
|
|
52
52
|
): string {
|
|
53
53
|
const lower = color.toLowerCase();
|
|
54
|
-
if (lower.startsWith('#')) return lower;
|
|
55
54
|
|
|
56
55
|
if (palette) {
|
|
57
56
|
const named = palette.colors[lower];
|
|
@@ -59,6 +58,7 @@ export function resolveColor(
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
if (colorNames[lower]) return colorNames[lower];
|
|
61
|
+
// Unknown color name — return as-is so callers can detect + warn
|
|
62
62
|
return color;
|
|
63
63
|
}
|
|
64
64
|
|