@ngroznykh/papirus 0.3.22 → 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 +297 -411
- 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) {
|
|
@@ -2621,8 +2638,10 @@ class TextLabel {
|
|
|
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
|
}
|
|
@@ -2667,6 +2686,12 @@ class TextLabel {
|
|
|
2667
2686
|
this._measureDirty = true;
|
|
2668
2687
|
this._onChange?.();
|
|
2669
2688
|
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Raw text style overrides (without StyleManager merge)
|
|
2691
|
+
*/
|
|
2692
|
+
get styleOverrides() {
|
|
2693
|
+
return { ...this._localStyle };
|
|
2694
|
+
}
|
|
2670
2695
|
/**
|
|
2671
2696
|
* Style class name for StyleManager
|
|
2672
2697
|
*/
|
|
@@ -2708,30 +2733,15 @@ class TextLabel {
|
|
|
2708
2733
|
this._measureDirty = true;
|
|
2709
2734
|
}
|
|
2710
2735
|
/**
|
|
2711
|
-
*
|
|
2736
|
+
* Inset from bounds edge to text (per side: top, right, bottom, left)
|
|
2712
2737
|
*/
|
|
2713
|
-
get
|
|
2714
|
-
return this.
|
|
2738
|
+
get inset() {
|
|
2739
|
+
return { ...this._inset };
|
|
2715
2740
|
}
|
|
2716
|
-
set
|
|
2717
|
-
const next =
|
|
2718
|
-
if (this.
|
|
2719
|
-
this.
|
|
2720
|
-
this._lines = [];
|
|
2721
|
-
this._measureDirty = true;
|
|
2722
|
-
this._onChange?.();
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
/**
|
|
2726
|
-
* Label margin
|
|
2727
|
-
*/
|
|
2728
|
-
get margin() {
|
|
2729
|
-
return this._margin;
|
|
2730
|
-
}
|
|
2731
|
-
set margin(value) {
|
|
2732
|
-
const next = Number.isFinite(value) ? Math.max(0, value) : this._margin;
|
|
2733
|
-
if (this._margin !== next) {
|
|
2734
|
-
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;
|
|
2735
2745
|
this._lines = [];
|
|
2736
2746
|
this._measureDirty = true;
|
|
2737
2747
|
this._onChange?.();
|
|
@@ -2764,7 +2774,7 @@ class TextLabel {
|
|
|
2764
2774
|
}
|
|
2765
2775
|
/**
|
|
2766
2776
|
* Get wrapped lines for export. Uses ctx to measure and wrap by words.
|
|
2767
|
-
* Call with maxWidth = inner bounds width (e.g. bounds.width -
|
|
2777
|
+
* Call with maxWidth = inner bounds width (e.g. bounds.width - inset*2).
|
|
2768
2778
|
*/
|
|
2769
2779
|
getWrappedLines(ctx, maxWidth) {
|
|
2770
2780
|
this.setAutoMaxWidth(maxWidth);
|
|
@@ -2786,8 +2796,11 @@ class TextLabel {
|
|
|
2786
2796
|
const effectiveMaxWidth = this._maxWidth ?? this._autoMaxWidth;
|
|
2787
2797
|
const text = this._text ?? "";
|
|
2788
2798
|
if (effectiveMaxWidth !== void 0) {
|
|
2789
|
-
const maxWidth = Math.max(
|
|
2790
|
-
|
|
2799
|
+
const maxWidth = Math.max(
|
|
2800
|
+
0,
|
|
2801
|
+
effectiveMaxWidth - this._inset.left - this._inset.right
|
|
2802
|
+
);
|
|
2803
|
+
this._lines = this.wrapText(ctx, text, maxWidth);
|
|
2791
2804
|
} else {
|
|
2792
2805
|
this._lines = text.split("\n");
|
|
2793
2806
|
}
|
|
@@ -2796,8 +2809,8 @@ class TextLabel {
|
|
|
2796
2809
|
const metrics = ctx.measureText(line);
|
|
2797
2810
|
maxLineWidth = Math.max(maxLineWidth, metrics.width);
|
|
2798
2811
|
}
|
|
2799
|
-
this._measuredWidth = maxLineWidth + this.
|
|
2800
|
-
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;
|
|
2801
2814
|
this._measureDirty = false;
|
|
2802
2815
|
return {
|
|
2803
2816
|
width: this._measuredWidth,
|
|
@@ -2818,25 +2831,41 @@ class TextLabel {
|
|
|
2818
2831
|
}
|
|
2819
2832
|
const lineHeight = (this._style.fontSize ?? 14) * 1.2;
|
|
2820
2833
|
const totalHeight = this._lines.length * lineHeight;
|
|
2821
|
-
const margin = this._margin;
|
|
2822
2834
|
const innerBounds = {
|
|
2823
|
-
x: bounds.x +
|
|
2824
|
-
y: bounds.y +
|
|
2825
|
-
width: Math.max(
|
|
2826
|
-
|
|
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
|
+
)
|
|
2827
2845
|
};
|
|
2828
2846
|
let x;
|
|
2829
2847
|
switch (align) {
|
|
2830
2848
|
case "left":
|
|
2831
|
-
x = innerBounds.x
|
|
2849
|
+
x = innerBounds.x;
|
|
2832
2850
|
break;
|
|
2833
2851
|
case "right":
|
|
2834
|
-
x = innerBounds.x + innerBounds.width
|
|
2852
|
+
x = innerBounds.x + innerBounds.width;
|
|
2835
2853
|
break;
|
|
2836
2854
|
default:
|
|
2837
2855
|
x = innerBounds.x + innerBounds.width / 2;
|
|
2838
2856
|
}
|
|
2839
|
-
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
|
+
}
|
|
2840
2869
|
ctx.fillStyle = this._style.color ?? "#000000";
|
|
2841
2870
|
for (let i = 0; i < this._lines.length; i++) {
|
|
2842
2871
|
const line = this._lines[i];
|
|
@@ -2851,16 +2880,13 @@ class TextLabel {
|
|
|
2851
2880
|
if (this._lines.length === 0) {
|
|
2852
2881
|
this.measure(ctx);
|
|
2853
2882
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
const y = startY + i * lineHeight;
|
|
2862
|
-
ctx.fillText(line, point.x, y);
|
|
2863
|
-
}
|
|
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);
|
|
2864
2890
|
}
|
|
2865
2891
|
applyStyle(ctx) {
|
|
2866
2892
|
const fontSize = this._style.fontSize ?? 14;
|
|
@@ -2906,7 +2932,9 @@ const snapshotLabel = (label) => {
|
|
|
2906
2932
|
return {
|
|
2907
2933
|
text: label.text,
|
|
2908
2934
|
editableText: label.editableText,
|
|
2909
|
-
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),
|
|
2910
2938
|
styleClass: label.styleClass,
|
|
2911
2939
|
maxWidth: label.maxWidth
|
|
2912
2940
|
};
|
|
@@ -4732,14 +4760,13 @@ class Edge extends Element {
|
|
|
4732
4760
|
this._label.measure(ctx);
|
|
4733
4761
|
const labelWidth = this._label.measuredWidth;
|
|
4734
4762
|
const labelHeight = this._label.measuredHeight;
|
|
4735
|
-
const bgPadding = this._labelBackground?.padding ?? EDGE_LABEL_BACKGROUND_PADDING;
|
|
4736
4763
|
const bgColor = this._labelBackground?.color ?? "#ffffff";
|
|
4737
4764
|
const bgOpacity = this._labelBackground?.opacity ?? 1;
|
|
4738
4765
|
const bgRadius = this._labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
4739
|
-
const bgX = labelPosition.x - labelWidth / 2
|
|
4740
|
-
const bgY = labelPosition.y - labelHeight / 2
|
|
4741
|
-
const bgWidth = labelWidth
|
|
4742
|
-
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;
|
|
4743
4770
|
ctx.fillStyle = bgColor;
|
|
4744
4771
|
ctx.globalAlpha = bgOpacity;
|
|
4745
4772
|
if (bgRadius > 0) {
|
|
@@ -5044,6 +5071,7 @@ class InteractionManager {
|
|
|
5044
5071
|
if (shallowEqual(before, after)) {
|
|
5045
5072
|
return;
|
|
5046
5073
|
}
|
|
5074
|
+
this.renderer.markStyleDirty();
|
|
5047
5075
|
this.queuePropertyChange("node", nodeId, before, after);
|
|
5048
5076
|
}
|
|
5049
5077
|
changeEdgeProperties(edgeId, apply) {
|
|
@@ -5057,6 +5085,7 @@ class InteractionManager {
|
|
|
5057
5085
|
if (shallowEqual(before, after)) {
|
|
5058
5086
|
return;
|
|
5059
5087
|
}
|
|
5088
|
+
this.renderer.markStyleDirty();
|
|
5060
5089
|
this.queuePropertyChange("edge", edgeId, before, after);
|
|
5061
5090
|
}
|
|
5062
5091
|
changeGroupProperties(groupId, apply) {
|
|
@@ -5070,6 +5099,7 @@ class InteractionManager {
|
|
|
5070
5099
|
if (shallowEqual(before, after)) {
|
|
5071
5100
|
return;
|
|
5072
5101
|
}
|
|
5102
|
+
this.renderer.markStyleDirty();
|
|
5073
5103
|
this.queuePropertyChange("group", groupId, before, after);
|
|
5074
5104
|
}
|
|
5075
5105
|
removeNodeFromGroups(nodeId, groupIds) {
|
|
@@ -5560,6 +5590,7 @@ class InteractionManager {
|
|
|
5560
5590
|
pending.after
|
|
5561
5591
|
)
|
|
5562
5592
|
);
|
|
5593
|
+
this.renderer.markStyleDirty();
|
|
5563
5594
|
break;
|
|
5564
5595
|
case "edge":
|
|
5565
5596
|
this.historyManager.execute(
|
|
@@ -5570,6 +5601,7 @@ class InteractionManager {
|
|
|
5570
5601
|
pending.after
|
|
5571
5602
|
)
|
|
5572
5603
|
);
|
|
5604
|
+
this.renderer.markStyleDirty();
|
|
5573
5605
|
break;
|
|
5574
5606
|
case "group":
|
|
5575
5607
|
this.historyManager.execute(
|
|
@@ -5580,6 +5612,7 @@ class InteractionManager {
|
|
|
5580
5612
|
pending.after
|
|
5581
5613
|
)
|
|
5582
5614
|
);
|
|
5615
|
+
this.renderer.markStyleDirty();
|
|
5583
5616
|
break;
|
|
5584
5617
|
}
|
|
5585
5618
|
}
|
|
@@ -8045,8 +8078,10 @@ class NodeImage {
|
|
|
8045
8078
|
get placement() {
|
|
8046
8079
|
return this._options.placement ?? "center";
|
|
8047
8080
|
}
|
|
8048
|
-
|
|
8049
|
-
|
|
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;
|
|
8050
8085
|
}
|
|
8051
8086
|
setSource(source) {
|
|
8052
8087
|
this._loaded = false;
|
|
@@ -8061,23 +8096,18 @@ class NodeImage {
|
|
|
8061
8096
|
if (!this._loaded) {
|
|
8062
8097
|
return;
|
|
8063
8098
|
}
|
|
8064
|
-
const
|
|
8065
|
-
const margin = Math.max(0, this._options.margin ?? 0);
|
|
8099
|
+
const ins = this.inset;
|
|
8066
8100
|
const fit = this._options.fit ?? "none";
|
|
8067
8101
|
const scaleWithBounds = this._options.scaleWithBounds ?? false;
|
|
8068
8102
|
const opacity = this._options.opacity ?? 1;
|
|
8069
|
-
const align = this._options.align ?? "center";
|
|
8070
|
-
const verticalAlign = this._options.verticalAlign ?? "center";
|
|
8071
|
-
const offsetX = this._options.offsetX ?? 0;
|
|
8072
|
-
const offsetY = this._options.offsetY ?? 0;
|
|
8073
8103
|
const innerBounds = {
|
|
8074
|
-
x: bounds.x +
|
|
8075
|
-
y: bounds.y +
|
|
8076
|
-
width: Math.max(0, bounds.width -
|
|
8077
|
-
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)
|
|
8078
8108
|
};
|
|
8079
|
-
const availableWidth = Math.max(0, innerBounds.width
|
|
8080
|
-
const availableHeight = Math.max(0, innerBounds.height
|
|
8109
|
+
const availableWidth = Math.max(0, innerBounds.width);
|
|
8110
|
+
const availableHeight = Math.max(0, innerBounds.height);
|
|
8081
8111
|
let drawWidth = this._options.width ?? this._naturalWidth;
|
|
8082
8112
|
let drawHeight = this._options.height ?? this._naturalHeight;
|
|
8083
8113
|
if (scaleWithBounds) {
|
|
@@ -8096,21 +8126,13 @@ class NodeImage {
|
|
|
8096
8126
|
const maxHeight = Math.max(0, availableHeight);
|
|
8097
8127
|
drawWidth = Math.min(drawWidth, maxWidth);
|
|
8098
8128
|
drawHeight = Math.min(drawHeight, maxHeight);
|
|
8099
|
-
let x = innerBounds.x
|
|
8100
|
-
let y = innerBounds.y
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
} else if (align === "right") {
|
|
8104
|
-
x = innerBounds.x + innerBounds.width - drawWidth - padding;
|
|
8105
|
-
}
|
|
8106
|
-
if (verticalAlign === "center") {
|
|
8107
|
-
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
8108
|
-
} else if (verticalAlign === "bottom") {
|
|
8109
|
-
y = innerBounds.y + innerBounds.height - drawHeight - padding;
|
|
8110
|
-
}
|
|
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;
|
|
8111
8133
|
ctx.save();
|
|
8112
8134
|
ctx.globalAlpha = opacity;
|
|
8113
|
-
ctx.drawImage(this._image, x
|
|
8135
|
+
ctx.drawImage(this._image, x, y, drawWidth, drawHeight);
|
|
8114
8136
|
ctx.restore();
|
|
8115
8137
|
}
|
|
8116
8138
|
getSize() {
|
|
@@ -8200,8 +8222,8 @@ class Node extends Element {
|
|
|
8200
8222
|
this._nodeStyle = { ...DEFAULT_NODE_STYLE, ...options.style };
|
|
8201
8223
|
this._showPortsAlways = options.showPortsAlways ?? false;
|
|
8202
8224
|
this._anchorPoints = this.normalizeAnchorPoints(options.anchorPoints);
|
|
8203
|
-
this._labelPlacement = options.labelPlacement ?? "auto";
|
|
8204
8225
|
this._resizeHandlesEnabled = options.resizeHandlesEnabled ?? true;
|
|
8226
|
+
this._contentInset = this.normalizeContentInset(options.contentInset);
|
|
8205
8227
|
if (options.label !== void 0) {
|
|
8206
8228
|
if (typeof options.label === "string") {
|
|
8207
8229
|
this._label = new TextLabel({
|
|
@@ -8398,24 +8420,22 @@ class Node extends Element {
|
|
|
8398
8420
|
this._anchorPoints = this.normalizeAnchorPoints(value);
|
|
8399
8421
|
this.markDirty();
|
|
8400
8422
|
}
|
|
8401
|
-
/**
|
|
8402
|
-
* Label placement inside the node
|
|
8403
|
-
*/
|
|
8404
|
-
get labelPlacement() {
|
|
8405
|
-
return this._labelPlacement;
|
|
8406
|
-
}
|
|
8407
|
-
set labelPlacement(value) {
|
|
8408
|
-
if (this._labelPlacement !== value) {
|
|
8409
|
-
this._labelPlacement = value;
|
|
8410
|
-
this.markDirty();
|
|
8411
|
-
}
|
|
8412
|
-
}
|
|
8413
8423
|
/**
|
|
8414
8424
|
* Default size (initial width/height)
|
|
8415
8425
|
*/
|
|
8416
8426
|
get defaultSize() {
|
|
8417
8427
|
return { ...this._defaultSize };
|
|
8418
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
|
+
}
|
|
8419
8439
|
/**
|
|
8420
8440
|
* Set getter for attachToOutline (called by DiagramRenderer when node is added)
|
|
8421
8441
|
*/
|
|
@@ -8438,87 +8458,20 @@ class Node extends Element {
|
|
|
8438
8458
|
}
|
|
8439
8459
|
}
|
|
8440
8460
|
/**
|
|
8441
|
-
* Calculate layout for icon and label
|
|
8442
|
-
*
|
|
8461
|
+
* Calculate layout for icon and label.
|
|
8462
|
+
* Label always gets full content area; icon is placed within the same content area.
|
|
8443
8463
|
*/
|
|
8444
|
-
calculateContentLayout(bounds, iconBoxSize,
|
|
8445
|
-
|
|
8446
|
-
let
|
|
8464
|
+
calculateContentLayout(bounds, iconBoxSize, _labelSize) {
|
|
8465
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8466
|
+
let iconBounds = contentBounds;
|
|
8447
8467
|
if (this._icon && iconBoxSize) {
|
|
8448
|
-
iconBounds = this.getIconBounds(
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
if (isCornerPlacement(placement)) {
|
|
8454
|
-
iconBounds = this.getIconBounds(bounds, iconBoxSize, placement);
|
|
8455
|
-
labelBounds = this.getLabelContainerBounds(bounds);
|
|
8456
|
-
} else {
|
|
8457
|
-
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8458
|
-
switch (placement) {
|
|
8459
|
-
case "top":
|
|
8460
|
-
iconBounds = {
|
|
8461
|
-
x: bounds.x,
|
|
8462
|
-
y: bounds.y,
|
|
8463
|
-
width: bounds.width,
|
|
8464
|
-
height: iconBoxSize.height
|
|
8465
|
-
};
|
|
8466
|
-
labelBounds = {
|
|
8467
|
-
x: contentBounds.x,
|
|
8468
|
-
y: contentBounds.y + iconBoxSize.height + gap,
|
|
8469
|
-
width: contentBounds.width,
|
|
8470
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8471
|
-
};
|
|
8472
|
-
break;
|
|
8473
|
-
case "bottom":
|
|
8474
|
-
iconBounds = {
|
|
8475
|
-
x: bounds.x,
|
|
8476
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
8477
|
-
width: bounds.width,
|
|
8478
|
-
height: iconBoxSize.height
|
|
8479
|
-
};
|
|
8480
|
-
labelBounds = {
|
|
8481
|
-
x: contentBounds.x,
|
|
8482
|
-
y: contentBounds.y,
|
|
8483
|
-
width: contentBounds.width,
|
|
8484
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8485
|
-
};
|
|
8486
|
-
break;
|
|
8487
|
-
case "left":
|
|
8488
|
-
iconBounds = {
|
|
8489
|
-
x: bounds.x,
|
|
8490
|
-
y: bounds.y,
|
|
8491
|
-
width: iconBoxSize.width,
|
|
8492
|
-
height: bounds.height
|
|
8493
|
-
};
|
|
8494
|
-
labelBounds = {
|
|
8495
|
-
x: contentBounds.x + iconBoxSize.width + gap,
|
|
8496
|
-
y: contentBounds.y,
|
|
8497
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8498
|
-
height: contentBounds.height
|
|
8499
|
-
};
|
|
8500
|
-
break;
|
|
8501
|
-
case "right":
|
|
8502
|
-
iconBounds = {
|
|
8503
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
8504
|
-
y: bounds.y,
|
|
8505
|
-
width: iconBoxSize.width,
|
|
8506
|
-
height: bounds.height
|
|
8507
|
-
};
|
|
8508
|
-
labelBounds = {
|
|
8509
|
-
x: contentBounds.x,
|
|
8510
|
-
y: contentBounds.y,
|
|
8511
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8512
|
-
height: contentBounds.height
|
|
8513
|
-
};
|
|
8514
|
-
break;
|
|
8515
|
-
}
|
|
8516
|
-
}
|
|
8517
|
-
} else if (this._label && labelSize) {
|
|
8518
|
-
const autoBounds = this.getAutoLabelBounds(bounds, iconBoxSize);
|
|
8519
|
-
labelBounds = this.getLabelBounds(autoBounds, labelSize, this._labelPlacement);
|
|
8468
|
+
iconBounds = this.getIconBounds(
|
|
8469
|
+
contentBounds,
|
|
8470
|
+
iconBoxSize,
|
|
8471
|
+
this._icon.placement
|
|
8472
|
+
);
|
|
8520
8473
|
}
|
|
8521
|
-
return { iconBounds, labelBounds };
|
|
8474
|
+
return { iconBounds, labelBounds: contentBounds };
|
|
8522
8475
|
}
|
|
8523
8476
|
/**
|
|
8524
8477
|
* Get label bounds and wrapped lines for SVG export. Replicates renderContents layout logic.
|
|
@@ -8529,12 +8482,19 @@ class Node extends Element {
|
|
|
8529
8482
|
return null;
|
|
8530
8483
|
}
|
|
8531
8484
|
const bounds = this.getBounds();
|
|
8485
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8532
8486
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
8533
|
-
|
|
8534
|
-
this._label.setAutoMaxWidth(autoBounds.width);
|
|
8487
|
+
this._label.setAutoMaxWidth(contentBounds.width);
|
|
8535
8488
|
const labelSize = this._label.measure(ctx);
|
|
8536
|
-
const { labelBounds } = this.calculateContentLayout(
|
|
8537
|
-
|
|
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
|
+
);
|
|
8538
8498
|
return { bounds: labelBounds, lines };
|
|
8539
8499
|
}
|
|
8540
8500
|
/**
|
|
@@ -8550,23 +8510,14 @@ class Node extends Element {
|
|
|
8550
8510
|
ctx.globalAlpha = 1;
|
|
8551
8511
|
}
|
|
8552
8512
|
/**
|
|
8553
|
-
* Get world position of label center.
|
|
8513
|
+
* Get world position of label center (center of content area).
|
|
8554
8514
|
*/
|
|
8555
8515
|
getLabelPosition() {
|
|
8556
|
-
const bounds = this.getBounds();
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
case "bottom":
|
|
8562
|
-
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height };
|
|
8563
|
-
case "left":
|
|
8564
|
-
return { x: bounds.x, y: bounds.y + bounds.height / 2 };
|
|
8565
|
-
case "right":
|
|
8566
|
-
return { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 };
|
|
8567
|
-
default:
|
|
8568
|
-
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
8569
|
-
}
|
|
8516
|
+
const bounds = this.getLabelContainerBounds(this.getBounds());
|
|
8517
|
+
return {
|
|
8518
|
+
x: bounds.x + bounds.width / 2,
|
|
8519
|
+
y: bounds.y + bounds.height / 2
|
|
8520
|
+
};
|
|
8570
8521
|
}
|
|
8571
8522
|
/**
|
|
8572
8523
|
* Render icon, label, and ports
|
|
@@ -8577,14 +8528,14 @@ class Node extends Element {
|
|
|
8577
8528
|
let bounds = this.getBounds();
|
|
8578
8529
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
8579
8530
|
if (this._label) {
|
|
8580
|
-
this._label.setAutoMaxWidth(this.
|
|
8531
|
+
this._label.setAutoMaxWidth(this.getLabelContainerBounds(bounds).width);
|
|
8581
8532
|
}
|
|
8582
8533
|
const labelSize = this._label ? this._label.measure(ctx) : void 0;
|
|
8583
8534
|
if (labelSize || iconBoxSize) {
|
|
8584
8535
|
this.ensureContentsFit(labelSize, iconBoxSize);
|
|
8585
8536
|
if (this._label && iconBoxSize) {
|
|
8586
8537
|
bounds = this.getBounds();
|
|
8587
|
-
this._label.setAutoMaxWidth(this.
|
|
8538
|
+
this._label.setAutoMaxWidth(this.getLabelContainerBounds(bounds).width);
|
|
8588
8539
|
}
|
|
8589
8540
|
}
|
|
8590
8541
|
const { iconBounds, labelBounds } = this.calculateContentLayout(
|
|
@@ -8869,124 +8820,45 @@ class Node extends Element {
|
|
|
8869
8820
|
}
|
|
8870
8821
|
}
|
|
8871
8822
|
calculateContentMinSize(labelSize, iconBoxSize, bounds) {
|
|
8872
|
-
let minWidth = 0;
|
|
8873
|
-
let minHeight = 0;
|
|
8874
8823
|
const labelContainer = this.getLabelContainerBounds(bounds);
|
|
8875
8824
|
const widthFactor = labelContainer.width > 0 ? bounds.width / labelContainer.width : 1;
|
|
8876
8825
|
const heightFactor = labelContainer.height > 0 ? bounds.height / labelContainer.height : 1;
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
if (this._labelPlacement === "auto" && iconPlacement !== "center") {
|
|
8890
|
-
if (isCornerPlacement(iconPlacement)) {
|
|
8891
|
-
minWidth = Math.max(minWidth, iconBoxSize.width, labelSize.width);
|
|
8892
|
-
minHeight = Math.max(minHeight, iconBoxSize.height, labelSize.height);
|
|
8893
|
-
} else if (iconPlacement === "top" || iconPlacement === "bottom") {
|
|
8894
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8895
|
-
minWidth = Math.max(minWidth, iconBoxSize.width, labelSize.width);
|
|
8896
|
-
} else if (iconPlacement === "left" || iconPlacement === "right") {
|
|
8897
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8898
|
-
minHeight = Math.max(minHeight, iconBoxSize.height, labelSize.height);
|
|
8899
|
-
}
|
|
8900
|
-
} else {
|
|
8901
|
-
const labelHorizontal = labelPlacement === "left" || labelPlacement === "right";
|
|
8902
|
-
const labelVertical = labelPlacement === "top" || labelPlacement === "bottom";
|
|
8903
|
-
const iconHorizontal = iconPlacement === "left" || iconPlacement === "right";
|
|
8904
|
-
const iconVertical = iconPlacement === "top" || iconPlacement === "bottom";
|
|
8905
|
-
const labelSharesIconAxis = iconHorizontal && (labelPlacement === "center" || labelPlacement === iconPlacement) || iconVertical && (labelPlacement === "center" || labelPlacement === iconPlacement);
|
|
8906
|
-
if (labelHorizontal && iconHorizontal && labelPlacement !== iconPlacement) {
|
|
8907
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8908
|
-
}
|
|
8909
|
-
if (labelVertical && iconVertical && labelPlacement !== iconPlacement) {
|
|
8910
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8911
|
-
}
|
|
8912
|
-
if (labelSharesIconAxis) {
|
|
8913
|
-
if (iconHorizontal) {
|
|
8914
|
-
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
8915
|
-
}
|
|
8916
|
-
if (iconVertical) {
|
|
8917
|
-
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
8918
|
-
}
|
|
8919
|
-
}
|
|
8920
|
-
}
|
|
8921
|
-
}
|
|
8922
|
-
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
|
+
};
|
|
8923
8838
|
}
|
|
8924
8839
|
getLabelContainerBounds(bounds) {
|
|
8925
|
-
|
|
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 };
|
|
8926
8846
|
}
|
|
8927
|
-
|
|
8928
|
-
const
|
|
8929
|
-
if (
|
|
8930
|
-
return
|
|
8931
|
-
}
|
|
8932
|
-
const gap = this._icon.gap;
|
|
8933
|
-
if (isCornerPlacement(this._icon.placement)) {
|
|
8934
|
-
return contentBounds;
|
|
8935
|
-
}
|
|
8936
|
-
switch (this._icon.placement) {
|
|
8937
|
-
case "left":
|
|
8938
|
-
return {
|
|
8939
|
-
x: contentBounds.x + iconBoxSize.width + gap,
|
|
8940
|
-
y: contentBounds.y,
|
|
8941
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8942
|
-
height: contentBounds.height
|
|
8943
|
-
};
|
|
8944
|
-
case "right":
|
|
8945
|
-
return {
|
|
8946
|
-
x: contentBounds.x,
|
|
8947
|
-
y: contentBounds.y,
|
|
8948
|
-
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
8949
|
-
height: contentBounds.height
|
|
8950
|
-
};
|
|
8951
|
-
case "top":
|
|
8952
|
-
return {
|
|
8953
|
-
x: contentBounds.x,
|
|
8954
|
-
y: contentBounds.y + iconBoxSize.height + gap,
|
|
8955
|
-
width: contentBounds.width,
|
|
8956
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8957
|
-
};
|
|
8958
|
-
case "bottom":
|
|
8959
|
-
return {
|
|
8960
|
-
x: contentBounds.x,
|
|
8961
|
-
y: contentBounds.y,
|
|
8962
|
-
width: contentBounds.width,
|
|
8963
|
-
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
8964
|
-
};
|
|
8965
|
-
default:
|
|
8966
|
-
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 };
|
|
8967
8851
|
}
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
const width = Math.min(labelSize.width, bounds.width);
|
|
8972
|
-
const height = Math.min(labelSize.height, bounds.height);
|
|
8973
|
-
let x = bounds.x + (bounds.width - width) / 2;
|
|
8974
|
-
let y = bounds.y + (bounds.height - height) / 2;
|
|
8975
|
-
switch (normalizedPlacement) {
|
|
8976
|
-
case "top":
|
|
8977
|
-
y = bounds.y;
|
|
8978
|
-
break;
|
|
8979
|
-
case "bottom":
|
|
8980
|
-
y = bounds.y + bounds.height - height;
|
|
8981
|
-
break;
|
|
8982
|
-
case "left":
|
|
8983
|
-
x = bounds.x;
|
|
8984
|
-
break;
|
|
8985
|
-
case "right":
|
|
8986
|
-
x = bounds.x + bounds.width - width;
|
|
8987
|
-
break;
|
|
8852
|
+
if (typeof value === "number") {
|
|
8853
|
+
const v = n(value);
|
|
8854
|
+
return { top: v, right: v, bottom: v, left: v };
|
|
8988
8855
|
}
|
|
8989
|
-
return {
|
|
8856
|
+
return {
|
|
8857
|
+
top: n(value.top),
|
|
8858
|
+
right: n(value.right),
|
|
8859
|
+
bottom: n(value.bottom),
|
|
8860
|
+
left: n(value.left)
|
|
8861
|
+
};
|
|
8990
8862
|
}
|
|
8991
8863
|
getIconBoxSize() {
|
|
8992
8864
|
if (!this._icon) {
|
|
@@ -8996,14 +8868,14 @@ class Node extends Element {
|
|
|
8996
8868
|
if (width <= 0 || height <= 0) {
|
|
8997
8869
|
return void 0;
|
|
8998
8870
|
}
|
|
8999
|
-
const
|
|
9000
|
-
const margin = Math.max(0, this._icon.options.margin ?? 0);
|
|
8871
|
+
const ins = this._icon.inset;
|
|
9001
8872
|
return {
|
|
9002
|
-
width: width +
|
|
9003
|
-
height: height +
|
|
8873
|
+
width: width + ins * 2,
|
|
8874
|
+
height: height + ins * 2
|
|
9004
8875
|
};
|
|
9005
8876
|
}
|
|
9006
8877
|
getIconBounds(bounds, iconBoxSize, placement) {
|
|
8878
|
+
const ins = this._icon?.inset ?? 0;
|
|
9007
8879
|
switch (placement) {
|
|
9008
8880
|
case "top":
|
|
9009
8881
|
return {
|
|
@@ -9035,29 +8907,29 @@ class Node extends Element {
|
|
|
9035
8907
|
};
|
|
9036
8908
|
case "top-left":
|
|
9037
8909
|
return {
|
|
9038
|
-
x: bounds.x,
|
|
9039
|
-
y: bounds.y,
|
|
8910
|
+
x: bounds.x + ins,
|
|
8911
|
+
y: bounds.y + ins,
|
|
9040
8912
|
width: iconBoxSize.width,
|
|
9041
8913
|
height: iconBoxSize.height
|
|
9042
8914
|
};
|
|
9043
8915
|
case "top-right":
|
|
9044
8916
|
return {
|
|
9045
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
9046
|
-
y: bounds.y,
|
|
8917
|
+
x: bounds.x + bounds.width - iconBoxSize.width - ins,
|
|
8918
|
+
y: bounds.y + ins,
|
|
9047
8919
|
width: iconBoxSize.width,
|
|
9048
8920
|
height: iconBoxSize.height
|
|
9049
8921
|
};
|
|
9050
8922
|
case "bottom-left":
|
|
9051
8923
|
return {
|
|
9052
|
-
x: bounds.x,
|
|
9053
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
8924
|
+
x: bounds.x + ins,
|
|
8925
|
+
y: bounds.y + bounds.height - iconBoxSize.height - ins,
|
|
9054
8926
|
width: iconBoxSize.width,
|
|
9055
8927
|
height: iconBoxSize.height
|
|
9056
8928
|
};
|
|
9057
8929
|
case "bottom-right":
|
|
9058
8930
|
return {
|
|
9059
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
9060
|
-
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,
|
|
9061
8933
|
width: iconBoxSize.width,
|
|
9062
8934
|
height: iconBoxSize.height
|
|
9063
8935
|
};
|
|
@@ -9956,18 +9828,14 @@ const DEFAULT_THEME = {
|
|
|
9956
9828
|
cornerRadius: 4
|
|
9957
9829
|
},
|
|
9958
9830
|
selected: {
|
|
9959
|
-
|
|
9960
|
-
strokeColor: "#333333",
|
|
9831
|
+
strokeColor: "#3b82f6",
|
|
9961
9832
|
strokeWidth: 2,
|
|
9962
|
-
opacity: 1
|
|
9963
|
-
cornerRadius: 4
|
|
9833
|
+
opacity: 1
|
|
9964
9834
|
},
|
|
9965
9835
|
dragging: {
|
|
9966
|
-
fillColor: "#f0f0f0",
|
|
9967
9836
|
strokeColor: "#333333",
|
|
9968
9837
|
strokeWidth: 2,
|
|
9969
|
-
opacity: 0.8
|
|
9970
|
-
cornerRadius: 4
|
|
9838
|
+
opacity: 0.8
|
|
9971
9839
|
}
|
|
9972
9840
|
},
|
|
9973
9841
|
edge: {
|
|
@@ -10228,15 +10096,16 @@ class StyleManager {
|
|
|
10228
10096
|
return baseStyle;
|
|
10229
10097
|
}
|
|
10230
10098
|
getBaseNodeStyle(state) {
|
|
10099
|
+
const d = this._theme.node.default;
|
|
10231
10100
|
switch (state) {
|
|
10232
10101
|
case "hover":
|
|
10233
|
-
return this._theme.node.hover;
|
|
10102
|
+
return { ...d, ...this._theme.node.hover };
|
|
10234
10103
|
case "selected":
|
|
10235
|
-
return this._theme.node.selected;
|
|
10104
|
+
return { ...d, ...this._theme.node.selected };
|
|
10236
10105
|
case "dragging":
|
|
10237
|
-
return this._theme.node.dragging;
|
|
10106
|
+
return { ...d, ...this._theme.node.dragging };
|
|
10238
10107
|
default:
|
|
10239
|
-
return
|
|
10108
|
+
return d;
|
|
10240
10109
|
}
|
|
10241
10110
|
}
|
|
10242
10111
|
getBaseEdgeStyle(state) {
|
|
@@ -10434,13 +10303,16 @@ class Serializer {
|
|
|
10434
10303
|
}));
|
|
10435
10304
|
let label;
|
|
10436
10305
|
if (node.label) {
|
|
10437
|
-
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 };
|
|
10438
10311
|
const labelData = {
|
|
10439
10312
|
text: node.label.text,
|
|
10440
10313
|
style: node.label.style,
|
|
10441
10314
|
maxWidth: node.label.maxWidth,
|
|
10442
|
-
|
|
10443
|
-
margin: node.label.margin,
|
|
10315
|
+
inset: serializedInset ?? void 0,
|
|
10444
10316
|
styleClass: node.label.styleClass
|
|
10445
10317
|
};
|
|
10446
10318
|
const hasExtendedOptions = hasNonDefaultValues(labelData, labelDefaults);
|
|
@@ -10449,8 +10321,7 @@ class Serializer {
|
|
|
10449
10321
|
text: node.label.text,
|
|
10450
10322
|
style: Object.keys(node.label.style).length > 0 ? node.label.style : void 0,
|
|
10451
10323
|
maxWidth: node.label.maxWidth,
|
|
10452
|
-
|
|
10453
|
-
margin: node.label.margin !== 0 ? node.label.margin : void 0,
|
|
10324
|
+
inset: serializedInset,
|
|
10454
10325
|
styleClass: node.label.styleClass
|
|
10455
10326
|
});
|
|
10456
10327
|
} else {
|
|
@@ -10462,22 +10333,16 @@ class Serializer {
|
|
|
10462
10333
|
const opts = node.icon.options;
|
|
10463
10334
|
const source = typeof opts.source === "string" ? opts.source : void 0;
|
|
10464
10335
|
if (source) {
|
|
10465
|
-
icon = {
|
|
10336
|
+
icon = omitEmptyValues({
|
|
10466
10337
|
source,
|
|
10467
10338
|
width: opts.width,
|
|
10468
10339
|
height: opts.height,
|
|
10469
10340
|
fit: opts.fit,
|
|
10470
10341
|
placement: opts.placement,
|
|
10471
10342
|
scaleWithBounds: opts.scaleWithBounds,
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
opacity: opts.opacity,
|
|
10476
|
-
align: opts.align,
|
|
10477
|
-
verticalAlign: opts.verticalAlign,
|
|
10478
|
-
offsetX: opts.offsetX,
|
|
10479
|
-
offsetY: opts.offsetY
|
|
10480
|
-
};
|
|
10343
|
+
inset: node.icon.inset !== 6 ? node.icon.inset : void 0,
|
|
10344
|
+
opacity: opts.opacity
|
|
10345
|
+
});
|
|
10481
10346
|
}
|
|
10482
10347
|
}
|
|
10483
10348
|
let anchorPoints;
|
|
@@ -10487,7 +10352,9 @@ class Serializer {
|
|
|
10487
10352
|
if (hasNonDefaultValues(anchorData, anchorDefaults)) {
|
|
10488
10353
|
anchorPoints = omitDefaultValues(anchorData, anchorDefaults);
|
|
10489
10354
|
}
|
|
10490
|
-
|
|
10355
|
+
const contentInset = node.contentInset;
|
|
10356
|
+
const hasContentInset = contentInset.top !== 0 || contentInset.right !== 0 || contentInset.bottom !== 0 || contentInset.left !== 0;
|
|
10357
|
+
return omitEmptyValues({
|
|
10491
10358
|
id: node.id,
|
|
10492
10359
|
type: node.typeName,
|
|
10493
10360
|
x: node.x,
|
|
@@ -10498,12 +10365,12 @@ class Serializer {
|
|
|
10498
10365
|
styleClass: node.styleClass,
|
|
10499
10366
|
label,
|
|
10500
10367
|
labelStyleClass: typeof label === "string" ? node.label?.styleClass : void 0,
|
|
10501
|
-
labelPlacement: node.labelPlacement !== "auto" ? node.labelPlacement : void 0,
|
|
10502
10368
|
icon,
|
|
10369
|
+
contentInset: hasContentInset ? contentInset : void 0,
|
|
10503
10370
|
anchorPoints,
|
|
10504
10371
|
ports: ports.length > 0 ? ports : void 0,
|
|
10505
10372
|
data: Object.keys(node.data).length > 0 ? node.data : void 0
|
|
10506
|
-
};
|
|
10373
|
+
});
|
|
10507
10374
|
}
|
|
10508
10375
|
serializeEdge(edge) {
|
|
10509
10376
|
return {
|
|
@@ -10814,7 +10681,7 @@ class SvgExporter {
|
|
|
10814
10681
|
const parts = [];
|
|
10815
10682
|
const renderedEdges = [];
|
|
10816
10683
|
parts.push(
|
|
10817
|
-
`<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}">`
|
|
10818
10685
|
);
|
|
10819
10686
|
for (const edge of this.renderer.edges.values()) {
|
|
10820
10687
|
if (edge.visible) {
|
|
@@ -10955,22 +10822,37 @@ class SvgExporter {
|
|
|
10955
10822
|
if (!edge.label) {
|
|
10956
10823
|
return "";
|
|
10957
10824
|
}
|
|
10958
|
-
const metrics = this.measureTextLabel(edge.label.text, edge.label.style, edge.label.
|
|
10959
|
-
const bgPadding = edge.labelBackground?.padding ?? EDGE_LABEL_BACKGROUND_PADDING;
|
|
10825
|
+
const metrics = this.measureTextLabel(edge.label.text, edge.label.style, edge.label.inset);
|
|
10960
10826
|
const bgColor = edge.labelBackground?.color ?? "#ffffff";
|
|
10961
10827
|
const bgOpacity = edge.labelBackground?.opacity ?? 1;
|
|
10962
10828
|
const bgRadius = edge.labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
10963
|
-
const x = point.x - metrics.width / 2
|
|
10964
|
-
const y = point.y - metrics.height / 2
|
|
10965
|
-
const width = metrics.width
|
|
10966
|
-
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;
|
|
10967
10833
|
const radius = Math.max(0, Math.min(bgRadius, width / 2, height / 2));
|
|
10968
10834
|
if (radius <= 0) {
|
|
10969
10835
|
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
|
|
10970
10836
|
}
|
|
10971
10837
|
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
|
|
10972
10838
|
}
|
|
10973
|
-
|
|
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) {
|
|
10974
10856
|
const fontSize = style.fontSize ?? 14;
|
|
10975
10857
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
10976
10858
|
const fontWeight = style.fontWeight ?? "normal";
|
|
@@ -10991,11 +10873,10 @@ class SvgExporter {
|
|
|
10991
10873
|
const longestLine = lines.reduce((max, line) => line.length > max.length ? line : max, "");
|
|
10992
10874
|
maxWidth = longestLine.length * fontSize * 0.6;
|
|
10993
10875
|
}
|
|
10994
|
-
const
|
|
10995
|
-
const resolvedMargin = Math.max(0, margin);
|
|
10876
|
+
const ins = this.normalizeLabelInset(inset, 8);
|
|
10996
10877
|
return {
|
|
10997
|
-
width: maxWidth +
|
|
10998
|
-
height: lines.length * lineHeight +
|
|
10878
|
+
width: maxWidth + ins.left + ins.right,
|
|
10879
|
+
height: lines.length * lineHeight + ins.top + ins.bottom
|
|
10999
10880
|
};
|
|
11000
10881
|
}
|
|
11001
10882
|
resolveMarkerConfig(edge, side) {
|
|
@@ -11120,9 +11001,9 @@ class SvgExporter {
|
|
|
11120
11001
|
return "";
|
|
11121
11002
|
}
|
|
11122
11003
|
const style = label.style;
|
|
11123
|
-
const
|
|
11124
|
-
const margin = Math.max(0, label.margin);
|
|
11004
|
+
const ins = this.normalizeLabelInset(label.inset, 8);
|
|
11125
11005
|
const align = style.align ?? "center";
|
|
11006
|
+
const verticalAlign = style.verticalAlign ?? "middle";
|
|
11126
11007
|
const ctx = this.getMeasurementContext();
|
|
11127
11008
|
let bounds;
|
|
11128
11009
|
let lines;
|
|
@@ -11140,18 +11021,27 @@ class SvgExporter {
|
|
|
11140
11021
|
lines = label.text.split("\n");
|
|
11141
11022
|
}
|
|
11142
11023
|
const inner = {
|
|
11143
|
-
x: bounds.x +
|
|
11144
|
-
y: bounds.y +
|
|
11145
|
-
width: Math.max(0, bounds.width -
|
|
11146
|
-
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)
|
|
11147
11028
|
};
|
|
11148
11029
|
let x = inner.x + inner.width / 2;
|
|
11149
11030
|
if (align === "left") {
|
|
11150
|
-
x = inner.x
|
|
11031
|
+
x = inner.x;
|
|
11151
11032
|
} else if (align === "right") {
|
|
11152
|
-
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;
|
|
11153
11044
|
}
|
|
11154
|
-
const y = inner.y + inner.height / 2;
|
|
11155
11045
|
return this.renderTextLabel(lines.join("\n"), { x, y }, style);
|
|
11156
11046
|
}
|
|
11157
11047
|
getNodeCornerRadius(node, bounds) {
|
|
@@ -11168,9 +11058,15 @@ class SvgExporter {
|
|
|
11168
11058
|
if (iconSize.width <= 0 || iconSize.height <= 0) {
|
|
11169
11059
|
return "";
|
|
11170
11060
|
}
|
|
11171
|
-
const
|
|
11172
|
-
const
|
|
11173
|
-
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);
|
|
11174
11070
|
if (drawRect.width <= 0 || drawRect.height <= 0) {
|
|
11175
11071
|
return "";
|
|
11176
11072
|
}
|
|
@@ -11179,17 +11075,16 @@ class SvgExporter {
|
|
|
11179
11075
|
return "";
|
|
11180
11076
|
}
|
|
11181
11077
|
const opacity = opts.opacity ?? 1;
|
|
11182
|
-
|
|
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"/>`;
|
|
11183
11080
|
}
|
|
11184
|
-
getIconBoxSize(
|
|
11185
|
-
const padding = opts.padding ?? 8;
|
|
11186
|
-
const margin = Math.max(0, opts.margin ?? 0);
|
|
11081
|
+
getIconBoxSize(imageSize, inset) {
|
|
11187
11082
|
return {
|
|
11188
|
-
width: imageSize.width +
|
|
11189
|
-
height: imageSize.height +
|
|
11083
|
+
width: imageSize.width + inset * 2,
|
|
11084
|
+
height: imageSize.height + inset * 2
|
|
11190
11085
|
};
|
|
11191
11086
|
}
|
|
11192
|
-
getIconBounds(bounds, iconBoxSize, placement) {
|
|
11087
|
+
getIconBounds(bounds, iconBoxSize, placement, inset) {
|
|
11193
11088
|
switch (placement) {
|
|
11194
11089
|
case "top":
|
|
11195
11090
|
return { x: bounds.x, y: bounds.y, width: bounds.width, height: iconBoxSize.height };
|
|
@@ -11210,25 +11105,30 @@ class SvgExporter {
|
|
|
11210
11105
|
height: bounds.height
|
|
11211
11106
|
};
|
|
11212
11107
|
case "top-left":
|
|
11213
|
-
return {
|
|
11108
|
+
return {
|
|
11109
|
+
x: bounds.x + inset,
|
|
11110
|
+
y: bounds.y + inset,
|
|
11111
|
+
width: iconBoxSize.width,
|
|
11112
|
+
height: iconBoxSize.height
|
|
11113
|
+
};
|
|
11214
11114
|
case "top-right":
|
|
11215
11115
|
return {
|
|
11216
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
11217
|
-
y: bounds.y,
|
|
11116
|
+
x: bounds.x + bounds.width - iconBoxSize.width - inset,
|
|
11117
|
+
y: bounds.y + inset,
|
|
11218
11118
|
width: iconBoxSize.width,
|
|
11219
11119
|
height: iconBoxSize.height
|
|
11220
11120
|
};
|
|
11221
11121
|
case "bottom-left":
|
|
11222
11122
|
return {
|
|
11223
|
-
x: bounds.x,
|
|
11224
|
-
y: bounds.y + bounds.height - iconBoxSize.height,
|
|
11123
|
+
x: bounds.x + inset,
|
|
11124
|
+
y: bounds.y + bounds.height - iconBoxSize.height - inset,
|
|
11225
11125
|
width: iconBoxSize.width,
|
|
11226
11126
|
height: iconBoxSize.height
|
|
11227
11127
|
};
|
|
11228
11128
|
case "bottom-right":
|
|
11229
11129
|
return {
|
|
11230
|
-
x: bounds.x + bounds.width - iconBoxSize.width,
|
|
11231
|
-
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,
|
|
11232
11132
|
width: iconBoxSize.width,
|
|
11233
11133
|
height: iconBoxSize.height
|
|
11234
11134
|
};
|
|
@@ -11237,23 +11137,17 @@ class SvgExporter {
|
|
|
11237
11137
|
return bounds;
|
|
11238
11138
|
}
|
|
11239
11139
|
}
|
|
11240
|
-
getIconDrawRect(bounds, opts, imageSize) {
|
|
11241
|
-
const padding = opts.padding ?? 8;
|
|
11242
|
-
const margin = Math.max(0, opts.margin ?? 0);
|
|
11140
|
+
getIconDrawRect(bounds, opts, imageSize, inset) {
|
|
11243
11141
|
const fit = opts.fit ?? "none";
|
|
11244
11142
|
const scaleWithBounds = opts.scaleWithBounds ?? false;
|
|
11245
|
-
const align = opts.align ?? "center";
|
|
11246
|
-
const verticalAlign = opts.verticalAlign ?? "center";
|
|
11247
|
-
const offsetX = opts.offsetX ?? 0;
|
|
11248
|
-
const offsetY = opts.offsetY ?? 0;
|
|
11249
11143
|
const innerBounds = {
|
|
11250
|
-
x: bounds.x +
|
|
11251
|
-
y: bounds.y +
|
|
11252
|
-
width: Math.max(0, bounds.width -
|
|
11253
|
-
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)
|
|
11254
11148
|
};
|
|
11255
|
-
const availableWidth = Math.max(0, innerBounds.width
|
|
11256
|
-
const availableHeight = Math.max(0, innerBounds.height
|
|
11149
|
+
const availableWidth = Math.max(0, innerBounds.width);
|
|
11150
|
+
const availableHeight = Math.max(0, innerBounds.height);
|
|
11257
11151
|
let drawWidth = opts.width ?? imageSize.width;
|
|
11258
11152
|
let drawHeight = opts.height ?? imageSize.height;
|
|
11259
11153
|
if (scaleWithBounds) {
|
|
@@ -11270,21 +11164,13 @@ class SvgExporter {
|
|
|
11270
11164
|
}
|
|
11271
11165
|
drawWidth = Math.min(Math.max(0, drawWidth), Math.max(0, availableWidth));
|
|
11272
11166
|
drawHeight = Math.min(Math.max(0, drawHeight), Math.max(0, availableHeight));
|
|
11273
|
-
let x = innerBounds.x
|
|
11274
|
-
let y = innerBounds.y
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
} else if (align === "right") {
|
|
11278
|
-
x = innerBounds.x + innerBounds.width - drawWidth - padding;
|
|
11279
|
-
}
|
|
11280
|
-
if (verticalAlign === "center") {
|
|
11281
|
-
y = innerBounds.y + (innerBounds.height - drawHeight) / 2;
|
|
11282
|
-
} else if (verticalAlign === "bottom") {
|
|
11283
|
-
y = innerBounds.y + innerBounds.height - drawHeight - padding;
|
|
11284
|
-
}
|
|
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;
|
|
11285
11171
|
return {
|
|
11286
|
-
x
|
|
11287
|
-
y
|
|
11172
|
+
x,
|
|
11173
|
+
y,
|
|
11288
11174
|
width: drawWidth,
|
|
11289
11175
|
height: drawHeight
|
|
11290
11176
|
};
|
|
@@ -11379,7 +11265,7 @@ class SvgExporter {
|
|
|
11379
11265
|
}
|
|
11380
11266
|
createEmptySvg(width, height, backgroundColor, includeBackground) {
|
|
11381
11267
|
return [
|
|
11382
|
-
`<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}">`,
|
|
11383
11269
|
includeBackground ? `<rect width="100%" height="100%" fill="${backgroundColor}"/>` : "",
|
|
11384
11270
|
"</svg>"
|
|
11385
11271
|
].join("");
|