@joint/core 4.0.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/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- package/types/vectorizer.d.ts +327 -0
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
import { config } from '../config/index.mjs';
|
|
2
|
+
import { View } from '../mvc/index.mjs';
|
|
3
|
+
import {
|
|
4
|
+
assign,
|
|
5
|
+
guid,
|
|
6
|
+
omit,
|
|
7
|
+
parseDOMJSON,
|
|
8
|
+
isFunction,
|
|
9
|
+
isObject,
|
|
10
|
+
isPlainObject,
|
|
11
|
+
isBoolean,
|
|
12
|
+
isEmpty,
|
|
13
|
+
isString,
|
|
14
|
+
result,
|
|
15
|
+
sortedIndex,
|
|
16
|
+
merge,
|
|
17
|
+
uniq
|
|
18
|
+
} from '../util/index.mjs';
|
|
19
|
+
import { Point, Rect } from '../g/index.mjs';
|
|
20
|
+
import V from '../V/index.mjs';
|
|
21
|
+
import $ from '../mvc/Dom/index.mjs';
|
|
22
|
+
import { HighlighterView } from './HighlighterView.mjs';
|
|
23
|
+
import { evalAttributes, evalAttribute } from './attributes/eval.mjs';
|
|
24
|
+
|
|
25
|
+
const HighlightingTypes = {
|
|
26
|
+
DEFAULT: 'default',
|
|
27
|
+
EMBEDDING: 'embedding',
|
|
28
|
+
CONNECTING: 'connecting',
|
|
29
|
+
MAGNET_AVAILABILITY: 'magnetAvailability',
|
|
30
|
+
ELEMENT_AVAILABILITY: 'elementAvailability'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const Flags = {
|
|
34
|
+
TOOLS: 'TOOLS',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// CellView base view and controller.
|
|
38
|
+
// --------------------------------------------
|
|
39
|
+
|
|
40
|
+
// This is the base view and controller for `ElementView` and `LinkView`.
|
|
41
|
+
export const CellView = View.extend({
|
|
42
|
+
|
|
43
|
+
tagName: 'g',
|
|
44
|
+
|
|
45
|
+
svgElement: true,
|
|
46
|
+
|
|
47
|
+
selector: 'root',
|
|
48
|
+
|
|
49
|
+
metrics: null,
|
|
50
|
+
|
|
51
|
+
className: function() {
|
|
52
|
+
|
|
53
|
+
var classNames = ['cell'];
|
|
54
|
+
var type = this.model.get('type');
|
|
55
|
+
|
|
56
|
+
if (type) {
|
|
57
|
+
|
|
58
|
+
type.toLowerCase().split('.').forEach(function(value, index, list) {
|
|
59
|
+
classNames.push('type-' + list.slice(0, index + 1).join('-'));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return classNames.join(' ');
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
_presentationAttributes: null,
|
|
67
|
+
_flags: null,
|
|
68
|
+
|
|
69
|
+
setFlags: function() {
|
|
70
|
+
var flags = {};
|
|
71
|
+
var attributes = {};
|
|
72
|
+
var shift = 0;
|
|
73
|
+
var i, n, label;
|
|
74
|
+
var presentationAttributes = result(this, 'presentationAttributes');
|
|
75
|
+
for (var attribute in presentationAttributes) {
|
|
76
|
+
if (!presentationAttributes.hasOwnProperty(attribute)) continue;
|
|
77
|
+
var labels = presentationAttributes[attribute];
|
|
78
|
+
if (!Array.isArray(labels)) labels = [labels];
|
|
79
|
+
for (i = 0, n = labels.length; i < n; i++) {
|
|
80
|
+
label = labels[i];
|
|
81
|
+
var flag = flags[label];
|
|
82
|
+
if (!flag) {
|
|
83
|
+
flag = flags[label] = 1<<(shift++);
|
|
84
|
+
}
|
|
85
|
+
attributes[attribute] |= flag;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var initFlag = result(this, 'initFlag');
|
|
89
|
+
if (!Array.isArray(initFlag)) initFlag = [initFlag];
|
|
90
|
+
for (i = 0, n = initFlag.length; i < n; i++) {
|
|
91
|
+
label = initFlag[i];
|
|
92
|
+
if (!flags[label]) flags[label] = 1<<(shift++);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 26 - 30 are reserved for paper flags
|
|
96
|
+
// 31+ overflows maximal number
|
|
97
|
+
if (shift > 25) throw new Error('dia.CellView: Maximum number of flags exceeded.');
|
|
98
|
+
|
|
99
|
+
this._flags = flags;
|
|
100
|
+
this._presentationAttributes = attributes;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
hasFlag: function(flag, label) {
|
|
104
|
+
return flag & this.getFlag(label);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
removeFlag: function(flag, label) {
|
|
108
|
+
return flag ^ (flag & this.getFlag(label));
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
getFlag: function(label) {
|
|
112
|
+
var flags = this._flags;
|
|
113
|
+
if (!flags) return 0;
|
|
114
|
+
var flag = 0;
|
|
115
|
+
if (Array.isArray(label)) {
|
|
116
|
+
for (var i = 0, n = label.length; i < n; i++) flag |= flags[label[i]];
|
|
117
|
+
} else {
|
|
118
|
+
flag |= flags[label];
|
|
119
|
+
}
|
|
120
|
+
return flag;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
attributes: function() {
|
|
124
|
+
var cell = this.model;
|
|
125
|
+
return {
|
|
126
|
+
'model-id': cell.id,
|
|
127
|
+
'data-type': cell.attributes.type
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
constructor: function(options) {
|
|
132
|
+
|
|
133
|
+
// Make sure a global unique id is assigned to this view. Store this id also to the properties object.
|
|
134
|
+
// The global unique id makes sure that the same view can be rendered on e.g. different machines and
|
|
135
|
+
// still be associated to the same object among all those clients. This is necessary for real-time
|
|
136
|
+
// collaboration mechanism.
|
|
137
|
+
options.id = options.id || guid(this);
|
|
138
|
+
|
|
139
|
+
View.call(this, options);
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
initialize: function() {
|
|
143
|
+
|
|
144
|
+
this.setFlags();
|
|
145
|
+
|
|
146
|
+
View.prototype.initialize.apply(this, arguments);
|
|
147
|
+
|
|
148
|
+
this.cleanNodesCache();
|
|
149
|
+
|
|
150
|
+
this.startListening();
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
startListening: function() {
|
|
154
|
+
this.listenTo(this.model, 'change', this.onAttributesChange);
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
onAttributesChange: function(model, opt) {
|
|
158
|
+
var flag = model.getChangeFlag(this._presentationAttributes);
|
|
159
|
+
if (opt.updateHandled || !flag) return;
|
|
160
|
+
if (opt.dirty && this.hasFlag(flag, 'UPDATE')) flag |= this.getFlag('RENDER');
|
|
161
|
+
// TODO: tool changes does not need to be sync
|
|
162
|
+
// Fix Segments tools
|
|
163
|
+
if (opt.tool) opt.async = false;
|
|
164
|
+
this.requestUpdate(flag, opt);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
requestUpdate: function(flags, opt) {
|
|
168
|
+
const { paper } = this;
|
|
169
|
+
if (paper && flags > 0) {
|
|
170
|
+
paper.requestViewUpdate(this, flags, this.UPDATE_PRIORITY, opt);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
parseDOMJSON: function(markup, root) {
|
|
175
|
+
|
|
176
|
+
var doc = parseDOMJSON(markup);
|
|
177
|
+
var selectors = doc.selectors;
|
|
178
|
+
var groups = doc.groupSelectors;
|
|
179
|
+
for (var group in groups) {
|
|
180
|
+
if (selectors[group]) throw new Error('dia.CellView: ambiguous group selector');
|
|
181
|
+
selectors[group] = groups[group];
|
|
182
|
+
}
|
|
183
|
+
if (root) {
|
|
184
|
+
var rootSelector = this.selector;
|
|
185
|
+
if (selectors[rootSelector]) throw new Error('dia.CellView: ambiguous root selector.');
|
|
186
|
+
selectors[rootSelector] = root;
|
|
187
|
+
}
|
|
188
|
+
return { fragment: doc.fragment, selectors: selectors };
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Return `true` if cell link is allowed to perform a certain UI `feature`.
|
|
192
|
+
// Example: `can('labelMove')`.
|
|
193
|
+
can: function(feature) {
|
|
194
|
+
|
|
195
|
+
var interactive = isFunction(this.options.interactive)
|
|
196
|
+
? this.options.interactive(this)
|
|
197
|
+
: this.options.interactive;
|
|
198
|
+
|
|
199
|
+
return (isObject(interactive) && interactive[feature] !== false) ||
|
|
200
|
+
(isBoolean(interactive) && interactive !== false);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
findBySelector: function(selector, root, selectors) {
|
|
204
|
+
|
|
205
|
+
// These are either descendants of `this.$el` of `this.$el` itself.
|
|
206
|
+
// `.` is a special selector used to select the wrapping `<g>` element.
|
|
207
|
+
if (!selector || selector === '.') return [root];
|
|
208
|
+
if (selectors) {
|
|
209
|
+
var nodes = selectors[selector];
|
|
210
|
+
if (nodes) {
|
|
211
|
+
if (Array.isArray(nodes)) return nodes;
|
|
212
|
+
return [nodes];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Maintaining backwards compatibility
|
|
217
|
+
// e.g. `circle:first` would fail with querySelector() call
|
|
218
|
+
if (this.useCSSSelectors) return $(root).find(selector).toArray();
|
|
219
|
+
|
|
220
|
+
return [];
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
findNodes: function(selector) {
|
|
224
|
+
return this.findBySelector(selector, this.el, this.selectors);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
findNode: function(selector) {
|
|
228
|
+
const [node = null] = this.findNodes(selector);
|
|
229
|
+
return node;
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
notify: function(eventName) {
|
|
233
|
+
|
|
234
|
+
if (this.paper) {
|
|
235
|
+
|
|
236
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
|
237
|
+
|
|
238
|
+
// Trigger the event on both the element itself and also on the paper.
|
|
239
|
+
this.trigger.apply(this, [eventName].concat(args));
|
|
240
|
+
|
|
241
|
+
// Paper event handlers receive the view object as the first argument.
|
|
242
|
+
this.paper.trigger.apply(this.paper, [eventName, this].concat(args));
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
getBBox: function(opt) {
|
|
247
|
+
|
|
248
|
+
var bbox;
|
|
249
|
+
if (opt && opt.useModelGeometry) {
|
|
250
|
+
var model = this.model;
|
|
251
|
+
bbox = model.getBBox().bbox(model.angle());
|
|
252
|
+
} else {
|
|
253
|
+
bbox = this.getNodeBBox(this.el);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return this.paper.localToPaperRect(bbox);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
getNodeBBox: function(magnet) {
|
|
260
|
+
|
|
261
|
+
const rect = this.getNodeBoundingRect(magnet);
|
|
262
|
+
const transformMatrix = this.getRootTranslateMatrix().multiply(this.getNodeRotateMatrix(magnet));
|
|
263
|
+
const magnetMatrix = this.getNodeMatrix(magnet);
|
|
264
|
+
return V.transformRect(rect, transformMatrix.multiply(magnetMatrix));
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
getNodeRotateMatrix(node) {
|
|
268
|
+
if (!this.rotatableNode || this.rotatableNode.contains(node)) {
|
|
269
|
+
// Rotate transformation is applied to all nodes when no rotatableGroup
|
|
270
|
+
// is present or to nodes inside the rotatableGroup only.
|
|
271
|
+
return this.getRootRotateMatrix();
|
|
272
|
+
}
|
|
273
|
+
// Nodes outside the rotatable group
|
|
274
|
+
return V.createSVGMatrix();
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
getNodeUnrotatedBBox: function(magnet) {
|
|
278
|
+
|
|
279
|
+
var rect = this.getNodeBoundingRect(magnet);
|
|
280
|
+
var magnetMatrix = this.getNodeMatrix(magnet);
|
|
281
|
+
var translateMatrix = this.getRootTranslateMatrix();
|
|
282
|
+
return V.transformRect(rect, translateMatrix.multiply(magnetMatrix));
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
getRootTranslateMatrix: function() {
|
|
286
|
+
|
|
287
|
+
var model = this.model;
|
|
288
|
+
var position = model.position();
|
|
289
|
+
var mt = V.createSVGMatrix().translate(position.x, position.y);
|
|
290
|
+
return mt;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
getRootRotateMatrix: function() {
|
|
294
|
+
|
|
295
|
+
var mr = V.createSVGMatrix();
|
|
296
|
+
var model = this.model;
|
|
297
|
+
var angle = model.angle();
|
|
298
|
+
if (angle) {
|
|
299
|
+
var bbox = model.getBBox();
|
|
300
|
+
var cx = bbox.width / 2;
|
|
301
|
+
var cy = bbox.height / 2;
|
|
302
|
+
mr = mr.translate(cx, cy).rotate(angle).translate(-cx, -cy);
|
|
303
|
+
}
|
|
304
|
+
return mr;
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
_notifyHighlight: function(eventName, el, opt = {}) {
|
|
308
|
+
const { el: rootNode } = this;
|
|
309
|
+
let node;
|
|
310
|
+
if (typeof el === 'string') {
|
|
311
|
+
node = this.findNode(el) || rootNode;
|
|
312
|
+
} else {
|
|
313
|
+
[node = rootNode] = this.$(el);
|
|
314
|
+
}
|
|
315
|
+
// set partial flag if the highlighted element is not the entire view.
|
|
316
|
+
opt.partial = (node !== rootNode);
|
|
317
|
+
// translate type flag into a type string
|
|
318
|
+
if (opt.type === undefined) {
|
|
319
|
+
let type;
|
|
320
|
+
switch (true) {
|
|
321
|
+
case opt.embedding:
|
|
322
|
+
type = HighlightingTypes.EMBEDDING;
|
|
323
|
+
break;
|
|
324
|
+
case opt.connecting:
|
|
325
|
+
type = HighlightingTypes.CONNECTING;
|
|
326
|
+
break;
|
|
327
|
+
case opt.magnetAvailability:
|
|
328
|
+
type = HighlightingTypes.MAGNET_AVAILABILITY;
|
|
329
|
+
break;
|
|
330
|
+
case opt.elementAvailability:
|
|
331
|
+
type = HighlightingTypes.ELEMENT_AVAILABILITY;
|
|
332
|
+
break;
|
|
333
|
+
default:
|
|
334
|
+
type = HighlightingTypes.DEFAULT;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
opt.type = type;
|
|
338
|
+
}
|
|
339
|
+
this.notify(eventName, node, opt);
|
|
340
|
+
return this;
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
highlight: function(el, opt) {
|
|
344
|
+
return this._notifyHighlight('cell:highlight', el, opt);
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
unhighlight: function(el, opt = {}) {
|
|
348
|
+
return this._notifyHighlight('cell:unhighlight', el, opt);
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// Find the closest element that has the `magnet` attribute set to `true`. If there was not such
|
|
352
|
+
// an element found, return the root element of the cell view.
|
|
353
|
+
findMagnet: function(el) {
|
|
354
|
+
|
|
355
|
+
const root = this.el;
|
|
356
|
+
let magnet = this.$(el)[0];
|
|
357
|
+
if (!magnet) {
|
|
358
|
+
magnet = root;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
do {
|
|
362
|
+
const magnetAttribute = magnet.getAttribute('magnet');
|
|
363
|
+
const isMagnetRoot = (magnet === root);
|
|
364
|
+
if ((magnetAttribute || isMagnetRoot) && magnetAttribute !== 'false') {
|
|
365
|
+
return magnet;
|
|
366
|
+
}
|
|
367
|
+
if (isMagnetRoot) {
|
|
368
|
+
// If the overall cell has set `magnet === false`, then return `undefined` to
|
|
369
|
+
// announce there is no magnet found for this cell.
|
|
370
|
+
// This is especially useful to set on cells that have 'ports'. In this case,
|
|
371
|
+
// only the ports have set `magnet === true` and the overall element has `magnet === false`.
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
magnet = magnet.parentNode;
|
|
375
|
+
} while (magnet);
|
|
376
|
+
|
|
377
|
+
return undefined;
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
findProxyNode: function(el, type) {
|
|
381
|
+
el || (el = this.el);
|
|
382
|
+
const nodeSelector = el.getAttribute(`${type}-selector`);
|
|
383
|
+
if (nodeSelector) {
|
|
384
|
+
const proxyNode = this.findNode(nodeSelector);
|
|
385
|
+
if (proxyNode) return proxyNode;
|
|
386
|
+
}
|
|
387
|
+
return el;
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// Construct a unique selector for the `el` element within this view.
|
|
391
|
+
// `prevSelector` is being collected through the recursive call.
|
|
392
|
+
// No value for `prevSelector` is expected when using this method.
|
|
393
|
+
getSelector: function(el, prevSelector) {
|
|
394
|
+
|
|
395
|
+
var selector;
|
|
396
|
+
|
|
397
|
+
if (el === this.el) {
|
|
398
|
+
if (typeof prevSelector === 'string') selector = ':scope > ' + prevSelector;
|
|
399
|
+
return selector;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (el) {
|
|
403
|
+
|
|
404
|
+
var nthChild = V(el).index() + 1;
|
|
405
|
+
selector = el.tagName + ':nth-child(' + nthChild + ')';
|
|
406
|
+
|
|
407
|
+
if (prevSelector) {
|
|
408
|
+
selector += ' > ' + prevSelector;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
selector = this.getSelector(el.parentNode, selector);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return selector;
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
addLinkFromMagnet: function(magnet, x, y) {
|
|
418
|
+
|
|
419
|
+
var paper = this.paper;
|
|
420
|
+
var graph = paper.model;
|
|
421
|
+
|
|
422
|
+
var link = paper.getDefaultLink(this, magnet);
|
|
423
|
+
link.set({
|
|
424
|
+
source: this.getLinkEnd(magnet, x, y, link, 'source'),
|
|
425
|
+
target: { x: x, y: y }
|
|
426
|
+
}).addTo(graph, {
|
|
427
|
+
async: false,
|
|
428
|
+
ui: true
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return link.findView(paper);
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
getLinkEnd: function(magnet, ...args) {
|
|
435
|
+
|
|
436
|
+
var model = this.model;
|
|
437
|
+
var id = model.id;
|
|
438
|
+
var port = this.findAttribute('port', magnet);
|
|
439
|
+
// Find a unique `selector` of the element under pointer that is a magnet.
|
|
440
|
+
var selector = magnet.getAttribute('joint-selector');
|
|
441
|
+
|
|
442
|
+
var end = { id: id };
|
|
443
|
+
if (selector != null) end.magnet = selector;
|
|
444
|
+
if (port != null) {
|
|
445
|
+
end.port = port;
|
|
446
|
+
if (!model.hasPort(port) && !selector) {
|
|
447
|
+
// port created via the `port` attribute (not API)
|
|
448
|
+
end.selector = this.getSelector(magnet);
|
|
449
|
+
}
|
|
450
|
+
} else if (selector == null && this.el !== magnet) {
|
|
451
|
+
end.selector = this.getSelector(magnet);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return this.customizeLinkEnd(end, magnet, ...args);
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
customizeLinkEnd: function(end, magnet, x, y, link, endType) {
|
|
458
|
+
const { paper } = this;
|
|
459
|
+
const { connectionStrategy } = paper.options;
|
|
460
|
+
if (typeof connectionStrategy === 'function') {
|
|
461
|
+
var strategy = connectionStrategy.call(paper, end, this, magnet, new Point(x, y), link, endType, paper);
|
|
462
|
+
if (strategy) return strategy;
|
|
463
|
+
}
|
|
464
|
+
return end;
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
getMagnetFromLinkEnd: function(end) {
|
|
468
|
+
|
|
469
|
+
var port = end.port;
|
|
470
|
+
var selector = end.magnet;
|
|
471
|
+
var model = this.model;
|
|
472
|
+
var magnet;
|
|
473
|
+
if (port != null && model.isElement() && model.hasPort(port)) {
|
|
474
|
+
magnet = this.findPortNode(port, selector) || this.el;
|
|
475
|
+
} else {
|
|
476
|
+
if (!selector) selector = end.selector;
|
|
477
|
+
if (!selector && port != null) {
|
|
478
|
+
// link end has only `id` and `port` property referencing
|
|
479
|
+
// a port created via the `port` attribute (not API).
|
|
480
|
+
selector = '[port="' + port + '"]';
|
|
481
|
+
}
|
|
482
|
+
magnet = this.findNode(selector);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return this.findProxyNode(magnet, 'magnet');
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
dragLinkStart: function(evt, magnet, x, y) {
|
|
489
|
+
this.model.startBatch('add-link');
|
|
490
|
+
const linkView = this.addLinkFromMagnet(magnet, x, y);
|
|
491
|
+
// backwards compatibility events
|
|
492
|
+
linkView.notifyPointerdown(evt, x, y);
|
|
493
|
+
linkView.eventData(evt, linkView.startArrowheadMove('target', { whenNotAllowed: 'remove' }));
|
|
494
|
+
this.eventData(evt, { linkView });
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
dragLink: function(evt, x, y) {
|
|
498
|
+
var data = this.eventData(evt);
|
|
499
|
+
var linkView = data.linkView;
|
|
500
|
+
if (linkView) {
|
|
501
|
+
linkView.pointermove(evt, x, y);
|
|
502
|
+
} else {
|
|
503
|
+
var paper = this.paper;
|
|
504
|
+
var magnetThreshold = paper.options.magnetThreshold;
|
|
505
|
+
var currentTarget = this.getEventTarget(evt);
|
|
506
|
+
var targetMagnet = data.targetMagnet;
|
|
507
|
+
if (magnetThreshold === 'onleave') {
|
|
508
|
+
// magnetThreshold when the pointer leaves the magnet
|
|
509
|
+
if (targetMagnet === currentTarget || V(targetMagnet).contains(currentTarget)) return;
|
|
510
|
+
} else {
|
|
511
|
+
// magnetThreshold defined as a number of movements
|
|
512
|
+
if (paper.eventData(evt).mousemoved <= magnetThreshold) return;
|
|
513
|
+
}
|
|
514
|
+
this.dragLinkStart(evt, targetMagnet, x, y);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
dragLinkEnd: function(evt, x, y) {
|
|
519
|
+
var data = this.eventData(evt);
|
|
520
|
+
var linkView = data.linkView;
|
|
521
|
+
if (!linkView) return;
|
|
522
|
+
linkView.pointerup(evt, x, y);
|
|
523
|
+
this.model.stopBatch('add-link');
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
getAttributeDefinition: function(attrName) {
|
|
527
|
+
|
|
528
|
+
return this.model.constructor.getAttributeDefinition(attrName);
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
setNodeAttributes: function(node, attrs) {
|
|
532
|
+
|
|
533
|
+
if (!isEmpty(attrs)) {
|
|
534
|
+
if (node instanceof SVGElement) {
|
|
535
|
+
V(node).attr(attrs);
|
|
536
|
+
} else {
|
|
537
|
+
$(node).attr(attrs);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
processNodeAttributes: function(node, attrs) {
|
|
543
|
+
|
|
544
|
+
var attrName, attrVal, def, i, n;
|
|
545
|
+
var normalAttrs, setAttrs, positionAttrs, offsetAttrs;
|
|
546
|
+
var relatives = [];
|
|
547
|
+
const rawAttrs = {};
|
|
548
|
+
for (attrName in attrs) {
|
|
549
|
+
if (!attrs.hasOwnProperty(attrName)) continue;
|
|
550
|
+
rawAttrs[V.attributeNames[attrName]] = attrs[attrName];
|
|
551
|
+
}
|
|
552
|
+
// divide the attributes between normal and special
|
|
553
|
+
for (attrName in rawAttrs) {
|
|
554
|
+
if (!rawAttrs.hasOwnProperty(attrName)) continue;
|
|
555
|
+
attrVal = rawAttrs[attrName];
|
|
556
|
+
def = this.getAttributeDefinition(attrName);
|
|
557
|
+
if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, rawAttrs, this))) {
|
|
558
|
+
if (isString(def.set)) {
|
|
559
|
+
normalAttrs || (normalAttrs = {});
|
|
560
|
+
normalAttrs[def.set] = attrVal;
|
|
561
|
+
}
|
|
562
|
+
if (attrVal !== null) {
|
|
563
|
+
relatives.push(attrName, def);
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
normalAttrs || (normalAttrs = {});
|
|
567
|
+
normalAttrs[attrName] = attrVal;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// handle the rest of attributes via related method
|
|
572
|
+
// from the special attributes namespace.
|
|
573
|
+
for (i = 0, n = relatives.length; i < n; i+=2) {
|
|
574
|
+
attrName = relatives[i];
|
|
575
|
+
def = relatives[i+1];
|
|
576
|
+
attrVal = attrs[attrName];
|
|
577
|
+
if (isFunction(def.set)) {
|
|
578
|
+
setAttrs || (setAttrs = {});
|
|
579
|
+
setAttrs[attrName] = attrVal;
|
|
580
|
+
}
|
|
581
|
+
if (isFunction(def.position)) {
|
|
582
|
+
positionAttrs || (positionAttrs = {});
|
|
583
|
+
positionAttrs[attrName] = attrVal;
|
|
584
|
+
}
|
|
585
|
+
if (isFunction(def.offset)) {
|
|
586
|
+
offsetAttrs || (offsetAttrs = {});
|
|
587
|
+
offsetAttrs[attrName] = attrVal;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
raw: rawAttrs,
|
|
593
|
+
normal: normalAttrs,
|
|
594
|
+
set: setAttrs,
|
|
595
|
+
position: positionAttrs,
|
|
596
|
+
offset: offsetAttrs
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
updateRelativeAttributes: function(node, attrs, refBBox, opt) {
|
|
601
|
+
|
|
602
|
+
opt || (opt = {});
|
|
603
|
+
|
|
604
|
+
var attrName, attrVal, def;
|
|
605
|
+
var evalAttrs = evalAttributes(attrs.raw || {}, refBBox);
|
|
606
|
+
var nodeAttrs = attrs.normal || {};
|
|
607
|
+
for (const nodeAttrName in nodeAttrs) {
|
|
608
|
+
nodeAttrs[nodeAttrName] = evalAttrs[nodeAttrName];
|
|
609
|
+
}
|
|
610
|
+
var setAttrs = attrs.set;
|
|
611
|
+
var positionAttrs = attrs.position;
|
|
612
|
+
var offsetAttrs = attrs.offset;
|
|
613
|
+
|
|
614
|
+
for (attrName in setAttrs) {
|
|
615
|
+
attrVal = evalAttrs[attrName];
|
|
616
|
+
def = this.getAttributeDefinition(attrName);
|
|
617
|
+
// SET - set function should return attributes to be set on the node,
|
|
618
|
+
// which will affect the node dimensions based on the reference bounding
|
|
619
|
+
// box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points
|
|
620
|
+
var setResult = def.set.call(this, attrVal, refBBox.clone(), node, evalAttrs, this);
|
|
621
|
+
if (isObject(setResult)) {
|
|
622
|
+
assign(nodeAttrs, setResult);
|
|
623
|
+
} else if (setResult !== undefined) {
|
|
624
|
+
nodeAttrs[attrName] = setResult;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (node instanceof HTMLElement) {
|
|
629
|
+
// TODO: setting the `transform` attribute on HTMLElements
|
|
630
|
+
// via `node.style.transform = 'matrix(...)';` would introduce
|
|
631
|
+
// a breaking change (e.g. basic.TextBlock).
|
|
632
|
+
this.setNodeAttributes(node, nodeAttrs);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// The final translation of the subelement.
|
|
637
|
+
var nodeTransform = nodeAttrs.transform;
|
|
638
|
+
var nodeMatrix = V.transformStringToMatrix(nodeTransform);
|
|
639
|
+
var nodePosition = Point(nodeMatrix.e, nodeMatrix.f);
|
|
640
|
+
if (nodeTransform) {
|
|
641
|
+
nodeAttrs = omit(nodeAttrs, 'transform');
|
|
642
|
+
nodeMatrix.e = nodeMatrix.f = 0;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Calculate node scale determined by the scalable group
|
|
646
|
+
// only if later needed.
|
|
647
|
+
var sx, sy, translation;
|
|
648
|
+
if (positionAttrs || offsetAttrs) {
|
|
649
|
+
var nodeScale = this.getNodeScale(node, opt.scalableNode);
|
|
650
|
+
sx = nodeScale.sx;
|
|
651
|
+
sy = nodeScale.sy;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
var positioned = false;
|
|
655
|
+
for (attrName in positionAttrs) {
|
|
656
|
+
attrVal = evalAttrs[attrName];
|
|
657
|
+
def = this.getAttributeDefinition(attrName);
|
|
658
|
+
// POSITION - position function should return a point from the
|
|
659
|
+
// reference bounding box. The default position of the node is x:0, y:0 of
|
|
660
|
+
// the reference bounding box or could be further specify by some
|
|
661
|
+
// SVG attributes e.g. `x`, `y`
|
|
662
|
+
translation = def.position.call(this, attrVal, refBBox.clone(), node, evalAttrs, this);
|
|
663
|
+
if (translation) {
|
|
664
|
+
nodePosition.offset(Point(translation).scale(sx, sy));
|
|
665
|
+
positioned || (positioned = true);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// The node bounding box could depend on the `size` set from the previous loop.
|
|
670
|
+
// Here we know, that all the size attributes have been already set.
|
|
671
|
+
this.setNodeAttributes(node, nodeAttrs);
|
|
672
|
+
|
|
673
|
+
var offseted = false;
|
|
674
|
+
if (offsetAttrs) {
|
|
675
|
+
// Check if the node is visible
|
|
676
|
+
var nodeBoundingRect = this.getNodeBoundingRect(node);
|
|
677
|
+
if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) {
|
|
678
|
+
var nodeBBox = V.transformRect(nodeBoundingRect, nodeMatrix).scale(1 / sx, 1 / sy);
|
|
679
|
+
for (attrName in offsetAttrs) {
|
|
680
|
+
attrVal = evalAttrs[attrName];
|
|
681
|
+
def = this.getAttributeDefinition(attrName);
|
|
682
|
+
// OFFSET - offset function should return a point from the element
|
|
683
|
+
// bounding box. The default offset point is x:0, y:0 (origin) or could be further
|
|
684
|
+
// specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy`
|
|
685
|
+
translation = def.offset.call(this, attrVal, nodeBBox, node, evalAttrs, this);
|
|
686
|
+
if (translation) {
|
|
687
|
+
nodePosition.offset(Point(translation).scale(sx, sy));
|
|
688
|
+
offseted || (offseted = true);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Do not touch node's transform attribute if there is no transformation applied.
|
|
695
|
+
if (nodeTransform !== undefined || positioned || offseted) {
|
|
696
|
+
// Round the coordinates to 1 decimal point.
|
|
697
|
+
nodePosition.round(1);
|
|
698
|
+
nodeMatrix.e = nodePosition.x;
|
|
699
|
+
nodeMatrix.f = nodePosition.y;
|
|
700
|
+
node.setAttribute('transform', V.matrixToTransformString(nodeMatrix));
|
|
701
|
+
// TODO: store nodeMatrix metrics?
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
getNodeScale: function(node, scalableNode) {
|
|
706
|
+
|
|
707
|
+
// Check if the node is a descendant of the scalable group.
|
|
708
|
+
var sx, sy;
|
|
709
|
+
if (scalableNode && scalableNode.contains(node)) {
|
|
710
|
+
var scale = scalableNode.scale();
|
|
711
|
+
sx = 1 / scale.sx;
|
|
712
|
+
sy = 1 / scale.sy;
|
|
713
|
+
} else {
|
|
714
|
+
sx = 1;
|
|
715
|
+
sy = 1;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return { sx: sx, sy: sy };
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
cleanNodesCache: function() {
|
|
722
|
+
this.metrics = {};
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
nodeCache: function(magnet) {
|
|
726
|
+
|
|
727
|
+
var metrics = this.metrics;
|
|
728
|
+
// Don't use cache? It most likely a custom view with overridden update.
|
|
729
|
+
if (!metrics) return {};
|
|
730
|
+
var id = V.ensureId(magnet);
|
|
731
|
+
var value = metrics[id];
|
|
732
|
+
if (!value) value = metrics[id] = {};
|
|
733
|
+
return value;
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
getNodeData: function(magnet) {
|
|
737
|
+
|
|
738
|
+
var metrics = this.nodeCache(magnet);
|
|
739
|
+
if (!metrics.data) metrics.data = {};
|
|
740
|
+
return metrics.data;
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
getNodeBoundingRect: function(magnet) {
|
|
744
|
+
|
|
745
|
+
var metrics = this.nodeCache(magnet);
|
|
746
|
+
if (metrics.boundingRect === undefined) metrics.boundingRect = V(magnet).getBBox();
|
|
747
|
+
return new Rect(metrics.boundingRect);
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
getNodeMatrix: function(magnet) {
|
|
751
|
+
|
|
752
|
+
const metrics = this.nodeCache(magnet);
|
|
753
|
+
if (metrics.magnetMatrix === undefined) {
|
|
754
|
+
const { rotatableNode, el } = this;
|
|
755
|
+
let target;
|
|
756
|
+
if (rotatableNode && rotatableNode.contains(magnet)) {
|
|
757
|
+
target = rotatableNode;
|
|
758
|
+
} else {
|
|
759
|
+
target = el;
|
|
760
|
+
}
|
|
761
|
+
metrics.magnetMatrix = V(magnet).getTransformToElement(target);
|
|
762
|
+
}
|
|
763
|
+
return V.createSVGMatrix(metrics.magnetMatrix);
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
getNodeShape: function(magnet) {
|
|
767
|
+
|
|
768
|
+
var metrics = this.nodeCache(magnet);
|
|
769
|
+
if (metrics.geometryShape === undefined) metrics.geometryShape = V(magnet).toGeometryShape();
|
|
770
|
+
return metrics.geometryShape.clone();
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
isNodeConnection: function(node) {
|
|
774
|
+
return this.model.isLink() && (!node || node === this.el);
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
findNodesAttributes: function(attrs, root, selectorCache, selectors) {
|
|
778
|
+
|
|
779
|
+
var i, n, nodeAttrs, nodeId;
|
|
780
|
+
var nodesAttrs = {};
|
|
781
|
+
var mergeIds = [];
|
|
782
|
+
for (var selector in attrs) {
|
|
783
|
+
if (!attrs.hasOwnProperty(selector)) continue;
|
|
784
|
+
nodeAttrs = attrs[selector];
|
|
785
|
+
if (!isPlainObject(nodeAttrs)) continue; // Not a valid selector-attributes pair
|
|
786
|
+
var selected = selectorCache[selector] = this.findBySelector(selector, root, selectors);
|
|
787
|
+
for (i = 0, n = selected.length; i < n; i++) {
|
|
788
|
+
var node = selected[i];
|
|
789
|
+
nodeId = V.ensureId(node);
|
|
790
|
+
// "unique" selectors are selectors that referencing a single node (defined by `selector`)
|
|
791
|
+
// groupSelector referencing a single node is not "unique"
|
|
792
|
+
var unique = (selectors && selectors[selector] === node);
|
|
793
|
+
var prevNodeAttrs = nodesAttrs[nodeId];
|
|
794
|
+
if (prevNodeAttrs) {
|
|
795
|
+
// Note, that nodes referenced by deprecated `CSS selectors` are not taken into account.
|
|
796
|
+
// e.g. css:`.circle` and selector:`circle` can be applied in a random order
|
|
797
|
+
if (!prevNodeAttrs.array) {
|
|
798
|
+
mergeIds.push(nodeId);
|
|
799
|
+
prevNodeAttrs.array = true;
|
|
800
|
+
prevNodeAttrs.attributes = [prevNodeAttrs.attributes];
|
|
801
|
+
prevNodeAttrs.selectedLength = [prevNodeAttrs.selectedLength];
|
|
802
|
+
}
|
|
803
|
+
var attributes = prevNodeAttrs.attributes;
|
|
804
|
+
var selectedLength = prevNodeAttrs.selectedLength;
|
|
805
|
+
if (unique) {
|
|
806
|
+
// node referenced by `selector`
|
|
807
|
+
attributes.unshift(nodeAttrs);
|
|
808
|
+
selectedLength.unshift(-1);
|
|
809
|
+
} else {
|
|
810
|
+
// node referenced by `groupSelector`
|
|
811
|
+
var sortIndex = sortedIndex(selectedLength, n);
|
|
812
|
+
attributes.splice(sortIndex, 0, nodeAttrs);
|
|
813
|
+
selectedLength.splice(sortIndex, 0, n);
|
|
814
|
+
}
|
|
815
|
+
} else {
|
|
816
|
+
nodesAttrs[nodeId] = {
|
|
817
|
+
attributes: nodeAttrs,
|
|
818
|
+
selectedLength: unique ? -1 : n,
|
|
819
|
+
node: node,
|
|
820
|
+
array: false
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
for (i = 0, n = mergeIds.length; i < n; i++) {
|
|
827
|
+
nodeId = mergeIds[i];
|
|
828
|
+
nodeAttrs = nodesAttrs[nodeId];
|
|
829
|
+
nodeAttrs.attributes = merge({}, ...nodeAttrs.attributes.reverse());
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return nodesAttrs;
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
getEventTarget: function(evt, opt = {}) {
|
|
836
|
+
const { target, type, clientX = 0, clientY = 0 } = evt;
|
|
837
|
+
if (
|
|
838
|
+
// Explicitly defined `fromPoint` option
|
|
839
|
+
opt.fromPoint ||
|
|
840
|
+
// Touchmove/Touchend event's target is not reflecting the element under the coordinates as mousemove does.
|
|
841
|
+
// It holds the element when a touchstart triggered.
|
|
842
|
+
type === 'touchmove' || type === 'touchend' ||
|
|
843
|
+
// Pointermove/Pointerup event with the pointer captured
|
|
844
|
+
('pointerId' in evt && target.hasPointerCapture(evt.pointerId))
|
|
845
|
+
) {
|
|
846
|
+
return document.elementFromPoint(clientX, clientY);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return target;
|
|
850
|
+
},
|
|
851
|
+
|
|
852
|
+
// Default is to process the `model.attributes.attrs` object and set attributes on subelements based on the selectors,
|
|
853
|
+
// unless `attrs` parameter was passed.
|
|
854
|
+
updateDOMSubtreeAttributes: function(rootNode, attrs, opt) {
|
|
855
|
+
|
|
856
|
+
opt || (opt = {});
|
|
857
|
+
opt.rootBBox || (opt.rootBBox = Rect());
|
|
858
|
+
opt.selectors || (opt.selectors = this.selectors); // selector collection to use
|
|
859
|
+
|
|
860
|
+
// Cache table for query results and bounding box calculation.
|
|
861
|
+
// Note that `selectorCache` needs to be invalidated for all
|
|
862
|
+
// `updateAttributes` calls, as the selectors might pointing
|
|
863
|
+
// to nodes designated by an attribute or elements dynamically
|
|
864
|
+
// created.
|
|
865
|
+
var selectorCache = {};
|
|
866
|
+
var bboxCache = {};
|
|
867
|
+
var relativeItems = [];
|
|
868
|
+
var relativeRefItems = [];
|
|
869
|
+
var item, node, nodeAttrs, nodeData, processedAttrs;
|
|
870
|
+
|
|
871
|
+
var roAttrs = opt.roAttributes;
|
|
872
|
+
var nodesAttrs = this.findNodesAttributes(roAttrs || attrs, rootNode, selectorCache, opt.selectors);
|
|
873
|
+
// `nodesAttrs` are different from all attributes, when
|
|
874
|
+
// rendering only attributes sent to this method.
|
|
875
|
+
var nodesAllAttrs = (roAttrs)
|
|
876
|
+
? this.findNodesAttributes(attrs, rootNode, selectorCache, opt.selectors)
|
|
877
|
+
: nodesAttrs;
|
|
878
|
+
|
|
879
|
+
for (var nodeId in nodesAttrs) {
|
|
880
|
+
nodeData = nodesAttrs[nodeId];
|
|
881
|
+
nodeAttrs = nodeData.attributes;
|
|
882
|
+
node = nodeData.node;
|
|
883
|
+
processedAttrs = this.processNodeAttributes(node, nodeAttrs);
|
|
884
|
+
|
|
885
|
+
if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset && !processedAttrs.raw.ref) {
|
|
886
|
+
// Set all the normal attributes right on the SVG/HTML element.
|
|
887
|
+
this.setNodeAttributes(node, evalAttributes(processedAttrs.normal, opt.rootBBox));
|
|
888
|
+
|
|
889
|
+
} else {
|
|
890
|
+
|
|
891
|
+
var nodeAllAttrs = nodesAllAttrs[nodeId] && nodesAllAttrs[nodeId].attributes;
|
|
892
|
+
var refSelector = (nodeAllAttrs && (nodeAttrs.ref === undefined))
|
|
893
|
+
? nodeAllAttrs.ref
|
|
894
|
+
: nodeAttrs.ref;
|
|
895
|
+
|
|
896
|
+
var refNode;
|
|
897
|
+
if (refSelector) {
|
|
898
|
+
refNode = (selectorCache[refSelector] || this.findBySelector(refSelector, rootNode, opt.selectors))[0];
|
|
899
|
+
if (!refNode) {
|
|
900
|
+
throw new Error('dia.CellView: "' + refSelector + '" reference does not exist.');
|
|
901
|
+
}
|
|
902
|
+
} else {
|
|
903
|
+
refNode = null;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
item = {
|
|
907
|
+
node: node,
|
|
908
|
+
refNode: refNode,
|
|
909
|
+
processedAttributes: processedAttrs,
|
|
910
|
+
allAttributes: nodeAllAttrs
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
if (refNode) {
|
|
914
|
+
// If an element in the list is positioned relative to this one, then
|
|
915
|
+
// we want to insert this one before it in the list.
|
|
916
|
+
var itemIndex = relativeRefItems.findIndex(function(item) {
|
|
917
|
+
return item.refNode === node;
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
if (itemIndex > -1) {
|
|
921
|
+
relativeRefItems.splice(itemIndex, 0, item);
|
|
922
|
+
} else {
|
|
923
|
+
relativeRefItems.push(item);
|
|
924
|
+
}
|
|
925
|
+
} else {
|
|
926
|
+
// A node with no ref attribute. To be updated before the nodes referencing other nodes.
|
|
927
|
+
// The order of no-ref-items is not specified/important.
|
|
928
|
+
relativeItems.push(item);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
relativeItems.push(...relativeRefItems);
|
|
934
|
+
|
|
935
|
+
for (let i = 0, n = relativeItems.length; i < n; i++) {
|
|
936
|
+
item = relativeItems[i];
|
|
937
|
+
node = item.node;
|
|
938
|
+
refNode = item.refNode;
|
|
939
|
+
|
|
940
|
+
// Find the reference element bounding box. If no reference was provided, we
|
|
941
|
+
// use the optional bounding box.
|
|
942
|
+
const refNodeId = refNode ? V.ensureId(refNode) : '';
|
|
943
|
+
let refBBox = bboxCache[refNodeId];
|
|
944
|
+
if (!refBBox) {
|
|
945
|
+
// Get the bounding box of the reference element using to the common ancestor
|
|
946
|
+
// transformation space.
|
|
947
|
+
//
|
|
948
|
+
// @example 1
|
|
949
|
+
// <g transform="translate(11, 13)">
|
|
950
|
+
// <rect @selector="b" x="1" y="2" width="3" height="4"/>
|
|
951
|
+
// <rect @selector="a"/>
|
|
952
|
+
// </g>
|
|
953
|
+
//
|
|
954
|
+
// In this case, the reference bounding box can not be affected
|
|
955
|
+
// by the `transform` attribute of the `<g>` element,
|
|
956
|
+
// because the exact transformation will be applied to the `a` element
|
|
957
|
+
// as well as to the `b` element.
|
|
958
|
+
//
|
|
959
|
+
// @example 2
|
|
960
|
+
// <g transform="translate(11, 13)">
|
|
961
|
+
// <rect @selector="b" x="1" y="2" width="3" height="4"/>
|
|
962
|
+
// </g>
|
|
963
|
+
// <rect @selector="a"/>
|
|
964
|
+
//
|
|
965
|
+
// In this case, the reference bounding box have to be affected by the
|
|
966
|
+
// `transform` attribute of the `<g>` element, because the `a` element
|
|
967
|
+
// is not descendant of the `<g>` element and will not be affected
|
|
968
|
+
// by the transformation.
|
|
969
|
+
refBBox = bboxCache[refNodeId] = (refNode)
|
|
970
|
+
? V(refNode).getBBox({ target: getCommonAncestorNode(node, refNode) })
|
|
971
|
+
: opt.rootBBox;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (roAttrs) {
|
|
975
|
+
// if there was a special attribute affecting the position amongst passed-in attributes
|
|
976
|
+
// we have to merge it with the rest of the element's attributes as they are necessary
|
|
977
|
+
// to update the position relatively (i.e `ref-x` && 'ref-dx')
|
|
978
|
+
processedAttrs = this.processNodeAttributes(node, item.allAttributes);
|
|
979
|
+
this.mergeProcessedAttributes(processedAttrs, item.processedAttributes);
|
|
980
|
+
|
|
981
|
+
} else {
|
|
982
|
+
processedAttrs = item.processedAttributes;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
this.updateRelativeAttributes(node, processedAttrs, refBBox, opt);
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
mergeProcessedAttributes: function(processedAttrs, roProcessedAttrs) {
|
|
990
|
+
|
|
991
|
+
processedAttrs.set || (processedAttrs.set = {});
|
|
992
|
+
processedAttrs.position || (processedAttrs.position = {});
|
|
993
|
+
processedAttrs.offset || (processedAttrs.offset = {});
|
|
994
|
+
|
|
995
|
+
assign(processedAttrs.set, roProcessedAttrs.set);
|
|
996
|
+
assign(processedAttrs.position, roProcessedAttrs.position);
|
|
997
|
+
assign(processedAttrs.offset, roProcessedAttrs.offset);
|
|
998
|
+
|
|
999
|
+
// Handle also the special transform property.
|
|
1000
|
+
var transform = processedAttrs.normal && processedAttrs.normal.transform;
|
|
1001
|
+
if (transform !== undefined && roProcessedAttrs.normal) {
|
|
1002
|
+
roProcessedAttrs.normal.transform = transform;
|
|
1003
|
+
}
|
|
1004
|
+
processedAttrs.normal = roProcessedAttrs.normal;
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
// Lifecycle methods
|
|
1008
|
+
|
|
1009
|
+
// Called when the view is attached to the DOM,
|
|
1010
|
+
// as result of `cell.addTo(graph)` being called (isInitialMount === true)
|
|
1011
|
+
// or `paper.options.viewport` returning `true` (isInitialMount === false).
|
|
1012
|
+
onMount(isInitialMount) {
|
|
1013
|
+
if (isInitialMount) return;
|
|
1014
|
+
this.mountTools();
|
|
1015
|
+
HighlighterView.mount(this);
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
// Called when the view is detached from the DOM,
|
|
1019
|
+
// as result of `paper.options.viewport` returning `false`.
|
|
1020
|
+
onDetach() {
|
|
1021
|
+
this.unmountTools();
|
|
1022
|
+
HighlighterView.unmount(this);
|
|
1023
|
+
},
|
|
1024
|
+
|
|
1025
|
+
// Called when the view is removed from the DOM
|
|
1026
|
+
// as result of `cell.remove()`.
|
|
1027
|
+
onRemove: function() {
|
|
1028
|
+
this.removeTools();
|
|
1029
|
+
this.removeHighlighters();
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
_toolsView: null,
|
|
1033
|
+
|
|
1034
|
+
hasTools: function(name) {
|
|
1035
|
+
var toolsView = this._toolsView;
|
|
1036
|
+
if (!toolsView) return false;
|
|
1037
|
+
if (!name) return true;
|
|
1038
|
+
return (toolsView.getName() === name);
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
addTools: function(toolsView) {
|
|
1042
|
+
|
|
1043
|
+
this.removeTools();
|
|
1044
|
+
|
|
1045
|
+
if (toolsView) {
|
|
1046
|
+
this._toolsView = toolsView;
|
|
1047
|
+
toolsView.configure({ relatedView: this });
|
|
1048
|
+
toolsView.listenTo(this.paper, 'tools:event', this.onToolEvent.bind(this));
|
|
1049
|
+
}
|
|
1050
|
+
return this;
|
|
1051
|
+
},
|
|
1052
|
+
|
|
1053
|
+
unmountTools() {
|
|
1054
|
+
const toolsView = this._toolsView;
|
|
1055
|
+
if (toolsView) toolsView.unmount();
|
|
1056
|
+
return this;
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
mountTools() {
|
|
1060
|
+
const toolsView = this._toolsView;
|
|
1061
|
+
// Prevent unnecessary re-appending of the tools.
|
|
1062
|
+
if (toolsView && !toolsView.isMounted()) toolsView.mount();
|
|
1063
|
+
return this;
|
|
1064
|
+
},
|
|
1065
|
+
|
|
1066
|
+
updateTools: function(opt) {
|
|
1067
|
+
|
|
1068
|
+
var toolsView = this._toolsView;
|
|
1069
|
+
if (toolsView) toolsView.update(opt);
|
|
1070
|
+
return this;
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
removeTools: function() {
|
|
1074
|
+
|
|
1075
|
+
var toolsView = this._toolsView;
|
|
1076
|
+
if (toolsView) {
|
|
1077
|
+
toolsView.remove();
|
|
1078
|
+
this._toolsView = null;
|
|
1079
|
+
}
|
|
1080
|
+
return this;
|
|
1081
|
+
},
|
|
1082
|
+
|
|
1083
|
+
hideTools: function() {
|
|
1084
|
+
|
|
1085
|
+
var toolsView = this._toolsView;
|
|
1086
|
+
if (toolsView) toolsView.hide();
|
|
1087
|
+
return this;
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
showTools: function() {
|
|
1091
|
+
|
|
1092
|
+
var toolsView = this._toolsView;
|
|
1093
|
+
if (toolsView) toolsView.show();
|
|
1094
|
+
return this;
|
|
1095
|
+
},
|
|
1096
|
+
|
|
1097
|
+
onToolEvent: function(event) {
|
|
1098
|
+
switch (event) {
|
|
1099
|
+
case 'remove':
|
|
1100
|
+
this.removeTools();
|
|
1101
|
+
break;
|
|
1102
|
+
case 'hide':
|
|
1103
|
+
this.hideTools();
|
|
1104
|
+
break;
|
|
1105
|
+
case 'show':
|
|
1106
|
+
this.showTools();
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
removeHighlighters: function() {
|
|
1112
|
+
HighlighterView.remove(this);
|
|
1113
|
+
},
|
|
1114
|
+
|
|
1115
|
+
updateHighlighters: function(dirty = false) {
|
|
1116
|
+
HighlighterView.update(this, null, dirty);
|
|
1117
|
+
},
|
|
1118
|
+
|
|
1119
|
+
transformHighlighters: function() {
|
|
1120
|
+
HighlighterView.transform(this);
|
|
1121
|
+
},
|
|
1122
|
+
|
|
1123
|
+
// Interaction. The controller part.
|
|
1124
|
+
// ---------------------------------
|
|
1125
|
+
|
|
1126
|
+
preventDefaultInteraction(evt) {
|
|
1127
|
+
this.eventData(evt, { defaultInteractionPrevented: true });
|
|
1128
|
+
},
|
|
1129
|
+
|
|
1130
|
+
isDefaultInteractionPrevented(evt) {
|
|
1131
|
+
const { defaultInteractionPrevented = false } = this.eventData(evt);
|
|
1132
|
+
return defaultInteractionPrevented;
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
// Interaction is handled by the paper and delegated to the view in interest.
|
|
1136
|
+
// `x` & `y` parameters passed to these functions represent the coordinates already snapped to the paper grid.
|
|
1137
|
+
// If necessary, real coordinates can be obtained from the `evt` event object.
|
|
1138
|
+
|
|
1139
|
+
// These functions are supposed to be overridden by the views that inherit from `joint.dia.Cell`,
|
|
1140
|
+
// i.e. `joint.dia.Element` and `joint.dia.Link`.
|
|
1141
|
+
|
|
1142
|
+
pointerdblclick: function(evt, x, y) {
|
|
1143
|
+
|
|
1144
|
+
this.notify('cell:pointerdblclick', evt, x, y);
|
|
1145
|
+
},
|
|
1146
|
+
|
|
1147
|
+
pointerclick: function(evt, x, y) {
|
|
1148
|
+
|
|
1149
|
+
this.notify('cell:pointerclick', evt, x, y);
|
|
1150
|
+
},
|
|
1151
|
+
|
|
1152
|
+
contextmenu: function(evt, x, y) {
|
|
1153
|
+
|
|
1154
|
+
this.notify('cell:contextmenu', evt, x, y);
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
pointerdown: function(evt, x, y) {
|
|
1158
|
+
|
|
1159
|
+
const { model } = this;
|
|
1160
|
+
const { graph } = model;
|
|
1161
|
+
if (graph) {
|
|
1162
|
+
model.startBatch('pointer');
|
|
1163
|
+
this.eventData(evt, { graph });
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
this.notify('cell:pointerdown', evt, x, y);
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
pointermove: function(evt, x, y) {
|
|
1170
|
+
|
|
1171
|
+
this.notify('cell:pointermove', evt, x, y);
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1174
|
+
pointerup: function(evt, x, y) {
|
|
1175
|
+
|
|
1176
|
+
const { graph } = this.eventData(evt);
|
|
1177
|
+
|
|
1178
|
+
this.notify('cell:pointerup', evt, x, y);
|
|
1179
|
+
|
|
1180
|
+
if (graph) {
|
|
1181
|
+
// we don't want to trigger event on model as model doesn't
|
|
1182
|
+
// need to be member of collection anymore (remove)
|
|
1183
|
+
graph.stopBatch('pointer', { cell: this.model });
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
|
|
1187
|
+
mouseover: function(evt) {
|
|
1188
|
+
|
|
1189
|
+
this.notify('cell:mouseover', evt);
|
|
1190
|
+
},
|
|
1191
|
+
|
|
1192
|
+
mouseout: function(evt) {
|
|
1193
|
+
|
|
1194
|
+
this.notify('cell:mouseout', evt);
|
|
1195
|
+
},
|
|
1196
|
+
|
|
1197
|
+
mouseenter: function(evt) {
|
|
1198
|
+
|
|
1199
|
+
this.notify('cell:mouseenter', evt);
|
|
1200
|
+
},
|
|
1201
|
+
|
|
1202
|
+
mouseleave: function(evt) {
|
|
1203
|
+
|
|
1204
|
+
this.notify('cell:mouseleave', evt);
|
|
1205
|
+
},
|
|
1206
|
+
|
|
1207
|
+
mousewheel: function(evt, x, y, delta) {
|
|
1208
|
+
|
|
1209
|
+
this.notify('cell:mousewheel', evt, x, y, delta);
|
|
1210
|
+
},
|
|
1211
|
+
|
|
1212
|
+
onevent: function(evt, eventName, x, y) {
|
|
1213
|
+
|
|
1214
|
+
this.notify(eventName, evt, x, y);
|
|
1215
|
+
},
|
|
1216
|
+
|
|
1217
|
+
onmagnet: function() {
|
|
1218
|
+
|
|
1219
|
+
// noop
|
|
1220
|
+
},
|
|
1221
|
+
|
|
1222
|
+
magnetpointerdblclick: function() {
|
|
1223
|
+
|
|
1224
|
+
// noop
|
|
1225
|
+
},
|
|
1226
|
+
|
|
1227
|
+
magnetcontextmenu: function() {
|
|
1228
|
+
|
|
1229
|
+
// noop
|
|
1230
|
+
},
|
|
1231
|
+
|
|
1232
|
+
checkMouseleave(evt) {
|
|
1233
|
+
const { paper, model } = this;
|
|
1234
|
+
if (paper.isAsync()) {
|
|
1235
|
+
// Make sure the source/target views are updated before this view.
|
|
1236
|
+
// It's not 100% bulletproof (see below) but it's a good enough solution for now.
|
|
1237
|
+
// The connected cells could be links as well. In that case, we would
|
|
1238
|
+
// need to recursively go through all the connected links and update
|
|
1239
|
+
// their source/target views as well.
|
|
1240
|
+
if (model.isLink()) {
|
|
1241
|
+
// The `this.sourceView` and `this.targetView` might not be updated yet.
|
|
1242
|
+
// We need to find the view by the model.
|
|
1243
|
+
const sourceElement = model.getSourceElement();
|
|
1244
|
+
if (sourceElement) {
|
|
1245
|
+
const sourceView = paper.findViewByModel(sourceElement);
|
|
1246
|
+
if (sourceView) {
|
|
1247
|
+
paper.dumpView(sourceView);
|
|
1248
|
+
paper.checkViewVisibility(sourceView);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const targetElement = model.getTargetElement();
|
|
1252
|
+
if (targetElement) {
|
|
1253
|
+
const targetView = paper.findViewByModel(targetElement);
|
|
1254
|
+
if (targetView) {
|
|
1255
|
+
paper.dumpView(targetView);
|
|
1256
|
+
paper.checkViewVisibility(targetView);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// Do the updates of the current view synchronously now
|
|
1261
|
+
paper.dumpView(this);
|
|
1262
|
+
paper.checkViewVisibility(this);
|
|
1263
|
+
}
|
|
1264
|
+
const target = this.getEventTarget(evt, { fromPoint: true });
|
|
1265
|
+
const view = paper.findView(target);
|
|
1266
|
+
if (view === this) return;
|
|
1267
|
+
// Leaving the current view
|
|
1268
|
+
this.mouseleave(evt);
|
|
1269
|
+
if (!view) return;
|
|
1270
|
+
// Entering another view
|
|
1271
|
+
view.mouseenter(evt);
|
|
1272
|
+
},
|
|
1273
|
+
|
|
1274
|
+
setInteractivity: function(value) {
|
|
1275
|
+
|
|
1276
|
+
this.options.interactive = value;
|
|
1277
|
+
}
|
|
1278
|
+
}, {
|
|
1279
|
+
|
|
1280
|
+
Flags,
|
|
1281
|
+
|
|
1282
|
+
Highlighting: HighlightingTypes,
|
|
1283
|
+
|
|
1284
|
+
addPresentationAttributes: function(presentationAttributes) {
|
|
1285
|
+
return merge({}, result(this.prototype, 'presentationAttributes'), presentationAttributes, function(a, b) {
|
|
1286
|
+
if (!a || !b) return;
|
|
1287
|
+
if (typeof a === 'string') a = [a];
|
|
1288
|
+
if (typeof b === 'string') b = [b];
|
|
1289
|
+
if (Array.isArray(a) && Array.isArray(b)) return uniq(a.concat(b));
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
|
|
1293
|
+
evalAttribute,
|
|
1294
|
+
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
Object.defineProperty(CellView.prototype, 'useCSSSelectors', {
|
|
1299
|
+
get() {
|
|
1300
|
+
const localUse = this.model.useCSSSelectors;
|
|
1301
|
+
if (localUse !== undefined) return localUse;
|
|
1302
|
+
return config.useCSSSelectors;
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// TODO: Move to Vectorizer library.
|
|
1307
|
+
function getCommonAncestorNode(node1, node2) {
|
|
1308
|
+
let parent = node1;
|
|
1309
|
+
do {
|
|
1310
|
+
if (parent.contains(node2)) return parent;
|
|
1311
|
+
parent = parent.parentNode;
|
|
1312
|
+
} while (parent);
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
|