@stocksharp/diagram 1.3.0 → 1.5.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 (41) hide show
  1. package/README.md +84 -1
  2. package/dist/esm/canvas-renderer.js +178 -31
  3. package/dist/esm/canvas-renderer.js.map +1 -1
  4. package/dist/esm/color.js +110 -0
  5. package/dist/esm/color.js.map +1 -1
  6. package/dist/esm/core/document.js +11 -0
  7. package/dist/esm/core/document.js.map +1 -1
  8. package/dist/esm/core/model.js.map +1 -1
  9. package/dist/esm/diagram/stocksharp-diagram.js +23 -3
  10. package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
  11. package/dist/esm/embed.js +104 -20
  12. package/dist/esm/embed.js.map +1 -1
  13. package/dist/esm/index.js +1 -1
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/ssdiagram-headless.js +247 -26
  16. package/dist/ssdiagram-headless.js.map +2 -2
  17. package/dist/ssdiagram.js +352 -48
  18. package/dist/ssdiagram.js.map +3 -3
  19. package/dist/types/canvas-renderer.d.ts +26 -3
  20. package/dist/types/canvas-renderer.d.ts.map +1 -1
  21. package/dist/types/color.d.ts +9 -0
  22. package/dist/types/color.d.ts.map +1 -1
  23. package/dist/types/core/model.d.ts +7 -0
  24. package/dist/types/core/model.d.ts.map +1 -1
  25. package/dist/types/diagram/api.d.ts +6 -0
  26. package/dist/types/diagram/api.d.ts.map +1 -1
  27. package/dist/types/diagram/stocksharp-diagram.d.ts +11 -0
  28. package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
  29. package/dist/types/embed.d.ts +21 -0
  30. package/dist/types/embed.d.ts.map +1 -1
  31. package/dist/types/index.d.ts +2 -2
  32. package/dist/types/index.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/canvas-renderer.ts +182 -30
  35. package/src/color.ts +130 -0
  36. package/src/core/document.ts +12 -0
  37. package/src/core/model.ts +7 -0
  38. package/src/diagram/api.ts +6 -0
  39. package/src/diagram/stocksharp-diagram.ts +27 -3
  40. package/src/embed.ts +147 -29
  41. package/src/index.ts +2 -0
