bpmn-js 0.20.4 → 0.21.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 (32) hide show
  1. package/.travis.yml +2 -0
  2. package/lib/draw/BpmnRenderer.js +4 -3
  3. package/lib/features/copy-paste/BpmnCopyPaste.js +22 -19
  4. package/lib/features/label-editing/LabelEditingPreview.js +122 -0
  5. package/lib/features/label-editing/LabelEditingProvider.js +154 -55
  6. package/lib/features/label-editing/cmd/UpdateLabelHandler.js +10 -6
  7. package/lib/features/label-editing/index.js +8 -3
  8. package/lib/features/modeling/BpmnUpdater.js +17 -13
  9. package/lib/features/modeling/ElementFactory.js +36 -19
  10. package/lib/features/modeling/Modeling.js +5 -1
  11. package/lib/features/modeling/behavior/DropOnFlowBehavior.js +50 -16
  12. package/lib/features/modeling/behavior/ReplaceElementBehaviour.js +4 -2
  13. package/lib/features/modeling/behavior/util/LabelLayoutUtil.js +3 -2
  14. package/lib/features/modeling/behavior/util/LineAttachmentUtil.js +36 -30
  15. package/lib/features/rules/BpmnRules.js +29 -11
  16. package/lib/features/snapping/BpmnSnapping.js +22 -6
  17. package/package.json +22 -22
  18. package/test/TestHelper.js +1 -1
  19. package/test/fixtures/bpmn/features/label-editing/labels.bpmn +132 -202
  20. package/test/integration/CustomElementsSpec.js +2 -5
  21. package/test/spec/features/copy-paste/BpmnCopyPasteSpec.js +38 -0
  22. package/test/spec/features/label-editing/LabelEditingPreviewSpec.js +133 -0
  23. package/test/spec/features/label-editing/LabelEditingProviderSpec.js +207 -172
  24. package/test/spec/features/modeling/LabelBoundsSpec.js +3 -1
  25. package/test/spec/features/modeling/LabelLayoutingSpec.js +211 -232
  26. package/test/spec/features/modeling/behavior/DropOnFlowBehavior.bpmn +5 -1
  27. package/test/spec/features/modeling/behavior/DropOnFlowBehaviorSpec.js +174 -1
  28. package/test/spec/features/modeling/behavior/util/LabelLayoutUtilSpec.js +75 -0
  29. package/test/spec/features/modeling/behavior/util/LineAttachmentUtilSpec.js +55 -7
  30. package/test/spec/features/rules/BpmnRulesSpec.js +108 -64
  31. package/test/spec/features/snapping/BpmnSnapping.sequenceFlow.bpmn +41 -0
  32. package/test/spec/features/snapping/BpmnSnappingSpec.js +66 -0
@@ -81,7 +81,7 @@ ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
81
81
  delete attrs.colors;
82
82
  }
83
83
 
