@diagrammo/dgmo 0.86.0 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/advanced.d.ts +0 -18
  2. package/dist/advanced.js +36 -35
  3. package/dist/block.js +5 -17
  4. package/dist/{chunk-FAV4RDVT.js → chunk-2A4DQAXG.js} +1 -1
  5. package/dist/{chunk-JIPUBFAM.js → chunk-5KGM44TG.js} +2 -2
  6. package/dist/{chunk-QJ2JOLFN.js → chunk-6I5UN4FD.js} +2 -2
  7. package/dist/{chunk-PKXKWHDW.js → chunk-AUS7V54P.js} +1 -1
  8. package/dist/{chunk-BF7JAFJI.js → chunk-B7CU2HW5.js} +2 -2
  9. package/dist/{chunk-X2VEJ5NT.js → chunk-BMUWF77V.js} +2 -2
  10. package/dist/{chunk-BP4BT5TH.js → chunk-CLQIXD2C.js} +1 -1
  11. package/dist/{chunk-QJFT5MAH.js → chunk-DSG6GBUQ.js} +2 -2
  12. package/dist/{chunk-EWSESGC2.js → chunk-EJS3K44P.js} +117 -78
  13. package/dist/{chunk-VEE777MD.js → chunk-FBKPSU3A.js} +3 -3
  14. package/dist/{chunk-5JO443MB.js → chunk-HAH5X345.js} +1 -1
  15. package/dist/{chunk-HBCAY3KR.js → chunk-HTNLLT34.js} +1 -1
  16. package/dist/{chunk-HUMQIRJK.js → chunk-HTPZDZKQ.js} +2 -2
  17. package/dist/{chunk-VPVWG2GH.js → chunk-J7Q4VPBJ.js} +1 -1
  18. package/dist/{chunk-3CQS3CIO.js → chunk-LL6D3MES.js} +1 -1
  19. package/dist/{chunk-QPE646M2.js → chunk-LPXC4I4R.js} +18 -1
  20. package/dist/{chunk-ERYHC4KJ.js → chunk-LSQGJN5H.js} +1 -1
  21. package/dist/{chunk-E6S2K6O2.js → chunk-N7XRBWXA.js} +2 -2
  22. package/dist/{chunk-WMIKC5XO.js → chunk-NPWVOFB3.js} +1 -1
  23. package/dist/{chunk-MYHPRQHR.js → chunk-O6MMQQLF.js} +3 -3
  24. package/dist/{chunk-GYORDNOM.js → chunk-P5V63L35.js} +1 -1
  25. package/dist/{chunk-4YA5XW43.js → chunk-PJQZWJ5I.js} +50 -28
  26. package/dist/{chunk-BHZE36F7.js → chunk-PUVTFUNH.js} +1 -1
  27. package/dist/{chunk-ETBXIKK6.js → chunk-Q2U5635V.js} +1 -1
  28. package/dist/{chunk-TW6WMZFW.js → chunk-QT3KDDXK.js} +156 -7
  29. package/dist/{chunk-ZGHV4QLK.js → chunk-RJNZHFNG.js} +1 -1
  30. package/dist/{chunk-FWNJDD4U.js → chunk-S7W75MIM.js} +3 -20
  31. package/dist/{chunk-C6FZZFYD.js → chunk-WZ23JJ7U.js} +1 -1
  32. package/dist/{chunk-LIYWJ6DV.js → chunk-XIFK423U.js} +108 -13
  33. package/dist/{chunk-4ELXLATB.js → chunk-XUDGZJTJ.js} +2 -2
  34. package/dist/{chunk-H6AFNCN3.js → chunk-XVSJYAK7.js} +1 -1
  35. package/dist/{chunk-LHBMQJC3.js → chunk-XWYR5T52.js} +2 -2
  36. package/dist/{chunk-QNKHJ5CK.js → chunk-YK4N6TBN.js} +4 -2
  37. package/dist/{chunk-XFMQSFS4.js → chunk-YMG7JDEY.js} +16 -6
  38. package/dist/chunk-Z6DXWULM.js +23 -0
  39. package/dist/{chunk-PBEH52A2.js → chunk-ZNNVI5SR.js} +1 -1
  40. package/dist/{flowchart-renderer-H72L7BEC.js → flowchart-renderer-THC6Y7EH.js} +7 -6
  41. package/dist/index.d.ts +68 -1
  42. package/dist/index.js +75 -7
  43. package/dist/{layout-QIPCNUSB.js → layout-BOVTLAOT.js} +7 -6
  44. package/dist/{layout-6JARW64V.js → layout-EHTYSMV4.js} +6 -5
  45. package/dist/{layout-YELNBVPC.js → layout-P6DTD4RT.js} +6 -5
  46. package/dist/{layout-T6TAB5OI.js → layout-RANVNAKI.js} +3 -3
  47. package/dist/{layout-FMKSENGL.js → layout-XUW444O5.js} +5 -4
  48. package/dist/{layout-search-AW5VBDCV.js → layout-search-VKSBUCDM.js} +57 -28
  49. package/dist/{renderer-AI2MD52D.js → renderer-222OTVQV.js} +3 -2
  50. package/dist/{renderer-UUWR7NGF.js → renderer-2KVQGGTT.js} +4 -3
  51. package/dist/{renderer-E5EKGIX2.js → renderer-2OOPQ4CH.js} +4 -3
  52. package/dist/{renderer-RLSR7N6I.js → renderer-3CTUWCO7.js} +5 -4
  53. package/dist/{renderer-CZJ4AMYV.js → renderer-3FF4QVGW.js} +3 -2
  54. package/dist/{renderer-R6VJDZFO.js → renderer-4BHOELCE.js} +4 -3
  55. package/dist/{renderer-425QUIYO.js → renderer-4BIMRO4X.js} +4 -3
  56. package/dist/{renderer-5JVVJBAS.js → renderer-A4LMOBZL.js} +8 -7
  57. package/dist/{renderer-3AOOLHJU.js → renderer-A6ACVK4X.js} +3 -2
  58. package/dist/{renderer-ML2CLU25.js → renderer-DCFQDBGJ.js} +4 -3
  59. package/dist/{renderer-ADX4DWRJ.js → renderer-ELYPDPNF.js} +3 -2
  60. package/dist/{renderer-RH4U3UVM.js → renderer-LODEVN3V.js} +3 -2
  61. package/dist/{renderer-E7WF75GY.js → renderer-NHPKNNBD.js} +5 -4
  62. package/dist/{renderer-JEEGPLMM.js → renderer-R34GJL7U.js} +8 -7
  63. package/dist/{renderer-2BWRW23Z.js → renderer-RENDISO5.js} +1 -1
  64. package/dist/{renderer-VKAJ52UO.js → renderer-SDUSBCJI.js} +3 -2
  65. package/dist/{renderer-RUGMKHX5.js → renderer-WH2HWE5Z.js} +3 -2
  66. package/dist/{renderer-V6LLASGN.js → renderer-X5A5VIRD.js} +5 -4
  67. package/dist/{renderer-EWJS2RAZ.js → renderer-YDAVCRTP.js} +3 -2
  68. package/dist/{state-renderer-DDVJ43HR.js → state-renderer-5IQ4OJUI.js} +7 -6
  69. package/gallery/fixtures/journey-map.dgmo +28 -0
  70. package/gallery/fixtures/mindmap.dgmo +31 -0
  71. package/gallery/fixtures/pert.dgmo +27 -0
  72. package/gallery/fixtures/wireframe.dgmo +30 -0
  73. package/package.json +2 -2
