@joint/core 4.1.3 → 4.2.0-alpha.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 (58) hide show
  1. package/README.md +4 -2
  2. package/dist/geometry.js +129 -124
  3. package/dist/geometry.min.js +4 -3
  4. package/dist/joint.d.ts +352 -160
  5. package/dist/joint.js +3654 -2191
  6. package/dist/joint.min.js +4 -3
  7. package/dist/joint.nowrap.js +3653 -2188
  8. package/dist/joint.nowrap.min.js +4 -3
  9. package/dist/vectorizer.js +489 -279
  10. package/dist/vectorizer.min.js +4 -3
  11. package/dist/version.mjs +1 -1
  12. package/package.json +33 -27
  13. package/src/V/create.mjs +51 -0
  14. package/src/V/index.mjs +89 -159
  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/alg/Deque.mjs +126 -0
  19. package/src/anchors/index.mjs +140 -33
  20. package/src/cellTools/Boundary.mjs +15 -13
  21. package/src/cellTools/Button.mjs +7 -5
  22. package/src/cellTools/Control.mjs +38 -15
  23. package/src/cellTools/HoverConnect.mjs +5 -1
  24. package/src/cellTools/helpers.mjs +44 -3
  25. package/src/config/index.mjs +8 -0
  26. package/src/connectionPoints/index.mjs +24 -9
  27. package/src/connectionStrategies/index.mjs +1 -1
  28. package/src/connectors/jumpover.mjs +1 -1
  29. package/src/dia/Cell.mjs +32 -12
  30. package/src/dia/CellView.mjs +53 -38
  31. package/src/dia/Element.mjs +81 -35
  32. package/src/dia/ElementView.mjs +2 -1
  33. package/src/dia/HighlighterView.mjs +54 -11
  34. package/src/dia/LinkView.mjs +118 -98
  35. package/src/dia/Paper.mjs +831 -231
  36. package/src/dia/PaperLayer.mjs +9 -2
  37. package/src/dia/ToolView.mjs +4 -0
  38. package/src/dia/ToolsView.mjs +12 -3
  39. package/src/dia/attributes/text.mjs +16 -5
  40. package/src/dia/layers/GridLayer.mjs +5 -0
  41. package/src/dia/ports.mjs +344 -111
  42. package/src/elementTools/HoverConnect.mjs +14 -8
  43. package/src/env/index.mjs +7 -4
  44. package/src/g/rect.mjs +7 -0
  45. package/src/highlighters/stroke.mjs +1 -1
  46. package/src/layout/ports/port.mjs +30 -15
  47. package/src/layout/ports/portLabel.mjs +1 -1
  48. package/src/linkAnchors/index.mjs +2 -2
  49. package/src/linkTools/Anchor.mjs +2 -2
  50. package/src/linkTools/Vertices.mjs +4 -6
  51. package/src/mvc/View.mjs +4 -0
  52. package/src/mvc/ViewBase.mjs +1 -1
  53. package/src/util/util.mjs +1 -1
  54. package/src/util/utilHelpers.mjs +2 -0
  55. package/types/geometry.d.ts +65 -59
  56. package/types/joint.d.ts +278 -102
  57. package/types/vectorizer.d.ts +11 -1
  58. package/src/V/annotation.mjs +0 -0
package/src/dia/ports.mjs CHANGED
@@ -4,19 +4,38 @@ import { Rect, Point } from '../g/index.mjs';
4
4
  import * as Port from '../layout/ports/port.mjs';
5
5
  import * as PortLabel from '../layout/ports/portLabel.mjs';
6
6
 
