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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dia/Cell.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- uniqueId,
3
2
  union,
4
3
  result,
5
4
  merge,
@@ -33,6 +32,7 @@ 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,38 +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.
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) {
81
71
  const customizer = (options && options.mergeArrays === true)
82
72
  ? false
83
73
  : config.cellDefaultsMergeStrategy || attributesMerger;
84
- attrs = merge({}, defaults, attrs, customizer);
85
- //</custom code>
86
- }
87
- this.set(attrs, options);
88
- this.changed = {};
89
- if (options && options.portLayoutNamespace) {
90
- this.portLayoutNamespace = options.portLayoutNamespace;
91
- }
92
- if (options && options.portLabelLayoutNamespace) {
93
- this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
74
+ attributes = merge({}, attributeDefaults, ctorAttributes, customizer);
75
+ } else {
76
+ attributes = ctorAttributes;
94
77
  }
95
- this.initialize.apply(this, arguments);
78
+ this.set(attributes, options);
96
79
  },
97
80
 
98
81
  translate: function(dx, dy, opt) {
@@ -141,10 +124,10 @@ export const Cell = Model.extend({
141
124
  return finalAttributes;
142
125
  },
143
126
 
144
- initialize: function(options) {
127
+ initialize: function(attributes) {
145
128
 
146
129
  const idAttribute = this.getIdAttribute();
147
- if (!options || options[idAttribute] === undefined) {
130
+ if (!attributes || attributes[idAttribute] === undefined) {
148
131
  this.set(idAttribute, this.generateId(), { silent: true });
149
132
  }
150
133
 
@@ -220,42 +203,20 @@ export const Cell = Model.extend({
220
203
  },
221
204
 
222
205
  remove: function(opt = {}) {
223
-
224
- // Store the graph in a variable because `this.graph` won't be accessible
225
- // after `this.trigger('remove', ...)` down below.
226
206
  const { graph, collection } = this;
227
- 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 {
228
212
  // The collection is a common mvc collection (not the graph collection).
229
213
  if (collection) collection.remove(this, opt);
230
- return this;
231
214
  }
232
-
233
- graph.startBatch('remove');
234
-
235
- // First, unembed this cell from its parent cell if there is one.
236
- const parentCell = this.getParentCell();
237
- if (parentCell) {
238
- parentCell.unembed(this, opt);
239
- }
240
-
241
- // Remove also all the cells, which were embedded into this cell
242
- const embeddedCells = this.getEmbeddedCells();
243
- for (let i = 0, n = embeddedCells.length; i < n; i++) {
244
- const embed = embeddedCells[i];
245
- if (embed) {
246
- embed.remove(opt);
247
- }
248
- }
249
-
250
- this.trigger('remove', this, graph.attributes.cells, opt);
251
-
252
- graph.stopBatch('remove');
253
-
254
215
  return this;
255
216
  },
256
217
 
257
218
  toFront: function(opt) {
258
- var graph = this.graph;
219
+ const { graph } = this;
259
220
  if (graph) {
260
221
  opt = defaults(opt || {}, { foregroundEmbeds: true });
261
222
 
@@ -269,12 +230,14 @@ export const Cell = Model.extend({
269
230
 
270
231
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
271
232
 
272
- const maxZ = graph.maxZIndex();
233
+ const layerId = graph.getCellLayerId(this);
234
+
235
+ const maxZ = graph.maxZIndex(layerId);
273
236
  let z = maxZ - cells.length + 1;
274
237
 
275
- const collection = graph.get('cells');
238
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
276
239
 
277
- let shouldUpdate = (collection.toArray().indexOf(sortedCells[0]) !== (collection.length - cells.length));
240
+ let shouldUpdate = (layerCells.indexOf(sortedCells[0]) !== (layerCells.length - cells.length));
278
241
  if (!shouldUpdate) {
279
242
  shouldUpdate = sortedCells.some(function(cell, index) {
280
243
  return cell.z() !== z + index;
@@ -298,7 +261,7 @@ export const Cell = Model.extend({
298
261
  },
299
262
 
300
263
  toBack: function(opt) {
301
- var graph = this.graph;
264
+ const { graph } = this;
302
265
  if (graph) {
303
266
  opt = defaults(opt || {}, { foregroundEmbeds: true });
304
267
 
@@ -312,11 +275,13 @@ export const Cell = Model.extend({
312
275
 
313
276
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
314
277
 
315
- let z = graph.minZIndex();
278
+ const layerId = graph.getCellLayerId(this);
316
279
 
317
- var collection = graph.get('cells');
280
+ let z = graph.minZIndex(layerId);
318
281
 
319
- 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);
320
285
  if (!shouldUpdate) {
321
286
  shouldUpdate = sortedCells.some(function(cell, index) {
322
287
  return cell.z() !== z + index;
@@ -754,6 +719,7 @@ export const Cell = Model.extend({
754
719
 
755
720
  var firstFrameTime = 0;
756
721
  var interpolatingFunction;
722
+ const transitionKey = Array.isArray(path) ? path.join(delim) : path;
757
723
 
758
724
  var setter = function(runtime) {
759
725
 
@@ -764,10 +730,10 @@ export const Cell = Model.extend({
764
730
  progress = runtime / opt.duration;
765
731
 
766
732
  if (progress < 1) {
767
- this._transitionIds[path] = id = nextFrame(setter);
733
+ this._transitionIds[transitionKey] = id = nextFrame(setter);
768
734
  } else {
769
735
  progress = 1;
770
- delete this._transitionIds[path];
736
+ delete this._transitionIds[transitionKey];
771
737
  }
772
738
 
773
739
  propertyValue = interpolatingFunction(opt.timingFunction(progress));
@@ -776,7 +742,7 @@ export const Cell = Model.extend({
776
742
 
777
743
  this.prop(path, propertyValue, opt);
778
744
 
779
- if (!id) this.trigger('transition:end', this, path);
745
+ if (!id) this.trigger('transition:end', this, transitionKey);
780
746
 
781
747
  }.bind(this);
782
748
 
@@ -785,10 +751,10 @@ export const Cell = Model.extend({
785
751
 
786
752
  var initiator = (callback) => {
787
753
 
788
- if (_scheduledTransitionIds[path]) {
789
- _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
790
- if (_scheduledTransitionIds[path].length === 0) {
791
- delete _scheduledTransitionIds[path];
754
+ if (_scheduledTransitionIds[transitionKey]) {
755
+ _scheduledTransitionIds[transitionKey] = without(_scheduledTransitionIds[transitionKey], initialId);
756
+ if (_scheduledTransitionIds[transitionKey].length === 0) {
757
+ delete _scheduledTransitionIds[transitionKey];
792
758
  }
793
759
  }
794
760
 
@@ -796,16 +762,16 @@ export const Cell = Model.extend({
796
762
 
797
763
  interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
798
764
 
799
- this._transitionIds[path] = nextFrame(callback);
765
+ this._transitionIds[transitionKey] = nextFrame(callback);
800
766
 
801
- this.trigger('transition:start', this, path);
767
+ this.trigger('transition:start', this, transitionKey);
802
768
 
803
769
  };
804
770
 
805
771
  initialId = setTimeout(initiator, opt.delay, setter);
806
772
 
807
- _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
808
- _scheduledTransitionIds[path].push(initialId);
773
+ _scheduledTransitionIds[transitionKey] || (_scheduledTransitionIds[transitionKey] = []);
774
+ _scheduledTransitionIds[transitionKey].push(initialId);
809
775
 
810
776
  return initialId;
811
777
  },
@@ -821,7 +787,8 @@ export const Cell = Model.extend({
821
787
  const { _scheduledTransitionIds = {}} = this;
822
788
  let transitions = Object.keys(_scheduledTransitionIds);
823
789
  if (path) {
824
- 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);
825
792
  transitions = transitions.filter((key) => {
826
793
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
827
794
  });
@@ -840,7 +807,8 @@ export const Cell = Model.extend({
840
807
  const { _transitionIds = {}} = this;
841
808
  let transitions = Object.keys(_transitionIds);
842
809
  if (path) {
843
- 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);
844
812
  transitions = transitions.filter((key) => {
845
813
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
846
814
  });
@@ -860,7 +828,7 @@ export const Cell = Model.extend({
860
828
  return this;
861
829
  },
862
830
 
863
- // A shorcut making it easy to create constructs like the following:
831
+ // A shortcut making it easy to create constructs like the following:
864
832
  // `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
865
833
  addTo: function(graph, opt) {
866
834
 
@@ -962,6 +930,31 @@ export const Cell = Model.extend({
962
930
  .getPointRotatedAroundCenter(this.angle(), x, y)
963
931
  // Transform the absolute position into relative
964
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);
965
958
  }
966
959
 
967
960
  }, {
@@ -990,3 +983,7 @@ export const Cell = Model.extend({
990
983
  return Cell;
991
984
  }
992
985
  });
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',
@@ -1396,8 +1397,6 @@ Object.defineProperty(CellView.prototype, 'useCSSSelectors', {
1396
1397
  // Internal tag to identify this object as a cell view instance.
1397
1398
  // Used instead of `instanceof` for performance and cross-frame safety.
1398
1399
 
1399
- export const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
1400
-
1401
1400
  Object.defineProperty(CellView.prototype, CELL_VIEW_MARKER, {
1402
1401
  value: true,
1403
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