@@ -1,26 +1,10 @@
1
+ import {
2
+ safeHref
3
+ } from "./chunk-Z6DXWULM.js";
1
4
  import {
2
5
  MONO_FAMILY
3
6
  } from "./chunk-ICLB27PZ.js";
4
7
 
5
- // src/utils/safe-href.ts
6
- var ALLOWED_SCHEMES = ["http", "https", "mailto"];
7
- var SCHEME_RE = /^([a-z][a-z0-9+.-]*):/i;
8
- var LEADING_TRIM_RE = /^[\x00-\x20]+/;
9
- function safeHref(url) {
10
- if (typeof url !== "string") return null;
11
- const trimmed = url.replace(LEADING_TRIM_RE, "");
12
- if (trimmed.length === 0) return null;
13
- const match = SCHEME_RE.exec(trimmed);
14
- if (!match) {
15
- return url;
16
- }
17
- const scheme = match[1].toLowerCase();
18
- if (ALLOWED_SCHEMES.includes(scheme)) {
19
- return url;
20
- }
21
- return null;
22
- }
23
-
24
8
  // src/utils/inline-markdown.ts
25
9
  function parseInlineMarkdown(text) {
26
10
  const spans = [];
@@ -88,7 +72,6 @@ function renderInlineText(textEl, text, palette, fontSize) {
88
72
  }
89
73
 
90
74
  export {
91
- safeHref,
92
75
  parseInlineMarkdown,
93
76
  truncateBareUrl,
94
77
  stripInlineMarkdown,
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-XBJJP2HN.js";
12
12
  import {
13
13
  renderInlineText
14
- } from "./chunk-FWNJDD4U.js";
14
+ } from "./chunk-S7W75MIM.js";
15
15
  import {
16
16
  ARROWHEAD_HEIGHT,
17
17
  ARROWHEAD_WIDTH,
@@ -3,10 +3,10 @@ import {
3
3
  NODE_HEIGHT,
4
4
  NODE_WIDTH,
5
5
  placeEdgeLabels
6
- } from "./chunk-TW6WMZFW.js";
6
+ } from "./chunk-QT3KDDXK.js";
7
7
  import {
8
8
  buildPlacedNotes
9
- } from "./chunk-HBCAY3KR.js";
9
+ } from "./chunk-HTNLLT34.js";
10
10
  import {
11
11
  noteCanvasShift
12
12
  } from "./chunk-6EK55ART.js";