7
- var PortData = function(data) {
7
+ const DEFAULT_PORT_POSITION_NAME = 'left';
8
+ const DEFAULT_ABSOLUTE_PORT_POSITION_NAME = 'absolute';
9
+ const DEFAULT_PORT_LABEL_POSITION_NAME = 'left';
8
10
 
9
- var clonedData = util.cloneDeep(data) || {};
11
+ const PortData = function(model) {
12
+
13
+ const { portLayoutNamespace = Port, portLabelLayoutNamespace = PortLabel } = model;
14
+
15
+ const clonedData = util.cloneDeep(model.get('ports')) || {};
10
16
  this.ports = [];
17
+ this.portsMap = {};
11
18
  this.groups = {};
12
- this.portLayoutNamespace = Port;
13
- this.portLabelLayoutNamespace = PortLabel;
19
+ this.portLayoutNamespace = portLayoutNamespace;
20
+ this.portLabelLayoutNamespace = portLabelLayoutNamespace;
21
+ this.metrics = {};
22
+ this.metricsKey = null;
14
23
 
15
24
  this._init(clonedData);
16
25
  };
17
26
 
18
27
  PortData.prototype = {
19
28
 
29
+ hasPort: function(id) {
30
+ return id in this.portsMap;
31
+ },
32
+
33
+ getPort: function(id) {
34
+ const port = this.portsMap[id];
35
+ if (port) return port;
36
+ throw new Error('Element: unable to find port with id ' + id);
37
+ },
38
+
20
39
  getPorts: function() {
21
40
  return this.ports;
22
41
  },
@@ -32,52 +51,92 @@ PortData.prototype = {
32
51
  });
33
52
  },
34
53
 
