@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/dist/joint.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! JointJS v4.2.0-alpha.1 (2025-09-25) - JavaScript diagramming library
1
+ /*! JointJS v4.2.0-beta.2 (2025-11-04) - JavaScript diagramming library
2
2
 
3
3
  This Source Code Form is subject to the terms of the Mozilla Public
4
4
  License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12520,7 +12520,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
12520
12520
  defaultTheme: 'default',
12521
12521
  // The maximum delay required for two consecutive touchend events to be interpreted
12522
12522
  // as a double-tap.
12523
- doubleTapInterval: 300
12523
+ doubleTapInterval: 300,
12524
+ // Name of the attribute used to store the layer id on the cell model.
12525
+ layerAttribute: 'layer'
12524
12526
  };
12525
12527
 
12526
12528
  // TODO: should not read config outside the mvc package
@@ -14450,16 +14452,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
14450
14452
  var Model = function (attributes, options) {
14451
14453
  var attrs = attributes || {};
14452
14454
  options || (options = {});
14455
+ this.eventPrefix = options.eventPrefix || '';
14453
14456
  this.preinitialize.apply(this, arguments);
14454
14457
  this.cid = uniqueId(this.cidPrefix);
14455
14458
  this.attributes = {};
14456
14459
  if (options.collection) this.collection = options.collection;
14457
- var attributeDefaults = result(this, 'defaults');
14458
-
14459
- // Just _.defaults would work fine, but the additional _.extends
14460
- // is in there for historical reasons. See #3843.
14461
- attrs = defaults(assign({}, attributeDefaults, attrs), attributeDefaults);
14462
- this.set(attrs, options);
14460
+ this._setDefaults(attrs, options);
14463
14461
  this.changed = {};
14464
14462
  this.initialize.apply(this, arguments);
14465
14463
  };
@@ -14548,14 +14546,14 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
14548
14546
  if (this.idAttribute in attrs) {
14549
14547
  var prevId = this.id;
14550
14548
  this.id = this.get(this.idAttribute);
14551
- this.trigger('changeId', this, prevId, options);
14549
+ this.trigger(this.eventPrefix + 'changeId', this, prevId, options);
14552
14550
  }
14553
14551
 
14554
14552
  // Trigger all relevant attribute changes.
14555
14553
  if (!silent) {
14556
14554
  if (changes.length) this._pending = options;
14557
14555
  for (var i = 0; i < changes.length; i++) {
14558
- this.trigger('change:' + changes[i], this, current[changes[i]], options);
14556
+ this.trigger(this.eventPrefix + 'change:' + changes[i], this, current[changes[i]], options);
14559
14557
  }
14560
14558
  }
14561
14559
 
@@ -14566,7 +14564,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
14566
14564
  while (this._pending) {
14567
14565
  options = this._pending;
14568
14566
  this._pending = false;
14569
- this.trigger('change', this, options);
14567
+ this.trigger(this.eventPrefix + 'change', this, options);
14570
14568
  }
14571
14569
  }
14572
14570
  this._pending = false;
@@ -14645,6 +14643,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
14645
14643
  validationError: error
14646
14644
  }));
14647
14645
  return false;
14646
+ },
14647
+ _setDefaults: function (ctorAttributes, options) {
14648
+ const attributeDefaults = result(this, 'defaults');
14649
+ // Just _.defaults would work fine, but the additional _.extends
14650
+ // is in there for historical reasons. See #3843.
14651
+ const attributes = defaults(assign({}, attributeDefaults, ctorAttributes), attributeDefaults);
14652
+ this.set(attributes, options);
14648
14653
  }
14649
14654
  });
14650
14655
 
@@ -15564,6 +15569,30 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15564
15569
  assign(attributesNS, offsetAttributesNS);
15565
15570
  const attributes = attributesNS;
15566
15571
 
15572
+ // Internal tags to identify objects as specific JointJS types.
15573
+ // Used instead of `instanceof` for performance and cross-frame safety.
15574
+
15575
+ // dia.Cell
15576
+ const CELL_MARKER = Symbol('joint.cellMarker');
15577
+
15578
+ // dia.CellCollection
15579
+ const CELL_COLLECTION_MARKER = Symbol('joint.cellCollectionMarker');
15580
+
15581
+ // dia.GraphLayer
15582
+ const GRAPH_LAYER_MARKER = Symbol('joint.graphLayerMarker');
15583
+
15584
+ // dia.GraphLayerCollection
15585
+ const GRAPH_LAYER_COLLECTION_MARKER = Symbol('joint.graphLayerCollectionMarker');
15586
+
15587
+ // dia.CellView
15588
+ const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
15589
+
15590
+ // dia.LayerView
15591
+ const LAYER_VIEW_MARKER = Symbol('joint.layerViewMarker');
15592
+
15593
+ // dia.GraphLayerView
15594
+ const GRAPH_LAYER_VIEW_MARKER = Symbol('joint.graphLayerViewMarker');
15595
+
15567
15596
  // Cell base model.
15568
15597
  // --------------------------
15569
15598
 
@@ -15584,35 +15613,18 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15584
15613
  }
15585
15614
  }
15586
15615
  const Cell = Model.extend({
15587
- // This is the same as mvc.Model with the only difference that is uses util.merge
15588
- // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
15589
- constructor: function (attributes, options) {
15590
- var defaults;
15591
- var attrs = attributes || {};
15592
- if (typeof this.preinitialize === 'function') {
15593
- // Check to support an older version
15594
- this.preinitialize.apply(this, arguments);
15595
- }
15596
- this.cid = uniqueId('c');
15597
- this.attributes = {};
15598
- if (options && options.collection) this.collection = options.collection;
15599
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
15600
- if (defaults = result(this, 'defaults')) {
15601
- //<custom code>
15602
- // Replaced the call to _.defaults with util.merge.
15616
+ cidPrefix: 'c',
15617
+ // Default attributes are merged deeply instead of shallowly.
15618
+ _setDefaults: function (ctorAttributes, options) {
15619
+ let attributes;
15620
+ const attributeDefaults = result(this, 'defaults');
15621
+ if (attributeDefaults) {
15603
15622
  const customizer = options && options.mergeArrays === true ? false : config$3.cellDefaultsMergeStrategy || attributesMerger;
15604
- attrs = merge({}, defaults, attrs, customizer);
15605
- //</custom code>
15606
- }
15607
- this.set(attrs, options);
15608
- this.changed = {};
15609
- if (options && options.portLayoutNamespace) {
15610
- this.portLayoutNamespace = options.portLayoutNamespace;
15611
- }
15612
- if (options && options.portLabelLayoutNamespace) {
15613
- this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
15623
+ attributes = merge({}, attributeDefaults, ctorAttributes, customizer);
15624
+ } else {
15625
+ attributes = ctorAttributes;
15614
15626
  }
15615
- this.initialize.apply(this, arguments);
15627
+ this.set(attributes, options);
15616
15628
  },
15617
15629
  translate: function (dx, dy, opt) {
15618
15630
  throw new Error('Must define a translate() method.');
@@ -15652,9 +15664,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15652
15664
  }
15653
15665
  return finalAttributes;
15654
15666
  },
15655
- initialize: function (options) {
15667
+ initialize: function (attributes) {
15656
15668
  const idAttribute = this.getIdAttribute();
15657
- if (!options || options[idAttribute] === undefined) {
15669
+ if (!attributes || attributes[idAttribute] === undefined) {
15658
15670
  this.set(idAttribute, this.generateId(), {
15659
15671
  silent: true
15660
15672
  });
@@ -15725,39 +15737,24 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15725
15737
  this.ports = ports;
15726
15738
  },
15727
15739
  remove: function (opt = {}) {
15728
- // Store the graph in a variable because `this.graph` won't be accessible
15729
- // after `this.trigger('remove', ...)` down below.
15730
15740
  const {
15731
15741
  graph,
15732
15742
  collection
15733
15743
  } = this;
15734
- if (!graph) {
15744
+ // If the cell is part of a graph, remove it using the graph API.
15745
+ // To make sure the cell is removed in a batch operation.
15746
+ if (graph) {
15747
+ graph.removeCell(this, opt);
15748
+ } else {
15735
15749
  // The collection is a common mvc collection (not the graph collection).
15736
15750
  if (collection) collection.remove(this, opt);
15737
- return this;
15738
- }
15739
- graph.startBatch('remove');
15740
-
15741
- // First, unembed this cell from its parent cell if there is one.
15742
- const parentCell = this.getParentCell();
15743
- if (parentCell) {
15744
- parentCell.unembed(this, opt);
15745
15751
  }
15746
-
15747
- // Remove also all the cells, which were embedded into this cell
15748
- const embeddedCells = this.getEmbeddedCells();
15749
- for (let i = 0, n = embeddedCells.length; i < n; i++) {
15750
- const embed = embeddedCells[i];
15751
- if (embed) {
15752
- embed.remove(opt);
15753
- }
15754
- }
15755
- this.trigger('remove', this, graph.attributes.cells, opt);
15756
- graph.stopBatch('remove');
15757
15752
  return this;
15758
15753
  },
15759
15754
  toFront: function (opt) {
15760
- var graph = this.graph;
15755
+ const {
15756
+ graph
15757
+ } = this;
15761
15758
  if (graph) {
15762
15759
  opt = defaults(opt || {}, {
15763
15760
  foregroundEmbeds: true
@@ -15774,10 +15771,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15774
15771
  cells = [this];
15775
15772
  }
15776
15773
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
15777
- const maxZ = graph.maxZIndex();
15774
+ const layerId = graph.getCellLayerId(this);
15775
+ const maxZ = graph.maxZIndex(layerId);
15778
15776
  let z = maxZ - cells.length + 1;
15779
- const collection = graph.get('cells');
15780
- let shouldUpdate = collection.toArray().indexOf(sortedCells[0]) !== collection.length - cells.length;
15777
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
15778
+ let shouldUpdate = layerCells.indexOf(sortedCells[0]) !== layerCells.length - cells.length;
15781
15779
  if (!shouldUpdate) {
15782
15780
  shouldUpdate = sortedCells.some(function (cell, index) {
15783
15781
  return cell.z() !== z + index;
@@ -15795,7 +15793,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15795
15793
  return this;
15796
15794
  },
15797
15795
  toBack: function (opt) {
15798
- var graph = this.graph;
15796
+ const {
15797
+ graph
15798
+ } = this;
15799
15799
  if (graph) {
15800
15800
  opt = defaults(opt || {}, {
15801
15801
  foregroundEmbeds: true
@@ -15812,9 +15812,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
15812
15812
  cells = [this];
15813
15813
  }
15814
15814
  const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z());
15815
- let z = graph.minZIndex();
15816
- var collection = graph.get('cells');
15817
- let shouldUpdate = collection.toArray().indexOf(sortedCells[0]) !== 0;
15815
+ const layerId = graph.getCellLayerId(this);
15816
+ let z = graph.minZIndex(layerId);
15817
+ const layerCells = graph.getLayer(layerId).cellCollection.toArray();
15818
+ let shouldUpdate = layerCells.indexOf(sortedCells[0]) !== 0;
15818
15819
  if (!shouldUpdate) {
15819
15820
  shouldUpdate = sortedCells.some(function (cell, index) {
15820
15821
  return cell.z() !== z + index;
@@ -16177,41 +16178,42 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16177
16178
  opt = assign(defaults, opt);
16178
16179
  var firstFrameTime = 0;
16179
16180
  var interpolatingFunction;
16181
+ const transitionKey = Array.isArray(path) ? path.join(delim) : path;
16180
16182
  var setter = function (runtime) {
16181
16183
  var id, progress, propertyValue;
16182
16184
  firstFrameTime = firstFrameTime || runtime;
16183
16185
  runtime -= firstFrameTime;
16184
16186
  progress = runtime / opt.duration;
16185
16187
  if (progress < 1) {
16186
- this._transitionIds[path] = id = nextFrame(setter);
16188
+ this._transitionIds[transitionKey] = id = nextFrame(setter);
16187
16189
  } else {
16188
16190
  progress = 1;
16189
- delete this._transitionIds[path];
16191
+ delete this._transitionIds[transitionKey];
16190
16192
  }
16191
16193
  propertyValue = interpolatingFunction(opt.timingFunction(progress));
16192
16194
  opt.transitionId = id;
16193
16195
  this.prop(path, propertyValue, opt);
16194
- if (!id) this.trigger('transition:end', this, path);
16196
+ if (!id) this.trigger('transition:end', this, transitionKey);
16195
16197
  }.bind(this);
16196
16198
  const {
16197
16199
  _scheduledTransitionIds
16198
16200
  } = this;
16199
16201
  let initialId;
16200
16202
  var initiator = callback => {
16201
- if (_scheduledTransitionIds[path]) {
16202
- _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
16203
- if (_scheduledTransitionIds[path].length === 0) {
16204
- delete _scheduledTransitionIds[path];
16203
+ if (_scheduledTransitionIds[transitionKey]) {
16204
+ _scheduledTransitionIds[transitionKey] = without(_scheduledTransitionIds[transitionKey], initialId);
16205
+ if (_scheduledTransitionIds[transitionKey].length === 0) {
16206
+ delete _scheduledTransitionIds[transitionKey];
16205
16207
  }
16206
16208
  }
16207
16209
  this.stopPendingTransitions(path, delim);
16208
16210
  interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value);
16209
- this._transitionIds[path] = nextFrame(callback);
16210
- this.trigger('transition:start', this, path);
16211
+ this._transitionIds[transitionKey] = nextFrame(callback);
16212
+ this.trigger('transition:start', this, transitionKey);
16211
16213
  };
16212
16214
  initialId = setTimeout(initiator, opt.delay, setter);
16213
- _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
16214
- _scheduledTransitionIds[path].push(initialId);
16215
+ _scheduledTransitionIds[transitionKey] || (_scheduledTransitionIds[transitionKey] = []);
16216
+ _scheduledTransitionIds[transitionKey].push(initialId);
16215
16217
  return initialId;
16216
16218
  },
16217
16219
  getTransitions: function () {
@@ -16223,7 +16225,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16223
16225
  } = this;
16224
16226
  let transitions = Object.keys(_scheduledTransitionIds);
16225
16227
  if (path) {
16226
- const pathArray = path.split(delim);
16228
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
16229
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
16227
16230
  transitions = transitions.filter(key => {
16228
16231
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
16229
16232
  });
@@ -16243,7 +16246,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16243
16246
  } = this;
16244
16247
  let transitions = Object.keys(_transitionIds);
16245
16248
  if (path) {
16246
- const pathArray = path.split(delim);
16249
+ // Ensure all path segments are strings for `isEqual` comparison, since it strictly compares values
16250
+ const pathArray = Array.isArray(path) ? path.map(item => String(item)) : path.split(delim);
16247
16251
  transitions = transitions.filter(key => {
16248
16252
  return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
16249
16253
  });
@@ -16261,7 +16265,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16261
16265
  this.stopPendingTransitions(path, delim);
16262
16266
  return this;
16263
16267
  },
16264
- // A shorcut making it easy to create constructs like the following:
16268
+ // A shortcut making it easy to create constructs like the following:
16265
16269
  // `var el = (new joint.shapes.standard.Rectangle()).addTo(graph)`.
16266
16270
  addTo: function (graph, opt) {
16267
16271
  graph.addCell(this, opt);
@@ -16343,6 +16347,29 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16343
16347
  .getPointRotatedAroundCenter(this.angle(), x, y)
16344
16348
  // Transform the absolute position into relative
16345
16349
  .difference(this.position());
16350
+ },
16351
+ layer: function (layerId, opt) {
16352
+ const layerAttribute = config$3.layerAttribute;
16353
+
16354
+ // Getter:
16355
+
16356
+ // If `undefined` return the current layer ID
16357
+ if (layerId === undefined) {
16358
+ return this.get(layerAttribute) || null;
16359
+ }
16360
+
16361
+ // Setter:
16362
+
16363
+ // If strictly `null` unset the layer
16364
+ if (layerId === null) {
16365
+ return this.unset(layerAttribute, opt);
16366
+ }
16367
+
16368
+ // Otherwise set the layer ID
16369
+ if (!isString(layerId)) {
16370
+ throw new Error('dia.Cell: Layer id must be a string.');
16371
+ }
16372
+ return this.set(layerAttribute, layerId, opt);
16346
16373
  }
16347
16374
  }, {
16348
16375
  getAttributeDefinition: function (attrName) {
@@ -16367,6 +16394,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
16367
16394
  return Cell;
16368
16395
  }
16369
16396
  });
16397
+ Object.defineProperty(Cell.prototype, CELL_MARKER, {
16398
+ value: true
16399
+ });
16370
16400
 
16371
16401
  const wrapWith = function (object, methods, wrapper) {
16372
16402
  if (isString(wrapper)) {
@@ -17397,7 +17427,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
17397
17427
  }
17398
17428
  };
17399
17429
  const elementPortPrototype = {
17400
- _initializePorts: function () {
17430
+ _initializePorts: function (options) {
17431
+ if (options) {
17432
+ // Override port layout namespaces if provided in options
17433
+ if (options.portLayoutNamespace) {
17434
+ this.portLayoutNamespace = options.portLayoutNamespace;
17435
+ }
17436
+ // Override port label layout namespaces if provided in options
17437
+ if (options.portLabelLayoutNamespace) {
17438
+ this.portLabelLayoutNamespace = options.portLabelLayoutNamespace;
17439
+ }
17440
+ }
17401
17441
  this._createPortData();
17402
17442
  this.on('change:ports', function () {
17403
17443
  this._processRemovedPort();
@@ -18079,8 +18119,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
18079
18119
  },
18080
18120
  angle: 0
18081
18121
  },
18082
- initialize: function () {
18083
- this._initializePorts();
18122
+ initialize: function (attributes, options) {
18123
+ this._initializePorts(options);
18084
18124
  Cell.prototype.initialize.apply(this, arguments);
18085
18125
  },
18086
18126
  /**
@@ -21621,7 +21661,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21621
21661
  for (i = 0; i < toAdd.length; i++) {
21622
21662
  if (at != null) options.index = at + i;
21623
21663
  model = toAdd[i];
21624
- model.trigger('add', model, this, options);
21664
+ model.trigger(model.eventPrefix + 'add', model, this, options);
21625
21665
  }
21626
21666
  if (sort || orderChanged) this.trigger('sort', this, options);
21627
21667
  if (toAdd.length || toRemove.length || toMerge.length) {
@@ -21684,7 +21724,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21684
21724
  // properties, or an attributes object that is transformed through modelId.
21685
21725
  get: function (obj) {
21686
21726
  if (obj == null) return void 0;
21687
- return this._byId[obj] || this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] || obj.cid && this._byId[obj.cid];
21727
+ return this._byId.get(obj) || this._byId.get(this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)) || obj.cid && this._byId.get(obj.cid);
21688
21728
  },
21689
21729
  // Returns `true` if the model is in the collection.
21690
21730
  has: function (obj) {
@@ -21723,7 +21763,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21723
21763
  },
21724
21764
  // Define how to uniquely identify models in the collection.
21725
21765
  modelId: function (attrs, idAttribute) {
21726
- return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
21766
+ var _this$model$prototype;
21767
+ return attrs[idAttribute || ((_this$model$prototype = this.model.prototype) === null || _this$model$prototype === void 0 ? void 0 : _this$model$prototype.idAttribute) || 'id'];
21727
21768
  },
21728
21769
  // Get an iterator of all models in this collection.
21729
21770
  values: function () {
@@ -21780,17 +21821,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21780
21821
  _reset: function () {
21781
21822
  this.length = 0;
21782
21823
  this.models = [];
21783
- this._byId = {};
21824
+ this._byId = new Map();
21784
21825
  },
21785
21826
  // Prepare a hash of attributes (or other model) to be added to this
21786
21827
  // collection.
21787
21828
  _prepareModel: function (attrs, options) {
21788
21829
  if (this._isModel(attrs)) {
21789
- if (!attrs.collection) attrs.collection = this;
21830
+ if (!options.dry && !attrs.collection) attrs.collection = this;
21790
21831
  return attrs;
21791
21832
  }
21792
21833
  options = options ? clone$1(options) : {};
21793
- options.collection = this;
21834
+ if (!options.dry) options.collection = this;
21794
21835
  var model;
21795
21836
  if (this.model.prototype) {
21796
21837
  model = new this.model(attrs, options);
@@ -21814,12 +21855,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21814
21855
 
21815
21856
  // Remove references before triggering 'remove' event to prevent an
21816
21857
  // infinite loop. #3693
21817
- delete this._byId[model.cid];
21858
+ this._byId.delete(model.cid);
21818
21859
  var id = this.modelId(model.attributes, model.idAttribute);
21819
- if (id != null) delete this._byId[id];
21860
+ if (id != null) this._byId.delete(id);
21820
21861
  if (!options.silent) {
21821
21862
  options.index = index;
21822
- model.trigger('remove', model, this, options);
21863
+ model.trigger(model.eventPrefix + 'remove', model, this, options);
21823
21864
  }
21824
21865
  removed.push(model);
21825
21866
  this._removeReference(model, options);
@@ -21834,17 +21875,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21834
21875
  },
21835
21876
  // Internal method to create a model's ties to a collection.
21836
21877
  _addReference: function (model, options) {
21837
- this._byId[model.cid] = model;
21878
+ this._byId.set(model.cid, model);
21838
21879
  var id = this.modelId(model.attributes, model.idAttribute);
21839
- if (id != null) this._byId[id] = model;
21880
+ if (id != null) this._byId.set(id, model);
21840
21881
  model.on('all', this._onModelEvent, this);
21841
21882
  },
21842
21883
  // Internal method to sever a model's ties to a collection.
21843
21884
  _removeReference: function (model, options) {
21844
- delete this._byId[model.cid];
21885
+ this._byId.delete(model.cid);
21845
21886
  var id = this.modelId(model.attributes, model.idAttribute);
21846
- if (id != null) delete this._byId[id];
21847
- if (this === model.collection) delete model.collection;
21887
+ if (id != null) this._byId.delete(id);
21888
+ if (!options.dry && this === model.collection) delete model.collection;
21848
21889
  model.off('all', this._onModelEvent, this);
21849
21890
  },
21850
21891
  // Internal method called every time a model in the set fires an event.
@@ -21853,12 +21894,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
21853
21894
  // in other collections are ignored.
21854
21895
  _onModelEvent: function (event, model, collection, options) {
21855
21896
  if (model) {
21856
- if ((event === 'add' || event === 'remove') && collection !== this) return;
21897
+ if ((event === model.eventPrefix + 'add' || event === model.eventPrefix + 'remove') && collection !== this) return;
21857
21898
  if (event === 'changeId') {
21858
21899
  var prevId = this.modelId(model.previousAttributes(), model.idAttribute);
21859
21900
  var id = this.modelId(model.attributes, model.idAttribute);
21860
- if (prevId != null) delete this._byId[prevId];
21861
- if (id != null) this._byId[id] = model;
21901
+ if (prevId != null) this._byId.delete(prevId);
21902
+ if (id != null) this._byId.set(id, model);
21862
21903
  }
21863
21904
  }
21864
21905
  this.trigger.apply(this, arguments);
@@ -26684,21 +26725,181 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26684
26725
  topRight: topRight
26685
26726
  };
26686
26727
 
26687
- const GraphCells = Collection.extend({
26688
- initialize: function (models, opt) {
26689
- // Set the optional namespace where all model classes are defined.
26690
- if (opt.cellNamespace) {
26691
- this.cellNamespace = opt.cellNamespace;
26692
- } else {
26693
- /* eslint-disable no-undef */
26694
- this.cellNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
26695
- /* eslint-enable no-undef */
26728
+ /**
26729
+ * @class GraphLayersController
26730
+ * @description Coordinates interactions between the graph and its layers.
26731
+ * Automatically moves cells between layers when the layer attribute changes.
26732
+ */
26733
+ class GraphLayersController extends Listener {
26734
+ constructor(options) {
26735
+ super(options);
26736
+
26737
+ // Make sure there are no arguments passed to the callbacks.
26738
+ // See the `mvc.Listener` documentation for more details.
26739
+ this.callbackArguments = [];
26740
+ const graph = options.graph;
26741
+ if (!graph) {
26742
+ throw new Error('GraphLayersController: "graph" option is required.');
26743
+ }
26744
+ this.graph = graph;
26745
+ this.layerCollection = graph.layerCollection;
26746
+ this.startListening();
26747
+ }
26748
+ startListening() {
26749
+ // Handle all events from the layer collection and its inner cell collections.
26750
+ this.listenTo(this.layerCollection, 'all', this.onLayerCollectionEvent);
26751
+ }
26752
+
26753
+ /**
26754
+ * @description When a cell changes its layer attribute,
26755
+ * move the cell to the target layer.
26756
+ */
26757
+ onCellChange(cell, options) {
26758
+ if (!cell.hasChanged(config$3.layerAttribute)) return;
26759
+ // Move the cell to the appropriate layer
26760
+ const targetLayerId = this.graph.getCellLayerId(cell);
26761
+ this.layerCollection.moveCellBetweenLayers(cell, targetLayerId, options);
26762
+ }
26763
+
26764
+ /**
26765
+ * @description When a cell is removed from a layer,
26766
+ * also remove its embeds and connected links from the graph.
26767
+ * Note: an embedded cell might come from a different layer,
26768
+ * so we can not use the layer's cell collection to remove it.
26769
+ */
26770
+ onCellRemove(cell, options) {
26771
+ // If the cell is being moved from one layer to another,
26772
+ // no further action is needed.
26773
+ if (options.fromLayer) return;
26774
+
26775
+ // When replacing a cell, we do not want to remove its embeds or
26776
+ // unembed it from its parent.
26777
+ if (options.replace) return;
26778
+
26779
+ // First, unembed this cell from its parent cell if there is one.
26780
+ const parentCell = cell.getParentCell();
26781
+ if (parentCell) {
26782
+ parentCell.unembed(cell, options);
26783
+ }
26784
+
26785
+ // Remove also all the cells, which were embedded into this cell
26786
+ const embeddedCells = cell.getEmbeddedCells();
26787
+ for (let i = 0, n = embeddedCells.length; i < n; i++) {
26788
+ const embed = embeddedCells[i];
26789
+ if (embed) {
26790
+ this.layerCollection.removeCell(embed, options);
26791
+ }
26792
+ }
26793
+
26794
+ // When not clearing the whole graph or replacing the cell,
26795
+ // we don't want to remove the connected links.
26796
+ if (!options.clear) {
26797
+ // Applications might provide a `disconnectLinks` option set to `true` in order to
26798
+ // disconnect links when a cell is removed rather then removing them. The default
26799
+ // is to remove all the associated links.
26800
+ if (options.disconnectLinks) {
26801
+ this.graph.disconnectLinks(cell, options);
26802
+ } else {
26803
+ this.graph.removeLinks(cell, options);
26804
+ }
26805
+ }
26806
+ }
26807
+ onLayerCollectionEvent(eventName, model) {
26808
+ if (!model) return;
26809
+ if (model[CELL_MARKER]) {
26810
+ // First handle cell-specific cases that require custom processing,
26811
+ // then forward the event to the graph.
26812
+ // For example, when a cell is removed from a layer, its embeds and
26813
+ // connected links must be removed as well. Listeners on the graph
26814
+ // should receive removal notifications in the following order:
26815
+ // embeds → links → cell.
26816
+ switch (eventName) {
26817
+ case 'change':
26818
+ /* ('change', cell, options) */
26819
+ this.onCellChange.call(this, model, arguments[2]);
26820
+ break;
26821
+ case 'remove':
26822
+ /* ('remove', cell, collection, options) */
26823
+ // When a cell is removed from a layer,
26824
+ // ensure it is also removed from the graph.
26825
+ this.onCellRemove.call(this, model, arguments[3]);
26826
+ break;
26827
+ }
26828
+ // Notify the graph about cell events.
26829
+ this.forwardCellEvent.apply(this, arguments);
26830
+ return;
26831
+ }
26832
+ if (model[CELL_COLLECTION_MARKER]) {
26833
+ this.forwardCellCollectionEvent.apply(this, arguments);
26834
+ return;
26835
+ }
26836
+ if (model[GRAPH_LAYER_MARKER]) {
26837
+ this.forwardLayerEvent.apply(this, arguments);
26838
+ return;
26839
+ }
26840
+ if (model[GRAPH_LAYER_COLLECTION_MARKER]) {
26841
+ this.forwardLayerCollectionEvent.apply(this, arguments);
26842
+ return;
26843
+ }
26844
+ }
26845
+ forwardLayerEvent() {
26846
+ // Note: the layer event prefix is `layer:`
26847
+ this.graph.trigger.apply(this.graph, arguments);
26848
+ }
26849
+ forwardCellEvent(eventName, cell) {
26850
+ var _arguments$;
26851
+ // Moving a cell from one layer to another is an internal operation
26852
+ // that should not be exposed at the graph level.
26853
+ // The single `move` event is triggered instead.
26854
+ if ((eventName === 'remove' || eventName === 'add') && (_arguments$ = arguments[3]) !== null && _arguments$ !== void 0 && _arguments$.fromLayer) return;
26855
+ this.graph.trigger.apply(this.graph, arguments);
26856
+ }
26857
+ forwardCellCollectionEvent(eventName) {
26858
+ // Do not forward `layer:remove` or `layer:sort` events to the graph
26859
+ if (eventName !== 'sort') return;
26860
+ // Backwards compatibility:
26861
+ // Trigger 'sort' event for cell collection 'sort' events
26862
+ this.graph.trigger.apply(this.graph, arguments);
26863
+ }
26864
+ forwardLayerCollectionEvent(eventName) {
26865
+ if (eventName === 'reset') {
26866
+ // Currently, there is no need to forward `layers:reset` event.
26867
+ // The graph `fromJSON()` triggers a single `reset` event after
26868
+ // resetting cells, layers and attributes.
26869
+ return;
26870
+ }
26871
+ // Forward layer collection events with `layers:` prefix.
26872
+ // For example `layers:reset` event when the layer collection is reset
26873
+ arguments[0] = 'layers:' + arguments[0];
26874
+ this.graph.trigger.apply(this.graph, arguments);
26875
+ }
26876
+ }
26877
+
26878
+ /**
26879
+ * @class CellCollection
26880
+ * @description A CellCollection is a collection of cells which supports z-index management.
26881
+ * Additionally, it facilitates creating cell models from JSON using cellNamespace
26882
+ * and stores a reference to the graph when the cell model has been added.
26883
+ */
26884
+ class CellCollection extends Collection {
26885
+ [CELL_COLLECTION_MARKER] = true;
26886
+ initialize(_models, opt) {
26887
+ this.layer = opt.layer;
26888
+ }
26889
+
26890
+ // Method for checking whether an object should be considered a model for
26891
+ // the purposes of adding to the collection.
26892
+ _isModel(model) {
26893
+ return Boolean(model[CELL_MARKER]);
26894
+ }
26895
+
26896
+ // Overriding the default `model` method to create cell models
26897
+ // based on their `type` attribute and the `cellNamespace` option.
26898
+ model(attrs, opt) {
26899
+ const namespace = this.cellNamespace;
26900
+ if (!namespace) {
26901
+ throw new Error('dia.CellCollection: cellNamespace is required to instantiate a Cell from JSON.');
26696
26902
  }
26697
- this.graph = opt.graph;
26698
- },
26699
- model: function (attrs, opt) {
26700
- const collection = opt.collection;
26701
- const namespace = collection.cellNamespace;
26702
26903
  const {
26703
26904
  type
26704
26905
  } = attrs;
@@ -26709,56 +26910,474 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26709
26910
  throw new Error(`dia.Graph: Could not find cell constructor for type: '${type}'. Make sure to add the constructor to 'cellNamespace'.`);
26710
26911
  }
26711
26912
  return new ModelClass(attrs, opt);
26712
- },
26713
- _addReference: function (model, options) {
26714
- Collection.prototype._addReference.apply(this, arguments);
26913
+ }
26914
+
26915
+ // Override to set graph reference
26916
+ _addReference(model, options) {
26917
+ super._addReference(model, options);
26918
+
26715
26919
  // If not in `dry` mode and the model does not have a graph reference yet,
26716
26920
  // set the reference.
26717
26921
  if (!options.dry && !model.graph) {
26718
- model.graph = this.graph;
26922
+ model.graph = this.layer.graph;
26719
26923
  }
26720
- },
26721
- _removeReference: function (model, options) {
26722
- Collection.prototype._removeReference.apply(this, arguments);
26924
+ }
26925
+
26926
+ // Override to remove graph reference
26927
+ _removeReference(model, options) {
26928
+ super._removeReference(model, options);
26929
+
26723
26930
  // If not in `dry` mode and the model has a reference to this exact graph,
26724
26931
  // remove the reference.
26725
- if (!options.dry && model.graph === this.graph) {
26932
+ // Note: graph reference is removed from the layer after the `remove` event is fired.
26933
+ // Due to this, event handlers can still access the graph during the `remove` event.
26934
+ if (!options.dry && model.graph === this.layer.graph) {
26726
26935
  model.graph = null;
26727
26936
  }
26728
- },
26937
+ }
26938
+
26939
+ // remove graph reference additionally
26940
+ _removeReferenceFast(model, options) {
26941
+ model.off('all', this._onModelEvent, this);
26942
+ if (!options.dry) {
26943
+ // If not in `dry` mode and the model has a reference
26944
+ // to this exact graph/collection, remove the reference.
26945
+ if (this === model.collection) {
26946
+ delete model.collection;
26947
+ }
26948
+ if (model.graph === this.layer.graph) {
26949
+ model.graph = null;
26950
+ }
26951
+ }
26952
+ }
26953
+
26729
26954
  // `comparator` makes it easy to sort cells based on their `z` index.
