@joint/core 4.2.0-alpha.0 → 4.2.0-alpha.1
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 +3 -1
- package/dist/geometry.js +2 -2
- package/dist/geometry.min.js +3 -2
- package/dist/joint.d.ts +280 -149
- package/dist/joint.js +1427 -475
- package/dist/joint.min.js +4 -3
- package/dist/joint.nowrap.js +1427 -475
- package/dist/joint.nowrap.min.js +4 -3
- package/dist/vectorizer.js +21 -8
- package/dist/vectorizer.min.js +4 -3
- package/dist/version.mjs +1 -1
- package/package.json +7 -7
- package/src/V/index.mjs +20 -5
- package/src/alg/Deque.mjs +126 -0
- package/src/cellTools/Boundary.mjs +15 -13
- package/src/cellTools/Button.mjs +7 -5
- package/src/cellTools/Control.mjs +37 -14
- package/src/cellTools/HoverConnect.mjs +5 -1
- package/src/cellTools/helpers.mjs +44 -3
- package/src/config/index.mjs +8 -0
- package/src/dia/Cell.mjs +26 -10
- package/src/dia/CellView.mjs +7 -0
- package/src/dia/Element.mjs +4 -2
- package/src/dia/ElementView.mjs +2 -1
- package/src/dia/HighlighterView.mjs +22 -0
- package/src/dia/LinkView.mjs +118 -98
- package/src/dia/Paper.mjs +697 -209
- package/src/dia/ToolView.mjs +4 -0
- package/src/dia/ToolsView.mjs +12 -3
- package/src/dia/attributes/text.mjs +4 -2
- package/src/dia/ports.mjs +202 -82
- package/src/elementTools/HoverConnect.mjs +14 -8
- package/src/env/index.mjs +6 -3
- package/src/layout/ports/port.mjs +30 -15
- package/src/layout/ports/portLabel.mjs +1 -1
- package/src/mvc/View.mjs +4 -0
- package/src/mvc/ViewBase.mjs +1 -1
- package/types/geometry.d.ts +64 -60
- package/types/joint.d.ts +205 -88
- package/types/vectorizer.d.ts +11 -1
package/src/V/index.mjs
CHANGED
|
@@ -118,8 +118,11 @@ const V = (function() {
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
121
|
+
* Calculates the transformation matrix from this element to the target element.
|
|
122
|
+
* @param {SVGElement|V} target - The target element.
|
|
123
|
+
* @param {Object} [opt] - Options object for transformation calculation.
|
|
124
|
+
* @param {boolean} [opt.safe] - Use a safe traversal method to compute the matrix.
|
|
125
|
+
* @returns {DOMMatrix} The transformation matrix from this element to the target element.
|
|
123
126
|
*/
|
|
124
127
|
VPrototype.getTransformToElement = function(target, opt) {
|
|
125
128
|
const node = this.node;
|
|
@@ -485,10 +488,19 @@ const V = (function() {
|
|
|
485
488
|
|
|
486
489
|
if (content && typeof content !== 'string') throw new Error('Vectorizer: text() expects the first argument to be a string.');
|
|
487
490
|
|
|
488
|
-
// Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
|
|
489
|
-
// IE would otherwise collapse all spaces into one.
|
|
490
|
-
content = V.sanitizeText(content);
|
|
491
491
|
opt || (opt = {});
|
|
492
|
+
|
|
493
|
+
// Backwards-compatibility: if no content was provided, treat it as an
|
|
494
|
+
// empty string so that subsequent string operations (e.g. split) do
|
|
495
|
+
// not throw and behaviour matches the previous implementation that
|
|
496
|
+
// always sanitised the input.
|
|
497
|
+
if (content == null) content = '';
|
|
498
|
+
|
|
499
|
+
if (opt.useNoBreakSpace) {
|
|
500
|
+
// Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
|
|
501
|
+
// IE would otherwise collapse all spaces into one.
|
|
502
|
+
content = V.sanitizeText(content);
|
|
503
|
+
}
|
|
492
504
|
// Should we allow the text to be selected?
|
|
493
505
|
var displayEmpty = opt.displayEmpty;
|
|
494
506
|
// End of Line character
|
|
@@ -1332,6 +1344,9 @@ const V = (function() {
|
|
|
1332
1344
|
// also exposed so that the programmer can use it in case he needs to. This is useful e.g. in tests
|
|
1333
1345
|
// when you want to compare the actual DOM text content without having to add the unicode character in
|
|
1334
1346
|
// the place of all spaces.
|
|
1347
|
+
/**
|
|
1348
|
+
* @deprecated Use regular spaces and rely on xml:space="preserve" instead.
|
|
1349
|
+
*/
|
|
1335
1350
|
V.sanitizeText = function(text) {
|
|
1336
1351
|
|
|
1337
1352
|
return (text || '').replace(/ /g, '\u00A0');
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deque implementation for managing a double-ended queue.
|
|
3
|
+
* This implementation uses a doubly linked list for efficient operations.
|
|
4
|
+
* It supports operations like push, pop, move to head, and delete.
|
|
5
|
+
* The deque maintains a map for O(1) access to nodes by key.
|
|
6
|
+
*/
|
|
7
|
+
export class Deque {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.head = null;
|
|
10
|
+
this.tail = null;
|
|
11
|
+
this.map = new Map(); // key -> node
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Return an array of keys in the deque
|
|
15
|
+
keys() {
|
|
16
|
+
let current = this.head;
|
|
17
|
+
const keys = [];
|
|
18
|
+
while (current) {
|
|
19
|
+
keys.push(current.key);
|
|
20
|
+
current = current.next;
|
|
21
|
+
}
|
|
22
|
+
return keys;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Return the first node and remove it from the deque
|
|
26
|
+
popHead() {
|
|
27
|
+
if (!this.head) return null;
|
|
28
|
+
const node = this.head;
|
|
29
|
+
this.map.delete(node.key);
|
|
30
|
+
this.head = node.next;
|
|
31
|
+
if (this.head) {
|
|
32
|
+
this.head.prev = null;
|
|
33
|
+
} else {
|
|
34
|
+
this.tail = null;
|
|
35
|
+
}
|
|
36
|
+
return node;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add a new node to the back of the deque
|
|
40
|
+
pushTail(key, value) {
|
|
41
|
+
if (this.map.has(key)) {
|
|
42
|
+
throw new Error(`Key "${key}" already exists in the deque.`);
|
|
43
|
+
}
|
|
44
|
+
const node = {
|
|
45
|
+
key,
|
|
46
|
+
value,
|
|
47
|
+
prev: null,
|
|
48
|
+
next: null
|
|
49
|
+
};
|
|
50
|
+
this.map.set(key, node);
|
|
51
|
+
if (!this.tail) {
|
|
52
|
+
this.head = this.tail = node;
|
|
53
|
+
} else {
|
|
54
|
+
this.tail.next = node;
|
|
55
|
+
node.prev = this.tail;
|
|
56
|
+
this.tail = node;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Move a node from the deque to the head
|
|
61
|
+
moveToHead(key) {
|
|
62
|
+
const node = this.map.get(key);
|
|
63
|
+
if (!node) return;
|
|
64
|
+
if (node === this.head) return; // already at head
|
|
65
|
+
// Remove node from its current position
|
|
66
|
+
if (node.prev) node.prev.next = node.next;
|
|
67
|
+
if (node.next) node.next.prev = node.prev;
|
|
68
|
+
if (node === this.tail) this.tail = node.prev; // if it's the tail
|
|
69
|
+
if (node === this.head) this.head = node.next; // if it's the head
|
|
70
|
+
// Move node to head
|
|
71
|
+
node.prev = null;
|
|
72
|
+
node.next = this.head;
|
|
73
|
+
if (this.head) {
|
|
74
|
+
this.head.prev = node; // link old head back to new head
|
|
75
|
+
}
|
|
76
|
+
this.head = node; // update head to be the moved node
|
|
77
|
+
if (!this.tail) {
|
|
78
|
+
this.tail = node; // if it was the only node, set tail as well
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Return the first node without removing it
|
|
83
|
+
peekHead() {
|
|
84
|
+
return this.head || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Move the head node to the back of the deque
|
|
88
|
+
rotate() {
|
|
89
|
+
if (!this.head || !this.head.next) return;
|
|
90
|
+
this.tail.next = this.head; // link tail to head
|
|
91
|
+
this.head.prev = this.tail; // link head back to tail
|
|
92
|
+
this.tail = this.head; // update tail to be the old head
|
|
93
|
+
this.head = this.head.next; // move head to the next node
|
|
94
|
+
this.tail.next = null; // set new tail's next to null
|
|
95
|
+
this.head.prev = null; // set new head's prev to null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Remove a node from the deque
|
|
99
|
+
delete(key) {
|
|
100
|
+
const node = this.map.get(key);
|
|
101
|
+
if (!node) return;
|
|
102
|
+
|
|
103
|
+
if (node.prev) node.prev.next = node.next;
|
|
104
|
+
else this.head = node.next;
|
|
105
|
+
|
|
106
|
+
if (node.next) node.next.prev = node.prev;
|
|
107
|
+
else this.tail = node.prev;
|
|
108
|
+
|
|
109
|
+
this.map.delete(key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Does the deque contain a node with the given key?
|
|
113
|
+
has(key) {
|
|
114
|
+
return this.map.has(key);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get the node with the given key
|
|
118
|
+
get(key) {
|
|
119
|
+
return this.map.get(key) || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Number of nodes in the deque
|
|
123
|
+
get length() {
|
|
124
|
+
return this.map.size;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import * as util from '../util/index.mjs';
|
|
3
3
|
import { ToolView } from '../dia/ToolView.mjs';
|
|
4
|
-
import { getViewBBox } from './helpers.mjs';
|
|
4
|
+
import { getToolOptions, getViewBBox } from './helpers.mjs';
|
|
5
5
|
|
|
6
6
|
export const Boundary = ToolView.extend({
|
|
7
7
|
name: 'boundary',
|
|
@@ -21,21 +21,23 @@ export const Boundary = ToolView.extend({
|
|
|
21
21
|
this.update();
|
|
22
22
|
},
|
|
23
23
|
update: function() {
|
|
24
|
-
const { relatedView: view,
|
|
25
|
-
const { useModelGeometry, rotate } =
|
|
26
|
-
const
|
|
27
|
-
let bbox = getViewBBox(view, useModelGeometry).moveAndExpand({
|
|
28
|
-
x: -
|
|
29
|
-
y: -
|
|
30
|
-
width:
|
|
31
|
-
height:
|
|
24
|
+
const { relatedView: view, vel } = this;
|
|
25
|
+
const { useModelGeometry, rotate, relative, padding } = getToolOptions(this);
|
|
26
|
+
const normalizedPadding = util.normalizeSides(padding);
|
|
27
|
+
let bbox = getViewBBox(view, { useModelGeometry, relative }).moveAndExpand({
|
|
28
|
+
x: -normalizedPadding.left,
|
|
29
|
+
y: -normalizedPadding.top,
|
|
30
|
+
width: normalizedPadding.left + normalizedPadding.right,
|
|
31
|
+
height: normalizedPadding.top + normalizedPadding.bottom
|
|
32
32
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const model = view.model;
|
|
34
|
+
// With relative positioning, rotation is implicit
|
|
35
|
+
// (the tool rotates along with the element).
|
|
36
|
+
if (model.isElement() && !relative) {
|
|
37
|
+
const angle = model.angle();
|
|
36
38
|
if (angle) {
|
|
37
39
|
if (rotate) {
|
|
38
|
-
|
|
40
|
+
const origin = model.getCenter();
|
|
39
41
|
vel.rotate(angle, origin.x, origin.y, { absolute: true });
|
|
40
42
|
} else {
|
|
41
43
|
bbox = bbox.bbox(angle);
|
package/src/cellTools/Button.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ToolView } from '../dia/ToolView.mjs';
|
|
2
|
-
import { getViewBBox } from './helpers.mjs';
|
|
2
|
+
import { getToolOptions, getViewBBox } from './helpers.mjs';
|
|
3
3
|
import * as util from '../util/index.mjs';
|
|
4
4
|
import * as g from '../g/index.mjs';
|
|
5
5
|
import V from '../V/index.mjs';
|
|
@@ -32,9 +32,9 @@ export const Button = ToolView.extend({
|
|
|
32
32
|
return this.relatedView.model.isLink() ? this.getLinkMatrix() : this.getElementMatrix();
|
|
33
33
|
},
|
|
34
34
|
getElementMatrix() {
|
|
35
|
-
const { relatedView: view
|
|
36
|
-
let { x = 0, y = 0, offset = {}, useModelGeometry, rotate, scale } =
|
|
37
|
-
let bbox = getViewBBox(view, useModelGeometry);
|
|
35
|
+
const { relatedView: view } = this;
|
|
36
|
+
let { x = 0, y = 0, offset = {}, useModelGeometry, rotate, scale, relative } = getToolOptions(this);
|
|
37
|
+
let bbox = getViewBBox(view, { useModelGeometry, relative });
|
|
38
38
|
const angle = view.model.angle();
|
|
39
39
|
if (!rotate) bbox = bbox.bbox(angle);
|
|
40
40
|
const { x: offsetX = 0, y: offsetY = 0 } = offset;
|
|
@@ -49,7 +49,9 @@ export const Button = ToolView.extend({
|
|
|
49
49
|
y = Number(util.evalCalcExpression(y, bbox));
|
|
50
50
|
}
|
|
51
51
|
let matrix = V.createSVGMatrix().translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
|
|
52
|
-
|
|
52
|
+
// With relative positioning, rotation is implicit
|
|
53
|
+
// (the tool rotates along with the element).
|
|
54
|
+
if (rotate && !relative) matrix = matrix.rotate(angle);
|
|
53
55
|
matrix = matrix.translate(x + offsetX - bbox.width / 2, y + offsetY - bbox.height / 2);
|
|
54
56
|
if (scale) matrix = matrix.scale(scale);
|
|
55
57
|
return matrix;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ToolView } from '../dia/ToolView.mjs';
|
|
2
2
|
import * as util from '../util/index.mjs';
|
|
3
|
+
import { getToolOptions, getViewBBox } from './helpers.mjs';
|
|
3
4
|
|
|
4
5
|
export const Control = ToolView.extend({
|
|
5
6
|
tagName: 'g',
|
|
@@ -72,36 +73,58 @@ export const Control = ToolView.extend({
|
|
|
72
73
|
return this;
|
|
73
74
|
},
|
|
74
75
|
updateHandle: function(handleNode) {
|
|
76
|
+
const { options: { handleAttributes }} = this;
|
|
77
|
+
handleNode.setAttribute('transform', this.getHandleTransformString());
|
|
78
|
+
if (handleAttributes) {
|
|
79
|
+
for (let attrName in handleAttributes) {
|
|
80
|
+
handleNode.setAttribute(attrName, handleAttributes[attrName]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
getHandleTransformString() {
|
|
75
85
|
const { relatedView, options } = this;
|
|
86
|
+
const { scale } = options;
|
|
76
87
|
const { model } = relatedView;
|
|
77
88
|
const relativePos = this.getPosition(relatedView, this);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
const translate = this.isOverlay()
|
|
90
|
+
// The tool is rendered in the coordinate system of the paper
|
|
91
|
+
? model.getAbsolutePointFromRelative(relativePos)
|
|
92
|
+
// The tool is rendered in the coordinate system of the relatedView
|
|
93
|
+
: relativePos;
|
|
94
|
+
let transformString = `translate(${translate.x},${translate.y})`;
|
|
81
95
|
if (scale) {
|
|
82
96
|
transformString += ` scale(${scale})`;
|
|
83
97
|
}
|
|
84
|
-
|
|
85
|
-
if (handleAttributes) {
|
|
86
|
-
for (let attrName in handleAttributes) {
|
|
87
|
-
handleNode.setAttribute(attrName, handleAttributes[attrName]);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
98
|
+
return transformString;
|
|
90
99
|
},
|
|
91
100
|
updateExtras: function(extrasNode) {
|
|
92
101
|
const { relatedView, options } = this;
|
|
93
|
-
const { selector } = this
|
|
102
|
+
const { selector, relative, useModelGeometry } = getToolOptions(this);
|
|
94
103
|
if (!selector) {
|
|
104
|
+
// Hide the extras if no selector is given.
|
|
95
105
|
this.toggleExtras(false);
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
// Get the size for the extras rectangle and update it.
|
|
109
|
+
let bbox;
|
|
110
|
+
if (useModelGeometry) {
|
|
111
|
+
if (selector !== 'root') {
|
|
112
|
+
// A selector other than null or `root` was provided.
|
|
113
|
+
console.warn('Control: selector will be ignored when `useModelGeometry` is used.');
|
|
114
|
+
}
|
|
115
|
+
bbox = getViewBBox(relatedView, { useModelGeometry, relative });
|
|
116
|
+
} else {
|
|
117
|
+
// The reference node for calculating the bounding box of the extras.
|
|
118
|
+
const el = relatedView.findNode(selector);
|
|
119
|
+
if (!el) throw new Error('Control: invalid selector.');
|
|
120
|
+
bbox = getViewBBox(relatedView, { el });
|
|
121
|
+
}
|
|
100
122
|
let padding = options.padding;
|
|
101
123
|
if (!isFinite(padding)) padding = 0;
|
|
102
|
-
const bbox = relatedView.getNodeUnrotatedBBox(magnet);
|
|
103
124
|
const model = relatedView.model;
|
|
104
|
-
|
|
125
|
+
// With relative positioning, rotation is implicit
|
|
126
|
+
// (the tool rotates along with the element).
|
|
127
|
+
const angle = relative ? 0 : model.angle();
|
|
105
128
|
const center = bbox.center();
|
|
106
129
|
if (angle) center.rotate(model.getCenter(), -angle);
|
|
107
130
|
bbox.inflate(padding);
|
|
@@ -121,10 +121,14 @@ export const HoverConnect = Connect.extend({
|
|
|
121
121
|
return V.createSVGMatrix();
|
|
122
122
|
},
|
|
123
123
|
|
|
124
|
+
getTrackMatrixAbsolute() {
|
|
125
|
+
return this.getTrackMatrix();
|
|
126
|
+
},
|
|
127
|
+
|
|
124
128
|
getTrackRatioFromEvent(evt) {
|
|
125
129
|
const { relatedView, trackPath } = this;
|
|
126
130
|
const localPoint = relatedView.paper.clientToLocalPoint(evt.clientX, evt.clientY);
|
|
127
|
-
const trackPoint = V.transformPoint(localPoint, this.
|
|
131
|
+
const trackPoint = V.transformPoint(localPoint, this.getTrackMatrixAbsolute().inverse());
|
|
128
132
|
return trackPath.closestPointLength(trackPoint);
|
|
129
133
|
},
|
|
130
134
|
|
|
@@ -1,9 +1,50 @@
|
|
|
1
1
|
import * as connectionStrategies from '../connectionStrategies/index.mjs';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Common helper for getting a cell view’s bounding box,
|
|
5
|
+
* configurable with `useModelGeometry`, `relative`, and `el`.
|
|
6
|
+
*/
|
|
7
|
+
export function getViewBBox(view, {
|
|
8
|
+
useModelGeometry = false,
|
|
9
|
+
relative = false,
|
|
10
|
+
el = view.el
|
|
11
|
+
} = {}) {
|
|
4
12
|
const { model } = view;
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
let bbox;
|
|
14
|
+
if (useModelGeometry) {
|
|
15
|
+
// cell model bbox
|
|
16
|
+
bbox = model.getBBox();
|
|
17
|
+
} else if (model.isLink()) {
|
|
18
|
+
// link view bbox
|
|
19
|
+
bbox = view.getConnection().bbox();
|
|
20
|
+
} else {
|
|
21
|
+
// element view bbox
|
|
22
|
+
bbox = view.getNodeUnrotatedBBox(el);
|
|
23
|
+
}
|
|
24
|
+
if (relative) {
|
|
25
|
+
// Relative to the element position.
|
|
26
|
+
const position = model.position();
|
|
27
|
+
bbox.x -= position.x;
|
|
28
|
+
bbox.y -= position.y;
|
|
29
|
+
}
|
|
30
|
+
return bbox;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves the tool options.
|
|
35
|
+
* Automatically overrides `useModelGeometry` and `rotate`
|
|
36
|
+
* if the tool is positioned relative to the element.
|
|
37
|
+
*/
|
|
38
|
+
export function getToolOptions(toolView) {
|
|
39
|
+
// Positioning is relative if the tool is drawn within the element view.
|
|
40
|
+
const relative = !toolView.isOverlay();
|
|
41
|
+
const { useModelGeometry, rotate, ...otherOptions } = toolView.options;
|
|
42
|
+
return {
|
|
43
|
+
...otherOptions,
|
|
44
|
+
useModelGeometry: useModelGeometry || relative,
|
|
45
|
+
rotate: rotate || relative,
|
|
46
|
+
relative,
|
|
47
|
+
};
|
|
7
48
|
}
|
|
8
49
|
|
|
9
50
|
export function getAnchor(coords, view, magnet) {
|
package/src/config/index.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export const config = {
|
|
2
|
+
// How the cell attributes are merged when `cell.prop()` is called.
|
|
3
|
+
// DEFAULT: the arrays are merged into the source array.
|
|
4
|
+
cellMergeStrategy: null,
|
|
5
|
+
// How the cell default attributes are merged with the attributes provided
|
|
6
|
+
// in the cell constructor.
|
|
7
|
+
// DEFAULT: the arrays are merged by replacing the source array
|
|
8
|
+
// with the destination array.
|
|
9
|
+
cellDefaultsMergeStrategy: null,
|
|
2
10
|
// When set to `true` the cell selectors could be defined as CSS selectors.
|
|
3
11
|
// If not, only JSON Markup selectors are taken into account.
|
|
4
12
|
useCSSSelectors: false,
|
package/src/dia/Cell.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import { Model } from '../mvc/Model.mjs';
|
|
|
32
32
|
import { cloneCells } from '../util/cloneCells.mjs';
|
|
33
33
|
import { attributes } from './attributes/index.mjs';
|
|
34
34
|
import * as g from '../g/index.mjs';
|
|
35
|
-
|
|
35
|
+
import { config } from '../config/index.mjs';
|
|
36
36
|
|
|
37
37
|
// Cell base model.
|
|
38
38
|
// --------------------------
|
|
@@ -78,12 +78,20 @@ export const Cell = Model.extend({
|
|
|
78
78
|
if ((defaults = result(this, 'defaults'))) {
|
|
79
79
|
//<custom code>
|
|
80
80
|
// Replaced the call to _.defaults with util.merge.
|
|
81
|
-
const customizer = (options && options.mergeArrays === true)
|
|
81
|
+
const customizer = (options && options.mergeArrays === true)
|
|
82
|
+
? false
|
|
83
|
+
: config.cellDefaultsMergeStrategy || attributesMerger;
|
|
82
84
|
attrs = merge({}, defaults, attrs, customizer);
|
|
83
85
|
//</custom code>
|
|
84
86
|
}
|
|
85
87
|
this.set(attrs, options);
|
|
86
88
|
this.changed = {};
|
|
89
|
+
if (options && options.portLayoutNamespace) {
|
|
90
|
+
this.portLayoutNamespace = options.portLayoutNamespace;
|
|
91
|
+
}
|
|
92
|
+
if (options && options.portLabelLayoutNamespace) {
|
|
93
|
+
this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
|
|
94
|
+
}
|
|
87
95
|
this.initialize.apply(this, arguments);
|
|
88
96
|
},
|
|
89
97
|
|
|
@@ -543,7 +551,11 @@ export const Cell = Model.extend({
|
|
|
543
551
|
if (!opt.deep) {
|
|
544
552
|
// Shallow cloning.
|
|
545
553
|
|
|
546
|
-
|
|
554
|
+
// Preserve the original's `portLayoutNamespace` and `portLabelLayoutNamespace`.
|
|
555
|
+
const clone = new this.constructor(this.attributes, {
|
|
556
|
+
portLayoutNamespace: this.portLayoutNamespace,
|
|
557
|
+
portLabelLayoutNamespace: this.portLabelLayoutNamespace
|
|
558
|
+
});
|
|
547
559
|
// We don't want the clone to have the same ID as the original.
|
|
548
560
|
clone.set(this.getIdAttribute(), this.generateId());
|
|
549
561
|
// A shallow cloned element does not carry over the original embeds.
|
|
@@ -557,7 +569,7 @@ export const Cell = Model.extend({
|
|
|
557
569
|
} else {
|
|
558
570
|
// Deep cloning.
|
|
559
571
|
|
|
560
|
-
// For a deep clone, simply call `
|
|
572
|
+
// For a deep clone, simply call `util.cloneCells()` with the cell and all its embedded cells.
|
|
561
573
|
return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true }))));
|
|
562
574
|
}
|
|
563
575
|
},
|
|
@@ -630,7 +642,7 @@ export const Cell = Model.extend({
|
|
|
630
642
|
options.rewrite && unsetByPath(baseAttributes, path, '/');
|
|
631
643
|
|
|
632
644
|
// Merge update with the model attributes.
|
|
633
|
-
var attributes = merge(baseAttributes, update);
|
|
645
|
+
var attributes = merge(baseAttributes, update, config.cellMergeStrategy);
|
|
634
646
|
// Finally, set the property to the updated attributes.
|
|
635
647
|
return this.set(property, attributes[property], options);
|
|
636
648
|
|
|
@@ -653,7 +665,11 @@ export const Cell = Model.extend({
|
|
|
653
665
|
const changedAttributes = {};
|
|
654
666
|
for (const key in props) {
|
|
655
667
|
// Merging the values of changed attributes with the current ones.
|
|
656
|
-
const { changedValue } = merge(
|
|
668
|
+
const { changedValue } = merge(
|
|
669
|
+
merge({}, { changedValue: this.attributes[key] }),
|
|
670
|
+
{ changedValue: props[key] },
|
|
671
|
+
config.cellMergeStrategy
|
|
672
|
+
);
|
|
657
673
|
changedAttributes[key] = changedValue;
|
|
658
674
|
}
|
|
659
675
|
|
|
@@ -952,9 +968,10 @@ export const Cell = Model.extend({
|
|
|
952
968
|
|
|
953
969
|
getAttributeDefinition: function(attrName) {
|
|
954
970
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
971
|
+
const defNS = this.attributes;
|
|
972
|
+
const globalDefNS = attributes;
|
|
973
|
+
const definition = (defNS && defNS[attrName]) || globalDefNS[attrName];
|
|
974
|
+
return definition !== undefined ? definition : null;
|
|
958
975
|
},
|
|
959
976
|
|
|
960
977
|
define: function(type, defaults, protoProps, staticProps) {
|
|
@@ -973,4 +990,3 @@ export const Cell = Model.extend({
|
|
|
973
990
|
return Cell;
|
|
974
991
|
}
|
|
975
992
|
});
|
|
976
|
-
|
package/src/dia/CellView.mjs
CHANGED
|
@@ -1393,4 +1393,11 @@ Object.defineProperty(CellView.prototype, 'useCSSSelectors', {
|
|
|
1393
1393
|
}
|
|
1394
1394
|
});
|
|
1395
1395
|
|
|
1396
|
+
// Internal tag to identify this object as a cell view instance.
|
|
1397
|
+
// Used instead of `instanceof` for performance and cross-frame safety.
|
|
1396
1398
|
|
|
1399
|
+
export const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
|
|
1400
|
+
|
|
1401
|
+
Object.defineProperty(CellView.prototype, CELL_VIEW_MARKER, {
|
|
1402
|
+
value: true,
|
|
1403
|
+
});
|
package/src/dia/Element.mjs
CHANGED
|
@@ -421,8 +421,10 @@ export const Element = Cell.extend({
|
|
|
421
421
|
|
|
422
422
|
let minBBox = null;
|
|
423
423
|
if (opt.minRect) {
|
|
424
|
-
// Coerce `opt.minRect` to g.Rect
|
|
425
|
-
|
|
424
|
+
// Coerce `opt.minRect` to g.Rect
|
|
425
|
+
// (missing properties are taken from this element's current bbox).
|
|
426
|
+
const minRect = assign(this.getBBox(), opt.minRect);
|
|
427
|
+
minBBox = new Rect(minRect);
|
|
426
428
|
}
|
|
427
429
|
|
|
428
430
|
const elementsBBox = this.graph.getCellsBBox(opt.elements);
|
package/src/dia/ElementView.mjs
CHANGED
|
@@ -389,7 +389,8 @@ export const ElementView = CellView.extend({
|
|
|
389
389
|
parent.unembed(element, { ui: true });
|
|
390
390
|
data.initialParentId = parentId;
|
|
391
391
|
} else {
|
|
392
|
-
data.initialParentId
|
|
392
|
+
// `data.initialParentId` can be explicitly set to a dummy value to enable validation of unembedding.
|
|
393
|
+
data.initialParentId = data.initialParentId || null;
|
|
393
394
|
}
|
|
394
395
|
},
|
|
395
396
|
|
|
@@ -262,6 +262,28 @@ export const HighlighterView = mvc.View.extend({
|
|
|
262
262
|
}
|
|
263
263
|
},
|
|
264
264
|
|
|
265
|
+
// Check if the cellView has a highlighter with the given `id`.
|
|
266
|
+
// If no `id` is provided, it checks if the cellView has any highlighter.
|
|
267
|
+
has(cellView, id = null) {
|
|
268
|
+
const { cid } = cellView;
|
|
269
|
+
const { _views } = this;
|
|
270
|
+
const refs = _views[cid];
|
|
271
|
+
if (!refs) return false;
|
|
272
|
+
if (id === null) {
|
|
273
|
+
// any highlighter
|
|
274
|
+
for (let hid in refs) {
|
|
275
|
+
if (refs[hid] instanceof this) return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
} else {
|
|
279
|
+
// single highlighter
|
|
280
|
+
if (id in refs) {
|
|
281
|
+
if (refs[id] instanceof this) return true;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
265
287
|
add(cellView, nodeSelector, id, opt = {}) {
|
|
266
288
|
if (!id) throw new Error('dia.HighlighterView: An ID required.');
|
|
267
289
|
// Search the existing view amongst all the highlighters
|