35
- getGroupPortsMetrics: function(groupName, elBBox) {
54
+ // Calculate SVG transformations based on evaluated group + port data
55
+ // NOTE: This function is also called for ports without a group (groupName = undefined)
56
+ getGroupPortsMetrics: function(groupName, rect) {
36
57
 
37
- var group = this.getGroup(groupName);
38
- var ports = this.getPortsByGroup(groupName);
58
+ const { x = 0, y = 0, width = 0, height = 0 } = rect;
59
+ const metricsKey = `${x}:${y}:${width}:${height}`;
60
+ if (this.metricsKey !== metricsKey) {
61
+ // Clear the cache (the element size has changed)
62
+ this.metrics = {};
63
+ this.metricsKey = metricsKey;
64
+ }
39
65
 
40
- var groupPosition = group.position || {};
41
- var groupPositionName = groupPosition.name;
42
- var namespace = this.portLayoutNamespace;
43
- if (!namespace[groupPositionName]) {
44
- groupPositionName = 'left';
66
+ let groupPortsMetrics = this.metrics[groupName];
67
+ if (groupPortsMetrics) {
68
+ // Return cached metrics
69
+ return groupPortsMetrics;
45
70
  }
46
71
 
47
- var groupArgs = groupPosition.args || {};
48
- var portsArgs = ports.map(function(port) {
72
+ // Calculate the metrics
73
+ groupPortsMetrics = this.resolveGroupPortsMetrics(groupName, new Rect(x, y, width, height));
74
+ this.metrics[groupName] = groupPortsMetrics;
75
+ return groupPortsMetrics;
76
+ },
77
+
78
+ resolveGroupPortsMetrics: function(groupName, elBBox) {
79
+
80
+ // `groupName` of `undefined` (= not a string) means "the group of ports which do not have the `group` property".
81
+ const isNoGroup = (groupName === undefined);
82
+
83
+ const group = this.getGroup(groupName);
84
+ const ports = this.getPortsByGroup(groupName);
85
+
86
+ const portsArgs = ports.map(function(port) {
49
87
  return port && port.position && port.position.args;
50
88
  });
51
- var groupPortTransformations = namespace[groupPositionName](portsArgs, elBBox, groupArgs);
52
89
 
53
- var accumulator = {
90
+ // Get an array of transformations of individual ports according to the group's port layout function:
91
+ let groupPortTransformations;
92
+ if (isNoGroup) {
93
+ // Apply default port layout function to the set of ports without `group` property.
94
+ const noGroup = this._evaluateGroup({});
95
+ groupPortTransformations = this._getGroupPortTransformations(noGroup, portsArgs, elBBox);
96
+
97
+ } else {
98
+ groupPortTransformations = this._getGroupPortTransformations(group, portsArgs, elBBox);
99
+ }
100
+
101
+ let accumulator = {
54
102
  ports: ports,
55
- result: []
103
+ result: {}
56
104
  };
57
105
 
58
- util.toArray(groupPortTransformations).reduce(function(res, portTransformation, index) {
59
- var port = res.ports[index];
60
- res.result.push({
61
- portId: port.id,
106
+ // For each individual port transformation, find the information necessary to calculate SVG transformations:
107
+ util.toArray(groupPortTransformations).reduce((res, portTransformation, index) => {
108
+ const port = res.ports[index];
109
+ const portId = port.id;
110
+ res.result[portId] = {
111
+ index,
112
+ portId,
62
113
  portTransformation: portTransformation,
63
- labelTransformation: this._getPortLabelLayout(port, Point(portTransformation), elBBox),
114
+ labelTransformation: this._getPortLabelTransformation(port, Point(portTransformation), elBBox),
64
115
  portAttrs: port.attrs,
65
116
  portSize: port.size,
66
117
  labelSize: port.label.size
67
- });
118
+ };
68
119
  return res;
69
- }.bind(this), accumulator);
120
+ }, accumulator);
70
121
 
71
122
  return accumulator.result;
72
123
  },
73
124
 
74
- _getPortLabelLayout: function(port, portPosition, elBBox) {
125
+ _getGroupPortTransformations: function(group, portsArgs, elBBox) {
75
126
 
76
- var namespace = this.portLabelLayoutNamespace;
77
- var labelPosition = port.label.position.name || 'left';
127
+ const groupPosition = group.position || {};
128
+ const groupPositionArgs = groupPosition.args || {};
129
+ const groupPositionLayoutCallback = groupPosition.layoutCallback;
130
+ return groupPositionLayoutCallback(portsArgs, elBBox, groupPositionArgs);
131
+ },
78
132
 
79
- if (namespace[labelPosition]) {
80
- return namespace[labelPosition](portPosition, elBBox, port.label.position.args);
133
+ _getPortLabelTransformation: function(port, portPosition, elBBox) {
134
+
135
+ const portLabelPosition = port.label.position || {};
136
+ const portLabelPositionArgs = portLabelPosition.args || {};
137
+ const portLabelPositionLayoutCallback = portLabelPosition.layoutCallback;
138
+ if (portLabelPositionLayoutCallback) {
139
+ return portLabelPositionLayoutCallback(portPosition, elBBox, portLabelPositionArgs);
81
140
  }
82
141
 
83
142
  return null;
@@ -85,7 +144,8 @@ PortData.prototype = {
85
144
 
86
145
  _init: function(data) {
87
146
 
88
- // prepare groups
147
+ // Prepare groups:
148
+ // NOTE: This overwrites passed group properties with evaluated group properties.
89
149
  if (util.isObject(data.groups)) {
90
150
  var groups = Object.keys(data.groups);
91
151
  for (var i = 0, n = groups.length; i < n; i++) {
@@ -94,93 +154,197 @@ PortData.prototype = {
94
154
  }
95
155
  }
96
156
 
97
- // prepare ports
157
+ // Prepare ports:
158
+ // NOTE: This overwrites passed port properties with evaluated port properties, plus mixed-in evaluated group properties (see above).
98
159
  var ports = util.toArray(data.items);
99
160
  for (var j = 0, m = ports.length; j < m; j++) {
100
- this.ports.push(this._evaluatePort(ports[j]));
161
+ const resolvedPort = this._evaluatePort(ports[j]);
162
+ this.ports.push(resolvedPort);
163
+ this.portsMap[resolvedPort.id] = resolvedPort;
101
164
  }
102
165
  },
103
166
 
104
167
  _evaluateGroup: function(group) {
105
168
 
106
- return util.merge(group, {
107
- position: this._getPosition(group.position, true),
108
- label: this._getLabel(group, true)
109
- });
169
+ return util.merge(
170
+ {},
171
+ group,
172
+ {
173
+ position: this._evaluateGroupPositionProperty(group),
174
+ label: this._evaluateGroupLabelProperty(group)
175
+ }
176
+ );
110
177
  },
111
178
 
112
- _evaluatePort: function(port) {
179
+ _evaluateGroupPositionProperty: function(group) {
113
180
 
114
- var evaluated = util.assign({}, port);
181
+ const namespace = this.portLayoutNamespace;
182
+ const groupPosition = group.position;
183
+ if (groupPosition === undefined) {
184
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, DEFAULT_PORT_POSITION_NAME, 'Default port group');
185
+ return { layoutCallback };
115
186
 
116
- var group = this.getGroup(port.group);
187
+ } else if (util.isFunction(groupPosition)) {
188
+ return { layoutCallback: groupPosition };
117
189
 
118
- evaluated.markup = evaluated.markup || group.markup;
119
- evaluated.attrs = util.merge({}, group.attrs, evaluated.attrs);
120
- evaluated.position = this._createPositionNode(group, evaluated);
121
- evaluated.label = util.merge({}, group.label, this._getLabel(evaluated));
122
- evaluated.z = this._getZIndex(group, evaluated);
123
- evaluated.size = util.assign({}, group.size, evaluated.size);
190
+ } else if (util.isObject(groupPosition)) {
191
+ if (groupPosition.name) {
192
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, groupPosition.name, 'Provided port group');
193
+ return { layoutCallback, args: groupPosition.args };
194
+ } else {
195
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, DEFAULT_PORT_POSITION_NAME, 'Default port group');
196
+ return { layoutCallback, args: groupPosition.args };
197
+ }
124
198
 
125
- return evaluated;
199
+ } else if (util.isString(groupPosition)) {
200
+ // TODO: Remove legacy signature (see `this._evaluateGroupLabelPositionProperty()`).
201
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, groupPosition, 'Provided port group');
202
+ return { layoutCallback };
203
+
204
+ } else if (Array.isArray(groupPosition)) {
205
+ // TODO: Remove legacy signature (see `this._evaluateGroupLabelPositionProperty()`).
206
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, DEFAULT_ABSOLUTE_PORT_POSITION_NAME, 'Default absolute port group');
207
+ return { layoutCallback, args: { x: groupPosition[0], y: groupPosition[1] }};
208
+
209
+ } else {
210
+ throw new Error('dia.Element: Provided port group position value has an invalid type.');
211
+ }
126
212
  },
127
213
 
128
- _getZIndex: function(group, port) {
214
+ _evaluateGroupLabelProperty: function(group) {
129
215
 
130
- if (util.isNumber(port.z)) {
131
- return port.z;
216
+ const groupLabel = group.label;
217
+ if (!groupLabel) {
218
+ return {
219
+ position: this._evaluateGroupLabelPositionProperty({})
220
+ };
132
221
  }
133
- if (util.isNumber(group.z) || group.z === 'auto') {
134
- return group.z;
222
+
223
+ return util.merge(
224
+ {},
225
+ groupLabel,
226
+ {
227
+ position: this._evaluateGroupLabelPositionProperty(groupLabel)
228
+ }
229
+ );
230
+ },
231
+
232
+ _evaluateGroupLabelPositionProperty: function(groupLabel) {
233
+
234
+ const namespace = this.portLabelLayoutNamespace;
235
+ const groupLabelPosition = groupLabel.position;
236
+ if (groupLabelPosition === undefined) {
237
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, DEFAULT_PORT_LABEL_POSITION_NAME, 'Default port group label');
238
+ return { layoutCallback };
239
+
240
+ } else if (util.isFunction(groupLabelPosition)) {
241
+ return { layoutCallback: groupLabelPosition };
242
+
243
+ } else if (util.isObject(groupLabelPosition)) {
244
+ if (groupLabelPosition.name) {
245
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, groupLabelPosition.name, 'Provided port group label');
246
+ return { layoutCallback, args: groupLabelPosition.args };
247
+ } else {
248
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, DEFAULT_PORT_LABEL_POSITION_NAME, 'Default port group label');
249
+ return { layoutCallback, args: groupLabelPosition.args };
250
+ }
251
+
252
+ } else {
253
+ throw new Error('dia.Element: Provided port group label position value has an invalid type.');
135
254
  }
136
- return 'auto';
137
255
  },
138
256
 
139
- _createPositionNode: function(group, port) {
257
+ _evaluatePort: function(port) {
140
258
 
141
- return util.merge({
142
- name: 'left',
143
- args: {}
144
- }, group.position, { args: port.args });
259
+ const group = this.getGroup(port.group);
260
+
261
+ const evaluated = util.assign({}, port);
262
+ evaluated.markup = evaluated.markup || group.markup;
263
+ evaluated.attrs = util.merge({}, group.attrs, evaluated.attrs);
264
+ evaluated.position = this._evaluatePortPositionProperty(group, evaluated);
265
+ evaluated.label = this._evaluatePortLabelProperty(group, evaluated);
266
+ evaluated.z = this._evaluatePortZProperty(group, evaluated);
267
+ evaluated.size = util.assign({}, group.size, evaluated.size);
268
+ return evaluated;
145
269
  },
146
270
 
147
- _getPosition: function(position, setDefault) {
271
+ _evaluatePortPositionProperty: function(group, port) {
148
272
 
149
- var args = {};
150
- var positionName;
273
+ return {
274
+ args: util.merge(
275
+ {},
276
+ // NOTE: `x != null` is equivalent to `x !== null && x !== undefined`.
277
+ (group.position != null) ? group.position.args : {},
278
+ // Port can overwrite `group.position.args` via `port.position.args` or `port.args`.
279
+ // TODO: Remove `port.args` backwards compatibility.
280
+ (((port.position != null) && (port.position.args != null)) ? port.position.args : port.args))
281
+ };
282
+ },
151
283
 
152
- if (util.isFunction(position)) {
153
- positionName = 'fn';
154
- args.fn = position;
155
- } else if (util.isString(position)) {
156
- positionName = position;
157
- } else if (position === undefined) {
158
- positionName = setDefault ? 'left' : null;
159
- } else if (Array.isArray(position)) {
160
- positionName = 'absolute';
161
- args.x = position[0];
162
- args.y = position[1];
163
- } else if (util.isObject(position)) {
164
- positionName = position.name;
165
- util.assign(args, position.args);
284
+ _evaluatePortLabelProperty: function(group, port) {
285
+
286
+ const groupLabel = group.label;
287
+ const portLabel = port.label;
288
+ if (!portLabel) {
289
+ return util.assign(
290
+ {},
291
+ groupLabel
292
+ );
166
293
  }
167
294
 
168
- var result = { args: args };
295
+ return util.merge(
296
+ {},
297
+ groupLabel,
298
+ util.merge(
299
+ {},
300
+ portLabel,
301
+ {
302
+ position: this._evaluatePortLabelPositionProperty(portLabel)
303
+ }
304
+ )
305
+ );
306
+ },
307
+
308
+ _evaluatePortLabelPositionProperty: function(portLabel) {
309
+
310
+ const namespace = this.portLabelLayoutNamespace;
311
+ const portLabelPosition = portLabel.position;
312
+ if (portLabelPosition === undefined) {
313
+ return {};
314
+
315
+ } else if (util.isFunction(portLabelPosition)) {
316
+ return { layoutCallback: portLabelPosition };
169
317
 
170
- if (positionName) {
171
- result.name = positionName;
318
+ } else if (util.isObject(portLabelPosition)) {
319
+ if (portLabelPosition.name) {
320
+ const layoutCallback = this._resolveLayoutCallbackOrThrow(namespace, portLabelPosition.name, 'Provided port label');
321
+ return { layoutCallback, args: portLabelPosition.args };
322
+ } else {
323
+ return { args: portLabelPosition.args };
324
+ }
325
+
326
+ } else {
327
+ throw new Error('dia.Element: Provided port label position value has an invalid type.');
172
328
  }
173
- return result;
174
329
  },
175
330
 
176
- _getLabel: function(item, setDefaults) {
331
+ _evaluatePortZProperty: function(group, port) {
177
332
 
178
- var label = item.label || {};
179
-
180
- var ret = label;
181
- ret.position = this._getPosition(label.position, setDefaults);
333
+ if (util.isNumber(port.z)) {
334
+ return port.z;
335
+ }
336
+ if (util.isNumber(group.z) || group.z === 'auto') {
337
+ return group.z;
338
+ }
339
+ return 'auto';
340
+ },
182
341
 
183
- return ret;
342
+ _resolveLayoutCallbackOrThrow: function(namespace, name, errorSubstring) {
343
+ const layoutCallback = namespace[name];
344
+ if (!layoutCallback) {
345
+ throw new Error(`dia.Element: ${errorSubstring} layout name is not recognized.`);
346
+ }
347
+ return layoutCallback;
184
348
  }
185
349
  };
186
350
 
@@ -240,8 +404,7 @@ export const elementPortPrototype = {
240
404
  */
241
405
  hasPorts: function() {
242
406
 
243
- var ports = this.prop('ports/items');
244
- return Array.isArray(ports) && ports.length > 0;
407
+ return this._portSettingsData.getPorts().length > 0;
245
408
  },
246
409
 
247
410
  /**
@@ -250,7 +413,7 @@ export const elementPortPrototype = {
250
413
  */
251
414
  hasPort: function(id) {
252
415
 
253
- return this.getPortIndex(id) !== -1;
416
+ return this._portSettingsData.hasPort(id);
254
417
  },
255
418
 
256
419
  /**
@@ -274,10 +437,8 @@ export const elementPortPrototype = {
274
437
  * @returns {object}
275
438
  */
276
439
  getPort: function(id) {
277
-
278
- return util.cloneDeep(util.toArray(this.prop('ports/items')).find(function(port) {
279
- return port.id && port.id === id;
280
- }));
440
+ const port = util.toArray(this.prop('ports/items')).find(port => port.id && port.id === id);
441
+ return util.cloneDeep(port);
281
442
  },
282
443
 
283
444
  getPortGroupNames: function() {
@@ -290,17 +451,90 @@ export const elementPortPrototype = {
290
451
  */
291
452
  getPortsPositions: function(groupName) {
292
453
 
293
- var portsMetrics = this._portSettingsData.getGroupPortsMetrics(groupName, Rect(this.size()));
294
-
295
- return portsMetrics.reduce(function(positions, metrics) {
296
- var transformation = metrics.portTransformation;
297
- positions[metrics.portId] = {
298
- x: transformation.x,
299
- y: transformation.y,
300
- angle: transformation.angle
454
+ const portsMetrics = this.getGroupPortsMetrics(groupName);
455
+ const portsPosition = {};
456
+ for (const portId in portsMetrics) {
457
+ const {
458
+ portTransformation: { x, y, angle },
459
+ } = portsMetrics[portId];
460
+ portsPosition[portId] = {
461
+ x: x,
462
+ y: y,
463
+ angle
301
464
  };
302
- return positions;
303
- }, {});
465
+ }
466
+ return portsPosition;
467
+ },
468
+
469
+ getPortMetrics: function(portId) {
470
+ const port = this._portSettingsData.getPort(portId);
471
+ return this.getGroupPortsMetrics(port.group)[portId];
472
+ },
473
+
474
+ getGroupPortsMetrics: function(groupName) {
475
+ return this._portSettingsData.getGroupPortsMetrics(groupName, this.size());
476
+ },
477
+
478
+ getPortRelativePosition: function(portId) {
479
+ const { portTransformation: { x, y, angle }} = this.getPortMetrics(portId);
480
+ return { x, y, angle };
481
+ },
482
+
483
+ getPortRelativeRect(portId) {
484
+ const {
485
+ portTransformation: { x, y, angle },
486
+ portSize: { width, height }
487
+ } = this.getPortMetrics(portId);
488
+ const portRect = {
489
+ x: x - width / 2,
490
+ y: y - height / 2,
491
+ width,
492
+ height,
493
+ angle
494
+ };
495
+ return portRect;
496
+ },
497
+
498
+ /**
499
+ * @param {string} portId
500
+ * @returns {Point}
501
+ * @description Returns the port center in the graph coordinate system.
502
+ * The port center is in the graph coordinate system, and the position
503
+ * already takes into account the element rotation.
504
+ **/
505
+ getPortCenter(portId) {
506
+ const elementBBox = this.getBBox();
507
+ const portPosition = this.getPortRelativePosition(portId);
508
+ const portCenter = new Point(portPosition).offset(elementBBox.x, elementBBox.y);
509
+ const angle = this.angle();
510
+ if (angle) portCenter.rotate(elementBBox.center(), -angle);
511
+ return portCenter;
512
+ },
513
+
514
+ /**
515
+ * @param {string} portId
516
+ * @param {object} [opt]
517
+ * @param {boolean} [opt.rotate] - If true, the port bounding box is rotated
518
+ * around the port center.
519
+ * @returns {Rect}
520
+ * @description Returns the bounding box of the port in the graph coordinate system.
521
+ * The port center is rotated around the element center, but the port bounding box
522
+ * is not rotated (unless `opt.rotate` is set to true).
523
+ */
524
+ getPortBBox: function(portId, opt) {
525
+ const portRect = this.getPortRelativeRect(portId);
526
+ const elementBBox = this.getBBox();
527
+ // Note: the `angle` property of the `port` is ignore here for now
528
+ const portBBox = new Rect(portRect);
529
+ portBBox.offset(elementBBox.x, elementBBox.y);
530
+ const angle = this.angle();
531
+ if (angle) {
532
+ portBBox.moveAroundPoint(elementBBox.center(), -angle);
533
+ }
534
+ if (opt && opt.rotate) {
535
+ portBBox.rotateAroundCenter(angle);
536
+ }
537
+ return portBBox;
304
538
  },
305
539
 
306
540
  /**
@@ -499,7 +733,7 @@ export const elementPortPrototype = {
499
733
  prevPortData = this._portSettingsData.getPorts();
500
734
  }
501
735
 
502
- this._portSettingsData = new PortData(this.get('ports'));
736
+ this._portSettingsData = new PortData(this);
503
737
 
504
738
  var curPortData = this._portSettingsData.getPorts();
505
739
 
@@ -822,15 +1056,15 @@ export const elementViewPortPrototype = {
822
1056
  */
823
1057
  _updatePortGroup: function(groupName) {
824
1058
 
825
- var elementBBox = Rect(this.model.size());
826
- var portsMetrics = this.model._portSettingsData.getGroupPortsMetrics(groupName, elementBBox);
1059
+ const portsMetrics = this.model.getGroupPortsMetrics(groupName);
1060
+ const portsIds = Object.keys(portsMetrics);
827
1061
 
828
- for (var i = 0, n = portsMetrics.length; i < n; i++) {
829
- var metrics = portsMetrics[i];
830
- var portId = metrics.portId;
831
- var cached = this._portElementsCache[portId] || {};
832
- var portTransformation = metrics.portTransformation;
833
- var labelTransformation = metrics.labelTransformation;
1062
+ for (let i = 0, n = portsIds.length; i < n; i++) {
1063
+ const portId = portsIds[i];
1064
+ const metrics = portsMetrics[portId];
1065
+ const cached = this._portElementsCache[portId] || {};
1066
+ const portTransformation = metrics.portTransformation;
1067
+ const labelTransformation = metrics.labelTransformation;
834
1068
  if (labelTransformation && cached.portLabelElement) {
835
1069
  this.updateDOMSubtreeAttributes(cached.portLabelElement.node, labelTransformation.attrs, {
836
1070
  rootBBox: new Rect(metrics.labelSize),
@@ -882,4 +1116,3 @@ export const elementViewPortPrototype = {
882
1116
  return label.markup || this.model.get('portLabelMarkup') || this.model.portLabelMarkup || this.portLabelMarkup;
883
1117
  }
884
1118
  };
885
-
@@ -2,30 +2,36 @@ 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
4
  import { isCalcExpression, evalCalcExpression } from '../util/calc.mjs';
5
- import { getViewBBox } from '../cellTools/helpers.mjs';
5
+ import { getToolOptions, getViewBBox } from '../cellTools/helpers.mjs';
6
6
 
7
7
  export const HoverConnect = LinkHoverConnect.extend({
8
8
 
9
9
  getTrackPath() {
10
- const { relatedView: view, options } = this;
10
+ const { relatedView: view } = this;
11
11
  let {
12
12
  useModelGeometry,
13
- trackPath = 'M 0 0 H calc(w) V calc(h) H 0 Z'
14
- } = options;
13
+ relative,
14
+ trackPath = 'M 0 0 H calc(w) V calc(h) H 0 Z'
15
+ } = getToolOptions(this);
15
16
  if (typeof trackPath === 'function') {
16
17
  trackPath = trackPath.call(this, view);
17
18
  }
18
19
  if (isCalcExpression(trackPath)) {
19
- const bbox = getViewBBox(view, useModelGeometry);
20
+ const bbox = getViewBBox(view, { useModelGeometry, relative });
20
21
  trackPath = evalCalcExpression(trackPath, bbox);
21
22
  }
22
23
  return new g.Path(V.normalizePathData(trackPath));
23
24
  },
24
25
 
25
26
  getTrackMatrix() {
26
- const { relatedView: view, options } = this;
27
- let { useModelGeometry, rotate } = options;
28
- let bbox = getViewBBox(view, useModelGeometry);
27
+ if (this.isOverlay()) return this.getTrackMatrixAbsolute();
28
+ return V.createSVGMatrix();
29
+ },
30
+
31
+ getTrackMatrixAbsolute() {
32
+ const { relatedView: view } = this;
33
+ let { useModelGeometry, rotate } = getToolOptions(this);
34
+ let bbox = getViewBBox(view, { useModelGeometry });
29
35
  const angle = view.model.angle();
30
36
  if (!rotate) bbox = bbox.bbox(angle);
31
37
  let matrix = V.createSVGMatrix().translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
package/src/env/index.mjs CHANGED
@@ -9,9 +9,12 @@ export const env = {
9
9
  /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')));
10
10
  },
11
11
 
12
- // works for iOS browsers too
13
- isSafari: function() {
14
- return /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
12
+ // works for: (1) macOS Safari, (2) any WKWebView, (3) any iOS browser (including Safari, CriOS, EdgiOS, OPR, FxiOS)
13
+ isAppleWebKit: function() {
14
+ const userAgent = navigator.userAgent;
15
+ const isAppleWebKit = /applewebkit/i.test(userAgent);
16
+ const isChromium = /chrome/i.test(userAgent); // e.g. Chrome, Edge, Opera, SamsungBrowser
17
+ return isAppleWebKit && !isChromium;
15
18
  }
16
19
  },
17
20
 
@@ -36,7 +39,7 @@ export const env = {
36
39
 
37
40
  try {
38
41
  result = fn();
39
- } catch (error) {
42
+ } catch {
40
43
  result = false;
41
44
  }
42
45