@ngroznykh/papirus 0.3.21 → 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.
- package/README.md +39 -2
- package/README.ru.md +41 -4
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/core/history/commands.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/Node.d.ts +14 -15
- package/dist/elements/Node.d.ts.map +1 -1
- package/dist/elements/NodeImage.d.ts +7 -7
- package/dist/elements/NodeImage.d.ts.map +1 -1
- package/dist/elements/TextLabel.d.ts +12 -12
- package/dist/elements/TextLabel.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/papirus.js +303 -415
- package/dist/papirus.js.map +1 -1
- package/dist/styles/StyleManager.d.ts.map +1 -1
- package/dist/types.d.ts +24 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/Serializer.d.ts.map +1 -1
- package/dist/utils/SvgExporter.d.ts +1 -0
- package/dist/utils/SvgExporter.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -457,7 +457,6 @@ const MARKER_SIZES = {
|
|
|
457
457
|
diamond: 14,
|
|
458
458
|
circle: 6
|
|
459
459
|
};
|
|
460
|
-
const EDGE_LABEL_BACKGROUND_PADDING = 4;
|
|
461
460
|
const EDGE_LABEL_BACKGROUND_RADIUS = 2;
|
|
462
461
|
const RESIZE_HANDLE_SIZE = 8;
|
|
463
462
|
const RESIZE_HANDLE_OFFSET = 6;
|
|
@@ -2600,6 +2599,23 @@ function applyStyleManagerToElements(styleManager, groups, edges, nodes) {
|
|
|
2600
2599
|
node.applyStyleManager(styleManager);
|
|
2601
2600
|
}
|
|
2602
2601
|
}
|
|
2602
|
+
const DEFAULT_INSET = 8;
|
|
2603
|
+
function normalizeTextInset(value, fallback) {
|
|
2604
|
+
const n = (v) => v !== void 0 && Number.isFinite(v) ? Math.max(0, v) : fallback;
|
|
2605
|
+
if (value === void 0) {
|
|
2606
|
+
return { top: fallback, right: fallback, bottom: fallback, left: fallback };
|
|
2607
|
+
}
|
|
2608
|
+
if (typeof value === "number") {
|
|
2609
|
+
const v = n(value);
|
|
2610
|
+
return { top: v, right: v, bottom: v, left: v };
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
top: n(value.top),
|
|
2614
|
+
right: n(value.right),
|
|
2615
|
+
bottom: n(value.bottom),
|
|
2616
|
+
left: n(value.left)
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2603
2619
|
const DEFAULT_STYLE = {
|
|
2604
2620
|
font: "14px sans-serif",
|
|
2605
2621
|
fontSize: 14,
|
|
@@ -2608,7 +2624,8 @@ const DEFAULT_STYLE = {
|
|
|
2608
2624
|
color: "#000000",
|
|
2609
2625
|
opacity: 1,
|
|
2610
2626
|
align: "center",
|
|
2611
|
-
baseline: "middle"
|
|
2627
|
+
baseline: "middle",
|
|
2628
|
+
verticalAlign: "middle"
|
|
2612
2629
|
};
|
|
2613
2630
|
class TextLabel {
|
|
2614
2631
|
constructor(options) {
|
|
@@ -2616,13 +2633,15 @@ class TextLabel {
|
|
|
2616
2633
|
this._measuredWidth = 0;
|
|
2617
2634
|
this._measuredHeight = 0;
|
|
2618
2635
|
this._measureDirty = true;
|
|
2619
|
-
this._text = options.text;
|
|
2636
|
+
this._text = options.text ?? "";
|
|
2620
2637
|
this._editableText = options.editableText;
|
|
2621
2638
|
this._localStyle = { ...options.style };
|
|
2622
2639
|
this._style = { ...DEFAULT_STYLE, ...options.style };
|
|
2623
2640
|
this._maxWidth = options.maxWidth;
|
|
2624
|
-
this.
|
|
2625
|
-
|
|
2641
|
+
this._inset = normalizeTextInset(
|
|
2642
|
+
options.inset ?? options.margin ?? options.padding,
|
|
2643
|
+
DEFAULT_INSET
|
|
2644
|
+
);
|
|
2626
2645
|
this._styleClass = options.styleClass;
|
|
2627
2646
|
this._onChange = options.onChange;
|
|
2628
2647
|
}
|
|
@@ -2633,8 +2652,9 @@ class TextLabel {
|
|
|
2633
2652
|
return this._text;
|
|
2634
2653
|
}
|
|
2635
2654
|
set text(value) {
|
|
2636
|
-
|
|
2637
|
-
|
|
2655
|
+
const next = value ?? "";
|
|
2656
|
+
if (this._text !== next) {
|
|
2657
|
+
this._text = next;
|
|
2638
2658
|
this._lines = [];
|
|
2639
2659
|
this._measureDirty = true;
|
|
2640
2660
|
this._onChange?.();
|
|
@@ -2666,6 +2686,12 @@ class TextLabel {
|
|
|
2666
2686
|
this._measureDirty = true;
|
|
2667
2687
|
this._onChange?.();
|
|
2668
2688
|
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Raw text style overrides (without StyleManager merge)
|
|
2691
|
+
*/
|
|
2692
|
+
get styleOverrides() {
|
|
2693
|
+
return { ...this._localStyle };
|
|
2694
|
+
}
|
|
2669
2695
|
/**
|
|
2670
2696
|
* Style class name for StyleManager
|
|
2671
2697
|
*/
|
|
@@ -2707,30 +2733,15 @@ class TextLabel {
|
|
|
2707
2733
|
this._measureDirty = true;
|
|
2708
2734
|
}
|
|
2709
2735
|
/**
|
|
2710
|
-
*
|
|
2736
|
+
* Inset from bounds edge to text (per side: top, right, bottom, left)
|
|
2711
2737
|
*/
|
|
2712
|
-
get
|
|
2713
|
-
return this.
|
|
2738
|
+
get inset() {
|
|
2739
|
+
return { ...this._inset };
|
|
2714
2740
|
}
|
|
2715
|
-
set
|
|
2716
|
-
const next =
|
|
2717
|
-
if (this.
|
|
2718
|
-
this.
|
|
2719
|
-
this._lines = [];
|
|
2720
|
-
this._measureDirty = true;
|
|
2721
|
-
this._onChange?.();
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
/**
|
|
2725
|
-
* Label margin
|
|
2726
|
-
*/
|
|
2727
|
-
get margin() {
|
|
2728
|
-
return this._margin;
|
|
2729
|
-
}
|
|
2730
|
-
set margin(value) {
|
|
2731
|
-
const next = Number.isFinite(value) ? Math.max(0, value) : this._margin;
|
|
2732
|
-
if (this._margin !== next) {
|
|
2733
|
-
this._margin = next;
|
|
2741
|
+
set inset(value) {
|
|
2742
|
+
const next = normalizeTextInset(value, DEFAULT_INSET);
|
|
2743
|
+
if (this._inset.top !== next.top || this._inset.right !== next.right || this._inset.bottom !== next.bottom || this._inset.left !== next.left) {
|
|
2744
|
+
this._inset = next;
|
|
2734
2745
|
this._lines = [];
|
|
2735
2746
|
this._measureDirty = true;
|
|
2736
2747
|
this._onChange?.();
|
|
@@ -2763,7 +2774,7 @@ class TextLabel {
|
|
|
2763
2774
|
}
|
|
2764
2775
|
/**
|
|
2765
2776
|
* Get wrapped lines for export. Uses ctx to measure and wrap by words.
|
|
2766
|
-
* Call with maxWidth = inner bounds width (e.g. bounds.width -
|
|
2777
|
+
* Call with maxWidth = inner bounds width (e.g. bounds.width - inset*2).
|
|
2767
2778
|
*/
|
|
2768
2779
|
getWrappedLines(ctx, maxWidth) {
|
|
2769
2780
|
this.setAutoMaxWidth(maxWidth);
|
|
@@ -2783,19 +2794,23 @@ class TextLabel {
|
|
|
2783
2794
|
this.applyStyle(ctx);
|
|
2784
2795
|
const lineHeight = (this._style.fontSize ?? 14) * 1.2;
|
|
2785
2796
|
const effectiveMaxWidth = this._maxWidth ?? this._autoMaxWidth;
|
|
2797
|
+
const text = this._text ?? "";
|
|
2786
2798
|
if (effectiveMaxWidth !== void 0) {
|
|
2787
|
-
const maxWidth = Math.max(
|
|
2788
|
-
|
|
2799
|
+
const maxWidth = Math.max(
|
|
2800
|
+
0,
|
|
2801
|
+
effectiveMaxWidth - this._inset.left - this._inset.right
|
|
2802
|
+
);
|
|
2803
|
+
this._lines = this.wrapText(ctx, text, maxWidth);
|
|
2789
2804
|
} else {
|
|
2790
|
-
this._lines =
|
|
2805
|
+
this._lines = text.split("\n");
|
|
2791
2806
|
}
|
|
2792
2807
|
let maxLineWidth = 0;
|
|
2793
2808
|
for (const line of this._lines) {
|
|
2794
2809
|
const metrics = ctx.measureText(line);
|
|
2795
2810
|
maxLineWidth = Math.max(maxLineWidth, metrics.width);
|
|
2796
2811
|
}
|
|
2797
|
-
this._measuredWidth = maxLineWidth + this.
|
|
2798
|
-
this._measuredHeight = this._lines.length * lineHeight + this.
|
|
2812
|
+
this._measuredWidth = maxLineWidth + this._inset.left + this._inset.right;
|
|
2813
|
+
this._measuredHeight = this._lines.length * lineHeight + this._inset.top + this._inset.bottom;
|
|
2799
2814
|
this._measureDirty = false;
|
|
2800
2815
|
return {
|
|
2801
2816
|
width: this._measuredWidth,
|
|
@@ -2816,25 +2831,41 @@ class TextLabel {
|
|
|
2816
2831
|
}
|
|
2817
2832
|
const lineHeight = (this._style.fontSize ?? 14) * 1.2;
|
|
2818
2833
|
const totalHeight = this._lines.length * lineHeight;
|
|
2819
|
-
const margin = this._margin;
|
|
2820
2834
|
const innerBounds = {
|
|
2821
|
-
x: bounds.x +
|
|
2822
|
-
y: bounds.y +
|
|
2823
|
-
width: Math.max(
|
|
2824
|
-
|
|
2835
|
+
x: bounds.x + this._inset.left,
|
|
2836
|
+
y: bounds.y + this._inset.top,
|
|
2837
|
+
width: Math.max(
|
|
2838
|
+
0,
|
|
2839
|
+
bounds.width - this._inset.left - this._inset.right
|
|
2840
|
+
),
|
|
2841
|
+
height: Math.max(
|
|
2842
|
+
0,
|
|
2843
|
+
bounds.height - this._inset.top - this._inset.bottom
|
|
2844
|
+
)
|
|
2825
2845
|
};
|
|
2826
2846
|
let x;
|
|
2827
2847
|
switch (align) {
|
|
2828
2848
|
case "left":
|
|
2829
|
-
x = innerBounds.x
|
|
2849
|
+
x = innerBounds.x;
|
|
2830
2850
|
break;
|
|
2831
2851
|
case "right":
|
|
2832
|
-
x = innerBounds.x + innerBounds.width
|
|
2852
|
+
x = innerBounds.x + innerBounds.width;
|
|
2833
2853
|
break;
|
|
2834
2854
|
default:
|
|
2835
2855
|
x = innerBounds.x + innerBounds.width / 2;
|
|
2836
2856
|
}
|
|
2837
|
-
const
|
|
2857
|
+
const verticalAlign = this._style.verticalAlign ?? "middle";
|
|
2858
|
+
let startY;
|
|
2859
|
+
switch (verticalAlign) {
|
|
2860
|
+
case "top":
|
|
2861
|
+
startY = innerBounds.y + lineHeight / 2;
|
|
2862
|
+
break;
|
|
2863
|
+
case "bottom":
|
|
2864
|
+
startY = innerBounds.y + innerBounds.height - totalHeight + lineHeight / 2;
|
|
2865
|
+
break;
|
|
2866
|
+
default:
|
|
2867
|
+
startY = innerBounds.y + (innerBounds.height - totalHeight) / 2 + lineHeight / 2;
|
|
2868
|
+
}
|
|
2838
2869
|
ctx.fillStyle = this._style.color ?? "#000000";
|
|
2839
2870
|
for (let i = 0; i < this._lines.length; i++) {
|
|
2840
2871
|
const line = this._lines[i];
|
|
@@ -2849,16 +2880,13 @@ class TextLabel {
|
|
|
2849
2880
|
if (this._lines.length === 0) {
|
|
2850
2881
|
this.measure(ctx);
|
|
2851
2882
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
const y = startY + i * lineHeight;
|
|
2860
|
-
ctx.fillText(line, point.x, y);
|
|
2861
|
-
}
|
|
2883
|
+
const bounds = {
|
|
2884
|
+
x: point.x - this._measuredWidth / 2,
|
|
2885
|
+
y: point.y - this._measuredHeight / 2,
|
|
2886
|
+
width: this._measuredWidth,
|
|
2887
|
+
height: this._measuredHeight
|
|
2888
|
+
};
|
|
2889
|
+
this.render(ctx, bounds);
|
|
2862
2890
|
}
|
|
2863
2891
|
applyStyle(ctx) {
|
|
2864
2892
|
const fontSize = this._style.fontSize ?? 14;
|
|
@@ -2904,7 +2932,9 @@ const snapshotLabel = (label) => {
|
|
|
2904
2932
|
return {
|
|
2905
2933
|
text: label.text,
|
|
2906
2934
|
editableText: label.editableText,
|
|
2907
|
-
style
|
|
2935
|
+
// Keep only local text overrides; computed style contains theme/class values
|
|
2936
|
+
// and would overwrite class text color on debounced history replay.
|
|
2937
|
+
style: cloneValue(label.styleOverrides),
|
|
2908
2938
|
styleClass: label.styleClass,
|
|
2909
2939
|
maxWidth: label.maxWidth
|
|
2910
2940
|
};
|
|
@@ -4730,14 +4760,13 @@ class Edge extends Element {
|
|
|
4730
4760
|
this._label.measure(ctx);
|
|
4731
4761
|
const labelWidth = this._label.measuredWidth;
|
|
4732
4762
|
const labelHeight = this._label.measuredHeight;
|
|
4733
|
-
const bgPadding = this._labelBackground?.padding ?? EDGE_LABEL_BACKGROUND_PADDING;
|
|
4734
4763
|
const bgColor = this._labelBackground?.color ?? "#ffffff";
|
|
4735
4764
|
const bgOpacity = this._labelBackground?.opacity ?? 1;
|
|
4736
4765
|
const bgRadius = this._labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
4737
|
-
const bgX = labelPosition.x - labelWidth / 2
|
|
4738
|
-
const bgY = labelPosition.y - labelHeight / 2
|
|
4739
|
-
const bgWidth = labelWidth
|
|
4740
|
-
const bgHeight = labelHeight
|
|
4766
|
+
const bgX = labelPosition.x - labelWidth / 2;
|
|
4767
|
+
const bgY = labelPosition.y - labelHeight / 2;
|
|
4768
|
+
const bgWidth = labelWidth;
|
|
4769
|
+
const bgHeight = labelHeight;
|
|
4741
4770
|
ctx.fillStyle = bgColor;
|
|
4742
4771
|
ctx.globalAlpha = bgOpacity;
|
|
4743
4772
|
if (bgRadius > 0) {
|
|
@@ -5042,6 +5071,7 @@ class InteractionManager {
|
|
|
5042
5071
|
if (shallowEqual(before, after)) {
|
|
5043
5072
|
return;
|
|
5044
5073
|
}
|
|
5074
|
+
this.renderer.markStyleDirty();
|
|
5045
5075
|
this.queuePropertyChange("node", nodeId, before, after);
|
|
5046
5076
|
}
|
|
5047
5077
|
changeEdgeProperties(edgeId, apply) {
|
|
@@ -5055,6 +5085,7 @@ class InteractionManager {
|
|
|
5055
5085
|
if (shallowEqual(before, after)) {
|
|
5056
5086
|
return;
|
|
5057
5087
|
}
|
|
5088
|
+
this.renderer.markStyleDirty();
|
|
5058
5089
|
this.queuePropertyChange("edge", edgeId, before, after);
|
|
5059
5090
|
}
|
|
5060
5091
|
changeGroupProperties(groupId, apply) {
|
|
@@ -5068,6 +5099,7 @@ class InteractionManager {
|
|
|
5068
5099
|
if (shallowEqual(before, after)) {
|
|
5069
5100
|
return;
|
|
5070
5101
|
}
|
|
5102
|
+
this.renderer.markStyleDirty();
|
|
5071
5103
|
this.queuePropertyChange("group", groupId, before, after);
|
|
5072
5104
|
}
|
|
5073
5105
|
removeNodeFromGroups(nodeId, groupIds) {
|
|
@@ -5558,6 +5590,7 @@ class InteractionManager {
|
|
|
5558
5590
|
pending.after
|
|
5559
5591
|
)
|
|
5560
5592
|
);
|
|
5593
|
+
this.renderer.markStyleDirty();
|
|
5561
5594
|
break;
|
|
5562
5595
|
case "edge":
|
|
5563
5596
|
this.historyManager.execute(
|
|
@@ -5568,6 +5601,7 @@ class InteractionManager {
|
|
|
5568
5601
|
pending.after
|
|
5569
5602
|
)
|
|
5570
5603
|
);
|
|
5604
|
+
this.renderer.markStyleDirty();
|
|
5571
5605
|
break;
|
|
5572
5606
|
case "group":
|
|
5573
5607
|
this.historyManager.execute(
|
|
@@ -5578,6 +5612,7 @@ class InteractionManager {
|
|
|
5578
5612
|
pending.after
|
|
5579
5613
|
)
|
|
5580
5614
|
);
|
|
5615
|
+
this.renderer.markStyleDirty();
|
|
5581
5616
|
break;
|
|
5582
5617
|
}
|
|
5583
5618
|
}
|
|
@@ -8043,8 +8078,10 @@ class NodeImage {
|
|
|
8043
8078
|
get placement() {
|
|
8044
8079
|
return this._options.placement ?? "center";
|
|
8045
8080
|
}
|
|
8046
|
-
|
|
8047
|
-
|
|
8081
|
+
/** Inset from edge of icon zone to image (single value for all sides) */
|
|
8082
|
+
get inset() {
|
|
8083
|
+
const v = this._options.inset ?? this._options.margin ?? this._options.padding;
|
|
8084
|
+
return v !== void 0 && Number.isFinite(v) ? Math.max(0, v) : 6;
|
|
8048
8085
|
}
|
|
8049
8086
|
setSource(source) {
|
|
8050
8087
|
this._loaded = false;
|
|
@@ -8059,23 +8096,18 @@ class NodeImage {
|
|
|
8059
8096
|
if (!this._loaded) {
|
|
8060
8097
|
return;
|
|
8061
8098
|
}
|
|
8062
|
-
const
|
|
8063
|
-
const margin = Math.max(0, this._options.margin ?? 0);
|
|
8099
|
+
const ins = this.inset;
|
|
8064
8100
|
const fit = this._options.fit ?? "none";
|
|
8065
8101
|
const scaleWithBounds = this._options.scaleWithBounds ?? false;
|
|
8066
8102
|
const opacity = this._options.opacity ?? 1;
|
|
8067
|
-
const align = this._options.align ?? "center";
|
|
8068
|
-
const verticalAlign = this._options.verticalAlign ?? "center";
|
|
8069
|
-
const offsetX = this._options.offsetX ?? 0;
|
|
8070
|
-
const offsetY = this._options.offsetY ?? 0;
|
|
8071
8103
|
const innerBounds = {
|
|
8072
|
-
x: bounds.x +
|
|
8073
|
-
y: bounds.y +
|
|
8074
|
-
width: Math.max(0, bounds.width -
|
|
8075
|
-
height: Math.max(0, bounds.height -
|
|
8104
|
+
x: bounds.x + ins,
|
|
8105
|
+
y: bounds.y + ins,
|
|
8106
|
+
width: Math.max(0, bounds.width - ins * 2),
|
|
8107
|
+
height: Math.max(0, bounds.height - ins * 2)
|
|
8076
8108
|
};
|
|
8077
|
-
const availableWidth = Math.max(0, innerBounds.width
|
|
8078
|
-
const availableHeight = Math.max(0, innerBounds.height
|
|
8109
|
+
const availableWidth = Math.max(0, innerBounds.width);
|
|
8110
|
+
const availableHeight = Math.max(0, innerBounds.height);
|
|
8079
8111
|
let drawWidth = this._options.width ?? this._naturalWidth;
|
|
8080
8112
|
let drawHeight = this._options.height ?? this._naturalHeight;
|
|
8081
8113
|
if (scaleWithBounds) {
|
|
@@ -8094,21 +8126,13 @@ class NodeImage {
|
|
|
8094
8126
|
const maxHeight = Math.max(0, availableHeight);
|
|
8095
8127
|
drawWidth = Math.min(drawWidth, maxWidth);
|
|
8096
8128
|
drawHeight = Math.min(drawHeight, maxHeight);
|
|
8097
|
-
let x = innerBounds.x
|
|
8098
|
-
let y = innerBounds.y
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
} else if (align === "right") {
|
|
8102
|
-
x = innerBounds.x + innerBounds.width - drawWidth - padding;
|
|
8103
|
-
}
|
|
8104
|
-
if (verticalAlign === "center") {
|
|
8105
|
-
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
8106
|
-
} else if (verticalAlign === "bottom") {
|
|
8107
|
-
y = innerBounds.y + innerBounds.height - drawHeight - padding;
|
|
8108
|
-
}
|
|
8129
|
+
let x = innerBounds.x;
|
|
8130
|
+
let y = innerBounds.y;
|
|
8131
|
+
x = innerBounds.x + (innerBounds.width - drawWidth) / 2;
|
|
8132
|
+
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
8109
8133
|
ctx.save();
|
|
8110
8134
|
ctx.globalAlpha = opacity;
|
|
8111
|
-
ctx.drawImage(this._image, x
|
|
8135
|
+
ctx.drawImage(this._image, x, y, drawWidth, drawHeight);
|
|
8112
8136
|
ctx.restore();
|
|
8113
8137
|
}
|
|
8114
8138
|
getSize() {
|
|
@@ -8198,8 +8222,8 @@ class Node extends Element {
|
|
|
8198
8222
|
this._nodeStyle = { ...DEFAULT_NODE_STYLE, ...options.style };
|
|
8199
8223
|
this._showPortsAlways = options.showPortsAlways ?? false;
|
|
8200
8224
|
this._anchorPoints = this.normalizeAnchorPoints(options.anchorPoints);
|
|
8201
|
-
this._labelPlacement = options.labelPlacement ?? "auto";
|
|
8202
8225
|
this._resizeHandlesEnabled = options.resizeHandlesEnabled ?? true;
|
|
8226
|
+
this._contentInset = this.normalizeContentInset(options.contentInset);
|
|
8203
8227
|
if (options.label !== void 0) {
|
|
8204
8228
|
if (typeof options.label === "string") {
|
|
8205
8229
|
this._label = new TextLabel({
|
|
@@ -8396,24 +8420,22 @@ class Node extends Element {
|
|
|
8396
8420
|
this._anchorPoints = this.normalizeAnchorPoints(value);
|
|
8397
8421
|
this.markDirty();
|
|
8398
8422
|
}
|
|
8399
|
-
/**
|
|
8400
|
-
* Label placement inside the node
|
|
8401
|
-
*/
|
|
8402
|
-
get labelPlacement() {
|
|
8403
|
-
return this._labelPlacement;
|
|
8404
|
-
}
|
|
8405
|
-
set labelPlacement(value) {
|
|
8406
|
-
if (this._labelPlacement !== value) {
|
|
8407
|
-
this._labelPlacement = value;
|
|
8408
|
-
this.markDirty();
|
|
8409
|
-
}
|
|
8410
|
-
}
|
|
8411
8423
|
/**
|
|
8412
8424
|
* Default size (initial width/height)
|
|
8413
8425
|
*/
|
|
8414
8426
|
get defaultSize() {
|
|
8415
8427
|
return { ...this._defaultSize };
|
|
8416
8428
|
}
|
|
8429
|
+
/**
|
|
8430
|
+
* Content area insets (per side). When all zero, content area = node bounds.
|
|
8431
|
+
*/
|
|
8432
|
+
get contentInset() {
|
|
8433
|
+
return { ...this._contentInset };
|
|
8434
|
+
}
|
|
8435
|
+
set contentInset(value) {
|
|
8436
|
+
this._contentInset = this.normalizeContentInset(value);
|
|
8437
|
+
this.markDirty();
|
|
8438
|
+
}
|
|
8417
8439
|
/**
|
|
8418
8440
|
* Set getter for attachToOutline (called by DiagramRenderer when node is added)
|
|
8419
8441
|
*/
|
|
@@ -8436,87 +8458,20 @@ class Node extends Element {
|
|
|
8436
8458
|
}
|
|
8437
8459
|
}
|
|
8438
8460
|
/**
|
|
8439
|
-
* Calculate layout for icon and label
|
|
8440
|
-
*
|
|
8461
|
+
* Calculate layout for icon and label.
|
|
8462
|
+
* Label always gets full content area; icon is placed within the same content area.
|
|
8441
8463
|
*/
|
|
8442
|
-
calculateContentLayout(bounds, iconBoxSize,
|
|
8443
|
-
|
|
8444
|
-
let
|
|
8464
|
+
calculateContentLayout(bounds, iconBoxSize, _labelSize) {
|
|
8465
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8466
|
+
let iconBounds = contentBounds;
|
|
8445
8467
|
if (this._icon && iconBoxSize) {
|
|
8446
|
-
iconBounds = this.getIconBounds(
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
if (isCornerPlacement(placement)) {
|
|
8452
|
-
iconBounds = this.getIconBounds(bounds, iconBoxSize, placement);
|
|
8453
|
-
labelBounds = this.getLabelContainerBounds(bounds);
|
|
8454
|
-
} else {
|
|
8455
|
-
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8456
|
-
switch (placement) {
|
|
8457
|
-
case "top":
|
|
8458
|
-
iconBounds = {
|
|
8459
|
-
x: bounds.x,
|
|
8460
|
-
y: bounds.y,
|
|
8461
|
-
width: bounds.width,
|
|
8462
|
-
height: iconBoxSize.height
|
|
8463
|
-
};
|
|
8464
|
-
labelBounds = {
|
|
8465
|
-
x: contentBounds.x,
|
|
8466
|
-
y: contentBounds.y + iconBoxSize.height + gap,
|
|
8467
|
-
width: contentBounds.width,
|
|
8468
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8469
|
-
};
|
|
8470
|
-
break;
|
|
8471
|
-
case "bottom":
|
|
8472
|
-
iconBounds = {
|
|
8473
|
-
x: bounds.x,
|
|
8474
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
8475
|
-
width: bounds.width,
|
|
8476
|
-
height: iconBoxSize.height
|
|
8477
|
-
};
|
|
8478
|
-
labelBounds = {
|
|
8479
|
-
x: contentBounds.x,
|
|
8480
|
-
y: contentBounds.y,
|
|
8481
|
-
width: contentBounds.width,
|
|
8482
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8483
|
-
};
|
|
8484
|
-
break;
|
|
8485
|
-
case "left":
|
|
8486
|
-
iconBounds = {
|
|
8487
|
-
x: bounds.x,
|
|
8488
|
-
y: bounds.y,
|
|
8489
|
-
width: iconBoxSize.width,
|
|
8490
|
-
height: bounds.height
|
|
8491
|
-
};
|
|
8492
|
-
labelBounds = {
|
|
8493
|
-
x: contentBounds.x + iconBoxSize.width + gap,
|
|
8494
|
-
y: contentBounds.y,
|
|
8495
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8496
|
-
height: contentBounds.height
|
|
8497
|
-
};
|
|
8498
|
-
break;
|
|
8499
|
-
case "right":
|
|
8500
|
-
iconBounds = {
|
|
8501
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
8502
|
-
y: bounds.y,
|
|
8503
|
-
width: iconBoxSize.width,
|
|
8504
|
-
height: bounds.height
|
|
8505
|
-
};
|
|
8506
|
-
labelBounds = {
|
|
8507
|
-
x: contentBounds.x,
|
|
8508
|
-
y: contentBounds.y,
|
|
8509
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8510
|
-
height: contentBounds.height
|
|
8511
|
-
};
|
|
8512
|
-
break;
|
|
8513
|
-
}
|
|
8514
|
-
}
|
|
8515
|
-
} else if (this._label && labelSize) {
|
|
8516
|
-
const autoBounds = this.getAutoLabelBounds(bounds, iconBoxSize);
|
|
8517
|
-
labelBounds = this.getLabelBounds(autoBounds, labelSize, this._labelPlacement);
|
|
8468
|
+
iconBounds = this.getIconBounds(
|
|
8469
|
+
contentBounds,
|
|
8470
|
+
iconBoxSize,
|
|
8471
|
+
this._icon.placement
|
|
8472
|
+
);
|
|
8518
8473
|
}
|
|
8519
|
-
return { iconBounds, labelBounds };
|
|
8474
|
+
return { iconBounds, labelBounds: contentBounds };
|
|
8520
8475
|
}
|
|
8521
8476
|
/**
|
|
8522
8477
|
* Get label bounds and wrapped lines for SVG export. Replicates renderContents layout logic.
|
|
@@ -8527,12 +8482,19 @@ class Node extends Element {
|
|
|
8527
8482
|
return null;
|
|
8528
8483
|
}
|
|
8529
8484
|
const bounds = this.getBounds();
|
|
8485
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8530
8486
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
8531
|
-
|
|
8532
|
-
this._label.setAutoMaxWidth(autoBounds.width);
|
|
8487
|
+
this._label.setAutoMaxWidth(contentBounds.width);
|
|
8533
8488
|
const labelSize = this._label.measure(ctx);
|
|
8534
|
-
const { labelBounds } = this.calculateContentLayout(
|
|
8535
|
-
|
|
8489
|
+
const { labelBounds } = this.calculateContentLayout(
|
|
8490
|
+
bounds,
|
|
8491
|
+
iconBoxSize,
|
|
8492
|
+
labelSize
|
|
8493
|
+
);
|
|
8494
|
+
const lines = this._label.getWrappedLines(
|
|
8495
|
+
ctx,
|
|
8496
|
+
Math.max(0, contentBounds.width)
|
|
8497
|
+
);
|
|
8536
8498
|
return { bounds: labelBounds, lines };
|
|
8537
8499
|
}
|
|
8538
8500
|
/**
|
|
@@ -8548,23 +8510,14 @@ class Node extends Element {
|
|
|
8548
8510
|
ctx.globalAlpha = 1;
|
|
8549
8511
|
}
|
|
8550
8512
|
/**
|
|
8551
|
-
* Get world position of label center.
|
|
8513
|
+
* Get world position of label center (center of content area).
|
|
8552
8514
|
*/
|
|
8553
8515
|
getLabelPosition() {
|
|
8554
|
-
const bounds = this.getBounds();
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
case "bottom":
|
|
8560
|
-
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height };
|
|
8561
|
-
case "left":
|
|
8562
|
-
return { x: bounds.x, y: bounds.y + bounds.height / 2 };
|
|
8563
|
-
case "right":
|
|
8564
|
-
return { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 };
|
|
8565
|
-
default:
|
|
8566
|
-
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
8567
|
-
}
|
|
8516
|
+
const bounds = this.getLabelContainerBounds(this.getBounds());
|
|
8517
|
+
return {
|
|
8518
|
+
x: bounds.x + bounds.width / 2,
|
|
8519
|
+
y: bounds.y + bounds.height / 2
|
|
8520
|
+
};
|
|
8568
8521
|
}
|
|
8569
8522
|
/**
|
|
8570
8523
|
* Render icon, label, and ports
|
|
@@ -8575,14 +8528,14 @@ class Node extends Element {
|
|
|
8575
8528
|
let bounds = this.getBounds();
|
|
8576
8529
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
8577
8530
|
if (this._label) {
|
|
8578
|
-
this._label.setAutoMaxWidth(this.
|
|
8531
|
+
this._label.setAutoMaxWidth(this.getLabelContainerBounds(bounds).width);
|
|
8579
8532
|
}
|
|
8580
8533
|
const labelSize = this._label ? this._label.measure(ctx) : void 0;
|
|
8581
8534
|
if (labelSize || iconBoxSize) {
|
|
8582
8535
|
this.ensureContentsFit(labelSize, iconBoxSize);
|
|
8583
8536
|
if (this._label && iconBoxSize) {
|
|
8584
8537
|
bounds = this.getBounds();
|
|
8585
|
-
this._label.setAutoMaxWidth(this.
|
|
8538
|
+
this._label.setAutoMaxWidth(this.getLabelContainerBounds(bounds).width);
|
|
8586
8539
|
}
|
|
8587
8540
|
}
|
|
8588
8541
|
const { iconBounds, labelBounds } = this.calculateContentLayout(
|
|
@@ -8867,124 +8820,45 @@ class Node extends Element {
|
|
|
8867
8820
|
}
|
|
8868
8821
|
}
|
|
8869
8822
|
calculateContentMinSize(labelSize, iconBoxSize, bounds) {
|
|
8870
|
-
let minWidth = 0;
|
|
8871
|
-
let minHeight = 0;
|
|
8872
8823
|
const labelContainer = this.getLabelContainerBounds(bounds);
|
|
8873
8824
|
const widthFactor = labelContainer.width > 0 ? bounds.width / labelContainer.width : 1;
|
|
8874
8825
|
const heightFactor = labelContainer.height > 0 ? bounds.height / labelContainer.height : 1;
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
if (this._labelPlacement === "auto" && iconPlacement !== "center") {
|
|
8888
|
-
if (isCornerPlacement(iconPlacement)) {
|
|
8889
|
-
minWidth = Math.max(minWidth, iconBoxSize.width, labelSize.width);
|
|
8890
|
-
minHeight = Math.max(minHeight, iconBoxSize.height, labelSize.height);
|
|
8891
|
-
} else if (iconPlacement === "top" || iconPlacement === "bottom") {
|
|
8892
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8893
|
-
minWidth = Math.max(minWidth, iconBoxSize.width, labelSize.width);
|
|
8894
|
-
} else if (iconPlacement === "left" || iconPlacement === "right") {
|
|
8895
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8896
|
-
minHeight = Math.max(minHeight, iconBoxSize.height, labelSize.height);
|
|
8897
|
-
}
|
|
8898
|
-
} else {
|
|
8899
|
-
const labelHorizontal = labelPlacement === "left" || labelPlacement === "right";
|
|
8900
|
-
const labelVertical = labelPlacement === "top" || labelPlacement === "bottom";
|
|
8901
|
-
const iconHorizontal = iconPlacement === "left" || iconPlacement === "right";
|
|
8902
|
-
const iconVertical = iconPlacement === "top" || iconPlacement === "bottom";
|
|
8903
|
-
const labelSharesIconAxis = iconHorizontal && (labelPlacement === "center" || labelPlacement === iconPlacement) || iconVertical && (labelPlacement === "center" || labelPlacement === iconPlacement);
|
|
8904
|
-
if (labelHorizontal && iconHorizontal && labelPlacement !== iconPlacement) {
|
|
8905
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8906
|
-
}
|
|
8907
|
-
if (labelVertical && iconVertical && labelPlacement !== iconPlacement) {
|
|
8908
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8909
|
-
}
|
|
8910
|
-
if (labelSharesIconAxis) {
|
|
8911
|
-
if (iconHorizontal) {
|
|
8912
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8913
|
-
}
|
|
8914
|
-
if (iconVertical) {
|
|
8915
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8916
|
-
}
|
|
8917
|
-
}
|
|
8918
|
-
}
|
|
8919
|
-
}
|
|
8920
|
-
return { width: minWidth, height: minHeight };
|
|
8826
|
+
const minContentWidth = Math.max(
|
|
8827
|
+
labelSize?.width ?? 0,
|
|
8828
|
+
iconBoxSize?.width ?? 0
|
|
8829
|
+
);
|
|
8830
|
+
const minContentHeight = Math.max(
|
|
8831
|
+
labelSize?.height ?? 0,
|
|
8832
|
+
iconBoxSize?.height ?? 0
|
|
8833
|
+
);
|
|
8834
|
+
return {
|
|
8835
|
+
width: minContentWidth * widthFactor,
|
|
8836
|
+
height: minContentHeight * heightFactor
|
|
8837
|
+
};
|
|
8921
8838
|
}
|
|
8922
8839
|
getLabelContainerBounds(bounds) {
|
|
8923
|
-
|
|
8840
|
+
const { top, right, bottom, left } = this._contentInset;
|
|
8841
|
+
const x = bounds.x + left;
|
|
8842
|
+
const y = bounds.y + top;
|
|
8843
|
+
const width = Math.max(0, bounds.width - left - right);
|
|
8844
|
+
const height = Math.max(0, bounds.height - top - bottom);
|
|
8845
|
+
return { x, y, width, height };
|
|
8924
8846
|
}
|
|
8925
|
-
|
|
8926
|
-
const
|
|
8927
|
-
if (
|
|
8928
|
-
return
|
|
8929
|
-
}
|
|
8930
|
-
const gap = this._icon.gap;
|
|
8931
|
-
if (isCornerPlacement(this._icon.placement)) {
|
|
8932
|
-
return contentBounds;
|
|
8933
|
-
}
|
|
8934
|
-
switch (this._icon.placement) {
|
|
8935
|
-
case "left":
|
|
8936
|
-
return {
|
|
8937
|
-
x: contentBounds.x + iconBoxSize.width + gap,
|
|
8938
|
-
y: contentBounds.y,
|
|
8939
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8940
|
-
height: contentBounds.height
|
|
8941
|
-
};
|
|
8942
|
-
case "right":
|
|
8943
|
-
return {
|
|
8944
|
-
x: contentBounds.x,
|
|
8945
|
-
y: contentBounds.y,
|
|
8946
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8947
|
-
height: contentBounds.height
|
|
8948
|
-
};
|
|
8949
|
-
case "top":
|
|
8950
|
-
return {
|
|
8951
|
-
x: contentBounds.x,
|
|
8952
|
-
y: contentBounds.y + iconBoxSize.height + gap,
|
|
8953
|
-
width: contentBounds.width,
|
|
8954
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8955
|
-
};
|
|
8956
|
-
case "bottom":
|
|
8957
|
-
return {
|
|
8958
|
-
x: contentBounds.x,
|
|
8959
|
-
y: contentBounds.y,
|
|
8960
|
-
width: contentBounds.width,
|
|
8961
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8962
|
-
};
|
|
8963
|
-
default:
|
|
8964
|
-
return contentBounds;
|
|
8847
|
+
normalizeContentInset(value) {
|
|
8848
|
+
const n = (v) => v !== void 0 && Number.isFinite(v) ? Math.max(0, v) : 0;
|
|
8849
|
+
if (value === void 0) {
|
|
8850
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
8965
8851
|
}
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
const width = Math.min(labelSize.width, bounds.width);
|
|
8970
|
-
const height = Math.min(labelSize.height, bounds.height);
|
|
8971
|
-
let x = bounds.x + (bounds.width - width) / 2;
|
|
8972
|
-
let y = bounds.y + (bounds.height - height) / 2;
|
|
8973
|
-
switch (normalizedPlacement) {
|
|
8974
|
-
case "top":
|
|
8975
|
-
y = bounds.y;
|
|
8976
|
-
break;
|
|
8977
|
-
case "bottom":
|
|
8978
|
-
y = bounds.y + bounds.height - height;
|
|
8979
|
-
break;
|
|
8980
|
-
case "left":
|
|
8981
|
-
x = bounds.x;
|
|
8982
|
-
break;
|
|
8983
|
-
case "right":
|
|
8984
|
-
x = bounds.x + bounds.width - width;
|
|
8985
|
-
break;
|
|
8852
|
+
if (typeof value === "number") {
|
|
8853
|
+
const v = n(value);
|
|
8854
|
+
return { top: v, right: v, bottom: v, left: v };
|
|
8986
8855
|
}
|
|
8987
|
-
return {
|
|
8856
|
+
return {
|
|
8857
|
+
top: n(value.top),
|
|
8858
|
+
right: n(value.right),
|
|
8859
|
+
bottom: n(value.bottom),
|
|
8860
|
+
left: n(value.left)
|
|
8861
|
+
};
|
|
8988
8862
|
}
|
|
8989
8863
|
getIconBoxSize() {
|
|
8990
8864
|
if (!this._icon) {
|
|
@@ -8994,14 +8868,14 @@ class Node extends Element {
|
|
|
8994
8868
|
if (width <= 0 || height <= 0) {
|
|
8995
8869
|
return void 0;
|
|
8996
8870
|
}
|
|
8997
|
-
const
|
|
8998
|
-
const margin = Math.max(0, this._icon.options.margin ?? 0);
|
|
8871
|
+
const ins = this._icon.inset;
|
|
8999
8872
|
return {
|
|
9000
|
-
width: width +
|
|
9001
|
-
height: height +
|
|
8873
|
+
width: width + ins * 2,
|
|
8874
|
+
height: height + ins * 2
|
|
9002
8875
|
};
|
|
9003
8876
|
}
|
|
9004
8877
|
getIconBounds(bounds, iconBoxSize, placement) {
|
|
8878
|
+
const ins = this._icon?.inset ?? 0;
|
|
9005
8879
|
switch (placement) {
|
|
9006
8880
|
case "top":
|
|
9007
8881
|
return {
|
|
@@ -9033,29 +8907,29 @@ class Node extends Element {
|
|
|
9033
8907
|
};
|
|
9034
8908
|
case "top-left":
|
|
9035
8909
|
return {
|
|
9036
|
-
x: bounds.x,
|
|
9037
|
-
y: bounds.y,
|
|
8910
|
+
x: bounds.x + ins,
|
|
8911
|
+
y: bounds.y + ins,
|
|
9038
8912
|
width: iconBoxSize.width,
|
|
9039
8913
|
height: iconBoxSize.height
|
|
9040
8914
|
};
|
|
9041
8915
|
case "top-right":
|
|
9042
8916
|
return {
|
|
9043
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
9044
|
-
y: bounds.y,
|
|
8917
|
+
x: bounds.x + bounds.width - iconBoxSize.width - ins,
|
|
8918
|
+
y: bounds.y + ins,
|
|
9045
8919
|
width: iconBoxSize.width,
|
|
9046
8920
|
height: iconBoxSize.height
|
|
9047
8921
|
};
|
|
9048
8922
|
case "bottom-left":
|
|
9049
8923
|
return {
|
|
9050
|
-
x: bounds.x,
|
|
9051
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
8924
|
+
x: bounds.x + ins,
|
|
8925
|
+
y: bounds.y + bounds.height - iconBoxSize.height - ins,
|
|
9052
8926
|
width: iconBoxSize.width,
|
|
9053
8927
|
height: iconBoxSize.height
|
|
9054
8928
|
};
|
|
9055
8929
|
case "bottom-right":
|
|
9056
8930
|
return {
|
|
9057
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
9058
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
8931
|
+
x: bounds.x + bounds.width - iconBoxSize.width - ins,
|
|
8932
|
+
y: bounds.y + bounds.height - iconBoxSize.height - ins,
|
|
9059
8933
|
width: iconBoxSize.width,
|
|
9060
8934
|
height: iconBoxSize.height
|
|
9061
8935
|
};
|
|
@@ -9954,18 +9828,14 @@ const DEFAULT_THEME = {
|
|
|
9954
9828
|
cornerRadius: 4
|
|
9955
9829
|
},
|
|
9956
9830
|
selected: {
|
|
9957
|
-
|
|
9958
|
-
strokeColor: "#333333",
|
|
9831
|
+
strokeColor: "#3b82f6",
|
|
9959
9832
|
strokeWidth: 2,
|
|
9960
|
-
opacity: 1
|
|
9961
|
-
cornerRadius: 4
|
|
9833
|
+
opacity: 1
|
|
9962
9834
|
},
|
|
9963
9835
|
dragging: {
|
|
9964
|
-
fillColor: "#f0f0f0",
|
|
9965
9836
|
strokeColor: "#333333",
|
|
9966
9837
|
strokeWidth: 2,
|
|
9967
|
-
opacity: 0.8
|
|
9968
|
-
cornerRadius: 4
|
|
9838
|
+
opacity: 0.8
|
|
9969
9839
|
}
|
|
9970
9840
|
},
|
|
9971
9841
|
edge: {
|
|
@@ -10226,15 +10096,16 @@ class StyleManager {
|
|
|
10226
10096
|
return baseStyle;
|
|
10227
10097
|
}
|
|
10228
10098
|
getBaseNodeStyle(state) {
|
|
10099
|
+
const d = this._theme.node.default;
|
|
10229
10100
|
switch (state) {
|
|
10230
10101
|
case "hover":
|
|
10231
|
-
return this._theme.node.hover;
|
|
10102
|
+
return { ...d, ...this._theme.node.hover };
|
|
10232
10103
|
case "selected":
|
|
10233
|
-
return this._theme.node.selected;
|
|
10104
|
+
return { ...d, ...this._theme.node.selected };
|
|
10234
10105
|
case "dragging":
|
|
10235
|
-
return this._theme.node.dragging;
|
|
10106
|
+
return { ...d, ...this._theme.node.dragging };
|
|
10236
10107
|
default:
|
|
10237
|
-
return
|
|
10108
|
+
return d;
|
|
10238
10109
|
}
|
|
10239
10110
|
}
|
|
10240
10111
|
getBaseEdgeStyle(state) {
|
|
@@ -10432,13 +10303,16 @@ class Serializer {
|
|
|
10432
10303
|
}));
|
|
10433
10304
|
let label;
|
|
10434
10305
|
if (node.label) {
|
|
10435
|
-
const
|
|
10306
|
+
const ins = node.label.inset;
|
|
10307
|
+
const defaultInset = 8;
|
|
10308
|
+
const insetAllDefault = ins.top === defaultInset && ins.right === defaultInset && ins.bottom === defaultInset && ins.left === defaultInset;
|
|
10309
|
+
const serializedInset = insetAllDefault ? void 0 : ins.top === ins.right && ins.top === ins.bottom && ins.top === ins.left ? ins.top : { top: ins.top, right: ins.right, bottom: ins.bottom, left: ins.left };
|
|
10310
|
+
const labelDefaults = { inset: void 0, style: {}, maxWidth: void 0, styleClass: void 0 };
|
|
10436
10311
|
const labelData = {
|
|
10437
10312
|
text: node.label.text,
|
|
10438
10313
|
style: node.label.style,
|
|
10439
10314
|
maxWidth: node.label.maxWidth,
|
|
10440
|
-
|
|
10441
|
-
margin: node.label.margin,
|
|
10315
|
+
inset: serializedInset ?? void 0,
|
|
10442
10316
|
styleClass: node.label.styleClass
|
|
10443
10317
|
};
|
|
10444
10318
|
const hasExtendedOptions = hasNonDefaultValues(labelData, labelDefaults);
|
|
@@ -10447,8 +10321,7 @@ class Serializer {
|
|
|
10447
10321
|
text: node.label.text,
|
|
10448
10322
|
style: Object.keys(node.label.style).length > 0 ? node.label.style : void 0,
|
|
10449
10323
|
maxWidth: node.label.maxWidth,
|
|
10450
|
-
|
|
10451
|
-
margin: node.label.margin !== 0 ? node.label.margin : void 0,
|
|
10324
|
+
inset: serializedInset,
|
|
10452
10325
|
styleClass: node.label.styleClass
|
|
10453
10326
|
});
|
|
10454
10327
|
} else {
|
|
@@ -10460,22 +10333,16 @@ class Serializer {
|
|
|
10460
10333
|
const opts = node.icon.options;
|
|
10461
10334
|
const source = typeof opts.source === "string" ? opts.source : void 0;
|
|
10462
10335
|
if (source) {
|
|
10463
|
-
icon = {
|
|
10336
|
+
icon = omitEmptyValues({
|
|
10464
10337
|
source,
|
|
10465
10338
|
width: opts.width,
|
|
10466
10339
|
height: opts.height,
|
|
10467
10340
|
fit: opts.fit,
|
|
10468
10341
|
placement: opts.placement,
|
|
10469
10342
|
scaleWithBounds: opts.scaleWithBounds,
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
opacity: opts.opacity,
|
|
10474
|
-
align: opts.align,
|
|
10475
|
-
verticalAlign: opts.verticalAlign,
|
|
10476
|
-
offsetX: opts.offsetX,
|
|
10477
|
-
offsetY: opts.offsetY
|
|
10478
|
-
};
|
|
10343
|
+
inset: node.icon.inset !== 6 ? node.icon.inset : void 0,
|
|
10344
|
+
opacity: opts.opacity
|
|
10345
|
+
});
|
|
10479
10346
|
}
|
|
10480
10347
|
}
|
|
10481
10348
|
let anchorPoints;
|
|
@@ -10485,7 +10352,9 @@ class Serializer {
|
|
|
10485
10352
|
if (hasNonDefaultValues(anchorData, anchorDefaults)) {
|
|
10486
10353
|
anchorPoints = omitDefaultValues(anchorData, anchorDefaults);
|
|
10487
10354
|
}
|
|
10488
|
-
|
|
10355
|
+
const contentInset = node.contentInset;
|
|
10356
|
+
const hasContentInset = contentInset.top !== 0 || contentInset.right !== 0 || contentInset.bottom !== 0 || contentInset.left !== 0;
|
|
10357
|
+
return omitEmptyValues({
|
|
10489
10358
|
id: node.id,
|
|
10490
10359
|
type: node.typeName,
|
|
10491
10360
|
x: node.x,
|
|
@@ -10496,12 +10365,12 @@ class Serializer {
|
|
|
10496
10365
|
styleClass: node.styleClass,
|
|
10497
10366
|
label,
|
|
10498
10367
|
labelStyleClass: typeof label === "string" ? node.label?.styleClass : void 0,
|
|
10499
|
-
labelPlacement: node.labelPlacement !== "auto" ? node.labelPlacement : void 0,
|
|
10500
10368
|
icon,
|
|
10369
|
+
contentInset: hasContentInset ? contentInset : void 0,
|
|
10501
10370
|
anchorPoints,
|
|
10502
10371
|
ports: ports.length > 0 ? ports : void 0,
|
|
10503
10372
|
data: Object.keys(node.data).length > 0 ? node.data : void 0
|
|
10504
|
-
};
|
|
10373
|
+
});
|
|
10505
10374
|
}
|
|
10506
10375
|
serializeEdge(edge) {
|
|
10507
10376
|
return {
|
|
@@ -10812,7 +10681,7 @@ class SvgExporter {
|
|
|
10812
10681
|
const parts = [];
|
|
10813
10682
|
const renderedEdges = [];
|
|
10814
10683
|
parts.push(
|
|
10815
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
|
|
10684
|
+
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
|
|
10816
10685
|
);
|
|
10817
10686
|
for (const edge of this.renderer.edges.values()) {
|
|
10818
10687
|
if (edge.visible) {
|
|
@@ -10953,22 +10822,37 @@ class SvgExporter {
|
|
|
10953
10822
|
if (!edge.label) {
|
|
10954
10823
|
return "";
|
|
10955
10824
|
}
|
|
10956
|
-
const metrics = this.measureTextLabel(edge.label.text, edge.label.style, edge.label.
|
|
10957
|
-
const bgPadding = edge.labelBackground?.padding ?? EDGE_LABEL_BACKGROUND_PADDING;
|
|
10825
|
+
const metrics = this.measureTextLabel(edge.label.text, edge.label.style, edge.label.inset);
|
|
10958
10826
|
const bgColor = edge.labelBackground?.color ?? "#ffffff";
|
|
10959
10827
|
const bgOpacity = edge.labelBackground?.opacity ?? 1;
|
|
10960
10828
|
const bgRadius = edge.labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
10961
|
-
const x = point.x - metrics.width / 2
|
|
10962
|
-
const y = point.y - metrics.height / 2
|
|
10963
|
-
const width = metrics.width
|
|
10964
|
-
const height = metrics.height
|
|
10829
|
+
const x = point.x - metrics.width / 2;
|
|
10830
|
+
const y = point.y - metrics.height / 2;
|
|
10831
|
+
const width = metrics.width;
|
|
10832
|
+
const height = metrics.height;
|
|
10965
10833
|
const radius = Math.max(0, Math.min(bgRadius, width / 2, height / 2));
|
|
10966
10834
|
if (radius <= 0) {
|
|
10967
10835
|
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
|
|
10968
10836
|
}
|
|
10969
10837
|
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
|
|
10970
10838
|
}
|
|
10971
|
-
|
|
10839
|
+
normalizeLabelInset(value, defaultVal) {
|
|
10840
|
+
const n = (v) => v !== void 0 && Number.isFinite(v) ? Math.max(0, v) : defaultVal;
|
|
10841
|
+
if (value === void 0) {
|
|
10842
|
+
return { top: defaultVal, right: defaultVal, bottom: defaultVal, left: defaultVal };
|
|
10843
|
+
}
|
|
10844
|
+
if (typeof value === "number") {
|
|
10845
|
+
const v = n(value);
|
|
10846
|
+
return { top: v, right: v, bottom: v, left: v };
|
|
10847
|
+
}
|
|
10848
|
+
return {
|
|
10849
|
+
top: n(value.top),
|
|
10850
|
+
right: n(value.right),
|
|
10851
|
+
bottom: n(value.bottom),
|
|
10852
|
+
left: n(value.left)
|
|
10853
|
+
};
|
|
10854
|
+
}
|
|
10855
|
+
measureTextLabel(text, style = {}, inset = 8) {
|
|
10972
10856
|
const fontSize = style.fontSize ?? 14;
|
|
10973
10857
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
10974
10858
|
const fontWeight = style.fontWeight ?? "normal";
|
|
@@ -10989,11 +10873,10 @@ class SvgExporter {
|
|
|
10989
10873
|
const longestLine = lines.reduce((max, line) => line.length > max.length ? line : max, "");
|
|
10990
10874
|
maxWidth = longestLine.length * fontSize * 0.6;
|
|
10991
10875
|
}
|
|
10992
|
-
const
|
|
10993
|
-
const resolvedMargin = Math.max(0, margin);
|
|
10876
|
+
const ins = this.normalizeLabelInset(inset, 8);
|
|
10994
10877
|
return {
|
|
10995
|
-
width: maxWidth +
|
|
10996
|
-
height: lines.length * lineHeight +
|
|
10878
|
+
width: maxWidth + ins.left + ins.right,
|
|
10879
|
+
height: lines.length * lineHeight + ins.top + ins.bottom
|
|
10997
10880
|
};
|
|
10998
10881
|
}
|
|
10999
10882
|
resolveMarkerConfig(edge, side) {
|
|
@@ -11118,9 +11001,9 @@ class SvgExporter {
|
|
|
11118
11001
|
return "";
|
|
11119
11002
|
}
|
|
11120
11003
|
const style = label.style;
|
|
11121
|
-
const
|
|
11122
|
-
const margin = Math.max(0, label.margin);
|
|
11004
|
+
const ins = this.normalizeLabelInset(label.inset, 8);
|
|
11123
11005
|
const align = style.align ?? "center";
|
|
11006
|
+
const verticalAlign = style.verticalAlign ?? "middle";
|
|
11124
11007
|
const ctx = this.getMeasurementContext();
|
|
11125
11008
|
let bounds;
|
|
11126
11009
|
let lines;
|
|
@@ -11138,18 +11021,27 @@ class SvgExporter {
|
|
|
11138
11021
|
lines = label.text.split("\n");
|
|
11139
11022
|
}
|
|
11140
11023
|
const inner = {
|
|
11141
|
-
x: bounds.x +
|
|
11142
|
-
y: bounds.y +
|
|
11143
|
-
width: Math.max(0, bounds.width -
|
|
11144
|
-
height: Math.max(0, bounds.height -
|
|
11024
|
+
x: bounds.x + ins.left,
|
|
11025
|
+
y: bounds.y + ins.top,
|
|
11026
|
+
width: Math.max(0, bounds.width - ins.left - ins.right),
|
|
11027
|
+
height: Math.max(0, bounds.height - ins.top - ins.bottom)
|
|
11145
11028
|
};
|
|
11146
11029
|
let x = inner.x + inner.width / 2;
|
|
11147
11030
|
if (align === "left") {
|
|
11148
|
-
x = inner.x
|
|
11031
|
+
x = inner.x;
|
|
11149
11032
|
} else if (align === "right") {
|
|
11150
|
-
x = inner.x + inner.width
|
|
11033
|
+
x = inner.x + inner.width;
|
|
11034
|
+
}
|
|
11035
|
+
const lineHeight = (style.fontSize ?? 14) * 1.2;
|
|
11036
|
+
const totalHeight = lines.length * lineHeight;
|
|
11037
|
+
let y;
|
|
11038
|
+
if (verticalAlign === "top") {
|
|
11039
|
+
y = inner.y + totalHeight / 2;
|
|
11040
|
+
} else if (verticalAlign === "bottom") {
|
|
11041
|
+
y = inner.y + inner.height - totalHeight / 2;
|
|
11042
|
+
} else {
|
|
11043
|
+
y = inner.y + inner.height / 2;
|
|
11151
11044
|
}
|
|
11152
|
-
const y = inner.y + inner.height / 2;
|
|
11153
11045
|
return this.renderTextLabel(lines.join("\n"), { x, y }, style);
|
|
11154
11046
|
}
|
|
11155
11047
|
getNodeCornerRadius(node, bounds) {
|
|
@@ -11166,9 +11058,15 @@ class SvgExporter {
|
|
|
11166
11058
|
if (iconSize.width <= 0 || iconSize.height <= 0) {
|
|
11167
11059
|
return "";
|
|
11168
11060
|
}
|
|
11169
|
-
const
|
|
11170
|
-
const
|
|
11171
|
-
const
|
|
11061
|
+
const iconInset = icon.inset;
|
|
11062
|
+
const iconBoxSize = this.getIconBoxSize(iconSize, iconInset);
|
|
11063
|
+
const iconBounds = this.getIconBounds(
|
|
11064
|
+
nodeBounds,
|
|
11065
|
+
iconBoxSize,
|
|
11066
|
+
opts.placement ?? "center",
|
|
11067
|
+
iconInset
|
|
11068
|
+
);
|
|
11069
|
+
const drawRect = this.getIconDrawRect(iconBounds, opts, iconSize, iconInset);
|
|
11172
11070
|
if (drawRect.width <= 0 || drawRect.height <= 0) {
|
|
11173
11071
|
return "";
|
|
11174
11072
|
}
|
|
@@ -11177,17 +11075,16 @@ class SvgExporter {
|
|
|
11177
11075
|
return "";
|
|
11178
11076
|
}
|
|
11179
11077
|
const opacity = opts.opacity ?? 1;
|
|
11180
|
-
|
|
11078
|
+
const escapedHref = this.escapeAttribute(href);
|
|
11079
|
+
return `<image href="${escapedHref}" xlink:href="${escapedHref}" x="${drawRect.x}" y="${drawRect.y}" width="${drawRect.width}" height="${drawRect.height}" opacity="${opacity}" preserveAspectRatio="none"/>`;
|
|
11181
11080
|
}
|
|
11182
|
-
getIconBoxSize(
|
|
11183
|
-
const padding = opts.padding ?? 8;
|
|
11184
|
-
const margin = Math.max(0, opts.margin ?? 0);
|
|
11081
|
+
getIconBoxSize(imageSize, inset) {
|
|
11185
11082
|
return {
|
|
11186
|
-
width: imageSize.width +
|
|
11187
|
-
height: imageSize.height +
|
|
11083
|
+
width: imageSize.width + inset * 2,
|
|
11084
|
+
height: imageSize.height + inset * 2
|
|
11188
11085
|
};
|
|
11189
11086
|
}
|
|
11190
|
-
getIconBounds(bounds, iconBoxSize, placement) {
|
|
11087
|
+
getIconBounds(bounds, iconBoxSize, placement, inset) {
|
|
11191
11088
|
switch (placement) {
|
|
11192
11089
|
case "top":
|
|
11193
11090
|
return { x: bounds.x, y: bounds.y, width: bounds.width, height: iconBoxSize.height };
|
|
@@ -11208,25 +11105,30 @@ class SvgExporter {
|
|
|
11208
11105
|
height: bounds.height
|
|
11209
11106
|
};
|
|
11210
11107
|
case "top-left":
|
|
11211
|
-
return {
|
|
11108
|
+
return {
|
|
11109
|
+
x: bounds.x + inset,
|
|
11110
|
+
y: bounds.y + inset,
|
|
11111
|
+
width: iconBoxSize.width,
|
|
11112
|
+
height: iconBoxSize.height
|
|
11113
|
+
};
|
|
11212
11114
|
case "top-right":
|
|
11213
11115
|
return {
|
|
11214
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
11215
|
-
y: bounds.y,
|
|
11116
|
+
x: bounds.x + bounds.width - iconBoxSize.width - inset,
|
|
11117
|
+
y: bounds.y + inset,
|
|
11216
11118
|
width: iconBoxSize.width,
|
|
11217
11119
|
height: iconBoxSize.height
|
|
11218
11120
|
};
|
|
11219
11121
|
case "bottom-left":
|
|
11220
11122
|
return {
|
|
11221
|
-
x: bounds.x,
|
|
11222
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
11123
|
+
x: bounds.x + inset,
|
|
11124
|
+
y: bounds.y + bounds.height - iconBoxSize.height - inset,
|
|
11223
11125
|
width: iconBoxSize.width,
|
|
11224
11126
|
height: iconBoxSize.height
|
|
11225
11127
|
};
|
|
11226
11128
|
case "bottom-right":
|
|
11227
11129
|
return {
|
|
11228
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
11229
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
11130
|
+
x: bounds.x + bounds.width - iconBoxSize.width - inset,
|
|
11131
|
+
y: bounds.y + bounds.height - iconBoxSize.height - inset,
|
|
11230
11132
|
width: iconBoxSize.width,
|
|
11231
11133
|
height: iconBoxSize.height
|
|
11232
11134
|
};
|
|
@@ -11235,23 +11137,17 @@ class SvgExporter {
|
|
|
11235
11137
|
return bounds;
|
|
11236
11138
|
}
|
|
11237
11139
|
}
|
|
11238
|
-
getIconDrawRect(bounds, opts, imageSize) {
|
|
11239
|
-
const padding = opts.padding ?? 8;
|
|
11240
|
-
const margin = Math.max(0, opts.margin ?? 0);
|
|
11140
|
+
getIconDrawRect(bounds, opts, imageSize, inset) {
|
|
11241
11141
|
const fit = opts.fit ?? "none";
|
|
11242
11142
|
const scaleWithBounds = opts.scaleWithBounds ?? false;
|
|
11243
|
-
const align = opts.align ?? "center";
|
|
11244
|
-
const verticalAlign = opts.verticalAlign ?? "center";
|
|
11245
|
-
const offsetX = opts.offsetX ?? 0;
|
|
11246
|
-
const offsetY = opts.offsetY ?? 0;
|
|
11247
11143
|
const innerBounds = {
|
|
11248
|
-
x: bounds.x +
|
|
11249
|
-
y: bounds.y +
|
|
11250
|
-
width: Math.max(0, bounds.width -
|
|
11251
|
-
height: Math.max(0, bounds.height -
|
|
11144
|
+
x: bounds.x + inset,
|
|
11145
|
+
y: bounds.y + inset,
|
|
11146
|
+
width: Math.max(0, bounds.width - inset * 2),
|
|
11147
|
+
height: Math.max(0, bounds.height - inset * 2)
|
|
11252
11148
|
};
|
|
11253
|
-
const availableWidth = Math.max(0, innerBounds.width
|
|
11254
|
-
const availableHeight = Math.max(0, innerBounds.height
|
|
11149
|
+
const availableWidth = Math.max(0, innerBounds.width);
|
|
11150
|
+
const availableHeight = Math.max(0, innerBounds.height);
|
|
11255
11151
|
let drawWidth = opts.width ?? imageSize.width;
|
|
11256
11152
|
let drawHeight = opts.height ?? imageSize.height;
|
|
11257
11153
|
if (scaleWithBounds) {
|
|
@@ -11268,21 +11164,13 @@ class SvgExporter {
|
|
|
11268
11164
|
}
|
|
11269
11165
|
drawWidth = Math.min(Math.max(0, drawWidth), Math.max(0, availableWidth));
|
|
11270
11166
|
drawHeight = Math.min(Math.max(0, drawHeight), Math.max(0, availableHeight));
|
|
11271
|
-
let x = innerBounds.x
|
|
11272
|
-
let y = innerBounds.y
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
} else if (align === "right") {
|
|
11276
|
-
x = innerBounds.x + innerBounds.width - drawWidth - padding;
|
|
11277
|
-
}
|
|
11278
|
-
if (verticalAlign === "center") {
|
|
11279
|
-
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
11280
|
-
} else if (verticalAlign === "bottom") {
|
|
11281
|
-
y = innerBounds.y + innerBounds.height - drawHeight - padding;
|
|
11282
|
-
}
|
|
11167
|
+
let x = innerBounds.x;
|
|
11168
|
+
let y = innerBounds.y;
|
|
11169
|
+
x = innerBounds.x + (innerBounds.width - drawWidth) / 2;
|
|
11170
|
+
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
11283
11171
|
return {
|
|
11284
|
-
x
|
|
11285
|
-
y
|
|
11172
|
+
x,
|
|
11173
|
+
y,
|
|
11286
11174
|
width: drawWidth,
|
|
11287
11175
|
height: drawHeight
|
|
11288
11176
|
};
|
|
@@ -11377,7 +11265,7 @@ class SvgExporter {
|
|
|
11377
11265
|
}
|
|
11378
11266
|
createEmptySvg(width, height, backgroundColor, includeBackground) {
|
|
11379
11267
|
return [
|
|
11380
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
|
|
11268
|
+
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
|
|
11381
11269
|
includeBackground ? `<rect width="100%" height="100%" fill="${backgroundColor}"/>` : "",
|
|
11382
11270
|
"</svg>"
|
|
11383
11271
|
].join("");
|