@stocksharp/diagram 0.2.2 → 0.3.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 (68) hide show
  1. package/README.md +12 -0
  2. package/dist/esm/canvas-renderer.js +28 -22
  3. package/dist/esm/canvas-renderer.js.map +1 -1
  4. package/dist/esm/core/action-registry.js +24 -0
  5. package/dist/esm/core/action-registry.js.map +1 -1
  6. package/dist/esm/core/document.js +85 -28
  7. package/dist/esm/core/document.js.map +1 -1
  8. package/dist/esm/core/json.js +17 -0
  9. package/dist/esm/core/json.js.map +1 -0
  10. package/dist/esm/core/model.js +4 -0
  11. package/dist/esm/core/model.js.map +1 -1
  12. package/dist/esm/core/state.js +16 -2
  13. package/dist/esm/core/state.js.map +1 -1
  14. package/dist/esm/diagram/catalog.js +17 -5
  15. package/dist/esm/diagram/catalog.js.map +1 -1
  16. package/dist/esm/diagram/event-emitter.js +10 -0
  17. package/dist/esm/diagram/event-emitter.js.map +1 -1
  18. package/dist/esm/diagram/stocksharp-diagram.js +69 -57
  19. package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
  20. package/dist/esm/diagram/types.js +2 -1
  21. package/dist/esm/diagram/types.js.map +1 -1
  22. package/dist/esm/embed.js +129 -39
  23. package/dist/esm/embed.js.map +1 -1
  24. package/dist/esm/index.js +1 -1
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/ssdiagram.js +314 -147
  27. package/dist/ssdiagram.js.map +4 -4
  28. package/dist/types/canvas-renderer.d.ts +3 -5
  29. package/dist/types/canvas-renderer.d.ts.map +1 -1
  30. package/dist/types/core/action-registry.d.ts +7 -0
  31. package/dist/types/core/action-registry.d.ts.map +1 -1
  32. package/dist/types/core/document.d.ts.map +1 -1
  33. package/dist/types/core/json.d.ts +11 -0
  34. package/dist/types/core/json.d.ts.map +1 -0
  35. package/dist/types/core/model.d.ts +10 -1
  36. package/dist/types/core/model.d.ts.map +1 -1
  37. package/dist/types/core/state.d.ts +24 -1
  38. package/dist/types/core/state.d.ts.map +1 -1
  39. package/dist/types/diagram/api.d.ts +13 -6
  40. package/dist/types/diagram/api.d.ts.map +1 -1
  41. package/dist/types/diagram/catalog.d.ts +2 -1
  42. package/dist/types/diagram/catalog.d.ts.map +1 -1
  43. package/dist/types/diagram/event-emitter.d.ts +6 -1
  44. package/dist/types/diagram/event-emitter.d.ts.map +1 -1
  45. package/dist/types/diagram/palette.d.ts +1 -1
  46. package/dist/types/diagram/palette.d.ts.map +1 -1
  47. package/dist/types/diagram/stocksharp-diagram.d.ts +32 -21
  48. package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
  49. package/dist/types/diagram/types.d.ts +4 -4
  50. package/dist/types/diagram/types.d.ts.map +1 -1
  51. package/dist/types/embed.d.ts.map +1 -1
  52. package/dist/types/index.d.ts +3 -3
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/package.json +2 -2
  55. package/src/canvas-renderer.ts +26 -24
  56. package/src/core/action-registry.ts +25 -0
  57. package/src/core/document.ts +91 -31
  58. package/src/core/json.ts +16 -0
  59. package/src/core/model.ts +14 -1
  60. package/src/core/state.ts +41 -3
  61. package/src/diagram/api.ts +14 -5
  62. package/src/diagram/catalog.ts +18 -6
  63. package/src/diagram/event-emitter.ts +12 -1
  64. package/src/diagram/palette.ts +1 -1
  65. package/src/diagram/stocksharp-diagram.ts +91 -75
  66. package/src/diagram/types.ts +6 -5
  67. package/src/embed.ts +149 -40
  68. package/src/index.ts +4 -0