@@ -22,6 +22,9 @@ import {
22
22
  var MARGIN = 40;
23
23
  var MAX_PARALLEL_EDGES = 5;
24
24
  var PARALLEL_SPACING = 22;
25
+ var DETOUR_TOLERANCE = 2 * PARALLEL_SPACING;
26
+ var MIN_PORT_GAP = 16;
27
+ var PORT_CORNER_CLEAR = 6;
25
28
  var DESC_NODE_WIDTH = 140;
26
29
  var DESC_FONT_SIZE = 10;
27
30
  var DESC_LINE_HEIGHT = 1.4;
@@ -104,7 +107,7 @@ function computeNodeSize(node, reserveValueRow) {
104
107
  return { width: w, height: Math.max(NODE_HEIGHT, totalHeight) };
105
108
  }
106
109
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
107
- const { layoutBoxesAndLinesSearch } = await import("./layout-search-AW5VBDCV.js");
110
+ const { layoutBoxesAndLinesSearch } = await import("./layout-search-VKSBUCDM.js");
108
111
  const searchOpts = {
109
112
  ...layoutOptions?.hideDescriptions !== void 0 && {
110
113
  hideDescriptions: layoutOptions.hideDescriptions
@@ -117,13 +120,6 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
117
120
  },
118
121
  ...layoutOptions?.onProgress !== void 0 && {
119
122
  onProgress: layoutOptions.onProgress
120
- },
121
- // Spread into the label-reserving relayout below too, via `...searchOpts`:
122
- // that second search has its OWN clock, so a caller that disabled the
123
- // deadline must have it disabled for both passes or the fixture that needs
124
- // two passes is exactly the one still racing.
125
- ...layoutOptions?.budgetMs !== void 0 && {
126
- budgetMs: layoutOptions.budgetMs
127
123
  }
128
124
  };
129
125
  let topConfigs;
@@ -134,7 +130,7 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
134
130
  }
135
131
  });
136
132
  const labelOpts = { groups: parsed.groups };
137
- let chosen = applyParallelEdgeOffsets(searched);
133
+ let chosen = applyParallelEdgeOffsets(spreadCrowdedPorts(searched));
138
134
  let placed = placeEdgeLabels(chosen, labelOpts);
