@joint/core 4.0.3 → 4.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +2 -10
  2. package/dist/geometry.js +4962 -6132
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +338 -52
  5. package/dist/joint.js +34067 -37525
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +34067 -37525
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +7288 -8907
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +10 -15
  13. package/src/{linkTools → cellTools}/Button.mjs +8 -6
  14. package/src/{elementTools → cellTools}/Control.mjs +3 -3
  15. package/src/{linkTools → cellTools}/HoverConnect.mjs +1 -1
  16. package/src/dia/Cell.mjs +60 -33
  17. package/src/dia/CellView.mjs +75 -8
  18. package/src/dia/ElementView.mjs +13 -8
  19. package/src/dia/Graph.mjs +148 -40
  20. package/src/dia/HighlighterView.mjs +8 -4
  21. package/src/dia/LinkView.mjs +61 -4
  22. package/src/dia/Paper.mjs +84 -0
  23. package/src/dia/ToolView.mjs +29 -4
  24. package/src/dia/ToolsView.mjs +25 -10
  25. package/src/dia/attributes/connection.mjs +5 -0
  26. package/src/dia/attributes/defs.mjs +3 -0
  27. package/src/dia/attributes/eval.mjs +3 -3
  28. package/src/dia/attributes/index.mjs +3 -0
  29. package/src/dia/attributes/shape.mjs +4 -0
  30. package/src/dia/attributes/text.mjs +41 -15
  31. package/src/dia/ports.mjs +4 -0
  32. package/src/elementTools/HoverConnect.mjs +5 -5
  33. package/src/elementTools/index.mjs +5 -4
  34. package/src/env/index.mjs +5 -0
  35. package/src/g/rect.mjs +13 -5
  36. package/src/layout/ports/port.mjs +4 -5
  37. package/src/linkTools/Anchor.mjs +1 -1
  38. package/src/linkTools/Arrowhead.mjs +2 -1
  39. package/src/linkTools/RotateLabel.mjs +110 -0
  40. package/src/linkTools/Segments.mjs +1 -1
  41. package/src/linkTools/Vertices.mjs +41 -4
  42. package/src/linkTools/index.mjs +7 -4
  43. package/src/mvc/View.mjs +0 -1
  44. package/src/mvc/ViewBase.mjs +2 -1
  45. package/src/routers/rightAngle.mjs +538 -140
  46. package/src/shapes/standard.mjs +8 -1
  47. package/src/{dia/attributes → util}/calc.mjs +24 -12
  48. package/src/util/index.mjs +1 -0
  49. package/src/util/util.mjs +39 -0
  50. package/src/util/utilHelpers.mjs +2 -1
  51. package/types/geometry.d.ts +6 -1
  52. package/types/joint.d.ts +331 -50
  53. /package/src/{linkTools → cellTools}/Boundary.mjs +0 -0
  54. /package/src/{linkTools → cellTools}/Connect.mjs +0 -0
  55. /package/src/{linkTools → cellTools}/helpers.mjs +0 -0
@@ -2,10 +2,10 @@ import { CellView } from './CellView.mjs';
2
2
  import { Link } from './Link.mjs';
3
3
  import V from '../V/index.mjs';
4
4
  import { addClassNamePrefix, merge, assign, isObject, isFunction, clone, isPercentage, result, isEqual } from '../util/index.mjs';
5
- import { Point, Line, Path, normalizeAngle, Rect, Polyline } from '../g/index.mjs';
5
+ import { Point, Line, Path, normalizeAngle, Rect, Polyline, intersection } from '../g/index.mjs';
6
6
  import * as routers from '../routers/index.mjs';
7
7
  import * as connectors from '../connectors/index.mjs';
8
-
8
+ import { env } from '../env/index.mjs';
9
9
 
