@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,859 @@
1
+ import { assign, isFunction, toArray } from '../util/index.mjs';
2
+ import { CellView } from './CellView.mjs';
3
+ import { Cell } from './Cell.mjs';
4
+ import V from '../V/index.mjs';
5
+ import { elementViewPortPrototype } from './ports.mjs';
6
+ import { Rect, snapToGrid } from '../g/index.mjs';
7
+
8
+ const Flags = {
9
+ TOOLS: CellView.Flags.TOOLS,
10
+ UPDATE: 'UPDATE',
11
+ TRANSLATE: 'TRANSLATE',
12
+ RESIZE: 'RESIZE',
13
+ PORTS: 'PORTS',
14
+ ROTATE: 'ROTATE',
15
+ RENDER: 'RENDER'
16
+ };
17
+
18
+ const DragActions = {
19
+ MOVE: 'move',
20
+ MAGNET: 'magnet',
21
+ };
22
+ // Element base view and controller.
23
+ // -------------------------------------------
24
+
25
+ export const ElementView = CellView.extend({
26
+
27
+ /**
28
+ * @abstract
29
+ */
30
+ _removePorts: function() {
31
+ // implemented in ports.js
32
+ },
33
+
34
+ /**
35
+ *
36
+ * @abstract
37
+ */
38
+ _renderPorts: function() {
39
+ // implemented in ports.js
40
+ },
41
+
42
+ className: function() {
43
+
44
+ var classNames = CellView.prototype.className.apply(this).split(' ');
45
+
46
+ classNames.push('element');
47
+
48
+ return classNames.join(' ');
49
+ },
50
+
51
+ initialize: function() {
52
+
53
+ CellView.prototype.initialize.apply(this, arguments);
54
+
55
+ this._initializePorts();
56
+ },
57
+
58
+ presentationAttributes: {
59
+ 'attrs': [Flags.UPDATE],
60
+ 'position': [Flags.TRANSLATE, Flags.TOOLS],
61
+ 'size': [Flags.RESIZE, Flags.PORTS, Flags.TOOLS],
62
+ 'angle': [Flags.ROTATE, Flags.TOOLS],
63
+ 'markup': [Flags.RENDER],
64
+ 'ports': [Flags.PORTS],
65
+ },
66
+
67
+ initFlag: [Flags.RENDER],
68
+
69
+ UPDATE_PRIORITY: 0,
70
+
71
+ confirmUpdate: function(flag, opt) {
72
+
73
+ const { useCSSSelectors } = this;
74
+ if (this.hasFlag(flag, Flags.PORTS)) {
75
+ this._removePorts();
76
+ this._cleanPortsCache();
77
+ }
78
+ let transformHighlighters = false;
79
+ if (this.hasFlag(flag, Flags.RENDER)) {
80
+ this.render();
81
+ this.updateTools(opt);
82
+ this.updateHighlighters(true);
83
+ transformHighlighters = true;
84
+ flag = this.removeFlag(flag, [Flags.RENDER, Flags.UPDATE, Flags.RESIZE, Flags.TRANSLATE, Flags.ROTATE, Flags.PORTS, Flags.TOOLS]);
85
+ } else {
86
+ let updateHighlighters = false;
87
+
88
+ // Skip this branch if render is required
89
+ if (this.hasFlag(flag, Flags.RESIZE)) {
90
+ this.resize(opt);
91
+ updateHighlighters = true;
92
+ // Resize method is calling `update()` internally
93
+ flag = this.removeFlag(flag, [Flags.RESIZE, Flags.UPDATE]);
94
+ if (useCSSSelectors) {
95
+ // `resize()` rendered the ports when useCSSSelectors are enabled
96
+ flag = this.removeFlag(flag, Flags.PORTS);
97
+ }
98
+ }
99
+ if (this.hasFlag(flag, Flags.UPDATE)) {
100
+ this.update(this.model, null, opt);
101
+ flag = this.removeFlag(flag, Flags.UPDATE);
102
+ updateHighlighters = true;
103
+ if (useCSSSelectors) {
104
+ // `update()` will render ports when useCSSSelectors are enabled
105
+ flag = this.removeFlag(flag, Flags.PORTS);
106
+ }
107
+ }
108
+ if (this.hasFlag(flag, Flags.TRANSLATE)) {
109
+ this.translate();
110
+ flag = this.removeFlag(flag, Flags.TRANSLATE);
111
+ transformHighlighters = true;
112
+ }
113
+ if (this.hasFlag(flag, Flags.ROTATE)) {
114
+ this.rotate();
115
+ flag = this.removeFlag(flag, Flags.ROTATE);
116
+ transformHighlighters = true;
117
+ }
118
+ if (this.hasFlag(flag, Flags.PORTS)) {
119
+ this._renderPorts();
120
+ updateHighlighters = true;
121
+ flag = this.removeFlag(flag, Flags.PORTS);
122
+ }
123
+
124
+ if (updateHighlighters) {
125
+ this.updateHighlighters(false);
126
+ }
127
+ }
128
+
129
+ if (transformHighlighters) {
130
+ this.transformHighlighters();
131
+ }
132
+
133
+ if (this.hasFlag(flag, Flags.TOOLS)) {
134
+ this.updateTools(opt);
135
+ flag = this.removeFlag(flag, Flags.TOOLS);
136
+ }
137
+
138
+ return flag;
139
+ },
140
+
141
+ /**
142
+ * @abstract
143
+ */
144
+ _initializePorts: function() {
145
+
146
+ },
147
+
148
+ update: function(_, renderingOnlyAttrs) {
149
+
150
+ this.cleanNodesCache();
151
+
152
+ // When CSS selector strings are used, make sure no rule matches port nodes.
153
+ const { useCSSSelectors } = this;
154
+ if (useCSSSelectors) this._removePorts();
155
+
156
+ var model = this.model;
157
+ var modelAttrs = model.attr();
158
+ this.updateDOMSubtreeAttributes(this.el, modelAttrs, {
159
+ rootBBox: new Rect(model.size()),
160
+ selectors: this.selectors,
161
+ scalableNode: this.scalableNode,
162
+ rotatableNode: this.rotatableNode,
163
+ // Use rendering only attributes if they differs from the model attributes
164
+ roAttributes: (renderingOnlyAttrs === modelAttrs) ? null : renderingOnlyAttrs
165
+ });
166
+
167
+ if (useCSSSelectors) {
168
+ this._renderPorts();
169
+ }
170
+ },
171
+
172
+ rotatableSelector: 'rotatable',
173
+ scalableSelector: 'scalable',
174
+ scalableNode: null,
175
+ rotatableNode: null,
176
+
177
+ // `prototype.markup` is rendered by default. Set the `markup` attribute on the model if the
178
+ // default markup is not desirable.
179
+ renderMarkup: function() {
180
+
181
+ var element = this.model;
182
+ var markup = element.get('markup') || element.markup;
183
+ if (!markup) throw new Error('dia.ElementView: markup required');
184
+ if (Array.isArray(markup)) return this.renderJSONMarkup(markup);
185
+ if (typeof markup === 'string') return this.renderStringMarkup(markup);
186
+ throw new Error('dia.ElementView: invalid markup');
187
+ },
188
+
189
+ renderJSONMarkup: function(markup) {
190
+
191
+ var doc = this.parseDOMJSON(markup, this.el);
192
+ var selectors = this.selectors = doc.selectors;
193
+ this.rotatableNode = V(selectors[this.rotatableSelector]) || null;
194
+ this.scalableNode = V(selectors[this.scalableSelector]) || null;
195
+ // Fragment
196
+ this.vel.append(doc.fragment);
197
+ },
198
+
199
+ renderStringMarkup: function(markup) {
200
+
201
+ var vel = this.vel;
202
+ vel.append(V(markup));
203
+ // Cache transformation groups
204
+ this.rotatableNode = vel.findOne('.rotatable');
205
+ this.scalableNode = vel.findOne('.scalable');
206
+
207
+ var selectors = this.selectors = {};
208
+ selectors[this.selector] = this.el;
209
+ },
210
+
211
+ render: function() {
212
+
213
+ this.vel.empty();
214
+ this.renderMarkup();
215
+ if (this.scalableNode) {
216
+ // Double update is necessary for elements with the scalable group only
217
+ // Note the resize() triggers the other `update`.
218
+ this.update();
219
+ }
220
+ this.resize();
221
+ if (this.rotatableNode) {
222
+ // Translate transformation is applied on `this.el` while the rotation transformation
223
+ // on `this.rotatableNode`
224
+ this.rotate();
225
+ this.translate();
226
+ } else {
227
+ this.updateTransformation();
228
+ }
229
+ if (!this.useCSSSelectors) this._renderPorts();
230
+ return this;
231
+ },
232
+
233
+ resize: function(opt) {
234
+
235
+ if (this.scalableNode) return this.sgResize(opt);
236
+ if (this.model.attributes.angle) this.rotate();
237
+ this.update();
238
+ },
239
+
240
+ translate: function() {
241
+
242
+ if (this.rotatableNode) return this.rgTranslate();
243
+ this.updateTransformation();
244
+ },
245
+
246
+ rotate: function() {
247
+
248
+ if (this.rotatableNode) {
249
+ this.rgRotate();
250
+ // It's necessary to call the update for the nodes outside
251
+ // the rotatable group referencing nodes inside the group
252
+ this.update();
253
+ return;
254
+ }
255
+ this.updateTransformation();
256
+ },
257
+
258
+ updateTransformation: function() {
259
+
260
+ var transformation = this.getTranslateString();
261
+ var rotateString = this.getRotateString();
262
+ if (rotateString) transformation += ' ' + rotateString;
263
+ this.vel.attr('transform', transformation);
264
+ },
265
+
266
+ getTranslateString: function() {
267
+
268
+ var position = this.model.attributes.position;
269
+ return 'translate(' + position.x + ',' + position.y + ')';
270
+ },
271
+
272
+ getRotateString: function() {
273
+ var attributes = this.model.attributes;
274
+ var angle = attributes.angle;
275
+ if (!angle) return null;
276
+ var size = attributes.size;
277
+ return 'rotate(' + angle + ',' + (size.width / 2) + ',' + (size.height / 2) + ')';
278
+ },
279
+
280
+ // Rotatable & Scalable Group
281
+ // always slower, kept mainly for backwards compatibility
282
+
283
+ rgRotate: function() {
284
+
285
+ this.rotatableNode.attr('transform', this.getRotateString());
286
+ },
287
+
288
+ rgTranslate: function() {
289
+
290
+ this.vel.attr('transform', this.getTranslateString());
291
+ },
292
+
293
+ sgResize: function(opt) {
294
+
295
+ var model = this.model;
296
+ var angle = model.angle();
297
+ var size = model.size();
298
+ var scalable = this.scalableNode;
299
+
300
+ // Getting scalable group's bbox.
301
+ // Due to a bug in webkit's native SVG .getBBox implementation, the bbox of groups with path children includes the paths' control points.
302
+ // To work around the issue, we need to check whether there are any path elements inside the scalable group.
303
+ var recursive = false;
304
+ if (scalable.node.getElementsByTagName('path').length > 0) {
305
+ // If scalable has at least one descendant that is a path, we need to switch to recursive bbox calculation.
306
+ // If there are no path descendants, group bbox calculation works and so we can use the (faster) native function directly.
307
+ recursive = true;
308
+ }
309
+ var scalableBBox = scalable.getBBox({ recursive: recursive });
310
+
311
+ // Make sure `scalableBbox.width` and `scalableBbox.height` are not zero which can happen if the element does not have any content. By making
312
+ // the width/height 1, we prevent HTML errors of the type `scale(Infinity, Infinity)`.
313
+ var sx = (size.width / (scalableBBox.width || 1));
314
+ var sy = (size.height / (scalableBBox.height || 1));
315
+ scalable.attr('transform', 'scale(' + sx + ',' + sy + ')');
316
+
317
+ // Now the interesting part. The goal is to be able to store the object geometry via just `x`, `y`, `angle`, `width` and `height`
318
+ // Order of transformations is significant but we want to reconstruct the object always in the order:
319
+ // resize(), rotate(), translate() no matter of how the object was transformed. For that to work,
320
+ // we must adjust the `x` and `y` coordinates of the object whenever we resize it (because the origin of the
321
+ // rotation changes). The new `x` and `y` coordinates are computed by canceling the previous rotation
322
+ // around the center of the resized object (which is a different origin then the origin of the previous rotation)
323
+ // and getting the top-left corner of the resulting object. Then we clean up the rotation back to what it originally was.
324
+
325
+ // Cancel the rotation but now around a different origin, which is the center of the scaled object.
326
+ var rotatable = this.rotatableNode;
327
+ var rotation = rotatable && rotatable.attr('transform');
328
+ if (rotation) {
329
+
330
+ rotatable.attr('transform', rotation + ' rotate(' + (-angle) + ',' + (size.width / 2) + ',' + (size.height / 2) + ')');
331
+ var rotatableBBox = scalable.getBBox({ target: this.paper.cells });
332
+
333
+ // Store new x, y and perform rotate() again against the new rotation origin.
334
+ model.set('position', { x: rotatableBBox.x, y: rotatableBBox.y }, assign({ updateHandled: true }, opt));
335
+ this.translate();
336
+ this.rotate();
337
+ }
338
+
339
+ // Update must always be called on non-rotated element. Otherwise, relative positioning
340
+ // would work with wrong (rotated) bounding boxes.
341
+ this.update();
342
+ },
343
+
344
+ // Embedding mode methods.
345
+ // -----------------------
346
+
347
+ prepareEmbedding: function(data = {}) {
348
+
349
+ const element = data.model || this.model;
350
+ const paper = data.paper || this.paper;
351
+ const graph = paper.model;
352
+
353
+ const initialZIndices = data.initialZIndices = {};
354
+ const embeddedCells = element.getEmbeddedCells({ deep: true });
355
+ const connectedLinks = graph.getConnectedLinks(element, { deep: true, includeEnclosed: true });
356
+
357
+ // Note: an embedded cell can be a connect link, but it's fine
358
+ // to iterate over the cell twice.
359
+ [
360
+ element,
361
+ ...embeddedCells,
362
+ ...connectedLinks
363
+ ].forEach(cell => initialZIndices[cell.id] = cell.attributes.z);
364
+
365
+ element.startBatch('to-front');
366
+
367
+ // Bring the model to the front with all his embeds.
368
+ element.toFront({ deep: true, ui: true });
369
+
370
+ // Note that at this point cells in the collection are not sorted by z index (it's running in the batch, see
371
+ // the dia.Graph._sortOnChangeZ), so we can't assume that the last cell in the collection has the highest z.
372
+ const maxZ = graph.getElements().reduce((max, cell) => Math.max(max, cell.attributes.z || 0), 0);
373
+
374
+ // Move to front also all the inbound and outbound links that are connected
375
+ // to any of the element descendant. If we bring to front only embedded elements,
376
+ // links connected to them would stay in the background.
377
+ connectedLinks.forEach((link) => {
378
+ if (link.attributes.z <= maxZ) {
379
+ link.set('z', maxZ + 1, { ui: true });
380
+ }
381
+ });
382
+
383
+ element.stopBatch('to-front');
384
+
385
+ // Before we start looking for suitable parent we remove the current one.
386
+ const parentId = element.parent();
387
+ if (parentId) {
388
+ const parent = graph.getCell(parentId);
389
+ parent.unembed(element, { ui: true });
390
+ data.initialParentId = parentId;
391
+ } else {
392
+ data.initialParentId = null;
393
+ }
394
+ },
395
+
396
+ processEmbedding: function(data = {}, evt, x, y) {
397
+
398
+ const model = data.model || this.model;
399
+ const paper = data.paper || this.paper;
400
+ const graph = paper.model;
401
+ const { findParentBy, frontParentOnly, validateEmbedding } = paper.options;
402
+
403
+ let candidates;
404
+ if (isFunction(findParentBy)) {
405
+ candidates = toArray(findParentBy.call(graph, this, evt, x, y));
406
+ } else if (findParentBy === 'pointer') {
407
+ candidates = toArray(graph.findModelsFromPoint({ x, y }));
408
+ } else {
409
+ candidates = graph.findModelsUnderElement(model, { searchBy: findParentBy });
410
+ }
411
+
412
+ candidates = candidates.filter((el) => {
413
+ return (el instanceof Cell) && (model.id !== el.id) && !el.isEmbeddedIn(model);
414
+ });
415
+
416
+ if (frontParentOnly) {
417
+ // pick the element with the highest `z` index
418
+ candidates = candidates.slice(-1);
419
+ }
420
+
421
+ let newCandidateView = null;
422
+ const prevCandidateView = data.candidateEmbedView;
423
+
424
+ // iterate over all candidates starting from the last one (has the highest z-index).
425
+ for (let i = candidates.length - 1; i >= 0; i--) {
426
+ const candidate = candidates[i];
427
+ if (prevCandidateView && prevCandidateView.model.id == candidate.id) {
428
+ // candidate remains the same
429
+ newCandidateView = prevCandidateView;
430
+ break;
431
+ } else {
432
+ const view = candidate.findView(paper);
433
+ if (!isFunction(validateEmbedding) || validateEmbedding.call(paper, this, view)) {
434
+ // flip to the new candidate
435
+ newCandidateView = view;
436
+ break;
437
+ }
438
+ }
439
+ }
440
+
441
+ if (newCandidateView && newCandidateView != prevCandidateView) {
442
+ // A new candidate view found. Highlight the new one.
443
+ this.clearEmbedding(data);
444
+ data.candidateEmbedView = newCandidateView.highlight(
445
+ newCandidateView.findProxyNode(null, 'container'),
446
+ { embedding: true }
447
+ );
448
+ }
449
+
450
+ if (!newCandidateView && prevCandidateView) {
451
+ // No candidate view found. Unhighlight the previous candidate.
452
+ this.clearEmbedding(data);
453
+ }
454
+ },
455
+
456
+ clearEmbedding: function(data) {
457
+
458
+ data || (data = {});
459
+
460
+ var candidateView = data.candidateEmbedView;
461
+ if (candidateView) {
462
+ // No candidate view found. Unhighlight the previous candidate.
463
+ candidateView.unhighlight(
464
+ candidateView.findProxyNode(null, 'container'),
465
+ { embedding: true }
466
+ );
467
+ data.candidateEmbedView = null;
468
+ }
469
+ },
470
+
471
+ finalizeEmbedding: function(data = {}) {
472
+
473
+ const candidateView = data.candidateEmbedView;
474
+ const element = data.model || this.model;
475
+ const paper = data.paper || this.paper;
476
+
477
+ if (candidateView) {
478
+
479
+ // We finished embedding. Candidate view is chosen to become the parent of the model.
480
+ candidateView.model.embed(element, { ui: true });
481
+ candidateView.unhighlight(candidateView.findProxyNode(null, 'container'), { embedding: true });
482
+
483
+ data.candidateEmbedView = null;
484
+
485
+ } else {
486
+
487
+ const { validateUnembedding } = paper.options;
488
+ const { initialParentId } = data;
489
+ // The element was originally embedded into another element.
490
+ // The interaction would unembed the element. Let's validate
491
+ // if the element can be unembedded.
492
+ if (
493
+ initialParentId &&
494
+ typeof validateUnembedding === 'function' &&
495
+ !validateUnembedding.call(paper, this)
496
+ ) {
497
+ this._disallowUnembed(data);
498
+ return;
499
+ }
500
+ }
501
+
502
+ paper.model.getConnectedLinks(element, { deep: true }).forEach(link => {
503
+ link.reparent({ ui: true });
504
+ });
505
+ },
506
+
507
+ _disallowUnembed: function(data) {
508
+ const { model, whenNotAllowed = 'revert' } = data;
509
+ const element = model || this.model;
510
+ const paper = data.paper || this.paper;
511
+ const graph = paper.model;
512
+ switch (whenNotAllowed) {
513
+ case 'remove': {
514
+ element.remove({ ui: true });
515
+ break;
516
+ }
517
+ case 'revert': {
518
+ const { initialParentId, initialPosition, initialZIndices } = data;
519
+ // Revert the element's position (and the position of its embedded cells if any)
520
+ if (initialPosition) {
521
+ const { x, y } = initialPosition;
522
+ element.position(x, y, { deep: true, ui: true });
523
+ }
524
+ // Revert all the z-indices changed during the embedding
525
+ if (initialZIndices) {
526
+ Object.keys(initialZIndices).forEach(id => {
527
+ const cell = graph.getCell(id);
528
+ if (cell) {
529
+ cell.set('z', initialZIndices[id], { ui: true });
530
+ }
531
+ });
532
+ }
533
+ // Revert the original parent
534
+ const parent = graph.getCell(initialParentId);
535
+ if (parent) {
536
+ parent.embed(element, { ui: true });
537
+ }
538
+ break;
539
+ }
540
+ }
541
+ },
542
+
543
+ getDelegatedView: function() {
544
+
545
+ var view = this;
546
+ var model = view.model;
547
+ var paper = view.paper;
548
+
549
+ while (view) {
550
+ if (model.isLink()) break;
551
+ if (!model.isEmbedded() || view.can('stopDelegation')) return view;
552
+ model = model.getParentCell();
553
+ view = paper.findViewByModel(model);
554
+ }
555
+
556
+ return null;
557
+ },
558
+
559
+ findProxyNode: function(el, type) {
560
+ el || (el = this.el);
561
+ const nodeSelector = el.getAttribute(`${type}-selector`);
562
+ if (nodeSelector) {
563
+ const port = this.findAttribute('port', el);
564
+ if (port) {
565
+ const proxyPortNode = this.findPortNode(port, nodeSelector);
566
+ if (proxyPortNode) return proxyPortNode;
567
+ } else {
568
+ const proxyNode = this.findNode(nodeSelector);
569
+ if (proxyNode) return proxyNode;
570
+ }
571
+ }
572
+ return el;
573
+ },
574
+
575
+ // Interaction. The controller part.
576
+ // ---------------------------------
577
+
578
+ notifyPointerdown(evt, x, y) {
579
+ CellView.prototype.pointerdown.call(this, evt, x, y);
580
+ this.notify('element:pointerdown', evt, x, y);
581
+ },
582
+
583
+ notifyPointermove(evt, x, y) {
584
+ CellView.prototype.pointermove.call(this, evt, x, y);
585
+ this.notify('element:pointermove', evt, x, y);
586
+ },
587
+
588
+ notifyPointerup(evt, x, y) {
589
+ this.notify('element:pointerup', evt, x, y);
590
+ CellView.prototype.pointerup.call(this, evt, x, y);
591
+ },
592
+
593
+ pointerdblclick: function(evt, x, y) {
594
+
595
+ CellView.prototype.pointerdblclick.apply(this, arguments);
596
+ this.notify('element:pointerdblclick', evt, x, y);
597
+ },
598
+
599
+ pointerclick: function(evt, x, y) {
600
+
601
+ CellView.prototype.pointerclick.apply(this, arguments);
602
+ this.notify('element:pointerclick', evt, x, y);
603
+ },
604
+
605
+ contextmenu: function(evt, x, y) {
606
+
607
+ CellView.prototype.contextmenu.apply(this, arguments);
608
+ this.notify('element:contextmenu', evt, x, y);
609
+ },
610
+
611
+ pointerdown: function(evt, x, y) {
612
+
613
+ this.notifyPointerdown(evt, x, y);
614
+ this.dragStart(evt, x, y);
615
+ },
616
+
617
+ pointermove: function(evt, x, y) {
618
+
619
+ const data = this.eventData(evt);
620
+ const { targetMagnet, action, delegatedView } = data;
621
+
622
+ if (targetMagnet) {
623
+ this.magnetpointermove(evt, targetMagnet, x, y);
624
+ }
625
+
626
+ switch (action) {
627
+ case DragActions.MAGNET:
628
+ this.dragMagnet(evt, x, y);
629
+ break;
630
+ case DragActions.MOVE:
631
+ (delegatedView || this).drag(evt, x, y);
632
+ // eslint: no-fallthrough=false
633
+ default:
634
+ if (data.preventPointerEvents) break;
635
+ this.notifyPointermove(evt, x, y);
636
+ break;
637
+ }
638
+
639
+ // Make sure the element view data is passed along.
640
+ // It could have been wiped out in the handlers above.
641
+ this.eventData(evt, data);
642
+ },
643
+
644
+ pointerup: function(evt, x, y) {
645
+
646
+ const data = this.eventData(evt);
647
+ const { targetMagnet, action, delegatedView } = data;
648
+
649
+ if (targetMagnet) {
650
+ this.magnetpointerup(evt, targetMagnet, x, y);
651
+ }
652
+
653
+ switch (action) {
654
+ case DragActions.MAGNET:
655
+ this.dragMagnetEnd(evt, x, y);
656
+ break;
657
+ case DragActions.MOVE:
658
+ (delegatedView || this).dragEnd(evt, x, y);
659
+ // eslint: no-fallthrough=false
660
+ default:
661
+ if (data.preventPointerEvents) break;
662
+ this.notifyPointerup(evt, x, y);
663
+ }
664
+
665
+ if (targetMagnet) {
666
+ this.magnetpointerclick(evt, targetMagnet, x, y);
667
+ }
668
+
669
+ this.checkMouseleave(evt);
670
+ },
671
+
672
+ mouseover: function(evt) {
673
+
674
+ CellView.prototype.mouseover.apply(this, arguments);
675
+ this.notify('element:mouseover', evt);
676
+ },
677
+
678
+ mouseout: function(evt) {
679
+
680
+ CellView.prototype.mouseout.apply(this, arguments);
681
+ this.notify('element:mouseout', evt);
682
+ },
683
+
684
+ mouseenter: function(evt) {
685
+
686
+ CellView.prototype.mouseenter.apply(this, arguments);
687
+ this.notify('element:mouseenter', evt);
688
+ },
689
+
690
+ mouseleave: function(evt) {
691
+
692
+ CellView.prototype.mouseleave.apply(this, arguments);
693
+ this.notify('element:mouseleave', evt);
694
+ },
695
+
696
+ mousewheel: function(evt, x, y, delta) {
697
+
698
+ CellView.prototype.mousewheel.apply(this, arguments);
699
+ this.notify('element:mousewheel', evt, x, y, delta);
700
+ },
701
+
702
+ onmagnet: function(evt, x, y) {
703
+
704
+ const { currentTarget: targetMagnet } = evt;
705
+ this.magnetpointerdown(evt, targetMagnet, x, y);
706
+ this.eventData(evt, { targetMagnet });
707
+ this.dragMagnetStart(evt, x, y);
708
+ },
709
+
710
+ magnetpointerdown: function(evt, magnet, x, y) {
711
+
712
+ this.notify('element:magnet:pointerdown', evt, magnet, x, y);
713
+ },
714
+
715
+ magnetpointermove: function(evt, magnet, x, y) {
716
+
717
+ this.notify('element:magnet:pointermove', evt, magnet, x, y);
718
+ },
719
+
720
+ magnetpointerup: function(evt, magnet, x, y) {
721
+
722
+ this.notify('element:magnet:pointerup', evt, magnet, x, y);
723
+ },
724
+
725
+ magnetpointerdblclick: function(evt, magnet, x, y) {
726
+
727
+ this.notify('element:magnet:pointerdblclick', evt, magnet, x, y);
728
+ },
729
+
730
+ magnetcontextmenu: function(evt, magnet, x, y) {
731
+
732
+ this.notify('element:magnet:contextmenu', evt, magnet, x, y);
733
+ },
734
+
735
+ // Drag Start Handlers
736
+
737
+ dragStart: function(evt, x, y) {
738
+
739
+ if (this.isDefaultInteractionPrevented(evt)) return;
740
+
741
+ var view = this.getDelegatedView();
742
+ if (!view || !view.can('elementMove')) return;
743
+
744
+ this.eventData(evt, {
745
+ action: DragActions.MOVE,
746
+ delegatedView: view
747
+ });
748
+
749
+ const position = view.model.position();
750
+ view.eventData(evt, {
751
+ initialPosition: position,
752
+ pointerOffset: position.difference(x, y),
753
+ restrictedArea: this.paper.getRestrictedArea(view, x, y)
754
+ });
755
+ },
756
+
757
+ dragMagnetStart: function(evt, x, y) {
758
+
759
+ const { paper } = this;
760
+ const isPropagationAlreadyStopped = evt.isPropagationStopped();
761
+ if (isPropagationAlreadyStopped) {
762
+ // Special case when the propagation was already stopped
763
+ // on the `element:magnet:pointerdown` event.
764
+ // Do not trigger any `element:pointer*` events
765
+ // but still start the magnet dragging.
766
+ this.eventData(evt, { preventPointerEvents: true });
767
+ }
768
+
769
+ if (this.isDefaultInteractionPrevented(evt) || !this.can('addLinkFromMagnet')) {
770
+ // Stop the default action, which is to start dragging a link.
771
+ return;
772
+ }
773
+
774
+ const { targetMagnet = evt.currentTarget } = this.eventData(evt);
775
+ evt.stopPropagation();
776
+
777
+ // Invalid (Passive) magnet. Start dragging the element.
778
+ if (!paper.options.validateMagnet.call(paper, this, targetMagnet, evt)) {
779
+ if (isPropagationAlreadyStopped) {
780
+ // Do not trigger `element:pointerdown` and start element dragging
781
+ // if the propagation was stopped.
782
+ this.dragStart(evt, x, y);
783
+ // The `element:pointerdown` event is not triggered because
784
+ // of `preventPointerEvents` flag.
785
+ } else {
786
+ // We need to reset the action
787
+ // to `MOVE` so that the element is dragged.
788
+ this.pointerdown(evt, x, y);
789
+ }
790
+ return;
791
+ }
792
+
793
+ // Valid magnet. Start dragging a link.
794
+ if (paper.options.magnetThreshold <= 0) {
795
+ this.dragLinkStart(evt, targetMagnet, x, y);
796
+ }
797
+ this.eventData(evt, { action: DragActions.MAGNET });
798
+ },
799
+
800
+ // Drag Handlers
801
+
802
+ drag: function(evt, x, y) {
803
+
804
+ var paper = this.paper;
805
+ var grid = paper.options.gridSize;
806
+ var element = this.model;
807
+ var data = this.eventData(evt);
808
+ var { pointerOffset, restrictedArea, embedding } = data;
809
+
810
+ // Make sure the new element's position always snaps to the current grid
811
+ var elX = snapToGrid(x + pointerOffset.x, grid);
812
+ var elY = snapToGrid(y + pointerOffset.y, grid);
813
+
814
+ element.position(elX, elY, { restrictedArea, deep: true, ui: true });
815
+
816
+ if (paper.options.embeddingMode) {
817
+ if (!embedding) {
818
+ // Prepare the element for embedding only if the pointer moves.
819
+ // We don't want to do unnecessary action with the element
820
+ // if an user only clicks/dblclicks on it.
821
+ this.prepareEmbedding(data);
822
+ embedding = true;
823
+ }
824
+ this.processEmbedding(data, evt, x, y);
825
+ }
826
+
827
+ this.eventData(evt, {
828
+ embedding
829
+ });
830
+ },
831
+
832
+ dragMagnet: function(evt, x, y) {
833
+ this.dragLink(evt, x, y);
834
+ },
835
+
836
+ // Drag End Handlers
837
+
838
+ dragEnd: function(evt, x, y) {
839
+
840
+ var data = this.eventData(evt);
841
+ if (data.embedding) this.finalizeEmbedding(data);
842
+ },
843
+
844
+ dragMagnetEnd: function(evt, x, y) {
845
+ this.dragLinkEnd(evt, x, y);
846
+ },
847
+
848
+ magnetpointerclick: function(evt, magnet, x, y) {
849
+ var paper = this.paper;
850
+ if (paper.eventData(evt).mousemoved > paper.options.clickThreshold) return;
851
+ this.notify('element:magnet:pointerclick', evt, magnet, x, y);
852
+ }
853
+
854
+ }, {
855
+
856
+ Flags: Flags,
857
+ });
858
+
859
+ assign(ElementView.prototype, elementViewPortPrototype);