139
135
  if (placed.unresolved.length > 0) {
140
136
  const relaid = await layoutBoxesAndLinesSearch(parsed, collapseInfo, {
@@ -145,7 +141,7 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
145
141
  // first search surfaced no dagre candidates.
146
142
  ...topConfigs !== void 0 && topConfigs.length > 0 && { configs: topConfigs }
147
143
  });
148
- const relaidChosen = applyParallelEdgeOffsets(relaid);
144
+ const relaidChosen = applyParallelEdgeOffsets(spreadCrowdedPorts(relaid));
149
145
  const relaidPlaced = placeEdgeLabels(relaidChosen, labelOpts);
150
146
  if (relaidPlaced.unresolved.length < placed.unresolved.length) {
151
147
  placed = relaidPlaced;
@@ -242,6 +238,102 @@ function attachNotes(layout, parsed, collapsedNotes) {
242
238
  height: bbMaxY + shiftY + MARGIN
243
239
  };
244
240
  }
241
+ function spreadCrowdedPorts(layout) {
242
+ const boxes = /* @__PURE__ */ new Map();
243
+ for (const n of layout.nodes)
244
+ boxes.set(n.label, { x: n.x, y: n.y, w: n.width, h: n.height });
245
+ for (const g of layout.groups)
246
+ if (g.collapsed)
247
+ boxes.set("__group_" + g.label, {
248
+ x: g.x,
249
+ y: g.y,
250
+ w: g.width,
251
+ h: g.height
252
+ });
253
+ const faces = /* @__PURE__ */ new Map();
254
+ const onSide = (box, p) => {
255
+ const E = 1;
256
+ const l = box.x - box.w / 2;
257
+ const r = box.x + box.w / 2;
258
+ const t = box.y - box.h / 2;
259
+ const b = box.y + box.h / 2;
260
+ if (p.x < l - E || p.x > r + E || p.y < t - E || p.y > b + E) return null;
261
+ if (Math.abs(p.y - t) <= E) return "top";
262
+ if (Math.abs(p.y - b) <= E) return "bottom";
263
+ if (Math.abs(p.x - l) <= E) return "left";
264
+ if (Math.abs(p.x - r) <= E) return "right";
265
+ return null;
266
+ };
267
+ layout.edges.forEach((e, i) => {
268
+ if (e.source === e.target || e.points.length < 2) return;
269
+ for (const last of [false, true]) {
270
+ const id = last ? e.target : e.source;
271
+ const box = boxes.get(id);
272
+ const p = last ? e.points[e.points.length - 1] : e.points[0];
273
+ if (!box) continue;
274
+ const side = onSide(box, p);
275
+ if (!side) continue;
276
+ const horiz = side === "top" || side === "bottom";
277
+ const key = `${id}\0${side}`;
278
+ let f = faces.get(key);
279
+ if (!f) faces.set(key, f = { box, horiz, ends: [] });
280
+ f.ends.push({ edge: i, last, along: horiz ? p.x : p.y });
281
+ }
282
+ });
283
+ const moved = /* @__PURE__ */ new Map();
284
+ for (const { box, horiz, ends } of faces.values()) {
285
+ if (ends.length < 2) continue;
286
+ ends.sort((a, b) => a.along - b.along || a.edge - b.edge);
287
+ let crowded = false;
288
+ for (let k = 1; k < ends.length; k++)
289
+ if (ends[k].along - ends[k - 1].along < MIN_PORT_GAP) crowded = true;
290
+ if (!crowded) continue;
291
+ const half = (horiz ? box.w : box.h) / 2 - PORT_CORNER_CLEAR;
292
+ const ctr = horiz ? box.x : box.y;
293
+ if (half <= 0) continue;
294
+ const gap = Math.min(MIN_PORT_GAP, 2 * half / (ends.length - 1));
295
+ const mean = ends.reduce((acc, e) => acc + e.along, 0) / ends.length;
296
+ const span = gap * (ends.length - 1);
297
+ const first = Math.max(
298
+ ctr - half,
299
+ Math.min(ctr + half - span, mean - span / 2)
300
+ );
301
+ ends.forEach((e, k) => {
302
+ moved.set(`${e.edge}:${e.last}`, { along: first + k * gap, horiz });
303
+ });
304
+ }
305
+ if (moved.size === 0) return layout;
306
+ return {
307
+ ...layout,
308
+ edges: layout.edges.map((e, i) => {
309
+ const a = moved.get(`${i}:false`);
310
+ const b = moved.get(`${i}:true`);
311
+ if (a === void 0 && b === void 0) return e;
312
+ const pts = e.points.map((p) => ({ x: p.x, y: p.y }));
313
+ const shift = (idx, to) => {
314
+ if (to.horiz) pts[idx].x = to.along;
315
+ else pts[idx].y = to.along;
316
+ };
317
+ if (a !== void 0) shift(0, a);
318
+ if (b !== void 0) shift(pts.length - 1, b);
319
+ return { ...e, points: pts };
320
+ })
321
+ };
322
+ }
323
+ function routeDetour(points) {
324
+ const s = points[0];
325
+ const t = points[points.length - 1];
326
+ if (!s || !t) return 0;
327
+ const dx = t.x - s.x;
328
+ const dy = t.y - s.y;
329
+ const len = Math.hypot(dx, dy);
330
+ let worst = 0;
331
+ for (const p of points) {
332
+ const d = len < 1e-6 ? Math.hypot(p.x - s.x, p.y - s.y) : Math.abs((p.x - s.x) * dy - (p.y - s.y) * dx) / len;
333
+ if (d > worst) worst = d;
334
+ }
335
+ return worst;
336
+ }
245
337
  function applyParallelEdgeOffsets(layout) {
246
338
  const groups = /* @__PURE__ */ new Map();
247
339
  layout.edges.forEach((e, i) => {
@@ -262,8 +354,11 @@ function applyParallelEdgeOffsets(layout) {
262
354
  layout.edges.length
263
355
  ).fill(void 0);
264
356
  for (const [key, idxs] of groups) {
265
- const capped = idxs.slice(0, MAX_PARALLEL_EDGES);
357
+ const kept = idxs.slice(0, MAX_PARALLEL_EDGES);
266
358
  for (const drop of idxs.slice(MAX_PARALLEL_EDGES)) count[drop] = 0;
359
+ const capped = kept.filter(
360
+ (idx) => routeDetour(layout.edges[idx].points) <= DETOUR_TOLERANCE
361
+ );
267
362
  if (capped.length < 2) continue;
268
363
  const sep = key.indexOf("\0");
269
364
  const ca = centre.get(key.slice(0, sep));
@@ -3,14 +3,14 @@ import {
3
3
  } from "./chunk-26EVL454.js";
4
4
  import {
5
5
  layoutGraph
6
- } from "./chunk-PBEH52A2.js";
6
+ } from "./chunk-ZNNVI5SR.js";
7
7
  import {
8
8
  NOTE_BADGE_RADIUS,
9
9
  noteConnectorPoints,
10
10
  renderNoteBadge,
11
11
  renderNoteBox,
12
12
  renderNoteConnector
13
- } from "./chunk-5JO443MB.js";
13
+ } from "./chunk-HAH5X345.js";
14
14
  import {
15
15
  appendArrowheadMarkers
16
16
  } from "./chunk-XBJJP2HN.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-LNO3ASX5.js";
4
4
  import {
5
5
  parseInlineMarkdown
6
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-S7W75MIM.js";
7
7
  import {
8
8
  TITLE_FONT_WEIGHT
9
9
  } from "./chunk-VLC6WNRD.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  layoutClassDiagram
3
- } from "./chunk-BP4BT5TH.js";
3
+ } from "./chunk-CLQIXD2C.js";
4
4
  import {
5
5
  NOTE_BADGE_RADIUS,
6
6
  noteConnectorPoints,
7
7
  renderNoteBadge,
8
8
  renderNoteBox,
9
9
  renderNoteConnector
10
- } from "./chunk-5JO443MB.js";
10
+ } from "./chunk-HAH5X345.js";
11
11
  import {
12
12
  ScaleContext
13
13
  } from "./chunk-24YPR5EH.js";
@@ -1,7 +1,9 @@
1
1
  import {
2
- parseInlineMarkdown,
2
+ parseInlineMarkdown
3
+ } from "./chunk-S7W75MIM.js";
4
+ import {
3
5
  safeHref
4
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-Z6DXWULM.js";
5
7
  import {
6
8
  ScaleContext
7
9
  } from "./chunk-24YPR5EH.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  wrapDescriptionLines
3
- } from "./chunk-WMIKC5XO.js";
3
+ } from "./chunk-NPWVOFB3.js";
4
4
  import {
5
5
  renderInlineText
6
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-S7W75MIM.js";
7
7
  import {
8
8
  ScaleContext
9
9
  } from "./chunk-24YPR5EH.js";
@@ -842,7 +842,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
842
842
  }
