@joint/core 4.1.2 → 4.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/geometry.js +128 -123
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +79 -16
  5. package/dist/joint.js +2249 -1730
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +2248 -1727
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +469 -272
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +28 -22
  13. package/src/V/create.mjs +51 -0
  14. package/src/V/index.mjs +69 -154
  15. package/src/V/namespace.mjs +9 -0
  16. package/src/V/transform.mjs +183 -0
  17. package/src/V/traverse.mjs +16 -0
  18. package/src/anchors/index.mjs +140 -33
  19. package/src/cellTools/Boundary.mjs +1 -1
  20. package/src/cellTools/Control.mjs +1 -1
  21. package/src/connectionPoints/index.mjs +24 -9
  22. package/src/connectionStrategies/index.mjs +1 -1
  23. package/src/connectors/jumpover.mjs +1 -1
  24. package/src/dia/Cell.mjs +6 -2
  25. package/src/dia/CellView.mjs +47 -39
  26. package/src/dia/Element.mjs +79 -35
  27. package/src/dia/ElementView.mjs +9 -3
  28. package/src/dia/HighlighterView.mjs +32 -11
  29. package/src/dia/Paper.mjs +134 -22
  30. package/src/dia/PaperLayer.mjs +9 -2
  31. package/src/dia/attributes/text.mjs +12 -3
  32. package/src/dia/layers/GridLayer.mjs +5 -0
  33. package/src/dia/ports.mjs +152 -39
  34. package/src/env/index.mjs +1 -1
  35. package/src/g/rect.mjs +7 -0
  36. package/src/highlighters/stroke.mjs +1 -1
  37. package/src/linkAnchors/index.mjs +2 -2
  38. package/src/linkTools/Anchor.mjs +2 -2
  39. package/src/linkTools/Vertices.mjs +4 -6
  40. package/src/mvc/Dom/methods.mjs +2 -2
  41. package/src/util/util.mjs +1 -1
  42. package/src/util/utilHelpers.mjs +2 -0
  43. package/types/geometry.d.ts +2 -0
  44. package/types/joint.d.ts +81 -20
  45. package/src/V/annotation.mjs +0 -0
@@ -1,6 +1,6 @@
1
1
  import { Cell } from './Cell.mjs';
2
2
  import { Point, toRad, normalizeAngle, Rect } from '../g/index.mjs';
3
- import { isNumber, isObject, interpolate, assign, invoke, normalizeSides } from '../util/index.mjs';
3
+ import { isNumber, isObject, interpolate, assign, invoke, normalizeSides, omit } from '../util/index.mjs';
4
4
  import { elementPortPrototype } from './ports.mjs';
5
5
 
6
6
  // Element base model.
