@ngroznykh/papirus 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -8
- package/README.ru.md +23 -8
- package/dist/core/InteractionManager.d.ts +5 -0
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts +4 -0
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/Node.d.ts +10 -0
- package/dist/elements/Node.d.ts.map +1 -1
- package/dist/elements/TextLabel.d.ts +9 -2
- package/dist/elements/TextLabel.d.ts.map +1 -1
- package/dist/elements/nodes/CircleNode.d.ts +2 -1
- package/dist/elements/nodes/CircleNode.d.ts.map +1 -1
- package/dist/elements/nodes/CustomShapeNode.d.ts +4 -0
- package/dist/elements/nodes/CustomShapeNode.d.ts.map +1 -1
- package/dist/elements/nodes/DiamondNode.d.ts +2 -1
- package/dist/elements/nodes/DiamondNode.d.ts.map +1 -1
- package/dist/papirus.js +357 -29
- package/dist/papirus.js.map +1 -1
- package/package.json +5 -2
package/dist/papirus.js
CHANGED
|
@@ -1968,23 +1968,55 @@ class TextLabel {
|
|
|
1968
1968
|
return this._maxWidth;
|
|
1969
1969
|
}
|
|
1970
1970
|
set maxWidth(value) {
|
|
1971
|
+
if (this._maxWidth === value) {
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1971
1974
|
this._maxWidth = value;
|
|
1972
1975
|
this._lines = [];
|
|
1973
1976
|
this._measureDirty = true;
|
|
1974
1977
|
this._onChange?.();
|
|
1975
1978
|
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Internal max width used for automatic wrapping by container elements.
|
|
1981
|
+
*/
|
|
1982
|
+
setAutoMaxWidth(value) {
|
|
1983
|
+
if (this._autoMaxWidth === value) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
this._autoMaxWidth = value;
|
|
1987
|
+
this._lines = [];
|
|
1988
|
+
this._measureDirty = true;
|
|
1989
|
+
}
|
|
1976
1990
|
/**
|
|
1977
1991
|
* Label padding
|
|
1978
1992
|
*/
|
|
1979
1993
|
get padding() {
|
|
1980
1994
|
return this._padding;
|
|
1981
1995
|
}
|
|
1996
|
+
set padding(value) {
|
|
1997
|
+
const next = Number.isFinite(value) ? Math.max(0, value) : this._padding;
|
|
1998
|
+
if (this._padding !== next) {
|
|
1999
|
+
this._padding = next;
|
|
2000
|
+
this._lines = [];
|
|
2001
|
+
this._measureDirty = true;
|
|
2002
|
+
this._onChange?.();
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1982
2005
|
/**
|
|
1983
2006
|
* Label margin
|
|
1984
2007
|
*/
|
|
1985
2008
|
get margin() {
|
|
1986
2009
|
return this._margin;
|
|
1987
2010
|
}
|
|
2011
|
+
set margin(value) {
|
|
2012
|
+
const next = Number.isFinite(value) ? Math.max(0, value) : this._margin;
|
|
2013
|
+
if (this._margin !== next) {
|
|
2014
|
+
this._margin = next;
|
|
2015
|
+
this._lines = [];
|
|
2016
|
+
this._measureDirty = true;
|
|
2017
|
+
this._onChange?.();
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1988
2020
|
applyStyleManager(styleManager) {
|
|
1989
2021
|
const baseStyle = styleManager.getTextStyle(this._styleClass);
|
|
1990
2022
|
const mergedStyle = { ...baseStyle, ...this._localStyle };
|
|
@@ -2022,8 +2054,9 @@ class TextLabel {
|
|
|
2022
2054
|
}
|
|
2023
2055
|
this.applyStyle(ctx);
|
|
2024
2056
|
const lineHeight = (this._style.fontSize ?? 14) * 1.2;
|
|
2025
|
-
|
|
2026
|
-
|
|
2057
|
+
const effectiveMaxWidth = this._maxWidth ?? this._autoMaxWidth;
|
|
2058
|
+
if (effectiveMaxWidth !== void 0) {
|
|
2059
|
+
const maxWidth = Math.max(0, effectiveMaxWidth - this._margin * 2);
|
|
2027
2060
|
this._lines = this.wrapText(ctx, this._text, Math.max(0, maxWidth - this._padding * 2));
|
|
2028
2061
|
} else {
|
|
2029
2062
|
this._lines = this._text.split("\n");
|
|
@@ -3508,6 +3541,19 @@ class Edge extends Element {
|
|
|
3508
3541
|
}
|
|
3509
3542
|
return this.getPolylineMidpoint(path);
|
|
3510
3543
|
}
|
|
3544
|
+
/**
|
|
3545
|
+
* Get world position of label center along path.
|
|
3546
|
+
*/
|
|
3547
|
+
getLabelPosition() {
|
|
3548
|
+
if (this._path.length < 2) {
|
|
3549
|
+
return null;
|
|
3550
|
+
}
|
|
3551
|
+
const midpoint = this.getPathMidpoint();
|
|
3552
|
+
return {
|
|
3553
|
+
x: midpoint.x,
|
|
3554
|
+
y: midpoint.y + this._labelOffset
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3511
3557
|
getPolylineMidpoint(path) {
|
|
3512
3558
|
if (path.length === 0) {
|
|
3513
3559
|
return { x: 0, y: 0 };
|
|
@@ -3578,6 +3624,7 @@ class InteractionManager {
|
|
|
3578
3624
|
this.clipboard = null;
|
|
3579
3625
|
this.pendingPropertyChanges = /* @__PURE__ */ new Map();
|
|
3580
3626
|
this.propertyChangeDebounceMs = 350;
|
|
3627
|
+
this.activeLabelEditor = null;
|
|
3581
3628
|
this.renderer = options.renderer;
|
|
3582
3629
|
this.inputHandler = new InputHandler({
|
|
3583
3630
|
canvas: this.renderer.getCanvas(),
|
|
@@ -3704,6 +3751,7 @@ class InteractionManager {
|
|
|
3704
3751
|
}
|
|
3705
3752
|
}
|
|
3706
3753
|
destroy() {
|
|
3754
|
+
this.finishInlineLabelEdit(false);
|
|
3707
3755
|
this.inputHandler.destroy();
|
|
3708
3756
|
this.overlayCleanup?.();
|
|
3709
3757
|
this.overlayCleanup = null;
|
|
@@ -3724,6 +3772,7 @@ class InteractionManager {
|
|
|
3724
3772
|
this.inputHandler.on("mousemove", (event) => this.handleMouseMove(event));
|
|
3725
3773
|
this.inputHandler.on("mouseup", (event) => this.handleMouseUp(event));
|
|
3726
3774
|
this.inputHandler.on("click", (event) => this.handleClick(event));
|
|
3775
|
+
this.inputHandler.on("dblclick", (event) => this.handleDoubleClick(event));
|
|
3727
3776
|
this.inputHandler.on("wheel", (event) => this.handleWheel(event));
|
|
3728
3777
|
this.inputHandler.on("pan", (event) => this.handlePan(event));
|
|
3729
3778
|
this.inputHandler.on("pinch", (event) => this.handlePinch(event));
|
|
@@ -3861,6 +3910,56 @@ class InteractionManager {
|
|
|
3861
3910
|
}
|
|
3862
3911
|
this.selectionManager.handleClick(event);
|
|
3863
3912
|
}
|
|
3913
|
+
handleDoubleClick(event) {
|
|
3914
|
+
if (this.dragManager.handledMouseDown || this.resizeManager.handledMouseDown || this.connectionManager.connecting) {
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
const point = { x: event.worldX, y: event.worldY };
|
|
3918
|
+
const edgeByLabel = this.getEdgeLabelAtPoint(point);
|
|
3919
|
+
if (edgeByLabel) {
|
|
3920
|
+
const labelPosition = edgeByLabel.getLabelPosition() ?? point;
|
|
3921
|
+
this.startInlineLabelEdit("edge", edgeByLabel.id, edgeByLabel.label?.text ?? "", labelPosition);
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
const hitElement = this.renderer.getElementAtPoint(point);
|
|
3925
|
+
if (!hitElement) {
|
|
3926
|
+
this.finishInlineLabelEdit(true);
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
if ("typeName" in hitElement) {
|
|
3930
|
+
this.startInlineLabelEdit("node", hitElement.id, hitElement.label?.text ?? "", hitElement.getLabelPosition());
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
if ("from" in hitElement && "to" in hitElement) {
|
|
3934
|
+
const labelPosition = hitElement.getLabelPosition() ?? point;
|
|
3935
|
+
this.startInlineLabelEdit("edge", hitElement.id, hitElement.label?.text ?? "", labelPosition);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
getEdgeLabelAtPoint(point) {
|
|
3939
|
+
const zoom = Math.max(this.renderer.zoom, 1e-4);
|
|
3940
|
+
const maxDistance = 28 / zoom;
|
|
3941
|
+
const maxDistanceSq = maxDistance * maxDistance;
|
|
3942
|
+
let closestEdge = null;
|
|
3943
|
+
let closestDistanceSq = Infinity;
|
|
3944
|
+
for (const edge of this.renderer.edges.values()) {
|
|
3945
|
+
if (!edge.visible || !edge.label) {
|
|
3946
|
+
continue;
|
|
3947
|
+
}
|
|
3948
|
+
const labelPosition = edge.getLabelPosition();
|
|
3949
|
+
if (!labelPosition) {
|
|
3950
|
+
continue;
|
|
3951
|
+
}
|
|
3952
|
+
const dx = point.x - labelPosition.x;
|
|
3953
|
+
const dy = point.y - labelPosition.y;
|
|
3954
|
+
const distanceSq = dx * dx + dy * dy;
|
|
3955
|
+
if (distanceSq > maxDistanceSq || distanceSq >= closestDistanceSq) {
|
|
3956
|
+
continue;
|
|
3957
|
+
}
|
|
3958
|
+
closestDistanceSq = distanceSq;
|
|
3959
|
+
closestEdge = edge;
|
|
3960
|
+
}
|
|
3961
|
+
return closestEdge;
|
|
3962
|
+
}
|
|
3864
3963
|
handleWheel(event) {
|
|
3865
3964
|
this.navigationManager.handleWheel(event);
|
|
3866
3965
|
}
|
|
@@ -3899,6 +3998,109 @@ class InteractionManager {
|
|
|
3899
3998
|
handleKeyUp(event) {
|
|
3900
3999
|
this.navigationManager.handleKeyUp(event);
|
|
3901
4000
|
}
|
|
4001
|
+
startInlineLabelEdit(kind, id, text, worldPosition) {
|
|
4002
|
+
this.finishInlineLabelEdit(true);
|
|
4003
|
+
const screenPoint = this.renderer.worldToScreen(worldPosition.x, worldPosition.y);
|
|
4004
|
+
const textarea = document.createElement("textarea");
|
|
4005
|
+
textarea.value = text;
|
|
4006
|
+
textarea.rows = 1;
|
|
4007
|
+
textarea.spellcheck = false;
|
|
4008
|
+
textarea.setAttribute("aria-label", "Edit label");
|
|
4009
|
+
textarea.style.position = "fixed";
|
|
4010
|
+
textarea.style.left = `${screenPoint.x}px`;
|
|
4011
|
+
textarea.style.top = `${screenPoint.y}px`;
|
|
4012
|
+
textarea.style.transform = "translate(-50%, -50%)";
|
|
4013
|
+
textarea.style.zIndex = "10001";
|
|
4014
|
+
textarea.style.minWidth = "120px";
|
|
4015
|
+
textarea.style.maxWidth = "420px";
|
|
4016
|
+
textarea.style.minHeight = "30px";
|
|
4017
|
+
textarea.style.padding = "6px 8px";
|
|
4018
|
+
textarea.style.border = "1px solid #6366f1";
|
|
4019
|
+
textarea.style.borderRadius = "6px";
|
|
4020
|
+
textarea.style.boxShadow = "0 8px 18px rgba(15, 23, 42, 0.15)";
|
|
4021
|
+
textarea.style.background = "#ffffff";
|
|
4022
|
+
textarea.style.color = "#0f172a";
|
|
4023
|
+
textarea.style.font = "14px sans-serif";
|
|
4024
|
+
textarea.style.lineHeight = "1.4";
|
|
4025
|
+
textarea.style.resize = "none";
|
|
4026
|
+
textarea.style.overflow = "hidden";
|
|
4027
|
+
const resizeTextarea = () => {
|
|
4028
|
+
textarea.style.height = "auto";
|
|
4029
|
+
textarea.style.height = `${Math.max(30, textarea.scrollHeight)}px`;
|
|
4030
|
+
};
|
|
4031
|
+
resizeTextarea();
|
|
4032
|
+
const commitAndClose = () => this.finishInlineLabelEdit(true);
|
|
4033
|
+
const cancelAndClose = () => this.finishInlineLabelEdit(false);
|
|
4034
|
+
const onKeyDown = (e) => {
|
|
4035
|
+
if (e.key === "Escape") {
|
|
4036
|
+
e.preventDefault();
|
|
4037
|
+
cancelAndClose();
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
4041
|
+
e.preventDefault();
|
|
4042
|
+
commitAndClose();
|
|
4043
|
+
}
|
|
4044
|
+
};
|
|
4045
|
+
const onBlur = () => commitAndClose();
|
|
4046
|
+
const onInput = () => resizeTextarea();
|
|
4047
|
+
textarea.addEventListener("keydown", onKeyDown);
|
|
4048
|
+
textarea.addEventListener("blur", onBlur);
|
|
4049
|
+
textarea.addEventListener("input", onInput);
|
|
4050
|
+
document.body.appendChild(textarea);
|
|
4051
|
+
textarea.focus();
|
|
4052
|
+
textarea.select();
|
|
4053
|
+
this.activeLabelEditor = {
|
|
4054
|
+
kind,
|
|
4055
|
+
id,
|
|
4056
|
+
textarea,
|
|
4057
|
+
cleanup: () => {
|
|
4058
|
+
textarea.removeEventListener("keydown", onKeyDown);
|
|
4059
|
+
textarea.removeEventListener("blur", onBlur);
|
|
4060
|
+
textarea.removeEventListener("input", onInput);
|
|
4061
|
+
textarea.remove();
|
|
4062
|
+
}
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
finishInlineLabelEdit(commit) {
|
|
4066
|
+
if (!this.activeLabelEditor) {
|
|
4067
|
+
return;
|
|
4068
|
+
}
|
|
4069
|
+
const { kind, id, textarea, cleanup } = this.activeLabelEditor;
|
|
4070
|
+
this.activeLabelEditor = null;
|
|
4071
|
+
const nextValue = textarea.value;
|
|
4072
|
+
cleanup();
|
|
4073
|
+
if (!commit) {
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
if (kind === "node") {
|
|
4077
|
+
this.changeNodeProperties(id, (node) => {
|
|
4078
|
+
const value = nextValue.trim();
|
|
4079
|
+
if (value.length === 0) {
|
|
4080
|
+
node.label = void 0;
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (!node.label) {
|
|
4084
|
+
node.label = value;
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
node.label.text = value;
|
|
4088
|
+
});
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
this.changeEdgeProperties(id, (edge) => {
|
|
4092
|
+
const value = nextValue.trim();
|
|
4093
|
+
if (value.length === 0) {
|
|
4094
|
+
edge.label = void 0;
|
|
4095
|
+
return;
|
|
4096
|
+
}
|
|
4097
|
+
if (!edge.label) {
|
|
4098
|
+
edge.label = value;
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
edge.label.text = value;
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
3902
4104
|
queuePropertyChange(kind, id, before, after) {
|
|
3903
4105
|
const key = `${kind}:${id}`;
|
|
3904
4106
|
const existing = this.pendingPropertyChanges.get(key);
|
|
@@ -5950,6 +6152,7 @@ class Node extends Element {
|
|
|
5950
6152
|
this._showPortsAlways = options.showPortsAlways ?? false;
|
|
5951
6153
|
this._anchorPoints = this.normalizeAnchorPoints(options.anchorPoints);
|
|
5952
6154
|
this._labelPlacement = options.labelPlacement ?? "auto";
|
|
6155
|
+
this._resizeHandlesEnabled = options.resizeHandlesEnabled ?? true;
|
|
5953
6156
|
if (options.label !== void 0) {
|
|
5954
6157
|
if (typeof options.label === "string") {
|
|
5955
6158
|
this._label = new TextLabel({ text: options.label, onChange: () => this.markDirty() });
|
|
@@ -6111,6 +6314,15 @@ class Node extends Element {
|
|
|
6111
6314
|
this.markDirty();
|
|
6112
6315
|
}
|
|
6113
6316
|
}
|
|
6317
|
+
get resizeHandlesEnabled() {
|
|
6318
|
+
return this._resizeHandlesEnabled;
|
|
6319
|
+
}
|
|
6320
|
+
set resizeHandlesEnabled(value) {
|
|
6321
|
+
if (this._resizeHandlesEnabled !== value) {
|
|
6322
|
+
this._resizeHandlesEnabled = value;
|
|
6323
|
+
this.markDirty();
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6114
6326
|
/**
|
|
6115
6327
|
* Anchor points configuration (per side)
|
|
6116
6328
|
*/
|
|
@@ -6163,6 +6375,25 @@ class Node extends Element {
|
|
|
6163
6375
|
this._label.render(ctx, bounds);
|
|
6164
6376
|
ctx.globalAlpha = 1;
|
|
6165
6377
|
}
|
|
6378
|
+
/**
|
|
6379
|
+
* Get world position of label center.
|
|
6380
|
+
*/
|
|
6381
|
+
getLabelPosition() {
|
|
6382
|
+
const bounds = this.getBounds();
|
|
6383
|
+
const placement = this._labelPlacement === "auto" ? "center" : this._labelPlacement;
|
|
6384
|
+
switch (placement) {
|
|
6385
|
+
case "top":
|
|
6386
|
+
return { x: bounds.x + bounds.width / 2, y: bounds.y };
|
|
6387
|
+
case "bottom":
|
|
6388
|
+
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height };
|
|
6389
|
+
case "left":
|
|
6390
|
+
return { x: bounds.x, y: bounds.y + bounds.height / 2 };
|
|
6391
|
+
case "right":
|
|
6392
|
+
return { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 };
|
|
6393
|
+
default:
|
|
6394
|
+
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6166
6397
|
/**
|
|
6167
6398
|
* Render icon, label, and ports
|
|
6168
6399
|
*/
|
|
@@ -6170,14 +6401,22 @@ class Node extends Element {
|
|
|
6170
6401
|
ctx.setLineDash([]);
|
|
6171
6402
|
ctx.lineDashOffset = 0;
|
|
6172
6403
|
let bounds = this.getBounds();
|
|
6173
|
-
|
|
6174
|
-
|
|
6404
|
+
let iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
6405
|
+
if (this._label) {
|
|
6406
|
+
this._label.setAutoMaxWidth(this.getAutoLabelBounds(bounds, iconBoxSize).width);
|
|
6407
|
+
}
|
|
6408
|
+
let labelSize = this._label ? this._label.measure(ctx) : void 0;
|
|
6175
6409
|
if (labelSize || iconBoxSize) {
|
|
6176
6410
|
this.ensureContentsFit(labelSize, iconBoxSize);
|
|
6177
6411
|
bounds = this.getBounds();
|
|
6412
|
+
iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
6413
|
+
if (this._label) {
|
|
6414
|
+
this._label.setAutoMaxWidth(this.getAutoLabelBounds(bounds, iconBoxSize).width);
|
|
6415
|
+
labelSize = this._label.measure(ctx);
|
|
6416
|
+
}
|
|
6178
6417
|
}
|
|
6179
6418
|
let iconBounds = bounds;
|
|
6180
|
-
let labelBounds = bounds;
|
|
6419
|
+
let labelBounds = this.getLabelContainerBounds(bounds);
|
|
6181
6420
|
if (this._icon && iconBoxSize) {
|
|
6182
6421
|
iconBounds = this.getIconBounds(bounds, iconBoxSize, this._icon.placement);
|
|
6183
6422
|
}
|
|
@@ -6186,8 +6425,9 @@ class Node extends Element {
|
|
|
6186
6425
|
const placement = this._icon.placement;
|
|
6187
6426
|
if (isCornerPlacement(placement)) {
|
|
6188
6427
|
iconBounds = this.getIconBounds(bounds, iconBoxSize, placement);
|
|
6189
|
-
labelBounds = bounds;
|
|
6428
|
+
labelBounds = this.getLabelContainerBounds(bounds);
|
|
6190
6429
|
} else {
|
|
6430
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
6191
6431
|
switch (placement) {
|
|
6192
6432
|
case "top":
|
|
6193
6433
|
iconBounds = {
|
|
@@ -6197,10 +6437,10 @@ class Node extends Element {
|
|
|
6197
6437
|
height: iconBoxSize.height
|
|
6198
6438
|
};
|
|
6199
6439
|
labelBounds = {
|
|
6200
|
-
x:
|
|
6201
|
-
y:
|
|
6202
|
-
width:
|
|
6203
|
-
height: Math.max(0,
|
|
6440
|
+
x: contentBounds.x,
|
|
6441
|
+
y: contentBounds.y + iconBoxSize.height + gap,
|
|
6442
|
+
width: contentBounds.width,
|
|
6443
|
+
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
6204
6444
|
};
|
|
6205
6445
|
break;
|
|
6206
6446
|
case "bottom":
|
|
@@ -6211,10 +6451,10 @@ class Node extends Element {
|
|
|
6211
6451
|
height: iconBoxSize.height
|
|
6212
6452
|
};
|
|
6213
6453
|
labelBounds = {
|
|
6214
|
-
x:
|
|
6215
|
-
y:
|
|
6216
|
-
width:
|
|
6217
|
-
height: Math.max(0,
|
|
6454
|
+
x: contentBounds.x,
|
|
6455
|
+
y: contentBounds.y,
|
|
6456
|
+
width: contentBounds.width,
|
|
6457
|
+
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
6218
6458
|
};
|
|
6219
6459
|
break;
|
|
6220
6460
|
case "left":
|
|
@@ -6225,10 +6465,10 @@ class Node extends Element {
|
|
|
6225
6465
|
height: bounds.height
|
|
6226
6466
|
};
|
|
6227
6467
|
labelBounds = {
|
|
6228
|
-
x:
|
|
6229
|
-
y:
|
|
6230
|
-
width: Math.max(0,
|
|
6231
|
-
height:
|
|
6468
|
+
x: contentBounds.x + iconBoxSize.width + gap,
|
|
6469
|
+
y: contentBounds.y,
|
|
6470
|
+
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
6471
|
+
height: contentBounds.height
|
|
6232
6472
|
};
|
|
6233
6473
|
break;
|
|
6234
6474
|
case "right":
|
|
@@ -6239,16 +6479,16 @@ class Node extends Element {
|
|
|
6239
6479
|
height: bounds.height
|
|
6240
6480
|
};
|
|
6241
6481
|
labelBounds = {
|
|
6242
|
-
x:
|
|
6243
|
-
y:
|
|
6244
|
-
width: Math.max(0,
|
|
6245
|
-
height:
|
|
6482
|
+
x: contentBounds.x,
|
|
6483
|
+
y: contentBounds.y,
|
|
6484
|
+
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
6485
|
+
height: contentBounds.height
|
|
6246
6486
|
};
|
|
6247
6487
|
break;
|
|
6248
6488
|
}
|
|
6249
6489
|
}
|
|
6250
6490
|
} else if (this._label && labelSize) {
|
|
6251
|
-
labelBounds = this.getLabelBounds(bounds, labelSize, this._labelPlacement);
|
|
6491
|
+
labelBounds = this.getLabelBounds(this.getAutoLabelBounds(bounds, iconBoxSize), labelSize, this._labelPlacement);
|
|
6252
6492
|
}
|
|
6253
6493
|
if (this._icon) {
|
|
6254
6494
|
this._icon.render(ctx, iconBounds);
|
|
@@ -6260,15 +6500,16 @@ class Node extends Element {
|
|
|
6260
6500
|
* Minimal size required to fit current contents
|
|
6261
6501
|
*/
|
|
6262
6502
|
getContentMinSize(ctx) {
|
|
6503
|
+
const bounds = this.getBounds();
|
|
6263
6504
|
const labelSize = this._label ? this._label.measure(ctx) : void 0;
|
|
6264
6505
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
6265
|
-
return this.calculateContentMinSize(labelSize, iconBoxSize);
|
|
6506
|
+
return this.calculateContentMinSize(labelSize, iconBoxSize, bounds);
|
|
6266
6507
|
}
|
|
6267
6508
|
/**
|
|
6268
6509
|
* Render resize handles when selected
|
|
6269
6510
|
*/
|
|
6270
6511
|
renderResizeHandles(ctx) {
|
|
6271
|
-
if (this._state !== "selected") {
|
|
6512
|
+
if (this._state !== "selected" || !this._resizeHandlesEnabled) {
|
|
6272
6513
|
return;
|
|
6273
6514
|
}
|
|
6274
6515
|
const bounds = this.getBounds();
|
|
@@ -6429,7 +6670,7 @@ class Node extends Element {
|
|
|
6429
6670
|
}
|
|
6430
6671
|
ensureContentsFit(labelSize, iconBoxSize) {
|
|
6431
6672
|
const bounds = this.getBounds();
|
|
6432
|
-
const minSize = this.calculateContentMinSize(labelSize, iconBoxSize);
|
|
6673
|
+
const minSize = this.calculateContentMinSize(labelSize, iconBoxSize, bounds);
|
|
6433
6674
|
if (minSize.width > bounds.width) {
|
|
6434
6675
|
this.width = minSize.width;
|
|
6435
6676
|
}
|
|
@@ -6437,12 +6678,15 @@ class Node extends Element {
|
|
|
6437
6678
|
this.height = minSize.height;
|
|
6438
6679
|
}
|
|
6439
6680
|
}
|
|
6440
|
-
calculateContentMinSize(labelSize, iconBoxSize) {
|
|
6681
|
+
calculateContentMinSize(labelSize, iconBoxSize, bounds) {
|
|
6441
6682
|
let minWidth = 0;
|
|
6442
6683
|
let minHeight = 0;
|
|
6684
|
+
const labelContainer = this.getLabelContainerBounds(bounds);
|
|
6685
|
+
const widthFactor = labelContainer.width > 0 ? bounds.width / labelContainer.width : 1;
|
|
6686
|
+
const heightFactor = labelContainer.height > 0 ? bounds.height / labelContainer.height : 1;
|
|
6443
6687
|
if (labelSize) {
|
|
6444
|
-
minWidth = Math.max(minWidth, labelSize.width);
|
|
6445
|
-
minHeight = Math.max(minHeight, labelSize.height);
|
|
6688
|
+
minWidth = Math.max(minWidth, labelSize.width * widthFactor);
|
|
6689
|
+
minHeight = Math.max(minHeight, labelSize.height * heightFactor);
|
|
6446
6690
|
}
|
|
6447
6691
|
if (iconBoxSize) {
|
|
6448
6692
|
minWidth = Math.max(minWidth, iconBoxSize.width);
|
|
@@ -6468,16 +6712,70 @@ class Node extends Element {
|
|
|
6468
6712
|
const labelVertical = labelPlacement === "top" || labelPlacement === "bottom";
|
|
6469
6713
|
const iconHorizontal = iconPlacement === "left" || iconPlacement === "right";
|
|
6470
6714
|
const iconVertical = iconPlacement === "top" || iconPlacement === "bottom";
|
|
6715
|
+
const labelSharesIconAxis = iconHorizontal && (labelPlacement === "center" || labelPlacement === iconPlacement) || iconVertical && (labelPlacement === "center" || labelPlacement === iconPlacement);
|
|
6471
6716
|
if (labelHorizontal && iconHorizontal && labelPlacement !== iconPlacement) {
|
|
6472
6717
|
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
6473
6718
|
}
|
|
6474
6719
|
if (labelVertical && iconVertical && labelPlacement !== iconPlacement) {
|
|
6475
6720
|
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
6476
6721
|
}
|
|
6722
|
+
if (labelSharesIconAxis) {
|
|
6723
|
+
if (iconHorizontal) {
|
|
6724
|
+
minWidth = Math.max(minWidth, iconBoxSize.width + gap + labelSize.width);
|
|
6725
|
+
}
|
|
6726
|
+
if (iconVertical) {
|
|
6727
|
+
minHeight = Math.max(minHeight, iconBoxSize.height + gap + labelSize.height);
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6477
6730
|
}
|
|
6478
6731
|
}
|
|
6479
6732
|
return { width: minWidth, height: minHeight };
|
|
6480
6733
|
}
|
|
6734
|
+
getLabelContainerBounds(bounds) {
|
|
6735
|
+
return bounds;
|
|
6736
|
+
}
|
|
6737
|
+
getAutoLabelBounds(bounds, iconBoxSize) {
|
|
6738
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
6739
|
+
if (!this._icon || !iconBoxSize || this._icon.placement === "center") {
|
|
6740
|
+
return contentBounds;
|
|
6741
|
+
}
|
|
6742
|
+
const gap = this._icon.gap;
|
|
6743
|
+
if (isCornerPlacement(this._icon.placement)) {
|
|
6744
|
+
return contentBounds;
|
|
6745
|
+
}
|
|
6746
|
+
switch (this._icon.placement) {
|
|
6747
|
+
case "left":
|
|
6748
|
+
return {
|
|
6749
|
+
x: contentBounds.x + iconBoxSize.width + gap,
|
|
6750
|
+
y: contentBounds.y,
|
|
6751
|
+
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
6752
|
+
height: contentBounds.height
|
|
6753
|
+
};
|
|
6754
|
+
case "right":
|
|
6755
|
+
return {
|
|
6756
|
+
x: contentBounds.x,
|
|
6757
|
+
y: contentBounds.y,
|
|
6758
|
+
width: Math.max(0, contentBounds.width - iconBoxSize.width - gap),
|
|
6759
|
+
height: contentBounds.height
|
|
6760
|
+
};
|
|
6761
|
+
case "top":
|
|
6762
|
+
return {
|
|
6763
|
+
x: contentBounds.x,
|
|
6764
|
+
y: contentBounds.y + iconBoxSize.height + gap,
|
|
6765
|
+
width: contentBounds.width,
|
|
6766
|
+
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
6767
|
+
};
|
|
6768
|
+
case "bottom":
|
|
6769
|
+
return {
|
|
6770
|
+
x: contentBounds.x,
|
|
6771
|
+
y: contentBounds.y,
|
|
6772
|
+
width: contentBounds.width,
|
|
6773
|
+
height: Math.max(0, contentBounds.height - iconBoxSize.height - gap)
|
|
6774
|
+
};
|
|
6775
|
+
default:
|
|
6776
|
+
return contentBounds;
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6481
6779
|
getLabelBounds(bounds, labelSize, placement) {
|
|
6482
6780
|
const normalizedPlacement = placement === "auto" ? "center" : placement;
|
|
6483
6781
|
const width = Math.min(labelSize.width, bounds.width);
|
|
@@ -6901,6 +7199,16 @@ class CircleNode extends Node {
|
|
|
6901
7199
|
y: center.y + dy * scale
|
|
6902
7200
|
};
|
|
6903
7201
|
}
|
|
7202
|
+
getLabelContainerBounds(bounds) {
|
|
7203
|
+
const width = bounds.width / Math.SQRT2;
|
|
7204
|
+
const height = bounds.height / Math.SQRT2;
|
|
7205
|
+
return {
|
|
7206
|
+
x: bounds.x + (bounds.width - width) / 2,
|
|
7207
|
+
y: bounds.y + (bounds.height - height) / 2,
|
|
7208
|
+
width,
|
|
7209
|
+
height
|
|
7210
|
+
};
|
|
7211
|
+
}
|
|
6904
7212
|
render(ctx) {
|
|
6905
7213
|
const center = this.getCenter();
|
|
6906
7214
|
const rx = this._width / 2;
|
|
@@ -6952,6 +7260,16 @@ class DiamondNode extends Node {
|
|
|
6952
7260
|
y: center.y + dy * t
|
|
6953
7261
|
};
|
|
6954
7262
|
}
|
|
7263
|
+
getLabelContainerBounds(bounds) {
|
|
7264
|
+
const width = bounds.width / 2;
|
|
7265
|
+
const height = bounds.height / 2;
|
|
7266
|
+
return {
|
|
7267
|
+
x: bounds.x + (bounds.width - width) / 2,
|
|
7268
|
+
y: bounds.y + (bounds.height - height) / 2,
|
|
7269
|
+
width,
|
|
7270
|
+
height
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
6955
7273
|
render(ctx) {
|
|
6956
7274
|
const center = this.getCenter();
|
|
6957
7275
|
const hw = this._width / 2;
|
|
@@ -6981,10 +7299,20 @@ class CustomShapeNode extends Node {
|
|
|
6981
7299
|
} else {
|
|
6982
7300
|
this._pathFactory = options.path;
|
|
6983
7301
|
}
|
|
7302
|
+
this._shapeType = options.shapeType;
|
|
6984
7303
|
}
|
|
6985
7304
|
get typeName() {
|
|
6986
7305
|
return "custom";
|
|
6987
7306
|
}
|
|
7307
|
+
get shapeType() {
|
|
7308
|
+
return this._shapeType;
|
|
7309
|
+
}
|
|
7310
|
+
set shapeType(value) {
|
|
7311
|
+
if (this._shapeType !== value) {
|
|
7312
|
+
this._shapeType = value;
|
|
7313
|
+
this.markDirty();
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
6988
7316
|
/**
|
|
6989
7317
|
* Set a new path factory
|
|
6990
7318
|
*/
|