843
843
  msgToLastStep.set(step.messageIndex, si);
844
844
  });
845
- const findAssociatedLastStep = (note) => {
845
+ const closestPrecedingMsgIndex = (note) => {
846
846
  let closestMsgIndex = -1;
847
847
  let closestLine = -1;
848
848
  for (let mi = 0; mi < messages.length; mi++) {
@@ -852,9 +852,15 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
852
852
  closestMsgIndex = mi;
853
853
  }
854
854
  }
855
- if (closestMsgIndex >= 0 && hiddenMsgIndices.has(closestMsgIndex)) {
856
- return -1;
857
- }
855
+ return closestMsgIndex;
856
+ };
857
+ const isNoteHiddenByCollapse = (note) => {
858
+ const closestMsgIndex = closestPrecedingMsgIndex(note);
859
+ return closestMsgIndex >= 0 && hiddenMsgIndices.has(closestMsgIndex);
860
+ };
861
+ const findAssociatedLastStep = (note) => {
862
+ if (isNoteHiddenByCollapse(note)) return -1;
863
+ const closestMsgIndex = closestPrecedingMsgIndex(note);
858
864
  if (closestMsgIndex < 0) return -1;
859
865
  return msgToLastStep.get(closestMsgIndex) ?? -1;
860
866
  };
@@ -942,6 +948,10 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
942
948
  for (let i = 0; i < els.length; i++) {
943
949
  const el = els[i];
944
950
  if (isSequenceNote(el)) {
951
+ if (isNoteHiddenByCollapse(el)) {
952
+ while (i + 1 < els.length && isSequenceNote(els[i + 1])) i++;
953
+ continue;
954
+ }
945
955
  const firstOffset = noteOffsetBelow(el);
946
956
  let totalExtent = firstOffset;
947
957
  let j = i;
@@ -0,0 +1,23 @@
1
+ // src/utils/safe-href.ts
2
+ var ALLOWED_SCHEMES = ["http", "https", "mailto"];
3
+ var SCHEME_RE = /^([a-z][a-z0-9+.-]*):/i;
4
+ var LEADING_TRIM_RE = /^[\x00-\x20]+/;
5
+ var URL_STRIP_RE = /[\x09\x0A\x0D]/g;
6
+ function safeHref(url) {
7
+ if (typeof url !== "string") return null;
8
+ const trimmed = url.replace(URL_STRIP_RE, "").replace(LEADING_TRIM_RE, "");
9
+ if (trimmed.length === 0) return null;
10
+ const match = SCHEME_RE.exec(trimmed);
11
+ if (!match) {
12
+ return url;
13
+ }
14
+ const scheme = match[1].toLowerCase();
15
+ if (ALLOWED_SCHEMES.includes(scheme)) {
16
+ return url;
17
+ }
18
+ return null;
19
+ }
20
+
21
+ export {
22
+ safeHref
23
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-6EK55ART.js";
5
5
  import {
6
6
  noteBoxSize
7
- } from "./chunk-ZGHV4QLK.js";
7
+ } from "./chunk-RJNZHFNG.js";
8
8
  import {
9
9
  resolveNotes
10
10
  } from "./chunk-O753JUEZ.js";
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  renderFlowchart,
3
3
  renderFlowchartForExport
4
- } from "./chunk-4ELXLATB.js";
4
+ } from "./chunk-XUDGZJTJ.js";
5
5
  import "./chunk-26EVL454.js";
6
- import "./chunk-PBEH52A2.js";
7
- import "./chunk-5JO443MB.js";
6
+ import "./chunk-ZNNVI5SR.js";
7
+ import "./chunk-HAH5X345.js";
8
8
  import "./chunk-XBJJP2HN.js";
9
9
  import "./chunk-6EK55ART.js";
10
- import "./chunk-ZGHV4QLK.js";
10
+ import "./chunk-RJNZHFNG.js";
11
11
  import "./chunk-5DH3KYFH.js";
12
- import "./chunk-WMIKC5XO.js";
13
- import "./chunk-FWNJDD4U.js";
12
+ import "./chunk-NPWVOFB3.js";
13
+ import "./chunk-S7W75MIM.js";
14
+ import "./chunk-Z6DXWULM.js";
14
15
  import "./chunk-EPDBWATV.js";
15
16
  import "./chunk-24YPR5EH.js";
16
17
  import "./chunk-XCKNFFKM.js";
