@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.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. 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
+