package/dist/ssdiagram.js CHANGED
@@ -53,6 +53,7 @@ var SSDiagram = (() => {
53
53
  parseDiagramViewState: () => parseDiagramViewState,
54
54
  renderAll: () => renderAll,
55
55
  renderDiagramSvg: () => renderDiagramSvg,
56
+ renderDocument: () => renderDocument,
56
57
  renderFromInline: () => renderFromInline,
57
58
  renderFromSource: () => renderFromSource,
58
59
  renderScheme: () => renderScheme,
@@ -162,10 +163,12 @@ var SSDiagram = (() => {
162
163
  function normalizeNode(value, path) {
163
164
  const input = requireStructure(value, path);
164
165
  const id = requireIdentifier(input.id, `${path}.id`);
166
+ const subtitle = requireOptionalText(input.subtitle, `${path}.subtitle`);
165
167
  return {
166
168
  id,
167
169
  typeId: requireIdentifier(input.typeId ?? id, `${path}.typeId`),
168
170
  name: requireString(input.name, `${path}.name`),
171
+ ...subtitle === "" ? {} : { subtitle },
169
172
  description: requireString(input.description ?? "", `${path}.description`),
170
173
  groupName: requireString(input.groupName ?? "Common", `${path}.groupName`),
171
174
  x: requireFiniteNumber(input.x ?? 0, `${path}.x`),
@@ -216,14 +219,19 @@ var SSDiagram = (() => {
216
219
  }
217
220
  function normalizeLink(value, path) {
218
221
  const input = requireStructure(value, path);
222
+ const label2 = requireOptionalText(input.label, `${path}.label`);
219
223
  return {
220
224
  id: requireIdentifier(input.id, `${path}.id`),
221
225
  from: normalizeEndpoint(input.from, `${path}.from`),
222
226
  to: normalizeEndpoint(input.to, `${path}.to`),
223
227
  style: requireLinkStyle(input.style, `${path}.style`),
228
+ ...label2 === "" ? {} : { label: label2 },
224
229
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
225
230
  };
226
231
  }
232
+ function requireOptionalText(value, path) {
233
+ return value === void 0 || value === null ? "" : requireString(value, path);
234
+ }
227
235
  function requireLinkStyle(value, path) {
228
236
  if (value === void 0 || value === null) return "solid";
229
237
  if (value !== "solid" && value !== "dashed") {
@@ -263,6 +271,7 @@ var SSDiagram = (() => {
263
271
  id: requireString(node.id, `${path}.id`),
264
272
  typeId: requireString(node.typeId, `${path}.typeId`),
265
273
  name: requireString(node.name, `${path}.name`),
274
+ subtitle: node.subtitle,
266
275
  description: requireString(node.description, `${path}.description`),
267
276
  groupName: requireString(node.groupName, `${path}.groupName`),
268
277
  x: requireNumber(node.x, `${path}.x`),
@@ -318,6 +327,7 @@ var SSDiagram = (() => {
318
327
  from: parseEndpoint(link.from, `${path}.from`),
319
328
  to: parseEndpoint(link.to, `${path}.to`),
320
329
  style: link.style === void 0 ? void 0 : requireLinkStyle(link.style, `${path}.style`),
330
+ label: link.label,
321
331
  metadata: cloneJsonObject(link.metadata, `${path}.metadata`)
322
332
  }, path);
323
333
  }
@@ -873,6 +883,82 @@ var SSDiagram = (() => {
873
883
  return DARK_OUTLINE;
874
884
  return contrast(background, relativeLuminance(LIGHT_OUTLINE)) > contrast(background, relativeLuminance(DARK_OUTLINE)) ? LIGHT_OUTLINE : DARK_OUTLINE;
875
885
  }
886
+ function toHsl(color) {
887
+ const hsl = color.trim().match(/^hsl\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*\)$/i);
888
+ if (hsl !== null)
889
+ return [+hsl[1], +hsl[2] / 100, +hsl[3] / 100];
890
+ const rgb = parseRgb(color);
891
+ if (rgb === null)
892
+ return null;
893
+ const [r, g, b] = rgb.map((channel) => channel / 255);
894
+ const max = Math.max(r, g, b);
895
+ const min = Math.min(r, g, b);
896
+ const lightness = (max + min) / 2;
897
+ if (max === min)
898
+ return [0, 0, lightness];
899
+ const span = max - min;
900
+ const saturation = lightness > 0.5 ? span / (2 - max - min) : span / (max + min);
901
+ const hue = max === r ? (g - b) / span + (g < b ? 6 : 0) : max === g ? (b - r) / span + 2 : (r - g) / span + 4;
902
+ return [hue * 60, saturation, lightness];
903
+ }
904
+ var DARK_CANVAS_FLOOR = 0.42;
905
+ var LIGHT_CANVAS_CEILING = 0.45;
906
+ function legibleOn(color, background) {
907
+ const hsl = toHsl(color);
908
+ if (hsl === null)
909
+ return color;
910
+ const canvas = relativeLuminance(background);
911
+ const [hue, saturation, lightness] = hsl;
912
+ const moved = canvas < 0 ? lightness : canvas > 0.18 ? Math.min(lightness, LIGHT_CANVAS_CEILING) : Math.max(lightness, DARK_CANVAS_FLOOR);
913
+ if (moved === lightness && /^hsl/i.test(color.trim()))
914
+ return color;
915
+ return `hsl(${Math.round(hue * 10) / 10}, ${Math.round(saturation * 1e3) / 10}%, ${Math.round(moved * 1e3) / 10}%)`;
916
+ }
917
+ function cappedLightness(color, maxLightness) {
918
+ const hsl = toHsl(color);
919
+ if (hsl === null)
920
+ return color;
921
+ const [hue, saturation, lightness] = hsl;
922
+ if (lightness <= maxLightness)
923
+ return color;
924
+ return `hsl(${Math.round(hue * 10) / 10}, ${Math.round(saturation * 1e3) / 10}%, ${Math.round(maxLightness * 1e3) / 10}%)`;
925
+ }
926
+ function resolveCssVariables(value, lookup) {
927
+ return resolveAt(value, lookup, 0);
928
+ }
929
+ function resolveAt(value, lookup, depth) {
930
+ const start = value.indexOf("var(");
931
+ if (start < 0)
932
+ return value;
933
+ if (depth > 8)
934
+ return null;
935
+ let level = 0;
936
+ let end = -1;
937
+ for (let i = start + 3; i < value.length; i += 1) {
938
+ if (value[i] === "(")
939
+ level += 1;
940
+ else if (value[i] === ")" && --level === 0) {
941
+ end = i;
942
+ break;
943
+ }
944
+ }
945
+ if (end < 0)
946
+ return null;
947
+ const inner = value.slice(start + 4, end);
948
+ const comma = inner.indexOf(",");
949
+ const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
950
+ const fallback = comma < 0 ? "" : inner.slice(comma + 1).trim();
951
+ let replacement = name.startsWith("--") ? lookup(name).trim() : "";
952
+ if (replacement === "")
953
+ replacement = fallback;
954
+ if (replacement === "")
955
+ return null;
956
+ const resolved = resolveAt(replacement, lookup, depth + 1);
957
+ const rest = resolveAt(value.slice(end + 1), lookup, depth);
958
+ if (resolved === null || rest === null)
959
+ return null;
960
+ return value.slice(0, start) + resolved + rest;
961
+ }
876
962
 
877
963
  // src/core/history.ts
878
964
  var DiagramCommandHistory = class {
@@ -1162,6 +1248,7 @@ var SSDiagram = (() => {
1162
1248
  this.id = id;
1163
1249
  this.typeId = init.typeId ?? id;
1164
1250
  this.name = init.name;
1251
+ this.subtitle = init.subtitle ?? "";
1165
1252
  this.description = init.description ?? "";
1166
1253
  this.groupName = init.groupName ?? "Common";
1167
1254
  this.color = init.color ?? "#d7d7d7";
@@ -1187,6 +1274,7 @@ var SSDiagram = (() => {
1187
1274
  id: this.id,
1188
1275
  typeId: this.typeId,
1189
1276
  name: this.name,
1277
+ ...this.subtitle === "" ? {} : { subtitle: this.subtitle },
1190
1278
  description: this.description,
1191
1279
  groupName: this.groupName,
1192
1280
  color: this.color,
@@ -1208,12 +1296,13 @@ var SSDiagram = (() => {
1208
1296
  }
1209
1297
  };
1210
1298
  var LinkModel = class {
1211
- constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid") {
1299
+ constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid", label2 = "") {
1212
1300
  this.from = from;
1213
1301
  this.fromPort = fromPort;
1214
1302
  this.to = to;
1215
1303
  this.toPort = toPort;
1216
1304
  this.style = style;
1305
+ this.label = label2;
1217
1306
  this.id = id;
1218
1307
  this.metadata = copyJsonObject(metadata);
1219
1308
  }
@@ -1228,6 +1317,7 @@ var SSDiagram = (() => {
1228
1317
  to: this.to,
1229
1318
  toPort: this.toPort,
1230
1319
  style: this.style,
1320
+ ...this.label === "" ? {} : { label: this.label },
1231
1321
  metadata: copyJsonObject(this.metadata)
1232
1322
  };
1233
1323
  }
@@ -1248,6 +1338,15 @@ var SSDiagram = (() => {
1248
1338
  var INTRO_RISE = 70;
1249
1339
  var ERROR_FLASH_MS = 1100;
1250
1340
  var ERROR_RED = "#f6465d";
1341
+ var DEFAULT_NODE_COLOR = "#d7d7d7";
1342
+ var DEFAULT_NODE_BORDER = "#8c8c8c";
1343
+ var DEFAULT_ZONE_TINT = "#8a8f98";
1344
+ var TITLE_FONT = "600 12px Segoe UI, Tahoma, sans-serif";
1345
+ var SUBTITLE_FONT = "11px Segoe UI, Tahoma, sans-serif";
1346
+ var SUBTITLE_MIN_H = 62;
1347
+ var LINK_LABEL_FONT = "600 10px Segoe UI, Tahoma, sans-serif";
1348
+ var ZONE_CAPTION_TOP = 9;
1349
+ var ZONE_CAPTION_H = 14;
1251
1350
  var ERROR_BACKGROUND = "#7d2632";
1252
1351
  var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
1253
1352
  function hashHue(s) {
@@ -1256,9 +1355,7 @@ var SSDiagram = (() => {
1256
1355
  return (h % 360 + 360) % 360;
1257
1356
  }
1258
1357
  function clampLightness(color, maxL) {
1259
- const m = color.match(/^hsl\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*\)$/i);
1260
- if (m === null) return color;
1261
- return `hsl(${m[1]}, ${m[2]}%, ${Math.min(+m[3], maxL * 100)}%)`;
1358
+ return cappedLightness(color, maxL);
1262
1359
  }
1263
1360
  function colorLuminance(color) {
1264
1361
  if (color === void 0) return 0;
@@ -1347,10 +1444,14 @@ var SSDiagram = (() => {
1347
1444
  this.ovGeo = null;
1348
1445
  this.validator = null;
1349
1446
  this.handlers = /* @__PURE__ */ new Map();
1447
+ // Computed style of the host, read at most once per frame: colours written as var(--token)
1448
+ // resolve against it, so a frame drawn after a theme switch picks up the new values.
1449
+ this.hostStyle = null;
1350
1450
  this.opts = opts;
1351
1451
  if (opts.typeColors !== void 0) this.setTypeColorsInternal(opts.typeColors);
1352
1452
  this.host = opts.host;
1353
1453
  this.gridSnapEnabled = opts.gridSnap ?? false;
1454
+ this.pageScroll = opts.pageScroll ?? false;
1354
1455
  this.gridSize = this.normalizeGridSize(opts.gridSize ?? DEFAULT_GRID_SIZE);
1355
1456
  this.history = new DiagramCommandHistory(() => {
1356
1457
  this.emit("undoStackChanged", { canUndo: this.canUndo(), canRedo: this.canRedo() });
@@ -1359,7 +1460,7 @@ var SSDiagram = (() => {
1359
1460
  this.canvas.style.display = "block";
1360
1461
  this.canvas.tabIndex = 0;
1361
1462
  this.canvas.style.outline = "none";
1362
- this.canvas.style.touchAction = "none";
1463
+ this.applyTouchAction();
1363
1464
  this.canvas.style.userSelect = "none";
1364
1465
  this.canvas.style.webkitUserSelect = "none";
1365
1466
  this.canvas.style.webkitTouchCallout = "none";
@@ -1413,6 +1514,19 @@ var SSDiagram = (() => {
1413
1514
  this.gridSize = nextSize;
1414
1515
  this.scheduleDraw();
1415
1516
  }
1517
+ /** See DiagramOptions.pageScroll. */
1518
+ setPageScroll(enabled) {
1519
+ this.pageScroll = enabled;
1520
+ this.applyTouchAction();
1521
+ }
1522
+ isPageScrollEnabled() {
1523
+ return this.pageScroll;
1524
+ }
1525
+ // pan-y hands a vertical swipe to the browser, which scrolls the page; a horizontal one and a
1526
+ // two-finger pinch still reach the canvas.
1527
+ applyTouchAction() {
1528
+ this.canvas.style.touchAction = this.pageScroll ? "pan-y" : "none";
1529
+ }
1416
1530
  getGridSnap() {
1417
1531
  return { enabled: this.gridSnapEnabled, size: this.gridSize };
1418
1532
  }
@@ -1628,7 +1742,7 @@ var SSDiagram = (() => {
1628
1742
  this.emit("linkValidation", { fromNode: fn, from: fp, toNode: tn, to: tp, ...validation });
1629
1743
  if (!validation.allowed) return false;
1630
1744
  }
1631
- const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid");
1745
+ const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid", init.label ?? "");
1632
1746
  if (this.links.some((l) => l.id === link.id)) return false;
1633
1747
  this.links.splice(clamp(Math.trunc(index), 0, this.links.length), 0, link);
1634
1748
  this.emit("linkAdded", { link });
@@ -1900,6 +2014,7 @@ var SSDiagram = (() => {
1900
2014
  updateNode(nodeId, patch) {
1901
2015
  return this.updateNodeState(nodeId, "update node", (node) => {
1902
2016
  if (patch.name !== void 0) node.name = patch.name;
2017
+ if (patch.subtitle !== void 0) node.subtitle = patch.subtitle;
1903
2018
  if (patch.description !== void 0) node.description = patch.description;
1904
2019
  if (patch.color !== void 0) node.color = patch.color;
1905
2020
  if (patch.border !== void 0) node.border = patch.border;
@@ -1945,6 +2060,7 @@ var SSDiagram = (() => {
1945
2060
  const restored = new NodeModel(snapshot, nodeId);
1946
2061
  node.typeId = restored.typeId;
1947
2062
  node.name = restored.name;
2063
+ node.subtitle = restored.subtitle;
1948
2064
  node.description = restored.description;
1949
2065
  node.groupName = restored.groupName;
1950
2066
  node.color = restored.color;
@@ -1975,6 +2091,10 @@ var SSDiagram = (() => {
1975
2091
  return true;
1976
2092
  }
1977
2093
  load(nodes, links) {
2094
+ this.loadModel(nodes, links, []);
2095
+ }
2096
+ // Zones are placed before the view is fitted, so the first frame frames them too.
2097
+ loadModel(nodes, links, zones) {
1978
2098
  this.clear();
1979
2099
  this.idSeq = 1;
1980
2100
  this.linkSeq = 1;
@@ -2001,9 +2121,10 @@ var SSDiagram = (() => {
2001
2121
  if (this.links.some((existing) => existing.id === id)) {
2002
2122
  throw new Error(`ssdiagram: duplicate link id "${id}"`);
2003
2123
  }
2004
- const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid");
2124
+ const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid", link.label ?? "");
2005
2125
  if (!this.links.some((existing) => existing.key() === model.key())) this.links.push(model);
2006
2126
  }
2127
+ this.zones = zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
2007
2128
  for (const node of this.nodes) {
2008
2129
  if (node.loadError.length === 0) continue;
2009
2130
  const state = createDiagramNodeRuntimeState();
@@ -2026,13 +2147,14 @@ var SSDiagram = (() => {
2026
2147
  to: l.to,
2027
2148
  toPort: l.toPort,
2028
2149
  style: l.style,
2150
+ ...l.label === "" ? {} : { label: l.label },
2029
2151
  metadata: copyJsonObject(l.metadata)
2030
2152
  }))
2031
2153
  };
2032
2154
  }
2033
2155
  loadDocument(source) {
2034
2156
  const document2 = parseDiagramDocument(source);
2035
- this.load(
2157
+ this.loadModel(
2036
2158
  document2.nodes,
2037
2159
  document2.links.map((link) => ({
2038
2160
  id: link.id,
@@ -2041,10 +2163,11 @@ var SSDiagram = (() => {
2041
2163
  to: link.to.nodeId,
2042
2164
  toPort: link.to.portId,
2043
2165
  style: link.style,
2166
+ label: link.label,
2044
2167
  metadata: link.metadata
2045
- }))
2168
+ })),
2169
+ document2.zones
2046
2170
  );
2047
- this.zones = document2.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
2048
2171
  this.documentMetadata = copyJsonObject(document2.metadata);
2049
2172
  }
2050
2173
  saveDocument() {
@@ -2055,6 +2178,7 @@ var SSDiagram = (() => {
2055
2178
  from: { nodeId: link.from, portId: link.fromPort },
2056
2179
  to: { nodeId: link.to, portId: link.toPort },
2057
2180
  style: link.style,
2181
+ label: link.label,
2058
2182
  metadata: link.metadata
2059
2183
  })),
2060
2184
  zones: this.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) })),
@@ -2481,8 +2605,10 @@ var SSDiagram = (() => {
2481
2605
  if (t2.overviewViewportFill !== void 0) this.opts.overviewViewportFill = t2.overviewViewportFill;
2482
2606
  this.scheduleDraw();
2483
2607
  }
2608
+ // Zones count: fitting or exporting only the nodes cut off the places they sit in, captions
2609
+ // included. A caption that is taller than its zone still has to fit.
2484
2610
  graphBounds() {
2485
- if (this.nodes.length === 0) return null;
2611
+ if (this.nodes.length === 0 && this.zones.length === 0) return null;
2486
2612
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2487
2613
  for (const n2 of this.nodes) {
2488
2614
  minX = Math.min(minX, n2.x);
@@ -2490,6 +2616,13 @@ var SSDiagram = (() => {
2490
2616
  maxX = Math.max(maxX, n2.x + n2.w);
2491
2617
  maxY = Math.max(maxY, n2.y + n2.h);
2492
2618
  }
2619
+ for (const zone of this.zones) {
2620
+ const bottom = zone.name.length > 0 ? Math.max(zone.height, ZONE_CAPTION_TOP + ZONE_CAPTION_H) : zone.height;
2621
+ minX = Math.min(minX, zone.x - 1);
2622
+ minY = Math.min(minY, zone.y - 1);
2623
+ maxX = Math.max(maxX, zone.x + zone.width + 1);
2624
+ maxY = Math.max(maxY, zone.y + bottom + 1);
2625
+ }
2493
2626
  return { minX, minY, maxX, maxY };
2494
2627
  }
2495
2628
  resize(w, h) {
@@ -2541,11 +2674,16 @@ var SSDiagram = (() => {
2541
2674
  // ---- geometry ---------------------------------------------------
2542
2675
  layoutNode(n2) {
2543
2676
  const ctx = this.ctx;
2544
- ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
2677
+ ctx.font = TITLE_FONT;
2545
2678
  const titleW = ctx.measureText(n2.name).width;
2679
+ let subtitleW = 0;
2680
+ if (n2.subtitle.length > 0) {
2681
+ ctx.font = SUBTITLE_FONT;
2682
+ subtitleW = ctx.measureText(n2.subtitle).width;
2683
+ }
2546
2684
  const rows = Math.max(n2.inPorts.length, n2.outPorts.length, 1);
2547
- n2.w = Math.max(170, Math.ceil(titleW) + 28);
2548
- n2.h = Math.max(48, rows * PORT_ROW_H + 16);
2685
+ n2.w = Math.max(170, Math.ceil(titleW) + 28, Math.ceil(subtitleW) + 28);
2686
+ n2.h = Math.max(n2.subtitle.length > 0 ? SUBTITLE_MIN_H : 48, rows * PORT_ROW_H + 16);
2549
2687
  const off = PORT_SQ / 2;
2550
2688
  const place = (ports, x) => {
2551
2689
  const stack = ports.some(isGrowAnchor) ? [...ports.filter((p) => !isGrowAnchor(p)), ...ports.filter(isGrowAnchor)] : ports;
@@ -2587,6 +2725,15 @@ var SSDiagram = (() => {
2587
2725
  if (p.direction !== "in" || p.type === "" || !isWildcardPortType(p.type)) return p.type;
2588
2726
  return this.portFeed.get(p) ?? p.type;
2589
2727
  }
2728
+ /// A type's colour as a wire.
2729
+ ///
2730
+ /// The same colour a socket of that type is filled with, moved into the band the canvas
2731
+ /// leaves room for. A socket can afford the host's exact colour because it is a filled
2732
+ /// square with an outline; a wire is a thin line with nothing behind it, and one drawn in
2733
+ /// #000000 on a dark canvas is not there at all.
2734
+ linkColor(type) {
2735
+ return legibleOn(this.portColor(type), this.opts.background ?? "#1b1b1f");
2736
+ }
2590
2737
  portColor(type) {
2591
2738
  const maxL = this.opts.linkMaxLightness;
2592
2739
  const light = maxL !== void 0 && maxL < 0.5;
@@ -2895,6 +3042,7 @@ var SSDiagram = (() => {
2895
3042
  // The clipboard is built from saveDocument(), so it carries the style;
2896
3043
  // rebuilding without it silently turned every pasted dashed link solid.
2897
3044
  style: l.style,
3045
+ label: l.label,
2898
3046
  metadata: l.metadata
2899
3047
  });
2900
3048
  }
@@ -3280,6 +3428,7 @@ var SSDiagram = (() => {
3280
3428
  this.listen(window, "pointerup", finish);
3281
3429
  this.listen(window, "pointercancel", finish);
3282
3430
  this.listen(this.canvas, "wheel", (e) => {
3431
+ if (this.pageScroll && !e.ctrlKey && !e.metaKey) return;
3283
3432
  e.preventDefault();
3284
3433
  const [sx, sy] = localXY(e);
3285
3434
  const [wx, wy] = this.toWorld(sx, sy);
@@ -3425,6 +3574,7 @@ var SSDiagram = (() => {
3425
3574
  }
3426
3575
  draw(options = SCREEN_RENDER_OPTIONS) {
3427
3576
  const ctx = this.ctx;
3577
+ this.hostStyle = null;
3428
3578
  ctx.clearRect(0, 0, this.width, this.height);
3429
3579
  const background = this.opts.background ?? "#1b1b1f";
3430
3580
  ctx.fillStyle = background;
@@ -3452,19 +3602,21 @@ var SSDiagram = (() => {
3452
3602
  );
3453
3603
  this.drawZones();
3454
3604
  const prior = [];
3605
+ const labels = [];
3455
3606
  const hoveredNode = this.hoverPort?.node ?? this.hoverNode;
3456
3607
  for (const l of this.links) {
3457
3608
  const a = this.endpoint(l, "from");
3458
3609
  const b = this.endpoint(l, "to");
3459
3610
  if (a === null || b === null) continue;
3460
3611
  const fp = this.nodes.find((n2) => n2.id === l.from)?.outPorts.find((p) => p.id === l.fromPort);
3461
- const baseColor = (this.opts.typedLinkColors ?? false) && fp !== void 0 ? this.portColor(fp.type) : this.portColor("");
3612
+ const baseColor = (this.opts.typedLinkColors ?? false) && fp !== void 0 ? this.linkColor(fp.type) : this.portColor("");
3462
3613
  const state = options.selection && l === this.selectedLink ? "sel" : options.transient && (l === this.hoveredLink || hoveredNode !== null && (l.from === hoveredNode.id || l.to === hoveredNode.id)) ? "hov" : "norm";
3463
3614
  const color = state === "sel" ? this.selectionColor() : state === "hov" ? this.linkHoverColor() : baseColor;
3464
3615
  const width = state === "sel" ? 3 : state === "hov" ? 2.6 : 2;
3465
3616
  const pts = this.routeLink(a, b, /* @__PURE__ */ new Set([l.from, l.to]));
3466
3617
  this.strokeRoute(pts, color, width, prior, l.style);
3467
3618
  this.drawArrow(pts, color);
3619
+ if (l.label.length > 0) labels.push({ pts, text: l.label, color });
3468
3620
  for (let k = 1; k < pts.length; k += 1) {
3469
3621
  const [x1, y1] = pts[k - 1];
3470
3622
  const [x2, y2] = pts[k];
@@ -3472,6 +3624,7 @@ var SSDiagram = (() => {
3472
3624
  else if (y1 === y2 && x1 !== x2) prior.push({ h: true, c: y1, a: Math.min(x1, x2), b: Math.max(x1, x2) });
3473
3625
  }
3474
3626
  }
3627
+ for (const label2 of labels) this.drawLinkLabel(label2.pts, label2.text, label2.color, background);
3475
3628
  if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
3476
3629
  this.portFeed = this.feedTypes();
3477
3630
  for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
@@ -3626,8 +3779,9 @@ ${runtime.error}`;
3626
3779
  drawZones() {
3627
3780
  if (this.zones.length === 0) return;
3628
3781
  const ctx = this.ctx;
3782
+ const defaultTint = this.resolveColor(this.opts.zoneColor ?? DEFAULT_ZONE_TINT, DEFAULT_ZONE_TINT);
3629
3783
  for (const zone of this.zones) {
3630
- const tint = zone.color.length > 0 ? zone.color : this.opts.zoneColor ?? "#8a8f98";
3784
+ const tint = zone.color.length > 0 ? this.resolveColor(zone.color, defaultTint) : defaultTint;
3631
3785
  ctx.save();
3632
3786
  roundRect(ctx, zone.x, zone.y, zone.width, zone.height, 10);
3633
3787
  ctx.globalAlpha = 0.12;
@@ -3643,10 +3797,10 @@ ${runtime.error}`;
3643
3797
  ctx.save();
3644
3798
  ctx.globalAlpha = 0.75;
3645
3799
  ctx.fillStyle = tint;
3646
- ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
3800
+ ctx.font = TITLE_FONT;
3647
3801
  ctx.textAlign = "left";
3648
3802
  ctx.textBaseline = "top";
3649
- ctx.fillText(zone.name, zone.x + 12, zone.y + 9, Math.max(0, zone.width - 24));
3803
+ ctx.fillText(zone.name, zone.x + 12, zone.y + ZONE_CAPTION_TOP, Math.max(0, zone.width - 24));
3650
3804
  ctx.restore();
3651
3805
  }
3652
3806
  }
@@ -3691,11 +3845,12 @@ ${runtime.error}`;
3691
3845
  const hasRuntimeError = options.runtime && n2.runtimeError.length > 0;
3692
3846
  const runtime = options.runtime ? getJsonKey(this.runtimeState.nodes, n2.id) : void 0;
3693
3847
  const active = options.runtime && (this.runtimeState.activeNodeId === n2.id || runtime?.active === true);
3848
+ const fill = this.resolveColor(n2.color, DEFAULT_NODE_COLOR);
3694
3849
  roundRect(ctx, n2.x, n2.y, n2.w, n2.h, 6);
3695
- ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" : n2.color;
3850
+ ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" : fill;
3696
3851
  ctx.fill();
3697
3852
  ctx.lineWidth = selected ? 2 : 1.5;
3698
- ctx.strokeStyle = selected ? this.selectionColor() : n2.border;
3853
+ ctx.strokeStyle = selected ? this.selectionColor() : this.resolveColor(n2.border, DEFAULT_NODE_BORDER);
3699
3854
  ctx.stroke();
3700
3855
  if (hasLoadError || hasRuntimeError) {
3701
3856
  let alpha = 1;
@@ -3728,11 +3883,22 @@ ${runtime.error}`;
3728
3883
  }
3729
3884
  }
3730
3885
  const titleShift = n2.icon ? iconW + 4 : 0;
3731
- ctx.fillStyle = hasLoadError ? "#ffffff" : readableTextOn(active ? "#ffd1dc" : n2.color);
3732
- ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
3886
+ const textX = n2.x + titleShift + (n2.w - titleShift) / 2;
3887
+ const textWidth = n2.w - titleShift - 14;
3888
+ ctx.fillStyle = hasLoadError ? "#ffffff" : readableTextOn(active ? "#ffd1dc" : fill);
3889
+ ctx.font = TITLE_FONT;
3733
3890
  ctx.textAlign = "center";
3734
3891
  ctx.textBaseline = "middle";
3735
- ctx.fillText(n2.name, n2.x + titleShift + (n2.w - titleShift) / 2, n2.y + n2.h / 2, n2.w - titleShift - 14);
3892
+ if (n2.subtitle.length === 0) {
3893
+ ctx.fillText(n2.name, textX, n2.y + n2.h / 2, textWidth);
3894
+ } else {
3895
+ ctx.fillText(n2.name, textX, n2.y + n2.h / 2 - 8, textWidth);
3896
+ ctx.save();
3897
+ ctx.globalAlpha *= 0.72;
3898
+ ctx.font = SUBTITLE_FONT;
3899
+ ctx.fillText(n2.subtitle, textX, n2.y + n2.h / 2 + 9, textWidth);
3900
+ ctx.restore();
3901
+ }
3736
3902
  for (const p of n2.inPorts) this.drawPort(n2, p, options);
3737
3903
  for (const p of n2.outPorts) this.drawPort(n2, p, options);
3738
3904
  }
@@ -3849,6 +4015,45 @@ ${runtime.error}`;
3849
4015
  ctx.fill();
3850
4016
  ctx.restore();
3851
4017
  }
4018
+ // A plate of the canvas colour under the text, edged in the wire's colour: the label reads the same
4019
+ // whatever the wire crosses, and still says which wire it belongs to.
4020
+ drawLinkLabel(pts, text, color, background) {
4021
+ const [x, y] = pathMidpoint(pts);
4022
+ const ctx = this.ctx;
4023
+ ctx.save();
4024
+ ctx.font = LINK_LABEL_FONT;
4025
+ const w = Math.ceil(ctx.measureText(text).width) + 12;
4026
+ const h = 16;
4027
+ roundRect(ctx, x - w / 2, y - h / 2, w, h, h / 2);
4028
+ ctx.fillStyle = background;
4029
+ ctx.fill();
4030
+ ctx.lineWidth = 1;
4031
+ ctx.strokeStyle = color;
4032
+ ctx.stroke();
4033
+ ctx.fillStyle = readableTextOn(background);
4034
+ ctx.textAlign = "center";
4035
+ ctx.textBaseline = "middle";
4036
+ ctx.fillText(text, x, y);
4037
+ ctx.restore();
4038
+ }
4039
+ // Scheme colours may name a custom property of the page, var(--token), which a canvas cannot
4040
+ // paint. They resolve against the host element; one that resolves to nothing and has no
4041
+ // fallback of its own gets the renderer's default for that slot.
4042
+ resolveColor(value, fallback) {
4043
+ if (!value.includes("var(")) return value;
4044
+ return resolveCssVariables(value, (name) => this.hostProperty(name)) ?? fallback;
4045
+ }
4046
+ hostProperty(name) {
4047
+ if (this.hostStyle === null) {
4048
+ if (typeof getComputedStyle !== "function") return "";
4049
+ try {
4050
+ this.hostStyle = getComputedStyle(this.host);
4051
+ } catch {
4052
+ return "";
4053
+ }
4054
+ }
4055
+ return this.hostStyle.getPropertyValue(name);
4056
+ }
3852
4057
  drawSelectedLinkEndpoints() {
3853
4058
  if (this.selectedLink === null) return;
3854
4059
  const ctx = this.ctx;
@@ -3906,7 +4111,7 @@ ${runtime.error}`;
3906
4111
  }
3907
4112
  if (snapped !== null) excludedNodes.add(snapped.node.id);
3908
4113
  const points = this.routeLink(a, b, excludedNodes);
3909
- ctx.strokeStyle = this.portColor(colorType);
4114
+ ctx.strokeStyle = this.linkColor(colorType);
3910
4115
  if (snapped !== null) {
3911
4116
  ctx.setLineDash([]);
3912
4117
  ctx.lineWidth = 2.6;
@@ -3964,9 +4169,9 @@ ${runtime.error}`;
3964
4169
  ctx.stroke();
3965
4170
  ctx.clip();
3966
4171
  for (const n2 of this.nodes) {
3967
- ctx.fillStyle = n2.color;
4172
+ ctx.fillStyle = this.resolveColor(n2.color, DEFAULT_NODE_COLOR);
3968
4173
  ctx.fillRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
3969
- ctx.strokeStyle = n2.border;
4174
+ ctx.strokeStyle = this.resolveColor(n2.border, DEFAULT_NODE_BORDER);
3970
4175
  ctx.lineWidth = 1;
3971
4176
  ctx.strokeRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
3972
4177
  }
@@ -4007,6 +4212,23 @@ ${runtime.error}`;
4007
4212
  ctx.arcTo(x, y, x + w, y, rr);
4008
4213
  ctx.closePath();
4009
4214
  }
4215
+ function pathMidpoint(pts) {
4216
+ let total = 0;
4217
+ for (let i = 1; i < pts.length; i += 1) total += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
4218
+ let remaining = total / 2;
4219
+ for (let i = 1; i < pts.length; i += 1) {
4220
+ const [x1, y1] = pts[i - 1];
4221
+ const [x2, y2] = pts[i];
4222
+ const length = Math.hypot(x2 - x1, y2 - y1);
4223
+ if (length > 0 && remaining <= length) {
4224
+ const t2 = remaining / length;
4225
+ return [x1 + (x2 - x1) * t2, y1 + (y2 - y1) * t2];
4226
+ }
4227
+ remaining -= length;
4228
+ }
4229
+ const last = pts[pts.length - 1];
4230
+ return [last[0], last[1]];
4231
+ }
4010
4232
  function route(a, b) {
4011
4233
  const [ax, ay] = a;
4012
4234
  const [bx, by] = b;
@@ -4991,6 +5213,7 @@ ${runtime.error}`;
4991
5213
  this.fullscreen = false;
4992
5214
  this.fullscreenButtonVisible = true;
4993
5215
  this.downloadButtonVisible = false;
5216
+ this.exportEnabled = true;
4994
5217
  this.downloadLabel = null;
4995
5218
  // null means the host never named the button, so its wording comes from the shared bundle.
4996
5219
  // Keeping the two apart is what lets an explicit label win while an untouched control still
@@ -5007,7 +5230,8 @@ ${runtime.error}`;
5007
5230
  host: this.div,
5008
5231
  typeColors: this.portTypeColors(),
5009
5232
  gridSnap: options.gridSnap ?? true,
5010
- gridSize: options.gridSize
5233
+ gridSize: options.gridSize,
5234
+ pageScroll: options.pageScroll ?? false
5011
5235
  });
5012
5236
  this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
5013
5237
  this.fullscreenLabels = options.fullscreenLabels ?? null;
@@ -5085,6 +5309,13 @@ ${runtime.error}`;
5085
5309
  getGridSnap() {
5086
5310
  return this.canvas.getGridSnap();
5087
5311
  }
5312
+ /** See DiagramOptions.pageScroll. */
5313
+ setPageScroll(enabled) {
5314
+ this.canvas.setPageScroll(enabled);
5315
+ }
5316
+ isPageScrollEnabled() {
5317
+ return this.canvas.isPageScrollEnabled();
5318
+ }
5088
5319
  nudgeSelection(dx, dy) {
5089
5320
  return this.canvas.nudgeSelection(dx, dy);
5090
5321
  }
@@ -5266,6 +5497,17 @@ ${runtime.error}`;
5266
5497
  this.downloadButton.hidden = false;
5267
5498
  this.downloadButton.style.display = "inline-flex";
5268
5499
  }
5500
+ /**
5501
+ * Turns the export commands of the context menu on or off. Off, the menu offers them greyed out and
5502
+ * `executeContextCommand` refuses them, for a page that does not want its diagram saved. The
5503
+ * download button is separate: hide it with `setDownloadButtonVisible(false)`.
5504
+ */
5505
+ setExportEnabled(enabled) {
5506
+ this.exportEnabled = enabled;
5507
+ }
5508
+ isExportEnabled() {
5509
+ return this.exportEnabled;
5510
+ }
5269
5511
  isDownloadButtonVisible() {
5270
5512
  return this.downloadButtonVisible;
5271
5513
  }
@@ -5767,15 +6009,15 @@ ${runtime.error}`;
5767
6009
  // picture, and an empty diagram has none to give. They stay on in read-only mode,
5768
6010
  // which forbids changing the diagram, not looking at it.
5769
6011
  exportDocument: {
5770
- canExecute: ({ document: document2 }) => document2.nodes.length > 0,
6012
+ canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
5771
6013
  execute: () => this.emit("exportRequested", { format: "document" })
5772
6014
  },
5773
6015
  exportPng: {
5774
- canExecute: ({ document: document2 }) => document2.nodes.length > 0,
6016
+ canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
5775
6017
  execute: () => this.emit("exportRequested", { format: "png" })
5776
6018
  },
5777
6019
  exportSvg: {
5778
- canExecute: ({ document: document2 }) => document2.nodes.length > 0,
6020
+ canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
5779
6021
  execute: () => this.emit("exportRequested", { format: "svg" })
5780
6022
  },
5781
6023
  // Always available: it shows the overview as readily as it hides it, and an empty
@@ -6411,17 +6653,62 @@ ${runtime.error}`;
6411
6653
  if (renderRevisions.get(div) !== revision)
6412
6654
  return null;
6413
6655
  const catalog = buildCatalog(palette);
6656
+ return mountDiagram(div, catalog, options, false, (diagram) => {
6657
+ const missingTypeTemplate = div.dataset.diagramMissingElement ?? t("embedMissingElement", 'Element type "{typeId}" is missing from the palette.');
6658
+ const converted = toDiagramNodes(
6659
+ scheme,
6660
+ catalog,
6661
+ (typeId) => missingTypeTemplate.replace("{typeId}", typeId || "(empty)")
6662
+ );
6663
+ diagram.load(
6664
+ converted.nodes,
6665
+ scheme.links.map((link) => new Link({
6666
+ outNode: link.from,
6667
+ outPort: link.fromPort,
6668
+ inNode: link.to,
6669
+ inPort: link.toPort
6670
+ })),
6671
+ { nodeErrors: converted.nodeErrors }
6672
+ );
6673
+ });
6674
+ }
6675
+ function renderDocument(div, source, options = {}) {
6676
+ const parsed = readDocument(source);
6677
+ beginRender(div);
6678
+ return mountDocument(div, parsed, options);
6679
+ }
6680
+ function readDocument(source) {
6681
+ let value = source;
6682
+ if (typeof source === "string") {
6683
+ try {
6684
+ value = JSON.parse(source);
6685
+ } catch (error) {
6686
+ throw new DiagramDocumentError(error instanceof Error ? error.message : "invalid JSON");
6687
+ }
6688
+ }
6689
+ if (!isRecord(value))
6690
+ throw new DiagramDocumentError("expected an object");
6691
+ if (value.version !== void 0 && value.version !== DIAGRAM_DOCUMENT_VERSION)
6692
+ throw new DiagramDocumentError(`unsupported document version ${String(value.version)}`, "$.version");
6693
+ return createDiagramDocument(value);
6694
+ }
6695
+ function mountDocument(div, source, options) {
6696
+ return mountDiagram(div, new StockSharpCatalog(), options, true, (diagram) => diagram.loadDocument(source));
6697
+ }
6698
+ function mountDiagram(div, catalog, options, pageScrollByDefault, populate) {
6414
6699
  disposeActiveRender(div);
6415
6700
  div.classList.remove("ss-diagram-error");
6416
6701
  div.replaceChildren();
6417
6702
  const [enterLabel, exitLabel] = (div.dataset.diagramFullscreen ?? "").split("|");
6418
6703
  const downloadLabel = div.dataset.diagramDownload;
6419
- const showDownloadButton = options.onExportRequested !== void 0;
6704
+ const exportAllowed = options.allowExport !== false && div.dataset.diagramExport !== "off";
6705
+ const showDownloadButton = exportAllowed && options.onExportRequested !== void 0;
6420
6706
  let diagram;
6421
6707
  try {
6422
6708
  diagram = new StockSharpDiagram({
6423
6709
  div,
6424
6710
  catalog,
6711
+ pageScroll: options.pageScroll ?? pageScrollByDefault,
6425
6712
  ...enterLabel && exitLabel ? { fullscreenLabels: { enter: enterLabel, exit: exitLabel } } : {},
6426
6713
  ...showDownloadButton ? { showDownloadButton: true } : {},
6427
6714
  ...downloadLabel ? { downloadLabel } : {}
@@ -6471,7 +6758,9 @@ ${runtime.error}`;
6471
6758
  activeRenders.set(div, handle);
6472
6759
  connectedRenders.add(handle);
6473
6760
  ensureDisconnectedHostObserver();
6474
- if (options.onExportRequested !== void 0) {
6761
+ if (!exportAllowed) {
6762
+ diagram.setExportEnabled(false);
6763
+ } else if (options.onExportRequested !== void 0) {
6475
6764
  const notifyHost = options.onExportRequested;
6476
6765
  cleanups.push(diagram.on("exportRequested", (request) => notifyHost(request, handle)));
6477
6766
  }
@@ -6498,22 +6787,7 @@ ${runtime.error}`;
6498
6787
  diagram.setReadOnly(true);
6499
6788
  diagram.setLinkValidator(() => true);
6500
6789
  diagram.setOverviewVisible(false);
6501
- const missingTypeTemplate = div.dataset.diagramMissingElement ?? t("embedMissingElement", 'Element type "{typeId}" is missing from the palette.');
6502
- const converted = toDiagramNodes(
6503
- scheme,
6504
- catalog,
6505
- (typeId) => missingTypeTemplate.replace("{typeId}", typeId || "(empty)")
6506
- );
6507
- diagram.load(
6508
- converted.nodes,
6509
- scheme.links.map((link) => new Link({
6510
- outNode: link.from,
6511
- outPort: link.fromPort,
6512
- inNode: link.to,
6513
- inPort: link.toPort
6514
- })),
6515
- { nodeErrors: converted.nodeErrors }
6516
- );
6790
+ populate(diagram);
6517
6791
  const fit = () => {
6518
6792
  if (!div.isConnected) {
6519
6793
  handle.destroy();
@@ -6610,6 +6884,8 @@ ${runtime.error}`;
6610
6884
  note(div, errors.load, revision);
6611
6885
  return null;
6612
6886
  }
6887
+ if (holdsDocument(div, raw))
6888
+ return drawDocumentAtRevision(div, raw, revision, options, errors);
6613
6889
  const scheme = parseRawScheme(raw);
6614
6890
  if (scheme.nodes.length === 0) {
6615
6891
  note(div, errors.empty, revision);
@@ -6632,6 +6908,8 @@ ${runtime.error}`;
6632
6908
  note(div, errors.empty, revision);
6633
6909
  return null;
6634
6910
  }
6911
+ if (holdsDocument(div, raw))
6912
+ return drawDocumentAtRevision(div, raw, revision, options, errors);
6635
6913
  const scheme = parseRawScheme(raw);
6636
6914
  if (scheme.nodes.length === 0) {
6637
6915
  note(div, errors.empty, revision);
@@ -6644,6 +6922,32 @@ ${runtime.error}`;
6644
6922
  return null;
6645
6923
  }
6646
6924
  }
6925
+ function holdsDocument(div, raw) {
6926
+ if (div.dataset.diagramKind === "document")
6927
+ return true;
6928
+ return isRecord(raw) && typeof raw.version === "number" && Array.isArray(raw.nodes);
6929
+ }
6930
+ function drawDocumentAtRevision(div, raw, revision, options, errors) {
6931
+ if (renderRevisions.get(div) !== revision)
6932
+ return null;
6933
+ let parsed;
6934
+ try {
6935
+ parsed = readDocument(raw);
6936
+ } catch {
6937
+ note(div, errors.empty, revision);
6938
+ return null;
6939
+ }
6940
+ if (parsed.nodes.length === 0 && parsed.zones.length === 0) {
6941
+ note(div, errors.empty, revision);
6942
+ return null;
6943
+ }
6944
+ try {
6945
+ return mountDocument(div, parsed, options);
6946
+ } catch {
6947
+ note(div, errors.draw, revision);
6948
+ return null;
6949
+ }
6950
+ }
6647
6951
  function renderAll(root = document, options = {}) {
6648
6952
  root.querySelectorAll(".ss-diagram-host").forEach((host) => {
6649
6953
  if (host.dataset.rendered === "1")