package/dist/index.d.ts CHANGED
@@ -102,6 +102,73 @@ declare function attachDataChartInteractions(svg: SVGSVGElement, opts?: DataChar
102
102
  declare const D3_DATA_CHART_TYPES: Set<string>;
103
103
  declare function supportsD3DataChart(type: string): boolean;
104
104
 
105
+ /**
106
+ * The supported sanitizing boundary for renderer output that is about to land
107
+ * in a live document.
108
+ *
109
+ * Renderer output is trusted only as far as the renderer that produced it, and
110
+ * every host — the desktop app, the web editor, Obsidian, the script-tag
111
+ * drop-ins, this library's own `mountD3DataChart` — puts that output into the
112
+ * DOM. This module is what they call first. It lives here, beside `safe-href`,
113
+ * rather than inside `src/auto/shared.ts` where it started: that module is the
114
+ * browser-embed bundle's private toolbox, reachable only from the two IIFE
115
+ * script-tag builds, so a host importing `@diagrammo/dgmo` could not call the
116
+ * sanitizer at all however much it wanted to.
117
+ *
118
+ * 🔴 **Parse into a DETACHED holder, take the `<svg>`, sanitize, insert.** All
119
+ * four steps, in that order:
120
+ *
121
+ * ```ts
122
+ * const holder = document.createElement('div');
123
+ * holder.innerHTML = svg; // inert: nothing here is connected yet
124
+ * const svgEl = holder.querySelector('svg');
125
+ * if (svgEl) {
126
+ * sanitizeSvgInPlace(svgEl);
127
+ * container.replaceChildren(svgEl);
128
+ * }
129
+ * ```
130
+ *
131
+ * Sanitizing *after* assigning into a live container is too late: connecting
132
+ * the subtree is what creates a nested browsing context for an `<iframe>` and
133
+ * runs a custom element's `connectedCallback`, both synchronously inside the
134
+ * assignment. And taking only the `<svg>` is half the safety — moving every
135
+ * child of the holder across would connect whatever a hostile document put
136
+ * *beside* the diagram. `auto/index.ts` and `element/index.ts` have always done
137
+ * both; `mount.ts` learned them on diagrammo/diagrammo#884.
138
+ *
139
+ * 🔴 **What this does NOT do**, so a caller is not misled about the contract:
140
+ *
141
+ * - It does not parse CSS. An `@import` or a `url()` inside a `<style>`
142
+ * element passes through untouched.
143
+ * - It removes the elements listed in `REMOVED_TAGS` and nothing else. That
144
+ * set is the script-execution and remote-content surface as of
145
+ * diagrammo/diagrammo#884; an element neither it nor the attribute scrub
146
+ * below names survives, so a caller sanitizing *arbitrary* HTML rather than
147
+ * dgmo renderer output needs its own allowlist on top.
148
+ * - It does not stop a resource load that an attribute alone starts —
149
+ * `<img src>` fetches whether or not the node is connected. What protects
150
+ * that case is the `on*` strip, not the detached holder.
151
+ * - It is no substitute for escaping where a renderer interpolates author
152
+ * text (see `src/embed/escape.ts`).
153
+ *
154
+ * Browser-only — it needs a live DOM. There is nothing to sanitize on the
155
+ * rasterising path, which never builds a document.
156
+ */
157
+ /**
158
+ * Strip script-execution surface from a freshly parsed SVG tree before it
159
+ * lands in the live DOM. This is the safety net that lets us build SVG from
160
+ * markup without trusting renderer output to be fully sanitized.
161
+ *
162
+ * Removes every element in `REMOVED_TAGS`, any SMIL element animating an
163
+ * `href`, any `on*` event-handler attribute, and any `href`/`xlink:href`
164
+ * failing the `safeHref` allowlist.
165
+ *
166
+ * Mutates `root` and returns nothing. Call it on a detached holder — see the
167
+ * module comment above for why the order matters, and for what this does not
168
+ * cover.
169
+ */
170
+ declare function sanitizeSvgInPlace(root: Element): void;
171
+
105
172
  /**
106
173
  * The full diagnostic catalog, sorted by code. Every coded diagnostic
107
174
  * dgmo can emit — its severity, owning chart type, canonical message,
@@ -206,4 +273,4 @@ interface DecodedDiagramUrl {
206
273
  */
207
274
  declare function decodeDiagramUrl(url: string): DecodedDiagramUrl | null;
208
275
 
209
- export { CompactViewState, D3_DATA_CHART_TYPES, type DataChartInteractionOpts, type DecodedDiagramUrl, DgmoError, DiagnosticSpec, type EncodeDiagramUrlOptions, MapDataSource, type MountD3Opts, type MountedD3Chart, type NormalizeSvgForEmbedOptions, PaletteConfig, type RenderOptions, type RenderResult, Theme, attachDataChartInteractions, decodeDiagramUrl, defaultEmbedBackground, encodeDiagramUrl, getDiagnosticSpec, getEmbedSvgViewBox, listDiagnosticCodes, mountD3DataChart, normalizeSvgForEmbed, render, supportsD3DataChart };
276
+ export { CompactViewState, D3_DATA_CHART_TYPES, type DataChartInteractionOpts, type DecodedDiagramUrl, DgmoError, DiagnosticSpec, type EncodeDiagramUrlOptions, MapDataSource, type MountD3Opts, type MountedD3Chart, type NormalizeSvgForEmbedOptions, PaletteConfig, type RenderOptions, type RenderResult, Theme, attachDataChartInteractions, decodeDiagramUrl, defaultEmbedBackground, encodeDiagramUrl, getDiagnosticSpec, getEmbedSvgViewBox, listDiagnosticCodes, mountD3DataChart, normalizeSvgForEmbed, render, sanitizeSvgInPlace, supportsD3DataChart };
package/dist/index.js CHANGED
@@ -9,9 +9,12 @@ import {
9
9
  searchMapLocations,
10
10
  themes
11
11
  } from "./chunk-SQAG3UXH.js";
12
+ import {
13
+ safeHref
14
+ } from "./chunk-Z6DXWULM.js";
12
15
  import {
13
16
  render
14
- } from "./chunk-4YA5XW43.js";
17
+ } from "./chunk-PJQZWJ5I.js";
15
18
  import {
16
19
  renderErrorCard
17
20
  } from "./chunk-D4M25W6R.js";