10
10
  const Flags = {
11
11
  TOOLS: CellView.Flags.TOOLS,
@@ -63,7 +63,7 @@ export const LinkView = CellView.extend({
63
63
  attrs: [Flags.UPDATE],
64
64
  router: [Flags.UPDATE],
65
65
  connector: [Flags.CONNECTOR],
66
- labels: [Flags.LABELS],
66
+ labels: [Flags.LABELS, Flags.TOOLS],
67
67
  labelMarkup: [Flags.LABELS],
68
68
  vertices: [Flags.UPDATE],
69
69
  source: [Flags.SOURCE, Flags.UPDATE],
@@ -73,6 +73,7 @@ export const LinkView = CellView.extend({
73
73
  initFlag: [Flags.RENDER, Flags.SOURCE, Flags.TARGET, Flags.TOOLS],
74
74
 
75
75
  UPDATE_PRIORITY: 1,
76
+ EPSILON: 1e-6,
76
77
 
77
78
  confirmUpdate: function(flags, opt) {
78
79
 
@@ -99,6 +100,11 @@ export const LinkView = CellView.extend({
99
100
  this.updateHighlighters(true);
100
101
  this.updateTools(opt);
101
102
  flags = this.removeFlag(flags, [Flags.RENDER, Flags.UPDATE, Flags.LABELS, Flags.TOOLS, Flags.CONNECTOR]);
103
+
104
+ if (env.test('isSafari')) {
105
+ this.__fixSafariBug268376();
106
+ }
107
+
102
108
  return flags;
103
109
  }
104
110
 
@@ -151,6 +157,19 @@ export const LinkView = CellView.extend({
151
157
  return flags;
152
158
  },
153
159
 
160
+ __fixSafariBug268376: function() {
161
+ // Safari has a bug where any change after the first render is not reflected in the DOM.
162
+ // https://bugs.webkit.org/show_bug.cgi?id=268376
163
+ const { el } = this;
164
+ const childNodes = Array.from(el.childNodes);
165
+ const fragment = document.createDocumentFragment();
166
+ for (let i = 0, n = childNodes.length; i < n; i++) {
167
+ el.removeChild(childNodes[i]);
168
+ fragment.appendChild(childNodes[i]);
169
+ }
170
+ el.appendChild(fragment);
171
+ },
172
+
154
173
  requestConnectionUpdate: function(opt) {
155
174
  this.requestUpdate(this.getFlag(Flags.UPDATE), opt);
156
175
  },
@@ -443,6 +462,13 @@ export const LinkView = CellView.extend({
443
462
 
444
463
  if (!this._V.labels) return this;
445
464
 
465
+ if (!this.paper.options.labelLayer) {
466
+ // If there is no label layer, the cache needs to be cleared
467
+ // of the root node because the labels are attached
468
+ // to it and could affect the bounding box.
469
+ this.cleanNodeCache(this.el);
470
+ }
471
+
446
472
  var model = this.model;
447
473
  var labels = model.get('labels') || [];
448
474
  var canLabelMove = this.can('labelMove');
@@ -798,6 +824,34 @@ export const LinkView = CellView.extend({
798
824
  return connectionPoint.round(this.decimalsRounding);
799
825
  },
800
826
 
827
+ isIntersecting: function(geometryShape, geometryData) {
828
+ const connection = this.getConnection();
829
+ if (!connection) return false;
830
+ return intersection.exists(
831
+ geometryShape,
832
+ connection,
833
+ geometryData,
834
+ { segmentSubdivisions: this.getConnectionSubdivisions() },
835
+ );
836
+ },
837
+
838
+ isEnclosedIn: function(geometryRect) {
839
+ const connection = this.getConnection();
840
+ if (!connection) return false;
841
+ const bbox = connection.bbox();
842
+ if (!bbox) return false;
843
+ return geometryRect.containsRect(bbox);
844
+ },
845
+
846
+ isAtPoint: function(point /*, options */) {
847
+ // Note: `strict` option is not applicable for links.
848
+ // There is currently no method to determine if a path contains a point.
849
+ const area = new Rect(point);
850
+ // Intersection with a zero-size area is not possible.
851
+ area.inflate(this.EPSILON);
852
+ return this.isIntersecting(area);
853
+ },
854
+
801
855
  // combine default label position with built-in default label position
802
856
  _getDefaultLabelPositionProperty: function() {
803
857
 
@@ -1805,7 +1859,10 @@ export const LinkView = CellView.extend({
1805
1859
  // checking view in close area of the pointer
1806
1860
 
1807
1861
  var r = snapLinks.radius || 50;
1808
- var viewsInArea = paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
1862
+ var viewsInArea = paper.findElementViewsInArea(
1863
+ { x: x - r, y: y - r, width: 2 * r, height: 2 * r },
1864
+ snapLinks.findInAreaOptions
1865
+ );
1809
1866
 
1810
1867
  var prevClosestView = data.closestView || null;
1811
1868
  var prevClosestMagnet = data.closestMagnet || null;
package/src/dia/Paper.mjs CHANGED
@@ -382,6 +382,11 @@ export const Paper = View.extend({
382
382
  ],
383
383
  MIN_SCALE: 1e-6,
384
384
 
385
+ // Default find buffer for the findViewsInArea and findViewsAtPoint methods.
386
+ // The find buffer is used to extend the area of the search
387
+ // to mitigate the differences between the model and view geometry.
388
+ DEFAULT_FIND_BUFFER: 200,
389
+
385
390
  init: function() {
386
391
 
387
392
  const { options } = this;
@@ -1858,6 +1863,85 @@ export const Paper = View.extend({
1858
1863
  }, this);
1859
1864
  },
1860
1865
 
1866
+ findElementViewsInArea(plainArea, opt) {
1867
+ return this._filterViewsInArea(
1868
+ plainArea,
1869
+ (extArea, findOpt) => this.model.findElementsInArea(extArea, findOpt),
1870
+ opt
1871
+ );
1872
+ },
1873
+
1874
+ findLinkViewsInArea: function(plainArea, opt) {
1875
+ return this._filterViewsInArea(
1876
+ plainArea,
1877
+ (extArea, findOpt) => this.model.findLinksInArea(extArea, findOpt),
1878
+ opt
1879
+ );
1880
+ },
1881
+
1882
+ findCellViewsInArea: function(plainArea, opt) {
1883
+ return this._filterViewsInArea(
1884
+ plainArea,
1885
+ (extArea, findOpt) => this.model.findCellsInArea(extArea, findOpt),
1886
+ opt
1887
+ );
1888
+ },
1889
+
1890
+ findElementViewsAtPoint: function(plainPoint, opt) {
1891
+ return this._filterViewsAtPoint(
1892
+ plainPoint,
1893
+ (extArea) => this.model.findElementsInArea(extArea),
1894
+ opt
1895
+ );
1896
+ },
1897
+
1898
+ findLinkViewsAtPoint: function(plainPoint, opt) {
1899
+ return this._filterViewsAtPoint(
1900
+ plainPoint,
1901
+ (extArea) => this.model.findLinksInArea(extArea),
1902
+ opt,
1903
+ );
1904
+ },
1905
+
1906
+ findCellViewsAtPoint: function(plainPoint, opt) {
1907
+ return this._filterViewsAtPoint(
1908
+ plainPoint,
1909
+ // Note: we do not want to pass `opt` to `findCellsInArea`
1910
+ // because the `strict` option works differently for querying at a point
1911
+ (extArea) => this.model.findCellsInArea(extArea),
1912
+ opt
1913
+ );
1914
+ },
1915
+
1916
+ _findInExtendedArea: function(area, findCellsFn, opt = {}) {
1917
+ const {
1918
+ buffer = this.DEFAULT_FIND_BUFFER,
1919
+ } = opt;
1920
+ const extendedArea = (new Rect(area)).inflate(buffer);
1921
+ const cellsInExtendedArea = findCellsFn(extendedArea, opt);
1922
+ return cellsInExtendedArea.map(element => this.findViewByModel(element));
1923
+ },
1924
+
1925
+ _filterViewsInArea: function(plainArea, findCells, opt = {}) {
1926
+ const area = new Rect(plainArea);
1927
+ const viewsInExtendedArea = this._findInExtendedArea(area, findCells, opt);
1928
+ const viewsInArea = viewsInExtendedArea.filter(view => {
1929
+ if (!view) return false;
1930
+ return view.isInArea(area, opt);
1931
+ });
1932
+ return viewsInArea;
1933
+ },
1934
+
1935
+ _filterViewsAtPoint: function(plainPoint, findCells, opt = {}) {
1936
+ const area = new Rect(plainPoint); // zero-size area
1937
+ const viewsInExtendedArea = this._findInExtendedArea(area, findCells, opt);
1938
+ const viewsAtPoint = viewsInExtendedArea.filter(view => {
1939
+ if (!view) return false;
1940
+ return view.isAtPoint(plainPoint, opt);
1941
+ });
1942
+ return viewsAtPoint;
1943
+ },
1944
+
1861
1945
  removeTools: function() {
1862
1946
  this.dispatchToolsEvent('remove');
1863
1947
  return this;
@@ -6,6 +6,7 @@ export const ToolView = mvc.View.extend({
6
6
  className: 'tool',
7
7
  svgElement: true,
8
8
  _visible: true,
9
+ _visibleExplicit: true,
9
10
 
10
11
  init: function() {
11
12
  var name = this.name;
@@ -30,16 +31,40 @@ export const ToolView = mvc.View.extend({
30
31
  return this.name;
31
32
  },
32
33
 
34
+ // Evaluate the visibility of the tool and update the `display` CSS property
35
+ updateVisibility: function() {
36
+ const isVisible = this.computeVisibility();
37
+ this.el.style.display = isVisible ? '' : 'none';
38
+ this._visible = isVisible;
39
+ },
40
+
41
+ // Evaluate the visibility of the tool. The method returns `true` if the tool
42
+ // should be visible in the DOM.
43
+ computeVisibility() {
44
+ if (!this.isExplicitlyVisible()) return false;
45
+ const { visibility } = this.options;
46
+ if (typeof visibility !== 'function') return true;
47
+ return !!visibility.call(this, this.relatedView, this);
48
+ },
49
+
33
50
  show: function() {
34
- this.el.style.display = '';
35
- this._visible = true;
51
+ this._visibleExplicit = true;
52
+ this.updateVisibility();
36
53
  },
37
54
 
38
55
  hide: function() {
39
- this.el.style.display = 'none';
40
- this._visible = false;
56
+ this._visibleExplicit = false;
57
+ this.updateVisibility();
58
+ },
59
+
60
+ // The method returns `false` if the `hide()` method was called on the tool.
61
+ isExplicitlyVisible: function() {
62
+ return !!this._visibleExplicit;
41
63
  },
42
64
 
65
+ // The method returns `false` if the tool is not visible (it has `display: none`).
66
+ // This can happen if the `hide()` method was called or the tool is not visible
67
+ // because of the `visibility` option was evaluated to `false`.
43
68
  isVisible: function() {
44
69
  return !!this._visible;
45
70
  },
@@ -44,25 +44,37 @@ export const ToolsView = mvc.View.extend({
44
44
  update: function(opt) {
45
45
 
46
46
  opt || (opt = {});
47
- var tools = this.tools;
47
+ const tools = this.tools;
48
48
  if (!tools) return this;
49
- var isRendered = this.isRendered;
50
- for (var i = 0, n = tools.length; i < n; i++) {
51
- var tool = tools[i];
52
- if (!isRendered) {
49
+ const n = tools.length;
50
+ const wasRendered = this.isRendered;
51
+ for (let i = 0; i < n; i++) {
52
+ const tool = tools[i];
53
+ tool.updateVisibility();
54
+ if (!tool.isVisible()) continue;
55
+ if (!this.isRendered) {
56
+ // There is at least one visible tool
57
+ this.isRendered = Array(n).fill(false);
58
+ }
59
+ if (!this.isRendered[i]) {
53
60
  // First update executes render()
54
61
  tool.render();
55
- } else if (opt.tool !== tool.cid && tool.isVisible()) {
62
+ this.isRendered[i] = true;
63
+ } else if (opt.tool !== tool.cid) {
56
64
  tool.update();
57
65
  }
58
66
  }
67
+ if (!this.isRendered && n > 0) {
68
+ // None of the tools is visible
69
+ // Note: ToolsView with no tools are always mounted
70
+ return this;
71
+ }
59
72
  if (!this.isMounted()) {
60
73
  this.mount();
61
74
  }
62
- if (!isRendered) {
75
+ if (!wasRendered) {
63
76
  // Make sure tools are visible (if they were hidden and the tool removed)
64
77
  this.blurTool();
65
- this.isRendered = true;
66
78
  }
67
79
  return this;
68
80
  },
@@ -87,9 +99,12 @@ export const ToolsView = mvc.View.extend({
87
99
  if (!tools) return this;
88
100
  for (var i = 0, n = tools.length; i < n; i++) {
89
101
  var tool = tools[i];
90
- if (tool !== blurredTool && !tool.isVisible()) {
102
+ if (tool !== blurredTool && !tool.isExplicitlyVisible()) {
91
103
  tool.show();
92
- tool.update();
104
+ // Check if the tool is conditionally visible too
105
+ if (tool.isVisible()) {
106
+ tool.update();
107
+ }
93
108
  }
94
109
  }
95
110
  return this;
@@ -25,6 +25,7 @@ const connectionAttributesNS = {
25
25
 
26
26
  'connection': {
27
27
  qualify: isLinkView,
28
+ unset: 'd',
28
29
  set: function({ stubs = 0 }) {
29
30
  let d;
30
31
  if (isFinite(stubs) && stubs !== 0) {
@@ -49,21 +50,25 @@ const connectionAttributesNS = {
49
50
 
50
51
  'at-connection-length-keep-gradient': {
51
52
  qualify: isLinkView,
53
+ unset: 'transform',
52
54
  set: atConnectionWrapper('getTangentAtLength', { rotate: true })
53
55
  },
54
56
 
55
57
  'at-connection-length-ignore-gradient': {
56
58
  qualify: isLinkView,
59
+ unset: 'transform',
57
60
  set: atConnectionWrapper('getTangentAtLength', { rotate: false })
58
61
  },
59
62
 
60
63
  'at-connection-ratio-keep-gradient': {
61
64
  qualify: isLinkView,
65
+ unset: 'transform',
62
66
  set: atConnectionWrapper('getTangentAtRatio', { rotate: true })
63
67
  },
64
68
 
65
69
  'at-connection-ratio-ignore-gradient': {
66
70
  qualify: isLinkView,
71
+ unset: 'transform',
67
72
  set: atConnectionWrapper('getTangentAtRatio', { rotate: false })
68
73
  }
69
74
 
@@ -33,6 +33,7 @@ const defsAttributesNS = {
33
33
 
34
34
  'source-marker': {
35
35
  qualify: isPlainObject,
36
+ unset: 'marker-start',
36
37
  set: function(marker, refBBox, node, attrs) {
37
38
  marker = assign(contextMarker(attrs), marker);
38
39
  return { 'marker-start': 'url(#' + this.paper.defineMarker(marker) + ')' };
@@ -41,6 +42,7 @@ const defsAttributesNS = {
41
42
 
42
43
  'target-marker': {
43
44
  qualify: isPlainObject,
45
+ unset: 'marker-end',
44
46
  set: function(marker, refBBox, node, attrs) {
45
47
  marker = assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker);
46
48
  return { 'marker-end': 'url(#' + this.paper.defineMarker(marker) + ')' };
@@ -49,6 +51,7 @@ const defsAttributesNS = {
49
51
 
50
52
  'vertex-marker': {
51
53
  qualify: isPlainObject,
54
+ unset: 'marker-mid',
52
55
  set: function(marker, refBBox, node, attrs) {
53
56
  marker = assign(contextMarker(attrs), marker);
54
57
  return { 'marker-mid': 'url(#' + this.paper.defineMarker(marker) + ')' };
@@ -1,4 +1,4 @@
1
- import { isCalcAttribute, evalCalcAttribute } from './calc.mjs';
1
+ import { isCalcExpression, evalCalcExpression } from '../../util/calc.mjs';
2
2
 
3
3
  const calcAttributesList = [
4
4
  'transform',
@@ -53,8 +53,8 @@ export function evalAttributes(attrs, refBBox) {
53
53
  }
54
54
 
55
55
  export function evalAttribute(attrName, attrValue, refBBox) {
56
- if (attrName in calcAttributes && isCalcAttribute(attrValue)) {
57
- let evalAttrValue = evalCalcAttribute(attrValue, refBBox);
56
+ if (attrName in calcAttributes && isCalcExpression(attrValue)) {
57
+ let evalAttrValue = evalCalcExpression(attrValue, refBBox);
58
58
  if (attrName in positiveValueAttributes) {
59
59
  evalAttrValue = Math.max(0, evalAttrValue);
60
60
  }
@@ -49,6 +49,9 @@ const attributesNS = {
49
49
  },
50
50
 
51
51
  'html': {
52
+ unset: function(node) {
53
+ $(node).empty();
54
+ },
52
55
  set: function(html, refBBox, node) {
53
56
  $(node).html(html + '');
54
57
  }
@@ -69,18 +69,22 @@ function pointsWrapper(opt) {
69
69
  const shapeAttributesNS = {
70
70
 
71
71
  'ref-d-reset-offset': {
72
+ unset: 'd',
72
73
  set: dWrapper({ resetOffset: true })
73
74
  },
74
75
 
75
76
  'ref-d-keep-offset': {
77
+ unset: 'd',
76
78
  set: dWrapper({ resetOffset: false })
77
79
  },
78
80
 
79
81
  'ref-points-reset-offset': {
82
+ unset: 'points',
80
83
  set: pointsWrapper({ resetOffset: true })
81
84
  },
82
85
 
83
86
  'ref-points-keep-offset': {
87
+ unset: 'points',
84
88
  set: pointsWrapper({ resetOffset: false })
85
89
  },
86
90
  };
@@ -1,5 +1,5 @@
1
1
  import { assign, isPlainObject, isObject, isPercentage, breakText } from '../../util/util.mjs';
2
- import { isCalcAttribute, evalCalcAttribute } from './calc.mjs';
2
+ import { isCalcExpression, evalCalcExpression } from '../../util/calc.mjs';
3
3
  import $ from '../../mvc/Dom/index.mjs';
4
4
  import V from '../../V/index.mjs';
5
5
 
@@ -7,6 +7,8 @@ function isTextInUse(_value, _node, attrs) {
7
7
  return (attrs.text !== undefined);
8
8
  }
9
9
 
10
+ const FONT_ATTRIBUTES = ['font-weight', 'font-family', 'font-size', 'letter-spacing', 'text-transform'];
11
+
10
12
  const textAttributesNS = {
11
13
 
12
14
  'line-height': {
@@ -38,6 +40,9 @@ const textAttributesNS = {
38
40
  const textWrap = attrs['text-wrap'];
39
41
  return !textWrap || !isPlainObject(textWrap);
40
42
  },
43
+ unset: function(node) {
44
+ node.textContent = '';
45
+ },
41
46
  set: function(text, refBBox, node, attrs) {
42
47
  const cacheName = 'joint-text';
43
48
  const cache = $.data.get(node, cacheName);
@@ -89,8 +94,8 @@ const textAttributesNS = {
89
94
  var width = value.width || 0;
90
95
  if (isPercentage(width)) {
91
96
  size.width = refBBox.width * parseFloat(width) / 100;
92
- } else if (isCalcAttribute(width)) {
93
- size.width = Number(evalCalcAttribute(width, refBBox));
97
+ } else if (isCalcExpression(width)) {
98
+ size.width = Number(evalCalcExpression(width, refBBox));
94
99
  } else {
95
100
  if (value.width === null) {
96
101
  // breakText() requires width to be specified.
@@ -105,8 +110,8 @@ const textAttributesNS = {
105
110
  var height = value.height || 0;
106
111
  if (isPercentage(height)) {
107
112
  size.height = refBBox.height * parseFloat(height) / 100;
108
- } else if (isCalcAttribute(height)) {
109
- size.height = Number(evalCalcAttribute(height, refBBox));
113
+ } else if (isCalcExpression(height)) {
114
+ size.height = Number(evalCalcExpression(height, refBBox));
110
115
  } else {
111
116
  if (value.height === null) {
112
117
  // if height is not specified breakText() does not
@@ -122,18 +127,28 @@ const textAttributesNS = {
122
127
  var text = value.text;
123
128
  if (text === undefined) text = attrs.text;
124
129
  if (text !== undefined) {
130
+
125
131
  const breakTextFn = value.breakText || breakText;
126
132
  const computedStyles = getComputedStyle(node);
133
+ const wrapFontAttributes = {};
134
+ // The font size attributes must be set on the node
135
+ // to get the correct text wrapping.
136
+ // TODO: set the native SVG attributes before special attributes
137
+ for (let i = 0; i < FONT_ATTRIBUTES.length; i++) {
138
+ const name = FONT_ATTRIBUTES[i];
139
+ if (name in attrs) {
140
+ node.setAttribute(name, attrs[name]);
141
+ }
142
+ // Note: computedStyles is a live object
143
+ // i.e. the properties are evaluated when accessed.
144
+ wrapFontAttributes[name] = computedStyles[name];
145
+ }
127
146
 
128
- wrappedText = breakTextFn('' + text, size, {
129
- 'font-weight': computedStyles.fontWeight,
130
- 'font-family': computedStyles.fontFamily,
131
- 'text-transform': computedStyles.textTransform,
132
- 'font-size': computedStyles.fontSize,
133
- 'letter-spacing': computedStyles.letterSpacing,
134
- // The `line-height` attribute in SVG is JoinJS specific.
135
- 'lineHeight': attrs['line-height'],
136
- }, {
147
+ // The `line-height` attribute in SVG is JoinJS specific.
148
+ // TODO: change the `lineHeight` to breakText option.
149
+ wrapFontAttributes.lineHeight = attrs['line-height'];
150
+
151
+ wrappedText = breakTextFn('' + text, size, wrapFontAttributes, {
137
152
  // Provide an existing SVG Document here
138
153
  // instead of creating a temporary one over again.
139
154
  svgDocument: this.paper.svg,
@@ -147,7 +162,11 @@ const textAttributesNS = {
147
162
  wrappedText = '';
148
163
  }
149
164
  textAttributesNS.text.set.call(this, wrappedText, refBBox, node, attrs);
150
- }
165
+ },
166
+ // We expose the font attributes list to allow
167
+ // the user to take other custom font attributes into account
168
+ // when wrapping the text.
169
+ FONT_ATTRIBUTES
151
170
  },
152
171
 
153
172
  'title': {
@@ -155,6 +174,13 @@ const textAttributesNS = {
155
174
  // HTMLElement title is specified via an attribute (i.e. not an element)
156
175
  return node instanceof SVGElement;
157
176
  },
177
+ unset: function(node) {
178
+ $.data.remove(node, 'joint-title');
179
+ const titleNode = node.firstElementChild;
180
+ if (titleNode) {
181
+ titleNode.remove();
182
+ }
183
+ },
158
184
  set: function(title, refBBox, node) {
159
185
  var cacheName = 'joint-title';
160
186
  var cache = $.data.get(node, cacheName);
package/src/dia/ports.mjs CHANGED
@@ -280,6 +280,10 @@ export const elementPortPrototype = {
280
280
  }));
281
281
  },
282
282
 
283
+ getPortGroupNames: function() {
284
+ return Object.keys(this._portSettingsData.groups);
285
+ },
286
+
283
287
  /**
284
288
  * @param {string} groupName
285
289
  * @returns {Object<portId, {x: number, y: number, angle: number}>}
@@ -1,8 +1,8 @@
1
- import { HoverConnect as LinkHoverConnect } from '../linkTools/HoverConnect.mjs';
1
+ import { HoverConnect as LinkHoverConnect } from '../cellTools/HoverConnect.mjs';
2
2
  import V from '../V/index.mjs';
3
3
  import * as g from '../g/index.mjs';
4
- import { getViewBBox } from '../linkTools/helpers.mjs';
5
- import { isCalcAttribute, evalCalcAttribute } from '../dia/attributes/calc.mjs';
4
+ import { isCalcExpression, evalCalcExpression } from '../util/calc.mjs';
5
+ import { getViewBBox } from '../cellTools/helpers.mjs';
6
6
 
7
7
  export const HoverConnect = LinkHoverConnect.extend({
8
8
 
@@ -15,9 +15,9 @@ export const HoverConnect = LinkHoverConnect.extend({
15
15
  if (typeof trackPath === 'function') {
16
16
  trackPath = trackPath.call(this, view);
17
17
  }
18
- if (isCalcAttribute(trackPath)) {
18
+ if (isCalcExpression(trackPath)) {
19
19
  const bbox = getViewBBox(view, useModelGeometry);
20
- trackPath = evalCalcAttribute(trackPath, bbox);
20
+ trackPath = evalCalcExpression(trackPath, bbox);
21
21
  }
22
22
  return new g.Path(V.normalizePathData(trackPath));
23
23
  },
@@ -1,5 +1,6 @@
1
- export * from './Control.mjs';
2
- export { Button, Remove } from '../linkTools/Button.mjs';
3
- export { Connect } from '../linkTools/Connect.mjs';
4
- export { Boundary } from '../linkTools/Boundary.mjs';
5
1
  export { HoverConnect } from './HoverConnect.mjs';
2
+
3
+ export { Button, Remove } from '../cellTools/Button.mjs';
4
+ export { Connect } from '../cellTools/Connect.mjs';
5
+ export { Boundary } from '../cellTools/Boundary.mjs';
6
+ export { Control } from '../cellTools/Control.mjs';
package/src/env/index.mjs CHANGED
@@ -7,6 +7,11 @@ export const env = {
7
7
  svgforeignobject: function() {
8
8
  return !!document.createElementNS &&
9
9
  /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')));
10
+ },
11
+
12
+ // works for iOS browsers too
13
+ isSafari: function() {
14
+ return /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
10
15
  }
11
16
  },
12
17
 
package/src/g/rect.mjs CHANGED
@@ -138,12 +138,20 @@ Rect.prototype = {
138
138
  },
139
139
 
140
140
  // @return {bool} true if point p is inside me.
141
- containsPoint: function(p) {
142
-
143
- if (!(p instanceof Point)) {
144
- p = new Point(p);
141
+ // @param {bool} strict If true, the point has to be strictly inside (not on the border).
142
+ containsPoint: function(p, opt) {
143
+ let x, y;
144
+ if (!p || (typeof p === 'string')) {
145
+ // Backwards compatibility: if the point is not provided,
146
+ // the point is considered to be the origin [0, 0].
147
+ ({ x, y } = new Point(p));
148
+ } else {
149
+ // Do not create a new Point object if the point is already a Point-like object.
150
+ ({ x = 0, y = 0 } = p);
145
151
  }
146
- return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height;
152
+ return opt && opt.strict
153
+ ? (x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height)
154
+ : x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
147
155
  },
148
156
 
149
157
  // @return {bool} true if rectangle `r` is inside me.
@@ -1,4 +1,3 @@
1
- import { evalCalcAttribute, isCalcAttribute } from '../../dia/attributes/calc.mjs';
2
1
  import * as g from '../../g/index.mjs';
3
2
  import * as util from '../../util/index.mjs';
4
3
 
@@ -58,13 +57,13 @@ function argTransform(bbox, args) {
58
57
  let { x, y, angle } = args;
59
58
  if (util.isPercentage(x)) {
60
59
  x = parseFloat(x) / 100 * bbox.width;
61
- } else if (isCalcAttribute(x)) {
62
- x = Number(evalCalcAttribute(x, bbox));
60
+ } else if (util.isCalcExpression(x)) {
61
+ x = Number(util.evalCalcExpression(x, bbox));
63
62
  }
64
63
  if (util.isPercentage(y)) {
65
64
  y = parseFloat(y) / 100 * bbox.height;
66
- } else if (isCalcAttribute(y)) {
67
- y = Number(evalCalcAttribute(y, bbox));
65
+ } else if (util.isCalcExpression(y)) {
66
+ y = Number(util.evalCalcExpression(y, bbox));
68
67
  }
69
68
  return { x, y, angle };
70
69
  }