@stocksharp/diagram 1.1.0 → 1.2.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.
- package/dist/esm/headless.js +111 -0
- package/dist/esm/headless.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/ssdiagram-headless.js +4176 -0
- package/dist/ssdiagram-headless.js.map +7 -0
- package/dist/ssdiagram.js +436 -328
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/headless.d.ts +24 -0
- package/dist/types/headless.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/headless.ts +139 -0
- package/src/index.ts +3 -0
package/dist/ssdiagram.js
CHANGED
|
@@ -52,6 +52,7 @@ var SSDiagram = (() => {
|
|
|
52
52
|
parseDiagramDocument: () => parseDiagramDocument,
|
|
53
53
|
parseDiagramViewState: () => parseDiagramViewState,
|
|
54
54
|
renderAll: () => renderAll,
|
|
55
|
+
renderDiagramSvg: () => renderDiagramSvg,
|
|
55
56
|
renderFromInline: () => renderFromInline,
|
|
56
57
|
renderFromSource: () => renderFromSource,
|
|
57
58
|
renderScheme: () => renderScheme,
|
|
@@ -471,334 +472,6 @@ var SSDiagram = (() => {
|
|
|
471
472
|
return value;
|
|
472
473
|
}
|
|
473
474
|
|
|
474
|
-
// src/core/action-registry.ts
|
|
475
|
-
var DiagramActionRegistry = class {
|
|
476
|
-
constructor() {
|
|
477
|
-
this.actions = /* @__PURE__ */ new Map();
|
|
478
|
-
}
|
|
479
|
-
register(action) {
|
|
480
|
-
if (this.actions.has(action.id)) {
|
|
481
|
-
throw new Error(`Diagram action "${action.id}" is already registered.`);
|
|
482
|
-
}
|
|
483
|
-
this.actions.set(action.id, action);
|
|
484
|
-
return () => {
|
|
485
|
-
if (this.actions.get(action.id) === action) this.actions.delete(action.id);
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Registers one action per id from a table keyed by TId, in key order.
|
|
490
|
-
* Because the table is a total Record, adding a member to TId without adding
|
|
491
|
-
* its action stops compiling instead of producing a command that silently
|
|
492
|
-
* does nothing.
|
|
493
|
-
*/
|
|
494
|
-
registerAll(actions) {
|
|
495
|
-
const ids = Object.keys(actions);
|
|
496
|
-
const disposers = ids.map((id) => {
|
|
497
|
-
const action = actions[id];
|
|
498
|
-
return this.register({
|
|
499
|
-
id,
|
|
500
|
-
canExecute: (context) => action.canExecute(context),
|
|
501
|
-
execute: (context) => action.execute(context)
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
return () => disposers.forEach((dispose) => dispose());
|
|
505
|
-
}
|
|
506
|
-
get(id) {
|
|
507
|
-
return this.actions.get(id) ?? null;
|
|
508
|
-
}
|
|
509
|
-
states(context) {
|
|
510
|
-
return [...this.actions.values()].map((action) => ({
|
|
511
|
-
id: action.id,
|
|
512
|
-
enabled: action.canExecute(context)
|
|
513
|
-
}));
|
|
514
|
-
}
|
|
515
|
-
canExecute(id, context) {
|
|
516
|
-
return this.actions.get(id)?.canExecute(context) ?? false;
|
|
517
|
-
}
|
|
518
|
-
execute(id, context) {
|
|
519
|
-
const action = this.actions.get(id);
|
|
520
|
-
if (action === void 0 || !action.canExecute(context)) return false;
|
|
521
|
-
action.execute(context);
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
// src/core/history.ts
|
|
527
|
-
var DiagramCommandHistory = class {
|
|
528
|
-
constructor(listener) {
|
|
529
|
-
this.listener = listener;
|
|
530
|
-
this.undoCommands = [];
|
|
531
|
-
this.redoCommands = [];
|
|
532
|
-
this.transactions = [];
|
|
533
|
-
this.replaying = false;
|
|
534
|
-
}
|
|
535
|
-
get state() {
|
|
536
|
-
return {
|
|
537
|
-
canUndo: this.undoCommands.length > 0,
|
|
538
|
-
canRedo: this.redoCommands.length > 0,
|
|
539
|
-
undoDepth: this.undoCommands.length,
|
|
540
|
-
redoDepth: this.redoCommands.length,
|
|
541
|
-
undoLabel: this.undoCommands[this.undoCommands.length - 1]?.label ?? null,
|
|
542
|
-
redoLabel: this.redoCommands[this.redoCommands.length - 1]?.label ?? null
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
execute(command) {
|
|
546
|
-
if (this.replaying) {
|
|
547
|
-
command.execute();
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
command.execute();
|
|
551
|
-
this.recordApplied(command);
|
|
552
|
-
}
|
|
553
|
-
/** Records a gesture that already changed the document, such as pointer drag. */
|
|
554
|
-
recordApplied(command) {
|
|
555
|
-
if (this.replaying) return;
|
|
556
|
-
const transaction = this.transactions[this.transactions.length - 1];
|
|
557
|
-
if (transaction !== void 0) {
|
|
558
|
-
transaction.commands.push(command);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
this.undoCommands.push(command);
|
|
562
|
-
this.redoCommands.length = 0;
|
|
563
|
-
this.notify();
|
|
564
|
-
}
|
|
565
|
-
transaction(label2, action) {
|
|
566
|
-
const frame = { label: label2, commands: [] };
|
|
567
|
-
this.transactions.push(frame);
|
|
568
|
-
try {
|
|
569
|
-
const result = action();
|
|
570
|
-
this.transactions.pop();
|
|
571
|
-
const command = compose(frame);
|
|
572
|
-
if (command !== null) this.recordApplied(command);
|
|
573
|
-
return result;
|
|
574
|
-
} catch (error) {
|
|
575
|
-
this.transactions.pop();
|
|
576
|
-
this.replay(() => {
|
|
577
|
-
for (const command of frame.commands.slice().reverse()) command.undo();
|
|
578
|
-
});
|
|
579
|
-
throw error;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
assertNoOpenTransaction(operation) {
|
|
583
|
-
if (this.transactions.length > 0) {
|
|
584
|
-
throw new Error(`Cannot ${operation} command history inside a transaction.`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
undo() {
|
|
588
|
-
this.assertNoOpenTransaction("undo");
|
|
589
|
-
const command = this.undoCommands.pop();
|
|
590
|
-
if (command === void 0) return false;
|
|
591
|
-
try {
|
|
592
|
-
this.replay(() => command.undo());
|
|
593
|
-
} catch (error) {
|
|
594
|
-
this.undoCommands.push(command);
|
|
595
|
-
throw error;
|
|
596
|
-
}
|
|
597
|
-
this.redoCommands.push(command);
|
|
598
|
-
this.notify();
|
|
599
|
-
return true;
|
|
600
|
-
}
|
|
601
|
-
redo() {
|
|
602
|
-
this.assertNoOpenTransaction("redo");
|
|
603
|
-
const command = this.redoCommands.pop();
|
|
604
|
-
if (command === void 0) return false;
|
|
605
|
-
try {
|
|
606
|
-
this.replay(() => command.execute());
|
|
607
|
-
} catch (error) {
|
|
608
|
-
this.redoCommands.push(command);
|
|
609
|
-
throw error;
|
|
610
|
-
}
|
|
611
|
-
this.undoCommands.push(command);
|
|
612
|
-
this.notify();
|
|
613
|
-
return true;
|
|
614
|
-
}
|
|
615
|
-
clear() {
|
|
616
|
-
this.assertNoOpenTransaction("clear");
|
|
617
|
-
this.undoCommands.length = 0;
|
|
618
|
-
this.redoCommands.length = 0;
|
|
619
|
-
this.notify();
|
|
620
|
-
}
|
|
621
|
-
replay(action) {
|
|
622
|
-
const previous = this.replaying;
|
|
623
|
-
this.replaying = true;
|
|
624
|
-
try {
|
|
625
|
-
action();
|
|
626
|
-
} finally {
|
|
627
|
-
this.replaying = previous;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
notify() {
|
|
631
|
-
this.listener?.(this.state);
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
function compose(frame) {
|
|
635
|
-
if (frame.commands.length === 0) return null;
|
|
636
|
-
if (frame.commands.length === 1) {
|
|
637
|
-
const command = frame.commands[0];
|
|
638
|
-
return frame.label === command.label ? command : {
|
|
639
|
-
label: frame.label,
|
|
640
|
-
execute: () => command.execute(),
|
|
641
|
-
undo: () => command.undo()
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
const commands = [...frame.commands];
|
|
645
|
-
return {
|
|
646
|
-
label: frame.label,
|
|
647
|
-
execute: () => {
|
|
648
|
-
for (const command of commands) command.execute();
|
|
649
|
-
},
|
|
650
|
-
undo: () => {
|
|
651
|
-
for (const command of commands.slice().reverse()) command.undo();
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// src/core/state.ts
|
|
657
|
-
function createDiagramRuntimeState() {
|
|
658
|
-
return { activeNodeId: null, nodes: {}, globalError: null };
|
|
659
|
-
}
|
|
660
|
-
function cloneDiagramRuntimeState(state) {
|
|
661
|
-
return {
|
|
662
|
-
activeNodeId: state.activeNodeId,
|
|
663
|
-
globalError: state.globalError === null ? null : { ...state.globalError },
|
|
664
|
-
nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
|
|
665
|
-
active: node.active,
|
|
666
|
-
errors: cloneDiagramNodeErrors(node.errors),
|
|
667
|
-
ports: {
|
|
668
|
-
in: Object.fromEntries(Object.entries(node.ports.in).map(([portId, port]) => [portId, { ...port }])),
|
|
669
|
-
out: Object.fromEntries(Object.entries(node.ports.out).map(([portId, port]) => [portId, { ...port }]))
|
|
670
|
-
}
|
|
671
|
-
}]))
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
function createDiagramViewState() {
|
|
675
|
-
return { zoom: 1, panX: 0, panY: 0, overviewVisible: true };
|
|
676
|
-
}
|
|
677
|
-
function createDiagramSelection() {
|
|
678
|
-
return {
|
|
679
|
-
nodeIds: [],
|
|
680
|
-
linkIds: [],
|
|
681
|
-
port: null,
|
|
682
|
-
primaryNodeId: null,
|
|
683
|
-
primaryLinkId: null
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
function createDiagramPortRuntimeState() {
|
|
687
|
-
return {
|
|
688
|
-
active: false,
|
|
689
|
-
selected: false,
|
|
690
|
-
breakpoint: false,
|
|
691
|
-
breakpointActive: false,
|
|
692
|
-
value: null,
|
|
693
|
-
error: null
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
function createEditableDiagramPermissions() {
|
|
697
|
-
return {
|
|
698
|
-
select: true,
|
|
699
|
-
inspect: true,
|
|
700
|
-
copy: true,
|
|
701
|
-
moveNodes: true,
|
|
702
|
-
createLinks: true,
|
|
703
|
-
deleteSelection: true,
|
|
704
|
-
paste: true,
|
|
705
|
-
history: true
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
function createReadOnlyDiagramPermissions() {
|
|
709
|
-
return {
|
|
710
|
-
select: true,
|
|
711
|
-
inspect: true,
|
|
712
|
-
copy: true,
|
|
713
|
-
moveNodes: false,
|
|
714
|
-
createLinks: false,
|
|
715
|
-
deleteSelection: false,
|
|
716
|
-
paste: false,
|
|
717
|
-
history: false
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
function createDiagramNodeRuntimeState() {
|
|
721
|
-
return {
|
|
722
|
-
active: false,
|
|
723
|
-
errors: {},
|
|
724
|
-
ports: { in: {}, out: {} }
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
function cloneDiagramNodeErrors(errors) {
|
|
728
|
-
const result = {};
|
|
729
|
-
if (errors === void 0 || errors === null) return result;
|
|
730
|
-
if (errors.runtime !== void 0) result.runtime = { ...errors.runtime };
|
|
731
|
-
if (errors.load !== void 0) result.load = { ...errors.load };
|
|
732
|
-
return result;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// src/core/view-state.ts
|
|
736
|
-
var DIAGRAM_VIEW_STATE_VERSION = 1;
|
|
737
|
-
var DiagramViewStateError = class extends Error {
|
|
738
|
-
constructor(message, path = "$") {
|
|
739
|
-
super(`${path}: ${message}`);
|
|
740
|
-
this.path = path;
|
|
741
|
-
this.name = "DiagramViewStateError";
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
function createDiagramViewStateDocument(view = createDiagramViewState()) {
|
|
745
|
-
return {
|
|
746
|
-
version: DIAGRAM_VIEW_STATE_VERSION,
|
|
747
|
-
view: normalizeView(view)
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
function parseDiagramViewState(source) {
|
|
751
|
-
let value = source;
|
|
752
|
-
if (typeof source === "string") {
|
|
753
|
-
try {
|
|
754
|
-
value = JSON.parse(source);
|
|
755
|
-
} catch (error) {
|
|
756
|
-
const reason = error instanceof Error ? error.message : "invalid JSON";
|
|
757
|
-
throw new DiagramViewStateError(reason);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
const root = requireObject2(value, "$");
|
|
761
|
-
const version = requireFiniteNumber2(root.version, "$.version");
|
|
762
|
-
if (version !== DIAGRAM_VIEW_STATE_VERSION) {
|
|
763
|
-
throw new DiagramViewStateError(`unsupported view-state version ${version}`, "$.version");
|
|
764
|
-
}
|
|
765
|
-
return normalizeView(requireObject2(root.view, "$.view"));
|
|
766
|
-
}
|
|
767
|
-
function serializeDiagramViewState(view, space) {
|
|
768
|
-
return JSON.stringify(createDiagramViewStateDocument(view), null, space);
|
|
769
|
-
}
|
|
770
|
-
function normalizeView(value) {
|
|
771
|
-
const view = requireObject2(value, "$.view");
|
|
772
|
-
const zoom = requireFiniteNumber2(view.zoom, "$.view.zoom");
|
|
773
|
-
if (zoom <= 0) throw new DiagramViewStateError("expected a positive number", "$.view.zoom");
|
|
774
|
-
return {
|
|
775
|
-
zoom,
|
|
776
|
-
panX: requireFiniteNumber2(view.panX, "$.view.panX"),
|
|
777
|
-
panY: requireFiniteNumber2(view.panY, "$.view.panY"),
|
|
778
|
-
overviewVisible: requireBoolean2(view.overviewVisible, "$.view.overviewVisible")
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
function requireObject2(value, path) {
|
|
782
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
783
|
-
throw new DiagramViewStateError("expected an object", path);
|
|
784
|
-
}
|
|
785
|
-
const prototype = Object.getPrototypeOf(value);
|
|
786
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
787
|
-
throw new DiagramViewStateError("expected a plain object", path);
|
|
788
|
-
}
|
|
789
|
-
return value;
|
|
790
|
-
}
|
|
791
|
-
function requireFiniteNumber2(value, path) {
|
|
792
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
793
|
-
throw new DiagramViewStateError("expected a finite number", path);
|
|
794
|
-
}
|
|
795
|
-
return value;
|
|
796
|
-
}
|
|
797
|
-
function requireBoolean2(value, path) {
|
|
798
|
-
if (typeof value !== "boolean") throw new DiagramViewStateError("expected a boolean", path);
|
|
799
|
-
return value;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
475
|
// src/svg-surface.ts
|
|
803
476
|
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
804
477
|
function multiply(m, n2) {
|
|
@@ -1123,6 +796,215 @@ var SSDiagram = (() => {
|
|
|
1123
796
|
}
|
|
1124
797
|
};
|
|
1125
798
|
|
|
799
|
+
// src/core/history.ts
|
|
800
|
+
var DiagramCommandHistory = class {
|
|
801
|
+
constructor(listener) {
|
|
802
|
+
this.listener = listener;
|
|
803
|
+
this.undoCommands = [];
|
|
804
|
+
this.redoCommands = [];
|
|
805
|
+
this.transactions = [];
|
|
806
|
+
this.replaying = false;
|
|
807
|
+
}
|
|
808
|
+
get state() {
|
|
809
|
+
return {
|
|
810
|
+
canUndo: this.undoCommands.length > 0,
|
|
811
|
+
canRedo: this.redoCommands.length > 0,
|
|
812
|
+
undoDepth: this.undoCommands.length,
|
|
813
|
+
redoDepth: this.redoCommands.length,
|
|
814
|
+
undoLabel: this.undoCommands[this.undoCommands.length - 1]?.label ?? null,
|
|
815
|
+
redoLabel: this.redoCommands[this.redoCommands.length - 1]?.label ?? null
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
execute(command) {
|
|
819
|
+
if (this.replaying) {
|
|
820
|
+
command.execute();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
command.execute();
|
|
824
|
+
this.recordApplied(command);
|
|
825
|
+
}
|
|
826
|
+
/** Records a gesture that already changed the document, such as pointer drag. */
|
|
827
|
+
recordApplied(command) {
|
|
828
|
+
if (this.replaying) return;
|
|
829
|
+
const transaction = this.transactions[this.transactions.length - 1];
|
|
830
|
+
if (transaction !== void 0) {
|
|
831
|
+
transaction.commands.push(command);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
this.undoCommands.push(command);
|
|
835
|
+
this.redoCommands.length = 0;
|
|
836
|
+
this.notify();
|
|
837
|
+
}
|
|
838
|
+
transaction(label2, action) {
|
|
839
|
+
const frame = { label: label2, commands: [] };
|
|
840
|
+
this.transactions.push(frame);
|
|
841
|
+
try {
|
|
842
|
+
const result = action();
|
|
843
|
+
this.transactions.pop();
|
|
844
|
+
const command = compose(frame);
|
|
845
|
+
if (command !== null) this.recordApplied(command);
|
|
846
|
+
return result;
|
|
847
|
+
} catch (error) {
|
|
848
|
+
this.transactions.pop();
|
|
849
|
+
this.replay(() => {
|
|
850
|
+
for (const command of frame.commands.slice().reverse()) command.undo();
|
|
851
|
+
});
|
|
852
|
+
throw error;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
assertNoOpenTransaction(operation) {
|
|
856
|
+
if (this.transactions.length > 0) {
|
|
857
|
+
throw new Error(`Cannot ${operation} command history inside a transaction.`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
undo() {
|
|
861
|
+
this.assertNoOpenTransaction("undo");
|
|
862
|
+
const command = this.undoCommands.pop();
|
|
863
|
+
if (command === void 0) return false;
|
|
864
|
+
try {
|
|
865
|
+
this.replay(() => command.undo());
|
|
866
|
+
} catch (error) {
|
|
867
|
+
this.undoCommands.push(command);
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
870
|
+
this.redoCommands.push(command);
|
|
871
|
+
this.notify();
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
redo() {
|
|
875
|
+
this.assertNoOpenTransaction("redo");
|
|
876
|
+
const command = this.redoCommands.pop();
|
|
877
|
+
if (command === void 0) return false;
|
|
878
|
+
try {
|
|
879
|
+
this.replay(() => command.execute());
|
|
880
|
+
} catch (error) {
|
|
881
|
+
this.redoCommands.push(command);
|
|
882
|
+
throw error;
|
|
883
|
+
}
|
|
884
|
+
this.undoCommands.push(command);
|
|
885
|
+
this.notify();
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
clear() {
|
|
889
|
+
this.assertNoOpenTransaction("clear");
|
|
890
|
+
this.undoCommands.length = 0;
|
|
891
|
+
this.redoCommands.length = 0;
|
|
892
|
+
this.notify();
|
|
893
|
+
}
|
|
894
|
+
replay(action) {
|
|
895
|
+
const previous = this.replaying;
|
|
896
|
+
this.replaying = true;
|
|
897
|
+
try {
|
|
898
|
+
action();
|
|
899
|
+
} finally {
|
|
900
|
+
this.replaying = previous;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
notify() {
|
|
904
|
+
this.listener?.(this.state);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
function compose(frame) {
|
|
908
|
+
if (frame.commands.length === 0) return null;
|
|
909
|
+
if (frame.commands.length === 1) {
|
|
910
|
+
const command = frame.commands[0];
|
|
911
|
+
return frame.label === command.label ? command : {
|
|
912
|
+
label: frame.label,
|
|
913
|
+
execute: () => command.execute(),
|
|
914
|
+
undo: () => command.undo()
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
const commands = [...frame.commands];
|
|
918
|
+
return {
|
|
919
|
+
label: frame.label,
|
|
920
|
+
execute: () => {
|
|
921
|
+
for (const command of commands) command.execute();
|
|
922
|
+
},
|
|
923
|
+
undo: () => {
|
|
924
|
+
for (const command of commands.slice().reverse()) command.undo();
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/core/state.ts
|
|
930
|
+
function createDiagramRuntimeState() {
|
|
931
|
+
return { activeNodeId: null, nodes: {}, globalError: null };
|
|
932
|
+
}
|
|
933
|
+
function cloneDiagramRuntimeState(state) {
|
|
934
|
+
return {
|
|
935
|
+
activeNodeId: state.activeNodeId,
|
|
936
|
+
globalError: state.globalError === null ? null : { ...state.globalError },
|
|
937
|
+
nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
|
|
938
|
+
active: node.active,
|
|
939
|
+
errors: cloneDiagramNodeErrors(node.errors),
|
|
940
|
+
ports: {
|
|
941
|
+
in: Object.fromEntries(Object.entries(node.ports.in).map(([portId, port]) => [portId, { ...port }])),
|
|
942
|
+
out: Object.fromEntries(Object.entries(node.ports.out).map(([portId, port]) => [portId, { ...port }]))
|
|
943
|
+
}
|
|
944
|
+
}]))
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function createDiagramViewState() {
|
|
948
|
+
return { zoom: 1, panX: 0, panY: 0, overviewVisible: true };
|
|
949
|
+
}
|
|
950
|
+
function createDiagramSelection() {
|
|
951
|
+
return {
|
|
952
|
+
nodeIds: [],
|
|
953
|
+
linkIds: [],
|
|
954
|
+
port: null,
|
|
955
|
+
primaryNodeId: null,
|
|
956
|
+
primaryLinkId: null
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function createDiagramPortRuntimeState() {
|
|
960
|
+
return {
|
|
961
|
+
active: false,
|
|
962
|
+
selected: false,
|
|
963
|
+
breakpoint: false,
|
|
964
|
+
breakpointActive: false,
|
|
965
|
+
value: null,
|
|
966
|
+
error: null
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function createEditableDiagramPermissions() {
|
|
970
|
+
return {
|
|
971
|
+
select: true,
|
|
972
|
+
inspect: true,
|
|
973
|
+
copy: true,
|
|
974
|
+
moveNodes: true,
|
|
975
|
+
createLinks: true,
|
|
976
|
+
deleteSelection: true,
|
|
977
|
+
paste: true,
|
|
978
|
+
history: true
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function createReadOnlyDiagramPermissions() {
|
|
982
|
+
return {
|
|
983
|
+
select: true,
|
|
984
|
+
inspect: true,
|
|
985
|
+
copy: true,
|
|
986
|
+
moveNodes: false,
|
|
987
|
+
createLinks: false,
|
|
988
|
+
deleteSelection: false,
|
|
989
|
+
paste: false,
|
|
990
|
+
history: false
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
function createDiagramNodeRuntimeState() {
|
|
994
|
+
return {
|
|
995
|
+
active: false,
|
|
996
|
+
errors: {},
|
|
997
|
+
ports: { in: {}, out: {} }
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function cloneDiagramNodeErrors(errors) {
|
|
1001
|
+
const result = {};
|
|
1002
|
+
if (errors === void 0 || errors === null) return result;
|
|
1003
|
+
if (errors.runtime !== void 0) result.runtime = { ...errors.runtime };
|
|
1004
|
+
if (errors.load !== void 0) result.load = { ...errors.load };
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1126
1008
|
// src/canvas-renderer.ts
|
|
1127
1009
|
var SCREEN_RENDER_OPTIONS = {
|
|
1128
1010
|
grid: true,
|
|
@@ -4237,6 +4119,232 @@ ${runtime.error}`;
|
|
|
4237
4119
|
return Math.hypot(px - cx, py - cy);
|
|
4238
4120
|
}
|
|
4239
4121
|
|
|
4122
|
+
// src/headless.ts
|
|
4123
|
+
function createContext() {
|
|
4124
|
+
const own = {
|
|
4125
|
+
globalAlpha: 1,
|
|
4126
|
+
measureText: (text) => ({ width: text.length * 7 })
|
|
4127
|
+
};
|
|
4128
|
+
return new Proxy(own, {
|
|
4129
|
+
get: (target, property) => property in target ? target[property] : () => void 0,
|
|
4130
|
+
set: (target, property, value) => {
|
|
4131
|
+
target[property] = value;
|
|
4132
|
+
return true;
|
|
4133
|
+
}
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
var StubElement = class {
|
|
4137
|
+
constructor() {
|
|
4138
|
+
this.style = {};
|
|
4139
|
+
this.className = "";
|
|
4140
|
+
this.tabIndex = 0;
|
|
4141
|
+
this.width = 0;
|
|
4142
|
+
this.height = 0;
|
|
4143
|
+
this.textContent = "";
|
|
4144
|
+
this.children = [];
|
|
4145
|
+
this.classList = { toggle: () => false, add: () => void 0, remove: () => void 0 };
|
|
4146
|
+
this.context = createContext();
|
|
4147
|
+
}
|
|
4148
|
+
appendChild(child) {
|
|
4149
|
+
this.children.push(child);
|
|
4150
|
+
return child;
|
|
4151
|
+
}
|
|
4152
|
+
removeChild(child) {
|
|
4153
|
+
const at = this.children.indexOf(child);
|
|
4154
|
+
if (at >= 0) this.children.splice(at, 1);
|
|
4155
|
+
return child;
|
|
4156
|
+
}
|
|
4157
|
+
remove() {
|
|
4158
|
+
}
|
|
4159
|
+
setAttribute() {
|
|
4160
|
+
}
|
|
4161
|
+
focus() {
|
|
4162
|
+
}
|
|
4163
|
+
addEventListener() {
|
|
4164
|
+
}
|
|
4165
|
+
removeEventListener() {
|
|
4166
|
+
}
|
|
4167
|
+
getContext() {
|
|
4168
|
+
return this.context;
|
|
4169
|
+
}
|
|
4170
|
+
getBoundingClientRect() {
|
|
4171
|
+
return { left: 0, top: 0, right: this.width, bottom: this.height, width: this.width, height: this.height, x: 0, y: 0, toJSON: () => ({}) };
|
|
4172
|
+
}
|
|
4173
|
+
get clientWidth() {
|
|
4174
|
+
return this.width;
|
|
4175
|
+
}
|
|
4176
|
+
get clientHeight() {
|
|
4177
|
+
return this.height;
|
|
4178
|
+
}
|
|
4179
|
+
};
|
|
4180
|
+
var GLOBAL_KEYS = ["window", "document", "requestAnimationFrame", "cancelAnimationFrame", "Image", "getComputedStyle", "performance"];
|
|
4181
|
+
function installGlobals() {
|
|
4182
|
+
const target = globalThis;
|
|
4183
|
+
if (target.document !== void 0)
|
|
4184
|
+
return () => void 0;
|
|
4185
|
+
const before = /* @__PURE__ */ new Map();
|
|
4186
|
+
for (const key of GLOBAL_KEYS) before.set(key, target[key]);
|
|
4187
|
+
const stubDocument = Object.assign(new StubElement(), {
|
|
4188
|
+
documentElement: new StubElement(),
|
|
4189
|
+
createElement: () => new StubElement()
|
|
4190
|
+
});
|
|
4191
|
+
Object.assign(target, {
|
|
4192
|
+
window: Object.assign(new StubElement(), { devicePixelRatio: 1 }),
|
|
4193
|
+
document: stubDocument,
|
|
4194
|
+
requestAnimationFrame: () => 1,
|
|
4195
|
+
cancelAnimationFrame: () => void 0,
|
|
4196
|
+
Image: class {
|
|
4197
|
+
},
|
|
4198
|
+
getComputedStyle: () => ({ getPropertyValue: () => "" }),
|
|
4199
|
+
// A host without a browser may have no clock either -- an embedded JS engine has none. The drawing
|
|
4200
|
+
// reads it for animation, and a still picture has no animation to advance.
|
|
4201
|
+
performance: globalThis.performance ?? { now: () => 0 }
|
|
4202
|
+
});
|
|
4203
|
+
return () => {
|
|
4204
|
+
for (const [key, value] of before) {
|
|
4205
|
+
if (value === void 0) delete target[key];
|
|
4206
|
+
else target[key] = value;
|
|
4207
|
+
}
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
function renderDiagramSvg(source, options = {}) {
|
|
4211
|
+
const { hostWidth = 1200, hostHeight = 800, ...screenshot } = options;
|
|
4212
|
+
const restore = installGlobals();
|
|
4213
|
+
try {
|
|
4214
|
+
const host = new StubElement();
|
|
4215
|
+
host.width = hostWidth;
|
|
4216
|
+
host.height = hostHeight;
|
|
4217
|
+
const diagram = new Diagram({ host });
|
|
4218
|
+
try {
|
|
4219
|
+
diagram.loadDocument(parseDiagramDocument(source));
|
|
4220
|
+
return diagram.takeSvg({ scope: "content", ...screenshot });
|
|
4221
|
+
} finally {
|
|
4222
|
+
diagram.destroy();
|
|
4223
|
+
}
|
|
4224
|
+
} finally {
|
|
4225
|
+
restore();
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
// src/core/action-registry.ts
|
|
4230
|
+
var DiagramActionRegistry = class {
|
|
4231
|
+
constructor() {
|
|
4232
|
+
this.actions = /* @__PURE__ */ new Map();
|
|
4233
|
+
}
|
|
4234
|
+
register(action) {
|
|
4235
|
+
if (this.actions.has(action.id)) {
|
|
4236
|
+
throw new Error(`Diagram action "${action.id}" is already registered.`);
|
|
4237
|
+
}
|
|
4238
|
+
this.actions.set(action.id, action);
|
|
4239
|
+
return () => {
|
|
4240
|
+
if (this.actions.get(action.id) === action) this.actions.delete(action.id);
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
/**
|
|
4244
|
+
* Registers one action per id from a table keyed by TId, in key order.
|
|
4245
|
+
* Because the table is a total Record, adding a member to TId without adding
|
|
4246
|
+
* its action stops compiling instead of producing a command that silently
|
|
4247
|
+
* does nothing.
|
|
4248
|
+
*/
|
|
4249
|
+
registerAll(actions) {
|
|
4250
|
+
const ids = Object.keys(actions);
|
|
4251
|
+
const disposers = ids.map((id) => {
|
|
4252
|
+
const action = actions[id];
|
|
4253
|
+
return this.register({
|
|
4254
|
+
id,
|
|
4255
|
+
canExecute: (context) => action.canExecute(context),
|
|
4256
|
+
execute: (context) => action.execute(context)
|
|
4257
|
+
});
|
|
4258
|
+
});
|
|
4259
|
+
return () => disposers.forEach((dispose) => dispose());
|
|
4260
|
+
}
|
|
4261
|
+
get(id) {
|
|
4262
|
+
return this.actions.get(id) ?? null;
|
|
4263
|
+
}
|
|
4264
|
+
states(context) {
|
|
4265
|
+
return [...this.actions.values()].map((action) => ({
|
|
4266
|
+
id: action.id,
|
|
4267
|
+
enabled: action.canExecute(context)
|
|
4268
|
+
}));
|
|
4269
|
+
}
|
|
4270
|
+
canExecute(id, context) {
|
|
4271
|
+
return this.actions.get(id)?.canExecute(context) ?? false;
|
|
4272
|
+
}
|
|
4273
|
+
execute(id, context) {
|
|
4274
|
+
const action = this.actions.get(id);
|
|
4275
|
+
if (action === void 0 || !action.canExecute(context)) return false;
|
|
4276
|
+
action.execute(context);
|
|
4277
|
+
return true;
|
|
4278
|
+
}
|
|
4279
|
+
};
|
|
4280
|
+
|
|
4281
|
+
// src/core/view-state.ts
|
|
4282
|
+
var DIAGRAM_VIEW_STATE_VERSION = 1;
|
|
4283
|
+
var DiagramViewStateError = class extends Error {
|
|
4284
|
+
constructor(message, path = "$") {
|
|
4285
|
+
super(`${path}: ${message}`);
|
|
4286
|
+
this.path = path;
|
|
4287
|
+
this.name = "DiagramViewStateError";
|
|
4288
|
+
}
|
|
4289
|
+
};
|
|
4290
|
+
function createDiagramViewStateDocument(view = createDiagramViewState()) {
|
|
4291
|
+
return {
|
|
4292
|
+
version: DIAGRAM_VIEW_STATE_VERSION,
|
|
4293
|
+
view: normalizeView(view)
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
4296
|
+
function parseDiagramViewState(source) {
|
|
4297
|
+
let value = source;
|
|
4298
|
+
if (typeof source === "string") {
|
|
4299
|
+
try {
|
|
4300
|
+
value = JSON.parse(source);
|
|
4301
|
+
} catch (error) {
|
|
4302
|
+
const reason = error instanceof Error ? error.message : "invalid JSON";
|
|
4303
|
+
throw new DiagramViewStateError(reason);
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
const root = requireObject2(value, "$");
|
|
4307
|
+
const version = requireFiniteNumber2(root.version, "$.version");
|
|
4308
|
+
if (version !== DIAGRAM_VIEW_STATE_VERSION) {
|
|
4309
|
+
throw new DiagramViewStateError(`unsupported view-state version ${version}`, "$.version");
|
|
4310
|
+
}
|
|
4311
|
+
return normalizeView(requireObject2(root.view, "$.view"));
|
|
4312
|
+
}
|
|
4313
|
+
function serializeDiagramViewState(view, space) {
|
|
4314
|
+
return JSON.stringify(createDiagramViewStateDocument(view), null, space);
|
|
4315
|
+
}
|
|
4316
|
+
function normalizeView(value) {
|
|
4317
|
+
const view = requireObject2(value, "$.view");
|
|
4318
|
+
const zoom = requireFiniteNumber2(view.zoom, "$.view.zoom");
|
|
4319
|
+
if (zoom <= 0) throw new DiagramViewStateError("expected a positive number", "$.view.zoom");
|
|
4320
|
+
return {
|
|
4321
|
+
zoom,
|
|
4322
|
+
panX: requireFiniteNumber2(view.panX, "$.view.panX"),
|
|
4323
|
+
panY: requireFiniteNumber2(view.panY, "$.view.panY"),
|
|
4324
|
+
overviewVisible: requireBoolean2(view.overviewVisible, "$.view.overviewVisible")
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
function requireObject2(value, path) {
|
|
4328
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4329
|
+
throw new DiagramViewStateError("expected an object", path);
|
|
4330
|
+
}
|
|
4331
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4332
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
4333
|
+
throw new DiagramViewStateError("expected a plain object", path);
|
|
4334
|
+
}
|
|
4335
|
+
return value;
|
|
4336
|
+
}
|
|
4337
|
+
function requireFiniteNumber2(value, path) {
|
|
4338
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
4339
|
+
throw new DiagramViewStateError("expected a finite number", path);
|
|
4340
|
+
}
|
|
4341
|
+
return value;
|
|
4342
|
+
}
|
|
4343
|
+
function requireBoolean2(value, path) {
|
|
4344
|
+
if (typeof value !== "boolean") throw new DiagramViewStateError("expected a boolean", path);
|
|
4345
|
+
return value;
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4240
4348
|
// src/i18n.ts
|
|
4241
4349
|
function t(key, fallback) {
|
|
4242
4350
|
if (typeof window === "undefined") return fallback;
|