26730
- comparator: function (model) {
26955
+ comparator(model) {
26731
26956
  return model.get('z') || 0;
26732
26957
  }
26733
- });
26734
- const Graph = Model.extend({
26735
- initialize: function (attrs, opt) {
26736
- opt = opt || {};
26737
26958
 
26738
- // Passing `cellModel` function in the options object to graph allows for
26739
- // setting models based on attribute objects. This is especially handy
26740
- // when processing JSON graphs that are in a different than JointJS format.
26741
- var cells = new GraphCells([], {
26742
- model: opt.cellModel,
26743
- cellNamespace: opt.cellNamespace,
26744
- graph: this
26959
+ // This method overrides base mvc.Collection implementation
26960
+ // in a way that improves performance of resetting large collections.
26961
+ // For layers specifically, there is an option where we put references
26962
+ // from the main collection in order to improve performance when
26963
+ // there is only one layer
26964
+ reset(models, options) {
26965
+ options = assign({}, {
26966
+ add: true,
26967
+ remove: false,
26968
+ merge: false
26969
+ }, options);
26970
+ for (let i = 0; i < this.models.length; i++) {
26971
+ this._removeReferenceFast(this.models[i], options);
26972
+ }
26973
+ options.previousModels = this.models;
26974
+ this._reset();
26975
+ for (let i = 0; i < models.length; i++) {
26976
+ const model = this._prepareModel(models[i], options);
26977
+ if (model) {
26978
+ this.models.push(model);
26979
+ this._addReference(model, options);
26980
+ }
26981
+ }
26982
+ this.length = this.models.length;
26983
+ const sort = this.comparator && options.sort !== false;
26984
+ if (sort) {
26985
+ this.sort({
26986
+ silent: true
26987
+ });
26988
+ }
26989
+ if (!options.silent) {
26990
+ this.trigger('reset', this, options);
26991
+ }
26992
+ return this.models;
26993
+ }
26994
+ minZIndex() {
26995
+ var _this$first;
26996
+ return ((_this$first = this.first()) === null || _this$first === void 0 ? void 0 : _this$first.get('z')) || 0;
26997
+ }
26998
+ maxZIndex() {
26999
+ var _this$last;
27000
+ return ((_this$last = this.last()) === null || _this$last === void 0 ? void 0 : _this$last.get('z')) || 0;
27001
+ }
27002
+ }
27003
+
27004
+ const DEFAULT_GRAPH_LAYER_TYPE = 'GraphLayer';
27005
+
27006
+ /**
27007
+ * @class GraphLayer
27008
+ * @description A GraphLayer is a model representing a single layer in a dia.Graph.
27009
+ */
27010
+ class GraphLayer extends Model {
27011
+ [GRAPH_LAYER_MARKER] = true;
27012
+ preinitialize() {
27013
+ // This allows for propagating events from the inner `cellCollection` collection
27014
+ // without any prefix and therefore distinguish them from the events
27015
+ // fired by the GraphLayer model itself.
27016
+ this.eventPrefix = 'layer:';
27017
+ }
27018
+ defaults() {
27019
+ return {
27020
+ type: DEFAULT_GRAPH_LAYER_TYPE
27021
+ };
27022
+ }
27023
+ initialize(attrs, options = {}) {
27024
+ super.initialize(attrs, options);
27025
+ this.cellCollection = new CellCollection([], {
27026
+ layer: this
26745
27027
  });
26746
- Model.prototype.set.call(this, 'cells', cells);
26747
27028
 
26748
- // Make all the events fired in the `cells` collection available.
26749
- // to the outside world.
26750
- cells.on('all', this.trigger, this);
27029
+ // Forward all events from the inner `cellCollection` collection
27030
+ this.cellCollection.on('all', this.trigger, this);
27031
+ // Listen to cell changes to manage z-index sorting
27032
+ this.cellCollection.on('change', this.onCellChange, this);
27033
+ }
27034
+ onCellChange(cell, opt) {
27035
+ if (opt.sort === false || !cell.hasChanged('z')) return;
27036
+ this.cellCollection.sort();
27037
+ }
27038
+
27039
+ /**
27040
+ * @public
27041
+ * @description Returns all cells in this layer.
27042
+ */
27043
+ getCells() {
27044
+ return this.cellCollection.toArray();
27045
+ }
27046
+ }
26751
27047
 
26752
- // JointJS automatically doesn't trigger re-sort if models attributes are changed later when
26753
- // they're already in the collection. Therefore, we're triggering sort manually here.
26754
- this.on('change:z', this._sortOnChangeZ, this);
27048
+ /**
27049
+ * @class GraphLayerCollection
27050
+ * @description A collection of layers used in dia.Graph. It facilitates creating layers from JSON using layerNamespace.
27051
+ */
27052
+ const GraphLayerCollection = Collection.extend({
27053
+ defaultLayerNamespace: {
27054
+ GraphLayer
27055
+ },
27056
+ /**
27057
+ * @override
27058
+ * @description Initializes the collection and sets up the layer and cell namespaces.
27059
+ */
27060
+ initialize: function (_models, options = {}) {
27061
+ const {
27062
+ layerNamespace,
27063
+ cellNamespace,
27064
+ graph
27065
+ } = options;
26755
27066
 
26756
- // `joint.dia.Graph` keeps an internal data structure (an adjacency list)
26757
- // for fast graph queries. All changes that affect the structure of the graph
26758
- // must be reflected in the `al` object. This object provides fast answers to
26759
- // questions such as "what are the neighbours of this node" or "what
26760
- // are the sibling links of this link".
27067
+ // Initialize the namespace that holds all available layer classes.
27068
+ // Custom namespaces are merged with the default ones.
27069
+ this.layerNamespace = assign({}, this.defaultLayerNamespace, layerNamespace);
27070
+
27071
+ // Initialize the namespace for all cell model classes, if provided.
27072
+ if (cellNamespace) {
27073
+ this.cellNamespace = cellNamespace;
27074
+ } else {
27075
+ /* eslint-disable no-undef */
27076
+ this.cellNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
27077
+ /* eslint-enable no-undef */
27078
+ }
27079
+ this.graph = graph;
27080
+ },
27081
+ /**
27082
+ * @override
27083
+ * @description Overrides the default `model` method
27084
+ * to create layer models based on their `type` attribute.
27085
+ */
27086
+ model: function (attrs, opt) {
27087
+ const collection = opt.collection;
27088
+ const namespace = collection.layerNamespace;
27089
+ const {
27090
+ type
27091
+ } = attrs;
27092
+
27093
+ // Find the model class based on the `type` attribute in the cell namespace
27094
+ const GraphLayerClass = getByPath(namespace, type, '.');
27095
+ if (!GraphLayerClass) {
27096
+ throw new Error(`dia.Graph: Could not find layer constructor for type: '${type}'. Make sure to add the constructor to 'layerNamespace'.`);
27097
+ }
27098
+ return new GraphLayerClass(attrs, opt);
27099
+ },
27100
+ // Override to set graph reference
27101
+ _addReference(layer, options) {
27102
+ Collection.prototype._addReference.call(this, layer, options);
27103
+
27104
+ // assign graph and cellNamespace references
27105
+ // to the added layer
27106
+ layer.graph = this.graph;
27107
+ layer.cellCollection.cellNamespace = this.cellNamespace;
27108
+ },
27109
+ // Override to remove graph reference
27110
+ _removeReference(layer, options) {
27111
+ Collection.prototype._removeReference.call(this, layer, options);
27112
+
27113
+ // remove graph and cellNamespace references
27114
+ // from the removed layer
27115
+ layer.graph = null;
27116
+ layer.cellCollection.cellNamespace = null;
27117
+ },
27118
+ /**
27119
+ * @override
27120
+ * @description Overrides the default `_prepareModel` method
27121
+ * to set default layer type if missing.
27122
+ */
27123
+ _prepareModel: function (attrs, options) {
27124
+ if (!attrs[GRAPH_LAYER_MARKER]) {
27125
+ // Add a mandatory `type` attribute if missing
27126
+ if (!attrs.type) {
27127
+ const preparedAttributes = clone$1(attrs);
27128
+ preparedAttributes.type = DEFAULT_GRAPH_LAYER_TYPE;
27129
+ arguments[0] = preparedAttributes;
27130
+ }
27131
+ }
27132
+ return Collection.prototype._prepareModel.apply(this, arguments);
27133
+ },
27134
+ /**
27135
+ * @override
27136
+ * @description Add an assertion to prevent direct resetting of the collection.
27137
+ */
27138
+ reset(models, options) {
27139
+ this._assertInternalCall(options);
27140
+ return Collection.prototype.reset.apply(this, arguments);
27141
+ },
27142
+ /**
27143
+ * @override
27144
+ * @description Add an assertion to prevent direct addition of layers.
27145
+ */
27146
+ add(models, options) {
27147
+ this._assertInternalCall(options);
27148
+ return Collection.prototype.add.apply(this, arguments);
27149
+ },
27150
+ /**
27151
+ * @override
27152
+ * @description Add an assertion to prevent direct removal of layers.
27153
+ */
27154
+ remove(models, options) {
27155
+ this._assertInternalCall(options);
27156
+ return Collection.prototype.remove.apply(this, arguments);
27157
+ },
27158
+ /**
27159
+ * @override
27160
+ * @description Overrides the default `_onModelEvent` method
27161
+ * to distinguish between events coming from different model types.
27162
+ */
27163
+ _onModelEvent(_, model) {
27164
+ if (model && model[CELL_MARKER]) {
27165
+ // Do not filter cell `add` and `remove` events
27166
+ // See `mvc.Collection` for more details
27167
+ this.trigger.apply(this, arguments);
27168
+ return;
27169
+ }
26761
27170
 
27171
+ // For other events, use the default behavior
27172
+ Collection.prototype._onModelEvent.apply(this, arguments);
27173
+ },
27174
+ /**
27175
+ * @protected
27176
+ * @description Asserts that the collection manipulation
27177
+ * is done via internal graph methods. Otherwise, it throws an error.
27178
+ * This is a temporary measure until layers API is stabilized.
27179
+ */
27180
+ _assertInternalCall(options) {
27181
+ if (options && !options.graph && !options.silent) {
27182
+ throw new Error('dia.GraphLayerCollection: direct manipulation of the collection is not supported, use graph methods instead.');
27183
+ }
27184
+ },
27185
+ /**
27186
+ * @public
27187
+ * @description Inserts a layer before another layer or at the end if `beforeLayerId` is null.
27188
+ */
27189
+ insert(layerInit, beforeLayerId = null, options = {}) {
27190
+ const id = layerInit.id;
27191
+ if (id === beforeLayerId) {
27192
+ // Inserting before itself is a no-op
27193
+ return;
27194
+ }
27195
+ if (beforeLayerId && !this.has(beforeLayerId)) {
27196
+ throw new Error(`dia.GraphLayerCollection: Layer "${beforeLayerId}" does not exist`);
27197
+ }
27198
+
27199
+ // See if the layer is already in the collection
27200
+ let currentIndex = -1;
27201
+ if (this.has(id)) {
27202
+ currentIndex = this.findIndex(l => l.id === id);
27203
+ if (currentIndex === this.length - 1 && !beforeLayerId) {
27204
+ // The layer is already at the end
27205
+ return;
27206
+ }
27207
+ // Remove the layer from its current position
27208
+ this.remove(id, {
27209
+ silent: true
27210
+ });
27211
+ }
27212
+
27213
+ // At what index to insert the layer?
27214
+ let insertAt;
27215
+ if (!beforeLayerId) {
27216
+ insertAt = this.length;
27217
+ } else {
27218
+ insertAt = this.findIndex(l => l.id === beforeLayerId);
27219
+ }
27220
+ if (currentIndex !== -1) {
27221
+ // Re-insert the layer at the new position.
27222
+ this.add(layerInit, {
27223
+ at: insertAt,
27224
+ silent: true
27225
+ });
27226
+ // Trigger `sort` event manually
27227
+ // since we are not using collection sorting workflow
27228
+ this.trigger('sort', this, options);
27229
+ } else {
27230
+ // Add to the collection and trigger an event
27231
+ // when new layer has been added
27232
+ this.add(layerInit, {
27233
+ ...options,
27234
+ at: insertAt
27235
+ });
27236
+ }
27237
+ },
27238
+ /**
27239
+ * @public
27240
+ * @description Finds and returns a cell by its id from all layers.
27241
+ */
27242
+ getCell(cellRef) {
27243
+ // TODO: should we create a map of cells for faster lookup?
27244
+ for (const layer of this.models) {
27245
+ const cell = layer.cellCollection.get(cellRef);
27246
+ if (cell) {
27247
+ return cell;
27248
+ }
27249
+ }
27250
+ // Backward compatibility: return undefined if cell is not found
27251
+ return undefined;
27252
+ },
27253
+ /**
27254
+ * @public
27255
+ * @description Returns all cells in all layers in the correct order.
27256
+ */
27257
+ getCells() {
27258
+ const layers = this.models;
27259
+ if (layers.length === 1) {
27260
+ // Single layer:
27261
+ // Fast path, just return the copy of the only layer's cells
27262
+ return layers[0].getCells();
27263
+ }
27264
+ // Multiple layers:
27265
+ // Each layer has its models sorted already, so we can just concatenate
27266
+ // them in the order of layers.
27267
+ const cells = [];
27268
+ for (const layer of layers) {
27269
+ Array.prototype.push.apply(cells, layer.cellCollection.models);
27270
+ }
27271
+ return cells;
27272
+ },
27273
+ /**
27274
+ * @public
27275
+ * @description Removes a cell from its current layer.
27276
+ */
27277
+ removeCell(cell, options = {}) {
27278
+ var _cell$collection;
27279
+ const cellCollection = (_cell$collection = cell.collection) === null || _cell$collection === void 0 || (_cell$collection = _cell$collection.layer) === null || _cell$collection === void 0 ? void 0 : _cell$collection.cellCollection;
27280
+ if (!cellCollection) return;
27281
+ cellCollection.remove(cell, options);
27282
+ },
27283
+ /**
27284
+ * @public
27285
+ * @description Move a cell from its current layer to a target layer.
27286
+ */
27287
+ moveCellBetweenLayers(cell, targetLayerId, options = {}) {
27288
+ var _cell$collection2;
27289
+ const sourceLayer = (_cell$collection2 = cell.collection) === null || _cell$collection2 === void 0 ? void 0 : _cell$collection2.layer;
27290
+ if (!sourceLayer) {
27291
+ throw new Error('dia.GraphLayerCollection: cannot move a cell that is not part of any layer.');
27292
+ }
27293
+ const targetLayer = this.get(targetLayerId);
27294
+ if (!targetLayer) {
27295
+ throw new Error(`dia.GraphLayerCollection: cannot move cell to layer '${targetLayerId}' because such layer does not exist.`);
27296
+ }
27297
+ if (sourceLayer === targetLayer) {
27298
+ // 1. The provided cell is already in the target layer
27299
+ // 2. Implicit default layer vs. explicit default (or vice versa)
27300
+ // No follow-up action needed
27301
+ return;
27302
+ }
27303
+ const moveOptions = {
27304
+ ...options,
27305
+ fromLayer: sourceLayer.id,
27306
+ toLayer: targetLayer.id
27307
+ };
27308
+ // Move the cell between the two layer collections
27309
+ sourceLayer.cellCollection.remove(cell, moveOptions);
27310
+ targetLayer.cellCollection.add(cell, moveOptions);
27311
+ // Trigger a single `move` event to ease distinguishing layer moves
27312
+ // from add/remove operations
27313
+ cell.trigger('move', cell, moveOptions);
27314
+ },
27315
+ /**
27316
+ * @public
27317
+ * @description Adds a cell to the specified layer.
27318
+ */
27319
+ addCellToLayer(cell, layerId, options = {}) {
27320
+ const targetLayer = this.get(layerId);
27321
+ if (!targetLayer) {
27322
+ throw new Error(`dia.GraphLayerCollection: layer "${layerId}" does not exist.`);
27323
+ }
27324
+ const addOptions = {
27325
+ ...options,
27326
+ toLayer: targetLayer.id
27327
+ };
27328
+ // Add the cell to the target layer collection
27329
+ targetLayer.cellCollection.add(cell, addOptions);
27330
+ }
27331
+ });
27332
+ Object.defineProperty(GraphLayerCollection.prototype, GRAPH_LAYER_COLLECTION_MARKER, {
27333
+ value: true
27334
+ });
27335
+
27336
+ /**
27337
+ * @class GraphTopologyIndex
27338
+ * @description Maintains an index of the graph topology (adjacency list)
27339
+ * for fast graph queries.
27340
+ */
27341
+ class GraphTopologyIndex extends Listener {
27342
+ constructor(options) {
27343
+ super(options);
27344
+
27345
+ // Make sure there are no arguments passed to the callbacks.
27346
+ // See the `mvc.Listener` documentation for more details.
27347
+ this.callbackArguments = [];
27348
+ this.layerCollection = options.layerCollection;
27349
+ if (!this.layerCollection) {
27350
+ throw new Error('GraphTopologyIndex: "layerCollection" option is required.');
27351
+ }
27352
+ this.initializeIndex();
27353
+ this.startListening();
27354
+ }
27355
+
27356
+ /**
27357
+ * @public
27358
+ * @description Start listening to graph and layer collection events
27359
+ * to maintain the topology index.
27360
+ */
27361
+ startListening() {
27362
+ this.listenTo(this.layerCollection.graph, {
27363
+ 'add': this._restructureOnAdd,
27364
+ 'remove': this._restructureOnRemove,
27365
+ 'reset': this._restructureOnReset
27366
+ });
27367
+ // Listening to the collection instead of the graph
27368
+ // to avoid reacting to graph attribute change events
27369
+ // e.g. graph.set('source', ...);
27370
+ this.listenTo(this.layerCollection, {
27371
+ 'change:source': this._restructureOnChangeSource,
27372
+ 'change:target': this._restructureOnChangeTarget
27373
+ });
27374
+ }
27375
+
27376
+ /**
27377
+ * @protected
27378
+ * @description Initialize the internal data structures.
27379
+ */
27380
+ initializeIndex() {
26762
27381
  // Outgoing edges per node. Note that we use a hash-table for the list
26763
27382
  // of outgoing edges for a faster lookup.
26764
27383
  // [nodeId] -> Object [edgeId] -> true
@@ -26774,21 +27393,27 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26774
27393
  // having to go through the whole cells array.
26775
27394
  // [edgeId] -> true
26776
27395
  this._edges = {};
26777
- this._batches = {};
26778
- cells.on('add', this._restructureOnAdd, this);
26779
- cells.on('remove', this._restructureOnRemove, this);
26780
- cells.on('reset', this._restructureOnReset, this);
26781
- cells.on('change:source', this._restructureOnChangeSource, this);
26782
- cells.on('change:target', this._restructureOnChangeTarget, this);
26783
- cells.on('remove', this._removeCell, this);
26784
- },
26785
- _sortOnChangeZ: function () {
26786
- this.get('cells').sort();
26787
- },
26788
- _restructureOnAdd: function (cell) {
27396
+ }
27397
+
27398
+ /**
27399
+ * @protected
27400
+ * @description Restructure the topology index on graph reset.
27401
+ * E.g. when fromJSON or resetCells is called.
27402
+ */
27403
+ _restructureOnReset() {
27404
+ this.initializeIndex();
27405
+ this.layerCollection.getCells().forEach(this._restructureOnAdd, this);
27406
+ }
27407
+
27408
+ /**
27409
+ * @protected
27410
+ * @description Restructure the topology index on cell addition.
27411
+ * @param {dia.Cell} cell - The cell being added.
27412
+ */
27413
+ _restructureOnAdd(cell) {
26789
27414
  if (cell.isLink()) {
26790
27415
  this._edges[cell.id] = true;
26791
- var {
27416
+ const {
26792
27417
  source,
26793
27418
  target
26794
27419
  } = cell.attributes;
@@ -26801,11 +27426,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26801
27426
  } else {
26802
27427
  this._nodes[cell.id] = true;
26803
27428
  }
26804
- },
26805
- _restructureOnRemove: function (cell) {
27429
+ }
27430
+
27431
+ /**
27432
+ * @protected
27433
+ * @description Restructure the topology index on cell removal.
27434
+ * @param {dia.Cell} cell - The cell being removed.
27435
+ */
27436
+ _restructureOnRemove(cell) {
26806
27437
  if (cell.isLink()) {
26807
27438
  delete this._edges[cell.id];
26808
- var {
27439
+ const {
26809
27440
  source,
26810
27441
  target
26811
27442
  } = cell.attributes;
@@ -26818,89 +27449,222 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26818
27449
  } else {
26819
27450
  delete this._nodes[cell.id];
26820
27451
  }
26821
- },
26822
- _restructureOnReset: function (cells) {
26823
- // Normalize into an array of cells. The original `cells` is GraphCells mvc collection.
26824
- cells = cells.models;
26825
- this._out = {};
26826
- this._in = {};
26827
- this._nodes = {};
26828
- this._edges = {};
26829
- cells.forEach(this._restructureOnAdd, this);
26830
- },
26831
- _restructureOnChangeSource: function (link) {
26832
- var prevSource = link.previous('source');
27452
+ }
27453
+
27454
+ /**
27455
+ * @protected
27456
+ * @description Restructure the topology index on link source change.
27457
+ * @param {dia.Link} link - The link being changed.
27458
+ */
27459
+ _restructureOnChangeSource(link) {
27460
+ const prevSource = link.previous('source');
26833
27461
  if (prevSource.id && this._out[prevSource.id]) {
26834
27462
  delete this._out[prevSource.id][link.id];
26835
27463
  }
26836
- var source = link.attributes.source;
27464
+ const source = link.attributes.source;
26837
27465
  if (source.id) {
26838
27466
  (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
26839
27467
  }
26840
- },
26841
- _restructureOnChangeTarget: function (link) {
26842
- var prevTarget = link.previous('target');
27468
+ }
27469
+
27470
+ /**
27471
+ * @protected
27472
+ * @description Restructure the topology index on link target change.
27473
+ * @param {dia.Link} link - The link being changed.
27474
+ */
27475
+ _restructureOnChangeTarget(link) {
27476
+ const prevTarget = link.previous('target');
26843
27477
  if (prevTarget.id && this._in[prevTarget.id]) {
26844
27478
  delete this._in[prevTarget.id][link.id];
26845
27479
  }
26846
- var target = link.get('target');
27480
+ const target = link.get('target');
26847
27481
  if (target.id) {
26848
27482
  (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
26849
27483
  }
26850
- },
26851
- // Return all outbound edges for the node. Return value is an object
26852
- // of the form: [edgeId] -> true
26853
- getOutboundEdges: function (node) {
26854
- return this._out && this._out[node] || {};
26855
- },
26856
- // Return all inbound edges for the node. Return value is an object
26857
- // of the form: [edgeId] -> true
26858
- getInboundEdges: function (node) {
26859
- return this._in && this._in[node] || {};
27484
+ }
27485
+
27486
+ /**
27487
+ * @public
27488
+ * @description Get all outbound edges for the node. Time complexity: O(1).
27489
+ * @param {string} nodeId - The id of the node.
27490
+ * @returns {Object} - An object of the form: [edgeId] -> true.
27491
+ */
27492
+ getOutboundEdges(nodeId) {
27493
+ return this._out[nodeId] || {};
27494
+ }
27495
+
27496
+ /**
27497
+ * @public
27498
+ * @description Get all inbound edges for the node. Time complexity: O(1).
27499
+ * @param {string} nodeId - The id of the node.
27500
+ * @returns {Object} - An object of the form: [edgeId] -> true.
27501
+ */
27502
+ getInboundEdges(nodeId) {
27503
+ return this._in[nodeId] || {};
27504
+ }
27505
+
27506
+ /**
27507
+ * @public
27508
+ * @description Get all sink nodes (leafs) in the graph. Time complexity: O(|V|).
27509
+ * @returns {string[]} - Array of node ids.
27510
+ */
27511
+ getSinkNodes() {
27512
+ const sinks = [];
27513
+ for (const nodeId in this._nodes) {
27514
+ if (!this._out[nodeId] || isEmpty(this._out[nodeId])) {
27515
+ sinks.push(nodeId);
27516
+ }
27517
+ }
27518
+ return sinks;
27519
+ }
27520
+
27521
+ /**
27522
+ * @public
27523
+ * @description Get all source nodes (roots) in the graph. Time complexity: O(|V|).
27524
+ * @returns {string[]} - Array of node ids.
27525
+ */
27526
+ getSourceNodes() {
27527
+ const sources = [];
27528
+ for (const nodeId in this._nodes) {
27529
+ if (!this._in[nodeId] || isEmpty(this._in[nodeId])) {
27530
+ sources.push(nodeId);
27531
+ }
27532
+ }
27533
+ return sources;
27534
+ }
27535
+
27536
+ /**
27537
+ * @public
27538
+ * @description Return `true` if `nodeId` is a source node (root). Time complexity: O(1).
27539
+ * @param {string} nodeId - The id of the node to check.
27540
+ * @returns {boolean}
27541
+ */
27542
+ isSourceNode(nodeId) {
27543
+ return !this._in[nodeId] || isEmpty(this._in[nodeId]);
27544
+ }
27545
+
27546
+ /**
27547
+ * @public
27548
+ * @description Return `true` if `nodeId` is a sink node (leaf). Time complexity: O(1).
27549
+ * @param {string} nodeId - The id of the node to check.
27550
+ * @returns {boolean}
27551
+ */
27552
+ isSinkNode(nodeId) {
27553
+ return !this._out[nodeId] || isEmpty(this._out[nodeId]);
27554
+ }
27555
+ }
27556
+
27557
+ // The ID of the default graph layer.
27558
+ const DEFAULT_LAYER_ID = 'cells';
27559
+ const Graph = Model.extend({
27560
+ /**
27561
+ * @todo Remove in v5.0.0
27562
+ * @description In legacy mode, the information about layers is not
27563
+ * exported into JSON.
27564
+ */
27565
+ legacyMode: true,
27566
+ /**
27567
+ * @protected
27568
+ * @description The ID of the default layer.
27569
+ */
27570
+ defaultLayerId: DEFAULT_LAYER_ID,
27571
+ initialize: function (attrs, options = {}) {
27572
+ const layerCollection = this.layerCollection = new GraphLayerCollection([], {
27573
+ layerNamespace: options.layerNamespace,
27574
+ cellNamespace: options.cellNamespace,
27575
+ graph: this,
27576
+ /** @deprecated use cellNamespace instead */
27577
+ model: options.cellModel
27578
+ });
27579
+
27580
+ // The default setup includes a single default layer.
27581
+ layerCollection.add({
27582
+ id: DEFAULT_LAYER_ID
27583
+ }, {
27584
+ graph: this.cid
27585
+ });
27586
+
27587
+ /**
27588
+ * @todo Remove in v5.0.0
27589
+ * @description Retain legacy 'cells' collection in attributes for backward compatibility.
27590
+ * Applicable only when the default layer setup is used.
27591
+ */
27592
+ this.attributes.cells = this.getLayer(DEFAULT_LAYER_ID).cellCollection;
27593
+
27594
+ // Controller that manages communication between the graph and its layers.
27595
+ this.layersController = new GraphLayersController({
27596
+ graph: this
27597
+ });
27598
+
27599
+ // `Graph` keeps an internal data structure (an adjacency list)
27600
+ // for fast graph queries. All changes that affect the structure of the graph
27601
+ // must be reflected in the `al` object. This object provides fast answers to
27602
+ // questions such as "what are the neighbors of this node" or "what
27603
+ // are the sibling links of this link".
27604
+ this.topologyIndex = new GraphTopologyIndex({
27605
+ layerCollection
27606
+ });
27607
+ this._batches = {};
26860
27608
  },
26861
27609
  toJSON: function (opt = {}) {
26862
- // JointJS does not recursively call `toJSON()` on attributes that are themselves models/collections.
26863
- // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
26864
- var json = Model.prototype.toJSON.apply(this, arguments);
26865
- json.cells = this.get('cells').toJSON(opt.cellAttributes);
27610
+ const {
27611
+ layerCollection
27612
+ } = this;
27613
+ // Get the graph model attributes as a base JSON.
27614
+ const json = Model.prototype.toJSON.apply(this, arguments);
27615
+
27616
+ // Add `cells` array holding all the cells in the graph.
27617
+ json.cells = this.getCells().map(cell => cell.toJSON(opt.cellAttributes));
27618
+ if (this.legacyMode) {
27619
+ // Backwards compatibility for legacy setup
27620
+ // with single default layer 'cells'.
27621
+ // In this case, we do not need to export layers.
27622
+ return json;
27623
+ }
27624
+
27625
+ // Add `layers` array holding all the layers in the graph.
27626
+ json.layers = layerCollection.toJSON();
27627
+
27628
+ // Add `defaultLayer` property indicating the default layer ID.
27629
+ json.defaultLayer = this.defaultLayerId;
26866
27630
  return json;
26867
27631
  },
26868
27632
  fromJSON: function (json, opt) {
26869
- if (!json.cells) {
27633
+ const {
27634
+ cells,
27635
+ layers,
27636
+ defaultLayer,
27637
+ ...attributes
27638
+ } = json;
27639
+ if (!cells) {
26870
27640
  throw new Error('Graph JSON must contain cells array.');
26871
27641
  }
26872
- return this.set(json, opt);
26873
- },
26874
- set: function (key, val, opt) {
26875
- var attrs;
26876
27642
 
26877
- // Handle both `key`, value and {key: value} style arguments.
26878
- if (typeof key === 'object') {
26879
- attrs = key;
26880
- opt = val;
26881
- } else {
26882
- (attrs = {})[key] = val;
27643
+ // The `fromJSON` should trigger a single 'reset' event at the end.
27644
+ // Set all attributes silently for now.
27645
+ this.set(attributes, {
27646
+ silent: true
27647
+ });
27648
+ if (layers) {
27649
+ // Reset the layers collection
27650
+ // (`layers:reset` is not forwarded to the graph).
27651
+ this._resetLayers(layers, defaultLayer, opt);
26883
27652
  }
26884
-
26885
- // Make sure that `cells` attribute is handled separately via resetCells().
26886
- if (attrs.hasOwnProperty('cells')) {
26887
- this.resetCells(attrs.cells, opt);
26888
- attrs = omit(attrs, 'cells');
27653
+ if (cells) {
27654
+ // Reset the cells collection and trigger the 'reset' event.
27655
+ this.resetCells(cells, opt);
26889
27656
  }
26890
-
26891
- // The rest of the attributes are applied via original set method.
26892
- return Model.prototype.set.call(this, attrs, opt);
27657
+ return this;
26893
27658
  },
27659
+ /** @deprecated */
26894
27660
  clear: function (opt) {
26895
27661
  opt = assign({}, opt, {
26896
27662
  clear: true
26897
27663
  });
26898
- var collection = this.get('cells');
26899
- if (collection.length === 0) return this;
27664
+ const cells = this.getCells();
27665
+ if (cells.length === 0) return this;
26900
27666
  this.startBatch('clear', opt);
26901
-
26902
- // The elements come after the links.
26903
- var cells = collection.sortBy(function (cell) {
27667
+ const sortedCells = sortBy(cells, cell => {
26904
27668
  return cell.isLink() ? 1 : 2;
26905
27669
  });
26906
27670
  do {
@@ -26908,43 +27672,56 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26908
27672
  // Note that all the links are removed first, so it's
26909
27673
  // safe to remove the elements without removing the connected
26910
27674
  // links first.
26911
- cells.shift().remove(opt);
26912
- } while (cells.length > 0);
27675
+ this.layerCollection.removeCell(sortedCells.shift(), opt);
27676
+ } while (sortedCells.length > 0);
26913
27677
  this.stopBatch('clear');
26914
27678
  return this;
26915
27679
  },
26916
- _prepareCell: function (cell) {
26917
- let attrs;
26918
- if (cell instanceof Model) {
26919
- attrs = cell.attributes;
27680
+ _prepareCell: function (cellInit, opt) {
27681
+ let cellAttributes;
27682
+ if (cellInit[CELL_MARKER]) {
27683
+ cellAttributes = cellInit.attributes;
26920
27684
  } else {
26921
- attrs = cell;
27685
+ cellAttributes = cellInit;
26922
27686
  }
26923
- if (!isString(attrs.type)) {
27687
+ if (!isString(cellAttributes.type)) {
26924
27688
  throw new TypeError('dia.Graph: cell type must be a string.');
26925
27689
  }
26926
- return cell;
27690
+
27691
+ // Backward compatibility: prior v4.2, z-index was not set during reset.
27692
+ if (opt && opt.ensureZIndex) {
27693
+ if (cellAttributes.z === undefined) {
27694
+ const layerId = cellAttributes[config$3.layerAttribute] || this.defaultLayerId;
27695
+ const zIndex = this.maxZIndex(layerId) + 1;
27696
+ if (cellInit[CELL_MARKER]) {
27697
+ // Set with event in case there is a listener
27698
+ // directly on the cell instance
27699
+ // (the cell is not part of graph yet)
27700
+ cellInit.set('z', zIndex, opt);
27701
+ } else {
27702
+ cellAttributes.z = zIndex;
27703
+ }
27704
+ }
27705
+ }
27706
+ return cellInit;
26927
27707
  },
26928
- minZIndex: function () {
26929
- var firstCell = this.get('cells').first();
26930
- return firstCell ? firstCell.get('z') || 0 : 0;
27708
+ minZIndex: function (layerId = this.defaultLayerId) {
27709
+ const layer = this.getLayer(layerId);
27710
+ return layer.cellCollection.minZIndex();
26931
27711
  },
26932
- maxZIndex: function () {
26933
- var lastCell = this.get('cells').last();
26934
- return lastCell ? lastCell.get('z') || 0 : 0;
27712
+ maxZIndex: function (layerId = this.defaultLayerId) {
27713
+ const layer = this.getLayer(layerId);
27714
+ return layer.cellCollection.maxZIndex();
26935
27715
  },
26936
- addCell: function (cell, opt) {
26937
- if (Array.isArray(cell)) {
26938
- return this.addCells(cell, opt);
27716
+ addCell: function (cellInit, options) {
27717
+ if (Array.isArray(cellInit)) {
27718
+ return this.addCells(cellInit, options);
26939
27719
  }
26940
- if (cell instanceof Model) {
26941
- if (!cell.has('z')) {
26942
- cell.set('z', this.maxZIndex() + 1);
26943
- }
26944
- } else if (cell.z === undefined) {
26945
- cell.z = this.maxZIndex() + 1;
26946
- }
26947
- this.get('cells').add(this._prepareCell(cell, opt), opt || {});
27720
+ this._prepareCell(cellInit, {
27721
+ ...options,
27722
+ ensureZIndex: true
27723
+ });
27724
+ this.layerCollection.addCellToLayer(cellInit, this.getCellLayerId(cellInit), options);
26948
27725
  return this;
26949
27726
  },
26950
27727
  addCells: function (cells, opt) {
@@ -26952,50 +27729,278 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
26952
27729
  cells = flattenDeep(cells);
26953
27730
  opt.maxPosition = opt.position = cells.length - 1;
26954
27731
  this.startBatch('add', opt);
26955
- cells.forEach(function (cell) {
27732
+ cells.forEach(cell => {
26956
27733
  this.addCell(cell, opt);
26957
27734
  opt.position--;
26958
- }, this);
27735
+ });
26959
27736
  this.stopBatch('add', opt);
26960
27737
  return this;
26961
27738
  },
26962
- // When adding a lot of cells, it is much more efficient to
26963
- // reset the entire cells collection in one go.
26964
- // Useful for bulk operations and optimizations.
26965
- resetCells: function (cells, opt) {
26966
- var preparedCells = toArray$1(cells).map(function (cell) {
26967
- return this._prepareCell(cell, opt);
26968
- }, this);
26969
- this.get('cells').reset(preparedCells, opt);
27739
+ /**
27740
+ * @public
27741
+ * @description Reset the cells in the graph.
27742
+ * Useful for bulk operations and optimizations.
27743
+ */
27744
+ resetCells: function (cellInits, options) {
27745
+ const {
27746
+ layerCollection
27747
+ } = this;
27748
+ // Note: `cellInits` is always an array and `options` is always an object.
27749
+ // See `wrappers.cells` at the end of this file.
27750
+
27751
+ // When resetting cells, do not set z-index if not provided.
27752
+ const prepareOptions = {
27753
+ ...options,
27754
+ ensureZIndex: false
27755
+ };
27756
+
27757
+ // Initialize a map of layer IDs to arrays of cells
27758
+ const layerCellsMap = layerCollection.reduce((map, layer) => {
27759
+ map[layer.id] = [];
27760
+ return map;
27761
+ }, {});
27762
+
27763
+ // Distribute cells into their respective layers
27764
+ for (let i = 0; i < cellInits.length; i++) {
27765
+ const cellInit = cellInits[i];
27766
+ const layerId = this.getCellLayerId(cellInit);
27767
+ if (layerId in layerCellsMap) {
27768
+ this._prepareCell(cellInit, prepareOptions);
27769
+ layerCellsMap[layerId].push(cellInit);
27770
+ } else {
27771
+ throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
27772
+ }
27773
+ }
27774
+
27775
+ // Reset each layer's cell collection with the corresponding cells.
27776
+ layerCollection.each(layer => {
27777
+ layer.cellCollection.reset(layerCellsMap[layer.id], options);
27778
+ });
27779
+
27780
+ // Trigger a single `reset` event on the graph
27781
+ // (while multiple `reset` events are triggered on layers).
27782
+ // Backwards compatibility: use default layer collection
27783
+ // The `collection` parameter is retained for backwards compatibility,
27784
+ // and it is subject to removal in future releases.
27785
+ this.trigger('reset', this.getDefaultLayer().cellCollection, options);
26970
27786
  return this;
26971
27787
  },
26972
- removeCells: function (cells, opt) {
26973
- if (cells.length) {
26974
- this.startBatch('remove');
26975
- invoke(cells, 'remove', opt);
26976
- this.stopBatch('remove');
27788
+ /**
27789
+ * @public
27790
+ * @description Get the layer ID in which the cell resides.
27791
+ * Cells without an explicit layer are assigned to the default layer.
27792
+ * @param {dia.Cell | Object} cellInit - Cell model or attributes.
27793
+ * @returns {string} - The layer ID.
27794
+ */
27795
+ getCellLayerId: function (cellInit) {
27796
+ if (!cellInit) {
27797
+ throw new Error('dia.Graph: No cell provided.');
27798
+ }
27799
+ const cellAttributes = cellInit[CELL_MARKER] ? cellInit.attributes : cellInit;
27800
+ return cellAttributes[config$3.layerAttribute] || this.defaultLayerId;
27801
+ },
27802
+ /**
27803
+ * @protected
27804
+ * @description Reset the layers in the graph.
27805
+ * It assumes the existing cells have been removed beforehand
27806
+ * or can be discarded.
27807
+ */
27808
+ _resetLayers: function (layers, defaultLayerId, options = {}) {
27809
+ if (!Array.isArray(layers) || layers.length === 0) {
27810
+ throw new Error('dia.Graph: At least one layer must be defined.');
27811
+ }
27812
+
27813
+ // Resetting layers disables legacy mode
27814
+ this.legacyMode = false;
27815
+ this.layerCollection.reset(layers, {
27816
+ ...options,
27817
+ graph: this.cid
27818
+ });
27819
+
27820
+ // If no default layer is specified, use the first layer as default
27821
+ if (defaultLayerId) {
27822
+ // The default layer must be one of the defined layers
27823
+ if (!this.hasLayer(defaultLayerId)) {
27824
+ throw new Error(`dia.Graph: default layer "${defaultLayerId}" does not exist.`);
27825
+ }
27826
+ this.defaultLayerId = defaultLayerId;
27827
+ } else {
27828
+ this.defaultLayerId = this.layerCollection.at(0).id;
26977
27829
  }
26978
27830
  return this;
26979
27831
  },
26980
- _removeCell: function (cell, collection, options) {
26981
- options = options || {};
26982
- if (!options.clear) {
26983
- // Applications might provide a `disconnectLinks` option set to `true` in order to
26984
- // disconnect links when a cell is removed rather then removing them. The default
26985
- // is to remove all the associated links.
26986
- if (options.disconnectLinks) {
26987
- this.disconnectLinks(cell, options);
27832
+ /**
27833
+ * @public
27834
+ * @description Remove multiple cells from the graph.
27835
+ * @param {Array<dia.Cell | dia.Cell.ID>} cellRefs - Array of cell references (models or IDs) to remove.
27836
+ * @param {Object} [options] - Removal options. See {@link dia.Graph#removeCell}.
27837
+ */
27838
+ removeCells: function (cellRefs, options) {
27839
+ if (!cellRefs.length) return this;
27840
+ // Remove multiple cells in a single batch
27841
+ this.startBatch('remove');
27842
+ for (const cellRef of cellRefs) {
27843
+ if (!cellRef) continue;
27844
+ let cell;
27845
+ if (cellRef[CELL_MARKER]) {
27846
+ cell = cellRef;
26988
27847
  } else {
26989
- this.removeLinks(cell, options);
27848
+ cell = this.getCell(cellRef);
27849
+ if (!cell) {
27850
+ // The cell might have been already removed (embedded cell, connected link, etc.)
27851
+ continue;
27852
+ }
26990
27853
  }
27854
+ this.layerCollection.removeCell(cell, options);
26991
27855
  }
26992
- // Silently remove the cell from the cells collection. Silently, because
26993
- // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is
26994
- // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events
26995
- // would be triggered on the graph model.
26996
- this.get('cells').remove(cell, {
26997
- silent: true
26998
- });
27856
+ this.stopBatch('remove');
27857
+ return this;
27858
+ },
27859
+ /**
27860
+ * @protected
27861
+ * @description Replace an existing cell with a new cell.
27862
+ */
27863
+ _replaceCell: function (currentCell, newCellInit, opt = {}) {
27864
+ const batchName = 'replace-cell';
27865
+ const replaceOptions = {
27866
+ ...opt,
27867
+ replace: true
27868
+ };
27869
+ this.startBatch(batchName, opt);
27870
+ // 1. Remove the cell without removing connected links or embedded cells.
27871
+ this.layerCollection.removeCell(currentCell, replaceOptions);
27872
+ const newCellInitAttributes = newCellInit[CELL_MARKER] ? newCellInit.attributes : newCellInit;
27873
+ // 2. Combine the current cell attributes with the new cell attributes
27874
+ const replacementCellAttributes = Object.assign({}, currentCell.attributes, newCellInitAttributes);
27875
+ let replacement;
27876
+ if (newCellInit[CELL_MARKER]) {
27877
+ // If the new cell is a model, set the merged attributes on the model
27878
+ newCellInit.set(replacementCellAttributes, replaceOptions);
27879
+ replacement = newCellInit;
27880
+ } else {
27881
+ replacement = replacementCellAttributes;
27882
+ }
27883
+
27884
+ // 3. Add the replacement cell
27885
+ this.addCell(replacement, replaceOptions);
27886
+ this.stopBatch(batchName);
27887
+ },
27888
+ /**
27889
+ * @protected
27890
+ * @description Synchronize a single graph cell with the provided cell (model or attributes).
27891
+ * If the cell with the same `id` exists, it is updated. If the cell does not exist, it is added.
27892
+ * If the existing cell type is different from the incoming cell type, the existing cell is replaced.
27893
+ */
27894
+ _syncCell: function (cellInit, opt = {}) {
27895
+ const cellAttributes = cellInit[CELL_MARKER] ? cellInit.attributes : cellInit;
27896
+ const currentCell = this.getCell(cellInit.id);
27897
+ if (currentCell) {
27898
+ // `cellInit` is either a model or attributes object
27899
+ if ('type' in cellAttributes && currentCell.get('type') !== cellAttributes.type) {
27900
+ // Replace the cell if the type has changed
27901
+ this._replaceCell(currentCell, cellInit, opt);
27902
+ } else {
27903
+ // Update existing cell
27904
+ // Note: the existing cell attributes are not removed,
27905
+ // if they're missing in `cellAttributes`.
27906
+ currentCell.set(cellAttributes, opt);
27907
+ }
27908
+ } else {
27909
+ // The cell does not exist yet, add it
27910
+ this.addCell(cellInit, opt);
27911
+ }
27912
+ },
27913
+ /**
27914
+ * @public
27915
+ * @description Synchronize the graph cells with the provided array of cells (models or attributes).
27916
+ */
27917
+ syncCells: function (cellInits, opt = {}) {
27918
+ const batchName = 'sync-cells';
27919
+ const {
27920
+ remove = false,
27921
+ ...setOpt
27922
+ } = opt;
27923
+ let currentCells, newCellsMap;
27924
+ if (remove) {
27925
+ // We need to track existing cells to remove the missing ones later
27926
+ currentCells = this.getCells();
27927
+ newCellsMap = new Map();
27928
+ }
27929
+
27930
+ // Observe changes to the graph cells
27931
+ let changeObserver, changedLayers;
27932
+ const shouldSort = opt.sort !== false;
27933
+ if (shouldSort) {
27934
+ changeObserver = new Listener();
27935
+ changedLayers = new Set();
27936
+ changeObserver.listenTo(this, {
27937
+ 'add': cell => {
27938
+ changedLayers.add(this.getCellLayerId(cell));
27939
+ },
27940
+ 'change': cell => {
27941
+ if (cell.hasChanged(config$3.layerAttribute) || cell.hasChanged('z')) {
27942
+ changedLayers.add(this.getCellLayerId(cell));
27943
+ }
27944
+ }
27945
+ });
27946
+ }
27947
+ this.startBatch(batchName, opt);
27948
+
27949
+ // Prevent multiple sorts during sync
27950
+ setOpt.sort = false;
27951
+
27952
+ // Add or update incoming cells
27953
+ for (const cellInit of cellInits) {
27954
+ if (remove) {
27955
+ // only track existence
27956
+ newCellsMap.set(cellInit.id, true);
27957
+ }
27958
+ this._syncCell(cellInit, setOpt);
27959
+ }
27960
+ if (remove) {
27961
+ // Remove cells not present in the incoming array
27962
+ for (const cell of currentCells) {
27963
+ if (!newCellsMap.has(cell.id)) {
27964
+ this.layerCollection.removeCell(cell, setOpt);
27965
+ }
27966
+ }
27967
+ }
27968
+ if (shouldSort) {
27969
+ // Sort layers that had changes affecting z-index or layer
27970
+ changeObserver.stopListening();
27971
+ for (const layerId of changedLayers) {
27972
+ this.getLayer(layerId).cellCollection.sort(opt);
27973
+ }
27974
+ }
27975
+ this.stopBatch(batchName);
27976
+ },
27977
+ /**
27978
+ * @public
27979
+ * @description Remove a cell from the graph.
27980
+ * @param {dia.Cell} cell
27981
+ * @param {Object} [options]
27982
+ * @param {boolean} [options.disconnectLinks=false] - If `true`, the connected links are
27983
+ * disconnected instead of removed.
27984
+ * @param {boolean} [options.clear=false] - If `true`, the connected links
27985
+ * are kept. @internal
27986
+ * @param {boolean} [options.replace=false] - If `true`, the connected links and
27987
+ * embedded cells are kept. @internal
27988
+ * @throws Will throw an error if no cell is provided
27989
+ * @throws Will throw an error if the ID of the cell to remove
27990
+ * does not exist in the graph
27991
+ **/
27992
+ removeCell: function (cellRef, options) {
27993
+ if (!cellRef) {
27994
+ throw new Error('dia.Graph: no cell provided.');
27995
+ }
27996
+ const cell = cellRef[CELL_MARKER] ? cellRef : this.getCell(cellRef);
27997
+ if (!cell) {
27998
+ throw new Error('dia.Graph: cell to remove does not exist in the graph.');
27999
+ }
28000
+ if (cell.graph !== this) return;
28001
+ this.startBatch('remove');
28002
+ cell.collection.remove(cell, options);
28003
+ this.stopBatch('remove');
26999
28004
  },
27000
28005
  transferCellEmbeds: function (sourceCell, targetCell, opt = {}) {
27001
28006
  const batchName = 'transfer-embeds';
@@ -27025,24 +28030,249 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27025
28030
  });
27026
28031
  this.stopBatch(batchName);
27027
28032
  },
27028
- // Get a cell by `id`.
27029
- getCell: function (id) {
27030
- return this.get('cells').get(id);
28033
+ /**
28034
+ * @private
28035
+ * Helper method for addLayer and moveLayer methods
28036
+ */
28037
+ _getBeforeLayerIdFromOptions(options, layer = null) {
28038
+ let {
28039
+ before = null,
28040
+ index
28041
+ } = options;
28042
+ if (before && index !== undefined) {
28043
+ throw new Error('dia.Graph: Options "before" and "index" are mutually exclusive.');
28044
+ }
28045
+ let computedBefore;
28046
+ if (index !== undefined) {
28047
+ const layersArray = this.getLayers();
28048
+ if (index >= layersArray.length) {
28049
+ // If index is greater than the number of layers,
28050
+ // return before as null (move to the end).
28051
+ computedBefore = null;
28052
+ } else if (index < 0) {
28053
+ // If index is negative, move to the beginning.
28054
+ computedBefore = layersArray[0].id;
28055
+ } else {
28056
+ var _layersArray$index;
28057
+ const originalIndex = layersArray.indexOf(layer);
28058
+ if (originalIndex !== -1 && index > originalIndex) {
28059
+ // If moving a layer upwards in the stack, we need to adjust the index
28060
+ // to account for the layer being removed from its original position.
28061
+ index += 1;
28062
+ }
28063
+ // Otherwise, get the layer ID at the specified index.
28064
+ computedBefore = ((_layersArray$index = layersArray[index]) === null || _layersArray$index === void 0 ? void 0 : _layersArray$index.id) || null;
28065
+ }
28066
+ } else {
28067
+ computedBefore = before;
28068
+ }
28069
+ return computedBefore;
28070
+ },
28071
+ /**
28072
+ * @public
28073
+ * Adds a new layer to the graph.
28074
+ * @param {GraphLayer | GraphLayerJSON} layerInit
28075
+ * @param {*} options
28076
+ * @param {string | null} [options.before] - ID of the layer
28077
+ * before which to insert the new layer. If `null`, the layer is added at the end.
28078
+ * @param {number} [options.index] - Zero-based index to which to add the layer.
28079
+ * @throws Will throw an error if the layer to add is invalid
28080
+ * @throws Will throw an error if a layer with the same ID already exists
28081
+ * @throws Will throw if `before` reference is invalid
28082
+ */
28083
+ addLayer(layerInit, options = {}) {
28084
+ if (!layerInit || !layerInit.id) {
28085
+ throw new Error('dia.Graph: Layer to add is invalid.');
28086
+ }
28087
+ if (this.hasLayer(layerInit.id)) {
28088
+ throw new Error(`dia.Graph: Layer "${layerInit.id}" already exists.`);
28089
+ }
28090
+ const {
28091
+ before = null,
28092
+ index,
28093
+ ...insertOptions
28094
+ } = options;
28095
+ insertOptions.graph = this.cid;
28096
+
28097
+ // Adding a new layer disables legacy mode
28098
+ this.legacyMode = false;
28099
+ const beforeId = this._getBeforeLayerIdFromOptions({
28100
+ before,
28101
+ index
28102
+ });
28103
+ this.layerCollection.insert(layerInit, beforeId, insertOptions);
28104
+ },
28105
+ /**
28106
+ * @public
28107
+ * Moves an existing layer to a new position in the layer stack.
28108
+ * @param {string | GraphLayer} layerRef - ID or reference of the layer to move.
28109
+ * @param {*} options
28110
+ * @param {string | null} [options.before] - ID of the layer
28111
+ * before which to insert the moved layer. If `null`, the layer is moved to the end.
28112
+ * @param {number} [options.index] - Zero-based index to which to move the layer.
28113
+ * @throws Will throw an error if the layer to move does not exist
28114
+ * @throws Will throw an error if `before` reference is invalid
28115
+ * @throws Will throw an error if both `before` and `index` options are provided
28116
+ */
28117
+ moveLayer(layerRef, options = {}) {
28118
+ if (!layerRef || !this.hasLayer(layerRef)) {
28119
+ throw new Error('dia.Graph: Layer to move does not exist.');
28120
+ }
28121
+ const layer = this.getLayer(layerRef);
28122
+ const {
28123
+ before = null,
28124
+ index,
28125
+ ...insertOptions
28126
+ } = options;
28127
+ insertOptions.graph = this.cid;
28128
+
28129
+ // Moving a layer disables legacy mode
28130
+ this.legacyMode = false;
28131
+ const beforeId = this._getBeforeLayerIdFromOptions({
28132
+ before,
28133
+ index
28134
+ }, layer);
28135
+ this.layerCollection.insert(layer, beforeId, insertOptions);
28136
+ },
28137
+ /**
28138
+ * @public
28139
+ * Removes an existing layer from the graph.
28140
+ * @param {string | GraphLayer} layerRef - ID or reference of the layer to remove.
28141
+ * @param {*} options
28142
+ * @throws Will throw an error if no layer is provided
28143
+ * @throws Will throw an error if the layer to remove does not exist
28144
+ */
28145
+ removeLayer(layerRef, options = {}) {
28146
+ if (!layerRef) {
28147
+ throw new Error('dia.Graph: No layer provided.');
28148
+ }
28149
+
28150
+ // The layer must exist
28151
+ const layerId = layerRef.id ? layerRef.id : layerRef;
28152
+ const layer = this.getLayer(layerId);
28153
+
28154
+ // Prevent removing the default layer
28155
+ // Note: if there is only one layer, it is also the default layer.
28156
+ const {
28157
+ id: defaultLayerId
28158
+ } = this.getDefaultLayer();
28159
+ if (layerId === defaultLayerId) {
28160
+ throw new Error('dia.Graph: default layer cannot be removed.');
28161
+ }
28162
+
28163
+ // A layer with cells cannot be removed
28164
+ if (layer.cellCollection.length > 0) {
28165
+ throw new Error(`dia.Graph: Layer "${layerId}" cannot be removed because it is not empty.`);
28166
+ }
28167
+ this.layerCollection.remove(layerId, {
28168
+ ...options,
28169
+ graph: this.cid
28170
+ });
28171
+ },
28172
+ getDefaultLayer() {
28173
+ return this.layerCollection.get(this.defaultLayerId);
28174
+ },
28175
+ setDefaultLayer(layerRef, options = {}) {
28176
+ if (!layerRef) {
28177
+ throw new Error('dia.Graph: No default layer ID provided.');
28178
+ }
28179
+
28180
+ // Make sure the layer exists
28181
+ const defaultLayerId = layerRef.id ? layerRef.id : layerRef;
28182
+ const defaultLayer = this.getLayer(defaultLayerId);
28183
+
28184
+ // If the default layer is not changing, do nothing
28185
+ const currentDefaultLayerId = this.defaultLayerId;
28186
+ if (defaultLayerId === currentDefaultLayerId) {
28187
+ // The default layer stays the same
28188
+ return;
28189
+ }
28190
+
28191
+ // Get all cells that belong to the current default layer implicitly
28192
+ const implicitLayerCells = this.getImplicitLayerCells();
28193
+
28194
+ // Set the new default layer ID
28195
+ this.defaultLayerId = defaultLayerId;
28196
+ const batchName = 'default-layer-change';
28197
+ this.startBatch(batchName, options);
28198
+ if (implicitLayerCells.length > 0) {
28199
+ // Reassign any cells lacking an explicit layer to the new default layer.
28200
+ // Do not sort yet, wait until all cells are moved.
28201
+ const moveOptions = {
28202
+ ...options,
28203
+ sort: false
28204
+ };
28205
+ for (const cell of implicitLayerCells) {
28206
+ this.layerCollection.moveCellBetweenLayers(cell, defaultLayerId, moveOptions);
28207
+ }
28208
+ // Now sort the new default layer
28209
+ if (options.sort !== false) {
28210
+ defaultLayer.cellCollection.sort(options);
28211
+ }
28212
+ }
28213
+
28214
+ // Pretend to trigger the event on the layer itself.
28215
+ // It will bubble up as `layer:default` event on the graph.
28216
+ defaultLayer.trigger(defaultLayer.eventPrefix + 'default', defaultLayer, {
28217
+ ...options,
28218
+ previousDefaultLayerId: currentDefaultLayerId
28219
+ });
28220
+ this.stopBatch(batchName, options);
28221
+ },
28222
+ /**
28223
+ * @protected
28224
+ * @description Get all cells that do not have an explicit layer assigned.
28225
+ * These cells belong to the default layer implicitly.
28226
+ * @return {Array<dia.Cell>} Array of cells without an explicit layer.
28227
+ */
28228
+ getImplicitLayerCells() {
28229
+ return this.getDefaultLayer().cellCollection.filter(cell => {
28230
+ return cell.get(config$3.layerAttribute) == null;
28231
+ });
28232
+ },
28233
+ getLayer(layerId) {
28234
+ if (!this.hasLayer(layerId)) {
28235
+ throw new Error(`dia.Graph: Layer "${layerId}" does not exist.`);
28236
+ }
28237
+ return this.layerCollection.get(layerId);
28238
+ },
28239
+ hasLayer(layerRef) {
28240
+ return this.layerCollection.has(layerRef);
28241
+ },
28242
+ getLayers() {
28243
+ return this.layerCollection.toArray();
28244
+ },
28245
+ getCell: function (cellRef) {
28246
+ return this.layerCollection.getCell(cellRef);
27031
28247
  },
27032
28248
  getCells: function () {
27033
- return this.get('cells').toArray();
28249
+ return this.layerCollection.getCells();
27034
28250
  },
27035
28251
  getElements: function () {
27036
- return this.get('cells').toArray().filter(cell => cell.isElement());
28252
+ return this.getCells().filter(cell => cell.isElement());
27037
28253
  },
27038
28254
  getLinks: function () {
27039
- return this.get('cells').toArray().filter(cell => cell.isLink());
28255
+ return this.getCells().filter(cell => cell.isLink());
27040
28256
  },
27041
- getFirstCell: function () {
27042
- return this.get('cells').first();
28257
+ getFirstCell: function (layerId) {
28258
+ let layer;
28259
+ if (!layerId) {
28260
+ // Get the first cell from the bottom-most layer
28261
+ layer = this.getLayers().at(0);
28262
+ } else {
28263
+ layer = this.getLayer(layerId);
28264
+ }
28265
+ return layer.cellCollection.models.at(0);
27043
28266
  },
27044
- getLastCell: function () {
27045
- return this.get('cells').last();
28267
+ getLastCell: function (layerId) {
28268
+ let layer;
28269
+ if (!layerId) {
28270
+ // Get the last cell from the top-most layer
28271
+ layer = this.getLayers().at(-1);
28272
+ } else {
28273
+ layer = this.getLayer(layerId);
28274
+ }
28275
+ return layer.cellCollection.models.at(-1);
27046
28276
  },
27047
28277
  // Get all inbound and outbound links connected to the cell `model`.
27048
28278
  getConnectedLinks: function (model, opt) {
@@ -27066,12 +28296,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27066
28296
  addInbounds(this, model);
27067
28297
  }
27068
28298
  function addOutbounds(graph, model) {
27069
- forIn(graph.getOutboundEdges(model.id), function (_, edge) {
28299
+ forIn(graph.topologyIndex.getOutboundEdges(model.id), function (_, edge) {
27070
28300
  // skip links that were already added
27071
28301
  // (those must be self-loop links)
27072
28302
  // (because they are inbound and outbound edges of the same two elements)
27073
28303
  if (edges[edge]) return;
27074
28304
  var link = graph.getCell(edge);
28305
+ if (!link) return;
27075
28306
  links.push(link);
27076
28307
  edges[edge] = true;
27077
28308
  if (indirect) {
@@ -27090,12 +28321,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27090
28321
  }
27091
28322
  }
27092
28323
  function addInbounds(graph, model) {
27093
- forIn(graph.getInboundEdges(model.id), function (_, edge) {
28324
+ forIn(graph.topologyIndex.getInboundEdges(model.id), function (_, edge) {
27094
28325
  // skip links that were already added
27095
28326
  // (those must be self-loop links)
27096
28327
  // (because they are inbound and outbound edges of the same two elements)
27097
28328
  if (edges[edge]) return;
27098
28329
  var link = graph.getCell(edge);
28330
+ if (!link) return;
27099
28331
  links.push(link);
27100
28332
  edges[edge] = true;
27101
28333
  if (indirect) {
@@ -27130,7 +28362,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27130
28362
  embeddedCells.forEach(function (cell) {
27131
28363
  if (cell.isLink()) return;
27132
28364
  if (outbound) {
27133
- forIn(this.getOutboundEdges(cell.id), function (exists, edge) {
28365
+ forIn(this.topologyIndex.getOutboundEdges(cell.id), function (exists, edge) {
27134
28366
  if (!edges[edge]) {
27135
28367
  var edgeCell = this.getCell(edge);
27136
28368
  var {
@@ -27150,7 +28382,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27150
28382
  }.bind(this));
27151
28383
  }
27152
28384
  if (inbound) {
27153
- forIn(this.getInboundEdges(cell.id), function (exists, edge) {
28385
+ forIn(this.topologyIndex.getInboundEdges(cell.id), function (exists, edge) {
27154
28386
  if (!edges[edge]) {
27155
28387
  var edgeCell = this.getCell(edge);
27156
28388
  var {
@@ -27429,31 +28661,19 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27429
28661
  },
27430
28662
  // Get all the roots of the graph. Time complexity: O(|V|).
27431
28663
  getSources: function () {
27432
- var sources = [];
27433
- forIn(this._nodes, function (exists, node) {
27434
- if (!this._in[node] || isEmpty(this._in[node])) {
27435
- sources.push(this.getCell(node));
27436
- }
27437
- }.bind(this));
27438
- return sources;
28664
+ return this.topologyIndex.getSourceNodes().map(nodeId => this.getCell(nodeId));
27439
28665
  },
27440
28666
  // Get all the leafs of the graph. Time complexity: O(|V|).
27441
28667
  getSinks: function () {
27442
- var sinks = [];
27443
- forIn(this._nodes, function (exists, node) {
27444
- if (!this._out[node] || isEmpty(this._out[node])) {
27445
- sinks.push(this.getCell(node));
27446
- }
27447
- }.bind(this));
27448
- return sinks;
28668
+ return this.topologyIndex.getSinkNodes().map(nodeId => this.getCell(nodeId));
27449
28669
  },
27450
28670
  // Return `true` if `element` is a root. Time complexity: O(1).
27451
28671
  isSource: function (element) {
27452
- return !this._in[element.id] || isEmpty(this._in[element.id]);
28672
+ return this.topologyIndex.isSourceNode(element.id);
27453
28673
  },
27454
28674
  // Return `true` if `element` is a leaf. Time complexity: O(1).
27455
28675
  isSink: function (element) {
27456
- return !this._out[element.id] || isEmpty(this._out[element.id]);
28676
+ return this.topologyIndex.isSinkNode(element.id);
27457
28677
  },
27458
28678
  // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise.
27459
28679
  isSuccessor: function (elementA, elementB) {
@@ -27524,8 +28744,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27524
28744
  });
27525
28745
  },
27526
28746
  // Remove links connected to the cell `model` completely.
27527
- removeLinks: function (model, opt) {
27528
- invoke(this.getConnectedLinks(model), 'remove', opt);
28747
+ removeLinks: function (cell, opt) {
28748
+ this.getConnectedLinks(cell).forEach(link => {
28749
+ this.layerCollection.removeCell(link, opt);
28750
+ });
27529
28751
  },
27530
28752
  // Find all cells at given point
27531
28753
 
@@ -27716,85 +28938,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
27716
28938
  });
27717
28939
  wrapWith(Graph.prototype, ['resetCells', 'addCells', 'removeCells'], wrappers.cells);
27718
28940
 
27719
- const LayersNames = {
27720
- GRID: 'grid',
27721
- CELLS: 'cells',
27722
- BACK: 'back',
27723
- FRONT: 'front',
27724
- TOOLS: 'tools',
27725
- LABELS: 'labels'
27726
- };
27727
- const PaperLayer = View.extend({
27728
- tagName: 'g',
27729
- svgElement: true,
27730
- pivotNodes: null,
27731
- defaultTheme: null,
27732
- options: {
27733
- name: ''
27734
- },
27735
- className: function () {
27736
- const {
27737
- name
27738
- } = this.options;
27739
- if (!name) return null;
27740
- return addClassNamePrefix(`${name}-layer`);
27741
- },
27742
- init: function () {
27743
- this.pivotNodes = {};
27744
- },
27745
- insertSortedNode: function (node, z) {
27746
- this.el.insertBefore(node, this.insertPivot(z));
27747
- },
27748
- insertNode: function (node) {
27749
- const {
27750
- el
27751
- } = this;
27752
- if (node.parentNode !== el) {
27753
- el.appendChild(node);
27754
- }
27755
- },
27756
- insertPivot: function (z) {
27757
- const {
27758
- el,
27759
- pivotNodes
27760
- } = this;
27761
- z = +z;
27762
- z || (z = 0);
27763
- let pivotNode = pivotNodes[z];
27764
- if (pivotNode) return pivotNode;
27765
- pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1));
27766
- let neighborZ = -Infinity;
27767
- for (let currentZ in pivotNodes) {
27768
- currentZ = +currentZ;
27769
- if (currentZ < z && currentZ > neighborZ) {
27770
- neighborZ = currentZ;
27771
- if (neighborZ === z - 1) continue;
27772
- }
27773
- }
27774
- if (neighborZ !== -Infinity) {
27775
- const neighborPivot = pivotNodes[neighborZ];
27776
- // Insert After
27777
- el.insertBefore(pivotNode, neighborPivot.nextSibling);
27778
- } else {
27779
- // First Child
27780
- el.insertBefore(pivotNode, el.firstChild);
27781
- }
27782
- return pivotNode;
27783
- },
27784
- removePivots: function () {
27785
- const {
27786
- el,
27787
- pivotNodes
27788
- } = this;
27789
- for (let z in pivotNodes) el.removeChild(pivotNodes[z]);
27790
- this.pivotNodes = {};
27791
- },
27792
- isEmpty: function () {
27793
- // Check if the layer has any child elements (pivot comments are not counted).
27794
- return this.el.children.length === 0;
27795
- }
27796
- });
27797
-
27798
28941
  const calcAttributesList = ['transform', 'x', 'y', 'cx', 'cy', 'dx', 'dy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size'];
27799
28942
  const positiveValueList = ['r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size'];
27800
28943
  const calcAttributes = calcAttributesList.reduce((acc, attrName) => {
@@ -29036,7 +30179,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
29036
30179
  // Internal tag to identify this object as a cell view instance.
29037
30180
  // Used instead of `instanceof` for performance and cross-frame safety.
29038
30181
 
29039
- const CELL_VIEW_MARKER = Symbol('joint.cellViewMarker');
29040
30182
  Object.defineProperty(CellView.prototype, CELL_VIEW_MARKER, {
29041
30183
  value: true
29042
30184
  });
@@ -31844,6 +32986,231 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
31844
32986
  }
31845
32987
  });
31846
32988
 
32989
+ const LayerView = View.extend({
32990
+ tagName: 'g',
32991
+ svgElement: true,
32992
+ pivotNodes: null,
32993
+ defaultTheme: null,
32994
+ UPDATE_PRIORITY: 4,
32995
+ options: {
32996
+ id: ''
32997
+ },
32998
+ paper: null,
32999
+ init: function () {
33000
+ this.pivotNodes = {};
33001
+ this.id = this.options.id || this.cid;
33002
+ },
33003
+ setPaperReference: function (paper) {
33004
+ this.paper = paper;
33005
+ this.afterPaperReferenceSet(paper);
33006
+ },
33007
+ unsetPaperReference: function () {
33008
+ this.beforePaperReferenceUnset();
33009
+ this.paper = null;
33010
+ },
33011
+ assertPaperReference() {
33012
+ if (!this.paper) {
33013
+ throw new Error('LayerView: paper reference is not set.');
33014
+ }
33015
+ },
33016
+ afterPaperReferenceSet: function () {
33017
+ // Can be overridden in subclasses.
33018
+ },
33019
+ beforePaperReferenceUnset: function () {
33020
+ // Can be overridden in subclasses.
33021
+ },
33022
+ // prevents id to be set on the DOM element
33023
+ _setAttributes: function (attrs) {
33024
+ const newAttrs = clone$1(attrs);
33025
+ delete newAttrs.id;
33026
+ View.prototype._setAttributes.call(this, newAttrs);
33027
+ },
33028
+ className: function () {
33029
+ const {
33030
+ id
33031
+ } = this.options;
33032
+ return addClassNamePrefix(`${id}-layer`);
33033
+ },
33034
+ insertSortedNode: function (node, z) {
33035
+ this.el.insertBefore(node, this.insertPivot(z));
33036
+ },
33037
+ insertNode: function (node) {
33038
+ const {
33039
+ el
33040
+ } = this;
33041
+ if (node.parentNode !== el) {
33042
+ el.appendChild(node);
33043
+ }
33044
+ },
33045
+ insertPivot: function (z) {
33046
+ const {
33047
+ el,
33048
+ pivotNodes
33049
+ } = this;
33050
+ z = +z;
33051
+ z || (z = 0);
33052
+ let pivotNode = pivotNodes[z];
33053
+ if (pivotNode) return pivotNode;
33054
+ pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1));
33055
+ let neighborZ = -Infinity;
33056
+ for (let currentZ in pivotNodes) {
33057
+ currentZ = +currentZ;
33058
+ if (currentZ < z && currentZ > neighborZ) {
33059
+ neighborZ = currentZ;
33060
+ if (neighborZ === z - 1) continue;
33061
+ }
33062
+ }
33063
+ if (neighborZ !== -Infinity) {
33064
+ const neighborPivot = pivotNodes[neighborZ];
33065
+ // Insert After
33066
+ el.insertBefore(pivotNode, neighborPivot.nextSibling);
33067
+ } else {
33068
+ // First Child
33069
+ el.insertBefore(pivotNode, el.firstChild);
33070
+ }
33071
+ return pivotNode;
33072
+ },
33073
+ removePivots: function () {
33074
+ const {
33075
+ el,
33076
+ pivotNodes
33077
+ } = this;
33078
+ for (let z in pivotNodes) el.removeChild(pivotNodes[z]);
33079
+ this.pivotNodes = {};
33080
+ },
33081
+ isEmpty: function () {
33082
+ // Check if the layer has any child elements (pivot comments are not counted).
33083
+ return this.el.children.length === 0;
33084
+ },
33085
+ reset: function () {
33086
+ this.removePivots();
33087
+ }
33088
+ });
33089
+ Object.defineProperty(LayerView.prototype, LAYER_VIEW_MARKER, {
33090
+ value: true
33091
+ });
33092
+
33093
+ /**
33094
+ * @class GraphLayerView
33095
+ * @description A GraphLayerView is responsible for managing the rendering of cell views inside a layer.
33096
+ * It listens to the corresponding GraphLayer model and updates the DOM accordingly.
33097
+ * It uses dia.Paper sorting options to sort cell views in the DOM based on their `z` attribute.
33098
+ */
33099
+ const GraphLayerView = LayerView.extend({
33100
+ SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
33101
+ style: {
33102
+ webkitUserSelect: 'none',
33103
+ userSelect: 'none'
33104
+ },
33105
+ graph: null,
33106
+ init() {
33107
+ LayerView.prototype.init.apply(this, arguments);
33108
+ this.graph = this.model.graph;
33109
+ },
33110
+ className: function () {
33111
+ const {
33112
+ id
33113
+ } = this.options;
33114
+ return [addClassNamePrefix(`${id}-layer`), addClassNamePrefix('cells')].join(' ');
33115
+ },
33116
+ afterPaperReferenceSet(paper) {
33117
+ this.listenTo(this.model, 'sort', this.onCellCollectionSort);
33118
+ this.listenTo(this.model, 'change', this.onCellChange);
33119
+ this.listenTo(this.model, 'move', this.onCellMove);
33120
+ this.listenTo(this.graph, 'batch:stop', this.onGraphBatchStop);
33121
+ },
33122
+ beforePaperReferenceUnset() {
33123
+ this.stopListening(this.model);
33124
+ this.stopListening(this.graph);
33125
+ },
33126
+ onCellCollectionSort() {
33127
+ if (this.graph.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
33128
+ this.sort();
33129
+ },
33130
+ onCellMove(cell, opt = {}) {
33131
+ // When a cell is moved from one layer to another,
33132
+ // request insertion of its view in the new layer.
33133
+ this.paper.requestCellViewInsertion(cell, opt);
33134
+ },
33135
+ onCellChange(cell, opt) {
33136
+ if (!cell.hasChanged('z')) return;
33137
+ // Re-insert the cell view to maintain correct z-ordering
33138
+ if (this.paper.options.sorting === sortingTypes.APPROX) {
33139
+ this.paper.requestCellViewInsertion(cell, opt);
33140
+ }
33141
+ },
33142
+ onGraphBatchStop(data) {
33143
+ const name = data && data.batchName;
33144
+ const sortDelayingBatches = this.SORT_DELAYING_BATCHES;
33145
+ // After certain batches, sorting may be required
33146
+ if (sortDelayingBatches.includes(name) && !this.graph.hasActiveBatch(sortDelayingBatches)) {
33147
+ this.sort();
33148
+ }
33149
+ },
33150
+ sort() {
33151
+ this.assertPaperReference();
33152
+ const {
33153
+ paper
33154
+ } = this;
33155
+ if (!paper.isExactSorting()) {
33156
+ // noop
33157
+ return;
33158
+ }
33159
+ if (paper.isFrozen()) {
33160
+ // sort views once unfrozen
33161
+ paper._updates.sort = true;
33162
+ return;
33163
+ }
33164
+ this.sortExact();
33165
+ },
33166
+ sortExact() {
33167
+ // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
33168
+ // associated model `z` attribute.
33169
+ const cellNodes = Array.from(this.el.children).filter(node => node.getAttribute('model-id'));
33170
+ const cellCollection = this.model.cellCollection;
33171
+ sortElements(cellNodes, function (a, b) {
33172
+ const cellA = cellCollection.get(a.getAttribute('model-id'));
33173
+ const cellB = cellCollection.get(b.getAttribute('model-id'));
33174
+ const zA = cellA.attributes.z || 0;
33175
+ const zB = cellB.attributes.z || 0;
33176
+ return zA === zB ? 0 : zA < zB ? -1 : 1;
33177
+ });
33178
+ },
33179
+ insertCellView(cellView) {
33180
+ this.assertPaperReference();
33181
+ const {
33182
+ paper
33183
+ } = this;
33184
+ const {
33185
+ el,
33186
+ model
33187
+ } = cellView;
33188
+ switch (paper.options.sorting) {
33189
+ case sortingTypes.APPROX:
33190
+ this.insertSortedNode(el, model.get('z'));
33191
+ break;
33192
+ case sortingTypes.EXACT:
33193
+ default:
33194
+ this.insertNode(el);
33195
+ break;
33196
+ }
33197
+ }
33198
+ });
33199
+ Object.defineProperty(GraphLayerView.prototype, GRAPH_LAYER_VIEW_MARKER, {
33200
+ value: true
33201
+ });
33202
+
33203
+ /**
33204
+ * @class LegacyGraphLayerView
33205
+ * @description A legacy GraphLayerView with an additional class name for backward compatibility.
33206
+ */
33207
+ const LegacyGraphLayerView = GraphLayerView.extend({
33208
+ className: function () {
33209
+ const className = GraphLayerView.prototype.className.apply(this, arguments);
33210
+ return className + ' ' + addClassNamePrefix('viewport');
33211
+ }
33212
+ });
33213
+
31847
33214
  /**
31848
33215
  * Deque implementation for managing a double-ended queue.
31849
33216
  * This implementation uses a doubly linked list for efficient operations.
@@ -31966,23 +33333,24 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
31966
33333
  }
31967
33334
  }
31968
33335
 
31969
- const GridLayer = PaperLayer.extend({
33336
+ const GridLayerView = LayerView.extend({
31970
33337
  style: {
31971
33338
  'pointer-events': 'none'
31972
33339
  },
31973
33340
  _gridCache: null,
31974
33341
  _gridSettings: null,
31975
33342
  init() {
31976
- PaperLayer.prototype.init.apply(this, arguments);
31977
- const {
31978
- options: {
31979
- paper
31980
- }
31981
- } = this;
33343
+ LayerView.prototype.init.apply(this, arguments);
33344
+ this.paper = this.options.paper;
31982
33345
  this._gridCache = null;
31983
33346
  this._gridSettings = [];
33347
+ },
33348
+ afterPaperReferenceSet(paper) {
31984
33349
  this.listenTo(paper, 'transform resize', this.updateGrid);
31985
33350
  },
33351
+ beforePaperReferenceUnset(paper) {
33352
+ this.stopListening(paper);
33353
+ },
31986
33354
  setGrid(drawGrid) {
31987
33355
  this._gridSettings = this.getGridSettings(drawGrid);
31988
33356
  this.renderGrid();
@@ -32007,9 +33375,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32007
33375
  },
32008
33376
  renderGrid() {
32009
33377
  const {
32010
- options: {
32011
- paper
32012
- }
33378
+ paper
32013
33379
  } = this;
32014
33380
  const {
32015
33381
  _gridSettings: gridSettings
@@ -32054,9 +33420,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32054
33420
  const {
32055
33421
  _gridCache: grid,
32056
33422
  _gridSettings: gridSettings,
32057
- options: {
32058
- paper
32059
- }
33423
+ paper
32060
33424
  } = this;
32061
33425
  if (!grid) return;
32062
33426
  const {
@@ -32090,7 +33454,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32090
33454
  });
32091
33455
  },
32092
33456
  _getPatternId(index) {
32093
- return `pattern_${this.options.paper.cid}_${index}`;
33457
+ return `pattern_${this.paper.cid}_${index}`;
32094
33458
  },
32095
33459
  _getGridRefs() {
32096
33460
  let {
@@ -32125,27 +33489,27 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32125
33489
  return grid;
32126
33490
  },
32127
33491
  _resolveDrawGridOption(opt) {
32128
- var namespace = this.options.patterns;
33492
+ const namespace = this.options.patterns;
32129
33493
  if (isString(opt) && Array.isArray(namespace[opt])) {
32130
33494
  return namespace[opt].map(function (item) {
32131
33495
  return assign({}, item);
32132
33496
  });
32133
33497
  }
32134
- var options = opt || {
33498
+ const options = opt || {
32135
33499
  args: [{}]
32136
33500
  };
32137
- var isArray = Array.isArray(options);
32138
- var name = options.name;
33501
+ const isArray = Array.isArray(options);
33502
+ let name = options.name;
32139
33503
  if (!isArray && !name && !options.markup) {
32140
33504
  name = 'dot';
32141
33505
  }
32142
33506
  if (name && Array.isArray(namespace[name])) {
32143
- var pattern = namespace[name].map(function (item) {
33507
+ const pattern = namespace[name].map(function (item) {
32144
33508
  return assign({}, item);
32145
33509
  });
32146
- var args = Array.isArray(options.args) ? options.args : [options.args || {}];
33510
+ const args = Array.isArray(options.args) ? options.args : [options.args || {}];
32147
33511
  defaults(args[0], omit(opt, 'args'));
32148
- for (var i = 0; i < args.length; i++) {
33512
+ for (let i = 0; i < args.length; i++) {
32149
33513
  if (pattern[i]) {
32150
33514
  assign(pattern[i], args[i]);
32151
33515
  }
@@ -32162,6 +33526,15 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32162
33526
  }
32163
33527
  });
32164
33528
 
33529
+ const paperLayers = {
33530
+ GRID: 'grid',
33531
+ BACK: 'back',
33532
+ /** @deprecated */
33533
+ CELLS: 'cells',
33534
+ FRONT: 'front',
33535
+ TOOLS: 'tools',
33536
+ LABELS: 'labels'
33537
+ };
32165
33538
  const sortingTypes = {
32166
33539
  NONE: 'sorting-none',
32167
33540
  APPROX: 'sorting-approximate',
@@ -32194,18 +33567,205 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32194
33567
  }
32195
33568
  }
32196
33569
  };
32197
- const defaultLayers = [{
32198
- name: LayersNames.GRID
32199
- }, {
32200
- name: LayersNames.BACK
33570
+ const gridPatterns = {
33571
+ dot: [{
33572
+ color: '#AAAAAA',
33573
+ thickness: 1,
33574
+ markup: 'rect',
33575
+ render: function (el, opt) {
33576
+ V(el).attr({
33577
+ width: opt.thickness,
33578
+ height: opt.thickness,
33579
+ fill: opt.color
33580
+ });
33581
+ }
33582
+ }],
33583
+ fixedDot: [{
33584
+ color: '#AAAAAA',
33585
+ thickness: 1,
33586
+ markup: 'rect',
33587
+ render: function (el, opt) {
33588
+ V(el).attr({
33589
+ fill: opt.color
33590
+ });
33591
+ },
33592
+ update: function (el, opt, paper) {
33593
+ const {
33594
+ sx,
33595
+ sy
33596
+ } = paper.scale();
33597
+ const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
33598
+ const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
33599
+ V(el).attr({
33600
+ width,
33601
+ height
33602
+ });
33603
+ }
33604
+ }],
33605
+ mesh: [{
33606
+ color: '#AAAAAA',
33607
+ thickness: 1,
33608
+ markup: 'path',
33609
+ render: function (el, opt) {
33610
+ var d;
33611
+ var width = opt.width;
33612
+ var height = opt.height;
33613
+ var thickness = opt.thickness;
33614
+ if (width - thickness >= 0 && height - thickness >= 0) {
33615
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33616
+ } else {
33617
+ d = 'M 0 0 0 0';
33618
+ }
33619
+ V(el).attr({
33620
+ 'd': d,
33621
+ stroke: opt.color,
33622
+ 'stroke-width': opt.thickness
33623
+ });
33624
+ }
33625
+ }],
33626
+ doubleMesh: [{
33627
+ color: '#AAAAAA',
33628
+ thickness: 1,
33629
+ markup: 'path',
33630
+ render: function (el, opt) {
33631
+ var d;
33632
+ var width = opt.width;
33633
+ var height = opt.height;
33634
+ var thickness = opt.thickness;
33635
+ if (width - thickness >= 0 && height - thickness >= 0) {
33636
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33637
+ } else {
33638
+ d = 'M 0 0 0 0';
33639
+ }
33640
+ V(el).attr({
33641
+ 'd': d,
33642
+ stroke: opt.color,
33643
+ 'stroke-width': opt.thickness
33644
+ });
33645
+ }
33646
+ }, {
33647
+ color: '#000000',
33648
+ thickness: 3,
33649
+ scaleFactor: 4,
33650
+ markup: 'path',
33651
+ render: function (el, opt) {
33652
+ var d;
33653
+ var width = opt.width;
33654
+ var height = opt.height;
33655
+ var thickness = opt.thickness;
33656
+ if (width - thickness >= 0 && height - thickness >= 0) {
33657
+ d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
33658
+ } else {
33659
+ d = 'M 0 0 0 0';
33660
+ }
33661
+ V(el).attr({
33662
+ 'd': d,
33663
+ stroke: opt.color,
33664
+ 'stroke-width': opt.thickness
33665
+ });
33666
+ }
33667
+ }]
33668
+ };
33669
+ const backgroundPatterns = {
33670
+ flipXy: function (img) {
33671
+ // d b
33672
+ // q p
33673
+
33674
+ var canvas = document.createElement('canvas');
33675
+ var imgWidth = img.width;
33676
+ var imgHeight = img.height;
33677
+ canvas.width = 2 * imgWidth;
33678
+ canvas.height = 2 * imgHeight;
33679
+ var ctx = canvas.getContext('2d');
33680
+ // top-left image
33681
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33682
+ // xy-flipped bottom-right image
33683
+ ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
33684
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33685
+ // x-flipped top-right image
33686
+ ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
33687
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33688
+ // y-flipped bottom-left image
33689
+ ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
33690
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33691
+ return canvas;
33692
+ },
33693
+ flipX: function (img) {
33694
+ // d b
33695
+ // d b
33696
+
33697
+ var canvas = document.createElement('canvas');
33698
+ var imgWidth = img.width;
33699
+ var imgHeight = img.height;
33700
+ canvas.width = imgWidth * 2;
33701
+ canvas.height = imgHeight;
33702
+ var ctx = canvas.getContext('2d');
33703
+ // left image
33704
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33705
+ // flipped right image
33706
+ ctx.translate(2 * imgWidth, 0);
33707
+ ctx.scale(-1, 1);
33708
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33709
+ return canvas;
33710
+ },
33711
+ flipY: function (img) {
33712
+ // d d
33713
+ // q q
33714
+
33715
+ var canvas = document.createElement('canvas');
33716
+ var imgWidth = img.width;
33717
+ var imgHeight = img.height;
33718
+ canvas.width = imgWidth;
33719
+ canvas.height = imgHeight * 2;
33720
+ var ctx = canvas.getContext('2d');
33721
+ // top image
33722
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33723
+ // flipped bottom image
33724
+ ctx.translate(0, 2 * imgHeight);
33725
+ ctx.scale(1, -1);
33726
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
33727
+ return canvas;
33728
+ },
33729
+ watermark: function (img, opt) {
33730
+ // d
33731
+ // d
33732
+
33733
+ opt = opt || {};
33734
+ var imgWidth = img.width;
33735
+ var imgHeight = img.height;
33736
+ var canvas = document.createElement('canvas');
33737
+ canvas.width = imgWidth * 3;
33738
+ canvas.height = imgHeight * 3;
33739
+ var ctx = canvas.getContext('2d');
33740
+ var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
33741
+ var radians = toRad(angle);
33742
+ var stepX = canvas.width / 4;
33743
+ var stepY = canvas.height / 4;
33744
+ for (var i = 0; i < 4; i++) {
33745
+ for (var j = 0; j < 4; j++) {
33746
+ if ((i + j) % 2 > 0) {
33747
+ // reset the current transformations
33748
+ ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
33749
+ ctx.rotate(radians);
33750
+ ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
33751
+ }
33752
+ }
33753
+ }
33754
+ return canvas;
33755
+ }
33756
+ };
33757
+ const implicitLayers = [{
33758
+ id: paperLayers.GRID,
33759
+ type: 'GridLayerView',
33760
+ patterns: gridPatterns
32201
33761
  }, {
32202
- name: LayersNames.CELLS
33762
+ id: paperLayers.BACK
32203
33763
  }, {
32204
- name: LayersNames.LABELS
33764
+ id: paperLayers.LABELS
32205
33765
  }, {
32206
- name: LayersNames.FRONT
33766
+ id: paperLayers.FRONT
32207
33767
  }, {
32208
- name: LayersNames.TOOLS
33768
+ id: paperLayers.TOOLS
32209
33769
  }];
32210
33770
  const CELL_VIEW_PLACEHOLDER_MARKER = Symbol('joint.cellViewPlaceholderMarker');
32211
33771
  const Paper = View.extend({
@@ -32275,7 +33835,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32275
33835
  // Do not create hard dependency on the joint.shapes.standard namespace (by importing the standard.Link model directly)
32276
33836
  const {
32277
33837
  cellNamespace
32278
- } = this.model.get('cells');
33838
+ } = this.model.layerCollection;
32279
33839
  const ctor = getByPath(cellNamespace, ['standard', 'Link']);
32280
33840
  if (!ctor) throw new Error('dia.Paper: no default link model found. Use `options.defaultLink` to specify a default link model.');
32281
33841
  return new ctor();
@@ -32361,12 +33921,24 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32361
33921
  viewManagement: false,
32362
33922
  // no docs yet
32363
33923
  onViewUpdate: function (view, flag, priority, opt, paper) {
32364
- // Do not update connected links when:
32365
- // 1. the view was just inserted (added to the graph and rendered)
32366
- // 2. the view was just mounted (added back to the paper by viewport function)
32367
- // 3. the change was marked as `isolate`.
32368
- // 4. the view model was just removed from the graph
32369
- if (flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE) || opt.mounting || opt.isolate) return;
33924
+ if (opt.mounting || opt.isolate) {
33925
+ // Do not update connected links when:
33926
+ // - the view was just mounted (added back to the paper by viewport function)
33927
+ // - the change was marked as `isolate`.
33928
+ return;
33929
+ }
33930
+ // Always update connected links when the view model was replaced with another model
33931
+ // with the same id.
33932
+ // Note: the removal is done in 2 steps: remove the old model, add the new model.
33933
+ // We update connected links on the add step.
33934
+ if (!(opt.replace && opt.add)) {
33935
+ if (flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE)) {
33936
+ // Do not update connected links when:
33937
+ // - the view was just inserted (added to the graph and rendered)
33938
+ // - the view model was just removed from the graph
33939
+ return;
33940
+ }
33941
+ }
32370
33942
  paper.requestConnectedLinksUpdate(view, priority, opt);
32371
33943
  },
32372
33944
  // no docs yet
@@ -32383,6 +33955,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32383
33955
  // Default namespaces
32384
33956
 
32385
33957
  cellViewNamespace: null,
33958
+ layerViewNamespace: null,
32386
33959
  routerNamespace: null,
32387
33960
  connectorNamespace: null,
32388
33961
  highlighterNamespace: highlighters,
@@ -32430,10 +34003,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32430
34003
  }
32431
34004
  `,
32432
34005
  svg: null,
32433
- viewport: null,
32434
34006
  defs: null,
32435
34007
  tools: null,
32436
34008
  layers: null,
34009
+ // deprecated, use layers element instead
34010
+ viewport: null,
32437
34011
  // For storing the current transformation matrix (CTM) of the paper's viewport.
32438
34012
  _viewportMatrix: null,
32439
34013
  // For verifying whether the CTM is up-to-date. The viewport transform attribute
@@ -32443,7 +34017,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32443
34017
  _updates: null,
32444
34018
  // Paper Layers
32445
34019
  _layers: null,
32446
- SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
32447
34020
  UPDATE_DELAYING_BATCHES: ['translate'],
32448
34021
  // If you interact with these elements,
32449
34022
  // the default interaction such as `element move` is prevented.
@@ -32463,12 +34036,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32463
34036
  // The find buffer is used to extend the area of the search
32464
34037
  // to mitigate the differences between the model and view geometry.
32465
34038
  DEFAULT_FIND_BUFFER: 200,
32466
- // Default layer to insert the cell views into.
32467
- DEFAULT_CELL_LAYER: LayersNames.CELLS,
32468
- // Update flags
32469
34039
  FLAG_INSERT: 1 << 30,
32470
34040
  FLAG_REMOVE: 1 << 29,
32471
34041
  FLAG_INIT: 1 << 28,
34042
+ // Layers that are always present on the paper (e.g. grid, back, front, tools)
34043
+ implicitLayers,
34044
+ // Reference layer for inserting new graph layers.
34045
+ graphLayerRefId: paperLayers.LABELS,
32472
34046
  init: function () {
32473
34047
  const {
32474
34048
  options
@@ -32478,6 +34052,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32478
34052
  options.cellViewNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null;
32479
34053
  /* eslint-enable no-undef */
32480
34054
  }
34055
+ const defaultLayerViewNamespace = {
34056
+ LayerView,
34057
+ GraphLayerView,
34058
+ GridLayerView
34059
+ };
34060
+ this.layerViewNamespace = defaultsDeep({}, options.layerViewNamespace || {}, defaultLayerViewNamespace);
32481
34061
  const model = this.model = options.model || new Graph();
32482
34062
 
32483
34063
  // This property tells us if we need to keep the compatibility
@@ -32487,18 +34067,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32487
34067
  // Layers (SVGGroups)
32488
34068
  this._layers = {
32489
34069
  viewsMap: {},
32490
- namesMap: {},
32491
34070
  order: []
32492
34071
  };
32493
- this.cloneOptions();
32494
- this.render();
32495
- this._setDimensions();
32496
- this.startListening();
32497
34072
 
32498
34073
  // Hash of all cell views.
32499
34074
  this._views = {};
32500
34075
  this._viewPlaceholders = {};
32501
34076
  this._idToCid = {};
34077
+ this.cloneOptions();
34078
+ this.render();
34079
+ this._setDimensions();
34080
+ this.startListening();
32502
34081
 
32503
34082
  // Mouse wheel events buffer
32504
34083
  this._mw_evt_buffer = {
@@ -32507,7 +34086,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32507
34086
  };
32508
34087
 
32509
34088
  // Render existing cells in the graph
32510
- this.resetViews(model.attributes.cells.models);
34089
+ this.resetViews(model.getCells());
32511
34090
  },
32512
34091
  _resetUpdates: function () {
32513
34092
  if (this._updates && this._updates.id) cancelFrame(this._updates.id);
@@ -32527,7 +34106,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32527
34106
  },
32528
34107
  startListening: function () {
32529
34108
  var model = this.model;
32530
- this.listenTo(model, 'add', this.onCellAdded).listenTo(model, 'remove', this.onCellRemoved).listenTo(model, 'change', this.onCellChange).listenTo(model, 'reset', this.onGraphReset).listenTo(model, 'sort', this.onGraphSort).listenTo(model, 'batch:stop', this.onGraphBatchStop);
34109
+ this.listenTo(model, 'add', this.onCellAdded).listenTo(model, 'remove', this.onCellRemoved).listenTo(model, 'reset', this.onGraphReset).listenTo(model, 'batch:stop', this.onGraphBatchStop);
34110
+ this.listenTo(model, 'layer:add', this.onGraphLayerAdd).listenTo(model, 'layer:remove', this.onGraphLayerRemove).listenTo(model, 'layers:sort', this.onGraphLayerCollectionSort);
32531
34111
  this.on('cell:highlight', this.onCellHighlight).on('cell:unhighlight', this.onCellUnhighlight).on('transform', this.update);
32532
34112
  },
32533
34113
  onCellAdded: function (cell, _, opt) {
@@ -32553,22 +34133,15 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32553
34133
  this.requestViewUpdate(viewLike, this.FLAG_REMOVE, viewLike.UPDATE_PRIORITY, opt);
32554
34134
  }
32555
34135
  },
32556
- onCellChange: function (cell, opt) {
32557
- if (cell === this.model.attributes.cells) return;
32558
- if (cell.hasChanged('layer') || cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX) {
32559
- const viewLike = this._getCellViewLike(cell);
32560
- if (viewLike) {
32561
- this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
32562
- }
32563
- }
32564
- },
32565
- onGraphReset: function (collection, opt) {
32566
- this.resetLayers();
32567
- this.resetViews(collection.models, opt);
32568
- },
32569
- onGraphSort: function () {
32570
- if (this.model.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
32571
- this.sortViews();
34136
+ onGraphReset: function (_collection, opt) {
34137
+ // Re-render all graph layer views
34138
+ // but keep the implicit layer views.
34139
+ this.renderGraphLayerViews();
34140
+ this.resetLayerViews();
34141
+ // Backward compatibility: reassign the `cells` property
34142
+ // with the default layer view.
34143
+ this.assertLayerViews();
34144
+ this.resetViews(this.model.getCells(), opt);
32572
34145
  },
32573
34146
  onGraphBatchStop: function (data) {
32574
34147
  if (this.isFrozen() || this.isIdle()) return;
@@ -32580,10 +34153,92 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32580
34153
  this.updateViews(data);
32581
34154
  }
32582
34155
  }
32583
- var sortDelayingBatches = this.SORT_DELAYING_BATCHES;
32584
- if (sortDelayingBatches.includes(name) && !graph.hasActiveBatch(sortDelayingBatches)) {
32585
- this.sortViews();
34156
+ },
34157
+ /**
34158
+ * @protected
34159
+ * @description When a new layer is added to the graph, we create a new layer view
34160
+ **/
34161
+ onGraphLayerAdd: function (layer, _, opt) {
34162
+ if (this.hasLayerView(layer.id)) return;
34163
+ const layerView = this.createLayerView({
34164
+ id: layer.id,
34165
+ model: layer
34166
+ });
34167
+ const layers = this.model.getLayers();
34168
+ let before;
34169
+ // Note: There is always at least one graph layer.
34170
+ if (layers[layers.length - 1] === layer) {
34171
+ // This is the last layer, so insert before the labels layer
34172
+ before = paperLayers.LABELS;
34173
+ } else {
34174
+ // There is a layer after the current one, so insert before that one
34175
+ const index = layers.indexOf(layer);
34176
+ before = layers[index + 1].id;
32586
34177
  }
34178
+ this.addLayerView(layerView, {
34179
+ before
34180
+ });
34181
+ },
34182
+ /**
34183
+ * @protected
34184
+ * @description When a layer is removed from the graph, we remove the corresponding layer view
34185
+ **/
34186
+ onGraphLayerRemove: function (layer, _, opt) {
34187
+ if (!this.hasLayerView(layer)) return;
34188
+
34189
+ // Request layer removal. Since the UPDATE_PRIORITY is lower
34190
+ // than cells update priority, the cell views will be removed first.
34191
+ this.requestLayerViewRemoval(layer);
34192
+ },
34193
+ /**
34194
+ * @protected
34195
+ * @description When the graph layer collection is sorted,
34196
+ * we reorder all graph layer views.
34197
+ **/
34198
+ onGraphLayerCollectionSort: function (layerCollection) {
34199
+ layerCollection.each(layer => {
34200
+ if (!this.hasLayerView(layer)) return;
34201
+ this.moveLayerView(layer, {
34202
+ before: this.graphLayerRefId
34203
+ });
34204
+ });
34205
+ },
34206
+ /**
34207
+ * @protected
34208
+ * @description Resets all graph layer views.
34209
+ */
34210
+ renderGraphLayerViews: function () {
34211
+ // Remove all existing graph layer views
34212
+ // Note: we don't use `getGraphLayerViews()` here because
34213
+ // rendered graph layer views could be different from the ones
34214
+ // in the graph layer collection (`onResetGraphLayerCollectionReset`).
34215
+ this.getLayerViews().forEach(layerView => {
34216
+ if (!layerView[GRAPH_LAYER_VIEW_MARKER]) return;
34217
+ this._removeLayerView(layerView);
34218
+ });
34219
+ // Create and insert new graph layer views
34220
+ this.model.getLayers().forEach(layer => {
34221
+ const layerView = this.createLayerView({
34222
+ id: layer.id,
34223
+ model: layer
34224
+ });
34225
+ // Insert the layer view into the paper layers, just before the labels layer.
34226
+ // All cell layers are positioned between the "back" and "labels" layers,
34227
+ // with the default "cells" layer originally occupying this position.
34228
+ this.addLayerView(layerView, {
34229
+ before: this.graphLayerRefId
34230
+ });
34231
+ });
34232
+ },
34233
+ /**
34234
+ * @protected
34235
+ * @description Renders all implicit layer views.
34236
+ */
34237
+ renderImplicitLayerViews: function () {
34238
+ this.implicitLayers.forEach(layerInit => {
34239
+ const layerView = this.createLayerView(layerInit);
34240
+ this.addLayerView(layerView);
34241
+ });
32587
34242
  },
32588
34243
  cloneOptions: function () {
32589
34244
  const {
@@ -32678,136 +34333,295 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32678
34333
  }]
32679
34334
  }];
32680
34335
  },
32681
- hasLayerView(layerName) {
32682
- return layerName in this._layers.viewsMap;
34336
+ /**
34337
+ * @public
34338
+ * @description Checks whether the layer view exists by the given layer id or layer model.
34339
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34340
+ * @return {boolean} True if the layer view exists, false otherwise.
34341
+ */
34342
+ hasLayerView(layerRef) {
34343
+ let layerId;
34344
+ if (isString(layerRef)) {
34345
+ layerId = layerRef;
34346
+ } else if (layerRef) {
34347
+ layerId = layerRef.id;
34348
+ } else {
34349
+ return false;
34350
+ }
34351
+ return layerId in this._layers.viewsMap;
32683
34352
  },
32684
- getLayerView(layerName) {
32685
- const {
32686
- _layers: {
32687
- viewsMap
32688
- }
32689
- } = this;
32690
- if (layerName in viewsMap) return viewsMap[layerName];
32691
- throw new Error(`dia.Paper: Unknown layer "${layerName}".`);
34353
+ /**
34354
+ * @public
34355
+ * @description Returns the layer view by the given layer id or layer model.
34356
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34357
+ * @return {dia.LayerView} The layer view.
34358
+ * @throws {Error} if the layer view is not found
34359
+ */
34360
+ getLayerView(layerRef) {
34361
+ let layerId;
34362
+ if (isString(layerRef)) {
34363
+ layerId = layerRef;
34364
+ } else if (layerRef) {
34365
+ layerId = layerRef.id;
34366
+ } else {
34367
+ throw new Error('dia.Paper: No layer provided.');
34368
+ }
34369
+ const layerView = this._layers.viewsMap[layerId];
34370
+ if (!layerView) {
34371
+ throw new Error(`dia.Paper: Unknown layer view "${layerId}".`);
34372
+ }
34373
+ return layerView;
32692
34374
  },
32693
- getLayerNode(layerName) {
32694
- return this.getLayerView(layerName).el;
34375
+ /**
34376
+ * @deprecated use `getLayerView(layerId).el` instead
34377
+ */
34378
+ getLayerNode(layerId) {
34379
+ return this.getLayerView(layerId).el;
32695
34380
  },
32696
- _removeLayer(layerView) {
32697
- this._unregisterLayer(layerView);
34381
+ /**
34382
+ * @protected
34383
+ * @description Removes the given layer view from the paper.
34384
+ * It does not check whether the layer view is empty.
34385
+ * @param {dia.LayerView} layerView - The layer view to remove.
34386
+ */
34387
+ _removeLayerView(layerView) {
34388
+ this._unregisterLayerView(layerView);
32698
34389
  layerView.remove();
32699
34390
  },
32700
- _unregisterLayer(layerView) {
32701
- const {
32702
- _layers: {
32703
- viewsMap,
32704
- namesMap,
32705
- order
32706
- }
32707
- } = this;
32708
- const layerName = this._getLayerName(layerView);
32709
- order.splice(order.indexOf(layerName), 1);
32710
- delete namesMap[layerView.cid];
32711
- delete viewsMap[layerName];
34391
+ /**
34392
+ * @protected
34393
+ * @description Removes all layer views from the paper.
34394
+ * It does not check whether the layer views are empty.
34395
+ */
34396
+ _removeLayerViews: function () {
34397
+ Object.values(this._layers.viewsMap).forEach(layerView => {
34398
+ this._removeLayerView(layerView);
34399
+ });
32712
34400
  },
32713
- _registerLayer(layerName, layerView, beforeLayerView) {
34401
+ /**
34402
+ * @protected
34403
+ * @description Unregisters the given layer view from the paper.
34404
+ * @param {dia.LayerView} layerView - The layer view to unregister.
34405
+ */
34406
+ _unregisterLayerView(layerView) {
32714
34407
  const {
32715
34408
  _layers: {
32716
34409
  viewsMap,
32717
- namesMap,
32718
34410
  order
32719
34411
  }
32720
34412
  } = this;
32721
- if (beforeLayerView) {
32722
- const beforeLayerName = this._getLayerName(beforeLayerView);
32723
- order.splice(order.indexOf(beforeLayerName), 0, layerName);
32724
- } else {
32725
- order.push(layerName);
34413
+ const layerId = layerView.id;
34414
+ // Remove the layer id from the order list.
34415
+ const layerIndex = order.indexOf(layerId);
34416
+ if (layerIndex !== -1) {
34417
+ order.splice(layerIndex, 1);
32726
34418
  }
32727
- viewsMap[layerName] = layerView;
32728
- namesMap[layerView.cid] = layerName;
34419
+ // Unlink the layer view from the paper.
34420
+ layerView.unsetPaperReference();
34421
+ // Remove the layer view from the paper's registry.
34422
+ delete viewsMap[layerId];
32729
34423
  },
32730
- _getLayerView(layer) {
32731
- const {
32732
- _layers: {
32733
- namesMap,
32734
- viewsMap
32735
- }
32736
- } = this;
32737
- if (layer instanceof PaperLayer) {
32738
- if (layer.cid in namesMap) return layer;
32739
- return null;
34424
+ /**
34425
+ * @protected
34426
+ * @description Registers the given layer view in the paper.
34427
+ * @param {dia.LayerView} layerView - The layer view to register.
34428
+ * @throws {Error} if the layer view is not an instance of dia.LayerView
34429
+ * @throws {Error} if the layer view already exists in the paper
34430
+ */
34431
+ _registerLayerView(layerView) {
34432
+ if (!layerView || !layerView[LAYER_VIEW_MARKER]) {
34433
+ throw new Error('dia.Paper: The layer view must be an instance of dia.LayerView.');
32740
34434
  }
32741
- if (layer in viewsMap) return viewsMap[layer];
32742
- return null;
34435
+ if (this.hasLayerView(layerView.id)) {
34436
+ throw new Error(`dia.Paper: The layer view "${layerView.id}" already exists.`);
34437
+ }
34438
+ // Link the layer view back to the paper.
34439
+ layerView.setPaperReference(this);
34440
+ // Store the layer view in the paper's registry.
34441
+ this._layers.viewsMap[layerView.id] = layerView;
32743
34442
  },
32744
- _getLayerName(layerView) {
34443
+ /**
34444
+ * @public
34445
+ * @description Removes the layer view by the given layer id or layer model.
34446
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34447
+ * @throws {Error} if the layer view is not empty
34448
+ */
34449
+ removeLayerView(layerRef) {
34450
+ const layerView = this.getLayerView(layerRef);
34451
+ if (!layerView.isEmpty()) {
34452
+ throw new Error('dia.Paper: The layer view is not empty.');
34453
+ }
34454
+ this._removeLayerView(layerView);
34455
+ },
34456
+ /**
34457
+ * @protected
34458
+ * @description Schedules the layer view removal by the given layer id or layer model.
34459
+ * The actual removal will be performed during the paper update cycle.
34460
+ * @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
34461
+ * @param {Object} [opt] - Update options.
34462
+ */
34463
+ requestLayerViewRemoval(layerRef, opt) {
34464
+ const layerView = this.getLayerView(layerRef);
32745
34465
  const {
32746
- _layers: {
32747
- namesMap
32748
- }
34466
+ FLAG_REMOVE
32749
34467
  } = this;
32750
- return namesMap[layerView.cid];
34468
+ const {
34469
+ UPDATE_PRIORITY
34470
+ } = layerView;
34471
+ this.requestViewUpdate(layerView, FLAG_REMOVE, UPDATE_PRIORITY, opt);
32751
34472
  },
32752
- _requireLayerView(layer) {
32753
- const layerView = this._getLayerView(layer);
32754
- if (!layerView) {
32755
- if (layer instanceof PaperLayer) {
32756
- throw new Error('dia.Paper: The layer is not registered.');
34473
+ /**
34474
+ * @public
34475
+ * @internal not documented
34476
+ * @description Schedules the cell view insertion into the appropriate layer view.
34477
+ * The actual insertion will be performed during the paper update cycle.
34478
+ * @param {dia.Cell} cell - The cell model whose view should be inserted.
34479
+ * @param {Object} [opt] - Update options.
34480
+ */
34481
+ requestCellViewInsertion(cell, opt) {
34482
+ const viewLike = this._getCellViewLike(cell);
34483
+ if (!viewLike) return;
34484
+ this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
34485
+ },
34486
+ /**
34487
+ * @private
34488
+ * Helper method for addLayerView and moveLayerView methods
34489
+ */
34490
+ _getBeforeLayerViewFromOptions(layerView, options) {
34491
+ let {
34492
+ before = null,
34493
+ index
34494
+ } = options;
34495
+ if (before && index !== undefined) {
34496
+ throw new Error('dia.Paper: Options "before" and "index" are mutually exclusive.');
34497
+ }
34498
+ let computedBefore;
34499
+ if (index !== undefined) {
34500
+ const {
34501
+ _layers: {
34502
+ order
34503
+ }
34504
+ } = this;
34505
+ if (index >= order.length) {
34506
+ // If index is greater than the number of layers,
34507
+ // return before as null (move to the end).
34508
+ computedBefore = null;
34509
+ } else if (index < 0) {
34510
+ // If index is negative, move to the beginning.
34511
+ computedBefore = order[0];
32757
34512
  } else {
32758
- throw new Error(`dia.Paper: Unknown layer "${layer}".`);
34513
+ const originalIndex = order.indexOf(layerView.id);
34514
+ if (originalIndex !== -1 && index > originalIndex) {
34515
+ // If moving a layer upwards in the stack, we need to adjust the index
34516
+ // to account for the layer being removed from its original position.
34517
+ index += 1;
34518
+ }
34519
+ // Otherwise, get the layer ID at the specified index.
34520
+ computedBefore = order[index] || null;
32759
34521
  }
34522
+ } else {
34523
+ computedBefore = before;
32760
34524
  }
32761
- return layerView;
34525
+ return computedBefore ? this.getLayerView(computedBefore) : null;
32762
34526
  },
32763
- hasLayer(layer) {
32764
- return this._getLayerView(layer) !== null;
34527
+ /**
34528
+ * @public
34529
+ * @description Adds the layer view to the paper.
34530
+ * @param {dia.LayerView} layerView - The layer view to add.
34531
+ * @param {Object} [options] - Adding options.
34532
+ * @param {string|dia.GraphLayer} [options.before] - Layer id or layer model before
34533
+ */
34534
+ addLayerView(layerView, options = {}) {
34535
+ this._registerLayerView(layerView);
34536
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
34537
+ this.insertLayerView(layerView, beforeLayerView);
32765
34538
  },
32766
- removeLayer(layer) {
32767
- const layerView = this._requireLayerView(layer);
32768
- if (!layerView.isEmpty()) {
32769
- throw new Error('dia.Paper: The layer is not empty.');
32770
- }
32771
- this._removeLayer(layerView);
34539
+ /**
34540
+ * @public
34541
+ * @description Moves the layer view.
34542
+ * @param {Paper.LayerRef} layerRef - The layer view reference to move.
34543
+ * @param {Object} [options] - Moving options.
34544
+ * @param {Paper.LayerRef} [options.before] - Layer id or layer model before
34545
+ * @param {number} [options.index] - Zero-based index to which to move the layer view.
34546
+ */
34547
+ moveLayerView(layerRef, options = {}) {
34548
+ const layerView = this.getLayerView(layerRef);
34549
+ const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
34550
+ this.insertLayerView(layerView, beforeLayerView);
32772
34551
  },
32773
- addLayer(layerName, layerView, options = {}) {
32774
- if (!layerName || typeof layerName !== 'string') {
32775
- throw new Error('dia.Paper: The layer name must be provided.');
32776
- }
32777
- if (this._getLayerView(layerName)) {
32778
- throw new Error(`dia.Paper: The layer "${layerName}" already exists.`);
32779
- }
32780
- if (!(layerView instanceof PaperLayer)) {
32781
- throw new Error('dia.Paper: The layer view is not an instance of dia.PaperLayer.');
32782
- }
34552
+ /**
34553
+ * @protected
34554
+ * @description Inserts the layer view into the paper.
34555
+ * If the layer view already exists in the paper, it is moved to the new position.
34556
+ * @param {dia.LayerView} layerView - The layer view to insert.
34557
+ * @param {dia.LayerView} [before] - Layer view before
34558
+ * which the layer view should be inserted.
34559
+ */
34560
+ insertLayerView(layerView, beforeLayerView) {
34561
+ const layerId = layerView.id;
32783
34562
  const {
32784
- insertBefore
32785
- } = options;
32786
- if (!insertBefore) {
32787
- this._registerLayer(layerName, layerView, null);
32788
- this.layers.appendChild(layerView.el);
32789
- } else {
32790
- const beforeLayerView = this._requireLayerView(insertBefore);
32791
- this._registerLayer(layerName, layerView, beforeLayerView);
34563
+ _layers: {
34564
+ order
34565
+ }
34566
+ } = this;
34567
+ const currentLayerIndex = order.indexOf(layerId);
34568
+
34569
+ // Should the layer view be inserted before another layer view?
34570
+ if (beforeLayerView) {
34571
+ const beforeLayerViewId = beforeLayerView.id;
34572
+ if (layerId === beforeLayerViewId) {
34573
+ // The layer view is already in the right place.
34574
+ return;
34575
+ }
34576
+ let beforeLayerPosition = order.indexOf(beforeLayerViewId);
34577
+ // Remove from the `order` list if the layer view is already in the order.
34578
+ if (currentLayerIndex !== -1) {
34579
+ if (currentLayerIndex < beforeLayerPosition) {
34580
+ beforeLayerPosition -= 1;
34581
+ }
34582
+ order.splice(currentLayerIndex, 1);
34583
+ }
34584
+ order.splice(beforeLayerPosition, 0, layerId);
32792
34585
  this.layers.insertBefore(layerView.el, beforeLayerView.el);
34586
+ return;
32793
34587
  }
34588
+
34589
+ // Remove from the `order` list if the layer view is already in the order.
34590
+ // This is needed for the case when the layer view is inserted in the new position.
34591
+ if (currentLayerIndex !== -1) {
34592
+ order.splice(currentLayerIndex, 1);
34593
+ }
34594
+ order.push(layerId);
34595
+ this.layers.appendChild(layerView.el);
32794
34596
  },
32795
- moveLayer(layer, insertBefore) {
32796
- const layerView = this._requireLayerView(layer);
32797
- if (layerView === this._getLayerView(insertBefore)) return;
32798
- const layerName = this._getLayerName(layerView);
32799
- this._unregisterLayer(layerView);
32800
- this.addLayer(layerName, layerView, {
32801
- insertBefore
32802
- });
32803
- },
32804
- getLayerNames() {
32805
- // Returns a sorted array of layer names.
34597
+ /**
34598
+ * @protected
34599
+ * @description Returns an array of layer view ids in the order they are rendered.
34600
+ * @returns {string[]} An array of layer view ids.
34601
+ */
34602
+ getLayerViewOrder() {
32806
34603
  return this._layers.order.slice();
32807
34604
  },
32808
- getLayers() {
32809
- // Returns a sorted array of layer views.
32810
- return this.getLayerNames().map(name => this.getLayerView(name));
34605
+ /**
34606
+ * @public
34607
+ * @description Returns an array of layer views in the order they are rendered.
34608
+ * @returns {dia.LayerView[]} An array of layer views.
34609
+ */
34610
+ getLayerViews() {
34611
+ return this.getLayerViewOrder().map(id => this.getLayerView(id));
34612
+ },
34613
+ /**
34614
+ * @public
34615
+ * @description Returns an array of graph layer views in the order they are rendered.
34616
+ * @returns {dia.GraphLayerView[]} An array of graph layer views.
34617
+ */
34618
+ getGraphLayerViews() {
34619
+ const {
34620
+ _layers: {
34621
+ viewsMap
34622
+ }
34623
+ } = this;
34624
+ return this.model.getLayers().map(layer => viewsMap[layer.id]);
32811
34625
  },
32812
34626
  render: function () {
32813
34627
  this.renderChildren();
@@ -32827,7 +34641,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32827
34641
  this.svg = svg;
32828
34642
  this.defs = defs;
32829
34643
  this.layers = layers;
32830
- this.renderLayers();
34644
+ this.renderLayerViews();
32831
34645
  V.ensureId(svg);
32832
34646
  this.addStylesheet(stylesheet);
32833
34647
  if (options.background) {
@@ -32842,60 +34656,79 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
32842
34656
  if (!css) return;
32843
34657
  V(this.svg).prepend(V.createSVGStyle(css));
32844
34658
  },
32845
- createLayer(name) {
32846
- switch (name) {
32847
- case LayersNames.GRID:
32848
- return new GridLayer({
32849
- name,
32850
- paper: this,
32851
- patterns: this.constructor.gridPatterns
32852
- });
32853
- default:
32854
- return new PaperLayer({
32855
- name
32856
- });
34659
+ /**
34660
+ * @protected
34661
+ * @description Creates a layer view instance based on the provided options.
34662
+ * It finds the appropriate layer view constructor from the paper's
34663
+ * `layerViewNamespace` and instantiates it.
34664
+ * @param {*} options See `dia.LayerView` options.
34665
+ * @returns {dia.LayerView}
34666
+ */
34667
+ createLayerView(options) {
34668
+ if (options == null) {
34669
+ throw new Error('dia.Paper: Layer view options are required.');
34670
+ }
34671
+ if (options.id == null) {
34672
+ throw new Error('dia.Paper: Layer view id is required.');
34673
+ }
34674
+ const viewOptions = clone$1(options);
34675
+ let viewConstructor;
34676
+ if (viewOptions.model) {
34677
+ const modelType = viewOptions.model.get('type') || viewOptions.model.constructor.name;
34678
+ const type = modelType + 'View';
34679
+
34680
+ // For backward compatibility we use the LegacyGraphLayerView for the default `cells` layer.
34681
+ if (this.model.layersController.legacyMode) {
34682
+ viewConstructor = LegacyGraphLayerView;
34683
+ } else {
34684
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
34685
+ }
34686
+ } else {
34687
+ // Paper layers
34688
+ const type = viewOptions.type;
34689
+ viewConstructor = this.layerViewNamespace[type] || LayerView;
32857
34690
  }
34691
+ return new viewConstructor(viewOptions);
32858
34692
  },
32859
- renderLayer: function (name) {
32860
- const layerView = this.createLayer(name);
32861
- this.addLayer(name, layerView);
32862
- return layerView;
34693
+ /**
34694
+ * @protected
34695
+ * @description Renders all paper layer views and graph layer views.
34696
+ */
34697
+ renderLayerViews: function () {
34698
+ this._removeLayerViews();
34699
+ // Render the paper layers.
34700
+ this.renderImplicitLayerViews();
34701
+ // Render the layers.
34702
+ this.renderGraphLayerViews();
34703
+ // Ensure that essential layer views are present.
34704
+ this.assertLayerViews();
32863
34705
  },
32864
- renderLayers: function (layers = defaultLayers) {
32865
- this.removeLayers();
32866
- layers.forEach(({
32867
- name
32868
- }) => this.renderLayer(name));
32869
- // Throws an exception if doesn't exist
32870
- const cellsLayerView = this.getLayerView(LayersNames.CELLS);
32871
- const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
32872
- const labelsLayerView = this.getLayerView(LayersNames.LABELS);
34706
+ /**
34707
+ * @protected
34708
+ * @description Ensures that essential layer views are present on the paper.
34709
+ * @throws {Error} if any of the essential layer views is missing
34710
+ */
34711
+ assertLayerViews: function () {
34712
+ // Throws an exception if essential layer views are missing.
34713
+ const cellsLayerView = this.getLayerView(this.model.getDefaultLayer().id);
34714
+ const toolsLayerView = this.getLayerView(paperLayers.TOOLS);
34715
+ const labelsLayerView = this.getLayerView(paperLayers.LABELS);
34716
+
32873
34717
  // backwards compatibility
32874
34718
  this.tools = toolsLayerView.el;
32875
34719
  this.cells = this.viewport = cellsLayerView.el;
32876
- // user-select: none;
32877
- cellsLayerView.vel.addClass(addClassNamePrefix('viewport'));
34720
+ // Backwards compatibility: same as `LegacyGraphLayerView` we keep
34721
+ // the `viewport` class on the labels layer.
32878
34722
  labelsLayerView.vel.addClass(addClassNamePrefix('viewport'));
32879
- cellsLayerView.el.style.webkitUserSelect = 'none';
32880
- cellsLayerView.el.style.userSelect = 'none';
32881
34723
  labelsLayerView.el.style.webkitUserSelect = 'none';
32882
34724
  labelsLayerView.el.style.userSelect = 'none';
32883
34725
  },
32884
- removeLayers: function () {
32885
- const {
32886
- _layers: {
32887
- viewsMap
32888
- }
32889
- } = this;
32890
- Object.values(viewsMap).forEach(layerView => this._removeLayer(layerView));
32891
- },
32892
- resetLayers: function () {
32893
- const {
32894
- _layers: {
32895
- viewsMap
32896
- }
32897
- } = this;
32898
- Object.values(viewsMap).forEach(layerView => layerView.removePivots());
34726
+ /**
34727
+ * @protected
34728
+ * @description Resets all layer views.
34729
+ */
34730
+ resetLayerViews: function () {
34731
+ this.getLayerViews().forEach(layerView => layerView.reset());
32899
34732
  },
32900
34733
  update: function () {
32901
34734
  if (this._background) {
@@ -33011,26 +34844,25 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
33011
34844
  return this;
33012
34845
  },
33013
34846
  clientMatrix: function () {
33014
- return V.createSVGMatrix(this.cells.getScreenCTM());
34847
+ return V.createSVGMatrix(this.layers.getScreenCTM());
33015
34848
  },
33016
34849
  requestConnectedLinksUpdate: function (view, priority, opt) {
33017
- if (view instanceof CellView) {
33018
- var model = view.model;
33019
- var links = this.model.getConnectedLinks(model);
33020
- for (var j = 0, n = links.length; j < n; j++) {
33021
- var link = links[j];
33022
- var linkView = this._getCellViewLike(link);
33023
- if (!linkView) continue;
33024
- // We do not have to update placeholder views.
33025
- // They will be updated on initial render.
33026
- if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
33027
- var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
33028
- this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
33029
- }
34850
+ if (!view || !view[CELL_VIEW_MARKER]) return;
34851
+ var model = view.model;
34852
+ var links = this.model.getConnectedLinks(model);
34853
+ for (var j = 0, n = links.length; j < n; j++) {
34854
+ var link = links[j];
34855
+ var linkView = this._getCellViewLike(link);
34856
+ if (!linkView) continue;
34857
+ // We do not have to update placeholder views.
34858
+ // They will be updated on initial render.
34859
+ if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
34860
+ var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
34861
+ this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
33030
34862
  }
33031
34863
  },
33032
34864
  forcePostponedViewUpdate: function (view, flag) {
33033
- if (!view || !(view instanceof CellView)) return false;
34865
+ if (!view || !view[CELL_VIEW_MARKER]) return false;
33034
34866
  const model = view.model;
33035
34867
  if (model.isElement()) return false;
33036
34868
  const dumpOptions = {
@@ -33145,6 +34977,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
33145
34977
  const {
33146
34978
  model
33147
34979
  } = view;
34980
+ if (view[GRAPH_LAYER_VIEW_MARKER]) {
34981
+ if (flag & FLAG_REMOVE) {
34982
+ this.removeLayerView(view);
34983
+ return 0;
34984
+ }
34985
+ }
33148
34986
  if (view[CELL_VIEW_MARKER]) {
33149
34987
  if (flag & FLAG_REMOVE) {
33150
34988
  this.removeView(model);
@@ -33744,7 +35582,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
33744
35582
  }
33745
35583
  this.options.frozen = updates.keyFrozen = false;
33746
35584
  if (updates.sort) {
33747
- this.sortViews();
35585
+ this.sortLayerViews();
33748
35586
  updates.sort = false;
33749
35587
  }
33750
35588
  },
@@ -33772,8 +35610,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
33772
35610
  this.freeze();
33773
35611
  this._updates.disabled = true;
33774
35612
  //clean up all DOM elements/views to prevent memory leaks
33775
- this.removeLayers();
33776
35613
  this.removeViews();
35614
+ this._removeLayerViews();
33777
35615
  },
33778
35616
  getComputedSize: function () {
33779
35617
  var options = this.options;
@@ -34011,7 +35849,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34011
35849
  if (opt && opt.useModelGeometry) {
34012
35850
  return this.model.getBBox() || new Rect();
34013
35851
  }
34014
- return V(this.cells).getBBox();
35852
+ const graphLayerViews = this.getGraphLayerViews();
35853
+ // Return an empty rectangle if there are no layers
35854
+ // should not happen in practice
35855
+ if (graphLayerViews.length === 0) {
35856
+ return new Rect();
35857
+ }
35858
+
35859
+ // Combine content area rectangles from all layers,
35860
+ // considering only graph layer views to exclude non-cell elements (e.g., grid, tools)
35861
+ const bbox = Rect.fromRectUnion(...graphLayerViews.map(view => view.vel.getBBox()));
35862
+ return bbox;
34015
35863
  },
34016
35864
  // Return the dimensions of the content bbox in the paper units (as it appears on screen).
34017
35865
  getContentBBox: function (opt) {
@@ -34087,7 +35935,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34087
35935
  cid,
34088
35936
  model: cell,
34089
35937
  interactive,
34090
- labelsLayer: labelsLayer === true ? LayersNames.LABELS : labelsLayer
35938
+ labelsLayer: labelsLayer === true ? paperLayers.LABELS : labelsLayer
34091
35939
  });
34092
35940
  },
34093
35941
  _resolveCellViewClass: function (cell) {
@@ -34248,7 +36096,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34248
36096
  this.unfreeze({
34249
36097
  key
34250
36098
  });
34251
- this.sortViews();
36099
+ this.sortLayerViews();
34252
36100
  },
34253
36101
  removeViews: function () {
34254
36102
  // Remove all views and their references from the paper.
@@ -34262,7 +36110,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34262
36110
  this._viewPlaceholders = {};
34263
36111
  this._idToCid = {};
34264
36112
  },
34265
- sortViews: function () {
36113
+ sortLayerViews: function () {
34266
36114
  if (!this.isExactSorting()) {
34267
36115
  // noop
34268
36116
  return;
@@ -34272,38 +36120,16 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34272
36120
  this._updates.sort = true;
34273
36121
  return;
34274
36122
  }
34275
- this.sortViewsExact();
36123
+ this.sortLayerViewsExact();
34276
36124
  },
34277
- sortViewsExact: function () {
34278
- // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
34279
- // associated model `z` attribute.
34280
-
34281
- var cellNodes = Array.from(this.cells.childNodes).filter(node => node.getAttribute('model-id'));
34282
- var cells = this.model.get('cells');
34283
- sortElements(cellNodes, function (a, b) {
34284
- var cellA = cells.get(a.getAttribute('model-id'));
34285
- var cellB = cells.get(b.getAttribute('model-id'));
34286
- var zA = cellA.attributes.z || 0;
34287
- var zB = cellB.attributes.z || 0;
34288
- return zA === zB ? 0 : zA < zB ? -1 : 1;
34289
- });
36125
+ sortLayerViewsExact: function () {
36126
+ this.getGraphLayerViews().forEach(view => view.sortExact());
34290
36127
  },
34291
36128
  insertView: function (view, isInitialInsert) {
34292
- const {
34293
- el,
34294
- model
34295
- } = view;
34296
- const layerName = model.get('layer') || this.DEFAULT_CELL_LAYER;
34297
- const layerView = this.getLayerView(layerName);
34298
- switch (this.options.sorting) {
34299
- case sortingTypes.APPROX:
34300
- layerView.insertSortedNode(el, model.get('z'));
34301
- break;
34302
- case sortingTypes.EXACT:
34303
- default:
34304
- layerView.insertNode(el);
34305
- break;
34306
- }
36129
+ // layer can be null if it is added to the graph with 'dry' option
36130
+ const layerId = this.model.getCellLayerId(view.model);
36131
+ const layerView = this.getLayerView(layerId);
36132
+ layerView.insertCellView(view);
34307
36133
  view.onMount(isInitialInsert);
34308
36134
  },
34309
36135
  _hideView: function (viewLike) {
@@ -34358,7 +36184,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34358
36184
  // Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
34359
36185
  // be a selector or a jQuery object.
34360
36186
  findView: function ($el) {
34361
- var el = isString($el) ? this.cells.querySelector($el) : $el instanceof $ ? $el[0] : $el;
36187
+ var el = isString($el) ? this.layers.querySelector($el) : $el instanceof $ ? $el[0] : $el;
34362
36188
  var id = this.findAttribute('model-id', el);
34363
36189
  if (id) return this._views[id];
34364
36190
  return undefined;
@@ -34396,7 +36222,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34396
36222
  var views = this.model.getElements().map(this.findViewByModel, this);
34397
36223
  return views.filter(function (view) {
34398
36224
  return view && view.vel.getBBox({
34399
- target: this.cells
36225
+ target: this.layers
34400
36226
  }).containsPoint(p);
34401
36227
  }, this);
34402
36228
  },
@@ -34410,7 +36236,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
34410
36236
  var method = opt.strict ? 'containsRect' : 'intersect';
34411
36237
  return views.filter(function (view) {
34412
36238
  return view && rect[method](view.vel.getBBox({
34413
- target: this.cells
36239
+ target: this.layers
34414
36240
  }));
34415
36241
  }, this);
34416
36242
  },
@@ -35194,7 +37020,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35194
37020
  if (this.GUARDED_TAG_NAMES.includes(target.tagName)) {
35195
37021
  return true;
35196
37022
  }
35197
- if (view && view.model && view.model instanceof Cell) {
37023
+ if (view && view.model && view.model[CELL_MARKER]) {
35198
37024
  return false;
35199
37025
  }
35200
37026
  if (this.el === target || this.svg.contains(target)) {
@@ -35209,12 +37035,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35209
37035
  options.gridSize = gridSize;
35210
37036
  if (options.drawGrid && !options.drawGridSize) {
35211
37037
  // Do not redraw the grid if the `drawGridSize` is set.
35212
- this.getLayerView(LayersNames.GRID).renderGrid();
37038
+ this.getLayerView(paperLayers.GRID).renderGrid();
35213
37039
  }
35214
37040
  return this;
35215
37041
  },
35216
37042
  setGrid: function (drawGrid) {
35217
- this.getLayerView(LayersNames.GRID).setGrid(drawGrid);
37043
+ this.getLayerView(paperLayers.GRID).setGrid(drawGrid);
35218
37044
  return this;
35219
37045
  },
35220
37046
  updateBackgroundImage: function (opt) {
@@ -35539,194 +37365,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35539
37365
  }
35540
37366
  }, {
35541
37367
  sorting: sortingTypes,
35542
- Layers: LayersNames,
35543
- backgroundPatterns: {
35544
- flipXy: function (img) {
35545
- // d b
35546
- // q p
35547
-
35548
- var canvas = document.createElement('canvas');
35549
- var imgWidth = img.width;
35550
- var imgHeight = img.height;
35551
- canvas.width = 2 * imgWidth;
35552
- canvas.height = 2 * imgHeight;
35553
- var ctx = canvas.getContext('2d');
35554
- // top-left image
35555
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35556
- // xy-flipped bottom-right image
35557
- ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
35558
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35559
- // x-flipped top-right image
35560
- ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
35561
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35562
- // y-flipped bottom-left image
35563
- ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
35564
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35565
- return canvas;
35566
- },
35567
- flipX: function (img) {
35568
- // d b
35569
- // d b
35570
-
35571
- var canvas = document.createElement('canvas');
35572
- var imgWidth = img.width;
35573
- var imgHeight = img.height;
35574
- canvas.width = imgWidth * 2;
35575
- canvas.height = imgHeight;
35576
- var ctx = canvas.getContext('2d');
35577
- // left image
35578
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35579
- // flipped right image
35580
- ctx.translate(2 * imgWidth, 0);
35581
- ctx.scale(-1, 1);
35582
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35583
- return canvas;
35584
- },
35585
- flipY: function (img) {
35586
- // d d
35587
- // q q
35588
-
35589
- var canvas = document.createElement('canvas');
35590
- var imgWidth = img.width;
35591
- var imgHeight = img.height;
35592
- canvas.width = imgWidth;
35593
- canvas.height = imgHeight * 2;
35594
- var ctx = canvas.getContext('2d');
35595
- // top image
35596
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35597
- // flipped bottom image
35598
- ctx.translate(0, 2 * imgHeight);
35599
- ctx.scale(1, -1);
35600
- ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
35601
- return canvas;
35602
- },
35603
- watermark: function (img, opt) {
35604
- // d
35605
- // d
35606
-
35607
- opt = opt || {};
35608
- var imgWidth = img.width;
35609
- var imgHeight = img.height;
35610
- var canvas = document.createElement('canvas');
35611
- canvas.width = imgWidth * 3;
35612
- canvas.height = imgHeight * 3;
35613
- var ctx = canvas.getContext('2d');
35614
- var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
35615
- var radians = toRad(angle);
35616
- var stepX = canvas.width / 4;
35617
- var stepY = canvas.height / 4;
35618
- for (var i = 0; i < 4; i++) {
35619
- for (var j = 0; j < 4; j++) {
35620
- if ((i + j) % 2 > 0) {
35621
- // reset the current transformations
35622
- ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
35623
- ctx.rotate(radians);
35624
- ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
35625
- }
35626
- }
35627
- }
35628
- return canvas;
35629
- }
35630
- },
35631
- gridPatterns: {
35632
- dot: [{
35633
- color: '#AAAAAA',
35634
- thickness: 1,
35635
- markup: 'rect',
35636
- render: function (el, opt) {
35637
- V(el).attr({
35638
- width: opt.thickness,
35639
- height: opt.thickness,
35640
- fill: opt.color
35641
- });
35642
- }
35643
- }],
35644
- fixedDot: [{
35645
- color: '#AAAAAA',
35646
- thickness: 1,
35647
- markup: 'rect',
35648
- render: function (el, opt) {
35649
- V(el).attr({
35650
- fill: opt.color
35651
- });
35652
- },
35653
- update: function (el, opt, paper) {
35654
- const {
35655
- sx,
35656
- sy
35657
- } = paper.scale();
35658
- const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
35659
- const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
35660
- V(el).attr({
35661
- width,
35662
- height
35663
- });
35664
- }
35665
- }],
35666
- mesh: [{
35667
- color: '#AAAAAA',
35668
- thickness: 1,
35669
- markup: 'path',
35670
- render: function (el, opt) {
35671
- var d;
35672
- var width = opt.width;
35673
- var height = opt.height;
35674
- var thickness = opt.thickness;
35675
- if (width - thickness >= 0 && height - thickness >= 0) {
35676
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35677
- } else {
35678
- d = 'M 0 0 0 0';
35679
- }
35680
- V(el).attr({
35681
- 'd': d,
35682
- stroke: opt.color,
35683
- 'stroke-width': opt.thickness
35684
- });
35685
- }
35686
- }],
35687
- doubleMesh: [{
35688
- color: '#AAAAAA',
35689
- thickness: 1,
35690
- markup: 'path',
35691
- render: function (el, opt) {
35692
- var d;
35693
- var width = opt.width;
35694
- var height = opt.height;
35695
- var thickness = opt.thickness;
35696
- if (width - thickness >= 0 && height - thickness >= 0) {
35697
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35698
- } else {
35699
- d = 'M 0 0 0 0';
35700
- }
35701
- V(el).attr({
35702
- 'd': d,
35703
- stroke: opt.color,
35704
- 'stroke-width': opt.thickness
35705
- });
35706
- }
35707
- }, {
35708
- color: '#000000',
35709
- thickness: 3,
35710
- scaleFactor: 4,
35711
- markup: 'path',
35712
- render: function (el, opt) {
35713
- var d;
35714
- var width = opt.width;
35715
- var height = opt.height;
35716
- var thickness = opt.thickness;
35717
- if (width - thickness >= 0 && height - thickness >= 0) {
35718
- d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
35719
- } else {
35720
- d = 'M 0 0 0 0';
35721
- }
35722
- V(el).attr({
35723
- 'd': d,
35724
- stroke: opt.color,
35725
- 'stroke-width': opt.thickness
35726
- });
35727
- }
35728
- }]
35729
- }
37368
+ Layers: paperLayers,
37369
+ backgroundPatterns,
37370
+ gridPatterns
35730
37371
  });
35731
37372
 
35732
37373
  const ToolView = View.extend({
@@ -35825,7 +37466,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35825
37466
  tools: null,
35826
37467
  relatedView: null,
35827
37468
  name: null
35828
- // layer?: LayersNames.TOOLS
37469
+ // layer?: Paper.Layers.TOOLS
35829
37470
  // z?: number
35830
37471
  },
35831
37472
  configure: function (options) {
@@ -35939,7 +37580,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35939
37580
  },
35940
37581
  getLayer() {
35941
37582
  const {
35942
- layer = LayersNames.TOOLS
37583
+ layer = Paper.Layers.TOOLS
35943
37584
  } = this.options;
35944
37585
  return layer;
35945
37586
  },
@@ -35968,21 +37609,26 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
35968
37609
 
35969
37610
  var index$2 = {
35970
37611
  __proto__: null,
35971
- CELL_VIEW_MARKER: CELL_VIEW_MARKER,
35972
37612
  Cell: Cell,
37613
+ CellCollection: CellCollection,
35973
37614
  CellView: CellView,
37615
+ DEFAULT_GRAPH_LAYER_TYPE: DEFAULT_GRAPH_LAYER_TYPE,
35974
37616
  Element: Element$1,
35975
37617
  ElementView: ElementView,
35976
37618
  Graph: Graph,
37619
+ GraphLayer: GraphLayer,
37620
+ GraphLayerCollection: GraphLayerCollection,
37621
+ GraphLayerView: GraphLayerView,
37622
+ GridLayerView: GridLayerView,
35977
37623
  HighlighterView: HighlighterView,
35978
- LayersNames: LayersNames,
37624
+ LayerView: LayerView,
35979
37625
  Link: Link$1,
35980
37626
  LinkView: LinkView,
35981
37627
  Paper: Paper,
35982
- PaperLayer: PaperLayer,
35983
37628
  ToolView: ToolView,
35984
37629
  ToolsView: ToolsView,
35985
- attributes: attributes
37630
+ attributes: attributes,
37631
+ sortingTypes: sortingTypes
35986
37632
  };
35987
37633
 
35988
37634
  // Vertex Handles
@@ -37989,7 +39635,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
37989
39635
  Remove: Remove
37990
39636
  };
37991
39637
 
37992
- var version = "4.2.0-alpha.1";
39638
+ var version = "4.2.0-beta.2";
37993
39639
 
37994
39640
  const Vectorizer = V;
37995
39641
  const layout$1 = {