84
- this.setAttrs(businessObject, attrs, [
84
+ applyAttributes(businessObject, attrs, [
85
85
  'processRef',
86
86
  'isInterrupting',
87
87
  'associationDirection',
@@ -89,7 +89,7 @@ ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
89
89
  ]);
90
90
 
91
91
  if (attrs.isExpanded) {
92
- this.setAttr(businessObject.di, attrs, 'isExpanded');
92
+ applyAttribute(businessObject.di, attrs, 'isExpanded');
93
93
  }
94
94
 
95
95
  if (is(businessObject, 'bpmn:ExclusiveGateway')) {
@@ -122,23 +122,6 @@ ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
122
122
  };
123
123
 
124
124
 
125
- ElementFactory.prototype.setAttrs = function(element, attrs, properties) {
126
-
127
- forEach(properties, function(property) {
128
- if (attrs[property] !== undefined) {
129
- this.setAttr(element, attrs, property);
130
- }
131
- }, this);
132
- };
133
-
134
-
135
- ElementFactory.prototype.setAttr = function(element, attrs, property) {
136
- element[property] = attrs[property];
137
-
138
- delete attrs[property];
139
- };
140
-
141
-
142
125
  ElementFactory.prototype._getDefaultSize = function(semantic) {
143
126
 
144
127
  if (is(semantic, 'bpmn:SubProcess')) {
@@ -200,3 +183,37 @@ ElementFactory.prototype.createParticipantShape = function(collapsed) {
200
183
 
201
184
  return this.createShape(attrs);
202
185
  };
186
+
187
+
188
+ //////////// helpers ////////////////////////////////////
189
+
190
+ /**
191
+ * Apply attributes from a map to the given element,
192
+ * remove attribute from the map on application.
193
+ *
194
+ * @param {Base} element
195
+ * @param {Object} attrs (in/out map of attributes)
196
+ * @param {Array<String>} attributeNames name of attributes to apply
197
+ */
198
+ function applyAttributes(element, attrs, attributeNames) {
199
+
200
+ forEach(attributeNames, function(property) {
201
+ if (attrs[property] !== undefined) {
202
+ applyAttribute(element, attrs, property);
203
+ }
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Apply named property to element and drain it from the attrs
209
+ * collection.
210
+ *
211
+ * @param {Base} element
212
+ * @param {Object} attrs (in/out map of attributes)
213
+ * @param {String} attributeName to apply
214
+ */
215
+ function applyAttribute(element, attrs, attributeName) {
216
+ element[attributeName] = attrs[attributeName];
217
+
218
+ delete attrs[attributeName];
219
+ }
@@ -64,7 +64,11 @@ Modeling.prototype.connect = function(source, target, attrs, hints) {
64
64
  var bpmnRules = this._bpmnRules;
65
65
 
66
66
  if (!attrs) {
67
- attrs = bpmnRules.canConnect(source, target) || { type: 'bpmn:Association' };
67
+ attrs = bpmnRules.canConnect(source, target);
68
+ }
69
+
70
+ if (!attrs) {
71
+ return;
68
72
  }
69
73
 
70
74
  return this.createConnection(source, target, attrs, source.parent, hints);
@@ -2,12 +2,23 @@
2
2
 
3
3
  var inherits = require('inherits');
4
4
 
5
- var assign = require('lodash/object/assign');
5
+ var assign = require('lodash/object/assign'),
6
+ find = require('lodash/collection/find');
6
7
 
7
8
  var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
8
9
 
9
10
  var getApproxIntersection = require('diagram-js/lib/util/LineIntersection').getApproxIntersection;
10
11
 
12
+ function isPointInsideBBox(bbox, point) {
13
+ var x = point.x,
14
+ y = point.y;
15
+
16
+ return x >= bbox.x &&
17
+ x <= bbox.x + bbox.width &&
18
+ y >= bbox.y &&
19
+ y <= bbox.y + bbox.height;
20
+ }
21
+
11
22
  function copy(obj) {
12
23
  return assign({}, obj);
13
24
  }
@@ -41,8 +52,15 @@ function DropOnFlow(eventBus, bpmnRules, modeling) {
41
52
 
42
53
  dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : position;
43
54
 
44
- waypointsBefore.push(copy(dockingPoint));
45
- waypointsAfter.unshift(copy(dockingPoint));
55
+ // if last waypointBefore is inside shape's bounds, ignore docking point
56
+ if (!isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length-1])) {
57
+ waypointsBefore.push(copy(dockingPoint));
58
+ }
59
+
60
+ // if first waypointAfter is inside shape's bounds, ignore docking point
61
+ if (!isPointInsideBBox(shape, waypointsAfter[0])) {
62
+ waypointsAfter.unshift(copy(dockingPoint));
63
+ }
46
64
  }
47
65
 
48
66
  source = targetFlow.source;
@@ -68,24 +86,40 @@ function DropOnFlow(eventBus, bpmnRules, modeling) {
68
86
 
69
87
  this.preExecute('elements.move', function(context) {
70
88
 
71
- var parent = context.newParent,
89
+ var newParent = context.newParent,
72
90
  shapes = context.shapes,
73
- shape,
74
- shapeMid,
75
- delta = context.delta;
91
+ delta = context.delta,
92
+ shape = shapes[0];
76
93
 
77
- if (bpmnRules.canInsert(shapes, parent)) {
94
+ if (!shape || !newParent) {
95
+ return;
96
+ }
78
97
 
79
- shape = shapes[0];
80
- shapeMid = getMid(shape);
98
+ // if the new parent is a connection,
99
+ // change it to the new parent's parent
100
+ if (newParent && newParent.waypoints) {
101
+ context.newParent = newParent = newParent.parent;
102
+ }
81
103
 
82
- context.targetFlow = parent;
83
- context.newParent = parent.parent;
84
- context.position = {
85
- x: shapeMid.x + delta.x,
86
- y: shapeMid.y + delta.y
87
- };
104
+ var shapeMid = getMid(shape);
105
+ var newShapeMid = {
106
+ x: shapeMid.x + delta.x,
107
+ y: shapeMid.y + delta.y
108
+ };
109
+
110
+ // find a connection which intersects with the
111
+ // element's mid point
112
+ var connection = find(newParent.children, function(element) {
113
+ var canInsert = bpmnRules.canInsert(shapes, element);
114
+
115
+ return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
116
+ });
117
+
118
+ if (connection) {
119
+ context.targetFlow = connection;
120
+ context.position = newShapeMid;
88
121
  }
122
+
89
123
  }, true);
90
124
 
91
125
  this.postExecuted('elements.move', function(context) {
@@ -110,7 +110,9 @@ ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElemen
110
110
  }
111
111
  };
112
112
 
113
- ReplaceElementBehaviour.$inject = [ 'eventBus', 'bpmnReplace', 'bpmnRules', 'elementRegistry',
114
- 'selection', 'modeling' ];
113
+ ReplaceElementBehaviour.$inject = [
114
+ 'eventBus', 'bpmnReplace', 'bpmnRules',
115
+ 'elementRegistry', 'selection', 'modeling'
116
+ ];
115
117
 
116
118
  module.exports = ReplaceElementBehaviour;
@@ -112,7 +112,7 @@ function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
112
112
  oldLabelLineIndex = attachment.segmentIndex,
113
113
  newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
114
114
 
115
- if ( newLabelLineIndex === null ) {
115
+ if (newLabelLineIndex === null) {
116
116
  return { x: x, y: y };
117
117
  }
118
118
 
@@ -210,8 +210,9 @@ function getLine(waypoints, idx) {
210
210
  }
211
211
 
212
212
  function getRelativeFootPosition(line, foot) {
213
+
213
214
  var length = getDistancePointPoint(line[0], line[1]),
214
215
  lengthToFoot = getDistancePointPoint(line[0], foot);
215
216
 
216
- return lengthToFoot / length;
217
+ return length === 0 ? 0 : lengthToFoot / length;
217
218
  }
@@ -2,7 +2,8 @@
2
2
 
3
3
  var sqrt = Math.sqrt,
4
4
  min = Math.min,
5
- max = Math.max;
5
+ max = Math.max,
6
+ abs = Math.abs;
6
7
 
7
8
  /**
8
9
  * Calculate the square (power to two) of a number.
@@ -74,15 +75,15 @@ function getAttachment(point, line) {
74
75
  segmentEnd = line[idx + 1];
75
76
 
76
77
  if (pointsEqual(segmentStart, segmentEnd)) {
77
- continue;
78
- }
79
-
80
- segmentStartDistance = getDistance(point, segmentStart);
81
- segmentEndDistance = getDistance(point, segmentEnd);
78
+ intersections = [ segmentStart ];
79
+ } else {
80
+ segmentStartDistance = getDistance(point, segmentStart);
81
+ segmentEndDistance = getDistance(point, segmentEnd);
82
82
 
83
- minDistance = min(segmentStartDistance, segmentEndDistance);
83
+ minDistance = min(segmentStartDistance, segmentEndDistance);
84
84
 
85
- intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
85
+ intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
86
+ }
86
87
 
87
88
  if (intersections.length < 1) {
88
89
  throw new Error('expected between [1, 2] circle -> line intersections');
@@ -136,12 +137,6 @@ module.exports.getAttachment = getAttachment;
136
137
  */
137
138
  function getCircleSegmentIntersections(s1, s2, cc, cr) {
138
139
 
139
- // silently round values
140
- s1 = roundPoint(s1);
141
- s2 = roundPoint(s2);
142
- cc = roundPoint(cc);
143
- cr = min(getDistance(s1, cc), getDistance(s2, cc));
144
-
145
140
  var baX = s2.x - s1.x;
146
141
  var baY = s2.y - s1.y;
147
142
  var caX = cc.x - s1.x;
@@ -155,6 +150,14 @@ function getCircleSegmentIntersections(s1, s2, cc, cr) {
155
150
  var q = c / a;
156
151
 
157
152
  var disc = pBy2 * pBy2 - q;
153
+
154
+ // check against negative value to work around
155
+ // negative, very close to zero results (-4e-15)
156
+ // being produced in some environments
157
+ if (disc < 0 && disc > -0.000001) {
158
+ disc = 0;
159
+ }
160
+
158
161
  if (disc < 0) {
159
162
  return [];
160
163
  }
@@ -165,8 +168,8 @@ function getCircleSegmentIntersections(s1, s2, cc, cr) {
165
168
  var abScalingFactor2 = -pBy2 - tmpSqrt;
166
169
 
167
170
  var i1 = {
168
- x: round(s1.x - baX * abScalingFactor1),
169
- y: round(s1.y - baY * abScalingFactor1)
171
+ x: s1.x - baX * abScalingFactor1,
172
+ y: s1.y - baY * abScalingFactor1
170
173
  };
171
174
 
172
175
  if (disc === 0) { // abScalingFactor1 == abScalingFactor2
@@ -174,10 +177,11 @@ function getCircleSegmentIntersections(s1, s2, cc, cr) {
174
177
  }
175
178
 
176
179
  var i2 = {
177
- x: round(s1.x - baX * abScalingFactor2),
178
- y: round(s1.y - baY * abScalingFactor2)
180
+ x: s1.x - baX * abScalingFactor2,
181
+ y: s1.y - baY * abScalingFactor2
179
182
  };
180
183
 
184
+ // return only points on line segment
181
185
  return [ i1, i2 ].filter(function(p) {
182
186
  return isPointInSegment(p, s1, s2);
183
187
  });
@@ -192,7 +196,14 @@ function isPointInSegment(p, segmentStart, segmentEnd) {
192
196
  }
193
197
 
194
198
  function fenced(n, rangeStart, rangeEnd) {
195
- return min(rangeStart, rangeEnd) <= n && n <= max(rangeStart, rangeEnd);
199
+
200
+ // use matching threshold to work around
201
+ // precisison errors in intersection computation
202
+
203
+ return (
204
+ n >= min(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
205
+ n <= max(rangeStart, rangeEnd) + EQUAL_THRESHOLD
206
+ );
196
207
  }
197
208
 
198
209
  /**
@@ -211,17 +222,12 @@ function mid(p1, p2) {
211
222
  };
212
223
  }
213
224
 
214
- function round(n) {
215
- return Math.round(n * 1000) / 1000;
216
- }
217
-
218
- function roundPoint(p) {
219
- return {
220
- x: round(p.x),
221
- y: round(p.y)
222
- };
223
- }
225
+ var EQUAL_THRESHOLD = 0.1;
224
226
 
225
227
  function pointsEqual(p1, p2) {
226
- return p1.x === p2.x && p1.y === p2.y;
228
+
229
+ return (
230
+ abs(p1.x - p2.x) <= EQUAL_THRESHOLD &&
231
+ abs(p1.y - p2.y) <= EQUAL_THRESHOLD
232
+ );
227
233
  }
@@ -369,14 +369,7 @@ function canConnect(source, target, connection) {
369
369
  };
370
370
  }
371
371
 
372
- if (is(connection, 'bpmn:Association') && canConnectAssociation(source, target)) {
373
-
374
- return {
375
- type: 'bpmn:Association'
376
- };
377
- }
378
-
379
- if (isTextAnnotation(source) || isTextAnnotation(target)) {
372
+ if (canConnectAssociation(source, target)) {
380
373
 
381
374
  return {
382
375
  type: 'bpmn:Association'
@@ -705,6 +698,22 @@ function canResize(shape, newBounds) {
705
698
  return false;
706
699
  }
707
700
 
701
+ /**
702
+ * Check, whether one side of the relationship
703
+ * is a text annotation.
704
+ */
705
+ function isOneTextAnnotation(source, target) {
706
+
707
+ var sourceTextAnnotation = isTextAnnotation(source),
708
+ targetTextAnnotation = isTextAnnotation(target);
709
+
710
+ return (
711
+ (sourceTextAnnotation || targetTextAnnotation) &&
712
+ (sourceTextAnnotation !== targetTextAnnotation)
713
+ );
714
+ }
715
+
716
+
708
717
  function canConnectAssociation(source, target) {
709
718
 
710
719
  // do not connect connections
@@ -712,9 +721,18 @@ function canConnectAssociation(source, target) {
712
721
  return false;
713
722
  }
714
723
 
715
- // connect if different parent
716
- return !isParent(target, source) &&
717
- !isParent(source, target);
724
+ // compensation boundary events are exception
725
+ if (isCompensationBoundary(source) && isForCompensation(target)) {
726
+ return true;
727
+ }
728
+
729
+ // don't connect parent <-> child
730
+ if (isParent(target, source) || isParent(source, target)) {
731
+ return false;
732
+ }
733
+
734
+ // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
735
+ return isOneTextAnnotation(source, target);
718
736
  }
719
737
 
720
738
  function canConnectMessageFlow(source, target) {
@@ -373,18 +373,34 @@ BpmnSnapping.prototype.addTargetSnaps = function(snapPoints, shape, target) {
373
373
 
374
374
  var siblings = this.getSiblings(shape, target) || [];
375
375
 
376
- forEach(siblings, function(s) {
376
+ forEach(siblings, function(sibling) {
377
377
 
378
378
  // do not snap to lanes
379
- if (is(s, 'bpmn:Lane')) {
379
+ if (is(sibling, 'bpmn:Lane')) {
380
380
  return;
381
381
  }
382
382
 
383
- snapPoints.add('mid', mid(s));
383
+ if (sibling.waypoints) {
384
+ forEach(sibling.waypoints, function(waypoint, i) {
385
+ var nextWaypoint = sibling.waypoints[i+1];
384
386
 
385
- if (is(s, 'bpmn:Participant')) {
386
- snapPoints.add('top-left', topLeft(s));
387
- snapPoints.add('bottom-right', bottomRight(s));
387
+ if (!nextWaypoint) {
388
+ return;
389
+ }
390
+
391
+ if (nextWaypoint.x === waypoint.x || nextWaypoint.y === waypoint.y) {
392
+ snapPoints.add('mid', waypoint);
393
+ }
394
+ });
395
+
396
+ return;
397
+ }
398
+
399
+ snapPoints.add('mid', mid(sibling));
400
+
401
+ if (is(sibling, 'bpmn:Participant')) {
402
+ snapPoints.add('top-left', topLeft(sibling));
403
+ snapPoints.add('bottom-right', bottomRight(sibling));
388
404
  }
389
405
  });
390
406
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmn-js",
3
- "version": "0.20.4",
3
+ "version": "0.21.0",
4
4
  "description": "A bpmn 2.0 toolkit and web modeler",
5
5
  "scripts": {
6
6
  "all": "grunt",
@@ -37,34 +37,34 @@
37
37
  "browserify": "^13.0.0",
38
38
  "browserify-derequire": "^0.9.1",
39
39
  "bundle-collapser": "^1.1.1",
40
- "chai": "~2.2.0",
41
- "eslint": "^2.11.1",
42
- "eslint-plugin-mocha": "^2.2.0",
40
+ "chai": "^3.5.0",
41
+ "eslint": "^3.19.0",
42
+ "eslint-plugin-mocha": "^4.9.0",
43
43
  "grunt": "^0.4.4",
44
44
  "grunt-cli": "^0.1.13",
45
45
  "grunt-contrib-copy": "^0.7.0",
46
- "grunt-eslint": "^18.1.0",
47
- "grunt-karma": "^0.12.0",
46
+ "grunt-eslint": "^19.0.0",
47
+ "grunt-karma": "^2.0.0",
48
48
  "grunt-release": "^0.7.0",
49
49
  "jsondiffpatch": "^0.1.26",
50
- "karma": "^0.13.0",
51
- "karma-browserify": "^5.0.1",
50
+ "karma": "^1.7.0",
51
+ "karma-browserify": "^5.1.1",
52
52
  "karma-chai": "^0.1.0",
53
- "karma-chrome-launcher": "^0.2.2",
53
+ "karma-chrome-launcher": "^2.1.1",
54
54
  "karma-env-preprocessor": "^0.1.1",
55
- "karma-firefox-launcher": "^0.1.3",
56
- "karma-ie-launcher": "^0.2.0",
57
- "karma-mocha": "~0.1.0",
58
- "karma-phantomjs-launcher": "^1.0.2",
59
- "karma-safari-launcher": "^0.1.1",
60
- "karma-sinon-chai": "~0.3.0",
61
- "karma-spec-reporter": "0.0.26",
55
+ "karma-firefox-launcher": "^1.0.1",
56
+ "karma-ie-launcher": "^1.0.0",
57
+ "karma-mocha": "^1.3.0",
58
+ "karma-phantomjs-launcher": "^1.0.4",
59
+ "karma-safari-launcher": "^1.0.0",
60
+ "karma-sinon-chai": "^1.3.1",
61
+ "karma-spec-reporter": "0.0.31",
62
62
  "load-grunt-tasks": "^0.3.0",
63
- "mocha": "^2.2.5",
63
+ "mocha": "^3.4.1",
64
64
  "mocha-test-container-support": "0.2.0",
65
65
  "phantomjs-prebuilt": "^2.1.12",
66
- "sinon": "~1.14.1",
67
- "sinon-chai": "~2.7.0",
66
+ "sinon": "^2.2.0",
67
+ "sinon-chai": "^2.10.0",
68
68
  "source-map-concat": "^1.0.0",
69
69
  "stringify": "^3.1.0",
70
70
  "time-grunt": "^0.3.2",
@@ -73,9 +73,9 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "bpmn-moddle": "^0.14.0",
76
- "diagram-js": "^0.19.2",
77
- "diagram-js-direct-editing": "^0.17.1",
78
- "diagram-js-origin": "^0.15.1",
76
+ "diagram-js": "^0.20.0",
77
+ "diagram-js-direct-editing": "^0.18.0",
78
+ "diagram-js-origin": "^0.15.2",
79
79
  "ids": "^0.2.0",
80
80
  "inherits": "^2.0.1",
81
81
  "lodash": "^3.0.1",
@@ -7,7 +7,7 @@ TestHelper.insertCSS('diagram-js.css', require('diagram-js/assets/diagram-js.css
7
7
  TestHelper.insertCSS('bpmn-embedded.css', require('../assets/bpmn-font/css/bpmn-embedded.css'));
8
8
 
9
9
  TestHelper.insertCSS('diagram-js-testing.css',
10
- '.test-container .result { height: 500px; }' + '.test-container > div'
10
+ '.test-container .result { height: 500px; }' + '.test-container { height: 1000px !important }'
11
11
  );
12
12
 
13
13