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

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