@diagrammo/dgmo 0.8.17 → 0.8.19
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 +103 -103
- package/dist/editor.cjs +1 -1
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +1 -1
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +1 -1
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +1306 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -15
- package/dist/index.d.ts +120 -15
- package/dist/index.js +1325 -151
- package/dist/index.js.map +1 -1
- package/docs/guide/how-dgmo-thinks.md +277 -0
- package/docs/guide/registry.json +1 -0
- package/gallery/fixtures/gantt-sprints.dgmo +20 -0
- package/package.json +1 -1
- package/src/colors.ts +1 -1
- package/src/editor/dgmo.grammar +1 -1
- package/src/editor/dgmo.grammar.js +1 -1
- package/src/gantt/calculator.ts +120 -7
- package/src/gantt/parser.ts +98 -3
- package/src/gantt/renderer.ts +410 -95
- package/src/gantt/types.ts +23 -2
- package/src/index.ts +10 -2
- package/src/sequence/collapse.ts +169 -0
- package/src/sequence/parser.ts +14 -2
- package/src/sequence/renderer.ts +186 -49
- package/src/sharing.ts +86 -49
- package/src/utils/duration.ts +16 -2
- package/src/utils/legend-constants.ts +11 -0
- package/src/utils/legend-d3.ts +171 -0
- package/src/utils/legend-layout.ts +148 -17
- package/src/utils/legend-types.ts +45 -0
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
LEGEND_ENTRY_TRAIL,
|
|
15
15
|
LEGEND_GROUP_GAP,
|
|
16
16
|
LEGEND_MAX_ENTRY_ROWS,
|
|
17
|
+
LEGEND_GEAR_PILL_W,
|
|
18
|
+
LEGEND_TOGGLE_DOT_R,
|
|
17
19
|
measureLegendText,
|
|
18
20
|
} from './legend-constants';
|
|
19
21
|
|
|
@@ -27,6 +29,8 @@ import type {
|
|
|
27
29
|
LegendControlLayout,
|
|
28
30
|
LegendEntryLayout,
|
|
29
31
|
LegendControl,
|
|
32
|
+
ControlsGroupLayout,
|
|
33
|
+
ControlsGroupToggleLayout,
|
|
30
34
|
} from './legend-types';
|
|
31
35
|
|
|
32
36
|
// ── Constants ───────────────────────────────────────────────
|
|
@@ -107,14 +111,16 @@ function capsuleWidth(
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
// Multi-row: compute how many entries fit per row
|
|
110
|
-
|
|
114
|
+
// Right boundary leaves one LEGEND_CAPSULE_PAD for right padding;
|
|
115
|
+
// left padding is already baked into the starting rowX.
|
|
116
|
+
const rowWidth = maxCapsuleW - LEGEND_CAPSULE_PAD;
|
|
111
117
|
let row = 1;
|
|
112
|
-
let rowX = pw + 4;
|
|
118
|
+
let rowX = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
|
|
113
119
|
let visible = 0;
|
|
114
120
|
|
|
115
121
|
for (let i = 0; i < entries.length; i++) {
|
|
116
122
|
const ew2 = entryWidth(entries[i].value);
|
|
117
|
-
if (rowX + ew2 > rowWidth &&
|
|
123
|
+
if (rowX + ew2 > rowWidth && i > 0) {
|
|
118
124
|
row++;
|
|
119
125
|
rowX = 0;
|
|
120
126
|
if (row > LEGEND_MAX_ENTRY_ROWS) {
|
|
@@ -138,6 +144,89 @@ function capsuleWidth(
|
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
|
|
147
|
+
// ── Controls group layout helpers ───────────────────────────
|
|
148
|
+
|
|
149
|
+
export function controlsGroupCapsuleWidth(
|
|
150
|
+
toggles: Array<{ label: string }>
|
|
151
|
+
): number {
|
|
152
|
+
let w = LEGEND_CAPSULE_PAD * 2 + LEGEND_GEAR_PILL_W + 4;
|
|
153
|
+
for (const t of toggles) {
|
|
154
|
+
w +=
|
|
155
|
+
LEGEND_TOGGLE_DOT_R * 2 +
|
|
156
|
+
LEGEND_ENTRY_DOT_GAP +
|
|
157
|
+
measureLegendText(t.label, LEGEND_ENTRY_FONT_SIZE) +
|
|
158
|
+
LEGEND_ENTRY_TRAIL;
|
|
159
|
+
}
|
|
160
|
+
return w;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildControlsGroupLayout(
|
|
164
|
+
config: LegendConfig,
|
|
165
|
+
state: LegendState
|
|
166
|
+
): ControlsGroupLayout | undefined {
|
|
167
|
+
const cg = config.controlsGroup;
|
|
168
|
+
if (!cg || cg.toggles.length === 0) return undefined;
|
|
169
|
+
|
|
170
|
+
const expanded = !!state.controlsExpanded;
|
|
171
|
+
const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
|
|
172
|
+
|
|
173
|
+
if (!expanded) {
|
|
174
|
+
// Collapsed: just a gear pill
|
|
175
|
+
return {
|
|
176
|
+
x: 0,
|
|
177
|
+
y: 0,
|
|
178
|
+
width: LEGEND_GEAR_PILL_W,
|
|
179
|
+
height: LEGEND_HEIGHT,
|
|
180
|
+
expanded: false,
|
|
181
|
+
pill: { x: 0, y: 0, width: LEGEND_GEAR_PILL_W, height: LEGEND_HEIGHT },
|
|
182
|
+
toggles: [],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Expanded capsule
|
|
187
|
+
const capsuleW = controlsGroupCapsuleWidth(cg.toggles);
|
|
188
|
+
const toggleLayouts: ControlsGroupToggleLayout[] = [];
|
|
189
|
+
let tx = LEGEND_CAPSULE_PAD + LEGEND_GEAR_PILL_W + 4;
|
|
190
|
+
|
|
191
|
+
for (const toggle of cg.toggles) {
|
|
192
|
+
const dotCx = tx + LEGEND_TOGGLE_DOT_R;
|
|
193
|
+
const dotCy = LEGEND_HEIGHT / 2;
|
|
194
|
+
const textX = tx + LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
|
|
195
|
+
const textY = LEGEND_HEIGHT / 2;
|
|
196
|
+
|
|
197
|
+
toggleLayouts.push({
|
|
198
|
+
id: toggle.id,
|
|
199
|
+
label: toggle.label,
|
|
200
|
+
active: toggle.active,
|
|
201
|
+
dotCx,
|
|
202
|
+
dotCy,
|
|
203
|
+
textX,
|
|
204
|
+
textY,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
tx +=
|
|
208
|
+
LEGEND_TOGGLE_DOT_R * 2 +
|
|
209
|
+
LEGEND_ENTRY_DOT_GAP +
|
|
210
|
+
measureLegendText(toggle.label, LEGEND_ENTRY_FONT_SIZE) +
|
|
211
|
+
LEGEND_ENTRY_TRAIL;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
x: 0,
|
|
216
|
+
y: 0,
|
|
217
|
+
width: capsuleW,
|
|
218
|
+
height: LEGEND_HEIGHT,
|
|
219
|
+
expanded: true,
|
|
220
|
+
pill: {
|
|
221
|
+
x: LEGEND_CAPSULE_PAD,
|
|
222
|
+
y: LEGEND_CAPSULE_PAD,
|
|
223
|
+
width: LEGEND_GEAR_PILL_W - LEGEND_CAPSULE_PAD * 2,
|
|
224
|
+
height: pillH,
|
|
225
|
+
},
|
|
226
|
+
toggles: toggleLayouts,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
141
230
|
// ── Main layout computation ─────────────────────────────────
|
|
142
231
|
|
|
143
232
|
export function computeLegendLayout(
|
|
@@ -151,7 +240,7 @@ export function computeLegendLayout(
|
|
|
151
240
|
// Filter groups for export: only active group shown
|
|
152
241
|
const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
|
|
153
242
|
|
|
154
|
-
// In export mode with no active group, no legend
|
|
243
|
+
// In export mode with no active group and no groups, no legend
|
|
155
244
|
if (isExport && !activeGroupName) {
|
|
156
245
|
return {
|
|
157
246
|
height: 0,
|
|
@@ -163,12 +252,18 @@ export function computeLegendLayout(
|
|
|
163
252
|
};
|
|
164
253
|
}
|
|
165
254
|
|
|
255
|
+
// Controls group (strip in export mode)
|
|
256
|
+
const controlsGroupLayout = isExport
|
|
257
|
+
? undefined
|
|
258
|
+
: buildControlsGroupLayout(config, state);
|
|
259
|
+
|
|
166
260
|
const visibleGroups = config.showEmptyGroups
|
|
167
261
|
? groups
|
|
168
262
|
: groups.filter((g) => g.entries.length > 0);
|
|
169
263
|
if (
|
|
170
264
|
visibleGroups.length === 0 &&
|
|
171
|
-
(!configControls || configControls.length === 0)
|
|
265
|
+
(!configControls || configControls.length === 0) &&
|
|
266
|
+
!controlsGroupLayout
|
|
172
267
|
) {
|
|
173
268
|
return {
|
|
174
269
|
height: 0,
|
|
@@ -236,10 +331,13 @@ export function computeLegendLayout(
|
|
|
236
331
|
if (totalControlsW > 0) totalControlsW -= CONTROL_GAP;
|
|
237
332
|
}
|
|
238
333
|
|
|
239
|
-
// Available width for tag groups (controls anchor right)
|
|
334
|
+
// Available width for tag groups (controls anchor right, gear pill at end of pills)
|
|
240
335
|
const controlsSpace =
|
|
241
336
|
totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0;
|
|
242
|
-
const
|
|
337
|
+
const gearSpace = controlsGroupLayout
|
|
338
|
+
? controlsGroupLayout.width + LEGEND_GROUP_GAP
|
|
339
|
+
: 0;
|
|
340
|
+
const groupAvailW = containerWidth - controlsSpace - gearSpace;
|
|
243
341
|
|
|
244
342
|
// Build pill/capsule layouts
|
|
245
343
|
const pills: LegendPillLayout[] = [];
|
|
@@ -254,7 +352,7 @@ export function computeLegendLayout(
|
|
|
254
352
|
if (isActive) {
|
|
255
353
|
activeCapsule = buildCapsuleLayout(
|
|
256
354
|
g,
|
|
257
|
-
|
|
355
|
+
groupAvailW,
|
|
258
356
|
config.capsulePillAddonWidth ?? 0
|
|
259
357
|
);
|
|
260
358
|
} else {
|
|
@@ -279,7 +377,8 @@ export function computeLegendLayout(
|
|
|
279
377
|
groupAvailW,
|
|
280
378
|
containerWidth,
|
|
281
379
|
totalControlsW,
|
|
282
|
-
alignLeft
|
|
380
|
+
alignLeft,
|
|
381
|
+
controlsGroupLayout
|
|
283
382
|
);
|
|
284
383
|
|
|
285
384
|
const height = rows.length * LEGEND_HEIGHT;
|
|
@@ -292,6 +391,7 @@ export function computeLegendLayout(
|
|
|
292
391
|
activeCapsule,
|
|
293
392
|
controls: controlLayouts,
|
|
294
393
|
pills,
|
|
394
|
+
controlsGroup: controlsGroupLayout,
|
|
295
395
|
};
|
|
296
396
|
}
|
|
297
397
|
|
|
@@ -323,7 +423,9 @@ function buildCapsuleLayout(
|
|
|
323
423
|
let ex = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
|
|
324
424
|
let ey = 0;
|
|
325
425
|
let rowX = ex;
|
|
326
|
-
|
|
426
|
+
// Right boundary: one LEGEND_CAPSULE_PAD for right padding.
|
|
427
|
+
// Left padding is already in ex/rowX starting position.
|
|
428
|
+
const maxRowW = containerWidth - LEGEND_CAPSULE_PAD;
|
|
327
429
|
let currentRow = 0;
|
|
328
430
|
|
|
329
431
|
for (let i = 0; i < info.visibleEntries; i++) {
|
|
@@ -382,7 +484,8 @@ function layoutRows(
|
|
|
382
484
|
groupAvailW: number,
|
|
383
485
|
containerWidth: number,
|
|
384
486
|
totalControlsW: number,
|
|
385
|
-
alignLeft = false
|
|
487
|
+
alignLeft = false,
|
|
488
|
+
controlsGroup?: ControlsGroupLayout
|
|
386
489
|
): Array<{
|
|
387
490
|
y: number;
|
|
388
491
|
items: Array<LegendPillLayout | LegendCapsuleLayout | LegendControlLayout>;
|
|
@@ -397,6 +500,9 @@ function layoutRows(
|
|
|
397
500
|
if (activeCapsule) groupItems.push(activeCapsule);
|
|
398
501
|
groupItems.push(...pills);
|
|
399
502
|
|
|
503
|
+
// Controls group width for centering offset
|
|
504
|
+
const gearW = controlsGroup ? controlsGroup.width + LEGEND_GROUP_GAP : 0;
|
|
505
|
+
|
|
400
506
|
// Compute total group items width
|
|
401
507
|
let currentRowItems: Array<
|
|
402
508
|
LegendPillLayout | LegendCapsuleLayout | LegendControlLayout
|
|
@@ -407,9 +513,16 @@ function layoutRows(
|
|
|
407
513
|
for (const item of groupItems) {
|
|
408
514
|
const itemW = item.width + LEGEND_GROUP_GAP;
|
|
409
515
|
if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
|
|
410
|
-
// Commit current row
|
|
411
|
-
if (!alignLeft)
|
|
412
|
-
|
|
516
|
+
// Commit current row (row 0 needs gear space deducted for centering)
|
|
517
|
+
if (!alignLeft) {
|
|
518
|
+
const rowGearW = rows.length === 0 ? gearW : 0;
|
|
519
|
+
centerRowItems(
|
|
520
|
+
currentRowItems,
|
|
521
|
+
containerWidth,
|
|
522
|
+
totalControlsW,
|
|
523
|
+
rowGearW
|
|
524
|
+
);
|
|
525
|
+
}
|
|
413
526
|
rows.push({ y: rowY, items: currentRowItems });
|
|
414
527
|
rowY += LEGEND_HEIGHT;
|
|
415
528
|
currentRowItems = [];
|
|
@@ -443,10 +556,26 @@ function layoutRows(
|
|
|
443
556
|
|
|
444
557
|
// Commit last row
|
|
445
558
|
if (currentRowItems.length > 0) {
|
|
446
|
-
centerRowItems(currentRowItems, containerWidth, totalControlsW);
|
|
559
|
+
centerRowItems(currentRowItems, containerWidth, totalControlsW, gearW);
|
|
447
560
|
rows.push({ y: rowY, items: currentRowItems });
|
|
448
561
|
}
|
|
449
562
|
|
|
563
|
+
// Position controls group AFTER centering so it follows the shifted items
|
|
564
|
+
if (controlsGroup) {
|
|
565
|
+
const row0Items = rows[0]?.items ?? [];
|
|
566
|
+
const groupItemsInRow0 = row0Items.filter(
|
|
567
|
+
(it) => 'groupName' in it
|
|
568
|
+
) as Array<LegendPillLayout | LegendCapsuleLayout>;
|
|
569
|
+
if (groupItemsInRow0.length > 0) {
|
|
570
|
+
const last = groupItemsInRow0[groupItemsInRow0.length - 1];
|
|
571
|
+
controlsGroup.x = last.x + last.width + LEGEND_GROUP_GAP;
|
|
572
|
+
} else {
|
|
573
|
+
// No group items — controls group at start
|
|
574
|
+
controlsGroup.x = 0;
|
|
575
|
+
}
|
|
576
|
+
controlsGroup.y = 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
450
579
|
// Ensure at least one row height
|
|
451
580
|
if (rows.length === 0) {
|
|
452
581
|
rows.push({ y: 0, items: [] });
|
|
@@ -458,7 +587,8 @@ function layoutRows(
|
|
|
458
587
|
function centerRowItems(
|
|
459
588
|
items: Array<LegendPillLayout | LegendCapsuleLayout | LegendControlLayout>,
|
|
460
589
|
containerWidth: number,
|
|
461
|
-
totalControlsW: number
|
|
590
|
+
totalControlsW: number,
|
|
591
|
+
controlsGroupW = 0
|
|
462
592
|
): void {
|
|
463
593
|
// Only center group items (pills and capsules), not controls
|
|
464
594
|
const groupItems = items.filter((it) => 'groupName' in it) as Array<
|
|
@@ -473,7 +603,8 @@ function centerRowItems(
|
|
|
473
603
|
|
|
474
604
|
const availW =
|
|
475
605
|
containerWidth -
|
|
476
|
-
(totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0)
|
|
606
|
+
(totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0) -
|
|
607
|
+
controlsGroupW;
|
|
477
608
|
const offset = Math.max(0, (availW - totalGroupW) / 2);
|
|
478
609
|
|
|
479
610
|
let x = offset;
|
|
@@ -9,6 +9,7 @@ import type { Selection } from 'd3-selection';
|
|
|
9
9
|
export interface LegendState {
|
|
10
10
|
activeGroup: string | null;
|
|
11
11
|
hiddenAttributes?: Set<string>;
|
|
12
|
+
controlsExpanded?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface LegendCallbacks {
|
|
@@ -23,6 +24,10 @@ export interface LegendCallbacks {
|
|
|
23
24
|
groupEl: D3Sel,
|
|
24
25
|
isActive: boolean
|
|
25
26
|
) => void;
|
|
27
|
+
/** Called when the controls group gear pill is clicked (expand/collapse) */
|
|
28
|
+
onControlsExpand?: () => void;
|
|
29
|
+
/** Called when a controls group toggle entry is clicked */
|
|
30
|
+
onControlsToggle?: (toggleId: string, active: boolean) => void;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
// ── Position & Layout ───────────────────────────────────────
|
|
@@ -53,12 +58,28 @@ export interface LegendControlEntry {
|
|
|
53
58
|
onClick?: () => void;
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
// ── Controls Group ─────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export interface ControlsGroupToggle {
|
|
64
|
+
id: string;
|
|
65
|
+
/** Only 'toggle' is implemented in v1. 'select' and 'action' future-proof for Infra playback etc. */
|
|
66
|
+
type: 'toggle' | 'select' | 'action';
|
|
67
|
+
label: string;
|
|
68
|
+
active: boolean;
|
|
69
|
+
onToggle: (active: boolean) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ControlsGroupConfig {
|
|
73
|
+
toggles: ControlsGroupToggle[];
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
// ── Config ──────────────────────────────────────────────────
|
|
57
77
|
|
|
58
78
|
export interface LegendConfig {
|
|
59
79
|
groups: import('./legend-svg').LegendGroupData[];
|
|
60
80
|
position: LegendPosition;
|
|
61
81
|
controls?: LegendControl[];
|
|
82
|
+
controlsGroup?: ControlsGroupConfig;
|
|
62
83
|
mode: LegendMode;
|
|
63
84
|
/** Title width in pixels — used for inline-with-title computation */
|
|
64
85
|
titleWidth?: number;
|
|
@@ -131,6 +152,28 @@ export interface LegendControlLayout {
|
|
|
131
152
|
}>;
|
|
132
153
|
}
|
|
133
154
|
|
|
155
|
+
export interface ControlsGroupToggleLayout {
|
|
156
|
+
id: string;
|
|
157
|
+
label: string;
|
|
158
|
+
active: boolean;
|
|
159
|
+
dotCx: number;
|
|
160
|
+
dotCy: number;
|
|
161
|
+
textX: number;
|
|
162
|
+
textY: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface ControlsGroupLayout {
|
|
166
|
+
x: number;
|
|
167
|
+
y: number;
|
|
168
|
+
width: number;
|
|
169
|
+
height: number;
|
|
170
|
+
expanded: boolean;
|
|
171
|
+
/** The gear pill layout (collapsed or inside capsule) */
|
|
172
|
+
pill: { x: number; y: number; width: number; height: number };
|
|
173
|
+
/** Toggle entries (only present when expanded) */
|
|
174
|
+
toggles: ControlsGroupToggleLayout[];
|
|
175
|
+
}
|
|
176
|
+
|
|
134
177
|
export interface LegendRowLayout {
|
|
135
178
|
y: number;
|
|
136
179
|
items: Array<LegendPillLayout | LegendCapsuleLayout | LegendControlLayout>;
|
|
@@ -149,6 +192,8 @@ export interface LegendLayout {
|
|
|
149
192
|
controls: LegendControlLayout[];
|
|
150
193
|
/** All pill layouts (collapsed groups) */
|
|
151
194
|
pills: LegendPillLayout[];
|
|
195
|
+
/** Controls group layout (gear pill / capsule) */
|
|
196
|
+
controlsGroup?: ControlsGroupLayout;
|
|
152
197
|
}
|
|
153
198
|
|
|
154
199
|
// ── Handle ──────────────────────────────────────────────────
|