@@ -34,7 +37,7 @@ import "./chunk-KMU65FYM.js";
34
37
  import "./chunk-EPDBWATV.js";
35
38
  import "./chunk-7UDAIP6W.js";
36
39
  import "./chunk-2CW5ZTNH.js";
37
- import "./chunk-QPE646M2.js";
40
+ import "./chunk-LPXC4I4R.js";
38
41
  import "./chunk-YRYKTYMQ.js";
39
42
  import "./chunk-24YPR5EH.js";
40
43
  import {
@@ -124,6 +127,50 @@ import {
124
127
  } from "./chunk-BYU54C3S.js";
125
128
  import "./chunk-5WRI5ZAA.js";
126
129
 
130
+ // src/utils/sanitize-svg.ts
131
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
132
+ var REMOVED_TAGS = "script, foreignObject, iframe, object, embed, frame, base, link, meta";
133
+ var ANIMATION_TAGS = new Set(
134
+ ["animate", "set", "animateTransform", "animateMotion"].map(
135
+ (t) => t.toLowerCase()
136
+ )
137
+ );
138
+ var ANIMATED_URL_ATTRS = /* @__PURE__ */ new Set(["href", "xlink:href"]);
139
+ function isUrlAnimation(node) {
140
+ if (!ANIMATION_TAGS.has(node.localName.toLowerCase())) return false;
141
+ const target = (node.getAttribute("attributeName") ?? "").trim().toLowerCase();
142
+ return ANIMATED_URL_ATTRS.has(target);
143
+ }
144
+ function scrubElement(node) {
145
+ for (const attr of Array.from(node.attributes)) {
146
+ if (attr.name.toLowerCase().startsWith("on")) {
147
+ node.removeAttribute(attr.name);
148
+ }
149
+ }
150
+ if (node.hasAttribute("href")) {
151
+ const safe = safeHref(node.getAttribute("href"));
152
+ if (safe === null) node.removeAttribute("href");
153
+ }
154
+ if (node.hasAttributeNS(XLINK_NS, "href")) {
155
+ const v = node.getAttributeNS(XLINK_NS, "href");
156
+ if (safeHref(v) === null) {
157
+ node.removeAttributeNS(XLINK_NS, "href");
158
+ }
159
+ }
160
+ }
161
+ function sanitizeSvgInPlace(root) {
162
+ const dangerous = root.querySelectorAll(REMOVED_TAGS);
163
+ dangerous.forEach((n) => n.remove());
164
+ const all = [root, ...Array.from(root.querySelectorAll("*"))];
165
+ for (const node of all) {
166
+ if (isUrlAnimation(node)) {
167
+ node.remove();
168
+ continue;
169
+ }
170
+ scrubElement(node);
171
+ }
172
+ }
173
+
127
174
  // src/charts-d3/interactions.ts
128
175
  var NS = "http://www.w3.org/2000/svg";
129
176
  var STYLE_ID = "dgmo-chart-interactions-style";
@@ -134,7 +181,8 @@ function ensureStyle(svg, muted) {
134
181
  style.id = STYLE_ID;
135
182
  style.textContent = `
136
183
  .dgmo-series,.dgmo-datum,.dgmo-tick,.dgmo-ptlabel{transition:opacity .12s ease}
137
- .dgmo-series.dgmo-dim,.dgmo-datum.dgmo-dim,.dgmo-ptlabel.dgmo-dim{opacity:.18}
184
+ [data-legend-entry]{transition:opacity .12s ease}
185
+ .dgmo-series.dgmo-dim,.dgmo-datum.dgmo-dim,.dgmo-ptlabel.dgmo-dim,[data-legend-entry].dgmo-dim{opacity:.18}
138
186
  .dgmo-tick.dgmo-faded{opacity:.22}
139
187
  .dgmo-datum{cursor:pointer}
140
188
  .dgmo-axis-label{cursor:pointer}
@@ -177,6 +225,9 @@ function attachDataChartInteractions(svg, opts = {}) {
177
225
  svg.querySelectorAll(".dgmo-series")
178
226
  );
179
227
  const datums = Array.from(svg.querySelectorAll(".dgmo-datum"));
228
+ const legendEntries = Array.from(
229
+ svg.querySelectorAll(".chart-legend [data-legend-entry]")
230
+ );
180
231
  const circles = Array.from(
181
232
  svg.querySelectorAll(".dgmo-pt")
182
233
  );
@@ -247,6 +298,15 @@ function attachDataChartInteractions(svg, opts = {}) {
247
298
  const ptLabels = Array.from(
248
299
  svg.querySelectorAll(".dgmo-ptlabel")
249
300
  );
301
+ const dimLegendExcept = (el) => {
302
+ const name = el?.getAttribute("data-series-name") ?? el?.getAttribute("data-emph-key") ?? null;
303
+ const paired = name != null && legendEntries.some((e) => e.getAttribute("data-series-name") === name);
304
+ for (const e of legendEntries)
305
+ e.classList.toggle(
306
+ "dgmo-dim",
307
+ paired && e.getAttribute("data-series-name") !== name
308
+ );
309
+ };
250
310
  const dimDatumsExcept = (el) => {
251
311
  for (const d of datums) d.classList.toggle("dgmo-dim", d !== el);
252
312
  const line = el?.getAttribute("data-line-number");
@@ -255,6 +315,7 @@ function attachDataChartInteractions(svg, opts = {}) {
255
315
  "dgmo-dim",
256
316
  line == null || l.getAttribute("data-line-number") !== line
257
317
  );
318
+ dimLegendExcept(el);
258
319
  };
259
320
  const dimByKey = (el) => {
260
321
  const key = el.getAttribute("data-emph-key");
@@ -263,11 +324,13 @@ function attachDataChartInteractions(svg, opts = {}) {
263
324
  if (key) d.classList.toggle("dgmo-dim", dk !== key);
264
325
  else d.classList.toggle("dgmo-dim", d !== el);
265
326
  }
327
+ dimLegendExcept(el);
266
328
  };
267
329
  const clearDim = () => {
268
330
  for (const d of datums) d.classList.remove("dgmo-dim");
269
331
  for (const g of seriesGroups) g.classList.remove("dgmo-dim");
270
332
  for (const l of ptLabels) l.classList.remove("dgmo-dim");
333
+ for (const e of legendEntries) e.classList.remove("dgmo-dim");
271
334
  reapplyAxisPin();
272
335
  };
273
336
  let axisPin = null;
@@ -513,9 +576,6 @@ function attachDataChartInteractions(svg, opts = {}) {
513
576
  svg.addEventListener("mousemove", onSvgMove);
514
577
  svg.addEventListener("mouseleave", onLeave);
515
578
  svg.addEventListener("click", onClick);
516
- const legendEntries = Array.from(
517
- svg.querySelectorAll(".chart-legend [data-legend-entry]")
518
- );
519
579
  const emphasizeSeries = (name) => {
520
580
  for (const g of seriesGroups) {
521
581
  const sn = g.getAttribute("data-series-name");
@@ -637,7 +697,14 @@ function mountD3DataChart(container, content, opts = {}) {
637
697
  interaction.destroy();
638
698
  interaction = null;
639
699
  }
640
- container.innerHTML = svg;
700
+ const holder = document.createElement("div");
701
+ holder.innerHTML = svg;
702
+ const parsed = holder.querySelector("svg");
703
+ container.innerHTML = "";
704
+ if (parsed) {
705
+ sanitizeSvgInPlace(parsed);
706
+ container.append(parsed);
707
+ }
641
708
  const el = container.querySelector("svg");
642
709
  if (el) {
643
710
  const pal = paletteOf(current);
@@ -750,6 +817,7 @@ export {
750
817
  palettes,
751
818
  render2 as render,
752
819
  resolvePaletteOrFallback,
820
+ sanitizeSvgInPlace,
753
821
  searchMapLocations,
754
822
  supportsD3DataChart,
755
823
  themes,
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  computeNodeSize,
3
3
  layoutBoxesAndLines
4
- } from "./chunk-LIYWJ6DV.js";
5
- import "./chunk-TW6WMZFW.js";
6
- import "./chunk-HBCAY3KR.js";
4
+ } from "./chunk-XIFK423U.js";
5
+ import "./chunk-QT3KDDXK.js";
6
+ import "./chunk-HTNLLT34.js";
7
7
  import "./chunk-6EK55ART.js";
8
- import "./chunk-ZGHV4QLK.js";
8
+ import "./chunk-RJNZHFNG.js";
9
9
  import "./chunk-5DH3KYFH.js";
10
- import "./chunk-WMIKC5XO.js";
11
- import "./chunk-FWNJDD4U.js";
10
+ import "./chunk-NPWVOFB3.js";
11
+ import "./chunk-S7W75MIM.js";
12
+ import "./chunk-Z6DXWULM.js";
12
13
  import "./chunk-ICLB27PZ.js";
13
14
  import "./chunk-KAO7JL4I.js";
14
15
  import "./chunk-O753JUEZ.js";
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  layoutERDiagram
3
- } from "./chunk-FAV4RDVT.js";
4
- import "./chunk-HBCAY3KR.js";
3
+ } from "./chunk-2A4DQAXG.js";
4
+ import "./chunk-HTNLLT34.js";
5
5
  import "./chunk-6EK55ART.js";
6
- import "./chunk-ZGHV4QLK.js";
6
+ import "./chunk-RJNZHFNG.js";
7
7
  import "./chunk-5DH3KYFH.js";
8
- import "./chunk-WMIKC5XO.js";
9
- import "./chunk-FWNJDD4U.js";
8
+ import "./chunk-NPWVOFB3.js";
9
+ import "./chunk-S7W75MIM.js";
10
+ import "./chunk-Z6DXWULM.js";
10
11
  import "./chunk-ICLB27PZ.js";
11
12
  import "./chunk-KAO7JL4I.js";
12
13
  import "./chunk-O753JUEZ.js";