package/dist/ssdiagram.js CHANGED
@@ -37,6 +37,7 @@ var SSDiagram = (() => {
37
37
  StockSharpDiagram: () => StockSharpDiagram,
38
38
  StockSharpPalette: () => StockSharpPalette,
39
39
  cloneDiagramDocument: () => cloneDiagramDocument,
40
+ cloneDiagramNodeErrors: () => cloneDiagramNodeErrors,
40
41
  cloneDiagramRuntimeState: () => cloneDiagramRuntimeState,
41
42
  createDiagramDocument: () => createDiagramDocument,
42
43
  createDiagramNodeRuntimeState: () => createDiagramNodeRuntimeState,
@@ -58,7 +59,19 @@ var SSDiagram = (() => {
58
59
  serializeDiagramViewState: () => serializeDiagramViewState
59
60
  });
60
61
 
62
+ // src/core/json.ts
63
+ function setJsonKey(target, key, value) {
64
+ if (key === "__proto__") {
65
+ Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
66
+ return;
67
+ }
68
+ target[key] = value;
69
+ }
70
+
61
71
  // src/core/model.ts
72
+ function toPortDynamicMode(value) {
73
+ return value === "manual" || value === "onConnect" ? value : "";
74
+ }
62
75
  var DIAGRAM_DOCUMENT_VERSION = 1;
63
76
 
64
77
  // src/core/document.ts
@@ -70,25 +83,29 @@ var SSDiagram = (() => {
70
83
  }
71
84
  };
72
85
  function createDiagramDocument(input = {}) {
73
- const nodes = (input.nodes ?? []).map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
86
+ const rawNodes = mapElements(input.nodes, "$.nodes");
87
+ const rawLinks = mapElements(input.links, "$.links");
88
+ const nodes = rawNodes.map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
74
89
  const usedLinkIds = /* @__PURE__ */ new Set();
75
- for (let index = 0; index < (input.links ?? []).length; index++) {
76
- const id = input.links?.[index].id;
77
- if (id === void 0) continue;
78
- requireIdentifier(id, `$.links[${index}].id`);
90
+ for (let index = 0; index < rawLinks.length; index++) {
91
+ const raw = rawLinks[index].id;
92
+ if (raw === void 0) continue;
93
+ const id = requireIdentifier(raw, `$.links[${index}].id`);
79
94
  if (usedLinkIds.has(id)) {
80
95
  throw new DiagramDocumentError(`duplicate link id "${id}"`, `$.links[${index}].id`);
81
96
  }
82
97
  usedLinkIds.add(id);
83
98
  }
84
99
  let sequence = 1;
85
- const links = (input.links ?? []).map((link, index) => {
100
+ const links = rawLinks.map((link, index) => {
86
101
  let id = link.id;
87
102
  if (id === void 0) {
103
+ let generated;
88
104
  do
89
- id = `link_${sequence++}`;
90
- while (usedLinkIds.has(id));
91
- usedLinkIds.add(id);
105
+ generated = `link_${sequence++}`;
106
+ while (usedLinkIds.has(generated));
107
+ usedLinkIds.add(generated);
108
+ id = generated;
92
109
  }
93
110
  return normalizeLink({ ...link, id }, `$.links[${index}]`);
94
111
  });
@@ -117,7 +134,7 @@ var SSDiagram = (() => {
117
134
  throw new DiagramDocumentError(reason);
118
135
  }
119
136
  }
120
- const root = requireObject(value, "$");
137
+ const root = requireStructure(value, "$");
121
138
  const version = requireNumber(root.version, "$.version");
122
139
  if (version !== DIAGRAM_DOCUMENT_VERSION) {
123
140
  throw new DiagramDocumentError(`unsupported document version ${version}`, "$.version");
@@ -131,7 +148,8 @@ var SSDiagram = (() => {
131
148
  validateDocument(document2);
132
149
  return document2;
133
150
  }
134
- function normalizeNode(input, path) {
151
+ function normalizeNode(value, path) {
152
+ const input = requireStructure(value, path);
135
153
  const id = requireIdentifier(input.id, `${path}.id`);
136
154
  return {
137
155
  id,
@@ -146,28 +164,30 @@ var SSDiagram = (() => {
146
164
  icon: requireString(input.icon ?? "", `${path}.icon`),
147
165
  message: requireString(input.message ?? "", `${path}.message`),
148
166
  openAction: requireString(input.openAction ?? "", `${path}.openAction`),
149
- inPorts: (input.inPorts ?? []).map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
150
- outPorts: (input.outPorts ?? []).map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
151
- parameters: (input.parameters ?? []).map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
167
+ inPorts: mapElements(input.inPorts, `${path}.inPorts`).map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
168
+ outPorts: mapElements(input.outPorts, `${path}.outPorts`).map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
169
+ parameters: mapElements(input.parameters, `${path}.parameters`).map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
152
170
  paramValues: cloneStringRecord(input.paramValues ?? {}, `${path}.paramValues`),
153
171
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
154
172
  };
155
173
  }
156
- function normalizePort(input, path) {
174
+ function normalizePort(value, path) {
175
+ const input = requireStructure(value, path);
157
176
  return {
158
177
  id: requireIdentifier(input.id, `${path}.id`),
159
178
  name: requireString(input.name, `${path}.name`),
160
179
  description: requireString(input.description ?? "", `${path}.description`),
161
180
  type: requireString(input.type ?? "", `${path}.type`),
162
181
  maxLinks: requireNonNegativeInteger(input.maxLinks ?? 0, `${path}.maxLinks`),
163
- availableTypes: (input.availableTypes ?? []).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
182
+ availableTypes: requireArray(input.availableTypes ?? [], `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
164
183
  isDynamic: requireBoolean(input.isDynamic ?? false, `${path}.isDynamic`),
165
- dynamicMode: requireString(input.dynamicMode ?? "", `${path}.dynamicMode`),
184
+ dynamicMode: requirePortDynamicMode(input.dynamicMode ?? "", `${path}.dynamicMode`),
166
185
  isSibling: requireBoolean(input.isSibling ?? false, `${path}.isSibling`),
167
186
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
168
187
  };
169
188
  }
170
- function normalizeParameter(input, path) {
189
+ function normalizeParameter(value, path) {
190
+ const input = requireStructure(value, path);
171
191
  return {
172
192
  name: requireIdentifier(input.name, `${path}.name`),
173
193
  displayName: requireString(input.displayName, `${path}.displayName`),
@@ -183,7 +203,8 @@ var SSDiagram = (() => {
183
203
  editorType: requireString(input.editorType, `${path}.editorType`)
184
204
  };
185
205
  }
186
- function normalizeLink(input, path) {
206
+ function normalizeLink(value, path) {
207
+ const input = requireStructure(value, path);
187
208
  return {
188
209
  id: requireIdentifier(input.id, `${path}.id`),
189
210
  from: normalizeEndpoint(input.from, `${path}.from`),
@@ -191,14 +212,15 @@ var SSDiagram = (() => {
191
212
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
192
213
  };
193
214
  }
194
- function normalizeEndpoint(input, path) {
215
+ function normalizeEndpoint(value, path) {
216
+ const input = requireStructure(value, path);
195
217
  return {
196
218
  nodeId: requireIdentifier(input.nodeId, `${path}.nodeId`),
197
219
  portId: requireIdentifier(input.portId, `${path}.portId`)
198
220
  };
199
221
  }
200
222
  function parseNode(value, path) {
201
- const node = requireObject(value, path);
223
+ const node = requireStructure(value, path);
202
224
  return normalizeNode({
203
225
  id: requireString(node.id, `${path}.id`),
204
226
  typeId: requireString(node.typeId, `${path}.typeId`),
@@ -220,7 +242,7 @@ var SSDiagram = (() => {
220
242
  }, path);
221
243
  }
222
244
  function parsePort(value, path) {
223
- const port = requireObject(value, path);
245
+ const port = requireStructure(value, path);
224
246
  return normalizePort({
225
247
  id: requireString(port.id, `${path}.id`),
226
248
  name: requireString(port.name, `${path}.name`),
@@ -229,13 +251,13 @@ var SSDiagram = (() => {
229
251
  maxLinks: requireNumber(port.maxLinks, `${path}.maxLinks`),
230
252
  availableTypes: requireArray(port.availableTypes, `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
231
253
  isDynamic: requireBoolean(port.isDynamic, `${path}.isDynamic`),
232
- dynamicMode: requireString(port.dynamicMode, `${path}.dynamicMode`),
254
+ dynamicMode: requirePortDynamicMode(port.dynamicMode, `${path}.dynamicMode`),
233
255
  isSibling: requireBoolean(port.isSibling, `${path}.isSibling`),
234
256
  metadata: cloneJsonObject(port.metadata, `${path}.metadata`)
235
257
  }, path);
236
258
  }
237
259
  function parseParameter(value, path) {
238
- const parameter = requireObject(value, path);
260
+ const parameter = requireStructure(value, path);
239
261
  return normalizeParameter({
240
262
  name: requireString(parameter.name, `${path}.name`),
241
263
  displayName: requireString(parameter.displayName, `${path}.displayName`),
@@ -252,7 +274,7 @@ var SSDiagram = (() => {
252
274
  }, path);
253
275
  }
254
276
  function parseLink(value, path) {
255
- const link = requireObject(value, path);
277
+ const link = requireStructure(value, path);
256
278
  return normalizeLink({
257
279
  id: requireString(link.id, `${path}.id`),
258
280
  from: parseEndpoint(link.from, `${path}.from`),
@@ -261,7 +283,7 @@ var SSDiagram = (() => {
261
283
  }, path);
262
284
  }
263
285
  function parseEndpoint(value, path) {
264
- const endpoint = requireObject(value, path);
286
+ const endpoint = requireStructure(value, path);
265
287
  return {
266
288
  nodeId: requireIdentifier(endpoint.nodeId, `${path}.nodeId`),
267
289
  portId: requireIdentifier(endpoint.portId, `${path}.portId`)
@@ -309,7 +331,7 @@ var SSDiagram = (() => {
309
331
  function cloneStringRecord(value, path) {
310
332
  const object = requireObject(value, path);
311
333
  const result = {};
312
- for (const [key, item] of Object.entries(object)) result[key] = requireString(item, `${path}.${key}`);
334
+ for (const [key, item] of Object.entries(object)) setJsonKey(result, key, requireString(item, `${path}.${key}`));
313
335
  return result;
314
336
  }
315
337
  function cloneJsonObject(value, path, ancestors = /* @__PURE__ */ new WeakSet()) {
@@ -319,7 +341,7 @@ var SSDiagram = (() => {
319
341
  const result = {};
320
342
  try {
321
343
  for (const [key, item] of Object.entries(object)) {
322
- result[key] = cloneJsonValue(item, `${path}.${key}`, ancestors);
344
+ setJsonKey(result, key, cloneJsonValue(item, `${path}.${key}`, ancestors));
323
345
  }
324
346
  } finally {
325
347
  ancestors.delete(object);
@@ -341,6 +363,12 @@ var SSDiagram = (() => {
341
363
  if (isObject(value)) return cloneJsonObject(value, path, ancestors);
342
364
  throw new DiagramDocumentError("expected a JSON value", path);
343
365
  }
366
+ function requireStructure(value, path) {
367
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
368
+ throw new DiagramDocumentError("expected an object", path);
369
+ }
370
+ return value;
371
+ }
344
372
  function requireObject(value, path) {
345
373
  if (!isObject(value)) throw new DiagramDocumentError("expected an object", path);
346
374
  return value;
@@ -350,6 +378,13 @@ var SSDiagram = (() => {
350
378
  const prototype = Object.getPrototypeOf(value);
351
379
  return prototype === Object.prototype || prototype === null;
352
380
  }
381
+ function mapElements(value, path) {
382
+ if (value === void 0 || value === null) return [];
383
+ return Array.from(
384
+ requireArray(value, path),
385
+ (element, index) => requireStructure(element, `${path}[${index}]`)
386
+ );
387
+ }
353
388
  function requireArray(value, path) {
354
389
  if (!Array.isArray(value)) throw new DiagramDocumentError("expected an array", path);
355
390
  return value;
@@ -358,6 +393,10 @@ var SSDiagram = (() => {
358
393
  if (typeof value !== "string") throw new DiagramDocumentError("expected a string", path);
359
394
  return value;
360
395
  }
396
+ function requirePortDynamicMode(value, path) {
397
+ const mode = requireString(value, path);
398
+ return toPortDynamicMode(mode);
399
+ }
361
400
  function requireIdentifier(value, path) {
362
401
  const id = requireString(value, path);
363
402
  if (id.trim().length === 0) throw new DiagramDocumentError("identifier cannot be empty", path);
@@ -399,6 +438,24 @@ var SSDiagram = (() => {
399
438
  if (this.actions.get(action.id) === action) this.actions.delete(action.id);
400
439
  };
401
440
  }
441
+ /**
442
+ * Registers one action per id from a table keyed by TId, in key order.
443
+ * Because the table is a total Record, adding a member to TId without adding
444
+ * its action stops compiling instead of producing a command that silently
445
+ * does nothing.
446
+ */
447
+ registerAll(actions) {
448
+ const ids = Object.keys(actions);
449
+ const disposers = ids.map((id) => {
450
+ const action = actions[id];
451
+ return this.register({
452
+ id,
453
+ canExecute: (context) => action.canExecute(context),
454
+ execute: (context) => action.execute(context)
455
+ });
456
+ });
457
+ return () => disposers.forEach((dispose) => dispose());
458
+ }
402
459
  get(id) {
403
460
  return this.actions.get(id) ?? null;
404
461
  }
@@ -554,7 +611,7 @@ var SSDiagram = (() => {
554
611
  globalError: state.globalError === null ? null : { ...state.globalError },
555
612
  nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
556
613
  active: node.active,
557
- error: node.error === null ? null : { ...node.error },
614
+ errors: cloneDiagramNodeErrors(node.errors),
558
615
  ports: {
559
616
  in: Object.fromEntries(Object.entries(node.ports.in).map(([portId, port]) => [portId, { ...port }])),
560
617
  out: Object.fromEntries(Object.entries(node.ports.out).map(([portId, port]) => [portId, { ...port }]))
@@ -611,10 +668,17 @@ var SSDiagram = (() => {
611
668
  function createDiagramNodeRuntimeState() {
612
669
  return {
613
670
  active: false,
614
- error: null,
671
+ errors: {},
615
672
  ports: { in: {}, out: {} }
616
673
  };
617
674
  }
675
+ function cloneDiagramNodeErrors(errors) {
676
+ const result = {};
677
+ if (errors === void 0 || errors === null) return result;
678
+ if (errors.runtime !== void 0) result.runtime = { ...errors.runtime };
679
+ if (errors.load !== void 0) result.load = { ...errors.load };
680
+ return result;
681
+ }
618
682
 
619
683
  // src/core/view-state.ts
620
684
  var DIAGRAM_VIEW_STATE_VERSION = 1;
@@ -699,7 +763,7 @@ var SSDiagram = (() => {
699
763
  function copyJsonObject(value) {
700
764
  if (value === void 0) return {};
701
765
  const result = {};
702
- for (const [key, item] of Object.entries(value)) result[key] = copyJsonValue(item);
766
+ for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue(item));
703
767
  return result;
704
768
  }
705
769
  function copyParameters(value) {
@@ -730,7 +794,7 @@ var SSDiagram = (() => {
730
794
  this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
731
795
  this.availableTypes = [...init.availableTypes ?? []];
732
796
  this.isDynamic = init.isDynamic ?? false;
733
- this.dynamicMode = init.dynamicMode ?? "";
797
+ this.dynamicMode = toPortDynamicMode(init.dynamicMode);
734
798
  this.isSibling = init.isSibling ?? false;
735
799
  this.metadata = copyJsonObject(init.metadata);
736
800
  }
@@ -897,8 +961,6 @@ var SSDiagram = (() => {
897
961
  this.dragStart = [];
898
962
  // group-drag origin
899
963
  this.dragAnchor = { wx: 0, wy: 0 };
900
- this.dragDX = 0;
901
- this.dragDY = 0;
902
964
  this.panning = false;
903
965
  this.panX = 0;
904
966
  this.panY = 0;
@@ -1482,7 +1544,7 @@ var SSDiagram = (() => {
1482
1544
  setNodeParamValue(nodeId, name, value) {
1483
1545
  return this.updateNodeState(nodeId, "set node parameter", (node) => {
1484
1546
  if (value === void 0) delete node.paramValues[name];
1485
- else node.paramValues[name] = value;
1547
+ else setJsonKey(node.paramValues, name, value);
1486
1548
  });
1487
1549
  }
1488
1550
  setShowNodeMessages(show) {
@@ -1565,7 +1627,7 @@ var SSDiagram = (() => {
1565
1627
  for (const node of this.nodes) {
1566
1628
  if (node.loadError.length === 0) continue;
1567
1629
  const state = createDiagramNodeRuntimeState();
1568
- state.error = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
1630
+ state.errors.load = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
1569
1631
  this.runtimeState.nodes[node.id] = state;
1570
1632
  }
1571
1633
  if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
@@ -1765,15 +1827,16 @@ var SSDiagram = (() => {
1765
1827
  this.runtimePulse = Math.max(
1766
1828
  this.runtimePulse,
1767
1829
  this.runtimeState.globalError?.pulse ?? 0,
1768
- ...Object.values(this.runtimeState.nodes).map((node) => node.error?.pulse ?? 0)
1830
+ ...Object.values(this.runtimeState.nodes).flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0])
1769
1831
  );
1770
1832
  for (const node of this.nodes) {
1771
- const error = this.runtimeState.nodes[node.id]?.error ?? null;
1772
- const previousError = previous.nodes[node.id]?.error ?? null;
1773
- node.runtimeError = error?.kind === "runtime" ? error.message : "";
1774
- node.loadError = error?.kind === "load" ? error.message : "";
1775
- if (error?.kind === "runtime") {
1776
- if (previousError?.kind !== "runtime" || previousError.pulse !== error.pulse) {
1833
+ const errors = this.runtimeState.nodes[node.id]?.errors;
1834
+ const previousRuntime = previous.nodes[node.id]?.errors.runtime;
1835
+ const runtimeError = errors?.runtime;
1836
+ node.runtimeError = runtimeError?.message ?? "";
1837
+ node.loadError = errors?.load?.message ?? "";
1838
+ if (runtimeError !== void 0) {
1839
+ if (previousRuntime === void 0 || previousRuntime.pulse !== runtimeError.pulse) {
1777
1840
  node.errorFlashStart = performance.now();
1778
1841
  }
1779
1842
  } else {
@@ -1822,7 +1885,9 @@ var SSDiagram = (() => {
1822
1885
  const state = this.getRuntimeState();
1823
1886
  const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
1824
1887
  state.nodes[id] = nodeState;
1825
- nodeState.error = message.length === 0 ? null : { kind, message, pulse: ++this.runtimePulse };
1888
+ if (message.length === 0) delete nodeState.errors[kind];
1889
+ else if (kind === "load") nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
1890
+ else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
1826
1891
  this.runtimeState = state;
1827
1892
  this.emitRuntimeStateChanged();
1828
1893
  return true;
@@ -1837,8 +1902,9 @@ var SSDiagram = (() => {
1837
1902
  if (kind === void 0 || kind === "load") node.loadError = "";
1838
1903
  const state = this.getRuntimeState();
1839
1904
  const nodeState = state.nodes[id];
1840
- if (nodeState !== void 0 && (kind === void 0 || nodeState.error?.kind === kind)) {
1841
- nodeState.error = node.runtimeError.length > 0 ? { kind: "runtime", message: node.runtimeError, pulse: ++this.runtimePulse } : node.loadError.length > 0 ? { kind: "load", message: node.loadError, pulse: ++this.runtimePulse } : null;
1905
+ if (nodeState !== void 0) {
1906
+ if (kind === void 0 || kind === "runtime") delete nodeState.errors.runtime;
1907
+ if (kind === void 0 || kind === "load") delete nodeState.errors.load;
1842
1908
  }
1843
1909
  this.runtimeState = state;
1844
1910
  this.emitRuntimeStateChanged();
@@ -3634,6 +3700,13 @@ ${runtime.error}`;
3634
3700
  }
3635
3701
  }
3636
3702
  }
3703
+ /**
3704
+ * Whether anyone is listening. Lets a subject skip building a payload that
3705
+ * is expensive to produce and that nothing would read.
3706
+ */
3707
+ hasHandlers(event) {
3708
+ return (this.handlers.get(event)?.size ?? 0) > 0;
3709
+ }
3637
3710
  clearEventHandlers() {
3638
3711
  this.handlers.clear();
3639
3712
  }
@@ -3655,7 +3728,7 @@ ${runtime.error}`;
3655
3728
  this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
3656
3729
  this.availableTypes = init.availableTypes ?? [];
3657
3730
  this.isDynamic = init.isDynamic ?? false;
3658
- this.dynamicMode = init.dynamicMode ?? "";
3731
+ this.dynamicMode = toPortDynamicMode(init.dynamicMode);
3659
3732
  this.isSibling = init.isSibling ?? false;
3660
3733
  }
3661
3734
  clone() {
@@ -3756,7 +3829,7 @@ ${runtime.error}`;
3756
3829
  this.destroyed = false;
3757
3830
  this.fullscreen = false;
3758
3831
  this.fullscreenButtonVisible = true;
3759
- this.linkValidator = null;
3832
+ this.fullscreenLabels = { enter: "Enter fullscreen", exit: "Exit fullscreen" };
3760
3833
  this.contextActions = new DiagramActionRegistry();
3761
3834
  this.div = options.div;
3762
3835
  this.catalog = options.catalog;
@@ -3770,6 +3843,7 @@ ${runtime.error}`;
3770
3843
  gridSize: options.gridSize
3771
3844
  });
3772
3845
  this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
3846
+ this.fullscreenLabels = options.fullscreenLabels ?? this.fullscreenLabels;
3773
3847
  this.prepareFullscreenButtonHost();
3774
3848
  this.fullscreenButton = this.createFullscreenButton();
3775
3849
  this.updateFullscreenButton();
@@ -3779,7 +3853,6 @@ ${runtime.error}`;
3779
3853
  this.updateZoomLabel();
3780
3854
  }
3781
3855
  setLinkValidator(validator) {
3782
- this.linkValidator = validator;
3783
3856
  this.canvas.setLinkValidator(validator === null ? null : ({ fromNode, fromPort, toNode, toPort }) => validator({
3784
3857
  fromNode: this.fromCanvasNode(fromNode),
3785
3858
  fromPort: this.fromCanvasPort(fromPort),
@@ -3853,21 +3926,25 @@ ${runtime.error}`;
3853
3926
  removeLink(link) {
3854
3927
  this.canvas.removeLink(this.toCanvasLink(link));
3855
3928
  }
3929
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3856
3930
  addPort(nodeId, direction, port) {
3857
- this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
3931
+ return this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
3858
3932
  }
3933
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3859
3934
  removePort(nodeId, direction, portId) {
3860
- this.canvas.removePort(nodeId, direction, portId);
3935
+ return this.canvas.removePort(nodeId, direction, portId);
3861
3936
  }
3937
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3862
3938
  updatePortType(nodeId, direction, portId, type) {
3863
- this.canvas.updatePortType(nodeId, direction, portId, type);
3939
+ return this.canvas.updatePortType(nodeId, direction, portId, type);
3864
3940
  }
3865
3941
  updatePort(nodeId, direction, portId, patch) {
3866
3942
  return this.canvas.updatePort(nodeId, direction, portId, patch);
3867
3943
  }
3944
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3868
3945
  setNodePorts(nodeId, inPorts, outPorts) {
3869
3946
  const current = this.canvas.findNode(nodeId);
3870
- if (current === void 0) return;
3947
+ if (current === void 0) return false;
3871
3948
  const convert = (port) => ({
3872
3949
  id: port.key,
3873
3950
  name: port.name,
@@ -3886,13 +3963,15 @@ ${runtime.error}`;
3886
3963
  for (const sibling of current.outPorts.filter((port) => port.isSibling)) {
3887
3964
  if (!nextOut.some((port) => port.id === sibling.id)) nextOut.push(sibling.toInit());
3888
3965
  }
3889
- this.canvas.setNodePorts(nodeId, nextIn, nextOut);
3966
+ return this.canvas.setNodePorts(nodeId, nextIn, nextOut);
3890
3967
  }
3968
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3891
3969
  updateNode(nodeId, patch) {
3892
- this.canvas.updateNode(nodeId, patch);
3970
+ return this.canvas.updateNode(nodeId, patch);
3893
3971
  }
3972
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3894
3973
  setNodeMessage(nodeId, message) {
3895
- this.canvas.updateNode(nodeId, { message });
3974
+ return this.canvas.updateNode(nodeId, { message });
3896
3975
  }
3897
3976
  setNodeError(nodeId, message, options = {}) {
3898
3977
  return this.canvas.setNodeError(nodeId, message, options);
@@ -3918,11 +3997,13 @@ ${runtime.error}`;
3918
3997
  setGlobalError(message, kind = "invalid") {
3919
3998
  this.canvas.setGlobalError(message, kind);
3920
3999
  }
4000
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3921
4001
  setNodeParamValue(nodeId, paramName, value) {
3922
- this.canvas.setNodeParamValue(nodeId, paramName, value);
4002
+ return this.canvas.setNodeParamValue(nodeId, paramName, value);
3923
4003
  }
4004
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3924
4005
  setNodeName(nodeId, value) {
3925
- this.canvas.updateNode(nodeId, { name: value });
4006
+ return this.canvas.updateNode(nodeId, { name: value });
3926
4007
  }
3927
4008
  /** Groups host-driven document edits into one undo/redo operation. */
3928
4009
  transaction(label, action) {
@@ -4001,6 +4082,14 @@ ${runtime.error}`;
4001
4082
  isFullscreenButtonVisible() {
4002
4083
  return this.fullscreenButtonVisible;
4003
4084
  }
4085
+ /** Text of the fullscreen button, for a host that renders in another language. */
4086
+ setFullscreenLabels(labels) {
4087
+ this.fullscreenLabels = labels;
4088
+ this.updateFullscreenButton();
4089
+ }
4090
+ getFullscreenLabels() {
4091
+ return { ...this.fullscreenLabels };
4092
+ }
4004
4093
  /** Updates only the control's state after the host changed its own layout. */
4005
4094
  setFullscreenState(fullscreen) {
4006
4095
  if (this.fullscreen === fullscreen) return;
@@ -4223,7 +4312,7 @@ ${runtime.error}`;
4223
4312
  }
4224
4313
  updateFullscreenButton() {
4225
4314
  const fullscreen = this.fullscreen;
4226
- const label = fullscreen ? "Exit fullscreen" : "Enter fullscreen";
4315
+ const label = fullscreen ? this.fullscreenLabels.exit : this.fullscreenLabels.enter;
4227
4316
  const path = fullscreen ? "M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5" : "M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5";
4228
4317
  this.fullscreenButton.className = fullscreen ? "ssdiagram-fullscreen-button is-active" : "ssdiagram-fullscreen-button";
4229
4318
  this.fullscreenButton.title = label;
@@ -4337,50 +4426,43 @@ ${runtime.error}`;
4337
4426
  }
4338
4427
  registerContextActions() {
4339
4428
  const permissions = () => this.canvas.getInteractionPermissions();
4340
- this.contextActions.register({
4341
- id: "undo",
4342
- canExecute: () => this.canUndo(),
4343
- execute: () => this.undo()
4344
- });
4345
- this.contextActions.register({
4346
- id: "redo",
4347
- canExecute: () => this.canRedo(),
4348
- execute: () => this.redo()
4349
- });
4350
- this.contextActions.register({
4351
- id: "cut",
4352
- canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
4353
- execute: () => this.cutSelection()
4354
- });
4355
- this.contextActions.register({
4356
- id: "copy",
4357
- canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
4358
- execute: () => this.copySelection()
4359
- });
4360
- this.contextActions.register({
4361
- id: "paste",
4362
- canExecute: () => this.canvas.hasClipboard() && permissions().paste,
4363
- execute: () => this.pasteSelection()
4364
- });
4365
- this.contextActions.register({
4366
- id: "open",
4367
- canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
4368
- execute: ({ nodes }) => this.emit("nodeOpen", { nodes })
4369
- });
4370
- this.contextActions.register({
4371
- id: "delete",
4372
- canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
4373
- execute: () => this.canvas.deleteSelection()
4374
- });
4375
- this.contextActions.register({
4376
- id: "properties",
4377
- canExecute: ({ nodes }) => nodes.length > 0,
4378
- execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
4379
- });
4380
- this.contextActions.register({
4381
- id: "help",
4382
- canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
4383
- execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
4429
+ this.contextActions.registerAll({
4430
+ undo: {
4431
+ canExecute: () => this.canUndo(),
4432
+ execute: () => this.undo()
4433
+ },
4434
+ redo: {
4435
+ canExecute: () => this.canRedo(),
4436
+ execute: () => this.redo()
4437
+ },
4438
+ cut: {
4439
+ canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
4440
+ execute: () => this.cutSelection()
4441
+ },
4442
+ copy: {
4443
+ canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
4444
+ execute: () => this.copySelection()
4445
+ },
4446
+ paste: {
4447
+ canExecute: () => this.canvas.hasClipboard() && permissions().paste,
4448
+ execute: () => this.pasteSelection()
4449
+ },
4450
+ open: {
4451
+ canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
4452
+ execute: ({ nodes }) => this.emit("nodeOpen", { nodes })
4453
+ },
4454
+ delete: {
4455
+ canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
4456
+ execute: () => this.canvas.deleteSelection()
4457
+ },
4458
+ properties: {
4459
+ canExecute: ({ nodes }) => nodes.length > 0,
4460
+ execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
4461
+ },
4462
+ help: {
4463
+ canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
4464
+ execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
4465
+ }
4384
4466
  });
4385
4467
  }
4386
4468
  contextActionContext() {
@@ -4548,19 +4630,26 @@ ${runtime.error}`;
4548
4630
  // imported node ("Element type is missing from the palette"). Node.id
4549
4631
  // keeps its original casing so save/load round-trips unchanged.
4550
4632
  addNodeType(node) {
4551
- const n = node instanceof Node ? node : new Node(node);
4633
+ const n = (node instanceof Node ? node : new Node(node)).clone();
4552
4634
  this.nodeTypes.set(n.id.toLowerCase(), n);
4553
- this.emit("nodeTypesChanged", this.getNodeTypes());
4635
+ this.emitNodeTypesChanged();
4554
4636
  }
4555
4637
  removeNodeType(id) {
4556
4638
  this.nodeTypes.delete(id.toLowerCase());
4557
- this.emit("nodeTypesChanged", this.getNodeTypes());
4639
+ this.emitNodeTypesChanged();
4558
4640
  }
4559
4641
  getNodeType(id) {
4560
- return this.nodeTypes.get(id.toLowerCase()) ?? null;
4642
+ return this.nodeTypes.get(id.toLowerCase())?.clone() ?? null;
4561
4643
  }
4562
4644
  getNodeTypes() {
4563
- return Array.from(this.nodeTypes.values());
4645
+ return Array.from(this.nodeTypes.values(), (node) => node.clone());
4646
+ }
4647
+ // Node definitions are mutable, so the payload has to be a copy of each one.
4648
+ // Building a catalog is a loop of addNodeType calls, which would make that
4649
+ // quadratic; skipping it while nobody listens keeps catalog construction --
4650
+ // where no subscriber exists yet -- as cheap as it was.
4651
+ emitNodeTypesChanged() {
4652
+ if (this.hasHandlers("nodeTypesChanged")) this.emit("nodeTypesChanged", this.getNodeTypes());
4564
4653
  }
4565
4654
  };
4566
4655
 
@@ -4873,69 +4962,141 @@ ${runtime.error}`;
4873
4962
  });
4874
4963
  return { nodes, nodeErrors };
4875
4964
  }
4965
+ function isRecord(value) {
4966
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4967
+ }
4968
+ function asRecord(value) {
4969
+ return isRecord(value) ? value : void 0;
4970
+ }
4971
+ function asArray(value) {
4972
+ return Array.isArray(value) ? value : [];
4973
+ }
4974
+ function asString(value) {
4975
+ return typeof value === "string" ? value : "";
4976
+ }
4977
+ function asFiniteNumber(value) {
4978
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
4979
+ }
4876
4980
  function nodeName(node) {
4877
- const settings = node?.Settings;
4878
- const parameters = settings?.Parameters;
4879
- const nameParam = parameters?.Name;
4880
- const nameVal = nameParam?.Value;
4881
- if (typeof nameVal === "string" && nameVal.length > 0)
4882
- return nameVal;
4883
- if (typeof node?.Figure === "string" && node.Figure.length > 0)
4884
- return node.Figure;
4885
- return typeof node?.Key === "string" ? node.Key : "";
4981
+ const named = asRecord(asRecord(asRecord(node.Settings)?.Parameters)?.Name)?.Value;
4982
+ if (typeof named === "string" && named.length > 0)
4983
+ return named;
4984
+ const figure = asString(node.Figure);
4985
+ return figure.length > 0 ? figure : asString(node.Key);
4886
4986
  }
4887
4987
  function parseRawScheme(raw) {
4888
4988
  const nodes = [];
4889
4989
  const links = [];
4890
- const root = raw;
4891
- const content = root?.Content;
4892
- const value = content?.Value;
4893
- const scheme = value?.Scheme;
4894
- const model = scheme?.Model;
4895
- if (!model)
4990
+ const model = asRecord(asRecord(asRecord(asRecord(raw)?.Content)?.Value)?.Scheme)?.Model;
4991
+ const fields = asRecord(model);
4992
+ if (fields === void 0)
4896
4993
  return { nodes, links };
4897
- for (const raw2 of model.Nodes ?? []) {
4898
- const key = raw2?.Key;
4994
+ for (const entry of asArray(fields.Nodes)) {
4995
+ const node = asRecord(entry);
4996
+ if (node === void 0)
4997
+ continue;
4998
+ const key = node.Key;
4899
4999
  if (typeof key !== "string" || key.length === 0)
4900
5000
  continue;
4901
5001
  nodes.push({
4902
5002
  id: key,
4903
- typeId: typeof raw2.TypeId === "string" ? raw2.TypeId : "",
4904
- name: nodeName(raw2),
4905
- x: typeof raw2.X === "number" ? raw2.X : 0,
4906
- y: typeof raw2.Y === "number" ? raw2.Y : 0
5003
+ typeId: asString(node.TypeId),
5004
+ name: nodeName(node),
5005
+ x: asFiniteNumber(node.X),
5006
+ y: asFiniteNumber(node.Y)
4907
5007
  });
4908
5008
  }
4909
- for (const raw2 of model.Links ?? []) {
4910
- const from = raw2?.From;
4911
- const to = raw2?.To;
5009
+ for (const entry of asArray(fields.Links)) {
5010
+ const link = asRecord(entry);
5011
+ if (link === void 0)
5012
+ continue;
5013
+ const from = link.From;
5014
+ const to = link.To;
4912
5015
  if (typeof from !== "string" || typeof to !== "string")
4913
5016
  continue;
4914
5017
  links.push({
4915
5018
  from,
4916
- fromPort: typeof raw2.FromPort === "string" ? raw2.FromPort : "",
5019
+ fromPort: asString(link.FromPort),
4917
5020
  to,
4918
- toPort: typeof raw2.ToPort === "string" ? raw2.ToPort : ""
5021
+ toPort: asString(link.ToPort)
4919
5022
  });
4920
5023
  }
4921
5024
  return { nodes, links };
4922
5025
  }
5026
+ function parsePalettePorts(value) {
5027
+ const ports = [];
5028
+ for (const entry of asArray(value)) {
5029
+ const port = asRecord(entry);
5030
+ if (port === void 0)
5031
+ continue;
5032
+ const key = asString(port.key);
5033
+ if (key.length === 0)
5034
+ continue;
5035
+ ports.push({
5036
+ key,
5037
+ name: asString(port.name),
5038
+ type: asString(port.type),
5039
+ maxLinks: asFiniteNumber(port.maxLinks),
5040
+ availableTypes: asArray(port.availableTypes).filter((item) => typeof item === "string"),
5041
+ isDynamic: port.isDynamic === true,
5042
+ dynamicMode: toPortDynamicMode(port.dynamicMode)
5043
+ });
5044
+ }
5045
+ return ports;
5046
+ }
5047
+ function parsePalette(value) {
5048
+ const socketTypes = [];
5049
+ const elements = [];
5050
+ const root = asRecord(value);
5051
+ if (root === void 0)
5052
+ return { socketTypes, elements };
5053
+ for (const entry of asArray(root.socketTypes)) {
5054
+ const socket = asRecord(entry);
5055
+ const name = asString(socket?.name);
5056
+ if (name.length === 0)
5057
+ continue;
5058
+ socketTypes.push({ name, color: asString(socket?.color) });
5059
+ }
5060
+ for (const entry of asArray(root.elements)) {
5061
+ const element = asRecord(entry);
5062
+ const typeId = asString(element?.typeId);
5063
+ if (element === void 0 || typeId.length === 0)
5064
+ continue;
5065
+ elements.push({
5066
+ typeId,
5067
+ name: asString(element.name),
5068
+ groupName: asString(element.groupName),
5069
+ icon: asString(element.icon),
5070
+ inPorts: parsePalettePorts(element.inPorts),
5071
+ outPorts: parsePalettePorts(element.outPorts)
5072
+ });
5073
+ }
5074
+ return { socketTypes, elements };
5075
+ }
4923
5076
  async function renderScheme(div, paletteUrl, scheme, options = {}) {
4924
5077
  return renderSchemeAtRevision(div, paletteUrl, scheme, beginRender(div), options);
4925
5078
  }
4926
5079
  async function renderSchemeAtRevision(div, paletteUrl, scheme, revision, options) {
4927
5080
  if (renderRevisions.get(div) !== revision)
4928
5081
  return null;
4929
- const palette = await fetch(paletteUrl).then((r) => r.json());
5082
+ const response = await fetch(paletteUrl);
5083
+ if (!response.ok)
5084
+ throw new Error(`Palette request to ${paletteUrl} failed with status ${response.status}.`);
5085
+ const palette = parsePalette(await response.json());
4930
5086
  if (renderRevisions.get(div) !== revision)
4931
5087
  return null;
4932
5088
  const catalog = buildCatalog(palette);
4933
5089
  disposeActiveRender(div);
4934
5090
  div.classList.remove("ss-diagram-error");
4935
5091
  div.replaceChildren();
5092
+ const [enterLabel, exitLabel] = (div.dataset.diagramFullscreen ?? "").split("|");
4936
5093
  let diagram;
4937
5094
  try {
4938
- diagram = new StockSharpDiagram({ div, catalog });
5095
+ diagram = new StockSharpDiagram({
5096
+ div,
5097
+ catalog,
5098
+ ...enterLabel && exitLabel ? { fullscreenLabels: { enter: enterLabel, exit: exitLabel } } : {}
5099
+ });
4939
5100
  } catch (error) {
4940
5101
  div.replaceChildren();
4941
5102
  throw error;
@@ -5085,6 +5246,14 @@ ${runtime.error}`;
5085
5246
  disconnectedHostObserver.disconnect();
5086
5247
  disconnectedHostObserver = null;
5087
5248
  }
5249
+ function errorTexts(div) {
5250
+ const parts = (div.dataset.diagramErrors ?? "").split("|");
5251
+ return {
5252
+ load: parts[0] || "Diagram source could not be loaded.",
5253
+ empty: parts[1] || "Diagram is empty or malformed.",
5254
+ draw: parts[2] || "Diagram could not be rendered."
5255
+ };
5256
+ }
5088
5257
  function note(div, message, revision) {
5089
5258
  if (renderRevisions.get(div) !== revision) return;
5090
5259
  disposeActiveRender(div);
@@ -5093,53 +5262,51 @@ ${runtime.error}`;
5093
5262
  }
5094
5263
  async function renderFromSource(div, paletteUrl, srcUrl, options = {}) {
5095
5264
  const revision = beginRender(div);
5096
- const errors = (div.dataset.diagramErrors ?? "").split("|");
5097
- const [errLoad = "Diagram source could not be loaded.", errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
5265
+ const errors = errorTexts(div);
5098
5266
  let raw;
5099
5267
  try {
5100
5268
  const resp = await fetch(srcUrl);
5101
5269
  if (!resp.ok) {
5102
- note(div, errLoad, revision);
5270
+ note(div, errors.load, revision);
5103
5271
  return null;
5104
5272
  }
5105
5273
  const text = (await resp.text()).replace(/^/, "");
5106
5274
  raw = JSON.parse(text);
5107
5275
  } catch {
5108
- note(div, errLoad, revision);
5276
+ note(div, errors.load, revision);
5109
5277
  return null;
5110
5278
  }
5111
5279
  const scheme = parseRawScheme(raw);
5112
5280
  if (scheme.nodes.length === 0) {
5113
- note(div, errEmpty, revision);
5281
+ note(div, errors.empty, revision);
5114
5282
  return null;
5115
5283
  }
5116
5284
  try {
5117
5285
  return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
5118
5286
  } catch {
5119
- note(div, errDraw, revision);
5287
+ note(div, errors.draw, revision);
5120
5288
  return null;
5121
5289
  }
5122
5290
  }
5123
5291
  async function renderFromInline(div, paletteUrl, json, options = {}) {
5124
5292
  const revision = beginRender(div);
5125
- const errors = (div.dataset.diagramErrors ?? "").split("|");
5126
- const [, errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
5293
+ const errors = errorTexts(div);
5127
5294
  let raw;
5128
5295
  try {
5129
5296
  raw = JSON.parse(json);
5130
5297
  } catch {
5131
- note(div, errEmpty, revision);
5298
+ note(div, errors.empty, revision);
5132
5299
  return null;
5133
5300
  }
5134
5301
  const scheme = parseRawScheme(raw);
5135
5302
  if (scheme.nodes.length === 0) {
5136
- note(div, errEmpty, revision);
5303
+ note(div, errors.empty, revision);
5137
5304
  return null;
5138
5305
  }
5139
5306
  try {
5140
5307
  return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
5141
5308
  } catch {
5142
- note(div, errDraw, revision);
5309
+ note(div, errors.draw, revision);
5143
5310
  return null;
5144
5311
  }
5145
5312
  }