@joint/core 4.2.0-alpha.0 → 4.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -1
  2. package/dist/geometry.js +2 -2
  3. package/dist/geometry.min.js +3 -3
  4. package/dist/joint.d.ts +595 -198
  5. package/dist/joint.js +3895 -1304
  6. package/dist/joint.min.js +3 -3
  7. package/dist/joint.nowrap.js +3895 -1304
  8. package/dist/joint.nowrap.min.js +3 -3
  9. package/dist/vectorizer.js +21 -8
  10. package/dist/vectorizer.min.js +3 -3
  11. package/dist/version.mjs +1 -1
  12. package/package.json +13 -13
  13. package/src/V/index.mjs +20 -5
  14. package/src/alg/Deque.mjs +126 -0
  15. package/src/cellTools/Boundary.mjs +15 -13
  16. package/src/cellTools/Button.mjs +7 -5
  17. package/src/cellTools/Control.mjs +37 -14
  18. package/src/cellTools/HoverConnect.mjs +5 -1
  19. package/src/cellTools/helpers.mjs +44 -3
  20. package/src/config/index.mjs +11 -1
  21. package/src/dia/Cell.mjs +96 -83
  22. package/src/dia/CellCollection.mjs +136 -0
  23. package/src/dia/CellView.mjs +6 -0
  24. package/src/dia/Element.mjs +6 -5
  25. package/src/dia/ElementView.mjs +2 -1
  26. package/src/dia/Graph.mjs +610 -317
  27. package/src/dia/GraphLayer.mjs +53 -0
  28. package/src/dia/GraphLayerCollection.mjs +313 -0
  29. package/src/dia/GraphLayerView.mjs +128 -0
  30. package/src/dia/GraphLayersController.mjs +166 -0
  31. package/src/dia/GraphTopologyIndex.mjs +222 -0
  32. package/src/dia/{layers/GridLayer.mjs → GridLayerView.mjs} +23 -16
  33. package/src/dia/HighlighterView.mjs +22 -0
  34. package/src/dia/{PaperLayer.mjs → LayerView.mjs} +52 -17
  35. package/src/dia/LegacyGraphLayerView.mjs +14 -0
  36. package/src/dia/LinkView.mjs +118 -98
  37. package/src/dia/Paper.mjs +1441 -620
  38. package/src/dia/ToolView.mjs +4 -0
  39. package/src/dia/ToolsView.mjs +14 -5
  40. package/src/dia/attributes/text.mjs +4 -2
  41. package/src/dia/index.mjs +6 -1
  42. package/src/dia/ports.mjs +213 -84
  43. package/src/dia/symbols.mjs +24 -0
  44. package/src/elementTools/HoverConnect.mjs +14 -8
  45. package/src/env/index.mjs +6 -3
  46. package/src/layout/ports/port.mjs +30 -15
  47. package/src/layout/ports/portLabel.mjs +1 -1
  48. package/src/mvc/Collection.mjs +19 -19
  49. package/src/mvc/Model.mjs +13 -10
  50. package/src/mvc/View.mjs +4 -0
  51. package/src/mvc/ViewBase.mjs +1 -1
  52. package/types/geometry.d.ts +64 -60
  53. package/types/joint.d.ts +520 -137
  54. package/types/vectorizer.d.ts +11 -1
package/src/dia/Cell.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- uniqueId,
3
2
  union,
4
3
  result,
5
4
  merge,
@@ -32,7 +31,8 @@ import { Model } from '../mvc/Model.mjs';
32
31
  import { cloneCells } from '../util/cloneCells.mjs';
33
32
  import { attributes } from './attributes/index.mjs';
34
33
  import * as g from '../g/index.mjs';
35
-
34
+ import { config } from '../config/index.mjs';
35
+ import { CELL_MARKER } from './symbols.mjs';
36
36
 
37
37
  // Cell base model.
38
38
  // --------------------------
