@stocksharp/diagram 0.2.3 → 0.4.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/dist/esm/canvas-renderer.js +79 -28
  2. package/dist/esm/canvas-renderer.js.map +1 -1
  3. package/dist/esm/core/action-registry.js +24 -0
  4. package/dist/esm/core/action-registry.js.map +1 -1
  5. package/dist/esm/core/document.js +134 -28
  6. package/dist/esm/core/document.js.map +1 -1
  7. package/dist/esm/core/json.js +17 -0
  8. package/dist/esm/core/json.js.map +1 -0
  9. package/dist/esm/core/model.js +8 -0
  10. package/dist/esm/core/model.js.map +1 -1
  11. package/dist/esm/core/state.js +16 -2
  12. package/dist/esm/core/state.js.map +1 -1
  13. package/dist/esm/diagram/catalog.js +17 -5
  14. package/dist/esm/diagram/catalog.js.map +1 -1
  15. package/dist/esm/diagram/event-emitter.js +10 -0
  16. package/dist/esm/diagram/event-emitter.js.map +1 -1
  17. package/dist/esm/diagram/stocksharp-diagram.js +58 -56
  18. package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
  19. package/dist/esm/diagram/types.js +2 -1
  20. package/dist/esm/diagram/types.js.map +1 -1
  21. package/dist/esm/embed.js +121 -38
  22. package/dist/esm/embed.js.map +1 -1
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/ssdiagram.js +380 -150
  26. package/dist/ssdiagram.js.map +4 -4
  27. package/dist/types/canvas-renderer.d.ts +11 -6
  28. package/dist/types/canvas-renderer.d.ts.map +1 -1
  29. package/dist/types/core/action-registry.d.ts +7 -0
  30. package/dist/types/core/action-registry.d.ts.map +1 -1
  31. package/dist/types/core/document.d.ts +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 +45 -2
  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 +6 -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 +29 -22
  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 +84 -29
  56. package/src/core/action-registry.ts +25 -0
  57. package/src/core/document.ts +147 -31
  58. package/src/core/json.ts +16 -0
  59. package/src/core/model.ts +55 -2
  60. package/src/core/state.ts +41 -3
  61. package/src/diagram/api.ts +6 -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 +79 -76
  66. package/src/diagram/types.ts +6 -5
  67. package/src/embed.ts +140 -39
  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,32 +83,47 @@ 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
  });
112
+ const rawZones = mapElements(input.zones, "$.zones");
113
+ const usedZoneIds = /* @__PURE__ */ new Set();
114
+ const zones = rawZones.map((zone, index) => {
115
+ const normalized = normalizeZone(zone, `$.zones[${index}]`);
116
+ if (usedZoneIds.has(normalized.id)) {
117
+ throw new DiagramDocumentError(`duplicate zone id "${normalized.id}"`, `$.zones[${index}].id`);
118
+ }
119
+ usedZoneIds.add(normalized.id);
120
+ return normalized;
121
+ });
95
122
  const document2 = {
96
123
  version: DIAGRAM_DOCUMENT_VERSION,
97
124
  nodes,
98
125
  links,
126
+ zones,
99
127
  metadata: cloneJsonObject(input.metadata ?? {}, "$.metadata")
100
128
  };
101
129
  validateDocument(document2);
@@ -117,7 +145,7 @@ var SSDiagram = (() => {
117
145
  throw new DiagramDocumentError(reason);
118
146
  }
119
147
  }
120
- const root = requireObject(value, "$");
148
+ const root = requireStructure(value, "$");
121
149
  const version = requireNumber(root.version, "$.version");