@@ -344,22 +344,24 @@ export const Element = Cell.extend({
344
344
  const { graph } = this;
345
345
  if (!graph) throw new Error('Element must be part of a graph.');
346
346
 
347
- const childElements = this.getEmbeddedCells().filter(cell => cell.isElement());
348
- if (childElements.length === 0) return this;
347
+ // Get element children, optionally filtered according to `opt.filter`.
348
+ const filteredChildElements = this._getFilteredChildElements(opt.filter);
349
349
 
350
350
  this.startBatch('fit-embeds', opt);
351
351
 
352
352
  if (opt.deep) {
353
353
  // `opt.deep = true` means "fit to all descendants".
354
354
  // As the first action of the fitting algorithm, recursively apply `fitToChildren()` on all descendants.
355
- // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant, then go up (= this element).
356
- invoke(childElements, 'fitToChildren', opt);
355
+ // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant, then go up (= this element)
356
+ // - omit `opt.minRect` - it only makes sense for the first level of recursion if there are no filtered children, but in this case we do have filtered children
357
+ invoke(filteredChildElements, 'fitToChildren', omit(opt, 'minRect'));
357
358
  }
358
359
 
359
360
  // Set new size and position of this element, based on:
360
- // - union of bboxes of all children
361
+ // - union of bboxes of filtered element children
361
362
  // - inflated by given `opt.padding`
362
- this._fitToElements(Object.assign({ elements: childElements }, opt));
363
+ // - containing at least `opt.minRect` (if this is the first level of recursion and there are no filtered children)
364
+ this._fitToElements(Object.assign({ elements: filteredChildElements }, opt));
363
365
 
364
366
  this.stopBatch('fit-embeds');
365
367
 
@@ -375,25 +377,27 @@ export const Element = Cell.extend({
375
377
  // If the current element is `opt.terminator`, it means that this element has already been processed as parent so we can exit now.
376
378
  if (opt.deep && opt.terminator && ((opt.terminator === this) || (opt.terminator === this.id))) return this;
377
379
 
380
+ // If this element has no parent, there is nothing for us to do.
378
381
  const parentElement = this.getParentCell();
379
382
  if (!parentElement || !parentElement.isElement()) return this;
380
383
 
381
- // Get all children of parent element (i.e. this element + any sibling elements).
382
- const siblingElements = parentElement.getEmbeddedCells().filter(cell => cell.isElement());
383
- if (siblingElements.length === 0) return this;
384
+ // Get element children of parent element (i.e. this element + any sibling elements), optionally filtered according to `opt.filter`.
385
+ const filteredSiblingElements = parentElement._getFilteredChildElements(opt.filter);
384
386
 
385
387
  this.startBatch('fit-parent', opt);
386
388
 
387
389
  // Set new size and position of parent element, based on:
388
- // - union of bboxes of all children of parent element (i.e. this element + any sibling elements)
390
+ // - union of bboxes of filtered element children of parent element (i.e. this element + any sibling elements)
389
391
  // - inflated by given `opt.padding`
390
- parentElement._fitToElements(Object.assign({ elements: siblingElements }, opt));
392
+ // - containing at least `opt.minRect` (if this is the first level of recursion and there are no filtered siblings)
393
+ parentElement._fitToElements(Object.assign({ elements: filteredSiblingElements }, opt));
391
394
 
392
395
  if (opt.deep) {
393
396
  // `opt.deep = true` means "fit all ancestors to their respective children".
394
397
  // As the last action of the fitting algorithm, recursively apply `fitParent()` on all ancestors.
395
- // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant (= this element), then go up.
396
- parentElement.fitParent(opt);
398
+ // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant (= this element), then go up
399
+ // - omit `opt.minRect` - `minRect` is not relevant for the parent of parent element (and upwards)
400
+ parentElement.fitParent(omit(opt, 'minRect'));
397
401
  }
398
402
 
399
403
  this.stopBatch('fit-parent');
@@ -401,45 +405,85 @@ export const Element = Cell.extend({
401
405
  return this;
402
406
  },
403
407
 
408
+ _getFilteredChildElements: function(filter) {
409
+
410
+ let filterFn;
411
+ if (typeof filter === 'function') {
412
+ filterFn = (cell) => (cell.isElement() && filter(cell));
413
+ } else {
414
+ filterFn = (cell) => (cell.isElement());
415
+ }
416
+ return this.getEmbeddedCells().filter(filterFn);
417
+ },
418
+
404
419
  // Assumption: This element is part of a graph.
405
420
  _fitToElements: function(opt = {}) {
406
421
 
422
+ let minBBox = null;
423
+ if (opt.minRect) {
424
+ // Coerce `opt.minRect` to g.Rect (missing properties = 0).
425
+ minBBox = new Rect(opt.minRect);
426
+ }
427
+
407
428
  const elementsBBox = this.graph.getCellsBBox(opt.elements);
408
- // If no `opt.elements` were provided, do nothing.
409
- if (!elementsBBox) return;
429
+ // If no `opt.elements` were provided, do nothing (but if `opt.minRect` was provided, set that as this element's bbox instead).
430
+ if (!elementsBBox) {
431
+ this._setBBox(minBBox, opt);
432
+ return;
433
+ }
410
434
 
411
435
  const { expandOnly, shrinkOnly } = opt;
412
- // This combination is meaningless, do nothing.
413
- if (expandOnly && shrinkOnly) return;
436
+ // This combination is meaningless, do nothing (but if `opt.minRect` was provided, set that as this element's bbox instead).
437
+ if (expandOnly && shrinkOnly) {
438
+ this._setBBox(minBBox, opt);
439
+ return;
440
+ }
414
441
 
415
442
  // Calculate new size and position of this element based on:
416
443
  // - union of bboxes of `opt.elements`
417
- // - inflated by `opt.padding` (if not provided, all four properties = 0)
444
+ // - inflated by normalized `opt.padding` (missing sides = 0)
418
445
  let { x, y, width, height } = elementsBBox;
419
446
  const { left, right, top, bottom } = normalizeSides(opt.padding);
420
447
  x -= left;
421
448
  y -= top;
422
449
  width += left + right;
423
450
  height += bottom + top;
424
- let resultBBox = new Rect(x, y, width, height);
451
+ let contentBBox = new Rect(x, y, width, height);
425
452
 
426
453
  if (expandOnly) {
427
454
  // Non-shrinking is enforced by taking union of this element's current bbox with bbox calculated from `opt.elements`.
428
- resultBBox = this.getBBox().union(resultBBox);
455
+ contentBBox = this.getBBox().union(contentBBox);
429
456
 
430
457
  } else if (shrinkOnly) {
431
458
  // Non-expansion is enforced by taking intersection of this element's current bbox with bbox calculated from `opt.elements`.
432
- const intersectionBBox = this.getBBox().intersect(resultBBox);
433
- // If all children are outside this element's current bbox, then `intersectionBBox` is `null` - does not make sense, do nothing.
434
- if (!intersectionBBox) return;
459
+ const intersectionBBox = this.getBBox().intersect(contentBBox);
460
+ // If all children are outside this element's current bbox, then `intersectionBBox` is `null`.
461
+ // That does not make sense, do nothing (but if `opt.minRect` was provided, set that as this element's bbox instead).
462
+ if (!intersectionBBox) {
463
+ this._setBBox(minBBox, opt);
464
+ return;
465
+ }
435
466
 
436
- resultBBox = intersectionBBox;
467
+ contentBBox = intersectionBBox;
437
468
  }
438
469
 
439
470
  // Set the new size and position of this element.
471
+ // - if `opt.minRect` was provided, add it via union to calculated bbox
472
+ let resultBBox = contentBBox;
473
+ if (minBBox) {
474
+ resultBBox = resultBBox.union(minBBox);
475
+ }
476
+ this._setBBox(resultBBox, opt);
477
+ },
478
+
479
+ _setBBox: function(bbox, opt) {
480
+
481
+ if (!bbox) return;
482
+
483
+ const { x, y, width, height } = bbox;
440
484
  this.set({
441
- position: { x: resultBBox.x, y: resultBBox.y },
442
- size: { width: resultBBox.width, height: resultBBox.height }
485
+ position: { x, y },
486
+ size: { width, height }
443
487
  }, opt);
444
488
  },
445
489
 
@@ -451,7 +495,7 @@ export const Element = Cell.extend({
451
495
 
452
496
  if (origin) {
453
497
 
454
- var center = this.getBBox().center();
498
+ var center = this.getCenter();
455
499
  var size = this.get('size');
456
500
  var position = this.get('position');
457
501
  center.rotate(origin, this.get('angle') - angle);
@@ -497,6 +541,11 @@ export const Element = Cell.extend({
497
541
  return bbox;
498
542
  },
499
543
 
544
+ getCenter: function() {
545
+ const { position: { x, y }, size: { width, height }} = this.attributes;
546
+ return new Point(x + width / 2, y + height / 2);
547
+ },
548
+
500
549
  getPointFromConnectedLink: function(link, endType) {
501
550
  // Center of the model
502
551
  var bbox = this.getBBox();
@@ -506,14 +555,9 @@ export const Element = Cell.extend({
506
555
  if (!endDef) return center;
507
556
  var portId = endDef.port;
508
557
  if (!portId || !this.hasPort(portId)) return center;
509
- var portGroup = this.portProp(portId, ['group']);
510
- var portsPositions = this.getPortsPositions(portGroup);
511
- var portCenter = new Point(portsPositions[portId]).offset(bbox.origin());
512
- var angle = this.angle();
513
- if (angle) portCenter.rotate(center, -angle);
514
- return portCenter;
558
+ return this.getPortCenter(portId);
515
559
  }
560
+
516
561
  });
517
562
 
518
563
  assign(Element.prototype, elementPortPrototype);
519
-
@@ -804,17 +804,23 @@ export const ElementView = CellView.extend({
804
804
 
805
805
  // Drag Handlers
806
806
 
807
+ snapToGrid: function(evt, x, y) {
808
+ const grid = this.paper.options.gridSize;
809
+ return {
810
+ x: snapToGrid(x, grid),
811
+ y: snapToGrid(y, grid)
812
+ };
813
+ },
814
+
807
815
  drag: function(evt, x, y) {
808
816
 
809
817
  var paper = this.paper;
810
- var grid = paper.options.gridSize;
811
818
  var element = this.model;
812
819
  var data = this.eventData(evt);
813
820
  var { pointerOffset, restrictedArea, embedding } = data;
814
821
 
815
822
  // Make sure the new element's position always snaps to the current grid
816
- var elX = snapToGrid(x + pointerOffset.x, grid);
817
- var elY = snapToGrid(y + pointerOffset.y, grid);
823
+ const { x: elX, y: elY } = this.snapToGrid(evt, x + pointerOffset.x, y + pointerOffset.y);
818
824
 
819
825
  element.position(elX, elY, { restrictedArea, deep: true, ui: true });
820
826
 
@@ -1,6 +1,6 @@
1
1
  import * as mvc from '../mvc/index.mjs';
2
2
  import V from '../V/index.mjs';
3
- import { isPlainObject, result } from '../util/util.mjs';
3
+ import { isNumber, isPlainObject, result } from '../util/util.mjs';
4
4
 
5
5
  function toArray(obj) {
6
6
  if (!obj) return [];
@@ -71,7 +71,7 @@ export const HighlighterView = mvc.View.extend({
71
71
  }
72
72
  } else if (nodeSelector) {
73
73
  el = V.toNode(nodeSelector);
74
- if (!(el instanceof SVGElement)) el = null;
74
+ if (!(el instanceof SVGElement) || !cellView.el.contains(el)) el = null;
75
75
  }
76
76
  return el ? el : null;
77
77
  },
@@ -106,8 +106,8 @@ export const HighlighterView = mvc.View.extend({
106
106
  this.transform();
107
107
  return;
108
108
  }
109
- const { vel: cellViewRoot, paper } = cellView;
110
- const { layer: layerName } = options;
109
+ const { paper } = cellView;
110
+ const { layer: layerName, z } = options;
111
111
  if (layerName) {
112
112
  let vGroup;
113
113
  if (detachedTransformGroup) {
@@ -117,20 +117,41 @@ export const HighlighterView = mvc.View.extend({
117
117
  vGroup = V('g').addClass('highlight-transform').append(el);
118
118
  }
119
119
  this.transformGroup = vGroup;
120
- paper.getLayerView(layerName).insertSortedNode(vGroup.node, options.z);
120
+ paper.getLayerView(layerName).insertSortedNode(vGroup.node, z);
121
121
  } else {
122
- // TODO: prepend vs append
123
- if (!el.parentNode || el.nextSibling) {
124
- // Not appended yet or not the last child
125
- cellViewRoot.append(el);
122
+ const children = cellView.el.children;
123
+
124
+ const index = Math.max(z, 0);
125
+ const beforeChild = children[index];
126
+
127
+ // If the provided `z` is a number and there is an element on the index,
128
+ // we need to insert the highlighter before the element on the index.
129
+ // Otherwise, the highlighter will be appended as the last child.
130
+ const toBeInserted = isNumber(z) && beforeChild;
131
+ const isElementAtTargetPosition = toBeInserted
132
+ // If the element is being inserted, check if it is not already at the correct position.
133
+ ? el === beforeChild
134
+ // If the element is being appended, check if it is not already last child.
135
+ : !el.nextElementSibling;
136
+
137
+ // If the element is already mounted and does not require repositioning, do nothing.
138
+ if (el.parentNode && isElementAtTargetPosition) return;
139
+
140
+ if (toBeInserted) {
141
+ cellView.el.insertBefore(el, beforeChild);
142
+ } else {
143
+ cellView.el.appendChild(el);
126
144
  }
127
145
  }
128
146
  },
129
147
 
130
148
  unmount() {
131
- const { MOUNTABLE, transformGroup, vel } = this;
149
+ const { MOUNTABLE, transformGroup, vel, options } = this;
132
150
  if (!MOUNTABLE) return;
133
- if (transformGroup) {
151
+ if (options.layer) {
152
+ if (!transformGroup) return;
153
+ // else: if `transformGroup` is not null, it means the highlighter
154
+ // has not been mounted yet
134
155
  this.transformGroup = null;
135
156
  this.detachedTransformGroup = transformGroup;
136
157
  transformGroup.remove();
package/src/dia/Paper.mjs CHANGED
@@ -387,6 +387,9 @@ export const Paper = View.extend({
387
387
  // to mitigate the differences between the model and view geometry.
388
388
  DEFAULT_FIND_BUFFER: 200,
389
389
 
390
+ // Default layer to insert the cell views into.
391
+ DEFAULT_CELL_LAYER: LayersNames.CELLS,
392
+
390
393
  init: function() {
391
394
 
392
395
  const { options } = this;
@@ -399,7 +402,11 @@ export const Paper = View.extend({
399
402
  const model = this.model = options.model || new Graph;
400
403
 
401
404
  // Layers (SVGGroups)
402
- this._layers = {};
405
+ this._layers = {
406
+ viewsMap: {},
407
+ namesMap: {},
408
+ order: [],
409
+ };
403
410
 
404
411
  this.cloneOptions();
405
412
  this.render();
@@ -471,7 +478,10 @@ export const Paper = View.extend({
471
478
 
472
479
  onCellChange: function(cell, opt) {
473
480
  if (cell === this.model.attributes.cells) return;
474
- if (cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX) {
481
+ if (
482
+ cell.hasChanged('layer') ||
483
+ (cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX)
484
+ ) {
475
485
  const view = this.findViewByModel(cell);
476
486
  if (view) this.requestViewUpdate(view, view.FLAG_INSERT, view.UPDATE_PRIORITY, opt);
477
487
  }
@@ -588,19 +598,122 @@ export const Paper = View.extend({
588
598
  },
589
599
 
590
600
  hasLayerView(layerName) {
591
- return (layerName in this._layers);
601
+ return (layerName in this._layers.viewsMap);
592
602
  },
593
603
 
594
604
  getLayerView(layerName) {
595
- const { _layers } = this;
596
- if (layerName in _layers) return _layers[layerName];
597
- throw new Error(`dia.Paper: Unknown layer "${layerName}"`);
605
+ const { _layers: { viewsMap }} = this;
606
+ if (layerName in viewsMap) return viewsMap[layerName];
607
+ throw new Error(`dia.Paper: Unknown layer "${layerName}".`);
598
608
  },
599
609
 
600
610
  getLayerNode(layerName) {
601
611
  return this.getLayerView(layerName).el;
602
612
  },
603
613
 
614
+ _removeLayer(layerView) {
615
+ this._unregisterLayer(layerView);
616
+ layerView.remove();
617
+ },
618
+
619
+ _unregisterLayer(layerView) {
620
+ const { _layers: { viewsMap, namesMap, order }} = this;
621
+ const layerName = this._getLayerName(layerView);
622
+ order.splice(order.indexOf(layerName), 1);
623
+ delete namesMap[layerView.cid];
624
+ delete viewsMap[layerName];
625
+ },
626
+
627
+ _registerLayer(layerName, layerView, beforeLayerView) {
628
+ const { _layers: { viewsMap, namesMap, order }} = this;
629
+ if (beforeLayerView) {
630
+ const beforeLayerName = this._getLayerName(beforeLayerView);
631
+ order.splice(order.indexOf(beforeLayerName), 0, layerName);
632
+ } else {
633
+ order.push(layerName);
634
+ }
635
+ viewsMap[layerName] = layerView;
636
+ namesMap[layerView.cid] = layerName;
637
+ },
638
+
639
+ _getLayerView(layer) {
640
+ const { _layers: { namesMap, viewsMap }} = this;
641
+ if (layer instanceof PaperLayer) {
642
+ if (layer.cid in namesMap) return layer;
643
+ return null;
644
+ }
645
+ if (layer in viewsMap) return viewsMap[layer];
646
+ return null;
647
+ },
648
+
649
+ _getLayerName(layerView) {
650
+ const { _layers: { namesMap }} = this;
651
+ return namesMap[layerView.cid];
652
+ },
653
+
654
+ _requireLayerView(layer) {
655
+ const layerView = this._getLayerView(layer);
656
+ if (!layerView) {
657
+ if (layer instanceof PaperLayer) {
658
+ throw new Error('dia.Paper: The layer is not registered.');
659
+ } else {
660
+ throw new Error(`dia.Paper: Unknown layer "${layer}".`);
661
+ }
662
+ }
663
+ return layerView;
664
+ },
665
+
666
+ hasLayer(layer) {
667
+ return this._getLayerView(layer) !== null;
668
+ },
669
+
670
+ removeLayer(layer) {
671
+ const layerView = this._requireLayerView(layer);
672
+ if (!layerView.isEmpty()) {
673
+ throw new Error('dia.Paper: The layer is not empty.');
674
+ }
675
+ this._removeLayer(layerView);
676
+ },
677
+
678
+ addLayer(layerName, layerView, options = {}) {
679
+ if (!layerName || typeof layerName !== 'string') {
680
+ throw new Error('dia.Paper: The layer name must be provided.');
681
+ }
682
+ if (this._getLayerView(layerName)) {
683
+ throw new Error(`dia.Paper: The layer "${layerName}" already exists.`);
684
+ }
685
+ if (!(layerView instanceof PaperLayer)) {
686
+ throw new Error('dia.Paper: The layer view is not an instance of dia.PaperLayer.');
687
+ }
688
+ const { insertBefore } = options;
689
+ if (!insertBefore) {
690
+ this._registerLayer(layerName, layerView, null);
691
+ this.layers.appendChild(layerView.el);
692
+ } else {
693
+ const beforeLayerView = this._requireLayerView(insertBefore);
694
+ this._registerLayer(layerName, layerView, beforeLayerView);
695
+ this.layers.insertBefore(layerView.el, beforeLayerView.el);
696
+ }
697
+ },
698
+
699
+ moveLayer(layer, insertBefore) {
700
+ const layerView = this._requireLayerView(layer);
701
+ if (layerView === this._getLayerView(insertBefore)) return;
702
+ const layerName = this._getLayerName(layerView);
703
+ this._unregisterLayer(layerView);
704
+ this.addLayer(layerName, layerView, { insertBefore });
705
+ },
706
+
707
+ getLayerNames() {
708
+ // Returns a sorted array of layer names.
709
+ return this._layers.order.slice();
710
+ },
711
+
712
+ getLayers() {
713
+ // Returns a sorted array of layer views.
714
+ return this.getLayerNames().map(name => this.getLayerView(name));
715
+ },
716
+
604
717
  render: function() {
605
718
 
606
719
  this.renderChildren();
@@ -645,14 +758,15 @@ export const Paper = View.extend({
645
758
  }
646
759
  },
647
760
 
761
+ renderLayer: function(name) {
762
+ const layerView = this.createLayer(name);
763
+ this.addLayer(name, layerView);
764
+ return layerView;
765
+ },
766
+
648
767
  renderLayers: function(layers = defaultLayers) {
649
768
  this.removeLayers();
650
- // TODO: Layers to be read from the graph `layers` attribute
651
- layers.forEach(({ name, sorted }) => {
652
- const layerView = this.createLayer(name);
653
- this.layers.appendChild(layerView.el);
654
- this._layers[name] = layerView;
655
- });
769
+ layers.forEach(({ name }) => this.renderLayer(name));
656
770
  // Throws an exception if doesn't exist
657
771
  const cellsLayerView = this.getLayerView(LayersNames.CELLS);
658
772
  const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
@@ -670,18 +784,13 @@ export const Paper = View.extend({
670
784
  },
671
785
 
672
786
  removeLayers: function() {
673
- const { _layers } = this;
674
- Object.keys(_layers).forEach(name => {
675
- _layers[name].remove();
676
- delete _layers[name];
677
- });
787
+ const { _layers: { viewsMap }} = this;
788
+ Object.values(viewsMap).forEach(layerView => this._removeLayer(layerView));
678
789
  },
679
790
 
680
791
  resetLayers: function() {
681
- const { _layers } = this;
682
- Object.keys(_layers).forEach(name => {
683
- _layers[name].removePivots();
684
- });
792
+ const { _layers: { viewsMap }} = this;
793
+ Object.values(viewsMap).forEach(layerView => layerView.removePivots());
685
794
  },
686
795
 
687
796
  update: function() {
@@ -1796,8 +1905,11 @@ export const Paper = View.extend({
1796
1905
  },
1797
1906
 
1798
1907
  insertView: function(view, isInitialInsert) {
1799
- const layerView = this.getLayerView(LayersNames.CELLS);
1800
1908
  const { el, model } = view;
1909
+
1910
+ const layerName = model.get('layer') || this.DEFAULT_CELL_LAYER;
1911
+ const layerView = this.getLayerView(layerName);
1912
+
1801
1913
  switch (this.options.sorting) {
1802
1914
  case sortingTypes.APPROX:
1803
1915
  layerView.insertSortedNode(el, model.get('z'));
@@ -22,7 +22,9 @@ export const PaperLayer = View.extend({
22
22
  },
23
23
 
24
24
  className: function() {
25
- return addClassNamePrefix(`${this.options.name}-layer`);
25
+ const { name } = this.options;
26
+ if (!name) return null;
27
+ return addClassNamePrefix(`${name}-layer`);
26
28
  },
27
29
 
28
30
  init: function() {
@@ -70,6 +72,11 @@ export const PaperLayer = View.extend({
70
72
  const { el, pivotNodes } = this;
71
73
  for (let z in pivotNodes) el.removeChild(pivotNodes[z]);
72
74
  this.pivotNodes = {};
73
- }
75
+ },
76
+
77
+ isEmpty: function() {
78
+ // Check if the layer has any child elements (pivot comments are not counted).
79
+ return this.el.children.length === 0;
80
+ },
74
81
 
75
82
  });
@@ -148,10 +148,19 @@ const textAttributesNS = {
148
148
  // TODO: change the `lineHeight` to breakText option.
149
149
  wrapFontAttributes.lineHeight = attrs['line-height'];
150
150
 
151
+ let svgDocument = this.paper.svg;
152
+ if (!svgDocument.checkVisibility()) {
153
+ // If the paper is visible, we can utilize
154
+ // its SVG element to measure the text size
155
+ // when breaking the text.
156
+ // Otherwise, we need to create a temporary
157
+ // SVG document and append it to the DOM,
158
+ // (the default behavior of `breakText`).
159
+ svgDocument = null;
160
+ }
161
+
151
162
  wrappedText = breakTextFn('' + text, size, wrapFontAttributes, {
152
- // Provide an existing SVG Document here
153
- // instead of creating a temporary one over again.
154
- svgDocument: this.paper.svg,
163
+ svgDocument,
155
164
  ellipsis: value.ellipsis,
156
165
  hyphen: value.hyphen,
157
166
  separator: value.separator,
@@ -177,4 +177,9 @@ export const GridLayer = PaperLayer.extend({
177
177
  return isArray ? options : [options];
178
178
  },
179
179
 
180
+ isEmpty() {
181
+ const { _gridCache: grid } = this;
182
+ return this.el.children.length === (grid ? 1 : 0);
183
+ }
184
+
180
185
  });