@@ -61,30 +61,21 @@ function removeEmptyAttributes(obj) {
61
61
 
62
62
  export const Cell = Model.extend({
63
63
 
64
- // This is the same as mvc.Model with the only difference that is uses util.merge
65
- // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
66
- constructor: function(attributes, options) {
67
-
68
- var defaults;
69
- var attrs = attributes || {};
70
- if (typeof this.preinitialize === 'function') {
71
- // Check to support an older version
72
- this.preinitialize.apply(this, arguments);
73
- }
74
- this.cid = uniqueId('c');
75
- this.attributes = {};
76
- if (options && options.collection) this.collection = options.collection;
77
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
78
- if ((defaults = result(this, 'defaults'))) {
79
- //<custom code>
80
- // Replaced the call to _.defaults with util.merge.
81
- const customizer = (options && options.mergeArrays === true) ? false : attributesMerger;
82
- attrs = merge({}, defaults, attrs, customizer);
83
- //</custom code>
64
+ cidPrefix: 'c',
65
+
66
+ // Default attributes are merged deeply instead of shallowly.
67
+ _setDefaults: function(ctorAttributes, options) {
68
+ let attributes;
69
+ const attributeDefaults = result(this, 'defaults');
70
+ if (attributeDefaults) {
71
+ const customizer = (options && options.mergeArrays === true)
72
+ ? false
73
+ : config.cellDefaultsMergeStrategy || attributesMerger;
74
+ attributes = merge({}, attributeDefaults, ctorAttributes, customizer);
75
+ } else {
76
+ attributes = ctorAttributes;
84
77
  }
85
- this.set(attrs, options);
86
- this.changed = {};
87
- this.initialize.apply(this, arguments);
78
+ this.set(attributes, options);
88
79
  },
89
80
 
90
81
  translate: function(dx, dy, opt) {
@@ -133,10 +124,10 @@ export const Cell = Model.extend({
133
124
  return finalAttributes;
134
125
  },
135
126
 
136
- initialize: function(options) {
127
+ initialize: function(attributes) {
137
128
 
138
129
  const idAttribute = this.getIdAttribute();
139
- if (!options || options[idAttribute] === undefined) {
130
+ if (!attributes || attributes[idAttribute] === undefined) {
140
131
  this.set(idAttribute, this.generateId(), { silent: true });
141
132
  }
142
133
 
@@ -212,42 +203,20 @@ export const Cell = Model.extend({
212
203
  },
213
204
 
214
205
  remove: function(opt = {}) {
215
-
216
- // Store the graph in a variable because `this.graph` won't be accessible
217
- // after `this.trigger('remove', ...)` down below.
218
206
  const { graph, collection } = this;
219
- if (!graph) {
207
+ // If the cell is part of a graph, remove it using the graph API.
208
+ // To make sure the cell is removed in a batch operation.
209
+ if (graph) {
210
+ graph.removeCell(this, opt);
211
+ } else {
220
212
  // The collection is a common mvc collection (not the graph collection).
221
213
  if (collection) collection.remove(this, opt);
222
- return this;
223
214
  }
224
-
225
- graph.startBatch('remove');
226
-
227
- // First, unembed this cell from its parent cell if there is one.
228
- const parentCell = this.getParentCell();
229
- if (parentCell) {
230
- parentCell.unembed(this, opt);
231
- }
232
-
233
- // Remove also all the cells, which were embedded into this cell
234
- const embeddedCells = this.getEmbeddedCells();
235
- for (let i = 0, n = embeddedCells.length; i < n; i++) {
236
- const embed = embeddedCells[i];
237
- if (embed) {
238
- embed.remove(opt);
239
- }
240
- }
241
-
242
- this.trigger('remove', this, graph.attributes.cells, opt);
243
-
244
- graph.stopBatch('remove');
245
-
246
215
  return this;
247
216
  },
248
217
 
249
218
  toFront: function(opt) {
250
- var graph = this.graph;
219
+ const { graph } = this;
251
220
  if (graph) {
252
221
  opt = defaults(opt || {}, { foregroundEmbeds: true });
253
222
 
@@ -261,12 +230,14 @@ export const Cell = Model.extend({
261
230
 
262
231
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
263
232
 
264
- const maxZ = graph.maxZIndex();
233
+ const layerId = graph.getCellLayerId(this);
234
+
235
+ const maxZ = graph.maxZIndex(layerId);
265
236
  let z = maxZ - cells.length + 1;
266
237
 
267
- const collection = graph.get('cells');
238
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
268
239
 
269
- let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== (collection.length - cells.length));
240
+ let shouldUpdate = (layerCells.indexOf(sortedCells[0]) !== (layerCells.length - cells.length));
270
241
  if (!shouldUpdate) {
271
242
  shouldUpdate = sortedCells.some(function(cell, index) {
272
243
  return cell.z() !== z + index;
@@ -290,7 +261,7 @@ export const Cell = Model.extend({
290
261
  },
291
262
 
292
263
  toBack: function(opt) {
293
- var graph = this.graph;
264
+ const { graph } = this;
294
265
  if (graph) {
295
266
  opt = defaults(opt || {}, { foregroundEmbeds: true });
296
267
 
@@ -304,11 +275,13 @@ export const Cell = Model.extend({
304
275
 
305
276
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
306
277
 
307
- let z = graph.minZIndex();
278
+ const layerId = graph.getCellLayerId(this);
308
279
 
309
- var collection = graph.get('cells');
280
+ let z = graph.minZIndex(layerId);
310
281
 
311
- let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== 0);
282
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
283
+
284
+ let shouldUpdate = (layerCells.indexOf(sortedCells[0]) !== 0);
312
285
  if (!shouldUpdate) {
313
286
  shouldUpdate = sortedCells.some(function(cell, index) {
314
287
  return cell.z() !== z + index;
@@ -543,7 +516,11 @@ export const Cell = Model.extend({
543
516
  if (!opt.deep) {
544
517
  // Shallow cloning.
545
518
 
546
- var clone = Model.prototype.clone.apply(this, arguments);
519
+ // Preserve the original's `portLayoutNamespace` and `portLabelLayoutNamespace`.
520
+ const clone = new this.constructor(this.attributes, {
521
+ portLayoutNamespace: this.portLayoutNamespace,
522
+ portLabelLayoutNamespace: this.portLabelLayoutNamespace
523
+ });
547
524
  // We don't want the clone to have the same ID as the original.
548
525
  clone.set(this.getIdAttribute(), this.generateId());
549
526
  // A shallow cloned element does not carry over the original embeds.
@@ -557,7 +534,7 @@ export const Cell = Model.extend({
557
534
  } else {
558
535
  // Deep cloning.
559
536
 
560
- // For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells.
537
+ // For a deep clone, simply call `util.cloneCells()` with the cell and all its embedded cells.
561
538
  return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true }))));
562
539
  }
563
540
  },
@@ -630,7 +607,7 @@ export const Cell = Model.extend({
630
607
  options.rewrite && unsetByPath(baseAttributes, path, '/');
631
608
 
632
609
  // Merge update with the model attributes.
633
- var attributes = merge(baseAttributes, update);
610
+ var attributes = merge(baseAttributes, update, config.cellMergeStrategy);
634
611
  // Finally, set the property to the updated attributes.
635
612
  return this.set(property, attributes[property], options);
636
613
 
@@ -653,7 +630,11 @@ export const Cell = Model.extend({
653
630
  const changedAttributes = {};
654
631
  for (const key in props) {
655
632
  // Merging the values of changed attributes with the current ones.
656
- const { changedValue } = merge({}, { changedValue: this.attributes[key] }, { changedValue: props[key] });
633
+ const { changedValue } = merge(
634
+ merge({}, { changedValue: this.attributes[key] }),
635
+ { changedValue: props[key] },
636
+ config.cellMergeStrategy
637
+ );
657
638
  changedAttributes[key] = changedValue;
658
639
  }
659
640
 
@@ -738,6 +719,7 @@ export const Cell = Model.extend({
738
719
 
739
720
  var firstFrameTime = 0;
740
721
  var interpolatingFunction;
722
+ const transitionKey = Array.isArray(path) ? path.join(delim) : path;
741
723
 
742
724
  var setter = function(runtime) {
743
725
 
@@ -748,10 +730,10 @@ export const Cell = Model.extend({
748
730
  progress = runtime / opt.duration;
749
731
 
750
732
  if (progress < 1) {
751
- this._transitionIds[path] = id = nextFrame(setter);
733
+ this._transitionIds[transitionKey] = id = nextFrame(setter);
752
734
  } else {
753
735
  progress = 1;
754
- delete this._transitionIds[path];
736
+ delete this._transitionIds[transitionKey];
755
737
  }
756
738
 
757
739
  propertyValue = interpolatingFunction(opt.timingFunction(progress));
@@ -760,7 +742,7 @@ export const Cell = Model.extend({
760
742
 
761
743
  this.prop(path, propertyValue, opt);
762
744
 
763
- if (!id) this.trigger('transition:end', this, path);
745
+ if (!id) this.trigger('transition:end', this, transitionKey);
764
746
 
765
747
  }.bind(this);
766
748
 
@@ -769,10 +751,10 @@ export const Cell = Model.extend({
769
751
 
770
752
  var initiator = (callback) => {
771
753
 
772
- if (_scheduledTransitionIds[path]) {
773
- _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
774
- if (_scheduledTransitionIds[path].length === 0) {
775
- delete _scheduledTransitionIds[path];
754
+ if (_scheduledTransitionIds[transitionKey]) {
755
+ _scheduledTransitionIds[transitionKey] = without(_scheduledTransitionIds[transitionKey], initialId);
756
+ if (_scheduledTransitionIds[transitionKey].length === 0) {
757
+ delete _scheduledTransitionIds[transitionKey];
776
758
  }
777
759
  }
778
760
 
@@ -780,16 +762,16 @@ export const Cell = Model.extend({
780
762
 
781
763
  interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
782
764
 
783
- this._transitionIds[path] = nextFrame(callback);
765
+ this._transitionIds[transitionKey] = nextFrame(callback);
784
766
 
785
- this.trigger('transition:start', this, path);
767
+ this.trigger('transition:start', this, transitionKey);
786
768
 
787
769
  };
788
770
 
789
771
  initialId = setTimeout(initiator, opt.delay, setter);
790
772
 
791
- _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
792
- _scheduledTransitionIds[path].push(initialId);
773
+ _scheduledTransitionIds[transitionKey] || (_scheduledTransitionIds[transitionKey] = []);
774
+ _scheduledTransitionIds[transitionKey].push(initialId);
793
775
 
794
776
  return initialId;
795
777
  },
@@ -805,7 +787,8 @@ export const Cell = Model.extend({
805
787
  const { _scheduledTransitionIds = {}} = this;
806
788
  let transitions = Object.keys(_scheduledTransitionIds);
807
789
  if (path) {
808
- const pathArray = path.split(delim);
790
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
791
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
809
792
  transitions = transitions.filter((key) => {
810
793
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
811
794
  });
@@ -824,7 +807,8 @@ export const Cell = Model.extend({
824
807
  const { _transitionIds = {}} = this;
825
808
  let transitions = Object.keys(_transitionIds);
826
809
  if (path) {
827
- const pathArray = path.split(delim);
810
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
811
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
828
812
  transitions = transitions.filter((key) => {
829
813
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
830
814
  });
@@ -844,7 +828,7 @@ export const Cell = Model.extend({
844
828
  return this;
845
829
  },
846
830
 
847
- // A shorcut making it easy to create constructs like the following:
831
+ // A shortcut making it easy to create constructs like the following:
848
832
  // `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
849
833
  addTo: function(graph, opt) {
850
834
 
@@ -946,15 +930,41 @@ export const Cell = Model.extend({
946
930
  .getPointRotatedAroundCenter(this.angle(), x, y)
947
931
  // Transform the absolute position into relative
948
932
  .difference(this.position());
933
+ },
934
+
935
+ layer: function(layerId, opt) {
936
+ const layerAttribute = config.layerAttribute;
937
+
938
+ // Getter:
939
+
940
+ // If `undefined` return the current layer ID
941
+ if (layerId === undefined) {
942
+ return this.get(layerAttribute) || null;
943
+ }
944
+
945
+ // Setter:
946
+
947
+ // If strictly `null` unset the layer
948
+ if (layerId === null) {
949
+ return this.unset(layerAttribute, opt);
950
+ }
951
+
952
+ // Otherwise set the layer ID
953
+ if (!isString(layerId)) {
954
+ throw new Error('dia.Cell: Layer id must be a string.');
955
+ }
956
+
957
+ return this.set(layerAttribute, layerId, opt);
949
958
  }
950
959
 
951
960
  }, {
952
961
 
953
962
  getAttributeDefinition: function(attrName) {
954
963
 
955
- var defNS = this.attributes;
956
- var globalDefNS = attributes;
957
- return (defNS && defNS[attrName]) || globalDefNS[attrName];
964
+ const defNS = this.attributes;
965
+ const globalDefNS = attributes;
966
+ const definition = (defNS && defNS[attrName]) || globalDefNS[attrName];
967
+ return definition !== undefined ? definition : null;
958
968
  },
959
969
 
960
970
  define: function(type, defaults, protoProps, staticProps) {
@@ -974,3 +984,6 @@ export const Cell = Model.extend({
974
984
  }
975
985
  });
976
986
 
987
+ Object.defineProperty(Cell.prototype, CELL_MARKER, {
988
+ value: true,
989
+ });
@@ -0,0 +1,136 @@
1
+ import * as util from '../util/index.mjs';
2
+ import { Collection } from '../mvc/Collection.mjs';
3
+ import { CELL_MARKER, CELL_COLLECTION_MARKER } from './symbols.mjs';
4
+
5
+ /**
6
+ * @class CellCollection
7
+ * @description A CellCollection is a collection of cells which supports z-index management.
8
+ * Additionally, it facilitates creating cell models from JSON using cellNamespace
9
+ * and stores a reference to the graph when the cell model has been added.
10
+ */
11
+ export class CellCollection extends Collection {
12
+
13
+ [CELL_COLLECTION_MARKER] = true;
14
+
15
+ initialize(_models, opt) {
16
+ this.layer = opt.layer;
17
+ }
18
+
19
+ // Method for checking whether an object should be considered a model for
20
+ // the purposes of adding to the collection.
21
+ _isModel(model) {
22
+ return Boolean(model[CELL_MARKER]);
23
+ }
24
+
25
+ // Overriding the default `model` method to create cell models
26
+ // based on their `type` attribute and the `cellNamespace` option.
27
+ model(attrs, opt) {
28
+
29
+ const namespace = this.cellNamespace;
30
+
31
+ if (!namespace) {
32
+ throw new Error('dia.CellCollection: cellNamespace is required to instantiate a Cell from JSON.');
33
+ }
34
+
35
+ const { type } = attrs;
36
+
37
+ // Find the model class based on the `type` attribute in the cell namespace
38
+ const ModelClass = util.getByPath(namespace, type, '.');
39
+ if (!ModelClass) {
40
+ throw new Error(`dia.Graph: Could not find cell constructor for type: '${type}'. Make sure to add the constructor to 'cellNamespace'.`);
41
+ }
42
+
43
+ return new ModelClass(attrs, opt);
44
+ }
45
+
46
+ // Override to set graph reference
47
+ _addReference(model, options) {
48
+ super._addReference(model, options);
49
+
50
+ // If not in `dry` mode and the model does not have a graph reference yet,
51
+ // set the reference.
52
+ if (!options.dry && !model.graph) {
53
+ model.graph = this.layer.graph;
54
+ }
55
+ }
56
+
57
+ // Override to remove graph reference
58
+ _removeReference(model, options) {
59
+ super._removeReference(model, options);
60
+
61
+ // If not in `dry` mode and the model has a reference to this exact graph,
62
+ // remove the reference.
63
+ // Note: graph reference is removed from the layer after the `remove` event is fired.
64
+ // Due to this, event handlers can still access the graph during the `remove` event.
65
+ if (!options.dry && model.graph === this.layer.graph) {
66
+ model.graph = null;
67
+ }
68
+ }
69
+
70
+ // remove graph reference additionally
71
+ _removeReferenceFast(model, options) {
72
+ model.off('all', this._onModelEvent, this);
73
+
74
+ if (!options.dry) {
75
+ // If not in `dry` mode and the model has a reference
76
+ // to this exact graph/collection, remove the reference.
77
+ if (this === model.collection) {
78
+ delete model.collection;
79
+ }
80
+
81
+ if (model.graph === this.layer.graph) {
82
+ model.graph = null;
83
+ }
84
+ }
85
+ }
86
+
87
+ // `comparator` makes it easy to sort cells based on their `z` index.
88
+ comparator(model) {
89
+ return model.get('z') || 0;
90
+ }
91
+
92
+ // This method overrides base mvc.Collection implementation
93
+ // in a way that improves performance of resetting large collections.
94
+ // For layers specifically, there is an option where we put references
95
+ // from the main collection in order to improve performance when
96
+ // there is only one layer
97
+ reset(models, options) {
98
+ options = util.assign({}, { add: true, remove: false, merge: false }, options);
99
+
100
+ for (let i = 0; i < this.models.length; i++) {
101
+ this._removeReferenceFast(this.models[i], options);
102
+ }
103
+ options.previousModels = this.models;
104
+ this._reset();
105
+
106
+ for (let i = 0; i < models.length; i++) {
107
+ const model = this._prepareModel(models[i], options);
108
+ if (model) {
109
+ this.models.push(model);
110
+ this._addReference(model, options);
111
+ }
112
+ }
113
+
114
+ this.length = this.models.length;
115
+
116
+ const sort = this.comparator && options.sort !== false;
117
+
118
+ if (sort) {
119
+ this.sort({ silent: true });
120
+ }
121
+
122
+ if (!options.silent) {
123
+ this.trigger('reset', this, options);
124
+ }
125
+
126
+ return this.models;
127
+ }
128
+
129
+ minZIndex() {
130
+ return (this.first()?.get('z') || 0);
131
+ }
132
+
133
+ maxZIndex() {
134
+ return (this.last()?.get('z') || 0);
135
+ }
136
+ }
@@ -21,6 +21,7 @@ import V from '../V/index.mjs';
21
21
  import $ from '../mvc/Dom/index.mjs';
22
22
  import { HighlighterView } from './HighlighterView.mjs';
23
23
  import { evalAttributes, evalAttribute } from './attributes/eval.mjs';
24
+ import { CELL_VIEW_MARKER } from './symbols.mjs';
24
25
 
25
26
  const HighlightingTypes = {
26
27
  DEFAULT: 'default',
@@ -1393,4 +1394,9 @@ Object.defineProperty(CellView.prototype, 'useCSSSelectors', {
1393
1394
  }
1394
1395
  });
1395
1396
 
1397
+ // Internal tag to identify this object as a cell view instance.
1398
+ // Used instead of `instanceof` for performance and cross-frame safety.
1396
1399
 
1400
+ Object.defineProperty(CellView.prototype, CELL_VIEW_MARKER, {
1401
+ value: true,
1402
+ });
@@ -14,9 +14,8 @@ export const Element = Cell.extend({
14
14
  angle: 0
15
15
  },
16
16
 
17
- initialize: function() {
18
-
19
- this._initializePorts();
17
+ initialize: function(attributes, options) {
18
+ this._initializePorts(options);
20
19
  Cell.prototype.initialize.apply(this, arguments);
21
20
  },
22
21
 
@@ -421,8 +420,10 @@ export const Element = Cell.extend({
421
420
 
422
421
  let minBBox = null;
423
422
  if (opt.minRect) {
424
- // Coerce `opt.minRect` to g.Rect (missing properties = 0).
425
- minBBox = new Rect(opt.minRect);
423
+ // Coerce `opt.minRect` to g.Rect
424
+ // (missing properties are taken from this element's current bbox).
425
+ const minRect = assign(this.getBBox(), opt.minRect);
426
+ minBBox = new Rect(minRect);
426
427
  }
427
428
 
428
429
  const elementsBBox = this.graph.getCellsBBox(opt.elements);
@@ -389,7 +389,8 @@ export const ElementView = CellView.extend({
389
389
  parent.unembed(element, { ui: true });
390
390
  data.initialParentId = parentId;
391
391
  } else {
392
- data.initialParentId = null;
392
+ // `data.initialParentId` can be explicitly set to a dummy value to enable validation of unembedding.
393
+ data.initialParentId = data.initialParentId || null;
393
394
  }
394
395
  },
395
396