122
150
  if (version !== DIAGRAM_DOCUMENT_VERSION) {
123
151
  throw new DiagramDocumentError(`unsupported document version ${version}`, "$.version");
@@ -126,12 +154,16 @@ var SSDiagram = (() => {
126
154
  version: DIAGRAM_DOCUMENT_VERSION,
127
155
  nodes: requireArray(root.nodes, "$.nodes").map((node, index) => parseNode(node, `$.nodes[${index}]`)),
128
156
  links: requireArray(root.links, "$.links").map((link, index) => parseLink(link, `$.links[${index}]`)),
157
+ // Zones arrived after the first documents were written, so their absence is not a fault:
158
+ // an older file simply has none, and reading it must not need a migration step.
159
+ zones: root.zones === void 0 || root.zones === null ? [] : requireArray(root.zones, "$.zones").map((zone, index) => normalizeZone(zone, `$.zones[${index}]`)),
129
160
  metadata: cloneJsonObject(root.metadata, "$.metadata")
130
161
  };
131
162
  validateDocument(document2);
132
163
  return document2;
133
164
  }
134
- function normalizeNode(input, path) {
165
+ function normalizeNode(value, path) {
166
+ const input = requireStructure(value, path);
135
167
  const id = requireIdentifier(input.id, `${path}.id`);
136
168
  return {
137
169
  id,
@@ -146,28 +178,30 @@ var SSDiagram = (() => {
146
178
  icon: requireString(input.icon ?? "", `${path}.icon`),
147
179
  message: requireString(input.message ?? "", `${path}.message`),
148
180
  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}]`)),
181
+ inPorts: mapElements(input.inPorts, `${path}.inPorts`).map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
182
+ outPorts: mapElements(input.outPorts, `${path}.outPorts`).map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
183
+ parameters: mapElements(input.parameters, `${path}.parameters`).map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
152
184
  paramValues: cloneStringRecord(input.paramValues ?? {}, `${path}.paramValues`),
153
185
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
154
186
  };
155
187
  }
156
- function normalizePort(input, path) {
188
+ function normalizePort(value, path) {
189
+ const input = requireStructure(value, path);
157
190
  return {
158
191
  id: requireIdentifier(input.id, `${path}.id`),
159
192
  name: requireString(input.name, `${path}.name`),
160
193
  description: requireString(input.description ?? "", `${path}.description`),
161
194
  type: requireString(input.type ?? "", `${path}.type`),
162
195
  maxLinks: requireNonNegativeInteger(input.maxLinks ?? 0, `${path}.maxLinks`),
163
- availableTypes: (input.availableTypes ?? []).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
196
+ availableTypes: requireArray(input.availableTypes ?? [], `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
164
197
  isDynamic: requireBoolean(input.isDynamic ?? false, `${path}.isDynamic`),
165
- dynamicMode: requireString(input.dynamicMode ?? "", `${path}.dynamicMode`),
198
+ dynamicMode: requirePortDynamicMode(input.dynamicMode ?? "", `${path}.dynamicMode`),
166
199
  isSibling: requireBoolean(input.isSibling ?? false, `${path}.isSibling`),
167
200
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
168
201
  };
169
202
  }
170
- function normalizeParameter(input, path) {
203
+ function normalizeParameter(value, path) {
204
+ const input = requireStructure(value, path);
171
205
  return {
172
206
  name: requireIdentifier(input.name, `${path}.name`),
173
207
  displayName: requireString(input.displayName, `${path}.displayName`),
@@ -183,22 +217,50 @@ var SSDiagram = (() => {
183
217
  editorType: requireString(input.editorType, `${path}.editorType`)
184
218
  };
185
219
  }
186
- function normalizeLink(input, path) {
220
+ function normalizeLink(value, path) {
221
+ const input = requireStructure(value, path);
187
222
  return {
188
223
  id: requireIdentifier(input.id, `${path}.id`),
189
224
  from: normalizeEndpoint(input.from, `${path}.from`),
190
225
  to: normalizeEndpoint(input.to, `${path}.to`),
226
+ style: requireLinkStyle(input.style, `${path}.style`),
191
227
  metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
192
228
  };
193
229
  }
194
- function normalizeEndpoint(input, path) {
230
+ function requireLinkStyle(value, path) {
231
+ if (value === void 0 || value === null) return "solid";
232
+ if (value !== "solid" && value !== "dashed") {
233
+ throw new DiagramDocumentError(`unknown link style "${String(value)}"`, path);
234
+ }
235
+ return value;
236
+ }
237
+ function normalizeZone(value, path) {
238
+ const input = requireStructure(value, path);
239
+ return {
240
+ id: requireIdentifier(input.id, `${path}.id`),
241
+ name: requireString(input.name, `${path}.name`),
242
+ x: requireNumber(input.x, `${path}.x`),
243
+ y: requireNumber(input.y, `${path}.y`),
244
+ width: requirePositiveSize(input.width, `${path}.width`),
245
+ height: requirePositiveSize(input.height, `${path}.height`),
246
+ color: input.color === void 0 || input.color === null ? "" : requireString(input.color, `${path}.color`),
247
+ metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
248
+ };
249
+ }
250
+ function requirePositiveSize(value, path) {
251
+ const size = requireNumber(value, path);
252
+ if (!(size > 0)) throw new DiagramDocumentError("size must be greater than zero", path);
253
+ return size;
254
+ }
255
+ function normalizeEndpoint(value, path) {
256
+ const input = requireStructure(value, path);
195
257
  return {
196
258
  nodeId: requireIdentifier(input.nodeId, `${path}.nodeId`),
197
259
  portId: requireIdentifier(input.portId, `${path}.portId`)
198
260
  };
199
261
  }
200
262
  function parseNode(value, path) {
201
- const node = requireObject(value, path);
263
+ const node = requireStructure(value, path);
202
264
  return normalizeNode({
203
265
  id: requireString(node.id, `${path}.id`),
204
266
  typeId: requireString(node.typeId, `${path}.typeId`),
@@ -220,7 +282,7 @@ var SSDiagram = (() => {
220
282
  }, path);
221
283
  }
222
284
  function parsePort(value, path) {
223
- const port = requireObject(value, path);
285
+ const port = requireStructure(value, path);
224
286
  return normalizePort({
225
287
  id: requireString(port.id, `${path}.id`),
226
288
  name: requireString(port.name, `${path}.name`),
@@ -229,13 +291,13 @@ var SSDiagram = (() => {
229
291
  maxLinks: requireNumber(port.maxLinks, `${path}.maxLinks`),
230
292
  availableTypes: requireArray(port.availableTypes, `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
231
293
  isDynamic: requireBoolean(port.isDynamic, `${path}.isDynamic`),
232
- dynamicMode: requireString(port.dynamicMode, `${path}.dynamicMode`),
294
+ dynamicMode: requirePortDynamicMode(port.dynamicMode, `${path}.dynamicMode`),
233
295
  isSibling: requireBoolean(port.isSibling, `${path}.isSibling`),
234
296
  metadata: cloneJsonObject(port.metadata, `${path}.metadata`)
235
297
  }, path);
236
298
  }
237
299
  function parseParameter(value, path) {
238
- const parameter = requireObject(value, path);
300
+ const parameter = requireStructure(value, path);
239
301
  return normalizeParameter({
240
302
  name: requireString(parameter.name, `${path}.name`),
241
303
  displayName: requireString(parameter.displayName, `${path}.displayName`),
@@ -252,16 +314,17 @@ var SSDiagram = (() => {
252
314
  }, path);
253
315
  }
254
316
  function parseLink(value, path) {
255
- const link = requireObject(value, path);
317
+ const link = requireStructure(value, path);
256
318
  return normalizeLink({
257
319
  id: requireString(link.id, `${path}.id`),
258
320
  from: parseEndpoint(link.from, `${path}.from`),
259
321
  to: parseEndpoint(link.to, `${path}.to`),
322
+ style: link.style === void 0 ? void 0 : requireLinkStyle(link.style, `${path}.style`),
260
323
  metadata: cloneJsonObject(link.metadata, `${path}.metadata`)
261
324
  }, path);
262
325
  }
263
326
  function parseEndpoint(value, path) {
264
- const endpoint = requireObject(value, path);
327
+ const endpoint = requireStructure(value, path);
265
328
  return {
266
329
  nodeId: requireIdentifier(endpoint.nodeId, `${path}.nodeId`),
267
330
  portId: requireIdentifier(endpoint.portId, `${path}.portId`)
@@ -309,7 +372,7 @@ var SSDiagram = (() => {
309
372
  function cloneStringRecord(value, path) {
310
373
  const object = requireObject(value, path);
311
374
  const result = {};
312
- for (const [key, item] of Object.entries(object)) result[key] = requireString(item, `${path}.${key}`);
375
+ for (const [key, item] of Object.entries(object)) setJsonKey(result, key, requireString(item, `${path}.${key}`));
313
376
  return result;
314
377
  }
315
378
  function cloneJsonObject(value, path, ancestors = /* @__PURE__ */ new WeakSet()) {
@@ -319,7 +382,7 @@ var SSDiagram = (() => {
319
382
  const result = {};
320
383
  try {
321
384
  for (const [key, item] of Object.entries(object)) {
322
- result[key] = cloneJsonValue(item, `${path}.${key}`, ancestors);
385
+ setJsonKey(result, key, cloneJsonValue(item, `${path}.${key}`, ancestors));
323
386
  }
324
387
  } finally {
325
388
  ancestors.delete(object);
@@ -341,6 +404,12 @@ var SSDiagram = (() => {
341
404
  if (isObject(value)) return cloneJsonObject(value, path, ancestors);
342
405
  throw new DiagramDocumentError("expected a JSON value", path);
343
406
  }
407
+ function requireStructure(value, path) {
408
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
409
+ throw new DiagramDocumentError("expected an object", path);
410
+ }
411
+ return value;
412
+ }
344
413
  function requireObject(value, path) {
345
414
  if (!isObject(value)) throw new DiagramDocumentError("expected an object", path);
346
415
  return value;
@@ -350,6 +419,13 @@ var SSDiagram = (() => {
350
419
  const prototype = Object.getPrototypeOf(value);
351
420
  return prototype === Object.prototype || prototype === null;
352
421
  }
422
+ function mapElements(value, path) {
423
+ if (value === void 0 || value === null) return [];
424
+ return Array.from(
425
+ requireArray(value, path),
426
+ (element, index) => requireStructure(element, `${path}[${index}]`)
427
+ );
428
+ }
353
429
  function requireArray(value, path) {
354
430
  if (!Array.isArray(value)) throw new DiagramDocumentError("expected an array", path);
355
431
  return value;
@@ -358,6 +434,10 @@ var SSDiagram = (() => {
358
434
  if (typeof value !== "string") throw new DiagramDocumentError("expected a string", path);
359
435
  return value;
360
436
  }
437
+ function requirePortDynamicMode(value, path) {
438
+ const mode = requireString(value, path);
439
+ return toPortDynamicMode(mode);
440
+ }
361
441
  function requireIdentifier(value, path) {
362
442
  const id = requireString(value, path);
363
443
  if (id.trim().length === 0) throw new DiagramDocumentError("identifier cannot be empty", path);
@@ -399,6 +479,24 @@ var SSDiagram = (() => {
399
479
  if (this.actions.get(action.id) === action) this.actions.delete(action.id);
400
480
  };
401
481
  }
482
+ /**
483
+ * Registers one action per id from a table keyed by TId, in key order.
484
+ * Because the table is a total Record, adding a member to TId without adding
485
+ * its action stops compiling instead of producing a command that silently
486
+ * does nothing.
487
+ */
488
+ registerAll(actions) {
489
+ const ids = Object.keys(actions);
490
+ const disposers = ids.map((id) => {
491
+ const action = actions[id];
492
+ return this.register({
493
+ id,
494
+ canExecute: (context) => action.canExecute(context),
495
+ execute: (context) => action.execute(context)
496
+ });
497
+ });
498
+ return () => disposers.forEach((dispose) => dispose());
499
+ }
402
500
  get(id) {
403
501
  return this.actions.get(id) ?? null;
404
502
  }
@@ -554,7 +652,7 @@ var SSDiagram = (() => {
554
652
  globalError: state.globalError === null ? null : { ...state.globalError },
555
653
  nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
556
654
  active: node.active,
557
- error: node.error === null ? null : { ...node.error },
655
+ errors: cloneDiagramNodeErrors(node.errors),
558
656
  ports: {
559
657
  in: Object.fromEntries(Object.entries(node.ports.in).map(([portId, port]) => [portId, { ...port }])),
560
658
  out: Object.fromEntries(Object.entries(node.ports.out).map(([portId, port]) => [portId, { ...port }]))
@@ -611,10 +709,17 @@ var SSDiagram = (() => {
611
709
  function createDiagramNodeRuntimeState() {
612
710
  return {
613
711
  active: false,
614
- error: null,
712
+ errors: {},
615
713
  ports: { in: {}, out: {} }
616
714
  };
617
715
  }
716
+ function cloneDiagramNodeErrors(errors) {
717
+ const result = {};
718
+ if (errors === void 0 || errors === null) return result;
719
+ if (errors.runtime !== void 0) result.runtime = { ...errors.runtime };
720
+ if (errors.load !== void 0) result.load = { ...errors.load };
721
+ return result;
722
+ }
618
723
 
619
724
  // src/core/view-state.ts
620
725
  var DIAGRAM_VIEW_STATE_VERSION = 1;
@@ -699,7 +804,7 @@ var SSDiagram = (() => {
699
804
  function copyJsonObject(value) {
700
805
  if (value === void 0) return {};
701
806
  const result = {};
702
- for (const [key, item] of Object.entries(value)) result[key] = copyJsonValue(item);
807
+ for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue(item));
703
808
  return result;
704
809
  }
705
810
  function copyParameters(value) {
@@ -730,7 +835,7 @@ var SSDiagram = (() => {
730
835
  this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
731
836
  this.availableTypes = [...init.availableTypes ?? []];
732
837
  this.isDynamic = init.isDynamic ?? false;
733
- this.dynamicMode = init.dynamicMode ?? "";
838
+ this.dynamicMode = toPortDynamicMode(init.dynamicMode);
734
839
  this.isSibling = init.isSibling ?? false;
735
840
  this.metadata = copyJsonObject(init.metadata);
736
841
  }
@@ -805,11 +910,12 @@ var SSDiagram = (() => {
805
910
  }
806
911
  };
807
912
  var LinkModel = class {
808
- constructor(from, fromPort, to, toPort, id = "", metadata) {
913
+ constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid") {
809
914
  this.from = from;
810
915
  this.fromPort = fromPort;
811
916
  this.to = to;
812
917
  this.toPort = toPort;
918
+ this.style = style;
813
919
  this.id = id;
814
920
  this.metadata = copyJsonObject(metadata);
815
921
  }
@@ -823,6 +929,7 @@ var SSDiagram = (() => {
823
929
  fromPort: this.fromPort,
824
930
  to: this.to,
825
931
  toPort: this.toPort,
932
+ style: this.style,
826
933
  metadata: copyJsonObject(this.metadata)
827
934
  };
828
935
  }
@@ -872,6 +979,7 @@ var SSDiagram = (() => {
872
979
  constructor(opts) {
873
980
  this.nodes = [];
874
981
  this.links = [];
982
+ this.zones = [];
875
983
  this.idSeq = 1;
876
984
  this.linkSeq = 1;
877
985
  this.documentMetadata = {};
@@ -897,8 +1005,6 @@ var SSDiagram = (() => {
897
1005
  this.dragStart = [];
898
1006
  // group-drag origin
899
1007
  this.dragAnchor = { wx: 0, wy: 0 };
900
- this.dragDX = 0;
901
- this.dragDY = 0;
902
1008
  this.panning = false;
903
1009
  this.panX = 0;
904
1010
  this.panY = 0;
@@ -1203,7 +1309,7 @@ var SSDiagram = (() => {
1203
1309
  this.emit("linkValidation", { fromNode: fn, from: fp, toNode: tn, to: tp, ...validation });
1204
1310
  if (!validation.allowed) return false;
1205
1311
  }
1206
- const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata);
1312
+ const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid");
1207
1313
  if (this.links.some((l) => l.id === link.id)) return false;
1208
1314
  this.links.splice(clamp(Math.trunc(index), 0, this.links.length), 0, link);
1209
1315
  this.emit("linkAdded", { link });
@@ -1482,7 +1588,7 @@ var SSDiagram = (() => {
1482
1588
  setNodeParamValue(nodeId, name, value) {
1483
1589
  return this.updateNodeState(nodeId, "set node parameter", (node) => {
1484
1590
  if (value === void 0) delete node.paramValues[name];
1485
- else node.paramValues[name] = value;
1591
+ else setJsonKey(node.paramValues, name, value);
1486
1592
  });
1487
1593
  }
1488
1594
  setShowNodeMessages(show) {
@@ -1559,13 +1665,13 @@ var SSDiagram = (() => {
1559
1665
  if (this.links.some((existing) => existing.id === id)) {
1560
1666
  throw new Error(`ssdiagram: duplicate link id "${id}"`);
1561
1667
  }
1562
- const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata);
1668
+ const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid");
1563
1669
  if (!this.links.some((existing) => existing.key() === model.key())) this.links.push(model);
1564
1670
  }
1565
1671
  for (const node of this.nodes) {
1566
1672
  if (node.loadError.length === 0) continue;
1567
1673
  const state = createDiagramNodeRuntimeState();
1568
- state.error = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
1674
+ state.errors.load = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
1569
1675
  this.runtimeState.nodes[node.id] = state;
1570
1676
  }
1571
1677
  if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
@@ -1590,9 +1696,11 @@ var SSDiagram = (() => {
1590
1696
  fromPort: link.from.portId,
1591
1697
  to: link.to.nodeId,
1592
1698
  toPort: link.to.portId,
1699
+ style: link.style,
1593
1700
  metadata: link.metadata
1594
1701
  }))
1595
1702
  );
1703
+ this.zones = document2.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
1596
1704
  this.documentMetadata = copyJsonObject(document2.metadata);
1597
1705
  }
1598
1706
  saveDocument() {
@@ -1602,8 +1710,10 @@ var SSDiagram = (() => {
1602
1710
  id: link.id,
1603
1711
  from: { nodeId: link.from, portId: link.fromPort },
1604
1712
  to: { nodeId: link.to, portId: link.toPort },
1713
+ style: link.style,
1605
1714
  metadata: link.metadata
1606
1715
  })),
1716
+ zones: this.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) })),
1607
1717
  metadata: this.documentMetadata
1608
1718
  });
1609
1719
  }
@@ -1765,15 +1875,16 @@ var SSDiagram = (() => {
1765
1875
  this.runtimePulse = Math.max(
1766
1876
  this.runtimePulse,
1767
1877
  this.runtimeState.globalError?.pulse ?? 0,
1768
- ...Object.values(this.runtimeState.nodes).map((node) => node.error?.pulse ?? 0)
1878
+ ...Object.values(this.runtimeState.nodes).flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0])
1769
1879
  );
1770
1880
  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) {
1881
+ const errors = this.runtimeState.nodes[node.id]?.errors;
1882
+ const previousRuntime = previous.nodes[node.id]?.errors.runtime;
1883
+ const runtimeError = errors?.runtime;
1884
+ node.runtimeError = runtimeError?.message ?? "";
1885
+ node.loadError = errors?.load?.message ?? "";
1886
+ if (runtimeError !== void 0) {
1887
+ if (previousRuntime === void 0 || previousRuntime.pulse !== runtimeError.pulse) {
1777
1888
  node.errorFlashStart = performance.now();
1778
1889
  }
1779
1890
  } else {
@@ -1822,7 +1933,9 @@ var SSDiagram = (() => {
1822
1933
  const state = this.getRuntimeState();
1823
1934
  const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
1824
1935
  state.nodes[id] = nodeState;
1825
- nodeState.error = message.length === 0 ? null : { kind, message, pulse: ++this.runtimePulse };
1936
+ if (message.length === 0) delete nodeState.errors[kind];
1937
+ else if (kind === "load") nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
1938
+ else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
1826
1939
  this.runtimeState = state;
1827
1940
  this.emitRuntimeStateChanged();
1828
1941
  return true;
@@ -1837,8 +1950,9 @@ var SSDiagram = (() => {
1837
1950
  if (kind === void 0 || kind === "load") node.loadError = "";
1838
1951
  const state = this.getRuntimeState();
1839
1952
  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;
1953
+ if (nodeState !== void 0) {
1954
+ if (kind === void 0 || kind === "runtime") delete nodeState.errors.runtime;
1955
+ if (kind === void 0 || kind === "load") delete nodeState.errors.load;
1842
1956
  }
1843
1957
  this.runtimeState = state;
1844
1958
  this.emitRuntimeStateChanged();
@@ -2830,6 +2944,7 @@ var SSDiagram = (() => {
2830
2944
  this.dpr * this.offX,
2831
2945
  this.dpr * (this.offY + introDy)
2832
2946
  );
2947
+ this.drawZones();
2833
2948
  const prior = [];
2834
2949
  const hoveredNode = this.hoverPort?.node ?? this.hoverNode;
2835
2950
  for (const l of this.links) {
@@ -2842,7 +2957,7 @@ var SSDiagram = (() => {
2842
2957
  const color = state === "sel" ? this.selectionColor() : state === "hov" ? this.linkHoverColor() : baseColor;
2843
2958
  const width = state === "sel" ? 3 : state === "hov" ? 2.6 : 2;
2844
2959
  const pts = this.routeLink(a, b, /* @__PURE__ */ new Set([l.from, l.to]));
2845
- this.strokeRoute(pts, color, width, prior);
2960
+ this.strokeRoute(pts, color, width, prior, l.style);
2846
2961
  this.drawArrow(pts, color);
2847
2962
  for (let k = 1; k < pts.length; k += 1) {
2848
2963
  const [x1, y1] = pts[k - 1];
@@ -2995,6 +3110,33 @@ ${runtime.error}`;
2995
3110
  }
2996
3111
  return lines.length > 0 ? lines : [""];
2997
3112
  }
3113
+ drawZones() {
3114
+ if (this.zones.length === 0) return;
3115
+ const ctx = this.ctx;
3116
+ for (const zone of this.zones) {
3117
+ const tint = zone.color.length > 0 ? zone.color : this.opts.zoneColor ?? "#8a8f98";
3118
+ ctx.save();
3119
+ roundRect(ctx, zone.x, zone.y, zone.width, zone.height, 10);
3120
+ ctx.globalAlpha = 0.12;
3121
+ ctx.fillStyle = tint;
3122
+ ctx.fill();
3123
+ ctx.globalAlpha = 0.5;
3124
+ ctx.setLineDash([10, 6]);
3125
+ ctx.lineWidth = 1.5;
3126
+ ctx.strokeStyle = tint;
3127
+ ctx.stroke();
3128
+ ctx.restore();
3129
+ if (zone.name.length === 0) continue;
3130
+ ctx.save();
3131
+ ctx.globalAlpha = 0.75;
3132
+ ctx.fillStyle = tint;
3133
+ ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
3134
+ ctx.textAlign = "left";
3135
+ ctx.textBaseline = "top";
3136
+ ctx.fillText(zone.name, zone.x + 12, zone.y + 9, Math.max(0, zone.width - 24));
3137
+ ctx.restore();
3138
+ }
3139
+ }
2998
3140
  drawGrid() {
2999
3141
  const ctx = this.ctx;
3000
3142
  const step = this.gridSize * this.scale;
@@ -3127,10 +3269,11 @@ ${runtime.error}`;
3127
3269
  // Symmetric jump-over: any segment of THIS link hops the perpendicular
3128
3270
  // segments of links drawn before it (H over earlier V, V over earlier
3129
3271
  // H). Exactly one bridge per real crossing, consistent everywhere.
3130
- strokeRoute(pts, color, width, prior) {
3272
+ strokeRoute(pts, color, width, prior, style = "solid") {
3131
3273
  const ctx = this.ctx;
3132
3274
  ctx.strokeStyle = color;
3133
3275
  ctx.lineWidth = width;
3276
+ if (style === "dashed") ctx.setLineDash([9, 6]);
3134
3277
  ctx.lineJoin = "miter";
3135
3278
  ctx.lineCap = "butt";
3136
3279
  ctx.beginPath();
@@ -3173,6 +3316,7 @@ ${runtime.error}`;
3173
3316
  }
3174
3317
  }
3175
3318
  ctx.stroke();
3319
+ ctx.setLineDash([]);
3176
3320
  }
3177
3321
  drawArrow(pts, color) {
3178
3322
  const n = pts.length;
@@ -3634,6 +3778,13 @@ ${runtime.error}`;
3634
3778
  }
3635
3779
  }
3636
3780
  }
3781
+ /**
3782
+ * Whether anyone is listening. Lets a subject skip building a payload that
3783
+ * is expensive to produce and that nothing would read.
3784
+ */
3785
+ hasHandlers(event) {
3786
+ return (this.handlers.get(event)?.size ?? 0) > 0;
3787
+ }
3637
3788
  clearEventHandlers() {
3638
3789
  this.handlers.clear();
3639
3790
  }
@@ -3655,7 +3806,7 @@ ${runtime.error}`;
3655
3806
  this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
3656
3807
  this.availableTypes = init.availableTypes ?? [];
3657
3808
  this.isDynamic = init.isDynamic ?? false;
3658
- this.dynamicMode = init.dynamicMode ?? "";
3809
+ this.dynamicMode = toPortDynamicMode(init.dynamicMode);
3659
3810
  this.isSibling = init.isSibling ?? false;
3660
3811
  }
3661
3812
  clone() {
@@ -3757,7 +3908,6 @@ ${runtime.error}`;
3757
3908
  this.fullscreen = false;
3758
3909
  this.fullscreenButtonVisible = true;
3759
3910
  this.fullscreenLabels = { enter: "Enter fullscreen", exit: "Exit fullscreen" };
3760
- this.linkValidator = null;
3761
3911
  this.contextActions = new DiagramActionRegistry();
3762
3912
  this.div = options.div;
3763
3913
  this.catalog = options.catalog;
@@ -3781,7 +3931,6 @@ ${runtime.error}`;
3781
3931
  this.updateZoomLabel();
3782
3932
  }
3783
3933
  setLinkValidator(validator) {
3784
- this.linkValidator = validator;
3785
3934
  this.canvas.setLinkValidator(validator === null ? null : ({ fromNode, fromPort, toNode, toPort }) => validator({
3786
3935
  fromNode: this.fromCanvasNode(fromNode),
3787
3936
  fromPort: this.fromCanvasPort(fromPort),
@@ -3855,21 +4004,25 @@ ${runtime.error}`;
3855
4004
  removeLink(link) {
3856
4005
  this.canvas.removeLink(this.toCanvasLink(link));
3857
4006
  }
4007
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3858
4008
  addPort(nodeId, direction, port) {
3859
- this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
4009
+ return this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
3860
4010
  }
4011
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3861
4012
  removePort(nodeId, direction, portId) {
3862
- this.canvas.removePort(nodeId, direction, portId);
4013
+ return this.canvas.removePort(nodeId, direction, portId);
3863
4014
  }
4015
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3864
4016
  updatePortType(nodeId, direction, portId, type) {
3865
- this.canvas.updatePortType(nodeId, direction, portId, type);
4017
+ return this.canvas.updatePortType(nodeId, direction, portId, type);
3866
4018
  }
3867
4019
  updatePort(nodeId, direction, portId, patch) {
3868
4020
  return this.canvas.updatePort(nodeId, direction, portId, patch);
3869
4021
  }
4022
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3870
4023
  setNodePorts(nodeId, inPorts, outPorts) {
3871
4024
  const current = this.canvas.findNode(nodeId);
3872
- if (current === void 0) return;
4025
+ if (current === void 0) return false;
3873
4026
  const convert = (port) => ({
3874
4027
  id: port.key,
3875
4028
  name: port.name,
@@ -3888,13 +4041,15 @@ ${runtime.error}`;
3888
4041
  for (const sibling of current.outPorts.filter((port) => port.isSibling)) {
3889
4042
  if (!nextOut.some((port) => port.id === sibling.id)) nextOut.push(sibling.toInit());
3890
4043
  }
3891
- this.canvas.setNodePorts(nodeId, nextIn, nextOut);
4044
+ return this.canvas.setNodePorts(nodeId, nextIn, nextOut);
3892
4045
  }
4046
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3893
4047
  updateNode(nodeId, patch) {
3894
- this.canvas.updateNode(nodeId, patch);
4048
+ return this.canvas.updateNode(nodeId, patch);
3895
4049
  }
4050
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3896
4051
  setNodeMessage(nodeId, message) {
3897
- this.canvas.updateNode(nodeId, { message });
4052
+ return this.canvas.updateNode(nodeId, { message });
3898
4053
  }
3899
4054
  setNodeError(nodeId, message, options = {}) {
3900
4055
  return this.canvas.setNodeError(nodeId, message, options);
@@ -3920,11 +4075,13 @@ ${runtime.error}`;
3920
4075
  setGlobalError(message, kind = "invalid") {
3921
4076
  this.canvas.setGlobalError(message, kind);
3922
4077
  }
4078
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3923
4079
  setNodeParamValue(nodeId, paramName, value) {
3924
- this.canvas.setNodeParamValue(nodeId, paramName, value);
4080
+ return this.canvas.setNodeParamValue(nodeId, paramName, value);
3925
4081
  }
4082
+ /** False when nothing changed: no such node or port, or the edit was a no-op. */
3926
4083
  setNodeName(nodeId, value) {
3927
- this.canvas.updateNode(nodeId, { name: value });
4084
+ return this.canvas.updateNode(nodeId, { name: value });
3928
4085
  }
3929
4086
  /** Groups host-driven document edits into one undo/redo operation. */
3930
4087
  transaction(label, action) {
@@ -4347,50 +4504,43 @@ ${runtime.error}`;
4347
4504
  }
4348
4505
  registerContextActions() {
4349
4506
  const permissions = () => this.canvas.getInteractionPermissions();
4350
- this.contextActions.register({
4351
- id: "undo",
4352
- canExecute: () => this.canUndo(),
4353
- execute: () => this.undo()
4354
- });
4355
- this.contextActions.register({
4356
- id: "redo",
4357
- canExecute: () => this.canRedo(),
4358
- execute: () => this.redo()
4359
- });
4360
- this.contextActions.register({
4361
- id: "cut",
4362
- canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
4363
- execute: () => this.cutSelection()
4364
- });
4365
- this.contextActions.register({
4366
- id: "copy",
4367
- canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
4368
- execute: () => this.copySelection()
4369
- });
4370
- this.contextActions.register({
4371
- id: "paste",
4372
- canExecute: () => this.canvas.hasClipboard() && permissions().paste,
4373
- execute: () => this.pasteSelection()
4374
- });
4375
- this.contextActions.register({
4376
- id: "open",
4377
- canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
4378
- execute: ({ nodes }) => this.emit("nodeOpen", { nodes })
4379
- });
4380
- this.contextActions.register({
4381
- id: "delete",
4382
- canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
4383
- execute: () => this.canvas.deleteSelection()
4384
- });
4385
- this.contextActions.register({
4386
- id: "properties",
4387
- canExecute: ({ nodes }) => nodes.length > 0,
4388
- execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
4389
- });
4390
- this.contextActions.register({
4391
- id: "help",
4392
- canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
4393
- execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
4507
+ this.contextActions.registerAll({
4508
+ undo: {
4509
+ canExecute: () => this.canUndo(),
4510
+ execute: () => this.undo()
4511
+ },
4512
+ redo: {
4513
+ canExecute: () => this.canRedo(),
4514
+ execute: () => this.redo()
4515
+ },
4516
+ cut: {
4517
+ canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
4518
+ execute: () => this.cutSelection()
4519
+ },
4520
+ copy: {
4521
+ canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
4522
+ execute: () => this.copySelection()
4523
+ },
4524
+ paste: {
4525
+ canExecute: () => this.canvas.hasClipboard() && permissions().paste,
4526
+ execute: () => this.pasteSelection()
4527
+ },
4528
+ open: {
4529
+ canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
4530
+ execute: ({ nodes }) => this.emit("nodeOpen", { nodes })
4531
+ },
4532
+ delete: {
4533
+ canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
4534
+ execute: () => this.canvas.deleteSelection()
4535
+ },
4536
+ properties: {
4537
+ canExecute: ({ nodes }) => nodes.length > 0,
4538
+ execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
4539
+ },
4540
+ help: {
4541
+ canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
4542
+ execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
4543
+ }
4394
4544
  });
4395
4545
  }
4396
4546
  contextActionContext() {
@@ -4558,19 +4708,26 @@ ${runtime.error}`;
4558
4708
  // imported node ("Element type is missing from the palette"). Node.id
4559
4709
  // keeps its original casing so save/load round-trips unchanged.
4560
4710
  addNodeType(node) {
4561
- const n = node instanceof Node ? node : new Node(node);
4711
+ const n = (node instanceof Node ? node : new Node(node)).clone();
4562
4712
  this.nodeTypes.set(n.id.toLowerCase(), n);
4563
- this.emit("nodeTypesChanged", this.getNodeTypes());
4713
+ this.emitNodeTypesChanged();
4564
4714
  }
4565
4715
  removeNodeType(id) {
4566
4716
  this.nodeTypes.delete(id.toLowerCase());
4567
- this.emit("nodeTypesChanged", this.getNodeTypes());
4717
+ this.emitNodeTypesChanged();
4568
4718
  }
4569
4719
  getNodeType(id) {
4570
- return this.nodeTypes.get(id.toLowerCase()) ?? null;
4720
+ return this.nodeTypes.get(id.toLowerCase())?.clone() ?? null;
4571
4721
  }
4572
4722
  getNodeTypes() {
4573
- return Array.from(this.nodeTypes.values());
4723
+ return Array.from(this.nodeTypes.values(), (node) => node.clone());
4724
+ }
4725
+ // Node definitions are mutable, so the payload has to be a copy of each one.
4726
+ // Building a catalog is a loop of addNodeType calls, which would make that
4727
+ // quadratic; skipping it while nobody listens keeps catalog construction --
4728
+ // where no subscriber exists yet -- as cheap as it was.
4729
+ emitNodeTypesChanged() {
4730
+ if (this.hasHandlers("nodeTypesChanged")) this.emit("nodeTypesChanged", this.getNodeTypes());
4574
4731
  }
4575
4732
  };
4576
4733
 
@@ -4883,60 +5040,127 @@ ${runtime.error}`;
4883
5040
  });
4884
5041
  return { nodes, nodeErrors };
4885
5042
  }
5043
+ function isRecord(value) {
5044
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5045
+ }
5046
+ function asRecord(value) {
5047
+ return isRecord(value) ? value : void 0;
5048
+ }
5049
+ function asArray(value) {
5050
+ return Array.isArray(value) ? value : [];
5051
+ }
5052
+ function asString(value) {
5053
+ return typeof value === "string" ? value : "";
5054
+ }
5055
+ function asFiniteNumber(value) {
5056
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5057
+ }
4886
5058
  function nodeName(node) {
4887
- const settings = node?.Settings;
4888
- const parameters = settings?.Parameters;
4889
- const nameParam = parameters?.Name;
4890
- const nameVal = nameParam?.Value;
4891
- if (typeof nameVal === "string" && nameVal.length > 0)
4892
- return nameVal;
4893
- if (typeof node?.Figure === "string" && node.Figure.length > 0)
4894
- return node.Figure;
4895
- return typeof node?.Key === "string" ? node.Key : "";
5059
+ const named = asRecord(asRecord(asRecord(node.Settings)?.Parameters)?.Name)?.Value;
5060
+ if (typeof named === "string" && named.length > 0)
5061
+ return named;
5062
+ const figure = asString(node.Figure);
5063
+ return figure.length > 0 ? figure : asString(node.Key);
4896
5064
  }
4897
5065
  function parseRawScheme(raw) {
4898
5066
  const nodes = [];
4899
5067
  const links = [];
4900
- const root = raw;
4901
- const content = root?.Content;
4902
- const value = content?.Value;
4903
- const scheme = value?.Scheme;
4904
- const model = scheme?.Model;
4905
- if (!model)
5068
+ const model = asRecord(asRecord(asRecord(asRecord(raw)?.Content)?.Value)?.Scheme)?.Model;
5069
+ const fields = asRecord(model);
5070
+ if (fields === void 0)
4906
5071
  return { nodes, links };
4907
- for (const raw2 of model.Nodes ?? []) {
4908
- const key = raw2?.Key;
5072
+ for (const entry of asArray(fields.Nodes)) {
5073
+ const node = asRecord(entry);
5074
+ if (node === void 0)
5075
+ continue;
5076
+ const key = node.Key;
4909
5077
  if (typeof key !== "string" || key.length === 0)
4910
5078
  continue;
4911
5079
  nodes.push({
4912
5080
  id: key,
4913
- typeId: typeof raw2.TypeId === "string" ? raw2.TypeId : "",
4914
- name: nodeName(raw2),
4915
- x: typeof raw2.X === "number" ? raw2.X : 0,
4916
- y: typeof raw2.Y === "number" ? raw2.Y : 0
5081
+ typeId: asString(node.TypeId),
5082
+ name: nodeName(node),
5083
+ x: asFiniteNumber(node.X),
5084
+ y: asFiniteNumber(node.Y)
4917
5085
  });
4918
5086
  }
4919
- for (const raw2 of model.Links ?? []) {
4920
- const from = raw2?.From;
4921
- const to = raw2?.To;
5087
+ for (const entry of asArray(fields.Links)) {
5088
+ const link = asRecord(entry);
5089
+ if (link === void 0)
5090
+ continue;
5091
+ const from = link.From;
5092
+ const to = link.To;
4922
5093
  if (typeof from !== "string" || typeof to !== "string")
4923
5094
  continue;
4924
5095
  links.push({
4925
5096
  from,
4926
- fromPort: typeof raw2.FromPort === "string" ? raw2.FromPort : "",
5097
+ fromPort: asString(link.FromPort),
4927
5098
  to,
4928
- toPort: typeof raw2.ToPort === "string" ? raw2.ToPort : ""
5099
+ toPort: asString(link.ToPort)
4929
5100
  });
4930
5101
  }
4931
5102
  return { nodes, links };
4932
5103
  }
5104
+ function parsePalettePorts(value) {
5105
+ const ports = [];
5106
+ for (const entry of asArray(value)) {
5107
+ const port = asRecord(entry);
5108
+ if (port === void 0)
5109
+ continue;
5110
+ const key = asString(port.key);
5111
+ if (key.length === 0)
5112
+ continue;
5113
+ ports.push({
5114
+ key,
5115
+ name: asString(port.name),
5116
+ type: asString(port.type),
5117
+ maxLinks: asFiniteNumber(port.maxLinks),
5118
+ availableTypes: asArray(port.availableTypes).filter((item) => typeof item === "string"),
5119
+ isDynamic: port.isDynamic === true,
5120
+ dynamicMode: toPortDynamicMode(port.dynamicMode)
5121
+ });
5122
+ }
5123
+ return ports;
5124
+ }
5125
+ function parsePalette(value) {
5126
+ const socketTypes = [];
5127
+ const elements = [];
5128
+ const root = asRecord(value);
5129
+ if (root === void 0)
5130
+ return { socketTypes, elements };
5131
+ for (const entry of asArray(root.socketTypes)) {
5132
+ const socket = asRecord(entry);
5133
+ const name = asString(socket?.name);
5134
+ if (name.length === 0)
5135
+ continue;
5136
+ socketTypes.push({ name, color: asString(socket?.color) });
5137
+ }
5138
+ for (const entry of asArray(root.elements)) {
5139
+ const element = asRecord(entry);
5140
+ const typeId = asString(element?.typeId);
5141
+ if (element === void 0 || typeId.length === 0)
5142
+ continue;
5143
+ elements.push({
5144
+ typeId,
5145
+ name: asString(element.name),
5146
+ groupName: asString(element.groupName),
5147
+ icon: asString(element.icon),
5148
+ inPorts: parsePalettePorts(element.inPorts),
5149
+ outPorts: parsePalettePorts(element.outPorts)
5150
+ });
5151
+ }
5152
+ return { socketTypes, elements };
5153
+ }
4933
5154
  async function renderScheme(div, paletteUrl, scheme, options = {}) {
4934
5155
  return renderSchemeAtRevision(div, paletteUrl, scheme, beginRender(div), options);
4935
5156
  }
4936
5157
  async function renderSchemeAtRevision(div, paletteUrl, scheme, revision, options) {
4937
5158
  if (renderRevisions.get(div) !== revision)
4938
5159
  return null;
4939
- const palette = await fetch(paletteUrl).then((r) => r.json());
5160
+ const response = await fetch(paletteUrl);
5161
+ if (!response.ok)
5162
+ throw new Error(`Palette request to ${paletteUrl} failed with status ${response.status}.`);
5163
+ const palette = parsePalette(await response.json());
4940
5164
  if (renderRevisions.get(div) !== revision)
4941
5165
  return null;
4942
5166
  const catalog = buildCatalog(palette);
@@ -5100,6 +5324,14 @@ ${runtime.error}`;
5100
5324
  disconnectedHostObserver.disconnect();
5101
5325
  disconnectedHostObserver = null;
5102
5326
  }
5327
+ function errorTexts(div) {
5328
+ const parts = (div.dataset.diagramErrors ?? "").split("|");
5329
+ return {
5330
+ load: parts[0] || "Diagram source could not be loaded.",
5331
+ empty: parts[1] || "Diagram is empty or malformed.",
5332
+ draw: parts[2] || "Diagram could not be rendered."
5333
+ };
5334
+ }
5103
5335
  function note(div, message, revision) {
5104
5336
  if (renderRevisions.get(div) !== revision) return;
5105
5337
  disposeActiveRender(div);
@@ -5108,53 +5340,51 @@ ${runtime.error}`;
5108
5340
  }
5109
5341
  async function renderFromSource(div, paletteUrl, srcUrl, options = {}) {
5110
5342
  const revision = beginRender(div);
5111
- const errors = (div.dataset.diagramErrors ?? "").split("|");
5112
- const [errLoad = "Diagram source could not be loaded.", errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
5343
+ const errors = errorTexts(div);
5113
5344
  let raw;
5114
5345
  try {
5115
5346
  const resp = await fetch(srcUrl);
5116
5347
  if (!resp.ok) {
5117
- note(div, errLoad, revision);
5348
+ note(div, errors.load, revision);
5118
5349
  return null;
5119
5350
  }
5120
5351
  const text = (await resp.text()).replace(/^/, "");
5121
5352
  raw = JSON.parse(text);
5122
5353
  } catch {
5123
- note(div, errLoad, revision);
5354
+ note(div, errors.load, revision);
5124
5355
  return null;
5125
5356
  }
5126
5357
  const scheme = parseRawScheme(raw);
5127
5358
  if (scheme.nodes.length === 0) {
5128
- note(div, errEmpty, revision);
5359
+ note(div, errors.empty, revision);
5129
5360
  return null;
5130
5361
  }
5131
5362
  try {
5132
5363
  return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
5133
5364
  } catch {
5134
- note(div, errDraw, revision);
5365
+ note(div, errors.draw, revision);
5135
5366
  return null;
5136
5367
  }
5137
5368
  }
5138
5369
  async function renderFromInline(div, paletteUrl, json, options = {}) {
5139
5370
  const revision = beginRender(div);
5140
- const errors = (div.dataset.diagramErrors ?? "").split("|");
5141
- const [, errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
5371
+ const errors = errorTexts(div);
5142
5372
  let raw;
5143
5373
  try {
5144
5374
  raw = JSON.parse(json);
5145
5375
  } catch {
5146
- note(div, errEmpty, revision);
5376
+ note(div, errors.empty, revision);
5147
5377
  return null;
5148
5378
  }
5149
5379
  const scheme = parseRawScheme(raw);
5150
5380
  if (scheme.nodes.length === 0) {
5151
- note(div, errEmpty, revision);
5381
+ note(div, errors.empty, revision);
5152
5382
  return null;
5153
5383
  }
5154
5384
  try {
5155
5385
  return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
5156
5386
  } catch {
5157
- note(div, errDraw, revision);
5387
+ note(div, errors.draw, revision);
5158
5388
  return null;
5159
5389
  }
5160
5390
  }