@joint/core 4.0.4 → 4.1.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 (54) hide show
  1. package/README.md +0 -8
  2. package/dist/geometry.js +4962 -6132
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +328 -50
  5. package/dist/joint.js +34067 -37565
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +34067 -37565
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +7288 -8907
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +10 -15
  13. package/src/{linkTools → cellTools}/Button.mjs +8 -6
  14. package/src/{elementTools → cellTools}/Control.mjs +3 -3
  15. package/src/{linkTools → cellTools}/HoverConnect.mjs +1 -1
  16. package/src/dia/Cell.mjs +60 -33
  17. package/src/dia/CellView.mjs +75 -8
  18. package/src/dia/ElementView.mjs +13 -8
  19. package/src/dia/Graph.mjs +148 -40
  20. package/src/dia/HighlighterView.mjs +8 -4
  21. package/src/dia/LinkView.mjs +42 -3
  22. package/src/dia/Paper.mjs +84 -0
  23. package/src/dia/ToolView.mjs +29 -4
  24. package/src/dia/ToolsView.mjs +25 -10
  25. package/src/dia/attributes/connection.mjs +5 -0
  26. package/src/dia/attributes/defs.mjs +3 -0
  27. package/src/dia/attributes/eval.mjs +3 -3
  28. package/src/dia/attributes/index.mjs +3 -0
  29. package/src/dia/attributes/shape.mjs +4 -0
  30. package/src/dia/attributes/text.mjs +15 -5
  31. package/src/dia/ports.mjs +4 -0
  32. package/src/elementTools/HoverConnect.mjs +5 -5
  33. package/src/elementTools/index.mjs +5 -4
  34. package/src/g/rect.mjs +13 -5
  35. package/src/layout/ports/port.mjs +4 -5
  36. package/src/linkTools/Anchor.mjs +1 -1
  37. package/src/linkTools/Arrowhead.mjs +2 -1
  38. package/src/linkTools/RotateLabel.mjs +110 -0
  39. package/src/linkTools/Segments.mjs +1 -1
  40. package/src/linkTools/Vertices.mjs +41 -4
  41. package/src/linkTools/index.mjs +7 -4
  42. package/src/mvc/View.mjs +0 -1
  43. package/src/mvc/ViewBase.mjs +2 -1
  44. package/src/routers/rightAngle.mjs +538 -140
  45. package/src/shapes/standard.mjs +8 -1
  46. package/src/{dia/attributes → util}/calc.mjs +24 -12
  47. package/src/util/index.mjs +1 -0
  48. package/src/util/util.mjs +39 -0
  49. package/src/util/utilHelpers.mjs +2 -1
  50. package/types/geometry.d.ts +6 -1
  51. package/types/joint.d.ts +321 -48
  52. /package/src/{linkTools → cellTools}/Boundary.mjs +0 -0
  53. /package/src/{linkTools → cellTools}/Connect.mjs +0 -0
  54. /package/src/{linkTools → cellTools}/helpers.mjs +0 -0
@@ -1,4 +1,3 @@
1
- import { evalCalcAttribute, isCalcAttribute } from '../dia/attributes/calc.mjs';
2
1
  import { ToolView } from '../dia/ToolView.mjs';
3
2
  import { getViewBBox } from './helpers.mjs';
4
3
  import * as util from '../util/index.mjs';
@@ -41,13 +40,13 @@ export const Button = ToolView.extend({
41
40
  const { x: offsetX = 0, y: offsetY = 0 } = offset;
42
41
  if (util.isPercentage(x)) {
43
42
  x = parseFloat(x) / 100 * bbox.width;
44
- } else if (isCalcAttribute(x)) {
45
- x = Number(evalCalcAttribute(x, bbox));
43
+ } else if (util.isCalcExpression(x)) {
44
+ x = Number(util.evalCalcExpression(x, bbox));
46
45
  }
47
46
  if (util.isPercentage(y)) {
48
47
  y = parseFloat(y) / 100 * bbox.height;
49
- } else if (isCalcAttribute(y)) {
50
- y = Number(evalCalcAttribute(y, bbox));
48
+ } else if (util.isCalcExpression(y)) {
49
+ y = Number(util.evalCalcExpression(y, bbox));
51
50
  }
52
51
  let matrix = V.createSVGMatrix().translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
53
52
  if (rotate) matrix = matrix.rotate(angle);
@@ -57,7 +56,10 @@ export const Button = ToolView.extend({
57
56
  },
58
57
  getLinkMatrix() {
59
58
  const { relatedView: view, options } = this;
60
- const { offset = 0, distance = 0, rotate, scale } = options;
59
+ const { offset = 0, distance: distanceOpt = 0, rotate, scale } = options;
60
+ const distance = (typeof distanceOpt === 'function')
61
+ ? distanceOpt.call(this, view, this)
62
+ : distanceOpt;
61
63
  let tangent, position, angle;
62
64
  if (util.isPercentage(distance)) {
63
65
  tangent = view.getTangentAtRatio(parseFloat(distance) / 100);
@@ -133,7 +133,7 @@ export const Control = ToolView.extend({
133
133
  const { clientX, clientY } = util.normalizeEvent(evt);
134
134
  const coords = paper.clientToLocalPoint(clientX, clientY);
135
135
  const relativeCoords = model.getRelativePointFromAbsolute(coords);
136
- this.setPosition(relatedView, relativeCoords, this);
136
+ this.setPosition(relatedView, relativeCoords, evt);
137
137
  this.update();
138
138
  },
139
139
  onPointerUp: function(_evt) {
@@ -144,9 +144,9 @@ export const Control = ToolView.extend({
144
144
  this.toggleExtras(false);
145
145
  relatedView.model.stopBatch('control-move', { ui: true, tool: this.cid });
146
146
  },
147
- onPointerDblClick: function() {
147
+ onPointerDblClick: function(evt) {
148
148
  const { relatedView } = this;
149
- this.resetPosition(relatedView, this);
149
+ this.resetPosition(relatedView, evt);
150
150
  this.update();
151
151
  }
152
152
 
@@ -1,4 +1,4 @@
1
- import { Connect } from '../linkTools/Connect.mjs';
1
+ import { Connect } from './Connect.mjs';
2
2
  import V from '../V/index.mjs';
3
3
  import $ from '../mvc/Dom/index.mjs';
4
4
  import * as util from '../util/index.mjs';
package/src/dia/Cell.mjs CHANGED
@@ -25,7 +25,8 @@ import {
25
25
  defaultsDeep,
26
26
  has,
27
27
  sortBy,
28
- defaults
28
+ defaults,
29
+ objectDifference
29
30
  } from '../util/util.mjs';
30
31
  import { Model } from '../mvc/Model.mjs';
31
32
  import { cloneCells } from '../util/cloneCells.mjs';
@@ -42,6 +43,22 @@ const attributesMerger = function(a, b) {
42
43
  }
43
44
  };
44
45
 
46
+ function removeEmptyAttributes(obj) {
47
+
48
+ // Remove toplevel empty attributes
49
+ for (const key in obj) {
50
+
51
+ const objValue = obj[key];
52
+ const isRealObject = isObject(objValue) && !Array.isArray(objValue);
53
+
54
+ if (!isRealObject) continue;
55
+
56
+ if (isEmpty(objValue)) {
57
+ delete obj[key];
58
+ }
59
+ }
60
+ }
61
+
45
62
  export const Cell = Model.extend({
46
63
 
47
64
  // This is the same as mvc.Model with the only difference that is uses util.merge
@@ -75,48 +92,45 @@ export const Cell = Model.extend({
75
92
  throw new Error('Must define a translate() method.');
76
93
  },
77
94
 
78
- toJSON: function() {
95
+ toJSON: function(opt) {
79
96
 
97
+ const { ignoreDefaults, ignoreEmptyAttributes = false } = opt || {};
80
98
  const defaults = result(this.constructor.prototype, 'defaults');
81
- const defaultAttrs = defaults.attrs || {};
82
- const attrs = this.attributes.attrs;
83
- const finalAttrs = {};
84
-
85
- // Loop through all the attributes and
86
- // omit the default attributes as they are implicitly reconstructible by the cell 'type'.
87
- forIn(attrs, function(attr, selector) {
88
99
 
89
- const defaultAttr = defaultAttrs[selector];
100
+ if (ignoreDefaults === false) {
101
+ // Return all attributes without omitting the defaults
102
+ const finalAttributes = cloneDeep(this.attributes);
90
103
 
91
- forIn(attr, function(value, name) {
104
+ if (!ignoreEmptyAttributes) return finalAttributes;
92
105
 
93
- // attr is mainly flat though it might have one more level (consider the `style` attribute).
94
- // Check if the `value` is object and if yes, go one level deep.
95
- if (isObject(value) && !Array.isArray(value)) {
106
+ removeEmptyAttributes(finalAttributes);
96
107
 
97
- forIn(value, function(value2, name2) {
98
-
99
- if (!defaultAttr || !defaultAttr[name] || !isEqual(defaultAttr[name][name2], value2)) {
108
+ return finalAttributes;
109
+ }
100
110
 
101
- finalAttrs[selector] = finalAttrs[selector] || {};
102
- (finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2;
103
- }
104
- });
111
+ let defaultAttributes = {};
112
+ let attributes = cloneDeep(this.attributes);
105
113
 
106
- } else if (!defaultAttr || !isEqual(defaultAttr[name], value)) {
107
- // `value` is not an object, default attribute for such a selector does not exist
108
- // or it is different than the attribute value set on the model.
114
+ if (ignoreDefaults === true) {
115
+ // Compare all attributes with the defaults
116
+ defaultAttributes = defaults;
117
+ } else {
118
+ // Compare only the specified attributes with the defaults, use `attrs` as a default if not specified
119
+ const differentiateKeys = Array.isArray(ignoreDefaults) ? ignoreDefaults : ['attrs'];
109
120
 
110
- finalAttrs[selector] = finalAttrs[selector] || {};
111
- finalAttrs[selector][name] = value;
112
- }
121
+ differentiateKeys.forEach((key) => {
122
+ defaultAttributes[key] = defaults[key] || {};
113
123
  });
114
- });
124
+ }
125
+
126
+ // Omit `id` and `type` attribute from the defaults since it should be always present
127
+ const finalAttributes = objectDifference(attributes, omit(defaultAttributes, 'id', 'type'), { maxDepth: 4 });
115
128
 
116
- const attributes = cloneDeep(omit(this.attributes, 'attrs'));
117
- attributes.attrs = finalAttrs;
129
+ if (ignoreEmptyAttributes) {
130
+ removeEmptyAttributes(finalAttributes);
131
+ }
118
132
 
119
- return attributes;
133
+ return finalAttributes;
120
134
  },
121
135
 
122
136
  initialize: function(options) {
@@ -325,12 +339,25 @@ export const Cell = Model.extend({
325
339
  return this.set('parent', parent, opt);
326
340
  },
327
341
 
328
- embed: function(cell, opt) {
342
+ embed: function(cell, opt = {}) {
329
343
  const cells = Array.isArray(cell) ? cell : [cell];
330
344
  if (!this.canEmbed(cells)) {
331
345
  throw new Error('Recursive embedding not allowed.');
332
346
  }
333
- if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
347
+ if (opt.reparent) {
348
+ const parents = uniq(cells.map(c => c.getParentCell()));
349
+
350
+ // Unembed cells from their current parents.
351
+ parents.forEach((parent) => {
352
+ // Cell doesn't have to be embedded.
353
+ if (!parent) return;
354
+
355
+ // Pass all the `cells` since the `dia.Cell._unembedCells` method can handle cases
356
+ // where not all elements of `cells` are embedded in the same parent.
357
+ parent._unembedCells(cells, opt);
358
+ });
359
+
360
+ } else if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
334
361
  throw new Error('Embedding of already embedded cells is not allowed.');
335
362
  }
336
363
  this._embedCells(cells, opt);
@@ -16,7 +16,7 @@ import {
16
16
  merge,
17
17
  uniq
18
18
  } from '../util/index.mjs';
19
- import { Point, Rect } from '../g/index.mjs';
19
+ import { Point, Rect, intersection } from '../g/index.mjs';
20
20
  import V from '../V/index.mjs';
21
21
  import $ from '../mvc/Dom/index.mjs';
22
22
  import { HighlighterView } from './HighlighterView.mjs';
@@ -559,13 +559,54 @@ export const CellView = View.extend({
559
559
  if (!rawAttrs.hasOwnProperty(attrName)) continue;
560
560
  attrVal = rawAttrs[attrName];
561
561
  def = this.getAttributeDefinition(attrName);
562
- if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, rawAttrs, this))) {
563
- if (isString(def.set)) {
564
- normalAttrs || (normalAttrs = {});
565
- normalAttrs[def.set] = attrVal;
566
- }
567
- if (attrVal !== null) {
568
- relatives.push(attrName, def);
562
+ if (def) {
563
+ if (attrVal === null) {
564
+ // Assign the unset attribute name.
565
+ let unsetAttrName;
566
+ if (isFunction(def.unset)) {
567
+ unsetAttrName = def.unset.call(this, node, rawAttrs, this);
568
+ } else {
569
+ unsetAttrName = def.unset;
570
+ }
571
+ if (!unsetAttrName && isString(def.set)) {
572
+ // We unset an alias attribute.
573
+ unsetAttrName = def.set;
574
+ }
575
+ if (!unsetAttrName) {
576
+ // There is no alias for the attribute. We unset the attribute itself.
577
+ unsetAttrName = attrName;
578
+ }
579
+ // Unset the attribute.
580
+ if (isString(unsetAttrName) && unsetAttrName) {
581
+ // Unset a single attribute.
582
+ normalAttrs || (normalAttrs = {});
583
+ // values takes precedence over unset values
584
+ if (unsetAttrName in normalAttrs) continue;
585
+ normalAttrs[unsetAttrName] = attrVal;
586
+ } else if (Array.isArray(unsetAttrName) && unsetAttrName.length > 0) {
587
+ // Unset multiple attributes.
588
+ normalAttrs || (normalAttrs = {});
589
+ for (i = 0, n = unsetAttrName.length; i < n; i++) {
590
+ const attrName = unsetAttrName[i];
591
+ // values takes precedence over unset values
592
+ if (attrName in normalAttrs) continue;
593
+ normalAttrs[attrName] = attrVal;
594
+ }
595
+ }
596
+ // The unset value is neither a string nor an array.
597
+ // The attribute is not unset.
598
+ } else {
599
+ if (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, rawAttrs, this)) {
600
+ if (isString(def.set)) {
601
+ // An alias e.g 'xlink:href' -> 'href'
602
+ normalAttrs || (normalAttrs = {});
603
+ normalAttrs[def.set] = attrVal;
604
+ }
605
+ relatives.push(attrName, def);
606
+ } else {
607
+ normalAttrs || (normalAttrs = {});
608
+ normalAttrs[attrName] = attrVal;
609
+ }
569
610
  }
570
611
  } else {
571
612
  normalAttrs || (normalAttrs = {});
@@ -727,6 +768,12 @@ export const CellView = View.extend({
727
768
  this.metrics = {};
728
769
  },
729
770
 
771
+ cleanNodeCache: function(node) {
772
+ const id = node.id;
773
+ if (!id) return;
774
+ delete this.metrics[id];
775
+ },
776
+
730
777
  nodeCache: function(magnet) {
731
778
 
732
779
  var metrics = this.metrics;
@@ -1279,7 +1326,27 @@ export const CellView = View.extend({
1279
1326
  setInteractivity: function(value) {
1280
1327
 
1281
1328
  this.options.interactive = value;
1329
+ },
1330
+
1331
+ isIntersecting: function(geometryShape, geometryData) {
1332
+ return intersection.exists(geometryShape, this.getNodeBBox(this.el), geometryData);
1333
+ },
1334
+
1335
+ isEnclosedIn: function(geometryRect) {
1336
+ return geometryRect.containsRect(this.getNodeBBox(this.el));
1337
+ },
1338
+
1339
+ isInArea: function(geometryRect, options = {}) {
1340
+ if (options.strict) {
1341
+ return this.isEnclosedIn(geometryRect);
1342
+ }
1343
+ return this.isIntersecting(geometryRect);
1344
+ },
1345
+
1346
+ isAtPoint: function(point, options) {
1347
+ return this.getNodeBBox(this.el).containsPoint(point, options);
1282
1348
  }
1349
+
1283
1350
  }, {
1284
1351
 
1285
1352
  Flags,
@@ -265,16 +265,16 @@ export const ElementView = CellView.extend({
265
265
 
266
266
  getTranslateString: function() {
267
267
 
268
- var position = this.model.attributes.position;
269
- return 'translate(' + position.x + ',' + position.y + ')';
268
+ const { x, y } = this.model.position();
269
+ return `translate(${x},${y})`;
270
270
  },
271
271
 
272
272
  getRotateString: function() {
273
- var attributes = this.model.attributes;
274
- var angle = attributes.angle;
273
+
274
+ const angle = this.model.angle();
275
275
  if (!angle) return null;
276
- var size = attributes.size;
277
- return 'rotate(' + angle + ',' + (size.width / 2) + ',' + (size.height / 2) + ')';
276
+ const { width, height } = this.model.size();
277
+ return `rotate(${angle},${width / 2},${height / 2})`;
278
278
  },
279
279
 
280
280
  // Rotatable & Scalable Group
@@ -404,9 +404,9 @@ export const ElementView = CellView.extend({
404
404
  if (isFunction(findParentBy)) {
405
405
  candidates = toArray(findParentBy.call(graph, this, evt, x, y));
406
406
  } else if (findParentBy === 'pointer') {
407
- candidates = toArray(graph.findModelsFromPoint({ x, y }));
407
+ candidates = graph.findElementsAtPoint({ x, y });
408
408
  } else {
409
- candidates = graph.findModelsUnderElement(model, { searchBy: findParentBy });
409
+ candidates = graph.findElementsUnderElement(model, { searchBy: findParentBy });
410
410
  }
411
411
 
412
412
  candidates = candidates.filter((el) => {
@@ -540,6 +540,11 @@ export const ElementView = CellView.extend({
540
540
  }
541
541
  },
542
542
 
543
+ getTargetParentView: function(evt) {
544
+ const { candidateEmbedView = null } = this.eventData(evt);
545
+ return candidateEmbedView;
546
+ },
547
+
543
548
  getDelegatedView: function() {
544
549
 
545
550
  var view = this;
package/src/dia/Graph.mjs CHANGED
@@ -35,14 +35,25 @@ const GraphCells = Collection.extend({
35
35
  throw new Error(`dia.Graph: Could not find cell constructor for type: '${type}'. Make sure to add the constructor to 'cellNamespace'.`);
36
36
  }
37
37
 
38
- const cell = new ModelClass(attrs, opt);
39
- // Add a reference to the graph. It is necessary to do this here because this is the earliest place
40
- // where a new model is created from a plain JS object. For other objects, see `joint.dia.Graph>>_prepareCell()`.
41
- if (!opt.dry) {
42
- cell.graph = collection.graph;
38
+ return new ModelClass(attrs, opt);
39
+ },
40
+
41
+ _addReference: function(model, options) {
42
+ Collection.prototype._addReference.apply(this, arguments);
43
+ // If not in `dry` mode and the model does not have a graph reference yet,
44
+ // set the reference.
45
+ if (!options.dry && !model.graph) {
46
+ model.graph = this.graph;
43
47
  }
48
+ },
44
49
 
45
- return cell;
50
+ _removeReference: function(model, options) {
51
+ Collection.prototype._removeReference.apply(this, arguments);
52
+ // If not in `dry` mode and the model has a reference to this exact graph,
53
+ // remove the reference.
54
+ if (!options.dry && model.graph === this.graph) {
55
+ model.graph = null;
56
+ }
46
57
  },
47
58
 
48
59
  // `comparator` makes it easy to sort cells based on their `z` index.
@@ -197,12 +208,12 @@ export const Graph = Model.extend({
197
208
  return (this._in && this._in[node]) || {};
198
209
  },
199
210
 
200
- toJSON: function() {
211
+ toJSON: function(opt = {}) {
201
212
 
202
213
  // JointJS does not recursively call `toJSON()` on attributes that are themselves models/collections.
203
214
  // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
204
215
  var json = Model.prototype.toJSON.apply(this, arguments);
205
- json.cells = this.get('cells').toJSON();
216
+ json.cells = this.get('cells').toJSON(opt.cellAttributes);
206
217
  return json;
207
218
  },
208
219
 
@@ -268,20 +279,12 @@ export const Graph = Model.extend({
268
279
  return this;
269
280
  },
270
281
 
271
- _prepareCell: function(cell, opt) {
282
+ _prepareCell: function(cell) {
272
283
 
273
- var attrs;
284
+ let attrs;
274
285
  if (cell instanceof Model) {
275
286
  attrs = cell.attributes;
276
- if (!cell.graph && (!opt || !opt.dry)) {
277
- // An element can not be member of more than one graph.
278
- // A cell stops being the member of the graph after it's explicitly removed.
279
- cell.graph = this;
280
- }
281
287
  } else {
282
- // In case we're dealing with a plain JS object, we have to set the reference
283
- // to the `graph` right after the actual model is created. This happens in the `model()` function
284
- // of `joint.dia.GraphCells`.
285
288
  attrs = cell;
286
289
  }
287
290
 
@@ -391,11 +394,39 @@ export const Graph = Model.extend({
391
394
  // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events
392
395
  // would be triggered on the graph model.
393
396
  this.get('cells').remove(cell, { silent: true });
397
+ },
394
398
 
395
- if (cell.graph === this) {
396
- // Remove the element graph reference only if the cell is the member of this graph.
397
- cell.graph = null;
398
- }
399
+ transferCellEmbeds: function(sourceCell, targetCell, opt = {}) {
400
+
401
+ const batchName = 'transfer-embeds';
402
+ this.startBatch(batchName);
403
+
404
+ // Embed children of the source cell in the target cell.
405
+ const children = sourceCell.getEmbeddedCells();
406
+ targetCell.embed(children, { ...opt, reparent: true });
407
+
408
+ this.stopBatch(batchName);
409
+ },
410
+
411
+ transferCellConnectedLinks: function(sourceCell, targetCell, opt = {}) {
412
+
413
+ const batchName = 'transfer-connected-links';
414
+ this.startBatch(batchName);
415
+
416
+ // Reconnect all the links connected to the old cell to the new cell.
417
+ const connectedLinks = this.getConnectedLinks(sourceCell, opt);
418
+ connectedLinks.forEach((link) => {
419
+
420
+ if (link.getSourceCell() === sourceCell) {
421
+ link.prop(['source', 'id'], targetCell.id, opt);
422
+ }
423
+
424
+ if (link.getTargetCell() === sourceCell) {
425
+ link.prop(['target', 'id'], targetCell.id, opt);
426
+ }
427
+ });
428
+
429
+ this.stopBatch(batchName);
399
430
  },
400
431
 
401
432
  // Get a cell by `id`.
@@ -961,28 +992,105 @@ export const Graph = Model.extend({
961
992
  util.invoke(this.getConnectedLinks(model), 'remove', opt);
962
993
  },
963
994
 
964
- // Find all elements at given point
965
- findModelsFromPoint: function(p) {
966
- return this.getElements().filter(el => el.getBBox({ rotate: true }).containsPoint(p));
995
+ // Find all cells at given point
996
+
997
+ findElementsAtPoint: function(point, opt) {
998
+ return this._filterAtPoint(this.getElements(), point, opt);
999
+ },
1000
+
1001
+ findLinksAtPoint: function(point, opt) {
1002
+ return this._filterAtPoint(this.getLinks(), point, opt);
1003
+ },
1004
+
1005
+ findCellsAtPoint: function(point, opt) {
1006
+ return this._filterAtPoint(this.getCells(), point, opt);
1007
+ },
1008
+
1009
+ _filterAtPoint: function(cells, point, opt = {}) {
1010
+ return cells.filter(el => el.getBBox({ rotate: true }).containsPoint(point, opt));
1011
+ },
1012
+
1013
+ // Find all cells in given area
1014
+
1015
+ findElementsInArea: function(area, opt = {}) {
1016
+ return this._filterInArea(this.getElements(), area, opt);
1017
+ },
1018
+
1019
+ findLinksInArea: function(area, opt = {}) {
1020
+ return this._filterInArea(this.getLinks(), area, opt);
967
1021
  },
968
1022
 
969
- // Find all elements in given area
970
- findModelsInArea: function(rect, opt = {}) {
971
- const r = new g.Rect(rect);
1023
+ findCellsInArea: function(area, opt = {}) {
1024
+ return this._filterInArea(this.getCells(), area, opt);
1025
+ },
1026
+
1027
+ _filterInArea: function(cells, area, opt = {}) {
1028
+ const r = new g.Rect(area);
972
1029
  const { strict = false } = opt;
973
1030
  const method = strict ? 'containsRect' : 'intersect';
974
- return this.getElements().filter(el => r[method](el.getBBox({ rotate: true })));
975
- },
976
-
977
- // Find all elements under the given element.
978
- findModelsUnderElement: function(element, opt = {}) {
979
- const { searchBy = 'bbox' } = opt;
980
- const bbox = element.getBBox().rotateAroundCenter(element.angle());
981
- const elements = (searchBy === 'bbox')
982
- ? this.findModelsInArea(bbox)
983
- : this.findModelsFromPoint(util.getRectPoint(bbox, searchBy));
984
- // don't account element itself or any of its descendants
985
- return elements.filter(el => element.id !== el.id && !el.isEmbeddedIn(element));
1031
+ return cells.filter(el => r[method](el.getBBox({ rotate: true })));
1032
+ },
1033
+
1034
+ // Find all cells under the given element.
1035
+
1036
+ findElementsUnderElement: function(element, opt) {
1037
+ return this._filterCellsUnderElement(this.getElements(), element, opt);
1038
+ },
1039
+
1040
+ findLinksUnderElement: function(element, opt) {
1041
+ return this._filterCellsUnderElement(this.getLinks(), element, opt);
1042
+ },
1043
+
1044
+ findCellsUnderElement: function(element, opt) {
1045
+ return this._filterCellsUnderElement(this.getCells(), element, opt);
1046
+ },
1047
+
1048
+ _isValidElementUnderElement: function(el1, el2) {
1049
+ return el1.id !== el2.id && !el1.isEmbeddedIn(el2);
1050
+ },
1051
+
1052
+ _isValidLinkUnderElement: function(link, el) {
1053
+ return (
1054
+ link.source().id !== el.id &&
1055
+ link.target().id !== el.id &&
1056
+ !link.isEmbeddedIn(el)
1057
+ );
1058
+ },
1059
+
1060
+ _validateCellsUnderElement: function(cells, element) {
1061
+ return cells.filter(cell => {
1062
+ return cell.isLink()
1063
+ ? this._isValidLinkUnderElement(cell, element)
1064
+ : this._isValidElementUnderElement(cell, element);
1065
+ });
1066
+ },
1067
+
1068
+ _getFindUnderElementGeometry: function(element, searchBy = 'bbox') {
1069
+ const bbox = element.getBBox({ rotate: true });
1070
+ return (searchBy !== 'bbox') ? util.getRectPoint(bbox, searchBy) : bbox;
1071
+ },
1072
+
1073
+ _filterCellsUnderElement: function(cells, element, opt = {}) {
1074
+ const geometry = this._getFindUnderElementGeometry(element, opt.searchBy);
1075
+ const filteredCells = (geometry.type === g.types.Point)
1076
+ ? this._filterAtPoint(cells, geometry)
1077
+ : this._filterInArea(cells, geometry, opt);
1078
+ return this._validateCellsUnderElement(filteredCells, element);
1079
+ },
1080
+
1081
+ // @deprecated use `findElementsInArea` instead
1082
+ findModelsInArea: function(area, opt) {
1083
+ return this.findElementsInArea(area, opt);
1084
+ },
1085
+
1086
+ // @deprecated use `findElementsAtPoint` instead
1087
+ findModelsFromPoint: function(point) {
1088
+ return this.findElementsAtPoint(point);
1089
+ },
1090
+
1091
+ // @deprecated use `findModelsUnderElement` instead
1092
+ findModelsUnderElement: function(element, opt) {
1093
+ return this.findElementsUnderElement(element, opt);
986
1094
  },
987
1095
 
988
1096
  // Return bounding box of all elements.
@@ -278,18 +278,22 @@ export const HighlighterView = mvc.View.extend({
278
278
  });
279
279
  },
280
280
 
281
- removeAll(paper, id = null) {
281
+ getAll(paper, id = null) {
282
+ const views = [];
282
283
  const { _views } = this;
283
-
284
284
  for (let cid in _views) {
285
285
  for (let hid in _views[cid]) {
286
286
  const view = _views[cid][hid];
287
-
288
287
  if (view.cellView.paper === paper && view instanceof this && (id === null || hid === id)) {
289
- view.remove();
288
+ views.push(view);
290
289
  }
291
290
  }
292
291
  }
292
+ return views;
293
+ },
294
+
295
+ removeAll(paper, id = null) {
296
+ this.getAll(paper, id).forEach(view => view.remove());
293
297
  },
294
298
 
295
299
  update(cellView, id = null, dirty = false) {