UNPKG

1.38 MBJavaScriptView Raw
1/*!
2 * bpmn-js - bpmn-modeler v8.7.1
3 *
4 * Copyright (c) 2014-present, camunda Services GmbH
5 *
6 * Released under the bpmn.io license
7 * http://bpmn.io/license
8 *
9 * Source Code: https://github.com/bpmn-io/bpmn-js
10 *
11 * Date: 2021-06-25
12 */
13(function (global, factory) {
14 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15 typeof define === 'function' && define.amd ? define(factory) :
16 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BpmnJS = factory());
17}(this, (function () { 'use strict';
18
19 var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
20
21 function getAugmentedNamespace(n) {
22 if (n.__esModule) return n;
23 var a = Object.defineProperty({}, '__esModule', {value: true});
24 Object.keys(n).forEach(function (k) {
25 var d = Object.getOwnPropertyDescriptor(n, k);
26 Object.defineProperty(a, k, d.get ? d : {
27 enumerable: true,
28 get: function () {
29 return n[k];
30 }
31 });
32 });
33 return a;
34 }
35
36 function createCommonjsModule(fn) {
37 var module = { exports: {} };
38 return fn(module, module.exports), module.exports;
39 }
40
41 var inherits_browser = createCommonjsModule(function (module) {
42 if (typeof Object.create === 'function') {
43 // implementation from standard node.js 'util' module
44 module.exports = function inherits(ctor, superCtor) {
45 if (superCtor) {
46 ctor.super_ = superCtor;
47 ctor.prototype = Object.create(superCtor.prototype, {
48 constructor: {
49 value: ctor,
50 enumerable: false,
51 writable: true,
52 configurable: true
53 }
54 });
55 }
56 };
57 } else {
58 // old school shim for old browsers
59 module.exports = function inherits(ctor, superCtor) {
60 if (superCtor) {
61 ctor.super_ = superCtor;
62 var TempCtor = function () {};
63 TempCtor.prototype = superCtor.prototype;
64 ctor.prototype = new TempCtor();
65 ctor.prototype.constructor = ctor;
66 }
67 };
68 }
69 });
70
71 function createCommonjsModule$1(fn, module) {
72 return module = { exports: {} }, fn(module, module.exports), module.exports;
73 }
74
75 var hat_1 = createCommonjsModule$1(function (module) {
76 var hat = module.exports = function (bits, base) {
77 if (!base) base = 16;
78 if (bits === undefined) bits = 128;
79 if (bits <= 0) return '0';
80
81 var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
82 for (var i = 2; digits === Infinity; i *= 2) {
83 digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
84 }
85
86 var rem = digits - Math.floor(digits);
87
88 var res = '';
89
90 for (var i = 0; i < Math.floor(digits); i++) {
91 var x = Math.floor(Math.random() * base).toString(base);
92 res = x + res;
93 }
94
95 if (rem) {
96 var b = Math.pow(base, rem);
97 var x = Math.floor(Math.random() * b).toString(base);
98 res = x + res;
99 }
100
101 var parsed = parseInt(res, base);
102 if (parsed !== Infinity && parsed >= Math.pow(2, bits)) {
103 return hat(bits, base)
104 }
105 else return res;
106 };
107
108 hat.rack = function (bits, base, expandBy) {
109 var fn = function (data) {
110 var iters = 0;
111 do {
112 if (iters ++ > 10) {
113 if (expandBy) bits += expandBy;
114 else throw new Error('too many ID collisions, use more bits')
115 }
116
117 var id = hat(bits, base);
118 } while (Object.hasOwnProperty.call(hats, id));
119
120 hats[id] = data;
121 return id;
122 };
123 var hats = fn.hats = {};
124
125 fn.get = function (id) {
126 return fn.hats[id];
127 };
128
129 fn.set = function (id, value) {
130 fn.hats[id] = value;
131 return fn;
132 };
133
134 fn.bits = bits || 128;
135 fn.base = base || 16;
136 return fn;
137 };
138 });
139
140 /**
141 * Create a new id generator / cache instance.
142 *
143 * You may optionally provide a seed that is used internally.
144 *
145 * @param {Seed} seed
146 */
147
148 function Ids(seed) {
149 if (!(this instanceof Ids)) {
150 return new Ids(seed);
151 }
152
153 seed = seed || [128, 36, 1];
154 this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed;
155 }
156 /**
157 * Generate a next id.
158 *
159 * @param {Object} [element] element to bind the id to
160 *
161 * @return {String} id
162 */
163
164 Ids.prototype.next = function (element) {
165 return this._seed(element || true);
166 };
167 /**
168 * Generate a next id with a given prefix.
169 *
170 * @param {Object} [element] element to bind the id to
171 *
172 * @return {String} id
173 */
174
175
176 Ids.prototype.nextPrefixed = function (prefix, element) {
177 var id;
178
179 do {
180 id = prefix + this.next(true);
181 } while (this.assigned(id)); // claim {prefix}{random}
182
183
184 this.claim(id, element); // return
185
186 return id;
187 };
188 /**
189 * Manually claim an existing id.
190 *
191 * @param {String} id
192 * @param {String} [element] element the id is claimed by
193 */
194
195
196 Ids.prototype.claim = function (id, element) {
197 this._seed.set(id, element || true);
198 };
199 /**
200 * Returns true if the given id has already been assigned.
201 *
202 * @param {String} id
203 * @return {Boolean}
204 */
205
206
207 Ids.prototype.assigned = function (id) {
208 return this._seed.get(id) || false;
209 };
210 /**
211 * Unclaim an id.
212 *
213 * @param {String} id the id to unclaim
214 */
215
216
217 Ids.prototype.unclaim = function (id) {
218 delete this._seed.hats[id];
219 };
220 /**
221 * Clear all claimed ids.
222 */
223
224
225 Ids.prototype.clear = function () {
226 var hats = this._seed.hats,
227 id;
228
229 for (id in hats) {
230 this.unclaim(id);
231 }
232 };
233
234 /**
235 * Flatten array, one level deep.
236 *
237 * @param {Array<?>} arr
238 *
239 * @return {Array<?>}
240 */
241 function flatten(arr) {
242 return Array.prototype.concat.apply([], arr);
243 }
244
245 var nativeToString = Object.prototype.toString;
246 var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
247 function isUndefined(obj) {
248 return obj === undefined;
249 }
250 function isDefined(obj) {
251 return obj !== undefined;
252 }
253 function isNil(obj) {
254 return obj == null;
255 }
256 function isArray(obj) {
257 return nativeToString.call(obj) === '[object Array]';
258 }
259 function isObject(obj) {
260 return nativeToString.call(obj) === '[object Object]';
261 }
262 function isNumber(obj) {
263 return nativeToString.call(obj) === '[object Number]';
264 }
265 function isFunction(obj) {
266 var tag = nativeToString.call(obj);
267 return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
268 }
269 function isString(obj) {
270 return nativeToString.call(obj) === '[object String]';
271 }
272 /**
273 * Ensure collection is an array.
274 *
275 * @param {Object} obj
276 */
277
278 function ensureArray(obj) {
279 if (isArray(obj)) {
280 return;
281 }
282
283 throw new Error('must supply array');
284 }
285 /**
286 * Return true, if target owns a property with the given key.
287 *
288 * @param {Object} target
289 * @param {String} key
290 *
291 * @return {Boolean}
292 */
293
294 function has(target, key) {
295 return nativeHasOwnProperty.call(target, key);
296 }
297
298 /**
299 * Find element in collection.
300 *
301 * @param {Array|Object} collection
302 * @param {Function|Object} matcher
303 *
304 * @return {Object}
305 */
306
307 function find(collection, matcher) {
308 matcher = toMatcher(matcher);
309 var match;
310 forEach(collection, function (val, key) {
311 if (matcher(val, key)) {
312 match = val;
313 return false;
314 }
315 });
316 return match;
317 }
318 /**
319 * Find element index in collection.
320 *
321 * @param {Array|Object} collection
322 * @param {Function} matcher
323 *
324 * @return {Object}
325 */
326
327 function findIndex(collection, matcher) {
328 matcher = toMatcher(matcher);
329 var idx = isArray(collection) ? -1 : undefined;
330 forEach(collection, function (val, key) {
331 if (matcher(val, key)) {
332 idx = key;
333 return false;
334 }
335 });
336 return idx;
337 }
338 /**
339 * Find element in collection.
340 *
341 * @param {Array|Object} collection
342 * @param {Function} matcher
343 *
344 * @return {Array} result
345 */
346
347 function filter(collection, matcher) {
348 var result = [];
349 forEach(collection, function (val, key) {
350 if (matcher(val, key)) {
351 result.push(val);
352 }
353 });
354 return result;
355 }
356 /**
357 * Iterate over collection; returning something
358 * (non-undefined) will stop iteration.
359 *
360 * @param {Array|Object} collection
361 * @param {Function} iterator
362 *
363 * @return {Object} return result that stopped the iteration
364 */
365
366 function forEach(collection, iterator) {
367 var val, result;
368
369 if (isUndefined(collection)) {
370 return;
371 }
372
373 var convertKey = isArray(collection) ? toNum : identity;
374
375 for (var key in collection) {
376 if (has(collection, key)) {
377 val = collection[key];
378 result = iterator(val, convertKey(key));
379
380 if (result === false) {
381 return val;
382 }
383 }
384 }
385 }
386 /**
387 * Return collection without element.
388 *
389 * @param {Array} arr
390 * @param {Function} matcher
391 *
392 * @return {Array}
393 */
394
395 function without(arr, matcher) {
396 if (isUndefined(arr)) {
397 return [];
398 }
399
400 ensureArray(arr);
401 matcher = toMatcher(matcher);
402 return arr.filter(function (el, idx) {
403 return !matcher(el, idx);
404 });
405 }
406 /**
407 * Reduce collection, returning a single result.
408 *
409 * @param {Object|Array} collection
410 * @param {Function} iterator
411 * @param {Any} result
412 *
413 * @return {Any} result returned from last iterator
414 */
415
416 function reduce(collection, iterator, result) {
417 forEach(collection, function (value, idx) {
418 result = iterator(result, value, idx);
419 });
420 return result;
421 }
422 /**
423 * Return true if every element in the collection
424 * matches the criteria.
425 *
426 * @param {Object|Array} collection
427 * @param {Function} matcher
428 *
429 * @return {Boolean}
430 */
431
432 function every(collection, matcher) {
433 return !!reduce(collection, function (matches, val, key) {
434 return matches && matcher(val, key);
435 }, true);
436 }
437 /**
438 * Return true if some elements in the collection
439 * match the criteria.
440 *
441 * @param {Object|Array} collection
442 * @param {Function} matcher
443 *
444 * @return {Boolean}
445 */
446
447 function some(collection, matcher) {
448 return !!find(collection, matcher);
449 }
450 /**
451 * Transform a collection into another collection
452 * by piping each member through the given fn.
453 *
454 * @param {Object|Array} collection
455 * @param {Function} fn
456 *
457 * @return {Array} transformed collection
458 */
459
460 function map(collection, fn) {
461 var result = [];
462 forEach(collection, function (val, key) {
463 result.push(fn(val, key));
464 });
465 return result;
466 }
467 /**
468 * Get the collections keys.
469 *
470 * @param {Object|Array} collection
471 *
472 * @return {Array}
473 */
474
475 function keys(collection) {
476 return collection && Object.keys(collection) || [];
477 }
478 /**
479 * Shorthand for `keys(o).length`.
480 *
481 * @param {Object|Array} collection
482 *
483 * @return {Number}
484 */
485
486 function size(collection) {
487 return keys(collection).length;
488 }
489 /**
490 * Get the values in the collection.
491 *
492 * @param {Object|Array} collection
493 *
494 * @return {Array}
495 */
496
497 function values(collection) {
498 return map(collection, function (val) {
499 return val;
500 });
501 }
502 /**
503 * Group collection members by attribute.
504 *
505 * @param {Object|Array} collection
506 * @param {Function} extractor
507 *
508 * @return {Object} map with { attrValue => [ a, b, c ] }
509 */
510
511 function groupBy(collection, extractor) {
512 var grouped = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
513 extractor = toExtractor(extractor);
514 forEach(collection, function (val) {
515 var discriminator = extractor(val) || '_';
516 var group = grouped[discriminator];
517
518 if (!group) {
519 group = grouped[discriminator] = [];
520 }
521
522 group.push(val);
523 });
524 return grouped;
525 }
526 function uniqueBy(extractor) {
527 extractor = toExtractor(extractor);
528 var grouped = {};
529
530 for (var _len = arguments.length, collections = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
531 collections[_key - 1] = arguments[_key];
532 }
533
534 forEach(collections, function (c) {
535 return groupBy(c, extractor, grouped);
536 });
537 var result = map(grouped, function (val, key) {
538 return val[0];
539 });
540 return result;
541 }
542 var unionBy = uniqueBy;
543 /**
544 * Sort collection by criteria.
545 *
546 * @param {Object|Array} collection
547 * @param {String|Function} extractor
548 *
549 * @return {Array}
550 */
551
552 function sortBy(collection, extractor) {
553 extractor = toExtractor(extractor);
554 var sorted = [];
555 forEach(collection, function (value, key) {
556 var disc = extractor(value, key);
557 var entry = {
558 d: disc,
559 v: value
560 };
561
562 for (var idx = 0; idx < sorted.length; idx++) {
563 var d = sorted[idx].d;
564
565 if (disc < d) {
566 sorted.splice(idx, 0, entry);
567 return;
568 }
569 } // not inserted, append (!)
570
571
572 sorted.push(entry);
573 });
574 return map(sorted, function (e) {
575 return e.v;
576 });
577 }
578 /**
579 * Create an object pattern matcher.
580 *
581 * @example
582 *
583 * const matcher = matchPattern({ id: 1 });
584 *
585 * var element = find(elements, matcher);
586 *
587 * @param {Object} pattern
588 *
589 * @return {Function} matcherFn
590 */
591
592 function matchPattern(pattern) {
593 return function (el) {
594 return every(pattern, function (val, key) {
595 return el[key] === val;
596 });
597 };
598 }
599
600 function toExtractor(extractor) {
601 return isFunction(extractor) ? extractor : function (e) {
602 return e[extractor];
603 };
604 }
605
606 function toMatcher(matcher) {
607 return isFunction(matcher) ? matcher : function (e) {
608 return e === matcher;
609 };
610 }
611
612 function identity(arg) {
613 return arg;
614 }
615
616 function toNum(arg) {
617 return Number(arg);
618 }
619
620 /**
621 * Debounce fn, calling it only once if
622 * the given time elapsed between calls.
623 *
624 * @param {Function} fn
625 * @param {Number} timeout
626 *
627 * @return {Function} debounced function
628 */
629 function debounce(fn, timeout) {
630 var timer;
631 var lastArgs;
632 var lastThis;
633 var lastNow;
634
635 function fire() {
636 var now = Date.now();
637 var scheduledDiff = lastNow + timeout - now;
638
639 if (scheduledDiff > 0) {
640 return schedule(scheduledDiff);
641 }
642
643 fn.apply(lastThis, lastArgs);
644 timer = lastNow = lastArgs = lastThis = undefined;
645 }
646
647 function schedule(timeout) {
648 timer = setTimeout(fire, timeout);
649 }
650
651 return function () {
652 lastNow = Date.now();
653
654 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
655 args[_key] = arguments[_key];
656 }
657
658 lastArgs = args;
659 lastThis = this; // ensure an execution is scheduled
660
661 if (!timer) {
662 schedule(timeout);
663 }
664 };
665 }
666 /**
667 * Throttle fn, calling at most once
668 * in the given interval.
669 *
670 * @param {Function} fn
671 * @param {Number} interval
672 *
673 * @return {Function} throttled function
674 */
675
676 function throttle(fn, interval) {
677 var throttling = false;
678 return function () {
679 if (throttling) {
680 return;
681 }
682
683 fn.apply(void 0, arguments);
684 throttling = true;
685 setTimeout(function () {
686 throttling = false;
687 }, interval);
688 };
689 }
690 /**
691 * Bind function against target <this>.
692 *
693 * @param {Function} fn
694 * @param {Object} target
695 *
696 * @return {Function} bound function
697 */
698
699 function bind(fn, target) {
700 return fn.bind(target);
701 }
702
703 function _extends() {
704 _extends = Object.assign || function (target) {
705 for (var i = 1; i < arguments.length; i++) {
706 var source = arguments[i];
707
708 for (var key in source) {
709 if (Object.prototype.hasOwnProperty.call(source, key)) {
710 target[key] = source[key];
711 }
712 }
713 }
714
715 return target;
716 };
717
718 return _extends.apply(this, arguments);
719 }
720
721 /**
722 * Convenience wrapper for `Object.assign`.
723 *
724 * @param {Object} target
725 * @param {...Object} others
726 *
727 * @return {Object} the target
728 */
729
730 function assign(target) {
731 for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
732 others[_key - 1] = arguments[_key];
733 }
734
735 return _extends.apply(void 0, [target].concat(others));
736 }
737 /**
738 * Pick given properties from the target object.
739 *
740 * @param {Object} target
741 * @param {Array} properties
742 *
743 * @return {Object} target
744 */
745
746 function pick(target, properties) {
747 var result = {};
748 var obj = Object(target);
749 forEach(properties, function (prop) {
750 if (prop in obj) {
751 result[prop] = target[prop];
752 }
753 });
754 return result;
755 }
756 /**
757 * Pick all target properties, excluding the given ones.
758 *
759 * @param {Object} target
760 * @param {Array} properties
761 *
762 * @return {Object} target
763 */
764
765 function omit(target, properties) {
766 var result = {};
767 var obj = Object(target);
768 forEach(obj, function (prop, key) {
769 if (properties.indexOf(key) === -1) {
770 result[key] = prop;
771 }
772 });
773 return result;
774 }
775 /**
776 * Recursively merge `...sources` into given target.
777 *
778 * Does support merging objects; does not support merging arrays.
779 *
780 * @param {Object} target
781 * @param {...Object} sources
782 *
783 * @return {Object} the target
784 */
785
786 function merge(target) {
787 for (var _len2 = arguments.length, sources = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
788 sources[_key2 - 1] = arguments[_key2];
789 }
790
791 if (!sources.length) {
792 return target;
793 }
794
795 forEach(sources, function (source) {
796 // skip non-obj sources, i.e. null
797 if (!source || !isObject(source)) {
798 return;
799 }
800
801 forEach(source, function (sourceVal, key) {
802 if (key === '__proto__') {
803 return;
804 }
805
806 var targetVal = target[key];
807
808 if (isObject(sourceVal)) {
809 if (!isObject(targetVal)) {
810 // override target[key] with object
811 targetVal = {};
812 }
813
814 target[key] = merge(targetVal, sourceVal);
815 } else {
816 target[key] = sourceVal;
817 }
818 });
819 });
820 return target;
821 }
822
823 var index_esm = /*#__PURE__*/Object.freeze({
824 __proto__: null,
825 flatten: flatten,
826 find: find,
827 findIndex: findIndex,
828 filter: filter,
829 forEach: forEach,
830 without: without,
831 reduce: reduce,
832 every: every,
833 some: some,
834 map: map,
835 keys: keys,
836 size: size,
837 values: values,
838 groupBy: groupBy,
839 uniqueBy: uniqueBy,
840 unionBy: unionBy,
841 sortBy: sortBy,
842 matchPattern: matchPattern,
843 debounce: debounce,
844 throttle: throttle,
845 bind: bind,
846 isUndefined: isUndefined,
847 isDefined: isDefined,
848 isNil: isNil,
849 isArray: isArray,
850 isObject: isObject,
851 isNumber: isNumber,
852 isFunction: isFunction,
853 isString: isString,
854 ensureArray: ensureArray,
855 has: has,
856 assign: assign,
857 pick: pick,
858 omit: omit,
859 merge: merge
860 });
861
862 /**
863 * Set attribute `name` to `val`, or get attr `name`.
864 *
865 * @param {Element} el
866 * @param {String} name
867 * @param {String} [val]
868 * @api public
869 */
870 function attr(el, name, val) {
871 // get
872 if (arguments.length == 2) {
873 return el.getAttribute(name);
874 }
875
876 // remove
877 if (val === null) {
878 return el.removeAttribute(name);
879 }
880
881 // set
882 el.setAttribute(name, val);
883
884 return el;
885 }
886
887 var indexOf = [].indexOf;
888
889 var indexof = function(arr, obj){
890 if (indexOf) return arr.indexOf(obj);
891 for (var i = 0; i < arr.length; ++i) {
892 if (arr[i] === obj) return i;
893 }
894 return -1;
895 };
896
897 /**
898 * Taken from https://github.com/component/classes
899 *
900 * Without the component bits.
901 */
902
903 /**
904 * Whitespace regexp.
905 */
906
907 var re = /\s+/;
908
909 /**
910 * toString reference.
911 */
912
913 var toString = Object.prototype.toString;
914
915 /**
916 * Wrap `el` in a `ClassList`.
917 *
918 * @param {Element} el
919 * @return {ClassList}
920 * @api public
921 */
922
923 function classes(el) {
924 return new ClassList(el);
925 }
926
927 /**
928 * Initialize a new ClassList for `el`.
929 *
930 * @param {Element} el
931 * @api private
932 */
933
934 function ClassList(el) {
935 if (!el || !el.nodeType) {
936 throw new Error('A DOM element reference is required');
937 }
938 this.el = el;
939 this.list = el.classList;
940 }
941
942 /**
943 * Add class `name` if not already present.
944 *
945 * @param {String} name
946 * @return {ClassList}
947 * @api public
948 */
949
950 ClassList.prototype.add = function (name) {
951 // classList
952 if (this.list) {
953 this.list.add(name);
954 return this;
955 }
956
957 // fallback
958 var arr = this.array();
959 var i = indexof(arr, name);
960 if (!~i) arr.push(name);
961 this.el.className = arr.join(' ');
962 return this;
963 };
964
965 /**
966 * Remove class `name` when present, or
967 * pass a regular expression to remove
968 * any which match.
969 *
970 * @param {String|RegExp} name
971 * @return {ClassList}
972 * @api public
973 */
974
975 ClassList.prototype.remove = function (name) {
976 if ('[object RegExp]' == toString.call(name)) {
977 return this.removeMatching(name);
978 }
979
980 // classList
981 if (this.list) {
982 this.list.remove(name);
983 return this;
984 }
985
986 // fallback
987 var arr = this.array();
988 var i = indexof(arr, name);
989 if (~i) arr.splice(i, 1);
990 this.el.className = arr.join(' ');
991 return this;
992 };
993
994 /**
995 * Remove all classes matching `re`.
996 *
997 * @param {RegExp} re
998 * @return {ClassList}
999 * @api private
1000 */
1001
1002 ClassList.prototype.removeMatching = function (re) {
1003 var arr = this.array();
1004 for (var i = 0; i < arr.length; i++) {
1005 if (re.test(arr[i])) {
1006 this.remove(arr[i]);
1007 }
1008 }
1009 return this;
1010 };
1011
1012 /**
1013 * Toggle class `name`, can force state via `force`.
1014 *
1015 * For browsers that support classList, but do not support `force` yet,
1016 * the mistake will be detected and corrected.
1017 *
1018 * @param {String} name
1019 * @param {Boolean} force
1020 * @return {ClassList}
1021 * @api public
1022 */
1023
1024 ClassList.prototype.toggle = function (name, force) {
1025 // classList
1026 if (this.list) {
1027 if ('undefined' !== typeof force) {
1028 if (force !== this.list.toggle(name, force)) {
1029 this.list.toggle(name); // toggle again to correct
1030 }
1031 } else {
1032 this.list.toggle(name);
1033 }
1034 return this;
1035 }
1036
1037 // fallback
1038 if ('undefined' !== typeof force) {
1039 if (!force) {
1040 this.remove(name);
1041 } else {
1042 this.add(name);
1043 }
1044 } else {
1045 if (this.has(name)) {
1046 this.remove(name);
1047 } else {
1048 this.add(name);
1049 }
1050 }
1051
1052 return this;
1053 };
1054
1055 /**
1056 * Return an array of classes.
1057 *
1058 * @return {Array}
1059 * @api public
1060 */
1061
1062 ClassList.prototype.array = function () {
1063 var className = this.el.getAttribute('class') || '';
1064 var str = className.replace(/^\s+|\s+$/g, '');
1065 var arr = str.split(re);
1066 if ('' === arr[0]) arr.shift();
1067 return arr;
1068 };
1069
1070 /**
1071 * Check if class `name` is present.
1072 *
1073 * @param {String} name
1074 * @return {ClassList}
1075 * @api public
1076 */
1077
1078 ClassList.prototype.has = ClassList.prototype.contains = function (name) {
1079 return this.list ? this.list.contains(name) : !!~indexof(this.array(), name);
1080 };
1081
1082 /**
1083 * Remove all children from the given element.
1084 */
1085 function clear(el) {
1086
1087 var c;
1088
1089 while (el.childNodes.length) {
1090 c = el.childNodes[0];
1091 el.removeChild(c);
1092 }
1093
1094 return el;
1095 }
1096
1097 var proto = typeof Element !== 'undefined' ? Element.prototype : {};
1098 var vendor = proto.matches
1099 || proto.matchesSelector
1100 || proto.webkitMatchesSelector
1101 || proto.mozMatchesSelector
1102 || proto.msMatchesSelector
1103 || proto.oMatchesSelector;
1104
1105 var matchesSelector = match;
1106
1107 /**
1108 * Match `el` to `selector`.
1109 *
1110 * @param {Element} el
1111 * @param {String} selector
1112 * @return {Boolean}
1113 * @api public
1114 */
1115
1116 function match(el, selector) {
1117 if (!el || el.nodeType !== 1) return false;
1118 if (vendor) return vendor.call(el, selector);
1119 var nodes = el.parentNode.querySelectorAll(selector);
1120 for (var i = 0; i < nodes.length; i++) {
1121 if (nodes[i] == el) return true;
1122 }
1123 return false;
1124 }
1125
1126 /**
1127 * Closest
1128 *
1129 * @param {Element} el
1130 * @param {String} selector
1131 * @param {Boolean} checkYourSelf (optional)
1132 */
1133 function closest (element, selector, checkYourSelf) {
1134 var currentElem = checkYourSelf ? element : element.parentNode;
1135
1136 while (currentElem && currentElem.nodeType !== document.DOCUMENT_NODE && currentElem.nodeType !== document.DOCUMENT_FRAGMENT_NODE) {
1137
1138 if (matchesSelector(currentElem, selector)) {
1139 return currentElem;
1140 }
1141
1142 currentElem = currentElem.parentNode;
1143 }
1144
1145 return matchesSelector(currentElem, selector) ? currentElem : null;
1146 }
1147
1148 var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent',
1149 unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
1150 prefix = bind$1 !== 'addEventListener' ? 'on' : '';
1151
1152 /**
1153 * Bind `el` event `type` to `fn`.
1154 *
1155 * @param {Element} el
1156 * @param {String} type
1157 * @param {Function} fn
1158 * @param {Boolean} capture
1159 * @return {Function}
1160 * @api public
1161 */
1162
1163 var bind_1 = function(el, type, fn, capture){
1164 el[bind$1](prefix + type, fn, capture || false);
1165 return fn;
1166 };
1167
1168 /**
1169 * Unbind `el` event `type`'s callback `fn`.
1170 *
1171 * @param {Element} el
1172 * @param {String} type
1173 * @param {Function} fn
1174 * @param {Boolean} capture
1175 * @return {Function}
1176 * @api public
1177 */
1178
1179 var unbind_1 = function(el, type, fn, capture){
1180 el[unbind](prefix + type, fn, capture || false);
1181 return fn;
1182 };
1183
1184 var componentEvent = {
1185 bind: bind_1,
1186 unbind: unbind_1
1187 };
1188
1189 /**
1190 * Module dependencies.
1191 */
1192
1193 /**
1194 * Delegate event `type` to `selector`
1195 * and invoke `fn(e)`. A callback function
1196 * is returned which may be passed to `.unbind()`.
1197 *
1198 * @param {Element} el
1199 * @param {String} selector
1200 * @param {String} type
1201 * @param {Function} fn
1202 * @param {Boolean} capture
1203 * @return {Function}
1204 * @api public
1205 */
1206
1207 // Some events don't bubble, so we want to bind to the capture phase instead
1208 // when delegating.
1209 var forceCaptureEvents = ['focus', 'blur'];
1210
1211 function bind$1$1(el, selector, type, fn, capture) {
1212 if (forceCaptureEvents.indexOf(type) !== -1) {
1213 capture = true;
1214 }
1215
1216 return componentEvent.bind(el, type, function (e) {
1217 var target = e.target || e.srcElement;
1218 e.delegateTarget = closest(target, selector, true);
1219 if (e.delegateTarget) {
1220 fn.call(el, e);
1221 }
1222 }, capture);
1223 }
1224
1225 /**
1226 * Unbind event `type`'s callback `fn`.
1227 *
1228 * @param {Element} el
1229 * @param {String} type
1230 * @param {Function} fn
1231 * @param {Boolean} capture
1232 * @api public
1233 */
1234 function unbind$1(el, type, fn, capture) {
1235 if (forceCaptureEvents.indexOf(type) !== -1) {
1236 capture = true;
1237 }
1238
1239 return componentEvent.unbind(el, type, fn, capture);
1240 }
1241
1242 var delegate = {
1243 bind: bind$1$1,
1244 unbind: unbind$1
1245 };
1246
1247 /**
1248 * Expose `parse`.
1249 */
1250
1251 var domify = parse;
1252
1253 /**
1254 * Tests for browser support.
1255 */
1256
1257 var innerHTMLBug = false;
1258 var bugTestDiv;
1259 if (typeof document !== 'undefined') {
1260 bugTestDiv = document.createElement('div');
1261 // Setup
1262 bugTestDiv.innerHTML = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
1263 // Make sure that link elements get serialized correctly by innerHTML
1264 // This requires a wrapper element in IE
1265 innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length;
1266 bugTestDiv = undefined;
1267 }
1268
1269 /**
1270 * Wrap map from jquery.
1271 */
1272
1273 var map$1 = {
1274 legend: [1, '<fieldset>', '</fieldset>'],
1275 tr: [2, '<table><tbody>', '</tbody></table>'],
1276 col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
1277 // for script/link/style tags to work in IE6-8, you have to wrap
1278 // in a div with a non-whitespace character in front, ha!
1279 _default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
1280 };
1281
1282 map$1.td =
1283 map$1.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];
1284
1285 map$1.option =
1286 map$1.optgroup = [1, '<select multiple="multiple">', '</select>'];
1287
1288 map$1.thead =
1289 map$1.tbody =
1290 map$1.colgroup =
1291 map$1.caption =
1292 map$1.tfoot = [1, '<table>', '</table>'];
1293
1294 map$1.polyline =
1295 map$1.ellipse =
1296 map$1.polygon =
1297 map$1.circle =
1298 map$1.text =
1299 map$1.line =
1300 map$1.path =
1301 map$1.rect =
1302 map$1.g = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];
1303
1304 /**
1305 * Parse `html` and return a DOM Node instance, which could be a TextNode,
1306 * HTML DOM Node of some kind (<div> for example), or a DocumentFragment
1307 * instance, depending on the contents of the `html` string.
1308 *
1309 * @param {String} html - HTML string to "domify"
1310 * @param {Document} doc - The `document` instance to create the Node for
1311 * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
1312 * @api private
1313 */
1314
1315 function parse(html, doc) {
1316 if ('string' != typeof html) throw new TypeError('String expected');
1317
1318 // default to the global `document` object
1319 if (!doc) doc = document;
1320
1321 // tag name
1322 var m = /<([\w:]+)/.exec(html);
1323 if (!m) return doc.createTextNode(html);
1324
1325 html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace
1326
1327 var tag = m[1];
1328
1329 // body support
1330 if (tag == 'body') {
1331 var el = doc.createElement('html');
1332 el.innerHTML = html;
1333 return el.removeChild(el.lastChild);
1334 }
1335
1336 // wrap map
1337 var wrap = map$1[tag] || map$1._default;
1338 var depth = wrap[0];
1339 var prefix = wrap[1];
1340 var suffix = wrap[2];
1341 var el = doc.createElement('div');
1342 el.innerHTML = prefix + html + suffix;
1343 while (depth--) el = el.lastChild;
1344
1345 // one element
1346 if (el.firstChild == el.lastChild) {
1347 return el.removeChild(el.firstChild);
1348 }
1349
1350 // several elements
1351 var fragment = doc.createDocumentFragment();
1352 while (el.firstChild) {
1353 fragment.appendChild(el.removeChild(el.firstChild));
1354 }
1355
1356 return fragment;
1357 }
1358
1359 function query(selector, el) {
1360 el = el || document;
1361
1362 return el.querySelector(selector);
1363 }
1364
1365 function all(selector, el) {
1366 el = el || document;
1367
1368 return el.querySelectorAll(selector);
1369 }
1370
1371 function remove(el) {
1372 el.parentNode && el.parentNode.removeChild(el);
1373 }
1374
1375 function ensureImported(element, target) {
1376
1377 if (element.ownerDocument !== target.ownerDocument) {
1378 try {
1379 // may fail on webkit
1380 return target.ownerDocument.importNode(element, true);
1381 } catch (e) {
1382 // ignore
1383 }
1384 }
1385
1386 return element;
1387 }
1388
1389 /**
1390 * appendTo utility
1391 */
1392
1393 /**
1394 * Append a node to a target element and return the appended node.
1395 *
1396 * @param {SVGElement} element
1397 * @param {SVGElement} target
1398 *
1399 * @return {SVGElement} the appended node
1400 */
1401 function appendTo(element, target) {
1402 return target.appendChild(ensureImported(element, target));
1403 }
1404
1405 /**
1406 * append utility
1407 */
1408
1409 /**
1410 * Append a node to an element
1411 *
1412 * @param {SVGElement} element
1413 * @param {SVGElement} node
1414 *
1415 * @return {SVGElement} the element
1416 */
1417 function append(target, node) {
1418 appendTo(node, target);
1419 return target;
1420 }
1421
1422 /**
1423 * attribute accessor utility
1424 */
1425
1426 var LENGTH_ATTR = 2;
1427
1428 var CSS_PROPERTIES = {
1429 'alignment-baseline': 1,
1430 'baseline-shift': 1,
1431 'clip': 1,
1432 'clip-path': 1,
1433 'clip-rule': 1,
1434 'color': 1,
1435 'color-interpolation': 1,
1436 'color-interpolation-filters': 1,
1437 'color-profile': 1,
1438 'color-rendering': 1,
1439 'cursor': 1,
1440 'direction': 1,
1441 'display': 1,
1442 'dominant-baseline': 1,
1443 'enable-background': 1,
1444 'fill': 1,
1445 'fill-opacity': 1,
1446 'fill-rule': 1,
1447 'filter': 1,
1448 'flood-color': 1,
1449 'flood-opacity': 1,
1450 'font': 1,
1451 'font-family': 1,
1452 'font-size': LENGTH_ATTR,
1453 'font-size-adjust': 1,
1454 'font-stretch': 1,
1455 'font-style': 1,
1456 'font-variant': 1,
1457 'font-weight': 1,
1458 'glyph-orientation-horizontal': 1,
1459 'glyph-orientation-vertical': 1,
1460 'image-rendering': 1,
1461 'kerning': 1,
1462 'letter-spacing': 1,
1463 'lighting-color': 1,
1464 'marker': 1,
1465 'marker-end': 1,
1466 'marker-mid': 1,
1467 'marker-start': 1,
1468 'mask': 1,
1469 'opacity': 1,
1470 'overflow': 1,
1471 'pointer-events': 1,
1472 'shape-rendering': 1,
1473 'stop-color': 1,
1474 'stop-opacity': 1,
1475 'stroke': 1,
1476 'stroke-dasharray': 1,
1477 'stroke-dashoffset': 1,
1478 'stroke-linecap': 1,
1479 'stroke-linejoin': 1,
1480 'stroke-miterlimit': 1,
1481 'stroke-opacity': 1,
1482 'stroke-width': LENGTH_ATTR,
1483 'text-anchor': 1,
1484 'text-decoration': 1,
1485 'text-rendering': 1,
1486 'unicode-bidi': 1,
1487 'visibility': 1,
1488 'word-spacing': 1,
1489 'writing-mode': 1
1490 };
1491
1492
1493 function getAttribute(node, name) {
1494 if (CSS_PROPERTIES[name]) {
1495 return node.style[name];
1496 } else {
1497 return node.getAttributeNS(null, name);
1498 }
1499 }
1500
1501 function setAttribute(node, name, value) {
1502 var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1503
1504 var type = CSS_PROPERTIES[hyphenated];
1505
1506 if (type) {
1507 // append pixel unit, unless present
1508 if (type === LENGTH_ATTR && typeof value === 'number') {
1509 value = String(value) + 'px';
1510 }
1511
1512 node.style[hyphenated] = value;
1513 } else {
1514 node.setAttributeNS(null, name, value);
1515 }
1516 }
1517
1518 function setAttributes(node, attrs) {
1519
1520 var names = Object.keys(attrs), i, name;
1521
1522 for (i = 0, name; (name = names[i]); i++) {
1523 setAttribute(node, name, attrs[name]);
1524 }
1525 }
1526
1527 /**
1528 * Gets or sets raw attributes on a node.
1529 *
1530 * @param {SVGElement} node
1531 * @param {Object} [attrs]
1532 * @param {String} [name]
1533 * @param {String} [value]
1534 *
1535 * @return {String}
1536 */
1537 function attr$1(node, name, value) {
1538 if (typeof name === 'string') {
1539 if (value !== undefined) {
1540 setAttribute(node, name, value);
1541 } else {
1542 return getAttribute(node, name);
1543 }
1544 } else {
1545 setAttributes(node, name);
1546 }
1547
1548 return node;
1549 }
1550
1551 /**
1552 * Clear utility
1553 */
1554 function index(arr, obj) {
1555 if (arr.indexOf) {
1556 return arr.indexOf(obj);
1557 }
1558
1559
1560 for (var i = 0; i < arr.length; ++i) {
1561 if (arr[i] === obj) {
1562 return i;
1563 }
1564 }
1565
1566 return -1;
1567 }
1568
1569 var re$1 = /\s+/;
1570
1571 var toString$1 = Object.prototype.toString;
1572
1573 function defined(o) {
1574 return typeof o !== 'undefined';
1575 }
1576
1577 /**
1578 * Wrap `el` in a `ClassList`.
1579 *
1580 * @param {Element} el
1581 * @return {ClassList}
1582 * @api public
1583 */
1584
1585 function classes$1(el) {
1586 return new ClassList$1(el);
1587 }
1588
1589 function ClassList$1(el) {
1590 if (!el || !el.nodeType) {
1591 throw new Error('A DOM element reference is required');
1592 }
1593 this.el = el;
1594 this.list = el.classList;
1595 }
1596
1597 /**
1598 * Add class `name` if not already present.
1599 *
1600 * @param {String} name
1601 * @return {ClassList}
1602 * @api public
1603 */
1604
1605 ClassList$1.prototype.add = function(name) {
1606
1607 // classList
1608 if (this.list) {
1609 this.list.add(name);
1610 return this;
1611 }
1612
1613 // fallback
1614 var arr = this.array();
1615 var i = index(arr, name);
1616 if (!~i) {
1617 arr.push(name);
1618 }
1619
1620 if (defined(this.el.className.baseVal)) {
1621 this.el.className.baseVal = arr.join(' ');
1622 } else {
1623 this.el.className = arr.join(' ');
1624 }
1625
1626 return this;
1627 };
1628
1629 /**
1630 * Remove class `name` when present, or
1631 * pass a regular expression to remove
1632 * any which match.
1633 *
1634 * @param {String|RegExp} name
1635 * @return {ClassList}
1636 * @api public
1637 */
1638
1639 ClassList$1.prototype.remove = function(name) {
1640 if ('[object RegExp]' === toString$1.call(name)) {
1641 return this.removeMatching(name);
1642 }
1643
1644 // classList
1645 if (this.list) {
1646 this.list.remove(name);
1647 return this;
1648 }
1649
1650 // fallback
1651 var arr = this.array();
1652 var i = index(arr, name);
1653 if (~i) {
1654 arr.splice(i, 1);
1655 }
1656 this.el.className.baseVal = arr.join(' ');
1657 return this;
1658 };
1659
1660 /**
1661 * Remove all classes matching `re`.
1662 *
1663 * @param {RegExp} re
1664 * @return {ClassList}
1665 * @api private
1666 */
1667
1668 ClassList$1.prototype.removeMatching = function(re) {
1669 var arr = this.array();
1670 for (var i = 0; i < arr.length; i++) {
1671 if (re.test(arr[i])) {
1672 this.remove(arr[i]);
1673 }
1674 }
1675 return this;
1676 };
1677
1678 /**
1679 * Toggle class `name`, can force state via `force`.
1680 *
1681 * For browsers that support classList, but do not support `force` yet,
1682 * the mistake will be detected and corrected.
1683 *
1684 * @param {String} name
1685 * @param {Boolean} force
1686 * @return {ClassList}
1687 * @api public
1688 */
1689
1690 ClassList$1.prototype.toggle = function(name, force) {
1691 // classList
1692 if (this.list) {
1693 if (defined(force)) {
1694 if (force !== this.list.toggle(name, force)) {
1695 this.list.toggle(name); // toggle again to correct
1696 }
1697 } else {
1698 this.list.toggle(name);
1699 }
1700 return this;
1701 }
1702
1703 // fallback
1704 if (defined(force)) {
1705 if (!force) {
1706 this.remove(name);
1707 } else {
1708 this.add(name);
1709 }
1710 } else {
1711 if (this.has(name)) {
1712 this.remove(name);
1713 } else {
1714 this.add(name);
1715 }
1716 }
1717
1718 return this;
1719 };
1720
1721 /**
1722 * Return an array of classes.
1723 *
1724 * @return {Array}
1725 * @api public
1726 */
1727
1728 ClassList$1.prototype.array = function() {
1729 var className = this.el.getAttribute('class') || '';
1730 var str = className.replace(/^\s+|\s+$/g, '');
1731 var arr = str.split(re$1);
1732 if ('' === arr[0]) {
1733 arr.shift();
1734 }
1735 return arr;
1736 };
1737
1738 /**
1739 * Check if class `name` is present.
1740 *
1741 * @param {String} name
1742 * @return {ClassList}
1743 * @api public
1744 */
1745
1746 ClassList$1.prototype.has =
1747 ClassList$1.prototype.contains = function(name) {
1748 return (
1749 this.list ?
1750 this.list.contains(name) :
1751 !! ~index(this.array(), name)
1752 );
1753 };
1754
1755 function remove$1(element) {
1756 var parent = element.parentNode;
1757
1758 if (parent) {
1759 parent.removeChild(element);
1760 }
1761
1762 return element;
1763 }
1764
1765 /**
1766 * Clear utility
1767 */
1768
1769 /**
1770 * Removes all children from the given element
1771 *
1772 * @param {DOMElement} element
1773 * @return {DOMElement} the element (for chaining)
1774 */
1775 function clear$1(element) {
1776 var child;
1777
1778 while ((child = element.firstChild)) {
1779 remove$1(child);
1780 }
1781
1782 return element;
1783 }
1784
1785 function clone(element) {
1786 return element.cloneNode(true);
1787 }
1788
1789 var ns = {
1790 svg: 'http://www.w3.org/2000/svg'
1791 };
1792
1793 /**
1794 * DOM parsing utility
1795 */
1796
1797 var SVG_START = '<svg xmlns="' + ns.svg + '"';
1798
1799 function parse$1(svg) {
1800
1801 var unwrap = false;
1802
1803 // ensure we import a valid svg document
1804 if (svg.substring(0, 4) === '<svg') {
1805 if (svg.indexOf(ns.svg) === -1) {
1806 svg = SVG_START + svg.substring(4);
1807 }
1808 } else {
1809 // namespace svg
1810 svg = SVG_START + '>' + svg + '</svg>';
1811 unwrap = true;
1812 }
1813
1814 var parsed = parseDocument(svg);
1815
1816 if (!unwrap) {
1817 return parsed;
1818 }
1819
1820 var fragment = document.createDocumentFragment();
1821
1822 var parent = parsed.firstChild;
1823
1824 while (parent.firstChild) {
1825 fragment.appendChild(parent.firstChild);
1826 }
1827
1828 return fragment;
1829 }
1830
1831 function parseDocument(svg) {
1832
1833 var parser;
1834
1835 // parse
1836 parser = new DOMParser();
1837 parser.async = false;
1838
1839 return parser.parseFromString(svg, 'text/xml');
1840 }
1841
1842 /**
1843 * Create utility for SVG elements
1844 */
1845
1846
1847 /**
1848 * Create a specific type from name or SVG markup.
1849 *
1850 * @param {String} name the name or markup of the element
1851 * @param {Object} [attrs] attributes to set on the element
1852 *
1853 * @returns {SVGElement}
1854 */
1855 function create(name, attrs) {
1856 var element;
1857
1858 if (name.charAt(0) === '<') {
1859 element = parse$1(name).firstChild;
1860 element = document.importNode(element, true);
1861 } else {
1862 element = document.createElementNS(ns.svg, name);
1863 }
1864
1865 if (attrs) {
1866 attr$1(element, attrs);
1867 }
1868
1869 return element;
1870 }
1871
1872 /**
1873 * Geometry helpers
1874 */
1875
1876 // fake node used to instantiate svg geometry elements
1877 var node = create('svg');
1878
1879 function extend(object, props) {
1880 var i, k, keys = Object.keys(props);
1881
1882 for (i = 0; (k = keys[i]); i++) {
1883 object[k] = props[k];
1884 }
1885
1886 return object;
1887 }
1888
1889 /**
1890 * Create matrix via args.
1891 *
1892 * @example
1893 *
1894 * createMatrix({ a: 1, b: 1 });
1895 * createMatrix();
1896 * createMatrix(1, 2, 0, 0, 30, 20);
1897 *
1898 * @return {SVGMatrix}
1899 */
1900 function createMatrix(a, b, c, d, e, f) {
1901 var matrix = node.createSVGMatrix();
1902
1903 switch (arguments.length) {
1904 case 0:
1905 return matrix;
1906 case 1:
1907 return extend(matrix, a);
1908 case 6:
1909 return extend(matrix, {
1910 a: a,
1911 b: b,
1912 c: c,
1913 d: d,
1914 e: e,
1915 f: f
1916 });
1917 }
1918 }
1919
1920 function createTransform(matrix) {
1921 if (matrix) {
1922 return node.createSVGTransformFromMatrix(matrix);
1923 } else {
1924 return node.createSVGTransform();
1925 }
1926 }
1927
1928 /**
1929 * Serialization util
1930 */
1931
1932 var TEXT_ENTITIES = /([&<>]{1})/g;
1933 var ATTR_ENTITIES = /([\n\r"]{1})/g;
1934
1935 var ENTITY_REPLACEMENT = {
1936 '&': '&amp;',
1937 '<': '&lt;',
1938 '>': '&gt;',
1939 '"': '\''
1940 };
1941
1942 function escape(str, pattern) {
1943
1944 function replaceFn(match, entity) {
1945 return ENTITY_REPLACEMENT[entity] || entity;
1946 }
1947
1948 return str.replace(pattern, replaceFn);
1949 }
1950
1951 function serialize(node, output) {
1952
1953 var i, len, attrMap, attrNode, childNodes;
1954
1955 switch (node.nodeType) {
1956 // TEXT
1957 case 3:
1958 // replace special XML characters
1959 output.push(escape(node.textContent, TEXT_ENTITIES));
1960 break;
1961
1962 // ELEMENT
1963 case 1:
1964 output.push('<', node.tagName);
1965
1966 if (node.hasAttributes()) {
1967 attrMap = node.attributes;
1968 for (i = 0, len = attrMap.length; i < len; ++i) {
1969 attrNode = attrMap.item(i);
1970 output.push(' ', attrNode.name, '="', escape(attrNode.value, ATTR_ENTITIES), '"');
1971 }
1972 }
1973
1974 if (node.hasChildNodes()) {
1975 output.push('>');
1976 childNodes = node.childNodes;
1977 for (i = 0, len = childNodes.length; i < len; ++i) {
1978 serialize(childNodes.item(i), output);
1979 }
1980 output.push('</', node.tagName, '>');
1981 } else {
1982 output.push('/>');
1983 }
1984 break;
1985
1986 // COMMENT
1987 case 8:
1988 output.push('<!--', escape(node.nodeValue, TEXT_ENTITIES), '-->');
1989 break;
1990
1991 // CDATA
1992 case 4:
1993 output.push('<![CDATA[', node.nodeValue, ']]>');
1994 break;
1995
1996 default:
1997 throw new Error('unable to handle node ' + node.nodeType);
1998 }
1999
2000 return output;
2001 }
2002
2003 /**
2004 * innerHTML like functionality for SVG elements.
2005 * based on innerSVG (https://code.google.com/p/innersvg)
2006 */
2007
2008
2009 function set(element, svg) {
2010
2011 var parsed = parse$1(svg);
2012
2013 // clear element contents
2014 clear$1(element);
2015
2016 if (!svg) {
2017 return;
2018 }
2019
2020 if (!isFragment(parsed)) {
2021 // extract <svg> from parsed document
2022 parsed = parsed.documentElement;
2023 }
2024
2025 var nodes = slice(parsed.childNodes);
2026
2027 // import + append each node
2028 for (var i = 0; i < nodes.length; i++) {
2029 appendTo(nodes[i], element);
2030 }
2031
2032 }
2033
2034 function get(element) {
2035 var child = element.firstChild,
2036 output = [];
2037
2038 while (child) {
2039 serialize(child, output);
2040 child = child.nextSibling;
2041 }
2042
2043 return output.join('');
2044 }
2045
2046 function isFragment(node) {
2047 return node.nodeName === '#document-fragment';
2048 }
2049
2050 function innerSVG(element, svg) {
2051
2052 if (svg !== undefined) {
2053
2054 try {
2055 set(element, svg);
2056 } catch (e) {
2057 throw new Error('error parsing SVG: ' + e.message);
2058 }
2059
2060 return element;
2061 } else {
2062 return get(element);
2063 }
2064 }
2065
2066
2067 function slice(arr) {
2068 return Array.prototype.slice.call(arr);
2069 }
2070
2071 /**
2072 * transform accessor utility
2073 */
2074
2075 function wrapMatrix(transformList, transform) {
2076 if (transform instanceof SVGMatrix) {
2077 return transformList.createSVGTransformFromMatrix(transform);
2078 }
2079
2080 return transform;
2081 }
2082
2083
2084 function setTransforms(transformList, transforms) {
2085 var i, t;
2086
2087 transformList.clear();
2088
2089 for (i = 0; (t = transforms[i]); i++) {
2090 transformList.appendItem(wrapMatrix(transformList, t));
2091 }
2092 }
2093
2094 /**
2095 * Get or set the transforms on the given node.
2096 *
2097 * @param {SVGElement} node
2098 * @param {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms]
2099 *
2100 * @return {SVGTransform} the consolidated transform
2101 */
2102 function transform(node, transforms) {
2103 var transformList = node.transform.baseVal;
2104
2105 if (transforms) {
2106
2107 if (!Array.isArray(transforms)) {
2108 transforms = [ transforms ];
2109 }
2110
2111 setTransforms(transformList, transforms);
2112 }
2113
2114 return transformList.consolidate();
2115 }
2116
2117 var CLASS_PATTERN = /^class /;
2118
2119 function isClass(fn) {
2120 return CLASS_PATTERN.test(fn.toString());
2121 }
2122
2123 function isArray$1(obj) {
2124 return Object.prototype.toString.call(obj) === '[object Array]';
2125 }
2126
2127 function hasOwnProp(obj, prop) {
2128 return Object.prototype.hasOwnProperty.call(obj, prop);
2129 }
2130
2131 function annotate() {
2132 var args = Array.prototype.slice.call(arguments);
2133
2134 if (args.length === 1 && isArray$1(args[0])) {
2135 args = args[0];
2136 }
2137
2138 var fn = args.pop();
2139
2140 fn.$inject = args;
2141
2142 return fn;
2143 }
2144
2145
2146 // Current limitations:
2147 // - can't put into "function arg" comments
2148 // function /* (no parenthesis like this) */ (){}
2149 // function abc( /* xx (no parenthesis like this) */ a, b) {}
2150 //
2151 // Just put the comment before function or inside:
2152 // /* (((this is fine))) */ function(a, b) {}
2153 // function abc(a) { /* (((this is fine))) */}
2154 //
2155 // - can't reliably auto-annotate constructor; we'll match the
2156 // first constructor(...) pattern found which may be the one
2157 // of a nested class, too.
2158
2159 var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m;
2160 var FN_ARGS = /^(?:async )?(?:function\s*)?[^(]*\(\s*([^)]*)\)/m;
2161 var FN_ARG = /\/\*([^*]*)\*\//m;
2162
2163 function parseAnnotations(fn) {
2164
2165 if (typeof fn !== 'function') {
2166 throw new Error('Cannot annotate "' + fn + '". Expected a function!');
2167 }
2168
2169 var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS);
2170
2171 // may parse class without constructor
2172 if (!match) {
2173 return [];
2174 }
2175
2176 return match[1] && match[1].split(',').map(function(arg) {
2177 match = arg.match(FN_ARG);
2178 return match ? match[1].trim() : arg.trim();
2179 }) || [];
2180 }
2181
2182 function Module() {
2183 var providers = [];
2184
2185 this.factory = function(name, factory) {
2186 providers.push([name, 'factory', factory]);
2187 return this;
2188 };
2189
2190 this.value = function(name, value) {
2191 providers.push([name, 'value', value]);
2192 return this;
2193 };
2194
2195 this.type = function(name, type) {
2196 providers.push([name, 'type', type]);
2197 return this;
2198 };
2199
2200 this.forEach = function(iterator) {
2201 providers.forEach(iterator);
2202 };
2203
2204 }
2205
2206 function Injector(modules, parent) {
2207 parent = parent || {
2208 get: function(name, strict) {
2209 currentlyResolving.push(name);
2210
2211 if (strict === false) {
2212 return null;
2213 } else {
2214 throw error('No provider for "' + name + '"!');
2215 }
2216 }
2217 };
2218
2219 var currentlyResolving = [];
2220 var providers = this._providers = Object.create(parent._providers || null);
2221 var instances = this._instances = Object.create(null);
2222
2223 var self = instances.injector = this;
2224
2225 var error = function(msg) {
2226 var stack = currentlyResolving.join(' -> ');
2227 currentlyResolving.length = 0;
2228 return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg);
2229 };
2230
2231 /**
2232 * Return a named service.
2233 *
2234 * @param {String} name
2235 * @param {Boolean} [strict=true] if false, resolve missing services to null
2236 *
2237 * @return {Object}
2238 */
2239 var get = function(name, strict) {
2240 if (!providers[name] && name.indexOf('.') !== -1) {
2241 var parts = name.split('.');
2242 var pivot = get(parts.shift());
2243
2244 while (parts.length) {
2245 pivot = pivot[parts.shift()];
2246 }
2247
2248 return pivot;
2249 }
2250
2251 if (hasOwnProp(instances, name)) {
2252 return instances[name];
2253 }
2254
2255 if (hasOwnProp(providers, name)) {
2256 if (currentlyResolving.indexOf(name) !== -1) {
2257 currentlyResolving.push(name);
2258 throw error('Cannot resolve circular dependency!');
2259 }
2260
2261 currentlyResolving.push(name);
2262 instances[name] = providers[name][0](providers[name][1]);
2263 currentlyResolving.pop();
2264
2265 return instances[name];
2266 }
2267
2268 return parent.get(name, strict);
2269 };
2270
2271 var fnDef = function(fn, locals) {
2272
2273 if (typeof locals === 'undefined') {
2274 locals = {};
2275 }
2276
2277 if (typeof fn !== 'function') {
2278 if (isArray$1(fn)) {
2279 fn = annotate(fn.slice());
2280 } else {
2281 throw new Error('Cannot invoke "' + fn + '". Expected a function!');
2282 }
2283 }
2284
2285 var inject = fn.$inject || parseAnnotations(fn);
2286 var dependencies = inject.map(function(dep) {
2287 if (hasOwnProp(locals, dep)) {
2288 return locals[dep];
2289 } else {
2290 return get(dep);
2291 }
2292 });
2293
2294 return {
2295 fn: fn,
2296 dependencies: dependencies
2297 };
2298 };
2299
2300 var instantiate = function(Type) {
2301 var def = fnDef(Type);
2302
2303 var fn = def.fn,
2304 dependencies = def.dependencies;
2305
2306 // instantiate var args constructor
2307 var Constructor = Function.prototype.bind.apply(fn, [ null ].concat(dependencies));
2308
2309 return new Constructor();
2310 };
2311
2312 var invoke = function(func, context, locals) {
2313 var def = fnDef(func, locals);
2314
2315 var fn = def.fn,
2316 dependencies = def.dependencies;
2317
2318 return fn.apply(context, dependencies);
2319 };
2320
2321
2322 var createPrivateInjectorFactory = function(privateChildInjector) {
2323 return annotate(function(key) {
2324 return privateChildInjector.get(key);
2325 });
2326 };
2327
2328 var createChild = function(modules, forceNewInstances) {
2329 if (forceNewInstances && forceNewInstances.length) {
2330 var fromParentModule = Object.create(null);
2331 var matchedScopes = Object.create(null);
2332
2333 var privateInjectorsCache = [];
2334 var privateChildInjectors = [];
2335 var privateChildFactories = [];
2336
2337 var provider;
2338 var cacheIdx;
2339 var privateChildInjector;
2340 var privateChildInjectorFactory;
2341 for (var name in providers) {
2342 provider = providers[name];
2343
2344 if (forceNewInstances.indexOf(name) !== -1) {
2345 if (provider[2] === 'private') {
2346 cacheIdx = privateInjectorsCache.indexOf(provider[3]);
2347 if (cacheIdx === -1) {
2348 privateChildInjector = provider[3].createChild([], forceNewInstances);
2349 privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector);
2350 privateInjectorsCache.push(provider[3]);
2351 privateChildInjectors.push(privateChildInjector);
2352 privateChildFactories.push(privateChildInjectorFactory);
2353 fromParentModule[name] = [privateChildInjectorFactory, name, 'private', privateChildInjector];
2354 } else {
2355 fromParentModule[name] = [privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx]];
2356 }
2357 } else {
2358 fromParentModule[name] = [provider[2], provider[1]];
2359 }
2360 matchedScopes[name] = true;
2361 }
2362
2363 if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) {
2364 /* jshint -W083 */
2365 forceNewInstances.forEach(function(scope) {
2366 if (provider[1].$scope.indexOf(scope) !== -1) {
2367 fromParentModule[name] = [provider[2], provider[1]];
2368 matchedScopes[scope] = true;
2369 }
2370 });
2371 }
2372 }
2373
2374 forceNewInstances.forEach(function(scope) {
2375 if (!matchedScopes[scope]) {
2376 throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!');
2377 }
2378 });
2379
2380 modules.unshift(fromParentModule);
2381 }
2382
2383 return new Injector(modules, self);
2384 };
2385
2386 var factoryMap = {
2387 factory: invoke,
2388 type: instantiate,
2389 value: function(value) {
2390 return value;
2391 }
2392 };
2393
2394 modules.forEach(function(module) {
2395
2396 function arrayUnwrap(type, value) {
2397 if (type !== 'value' && isArray$1(value)) {
2398 value = annotate(value.slice());
2399 }
2400
2401 return value;
2402 }
2403
2404 // TODO(vojta): handle wrong inputs (modules)
2405 if (module instanceof Module) {
2406 module.forEach(function(provider) {
2407 var name = provider[0];
2408 var type = provider[1];
2409 var value = provider[2];
2410
2411 providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
2412 });
2413 } else if (typeof module === 'object') {
2414 if (module.__exports__) {
2415 var clonedModule = Object.keys(module).reduce(function(m, key) {
2416 if (key.substring(0, 2) !== '__') {
2417 m[key] = module[key];
2418 }
2419 return m;
2420 }, Object.create(null));
2421
2422 var privateInjector = new Injector((module.__modules__ || []).concat([clonedModule]), self);
2423 var getFromPrivateInjector = annotate(function(key) {
2424 return privateInjector.get(key);
2425 });
2426 module.__exports__.forEach(function(key) {
2427 providers[key] = [getFromPrivateInjector, key, 'private', privateInjector];
2428 });
2429 } else {
2430 Object.keys(module).forEach(function(name) {
2431 if (module[name][2] === 'private') {
2432 providers[name] = module[name];
2433 return;
2434 }
2435
2436 var type = module[name][0];
2437 var value = module[name][1];
2438
2439 providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
2440 });
2441 }
2442 }
2443 });
2444
2445 // public API
2446 this.get = get;
2447 this.invoke = invoke;
2448 this.instantiate = instantiate;
2449 this.createChild = createChild;
2450 }
2451
2452 var DEFAULT_RENDER_PRIORITY = 1000;
2453
2454 /**
2455 * The base implementation of shape and connection renderers.
2456 *
2457 * @param {EventBus} eventBus
2458 * @param {number} [renderPriority=1000]
2459 */
2460 function BaseRenderer(eventBus, renderPriority) {
2461 var self = this;
2462
2463 renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY;
2464
2465 eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) {
2466 var type = evt.type,
2467 element = context.element,
2468 visuals = context.gfx;
2469
2470 if (self.canRender(element)) {
2471 if (type === 'render.shape') {
2472 return self.drawShape(visuals, element);
2473 } else {
2474 return self.drawConnection(visuals, element);
2475 }
2476 }
2477 });
2478
2479 eventBus.on([ 'render.getShapePath', 'render.getConnectionPath'], renderPriority, function(evt, element) {
2480 if (self.canRender(element)) {
2481 if (evt.type === 'render.getShapePath') {
2482 return self.getShapePath(element);
2483 } else {
2484 return self.getConnectionPath(element);
2485 }
2486 }
2487 });
2488 }
2489
2490 /**
2491 * Should check whether *this* renderer can render
2492 * the element/connection.
2493 *
2494 * @param {element} element
2495 *
2496 * @returns {boolean}
2497 */
2498 BaseRenderer.prototype.canRender = function() {};
2499
2500 /**
2501 * Provides the shape's snap svg element to be drawn on the `canvas`.
2502 *
2503 * @param {djs.Graphics} visuals
2504 * @param {Shape} shape
2505 *
2506 * @returns {Snap.svg} [returns a Snap.svg paper element ]
2507 */
2508 BaseRenderer.prototype.drawShape = function() {};
2509
2510 /**
2511 * Provides the shape's snap svg element to be drawn on the `canvas`.
2512 *
2513 * @param {djs.Graphics} visuals
2514 * @param {Connection} connection
2515 *
2516 * @returns {Snap.svg} [returns a Snap.svg paper element ]
2517 */
2518 BaseRenderer.prototype.drawConnection = function() {};
2519
2520 /**
2521 * Gets the SVG path of a shape that represents it's visual bounds.
2522 *
2523 * @param {Shape} shape
2524 *
2525 * @return {string} svg path
2526 */
2527 BaseRenderer.prototype.getShapePath = function() {};
2528
2529 /**
2530 * Gets the SVG path of a connection that represents it's visual bounds.
2531 *
2532 * @param {Connection} connection
2533 *
2534 * @return {string} svg path
2535 */
2536 BaseRenderer.prototype.getConnectionPath = function() {};
2537
2538 function componentsToPath(elements) {
2539 return elements.join(',').replace(/,?([A-z]),?/g, '$1');
2540 }
2541
2542 function toSVGPoints(points) {
2543 var result = '';
2544
2545 for (var i = 0, p; (p = points[i]); i++) {
2546 result += p.x + ',' + p.y + ' ';
2547 }
2548
2549 return result;
2550 }
2551
2552 function createLine(points, attrs) {
2553
2554 var line = create('polyline');
2555 attr$1(line, { points: toSVGPoints(points) });
2556
2557 if (attrs) {
2558 attr$1(line, attrs);
2559 }
2560
2561 return line;
2562 }
2563
2564 function updateLine(gfx, points) {
2565 attr$1(gfx, { points: toSVGPoints(points) });
2566
2567 return gfx;
2568 }
2569
2570 /**
2571 * Get parent elements.
2572 *
2573 * @param {Array<djs.model.base>} elements
2574 *
2575 * @returns {Array<djs.model.Base>}
2576 */
2577 function getParents(elements) {
2578
2579 // find elements that are not children of any other elements
2580 return filter(elements, function(element) {
2581 return !find(elements, function(e) {
2582 return e !== element && getParent(element, e);
2583 });
2584 });
2585 }
2586
2587
2588 function getParent(element, parent) {
2589 if (!parent) {
2590 return;
2591 }
2592
2593 if (element === parent) {
2594 return parent;
2595 }
2596
2597 if (!element.parent) {
2598 return;
2599 }
2600
2601 return getParent(element.parent, parent);
2602 }
2603
2604
2605 /**
2606 * Adds an element to a collection and returns true if the
2607 * element was added.
2608 *
2609 * @param {Array<Object>} elements
2610 * @param {Object} e
2611 * @param {boolean} unique
2612 */
2613 function add(elements, e, unique) {
2614 var canAdd = !unique || elements.indexOf(e) === -1;
2615
2616 if (canAdd) {
2617 elements.push(e);
2618 }
2619
2620 return canAdd;
2621 }
2622
2623
2624 /**
2625 * Iterate over each element in a collection, calling the iterator function `fn`
2626 * with (element, index, recursionDepth).
2627 *
2628 * Recurse into all elements that are returned by `fn`.
2629 *
2630 * @param {Object|Array<Object>} elements
2631 * @param {Function} fn iterator function called with (element, index, recursionDepth)
2632 * @param {number} [depth] maximum recursion depth
2633 */
2634 function eachElement(elements, fn, depth) {
2635
2636 depth = depth || 0;
2637
2638 if (!isArray(elements)) {
2639 elements = [ elements ];
2640 }
2641
2642 forEach(elements, function(s, i) {
2643 var filter = fn(s, i, depth);
2644
2645 if (isArray(filter) && filter.length) {
2646 eachElement(filter, fn, depth + 1);
2647 }
2648 });
2649 }
2650
2651
2652 /**
2653 * Collects self + child elements up to a given depth from a list of elements.
2654 *
2655 * @param {djs.model.Base|Array<djs.model.Base>} elements the elements to select the children from
2656 * @param {boolean} unique whether to return a unique result set (no duplicates)
2657 * @param {number} maxDepth the depth to search through or -1 for infinite
2658 *
2659 * @return {Array<djs.model.Base>} found elements
2660 */
2661 function selfAndChildren(elements, unique, maxDepth) {
2662 var result = [],
2663 processedChildren = [];
2664
2665 eachElement(elements, function(element, i, depth) {
2666 add(result, element, unique);
2667
2668 var children = element.children;
2669
2670 // max traversal depth not reached yet
2671 if (maxDepth === -1 || depth < maxDepth) {
2672
2673 // children exist && children not yet processed
2674 if (children && add(processedChildren, children, unique)) {
2675 return children;
2676 }
2677 }
2678 });
2679
2680 return result;
2681 }
2682
2683
2684 /**
2685 * Return self + ALL children for a number of elements
2686 *
2687 * @param {Array<djs.model.Base>} elements to query
2688 * @param {boolean} allowDuplicates to allow duplicates in the result set
2689 *
2690 * @return {Array<djs.model.Base>} the collected elements
2691 */
2692 function selfAndAllChildren(elements, allowDuplicates) {
2693 return selfAndChildren(elements, !allowDuplicates, -1);
2694 }
2695
2696
2697 /**
2698 * Gets the the closure for all selected elements,
2699 * their enclosed children and connections.
2700 *
2701 * @param {Array<djs.model.Base>} elements
2702 * @param {boolean} [isTopLevel=true]
2703 * @param {Object} [existingClosure]
2704 *
2705 * @return {Object} newClosure
2706 */
2707 function getClosure(elements, isTopLevel, closure) {
2708
2709 if (isUndefined(isTopLevel)) {
2710 isTopLevel = true;
2711 }
2712
2713 if (isObject(isTopLevel)) {
2714 closure = isTopLevel;
2715 isTopLevel = true;
2716 }
2717
2718
2719 closure = closure || {};
2720
2721 var allShapes = copyObject(closure.allShapes),
2722 allConnections = copyObject(closure.allConnections),
2723 enclosedElements = copyObject(closure.enclosedElements),
2724 enclosedConnections = copyObject(closure.enclosedConnections);
2725
2726 var topLevel = copyObject(
2727 closure.topLevel,
2728 isTopLevel && groupBy(elements, function(e) { return e.id; })
2729 );
2730
2731
2732 function handleConnection(c) {
2733 if (topLevel[c.source.id] && topLevel[c.target.id]) {
2734 topLevel[c.id] = [ c ];
2735 }
2736
2737 // not enclosed as a child, but maybe logically
2738 // (connecting two moved elements?)
2739 if (allShapes[c.source.id] && allShapes[c.target.id]) {
2740 enclosedConnections[c.id] = enclosedElements[c.id] = c;
2741 }
2742
2743 allConnections[c.id] = c;
2744 }
2745
2746 function handleElement(element) {
2747
2748 enclosedElements[element.id] = element;
2749
2750 if (element.waypoints) {
2751
2752 // remember connection
2753 enclosedConnections[element.id] = allConnections[element.id] = element;
2754 } else {
2755
2756 // remember shape
2757 allShapes[element.id] = element;
2758
2759 // remember all connections
2760 forEach(element.incoming, handleConnection);
2761
2762 forEach(element.outgoing, handleConnection);
2763
2764 // recurse into children
2765 return element.children;
2766 }
2767 }
2768
2769 eachElement(elements, handleElement);
2770
2771 return {
2772 allShapes: allShapes,
2773 allConnections: allConnections,
2774 topLevel: topLevel,
2775 enclosedConnections: enclosedConnections,
2776 enclosedElements: enclosedElements
2777 };
2778 }
2779
2780 /**
2781 * Returns the surrounding bbox for all elements in
2782 * the array or the element primitive.
2783 *
2784 * @param {Array<djs.model.Shape>|djs.model.Shape} elements
2785 * @param {boolean} stopRecursion
2786 */
2787 function getBBox(elements, stopRecursion) {
2788
2789 stopRecursion = !!stopRecursion;
2790 if (!isArray(elements)) {
2791 elements = [elements];
2792 }
2793
2794 var minX,
2795 minY,
2796 maxX,
2797 maxY;
2798
2799 forEach(elements, function(element) {
2800
2801 // If element is a connection the bbox must be computed first
2802 var bbox = element;
2803 if (element.waypoints && !stopRecursion) {
2804 bbox = getBBox(element.waypoints, true);
2805 }
2806
2807 var x = bbox.x,
2808 y = bbox.y,
2809 height = bbox.height || 0,
2810 width = bbox.width || 0;
2811
2812 if (x < minX || minX === undefined) {
2813 minX = x;
2814 }
2815 if (y < minY || minY === undefined) {
2816 minY = y;
2817 }
2818
2819 if ((x + width) > maxX || maxX === undefined) {
2820 maxX = x + width;
2821 }
2822 if ((y + height) > maxY || maxY === undefined) {
2823 maxY = y + height;
2824 }
2825 });
2826
2827 return {
2828 x: minX,
2829 y: minY,
2830 height: maxY - minY,
2831 width: maxX - minX
2832 };
2833 }
2834
2835
2836 /**
2837 * Returns all elements that are enclosed from the bounding box.
2838 *
2839 * * If bbox.(width|height) is not specified the method returns
2840 * all elements with element.x/y > bbox.x/y
2841 * * If only bbox.x or bbox.y is specified, method return all elements with
2842 * e.x > bbox.x or e.y > bbox.y
2843 *
2844 * @param {Array<djs.model.Shape>} elements List of Elements to search through
2845 * @param {djs.model.Shape} bbox the enclosing bbox.
2846 *
2847 * @return {Array<djs.model.Shape>} enclosed elements
2848 */
2849 function getEnclosedElements(elements, bbox) {
2850
2851 var filteredElements = {};
2852
2853 forEach(elements, function(element) {
2854
2855 var e = element;
2856
2857 if (e.waypoints) {
2858 e = getBBox(e);
2859 }
2860
2861 if (!isNumber(bbox.y) && (e.x > bbox.x)) {
2862 filteredElements[element.id] = element;
2863 }
2864 if (!isNumber(bbox.x) && (e.y > bbox.y)) {
2865 filteredElements[element.id] = element;
2866 }
2867 if (e.x > bbox.x && e.y > bbox.y) {
2868 if (isNumber(bbox.width) && isNumber(bbox.height) &&
2869 e.width + e.x < bbox.width + bbox.x &&
2870 e.height + e.y < bbox.height + bbox.y) {
2871
2872 filteredElements[element.id] = element;
2873 } else if (!isNumber(bbox.width) || !isNumber(bbox.height)) {
2874 filteredElements[element.id] = element;
2875 }
2876 }
2877 });
2878
2879 return filteredElements;
2880 }
2881
2882
2883 function getType(element) {
2884
2885 if ('waypoints' in element) {
2886 return 'connection';
2887 }
2888
2889 if ('x' in element) {
2890 return 'shape';
2891 }
2892
2893 return 'root';
2894 }
2895
2896 function isFrameElement(element) {
2897
2898 return !!(element && element.isFrame);
2899 }
2900
2901 // helpers ///////////////////////////////
2902
2903 function copyObject(src1, src2) {
2904 return assign({}, src1 || {}, src2 || {});
2905 }
2906
2907 // apply default renderer with lowest possible priority
2908 // so that it only kicks in if noone else could render
2909 var DEFAULT_RENDER_PRIORITY$1 = 1;
2910
2911 /**
2912 * The default renderer used for shapes and connections.
2913 *
2914 * @param {EventBus} eventBus
2915 * @param {Styles} styles
2916 */
2917 function DefaultRenderer(eventBus, styles) {
2918
2919 //
2920 BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY$1);
2921
2922 this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' });
2923 this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 });
2924 this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 });
2925 }
2926
2927 inherits_browser(DefaultRenderer, BaseRenderer);
2928
2929
2930 DefaultRenderer.prototype.canRender = function() {
2931 return true;
2932 };
2933
2934 DefaultRenderer.prototype.drawShape = function drawShape(visuals, element) {
2935 var rect = create('rect');
2936
2937 attr$1(rect, {
2938 x: 0,
2939 y: 0,
2940 width: element.width || 0,
2941 height: element.height || 0
2942 });
2943
2944 if (isFrameElement(element)) {
2945 attr$1(rect, this.FRAME_STYLE);
2946 } else {
2947 attr$1(rect, this.SHAPE_STYLE);
2948 }
2949
2950 append(visuals, rect);
2951
2952 return rect;
2953 };
2954
2955 DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection) {
2956
2957 var line = createLine(connection.waypoints, this.CONNECTION_STYLE);
2958 append(visuals, line);
2959
2960 return line;
2961 };
2962
2963 DefaultRenderer.prototype.getShapePath = function getShapePath(shape) {
2964
2965 var x = shape.x,
2966 y = shape.y,
2967 width = shape.width,
2968 height = shape.height;
2969
2970 var shapePath = [
2971 ['M', x, y],
2972 ['l', width, 0],
2973 ['l', 0, height],
2974 ['l', -width, 0],
2975 ['z']
2976 ];
2977
2978 return componentsToPath(shapePath);
2979 };
2980
2981 DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) {
2982 var waypoints = connection.waypoints;
2983
2984 var idx, point, connectionPath = [];
2985
2986 for (idx = 0; (point = waypoints[idx]); idx++) {
2987
2988 // take invisible docking into account
2989 // when creating the path
2990 point = point.original || point;
2991
2992 connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]);
2993 }
2994
2995 return componentsToPath(connectionPath);
2996 };
2997
2998
2999 DefaultRenderer.$inject = [ 'eventBus', 'styles' ];
3000
3001 /**
3002 * A component that manages shape styles
3003 */
3004 function Styles() {
3005
3006 var defaultTraits = {
3007
3008 'no-fill': {
3009 fill: 'none'
3010 },
3011 'no-border': {
3012 strokeOpacity: 0.0
3013 },
3014 'no-events': {
3015 pointerEvents: 'none'
3016 }
3017 };
3018
3019 var self = this;
3020
3021 /**
3022 * Builds a style definition from a className, a list of traits and an object of additional attributes.
3023 *
3024 * @param {string} className
3025 * @param {Array<string>} traits
3026 * @param {Object} additionalAttrs
3027 *
3028 * @return {Object} the style defintion
3029 */
3030 this.cls = function(className, traits, additionalAttrs) {
3031 var attrs = this.style(traits, additionalAttrs);
3032
3033 return assign(attrs, { 'class': className });
3034 };
3035
3036 /**
3037 * Builds a style definition from a list of traits and an object of additional attributes.
3038 *
3039 * @param {Array<string>} traits
3040 * @param {Object} additionalAttrs
3041 *
3042 * @return {Object} the style defintion
3043 */
3044 this.style = function(traits, additionalAttrs) {
3045
3046 if (!isArray(traits) && !additionalAttrs) {
3047 additionalAttrs = traits;
3048 traits = [];
3049 }
3050
3051 var attrs = reduce(traits, function(attrs, t) {
3052 return assign(attrs, defaultTraits[t] || {});
3053 }, {});
3054
3055 return additionalAttrs ? assign(attrs, additionalAttrs) : attrs;
3056 };
3057
3058 this.computeStyle = function(custom, traits, defaultStyles) {
3059 if (!isArray(traits)) {
3060 defaultStyles = traits;
3061 traits = [];
3062 }
3063
3064 return self.style(traits || [], assign({}, defaultStyles, custom || {}));
3065 };
3066 }
3067
3068 var DrawModule = {
3069 __init__: [ 'defaultRenderer' ],
3070 defaultRenderer: [ 'type', DefaultRenderer ],
3071 styles: [ 'type', Styles ]
3072 };
3073
3074 /**
3075 * Failsafe remove an element from a collection
3076 *
3077 * @param {Array<Object>} [collection]
3078 * @param {Object} [element]
3079 *
3080 * @return {number} the previous index of the element
3081 */
3082 function remove$2(collection, element) {
3083
3084 if (!collection || !element) {
3085 return -1;
3086 }
3087
3088 var idx = collection.indexOf(element);
3089
3090 if (idx !== -1) {
3091 collection.splice(idx, 1);
3092 }
3093
3094 return idx;
3095 }
3096
3097 /**
3098 * Fail save add an element to the given connection, ensuring
3099 * it does not yet exist.
3100 *
3101 * @param {Array<Object>} collection
3102 * @param {Object} element
3103 * @param {number} idx
3104 */
3105 function add$1(collection, element, idx) {
3106
3107 if (!collection || !element) {
3108 return;
3109 }
3110
3111 if (typeof idx !== 'number') {
3112 idx = -1;
3113 }
3114
3115 var currentIdx = collection.indexOf(element);
3116
3117 if (currentIdx !== -1) {
3118
3119 if (currentIdx === idx) {
3120
3121 // nothing to do, position has not changed
3122 return;
3123 } else {
3124
3125 if (idx !== -1) {
3126
3127 // remove from current position
3128 collection.splice(currentIdx, 1);
3129 } else {
3130
3131 // already exists in collection
3132 return;
3133 }
3134 }
3135 }
3136
3137 if (idx !== -1) {
3138
3139 // insert at specified position
3140 collection.splice(idx, 0, element);
3141 } else {
3142
3143 // push to end
3144 collection.push(element);
3145 }
3146 }
3147
3148
3149 /**
3150 * Fail save get the index of an element in a collection.
3151 *
3152 * @param {Array<Object>} collection
3153 * @param {Object} element
3154 *
3155 * @return {number} the index or -1 if collection or element do
3156 * not exist or the element is not contained.
3157 */
3158 function indexOf$1(collection, element) {
3159
3160 if (!collection || !element) {
3161 return -1;
3162 }
3163
3164 return collection.indexOf(element);
3165 }
3166
3167 /**
3168 * Computes the distance between two points
3169 *
3170 * @param {Point} p
3171 * @param {Point} q
3172 *
3173 * @return {number} distance
3174 */
3175 function pointDistance(a, b) {
3176 if (!a || !b) {
3177 return -1;
3178 }
3179
3180 return Math.sqrt(
3181 Math.pow(a.x - b.x, 2) +
3182 Math.pow(a.y - b.y, 2)
3183 );
3184 }
3185
3186
3187 /**
3188 * Returns true if the point r is on the line between p and q
3189 *
3190 * @param {Point} p
3191 * @param {Point} q
3192 * @param {Point} r
3193 * @param {number} [accuracy=5] accuracy for points on line check (lower is better)
3194 *
3195 * @return {boolean}
3196 */
3197 function pointsOnLine(p, q, r, accuracy) {
3198
3199 if (typeof accuracy === 'undefined') {
3200 accuracy = 5;
3201 }
3202
3203 if (!p || !q || !r) {
3204 return false;
3205 }
3206
3207 var val = (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x),
3208 dist = pointDistance(p, q);
3209
3210 // @see http://stackoverflow.com/a/907491/412190
3211 return Math.abs(val / dist) <= accuracy;
3212 }
3213
3214
3215 var ALIGNED_THRESHOLD = 2;
3216
3217 /**
3218 * Check whether two points are horizontally or vertically aligned.
3219 *
3220 * @param {Array<Point>|Point}
3221 * @param {Point}
3222 *
3223 * @return {string|boolean}
3224 */
3225 function pointsAligned(a, b) {
3226 var points;
3227
3228 if (isArray(a)) {
3229 points = a;
3230 } else {
3231 points = [ a, b ];
3232 }
3233
3234 if (pointsAlignedHorizontally(points)) {
3235 return 'h';
3236 }
3237
3238 if (pointsAlignedVertically(points)) {
3239 return 'v';
3240 }
3241
3242 return false;
3243 }
3244
3245 function pointsAlignedHorizontally(a, b) {
3246 var points;
3247
3248 if (isArray(a)) {
3249 points = a;
3250 } else {
3251 points = [ a, b ];
3252 }
3253
3254 var firstPoint = points.slice().shift();
3255
3256 return every(points, function(point) {
3257 return Math.abs(firstPoint.y - point.y) <= ALIGNED_THRESHOLD;
3258 });
3259 }
3260
3261 function pointsAlignedVertically(a, b) {
3262 var points;
3263
3264 if (isArray(a)) {
3265 points = a;
3266 } else {
3267 points = [ a, b ];
3268 }
3269
3270 var firstPoint = points.slice().shift();
3271
3272 return every(points, function(point) {
3273 return Math.abs(firstPoint.x - point.x) <= ALIGNED_THRESHOLD;
3274 });
3275 }
3276
3277
3278
3279 /**
3280 * Returns true if the point p is inside the rectangle rect
3281 *
3282 * @param {Point} p
3283 * @param {Rect} rect
3284 * @param {number} tolerance
3285 *
3286 * @return {boolean}
3287 */
3288 function pointInRect(p, rect, tolerance) {
3289 tolerance = tolerance || 0;
3290
3291 return p.x > rect.x - tolerance &&
3292 p.y > rect.y - tolerance &&
3293 p.x < rect.x + rect.width + tolerance &&
3294 p.y < rect.y + rect.height + tolerance;
3295 }
3296
3297 /**
3298 * Returns a point in the middle of points p and q
3299 *
3300 * @param {Point} p
3301 * @param {Point} q
3302 *
3303 * @return {Point} middle point
3304 */
3305 function getMidPoint(p, q) {
3306 return {
3307 x: Math.round(p.x + ((q.x - p.x) / 2.0)),
3308 y: Math.round(p.y + ((q.y - p.y) / 2.0))
3309 };
3310 }
3311
3312 /**
3313 * This file contains source code adapted from Snap.svg (licensed Apache-2.0).
3314 *
3315 * @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js
3316 */
3317
3318 /* eslint no-fallthrough: "off" */
3319
3320 var p2s = /,?([a-z]),?/gi,
3321 toFloat = parseFloat,
3322 math = Math,
3323 PI = math.PI,
3324 mmin = math.min,
3325 mmax = math.max,
3326 pow = math.pow,
3327 abs = math.abs,
3328 pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?[\s]*,?[\s]*)+)/ig,
3329 pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)[\s]*,?[\s]*/ig;
3330
3331 var isArray$2 = Array.isArray || function(o) { return o instanceof Array; };
3332
3333 function hasProperty(obj, property) {
3334 return Object.prototype.hasOwnProperty.call(obj, property);
3335 }
3336
3337 function clone$1(obj) {
3338
3339 if (typeof obj == 'function' || Object(obj) !== obj) {
3340 return obj;
3341 }
3342
3343 var res = new obj.constructor;
3344
3345 for (var key in obj) {
3346 if (hasProperty(obj, key)) {
3347 res[key] = clone$1(obj[key]);
3348 }
3349 }
3350
3351 return res;
3352 }
3353
3354 function repush(array, item) {
3355 for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
3356 return array.push(array.splice(i, 1)[0]);
3357 }
3358 }
3359
3360 function cacher(f) {
3361
3362 function newf() {
3363
3364 var arg = Array.prototype.slice.call(arguments, 0),
3365 args = arg.join('\u2400'),
3366 cache = newf.cache = newf.cache || {},
3367 count = newf.count = newf.count || [];
3368
3369 if (hasProperty(cache, args)) {
3370 repush(count, args);
3371 return cache[args];
3372 }
3373
3374 count.length >= 1e3 && delete cache[count.shift()];
3375 count.push(args);
3376 cache[args] = f.apply(0, arg);
3377
3378 return cache[args];
3379 }
3380 return newf;
3381 }
3382
3383 function parsePathString(pathString) {
3384
3385 if (!pathString) {
3386 return null;
3387 }
3388
3389 var pth = paths(pathString);
3390
3391 if (pth.arr) {
3392 return clone$1(pth.arr);
3393 }
3394
3395 var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 },
3396 data = [];
3397
3398 if (isArray$2(pathString) && isArray$2(pathString[0])) { // rough assumption
3399 data = clone$1(pathString);
3400 }
3401
3402 if (!data.length) {
3403
3404 String(pathString).replace(pathCommand, function(a, b, c) {
3405 var params = [],
3406 name = b.toLowerCase();
3407
3408 c.replace(pathValues, function(a, b) {
3409 b && params.push(+b);
3410 });
3411
3412 if (name == 'm' && params.length > 2) {
3413 data.push([b].concat(params.splice(0, 2)));
3414 name = 'l';
3415 b = b == 'm' ? 'l' : 'L';
3416 }
3417
3418 while (params.length >= paramCounts[name]) {
3419 data.push([b].concat(params.splice(0, paramCounts[name])));
3420 if (!paramCounts[name]) {
3421 break;
3422 }
3423 }
3424 });
3425 }
3426
3427 data.toString = paths.toString;
3428 pth.arr = clone$1(data);
3429
3430 return data;
3431 }
3432
3433 function paths(ps) {
3434 var p = paths.ps = paths.ps || {};
3435
3436 if (p[ps]) {
3437 p[ps].sleep = 100;
3438 } else {
3439 p[ps] = {
3440 sleep: 100
3441 };
3442 }
3443
3444 setTimeout(function() {
3445 for (var key in p) {
3446 if (hasProperty(p, key) && key != ps) {
3447 p[key].sleep--;
3448 !p[key].sleep && delete p[key];
3449 }
3450 }
3451 });
3452
3453 return p[ps];
3454 }
3455
3456 function rectBBox(x, y, width, height) {
3457
3458 if (arguments.length === 1) {
3459 y = x.y;
3460 width = x.width;
3461 height = x.height;
3462 x = x.x;
3463 }
3464
3465 return {
3466 x: x,
3467 y: y,
3468 width: width,
3469 height: height,
3470 x2: x + width,
3471 y2: y + height
3472 };
3473 }
3474
3475 function pathToString() {
3476 return this.join(',').replace(p2s, '$1');
3477 }
3478
3479 function pathClone(pathArray) {
3480 var res = clone$1(pathArray);
3481 res.toString = pathToString;
3482 return res;
3483 }
3484
3485 function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
3486 var t1 = 1 - t,
3487 t13 = pow(t1, 3),
3488 t12 = pow(t1, 2),
3489 t2 = t * t,
3490 t3 = t2 * t,
3491 x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
3492 y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y;
3493
3494 return {
3495 x: fixError(x),
3496 y: fixError(y)
3497 };
3498 }
3499
3500 function bezierBBox(points) {
3501
3502 var bbox = curveBBox.apply(null, points);
3503
3504 return rectBBox(
3505 bbox.x0,
3506 bbox.y0,
3507 bbox.x1 - bbox.x0,
3508 bbox.y1 - bbox.y0
3509 );
3510 }
3511
3512 function isPointInsideBBox(bbox, x, y) {
3513 return x >= bbox.x &&
3514 x <= bbox.x + bbox.width &&
3515 y >= bbox.y &&
3516 y <= bbox.y + bbox.height;
3517 }
3518
3519 function isBBoxIntersect(bbox1, bbox2) {
3520 bbox1 = rectBBox(bbox1);
3521 bbox2 = rectBBox(bbox2);
3522 return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
3523 || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
3524 || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
3525 || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
3526 || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
3527 || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
3528 || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
3529 || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
3530 || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
3531 || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
3532 && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
3533 || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
3534 }
3535
3536 function base3(t, p1, p2, p3, p4) {
3537 var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
3538 t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
3539 return t * t2 - 3 * p1 + 3 * p2;
3540 }
3541
3542 function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
3543
3544 if (z == null) {
3545 z = 1;
3546 }
3547
3548 z = z > 1 ? 1 : z < 0 ? 0 : z;
3549
3550 var z2 = z / 2,
3551 n = 12,
3552 Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
3553 Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
3554 sum = 0;
3555
3556 for (var i = 0; i < n; i++) {
3557 var ct = z2 * Tvalues[i] + z2,
3558 xbase = base3(ct, x1, x2, x3, x4),
3559 ybase = base3(ct, y1, y2, y3, y4),
3560 comb = xbase * xbase + ybase * ybase;
3561
3562 sum += Cvalues[i] * math.sqrt(comb);
3563 }
3564
3565 return z2 * sum;
3566 }
3567
3568
3569 function intersectLines(x1, y1, x2, y2, x3, y3, x4, y4) {
3570
3571 if (
3572 mmax(x1, x2) < mmin(x3, x4) ||
3573 mmin(x1, x2) > mmax(x3, x4) ||
3574 mmax(y1, y2) < mmin(y3, y4) ||
3575 mmin(y1, y2) > mmax(y3, y4)
3576 ) {
3577 return;
3578 }
3579
3580 var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
3581 ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
3582 denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
3583
3584 if (!denominator) {
3585 return;
3586 }
3587
3588 var px = fixError(nx / denominator),
3589 py = fixError(ny / denominator),
3590 px2 = +px.toFixed(2),
3591 py2 = +py.toFixed(2);
3592
3593 if (
3594 px2 < +mmin(x1, x2).toFixed(2) ||
3595 px2 > +mmax(x1, x2).toFixed(2) ||
3596 px2 < +mmin(x3, x4).toFixed(2) ||
3597 px2 > +mmax(x3, x4).toFixed(2) ||
3598 py2 < +mmin(y1, y2).toFixed(2) ||
3599 py2 > +mmax(y1, y2).toFixed(2) ||
3600 py2 < +mmin(y3, y4).toFixed(2) ||
3601 py2 > +mmax(y3, y4).toFixed(2)
3602 ) {
3603 return;
3604 }
3605
3606 return { x: px, y: py };
3607 }
3608
3609 function fixError(number) {
3610 return Math.round(number * 100000000000) / 100000000000;
3611 }
3612
3613 function findBezierIntersections(bez1, bez2, justCount) {
3614 var bbox1 = bezierBBox(bez1),
3615 bbox2 = bezierBBox(bez2);
3616
3617 if (!isBBoxIntersect(bbox1, bbox2)) {
3618 return justCount ? 0 : [];
3619 }
3620
3621 // As an optimization, lines will have only 1 segment
3622
3623 var l1 = bezlen.apply(0, bez1),
3624 l2 = bezlen.apply(0, bez2),
3625 n1 = isLine(bez1) ? 1 : ~~(l1 / 5) || 1,
3626 n2 = isLine(bez2) ? 1 : ~~(l2 / 5) || 1,
3627 dots1 = [],
3628 dots2 = [],
3629 xy = {},
3630 res = justCount ? 0 : [];
3631
3632 for (var i = 0; i < n1 + 1; i++) {
3633 var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
3634 dots1.push({ x: p.x, y: p.y, t: i / n1 });
3635 }
3636
3637 for (i = 0; i < n2 + 1; i++) {
3638 p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
3639 dots2.push({ x: p.x, y: p.y, t: i / n2 });
3640 }
3641
3642 for (i = 0; i < n1; i++) {
3643
3644 for (var j = 0; j < n2; j++) {
3645 var di = dots1[i],
3646 di1 = dots1[i + 1],
3647 dj = dots2[j],
3648 dj1 = dots2[j + 1],
3649 ci = abs(di1.x - di.x) < .01 ? 'y' : 'x',
3650 cj = abs(dj1.x - dj.x) < .01 ? 'y' : 'x',
3651 is = intersectLines(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y),
3652 key;
3653
3654 if (is) {
3655 key = is.x.toFixed(9) + '#' + is.y.toFixed(9);
3656
3657 if (xy[key]) {
3658 continue;
3659 }
3660
3661 xy[key] = true;
3662
3663 var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
3664 t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
3665
3666 if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
3667
3668 if (justCount) {
3669 res++;
3670 } else {
3671 res.push({
3672 x: is.x,
3673 y: is.y,
3674 t1: t1,
3675 t2: t2
3676 });
3677 }
3678 }
3679 }
3680 }
3681 }
3682
3683 return res;
3684 }
3685
3686
3687 /**
3688 * Find or counts the intersections between two SVG paths.
3689 *
3690 * Returns a number in counting mode and a list of intersections otherwise.
3691 *
3692 * A single intersection entry contains the intersection coordinates (x, y)
3693 * as well as additional information regarding the intersecting segments
3694 * on each path (segment1, segment2) and the relative location of the
3695 * intersection on these segments (t1, t2).
3696 *
3697 * The path may be an SVG path string or a list of path components
3698 * such as `[ [ 'M', 0, 10 ], [ 'L', 20, 0 ] ]`.
3699 *
3700 * @example
3701 *
3702 * var intersections = findPathIntersections(
3703 * 'M0,0L100,100',
3704 * [ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ]
3705 * );
3706 *
3707 * // intersections = [
3708 * // { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 }
3709 * // ]
3710 *
3711 * @param {String|Array<PathDef>} path1
3712 * @param {String|Array<PathDef>} path2
3713 * @param {Boolean} [justCount=false]
3714 *
3715 * @return {Array<Intersection>|Number}
3716 */
3717 function findPathIntersections(path1, path2, justCount) {
3718 path1 = pathToCurve(path1);
3719 path2 = pathToCurve(path2);
3720
3721 var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
3722 res = justCount ? 0 : [];
3723
3724 for (var i = 0, ii = path1.length; i < ii; i++) {
3725 var pi = path1[i];
3726
3727 if (pi[0] == 'M') {
3728 x1 = x1m = pi[1];
3729 y1 = y1m = pi[2];
3730 } else {
3731
3732 if (pi[0] == 'C') {
3733 bez1 = [x1, y1].concat(pi.slice(1));
3734 x1 = bez1[6];
3735 y1 = bez1[7];
3736 } else {
3737 bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
3738 x1 = x1m;
3739 y1 = y1m;
3740 }
3741
3742 for (var j = 0, jj = path2.length; j < jj; j++) {
3743 var pj = path2[j];
3744
3745 if (pj[0] == 'M') {
3746 x2 = x2m = pj[1];
3747 y2 = y2m = pj[2];
3748 } else {
3749
3750 if (pj[0] == 'C') {
3751 bez2 = [x2, y2].concat(pj.slice(1));
3752 x2 = bez2[6];
3753 y2 = bez2[7];
3754 } else {
3755 bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
3756 x2 = x2m;
3757 y2 = y2m;
3758 }
3759
3760 var intr = findBezierIntersections(bez1, bez2, justCount);
3761
3762 if (justCount) {
3763 res += intr;
3764 } else {
3765
3766 for (var k = 0, kk = intr.length; k < kk; k++) {
3767 intr[k].segment1 = i;
3768 intr[k].segment2 = j;
3769 intr[k].bez1 = bez1;
3770 intr[k].bez2 = bez2;
3771 }
3772
3773 res = res.concat(intr);
3774 }
3775 }
3776 }
3777 }
3778 }
3779
3780 return res;
3781 }
3782
3783
3784 function pathToAbsolute(pathArray) {
3785 var pth = paths(pathArray);
3786
3787 if (pth.abs) {
3788 return pathClone(pth.abs);
3789 }
3790
3791 if (!isArray$2(pathArray) || !isArray$2(pathArray && pathArray[0])) { // rough assumption
3792 pathArray = parsePathString(pathArray);
3793 }
3794
3795 if (!pathArray || !pathArray.length) {
3796 return [['M', 0, 0]];
3797 }
3798
3799 var res = [],
3800 x = 0,
3801 y = 0,
3802 mx = 0,
3803 my = 0,
3804 start = 0,
3805 pa0;
3806
3807 if (pathArray[0][0] == 'M') {
3808 x = +pathArray[0][1];
3809 y = +pathArray[0][2];
3810 mx = x;
3811 my = y;
3812 start++;
3813 res[0] = ['M', x, y];
3814 }
3815
3816 for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
3817 res.push(r = []);
3818 pa = pathArray[i];
3819 pa0 = pa[0];
3820
3821 if (pa0 != pa0.toUpperCase()) {
3822 r[0] = pa0.toUpperCase();
3823
3824 switch (r[0]) {
3825 case 'A':
3826 r[1] = pa[1];
3827 r[2] = pa[2];
3828 r[3] = pa[3];
3829 r[4] = pa[4];
3830 r[5] = pa[5];
3831 r[6] = +pa[6] + x;
3832 r[7] = +pa[7] + y;
3833 break;
3834 case 'V':
3835 r[1] = +pa[1] + y;
3836 break;
3837 case 'H':
3838 r[1] = +pa[1] + x;
3839 break;
3840 case 'M':
3841 mx = +pa[1] + x;
3842 my = +pa[2] + y;
3843 default:
3844 for (var j = 1, jj = pa.length; j < jj; j++) {
3845 r[j] = +pa[j] + ((j % 2) ? x : y);
3846 }
3847 }
3848 } else {
3849 for (var k = 0, kk = pa.length; k < kk; k++) {
3850 r[k] = pa[k];
3851 }
3852 }
3853 pa0 = pa0.toUpperCase();
3854
3855 switch (r[0]) {
3856 case 'Z':
3857 x = +mx;
3858 y = +my;
3859 break;
3860 case 'H':
3861 x = r[1];
3862 break;
3863 case 'V':
3864 y = r[1];
3865 break;
3866 case 'M':
3867 mx = r[r.length - 2];
3868 my = r[r.length - 1];
3869 default:
3870 x = r[r.length - 2];
3871 y = r[r.length - 1];
3872 }
3873 }
3874
3875 res.toString = pathToString;
3876 pth.abs = pathClone(res);
3877
3878 return res;
3879 }
3880
3881 function isLine(bez) {
3882 return (
3883 bez[0] === bez[2] &&
3884 bez[1] === bez[3] &&
3885 bez[4] === bez[6] &&
3886 bez[5] === bez[7]
3887 );
3888 }
3889
3890 function lineToCurve(x1, y1, x2, y2) {
3891 return [
3892 x1, y1, x2,
3893 y2, x2, y2
3894 ];
3895 }
3896
3897 function qubicToCurve(x1, y1, ax, ay, x2, y2) {
3898 var _13 = 1 / 3,
3899 _23 = 2 / 3;
3900
3901 return [
3902 _13 * x1 + _23 * ax,
3903 _13 * y1 + _23 * ay,
3904 _13 * x2 + _23 * ax,
3905 _13 * y2 + _23 * ay,
3906 x2,
3907 y2
3908 ];
3909 }
3910
3911 function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
3912
3913 // for more information of where this math came from visit:
3914 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
3915 var _120 = PI * 120 / 180,
3916 rad = PI / 180 * (+angle || 0),
3917 res = [],
3918 xy,
3919 rotate = cacher(function(x, y, rad) {
3920 var X = x * math.cos(rad) - y * math.sin(rad),
3921 Y = x * math.sin(rad) + y * math.cos(rad);
3922
3923 return { x: X, y: Y };
3924 });
3925
3926 if (!recursive) {
3927 xy = rotate(x1, y1, -rad);
3928 x1 = xy.x;
3929 y1 = xy.y;
3930 xy = rotate(x2, y2, -rad);
3931 x2 = xy.x;
3932 y2 = xy.y;
3933
3934 var x = (x1 - x2) / 2,
3935 y = (y1 - y2) / 2;
3936
3937 var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
3938
3939 if (h > 1) {
3940 h = math.sqrt(h);
3941 rx = h * rx;
3942 ry = h * ry;
3943 }
3944
3945 var rx2 = rx * rx,
3946 ry2 = ry * ry,
3947 k = (large_arc_flag == sweep_flag ? -1 : 1) *
3948 math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
3949 cx = k * rx * y / ry + (x1 + x2) / 2,
3950 cy = k * -ry * x / rx + (y1 + y2) / 2,
3951 f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
3952 f2 = math.asin(((y2 - cy) / ry).toFixed(9));
3953
3954 f1 = x1 < cx ? PI - f1 : f1;
3955 f2 = x2 < cx ? PI - f2 : f2;
3956 f1 < 0 && (f1 = PI * 2 + f1);
3957 f2 < 0 && (f2 = PI * 2 + f2);
3958
3959 if (sweep_flag && f1 > f2) {
3960 f1 = f1 - PI * 2;
3961 }
3962 if (!sweep_flag && f2 > f1) {
3963 f2 = f2 - PI * 2;
3964 }
3965 } else {
3966 f1 = recursive[0];
3967 f2 = recursive[1];
3968 cx = recursive[2];
3969 cy = recursive[3];
3970 }
3971
3972 var df = f2 - f1;
3973
3974 if (abs(df) > _120) {
3975 var f2old = f2,
3976 x2old = x2,
3977 y2old = y2;
3978
3979 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
3980 x2 = cx + rx * math.cos(f2);
3981 y2 = cy + ry * math.sin(f2);
3982 res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
3983 }
3984
3985 df = f2 - f1;
3986
3987 var c1 = math.cos(f1),
3988 s1 = math.sin(f1),
3989 c2 = math.cos(f2),
3990 s2 = math.sin(f2),
3991 t = math.tan(df / 4),
3992 hx = 4 / 3 * rx * t,
3993 hy = 4 / 3 * ry * t,
3994 m1 = [x1, y1],
3995 m2 = [x1 + hx * s1, y1 - hy * c1],
3996 m3 = [x2 + hx * s2, y2 - hy * c2],
3997 m4 = [x2, y2];
3998
3999 m2[0] = 2 * m1[0] - m2[0];
4000 m2[1] = 2 * m1[1] - m2[1];
4001
4002 if (recursive) {
4003 return [m2, m3, m4].concat(res);
4004 } else {
4005 res = [m2, m3, m4].concat(res).join().split(',');
4006 var newres = [];
4007
4008 for (var i = 0, ii = res.length; i < ii; i++) {
4009 newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
4010 }
4011
4012 return newres;
4013 }
4014 }
4015
4016 // Returns bounding box of cubic bezier curve.
4017 // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
4018 // Original version: NISHIO Hirokazu
4019 // Modifications: https://github.com/timo22345
4020 function curveBBox(x0, y0, x1, y1, x2, y2, x3, y3) {
4021 var tvalues = [],
4022 bounds = [[], []],
4023 a, b, c, t, t1, t2, b2ac, sqrtb2ac;
4024
4025 for (var i = 0; i < 2; ++i) {
4026
4027 if (i == 0) {
4028 b = 6 * x0 - 12 * x1 + 6 * x2;
4029 a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
4030 c = 3 * x1 - 3 * x0;
4031 } else {
4032 b = 6 * y0 - 12 * y1 + 6 * y2;
4033 a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
4034 c = 3 * y1 - 3 * y0;
4035 }
4036
4037 if (abs(a) < 1e-12) {
4038
4039 if (abs(b) < 1e-12) {
4040 continue;
4041 }
4042
4043 t = -c / b;
4044
4045 if (0 < t && t < 1) {
4046 tvalues.push(t);
4047 }
4048
4049 continue;
4050 }
4051
4052 b2ac = b * b - 4 * c * a;
4053 sqrtb2ac = math.sqrt(b2ac);
4054
4055 if (b2ac < 0) {
4056 continue;
4057 }
4058
4059 t1 = (-b + sqrtb2ac) / (2 * a);
4060
4061 if (0 < t1 && t1 < 1) {
4062 tvalues.push(t1);
4063 }
4064
4065 t2 = (-b - sqrtb2ac) / (2 * a);
4066
4067 if (0 < t2 && t2 < 1) {
4068 tvalues.push(t2);
4069 }
4070 }
4071
4072 var j = tvalues.length,
4073 jlen = j,
4074 mt;
4075
4076 while (j--) {
4077 t = tvalues[j];
4078 mt = 1 - t;
4079 bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
4080 bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
4081 }
4082
4083 bounds[0][jlen] = x0;
4084 bounds[1][jlen] = y0;
4085 bounds[0][jlen + 1] = x3;
4086 bounds[1][jlen + 1] = y3;
4087 bounds[0].length = bounds[1].length = jlen + 2;
4088
4089 return {
4090 x0: mmin.apply(0, bounds[0]),
4091 y0: mmin.apply(0, bounds[1]),
4092 x1: mmax.apply(0, bounds[0]),
4093 y1: mmax.apply(0, bounds[1])
4094 };
4095 }
4096
4097 function pathToCurve(path) {
4098
4099 var pth = paths(path);
4100
4101 // return cached curve, if existing
4102 if (pth.curve) {
4103 return pathClone(pth.curve);
4104 }
4105
4106 var curvedPath = pathToAbsolute(path),
4107 attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null },
4108 processPath = function(path, d, pathCommand) {
4109 var nx, ny;
4110
4111 if (!path) {
4112 return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
4113 }
4114
4115 !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null);
4116
4117 switch (path[0]) {
4118 case 'M':
4119 d.X = path[1];
4120 d.Y = path[2];
4121 break;
4122 case 'A':
4123 path = ['C'].concat(arcToCurve.apply(0, [d.x, d.y].concat(path.slice(1))));
4124 break;
4125 case 'S':
4126 if (pathCommand == 'C' || pathCommand == 'S') {
4127
4128 // In 'S' case we have to take into account, if the previous command is C/S.
4129 nx = d.x * 2 - d.bx;
4130
4131 // And reflect the previous
4132 ny = d.y * 2 - d.by;
4133
4134 // command's control point relative to the current point.
4135 }
4136 else {
4137
4138 // or some else or nothing
4139 nx = d.x;
4140 ny = d.y;
4141 }
4142 path = ['C', nx, ny].concat(path.slice(1));
4143 break;
4144 case 'T':
4145 if (pathCommand == 'Q' || pathCommand == 'T') {
4146
4147 // In 'T' case we have to take into account, if the previous command is Q/T.
4148 d.qx = d.x * 2 - d.qx;
4149
4150 // And make a reflection similar
4151 d.qy = d.y * 2 - d.qy;
4152
4153 // to case 'S'.
4154 }
4155 else {
4156
4157 // or something else or nothing
4158 d.qx = d.x;
4159 d.qy = d.y;
4160 }
4161 path = ['C'].concat(qubicToCurve(d.x, d.y, d.qx, d.qy, path[1], path[2]));
4162 break;
4163 case 'Q':
4164 d.qx = path[1];
4165 d.qy = path[2];
4166 path = ['C'].concat(qubicToCurve(d.x, d.y, path[1], path[2], path[3], path[4]));
4167 break;
4168 case 'L':
4169 path = ['C'].concat(lineToCurve(d.x, d.y, path[1], path[2]));
4170 break;
4171 case 'H':
4172 path = ['C'].concat(lineToCurve(d.x, d.y, path[1], d.y));
4173 break;
4174 case 'V':
4175 path = ['C'].concat(lineToCurve(d.x, d.y, d.x, path[1]));
4176 break;
4177 case 'Z':
4178 path = ['C'].concat(lineToCurve(d.x, d.y, d.X, d.Y));
4179 break;
4180 }
4181
4182 return path;
4183 },
4184
4185 fixArc = function(pp, i) {
4186
4187 if (pp[i].length > 7) {
4188 pp[i].shift();
4189 var pi = pp[i];
4190
4191 while (pi.length) {
4192 pathCommands[i] = 'A'; // if created multiple C:s, their original seg is saved
4193 pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
4194 }
4195
4196 pp.splice(i, 1);
4197 ii = curvedPath.length;
4198 }
4199 },
4200
4201 pathCommands = [], // path commands of original path p
4202 pfirst = '', // temporary holder for original path command
4203 pathCommand = ''; // holder for previous path command of original path
4204
4205 for (var i = 0, ii = curvedPath.length; i < ii; i++) {
4206 curvedPath[i] && (pfirst = curvedPath[i][0]); // save current path command
4207
4208 if (pfirst != 'C') // C is not saved yet, because it may be result of conversion
4209 {
4210 pathCommands[i] = pfirst; // Save current path command
4211 i && (pathCommand = pathCommands[i - 1]); // Get previous path command pathCommand
4212 }
4213 curvedPath[i] = processPath(curvedPath[i], attrs, pathCommand); // Previous path command is inputted to processPath
4214
4215 if (pathCommands[i] != 'A' && pfirst == 'C') pathCommands[i] = 'C'; // A is the only command
4216 // which may produce multiple C:s
4217 // so we have to make sure that C is also C in original path
4218
4219 fixArc(curvedPath, i); // fixArc adds also the right amount of A:s to pathCommands
4220
4221 var seg = curvedPath[i],
4222 seglen = seg.length;
4223
4224 attrs.x = seg[seglen - 2];
4225 attrs.y = seg[seglen - 1];
4226 attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
4227 attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
4228 }
4229
4230 // cache curve
4231 pth.curve = pathClone(curvedPath);
4232
4233 return curvedPath;
4234 }
4235
4236 var intersect = findPathIntersections;
4237
4238 function roundBounds(bounds) {
4239 return {
4240 x: Math.round(bounds.x),
4241 y: Math.round(bounds.y),
4242 width: Math.round(bounds.width),
4243 height: Math.round(bounds.height)
4244 };
4245 }
4246
4247
4248 function roundPoint(point) {
4249
4250 return {
4251 x: Math.round(point.x),
4252 y: Math.round(point.y)
4253 };
4254 }
4255
4256
4257 /**
4258 * Convert the given bounds to a { top, left, bottom, right } descriptor.
4259 *
4260 * @param {Bounds|Point} bounds
4261 *
4262 * @return {Object}
4263 */
4264 function asTRBL(bounds) {
4265 return {
4266 top: bounds.y,
4267 right: bounds.x + (bounds.width || 0),
4268 bottom: bounds.y + (bounds.height || 0),
4269 left: bounds.x
4270 };
4271 }
4272
4273
4274 /**
4275 * Convert a { top, left, bottom, right } to an objects bounds.
4276 *
4277 * @param {Object} trbl
4278 *
4279 * @return {Bounds}
4280 */
4281 function asBounds(trbl) {
4282 return {
4283 x: trbl.left,
4284 y: trbl.top,
4285 width: trbl.right - trbl.left,
4286 height: trbl.bottom - trbl.top
4287 };
4288 }
4289
4290
4291 /**
4292 * Get the mid of the given bounds or point.
4293 *
4294 * @param {Bounds|Point} bounds
4295 *
4296 * @return {Point}
4297 */
4298 function getMid(bounds) {
4299 return roundPoint({
4300 x: bounds.x + (bounds.width || 0) / 2,
4301 y: bounds.y + (bounds.height || 0) / 2
4302 });
4303 }
4304
4305
4306 // orientation utils //////////////////////
4307
4308 /**
4309 * Get orientation of the given rectangle with respect to
4310 * the reference rectangle.
4311 *
4312 * A padding (positive or negative) may be passed to influence
4313 * horizontal / vertical orientation and intersection.
4314 *
4315 * @param {Bounds} rect
4316 * @param {Bounds} reference
4317 * @param {Point|number} padding
4318 *
4319 * @return {string} the orientation; one of top, top-left, left, ..., bottom, right or intersect.
4320 */
4321 function getOrientation(rect, reference, padding) {
4322
4323 padding = padding || 0;
4324
4325 // make sure we can use an object, too
4326 // for individual { x, y } padding
4327 if (!isObject(padding)) {
4328 padding = { x: padding, y: padding };
4329 }
4330
4331
4332 var rectOrientation = asTRBL(rect),
4333 referenceOrientation = asTRBL(reference);
4334
4335 var top = rectOrientation.bottom + padding.y <= referenceOrientation.top,
4336 right = rectOrientation.left - padding.x >= referenceOrientation.right,
4337 bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom,
4338 left = rectOrientation.right + padding.x <= referenceOrientation.left;
4339
4340 var vertical = top ? 'top' : (bottom ? 'bottom' : null),
4341 horizontal = left ? 'left' : (right ? 'right' : null);
4342
4343 if (horizontal && vertical) {
4344 return vertical + '-' + horizontal;
4345 } else {
4346 return horizontal || vertical || 'intersect';
4347 }
4348 }
4349
4350
4351 // intersection utils //////////////////////
4352
4353 /**
4354 * Get intersection between an element and a line path.
4355 *
4356 * @param {PathDef} elementPath
4357 * @param {PathDef} linePath
4358 * @param {boolean} cropStart crop from start or end
4359 *
4360 * @return {Point}
4361 */
4362 function getElementLineIntersection(elementPath, linePath, cropStart) {
4363
4364 var intersections = getIntersections(elementPath, linePath);
4365
4366 // recognize intersections
4367 // only one -> choose
4368 // two close together -> choose first
4369 // two or more distinct -> pull out appropriate one
4370 // none -> ok (fallback to point itself)
4371 if (intersections.length === 1) {
4372 return roundPoint(intersections[0]);
4373 } else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
4374 return roundPoint(intersections[0]);
4375 } else if (intersections.length > 1) {
4376
4377 // sort by intersections based on connection segment +
4378 // distance from start
4379 intersections = sortBy(intersections, function(i) {
4380 var distance = Math.floor(i.t2 * 100) || 1;
4381
4382 distance = 100 - distance;
4383
4384 distance = (distance < 10 ? '0' : '') + distance;
4385
4386 // create a sort string that makes sure we sort
4387 // line segment ASC + line segment position DESC (for cropStart)
4388 // line segment ASC + line segment position ASC (for cropEnd)
4389 return i.segment2 + '#' + distance;
4390 });
4391
4392 return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
4393 }
4394
4395 return null;
4396 }
4397
4398
4399 function getIntersections(a, b) {
4400 return intersect(a, b);
4401 }
4402
4403
4404 function filterRedundantWaypoints(waypoints) {
4405
4406 // alter copy of waypoints, not original
4407 waypoints = waypoints.slice();
4408
4409 var idx = 0,
4410 point,
4411 previousPoint,
4412 nextPoint;
4413
4414 while (waypoints[idx]) {
4415 point = waypoints[idx];
4416 previousPoint = waypoints[idx - 1];
4417 nextPoint = waypoints[idx + 1];
4418
4419 if (pointDistance(point, nextPoint) === 0 ||
4420 pointsOnLine(previousPoint, nextPoint, point)) {
4421
4422 // remove point, if overlapping with {nextPoint}
4423 // or on line with {previousPoint} -> {point} -> {nextPoint}
4424 waypoints.splice(idx, 1);
4425 } else {
4426 idx++;
4427 }
4428 }
4429
4430 return waypoints;
4431 }
4432
4433 function round(number, resolution) {
4434 return Math.round(number * resolution) / resolution;
4435 }
4436
4437 function ensurePx(number) {
4438 return isNumber(number) ? number + 'px' : number;
4439 }
4440
4441 /**
4442 * Creates a HTML container element for a SVG element with
4443 * the given configuration
4444 *
4445 * @param {Object} options
4446 * @return {HTMLElement} the container element
4447 */
4448 function createContainer(options) {
4449
4450 options = assign({}, { width: '100%', height: '100%' }, options);
4451
4452 var container = options.container || document.body;
4453
4454 // create a <div> around the svg element with the respective size
4455 // this way we can always get the correct container size
4456 // (this is impossible for <svg> elements at the moment)
4457 var parent = document.createElement('div');
4458 parent.setAttribute('class', 'djs-container');
4459
4460 assign(parent.style, {
4461 position: 'relative',
4462 overflow: 'hidden',
4463 width: ensurePx(options.width),
4464 height: ensurePx(options.height)
4465 });
4466
4467 container.appendChild(parent);
4468
4469 return parent;
4470 }
4471
4472 function createGroup(parent, cls, childIndex) {
4473 var group = create('g');
4474 classes$1(group).add(cls);
4475
4476 var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1;
4477
4478 // must ensure second argument is node or _null_
4479 // cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
4480 parent.insertBefore(group, parent.childNodes[index] || null);
4481
4482 return group;
4483 }
4484
4485 var BASE_LAYER = 'base';
4486
4487
4488 var REQUIRED_MODEL_ATTRS = {
4489 shape: [ 'x', 'y', 'width', 'height' ],
4490 connection: [ 'waypoints' ]
4491 };
4492
4493 /**
4494 * The main drawing canvas.
4495 *
4496 * @class
4497 * @constructor
4498 *
4499 * @emits Canvas#canvas.init
4500 *
4501 * @param {Object} config
4502 * @param {EventBus} eventBus
4503 * @param {GraphicsFactory} graphicsFactory
4504 * @param {ElementRegistry} elementRegistry
4505 */
4506 function Canvas(config, eventBus, graphicsFactory, elementRegistry) {
4507
4508 this._eventBus = eventBus;
4509 this._elementRegistry = elementRegistry;
4510 this._graphicsFactory = graphicsFactory;
4511
4512 this._init(config || {});
4513 }
4514
4515 Canvas.$inject = [
4516 'config.canvas',
4517 'eventBus',
4518 'graphicsFactory',
4519 'elementRegistry'
4520 ];
4521
4522
4523 Canvas.prototype._init = function(config) {
4524
4525 var eventBus = this._eventBus;
4526
4527 // Creates a <svg> element that is wrapped into a <div>.
4528 // This way we are always able to correctly figure out the size of the svg element
4529 // by querying the parent node.
4530 //
4531 // (It is not possible to get the size of a svg element cross browser @ 2014-04-01)
4532 //
4533 // <div class="djs-container" style="width: {desired-width}, height: {desired-height}">
4534 // <svg width="100%" height="100%">
4535 // ...
4536 // </svg>
4537 // </div>
4538
4539 // html container
4540 var container = this._container = createContainer(config);
4541
4542 var svg = this._svg = create('svg');
4543 attr$1(svg, { width: '100%', height: '100%' });
4544
4545 append(container, svg);
4546
4547 var viewport = this._viewport = createGroup(svg, 'viewport');
4548
4549 this._layers = {};
4550
4551 // debounce canvas.viewbox.changed events
4552 // for smoother diagram interaction
4553 if (config.deferUpdate !== false) {
4554 this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300);
4555 }
4556
4557 eventBus.on('diagram.init', function() {
4558
4559 /**
4560 * An event indicating that the canvas is ready to be drawn on.
4561 *
4562 * @memberOf Canvas
4563 *
4564 * @event canvas.init
4565 *
4566 * @type {Object}
4567 * @property {SVGElement} svg the created svg element
4568 * @property {SVGElement} viewport the direct parent of diagram elements and shapes
4569 */
4570 eventBus.fire('canvas.init', {
4571 svg: svg,
4572 viewport: viewport
4573 });
4574
4575 }, this);
4576
4577 // reset viewbox on shape changes to
4578 // recompute the viewbox
4579 eventBus.on([
4580 'shape.added',
4581 'connection.added',
4582 'shape.removed',
4583 'connection.removed',
4584 'elements.changed'
4585 ], function() {
4586 delete this._cachedViewbox;
4587 }, this);
4588
4589 eventBus.on('diagram.destroy', 500, this._destroy, this);
4590 eventBus.on('diagram.clear', 500, this._clear, this);
4591 };
4592
4593 Canvas.prototype._destroy = function(emit) {
4594 this._eventBus.fire('canvas.destroy', {
4595 svg: this._svg,
4596 viewport: this._viewport
4597 });
4598
4599 var parent = this._container.parentNode;
4600
4601 if (parent) {
4602 parent.removeChild(this._container);
4603 }
4604
4605 delete this._svg;
4606 delete this._container;
4607 delete this._layers;
4608 delete this._rootElement;
4609 delete this._viewport;
4610 };
4611
4612 Canvas.prototype._clear = function() {
4613
4614 var self = this;
4615
4616 var allElements = this._elementRegistry.getAll();
4617
4618 // remove all elements
4619 allElements.forEach(function(element) {
4620 var type = getType(element);
4621
4622 if (type === 'root') {
4623 self.setRootElement(null, true);
4624 } else {
4625 self._removeElement(element, type);
4626 }
4627 });
4628
4629 // force recomputation of view box
4630 delete this._cachedViewbox;
4631 };
4632
4633 /**
4634 * Returns the default layer on which
4635 * all elements are drawn.
4636 *
4637 * @returns {SVGElement}
4638 */
4639 Canvas.prototype.getDefaultLayer = function() {
4640 return this.getLayer(BASE_LAYER, 0);
4641 };
4642
4643 /**
4644 * Returns a layer that is used to draw elements
4645 * or annotations on it.
4646 *
4647 * Non-existing layers retrieved through this method
4648 * will be created. During creation, the optional index
4649 * may be used to create layers below or above existing layers.
4650 * A layer with a certain index is always created above all
4651 * existing layers with the same index.
4652 *
4653 * @param {string} name
4654 * @param {number} index
4655 *
4656 * @returns {SVGElement}
4657 */
4658 Canvas.prototype.getLayer = function(name, index) {
4659
4660 if (!name) {
4661 throw new Error('must specify a name');
4662 }
4663
4664 var layer = this._layers[name];
4665
4666 if (!layer) {
4667 layer = this._layers[name] = this._createLayer(name, index);
4668 }
4669
4670 // throw an error if layer creation / retrival is
4671 // requested on different index
4672 if (typeof index !== 'undefined' && layer.index !== index) {
4673 throw new Error('layer <' + name + '> already created at index <' + index + '>');
4674 }
4675
4676 return layer.group;
4677 };
4678
4679 /**
4680 * Creates a given layer and returns it.
4681 *
4682 * @param {string} name
4683 * @param {number} [index=0]
4684 *
4685 * @return {Object} layer descriptor with { index, group: SVGGroup }
4686 */
4687 Canvas.prototype._createLayer = function(name, index) {
4688
4689 if (!index) {
4690 index = 0;
4691 }
4692
4693 var childIndex = reduce(this._layers, function(childIndex, layer) {
4694 if (index >= layer.index) {
4695 childIndex++;
4696 }
4697
4698 return childIndex;
4699 }, 0);
4700
4701 return {
4702 group: createGroup(this._viewport, 'layer-' + name, childIndex),
4703 index: index
4704 };
4705
4706 };
4707
4708 /**
4709 * Returns the html element that encloses the
4710 * drawing canvas.
4711 *
4712 * @return {DOMNode}
4713 */
4714 Canvas.prototype.getContainer = function() {
4715 return this._container;
4716 };
4717
4718
4719 // markers //////////////////////
4720
4721 Canvas.prototype._updateMarker = function(element, marker, add) {
4722 var container;
4723
4724 if (!element.id) {
4725 element = this._elementRegistry.get(element);
4726 }
4727
4728 // we need to access all
4729 container = this._elementRegistry._elements[element.id];
4730
4731 if (!container) {
4732 return;
4733 }
4734
4735 forEach([ container.gfx, container.secondaryGfx ], function(gfx) {
4736 if (gfx) {
4737
4738 // invoke either addClass or removeClass based on mode
4739 if (add) {
4740 classes$1(gfx).add(marker);
4741 } else {
4742 classes$1(gfx).remove(marker);
4743 }
4744 }
4745 });
4746
4747 /**
4748 * An event indicating that a marker has been updated for an element
4749 *
4750 * @event element.marker.update
4751 * @type {Object}
4752 * @property {djs.model.Element} element the shape
4753 * @property {Object} gfx the graphical representation of the shape
4754 * @property {string} marker
4755 * @property {boolean} add true if the marker was added, false if it got removed
4756 */
4757 this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add });
4758 };
4759
4760
4761 /**
4762 * Adds a marker to an element (basically a css class).
4763 *
4764 * Fires the element.marker.update event, making it possible to
4765 * integrate extension into the marker life-cycle, too.
4766 *
4767 * @example
4768 * canvas.addMarker('foo', 'some-marker');
4769 *
4770 * var fooGfx = canvas.getGraphics('foo');
4771 *
4772 * fooGfx; // <g class="... some-marker"> ... </g>
4773 *
4774 * @param {string|djs.model.Base} element
4775 * @param {string} marker
4776 */
4777 Canvas.prototype.addMarker = function(element, marker) {
4778 this._updateMarker(element, marker, true);
4779 };
4780
4781
4782 /**
4783 * Remove a marker from an element.
4784 *
4785 * Fires the element.marker.update event, making it possible to
4786 * integrate extension into the marker life-cycle, too.
4787 *
4788 * @param {string|djs.model.Base} element
4789 * @param {string} marker
4790 */
4791 Canvas.prototype.removeMarker = function(element, marker) {
4792 this._updateMarker(element, marker, false);
4793 };
4794
4795 /**
4796 * Check the existence of a marker on element.
4797 *
4798 * @param {string|djs.model.Base} element
4799 * @param {string} marker
4800 */
4801 Canvas.prototype.hasMarker = function(element, marker) {
4802 if (!element.id) {
4803 element = this._elementRegistry.get(element);
4804 }
4805
4806 var gfx = this.getGraphics(element);
4807
4808 return classes$1(gfx).has(marker);
4809 };
4810
4811 /**
4812 * Toggles a marker on an element.
4813 *
4814 * Fires the element.marker.update event, making it possible to
4815 * integrate extension into the marker life-cycle, too.
4816 *
4817 * @param {string|djs.model.Base} element
4818 * @param {string} marker
4819 */
4820 Canvas.prototype.toggleMarker = function(element, marker) {
4821 if (this.hasMarker(element, marker)) {
4822 this.removeMarker(element, marker);
4823 } else {
4824 this.addMarker(element, marker);
4825 }
4826 };
4827
4828 Canvas.prototype.getRootElement = function() {
4829 if (!this._rootElement) {
4830 this.setRootElement({ id: '__implicitroot', children: [] });
4831 }
4832
4833 return this._rootElement;
4834 };
4835
4836
4837
4838 // root element handling //////////////////////
4839
4840 /**
4841 * Sets a given element as the new root element for the canvas
4842 * and returns the new root element.
4843 *
4844 * @param {Object|djs.model.Root} element
4845 * @param {boolean} [override] whether to override the current root element, if any
4846 *
4847 * @return {Object|djs.model.Root} new root element
4848 */
4849 Canvas.prototype.setRootElement = function(element, override) {
4850
4851 if (element) {
4852 this._ensureValid('root', element);
4853 }
4854
4855 var currentRoot = this._rootElement,
4856 elementRegistry = this._elementRegistry,
4857 eventBus = this._eventBus;
4858
4859 if (currentRoot) {
4860 if (!override) {
4861 throw new Error('rootElement already set, need to specify override');
4862 }
4863
4864 // simulate element remove event sequence
4865 eventBus.fire('root.remove', { element: currentRoot });
4866 eventBus.fire('root.removed', { element: currentRoot });
4867
4868 elementRegistry.remove(currentRoot);
4869 }
4870
4871 if (element) {
4872 var gfx = this.getDefaultLayer();
4873
4874 // resemble element add event sequence
4875 eventBus.fire('root.add', { element: element });
4876
4877 elementRegistry.add(element, gfx, this._svg);
4878
4879 eventBus.fire('root.added', { element: element, gfx: gfx });
4880 }
4881
4882 this._rootElement = element;
4883
4884 return element;
4885 };
4886
4887
4888
4889 // add functionality //////////////////////
4890
4891 Canvas.prototype._ensureValid = function(type, element) {
4892 if (!element.id) {
4893 throw new Error('element must have an id');
4894 }
4895
4896 if (this._elementRegistry.get(element.id)) {
4897 throw new Error('element with id ' + element.id + ' already exists');
4898 }
4899
4900 var requiredAttrs = REQUIRED_MODEL_ATTRS[type];
4901
4902 var valid = every(requiredAttrs, function(attr) {
4903 return typeof element[attr] !== 'undefined';
4904 });
4905
4906 if (!valid) {
4907 throw new Error(
4908 'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type);
4909 }
4910 };
4911
4912 Canvas.prototype._setParent = function(element, parent, parentIndex) {
4913 add$1(parent.children, element, parentIndex);
4914 element.parent = parent;
4915 };
4916
4917 /**
4918 * Adds an element to the canvas.
4919 *
4920 * This wires the parent <-> child relationship between the element and
4921 * a explicitly specified parent or an implicit root element.
4922 *
4923 * During add it emits the events
4924 *
4925 * * <{type}.add> (element, parent)
4926 * * <{type}.added> (element, gfx)
4927 *
4928 * Extensions may hook into these events to perform their magic.
4929 *
4930 * @param {string} type
4931 * @param {Object|djs.model.Base} element
4932 * @param {Object|djs.model.Base} [parent]
4933 * @param {number} [parentIndex]
4934 *
4935 * @return {Object|djs.model.Base} the added element
4936 */
4937 Canvas.prototype._addElement = function(type, element, parent, parentIndex) {
4938
4939 parent = parent || this.getRootElement();
4940
4941 var eventBus = this._eventBus,
4942 graphicsFactory = this._graphicsFactory;
4943
4944 this._ensureValid(type, element);
4945
4946 eventBus.fire(type + '.add', { element: element, parent: parent });
4947
4948 this._setParent(element, parent, parentIndex);
4949
4950 // create graphics
4951 var gfx = graphicsFactory.create(type, element, parentIndex);
4952
4953 this._elementRegistry.add(element, gfx);
4954
4955 // update its visual
4956 graphicsFactory.update(type, element, gfx);
4957
4958 eventBus.fire(type + '.added', { element: element, gfx: gfx });
4959
4960 return element;
4961 };
4962
4963 /**
4964 * Adds a shape to the canvas
4965 *
4966 * @param {Object|djs.model.Shape} shape to add to the diagram
4967 * @param {djs.model.Base} [parent]
4968 * @param {number} [parentIndex]
4969 *
4970 * @return {djs.model.Shape} the added shape
4971 */
4972 Canvas.prototype.addShape = function(shape, parent, parentIndex) {
4973 return this._addElement('shape', shape, parent, parentIndex);
4974 };
4975
4976 /**
4977 * Adds a connection to the canvas
4978 *
4979 * @param {Object|djs.model.Connection} connection to add to the diagram
4980 * @param {djs.model.Base} [parent]
4981 * @param {number} [parentIndex]
4982 *
4983 * @return {djs.model.Connection} the added connection
4984 */
4985 Canvas.prototype.addConnection = function(connection, parent, parentIndex) {
4986 return this._addElement('connection', connection, parent, parentIndex);
4987 };
4988
4989
4990 /**
4991 * Internal remove element
4992 */
4993 Canvas.prototype._removeElement = function(element, type) {
4994
4995 var elementRegistry = this._elementRegistry,
4996 graphicsFactory = this._graphicsFactory,
4997 eventBus = this._eventBus;
4998
4999 element = elementRegistry.get(element.id || element);
5000
5001 if (!element) {
5002
5003 // element was removed already
5004 return;
5005 }
5006
5007 eventBus.fire(type + '.remove', { element: element });
5008
5009 graphicsFactory.remove(element);
5010
5011 // unset parent <-> child relationship
5012 remove$2(element.parent && element.parent.children, element);
5013 element.parent = null;
5014
5015 eventBus.fire(type + '.removed', { element: element });
5016
5017 elementRegistry.remove(element);
5018
5019 return element;
5020 };
5021
5022
5023 /**
5024 * Removes a shape from the canvas
5025 *
5026 * @param {string|djs.model.Shape} shape or shape id to be removed
5027 *
5028 * @return {djs.model.Shape} the removed shape
5029 */
5030 Canvas.prototype.removeShape = function(shape) {
5031
5032 /**
5033 * An event indicating that a shape is about to be removed from the canvas.
5034 *
5035 * @memberOf Canvas
5036 *
5037 * @event shape.remove
5038 * @type {Object}
5039 * @property {djs.model.Shape} element the shape descriptor
5040 * @property {Object} gfx the graphical representation of the shape
5041 */
5042
5043 /**
5044 * An event indicating that a shape has been removed from the canvas.
5045 *
5046 * @memberOf Canvas
5047 *
5048 * @event shape.removed
5049 * @type {Object}
5050 * @property {djs.model.Shape} element the shape descriptor
5051 * @property {Object} gfx the graphical representation of the shape
5052 */
5053 return this._removeElement(shape, 'shape');
5054 };
5055
5056
5057 /**
5058 * Removes a connection from the canvas
5059 *
5060 * @param {string|djs.model.Connection} connection or connection id to be removed
5061 *
5062 * @return {djs.model.Connection} the removed connection
5063 */
5064 Canvas.prototype.removeConnection = function(connection) {
5065
5066 /**
5067 * An event indicating that a connection is about to be removed from the canvas.
5068 *
5069 * @memberOf Canvas
5070 *
5071 * @event connection.remove
5072 * @type {Object}
5073 * @property {djs.model.Connection} element the connection descriptor
5074 * @property {Object} gfx the graphical representation of the connection
5075 */
5076
5077 /**
5078 * An event indicating that a connection has been removed from the canvas.
5079 *
5080 * @memberOf Canvas
5081 *
5082 * @event connection.removed
5083 * @type {Object}
5084 * @property {djs.model.Connection} element the connection descriptor
5085 * @property {Object} gfx the graphical representation of the connection
5086 */
5087 return this._removeElement(connection, 'connection');
5088 };
5089
5090
5091 /**
5092 * Return the graphical object underlaying a certain diagram element
5093 *
5094 * @param {string|djs.model.Base} element descriptor of the element
5095 * @param {boolean} [secondary=false] whether to return the secondary connected element
5096 *
5097 * @return {SVGElement}
5098 */
5099 Canvas.prototype.getGraphics = function(element, secondary) {
5100 return this._elementRegistry.getGraphics(element, secondary);
5101 };
5102
5103
5104 /**
5105 * Perform a viewbox update via a given change function.
5106 *
5107 * @param {Function} changeFn
5108 */
5109 Canvas.prototype._changeViewbox = function(changeFn) {
5110
5111 // notify others of the upcoming viewbox change
5112 this._eventBus.fire('canvas.viewbox.changing');
5113
5114 // perform actual change
5115 changeFn.apply(this);
5116
5117 // reset the cached viewbox so that
5118 // a new get operation on viewbox or zoom
5119 // triggers a viewbox re-computation
5120 this._cachedViewbox = null;
5121
5122 // notify others of the change; this step
5123 // may or may not be debounced
5124 this._viewboxChanged();
5125 };
5126
5127 Canvas.prototype._viewboxChanged = function() {
5128 this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() });
5129 };
5130
5131
5132 /**
5133 * Gets or sets the view box of the canvas, i.e. the
5134 * area that is currently displayed.
5135 *
5136 * The getter may return a cached viewbox (if it is currently
5137 * changing). To force a recomputation, pass `false` as the first argument.
5138 *
5139 * @example
5140 *
5141 * canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 })
5142 *
5143 * // sets the visible area of the diagram to (100|100) -> (600|100)
5144 * // and and scales it according to the diagram width
5145 *
5146 * var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box.
5147 *
5148 * console.log(viewbox);
5149 * // {
5150 * // inner: Dimensions,
5151 * // outer: Dimensions,
5152 * // scale,
5153 * // x, y,
5154 * // width, height
5155 * // }
5156 *
5157 * // if the current diagram is zoomed and scrolled, you may reset it to the
5158 * // default zoom via this method, too:
5159 *
5160 * var zoomedAndScrolledViewbox = canvas.viewbox();
5161 *
5162 * canvas.viewbox({
5163 * x: 0,
5164 * y: 0,
5165 * width: zoomedAndScrolledViewbox.outer.width,
5166 * height: zoomedAndScrolledViewbox.outer.height
5167 * });
5168 *
5169 * @param {Object} [box] the new view box to set
5170 * @param {number} box.x the top left X coordinate of the canvas visible in view box
5171 * @param {number} box.y the top left Y coordinate of the canvas visible in view box
5172 * @param {number} box.width the visible width
5173 * @param {number} box.height
5174 *
5175 * @return {Object} the current view box
5176 */
5177 Canvas.prototype.viewbox = function(box) {
5178
5179 if (box === undefined && this._cachedViewbox) {
5180 return this._cachedViewbox;
5181 }
5182
5183 var viewport = this._viewport,
5184 innerBox,
5185 outerBox = this.getSize(),
5186 matrix,
5187 transform$1,
5188 scale,
5189 x, y;
5190
5191 if (!box) {
5192
5193 // compute the inner box based on the
5194 // diagrams default layer. This allows us to exclude
5195 // external components, such as overlays
5196 innerBox = this.getDefaultLayer().getBBox();
5197
5198 transform$1 = transform(viewport);
5199 matrix = transform$1 ? transform$1.matrix : createMatrix();
5200 scale = round(matrix.a, 1000);
5201
5202 x = round(-matrix.e || 0, 1000);
5203 y = round(-matrix.f || 0, 1000);
5204
5205 box = this._cachedViewbox = {
5206 x: x ? x / scale : 0,
5207 y: y ? y / scale : 0,
5208 width: outerBox.width / scale,
5209 height: outerBox.height / scale,
5210 scale: scale,
5211 inner: {
5212 width: innerBox.width,
5213 height: innerBox.height,
5214 x: innerBox.x,
5215 y: innerBox.y
5216 },
5217 outer: outerBox
5218 };
5219
5220 return box;
5221 } else {
5222
5223 this._changeViewbox(function() {
5224 scale = Math.min(outerBox.width / box.width, outerBox.height / box.height);
5225
5226 var matrix = this._svg.createSVGMatrix()
5227 .scale(scale)
5228 .translate(-box.x, -box.y);
5229
5230 transform(viewport, matrix);
5231 });
5232 }
5233
5234 return box;
5235 };
5236
5237
5238 /**
5239 * Gets or sets the scroll of the canvas.
5240 *
5241 * @param {Object} [delta] the new scroll to apply.
5242 *
5243 * @param {number} [delta.dx]
5244 * @param {number} [delta.dy]
5245 */
5246 Canvas.prototype.scroll = function(delta) {
5247
5248 var node = this._viewport;
5249 var matrix = node.getCTM();
5250
5251 if (delta) {
5252 this._changeViewbox(function() {
5253 delta = assign({ dx: 0, dy: 0 }, delta || {});
5254
5255 matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix);
5256
5257 setCTM(node, matrix);
5258 });
5259 }
5260
5261 return { x: matrix.e, y: matrix.f };
5262 };
5263
5264 /**
5265 * Scrolls the viewbox to contain the given element.
5266 * Optionally specify a padding to be applied to the edges.
5267 *
5268 * @param {Object} [element] the element to scroll to.
5269 * @param {Object|Number} [padding=100] the padding to be applied. Can also specify top, bottom, left and right.
5270 *
5271 */
5272 Canvas.prototype.scrollToElement = function(element, padding) {
5273 var defaultPadding = 100;
5274 if (!padding) {
5275 padding = {};
5276 }
5277 if (typeof padding === 'number') {
5278 defaultPadding = padding;
5279 }
5280
5281 padding = {
5282 top: padding.top || defaultPadding,
5283 right: padding.right || defaultPadding,
5284 bottom: padding.bottom || defaultPadding,
5285 left: padding.left || defaultPadding
5286 };
5287
5288 var elementBounds = getBBox(element),
5289 elementTrbl = asTRBL(elementBounds),
5290 viewboxBounds = this.viewbox(),
5291 zoom = this.zoom(),
5292 dx, dy;
5293
5294 // Shrink viewboxBounds with padding
5295 viewboxBounds.y += padding.top / zoom;
5296 viewboxBounds.x += padding.left / zoom;
5297 viewboxBounds.width -= (padding.right + padding.left) / zoom;
5298 viewboxBounds.height -= (padding.bottom + padding.top) / zoom;
5299
5300 var viewboxTrbl = asTRBL(viewboxBounds);
5301
5302 var canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height;
5303
5304 if (!canFit) {
5305
5306 // top-left when element can't fit
5307 dx = elementBounds.x - viewboxBounds.x;
5308 dy = elementBounds.y - viewboxBounds.y;
5309
5310 } else {
5311
5312 var dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right),
5313 dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left),
5314 dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom),
5315 dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top);
5316
5317 dx = dRight || dLeft;
5318 dy = dBottom || dTop;
5319
5320 }
5321
5322 this.scroll({ dx: -dx * zoom, dy: -dy * zoom });
5323 };
5324
5325 /**
5326 * Gets or sets the current zoom of the canvas, optionally zooming
5327 * to the specified position.
5328 *
5329 * The getter may return a cached zoom level. Call it with `false` as
5330 * the first argument to force recomputation of the current level.
5331 *
5332 * @param {string|number} [newScale] the new zoom level, either a number, i.e. 0.9,
5333 * or `fit-viewport` to adjust the size to fit the current viewport
5334 * @param {string|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null
5335 *
5336 * @return {number} the current scale
5337 */
5338 Canvas.prototype.zoom = function(newScale, center) {
5339
5340 if (!newScale) {
5341 return this.viewbox(newScale).scale;
5342 }
5343
5344 if (newScale === 'fit-viewport') {
5345 return this._fitViewport(center);
5346 }
5347
5348 var outer,
5349 matrix;
5350
5351 this._changeViewbox(function() {
5352
5353 if (typeof center !== 'object') {
5354 outer = this.viewbox().outer;
5355
5356 center = {
5357 x: outer.width / 2,
5358 y: outer.height / 2
5359 };
5360 }
5361
5362 matrix = this._setZoom(newScale, center);
5363 });
5364
5365 return round(matrix.a, 1000);
5366 };
5367
5368 function setCTM(node, m) {
5369 var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')';
5370 node.setAttribute('transform', mstr);
5371 }
5372
5373 Canvas.prototype._fitViewport = function(center) {
5374
5375 var vbox = this.viewbox(),
5376 outer = vbox.outer,
5377 inner = vbox.inner,
5378 newScale,
5379 newViewbox;
5380
5381 // display the complete diagram without zooming in.
5382 // instead of relying on internal zoom, we perform a
5383 // hard reset on the canvas viewbox to realize this
5384 //
5385 // if diagram does not need to be zoomed in, we focus it around
5386 // the diagram origin instead
5387
5388 if (inner.x >= 0 &&
5389 inner.y >= 0 &&
5390 inner.x + inner.width <= outer.width &&
5391 inner.y + inner.height <= outer.height &&
5392 !center) {
5393
5394 newViewbox = {
5395 x: 0,
5396 y: 0,
5397 width: Math.max(inner.width + inner.x, outer.width),
5398 height: Math.max(inner.height + inner.y, outer.height)
5399 };
5400 } else {
5401
5402 newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height);
5403 newViewbox = {
5404 x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0),
5405 y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0),
5406 width: outer.width / newScale,
5407 height: outer.height / newScale
5408 };
5409 }
5410
5411 this.viewbox(newViewbox);
5412
5413 return this.viewbox(false).scale;
5414 };
5415
5416
5417 Canvas.prototype._setZoom = function(scale, center) {
5418
5419 var svg = this._svg,
5420 viewport = this._viewport;
5421
5422 var matrix = svg.createSVGMatrix();
5423 var point = svg.createSVGPoint();
5424
5425 var centerPoint,
5426 originalPoint,
5427 currentMatrix,
5428 scaleMatrix,
5429 newMatrix;
5430
5431 currentMatrix = viewport.getCTM();
5432
5433 var currentScale = currentMatrix.a;
5434
5435 if (center) {
5436 centerPoint = assign(point, center);
5437
5438 // revert applied viewport transformations
5439 originalPoint = centerPoint.matrixTransform(currentMatrix.inverse());
5440
5441 // create scale matrix
5442 scaleMatrix = matrix
5443 .translate(originalPoint.x, originalPoint.y)
5444 .scale(1 / currentScale * scale)
5445 .translate(-originalPoint.x, -originalPoint.y);
5446
5447 newMatrix = currentMatrix.multiply(scaleMatrix);
5448 } else {
5449 newMatrix = matrix.scale(scale);
5450 }
5451
5452 setCTM(this._viewport, newMatrix);
5453
5454 return newMatrix;
5455 };
5456
5457
5458 /**
5459 * Returns the size of the canvas
5460 *
5461 * @return {Dimensions}
5462 */
5463 Canvas.prototype.getSize = function() {
5464 return {
5465 width: this._container.clientWidth,
5466 height: this._container.clientHeight
5467 };
5468 };
5469
5470
5471 /**
5472 * Return the absolute bounding box for the given element
5473 *
5474 * The absolute bounding box may be used to display overlays in the
5475 * callers (browser) coordinate system rather than the zoomed in/out
5476 * canvas coordinates.
5477 *
5478 * @param {ElementDescriptor} element
5479 * @return {Bounds} the absolute bounding box
5480 */
5481 Canvas.prototype.getAbsoluteBBox = function(element) {
5482 var vbox = this.viewbox();
5483 var bbox;
5484
5485 // connection
5486 // use svg bbox
5487 if (element.waypoints) {
5488 var gfx = this.getGraphics(element);
5489
5490 bbox = gfx.getBBox();
5491 }
5492
5493 // shapes
5494 // use data
5495 else {
5496 bbox = element;
5497 }
5498
5499 var x = bbox.x * vbox.scale - vbox.x * vbox.scale;
5500 var y = bbox.y * vbox.scale - vbox.y * vbox.scale;
5501
5502 var width = bbox.width * vbox.scale;
5503 var height = bbox.height * vbox.scale;
5504
5505 return {
5506 x: x,
5507 y: y,
5508 width: width,
5509 height: height
5510 };
5511 };
5512
5513 /**
5514 * Fires an event in order other modules can react to the
5515 * canvas resizing
5516 */
5517 Canvas.prototype.resized = function() {
5518
5519 // force recomputation of view box
5520 delete this._cachedViewbox;
5521
5522 this._eventBus.fire('canvas.resized');
5523 };
5524
5525 var ELEMENT_ID = 'data-element-id';
5526
5527
5528 /**
5529 * @class
5530 *
5531 * A registry that keeps track of all shapes in the diagram.
5532 */
5533 function ElementRegistry(eventBus) {
5534 this._elements = {};
5535
5536 this._eventBus = eventBus;
5537 }
5538
5539 ElementRegistry.$inject = [ 'eventBus' ];
5540
5541 /**
5542 * Register a pair of (element, gfx, (secondaryGfx)).
5543 *
5544 * @param {djs.model.Base} element
5545 * @param {SVGElement} gfx
5546 * @param {SVGElement} [secondaryGfx] optional other element to register, too
5547 */
5548 ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) {
5549
5550 var id = element.id;
5551
5552 this._validateId(id);
5553
5554 // associate dom node with element
5555 attr$1(gfx, ELEMENT_ID, id);
5556
5557 if (secondaryGfx) {
5558 attr$1(secondaryGfx, ELEMENT_ID, id);
5559 }
5560
5561 this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx };
5562 };
5563
5564 /**
5565 * Removes an element from the registry.
5566 *
5567 * @param {djs.model.Base} element
5568 */
5569 ElementRegistry.prototype.remove = function(element) {
5570 var elements = this._elements,
5571 id = element.id || element,
5572 container = id && elements[id];
5573
5574 if (container) {
5575
5576 // unset element id on gfx
5577 attr$1(container.gfx, ELEMENT_ID, '');
5578
5579 if (container.secondaryGfx) {
5580 attr$1(container.secondaryGfx, ELEMENT_ID, '');
5581 }
5582
5583 delete elements[id];
5584 }
5585 };
5586
5587 /**
5588 * Update the id of an element
5589 *
5590 * @param {djs.model.Base} element
5591 * @param {string} newId
5592 */
5593 ElementRegistry.prototype.updateId = function(element, newId) {
5594
5595 this._validateId(newId);
5596
5597 if (typeof element === 'string') {
5598 element = this.get(element);
5599 }
5600
5601 this._eventBus.fire('element.updateId', {
5602 element: element,
5603 newId: newId
5604 });
5605
5606 var gfx = this.getGraphics(element),
5607 secondaryGfx = this.getGraphics(element, true);
5608
5609 this.remove(element);
5610
5611 element.id = newId;
5612
5613 this.add(element, gfx, secondaryGfx);
5614 };
5615
5616 /**
5617 * Return the model element for a given id or graphics.
5618 *
5619 * @example
5620 *
5621 * elementRegistry.get('SomeElementId_1');
5622 * elementRegistry.get(gfx);
5623 *
5624 *
5625 * @param {string|SVGElement} filter for selecting the element
5626 *
5627 * @return {djs.model.Base}
5628 */
5629 ElementRegistry.prototype.get = function(filter) {
5630 var id;
5631
5632 if (typeof filter === 'string') {
5633 id = filter;
5634 } else {
5635 id = filter && attr$1(filter, ELEMENT_ID);
5636 }
5637
5638 var container = this._elements[id];
5639 return container && container.element;
5640 };
5641
5642 /**
5643 * Return all elements that match a given filter function.
5644 *
5645 * @param {Function} fn
5646 *
5647 * @return {Array<djs.model.Base>}
5648 */
5649 ElementRegistry.prototype.filter = function(fn) {
5650
5651 var filtered = [];
5652
5653 this.forEach(function(element, gfx) {
5654 if (fn(element, gfx)) {
5655 filtered.push(element);
5656 }
5657 });
5658
5659 return filtered;
5660 };
5661
5662 /**
5663 * Return the first element that satisfies the provided testing function.
5664 *
5665 * @param {Function} fn
5666 *
5667 * @return {djs.model.Base}
5668 */
5669 ElementRegistry.prototype.find = function(fn) {
5670 var map = this._elements,
5671 keys = Object.keys(map);
5672
5673 for (var i = 0; i < keys.length; i++) {
5674 var id = keys[i],
5675 container = map[id],
5676 element = container.element,
5677 gfx = container.gfx;
5678
5679 if (fn(element, gfx)) {
5680 return element;
5681 }
5682 }
5683 };
5684
5685 /**
5686 * Return all rendered model elements.
5687 *
5688 * @return {Array<djs.model.Base>}
5689 */
5690 ElementRegistry.prototype.getAll = function() {
5691 return this.filter(function(e) { return e; });
5692 };
5693
5694 /**
5695 * Iterate over all diagram elements.
5696 *
5697 * @param {Function} fn
5698 */
5699 ElementRegistry.prototype.forEach = function(fn) {
5700
5701 var map = this._elements;
5702
5703 Object.keys(map).forEach(function(id) {
5704 var container = map[id],
5705 element = container.element,
5706 gfx = container.gfx;
5707
5708 return fn(element, gfx);
5709 });
5710 };
5711
5712 /**
5713 * Return the graphical representation of an element or its id.
5714 *
5715 * @example
5716 * elementRegistry.getGraphics('SomeElementId_1');
5717 * elementRegistry.getGraphics(rootElement); // <g ...>
5718 *
5719 * elementRegistry.getGraphics(rootElement, true); // <svg ...>
5720 *
5721 *
5722 * @param {string|djs.model.Base} filter
5723 * @param {boolean} [secondary=false] whether to return the secondary connected element
5724 *
5725 * @return {SVGElement}
5726 */
5727 ElementRegistry.prototype.getGraphics = function(filter, secondary) {
5728 var id = filter.id || filter;
5729
5730 var container = this._elements[id];
5731 return container && (secondary ? container.secondaryGfx : container.gfx);
5732 };
5733
5734 /**
5735 * Validate the suitability of the given id and signals a problem
5736 * with an exception.
5737 *
5738 * @param {string} id
5739 *
5740 * @throws {Error} if id is empty or already assigned
5741 */
5742 ElementRegistry.prototype._validateId = function(id) {
5743 if (!id) {
5744 throw new Error('element must have an id');
5745 }
5746
5747 if (this._elements[id]) {
5748 throw new Error('element with id ' + id + ' already added');
5749 }
5750 };
5751
5752 /**
5753 * An empty collection stub. Use {@link RefsCollection.extend} to extend a
5754 * collection with ref semantics.
5755 *
5756 * @class RefsCollection
5757 */
5758
5759 /**
5760 * Extends a collection with {@link Refs} aware methods
5761 *
5762 * @memberof RefsCollection
5763 * @static
5764 *
5765 * @param {Array<Object>} collection
5766 * @param {Refs} refs instance
5767 * @param {Object} property represented by the collection
5768 * @param {Object} target object the collection is attached to
5769 *
5770 * @return {RefsCollection<Object>} the extended array
5771 */
5772 function extend$1(collection, refs, property, target) {
5773
5774 var inverseProperty = property.inverse;
5775
5776 /**
5777 * Removes the given element from the array and returns it.
5778 *
5779 * @method RefsCollection#remove
5780 *
5781 * @param {Object} element the element to remove
5782 */
5783 Object.defineProperty(collection, 'remove', {
5784 value: function(element) {
5785 var idx = this.indexOf(element);
5786 if (idx !== -1) {
5787 this.splice(idx, 1);
5788
5789 // unset inverse
5790 refs.unset(element, inverseProperty, target);
5791 }
5792
5793 return element;
5794 }
5795 });
5796
5797 /**
5798 * Returns true if the collection contains the given element
5799 *
5800 * @method RefsCollection#contains
5801 *
5802 * @param {Object} element the element to check for
5803 */
5804 Object.defineProperty(collection, 'contains', {
5805 value: function(element) {
5806 return this.indexOf(element) !== -1;
5807 }
5808 });
5809
5810 /**
5811 * Adds an element to the array, unless it exists already (set semantics).
5812 *
5813 * @method RefsCollection#add
5814 *
5815 * @param {Object} element the element to add
5816 * @param {Number} optional index to add element to
5817 * (possibly moving other elements around)
5818 */
5819 Object.defineProperty(collection, 'add', {
5820 value: function(element, idx) {
5821
5822 var currentIdx = this.indexOf(element);
5823
5824 if (typeof idx === 'undefined') {
5825
5826 if (currentIdx !== -1) {
5827 // element already in collection (!)
5828 return;
5829 }
5830
5831 // add to end of array, as no idx is specified
5832 idx = this.length;
5833 }
5834
5835 // handle already in collection
5836 if (currentIdx !== -1) {
5837
5838 // remove element from currentIdx
5839 this.splice(currentIdx, 1);
5840 }
5841
5842 // add element at idx
5843 this.splice(idx, 0, element);
5844
5845 if (currentIdx === -1) {
5846 // set inverse, unless element was
5847 // in collection already
5848 refs.set(element, inverseProperty, target);
5849 }
5850 }
5851 });
5852
5853 // a simple marker, identifying this element
5854 // as being a refs collection
5855 Object.defineProperty(collection, '__refs_collection', {
5856 value: true
5857 });
5858
5859 return collection;
5860 }
5861
5862
5863 function isExtended(collection) {
5864 return collection.__refs_collection === true;
5865 }
5866
5867 var extend_1 = extend$1;
5868
5869 var isExtended_1 = isExtended;
5870
5871 var collection = {
5872 extend: extend_1,
5873 isExtended: isExtended_1
5874 };
5875
5876 function hasOwnProperty(e, property) {
5877 return Object.prototype.hasOwnProperty.call(e, property.name || property);
5878 }
5879
5880 function defineCollectionProperty(ref, property, target) {
5881
5882 var collection$1 = collection.extend(target[property.name] || [], ref, property, target);
5883
5884 Object.defineProperty(target, property.name, {
5885 enumerable: property.enumerable,
5886 value: collection$1
5887 });
5888
5889 if (collection$1.length) {
5890
5891 collection$1.forEach(function(o) {
5892 ref.set(o, property.inverse, target);
5893 });
5894 }
5895 }
5896
5897
5898 function defineProperty(ref, property, target) {
5899
5900 var inverseProperty = property.inverse;
5901
5902 var _value = target[property.name];
5903
5904 Object.defineProperty(target, property.name, {
5905 configurable: property.configurable,
5906 enumerable: property.enumerable,
5907
5908 get: function() {
5909 return _value;
5910 },
5911
5912 set: function(value) {
5913
5914 // return if we already performed all changes
5915 if (value === _value) {
5916 return;
5917 }
5918
5919 var old = _value;
5920
5921 // temporary set null
5922 _value = null;
5923
5924 if (old) {
5925 ref.unset(old, inverseProperty, target);
5926 }
5927
5928 // set new value
5929 _value = value;
5930
5931 // set inverse value
5932 ref.set(_value, inverseProperty, target);
5933 }
5934 });
5935
5936 }
5937
5938 /**
5939 * Creates a new references object defining two inversly related
5940 * attribute descriptors a and b.
5941 *
5942 * <p>
5943 * When bound to an object using {@link Refs#bind} the references
5944 * get activated and ensure that add and remove operations are applied
5945 * reversely, too.
5946 * </p>
5947 *
5948 * <p>
5949 * For attributes represented as collections {@link Refs} provides the
5950 * {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions
5951 * that must be used to properly hook into the inverse change mechanism.
5952 * </p>
5953 *
5954 * @class Refs
5955 *
5956 * @classdesc A bi-directional reference between two attributes.
5957 *
5958 * @param {Refs.AttributeDescriptor} a property descriptor
5959 * @param {Refs.AttributeDescriptor} b property descriptor
5960 *
5961 * @example
5962 *
5963 * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' });
5964 *
5965 * var car = { name: 'toyota' };
5966 * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }];
5967 *
5968 * refs.bind(car, 'wheels');
5969 *
5970 * car.wheels // []
5971 * car.wheels.add(wheels[0]);
5972 * car.wheels.add(wheels[1]);
5973 *
5974 * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }]
5975 *
5976 * wheels[0].car // { name: 'toyota' };
5977 * car.wheels.remove(wheels[0]);
5978 *
5979 * wheels[0].car // undefined
5980 */
5981 function Refs(a, b) {
5982
5983 if (!(this instanceof Refs)) {
5984 return new Refs(a, b);
5985 }
5986
5987 // link
5988 a.inverse = b;
5989 b.inverse = a;
5990
5991 this.props = {};
5992 this.props[a.name] = a;
5993 this.props[b.name] = b;
5994 }
5995
5996 /**
5997 * Binds one side of a bi-directional reference to a
5998 * target object.
5999 *
6000 * @memberOf Refs
6001 *
6002 * @param {Object} target
6003 * @param {String} property
6004 */
6005 Refs.prototype.bind = function(target, property) {
6006 if (typeof property === 'string') {
6007 if (!this.props[property]) {
6008 throw new Error('no property <' + property + '> in ref');
6009 }
6010 property = this.props[property];
6011 }
6012
6013 if (property.collection) {
6014 defineCollectionProperty(this, property, target);
6015 } else {
6016 defineProperty(this, property, target);
6017 }
6018 };
6019
6020 Refs.prototype.ensureRefsCollection = function(target, property) {
6021
6022 var collection$1 = target[property.name];
6023
6024 if (!collection.isExtended(collection$1)) {
6025 defineCollectionProperty(this, property, target);
6026 }
6027
6028 return collection$1;
6029 };
6030
6031 Refs.prototype.ensureBound = function(target, property) {
6032 if (!hasOwnProperty(target, property)) {
6033 this.bind(target, property);
6034 }
6035 };
6036
6037 Refs.prototype.unset = function(target, property, value) {
6038
6039 if (target) {
6040 this.ensureBound(target, property);
6041
6042 if (property.collection) {
6043 this.ensureRefsCollection(target, property).remove(value);
6044 } else {
6045 target[property.name] = undefined;
6046 }
6047 }
6048 };
6049
6050 Refs.prototype.set = function(target, property, value) {
6051
6052 if (target) {
6053 this.ensureBound(target, property);
6054
6055 if (property.collection) {
6056 this.ensureRefsCollection(target, property).add(value);
6057 } else {
6058 target[property.name] = value;
6059 }
6060 }
6061 };
6062
6063 var refs = Refs;
6064
6065 var objectRefs = refs;
6066
6067 var Collection = collection;
6068 objectRefs.Collection = Collection;
6069
6070 var parentRefs = new objectRefs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }),
6071 labelRefs = new objectRefs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }),
6072 attacherRefs = new objectRefs({ name: 'attachers', collection: true }, { name: 'host' }),
6073 outgoingRefs = new objectRefs({ name: 'outgoing', collection: true }, { name: 'source' }),
6074 incomingRefs = new objectRefs({ name: 'incoming', collection: true }, { name: 'target' });
6075
6076 /**
6077 * @namespace djs.model
6078 */
6079
6080 /**
6081 * @memberOf djs.model
6082 */
6083
6084 /**
6085 * The basic graphical representation
6086 *
6087 * @class
6088 *
6089 * @abstract
6090 */
6091 function Base() {
6092
6093 /**
6094 * The object that backs up the shape
6095 *
6096 * @name Base#businessObject
6097 * @type Object
6098 */
6099 Object.defineProperty(this, 'businessObject', {
6100 writable: true
6101 });
6102
6103
6104 /**
6105 * Single label support, will mapped to multi label array
6106 *
6107 * @name Base#label
6108 * @type Object
6109 */
6110 Object.defineProperty(this, 'label', {
6111 get: function() {
6112 return this.labels[0];
6113 },
6114 set: function(newLabel) {
6115
6116 var label = this.label,
6117 labels = this.labels;
6118
6119 if (!newLabel && label) {
6120 labels.remove(label);
6121 } else {
6122 labels.add(newLabel, 0);
6123 }
6124 }
6125 });
6126
6127 /**
6128 * The parent shape
6129 *
6130 * @name Base#parent
6131 * @type Shape
6132 */
6133 parentRefs.bind(this, 'parent');
6134
6135 /**
6136 * The list of labels
6137 *
6138 * @name Base#labels
6139 * @type Label
6140 */
6141 labelRefs.bind(this, 'labels');
6142
6143 /**
6144 * The list of outgoing connections
6145 *
6146 * @name Base#outgoing
6147 * @type Array<Connection>
6148 */
6149 outgoingRefs.bind(this, 'outgoing');
6150
6151 /**
6152 * The list of incoming connections
6153 *
6154 * @name Base#incoming
6155 * @type Array<Connection>
6156 */
6157 incomingRefs.bind(this, 'incoming');
6158 }
6159
6160
6161 /**
6162 * A graphical object
6163 *
6164 * @class
6165 * @constructor
6166 *
6167 * @extends Base
6168 */
6169 function Shape() {
6170 Base.call(this);
6171
6172 /**
6173 * Indicates frame shapes
6174 *
6175 * @name Shape#isFrame
6176 * @type boolean
6177 */
6178
6179 /**
6180 * The list of children
6181 *
6182 * @name Shape#children
6183 * @type Array<Base>
6184 */
6185 parentRefs.bind(this, 'children');
6186
6187 /**
6188 * @name Shape#host
6189 * @type Shape
6190 */
6191 attacherRefs.bind(this, 'host');
6192
6193 /**
6194 * @name Shape#attachers
6195 * @type Shape
6196 */
6197 attacherRefs.bind(this, 'attachers');
6198 }
6199
6200 inherits_browser(Shape, Base);
6201
6202
6203 /**
6204 * A root graphical object
6205 *
6206 * @class
6207 * @constructor
6208 *
6209 * @extends Shape
6210 */
6211 function Root() {
6212 Shape.call(this);
6213 }
6214
6215 inherits_browser(Root, Shape);
6216
6217
6218 /**
6219 * A label for an element
6220 *
6221 * @class
6222 * @constructor
6223 *
6224 * @extends Shape
6225 */
6226 function Label() {
6227 Shape.call(this);
6228
6229 /**
6230 * The labeled element
6231 *
6232 * @name Label#labelTarget
6233 * @type Base
6234 */
6235 labelRefs.bind(this, 'labelTarget');
6236 }
6237
6238 inherits_browser(Label, Shape);
6239
6240
6241 /**
6242 * A connection between two elements
6243 *
6244 * @class
6245 * @constructor
6246 *
6247 * @extends Base
6248 */
6249 function Connection() {
6250 Base.call(this);
6251
6252 /**
6253 * The element this connection originates from
6254 *
6255 * @name Connection#source
6256 * @type Base
6257 */
6258 outgoingRefs.bind(this, 'source');
6259
6260 /**
6261 * The element this connection points to
6262 *
6263 * @name Connection#target
6264 * @type Base
6265 */
6266 incomingRefs.bind(this, 'target');
6267 }
6268
6269 inherits_browser(Connection, Base);
6270
6271
6272 var types = {
6273 connection: Connection,
6274 shape: Shape,
6275 label: Label,
6276 root: Root
6277 };
6278
6279 /**
6280 * Creates a new model element of the specified type
6281 *
6282 * @method create
6283 *
6284 * @example
6285 *
6286 * var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 });
6287 * var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 });
6288 *
6289 * var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] });
6290 *
6291 * @param {string} type lower-cased model name
6292 * @param {Object} attrs attributes to initialize the new model instance with
6293 *
6294 * @return {Base} the new model instance
6295 */
6296 function create$1(type, attrs) {
6297 var Type = types[type];
6298 if (!Type) {
6299 throw new Error('unknown type: <' + type + '>');
6300 }
6301 return assign(new Type(), attrs);
6302 }
6303
6304 /**
6305 * A factory for diagram-js shapes
6306 */
6307 function ElementFactory() {
6308 this._uid = 12;
6309 }
6310
6311
6312 ElementFactory.prototype.createRoot = function(attrs) {
6313 return this.create('root', attrs);
6314 };
6315
6316 ElementFactory.prototype.createLabel = function(attrs) {
6317 return this.create('label', attrs);
6318 };
6319
6320 ElementFactory.prototype.createShape = function(attrs) {
6321 return this.create('shape', attrs);
6322 };
6323
6324 ElementFactory.prototype.createConnection = function(attrs) {
6325 return this.create('connection', attrs);
6326 };
6327
6328 /**
6329 * Create a model element with the given type and
6330 * a number of pre-set attributes.
6331 *
6332 * @param {string} type
6333 * @param {Object} attrs
6334 * @return {djs.model.Base} the newly created model instance
6335 */
6336 ElementFactory.prototype.create = function(type, attrs) {
6337
6338 attrs = assign({}, attrs || {});
6339
6340 if (!attrs.id) {
6341 attrs.id = type + '_' + (this._uid++);
6342 }
6343
6344 return create$1(type, attrs);
6345 };
6346
6347 var FN_REF = '__fn';
6348
6349 var DEFAULT_PRIORITY = 1000;
6350
6351 var slice$1 = Array.prototype.slice;
6352
6353 /**
6354 * A general purpose event bus.
6355 *
6356 * This component is used to communicate across a diagram instance.
6357 * Other parts of a diagram can use it to listen to and broadcast events.
6358 *
6359 *
6360 * ## Registering for Events
6361 *
6362 * The event bus provides the {@link EventBus#on} and {@link EventBus#once}
6363 * methods to register for events. {@link EventBus#off} can be used to
6364 * remove event registrations. Listeners receive an instance of {@link Event}
6365 * as the first argument. It allows them to hook into the event execution.
6366 *
6367 * ```javascript
6368 *
6369 * // listen for event
6370 * eventBus.on('foo', function(event) {
6371 *
6372 * // access event type
6373 * event.type; // 'foo'
6374 *
6375 * // stop propagation to other listeners
6376 * event.stopPropagation();
6377 *
6378 * // prevent event default
6379 * event.preventDefault();
6380 * });
6381 *
6382 * // listen for event with custom payload
6383 * eventBus.on('bar', function(event, payload) {
6384 * console.log(payload);
6385 * });
6386 *
6387 * // listen for event returning value
6388 * eventBus.on('foobar', function(event) {
6389 *
6390 * // stop event propagation + prevent default
6391 * return false;
6392 *
6393 * // stop event propagation + return custom result
6394 * return {
6395 * complex: 'listening result'
6396 * };
6397 * });
6398 *
6399 *
6400 * // listen with custom priority (default=1000, higher is better)
6401 * eventBus.on('priorityfoo', 1500, function(event) {
6402 * console.log('invoked first!');
6403 * });
6404 *
6405 *
6406 * // listen for event and pass the context (`this`)
6407 * eventBus.on('foobar', function(event) {
6408 * this.foo();
6409 * }, this);
6410 * ```
6411 *
6412 *
6413 * ## Emitting Events
6414 *
6415 * Events can be emitted via the event bus using {@link EventBus#fire}.
6416 *
6417 * ```javascript
6418 *
6419 * // false indicates that the default action
6420 * // was prevented by listeners
6421 * if (eventBus.fire('foo') === false) {
6422 * console.log('default has been prevented!');
6423 * };
6424 *
6425 *
6426 * // custom args + return value listener
6427 * eventBus.on('sum', function(event, a, b) {
6428 * return a + b;
6429 * });
6430 *
6431 * // you can pass custom arguments + retrieve result values.
6432 * var sum = eventBus.fire('sum', 1, 2);
6433 * console.log(sum); // 3
6434 * ```
6435 */
6436 function EventBus() {
6437 this._listeners = {};
6438
6439 // cleanup on destroy on lowest priority to allow
6440 // message passing until the bitter end
6441 this.on('diagram.destroy', 1, this._destroy, this);
6442 }
6443
6444
6445 /**
6446 * Register an event listener for events with the given name.
6447 *
6448 * The callback will be invoked with `event, ...additionalArguments`
6449 * that have been passed to {@link EventBus#fire}.
6450 *
6451 * Returning false from a listener will prevent the events default action
6452 * (if any is specified). To stop an event from being processed further in
6453 * other listeners execute {@link Event#stopPropagation}.
6454 *
6455 * Returning anything but `undefined` from a listener will stop the listener propagation.
6456 *
6457 * @param {string|Array<string>} events
6458 * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
6459 * @param {Function} callback
6460 * @param {Object} [that] Pass context (`this`) to the callback
6461 */
6462 EventBus.prototype.on = function(events, priority, callback, that) {
6463
6464 events = isArray(events) ? events : [ events ];
6465
6466 if (isFunction(priority)) {
6467 that = callback;
6468 callback = priority;
6469 priority = DEFAULT_PRIORITY;
6470 }
6471
6472 if (!isNumber(priority)) {
6473 throw new Error('priority must be a number');
6474 }
6475
6476 var actualCallback = callback;
6477
6478 if (that) {
6479 actualCallback = bind(callback, that);
6480
6481 // make sure we remember and are able to remove
6482 // bound callbacks via {@link #off} using the original
6483 // callback
6484 actualCallback[FN_REF] = callback[FN_REF] || callback;
6485 }
6486
6487 var self = this;
6488
6489 events.forEach(function(e) {
6490 self._addListener(e, {
6491 priority: priority,
6492 callback: actualCallback,
6493 next: null
6494 });
6495 });
6496 };
6497
6498
6499 /**
6500 * Register an event listener that is executed only once.
6501 *
6502 * @param {string} event the event name to register for
6503 * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
6504 * @param {Function} callback the callback to execute
6505 * @param {Object} [that] Pass context (`this`) to the callback
6506 */
6507 EventBus.prototype.once = function(event, priority, callback, that) {
6508 var self = this;
6509
6510 if (isFunction(priority)) {
6511 that = callback;
6512 callback = priority;
6513 priority = DEFAULT_PRIORITY;
6514 }
6515
6516 if (!isNumber(priority)) {
6517 throw new Error('priority must be a number');
6518 }
6519
6520 function wrappedCallback() {
6521 wrappedCallback.__isTomb = true;
6522
6523 var result = callback.apply(that, arguments);
6524
6525 self.off(event, wrappedCallback);
6526
6527 return result;
6528 }
6529
6530 // make sure we remember and are able to remove
6531 // bound callbacks via {@link #off} using the original
6532 // callback
6533 wrappedCallback[FN_REF] = callback;
6534
6535 this.on(event, priority, wrappedCallback);
6536 };
6537
6538
6539 /**
6540 * Removes event listeners by event and callback.
6541 *
6542 * If no callback is given, all listeners for a given event name are being removed.
6543 *
6544 * @param {string|Array<string>} events
6545 * @param {Function} [callback]
6546 */
6547 EventBus.prototype.off = function(events, callback) {
6548
6549 events = isArray(events) ? events : [ events ];
6550
6551 var self = this;
6552
6553 events.forEach(function(event) {
6554 self._removeListener(event, callback);
6555 });
6556
6557 };
6558
6559
6560 /**
6561 * Create an EventBus event.
6562 *
6563 * @param {Object} data
6564 *
6565 * @return {Object} event, recognized by the eventBus
6566 */
6567 EventBus.prototype.createEvent = function(data) {
6568 var event = new InternalEvent();
6569
6570 event.init(data);
6571
6572 return event;
6573 };
6574
6575
6576 /**
6577 * Fires a named event.
6578 *
6579 * @example
6580 *
6581 * // fire event by name
6582 * events.fire('foo');
6583 *
6584 * // fire event object with nested type
6585 * var event = { type: 'foo' };
6586 * events.fire(event);
6587 *
6588 * // fire event with explicit type
6589 * var event = { x: 10, y: 20 };
6590 * events.fire('element.moved', event);
6591 *
6592 * // pass additional arguments to the event
6593 * events.on('foo', function(event, bar) {
6594 * alert(bar);
6595 * });
6596 *
6597 * events.fire({ type: 'foo' }, 'I am bar!');
6598 *
6599 * @param {string} [name] the optional event name
6600 * @param {Object} [event] the event object
6601 * @param {...Object} additional arguments to be passed to the callback functions
6602 *
6603 * @return {boolean} the events return value, if specified or false if the
6604 * default action was prevented by listeners
6605 */
6606 EventBus.prototype.fire = function(type, data) {
6607 var event,
6608 firstListener,
6609 returnValue,
6610 args;
6611
6612 args = slice$1.call(arguments);
6613
6614 if (typeof type === 'object') {
6615 data = type;
6616 type = data.type;
6617 }
6618
6619 if (!type) {
6620 throw new Error('no event type specified');
6621 }
6622
6623 firstListener = this._listeners[type];
6624
6625 if (!firstListener) {
6626 return;
6627 }
6628
6629 // we make sure we fire instances of our home made
6630 // events here. We wrap them only once, though
6631 if (data instanceof InternalEvent) {
6632
6633 // we are fine, we alread have an event
6634 event = data;
6635 } else {
6636 event = this.createEvent(data);
6637 }
6638
6639 // ensure we pass the event as the first parameter
6640 args[0] = event;
6641
6642 // original event type (in case we delegate)
6643 var originalType = event.type;
6644
6645 // update event type before delegation
6646 if (type !== originalType) {
6647 event.type = type;
6648 }
6649
6650 try {
6651 returnValue = this._invokeListeners(event, args, firstListener);
6652 } finally {
6653
6654 // reset event type after delegation
6655 if (type !== originalType) {
6656 event.type = originalType;
6657 }
6658 }
6659
6660 // set the return value to false if the event default
6661 // got prevented and no other return value exists
6662 if (returnValue === undefined && event.defaultPrevented) {
6663 returnValue = false;
6664 }
6665
6666 return returnValue;
6667 };
6668
6669
6670 EventBus.prototype.handleError = function(error) {
6671 return this.fire('error', { error: error }) === false;
6672 };
6673
6674
6675 EventBus.prototype._destroy = function() {
6676 this._listeners = {};
6677 };
6678
6679 EventBus.prototype._invokeListeners = function(event, args, listener) {
6680
6681 var returnValue;
6682
6683 while (listener) {
6684
6685 // handle stopped propagation
6686 if (event.cancelBubble) {
6687 break;
6688 }
6689
6690 returnValue = this._invokeListener(event, args, listener);
6691
6692 listener = listener.next;
6693 }
6694
6695 return returnValue;
6696 };
6697
6698 EventBus.prototype._invokeListener = function(event, args, listener) {
6699
6700 var returnValue;
6701
6702 if (listener.callback.__isTomb) {
6703 return returnValue;
6704 }
6705
6706 try {
6707
6708 // returning false prevents the default action
6709 returnValue = invokeFunction(listener.callback, args);
6710
6711 // stop propagation on return value
6712 if (returnValue !== undefined) {
6713 event.returnValue = returnValue;
6714 event.stopPropagation();
6715 }
6716
6717 // prevent default on return false
6718 if (returnValue === false) {
6719 event.preventDefault();
6720 }
6721 } catch (e) {
6722 if (!this.handleError(e)) {
6723 console.error('unhandled error in event listener');
6724 console.error(e.stack);
6725
6726 throw e;
6727 }
6728 }
6729
6730 return returnValue;
6731 };
6732
6733 /*
6734 * Add new listener with a certain priority to the list
6735 * of listeners (for the given event).
6736 *
6737 * The semantics of listener registration / listener execution are
6738 * first register, first serve: New listeners will always be inserted
6739 * after existing listeners with the same priority.
6740 *
6741 * Example: Inserting two listeners with priority 1000 and 1300
6742 *
6743 * * before: [ 1500, 1500, 1000, 1000 ]
6744 * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
6745 *
6746 * @param {string} event
6747 * @param {Object} listener { priority, callback }
6748 */
6749 EventBus.prototype._addListener = function(event, newListener) {
6750
6751 var listener = this._getListeners(event),
6752 previousListener;
6753
6754 // no prior listeners
6755 if (!listener) {
6756 this._setListeners(event, newListener);
6757
6758 return;
6759 }
6760
6761 // ensure we order listeners by priority from
6762 // 0 (high) to n > 0 (low)
6763 while (listener) {
6764
6765 if (listener.priority < newListener.priority) {
6766
6767 newListener.next = listener;
6768
6769 if (previousListener) {
6770 previousListener.next = newListener;
6771 } else {
6772 this._setListeners(event, newListener);
6773 }
6774
6775 return;
6776 }
6777
6778 previousListener = listener;
6779 listener = listener.next;
6780 }
6781
6782 // add new listener to back
6783 previousListener.next = newListener;
6784 };
6785
6786
6787 EventBus.prototype._getListeners = function(name) {
6788 return this._listeners[name];
6789 };
6790
6791 EventBus.prototype._setListeners = function(name, listener) {
6792 this._listeners[name] = listener;
6793 };
6794
6795 EventBus.prototype._removeListener = function(event, callback) {
6796
6797 var listener = this._getListeners(event),
6798 nextListener,
6799 previousListener,
6800 listenerCallback;
6801
6802 if (!callback) {
6803
6804 // clear listeners
6805 this._setListeners(event, null);
6806
6807 return;
6808 }
6809
6810 while (listener) {
6811
6812 nextListener = listener.next;
6813
6814 listenerCallback = listener.callback;
6815
6816 if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
6817 if (previousListener) {
6818 previousListener.next = nextListener;
6819 } else {
6820
6821 // new first listener
6822 this._setListeners(event, nextListener);
6823 }
6824 }
6825
6826 previousListener = listener;
6827 listener = nextListener;
6828 }
6829 };
6830
6831 /**
6832 * A event that is emitted via the event bus.
6833 */
6834 function InternalEvent() { }
6835
6836 InternalEvent.prototype.stopPropagation = function() {
6837 this.cancelBubble = true;
6838 };
6839
6840 InternalEvent.prototype.preventDefault = function() {
6841 this.defaultPrevented = true;
6842 };
6843
6844 InternalEvent.prototype.init = function(data) {
6845 assign(this, data || {});
6846 };
6847
6848
6849 /**
6850 * Invoke function. Be fast...
6851 *
6852 * @param {Function} fn
6853 * @param {Array<Object>} args
6854 *
6855 * @return {Any}
6856 */
6857 function invokeFunction(fn, args) {
6858 return fn.apply(null, args);
6859 }
6860
6861 /**
6862 * SVGs for elements are generated by the {@link GraphicsFactory}.
6863 *
6864 * This utility gives quick access to the important semantic
6865 * parts of an element.
6866 */
6867
6868 /**
6869 * Returns the visual part of a diagram element
6870 *
6871 * @param {Snap<SVGElement>} gfx
6872 *
6873 * @return {Snap<SVGElement>}
6874 */
6875 function getVisual(gfx) {
6876 return gfx.childNodes[0];
6877 }
6878
6879 /**
6880 * Returns the children for a given diagram element.
6881 *
6882 * @param {Snap<SVGElement>} gfx
6883 * @return {Snap<SVGElement>}
6884 */
6885 function getChildren(gfx) {
6886 return gfx.parentNode.childNodes[1];
6887 }
6888
6889 /**
6890 * @param {<SVGElement>} element
6891 * @param {number} x
6892 * @param {number} y
6893 * @param {number} angle
6894 * @param {number} amount
6895 */
6896 function transform$1(gfx, x, y, angle, amount) {
6897 var translate = createTransform();
6898 translate.setTranslate(x, y);
6899
6900 var rotate = createTransform();
6901 rotate.setRotate(angle || 0, 0, 0);
6902
6903 var scale = createTransform();
6904 scale.setScale(amount || 1, amount || 1);
6905
6906 transform(gfx, [ translate, rotate, scale ]);
6907 }
6908
6909
6910 /**
6911 * @param {SVGElement} element
6912 * @param {number} x
6913 * @param {number} y
6914 */
6915 function translate(gfx, x, y) {
6916 var translate = createTransform();
6917 translate.setTranslate(x, y);
6918
6919 transform(gfx, translate);
6920 }
6921
6922
6923 /**
6924 * @param {SVGElement} element
6925 * @param {number} angle
6926 */
6927 function rotate(gfx, angle) {
6928 var rotate = createTransform();
6929 rotate.setRotate(angle, 0, 0);
6930
6931 transform(gfx, rotate);
6932 }
6933
6934 /**
6935 * A factory that creates graphical elements
6936 *
6937 * @param {EventBus} eventBus
6938 * @param {ElementRegistry} elementRegistry
6939 */
6940 function GraphicsFactory(eventBus, elementRegistry) {
6941 this._eventBus = eventBus;
6942 this._elementRegistry = elementRegistry;
6943 }
6944
6945 GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ];
6946
6947
6948 GraphicsFactory.prototype._getChildrenContainer = function(element) {
6949
6950 var gfx = this._elementRegistry.getGraphics(element);
6951
6952 var childrenGfx;
6953
6954 // root element
6955 if (!element.parent) {
6956 childrenGfx = gfx;
6957 } else {
6958 childrenGfx = getChildren(gfx);
6959 if (!childrenGfx) {
6960 childrenGfx = create('g');
6961 classes$1(childrenGfx).add('djs-children');
6962
6963 append(gfx.parentNode, childrenGfx);
6964 }
6965 }
6966
6967 return childrenGfx;
6968 };
6969
6970 /**
6971 * Clears the graphical representation of the element and returns the
6972 * cleared visual (the <g class="djs-visual" /> element).
6973 */
6974 GraphicsFactory.prototype._clear = function(gfx) {
6975 var visual = getVisual(gfx);
6976
6977 clear(visual);
6978
6979 return visual;
6980 };
6981
6982 /**
6983 * Creates a gfx container for shapes and connections
6984 *
6985 * The layout is as follows:
6986 *
6987 * <g class="djs-group">
6988 *
6989 * <!-- the gfx -->
6990 * <g class="djs-element djs-(shape|connection|frame)">
6991 * <g class="djs-visual">
6992 * <!-- the renderer draws in here -->
6993 * </g>
6994 *
6995 * <!-- extensions (overlays, click box, ...) goes here
6996 * </g>
6997 *
6998 * <!-- the gfx child nodes -->
6999 * <g class="djs-children"></g>
7000 * </g>
7001 *
7002 * @param {string} type the type of the element, i.e. shape | connection
7003 * @param {SVGElement} [childrenGfx]
7004 * @param {number} [parentIndex] position to create container in parent
7005 * @param {boolean} [isFrame] is frame element
7006 *
7007 * @return {SVGElement}
7008 */
7009 GraphicsFactory.prototype._createContainer = function(
7010 type, childrenGfx, parentIndex, isFrame
7011 ) {
7012 var outerGfx = create('g');
7013 classes$1(outerGfx).add('djs-group');
7014
7015 // insert node at position
7016 if (typeof parentIndex !== 'undefined') {
7017 prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]);
7018 } else {
7019 append(childrenGfx, outerGfx);
7020 }
7021
7022 var gfx = create('g');
7023 classes$1(gfx).add('djs-element');
7024 classes$1(gfx).add('djs-' + type);
7025
7026 if (isFrame) {
7027 classes$1(gfx).add('djs-frame');
7028 }
7029
7030 append(outerGfx, gfx);
7031
7032 // create visual
7033 var visual = create('g');
7034 classes$1(visual).add('djs-visual');
7035
7036 append(gfx, visual);
7037
7038 return gfx;
7039 };
7040
7041 GraphicsFactory.prototype.create = function(type, element, parentIndex) {
7042 var childrenGfx = this._getChildrenContainer(element.parent);
7043 return this._createContainer(type, childrenGfx, parentIndex, isFrameElement(element));
7044 };
7045
7046 GraphicsFactory.prototype.updateContainments = function(elements) {
7047
7048 var self = this,
7049 elementRegistry = this._elementRegistry,
7050 parents;
7051
7052 parents = reduce(elements, function(map, e) {
7053
7054 if (e.parent) {
7055 map[e.parent.id] = e.parent;
7056 }
7057
7058 return map;
7059 }, {});
7060
7061 // update all parents of changed and reorganized their children
7062 // in the correct order (as indicated in our model)
7063 forEach(parents, function(parent) {
7064
7065 var children = parent.children;
7066
7067 if (!children) {
7068 return;
7069 }
7070
7071 var childrenGfx = self._getChildrenContainer(parent);
7072
7073 forEach(children.slice().reverse(), function(child) {
7074 var childGfx = elementRegistry.getGraphics(child);
7075
7076 prependTo(childGfx.parentNode, childrenGfx);
7077 });
7078 });
7079 };
7080
7081 GraphicsFactory.prototype.drawShape = function(visual, element) {
7082 var eventBus = this._eventBus;
7083
7084 return eventBus.fire('render.shape', { gfx: visual, element: element });
7085 };
7086
7087 GraphicsFactory.prototype.getShapePath = function(element) {
7088 var eventBus = this._eventBus;
7089
7090 return eventBus.fire('render.getShapePath', element);
7091 };
7092
7093 GraphicsFactory.prototype.drawConnection = function(visual, element) {
7094 var eventBus = this._eventBus;
7095
7096 return eventBus.fire('render.connection', { gfx: visual, element: element });
7097 };
7098
7099 GraphicsFactory.prototype.getConnectionPath = function(waypoints) {
7100 var eventBus = this._eventBus;
7101
7102 return eventBus.fire('render.getConnectionPath', waypoints);
7103 };
7104
7105 GraphicsFactory.prototype.update = function(type, element, gfx) {
7106
7107 // do NOT update root element
7108 if (!element.parent) {
7109 return;
7110 }
7111
7112 var visual = this._clear(gfx);
7113
7114 // redraw
7115 if (type === 'shape') {
7116 this.drawShape(visual, element);
7117
7118 // update positioning
7119 translate(gfx, element.x, element.y);
7120 } else
7121 if (type === 'connection') {
7122 this.drawConnection(visual, element);
7123 } else {
7124 throw new Error('unknown type: ' + type);
7125 }
7126
7127 if (element.hidden) {
7128 attr$1(gfx, 'display', 'none');
7129 } else {
7130 attr$1(gfx, 'display', 'block');
7131 }
7132 };
7133
7134 GraphicsFactory.prototype.remove = function(element) {
7135 var gfx = this._elementRegistry.getGraphics(element);
7136
7137 // remove
7138 remove$1(gfx.parentNode);
7139 };
7140
7141
7142 // helpers //////////
7143
7144 function prependTo(newNode, parentNode, siblingNode) {
7145 var node = siblingNode || parentNode.firstChild;
7146
7147 // do not prepend node to itself to prevent IE from crashing
7148 // https://github.com/bpmn-io/bpmn-js/issues/746
7149 if (newNode === node) {
7150 return;
7151 }
7152
7153 parentNode.insertBefore(newNode, node);
7154 }
7155
7156 var CoreModule = {
7157 __depends__: [ DrawModule ],
7158 __init__: [ 'canvas' ],
7159 canvas: [ 'type', Canvas ],
7160 elementRegistry: [ 'type', ElementRegistry ],
7161 elementFactory: [ 'type', ElementFactory ],
7162 eventBus: [ 'type', EventBus ],
7163 graphicsFactory: [ 'type', GraphicsFactory ]
7164 };
7165
7166 /**
7167 * Bootstrap an injector from a list of modules, instantiating a number of default components
7168 *
7169 * @ignore
7170 * @param {Array<didi.Module>} bootstrapModules
7171 *
7172 * @return {didi.Injector} a injector to use to access the components
7173 */
7174 function bootstrap(bootstrapModules) {
7175
7176 var modules = [],
7177 components = [];
7178
7179 function hasModule(m) {
7180 return modules.indexOf(m) >= 0;
7181 }
7182
7183 function addModule(m) {
7184 modules.push(m);
7185 }
7186
7187 function visit(m) {
7188 if (hasModule(m)) {
7189 return;
7190 }
7191
7192 (m.__depends__ || []).forEach(visit);
7193
7194 if (hasModule(m)) {
7195 return;
7196 }
7197
7198 addModule(m);
7199
7200 (m.__init__ || []).forEach(function(c) {
7201 components.push(c);
7202 });
7203 }
7204
7205 bootstrapModules.forEach(visit);
7206
7207 var injector = new Injector(modules);
7208
7209 components.forEach(function(c) {
7210
7211 try {
7212
7213 // eagerly resolve component (fn or string)
7214 injector[typeof c === 'string' ? 'get' : 'invoke'](c);
7215 } catch (e) {
7216 console.error('Failed to instantiate component');
7217 console.error(e.stack);
7218
7219 throw e;
7220 }
7221 });
7222
7223 return injector;
7224 }
7225
7226 /**
7227 * Creates an injector from passed options.
7228 *
7229 * @ignore
7230 * @param {Object} options
7231 * @return {didi.Injector}
7232 */
7233 function createInjector(options) {
7234
7235 options = options || {};
7236
7237 var configModule = {
7238 'config': ['value', options]
7239 };
7240
7241 var modules = [ configModule, CoreModule ].concat(options.modules || []);
7242
7243 return bootstrap(modules);
7244 }
7245
7246
7247 /**
7248 * The main diagram-js entry point that bootstraps the diagram with the given
7249 * configuration.
7250 *
7251 * To register extensions with the diagram, pass them as Array<didi.Module> to the constructor.
7252 *
7253 * @class djs.Diagram
7254 * @memberOf djs
7255 * @constructor
7256 *
7257 * @example
7258 *
7259 * <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption>
7260 *
7261 * // plug-in implemenentation
7262 * function MyLoggingPlugin(eventBus) {
7263 * eventBus.on('shape.added', function(event) {
7264 * console.log('shape ', event.shape, ' was added to the diagram');
7265 * });
7266 * }
7267 *
7268 * // export as module
7269 * export default {
7270 * __init__: [ 'myLoggingPlugin' ],
7271 * myLoggingPlugin: [ 'type', MyLoggingPlugin ]
7272 * };
7273 *
7274 *
7275 * // instantiate the diagram with the new plug-in
7276 *
7277 * import MyLoggingModule from 'path-to-my-logging-plugin';
7278 *
7279 * var diagram = new Diagram({
7280 * modules: [
7281 * MyLoggingModule
7282 * ]
7283 * });
7284 *
7285 * diagram.invoke([ 'canvas', function(canvas) {
7286 * // add shape to drawing canvas
7287 * canvas.addShape({ x: 10, y: 10 });
7288 * });
7289 *
7290 * // 'shape ... was added to the diagram' logged to console
7291 *
7292 * @param {Object} options
7293 * @param {Array<didi.Module>} [options.modules] external modules to instantiate with the diagram
7294 * @param {didi.Injector} [injector] an (optional) injector to bootstrap the diagram with
7295 */
7296 function Diagram(options, injector) {
7297
7298 // create injector unless explicitly specified
7299 this.injector = injector = injector || createInjector(options);
7300
7301 // API
7302
7303 /**
7304 * Resolves a diagram service
7305 *
7306 * @method Diagram#get
7307 *
7308 * @param {string} name the name of the diagram service to be retrieved
7309 * @param {boolean} [strict=true] if false, resolve missing services to null
7310 */
7311 this.get = injector.get;
7312
7313 /**
7314 * Executes a function into which diagram services are injected
7315 *
7316 * @method Diagram#invoke
7317 *
7318 * @param {Function|Object[]} fn the function to resolve
7319 * @param {Object} locals a number of locals to use to resolve certain dependencies
7320 */
7321 this.invoke = injector.invoke;
7322
7323 // init
7324
7325 // indicate via event
7326
7327
7328 /**
7329 * An event indicating that all plug-ins are loaded.
7330 *
7331 * Use this event to fire other events to interested plug-ins
7332 *
7333 * @memberOf Diagram
7334 *
7335 * @event diagram.init
7336 *
7337 * @example
7338 *
7339 * eventBus.on('diagram.init', function() {
7340 * eventBus.fire('my-custom-event', { foo: 'BAR' });
7341 * });
7342 *
7343 * @type {Object}
7344 */
7345 this.get('eventBus').fire('diagram.init');
7346 }
7347
7348
7349 /**
7350 * Destroys the diagram
7351 *
7352 * @method Diagram#destroy
7353 */
7354 Diagram.prototype.destroy = function() {
7355 this.get('eventBus').fire('diagram.destroy');
7356 };
7357
7358 /**
7359 * Clear the diagram, removing all contents.
7360 */
7361 Diagram.prototype.clear = function() {
7362 this.get('eventBus').fire('diagram.clear');
7363 };
7364
7365 /**
7366 * Moddle base element.
7367 */
7368 function Base$1() { }
7369
7370 Base$1.prototype.get = function(name) {
7371 return this.$model.properties.get(this, name);
7372 };
7373
7374 Base$1.prototype.set = function(name, value) {
7375 this.$model.properties.set(this, name, value);
7376 };
7377
7378 /**
7379 * A model element factory.
7380 *
7381 * @param {Moddle} model
7382 * @param {Properties} properties
7383 */
7384 function Factory(model, properties) {
7385 this.model = model;
7386 this.properties = properties;
7387 }
7388
7389
7390 Factory.prototype.createType = function(descriptor) {
7391
7392 var model = this.model;
7393
7394 var props = this.properties,
7395 prototype = Object.create(Base$1.prototype);
7396
7397 // initialize default values
7398 forEach(descriptor.properties, function(p) {
7399 if (!p.isMany && p.default !== undefined) {
7400 prototype[p.name] = p.default;
7401 }
7402 });
7403
7404 props.defineModel(prototype, model);
7405 props.defineDescriptor(prototype, descriptor);
7406
7407 var name = descriptor.ns.name;
7408
7409 /**
7410 * The new type constructor
7411 */
7412 function ModdleElement(attrs) {
7413 props.define(this, '$type', { value: name, enumerable: true });
7414 props.define(this, '$attrs', { value: {} });
7415 props.define(this, '$parent', { writable: true });
7416
7417 forEach(attrs, bind(function(val, key) {
7418 this.set(key, val);
7419 }, this));
7420 }
7421
7422 ModdleElement.prototype = prototype;
7423
7424 ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;
7425
7426 // static links
7427 props.defineModel(ModdleElement, model);
7428 props.defineDescriptor(ModdleElement, descriptor);
7429
7430 return ModdleElement;
7431 };
7432
7433 /**
7434 * Built-in moddle types
7435 */
7436 var BUILTINS = {
7437 String: true,
7438 Boolean: true,
7439 Integer: true,
7440 Real: true,
7441 Element: true
7442 };
7443
7444 /**
7445 * Converters for built in types from string representations
7446 */
7447 var TYPE_CONVERTERS = {
7448 String: function(s) { return s; },
7449 Boolean: function(s) { return s === 'true'; },
7450 Integer: function(s) { return parseInt(s, 10); },
7451 Real: function(s) { return parseFloat(s); }
7452 };
7453
7454 /**
7455 * Convert a type to its real representation
7456 */
7457 function coerceType(type, value) {
7458
7459 var converter = TYPE_CONVERTERS[type];
7460
7461 if (converter) {
7462 return converter(value);
7463 } else {
7464 return value;
7465 }
7466 }
7467
7468 /**
7469 * Return whether the given type is built-in
7470 */
7471 function isBuiltIn(type) {
7472 return !!BUILTINS[type];
7473 }
7474
7475 /**
7476 * Return whether the given type is simple
7477 */
7478 function isSimple(type) {
7479 return !!TYPE_CONVERTERS[type];
7480 }
7481
7482 /**
7483 * Parses a namespaced attribute name of the form (ns:)localName to an object,
7484 * given a default prefix to assume in case no explicit namespace is given.
7485 *
7486 * @param {String} name
7487 * @param {String} [defaultPrefix] the default prefix to take, if none is present.
7488 *
7489 * @return {Object} the parsed name
7490 */
7491 function parseName(name, defaultPrefix) {
7492 var parts = name.split(/:/),
7493 localName, prefix;
7494
7495 // no prefix (i.e. only local name)
7496 if (parts.length === 1) {
7497 localName = name;
7498 prefix = defaultPrefix;
7499 } else
7500 // prefix + local name
7501 if (parts.length === 2) {
7502 localName = parts[1];
7503 prefix = parts[0];
7504 } else {
7505 throw new Error('expected <prefix:localName> or <localName>, got ' + name);
7506 }
7507
7508 name = (prefix ? prefix + ':' : '') + localName;
7509
7510 return {
7511 name: name,
7512 prefix: prefix,
7513 localName: localName
7514 };
7515 }
7516
7517 /**
7518 * A utility to build element descriptors.
7519 */
7520 function DescriptorBuilder(nameNs) {
7521 this.ns = nameNs;
7522 this.name = nameNs.name;
7523 this.allTypes = [];
7524 this.allTypesByName = {};
7525 this.properties = [];
7526 this.propertiesByName = {};
7527 }
7528
7529
7530 DescriptorBuilder.prototype.build = function() {
7531 return pick(this, [
7532 'ns',
7533 'name',
7534 'allTypes',
7535 'allTypesByName',
7536 'properties',
7537 'propertiesByName',
7538 'bodyProperty',
7539 'idProperty'
7540 ]);
7541 };
7542
7543 /**
7544 * Add property at given index.
7545 *
7546 * @param {Object} p
7547 * @param {Number} [idx]
7548 * @param {Boolean} [validate=true]
7549 */
7550 DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {
7551
7552 if (typeof idx === 'boolean') {
7553 validate = idx;
7554 idx = undefined;
7555 }
7556
7557 this.addNamedProperty(p, validate !== false);
7558
7559 var properties = this.properties;
7560
7561 if (idx !== undefined) {
7562 properties.splice(idx, 0, p);
7563 } else {
7564 properties.push(p);
7565 }
7566 };
7567
7568
7569 DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
7570 var oldNameNs = oldProperty.ns;
7571
7572 var props = this.properties,
7573 propertiesByName = this.propertiesByName,
7574 rename = oldProperty.name !== newProperty.name;
7575
7576 if (oldProperty.isId) {
7577 if (!newProperty.isId) {
7578 throw new Error(
7579 'property <' + newProperty.ns.name + '> must be id property ' +
7580 'to refine <' + oldProperty.ns.name + '>');
7581 }
7582
7583 this.setIdProperty(newProperty, false);
7584 }
7585
7586 if (oldProperty.isBody) {
7587
7588 if (!newProperty.isBody) {
7589 throw new Error(
7590 'property <' + newProperty.ns.name + '> must be body property ' +
7591 'to refine <' + oldProperty.ns.name + '>');
7592 }
7593
7594 // TODO: Check compatibility
7595 this.setBodyProperty(newProperty, false);
7596 }
7597
7598 // validate existence and get location of old property
7599 var idx = props.indexOf(oldProperty);
7600 if (idx === -1) {
7601 throw new Error('property <' + oldNameNs.name + '> not found in property list');
7602 }
7603
7604 // remove old property
7605 props.splice(idx, 1);
7606
7607 // replacing the named property is intentional
7608 //
7609 // * validate only if this is a "rename" operation
7610 // * add at specific index unless we "replace"
7611 //
7612 this.addProperty(newProperty, replace ? undefined : idx, rename);
7613
7614 // make new property available under old name
7615 propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
7616 };
7617
7618
7619 DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {
7620
7621 var nsPrefix = p.ns.prefix;
7622 var parts = targetPropertyName.split('#');
7623
7624 var name = parseName(parts[0], nsPrefix);
7625 var attrName = parseName(parts[1], name.prefix).name;
7626
7627 var redefinedProperty = this.propertiesByName[attrName];
7628 if (!redefinedProperty) {
7629 throw new Error('refined property <' + attrName + '> not found');
7630 } else {
7631 this.replaceProperty(redefinedProperty, p, replace);
7632 }
7633
7634 delete p.redefines;
7635 };
7636
7637 DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
7638 var ns = p.ns,
7639 propsByName = this.propertiesByName;
7640
7641 if (validate) {
7642 this.assertNotDefined(p, ns.name);
7643 this.assertNotDefined(p, ns.localName);
7644 }
7645
7646 propsByName[ns.name] = propsByName[ns.localName] = p;
7647 };
7648
7649 DescriptorBuilder.prototype.removeNamedProperty = function(p) {
7650 var ns = p.ns,
7651 propsByName = this.propertiesByName;
7652
7653 delete propsByName[ns.name];
7654 delete propsByName[ns.localName];
7655 };
7656
7657 DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {
7658
7659 if (validate && this.bodyProperty) {
7660 throw new Error(
7661 'body property defined multiple times ' +
7662 '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
7663 }
7664
7665 this.bodyProperty = p;
7666 };
7667
7668 DescriptorBuilder.prototype.setIdProperty = function(p, validate) {
7669
7670 if (validate && this.idProperty) {
7671 throw new Error(
7672 'id property defined multiple times ' +
7673 '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
7674 }
7675
7676 this.idProperty = p;
7677 };
7678
7679 DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
7680 var propertyName = p.name,
7681 definedProperty = this.propertiesByName[propertyName];
7682
7683 if (definedProperty) {
7684 throw new Error(
7685 'property <' + propertyName + '> already defined; ' +
7686 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
7687 '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
7688 }
7689 };
7690
7691 DescriptorBuilder.prototype.hasProperty = function(name) {
7692 return this.propertiesByName[name];
7693 };
7694
7695 DescriptorBuilder.prototype.addTrait = function(t, inherited) {
7696
7697 var typesByName = this.allTypesByName,
7698 types = this.allTypes;
7699
7700 var typeName = t.name;
7701
7702 if (typeName in typesByName) {
7703 return;
7704 }
7705
7706 forEach(t.properties, bind(function(p) {
7707
7708 // clone property to allow extensions
7709 p = assign({}, p, {
7710 name: p.ns.localName,
7711 inherited: inherited
7712 });
7713
7714 Object.defineProperty(p, 'definedBy', {
7715 value: t
7716 });
7717
7718 var replaces = p.replaces,
7719 redefines = p.redefines;
7720
7721 // add replace/redefine support
7722 if (replaces || redefines) {
7723 this.redefineProperty(p, replaces || redefines, replaces);
7724 } else {
7725 if (p.isBody) {
7726 this.setBodyProperty(p);
7727 }
7728 if (p.isId) {
7729 this.setIdProperty(p);
7730 }
7731 this.addProperty(p);
7732 }
7733 }, this));
7734
7735 types.push(t);
7736 typesByName[typeName] = t;
7737 };
7738
7739 /**
7740 * A registry of Moddle packages.
7741 *
7742 * @param {Array<Package>} packages
7743 * @param {Properties} properties
7744 */
7745 function Registry(packages, properties) {
7746 this.packageMap = {};
7747 this.typeMap = {};
7748
7749 this.packages = [];
7750
7751 this.properties = properties;
7752
7753 forEach(packages, bind(this.registerPackage, this));
7754 }
7755
7756
7757 Registry.prototype.getPackage = function(uriOrPrefix) {
7758 return this.packageMap[uriOrPrefix];
7759 };
7760
7761 Registry.prototype.getPackages = function() {
7762 return this.packages;
7763 };
7764
7765
7766 Registry.prototype.registerPackage = function(pkg) {
7767
7768 // copy package
7769 pkg = assign({}, pkg);
7770
7771 var pkgMap = this.packageMap;
7772
7773 ensureAvailable(pkgMap, pkg, 'prefix');
7774 ensureAvailable(pkgMap, pkg, 'uri');
7775
7776 // register types
7777 forEach(pkg.types, bind(function(descriptor) {
7778 this.registerType(descriptor, pkg);
7779 }, this));
7780
7781 pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
7782 this.packages.push(pkg);
7783 };
7784
7785
7786 /**
7787 * Register a type from a specific package with us
7788 */
7789 Registry.prototype.registerType = function(type, pkg) {
7790
7791 type = assign({}, type, {
7792 superClass: (type.superClass || []).slice(),
7793 extends: (type.extends || []).slice(),
7794 properties: (type.properties || []).slice(),
7795 meta: assign((type.meta || {}))
7796 });
7797
7798 var ns = parseName(type.name, pkg.prefix),
7799 name = ns.name,
7800 propertiesByName = {};
7801
7802 // parse properties
7803 forEach(type.properties, bind(function(p) {
7804
7805 // namespace property names
7806 var propertyNs = parseName(p.name, ns.prefix),
7807 propertyName = propertyNs.name;
7808
7809 // namespace property types
7810 if (!isBuiltIn(p.type)) {
7811 p.type = parseName(p.type, propertyNs.prefix).name;
7812 }
7813
7814 assign(p, {
7815 ns: propertyNs,
7816 name: propertyName
7817 });
7818
7819 propertiesByName[propertyName] = p;
7820 }, this));
7821
7822 // update ns + name
7823 assign(type, {
7824 ns: ns,
7825 name: name,
7826 propertiesByName: propertiesByName
7827 });
7828
7829 forEach(type.extends, bind(function(extendsName) {
7830 var extended = this.typeMap[extendsName];
7831
7832 extended.traits = extended.traits || [];
7833 extended.traits.push(name);
7834 }, this));
7835
7836 // link to package
7837 this.definePackage(type, pkg);
7838
7839 // register
7840 this.typeMap[name] = type;
7841 };
7842
7843
7844 /**
7845 * Traverse the type hierarchy from bottom to top,
7846 * calling iterator with (type, inherited) for all elements in
7847 * the inheritance chain.
7848 *
7849 * @param {Object} nsName
7850 * @param {Function} iterator
7851 * @param {Boolean} [trait=false]
7852 */
7853 Registry.prototype.mapTypes = function(nsName, iterator, trait) {
7854
7855 var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name];
7856
7857 var self = this;
7858
7859 /**
7860 * Traverse the selected trait.
7861 *
7862 * @param {String} cls
7863 */
7864 function traverseTrait(cls) {
7865 return traverseSuper(cls, true);
7866 }
7867
7868 /**
7869 * Traverse the selected super type or trait
7870 *
7871 * @param {String} cls
7872 * @param {Boolean} [trait=false]
7873 */
7874 function traverseSuper(cls, trait) {
7875 var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
7876 self.mapTypes(parentNs, iterator, trait);
7877 }
7878
7879 if (!type) {
7880 throw new Error('unknown type <' + nsName.name + '>');
7881 }
7882
7883 forEach(type.superClass, trait ? traverseTrait : traverseSuper);
7884
7885 // call iterator with (type, inherited=!trait)
7886 iterator(type, !trait);
7887
7888 forEach(type.traits, traverseTrait);
7889 };
7890
7891
7892 /**
7893 * Returns the effective descriptor for a type.
7894 *
7895 * @param {String} type the namespaced name (ns:localName) of the type
7896 *
7897 * @return {Descriptor} the resulting effective descriptor
7898 */
7899 Registry.prototype.getEffectiveDescriptor = function(name) {
7900
7901 var nsName = parseName(name);
7902
7903 var builder = new DescriptorBuilder(nsName);
7904
7905 this.mapTypes(nsName, function(type, inherited) {
7906 builder.addTrait(type, inherited);
7907 });
7908
7909 var descriptor = builder.build();
7910
7911 // define package link
7912 this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);
7913
7914 return descriptor;
7915 };
7916
7917
7918 Registry.prototype.definePackage = function(target, pkg) {
7919 this.properties.define(target, '$pkg', { value: pkg });
7920 };
7921
7922
7923
7924 ///////// helpers ////////////////////////////
7925
7926 function ensureAvailable(packageMap, pkg, identifierKey) {
7927
7928 var value = pkg[identifierKey];
7929
7930 if (value in packageMap) {
7931 throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
7932 }
7933 }
7934
7935 /**
7936 * A utility that gets and sets properties of model elements.
7937 *
7938 * @param {Model} model
7939 */
7940 function Properties(model) {
7941 this.model = model;
7942 }
7943
7944
7945 /**
7946 * Sets a named property on the target element.
7947 * If the value is undefined, the property gets deleted.
7948 *
7949 * @param {Object} target
7950 * @param {String} name
7951 * @param {Object} value
7952 */
7953 Properties.prototype.set = function(target, name, value) {
7954
7955 var property = this.model.getPropertyDescriptor(target, name);
7956
7957 var propertyName = property && property.name;
7958
7959 if (isUndefined$1(value)) {
7960 // unset the property, if the specified value is undefined;
7961 // delete from $attrs (for extensions) or the target itself
7962 if (property) {
7963 delete target[propertyName];
7964 } else {
7965 delete target.$attrs[name];
7966 }
7967 } else {
7968 // set the property, defining well defined properties on the fly
7969 // or simply updating them in target.$attrs (for extensions)
7970 if (property) {
7971 if (propertyName in target) {
7972 target[propertyName] = value;
7973 } else {
7974 defineProperty$1(target, property, value);
7975 }
7976 } else {
7977 target.$attrs[name] = value;
7978 }
7979 }
7980 };
7981
7982 /**
7983 * Returns the named property of the given element
7984 *
7985 * @param {Object} target
7986 * @param {String} name
7987 *
7988 * @return {Object}
7989 */
7990 Properties.prototype.get = function(target, name) {
7991
7992 var property = this.model.getPropertyDescriptor(target, name);
7993
7994 if (!property) {
7995 return target.$attrs[name];
7996 }
7997
7998 var propertyName = property.name;
7999
8000 // check if access to collection property and lazily initialize it
8001 if (!target[propertyName] && property.isMany) {
8002 defineProperty$1(target, property, []);
8003 }
8004
8005 return target[propertyName];
8006 };
8007
8008
8009 /**
8010 * Define a property on the target element
8011 *
8012 * @param {Object} target
8013 * @param {String} name
8014 * @param {Object} options
8015 */
8016 Properties.prototype.define = function(target, name, options) {
8017 Object.defineProperty(target, name, options);
8018 };
8019
8020
8021 /**
8022 * Define the descriptor for an element
8023 */
8024 Properties.prototype.defineDescriptor = function(target, descriptor) {
8025 this.define(target, '$descriptor', { value: descriptor });
8026 };
8027
8028 /**
8029 * Define the model for an element
8030 */
8031 Properties.prototype.defineModel = function(target, model) {
8032 this.define(target, '$model', { value: model });
8033 };
8034
8035
8036 function isUndefined$1(val) {
8037 return typeof val === 'undefined';
8038 }
8039
8040 function defineProperty$1(target, property, value) {
8041 Object.defineProperty(target, property.name, {
8042 enumerable: !property.isReference,
8043 writable: true,
8044 value: value,
8045 configurable: true
8046 });
8047 }
8048
8049 //// Moddle implementation /////////////////////////////////////////////////
8050
8051 /**
8052 * @class Moddle
8053 *
8054 * A model that can be used to create elements of a specific type.
8055 *
8056 * @example
8057 *
8058 * var Moddle = require('moddle');
8059 *
8060 * var pkg = {
8061 * name: 'mypackage',
8062 * prefix: 'my',
8063 * types: [
8064 * { name: 'Root' }
8065 * ]
8066 * };
8067 *
8068 * var moddle = new Moddle([pkg]);
8069 *
8070 * @param {Array<Package>} packages the packages to contain
8071 */
8072 function Moddle(packages) {
8073
8074 this.properties = new Properties(this);
8075
8076 this.factory = new Factory(this, this.properties);
8077 this.registry = new Registry(packages, this.properties);
8078
8079 this.typeCache = {};
8080 }
8081
8082
8083 /**
8084 * Create an instance of the specified type.
8085 *
8086 * @method Moddle#create
8087 *
8088 * @example
8089 *
8090 * var foo = moddle.create('my:Foo');
8091 * var bar = moddle.create('my:Bar', { id: 'BAR_1' });
8092 *
8093 * @param {String|Object} descriptor the type descriptor or name know to the model
8094 * @param {Object} attrs a number of attributes to initialize the model instance with
8095 * @return {Object} model instance
8096 */
8097 Moddle.prototype.create = function(descriptor, attrs) {
8098 var Type = this.getType(descriptor);
8099
8100 if (!Type) {
8101 throw new Error('unknown type <' + descriptor + '>');
8102 }
8103
8104 return new Type(attrs);
8105 };
8106
8107
8108 /**
8109 * Returns the type representing a given descriptor
8110 *
8111 * @method Moddle#getType
8112 *
8113 * @example
8114 *
8115 * var Foo = moddle.getType('my:Foo');
8116 * var foo = new Foo({ 'id' : 'FOO_1' });
8117 *
8118 * @param {String|Object} descriptor the type descriptor or name know to the model
8119 * @return {Object} the type representing the descriptor
8120 */
8121 Moddle.prototype.getType = function(descriptor) {
8122
8123 var cache = this.typeCache;
8124
8125 var name = isString(descriptor) ? descriptor : descriptor.ns.name;
8126
8127 var type = cache[name];
8128
8129 if (!type) {
8130 descriptor = this.registry.getEffectiveDescriptor(name);
8131 type = cache[name] = this.factory.createType(descriptor);
8132 }
8133
8134 return type;
8135 };
8136
8137
8138 /**
8139 * Creates an any-element type to be used within model instances.
8140 *
8141 * This can be used to create custom elements that lie outside the meta-model.
8142 * The created element contains all the meta-data required to serialize it
8143 * as part of meta-model elements.
8144 *
8145 * @method Moddle#createAny
8146 *
8147 * @example
8148 *
8149 * var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
8150 * value: 'bar'
8151 * });
8152 *
8153 * var container = moddle.create('my:Container', 'http://my', {
8154 * any: [ foo ]
8155 * });
8156 *
8157 * // go ahead and serialize the stuff
8158 *
8159 *
8160 * @param {String} name the name of the element
8161 * @param {String} nsUri the namespace uri of the element
8162 * @param {Object} [properties] a map of properties to initialize the instance with
8163 * @return {Object} the any type instance
8164 */
8165 Moddle.prototype.createAny = function(name, nsUri, properties) {
8166
8167 var nameNs = parseName(name);
8168
8169 var element = {
8170 $type: name,
8171 $instanceOf: function(type) {
8172 return type === this.$type;
8173 }
8174 };
8175
8176 var descriptor = {
8177 name: name,
8178 isGeneric: true,
8179 ns: {
8180 prefix: nameNs.prefix,
8181 localName: nameNs.localName,
8182 uri: nsUri
8183 }
8184 };
8185
8186 this.properties.defineDescriptor(element, descriptor);
8187 this.properties.defineModel(element, this);
8188 this.properties.define(element, '$parent', { enumerable: false, writable: true });
8189 this.properties.define(element, '$instanceOf', { enumerable: false, writable: true });
8190
8191 forEach(properties, function(a, key) {
8192 if (isObject(a) && a.value !== undefined) {
8193 element[a.name] = a.value;
8194 } else {
8195 element[key] = a;
8196 }
8197 });
8198
8199 return element;
8200 };
8201
8202 /**
8203 * Returns a registered package by uri or prefix
8204 *
8205 * @return {Object} the package
8206 */
8207 Moddle.prototype.getPackage = function(uriOrPrefix) {
8208 return this.registry.getPackage(uriOrPrefix);
8209 };
8210
8211 /**
8212 * Returns a snapshot of all known packages
8213 *
8214 * @return {Object} the package
8215 */
8216 Moddle.prototype.getPackages = function() {
8217 return this.registry.getPackages();
8218 };
8219
8220 /**
8221 * Returns the descriptor for an element
8222 */
8223 Moddle.prototype.getElementDescriptor = function(element) {
8224 return element.$descriptor;
8225 };
8226
8227 /**
8228 * Returns true if the given descriptor or instance
8229 * represents the given type.
8230 *
8231 * May be applied to this, if element is omitted.
8232 */
8233 Moddle.prototype.hasType = function(element, type) {
8234 if (type === undefined) {
8235 type = element;
8236 element = this;
8237 }
8238
8239 var descriptor = element.$model.getElementDescriptor(element);
8240
8241 return (type in descriptor.allTypesByName);
8242 };
8243
8244 /**
8245 * Returns the descriptor of an elements named property
8246 */
8247 Moddle.prototype.getPropertyDescriptor = function(element, property) {
8248 return this.getElementDescriptor(element).propertiesByName[property];
8249 };
8250
8251 /**
8252 * Returns a mapped type's descriptor
8253 */
8254 Moddle.prototype.getTypeDescriptor = function(type) {
8255 return this.registry.typeMap[type];
8256 };
8257
8258 var fromCharCode = String.fromCharCode;
8259
8260 var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
8261
8262 var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig;
8263
8264 var ENTITY_MAPPING = {
8265 'amp': '&',
8266 'apos': '\'',
8267 'gt': '>',
8268 'lt': '<',
8269 'quot': '"'
8270 };
8271
8272 // map UPPERCASE variants of supported special chars
8273 Object.keys(ENTITY_MAPPING).forEach(function(k) {
8274 ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k];
8275 });
8276
8277
8278 function replaceEntities(_, d, x, z) {
8279
8280 // reserved names, i.e. &nbsp;
8281 if (z) {
8282 if (hasOwnProperty$1.call(ENTITY_MAPPING, z)) {
8283 return ENTITY_MAPPING[z];
8284 } else {
8285
8286 // fall back to original value
8287 return '&' + z + ';';
8288 }
8289 }
8290
8291 // decimal encoded char
8292 if (d) {
8293 return fromCharCode(d);
8294 }
8295
8296 // hex encoded char
8297 return fromCharCode(parseInt(x, 16));
8298 }
8299
8300
8301 /**
8302 * A basic entity decoder that can decode a minimal
8303 * sub-set of reserved names (&amp;) as well as
8304 * hex (&#xaaf;) and decimal (&#1231;) encoded characters.
8305 *
8306 * @param {string} str
8307 *
8308 * @return {string} decoded string
8309 */
8310 function decodeEntities(s) {
8311 if (s.length > 3 && s.indexOf('&') !== -1) {
8312 return s.replace(ENTITY_PATTERN, replaceEntities);
8313 }
8314
8315 return s;
8316 }
8317
8318 var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance';
8319 var XSI_PREFIX = 'xsi';
8320 var XSI_TYPE = 'xsi:type';
8321
8322 var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';
8323
8324 function error(msg) {
8325 return new Error(msg);
8326 }
8327
8328 function missingNamespaceForPrefix(prefix) {
8329 return 'missing namespace for prefix <' + prefix + '>';
8330 }
8331
8332 function getter(getFn) {
8333 return {
8334 'get': getFn,
8335 'enumerable': true
8336 };
8337 }
8338
8339 function cloneNsMatrix(nsMatrix) {
8340 var clone = {}, key;
8341 for (key in nsMatrix) {
8342 clone[key] = nsMatrix[key];
8343 }
8344 return clone;
8345 }
8346
8347 function uriPrefix(prefix) {
8348 return prefix + '$uri';
8349 }
8350
8351 function buildNsMatrix(nsUriToPrefix) {
8352 var nsMatrix = {},
8353 uri,
8354 prefix;
8355
8356 for (uri in nsUriToPrefix) {
8357 prefix = nsUriToPrefix[uri];
8358 nsMatrix[prefix] = prefix;
8359 nsMatrix[uriPrefix(prefix)] = uri;
8360 }
8361
8362 return nsMatrix;
8363 }
8364
8365 function noopGetContext() {
8366 return { 'line': 0, 'column': 0 };
8367 }
8368
8369 function throwFunc(err) {
8370 throw err;
8371 }
8372
8373 /**
8374 * Creates a new parser with the given options.
8375 *
8376 * @constructor
8377 *
8378 * @param {!Object<string, ?>=} options
8379 */
8380 function Parser(options) {
8381
8382 if (!this) {
8383 return new Parser(options);
8384 }
8385
8386 var proxy = options && options['proxy'];
8387
8388 var onText,
8389 onOpenTag,
8390 onCloseTag,
8391 onCDATA,
8392 onError = throwFunc,
8393 onWarning,
8394 onComment,
8395 onQuestion,
8396 onAttention;
8397
8398 var getContext = noopGetContext;
8399
8400 /**
8401 * Do we need to parse the current elements attributes for namespaces?
8402 *
8403 * @type {boolean}
8404 */
8405 var maybeNS = false;
8406
8407 /**
8408 * Do we process namespaces at all?
8409 *
8410 * @type {boolean}
8411 */
8412 var isNamespace = false;
8413
8414 /**
8415 * The caught error returned on parse end
8416 *
8417 * @type {Error}
8418 */
8419 var returnError = null;
8420
8421 /**
8422 * Should we stop parsing?
8423 *
8424 * @type {boolean}
8425 */
8426 var parseStop = false;
8427
8428 /**
8429 * A map of { uri: prefix } used by the parser.
8430 *
8431 * This map will ensure we can normalize prefixes during processing;
8432 * for each uri, only one prefix will be exposed to the handlers.
8433 *
8434 * @type {!Object<string, string>}}
8435 */
8436 var nsUriToPrefix;
8437
8438 /**
8439 * Handle parse error.
8440 *
8441 * @param {string|Error} err
8442 */
8443 function handleError(err) {
8444 if (!(err instanceof Error)) {
8445 err = error(err);
8446 }
8447
8448 returnError = err;
8449
8450 onError(err, getContext);
8451 }
8452
8453 /**
8454 * Handle parse error.
8455 *
8456 * @param {string|Error} err
8457 */
8458 function handleWarning(err) {
8459
8460 if (!onWarning) {
8461 return;
8462 }
8463
8464 if (!(err instanceof Error)) {
8465 err = error(err);
8466 }
8467
8468 onWarning(err, getContext);
8469 }
8470
8471 /**
8472 * Register parse listener.
8473 *
8474 * @param {string} name
8475 * @param {Function} cb
8476 *
8477 * @return {Parser}
8478 */
8479 this['on'] = function(name, cb) {
8480
8481 if (typeof cb !== 'function') {
8482 throw error('required args <name, cb>');
8483 }
8484
8485 switch (name) {
8486 case 'openTag': onOpenTag = cb; break;
8487 case 'text': onText = cb; break;
8488 case 'closeTag': onCloseTag = cb; break;
8489 case 'error': onError = cb; break;
8490 case 'warn': onWarning = cb; break;
8491 case 'cdata': onCDATA = cb; break;
8492 case 'attention': onAttention = cb; break; // <!XXXXX zzzz="eeee">
8493 case 'question': onQuestion = cb; break; // <? .... ?>
8494 case 'comment': onComment = cb; break;
8495 default:
8496 throw error('unsupported event: ' + name);
8497 }
8498
8499 return this;
8500 };
8501
8502 /**
8503 * Set the namespace to prefix mapping.
8504 *
8505 * @example
8506 *
8507 * parser.ns({
8508 * 'http://foo': 'foo',
8509 * 'http://bar': 'bar'
8510 * });
8511 *
8512 * @param {!Object<string, string>} nsMap
8513 *
8514 * @return {Parser}
8515 */
8516 this['ns'] = function(nsMap) {
8517
8518 if (typeof nsMap === 'undefined') {
8519 nsMap = {};
8520 }
8521
8522 if (typeof nsMap !== 'object') {
8523 throw error('required args <nsMap={}>');
8524 }
8525
8526 var _nsUriToPrefix = {}, k;
8527
8528 for (k in nsMap) {
8529 _nsUriToPrefix[k] = nsMap[k];
8530 }
8531
8532 // FORCE default mapping for schema instance
8533 _nsUriToPrefix[XSI_URI] = XSI_PREFIX;
8534
8535 isNamespace = true;
8536 nsUriToPrefix = _nsUriToPrefix;
8537
8538 return this;
8539 };
8540
8541 /**
8542 * Parse xml string.
8543 *
8544 * @param {string} xml
8545 *
8546 * @return {Error} returnError, if not thrown
8547 */
8548 this['parse'] = function(xml) {
8549 if (typeof xml !== 'string') {
8550 throw error('required args <xml=string>');
8551 }
8552
8553 returnError = null;
8554
8555 parse(xml);
8556
8557 getContext = noopGetContext;
8558 parseStop = false;
8559
8560 return returnError;
8561 };
8562
8563 /**
8564 * Stop parsing.
8565 */
8566 this['stop'] = function() {
8567 parseStop = true;
8568 };
8569
8570 /**
8571 * Parse string, invoking configured listeners on element.
8572 *
8573 * @param {string} xml
8574 */
8575 function parse(xml) {
8576 var nsMatrixStack = isNamespace ? [] : null,
8577 nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null,
8578 _nsMatrix,
8579 nodeStack = [],
8580 anonymousNsCount = 0,
8581 tagStart = false,
8582 tagEnd = false,
8583 i = 0, j = 0,
8584 x, y, q, w, v,
8585 xmlns,
8586 elementName,
8587 _elementName,
8588 elementProxy
8589 ;
8590
8591 var attrsString = '',
8592 attrsStart = 0,
8593 cachedAttrs // false = parsed with errors, null = needs parsing
8594 ;
8595
8596 /**
8597 * Parse attributes on demand and returns the parsed attributes.
8598 *
8599 * Return semantics: (1) `false` on attribute parse error,
8600 * (2) object hash on extracted attrs.
8601 *
8602 * @return {boolean|Object}
8603 */
8604 function getAttrs() {
8605 if (cachedAttrs !== null) {
8606 return cachedAttrs;
8607 }
8608
8609 var nsUri,
8610 nsUriPrefix,
8611 nsName,
8612 defaultAlias = isNamespace && nsMatrix['xmlns'],
8613 attrList = isNamespace && maybeNS ? [] : null,
8614 i = attrsStart,
8615 s = attrsString,
8616 l = s.length,
8617 hasNewMatrix,
8618 newalias,
8619 value,
8620 alias,
8621 name,
8622 attrs = {},
8623 seenAttrs = {},
8624 skipAttr,
8625 w,
8626 j;
8627
8628 parseAttr:
8629 for (; i < l; i++) {
8630 skipAttr = false;
8631 w = s.charCodeAt(i);
8632
8633 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v}
8634 continue;
8635 }
8636
8637 // wait for non whitespace character
8638 if (w < 65 || w > 122 || (w > 90 && w < 97)) {
8639 if (w !== 95 && w !== 58) { // char 95"_" 58":"
8640 handleWarning('illegal first char attribute name');
8641 skipAttr = true;
8642 }
8643 }
8644
8645 // parse attribute name
8646 for (j = i + 1; j < l; j++) {
8647 w = s.charCodeAt(j);
8648
8649 if (
8650 w > 96 && w < 123 ||
8651 w > 64 && w < 91 ||
8652 w > 47 && w < 59 ||
8653 w === 46 || // '.'
8654 w === 45 || // '-'
8655 w === 95 // '_'
8656 ) {
8657 continue;
8658 }
8659
8660 // unexpected whitespace
8661 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
8662 handleWarning('missing attribute value');
8663 i = j;
8664
8665 continue parseAttr;
8666 }
8667
8668 // expected "="
8669 if (w === 61) { // "=" == 61
8670 break;
8671 }
8672
8673 handleWarning('illegal attribute name char');
8674 skipAttr = true;
8675 }
8676
8677 name = s.substring(i, j);
8678
8679 if (name === 'xmlns:xmlns') {
8680 handleWarning('illegal declaration of xmlns');
8681 skipAttr = true;
8682 }
8683
8684 w = s.charCodeAt(j + 1);
8685
8686 if (w === 34) { // '"'
8687 j = s.indexOf('"', i = j + 2);
8688
8689 if (j === -1) {
8690 j = s.indexOf('\'', i);
8691
8692 if (j !== -1) {
8693 handleWarning('attribute value quote missmatch');
8694 skipAttr = true;
8695 }
8696 }
8697
8698 } else if (w === 39) { // "'"
8699 j = s.indexOf('\'', i = j + 2);
8700
8701 if (j === -1) {
8702 j = s.indexOf('"', i);
8703
8704 if (j !== -1) {
8705 handleWarning('attribute value quote missmatch');
8706 skipAttr = true;
8707 }
8708 }
8709
8710 } else {
8711 handleWarning('missing attribute value quotes');
8712 skipAttr = true;
8713
8714 // skip to next space
8715 for (j = j + 1; j < l; j++) {
8716 w = s.charCodeAt(j + 1);
8717
8718 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
8719 break;
8720 }
8721 }
8722
8723 }
8724
8725 if (j === -1) {
8726 handleWarning('missing closing quotes');
8727
8728 j = l;
8729 skipAttr = true;
8730 }
8731
8732 if (!skipAttr) {
8733 value = s.substring(i, j);
8734 }
8735
8736 i = j;
8737
8738 // ensure SPACE follows attribute
8739 // skip illegal content otherwise
8740 // example a="b"c
8741 for (; j + 1 < l; j++) {
8742 w = s.charCodeAt(j + 1);
8743
8744 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
8745 break;
8746 }
8747
8748 // FIRST ILLEGAL CHAR
8749 if (i === j) {
8750 handleWarning('illegal character after attribute end');
8751 skipAttr = true;
8752 }
8753 }
8754
8755 // advance cursor to next attribute
8756 i = j + 1;
8757
8758 if (skipAttr) {
8759 continue parseAttr;
8760 }
8761
8762 // check attribute re-declaration
8763 if (name in seenAttrs) {
8764 handleWarning('attribute <' + name + '> already defined');
8765 continue;
8766 }
8767
8768 seenAttrs[name] = true;
8769
8770 if (!isNamespace) {
8771 attrs[name] = value;
8772 continue;
8773 }
8774
8775 // try to extract namespace information
8776 if (maybeNS) {
8777 newalias = (
8778 name === 'xmlns'
8779 ? 'xmlns'
8780 : (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:')
8781 ? name.substr(6)
8782 : null
8783 );
8784
8785 // handle xmlns(:alias) assignment
8786 if (newalias !== null) {
8787 nsUri = decodeEntities(value);
8788 nsUriPrefix = uriPrefix(newalias);
8789
8790 alias = nsUriToPrefix[nsUri];
8791
8792 if (!alias) {
8793
8794 // no prefix defined or prefix collision
8795 if (
8796 (newalias === 'xmlns') ||
8797 (nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri)
8798 ) {
8799
8800 // alocate free ns prefix
8801 do {
8802 alias = 'ns' + (anonymousNsCount++);
8803 } while (typeof nsMatrix[alias] !== 'undefined');
8804 } else {
8805 alias = newalias;
8806 }
8807
8808 nsUriToPrefix[nsUri] = alias;
8809 }
8810
8811 if (nsMatrix[newalias] !== alias) {
8812 if (!hasNewMatrix) {
8813 nsMatrix = cloneNsMatrix(nsMatrix);
8814 hasNewMatrix = true;
8815 }
8816
8817 nsMatrix[newalias] = alias;
8818 if (newalias === 'xmlns') {
8819 nsMatrix[uriPrefix(alias)] = nsUri;
8820 defaultAlias = alias;
8821 }
8822
8823 nsMatrix[nsUriPrefix] = nsUri;
8824 }
8825
8826 // expose xmlns(:asd)="..." in attributes
8827 attrs[name] = value;
8828 continue;
8829 }
8830
8831 // collect attributes until all namespace
8832 // declarations are processed
8833 attrList.push(name, value);
8834 continue;
8835
8836 } /** end if (maybeNs) */
8837
8838 // handle attributes on element without
8839 // namespace declarations
8840 w = name.indexOf(':');
8841 if (w === -1) {
8842 attrs[name] = value;
8843 continue;
8844 }
8845
8846 // normalize ns attribute name
8847 if (!(nsName = nsMatrix[name.substring(0, w)])) {
8848 handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
8849 continue;
8850 }
8851
8852 name = defaultAlias === nsName
8853 ? name.substr(w + 1)
8854 : nsName + name.substr(w);
8855
8856 // end: normalize ns attribute name
8857
8858 // normalize xsi:type ns attribute value
8859 if (name === XSI_TYPE) {
8860 w = value.indexOf(':');
8861
8862 if (w !== -1) {
8863 nsName = value.substring(0, w);
8864
8865 // handle default prefixes, i.e. xs:String gracefully
8866 nsName = nsMatrix[nsName] || nsName;
8867 value = nsName + value.substring(w);
8868 } else {
8869 value = defaultAlias + ':' + value;
8870 }
8871 }
8872
8873 // end: normalize xsi:type ns attribute value
8874
8875 attrs[name] = value;
8876 }
8877
8878
8879 // handle deferred, possibly namespaced attributes
8880 if (maybeNS) {
8881
8882 // normalize captured attributes
8883 for (i = 0, l = attrList.length; i < l; i++) {
8884
8885 name = attrList[i++];
8886 value = attrList[i];
8887
8888 w = name.indexOf(':');
8889
8890 if (w !== -1) {
8891
8892 // normalize ns attribute name
8893 if (!(nsName = nsMatrix[name.substring(0, w)])) {
8894 handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
8895 continue;
8896 }
8897
8898 name = defaultAlias === nsName
8899 ? name.substr(w + 1)
8900 : nsName + name.substr(w);
8901
8902 // end: normalize ns attribute name
8903
8904 // normalize xsi:type ns attribute value
8905 if (name === XSI_TYPE) {
8906 w = value.indexOf(':');
8907
8908 if (w !== -1) {
8909 nsName = value.substring(0, w);
8910
8911 // handle default prefixes, i.e. xs:String gracefully
8912 nsName = nsMatrix[nsName] || nsName;
8913 value = nsName + value.substring(w);
8914 } else {
8915 value = defaultAlias + ':' + value;
8916 }
8917 }
8918
8919 // end: normalize xsi:type ns attribute value
8920 }
8921
8922 attrs[name] = value;
8923 }
8924
8925 // end: normalize captured attributes
8926 }
8927
8928 return cachedAttrs = attrs;
8929 }
8930
8931 /**
8932 * Extract the parse context { line, column, part }
8933 * from the current parser position.
8934 *
8935 * @return {Object} parse context
8936 */
8937 function getParseContext() {
8938 var splitsRe = /(\r\n|\r|\n)/g;
8939
8940 var line = 0;
8941 var column = 0;
8942 var startOfLine = 0;
8943 var endOfLine = j;
8944 var match;
8945 var data;
8946
8947 while (i >= startOfLine) {
8948
8949 match = splitsRe.exec(xml);
8950
8951 if (!match) {
8952 break;
8953 }
8954
8955 // end of line = (break idx + break chars)
8956 endOfLine = match[0].length + match.index;
8957
8958 if (endOfLine > i) {
8959 break;
8960 }
8961
8962 // advance to next line
8963 line += 1;
8964
8965 startOfLine = endOfLine;
8966 }
8967
8968 // EOF errors
8969 if (i == -1) {
8970 column = endOfLine;
8971 data = xml.substring(j);
8972 } else
8973
8974 // start errors
8975 if (j === 0) {
8976 data = xml.substring(j, i);
8977 }
8978
8979 // other errors
8980 else {
8981 column = i - startOfLine;
8982 data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1));
8983 }
8984
8985 return {
8986 'data': data,
8987 'line': line,
8988 'column': column
8989 };
8990 }
8991
8992 getContext = getParseContext;
8993
8994
8995 if (proxy) {
8996 elementProxy = Object.create({}, {
8997 'name': getter(function() {
8998 return elementName;
8999 }),
9000 'originalName': getter(function() {
9001 return _elementName;
9002 }),
9003 'attrs': getter(getAttrs),
9004 'ns': getter(function() {
9005 return nsMatrix;
9006 })
9007 });
9008 }
9009
9010 // actual parse logic
9011 while (j !== -1) {
9012
9013 if (xml.charCodeAt(j) === 60) { // "<"
9014 i = j;
9015 } else {
9016 i = xml.indexOf('<', j);
9017 }
9018
9019 // parse end
9020 if (i === -1) {
9021 if (nodeStack.length) {
9022 return handleError('unexpected end of file');
9023 }
9024
9025 if (j === 0) {
9026 return handleError('missing start tag');
9027 }
9028
9029 if (j < xml.length) {
9030 if (xml.substring(j).trim()) {
9031 handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
9032 }
9033 }
9034
9035 return;
9036 }
9037
9038 // parse text
9039 if (j !== i) {
9040
9041 if (nodeStack.length) {
9042 if (onText) {
9043 onText(xml.substring(j, i), decodeEntities, getContext);
9044
9045 if (parseStop) {
9046 return;
9047 }
9048 }
9049 } else {
9050 if (xml.substring(j, i).trim()) {
9051 handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
9052
9053 if (parseStop) {
9054 return;
9055 }
9056 }
9057 }
9058 }
9059
9060 w = xml.charCodeAt(i+1);
9061
9062 // parse comments + CDATA
9063 if (w === 33) { // "!"
9064 q = xml.charCodeAt(i+2);
9065
9066 // CDATA section
9067 if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "["
9068 j = xml.indexOf(']]>', i);
9069 if (j === -1) {
9070 return handleError('unclosed cdata');
9071 }
9072
9073 if (onCDATA) {
9074 onCDATA(xml.substring(i + 9, j), getContext);
9075 if (parseStop) {
9076 return;
9077 }
9078 }
9079
9080 j += 3;
9081 continue;
9082 }
9083
9084 // comment
9085 if (q === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-"
9086 j = xml.indexOf('-->', i);
9087 if (j === -1) {
9088 return handleError('unclosed comment');
9089 }
9090
9091
9092 if (onComment) {
9093 onComment(xml.substring(i + 4, j), decodeEntities, getContext);
9094 if (parseStop) {
9095 return;
9096 }
9097 }
9098
9099 j += 3;
9100 continue;
9101 }
9102 }
9103
9104 // parse question <? ... ?>
9105 if (w === 63) { // "?"
9106 j = xml.indexOf('?>', i);
9107 if (j === -1) {
9108 return handleError('unclosed question');
9109 }
9110
9111 if (onQuestion) {
9112 onQuestion(xml.substring(i, j + 2), getContext);
9113 if (parseStop) {
9114 return;
9115 }
9116 }
9117
9118 j += 2;
9119 continue;
9120 }
9121
9122 // find matching closing tag for attention or standard tags
9123 // for that we must skip through attribute values
9124 // (enclosed in single or double quotes)
9125 for (x = i + 1; ; x++) {
9126 v = xml.charCodeAt(x);
9127 if (isNaN(v)) {
9128 j = -1;
9129 return handleError('unclosed tag');
9130 }
9131
9132 // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"
9133 // skips the quoted string
9134 // (double quotes) does not appear in a literal enclosed by (double quotes)
9135 // (single quote) does not appear in a literal enclosed by (single quote)
9136 if (v === 34) { // '"'
9137 q = xml.indexOf('"', x + 1);
9138 x = q !== -1 ? q : x;
9139 } else if (v === 39) { // "'"
9140 q = xml.indexOf("'", x + 1);
9141 x = q !== -1 ? q : x;
9142 } else if (v === 62) { // '>'
9143 j = x;
9144 break;
9145 }
9146 }
9147
9148
9149 // parse attention <! ...>
9150 // previously comment and CDATA have already been parsed
9151 if (w === 33) { // "!"
9152
9153 if (onAttention) {
9154 onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
9155 if (parseStop) {
9156 return;
9157 }
9158 }
9159
9160 j += 1;
9161 continue;
9162 }
9163
9164 // don't process attributes;
9165 // there are none
9166 cachedAttrs = {};
9167
9168 // if (xml.charCodeAt(i+1) === 47) { // </...
9169 if (w === 47) { // </...
9170 tagStart = false;
9171 tagEnd = true;
9172
9173 if (!nodeStack.length) {
9174 return handleError('missing open tag');
9175 }
9176
9177 // verify open <-> close tag match
9178 x = elementName = nodeStack.pop();
9179 q = i + 2 + x.length;
9180
9181 if (xml.substring(i + 2, q) !== x) {
9182 return handleError('closing tag mismatch');
9183 }
9184
9185 // verify chars in close tag
9186 for (; q < j; q++) {
9187 w = xml.charCodeAt(q);
9188
9189 if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space
9190 continue;
9191 }
9192
9193 return handleError('close tag');
9194 }
9195
9196 } else {
9197 if (xml.charCodeAt(j - 1) === 47) { // .../>
9198 x = elementName = xml.substring(i + 1, j - 1);
9199
9200 tagStart = true;
9201 tagEnd = true;
9202
9203 } else {
9204 x = elementName = xml.substring(i + 1, j);
9205
9206 tagStart = true;
9207 tagEnd = false;
9208 }
9209
9210 if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":"
9211 return handleError('illegal first char nodeName');
9212 }
9213
9214 for (q = 1, y = x.length; q < y; q++) {
9215 w = x.charCodeAt(q);
9216
9217 if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) {
9218 continue;
9219 }
9220
9221 if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space
9222 elementName = x.substring(0, q);
9223
9224 // maybe there are attributes
9225 cachedAttrs = null;
9226 break;
9227 }
9228
9229 return handleError('invalid nodeName');
9230 }
9231
9232 if (!tagEnd) {
9233 nodeStack.push(elementName);
9234 }
9235 }
9236
9237 if (isNamespace) {
9238
9239 _nsMatrix = nsMatrix;
9240
9241 if (tagStart) {
9242
9243 // remember old namespace
9244 // unless we're self-closing
9245 if (!tagEnd) {
9246 nsMatrixStack.push(_nsMatrix);
9247 }
9248
9249 if (cachedAttrs === null) {
9250
9251 // quick check, whether there may be namespace
9252 // declarations on the node; if that is the case
9253 // we need to eagerly parse the node attributes
9254 if ((maybeNS = x.indexOf('xmlns', q) !== -1)) {
9255 attrsStart = q;
9256 attrsString = x;
9257
9258 getAttrs();
9259
9260 maybeNS = false;
9261 }
9262 }
9263 }
9264
9265 _elementName = elementName;
9266
9267 w = elementName.indexOf(':');
9268 if (w !== -1) {
9269 xmlns = nsMatrix[elementName.substring(0, w)];
9270
9271 // prefix given; namespace must exist
9272 if (!xmlns) {
9273 return handleError('missing namespace on <' + _elementName + '>');
9274 }
9275
9276 elementName = elementName.substr(w + 1);
9277 } else {
9278 xmlns = nsMatrix['xmlns'];
9279
9280 // if no default namespace is defined,
9281 // we'll import the element as anonymous.
9282 //
9283 // it is up to users to correct that to the document defined
9284 // targetNamespace, or whatever their undersanding of the
9285 // XML spec mandates.
9286 }
9287
9288 // adjust namespace prefixs as configured
9289 if (xmlns) {
9290 elementName = xmlns + ':' + elementName;
9291 }
9292
9293 }
9294
9295 if (tagStart) {
9296 attrsStart = q;
9297 attrsString = x;
9298
9299 if (onOpenTag) {
9300 if (proxy) {
9301 onOpenTag(elementProxy, decodeEntities, tagEnd, getContext);
9302 } else {
9303 onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext);
9304 }
9305
9306 if (parseStop) {
9307 return;
9308 }
9309 }
9310
9311 }
9312
9313 if (tagEnd) {
9314
9315 if (onCloseTag) {
9316 onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext);
9317
9318 if (parseStop) {
9319 return;
9320 }
9321 }
9322
9323 // restore old namespace
9324 if (isNamespace) {
9325 if (!tagStart) {
9326 nsMatrix = nsMatrixStack.pop();
9327 } else {
9328 nsMatrix = _nsMatrix;
9329 }
9330 }
9331 }
9332
9333 j += 1;
9334 }
9335 } /** end parse */
9336
9337 }
9338
9339 function hasLowerCaseAlias(pkg) {
9340 return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
9341 }
9342
9343 var DEFAULT_NS_MAP = {
9344 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
9345 'xml': 'http://www.w3.org/XML/1998/namespace'
9346 };
9347
9348 var XSI_TYPE$1 = 'xsi:type';
9349
9350 function serializeFormat(element) {
9351 return element.xml && element.xml.serialize;
9352 }
9353
9354 function serializeAsType(element) {
9355 return serializeFormat(element) === XSI_TYPE$1;
9356 }
9357
9358 function serializeAsProperty(element) {
9359 return serializeFormat(element) === 'property';
9360 }
9361
9362 function capitalize(str) {
9363 return str.charAt(0).toUpperCase() + str.slice(1);
9364 }
9365
9366 function aliasToName(aliasNs, pkg) {
9367
9368 if (!hasLowerCaseAlias(pkg)) {
9369 return aliasNs.name;
9370 }
9371
9372 return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
9373 }
9374
9375 function prefixedToName(nameNs, pkg) {
9376
9377 var name = nameNs.name,
9378 localName = nameNs.localName;
9379
9380 var typePrefix = pkg.xml && pkg.xml.typePrefix;
9381
9382 if (typePrefix && localName.indexOf(typePrefix) === 0) {
9383 return nameNs.prefix + ':' + localName.slice(typePrefix.length);
9384 } else {
9385 return name;
9386 }
9387 }
9388
9389 function normalizeXsiTypeName(name, model) {
9390
9391 var nameNs = parseName(name);
9392 var pkg = model.getPackage(nameNs.prefix);
9393
9394 return prefixedToName(nameNs, pkg);
9395 }
9396
9397 function error$1(message) {
9398 return new Error(message);
9399 }
9400
9401 /**
9402 * Get the moddle descriptor for a given instance or type.
9403 *
9404 * @param {ModdleElement|Function} element
9405 *
9406 * @return {Object} the moddle descriptor
9407 */
9408 function getModdleDescriptor(element) {
9409 return element.$descriptor;
9410 }
9411
9412
9413 /**
9414 * A parse context.
9415 *
9416 * @class
9417 *
9418 * @param {Object} options
9419 * @param {ElementHandler} options.rootHandler the root handler for parsing a document
9420 * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
9421 */
9422 function Context(options) {
9423
9424 /**
9425 * @property {ElementHandler} rootHandler
9426 */
9427
9428 /**
9429 * @property {Boolean} lax
9430 */
9431
9432 assign(this, options);
9433
9434 this.elementsById = {};
9435 this.references = [];
9436 this.warnings = [];
9437
9438 /**
9439 * Add an unresolved reference.
9440 *
9441 * @param {Object} reference
9442 */
9443 this.addReference = function(reference) {
9444 this.references.push(reference);
9445 };
9446
9447 /**
9448 * Add a processed element.
9449 *
9450 * @param {ModdleElement} element
9451 */
9452 this.addElement = function(element) {
9453
9454 if (!element) {
9455 throw error$1('expected element');
9456 }
9457
9458 var elementsById = this.elementsById;
9459
9460 var descriptor = getModdleDescriptor(element);
9461
9462 var idProperty = descriptor.idProperty,
9463 id;
9464
9465 if (idProperty) {
9466 id = element.get(idProperty.name);
9467
9468 if (id) {
9469
9470 // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
9471 if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
9472 throw new Error('illegal ID <' + id + '>');
9473 }
9474
9475 if (elementsById[id]) {
9476 throw error$1('duplicate ID <' + id + '>');
9477 }
9478
9479 elementsById[id] = element;
9480 }
9481 }
9482 };
9483
9484 /**
9485 * Add an import warning.
9486 *
9487 * @param {Object} warning
9488 * @param {String} warning.message
9489 * @param {Error} [warning.error]
9490 */
9491 this.addWarning = function(warning) {
9492 this.warnings.push(warning);
9493 };
9494 }
9495
9496 function BaseHandler() {}
9497
9498 BaseHandler.prototype.handleEnd = function() {};
9499 BaseHandler.prototype.handleText = function() {};
9500 BaseHandler.prototype.handleNode = function() {};
9501
9502
9503 /**
9504 * A simple pass through handler that does nothing except for
9505 * ignoring all input it receives.
9506 *
9507 * This is used to ignore unknown elements and
9508 * attributes.
9509 */
9510 function NoopHandler() { }
9511
9512 NoopHandler.prototype = Object.create(BaseHandler.prototype);
9513
9514 NoopHandler.prototype.handleNode = function() {
9515 return this;
9516 };
9517
9518 function BodyHandler() {}
9519
9520 BodyHandler.prototype = Object.create(BaseHandler.prototype);
9521
9522 BodyHandler.prototype.handleText = function(text) {
9523 this.body = (this.body || '') + text;
9524 };
9525
9526 function ReferenceHandler(property, context) {
9527 this.property = property;
9528 this.context = context;
9529 }
9530
9531 ReferenceHandler.prototype = Object.create(BodyHandler.prototype);
9532
9533 ReferenceHandler.prototype.handleNode = function(node) {
9534
9535 if (this.element) {
9536 throw error$1('expected no sub nodes');
9537 } else {
9538 this.element = this.createReference(node);
9539 }
9540
9541 return this;
9542 };
9543
9544 ReferenceHandler.prototype.handleEnd = function() {
9545 this.element.id = this.body;
9546 };
9547
9548 ReferenceHandler.prototype.createReference = function(node) {
9549 return {
9550 property: this.property.ns.name,
9551 id: ''
9552 };
9553 };
9554
9555 function ValueHandler(propertyDesc, element) {
9556 this.element = element;
9557 this.propertyDesc = propertyDesc;
9558 }
9559
9560 ValueHandler.prototype = Object.create(BodyHandler.prototype);
9561
9562 ValueHandler.prototype.handleEnd = function() {
9563
9564 var value = this.body || '',
9565 element = this.element,
9566 propertyDesc = this.propertyDesc;
9567
9568 value = coerceType(propertyDesc.type, value);
9569
9570 if (propertyDesc.isMany) {
9571 element.get(propertyDesc.name).push(value);
9572 } else {
9573 element.set(propertyDesc.name, value);
9574 }
9575 };
9576
9577
9578 function BaseElementHandler() {}
9579
9580 BaseElementHandler.prototype = Object.create(BodyHandler.prototype);
9581
9582 BaseElementHandler.prototype.handleNode = function(node) {
9583 var parser = this,
9584 element = this.element;
9585
9586 if (!element) {
9587 element = this.element = this.createElement(node);
9588
9589 this.context.addElement(element);
9590 } else {
9591 parser = this.handleChild(node);
9592 }
9593
9594 return parser;
9595 };
9596
9597 /**
9598 * @class Reader.ElementHandler
9599 *
9600 */
9601 function ElementHandler(model, typeName, context) {
9602 this.model = model;
9603 this.type = model.getType(typeName);
9604 this.context = context;
9605 }
9606
9607 ElementHandler.prototype = Object.create(BaseElementHandler.prototype);
9608
9609 ElementHandler.prototype.addReference = function(reference) {
9610 this.context.addReference(reference);
9611 };
9612
9613 ElementHandler.prototype.handleText = function(text) {
9614
9615 var element = this.element,
9616 descriptor = getModdleDescriptor(element),
9617 bodyProperty = descriptor.bodyProperty;
9618
9619 if (!bodyProperty) {
9620 throw error$1('unexpected body text <' + text + '>');
9621 }
9622
9623 BodyHandler.prototype.handleText.call(this, text);
9624 };
9625
9626 ElementHandler.prototype.handleEnd = function() {
9627
9628 var value = this.body,
9629 element = this.element,
9630 descriptor = getModdleDescriptor(element),
9631 bodyProperty = descriptor.bodyProperty;
9632
9633 if (bodyProperty && value !== undefined) {
9634 value = coerceType(bodyProperty.type, value);
9635 element.set(bodyProperty.name, value);
9636 }
9637 };
9638
9639 /**
9640 * Create an instance of the model from the given node.
9641 *
9642 * @param {Element} node the xml node
9643 */
9644 ElementHandler.prototype.createElement = function(node) {
9645 var attributes = node.attributes,
9646 Type = this.type,
9647 descriptor = getModdleDescriptor(Type),
9648 context = this.context,
9649 instance = new Type({}),
9650 model = this.model,
9651 propNameNs;
9652
9653 forEach(attributes, function(value, name) {
9654
9655 var prop = descriptor.propertiesByName[name],
9656 values;
9657
9658 if (prop && prop.isReference) {
9659
9660 if (!prop.isMany) {
9661 context.addReference({
9662 element: instance,
9663 property: prop.ns.name,
9664 id: value
9665 });
9666 } else {
9667
9668 // IDREFS: parse references as whitespace-separated list
9669 values = value.split(' ');
9670
9671 forEach(values, function(v) {
9672 context.addReference({
9673 element: instance,
9674 property: prop.ns.name,
9675 id: v
9676 });
9677 });
9678 }
9679
9680 } else {
9681 if (prop) {
9682 value = coerceType(prop.type, value);
9683 } else
9684 if (name !== 'xmlns') {
9685 propNameNs = parseName(name, descriptor.ns.prefix);
9686
9687 // check whether attribute is defined in a well-known namespace
9688 // if that is the case we emit a warning to indicate potential misuse
9689 if (model.getPackage(propNameNs.prefix)) {
9690
9691 context.addWarning({
9692 message: 'unknown attribute <' + name + '>',
9693 element: instance,
9694 property: name,
9695 value: value
9696 });
9697 }
9698 }
9699
9700 instance.set(name, value);
9701 }
9702 });
9703
9704 return instance;
9705 };
9706
9707 ElementHandler.prototype.getPropertyForNode = function(node) {
9708
9709 var name = node.name;
9710 var nameNs = parseName(name);
9711
9712 var type = this.type,
9713 model = this.model,
9714 descriptor = getModdleDescriptor(type);
9715
9716 var propertyName = nameNs.name,
9717 property = descriptor.propertiesByName[propertyName],
9718 elementTypeName,
9719 elementType;
9720
9721 // search for properties by name first
9722
9723 if (property && !property.isAttr) {
9724
9725 if (serializeAsType(property)) {
9726 elementTypeName = node.attributes[XSI_TYPE$1];
9727
9728 // xsi type is optional, if it does not exists the
9729 // default type is assumed
9730 if (elementTypeName) {
9731
9732 // take possible type prefixes from XML
9733 // into account, i.e.: xsi:type="t{ActualType}"
9734 elementTypeName = normalizeXsiTypeName(elementTypeName, model);
9735
9736 elementType = model.getType(elementTypeName);
9737
9738 return assign({}, property, {
9739 effectiveType: getModdleDescriptor(elementType).name
9740 });
9741 }
9742 }
9743
9744 // search for properties by name first
9745 return property;
9746 }
9747
9748 var pkg = model.getPackage(nameNs.prefix);
9749
9750 if (pkg) {
9751 elementTypeName = aliasToName(nameNs, pkg);
9752 elementType = model.getType(elementTypeName);
9753
9754 // search for collection members later
9755 property = find(descriptor.properties, function(p) {
9756 return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
9757 });
9758
9759 if (property) {
9760 return assign({}, property, {
9761 effectiveType: getModdleDescriptor(elementType).name
9762 });
9763 }
9764 } else {
9765
9766 // parse unknown element (maybe extension)
9767 property = find(descriptor.properties, function(p) {
9768 return !p.isReference && !p.isAttribute && p.type === 'Element';
9769 });
9770
9771 if (property) {
9772 return property;
9773 }
9774 }
9775
9776 throw error$1('unrecognized element <' + nameNs.name + '>');
9777 };
9778
9779 ElementHandler.prototype.toString = function() {
9780 return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
9781 };
9782
9783 ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
9784 return new ValueHandler(propertyDesc, element);
9785 };
9786
9787 ElementHandler.prototype.referenceHandler = function(propertyDesc) {
9788 return new ReferenceHandler(propertyDesc, this.context);
9789 };
9790
9791 ElementHandler.prototype.handler = function(type) {
9792 if (type === 'Element') {
9793 return new GenericElementHandler(this.model, type, this.context);
9794 } else {
9795 return new ElementHandler(this.model, type, this.context);
9796 }
9797 };
9798
9799 /**
9800 * Handle the child element parsing
9801 *
9802 * @param {Element} node the xml node
9803 */
9804 ElementHandler.prototype.handleChild = function(node) {
9805 var propertyDesc, type, element, childHandler;
9806
9807 propertyDesc = this.getPropertyForNode(node);
9808 element = this.element;
9809
9810 type = propertyDesc.effectiveType || propertyDesc.type;
9811
9812 if (isSimple(type)) {
9813 return this.valueHandler(propertyDesc, element);
9814 }
9815
9816 if (propertyDesc.isReference) {
9817 childHandler = this.referenceHandler(propertyDesc).handleNode(node);
9818 } else {
9819 childHandler = this.handler(type).handleNode(node);
9820 }
9821
9822 var newElement = childHandler.element;
9823
9824 // child handles may decide to skip elements
9825 // by not returning anything
9826 if (newElement !== undefined) {
9827
9828 if (propertyDesc.isMany) {
9829 element.get(propertyDesc.name).push(newElement);
9830 } else {
9831 element.set(propertyDesc.name, newElement);
9832 }
9833
9834 if (propertyDesc.isReference) {
9835 assign(newElement, {
9836 element: element
9837 });
9838
9839 this.context.addReference(newElement);
9840 } else {
9841
9842 // establish child -> parent relationship
9843 newElement.$parent = element;
9844 }
9845 }
9846
9847 return childHandler;
9848 };
9849
9850 /**
9851 * An element handler that performs special validation
9852 * to ensure the node it gets initialized with matches
9853 * the handlers type (namespace wise).
9854 *
9855 * @param {Moddle} model
9856 * @param {String} typeName
9857 * @param {Context} context
9858 */
9859 function RootElementHandler(model, typeName, context) {
9860 ElementHandler.call(this, model, typeName, context);
9861 }
9862
9863 RootElementHandler.prototype = Object.create(ElementHandler.prototype);
9864
9865 RootElementHandler.prototype.createElement = function(node) {
9866
9867 var name = node.name,
9868 nameNs = parseName(name),
9869 model = this.model,
9870 type = this.type,
9871 pkg = model.getPackage(nameNs.prefix),
9872 typeName = pkg && aliasToName(nameNs, pkg) || name;
9873
9874 // verify the correct namespace if we parse
9875 // the first element in the handler tree
9876 //
9877 // this ensures we don't mistakenly import wrong namespace elements
9878 if (!type.hasType(typeName)) {
9879 throw error$1('unexpected element <' + node.originalName + '>');
9880 }
9881
9882 return ElementHandler.prototype.createElement.call(this, node);
9883 };
9884
9885
9886 function GenericElementHandler(model, typeName, context) {
9887 this.model = model;
9888 this.context = context;
9889 }
9890
9891 GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);
9892
9893 GenericElementHandler.prototype.createElement = function(node) {
9894
9895 var name = node.name,
9896 ns = parseName(name),
9897 prefix = ns.prefix,
9898 uri = node.ns[prefix + '$uri'],
9899 attributes = node.attributes;
9900
9901 return this.model.createAny(name, uri, attributes);
9902 };
9903
9904 GenericElementHandler.prototype.handleChild = function(node) {
9905
9906 var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
9907 element = this.element;
9908
9909 var newElement = handler.element,
9910 children;
9911
9912 if (newElement !== undefined) {
9913 children = element.$children = element.$children || [];
9914 children.push(newElement);
9915
9916 // establish child -> parent relationship
9917 newElement.$parent = element;
9918 }
9919
9920 return handler;
9921 };
9922
9923 GenericElementHandler.prototype.handleEnd = function() {
9924 if (this.body) {
9925 this.element.$body = this.body;
9926 }
9927 };
9928
9929 /**
9930 * A reader for a meta-model
9931 *
9932 * @param {Object} options
9933 * @param {Model} options.model used to read xml files
9934 * @param {Boolean} options.lax whether to make parse errors warnings
9935 */
9936 function Reader(options) {
9937
9938 if (options instanceof Moddle) {
9939 options = {
9940 model: options
9941 };
9942 }
9943
9944 assign(this, { lax: false }, options);
9945 }
9946
9947 /**
9948 * The fromXML result.
9949 *
9950 * @typedef {Object} ParseResult
9951 *
9952 * @property {ModdleElement} rootElement
9953 * @property {Array<Object>} references
9954 * @property {Array<Error>} warnings
9955 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
9956 */
9957
9958 /**
9959 * The fromXML result.
9960 *
9961 * @typedef {Error} ParseError
9962 *
9963 * @property {Array<Error>} warnings
9964 */
9965
9966 /**
9967 * Parse the given XML into a moddle document tree.
9968 *
9969 * @param {String} xml
9970 * @param {ElementHandler|Object} options or rootHandler
9971 *
9972 * @returns {Promise<ParseResult, ParseError>}
9973 */
9974 Reader.prototype.fromXML = function(xml, options, done) {
9975
9976 var rootHandler = options.rootHandler;
9977
9978 if (options instanceof ElementHandler) {
9979
9980 // root handler passed via (xml, { rootHandler: ElementHandler }, ...)
9981 rootHandler = options;
9982 options = {};
9983 } else {
9984 if (typeof options === 'string') {
9985
9986 // rootHandler passed via (xml, 'someString', ...)
9987 rootHandler = this.handler(options);
9988 options = {};
9989 } else if (typeof rootHandler === 'string') {
9990
9991 // rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
9992 rootHandler = this.handler(rootHandler);
9993 }
9994 }
9995
9996 var model = this.model,
9997 lax = this.lax;
9998
9999 var context = new Context(assign({}, options, { rootHandler: rootHandler })),
10000 parser = new Parser({ proxy: true }),
10001 stack = createStack();
10002
10003 rootHandler.context = context;
10004
10005 // push root handler
10006 stack.push(rootHandler);
10007
10008
10009 /**
10010 * Handle error.
10011 *
10012 * @param {Error} err
10013 * @param {Function} getContext
10014 * @param {boolean} lax
10015 *
10016 * @return {boolean} true if handled
10017 */
10018 function handleError(err, getContext, lax) {
10019
10020 var ctx = getContext();
10021
10022 var line = ctx.line,
10023 column = ctx.column,
10024 data = ctx.data;
10025
10026 // we receive the full context data here,
10027 // for elements trim down the information
10028 // to the tag name, only
10029 if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
10030 data = data.slice(0, data.indexOf(' ')) + '>';
10031 }
10032
10033 var message =
10034 'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' +
10035 'line: ' + line + '\n\t' +
10036 'column: ' + column + '\n\t' +
10037 'nested error: ' + err.message;
10038
10039 if (lax) {
10040 context.addWarning({
10041 message: message,
10042 error: err
10043 });
10044
10045 return true;
10046 } else {
10047 throw error$1(message);
10048 }
10049 }
10050
10051 function handleWarning(err, getContext) {
10052
10053 // just like handling errors in <lax=true> mode
10054 return handleError(err, getContext, true);
10055 }
10056
10057 /**
10058 * Resolve collected references on parse end.
10059 */
10060 function resolveReferences() {
10061
10062 var elementsById = context.elementsById;
10063 var references = context.references;
10064
10065 var i, r;
10066
10067 for (i = 0; (r = references[i]); i++) {
10068 var element = r.element;
10069 var reference = elementsById[r.id];
10070 var property = getModdleDescriptor(element).propertiesByName[r.property];
10071
10072 if (!reference) {
10073 context.addWarning({
10074 message: 'unresolved reference <' + r.id + '>',
10075 element: r.element,
10076 property: r.property,
10077 value: r.id
10078 });
10079 }
10080
10081 if (property.isMany) {
10082 var collection = element.get(property.name),
10083 idx = collection.indexOf(r);
10084
10085 // we replace an existing place holder (idx != -1) or
10086 // append to the collection instead
10087 if (idx === -1) {
10088 idx = collection.length;
10089 }
10090
10091 if (!reference) {
10092
10093 // remove unresolvable reference
10094 collection.splice(idx, 1);
10095 } else {
10096
10097 // add or update reference in collection
10098 collection[idx] = reference;
10099 }
10100 } else {
10101 element.set(property.name, reference);
10102 }
10103 }
10104 }
10105
10106 function handleClose() {
10107 stack.pop().handleEnd();
10108 }
10109
10110 var PREAMBLE_START_PATTERN = /^<\?xml /i;
10111
10112 var ENCODING_PATTERN = / encoding="([^"]+)"/i;
10113
10114 var UTF_8_PATTERN = /^utf-8$/i;
10115
10116 function handleQuestion(question) {
10117
10118 if (!PREAMBLE_START_PATTERN.test(question)) {
10119 return;
10120 }
10121
10122 var match = ENCODING_PATTERN.exec(question);
10123 var encoding = match && match[1];
10124
10125 if (!encoding || UTF_8_PATTERN.test(encoding)) {
10126 return;
10127 }
10128
10129 context.addWarning({
10130 message:
10131 'unsupported document encoding <' + encoding + '>, ' +
10132 'falling back to UTF-8'
10133 });
10134 }
10135
10136 function handleOpen(node, getContext) {
10137 var handler = stack.peek();
10138
10139 try {
10140 stack.push(handler.handleNode(node));
10141 } catch (err) {
10142
10143 if (handleError(err, getContext, lax)) {
10144 stack.push(new NoopHandler());
10145 }
10146 }
10147 }
10148
10149 function handleCData(text, getContext) {
10150
10151 try {
10152 stack.peek().handleText(text);
10153 } catch (err) {
10154 handleWarning(err, getContext);
10155 }
10156 }
10157
10158 function handleText(text, getContext) {
10159
10160 // strip whitespace only nodes, i.e. before
10161 // <!CDATA[ ... ]> sections and in between tags
10162
10163 if (!text.trim()) {
10164 return;
10165 }
10166
10167 handleCData(text, getContext);
10168 }
10169
10170 var uriMap = model.getPackages().reduce(function(uriMap, p) {
10171 uriMap[p.uri] = p.prefix;
10172
10173 return uriMap;
10174 }, {
10175 'http://www.w3.org/XML/1998/namespace': 'xml' // add default xml ns
10176 });
10177 parser
10178 .ns(uriMap)
10179 .on('openTag', function(obj, decodeStr, selfClosing, getContext) {
10180
10181 // gracefully handle unparsable attributes (attrs=false)
10182 var attrs = obj.attrs || {};
10183
10184 var decodedAttrs = Object.keys(attrs).reduce(function(d, key) {
10185 var value = decodeStr(attrs[key]);
10186
10187 d[key] = value;
10188
10189 return d;
10190 }, {});
10191
10192 var node = {
10193 name: obj.name,
10194 originalName: obj.originalName,
10195 attributes: decodedAttrs,
10196 ns: obj.ns
10197 };
10198
10199 handleOpen(node, getContext);
10200 })
10201 .on('question', handleQuestion)
10202 .on('closeTag', handleClose)
10203 .on('cdata', handleCData)
10204 .on('text', function(text, decodeEntities, getContext) {
10205 handleText(decodeEntities(text), getContext);
10206 })
10207 .on('error', handleError)
10208 .on('warn', handleWarning);
10209
10210 // async XML parsing to make sure the execution environment
10211 // (node or brower) is kept responsive and that certain optimization
10212 // strategies can kick in.
10213 return new Promise(function(resolve, reject) {
10214
10215 var err;
10216
10217 try {
10218 parser.parse(xml);
10219
10220 resolveReferences();
10221 } catch (e) {
10222 err = e;
10223 }
10224
10225 var rootElement = rootHandler.element;
10226
10227 if (!err && !rootElement) {
10228 err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
10229 }
10230
10231 var warnings = context.warnings;
10232 var references = context.references;
10233 var elementsById = context.elementsById;
10234
10235 if (err) {
10236 err.warnings = warnings;
10237
10238 return reject(err);
10239 } else {
10240 return resolve({
10241 rootElement: rootElement,
10242 elementsById: elementsById,
10243 references: references,
10244 warnings: warnings
10245 });
10246 }
10247 });
10248 };
10249
10250 Reader.prototype.handler = function(name) {
10251 return new RootElementHandler(this.model, name);
10252 };
10253
10254
10255 // helpers //////////////////////////
10256
10257 function createStack() {
10258 var stack = [];
10259
10260 Object.defineProperty(stack, 'peek', {
10261 value: function() {
10262 return this[this.length - 1];
10263 }
10264 });
10265
10266 return stack;
10267 }
10268
10269 var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n';
10270
10271 var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g;
10272 var ESCAPE_CHARS = /<|>|&/g;
10273
10274
10275 function Namespaces(parent) {
10276
10277 var prefixMap = {};
10278 var uriMap = {};
10279 var used = {};
10280
10281 var wellknown = [];
10282 var custom = [];
10283
10284 // API
10285
10286 this.byUri = function(uri) {
10287 return uriMap[uri] || (
10288 parent && parent.byUri(uri)
10289 );
10290 };
10291
10292 this.add = function(ns, isWellknown) {
10293
10294 uriMap[ns.uri] = ns;
10295
10296 if (isWellknown) {
10297 wellknown.push(ns);
10298 } else {
10299 custom.push(ns);
10300 }
10301
10302 this.mapPrefix(ns.prefix, ns.uri);
10303 };
10304
10305 this.uriByPrefix = function(prefix) {
10306 return prefixMap[prefix || 'xmlns'];
10307 };
10308
10309 this.mapPrefix = function(prefix, uri) {
10310 prefixMap[prefix || 'xmlns'] = uri;
10311 };
10312
10313 this.getNSKey = function(ns) {
10314 return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri;
10315 };
10316
10317 this.logUsed = function(ns) {
10318
10319 var uri = ns.uri;
10320 var nsKey = this.getNSKey(ns);
10321
10322 used[nsKey] = this.byUri(uri);
10323
10324 // Inform parent recursively about the usage of this NS
10325 if (parent) {
10326 parent.logUsed(ns);
10327 }
10328 };
10329
10330 this.getUsed = function(ns) {
10331
10332 function isUsed(ns) {
10333 var nsKey = self.getNSKey(ns);
10334
10335 return used[nsKey];
10336 }
10337
10338 var self = this;
10339
10340 var allNs = [].concat(wellknown, custom);
10341
10342 return allNs.filter(isUsed);
10343 };
10344
10345 }
10346
10347 function lower(string) {
10348 return string.charAt(0).toLowerCase() + string.slice(1);
10349 }
10350
10351 function nameToAlias(name, pkg) {
10352 if (hasLowerCaseAlias(pkg)) {
10353 return lower(name);
10354 } else {
10355 return name;
10356 }
10357 }
10358
10359 function inherits(ctor, superCtor) {
10360 ctor.super_ = superCtor;
10361 ctor.prototype = Object.create(superCtor.prototype, {
10362 constructor: {
10363 value: ctor,
10364 enumerable: false,
10365 writable: true,
10366 configurable: true
10367 }
10368 });
10369 }
10370
10371 function nsName(ns) {
10372 if (isString(ns)) {
10373 return ns;
10374 } else {
10375 return (ns.prefix ? ns.prefix + ':' : '') + ns.localName;
10376 }
10377 }
10378
10379 function getNsAttrs(namespaces) {
10380
10381 return namespaces.getUsed().filter(function(ns) {
10382
10383 // do not serialize built in <xml> namespace
10384 return ns.prefix !== 'xml';
10385 }).map(function(ns) {
10386 var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : '');
10387 return { name: name, value: ns.uri };
10388 });
10389
10390 }
10391
10392 function getElementNs(ns, descriptor) {
10393 if (descriptor.isGeneric) {
10394 return assign({ localName: descriptor.ns.localName }, ns);
10395 } else {
10396 return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns);
10397 }
10398 }
10399
10400 function getPropertyNs(ns, descriptor) {
10401 return assign({ localName: descriptor.ns.localName }, ns);
10402 }
10403
10404 function getSerializableProperties(element) {
10405 var descriptor = element.$descriptor;
10406
10407 return filter(descriptor.properties, function(p) {
10408 var name = p.name;
10409
10410 if (p.isVirtual) {
10411 return false;
10412 }
10413
10414 // do not serialize defaults
10415 if (!has(element, name)) {
10416 return false;
10417 }
10418
10419 var value = element[name];
10420
10421 // do not serialize default equals
10422 if (value === p.default) {
10423 return false;
10424 }
10425
10426 // do not serialize null properties
10427 if (value === null) {
10428 return false;
10429 }
10430
10431 return p.isMany ? value.length : true;
10432 });
10433 }
10434
10435 var ESCAPE_ATTR_MAP = {
10436 '\n': '#10',
10437 '\n\r': '#10',
10438 '"': '#34',
10439 '\'': '#39',
10440 '<': '#60',
10441 '>': '#62',
10442 '&': '#38'
10443 };
10444
10445 var ESCAPE_MAP = {
10446 '<': 'lt',
10447 '>': 'gt',
10448 '&': 'amp'
10449 };
10450
10451 function escape$1(str, charPattern, replaceMap) {
10452
10453 // ensure we are handling strings here
10454 str = isString(str) ? str : '' + str;
10455
10456 return str.replace(charPattern, function(s) {
10457 return '&' + replaceMap[s] + ';';
10458 });
10459 }
10460
10461 /**
10462 * Escape a string attribute to not contain any bad values (line breaks, '"', ...)
10463 *
10464 * @param {String} str the string to escape
10465 * @return {String} the escaped string
10466 */
10467 function escapeAttr(str) {
10468 return escape$1(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
10469 }
10470
10471 function escapeBody(str) {
10472 return escape$1(str, ESCAPE_CHARS, ESCAPE_MAP);
10473 }
10474
10475 function filterAttributes(props) {
10476 return filter(props, function(p) { return p.isAttr; });
10477 }
10478
10479 function filterContained(props) {
10480 return filter(props, function(p) { return !p.isAttr; });
10481 }
10482
10483
10484 function ReferenceSerializer(tagName) {
10485 this.tagName = tagName;
10486 }
10487
10488 ReferenceSerializer.prototype.build = function(element) {
10489 this.element = element;
10490 return this;
10491 };
10492
10493 ReferenceSerializer.prototype.serializeTo = function(writer) {
10494 writer
10495 .appendIndent()
10496 .append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>')
10497 .appendNewLine();
10498 };
10499
10500 function BodySerializer() {}
10501
10502 BodySerializer.prototype.serializeValue =
10503 BodySerializer.prototype.serializeTo = function(writer) {
10504 writer.append(
10505 this.escape
10506 ? escapeBody(this.value)
10507 : this.value
10508 );
10509 };
10510
10511 BodySerializer.prototype.build = function(prop, value) {
10512 this.value = value;
10513
10514 if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) {
10515 this.escape = true;
10516 }
10517
10518 return this;
10519 };
10520
10521 function ValueSerializer(tagName) {
10522 this.tagName = tagName;
10523 }
10524
10525 inherits(ValueSerializer, BodySerializer);
10526
10527 ValueSerializer.prototype.serializeTo = function(writer) {
10528
10529 writer
10530 .appendIndent()
10531 .append('<' + this.tagName + '>');
10532
10533 this.serializeValue(writer);
10534
10535 writer
10536 .append('</' + this.tagName + '>')
10537 .appendNewLine();
10538 };
10539
10540 function ElementSerializer(parent, propertyDescriptor) {
10541 this.body = [];
10542 this.attrs = [];
10543
10544 this.parent = parent;
10545 this.propertyDescriptor = propertyDescriptor;
10546 }
10547
10548 ElementSerializer.prototype.build = function(element) {
10549 this.element = element;
10550
10551 var elementDescriptor = element.$descriptor,
10552 propertyDescriptor = this.propertyDescriptor;
10553
10554 var otherAttrs,
10555 properties;
10556
10557 var isGeneric = elementDescriptor.isGeneric;
10558
10559 if (isGeneric) {
10560 otherAttrs = this.parseGeneric(element);
10561 } else {
10562 otherAttrs = this.parseNsAttributes(element);
10563 }
10564
10565 if (propertyDescriptor) {
10566 this.ns = this.nsPropertyTagName(propertyDescriptor);
10567 } else {
10568 this.ns = this.nsTagName(elementDescriptor);
10569 }
10570
10571 // compute tag name
10572 this.tagName = this.addTagName(this.ns);
10573
10574 if (!isGeneric) {
10575 properties = getSerializableProperties(element);
10576
10577 this.parseAttributes(filterAttributes(properties));
10578 this.parseContainments(filterContained(properties));
10579 }
10580
10581 this.parseGenericAttributes(element, otherAttrs);
10582
10583 return this;
10584 };
10585
10586 ElementSerializer.prototype.nsTagName = function(descriptor) {
10587 var effectiveNs = this.logNamespaceUsed(descriptor.ns);
10588 return getElementNs(effectiveNs, descriptor);
10589 };
10590
10591 ElementSerializer.prototype.nsPropertyTagName = function(descriptor) {
10592 var effectiveNs = this.logNamespaceUsed(descriptor.ns);
10593 return getPropertyNs(effectiveNs, descriptor);
10594 };
10595
10596 ElementSerializer.prototype.isLocalNs = function(ns) {
10597 return ns.uri === this.ns.uri;
10598 };
10599
10600 /**
10601 * Get the actual ns attribute name for the given element.
10602 *
10603 * @param {Object} element
10604 * @param {Boolean} [element.inherited=false]
10605 *
10606 * @return {Object} nsName
10607 */
10608 ElementSerializer.prototype.nsAttributeName = function(element) {
10609
10610 var ns;
10611
10612 if (isString(element)) {
10613 ns = parseName(element);
10614 } else {
10615 ns = element.ns;
10616 }
10617
10618 // return just local name for inherited attributes
10619 if (element.inherited) {
10620 return { localName: ns.localName };
10621 }
10622
10623 // parse + log effective ns
10624 var effectiveNs = this.logNamespaceUsed(ns);
10625
10626 // LOG ACTUAL namespace use
10627 this.getNamespaces().logUsed(effectiveNs);
10628
10629 // strip prefix if same namespace like parent
10630 if (this.isLocalNs(effectiveNs)) {
10631 return { localName: ns.localName };
10632 } else {
10633 return assign({ localName: ns.localName }, effectiveNs);
10634 }
10635 };
10636
10637 ElementSerializer.prototype.parseGeneric = function(element) {
10638
10639 var self = this,
10640 body = this.body;
10641
10642 var attributes = [];
10643
10644 forEach(element, function(val, key) {
10645
10646 var nonNsAttr;
10647
10648 if (key === '$body') {
10649 body.push(new BodySerializer().build({ type: 'String' }, val));
10650 } else
10651 if (key === '$children') {
10652 forEach(val, function(child) {
10653 body.push(new ElementSerializer(self).build(child));
10654 });
10655 } else
10656 if (key.indexOf('$') !== 0) {
10657 nonNsAttr = self.parseNsAttribute(element, key, val);
10658
10659 if (nonNsAttr) {
10660 attributes.push({ name: key, value: val });
10661 }
10662 }
10663 });
10664
10665 return attributes;
10666 };
10667
10668 ElementSerializer.prototype.parseNsAttribute = function(element, name, value) {
10669 var model = element.$model;
10670
10671 var nameNs = parseName(name);
10672
10673 var ns;
10674
10675 // parse xmlns:foo="http://foo.bar"
10676 if (nameNs.prefix === 'xmlns') {
10677 ns = { prefix: nameNs.localName, uri: value };
10678 }
10679
10680 // parse xmlns="http://foo.bar"
10681 if (!nameNs.prefix && nameNs.localName === 'xmlns') {
10682 ns = { uri: value };
10683 }
10684
10685 if (!ns) {
10686 return {
10687 name: name,
10688 value: value
10689 };
10690 }
10691
10692 if (model && model.getPackage(value)) {
10693
10694 // register well known namespace
10695 this.logNamespace(ns, true, true);
10696 } else {
10697
10698 // log custom namespace directly as used
10699 var actualNs = this.logNamespaceUsed(ns, true);
10700
10701 this.getNamespaces().logUsed(actualNs);
10702 }
10703 };
10704
10705
10706 /**
10707 * Parse namespaces and return a list of left over generic attributes
10708 *
10709 * @param {Object} element
10710 * @return {Array<Object>}
10711 */
10712 ElementSerializer.prototype.parseNsAttributes = function(element, attrs) {
10713 var self = this;
10714
10715 var genericAttrs = element.$attrs;
10716
10717 var attributes = [];
10718
10719 // parse namespace attributes first
10720 // and log them. push non namespace attributes to a list
10721 // and process them later
10722 forEach(genericAttrs, function(value, name) {
10723
10724 var nonNsAttr = self.parseNsAttribute(element, name, value);
10725
10726 if (nonNsAttr) {
10727 attributes.push(nonNsAttr);
10728 }
10729 });
10730
10731 return attributes;
10732 };
10733
10734 ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) {
10735
10736 var self = this;
10737
10738 forEach(attributes, function(attr) {
10739
10740 // do not serialize xsi:type attribute
10741 // it is set manually based on the actual implementation type
10742 if (attr.name === XSI_TYPE$1) {
10743 return;
10744 }
10745
10746 try {
10747 self.addAttribute(self.nsAttributeName(attr.name), attr.value);
10748 } catch (e) {
10749 console.warn(
10750 'missing namespace information for ',
10751 attr.name, '=', attr.value, 'on', element,
10752 e);
10753 }
10754 });
10755 };
10756
10757 ElementSerializer.prototype.parseContainments = function(properties) {
10758
10759 var self = this,
10760 body = this.body,
10761 element = this.element;
10762
10763 forEach(properties, function(p) {
10764 var value = element.get(p.name),
10765 isReference = p.isReference,
10766 isMany = p.isMany;
10767
10768 if (!isMany) {
10769 value = [ value ];
10770 }
10771
10772 if (p.isBody) {
10773 body.push(new BodySerializer().build(p, value[0]));
10774 } else
10775 if (isSimple(p.type)) {
10776 forEach(value, function(v) {
10777 body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
10778 });
10779 } else
10780 if (isReference) {
10781 forEach(value, function(v) {
10782 body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v));
10783 });
10784 } else {
10785
10786 // allow serialization via type
10787 // rather than element name
10788 var asType = serializeAsType(p),
10789 asProperty = serializeAsProperty(p);
10790
10791 forEach(value, function(v) {
10792 var serializer;
10793
10794 if (asType) {
10795 serializer = new TypeSerializer(self, p);
10796 } else
10797 if (asProperty) {
10798 serializer = new ElementSerializer(self, p);
10799 } else {
10800 serializer = new ElementSerializer(self);
10801 }
10802
10803 body.push(serializer.build(v));
10804 });
10805 }
10806 });
10807 };
10808
10809 ElementSerializer.prototype.getNamespaces = function(local) {
10810
10811 var namespaces = this.namespaces,
10812 parent = this.parent,
10813 parentNamespaces;
10814
10815 if (!namespaces) {
10816 parentNamespaces = parent && parent.getNamespaces();
10817
10818 if (local || !parentNamespaces) {
10819 this.namespaces = namespaces = new Namespaces(parentNamespaces);
10820 } else {
10821 namespaces = parentNamespaces;
10822 }
10823 }
10824
10825 return namespaces;
10826 };
10827
10828 ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) {
10829 var namespaces = this.getNamespaces(local);
10830
10831 var nsUri = ns.uri,
10832 nsPrefix = ns.prefix;
10833
10834 var existing = namespaces.byUri(nsUri);
10835
10836 if (!existing || local) {
10837 namespaces.add(ns, wellknown);
10838 }
10839
10840 namespaces.mapPrefix(nsPrefix, nsUri);
10841
10842 return ns;
10843 };
10844
10845 ElementSerializer.prototype.logNamespaceUsed = function(ns, local) {
10846 var element = this.element,
10847 model = element.$model,
10848 namespaces = this.getNamespaces(local);
10849
10850 // ns may be
10851 //
10852 // * prefix only
10853 // * prefix:uri
10854 // * localName only
10855
10856 var prefix = ns.prefix,
10857 uri = ns.uri,
10858 newPrefix, idx,
10859 wellknownUri;
10860
10861 // handle anonymous namespaces (elementForm=unqualified), cf. #23
10862 if (!prefix && !uri) {
10863 return { localName: ns.localName };
10864 }
10865
10866 wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri;
10867
10868 uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);
10869
10870 if (!uri) {
10871 throw new Error('no namespace uri given for prefix <' + prefix + '>');
10872 }
10873
10874 ns = namespaces.byUri(uri);
10875
10876 if (!ns) {
10877 newPrefix = prefix;
10878 idx = 1;
10879
10880 // find a prefix that is not mapped yet
10881 while (namespaces.uriByPrefix(newPrefix)) {
10882 newPrefix = prefix + '_' + idx++;
10883 }
10884
10885 ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri);
10886 }
10887
10888 if (prefix) {
10889 namespaces.mapPrefix(prefix, uri);
10890 }
10891
10892 return ns;
10893 };
10894
10895 ElementSerializer.prototype.parseAttributes = function(properties) {
10896 var self = this,
10897 element = this.element;
10898
10899 forEach(properties, function(p) {
10900
10901 var value = element.get(p.name);
10902
10903 if (p.isReference) {
10904
10905 if (!p.isMany) {
10906 value = value.id;
10907 }
10908 else {
10909 var values = [];
10910 forEach(value, function(v) {
10911 values.push(v.id);
10912 });
10913
10914 // IDREFS is a whitespace-separated list of references.
10915 value = values.join(' ');
10916 }
10917
10918 }
10919
10920 self.addAttribute(self.nsAttributeName(p), value);
10921 });
10922 };
10923
10924 ElementSerializer.prototype.addTagName = function(nsTagName) {
10925 var actualNs = this.logNamespaceUsed(nsTagName);
10926
10927 this.getNamespaces().logUsed(actualNs);
10928
10929 return nsName(nsTagName);
10930 };
10931
10932 ElementSerializer.prototype.addAttribute = function(name, value) {
10933 var attrs = this.attrs;
10934
10935 if (isString(value)) {
10936 value = escapeAttr(value);
10937 }
10938
10939 attrs.push({ name: name, value: value });
10940 };
10941
10942 ElementSerializer.prototype.serializeAttributes = function(writer) {
10943 var attrs = this.attrs,
10944 namespaces = this.namespaces;
10945
10946 if (namespaces) {
10947 attrs = getNsAttrs(namespaces).concat(attrs);
10948 }
10949
10950 forEach(attrs, function(a) {
10951 writer
10952 .append(' ')
10953 .append(nsName(a.name)).append('="').append(a.value).append('"');
10954 });
10955 };
10956
10957 ElementSerializer.prototype.serializeTo = function(writer) {
10958 var firstBody = this.body[0],
10959 indent = firstBody && firstBody.constructor !== BodySerializer;
10960
10961 writer
10962 .appendIndent()
10963 .append('<' + this.tagName);
10964
10965 this.serializeAttributes(writer);
10966
10967 writer.append(firstBody ? '>' : ' />');
10968
10969 if (firstBody) {
10970
10971 if (indent) {
10972 writer
10973 .appendNewLine()
10974 .indent();
10975 }
10976
10977 forEach(this.body, function(b) {
10978 b.serializeTo(writer);
10979 });
10980
10981 if (indent) {
10982 writer
10983 .unindent()
10984 .appendIndent();
10985 }
10986
10987 writer.append('</' + this.tagName + '>');
10988 }
10989
10990 writer.appendNewLine();
10991 };
10992
10993 /**
10994 * A serializer for types that handles serialization of data types
10995 */
10996 function TypeSerializer(parent, propertyDescriptor) {
10997 ElementSerializer.call(this, parent, propertyDescriptor);
10998 }
10999
11000 inherits(TypeSerializer, ElementSerializer);
11001
11002 TypeSerializer.prototype.parseNsAttributes = function(element) {
11003
11004 // extracted attributes
11005 var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element);
11006
11007 var descriptor = element.$descriptor;
11008
11009 // only serialize xsi:type if necessary
11010 if (descriptor.name === this.propertyDescriptor.type) {
11011 return attributes;
11012 }
11013
11014 var typeNs = this.typeNs = this.nsTagName(descriptor);
11015 this.getNamespaces().logUsed(this.typeNs);
11016
11017 // add xsi:type attribute to represent the elements
11018 // actual type
11019
11020 var pkg = element.$model.getPackage(typeNs.uri),
11021 typePrefix = (pkg.xml && pkg.xml.typePrefix) || '';
11022
11023 this.addAttribute(
11024 this.nsAttributeName(XSI_TYPE$1),
11025 (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName
11026 );
11027
11028 return attributes;
11029 };
11030
11031 TypeSerializer.prototype.isLocalNs = function(ns) {
11032 return ns.uri === (this.typeNs || this.ns).uri;
11033 };
11034
11035 function SavingWriter() {
11036 this.value = '';
11037
11038 this.write = function(str) {
11039 this.value += str;
11040 };
11041 }
11042
11043 function FormatingWriter(out, format) {
11044
11045 var indent = [''];
11046
11047 this.append = function(str) {
11048 out.write(str);
11049
11050 return this;
11051 };
11052
11053 this.appendNewLine = function() {
11054 if (format) {
11055 out.write('\n');
11056 }
11057
11058 return this;
11059 };
11060
11061 this.appendIndent = function() {
11062 if (format) {
11063 out.write(indent.join(' '));
11064 }
11065
11066 return this;
11067 };
11068
11069 this.indent = function() {
11070 indent.push('');
11071 return this;
11072 };
11073
11074 this.unindent = function() {
11075 indent.pop();
11076 return this;
11077 };
11078 }
11079
11080 /**
11081 * A writer for meta-model backed document trees
11082 *
11083 * @param {Object} options output options to pass into the writer
11084 */
11085 function Writer(options) {
11086
11087 options = assign({ format: false, preamble: true }, options || {});
11088
11089 function toXML(tree, writer) {
11090 var internalWriter = writer || new SavingWriter();
11091 var formatingWriter = new FormatingWriter(internalWriter, options.format);
11092
11093 if (options.preamble) {
11094 formatingWriter.append(XML_PREAMBLE);
11095 }
11096
11097 new ElementSerializer().build(tree).serializeTo(formatingWriter);
11098
11099 if (!writer) {
11100 return internalWriter.value;
11101 }
11102 }
11103
11104 return {
11105 toXML: toXML
11106 };
11107 }
11108
11109 /**
11110 * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files.
11111 *
11112 * @class BpmnModdle
11113 * @extends Moddle
11114 *
11115 * @param {Object|Array} packages to use for instantiating the model
11116 * @param {Object} [options] additional options to pass over
11117 */
11118 function BpmnModdle(packages, options) {
11119 Moddle.call(this, packages, options);
11120 }
11121
11122 BpmnModdle.prototype = Object.create(Moddle.prototype);
11123
11124 /**
11125 * The fromXML result.
11126 *
11127 * @typedef {Object} ParseResult
11128 *
11129 * @property {ModdleElement} rootElement
11130 * @property {Array<Object>} references
11131 * @property {Array<Error>} warnings
11132 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
11133 */
11134
11135 /**
11136 * The fromXML error.
11137 *
11138 * @typedef {Error} ParseError
11139 *
11140 * @property {Array<Error>} warnings
11141 */
11142
11143 /**
11144 * Instantiates a BPMN model tree from a given xml string.
11145 *
11146 * @param {String} xmlStr
11147 * @param {String} [typeName='bpmn:Definitions'] name of the root element
11148 * @param {Object} [options] options to pass to the underlying reader
11149 *
11150 * @returns {Promise<ParseResult, ParseError>}
11151 */
11152 BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options) {
11153
11154 if (!isString(typeName)) {
11155 options = typeName;
11156 typeName = 'bpmn:Definitions';
11157 }
11158
11159 var reader = new Reader(assign({ model: this, lax: true }, options));
11160 var rootHandler = reader.handler(typeName);
11161
11162 return reader.fromXML(xmlStr, rootHandler);
11163 };
11164
11165
11166 /**
11167 * The toXML result.
11168 *
11169 * @typedef {Object} SerializationResult
11170 *
11171 * @property {String} xml
11172 */
11173
11174 /**
11175 * Serializes a BPMN 2.0 object tree to XML.
11176 *
11177 * @param {String} element the root element, typically an instance of `bpmn:Definitions`
11178 * @param {Object} [options] to pass to the underlying writer
11179 *
11180 * @returns {Promise<SerializationResult, Error>}
11181 */
11182 BpmnModdle.prototype.toXML = function(element, options) {
11183
11184 var writer = new Writer(options);
11185
11186 return new Promise(function(resolve, reject) {
11187 try {
11188 var result = writer.toXML(element);
11189
11190 return resolve({
11191 xml: result
11192 });
11193 } catch (err) {
11194 return reject(err);
11195 }
11196 });
11197 };
11198
11199 var name = "BPMN20";
11200 var uri = "http://www.omg.org/spec/BPMN/20100524/MODEL";
11201 var prefix$1 = "bpmn";
11202 var associations = [
11203 ];
11204 var types$1 = [
11205 {
11206 name: "Interface",
11207 superClass: [
11208 "RootElement"
11209 ],
11210 properties: [
11211 {
11212 name: "name",
11213 isAttr: true,
11214 type: "String"
11215 },
11216 {
11217 name: "operations",
11218 type: "Operation",
11219 isMany: true
11220 },
11221 {
11222 name: "implementationRef",
11223 isAttr: true,
11224 type: "String"
11225 }
11226 ]
11227 },
11228 {
11229 name: "Operation",
11230 superClass: [
11231 "BaseElement"
11232 ],
11233 properties: [
11234 {
11235 name: "name",
11236 isAttr: true,
11237 type: "String"
11238 },
11239 {
11240 name: "inMessageRef",
11241 type: "Message",
11242 isReference: true
11243 },
11244 {
11245 name: "outMessageRef",
11246 type: "Message",
11247 isReference: true
11248 },
11249 {
11250 name: "errorRef",
11251 type: "Error",
11252 isMany: true,
11253 isReference: true
11254 },
11255 {
11256 name: "implementationRef",
11257 isAttr: true,
11258 type: "String"
11259 }
11260 ]
11261 },
11262 {
11263 name: "EndPoint",
11264 superClass: [
11265 "RootElement"
11266 ]
11267 },
11268 {
11269 name: "Auditing",
11270 superClass: [
11271 "BaseElement"
11272 ]
11273 },
11274 {
11275 name: "GlobalTask",
11276 superClass: [
11277 "CallableElement"
11278 ],
11279 properties: [
11280 {
11281 name: "resources",
11282 type: "ResourceRole",
11283 isMany: true
11284 }
11285 ]
11286 },
11287 {
11288 name: "Monitoring",
11289 superClass: [
11290 "BaseElement"
11291 ]
11292 },
11293 {
11294 name: "Performer",
11295 superClass: [
11296 "ResourceRole"
11297 ]
11298 },
11299 {
11300 name: "Process",
11301 superClass: [
11302 "FlowElementsContainer",
11303 "CallableElement"
11304 ],
11305 properties: [
11306 {
11307 name: "processType",
11308 type: "ProcessType",
11309 isAttr: true
11310 },
11311 {
11312 name: "isClosed",
11313 isAttr: true,
11314 type: "Boolean"
11315 },
11316 {
11317 name: "auditing",
11318 type: "Auditing"
11319 },
11320 {
11321 name: "monitoring",
11322 type: "Monitoring"
11323 },
11324 {
11325 name: "properties",
11326 type: "Property",
11327 isMany: true
11328 },
11329 {
11330 name: "laneSets",
11331 isMany: true,
11332 replaces: "FlowElementsContainer#laneSets",
11333 type: "LaneSet"
11334 },
11335 {
11336 name: "flowElements",
11337 isMany: true,
11338 replaces: "FlowElementsContainer#flowElements",
11339 type: "FlowElement"
11340 },
11341 {
11342 name: "artifacts",
11343 type: "Artifact",
11344 isMany: true
11345 },
11346 {
11347 name: "resources",
11348 type: "ResourceRole",
11349 isMany: true
11350 },
11351 {
11352 name: "correlationSubscriptions",
11353 type: "CorrelationSubscription",
11354 isMany: true
11355 },
11356 {
11357 name: "supports",
11358 type: "Process",
11359 isMany: true,
11360 isReference: true
11361 },
11362 {
11363 name: "definitionalCollaborationRef",
11364 type: "Collaboration",
11365 isAttr: true,
11366 isReference: true
11367 },
11368 {
11369 name: "isExecutable",
11370 isAttr: true,
11371 type: "Boolean"
11372 }
11373 ]
11374 },
11375 {
11376 name: "LaneSet",
11377 superClass: [
11378 "BaseElement"
11379 ],
11380 properties: [
11381 {
11382 name: "lanes",
11383 type: "Lane",
11384 isMany: true
11385 },
11386 {
11387 name: "name",
11388 isAttr: true,
11389 type: "String"
11390 }
11391 ]
11392 },
11393 {
11394 name: "Lane",
11395 superClass: [
11396 "BaseElement"
11397 ],
11398 properties: [
11399 {
11400 name: "name",
11401 isAttr: true,
11402 type: "String"
11403 },
11404 {
11405 name: "partitionElementRef",
11406 type: "BaseElement",
11407 isAttr: true,
11408 isReference: true
11409 },
11410 {
11411 name: "partitionElement",
11412 type: "BaseElement"
11413 },
11414 {
11415 name: "flowNodeRef",
11416 type: "FlowNode",
11417 isMany: true,
11418 isReference: true
11419 },
11420 {
11421 name: "childLaneSet",
11422 type: "LaneSet",
11423 xml: {
11424 serialize: "xsi:type"
11425 }
11426 }
11427 ]
11428 },
11429 {
11430 name: "GlobalManualTask",
11431 superClass: [
11432 "GlobalTask"
11433 ]
11434 },
11435 {
11436 name: "ManualTask",
11437 superClass: [
11438 "Task"
11439 ]
11440 },
11441 {
11442 name: "UserTask",
11443 superClass: [
11444 "Task"
11445 ],
11446 properties: [
11447 {
11448 name: "renderings",
11449 type: "Rendering",
11450 isMany: true
11451 },
11452 {
11453 name: "implementation",
11454 isAttr: true,
11455 type: "String"
11456 }
11457 ]
11458 },
11459 {
11460 name: "Rendering",
11461 superClass: [
11462 "BaseElement"
11463 ]
11464 },
11465 {
11466 name: "HumanPerformer",
11467 superClass: [
11468 "Performer"
11469 ]
11470 },
11471 {
11472 name: "PotentialOwner",
11473 superClass: [
11474 "HumanPerformer"
11475 ]
11476 },
11477 {
11478 name: "GlobalUserTask",
11479 superClass: [
11480 "GlobalTask"
11481 ],
11482 properties: [
11483 {
11484 name: "implementation",
11485 isAttr: true,
11486 type: "String"
11487 },
11488 {
11489 name: "renderings",
11490 type: "Rendering",
11491 isMany: true
11492 }
11493 ]
11494 },
11495 {
11496 name: "Gateway",
11497 isAbstract: true,
11498 superClass: [
11499 "FlowNode"
11500 ],
11501 properties: [
11502 {
11503 name: "gatewayDirection",
11504 type: "GatewayDirection",
11505 "default": "Unspecified",
11506 isAttr: true
11507 }
11508 ]
11509 },
11510 {
11511 name: "EventBasedGateway",
11512 superClass: [
11513 "Gateway"
11514 ],
11515 properties: [
11516 {
11517 name: "instantiate",
11518 "default": false,
11519 isAttr: true,
11520 type: "Boolean"
11521 },
11522 {
11523 name: "eventGatewayType",
11524 type: "EventBasedGatewayType",
11525 isAttr: true,
11526 "default": "Exclusive"
11527 }
11528 ]
11529 },
11530 {
11531 name: "ComplexGateway",
11532 superClass: [
11533 "Gateway"
11534 ],
11535 properties: [
11536 {
11537 name: "activationCondition",
11538 type: "Expression",
11539 xml: {
11540 serialize: "xsi:type"
11541 }
11542 },
11543 {
11544 name: "default",
11545 type: "SequenceFlow",
11546 isAttr: true,
11547 isReference: true
11548 }
11549 ]
11550 },
11551 {
11552 name: "ExclusiveGateway",
11553 superClass: [
11554 "Gateway"
11555 ],
11556 properties: [
11557 {
11558 name: "default",
11559 type: "SequenceFlow",
11560 isAttr: true,
11561 isReference: true
11562 }
11563 ]
11564 },
11565 {
11566 name: "InclusiveGateway",
11567 superClass: [
11568 "Gateway"
11569 ],
11570 properties: [
11571 {
11572 name: "default",
11573 type: "SequenceFlow",
11574 isAttr: true,
11575 isReference: true
11576 }
11577 ]
11578 },
11579 {
11580 name: "ParallelGateway",
11581 superClass: [
11582 "Gateway"
11583 ]
11584 },
11585 {
11586 name: "RootElement",
11587 isAbstract: true,
11588 superClass: [
11589 "BaseElement"
11590 ]
11591 },
11592 {
11593 name: "Relationship",
11594 superClass: [
11595 "BaseElement"
11596 ],
11597 properties: [
11598 {
11599 name: "type",
11600 isAttr: true,
11601 type: "String"
11602 },
11603 {
11604 name: "direction",
11605 type: "RelationshipDirection",
11606 isAttr: true
11607 },
11608 {
11609 name: "source",
11610 isMany: true,
11611 isReference: true,
11612 type: "Element"
11613 },
11614 {
11615 name: "target",
11616 isMany: true,
11617 isReference: true,
11618 type: "Element"
11619 }
11620 ]
11621 },
11622 {
11623 name: "BaseElement",
11624 isAbstract: true,
11625 properties: [
11626 {
11627 name: "id",
11628 isAttr: true,
11629 type: "String",
11630 isId: true
11631 },
11632 {
11633 name: "documentation",
11634 type: "Documentation",
11635 isMany: true
11636 },
11637 {
11638 name: "extensionDefinitions",
11639 type: "ExtensionDefinition",
11640 isMany: true,
11641 isReference: true
11642 },
11643 {
11644 name: "extensionElements",
11645 type: "ExtensionElements"
11646 }
11647 ]
11648 },
11649 {
11650 name: "Extension",
11651 properties: [
11652 {
11653 name: "mustUnderstand",
11654 "default": false,
11655 isAttr: true,
11656 type: "Boolean"
11657 },
11658 {
11659 name: "definition",
11660 type: "ExtensionDefinition",
11661 isAttr: true,
11662 isReference: true
11663 }
11664 ]
11665 },
11666 {
11667 name: "ExtensionDefinition",
11668 properties: [
11669 {
11670 name: "name",
11671 isAttr: true,
11672 type: "String"
11673 },
11674 {
11675 name: "extensionAttributeDefinitions",
11676 type: "ExtensionAttributeDefinition",
11677 isMany: true
11678 }
11679 ]
11680 },
11681 {
11682 name: "ExtensionAttributeDefinition",
11683 properties: [
11684 {
11685 name: "name",
11686 isAttr: true,
11687 type: "String"
11688 },
11689 {
11690 name: "type",
11691 isAttr: true,
11692 type: "String"
11693 },
11694 {
11695 name: "isReference",
11696 "default": false,
11697 isAttr: true,
11698 type: "Boolean"
11699 },
11700 {
11701 name: "extensionDefinition",
11702 type: "ExtensionDefinition",
11703 isAttr: true,
11704 isReference: true
11705 }
11706 ]
11707 },
11708 {
11709 name: "ExtensionElements",
11710 properties: [
11711 {
11712 name: "valueRef",
11713 isAttr: true,
11714 isReference: true,
11715 type: "Element"
11716 },
11717 {
11718 name: "values",
11719 type: "Element",
11720 isMany: true
11721 },
11722 {
11723 name: "extensionAttributeDefinition",
11724 type: "ExtensionAttributeDefinition",
11725 isAttr: true,
11726 isReference: true
11727 }
11728 ]
11729 },
11730 {
11731 name: "Documentation",
11732 superClass: [
11733 "BaseElement"
11734 ],
11735 properties: [
11736 {
11737 name: "text",
11738 type: "String",
11739 isBody: true
11740 },
11741 {
11742 name: "textFormat",
11743 "default": "text/plain",
11744 isAttr: true,
11745 type: "String"
11746 }
11747 ]
11748 },
11749 {
11750 name: "Event",
11751 isAbstract: true,
11752 superClass: [
11753 "FlowNode",
11754 "InteractionNode"
11755 ],
11756 properties: [
11757 {
11758 name: "properties",
11759 type: "Property",
11760 isMany: true
11761 }
11762 ]
11763 },
11764 {
11765 name: "IntermediateCatchEvent",
11766 superClass: [
11767 "CatchEvent"
11768 ]
11769 },
11770 {
11771 name: "IntermediateThrowEvent",
11772 superClass: [
11773 "ThrowEvent"
11774 ]
11775 },
11776 {
11777 name: "EndEvent",
11778 superClass: [
11779 "ThrowEvent"
11780 ]
11781 },
11782 {
11783 name: "StartEvent",
11784 superClass: [
11785 "CatchEvent"
11786 ],
11787 properties: [
11788 {
11789 name: "isInterrupting",
11790 "default": true,
11791 isAttr: true,
11792 type: "Boolean"
11793 }
11794 ]
11795 },
11796 {
11797 name: "ThrowEvent",
11798 isAbstract: true,
11799 superClass: [
11800 "Event"
11801 ],
11802 properties: [
11803 {
11804 name: "dataInputs",
11805 type: "DataInput",
11806 isMany: true
11807 },
11808 {
11809 name: "dataInputAssociations",
11810 type: "DataInputAssociation",
11811 isMany: true
11812 },
11813 {
11814 name: "inputSet",
11815 type: "InputSet"
11816 },
11817 {
11818 name: "eventDefinitions",
11819 type: "EventDefinition",
11820 isMany: true
11821 },
11822 {
11823 name: "eventDefinitionRef",
11824 type: "EventDefinition",
11825 isMany: true,
11826 isReference: true
11827 }
11828 ]
11829 },
11830 {
11831 name: "CatchEvent",
11832 isAbstract: true,
11833 superClass: [
11834 "Event"
11835 ],
11836 properties: [
11837 {
11838 name: "parallelMultiple",
11839 isAttr: true,
11840 type: "Boolean",
11841 "default": false
11842 },
11843 {
11844 name: "dataOutputs",
11845 type: "DataOutput",
11846 isMany: true
11847 },
11848 {
11849 name: "dataOutputAssociations",
11850 type: "DataOutputAssociation",
11851 isMany: true
11852 },
11853 {
11854 name: "outputSet",
11855 type: "OutputSet"
11856 },
11857 {
11858 name: "eventDefinitions",
11859 type: "EventDefinition",
11860 isMany: true
11861 },
11862 {
11863 name: "eventDefinitionRef",
11864 type: "EventDefinition",
11865 isMany: true,
11866 isReference: true
11867 }
11868 ]
11869 },
11870 {
11871 name: "BoundaryEvent",
11872 superClass: [
11873 "CatchEvent"
11874 ],
11875 properties: [
11876 {
11877 name: "cancelActivity",
11878 "default": true,
11879 isAttr: true,
11880 type: "Boolean"
11881 },
11882 {
11883 name: "attachedToRef",
11884 type: "Activity",
11885 isAttr: true,
11886 isReference: true
11887 }
11888 ]
11889 },
11890 {
11891 name: "EventDefinition",
11892 isAbstract: true,
11893 superClass: [
11894 "RootElement"
11895 ]
11896 },
11897 {
11898 name: "CancelEventDefinition",
11899 superClass: [
11900 "EventDefinition"
11901 ]
11902 },
11903 {
11904 name: "ErrorEventDefinition",
11905 superClass: [
11906 "EventDefinition"
11907 ],
11908 properties: [
11909 {
11910 name: "errorRef",
11911 type: "Error",
11912 isAttr: true,
11913 isReference: true
11914 }
11915 ]
11916 },
11917 {
11918 name: "TerminateEventDefinition",
11919 superClass: [
11920 "EventDefinition"
11921 ]
11922 },
11923 {
11924 name: "EscalationEventDefinition",
11925 superClass: [
11926 "EventDefinition"
11927 ],
11928 properties: [
11929 {
11930 name: "escalationRef",
11931 type: "Escalation",
11932 isAttr: true,
11933 isReference: true
11934 }
11935 ]
11936 },
11937 {
11938 name: "Escalation",
11939 properties: [
11940 {
11941 name: "structureRef",
11942 type: "ItemDefinition",
11943 isAttr: true,
11944 isReference: true
11945 },
11946 {
11947 name: "name",
11948 isAttr: true,
11949 type: "String"
11950 },
11951 {
11952 name: "escalationCode",
11953 isAttr: true,
11954 type: "String"
11955 }
11956 ],
11957 superClass: [
11958 "RootElement"
11959 ]
11960 },
11961 {
11962 name: "CompensateEventDefinition",
11963 superClass: [
11964 "EventDefinition"
11965 ],
11966 properties: [
11967 {
11968 name: "waitForCompletion",
11969 isAttr: true,
11970 type: "Boolean",
11971 "default": true
11972 },
11973 {
11974 name: "activityRef",
11975 type: "Activity",
11976 isAttr: true,
11977 isReference: true
11978 }
11979 ]
11980 },
11981 {
11982 name: "TimerEventDefinition",
11983 superClass: [
11984 "EventDefinition"
11985 ],
11986 properties: [
11987 {
11988 name: "timeDate",
11989 type: "Expression",
11990 xml: {
11991 serialize: "xsi:type"
11992 }
11993 },
11994 {
11995 name: "timeCycle",
11996 type: "Expression",
11997 xml: {
11998 serialize: "xsi:type"
11999 }
12000 },
12001 {
12002 name: "timeDuration",
12003 type: "Expression",
12004 xml: {
12005 serialize: "xsi:type"
12006 }
12007 }
12008 ]
12009 },
12010 {
12011 name: "LinkEventDefinition",
12012 superClass: [
12013 "EventDefinition"
12014 ],
12015 properties: [
12016 {
12017 name: "name",
12018 isAttr: true,
12019 type: "String"
12020 },
12021 {
12022 name: "target",
12023 type: "LinkEventDefinition",
12024 isAttr: true,
12025 isReference: true
12026 },
12027 {
12028 name: "source",
12029 type: "LinkEventDefinition",
12030 isMany: true,
12031 isReference: true
12032 }
12033 ]
12034 },
12035 {
12036 name: "MessageEventDefinition",
12037 superClass: [
12038 "EventDefinition"
12039 ],
12040 properties: [
12041 {
12042 name: "messageRef",
12043 type: "Message",
12044 isAttr: true,
12045 isReference: true
12046 },
12047 {
12048 name: "operationRef",
12049 type: "Operation",
12050 isAttr: true,
12051 isReference: true
12052 }
12053 ]
12054 },
12055 {
12056 name: "ConditionalEventDefinition",
12057 superClass: [
12058 "EventDefinition"
12059 ],
12060 properties: [
12061 {
12062 name: "condition",
12063 type: "Expression",
12064 xml: {
12065 serialize: "xsi:type"
12066 }
12067 }
12068 ]
12069 },
12070 {
12071 name: "SignalEventDefinition",
12072 superClass: [
12073 "EventDefinition"
12074 ],
12075 properties: [
12076 {
12077 name: "signalRef",
12078 type: "Signal",
12079 isAttr: true,
12080 isReference: true
12081 }
12082 ]
12083 },
12084 {
12085 name: "Signal",
12086 superClass: [
12087 "RootElement"
12088 ],
12089 properties: [
12090 {
12091 name: "structureRef",
12092 type: "ItemDefinition",
12093 isAttr: true,
12094 isReference: true
12095 },
12096 {
12097 name: "name",
12098 isAttr: true,
12099 type: "String"
12100 }
12101 ]
12102 },
12103 {
12104 name: "ImplicitThrowEvent",
12105 superClass: [
12106 "ThrowEvent"
12107 ]
12108 },
12109 {
12110 name: "DataState",
12111 superClass: [
12112 "BaseElement"
12113 ],
12114 properties: [
12115 {
12116 name: "name",
12117 isAttr: true,
12118 type: "String"
12119 }
12120 ]
12121 },
12122 {
12123 name: "ItemAwareElement",
12124 superClass: [
12125 "BaseElement"
12126 ],
12127 properties: [
12128 {
12129 name: "itemSubjectRef",
12130 type: "ItemDefinition",
12131 isAttr: true,
12132 isReference: true
12133 },
12134 {
12135 name: "dataState",
12136 type: "DataState"
12137 }
12138 ]
12139 },
12140 {
12141 name: "DataAssociation",
12142 superClass: [
12143 "BaseElement"
12144 ],
12145 properties: [
12146 {
12147 name: "sourceRef",
12148 type: "ItemAwareElement",
12149 isMany: true,
12150 isReference: true
12151 },
12152 {
12153 name: "targetRef",
12154 type: "ItemAwareElement",
12155 isReference: true
12156 },
12157 {
12158 name: "transformation",
12159 type: "FormalExpression",
12160 xml: {
12161 serialize: "property"
12162 }
12163 },
12164 {
12165 name: "assignment",
12166 type: "Assignment",
12167 isMany: true
12168 }
12169 ]
12170 },
12171 {
12172 name: "DataInput",
12173 superClass: [
12174 "ItemAwareElement"
12175 ],
12176 properties: [
12177 {
12178 name: "name",
12179 isAttr: true,
12180 type: "String"
12181 },
12182 {
12183 name: "isCollection",
12184 "default": false,
12185 isAttr: true,
12186 type: "Boolean"
12187 },
12188 {
12189 name: "inputSetRef",
12190 type: "InputSet",
12191 isMany: true,
12192 isVirtual: true,
12193 isReference: true
12194 },
12195 {
12196 name: "inputSetWithOptional",
12197 type: "InputSet",
12198 isMany: true,
12199 isVirtual: true,
12200 isReference: true
12201 },
12202 {
12203 name: "inputSetWithWhileExecuting",
12204 type: "InputSet",
12205 isMany: true,
12206 isVirtual: true,
12207 isReference: true
12208 }
12209 ]
12210 },
12211 {
12212 name: "DataOutput",
12213 superClass: [
12214 "ItemAwareElement"
12215 ],
12216 properties: [
12217 {
12218 name: "name",
12219 isAttr: true,
12220 type: "String"
12221 },
12222 {
12223 name: "isCollection",
12224 "default": false,
12225 isAttr: true,
12226 type: "Boolean"
12227 },
12228 {
12229 name: "outputSetRef",
12230 type: "OutputSet",
12231 isMany: true,
12232 isVirtual: true,
12233 isReference: true
12234 },
12235 {
12236 name: "outputSetWithOptional",
12237 type: "OutputSet",
12238 isMany: true,
12239 isVirtual: true,
12240 isReference: true
12241 },
12242 {
12243 name: "outputSetWithWhileExecuting",
12244 type: "OutputSet",
12245 isMany: true,
12246 isVirtual: true,
12247 isReference: true
12248 }
12249 ]
12250 },
12251 {
12252 name: "InputSet",
12253 superClass: [
12254 "BaseElement"
12255 ],
12256 properties: [
12257 {
12258 name: "name",
12259 isAttr: true,
12260 type: "String"
12261 },
12262 {
12263 name: "dataInputRefs",
12264 type: "DataInput",
12265 isMany: true,
12266 isReference: true
12267 },
12268 {
12269 name: "optionalInputRefs",
12270 type: "DataInput",
12271 isMany: true,
12272 isReference: true
12273 },
12274 {
12275 name: "whileExecutingInputRefs",
12276 type: "DataInput",
12277 isMany: true,
12278 isReference: true
12279 },
12280 {
12281 name: "outputSetRefs",
12282 type: "OutputSet",
12283 isMany: true,
12284 isReference: true
12285 }
12286 ]
12287 },
12288 {
12289 name: "OutputSet",
12290 superClass: [
12291 "BaseElement"
12292 ],
12293 properties: [
12294 {
12295 name: "dataOutputRefs",
12296 type: "DataOutput",
12297 isMany: true,
12298 isReference: true
12299 },
12300 {
12301 name: "name",
12302 isAttr: true,
12303 type: "String"
12304 },
12305 {
12306 name: "inputSetRefs",
12307 type: "InputSet",
12308 isMany: true,
12309 isReference: true
12310 },
12311 {
12312 name: "optionalOutputRefs",
12313 type: "DataOutput",
12314 isMany: true,
12315 isReference: true
12316 },
12317 {
12318 name: "whileExecutingOutputRefs",
12319 type: "DataOutput",
12320 isMany: true,
12321 isReference: true
12322 }
12323 ]
12324 },
12325 {
12326 name: "Property",
12327 superClass: [
12328 "ItemAwareElement"
12329 ],
12330 properties: [
12331 {
12332 name: "name",
12333 isAttr: true,
12334 type: "String"
12335 }
12336 ]
12337 },
12338 {
12339 name: "DataInputAssociation",
12340 superClass: [
12341 "DataAssociation"
12342 ]
12343 },
12344 {
12345 name: "DataOutputAssociation",
12346 superClass: [
12347 "DataAssociation"
12348 ]
12349 },
12350 {
12351 name: "InputOutputSpecification",
12352 superClass: [
12353 "BaseElement"
12354 ],
12355 properties: [
12356 {
12357 name: "dataInputs",
12358 type: "DataInput",
12359 isMany: true
12360 },
12361 {
12362 name: "dataOutputs",
12363 type: "DataOutput",
12364 isMany: true
12365 },
12366 {
12367 name: "inputSets",
12368 type: "InputSet",
12369 isMany: true
12370 },
12371 {
12372 name: "outputSets",
12373 type: "OutputSet",
12374 isMany: true
12375 }
12376 ]
12377 },
12378 {
12379 name: "DataObject",
12380 superClass: [
12381 "FlowElement",
12382 "ItemAwareElement"
12383 ],
12384 properties: [
12385 {
12386 name: "isCollection",
12387 "default": false,
12388 isAttr: true,
12389 type: "Boolean"
12390 }
12391 ]
12392 },
12393 {
12394 name: "InputOutputBinding",
12395 properties: [
12396 {
12397 name: "inputDataRef",
12398 type: "InputSet",
12399 isAttr: true,
12400 isReference: true
12401 },
12402 {
12403 name: "outputDataRef",
12404 type: "OutputSet",
12405 isAttr: true,
12406 isReference: true
12407 },
12408 {
12409 name: "operationRef",
12410 type: "Operation",
12411 isAttr: true,
12412 isReference: true
12413 }
12414 ]
12415 },
12416 {
12417 name: "Assignment",
12418 superClass: [
12419 "BaseElement"
12420 ],
12421 properties: [
12422 {
12423 name: "from",
12424 type: "Expression",
12425 xml: {
12426 serialize: "xsi:type"
12427 }
12428 },
12429 {
12430 name: "to",
12431 type: "Expression",
12432 xml: {
12433 serialize: "xsi:type"
12434 }
12435 }
12436 ]
12437 },
12438 {
12439 name: "DataStore",
12440 superClass: [
12441 "RootElement",
12442 "ItemAwareElement"
12443 ],
12444 properties: [
12445 {
12446 name: "name",
12447 isAttr: true,
12448 type: "String"
12449 },
12450 {
12451 name: "capacity",
12452 isAttr: true,
12453 type: "Integer"
12454 },
12455 {
12456 name: "isUnlimited",
12457 "default": true,
12458 isAttr: true,
12459 type: "Boolean"
12460 }
12461 ]
12462 },
12463 {
12464 name: "DataStoreReference",
12465 superClass: [
12466 "ItemAwareElement",
12467 "FlowElement"
12468 ],
12469 properties: [
12470 {
12471 name: "dataStoreRef",
12472 type: "DataStore",
12473 isAttr: true,
12474 isReference: true
12475 }
12476 ]
12477 },
12478 {
12479 name: "DataObjectReference",
12480 superClass: [
12481 "ItemAwareElement",
12482 "FlowElement"
12483 ],
12484 properties: [
12485 {
12486 name: "dataObjectRef",
12487 type: "DataObject",
12488 isAttr: true,
12489 isReference: true
12490 }
12491 ]
12492 },
12493 {
12494 name: "ConversationLink",
12495 superClass: [
12496 "BaseElement"
12497 ],
12498 properties: [
12499 {
12500 name: "sourceRef",
12501 type: "InteractionNode",
12502 isAttr: true,
12503 isReference: true
12504 },
12505 {
12506 name: "targetRef",
12507 type: "InteractionNode",
12508 isAttr: true,
12509 isReference: true
12510 },
12511 {
12512 name: "name",
12513 isAttr: true,
12514 type: "String"
12515 }
12516 ]
12517 },
12518 {
12519 name: "ConversationAssociation",
12520 superClass: [
12521 "BaseElement"
12522 ],
12523 properties: [
12524 {
12525 name: "innerConversationNodeRef",
12526 type: "ConversationNode",
12527 isAttr: true,
12528 isReference: true
12529 },
12530 {
12531 name: "outerConversationNodeRef",
12532 type: "ConversationNode",
12533 isAttr: true,
12534 isReference: true
12535 }
12536 ]
12537 },
12538 {
12539 name: "CallConversation",
12540 superClass: [
12541 "ConversationNode"
12542 ],
12543 properties: [
12544 {
12545 name: "calledCollaborationRef",
12546 type: "Collaboration",
12547 isAttr: true,
12548 isReference: true
12549 },
12550 {
12551 name: "participantAssociations",
12552 type: "ParticipantAssociation",
12553 isMany: true
12554 }
12555 ]
12556 },
12557 {
12558 name: "Conversation",
12559 superClass: [
12560 "ConversationNode"
12561 ]
12562 },
12563 {
12564 name: "SubConversation",
12565 superClass: [
12566 "ConversationNode"
12567 ],
12568 properties: [
12569 {
12570 name: "conversationNodes",
12571 type: "ConversationNode",
12572 isMany: true
12573 }
12574 ]
12575 },
12576 {
12577 name: "ConversationNode",
12578 isAbstract: true,
12579 superClass: [
12580 "InteractionNode",
12581 "BaseElement"
12582 ],
12583 properties: [
12584 {
12585 name: "name",
12586 isAttr: true,
12587 type: "String"
12588 },
12589 {
12590 name: "participantRef",
12591 type: "Participant",
12592 isMany: true,
12593 isReference: true
12594 },
12595 {
12596 name: "messageFlowRefs",
12597 type: "MessageFlow",
12598 isMany: true,
12599 isReference: true
12600 },
12601 {
12602 name: "correlationKeys",
12603 type: "CorrelationKey",
12604 isMany: true
12605 }
12606 ]
12607 },
12608 {
12609 name: "GlobalConversation",
12610 superClass: [
12611 "Collaboration"
12612 ]
12613 },
12614 {
12615 name: "PartnerEntity",
12616 superClass: [
12617 "RootElement"
12618 ],
12619 properties: [
12620 {
12621 name: "name",
12622 isAttr: true,
12623 type: "String"
12624 },
12625 {
12626 name: "participantRef",
12627 type: "Participant",
12628 isMany: true,
12629 isReference: true
12630 }
12631 ]
12632 },
12633 {
12634 name: "PartnerRole",
12635 superClass: [
12636 "RootElement"
12637 ],
12638 properties: [
12639 {
12640 name: "name",
12641 isAttr: true,
12642 type: "String"
12643 },
12644 {
12645 name: "participantRef",
12646 type: "Participant",
12647 isMany: true,
12648 isReference: true
12649 }
12650 ]
12651 },
12652 {
12653 name: "CorrelationProperty",
12654 superClass: [
12655 "RootElement"
12656 ],
12657 properties: [
12658 {
12659 name: "correlationPropertyRetrievalExpression",
12660 type: "CorrelationPropertyRetrievalExpression",
12661 isMany: true
12662 },
12663 {
12664 name: "name",
12665 isAttr: true,
12666 type: "String"
12667 },
12668 {
12669 name: "type",
12670 type: "ItemDefinition",
12671 isAttr: true,
12672 isReference: true
12673 }
12674 ]
12675 },
12676 {
12677 name: "Error",
12678 superClass: [
12679 "RootElement"
12680 ],
12681 properties: [
12682 {
12683 name: "structureRef",
12684 type: "ItemDefinition",
12685 isAttr: true,
12686 isReference: true
12687 },
12688 {
12689 name: "name",
12690 isAttr: true,
12691 type: "String"
12692 },
12693 {
12694 name: "errorCode",
12695 isAttr: true,
12696 type: "String"
12697 }
12698 ]
12699 },
12700 {
12701 name: "CorrelationKey",
12702 superClass: [
12703 "BaseElement"
12704 ],
12705 properties: [
12706 {
12707 name: "correlationPropertyRef",
12708 type: "CorrelationProperty",
12709 isMany: true,
12710 isReference: true
12711 },
12712 {
12713 name: "name",
12714 isAttr: true,
12715 type: "String"
12716 }
12717 ]
12718 },
12719 {
12720 name: "Expression",
12721 superClass: [
12722 "BaseElement"
12723 ],
12724 isAbstract: false,
12725 properties: [
12726 {
12727 name: "body",
12728 isBody: true,
12729 type: "String"
12730 }
12731 ]
12732 },
12733 {
12734 name: "FormalExpression",
12735 superClass: [
12736 "Expression"
12737 ],
12738 properties: [
12739 {
12740 name: "language",
12741 isAttr: true,
12742 type: "String"
12743 },
12744 {
12745 name: "evaluatesToTypeRef",
12746 type: "ItemDefinition",
12747 isAttr: true,
12748 isReference: true
12749 }
12750 ]
12751 },
12752 {
12753 name: "Message",
12754 superClass: [
12755 "RootElement"
12756 ],
12757 properties: [
12758 {
12759 name: "name",
12760 isAttr: true,
12761 type: "String"
12762 },
12763 {
12764 name: "itemRef",
12765 type: "ItemDefinition",
12766 isAttr: true,
12767 isReference: true
12768 }
12769 ]
12770 },
12771 {
12772 name: "ItemDefinition",
12773 superClass: [
12774 "RootElement"
12775 ],
12776 properties: [
12777 {
12778 name: "itemKind",
12779 type: "ItemKind",
12780 isAttr: true
12781 },
12782 {
12783 name: "structureRef",
12784 isAttr: true,
12785 type: "String"
12786 },
12787 {
12788 name: "isCollection",
12789 "default": false,
12790 isAttr: true,
12791 type: "Boolean"
12792 },
12793 {
12794 name: "import",
12795 type: "Import",
12796 isAttr: true,
12797 isReference: true
12798 }
12799 ]
12800 },
12801 {
12802 name: "FlowElement",
12803 isAbstract: true,
12804 superClass: [
12805 "BaseElement"
12806 ],
12807 properties: [
12808 {
12809 name: "name",
12810 isAttr: true,
12811 type: "String"
12812 },
12813 {
12814 name: "auditing",
12815 type: "Auditing"
12816 },
12817 {
12818 name: "monitoring",
12819 type: "Monitoring"
12820 },
12821 {
12822 name: "categoryValueRef",
12823 type: "CategoryValue",
12824 isMany: true,
12825 isReference: true
12826 }
12827 ]
12828 },
12829 {
12830 name: "SequenceFlow",
12831 superClass: [
12832 "FlowElement"
12833 ],
12834 properties: [
12835 {
12836 name: "isImmediate",
12837 isAttr: true,
12838 type: "Boolean"
12839 },
12840 {
12841 name: "conditionExpression",
12842 type: "Expression",
12843 xml: {
12844 serialize: "xsi:type"
12845 }
12846 },
12847 {
12848 name: "sourceRef",
12849 type: "FlowNode",
12850 isAttr: true,
12851 isReference: true
12852 },
12853 {
12854 name: "targetRef",
12855 type: "FlowNode",
12856 isAttr: true,
12857 isReference: true
12858 }
12859 ]
12860 },
12861 {
12862 name: "FlowElementsContainer",
12863 isAbstract: true,
12864 superClass: [
12865 "BaseElement"
12866 ],
12867 properties: [
12868 {
12869 name: "laneSets",
12870 type: "LaneSet",
12871 isMany: true
12872 },
12873 {
12874 name: "flowElements",
12875 type: "FlowElement",
12876 isMany: true
12877 }
12878 ]
12879 },
12880 {
12881 name: "CallableElement",
12882 isAbstract: true,
12883 superClass: [
12884 "RootElement"
12885 ],
12886 properties: [
12887 {
12888 name: "name",
12889 isAttr: true,
12890 type: "String"
12891 },
12892 {
12893 name: "ioSpecification",
12894 type: "InputOutputSpecification",
12895 xml: {
12896 serialize: "property"
12897 }
12898 },
12899 {
12900 name: "supportedInterfaceRef",
12901 type: "Interface",
12902 isMany: true,
12903 isReference: true
12904 },
12905 {
12906 name: "ioBinding",
12907 type: "InputOutputBinding",
12908 isMany: true,
12909 xml: {
12910 serialize: "property"
12911 }
12912 }
12913 ]
12914 },
12915 {
12916 name: "FlowNode",
12917 isAbstract: true,
12918 superClass: [
12919 "FlowElement"
12920 ],
12921 properties: [
12922 {
12923 name: "incoming",
12924 type: "SequenceFlow",
12925 isMany: true,
12926 isReference: true
12927 },
12928 {
12929 name: "outgoing",
12930 type: "SequenceFlow",
12931 isMany: true,
12932 isReference: true
12933 },
12934 {
12935 name: "lanes",
12936 type: "Lane",
12937 isMany: true,
12938 isVirtual: true,
12939 isReference: true
12940 }
12941 ]
12942 },
12943 {
12944 name: "CorrelationPropertyRetrievalExpression",
12945 superClass: [
12946 "BaseElement"
12947 ],
12948 properties: [
12949 {
12950 name: "messagePath",
12951 type: "FormalExpression"
12952 },
12953 {
12954 name: "messageRef",
12955 type: "Message",
12956 isAttr: true,
12957 isReference: true
12958 }
12959 ]
12960 },
12961 {
12962 name: "CorrelationPropertyBinding",
12963 superClass: [
12964 "BaseElement"
12965 ],
12966 properties: [
12967 {
12968 name: "dataPath",
12969 type: "FormalExpression"
12970 },
12971 {
12972 name: "correlationPropertyRef",
12973 type: "CorrelationProperty",
12974 isAttr: true,
12975 isReference: true
12976 }
12977 ]
12978 },
12979 {
12980 name: "Resource",
12981 superClass: [
12982 "RootElement"
12983 ],
12984 properties: [
12985 {
12986 name: "name",
12987 isAttr: true,
12988 type: "String"
12989 },
12990 {
12991 name: "resourceParameters",
12992 type: "ResourceParameter",
12993 isMany: true
12994 }
12995 ]
12996 },
12997 {
12998 name: "ResourceParameter",
12999 superClass: [
13000 "BaseElement"
13001 ],
13002 properties: [
13003 {
13004 name: "name",
13005 isAttr: true,
13006 type: "String"
13007 },
13008 {
13009 name: "isRequired",
13010 isAttr: true,
13011 type: "Boolean"
13012 },
13013 {
13014 name: "type",
13015 type: "ItemDefinition",
13016 isAttr: true,
13017 isReference: true
13018 }
13019 ]
13020 },
13021 {
13022 name: "CorrelationSubscription",
13023 superClass: [
13024 "BaseElement"
13025 ],
13026 properties: [
13027 {
13028 name: "correlationKeyRef",
13029 type: "CorrelationKey",
13030 isAttr: true,
13031 isReference: true
13032 },
13033 {
13034 name: "correlationPropertyBinding",
13035 type: "CorrelationPropertyBinding",
13036 isMany: true
13037 }
13038 ]
13039 },
13040 {
13041 name: "MessageFlow",
13042 superClass: [
13043 "BaseElement"
13044 ],
13045 properties: [
13046 {
13047 name: "name",
13048 isAttr: true,
13049 type: "String"
13050 },
13051 {
13052 name: "sourceRef",
13053 type: "InteractionNode",
13054 isAttr: true,
13055 isReference: true
13056 },
13057 {
13058 name: "targetRef",
13059 type: "InteractionNode",
13060 isAttr: true,
13061 isReference: true
13062 },
13063 {
13064 name: "messageRef",
13065 type: "Message",
13066 isAttr: true,
13067 isReference: true
13068 }
13069 ]
13070 },
13071 {
13072 name: "MessageFlowAssociation",
13073 superClass: [
13074 "BaseElement"
13075 ],
13076 properties: [
13077 {
13078 name: "innerMessageFlowRef",
13079 type: "MessageFlow",
13080 isAttr: true,
13081 isReference: true
13082 },
13083 {
13084 name: "outerMessageFlowRef",
13085 type: "MessageFlow",
13086 isAttr: true,
13087 isReference: true
13088 }
13089 ]
13090 },
13091 {
13092 name: "InteractionNode",
13093 isAbstract: true,
13094 properties: [
13095 {
13096 name: "incomingConversationLinks",
13097 type: "ConversationLink",
13098 isMany: true,
13099 isVirtual: true,
13100 isReference: true
13101 },
13102 {
13103 name: "outgoingConversationLinks",
13104 type: "ConversationLink",
13105 isMany: true,
13106 isVirtual: true,
13107 isReference: true
13108 }
13109 ]
13110 },
13111 {
13112 name: "Participant",
13113 superClass: [
13114 "InteractionNode",
13115 "BaseElement"
13116 ],
13117 properties: [
13118 {
13119 name: "name",
13120 isAttr: true,
13121 type: "String"
13122 },
13123 {
13124 name: "interfaceRef",
13125 type: "Interface",
13126 isMany: true,
13127 isReference: true
13128 },
13129 {
13130 name: "participantMultiplicity",
13131 type: "ParticipantMultiplicity"
13132 },
13133 {
13134 name: "endPointRefs",
13135 type: "EndPoint",
13136 isMany: true,
13137 isReference: true
13138 },
13139 {
13140 name: "processRef",
13141 type: "Process",
13142 isAttr: true,
13143 isReference: true
13144 }
13145 ]
13146 },
13147 {
13148 name: "ParticipantAssociation",
13149 superClass: [
13150 "BaseElement"
13151 ],
13152 properties: [
13153 {
13154 name: "innerParticipantRef",
13155 type: "Participant",
13156 isAttr: true,
13157 isReference: true
13158 },
13159 {
13160 name: "outerParticipantRef",
13161 type: "Participant",
13162 isAttr: true,
13163 isReference: true
13164 }
13165 ]
13166 },
13167 {
13168 name: "ParticipantMultiplicity",
13169 properties: [
13170 {
13171 name: "minimum",
13172 "default": 0,
13173 isAttr: true,
13174 type: "Integer"
13175 },
13176 {
13177 name: "maximum",
13178 "default": 1,
13179 isAttr: true,
13180 type: "Integer"
13181 }
13182 ],
13183 superClass: [
13184 "BaseElement"
13185 ]
13186 },
13187 {
13188 name: "Collaboration",
13189 superClass: [
13190 "RootElement"
13191 ],
13192 properties: [
13193 {
13194 name: "name",
13195 isAttr: true,
13196 type: "String"
13197 },
13198 {
13199 name: "isClosed",
13200 isAttr: true,
13201 type: "Boolean"
13202 },
13203 {
13204 name: "participants",
13205 type: "Participant",
13206 isMany: true
13207 },
13208 {
13209 name: "messageFlows",
13210 type: "MessageFlow",
13211 isMany: true
13212 },
13213 {
13214 name: "artifacts",
13215 type: "Artifact",
13216 isMany: true
13217 },
13218 {
13219 name: "conversations",
13220 type: "ConversationNode",
13221 isMany: true
13222 },
13223 {
13224 name: "conversationAssociations",
13225 type: "ConversationAssociation"
13226 },
13227 {
13228 name: "participantAssociations",
13229 type: "ParticipantAssociation",
13230 isMany: true
13231 },
13232 {
13233 name: "messageFlowAssociations",
13234 type: "MessageFlowAssociation",
13235 isMany: true
13236 },
13237 {
13238 name: "correlationKeys",
13239 type: "CorrelationKey",
13240 isMany: true
13241 },
13242 {
13243 name: "choreographyRef",
13244 type: "Choreography",
13245 isMany: true,
13246 isReference: true
13247 },
13248 {
13249 name: "conversationLinks",
13250 type: "ConversationLink",
13251 isMany: true
13252 }
13253 ]
13254 },
13255 {
13256 name: "ChoreographyActivity",
13257 isAbstract: true,
13258 superClass: [
13259 "FlowNode"
13260 ],
13261 properties: [
13262 {
13263 name: "participantRef",
13264 type: "Participant",
13265 isMany: true,
13266 isReference: true
13267 },
13268 {
13269 name: "initiatingParticipantRef",
13270 type: "Participant",
13271 isAttr: true,
13272 isReference: true
13273 },
13274 {
13275 name: "correlationKeys",
13276 type: "CorrelationKey",
13277 isMany: true
13278 },
13279 {
13280 name: "loopType",
13281 type: "ChoreographyLoopType",
13282 "default": "None",
13283 isAttr: true
13284 }
13285 ]
13286 },
13287 {
13288 name: "CallChoreography",
13289 superClass: [
13290 "ChoreographyActivity"
13291 ],
13292 properties: [
13293 {
13294 name: "calledChoreographyRef",
13295 type: "Choreography",
13296 isAttr: true,
13297 isReference: true
13298 },
13299 {
13300 name: "participantAssociations",
13301 type: "ParticipantAssociation",
13302 isMany: true
13303 }
13304 ]
13305 },
13306 {
13307 name: "SubChoreography",
13308 superClass: [
13309 "ChoreographyActivity",
13310 "FlowElementsContainer"
13311 ],
13312 properties: [
13313 {
13314 name: "artifacts",
13315 type: "Artifact",
13316 isMany: true
13317 }
13318 ]
13319 },
13320 {
13321 name: "ChoreographyTask",
13322 superClass: [
13323 "ChoreographyActivity"
13324 ],
13325 properties: [
13326 {
13327 name: "messageFlowRef",
13328 type: "MessageFlow",
13329 isMany: true,
13330 isReference: true
13331 }
13332 ]
13333 },
13334 {
13335 name: "Choreography",
13336 superClass: [
13337 "Collaboration",
13338 "FlowElementsContainer"
13339 ]
13340 },
13341 {
13342 name: "GlobalChoreographyTask",
13343 superClass: [
13344 "Choreography"
13345 ],
13346 properties: [
13347 {
13348 name: "initiatingParticipantRef",
13349 type: "Participant",
13350 isAttr: true,
13351 isReference: true
13352 }
13353 ]
13354 },
13355 {
13356 name: "TextAnnotation",
13357 superClass: [
13358 "Artifact"
13359 ],
13360 properties: [
13361 {
13362 name: "text",
13363 type: "String"
13364 },
13365 {
13366 name: "textFormat",
13367 "default": "text/plain",
13368 isAttr: true,
13369 type: "String"
13370 }
13371 ]
13372 },
13373 {
13374 name: "Group",
13375 superClass: [
13376 "Artifact"
13377 ],
13378 properties: [
13379 {
13380 name: "categoryValueRef",
13381 type: "CategoryValue",
13382 isAttr: true,
13383 isReference: true
13384 }
13385 ]
13386 },
13387 {
13388 name: "Association",
13389 superClass: [
13390 "Artifact"
13391 ],
13392 properties: [
13393 {
13394 name: "associationDirection",
13395 type: "AssociationDirection",
13396 isAttr: true
13397 },
13398 {
13399 name: "sourceRef",
13400 type: "BaseElement",
13401 isAttr: true,
13402 isReference: true
13403 },
13404 {
13405 name: "targetRef",
13406 type: "BaseElement",
13407 isAttr: true,
13408 isReference: true
13409 }
13410 ]
13411 },
13412 {
13413 name: "Category",
13414 superClass: [
13415 "RootElement"
13416 ],
13417 properties: [
13418 {
13419 name: "categoryValue",
13420 type: "CategoryValue",
13421 isMany: true
13422 },
13423 {
13424 name: "name",
13425 isAttr: true,
13426 type: "String"
13427 }
13428 ]
13429 },
13430 {
13431 name: "Artifact",
13432 isAbstract: true,
13433 superClass: [
13434 "BaseElement"
13435 ]
13436 },
13437 {
13438 name: "CategoryValue",
13439 superClass: [
13440 "BaseElement"
13441 ],
13442 properties: [
13443 {
13444 name: "categorizedFlowElements",
13445 type: "FlowElement",
13446 isMany: true,
13447 isVirtual: true,
13448 isReference: true
13449 },
13450 {
13451 name: "value",
13452 isAttr: true,
13453 type: "String"
13454 }
13455 ]
13456 },
13457 {
13458 name: "Activity",
13459 isAbstract: true,
13460 superClass: [
13461 "FlowNode"
13462 ],
13463 properties: [
13464 {
13465 name: "isForCompensation",
13466 "default": false,
13467 isAttr: true,
13468 type: "Boolean"
13469 },
13470 {
13471 name: "default",
13472 type: "SequenceFlow",
13473 isAttr: true,
13474 isReference: true
13475 },
13476 {
13477 name: "ioSpecification",
13478 type: "InputOutputSpecification",
13479 xml: {
13480 serialize: "property"
13481 }
13482 },
13483 {
13484 name: "boundaryEventRefs",
13485 type: "BoundaryEvent",
13486 isMany: true,
13487 isReference: true
13488 },
13489 {
13490 name: "properties",
13491 type: "Property",
13492 isMany: true
13493 },
13494 {
13495 name: "dataInputAssociations",
13496 type: "DataInputAssociation",
13497 isMany: true
13498 },
13499 {
13500 name: "dataOutputAssociations",
13501 type: "DataOutputAssociation",
13502 isMany: true
13503 },
13504 {
13505 name: "startQuantity",
13506 "default": 1,
13507 isAttr: true,
13508 type: "Integer"
13509 },
13510 {
13511 name: "resources",
13512 type: "ResourceRole",
13513 isMany: true
13514 },
13515 {
13516 name: "completionQuantity",
13517 "default": 1,
13518 isAttr: true,
13519 type: "Integer"
13520 },
13521 {
13522 name: "loopCharacteristics",
13523 type: "LoopCharacteristics"
13524 }
13525 ]
13526 },
13527 {
13528 name: "ServiceTask",
13529 superClass: [
13530 "Task"
13531 ],
13532 properties: [
13533 {
13534 name: "implementation",
13535 isAttr: true,
13536 type: "String"
13537 },
13538 {
13539 name: "operationRef",
13540 type: "Operation",
13541 isAttr: true,
13542 isReference: true
13543 }
13544 ]
13545 },
13546 {
13547 name: "SubProcess",
13548 superClass: [
13549 "Activity",
13550 "FlowElementsContainer",
13551 "InteractionNode"
13552 ],
13553 properties: [
13554 {
13555 name: "triggeredByEvent",
13556 "default": false,
13557 isAttr: true,
13558 type: "Boolean"
13559 },
13560 {
13561 name: "artifacts",
13562 type: "Artifact",
13563 isMany: true
13564 }
13565 ]
13566 },
13567 {
13568 name: "LoopCharacteristics",
13569 isAbstract: true,
13570 superClass: [
13571 "BaseElement"
13572 ]
13573 },
13574 {
13575 name: "MultiInstanceLoopCharacteristics",
13576 superClass: [
13577 "LoopCharacteristics"
13578 ],
13579 properties: [
13580 {
13581 name: "isSequential",
13582 "default": false,
13583 isAttr: true,
13584 type: "Boolean"
13585 },
13586 {
13587 name: "behavior",
13588 type: "MultiInstanceBehavior",
13589 "default": "All",
13590 isAttr: true
13591 },
13592 {
13593 name: "loopCardinality",
13594 type: "Expression",
13595 xml: {
13596 serialize: "xsi:type"
13597 }
13598 },
13599 {
13600 name: "loopDataInputRef",
13601 type: "ItemAwareElement",
13602 isReference: true
13603 },
13604 {
13605 name: "loopDataOutputRef",
13606 type: "ItemAwareElement",
13607 isReference: true
13608 },
13609 {
13610 name: "inputDataItem",
13611 type: "DataInput",
13612 xml: {
13613 serialize: "property"
13614 }
13615 },
13616 {
13617 name: "outputDataItem",
13618 type: "DataOutput",
13619 xml: {
13620 serialize: "property"
13621 }
13622 },
13623 {
13624 name: "complexBehaviorDefinition",
13625 type: "ComplexBehaviorDefinition",
13626 isMany: true
13627 },
13628 {
13629 name: "completionCondition",
13630 type: "Expression",
13631 xml: {
13632 serialize: "xsi:type"
13633 }
13634 },
13635 {
13636 name: "oneBehaviorEventRef",
13637 type: "EventDefinition",
13638 isAttr: true,
13639 isReference: true
13640 },
13641 {
13642 name: "noneBehaviorEventRef",
13643 type: "EventDefinition",
13644 isAttr: true,
13645 isReference: true
13646 }
13647 ]
13648 },
13649 {
13650 name: "StandardLoopCharacteristics",
13651 superClass: [
13652 "LoopCharacteristics"
13653 ],
13654 properties: [
13655 {
13656 name: "testBefore",
13657 "default": false,
13658 isAttr: true,
13659 type: "Boolean"
13660 },
13661 {
13662 name: "loopCondition",
13663 type: "Expression",
13664 xml: {
13665 serialize: "xsi:type"
13666 }
13667 },
13668 {
13669 name: "loopMaximum",
13670 type: "Integer",
13671 isAttr: true
13672 }
13673 ]
13674 },
13675 {
13676 name: "CallActivity",
13677 superClass: [
13678 "Activity",
13679 "InteractionNode"
13680 ],
13681 properties: [
13682 {
13683 name: "calledElement",
13684 type: "String",
13685 isAttr: true
13686 }
13687 ]
13688 },
13689 {
13690 name: "Task",
13691 superClass: [
13692 "Activity",
13693 "InteractionNode"
13694 ]
13695 },
13696 {
13697 name: "SendTask",
13698 superClass: [
13699 "Task"
13700 ],
13701 properties: [
13702 {
13703 name: "implementation",
13704 isAttr: true,
13705 type: "String"
13706 },
13707 {
13708 name: "operationRef",
13709 type: "Operation",
13710 isAttr: true,
13711 isReference: true
13712 },
13713 {
13714 name: "messageRef",
13715 type: "Message",
13716 isAttr: true,
13717 isReference: true
13718 }
13719 ]
13720 },
13721 {
13722 name: "ReceiveTask",
13723 superClass: [
13724 "Task"
13725 ],
13726 properties: [
13727 {
13728 name: "implementation",
13729 isAttr: true,
13730 type: "String"
13731 },
13732 {
13733 name: "instantiate",
13734 "default": false,
13735 isAttr: true,
13736 type: "Boolean"
13737 },
13738 {
13739 name: "operationRef",
13740 type: "Operation",
13741 isAttr: true,
13742 isReference: true
13743 },
13744 {
13745 name: "messageRef",
13746 type: "Message",
13747 isAttr: true,
13748 isReference: true
13749 }
13750 ]
13751 },
13752 {
13753 name: "ScriptTask",
13754 superClass: [
13755 "Task"
13756 ],
13757 properties: [
13758 {
13759 name: "scriptFormat",
13760 isAttr: true,
13761 type: "String"
13762 },
13763 {
13764 name: "script",
13765 type: "String"
13766 }
13767 ]
13768 },
13769 {
13770 name: "BusinessRuleTask",
13771 superClass: [
13772 "Task"
13773 ],
13774 properties: [
13775 {
13776 name: "implementation",
13777 isAttr: true,
13778 type: "String"
13779 }
13780 ]
13781 },
13782 {
13783 name: "AdHocSubProcess",
13784 superClass: [
13785 "SubProcess"
13786 ],
13787 properties: [
13788 {
13789 name: "completionCondition",
13790 type: "Expression",
13791 xml: {
13792 serialize: "xsi:type"
13793 }
13794 },
13795 {
13796 name: "ordering",
13797 type: "AdHocOrdering",
13798 isAttr: true
13799 },
13800 {
13801 name: "cancelRemainingInstances",
13802 "default": true,
13803 isAttr: true,
13804 type: "Boolean"
13805 }
13806 ]
13807 },
13808 {
13809 name: "Transaction",
13810 superClass: [
13811 "SubProcess"
13812 ],
13813 properties: [
13814 {
13815 name: "protocol",
13816 isAttr: true,
13817 type: "String"
13818 },
13819 {
13820 name: "method",
13821 isAttr: true,
13822 type: "String"
13823 }
13824 ]
13825 },
13826 {
13827 name: "GlobalScriptTask",
13828 superClass: [
13829 "GlobalTask"
13830 ],
13831 properties: [
13832 {
13833 name: "scriptLanguage",
13834 isAttr: true,
13835 type: "String"
13836 },
13837 {
13838 name: "script",
13839 isAttr: true,
13840 type: "String"
13841 }
13842 ]
13843 },
13844 {
13845 name: "GlobalBusinessRuleTask",
13846 superClass: [
13847 "GlobalTask"
13848 ],
13849 properties: [
13850 {
13851 name: "implementation",
13852 isAttr: true,
13853 type: "String"
13854 }
13855 ]
13856 },
13857 {
13858 name: "ComplexBehaviorDefinition",
13859 superClass: [
13860 "BaseElement"
13861 ],
13862 properties: [
13863 {
13864 name: "condition",
13865 type: "FormalExpression"
13866 },
13867 {
13868 name: "event",
13869 type: "ImplicitThrowEvent"
13870 }
13871 ]
13872 },
13873 {
13874 name: "ResourceRole",
13875 superClass: [
13876 "BaseElement"
13877 ],
13878 properties: [
13879 {
13880 name: "resourceRef",
13881 type: "Resource",
13882 isReference: true
13883 },
13884 {
13885 name: "resourceParameterBindings",
13886 type: "ResourceParameterBinding",
13887 isMany: true
13888 },
13889 {
13890 name: "resourceAssignmentExpression",
13891 type: "ResourceAssignmentExpression"
13892 },
13893 {
13894 name: "name",
13895 isAttr: true,
13896 type: "String"
13897 }
13898 ]
13899 },
13900 {
13901 name: "ResourceParameterBinding",
13902 properties: [
13903 {
13904 name: "expression",
13905 type: "Expression",
13906 xml: {
13907 serialize: "xsi:type"
13908 }
13909 },
13910 {
13911 name: "parameterRef",
13912 type: "ResourceParameter",
13913 isAttr: true,
13914 isReference: true
13915 }
13916 ],
13917 superClass: [
13918 "BaseElement"
13919 ]
13920 },
13921 {
13922 name: "ResourceAssignmentExpression",
13923 properties: [
13924 {
13925 name: "expression",
13926 type: "Expression",
13927 xml: {
13928 serialize: "xsi:type"
13929 }
13930 }
13931 ],
13932 superClass: [
13933 "BaseElement"
13934 ]
13935 },
13936 {
13937 name: "Import",
13938 properties: [
13939 {
13940 name: "importType",
13941 isAttr: true,
13942 type: "String"
13943 },
13944 {
13945 name: "location",
13946 isAttr: true,
13947 type: "String"
13948 },
13949 {
13950 name: "namespace",
13951 isAttr: true,
13952 type: "String"
13953 }
13954 ]
13955 },
13956 {
13957 name: "Definitions",
13958 superClass: [
13959 "BaseElement"
13960 ],
13961 properties: [
13962 {
13963 name: "name",
13964 isAttr: true,
13965 type: "String"
13966 },
13967 {
13968 name: "targetNamespace",
13969 isAttr: true,
13970 type: "String"
13971 },
13972 {
13973 name: "expressionLanguage",
13974 "default": "http://www.w3.org/1999/XPath",
13975 isAttr: true,
13976 type: "String"
13977 },
13978 {
13979 name: "typeLanguage",
13980 "default": "http://www.w3.org/2001/XMLSchema",
13981 isAttr: true,
13982 type: "String"
13983 },
13984 {
13985 name: "imports",
13986 type: "Import",
13987 isMany: true
13988 },
13989 {
13990 name: "extensions",
13991 type: "Extension",
13992 isMany: true
13993 },
13994 {
13995 name: "rootElements",
13996 type: "RootElement",
13997 isMany: true
13998 },
13999 {
14000 name: "diagrams",
14001 isMany: true,
14002 type: "bpmndi:BPMNDiagram"
14003 },
14004 {
14005 name: "exporter",
14006 isAttr: true,
14007 type: "String"
14008 },
14009 {
14010 name: "relationships",
14011 type: "Relationship",
14012 isMany: true
14013 },
14014 {
14015 name: "exporterVersion",
14016 isAttr: true,
14017 type: "String"
14018 }
14019 ]
14020 }
14021 ];
14022 var enumerations = [
14023 {
14024 name: "ProcessType",
14025 literalValues: [
14026 {
14027 name: "None"
14028 },
14029 {
14030 name: "Public"
14031 },
14032 {
14033 name: "Private"
14034 }
14035 ]
14036 },
14037 {
14038 name: "GatewayDirection",
14039 literalValues: [
14040 {
14041 name: "Unspecified"
14042 },
14043 {
14044 name: "Converging"
14045 },
14046 {
14047 name: "Diverging"
14048 },
14049 {
14050 name: "Mixed"
14051 }
14052 ]
14053 },
14054 {
14055 name: "EventBasedGatewayType",
14056 literalValues: [
14057 {
14058 name: "Parallel"
14059 },
14060 {
14061 name: "Exclusive"
14062 }
14063 ]
14064 },
14065 {
14066 name: "RelationshipDirection",
14067 literalValues: [
14068 {
14069 name: "None"
14070 },
14071 {
14072 name: "Forward"
14073 },
14074 {
14075 name: "Backward"
14076 },
14077 {
14078 name: "Both"
14079 }
14080 ]
14081 },
14082 {
14083 name: "ItemKind",
14084 literalValues: [
14085 {
14086 name: "Physical"
14087 },
14088 {
14089 name: "Information"
14090 }
14091 ]
14092 },
14093 {
14094 name: "ChoreographyLoopType",
14095 literalValues: [
14096 {
14097 name: "None"
14098 },
14099 {
14100 name: "Standard"
14101 },
14102 {
14103 name: "MultiInstanceSequential"
14104 },
14105 {
14106 name: "MultiInstanceParallel"
14107 }
14108 ]
14109 },
14110 {
14111 name: "AssociationDirection",
14112 literalValues: [
14113 {
14114 name: "None"
14115 },
14116 {
14117 name: "One"
14118 },
14119 {
14120 name: "Both"
14121 }
14122 ]
14123 },
14124 {
14125 name: "MultiInstanceBehavior",
14126 literalValues: [
14127 {
14128 name: "None"
14129 },
14130 {
14131 name: "One"
14132 },
14133 {
14134 name: "All"
14135 },
14136 {
14137 name: "Complex"
14138 }
14139 ]
14140 },
14141 {
14142 name: "AdHocOrdering",
14143 literalValues: [
14144 {
14145 name: "Parallel"
14146 },
14147 {
14148 name: "Sequential"
14149 }
14150 ]
14151 }
14152 ];
14153 var xml = {
14154 tagAlias: "lowerCase",
14155 typePrefix: "t"
14156 };
14157 var BpmnPackage = {
14158 name: name,
14159 uri: uri,
14160 prefix: prefix$1,
14161 associations: associations,
14162 types: types$1,
14163 enumerations: enumerations,
14164 xml: xml
14165 };
14166
14167 var name$1 = "BPMNDI";
14168 var uri$1 = "http://www.omg.org/spec/BPMN/20100524/DI";
14169 var prefix$1$1 = "bpmndi";
14170 var types$1$1 = [
14171 {
14172 name: "BPMNDiagram",
14173 properties: [
14174 {
14175 name: "plane",
14176 type: "BPMNPlane",
14177 redefines: "di:Diagram#rootElement"
14178 },
14179 {
14180 name: "labelStyle",
14181 type: "BPMNLabelStyle",
14182 isMany: true
14183 }
14184 ],
14185 superClass: [
14186 "di:Diagram"
14187 ]
14188 },
14189 {
14190 name: "BPMNPlane",
14191 properties: [
14192 {
14193 name: "bpmnElement",
14194 isAttr: true,
14195 isReference: true,
14196 type: "bpmn:BaseElement",
14197 redefines: "di:DiagramElement#modelElement"
14198 }
14199 ],
14200 superClass: [
14201 "di:Plane"
14202 ]
14203 },
14204 {
14205 name: "BPMNShape",
14206 properties: [
14207 {
14208 name: "bpmnElement",
14209 isAttr: true,
14210 isReference: true,
14211 type: "bpmn:BaseElement",
14212 redefines: "di:DiagramElement#modelElement"
14213 },
14214 {
14215 name: "isHorizontal",
14216 isAttr: true,
14217 type: "Boolean"
14218 },
14219 {
14220 name: "isExpanded",
14221 isAttr: true,
14222 type: "Boolean"
14223 },
14224 {
14225 name: "isMarkerVisible",
14226 isAttr: true,
14227 type: "Boolean"
14228 },
14229 {
14230 name: "label",
14231 type: "BPMNLabel"
14232 },
14233 {
14234 name: "isMessageVisible",
14235 isAttr: true,
14236 type: "Boolean"
14237 },
14238 {
14239 name: "participantBandKind",
14240 type: "ParticipantBandKind",
14241 isAttr: true
14242 },
14243 {
14244 name: "choreographyActivityShape",
14245 type: "BPMNShape",
14246 isAttr: true,
14247 isReference: true
14248 }
14249 ],
14250 superClass: [
14251 "di:LabeledShape"
14252 ]
14253 },
14254 {
14255 name: "BPMNEdge",
14256 properties: [
14257 {
14258 name: "label",
14259 type: "BPMNLabel"
14260 },
14261 {
14262 name: "bpmnElement",
14263 isAttr: true,
14264 isReference: true,
14265 type: "bpmn:BaseElement",
14266 redefines: "di:DiagramElement#modelElement"
14267 },
14268 {
14269 name: "sourceElement",
14270 isAttr: true,
14271 isReference: true,
14272 type: "di:DiagramElement",
14273 redefines: "di:Edge#source"
14274 },
14275 {
14276 name: "targetElement",
14277 isAttr: true,
14278 isReference: true,
14279 type: "di:DiagramElement",
14280 redefines: "di:Edge#target"
14281 },
14282 {
14283 name: "messageVisibleKind",
14284 type: "MessageVisibleKind",
14285 isAttr: true,
14286 "default": "initiating"
14287 }
14288 ],
14289 superClass: [
14290 "di:LabeledEdge"
14291 ]
14292 },
14293 {
14294 name: "BPMNLabel",
14295 properties: [
14296 {
14297 name: "labelStyle",
14298 type: "BPMNLabelStyle",
14299 isAttr: true,
14300 isReference: true,
14301 redefines: "di:DiagramElement#style"
14302 }
14303 ],
14304 superClass: [
14305 "di:Label"
14306 ]
14307 },
14308 {
14309 name: "BPMNLabelStyle",
14310 properties: [
14311 {
14312 name: "font",
14313 type: "dc:Font"
14314 }
14315 ],
14316 superClass: [
14317 "di:Style"
14318 ]
14319 }
14320 ];
14321 var enumerations$1 = [
14322 {
14323 name: "ParticipantBandKind",
14324 literalValues: [
14325 {
14326 name: "top_initiating"
14327 },
14328 {
14329 name: "middle_initiating"
14330 },
14331 {
14332 name: "bottom_initiating"
14333 },
14334 {
14335 name: "top_non_initiating"
14336 },
14337 {
14338 name: "middle_non_initiating"
14339 },
14340 {
14341 name: "bottom_non_initiating"
14342 }
14343 ]
14344 },
14345 {
14346 name: "MessageVisibleKind",
14347 literalValues: [
14348 {
14349 name: "initiating"
14350 },
14351 {
14352 name: "non_initiating"
14353 }
14354 ]
14355 }
14356 ];
14357 var associations$1 = [
14358 ];
14359 var BpmnDiPackage = {
14360 name: name$1,
14361 uri: uri$1,
14362 prefix: prefix$1$1,
14363 types: types$1$1,
14364 enumerations: enumerations$1,
14365 associations: associations$1
14366 };
14367
14368 var name$2 = "DC";
14369 var uri$2 = "http://www.omg.org/spec/DD/20100524/DC";
14370 var prefix$2 = "dc";
14371 var types$2 = [
14372 {
14373 name: "Boolean"
14374 },
14375 {
14376 name: "Integer"
14377 },
14378 {
14379 name: "Real"
14380 },
14381 {
14382 name: "String"
14383 },
14384 {
14385 name: "Font",
14386 properties: [
14387 {
14388 name: "name",
14389 type: "String",
14390 isAttr: true
14391 },
14392 {
14393 name: "size",
14394 type: "Real",
14395 isAttr: true
14396 },
14397 {
14398 name: "isBold",
14399 type: "Boolean",
14400 isAttr: true
14401 },
14402 {
14403 name: "isItalic",
14404 type: "Boolean",
14405 isAttr: true
14406 },
14407 {
14408 name: "isUnderline",
14409 type: "Boolean",
14410 isAttr: true
14411 },
14412 {
14413 name: "isStrikeThrough",
14414 type: "Boolean",
14415 isAttr: true
14416 }
14417 ]
14418 },
14419 {
14420 name: "Point",
14421 properties: [
14422 {
14423 name: "x",
14424 type: "Real",
14425 "default": "0",
14426 isAttr: true
14427 },
14428 {
14429 name: "y",
14430 type: "Real",
14431 "default": "0",
14432 isAttr: true
14433 }
14434 ]
14435 },
14436 {
14437 name: "Bounds",
14438 properties: [
14439 {
14440 name: "x",
14441 type: "Real",
14442 "default": "0",
14443 isAttr: true
14444 },
14445 {
14446 name: "y",
14447 type: "Real",
14448 "default": "0",
14449 isAttr: true
14450 },
14451 {
14452 name: "width",
14453 type: "Real",
14454 isAttr: true
14455 },
14456 {
14457 name: "height",
14458 type: "Real",
14459 isAttr: true
14460 }
14461 ]
14462 }
14463 ];
14464 var associations$2 = [
14465 ];
14466 var DcPackage = {
14467 name: name$2,
14468 uri: uri$2,
14469 prefix: prefix$2,
14470 types: types$2,
14471 associations: associations$2
14472 };
14473
14474 var name$3 = "DI";
14475 var uri$3 = "http://www.omg.org/spec/DD/20100524/DI";
14476 var prefix$3 = "di";
14477 var types$3 = [
14478 {
14479 name: "DiagramElement",
14480 isAbstract: true,
14481 properties: [
14482 {
14483 name: "id",
14484 isAttr: true,
14485 isId: true,
14486 type: "String"
14487 },
14488 {
14489 name: "extension",
14490 type: "Extension"
14491 },
14492 {
14493 name: "owningDiagram",
14494 type: "Diagram",
14495 isReadOnly: true,
14496 isVirtual: true,
14497 isReference: true
14498 },
14499 {
14500 name: "owningElement",
14501 type: "DiagramElement",
14502 isReadOnly: true,
14503 isVirtual: true,
14504 isReference: true
14505 },
14506 {
14507 name: "modelElement",
14508 isReadOnly: true,
14509 isVirtual: true,
14510 isReference: true,
14511 type: "Element"
14512 },
14513 {
14514 name: "style",
14515 type: "Style",
14516 isReadOnly: true,
14517 isVirtual: true,
14518 isReference: true
14519 },
14520 {
14521 name: "ownedElement",
14522 type: "DiagramElement",
14523 isReadOnly: true,
14524 isMany: true,
14525 isVirtual: true
14526 }
14527 ]
14528 },
14529 {
14530 name: "Node",
14531 isAbstract: true,
14532 superClass: [
14533 "DiagramElement"
14534 ]
14535 },
14536 {
14537 name: "Edge",
14538 isAbstract: true,
14539 superClass: [
14540 "DiagramElement"
14541 ],
14542 properties: [
14543 {
14544 name: "source",
14545 type: "DiagramElement",
14546 isReadOnly: true,
14547 isVirtual: true,
14548 isReference: true
14549 },
14550 {
14551 name: "target",
14552 type: "DiagramElement",
14553 isReadOnly: true,
14554 isVirtual: true,
14555 isReference: true
14556 },
14557 {
14558 name: "waypoint",
14559 isUnique: false,
14560 isMany: true,
14561 type: "dc:Point",
14562 xml: {
14563 serialize: "xsi:type"
14564 }
14565 }
14566 ]
14567 },
14568 {
14569 name: "Diagram",
14570 isAbstract: true,
14571 properties: [
14572 {
14573 name: "id",
14574 isAttr: true,
14575 isId: true,
14576 type: "String"
14577 },
14578 {
14579 name: "rootElement",
14580 type: "DiagramElement",
14581 isReadOnly: true,
14582 isVirtual: true
14583 },
14584 {
14585 name: "name",
14586 isAttr: true,
14587 type: "String"
14588 },
14589 {
14590 name: "documentation",
14591 isAttr: true,
14592 type: "String"
14593 },
14594 {
14595 name: "resolution",
14596 isAttr: true,
14597 type: "Real"
14598 },
14599 {
14600 name: "ownedStyle",
14601 type: "Style",
14602 isReadOnly: true,
14603 isMany: true,
14604 isVirtual: true
14605 }
14606 ]
14607 },
14608 {
14609 name: "Shape",
14610 isAbstract: true,
14611 superClass: [
14612 "Node"
14613 ],
14614 properties: [
14615 {
14616 name: "bounds",
14617 type: "dc:Bounds"
14618 }
14619 ]
14620 },
14621 {
14622 name: "Plane",
14623 isAbstract: true,
14624 superClass: [
14625 "Node"
14626 ],
14627 properties: [
14628 {
14629 name: "planeElement",
14630 type: "DiagramElement",
14631 subsettedProperty: "DiagramElement-ownedElement",
14632 isMany: true
14633 }
14634 ]
14635 },
14636 {
14637 name: "LabeledEdge",
14638 isAbstract: true,
14639 superClass: [
14640 "Edge"
14641 ],
14642 properties: [
14643 {
14644 name: "ownedLabel",
14645 type: "Label",
14646 isReadOnly: true,
14647 subsettedProperty: "DiagramElement-ownedElement",
14648 isMany: true,
14649 isVirtual: true
14650 }
14651 ]
14652 },
14653 {
14654 name: "LabeledShape",
14655 isAbstract: true,
14656 superClass: [
14657 "Shape"
14658 ],
14659 properties: [
14660 {
14661 name: "ownedLabel",
14662 type: "Label",
14663 isReadOnly: true,
14664 subsettedProperty: "DiagramElement-ownedElement",
14665 isMany: true,
14666 isVirtual: true
14667 }
14668 ]
14669 },
14670 {
14671 name: "Label",
14672 isAbstract: true,
14673 superClass: [
14674 "Node"
14675 ],
14676 properties: [
14677 {
14678 name: "bounds",
14679 type: "dc:Bounds"
14680 }
14681 ]
14682 },
14683 {
14684 name: "Style",
14685 isAbstract: true,
14686 properties: [
14687 {
14688 name: "id",
14689 isAttr: true,
14690 isId: true,
14691 type: "String"
14692 }
14693 ]
14694 },
14695 {
14696 name: "Extension",
14697 properties: [
14698 {
14699 name: "values",
14700 isMany: true,
14701 type: "Element"
14702 }
14703 ]
14704 }
14705 ];
14706 var associations$3 = [
14707 ];
14708 var xml$1 = {
14709 tagAlias: "lowerCase"
14710 };
14711 var DiPackage = {
14712 name: name$3,
14713 uri: uri$3,
14714 prefix: prefix$3,
14715 types: types$3,
14716 associations: associations$3,
14717 xml: xml$1
14718 };
14719
14720 var name$4 = "bpmn.io colors for BPMN";
14721 var uri$4 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
14722 var prefix$4 = "bioc";
14723 var types$4 = [
14724 {
14725 name: "ColoredShape",
14726 "extends": [
14727 "bpmndi:BPMNShape"
14728 ],
14729 properties: [
14730 {
14731 name: "stroke",
14732 isAttr: true,
14733 type: "String"
14734 },
14735 {
14736 name: "fill",
14737 isAttr: true,
14738 type: "String"
14739 }
14740 ]
14741 },
14742 {
14743 name: "ColoredEdge",
14744 "extends": [
14745 "bpmndi:BPMNEdge"
14746 ],
14747 properties: [
14748 {
14749 name: "stroke",
14750 isAttr: true,
14751 type: "String"
14752 },
14753 {
14754 name: "fill",
14755 isAttr: true,
14756 type: "String"
14757 }
14758 ]
14759 }
14760 ];
14761 var enumerations$2 = [
14762 ];
14763 var associations$4 = [
14764 ];
14765 var BiocPackage = {
14766 name: name$4,
14767 uri: uri$4,
14768 prefix: prefix$4,
14769 types: types$4,
14770 enumerations: enumerations$2,
14771 associations: associations$4
14772 };
14773
14774 var name$5 = "BPMN in Color";
14775 var uri$5 = "http://www.omg.org/spec/BPMN/non-normative/color/1.0";
14776 var prefix$5 = "color";
14777 var types$5 = [
14778 {
14779 name: "ColoredLabel",
14780 "extends": [
14781 "bpmndi:BPMNLabel"
14782 ],
14783 properties: [
14784 {
14785 name: "color",
14786 isAttr: true,
14787 type: "String"
14788 }
14789 ]
14790 },
14791 {
14792 name: "ColoredShape",
14793 "extends": [
14794 "bpmndi:BPMNShape"
14795 ],
14796 properties: [
14797 {
14798 name: "background-color",
14799 isAttr: true,
14800 type: "String"
14801 },
14802 {
14803 name: "border-color",
14804 isAttr: true,
14805 type: "String"
14806 }
14807 ]
14808 },
14809 {
14810 name: "ColoredEdge",
14811 "extends": [
14812 "bpmndi:BPMNEdge"
14813 ],
14814 properties: [
14815 {
14816 name: "border-color",
14817 isAttr: true,
14818 type: "String"
14819 }
14820 ]
14821 }
14822 ];
14823 var enumerations$3 = [
14824 ];
14825 var associations$5 = [
14826 ];
14827 var BpmnInColorPackage = {
14828 name: name$5,
14829 uri: uri$5,
14830 prefix: prefix$5,
14831 types: types$5,
14832 enumerations: enumerations$3,
14833 associations: associations$5
14834 };
14835
14836 var packages = {
14837 bpmn: BpmnPackage,
14838 bpmndi: BpmnDiPackage,
14839 dc: DcPackage,
14840 di: DiPackage,
14841 bioc: BiocPackage,
14842 color: BpmnInColorPackage
14843 };
14844
14845 function simple(additionalPackages, options) {
14846 var pks = assign({}, packages, additionalPackages);
14847
14848 return new BpmnModdle(pks, options);
14849 }
14850
14851 function elementToString(e) {
14852 if (!e) {
14853 return '<null>';
14854 }
14855
14856 return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
14857 }
14858
14859 var diRefs = new objectRefs(
14860 { name: 'bpmnElement', enumerable: true },
14861 { name: 'di', configurable: true }
14862 );
14863
14864 /**
14865 * Returns true if an element has the given meta-model type
14866 *
14867 * @param {ModdleElement} element
14868 * @param {string} type
14869 *
14870 * @return {boolean}
14871 */
14872 function is(element, type) {
14873 return element.$instanceOf(type);
14874 }
14875
14876
14877 /**
14878 * Find a suitable display candidate for definitions where the DI does not
14879 * correctly specify one.
14880 */
14881 function findDisplayCandidate(definitions) {
14882 return find(definitions.rootElements, function(e) {
14883 return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration');
14884 });
14885 }
14886
14887
14888 function BpmnTreeWalker(handler, translate) {
14889
14890 // list of containers already walked
14891 var handledElements = {};
14892
14893 // list of elements to handle deferred to ensure
14894 // prerequisites are drawn
14895 var deferred = [];
14896
14897 // Helpers //////////////////////
14898
14899 function contextual(fn, ctx) {
14900 return function(e) {
14901 fn(e, ctx);
14902 };
14903 }
14904
14905 function handled(element) {
14906 handledElements[element.id] = element;
14907 }
14908
14909 function isHandled(element) {
14910 return handledElements[element.id];
14911 }
14912
14913 function visit(element, ctx) {
14914
14915 var gfx = element.gfx;
14916
14917 // avoid multiple rendering of elements
14918 if (gfx) {
14919 throw new Error(
14920 translate('already rendered {element}', { element: elementToString(element) })
14921 );
14922 }
14923
14924 // call handler
14925 return handler.element(element, ctx);
14926 }
14927
14928 function visitRoot(element, diagram) {
14929 return handler.root(element, diagram);
14930 }
14931
14932 function visitIfDi(element, ctx) {
14933
14934 try {
14935 var gfx = element.di && visit(element, ctx);
14936
14937 handled(element);
14938
14939 return gfx;
14940 } catch (e) {
14941 logError(e.message, { element: element, error: e });
14942
14943 console.error(translate('failed to import {element}', { element: elementToString(element) }));
14944 console.error(e);
14945 }
14946 }
14947
14948 function logError(message, context) {
14949 handler.error(message, context);
14950 }
14951
14952 // DI handling //////////////////////
14953
14954 function registerDi(di) {
14955 var bpmnElement = di.bpmnElement;
14956
14957 if (bpmnElement) {
14958 if (bpmnElement.di) {
14959 logError(
14960 translate('multiple DI elements defined for {element}', {
14961 element: elementToString(bpmnElement)
14962 }),
14963 { element: bpmnElement }
14964 );
14965 } else {
14966 diRefs.bind(bpmnElement, 'di');
14967 bpmnElement.di = di;
14968 }
14969 } else {
14970 logError(
14971 translate('no bpmnElement referenced in {element}', {
14972 element: elementToString(di)
14973 }),
14974 { element: di }
14975 );
14976 }
14977 }
14978
14979 function handleDiagram(diagram) {
14980 handlePlane(diagram.plane);
14981 }
14982
14983 function handlePlane(plane) {
14984 registerDi(plane);
14985
14986 forEach(plane.planeElement, handlePlaneElement);
14987 }
14988
14989 function handlePlaneElement(planeElement) {
14990 registerDi(planeElement);
14991 }
14992
14993
14994 // Semantic handling //////////////////////
14995
14996 /**
14997 * Handle definitions and return the rendered diagram (if any)
14998 *
14999 * @param {ModdleElement} definitions to walk and import
15000 * @param {ModdleElement} [diagram] specific diagram to import and display
15001 *
15002 * @throws {Error} if no diagram to display could be found
15003 */
15004 function handleDefinitions(definitions, diagram) {
15005
15006 // make sure we walk the correct bpmnElement
15007
15008 var diagrams = definitions.diagrams;
15009
15010 if (diagram && diagrams.indexOf(diagram) === -1) {
15011 throw new Error(translate('diagram not part of bpmn:Definitions'));
15012 }
15013
15014 if (!diagram && diagrams && diagrams.length) {
15015 diagram = diagrams[0];
15016 }
15017
15018 // no diagram -> nothing to import
15019 if (!diagram) {
15020 throw new Error(translate('no diagram to display'));
15021 }
15022
15023 // load DI from selected diagram only
15024 handleDiagram(diagram);
15025
15026
15027 var plane = diagram.plane;
15028
15029 if (!plane) {
15030 throw new Error(translate(
15031 'no plane for {element}',
15032 { element: elementToString(diagram) }
15033 ));
15034 }
15035
15036 var rootElement = plane.bpmnElement;
15037
15038 // ensure we default to a suitable display candidate (process or collaboration),
15039 // even if non is specified in DI
15040 if (!rootElement) {
15041 rootElement = findDisplayCandidate(definitions);
15042
15043 if (!rootElement) {
15044 throw new Error(translate('no process or collaboration to display'));
15045 } else {
15046
15047 logError(
15048 translate('correcting missing bpmnElement on {plane} to {rootElement}', {
15049 plane: elementToString(plane),
15050 rootElement: elementToString(rootElement)
15051 })
15052 );
15053
15054 // correct DI on the fly
15055 plane.bpmnElement = rootElement;
15056 registerDi(plane);
15057 }
15058 }
15059
15060
15061 var ctx = visitRoot(rootElement, plane);
15062
15063 if (is(rootElement, 'bpmn:Process')) {
15064 handleProcess(rootElement, ctx);
15065 } else if (is(rootElement, 'bpmn:Collaboration')) {
15066 handleCollaboration(rootElement);
15067
15068 // force drawing of everything not yet drawn that is part of the target DI
15069 handleUnhandledProcesses(definitions.rootElements, ctx);
15070 } else {
15071 throw new Error(
15072 translate('unsupported bpmnElement for {plane}: {rootElement}', {
15073 plane: elementToString(plane),
15074 rootElement: elementToString(rootElement)
15075 })
15076 );
15077 }
15078
15079 // handle all deferred elements
15080 handleDeferred();
15081 }
15082
15083 function handleDeferred() {
15084
15085 var fn;
15086
15087 // drain deferred until empty
15088 while (deferred.length) {
15089 fn = deferred.shift();
15090
15091 fn();
15092 }
15093 }
15094
15095 function handleProcess(process, context) {
15096 handleFlowElementsContainer(process, context);
15097 handleIoSpecification(process.ioSpecification, context);
15098
15099 handleArtifacts(process.artifacts, context);
15100
15101 // log process handled
15102 handled(process);
15103 }
15104
15105 function handleUnhandledProcesses(rootElements, ctx) {
15106
15107 // walk through all processes that have not yet been drawn and draw them
15108 // if they contain lanes with DI information.
15109 // we do this to pass the free-floating lane test cases in the MIWG test suite
15110 var processes = filter(rootElements, function(e) {
15111 return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets;
15112 });
15113
15114 processes.forEach(contextual(handleProcess, ctx));
15115 }
15116
15117 function handleMessageFlow(messageFlow, context) {
15118 visitIfDi(messageFlow, context);
15119 }
15120
15121 function handleMessageFlows(messageFlows, context) {
15122 forEach(messageFlows, contextual(handleMessageFlow, context));
15123 }
15124
15125 function handleDataAssociation(association, context) {
15126 visitIfDi(association, context);
15127 }
15128
15129 function handleDataInput(dataInput, context) {
15130 visitIfDi(dataInput, context);
15131 }
15132
15133 function handleDataOutput(dataOutput, context) {
15134 visitIfDi(dataOutput, context);
15135 }
15136
15137 function handleArtifact(artifact, context) {
15138
15139 // bpmn:TextAnnotation
15140 // bpmn:Group
15141 // bpmn:Association
15142
15143 visitIfDi(artifact, context);
15144 }
15145
15146 function handleArtifacts(artifacts, context) {
15147
15148 forEach(artifacts, function(e) {
15149 if (is(e, 'bpmn:Association')) {
15150 deferred.push(function() {
15151 handleArtifact(e, context);
15152 });
15153 } else {
15154 handleArtifact(e, context);
15155 }
15156 });
15157 }
15158
15159 function handleIoSpecification(ioSpecification, context) {
15160
15161 if (!ioSpecification) {
15162 return;
15163 }
15164
15165 forEach(ioSpecification.dataInputs, contextual(handleDataInput, context));
15166 forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
15167 }
15168
15169 function handleSubProcess(subProcess, context) {
15170 handleFlowElementsContainer(subProcess, context);
15171 handleArtifacts(subProcess.artifacts, context);
15172 }
15173
15174 function handleFlowNode(flowNode, context) {
15175 var childCtx = visitIfDi(flowNode, context);
15176
15177 if (is(flowNode, 'bpmn:SubProcess')) {
15178 handleSubProcess(flowNode, childCtx || context);
15179 }
15180
15181 if (is(flowNode, 'bpmn:Activity')) {
15182 handleIoSpecification(flowNode.ioSpecification, context);
15183 }
15184
15185 // defer handling of associations
15186 // affected types:
15187 //
15188 // * bpmn:Activity
15189 // * bpmn:ThrowEvent
15190 // * bpmn:CatchEvent
15191 //
15192 deferred.push(function() {
15193 forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
15194 forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
15195 });
15196 }
15197
15198 function handleSequenceFlow(sequenceFlow, context) {
15199 visitIfDi(sequenceFlow, context);
15200 }
15201
15202 function handleDataElement(dataObject, context) {
15203 visitIfDi(dataObject, context);
15204 }
15205
15206 function handleLane(lane, context) {
15207
15208 deferred.push(function() {
15209
15210 var newContext = visitIfDi(lane, context);
15211
15212 if (lane.childLaneSet) {
15213 handleLaneSet(lane.childLaneSet, newContext || context);
15214 }
15215
15216 wireFlowNodeRefs(lane);
15217 });
15218 }
15219
15220 function handleLaneSet(laneSet, context) {
15221 forEach(laneSet.lanes, contextual(handleLane, context));
15222 }
15223
15224 function handleLaneSets(laneSets, context) {
15225 forEach(laneSets, contextual(handleLaneSet, context));
15226 }
15227
15228 function handleFlowElementsContainer(container, context) {
15229 handleFlowElements(container.flowElements, context);
15230
15231 if (container.laneSets) {
15232 handleLaneSets(container.laneSets, context);
15233 }
15234 }
15235
15236 function handleFlowElements(flowElements, context) {
15237 forEach(flowElements, function(e) {
15238 if (is(e, 'bpmn:SequenceFlow')) {
15239 deferred.push(function() {
15240 handleSequenceFlow(e, context);
15241 });
15242 } else if (is(e, 'bpmn:BoundaryEvent')) {
15243 deferred.unshift(function() {
15244 handleFlowNode(e, context);
15245 });
15246 } else if (is(e, 'bpmn:FlowNode')) {
15247 handleFlowNode(e, context);
15248 } else if (is(e, 'bpmn:DataObject')) ; else if (is(e, 'bpmn:DataStoreReference')) {
15249 handleDataElement(e, context);
15250 } else if (is(e, 'bpmn:DataObjectReference')) {
15251 handleDataElement(e, context);
15252 } else {
15253 logError(
15254 translate('unrecognized flowElement {element} in context {context}', {
15255 element: elementToString(e),
15256 context: (context ? elementToString(context.businessObject) : 'null')
15257 }),
15258 { element: e, context: context }
15259 );
15260 }
15261 });
15262 }
15263
15264 function handleParticipant(participant, context) {
15265 var newCtx = visitIfDi(participant, context);
15266
15267 var process = participant.processRef;
15268 if (process) {
15269 handleProcess(process, newCtx || context);
15270 }
15271 }
15272
15273 function handleCollaboration(collaboration) {
15274
15275 forEach(collaboration.participants, contextual(handleParticipant));
15276
15277 handleArtifacts(collaboration.artifacts);
15278
15279 // handle message flows latest in the process
15280 deferred.push(function() {
15281 handleMessageFlows(collaboration.messageFlows);
15282 });
15283 }
15284
15285
15286 function wireFlowNodeRefs(lane) {
15287
15288 // wire the virtual flowNodeRefs <-> relationship
15289 forEach(lane.flowNodeRef, function(flowNode) {
15290 var lanes = flowNode.get('lanes');
15291
15292 if (lanes) {
15293 lanes.push(lane);
15294 }
15295 });
15296 }
15297
15298 // API //////////////////////
15299
15300 return {
15301 handleDeferred: handleDeferred,
15302 handleDefinitions: handleDefinitions,
15303 handleSubProcess: handleSubProcess,
15304 registerDi: registerDi
15305 };
15306 }
15307
15308 /**
15309 * The importBpmnDiagram result.
15310 *
15311 * @typedef {Object} ImportBPMNDiagramResult
15312 *
15313 * @property {Array<string>} warnings
15314 */
15315
15316 /**
15317 * The importBpmnDiagram error.
15318 *
15319 * @typedef {Error} ImportBPMNDiagramError
15320 *
15321 * @property {Array<string>} warnings
15322 */
15323
15324 /**
15325 * Import the definitions into a diagram.
15326 *
15327 * Errors and warnings are reported through the specified callback.
15328 *
15329 * @param {djs.Diagram} diagram
15330 * @param {ModdleElement<Definitions>} definitions
15331 * @param {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered
15332 * (if not provided, the first one will be rendered)
15333 *
15334 * Returns {Promise<ImportBPMNDiagramResult, ImportBPMNDiagramError>}
15335 */
15336 function importBpmnDiagram(diagram, definitions, bpmnDiagram) {
15337
15338 var importer,
15339 eventBus,
15340 translate;
15341
15342 var error,
15343 warnings = [];
15344
15345 /**
15346 * Walk the diagram semantically, importing (=drawing)
15347 * all elements you encounter.
15348 *
15349 * @param {ModdleElement<Definitions>} definitions
15350 * @param {ModdleElement<BPMNDiagram>} bpmnDiagram
15351 */
15352 function render(definitions, bpmnDiagram) {
15353
15354 var visitor = {
15355
15356 root: function(element) {
15357 return importer.add(element);
15358 },
15359
15360 element: function(element, parentShape) {
15361 return importer.add(element, parentShape);
15362 },
15363
15364 error: function(message, context) {
15365 warnings.push({ message: message, context: context });
15366 }
15367 };
15368
15369 var walker = new BpmnTreeWalker(visitor, translate);
15370
15371 // traverse BPMN 2.0 document model,
15372 // starting at definitions
15373 walker.handleDefinitions(definitions, bpmnDiagram);
15374 }
15375
15376 return new Promise(function(resolve, reject) {
15377 try {
15378 importer = diagram.get('bpmnImporter');
15379 eventBus = diagram.get('eventBus');
15380 translate = diagram.get('translate');
15381
15382 eventBus.fire('import.render.start', { definitions: definitions });
15383
15384 render(definitions, bpmnDiagram);
15385
15386 eventBus.fire('import.render.complete', {
15387 error: error,
15388 warnings: warnings
15389 });
15390
15391 return resolve({ warnings: warnings });
15392 } catch (e) {
15393
15394 e.warnings = warnings;
15395 return reject(e);
15396 }
15397 });
15398 }
15399
15400 // TODO(nikku): remove with future bpmn-js version
15401
15402 /**
15403 * Wraps APIs to check:
15404 *
15405 * 1) If a callback is passed -> Warn users about callback deprecation.
15406 * 2) If Promise class is implemented in current environment.
15407 *
15408 * @private
15409 */
15410 function wrapForCompatibility(api) {
15411
15412 return function() {
15413
15414 if (!window.Promise) {
15415 throw new Error('Promises is not supported in this environment. Please polyfill Promise.');
15416 }
15417
15418 var argLen = arguments.length;
15419 if (argLen >= 1 && isFunction(arguments[argLen - 1])) {
15420
15421 var callback = arguments[argLen - 1];
15422
15423 console.warn(new Error(
15424 'Passing callbacks to ' + api.name + ' is deprecated and will be removed in a future major release. ' +
15425 'Please switch to promises: https://bpmn.io/l/moving-to-promises.html'
15426 ));
15427
15428 var argsWithoutCallback = Array.prototype.slice.call(arguments, 0, -1);
15429
15430 api.apply(this, argsWithoutCallback).then(function(result) {
15431
15432 var firstKey = Object.keys(result)[0];
15433
15434 // The APIs we are wrapping all resolve a single item depending on the API.
15435 // For instance, importXML resolves { warnings } and saveXML returns { xml }.
15436 // That's why we can call the callback with the first item of result.
15437 return callback(null, result[firstKey]);
15438
15439 // Passing a second paramter instead of catch because we don't want to
15440 // catch errors thrown by callback().
15441 }, function(err) {
15442
15443 return callback(err, err.warnings);
15444 });
15445 } else {
15446
15447 return api.apply(this, arguments);
15448 }
15449 };
15450 }
15451
15452 /**
15453 * This file must not be changed or exchanged.
15454 *
15455 * @see http://bpmn.io/license for more information.
15456 */
15457
15458
15459 // inlined ../../resources/logo.svg
15460 var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.02 5.57" width="53" height="21" style="vertical-align:middle"><path fill="currentColor" d="M1.88.92v.14c0 .41-.13.68-.4.8.33.14.46.44.46.86v.33c0 .61-.33.95-.95.95H0V0h.95c.65 0 .93.3.93.92zM.63.57v1.06h.24c.24 0 .38-.1.38-.43V.98c0-.28-.1-.4-.32-.4zm0 1.63v1.22h.36c.2 0 .32-.1.32-.39v-.35c0-.37-.12-.48-.4-.48H.63zM4.18.99v.52c0 .64-.31.98-.94.98h-.3V4h-.62V0h.92c.63 0 .94.35.94.99zM2.94.57v1.35h.3c.2 0 .3-.09.3-.37v-.6c0-.29-.1-.38-.3-.38h-.3zm2.89 2.27L6.25 0h.88v4h-.6V1.12L6.1 3.99h-.6l-.46-2.82v2.82h-.55V0h.87zM8.14 1.1V4h-.56V0h.79L9 2.4V0h.56v4h-.64zm2.49 2.29v.6h-.6v-.6zM12.12 1c0-.63.33-1 .95-1 .61 0 .95.37.95 1v2.04c0 .64-.34 1-.95 1-.62 0-.95-.37-.95-1zm.62 2.08c0 .28.13.39.33.39s.32-.1.32-.4V.98c0-.29-.12-.4-.32-.4s-.33.11-.33.4z"/><path fill="currentColor" d="M0 4.53h14.02v1.04H0zM11.08 0h.63v.62h-.63zm.63 4V1h-.63v2.98z"/></svg>';
15461
15462 var BPMNIO_IMG = BPMNIO_LOGO_SVG;
15463
15464 function css(attrs) {
15465 return attrs.join(';');
15466 }
15467
15468 var LINK_STYLES = css([
15469 'color: #404040'
15470 ]);
15471
15472 var LIGHTBOX_STYLES = css([
15473 'z-index: 1001',
15474 'position: fixed',
15475 'top: 0',
15476 'left: 0',
15477 'right: 0',
15478 'bottom: 0'
15479 ]);
15480
15481 var BACKDROP_STYLES = css([
15482 'width: 100%',
15483 'height: 100%',
15484 'background: rgba(40,40,40,0.2)'
15485 ]);
15486
15487 var NOTICE_STYLES = css([
15488 'position: absolute',
15489 'left: 50%',
15490 'top: 40%',
15491 'transform: translate(-50%)',
15492 'width: 260px',
15493 'padding: 10px',
15494 'background: white',
15495 'box-shadow: 0 1px 4px rgba(0,0,0,0.3)',
15496 'font-family: Helvetica, Arial, sans-serif',
15497 'font-size: 14px',
15498 'display: flex',
15499 'line-height: 1.3'
15500 ]);
15501
15502 var LIGHTBOX_MARKUP =
15503 '<div class="bjs-powered-by-lightbox" style="' + LIGHTBOX_STYLES + '">' +
15504 '<div class="backdrop" style="' + BACKDROP_STYLES + '"></div>' +
15505 '<div class="notice" style="' + NOTICE_STYLES + '">' +
15506 '<a href="https://bpmn.io" target="_blank" rel="noopener" style="margin: 15px 20px 15px 10px; align-self: center;' + LINK_STYLES + '">' +
15507 BPMNIO_IMG +
15508 '</a>' +
15509 '<span>' +
15510 'Web-based tooling for BPMN, DMN and CMMN diagrams ' +
15511 'powered by <a href="https://bpmn.io" target="_blank" rel="noopener">bpmn.io</a>.' +
15512 '</span>' +
15513 '</div>' +
15514 '</div>';
15515
15516
15517 var lightbox;
15518
15519 function open() {
15520
15521 if (!lightbox) {
15522 lightbox = domify(LIGHTBOX_MARKUP);
15523
15524 delegate.bind(lightbox, '.backdrop', 'click', function(event) {
15525 document.body.removeChild(lightbox);
15526 });
15527 }
15528
15529 document.body.appendChild(lightbox);
15530 }
15531
15532 /**
15533 * The code in the <project-logo></project-logo> area
15534 * must not be changed.
15535 *
15536 * @see http://bpmn.io/license for more information.
15537 */
15538
15539 /**
15540 * A base viewer for BPMN 2.0 diagrams.
15541 *
15542 * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
15543 * bundles that include actual features.
15544 *
15545 * @param {Object} [options] configuration options to pass to the viewer
15546 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
15547 * @param {string|number} [options.width] the width of the viewer
15548 * @param {string|number} [options.height] the height of the viewer
15549 * @param {Object} [options.moddleExtensions] extension packages to provide
15550 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
15551 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
15552 */
15553 function BaseViewer(options) {
15554
15555 options = assign({}, DEFAULT_OPTIONS, options);
15556
15557 this._moddle = this._createModdle(options);
15558
15559 this._container = this._createContainer(options);
15560
15561 /* <project-logo> */
15562
15563 addProjectLogo(this._container);
15564
15565 /* </project-logo> */
15566
15567 this._init(this._container, this._moddle, options);
15568 }
15569
15570 inherits_browser(BaseViewer, Diagram);
15571
15572 /**
15573 * The importXML result.
15574 *
15575 * @typedef {Object} ImportXMLResult
15576 *
15577 * @property {Array<string>} warnings
15578 */
15579
15580 /**
15581 * The importXML error.
15582 *
15583 * @typedef {Error} ImportXMLError
15584 *
15585 * @property {Array<string>} warnings
15586 */
15587
15588 /**
15589 * Parse and render a BPMN 2.0 diagram.
15590 *
15591 * Once finished the viewer reports back the result to the
15592 * provided callback function with (err, warnings).
15593 *
15594 * ## Life-Cycle Events
15595 *
15596 * During import the viewer will fire life-cycle events:
15597 *
15598 * * import.parse.start (about to read model from xml)
15599 * * import.parse.complete (model read; may have worked or not)
15600 * * import.render.start (graphical import start)
15601 * * import.render.complete (graphical import finished)
15602 * * import.done (everything done)
15603 *
15604 * You can use these events to hook into the life-cycle.
15605 *
15606 * @param {string} xml the BPMN 2.0 xml
15607 * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
15608 *
15609 * Returns {Promise<ImportXMLResult, ImportXMLError>}
15610 */
15611 BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) {
15612
15613 var self = this;
15614
15615 function ParseCompleteEvent(data) {
15616
15617 var event = self.get('eventBus').createEvent(data);
15618
15619 // TODO(nikku): remove with future bpmn-js version
15620 Object.defineProperty(event, 'context', {
15621 enumerable: true,
15622 get: function() {
15623
15624 console.warn(new Error(
15625 'import.parse.complete <context> is deprecated ' +
15626 'and will be removed in future library versions'
15627 ));
15628
15629 return {
15630 warnings: data.warnings,
15631 references: data.references,
15632 elementsById: data.elementsById
15633 };
15634 }
15635 });
15636
15637 return event;
15638 }
15639
15640 return new Promise(function(resolve, reject) {
15641
15642 // hook in pre-parse listeners +
15643 // allow xml manipulation
15644 xml = self._emit('import.parse.start', { xml: xml }) || xml;
15645
15646 self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) {
15647 var definitions = result.rootElement;
15648 var references = result.references;
15649 var parseWarnings = result.warnings;
15650 var elementsById = result.elementsById;
15651
15652 // hook in post parse listeners +
15653 // allow definitions manipulation
15654 definitions = self._emit('import.parse.complete', ParseCompleteEvent({
15655 error: null,
15656 definitions: definitions,
15657 elementsById: elementsById,
15658 references: references,
15659 warnings: parseWarnings
15660 })) || definitions;
15661
15662 self.importDefinitions(definitions, bpmnDiagram).then(function(result) {
15663 var allWarnings = [].concat(parseWarnings, result.warnings || []);
15664
15665 self._emit('import.done', { error: null, warnings: allWarnings });
15666
15667 return resolve({ warnings: allWarnings });
15668 }).catch(function(err) {
15669 var allWarnings = [].concat(parseWarnings, err.warnings || []);
15670
15671 self._emit('import.done', { error: err, warnings: allWarnings });
15672
15673 return reject(addWarningsToError(err, allWarnings));
15674 });
15675 }).catch(function(err) {
15676
15677 self._emit('import.parse.complete', {
15678 error: err
15679 });
15680
15681 err = checkValidationError(err);
15682
15683 self._emit('import.done', { error: err, warnings: err.warnings });
15684
15685 return reject(err);
15686 });
15687 });
15688 });
15689
15690 /**
15691 * The importDefinitions result.
15692 *
15693 * @typedef {Object} ImportDefinitionsResult
15694 *
15695 * @property {Array<string>} warnings
15696 */
15697
15698 /**
15699 * The importDefinitions error.
15700 *
15701 * @typedef {Error} ImportDefinitionsError
15702 *
15703 * @property {Array<string>} warnings
15704 */
15705
15706 /**
15707 * Import parsed definitions and render a BPMN 2.0 diagram.
15708 *
15709 * Once finished the viewer reports back the result to the
15710 * provided callback function with (err, warnings).
15711 *
15712 * ## Life-Cycle Events
15713 *
15714 * During import the viewer will fire life-cycle events:
15715 *
15716 * * import.render.start (graphical import start)
15717 * * import.render.complete (graphical import finished)
15718 *
15719 * You can use these events to hook into the life-cycle.
15720 *
15721 * @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
15722 * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
15723 *
15724 * Returns {Promise<ImportDefinitionsResult, ImportDefinitionsError>}
15725 */
15726 BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) {
15727
15728 var self = this;
15729
15730 return new Promise(function(resolve, reject) {
15731
15732 self._setDefinitions(definitions);
15733
15734 self.open(bpmnDiagram).then(function(result) {
15735
15736 var warnings = result.warnings;
15737
15738 return resolve({ warnings: warnings });
15739 }).catch(function(err) {
15740
15741 return reject(err);
15742 });
15743 });
15744 });
15745
15746 /**
15747 * The open result.
15748 *
15749 * @typedef {Object} OpenResult
15750 *
15751 * @property {Array<string>} warnings
15752 */
15753
15754 /**
15755 * The open error.
15756 *
15757 * @typedef {Error} OpenError
15758 *
15759 * @property {Array<string>} warnings
15760 */
15761
15762 /**
15763 * Open diagram of previously imported XML.
15764 *
15765 * Once finished the viewer reports back the result to the
15766 * provided callback function with (err, warnings).
15767 *
15768 * ## Life-Cycle Events
15769 *
15770 * During switch the viewer will fire life-cycle events:
15771 *
15772 * * import.render.start (graphical import start)
15773 * * import.render.complete (graphical import finished)
15774 *
15775 * You can use these events to hook into the life-cycle.
15776 *
15777 * @param {string|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
15778 *
15779 * Returns {Promise<OpenResult, OpenError>}
15780 */
15781 BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) {
15782
15783 var definitions = this._definitions;
15784 var bpmnDiagram = bpmnDiagramOrId;
15785
15786 var self = this;
15787
15788 return new Promise(function(resolve, reject) {
15789 if (!definitions) {
15790 var err1 = new Error('no XML imported');
15791
15792 return reject(addWarningsToError(err1, []));
15793 }
15794
15795 if (typeof bpmnDiagramOrId === 'string') {
15796 bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
15797
15798 if (!bpmnDiagram) {
15799 var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
15800
15801 return reject(addWarningsToError(err2, []));
15802 }
15803 }
15804
15805 // clear existing rendered diagram
15806 // catch synchronous exceptions during #clear()
15807 try {
15808 self.clear();
15809 } catch (error) {
15810
15811 return reject(addWarningsToError(error, []));
15812 }
15813
15814 // perform graphical import
15815 importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) {
15816
15817 var warnings = result.warnings;
15818
15819 return resolve({ warnings: warnings });
15820 }).catch(function(err) {
15821
15822 return reject(err);
15823 });
15824 });
15825 });
15826
15827 /**
15828 * The saveXML result.
15829 *
15830 * @typedef {Object} SaveXMLResult
15831 *
15832 * @property {string} xml
15833 */
15834
15835 /**
15836 * Export the currently displayed BPMN 2.0 diagram as
15837 * a BPMN 2.0 XML document.
15838 *
15839 * ## Life-Cycle Events
15840 *
15841 * During XML saving the viewer will fire life-cycle events:
15842 *
15843 * * saveXML.start (before serialization)
15844 * * saveXML.serialized (after xml generation)
15845 * * saveXML.done (everything done)
15846 *
15847 * You can use these events to hook into the life-cycle.
15848 *
15849 * @param {Object} [options] export options
15850 * @param {boolean} [options.format=false] output formatted XML
15851 * @param {boolean} [options.preamble=true] output preamble
15852 *
15853 * Returns {Promise<SaveXMLResult, Error>}
15854 */
15855 BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) {
15856
15857 options = options || {};
15858
15859 var self = this;
15860
15861 var definitions = this._definitions;
15862
15863 return new Promise(function(resolve) {
15864
15865 if (!definitions) {
15866 return resolve({
15867 error: new Error('no definitions loaded')
15868 });
15869 }
15870
15871 // allow to fiddle around with definitions
15872 definitions = self._emit('saveXML.start', {
15873 definitions: definitions
15874 }) || definitions;
15875
15876 self._moddle.toXML(definitions, options).then(function(result) {
15877
15878 var xml = result.xml;
15879
15880 xml = self._emit('saveXML.serialized', {
15881 xml: xml
15882 }) || xml;
15883
15884 return resolve({
15885 xml: xml
15886 });
15887 });
15888 }).catch(function(error) {
15889 return { error: error };
15890 }).then(function(result) {
15891
15892 self._emit('saveXML.done', result);
15893
15894 var error = result.error;
15895
15896 if (error) {
15897 return Promise.reject(error);
15898 }
15899
15900 return result;
15901 });
15902 });
15903
15904 /**
15905 * The saveSVG result.
15906 *
15907 * @typedef {Object} SaveSVGResult
15908 *
15909 * @property {string} svg
15910 */
15911
15912 /**
15913 * Export the currently displayed BPMN 2.0 diagram as
15914 * an SVG image.
15915 *
15916 * ## Life-Cycle Events
15917 *
15918 * During SVG saving the viewer will fire life-cycle events:
15919 *
15920 * * saveSVG.start (before serialization)
15921 * * saveSVG.done (everything done)
15922 *
15923 * You can use these events to hook into the life-cycle.
15924 *
15925 * @param {Object} [options]
15926 *
15927 * Returns {Promise<SaveSVGResult, Error>}
15928 */
15929 BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) {
15930
15931 var self = this;
15932
15933 return new Promise(function(resolve, reject) {
15934
15935 self._emit('saveSVG.start');
15936
15937 var svg, err;
15938
15939 try {
15940 var canvas = self.get('canvas');
15941
15942 var contentNode = canvas.getDefaultLayer(),
15943 defsNode = query('defs', canvas._svg);
15944
15945 var contents = innerSVG(contentNode),
15946 defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
15947
15948 var bbox = contentNode.getBBox();
15949
15950 svg =
15951 '<?xml version="1.0" encoding="utf-8"?>\n' +
15952 '<!-- created with bpmn-js / http://bpmn.io -->\n' +
15953 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
15954 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
15955 'width="' + bbox.width + '" height="' + bbox.height + '" ' +
15956 'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
15957 defs + contents +
15958 '</svg>';
15959 } catch (e) {
15960 err = e;
15961 }
15962
15963 self._emit('saveSVG.done', {
15964 error: err,
15965 svg: svg
15966 });
15967
15968 if (!err) {
15969 return resolve({ svg: svg });
15970 }
15971
15972 return reject(err);
15973 });
15974 });
15975
15976 /**
15977 * Get a named diagram service.
15978 *
15979 * @example
15980 *
15981 * var elementRegistry = viewer.get('elementRegistry');
15982 * var startEventShape = elementRegistry.get('StartEvent_1');
15983 *
15984 * @param {string} name
15985 *
15986 * @return {Object} diagram service instance
15987 *
15988 * @method BaseViewer#get
15989 */
15990
15991 /**
15992 * Invoke a function in the context of this viewer.
15993 *
15994 * @example
15995 *
15996 * viewer.invoke(function(elementRegistry) {
15997 * var startEventShape = elementRegistry.get('StartEvent_1');
15998 * });
15999 *
16000 * @param {Function} fn to be invoked
16001 *
16002 * @return {Object} the functions return value
16003 *
16004 * @method BaseViewer#invoke
16005 */
16006
16007
16008 BaseViewer.prototype._setDefinitions = function(definitions) {
16009 this._definitions = definitions;
16010 };
16011
16012 BaseViewer.prototype.getModules = function() {
16013 return this._modules;
16014 };
16015
16016 /**
16017 * Remove all drawn elements from the viewer.
16018 *
16019 * After calling this method the viewer can still
16020 * be reused for opening another diagram.
16021 *
16022 * @method BaseViewer#clear
16023 */
16024 BaseViewer.prototype.clear = function() {
16025 if (!this.getDefinitions()) {
16026
16027 // no diagram to clear
16028 return;
16029 }
16030
16031 // remove businessObject#di binding
16032 //
16033 // this is necessary, as we establish the bindings
16034 // in the BpmnTreeWalker (and assume none are given
16035 // on reimport)
16036 this.get('elementRegistry').forEach(function(element) {
16037 var bo = element.businessObject;
16038
16039 if (bo && bo.di) {
16040 delete bo.di;
16041 }
16042 });
16043
16044 // remove drawn elements
16045 Diagram.prototype.clear.call(this);
16046 };
16047
16048 /**
16049 * Destroy the viewer instance and remove all its
16050 * remainders from the document tree.
16051 */
16052 BaseViewer.prototype.destroy = function() {
16053
16054 // diagram destroy
16055 Diagram.prototype.destroy.call(this);
16056
16057 // dom detach
16058 remove(this._container);
16059 };
16060
16061 /**
16062 * Register an event listener
16063 *
16064 * Remove a previously added listener via {@link #off(event, callback)}.
16065 *
16066 * @param {string} event
16067 * @param {number} [priority]
16068 * @param {Function} callback
16069 * @param {Object} [that]
16070 */
16071 BaseViewer.prototype.on = function(event, priority, callback, target) {
16072 return this.get('eventBus').on(event, priority, callback, target);
16073 };
16074
16075 /**
16076 * De-register an event listener
16077 *
16078 * @param {string} event
16079 * @param {Function} callback
16080 */
16081 BaseViewer.prototype.off = function(event, callback) {
16082 this.get('eventBus').off(event, callback);
16083 };
16084
16085 BaseViewer.prototype.attachTo = function(parentNode) {
16086
16087 if (!parentNode) {
16088 throw new Error('parentNode required');
16089 }
16090
16091 // ensure we detach from the
16092 // previous, old parent
16093 this.detach();
16094
16095 // unwrap jQuery if provided
16096 if (parentNode.get && parentNode.constructor.prototype.jquery) {
16097 parentNode = parentNode.get(0);
16098 }
16099
16100 if (typeof parentNode === 'string') {
16101 parentNode = query(parentNode);
16102 }
16103
16104 parentNode.appendChild(this._container);
16105
16106 this._emit('attach', {});
16107
16108 this.get('canvas').resized();
16109 };
16110
16111 BaseViewer.prototype.getDefinitions = function() {
16112 return this._definitions;
16113 };
16114
16115 BaseViewer.prototype.detach = function() {
16116
16117 var container = this._container,
16118 parentNode = container.parentNode;
16119
16120 if (!parentNode) {
16121 return;
16122 }
16123
16124 this._emit('detach', {});
16125
16126 parentNode.removeChild(container);
16127 };
16128
16129 BaseViewer.prototype._init = function(container, moddle, options) {
16130
16131 var baseModules = options.modules || this.getModules(),
16132 additionalModules = options.additionalModules || [],
16133 staticModules = [
16134 {
16135 bpmnjs: [ 'value', this ],
16136 moddle: [ 'value', moddle ]
16137 }
16138 ];
16139
16140 var diagramModules = [].concat(staticModules, baseModules, additionalModules);
16141
16142 var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
16143 canvas: assign({}, options.canvas, { container: container }),
16144 modules: diagramModules
16145 });
16146
16147 // invoke diagram constructor
16148 Diagram.call(this, diagramOptions);
16149
16150 if (options && options.container) {
16151 this.attachTo(options.container);
16152 }
16153 };
16154
16155 /**
16156 * Emit an event on the underlying {@link EventBus}
16157 *
16158 * @param {string} type
16159 * @param {Object} event
16160 *
16161 * @return {Object} event processing result (if any)
16162 */
16163 BaseViewer.prototype._emit = function(type, event) {
16164 return this.get('eventBus').fire(type, event);
16165 };
16166
16167 BaseViewer.prototype._createContainer = function(options) {
16168
16169 var container = domify('<div class="bjs-container"></div>');
16170
16171 assign(container.style, {
16172 width: ensureUnit(options.width),
16173 height: ensureUnit(options.height),
16174 position: options.position
16175 });
16176
16177 return container;
16178 };
16179
16180 BaseViewer.prototype._createModdle = function(options) {
16181 var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
16182
16183 return new simple(moddleOptions);
16184 };
16185
16186 BaseViewer.prototype._modules = [];
16187
16188 // helpers ///////////////
16189
16190 function addWarningsToError(err, warningsAry) {
16191 err.warnings = warningsAry;
16192 return err;
16193 }
16194
16195 function checkValidationError(err) {
16196
16197 // check if we can help the user by indicating wrong BPMN 2.0 xml
16198 // (in case he or the exporting tool did not get that right)
16199
16200 var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
16201 var match = pattern.exec(err.message);
16202
16203 if (match) {
16204 err.message =
16205 'unparsable content <' + match[1] + '> detected; ' +
16206 'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
16207 }
16208
16209 return err;
16210 }
16211
16212 var DEFAULT_OPTIONS = {
16213 width: '100%',
16214 height: '100%',
16215 position: 'relative'
16216 };
16217
16218
16219 /**
16220 * Ensure the passed argument is a proper unit (defaulting to px)
16221 */
16222 function ensureUnit(val) {
16223 return val + (isNumber(val) ? 'px' : '');
16224 }
16225
16226
16227 /**
16228 * Find BPMNDiagram in definitions by ID
16229 *
16230 * @param {ModdleElement<Definitions>} definitions
16231 * @param {string} diagramId
16232 *
16233 * @return {ModdleElement<BPMNDiagram>|null}
16234 */
16235 function findBPMNDiagram(definitions, diagramId) {
16236 if (!diagramId) {
16237 return null;
16238 }
16239
16240 return find(definitions.diagrams, function(element) {
16241 return element.id === diagramId;
16242 }) || null;
16243 }
16244
16245 /**
16246 * Adds the project logo to the diagram container as
16247 * required by the bpmn.io license.
16248 *
16249 * @see http://bpmn.io/license
16250 *
16251 * @param {Element} container
16252 */
16253 function addProjectLogo(container) {
16254 var img = BPMNIO_IMG;
16255
16256 var linkMarkup =
16257 '<a href="http://bpmn.io" ' +
16258 'target="_blank" ' +
16259 'class="bjs-powered-by" ' +
16260 'title="Powered by bpmn.io" ' +
16261 'style="position: absolute; bottom: 15px; right: 15px; z-index: 100; ' + LINK_STYLES + '">' +
16262 img +
16263 '</a>';
16264
16265 var linkElement = domify(linkMarkup);
16266
16267 container.appendChild(linkElement);
16268
16269 componentEvent.bind(linkElement, 'click', function(event) {
16270 open();
16271
16272 event.preventDefault();
16273 });
16274 }
16275
16276 /* </project-logo> */
16277
16278 /**
16279 * A base modeler for BPMN 2.0 diagrams.
16280 *
16281 * Have a look at {@link Modeler} for a bundle that includes actual features.
16282 *
16283 * @param {Object} [options] configuration options to pass to the viewer
16284 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
16285 * @param {string|number} [options.width] the width of the viewer
16286 * @param {string|number} [options.height] the height of the viewer
16287 * @param {Object} [options.moddleExtensions] extension packages to provide
16288 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
16289 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
16290 */
16291 function BaseModeler(options) {
16292 BaseViewer.call(this, options);
16293
16294 // hook ID collection into the modeler
16295 this.on('import.parse.complete', function(event) {
16296 if (!event.error) {
16297 this._collectIds(event.definitions, event.elementsById);
16298 }
16299 }, this);
16300
16301 this.on('diagram.destroy', function() {
16302 this.get('moddle').ids.clear();
16303 }, this);
16304 }
16305
16306 inherits_browser(BaseModeler, BaseViewer);
16307
16308
16309 /**
16310 * Create a moddle instance, attaching ids to it.
16311 *
16312 * @param {Object} options
16313 */
16314 BaseModeler.prototype._createModdle = function(options) {
16315 var moddle = BaseViewer.prototype._createModdle.call(this, options);
16316
16317 // attach ids to moddle to be able to track
16318 // and validated ids in the BPMN 2.0 XML document
16319 // tree
16320 moddle.ids = new Ids([ 32, 36, 1 ]);
16321
16322 return moddle;
16323 };
16324
16325 /**
16326 * Collect ids processed during parsing of the
16327 * definitions object.
16328 *
16329 * @param {ModdleElement} definitions
16330 * @param {Context} context
16331 */
16332 BaseModeler.prototype._collectIds = function(definitions, elementsById) {
16333
16334 var moddle = definitions.$model,
16335 ids = moddle.ids,
16336 id;
16337
16338 // remove references from previous import
16339 ids.clear();
16340
16341 for (id in elementsById) {
16342 ids.claim(id, elementsById[id]);
16343 }
16344 };
16345
16346 /**
16347 * Is an element of the given BPMN type?
16348 *
16349 * @param {djs.model.Base|ModdleElement} element
16350 * @param {string} type
16351 *
16352 * @return {boolean}
16353 */
16354 function is$1(element, type) {
16355 var bo = getBusinessObject(element);
16356
16357 return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
16358 }
16359
16360
16361 /**
16362 * Return the business object for a given element.
16363 *
16364 * @param {djs.model.Base|ModdleElement} element
16365 *
16366 * @return {ModdleElement}
16367 */
16368 function getBusinessObject(element) {
16369 return (element && element.businessObject) || element;
16370 }
16371
16372 var ModelUtil = /*#__PURE__*/Object.freeze({
16373 __proto__: null,
16374 is: is$1,
16375 getBusinessObject: getBusinessObject
16376 });
16377
16378 function isExpanded(element) {
16379
16380 if (is$1(element, 'bpmn:CallActivity')) {
16381 return false;
16382 }
16383
16384 if (is$1(element, 'bpmn:SubProcess')) {
16385 return getBusinessObject(element).di && !!getBusinessObject(element).di.isExpanded;
16386 }
16387
16388 if (is$1(element, 'bpmn:Participant')) {
16389 return !!getBusinessObject(element).processRef;
16390 }
16391
16392 return true;
16393 }
16394
16395 function isInterrupting(element) {
16396 return element && getBusinessObject(element).isInterrupting !== false;
16397 }
16398
16399 function isEventSubProcess(element) {
16400 return element && !!getBusinessObject(element).triggeredByEvent;
16401 }
16402
16403 function hasEventDefinition(element, eventType) {
16404 var bo = getBusinessObject(element),
16405 hasEventDefinition = false;
16406
16407 if (bo.eventDefinitions) {
16408 forEach(bo.eventDefinitions, function(event) {
16409 if (is$1(event, eventType)) {
16410 hasEventDefinition = true;
16411 }
16412 });
16413 }
16414
16415 return hasEventDefinition;
16416 }
16417
16418 function hasErrorEventDefinition(element) {
16419 return hasEventDefinition(element, 'bpmn:ErrorEventDefinition');
16420 }
16421
16422 function hasEscalationEventDefinition(element) {
16423 return hasEventDefinition(element, 'bpmn:EscalationEventDefinition');
16424 }
16425
16426 function hasCompensateEventDefinition(element) {
16427 return hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
16428 }
16429
16430 function getLabelAttr(semantic) {
16431 if (
16432 is$1(semantic, 'bpmn:FlowElement') ||
16433 is$1(semantic, 'bpmn:Participant') ||
16434 is$1(semantic, 'bpmn:Lane') ||
16435 is$1(semantic, 'bpmn:SequenceFlow') ||
16436 is$1(semantic, 'bpmn:MessageFlow') ||
16437 is$1(semantic, 'bpmn:DataInput') ||
16438 is$1(semantic, 'bpmn:DataOutput')
16439 ) {
16440 return 'name';
16441 }
16442
16443 if (is$1(semantic, 'bpmn:TextAnnotation')) {
16444 return 'text';
16445 }
16446
16447 if (is$1(semantic, 'bpmn:Group')) {
16448 return 'categoryValueRef';
16449 }
16450 }
16451
16452 function getCategoryValue(semantic) {
16453 var categoryValueRef = semantic['categoryValueRef'];
16454
16455 if (!categoryValueRef) {
16456 return '';
16457 }
16458
16459
16460 return categoryValueRef.value || '';
16461 }
16462
16463 function getLabel(element) {
16464 var semantic = element.businessObject,
16465 attr = getLabelAttr(semantic);
16466
16467 if (attr) {
16468
16469 if (attr === 'categoryValueRef') {
16470
16471 return getCategoryValue(semantic);
16472 }
16473
16474 return semantic[attr] || '';
16475 }
16476 }
16477
16478
16479 function setLabel(element, text, isExternal) {
16480 var semantic = element.businessObject,
16481 attr = getLabelAttr(semantic);
16482
16483 if (attr) {
16484
16485 if (attr === 'categoryValueRef') {
16486 semantic['categoryValueRef'].value = text;
16487 } else {
16488 semantic[attr] = text;
16489 }
16490
16491 }
16492
16493 return element;
16494 }
16495
16496 // element utils //////////////////////
16497
16498 /**
16499 * Checks if eventDefinition of the given element matches with semantic type.
16500 *
16501 * @return {boolean} true if element is of the given semantic type
16502 */
16503 function isTypedEvent(event, eventDefinitionType, filter) {
16504
16505 function matches(definition, filter) {
16506 return every(filter, function(val, key) {
16507
16508 // we want a == conversion here, to be able to catch
16509 // undefined == false and friends
16510 /* jshint -W116 */
16511 return definition[key] == val;
16512 });
16513 }
16514
16515 return some(event.eventDefinitions, function(definition) {
16516 return definition.$type === eventDefinitionType && matches(event, filter);
16517 });
16518 }
16519
16520 function isThrowEvent(event) {
16521 return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
16522 }
16523
16524 function isCollection(element) {
16525 var dataObject = element.dataObjectRef;
16526
16527 return element.isCollection || (dataObject && dataObject.isCollection);
16528 }
16529
16530 function getDi(element) {
16531 return element.businessObject.di;
16532 }
16533
16534 function getSemantic(element) {
16535 return element.businessObject;
16536 }
16537
16538
16539 // color access //////////////////////
16540
16541 function getFillColor(element, defaultColor) {
16542 var di = getDi(element);
16543
16544 return di.get('color:background-color') || di.get('bioc:fill') || defaultColor || 'white';
16545 }
16546
16547 function getStrokeColor(element, defaultColor) {
16548 var di = getDi(element);
16549
16550 return di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || 'black';
16551 }
16552
16553 function getLabelColor(element, defaultColor, defaultStrokeColor) {
16554 var di = getDi(element),
16555 label = di.get('label');
16556
16557 return label && label.get('color:color') || defaultColor ||
16558 getStrokeColor(element, defaultStrokeColor);
16559 }
16560
16561 // cropping path customizations //////////////////////
16562
16563 function getCirclePath(shape) {
16564
16565 var cx = shape.x + shape.width / 2,
16566 cy = shape.y + shape.height / 2,
16567 radius = shape.width / 2;
16568
16569 var circlePath = [
16570 ['M', cx, cy],
16571 ['m', 0, -radius],
16572 ['a', radius, radius, 0, 1, 1, 0, 2 * radius],
16573 ['a', radius, radius, 0, 1, 1, 0, -2 * radius],
16574 ['z']
16575 ];
16576
16577 return componentsToPath(circlePath);
16578 }
16579
16580 function getRoundRectPath(shape, borderRadius) {
16581
16582 var x = shape.x,
16583 y = shape.y,
16584 width = shape.width,
16585 height = shape.height;
16586
16587 var roundRectPath = [
16588 ['M', x + borderRadius, y],
16589 ['l', width - borderRadius * 2, 0],
16590 ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius],
16591 ['l', 0, height - borderRadius * 2],
16592 ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius],
16593 ['l', borderRadius * 2 - width, 0],
16594 ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius],
16595 ['l', 0, borderRadius * 2 - height],
16596 ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius],
16597 ['z']
16598 ];
16599
16600 return componentsToPath(roundRectPath);
16601 }
16602
16603 function getDiamondPath(shape) {
16604
16605 var width = shape.width,
16606 height = shape.height,
16607 x = shape.x,
16608 y = shape.y,
16609 halfWidth = width / 2,
16610 halfHeight = height / 2;
16611
16612 var diamondPath = [
16613 ['M', x + halfWidth, y],
16614 ['l', halfWidth, halfHeight],
16615 ['l', -halfWidth, halfHeight],
16616 ['l', -halfWidth, -halfHeight],
16617 ['z']
16618 ];
16619
16620 return componentsToPath(diamondPath);
16621 }
16622
16623 function getRectPath(shape) {
16624 var x = shape.x,
16625 y = shape.y,
16626 width = shape.width,
16627 height = shape.height;
16628
16629 var rectPath = [
16630 ['M', x, y],
16631 ['l', width, 0],
16632 ['l', 0, height],
16633 ['l', -width, 0],
16634 ['z']
16635 ];
16636
16637 return componentsToPath(rectPath);
16638 }
16639
16640 var RENDERER_IDS = new Ids();
16641
16642 var TASK_BORDER_RADIUS = 10;
16643 var INNER_OUTER_DIST = 3;
16644
16645 var DEFAULT_FILL_OPACITY = .95,
16646 HIGH_FILL_OPACITY = .35;
16647
16648 var ELEMENT_LABEL_DISTANCE = 10;
16649
16650 function BpmnRenderer(
16651 config, eventBus, styles, pathMap,
16652 canvas, textRenderer, priority) {
16653
16654 BaseRenderer.call(this, eventBus, priority);
16655
16656 var defaultFillColor = config && config.defaultFillColor,
16657 defaultStrokeColor = config && config.defaultStrokeColor,
16658 defaultLabelColor = config && config.defaultLabelColor;
16659
16660 var rendererId = RENDERER_IDS.next();
16661
16662 var markers = {};
16663
16664 var computeStyle = styles.computeStyle;
16665
16666 function addMarker(id, options) {
16667 var attrs = assign({
16668 fill: 'black',
16669 strokeWidth: 1,
16670 strokeLinecap: 'round',
16671 strokeDasharray: 'none'
16672 }, options.attrs);
16673
16674 var ref = options.ref || { x: 0, y: 0 };
16675
16676 var scale = options.scale || 1;
16677
16678 // fix for safari / chrome / firefox bug not correctly
16679 // resetting stroke dash array
16680 if (attrs.strokeDasharray === 'none') {
16681 attrs.strokeDasharray = [10000, 1];
16682 }
16683
16684 var marker = create('marker');
16685
16686 attr$1(options.element, attrs);
16687
16688 append(marker, options.element);
16689
16690 attr$1(marker, {
16691 id: id,
16692 viewBox: '0 0 20 20',
16693 refX: ref.x,
16694 refY: ref.y,
16695 markerWidth: 20 * scale,
16696 markerHeight: 20 * scale,
16697 orient: 'auto'
16698 });
16699
16700 var defs = query('defs', canvas._svg);
16701
16702 if (!defs) {
16703 defs = create('defs');
16704
16705 append(canvas._svg, defs);
16706 }
16707
16708 append(defs, marker);
16709
16710 markers[id] = marker;
16711 }
16712
16713 function colorEscape(str) {
16714
16715 // only allow characters and numbers
16716 return str.replace(/[^0-9a-zA-z]+/g, '_');
16717 }
16718
16719 function marker(type, fill, stroke) {
16720 var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId;
16721
16722 if (!markers[id]) {
16723 createMarker(id, type, fill, stroke);
16724 }
16725
16726 return 'url(#' + id + ')';
16727 }
16728
16729 function createMarker(id, type, fill, stroke) {
16730
16731 if (type === 'sequenceflow-end') {
16732 var sequenceflowEnd = create('path');
16733 attr$1(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' });
16734
16735 addMarker(id, {
16736 element: sequenceflowEnd,
16737 ref: { x: 11, y: 10 },
16738 scale: 0.5,
16739 attrs: {
16740 fill: stroke,
16741 stroke: stroke
16742 }
16743 });
16744 }
16745
16746 if (type === 'messageflow-start') {
16747 var messageflowStart = create('circle');
16748 attr$1(messageflowStart, { cx: 6, cy: 6, r: 3.5 });
16749
16750 addMarker(id, {
16751 element: messageflowStart,
16752 attrs: {
16753 fill: fill,
16754 stroke: stroke
16755 },
16756 ref: { x: 6, y: 6 }
16757 });
16758 }
16759
16760 if (type === 'messageflow-end') {
16761 var messageflowEnd = create('path');
16762 attr$1(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' });
16763
16764 addMarker(id, {
16765 element: messageflowEnd,
16766 attrs: {
16767 fill: fill,
16768 stroke: stroke,
16769 strokeLinecap: 'butt'
16770 },
16771 ref: { x: 8.5, y: 5 }
16772 });
16773 }
16774
16775 if (type === 'association-start') {
16776 var associationStart = create('path');
16777 attr$1(associationStart, { d: 'M 11 5 L 1 10 L 11 15' });
16778
16779 addMarker(id, {
16780 element: associationStart,
16781 attrs: {
16782 fill: 'none',
16783 stroke: stroke,
16784 strokeWidth: 1.5
16785 },
16786 ref: { x: 1, y: 10 },
16787 scale: 0.5
16788 });
16789 }
16790
16791 if (type === 'association-end') {
16792 var associationEnd = create('path');
16793 attr$1(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' });
16794
16795 addMarker(id, {
16796 element: associationEnd,
16797 attrs: {
16798 fill: 'none',
16799 stroke: stroke,
16800 strokeWidth: 1.5
16801 },
16802 ref: { x: 12, y: 10 },
16803 scale: 0.5
16804 });
16805 }
16806
16807 if (type === 'conditional-flow-marker') {
16808 var conditionalflowMarker = create('path');
16809 attr$1(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' });
16810
16811 addMarker(id, {
16812 element: conditionalflowMarker,
16813 attrs: {
16814 fill: fill,
16815 stroke: stroke
16816 },
16817 ref: { x: -1, y: 10 },
16818 scale: 0.5
16819 });
16820 }
16821
16822 if (type === 'conditional-default-flow-marker') {
16823 var conditionaldefaultflowMarker = create('path');
16824 attr$1(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' });
16825
16826 addMarker(id, {
16827 element: conditionaldefaultflowMarker,
16828 attrs: {
16829 stroke: stroke
16830 },
16831 ref: { x: 0, y: 10 },
16832 scale: 0.5
16833 });
16834 }
16835 }
16836
16837 function drawCircle(parentGfx, width, height, offset, attrs) {
16838
16839 if (isObject(offset)) {
16840 attrs = offset;
16841 offset = 0;
16842 }
16843
16844 offset = offset || 0;
16845
16846 attrs = computeStyle(attrs, {
16847 stroke: 'black',
16848 strokeWidth: 2,
16849 fill: 'white'
16850 });
16851
16852 if (attrs.fill === 'none') {
16853 delete attrs.fillOpacity;
16854 }
16855
16856 var cx = width / 2,
16857 cy = height / 2;
16858
16859 var circle = create('circle');
16860 attr$1(circle, {
16861 cx: cx,
16862 cy: cy,
16863 r: Math.round((width + height) / 4 - offset)
16864 });
16865 attr$1(circle, attrs);
16866
16867 append(parentGfx, circle);
16868
16869 return circle;
16870 }
16871
16872 function drawRect(parentGfx, width, height, r, offset, attrs) {
16873
16874 if (isObject(offset)) {
16875 attrs = offset;
16876 offset = 0;
16877 }
16878
16879 offset = offset || 0;
16880
16881 attrs = computeStyle(attrs, {
16882 stroke: 'black',
16883 strokeWidth: 2,
16884 fill: 'white'
16885 });
16886
16887 var rect = create('rect');
16888 attr$1(rect, {
16889 x: offset,
16890 y: offset,
16891 width: width - offset * 2,
16892 height: height - offset * 2,
16893 rx: r,
16894 ry: r
16895 });
16896 attr$1(rect, attrs);
16897
16898 append(parentGfx, rect);
16899
16900 return rect;
16901 }
16902
16903 function drawDiamond(parentGfx, width, height, attrs) {
16904
16905 var x_2 = width / 2;
16906 var y_2 = height / 2;
16907
16908 var points = [{ x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 }];
16909
16910 var pointsString = points.map(function(point) {
16911 return point.x + ',' + point.y;
16912 }).join(' ');
16913
16914 attrs = computeStyle(attrs, {
16915 stroke: 'black',
16916 strokeWidth: 2,
16917 fill: 'white'
16918 });
16919
16920 var polygon = create('polygon');
16921 attr$1(polygon, {
16922 points: pointsString
16923 });
16924 attr$1(polygon, attrs);
16925
16926 append(parentGfx, polygon);
16927
16928 return polygon;
16929 }
16930
16931 function drawLine(parentGfx, waypoints, attrs) {
16932 attrs = computeStyle(attrs, [ 'no-fill' ], {
16933 stroke: 'black',
16934 strokeWidth: 2,
16935 fill: 'none'
16936 });
16937
16938 var line = createLine(waypoints, attrs);
16939
16940 append(parentGfx, line);
16941
16942 return line;
16943 }
16944
16945 function drawPath(parentGfx, d, attrs) {
16946
16947 attrs = computeStyle(attrs, [ 'no-fill' ], {
16948 strokeWidth: 2,
16949 stroke: 'black'
16950 });
16951
16952 var path = create('path');
16953 attr$1(path, { d: d });
16954 attr$1(path, attrs);
16955
16956 append(parentGfx, path);
16957
16958 return path;
16959 }
16960
16961 function drawMarker(type, parentGfx, path, attrs) {
16962 return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs));
16963 }
16964
16965 function as(type) {
16966 return function(parentGfx, element) {
16967 return handlers[type](parentGfx, element);
16968 };
16969 }
16970
16971 function renderer(type) {
16972 return handlers[type];
16973 }
16974
16975 function renderEventContent(element, parentGfx) {
16976
16977 var event = getSemantic(element);
16978 var isThrowing = isThrowEvent(event);
16979
16980 if (event.eventDefinitions && event.eventDefinitions.length>1) {
16981 if (event.parallelMultiple) {
16982 return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing);
16983 }
16984 else {
16985 return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing);
16986 }
16987 }
16988
16989 if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) {
16990 return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing);
16991 }
16992
16993 if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) {
16994 return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing);
16995 }
16996
16997 if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) {
16998 return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element);
16999 }
17000
17001 if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) {
17002 return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing);
17003 }
17004
17005 if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) {
17006 return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing);
17007 }
17008
17009 if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) {
17010 return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing);
17011 }
17012
17013 if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) {
17014 return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing);
17015 }
17016
17017 if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) {
17018 return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing);
17019 }
17020
17021 if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) {
17022 return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing);
17023 }
17024
17025 if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) {
17026 return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing);
17027 }
17028
17029 return null;
17030 }
17031
17032 function renderLabel(parentGfx, label, options) {
17033
17034 options = assign({
17035 size: {
17036 width: 100
17037 }
17038 }, options);
17039
17040 var text = textRenderer.createText(label || '', options);
17041
17042 classes$1(text).add('djs-label');
17043
17044 append(parentGfx, text);
17045
17046 return text;
17047 }
17048
17049 function renderEmbeddedLabel(parentGfx, element, align) {
17050 var semantic = getSemantic(element);
17051
17052 return renderLabel(parentGfx, semantic.name, {
17053 box: element,
17054 align: align,
17055 padding: 5,
17056 style: {
17057 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
17058 }
17059 });
17060 }
17061
17062 function renderExternalLabel(parentGfx, element) {
17063
17064 var box = {
17065 width: 90,
17066 height: 30,
17067 x: element.width / 2 + element.x,
17068 y: element.height / 2 + element.y
17069 };
17070
17071 return renderLabel(parentGfx, getLabel(element), {
17072 box: box,
17073 fitBox: true,
17074 style: assign(
17075 {},
17076 textRenderer.getExternalStyle(),
17077 {
17078 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
17079 }
17080 )
17081 });
17082 }
17083
17084 function renderLaneLabel(parentGfx, text, element) {
17085 var textBox = renderLabel(parentGfx, text, {
17086 box: {
17087 height: 30,
17088 width: element.height
17089 },
17090 align: 'center-middle',
17091 style: {
17092 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
17093 }
17094 });
17095
17096 var top = -1 * element.height;
17097
17098 transform$1(textBox, 0, -top, 270);
17099 }
17100
17101 function createPathFromConnection(connection) {
17102 var waypoints = connection.waypoints;
17103
17104 var pathData = 'm ' + waypoints[0].x + ',' + waypoints[0].y;
17105 for (var i = 1; i < waypoints.length; i++) {
17106 pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' ';
17107 }
17108 return pathData;
17109 }
17110
17111 var handlers = this.handlers = {
17112 'bpmn:Event': function(parentGfx, element, attrs) {
17113
17114 if (!('fillOpacity' in attrs)) {
17115 attrs.fillOpacity = DEFAULT_FILL_OPACITY;
17116 }
17117
17118 return drawCircle(parentGfx, element.width, element.height, attrs);
17119 },
17120 'bpmn:StartEvent': function(parentGfx, element) {
17121 var attrs = {
17122 fill: getFillColor(element, defaultFillColor),
17123 stroke: getStrokeColor(element, defaultStrokeColor)
17124 };
17125
17126 var semantic = getSemantic(element);
17127
17128 if (!semantic.isInterrupting) {
17129 attrs = {
17130 strokeDasharray: '6',
17131 strokeLinecap: 'round',
17132 fill: getFillColor(element, defaultFillColor),
17133 stroke: getStrokeColor(element, defaultStrokeColor)
17134 };
17135 }
17136
17137 var circle = renderer('bpmn:Event')(parentGfx, element, attrs);
17138
17139 renderEventContent(element, parentGfx);
17140
17141 return circle;
17142 },
17143 'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) {
17144 var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
17145 xScaleFactor: 0.9,
17146 yScaleFactor: 0.9,
17147 containerWidth: element.width,
17148 containerHeight: element.height,
17149 position: {
17150 mx: 0.235,
17151 my: 0.315
17152 }
17153 });
17154
17155 var fill = isThrowing ? getStrokeColor(element, defaultStrokeColor) : getFillColor(element, defaultFillColor);
17156 var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor(element, defaultStrokeColor);
17157
17158 var messagePath = drawPath(parentGfx, pathData, {
17159 strokeWidth: 1,
17160 fill: fill,
17161 stroke: stroke
17162 });
17163
17164 return messagePath;
17165 },
17166 'bpmn:TimerEventDefinition': function(parentGfx, element) {
17167 var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, {
17168 strokeWidth: 2,
17169 fill: getFillColor(element, defaultFillColor),
17170 stroke: getStrokeColor(element, defaultStrokeColor)
17171 });
17172
17173 var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
17174 xScaleFactor: 0.75,
17175 yScaleFactor: 0.75,
17176 containerWidth: element.width,
17177 containerHeight: element.height,
17178 position: {
17179 mx: 0.5,
17180 my: 0.5
17181 }
17182 });
17183
17184 drawPath(parentGfx, pathData, {
17185 strokeWidth: 2,
17186 strokeLinecap: 'square',
17187 stroke: getStrokeColor(element, defaultStrokeColor)
17188 });
17189
17190 for (var i = 0;i < 12; i++) {
17191
17192 var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
17193 xScaleFactor: 0.75,
17194 yScaleFactor: 0.75,
17195 containerWidth: element.width,
17196 containerHeight: element.height,
17197 position: {
17198 mx: 0.5,
17199 my: 0.5
17200 }
17201 });
17202
17203 var width = element.width / 2;
17204 var height = element.height / 2;
17205
17206 drawPath(parentGfx, linePathData, {
17207 strokeWidth: 1,
17208 strokeLinecap: 'square',
17209 transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')',
17210 stroke: getStrokeColor(element, defaultStrokeColor)
17211 });
17212 }
17213
17214 return circle;
17215 },
17216 'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) {
17217 var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
17218 xScaleFactor: 1,
17219 yScaleFactor: 1,
17220 containerWidth: event.width,
17221 containerHeight: event.height,
17222 position: {
17223 mx: 0.5,
17224 my: 0.2
17225 }
17226 });
17227
17228 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17229
17230 return drawPath(parentGfx, pathData, {
17231 strokeWidth: 1,
17232 fill: fill,
17233 stroke: getStrokeColor(event, defaultStrokeColor)
17234 });
17235 },
17236 'bpmn:ConditionalEventDefinition': function(parentGfx, event) {
17237 var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
17238 xScaleFactor: 1,
17239 yScaleFactor: 1,
17240 containerWidth: event.width,
17241 containerHeight: event.height,
17242 position: {
17243 mx: 0.5,
17244 my: 0.222
17245 }
17246 });
17247
17248 return drawPath(parentGfx, pathData, {
17249 strokeWidth: 1,
17250 stroke: getStrokeColor(event, defaultStrokeColor)
17251 });
17252 },
17253 'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) {
17254 var pathData = pathMap.getScaledPath('EVENT_LINK', {
17255 xScaleFactor: 1,
17256 yScaleFactor: 1,
17257 containerWidth: event.width,
17258 containerHeight: event.height,
17259 position: {
17260 mx: 0.57,
17261 my: 0.263
17262 }
17263 });
17264
17265 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17266
17267 return drawPath(parentGfx, pathData, {
17268 strokeWidth: 1,
17269 fill: fill,
17270 stroke: getStrokeColor(event, defaultStrokeColor)
17271 });
17272 },
17273 'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) {
17274 var pathData = pathMap.getScaledPath('EVENT_ERROR', {
17275 xScaleFactor: 1.1,
17276 yScaleFactor: 1.1,
17277 containerWidth: event.width,
17278 containerHeight: event.height,
17279 position: {
17280 mx: 0.2,
17281 my: 0.722
17282 }
17283 });
17284
17285 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17286
17287 return drawPath(parentGfx, pathData, {
17288 strokeWidth: 1,
17289 fill: fill,
17290 stroke: getStrokeColor(event, defaultStrokeColor)
17291 });
17292 },
17293 'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) {
17294 var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
17295 xScaleFactor: 1.0,
17296 yScaleFactor: 1.0,
17297 containerWidth: event.width,
17298 containerHeight: event.height,
17299 position: {
17300 mx: 0.638,
17301 my: -0.055
17302 }
17303 });
17304
17305 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17306
17307 var path = drawPath(parentGfx, pathData, {
17308 strokeWidth: 1,
17309 fill: fill,
17310 stroke: getStrokeColor(event, defaultStrokeColor)
17311 });
17312
17313 rotate(path, 45);
17314
17315 return path;
17316 },
17317 'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) {
17318 var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
17319 xScaleFactor: 1,
17320 yScaleFactor: 1,
17321 containerWidth: event.width,
17322 containerHeight: event.height,
17323 position: {
17324 mx: 0.22,
17325 my: 0.5
17326 }
17327 });
17328
17329 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17330
17331 return drawPath(parentGfx, pathData, {
17332 strokeWidth: 1,
17333 fill: fill,
17334 stroke: getStrokeColor(event, defaultStrokeColor)
17335 });
17336 },
17337 'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) {
17338 var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
17339 xScaleFactor: 0.9,
17340 yScaleFactor: 0.9,
17341 containerWidth: event.width,
17342 containerHeight: event.height,
17343 position: {
17344 mx: 0.5,
17345 my: 0.2
17346 }
17347 });
17348
17349 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17350
17351 return drawPath(parentGfx, pathData, {
17352 strokeWidth: 1,
17353 fill: fill,
17354 stroke: getStrokeColor(event, defaultStrokeColor)
17355 });
17356 },
17357 'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) {
17358 var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
17359 xScaleFactor: 1.1,
17360 yScaleFactor: 1.1,
17361 containerWidth: event.width,
17362 containerHeight: event.height,
17363 position: {
17364 mx: 0.222,
17365 my: 0.36
17366 }
17367 });
17368
17369 var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none';
17370
17371 return drawPath(parentGfx, pathData, {
17372 strokeWidth: 1,
17373 fill: fill
17374 });
17375 },
17376 'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) {
17377 var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
17378 xScaleFactor: 1.2,
17379 yScaleFactor: 1.2,
17380 containerWidth: event.width,
17381 containerHeight: event.height,
17382 position: {
17383 mx: 0.458,
17384 my: 0.194
17385 }
17386 });
17387
17388 return drawPath(parentGfx, pathData, {
17389 strokeWidth: 1,
17390 fill: getStrokeColor(event, defaultStrokeColor),
17391 stroke: getStrokeColor(event, defaultStrokeColor)
17392 });
17393 },
17394 'bpmn:EndEvent': function(parentGfx, element) {
17395 var circle = renderer('bpmn:Event')(parentGfx, element, {
17396 strokeWidth: 4,
17397 fill: getFillColor(element, defaultFillColor),
17398 stroke: getStrokeColor(element, defaultStrokeColor)
17399 });
17400
17401 renderEventContent(element, parentGfx);
17402
17403 return circle;
17404 },
17405 'bpmn:TerminateEventDefinition': function(parentGfx, element) {
17406 var circle = drawCircle(parentGfx, element.width, element.height, 8, {
17407 strokeWidth: 4,
17408 fill: getStrokeColor(element, defaultStrokeColor),
17409 stroke: getStrokeColor(element, defaultStrokeColor)
17410 });
17411
17412 return circle;
17413 },
17414 'bpmn:IntermediateEvent': function(parentGfx, element) {
17415 var outer = renderer('bpmn:Event')(parentGfx, element, {
17416 strokeWidth: 1,
17417 fill: getFillColor(element, defaultFillColor),
17418 stroke: getStrokeColor(element, defaultStrokeColor)
17419 });
17420
17421 /* inner */
17422 drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
17423 strokeWidth: 1,
17424 fill: getFillColor(element, 'none'),
17425 stroke: getStrokeColor(element, defaultStrokeColor)
17426 });
17427
17428 renderEventContent(element, parentGfx);
17429
17430 return outer;
17431 },
17432 'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
17433 'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),
17434
17435 'bpmn:Activity': function(parentGfx, element, attrs) {
17436
17437 attrs = attrs || {};
17438
17439 if (!('fillOpacity' in attrs)) {
17440 attrs.fillOpacity = DEFAULT_FILL_OPACITY;
17441 }
17442
17443 return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs);
17444 },
17445
17446 'bpmn:Task': function(parentGfx, element) {
17447 var attrs = {
17448 fill: getFillColor(element, defaultFillColor),
17449 stroke: getStrokeColor(element, defaultStrokeColor)
17450 };
17451
17452 var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);
17453
17454 renderEmbeddedLabel(parentGfx, element, 'center-middle');
17455 attachTaskMarkers(parentGfx, element);
17456
17457 return rect;
17458 },
17459 'bpmn:ServiceTask': function(parentGfx, element) {
17460 var task = renderer('bpmn:Task')(parentGfx, element);
17461
17462 var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
17463 abspos: {
17464 x: 12,
17465 y: 18
17466 }
17467 });
17468
17469 /* service bg */ drawPath(parentGfx, pathDataBG, {
17470 strokeWidth: 1,
17471 fill: getFillColor(element, defaultFillColor),
17472 stroke: getStrokeColor(element, defaultStrokeColor)
17473 });
17474
17475 var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', {
17476 abspos: {
17477 x: 17.2,
17478 y: 18
17479 }
17480 });
17481
17482 /* service fill */ drawPath(parentGfx, fillPathData, {
17483 strokeWidth: 0,
17484 fill: getFillColor(element, defaultFillColor)
17485 });
17486
17487 var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
17488 abspos: {
17489 x: 17,
17490 y: 22
17491 }
17492 });
17493
17494 /* service */ drawPath(parentGfx, pathData, {
17495 strokeWidth: 1,
17496 fill: getFillColor(element, defaultFillColor),
17497 stroke: getStrokeColor(element, defaultStrokeColor)
17498 });
17499
17500 return task;
17501 },
17502 'bpmn:UserTask': function(parentGfx, element) {
17503 var task = renderer('bpmn:Task')(parentGfx, element);
17504
17505 var x = 15;
17506 var y = 12;
17507
17508 var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
17509 abspos: {
17510 x: x,
17511 y: y
17512 }
17513 });
17514
17515 /* user path */ drawPath(parentGfx, pathData, {
17516 strokeWidth: 0.5,
17517 fill: getFillColor(element, defaultFillColor),
17518 stroke: getStrokeColor(element, defaultStrokeColor)
17519 });
17520
17521 var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
17522 abspos: {
17523 x: x,
17524 y: y
17525 }
17526 });
17527
17528 /* user2 path */ drawPath(parentGfx, pathData2, {
17529 strokeWidth: 0.5,
17530 fill: getFillColor(element, defaultFillColor),
17531 stroke: getStrokeColor(element, defaultStrokeColor)
17532 });
17533
17534 var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
17535 abspos: {
17536 x: x,
17537 y: y
17538 }
17539 });
17540
17541 /* user3 path */ drawPath(parentGfx, pathData3, {
17542 strokeWidth: 0.5,
17543 fill: getStrokeColor(element, defaultStrokeColor),
17544 stroke: getStrokeColor(element, defaultStrokeColor)
17545 });
17546
17547 return task;
17548 },
17549 'bpmn:ManualTask': function(parentGfx, element) {
17550 var task = renderer('bpmn:Task')(parentGfx, element);
17551
17552 var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
17553 abspos: {
17554 x: 17,
17555 y: 15
17556 }
17557 });
17558
17559 /* manual path */ drawPath(parentGfx, pathData, {
17560 strokeWidth: 0.5, // 0.25,
17561 fill: getFillColor(element, defaultFillColor),
17562 stroke: getStrokeColor(element, defaultStrokeColor)
17563 });
17564
17565 return task;
17566 },
17567 'bpmn:SendTask': function(parentGfx, element) {
17568 var task = renderer('bpmn:Task')(parentGfx, element);
17569
17570 var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
17571 xScaleFactor: 1,
17572 yScaleFactor: 1,
17573 containerWidth: 21,
17574 containerHeight: 14,
17575 position: {
17576 mx: 0.285,
17577 my: 0.357
17578 }
17579 });
17580
17581 /* send path */ drawPath(parentGfx, pathData, {
17582 strokeWidth: 1,
17583 fill: getStrokeColor(element, defaultStrokeColor),
17584 stroke: getFillColor(element, defaultFillColor)
17585 });
17586
17587 return task;
17588 },
17589 'bpmn:ReceiveTask' : function(parentGfx, element) {
17590 var semantic = getSemantic(element);
17591
17592 var task = renderer('bpmn:Task')(parentGfx, element);
17593 var pathData;
17594
17595 if (semantic.instantiate) {
17596 drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 });
17597
17598 pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
17599 abspos: {
17600 x: 7.77,
17601 y: 9.52
17602 }
17603 });
17604 } else {
17605
17606 pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
17607 xScaleFactor: 0.9,
17608 yScaleFactor: 0.9,
17609 containerWidth: 21,
17610 containerHeight: 14,
17611 position: {
17612 mx: 0.3,
17613 my: 0.4
17614 }
17615 });
17616 }
17617
17618 /* receive path */ drawPath(parentGfx, pathData, {
17619 strokeWidth: 1,
17620 fill: getFillColor(element, defaultFillColor),
17621 stroke: getStrokeColor(element, defaultStrokeColor)
17622 });
17623
17624 return task;
17625 },
17626 'bpmn:ScriptTask': function(parentGfx, element) {
17627 var task = renderer('bpmn:Task')(parentGfx, element);
17628
17629 var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
17630 abspos: {
17631 x: 15,
17632 y: 20
17633 }
17634 });
17635
17636 /* script path */ drawPath(parentGfx, pathData, {
17637 strokeWidth: 1,
17638 stroke: getStrokeColor(element, defaultStrokeColor)
17639 });
17640
17641 return task;
17642 },
17643 'bpmn:BusinessRuleTask': function(parentGfx, element) {
17644 var task = renderer('bpmn:Task')(parentGfx, element);
17645
17646 var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
17647 abspos: {
17648 x: 8,
17649 y: 8
17650 }
17651 });
17652
17653 var businessHeaderPath = drawPath(parentGfx, headerPathData);
17654 attr$1(businessHeaderPath, {
17655 strokeWidth: 1,
17656 fill: getFillColor(element, '#aaaaaa'),
17657 stroke: getStrokeColor(element, defaultStrokeColor)
17658 });
17659
17660 var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
17661 abspos: {
17662 x: 8,
17663 y: 8
17664 }
17665 });
17666
17667 var businessPath = drawPath(parentGfx, headerData);
17668 attr$1(businessPath, {
17669 strokeWidth: 1,
17670 stroke: getStrokeColor(element, defaultStrokeColor)
17671 });
17672
17673 return task;
17674 },
17675 'bpmn:SubProcess': function(parentGfx, element, attrs) {
17676 attrs = assign({
17677 fill: getFillColor(element, defaultFillColor),
17678 stroke: getStrokeColor(element, defaultStrokeColor)
17679 }, attrs);
17680
17681 var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);
17682
17683 var expanded = isExpanded(element);
17684
17685 if (isEventSubProcess(element)) {
17686 attr$1(rect, {
17687 strokeDasharray: '1,2'
17688 });
17689 }
17690
17691 renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle');
17692
17693 if (expanded) {
17694 attachTaskMarkers(parentGfx, element);
17695 } else {
17696 attachTaskMarkers(parentGfx, element, ['SubProcessMarker']);
17697 }
17698
17699 return rect;
17700 },
17701 'bpmn:AdHocSubProcess': function(parentGfx, element) {
17702 return renderer('bpmn:SubProcess')(parentGfx, element);
17703 },
17704 'bpmn:Transaction': function(parentGfx, element) {
17705 var outer = renderer('bpmn:SubProcess')(parentGfx, element);
17706
17707 var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
17708 stroke: getStrokeColor(element, defaultStrokeColor)
17709 });
17710
17711 /* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs);
17712
17713 return outer;
17714 },
17715 'bpmn:CallActivity': function(parentGfx, element) {
17716 return renderer('bpmn:SubProcess')(parentGfx, element, {
17717 strokeWidth: 5
17718 });
17719 },
17720 'bpmn:Participant': function(parentGfx, element) {
17721
17722 var attrs = {
17723 fillOpacity: DEFAULT_FILL_OPACITY,
17724 fill: getFillColor(element, defaultFillColor),
17725 stroke: getStrokeColor(element, defaultStrokeColor)
17726 };
17727
17728 var lane = renderer('bpmn:Lane')(parentGfx, element, attrs);
17729
17730 var expandedPool = isExpanded(element);
17731
17732 if (expandedPool) {
17733 drawLine(parentGfx, [
17734 { x: 30, y: 0 },
17735 { x: 30, y: element.height }
17736 ], {
17737 stroke: getStrokeColor(element, defaultStrokeColor)
17738 });
17739 var text = getSemantic(element).name;
17740 renderLaneLabel(parentGfx, text, element);
17741 } else {
17742
17743 // Collapsed pool draw text inline
17744 var text2 = getSemantic(element).name;
17745 renderLabel(parentGfx, text2, {
17746 box: element, align: 'center-middle',
17747 style: {
17748 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
17749 }
17750 });
17751 }
17752
17753 var participantMultiplicity = !!(getSemantic(element).participantMultiplicity);
17754
17755 if (participantMultiplicity) {
17756 renderer('ParticipantMultiplicityMarker')(parentGfx, element);
17757 }
17758
17759 return lane;
17760 },
17761 'bpmn:Lane': function(parentGfx, element, attrs) {
17762 var rect = drawRect(parentGfx, element.width, element.height, 0, assign({
17763 fill: getFillColor(element, defaultFillColor),
17764 fillOpacity: HIGH_FILL_OPACITY,
17765 stroke: getStrokeColor(element, defaultStrokeColor)
17766 }, attrs));
17767
17768 var semantic = getSemantic(element);
17769
17770 if (semantic.$type === 'bpmn:Lane') {
17771 var text = semantic.name;
17772 renderLaneLabel(parentGfx, text, element);
17773 }
17774
17775 return rect;
17776 },
17777 'bpmn:InclusiveGateway': function(parentGfx, element) {
17778 var diamond = renderer('bpmn:Gateway')(parentGfx, element);
17779
17780 /* circle path */
17781 drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
17782 strokeWidth: 2.5,
17783 fill: getFillColor(element, defaultFillColor),
17784 stroke: getStrokeColor(element, defaultStrokeColor)
17785 });
17786
17787 return diamond;
17788 },
17789 'bpmn:ExclusiveGateway': function(parentGfx, element) {
17790 var diamond = renderer('bpmn:Gateway')(parentGfx, element);
17791
17792 var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
17793 xScaleFactor: 0.4,
17794 yScaleFactor: 0.4,
17795 containerWidth: element.width,
17796 containerHeight: element.height,
17797 position: {
17798 mx: 0.32,
17799 my: 0.3
17800 }
17801 });
17802
17803 if ((getDi(element).isMarkerVisible)) {
17804 drawPath(parentGfx, pathData, {
17805 strokeWidth: 1,
17806 fill: getStrokeColor(element, defaultStrokeColor),
17807 stroke: getStrokeColor(element, defaultStrokeColor)
17808 });
17809 }
17810
17811 return diamond;
17812 },
17813 'bpmn:ComplexGateway': function(parentGfx, element) {
17814 var diamond = renderer('bpmn:Gateway')(parentGfx, element);
17815
17816 var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
17817 xScaleFactor: 0.5,
17818 yScaleFactor:0.5,
17819 containerWidth: element.width,
17820 containerHeight: element.height,
17821 position: {
17822 mx: 0.46,
17823 my: 0.26
17824 }
17825 });
17826
17827 /* complex path */ drawPath(parentGfx, pathData, {
17828 strokeWidth: 1,
17829 fill: getStrokeColor(element, defaultStrokeColor),
17830 stroke: getStrokeColor(element, defaultStrokeColor)
17831 });
17832
17833 return diamond;
17834 },
17835 'bpmn:ParallelGateway': function(parentGfx, element) {
17836 var diamond = renderer('bpmn:Gateway')(parentGfx, element);
17837
17838 var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
17839 xScaleFactor: 0.6,
17840 yScaleFactor:0.6,
17841 containerWidth: element.width,
17842 containerHeight: element.height,
17843 position: {
17844 mx: 0.46,
17845 my: 0.2
17846 }
17847 });
17848
17849 /* parallel path */ drawPath(parentGfx, pathData, {
17850 strokeWidth: 1,
17851 fill: getStrokeColor(element, defaultStrokeColor),
17852 stroke: getStrokeColor(element, defaultStrokeColor)
17853 });
17854
17855 return diamond;
17856 },
17857 'bpmn:EventBasedGateway': function(parentGfx, element) {
17858
17859 var semantic = getSemantic(element);
17860
17861 var diamond = renderer('bpmn:Gateway')(parentGfx, element);
17862
17863 /* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
17864 strokeWidth: 1,
17865 fill: 'none',
17866 stroke: getStrokeColor(element, defaultStrokeColor)
17867 });
17868
17869 var type = semantic.eventGatewayType;
17870 var instantiate = !!semantic.instantiate;
17871
17872 function drawEvent() {
17873
17874 var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
17875 xScaleFactor: 0.18,
17876 yScaleFactor: 0.18,
17877 containerWidth: element.width,
17878 containerHeight: element.height,
17879 position: {
17880 mx: 0.36,
17881 my: 0.44
17882 }
17883 });
17884
17885 var attrs = {
17886 strokeWidth: 2,
17887 fill: getFillColor(element, 'none'),
17888 stroke: getStrokeColor(element, defaultStrokeColor)
17889 };
17890
17891 /* event path */ drawPath(parentGfx, pathData, attrs);
17892 }
17893
17894 if (type === 'Parallel') {
17895
17896 var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
17897 xScaleFactor: 0.4,
17898 yScaleFactor:0.4,
17899 containerWidth: element.width,
17900 containerHeight: element.height,
17901 position: {
17902 mx: 0.474,
17903 my: 0.296
17904 }
17905 });
17906
17907 var parallelPath = drawPath(parentGfx, pathData);
17908 attr$1(parallelPath, {
17909 strokeWidth: 1,
17910 fill: 'none'
17911 });
17912 } else if (type === 'Exclusive') {
17913
17914 if (!instantiate) {
17915 var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26);
17916 attr$1(innerCircle, {
17917 strokeWidth: 1,
17918 fill: 'none',
17919 stroke: getStrokeColor(element, defaultStrokeColor)
17920 });
17921 }
17922
17923 drawEvent();
17924 }
17925
17926
17927 return diamond;
17928 },
17929 'bpmn:Gateway': function(parentGfx, element) {
17930 var attrs = {
17931 fill: getFillColor(element, defaultFillColor),
17932 fillOpacity: DEFAULT_FILL_OPACITY,
17933 stroke: getStrokeColor(element, defaultStrokeColor)
17934 };
17935
17936 return drawDiamond(parentGfx, element.width, element.height, attrs);
17937 },
17938 'bpmn:SequenceFlow': function(parentGfx, element) {
17939 var pathData = createPathFromConnection(element);
17940
17941 var fill = getFillColor(element, defaultFillColor),
17942 stroke = getStrokeColor(element, defaultStrokeColor);
17943
17944 var attrs = {
17945 strokeLinejoin: 'round',
17946 markerEnd: marker('sequenceflow-end', fill, stroke),
17947 stroke: getStrokeColor(element, defaultStrokeColor)
17948 };
17949
17950 var path = drawPath(parentGfx, pathData, attrs);
17951
17952 var sequenceFlow = getSemantic(element);
17953
17954 var source;
17955
17956 if (element.source) {
17957 source = element.source.businessObject;
17958
17959 // conditional flow marker
17960 if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) {
17961 attr$1(path, {
17962 markerStart: marker('conditional-flow-marker', fill, stroke)
17963 });
17964 }
17965
17966 // default marker
17967 if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) &&
17968 source.default === sequenceFlow) {
17969 attr$1(path, {
17970 markerStart: marker('conditional-default-flow-marker', fill, stroke)
17971 });
17972 }
17973 }
17974
17975 return path;
17976 },
17977 'bpmn:Association': function(parentGfx, element, attrs) {
17978
17979 var semantic = getSemantic(element);
17980
17981 var fill = getFillColor(element, defaultFillColor),
17982 stroke = getStrokeColor(element, defaultStrokeColor);
17983
17984 attrs = assign({
17985 strokeDasharray: '0.5, 5',
17986 strokeLinecap: 'round',
17987 strokeLinejoin: 'round',
17988 stroke: getStrokeColor(element, defaultStrokeColor)
17989 }, attrs || {});
17990
17991 if (semantic.associationDirection === 'One' ||
17992 semantic.associationDirection === 'Both') {
17993 attrs.markerEnd = marker('association-end', fill, stroke);
17994 }
17995
17996 if (semantic.associationDirection === 'Both') {
17997 attrs.markerStart = marker('association-start', fill, stroke);
17998 }
17999
18000 return drawLine(parentGfx, element.waypoints, attrs);
18001 },
18002 'bpmn:DataInputAssociation': function(parentGfx, element) {
18003 var fill = getFillColor(element, defaultFillColor),
18004 stroke = getStrokeColor(element, defaultStrokeColor);
18005
18006 return renderer('bpmn:Association')(parentGfx, element, {
18007 markerEnd: marker('association-end', fill, stroke)
18008 });
18009 },
18010 'bpmn:DataOutputAssociation': function(parentGfx, element) {
18011 var fill = getFillColor(element, defaultFillColor),
18012 stroke = getStrokeColor(element, defaultStrokeColor);
18013
18014 return renderer('bpmn:Association')(parentGfx, element, {
18015 markerEnd: marker('association-end', fill, stroke)
18016 });
18017 },
18018 'bpmn:MessageFlow': function(parentGfx, element) {
18019
18020 var semantic = getSemantic(element),
18021 di = getDi(element);
18022
18023 var fill = getFillColor(element, defaultFillColor),
18024 stroke = getStrokeColor(element, defaultStrokeColor);
18025
18026 var pathData = createPathFromConnection(element);
18027
18028 var attrs = {
18029 markerEnd: marker('messageflow-end', fill, stroke),
18030 markerStart: marker('messageflow-start', fill, stroke),
18031 strokeDasharray: '10, 12',
18032 strokeLinecap: 'round',
18033 strokeLinejoin: 'round',
18034 strokeWidth: '1.5px',
18035 stroke: getStrokeColor(element, defaultStrokeColor)
18036 };
18037
18038 var path = drawPath(parentGfx, pathData, attrs);
18039
18040 if (semantic.messageRef) {
18041 var midPoint = path.getPointAtLength(path.getTotalLength() / 2);
18042
18043 var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
18044 abspos: {
18045 x: midPoint.x,
18046 y: midPoint.y
18047 }
18048 });
18049
18050 var messageAttrs = { strokeWidth: 1 };
18051
18052 if (di.messageVisibleKind === 'initiating') {
18053 messageAttrs.fill = 'white';
18054 messageAttrs.stroke = 'black';
18055 } else {
18056 messageAttrs.fill = '#888';
18057 messageAttrs.stroke = 'white';
18058 }
18059
18060 var message = drawPath(parentGfx, markerPathData, messageAttrs);
18061
18062 var labelText = semantic.messageRef.name;
18063 var label = renderLabel(parentGfx, labelText, {
18064 align: 'center-top',
18065 fitBox: true,
18066 style: {
18067 fill: getStrokeColor(element, defaultLabelColor)
18068 }
18069 });
18070
18071 var messageBounds = message.getBBox(),
18072 labelBounds = label.getBBox();
18073
18074 var translateX = midPoint.x - labelBounds.width / 2,
18075 translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE;
18076
18077 transform$1(label, translateX, translateY, 0);
18078
18079 }
18080
18081 return path;
18082 },
18083 'bpmn:DataObject': function(parentGfx, element) {
18084 var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
18085 xScaleFactor: 1,
18086 yScaleFactor: 1,
18087 containerWidth: element.width,
18088 containerHeight: element.height,
18089 position: {
18090 mx: 0.474,
18091 my: 0.296
18092 }
18093 });
18094
18095 var elementObject = drawPath(parentGfx, pathData, {
18096 fill: getFillColor(element, defaultFillColor),
18097 fillOpacity: DEFAULT_FILL_OPACITY,
18098 stroke: getStrokeColor(element, defaultStrokeColor)
18099 });
18100
18101 var semantic = getSemantic(element);
18102
18103 if (isCollection(semantic)) {
18104 renderDataItemCollection(parentGfx, element);
18105 }
18106
18107 return elementObject;
18108 },
18109 'bpmn:DataObjectReference': as('bpmn:DataObject'),
18110 'bpmn:DataInput': function(parentGfx, element) {
18111
18112 var arrowPathData = pathMap.getRawPath('DATA_ARROW');
18113
18114 // page
18115 var elementObject = renderer('bpmn:DataObject')(parentGfx, element);
18116
18117 /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 });
18118
18119 return elementObject;
18120 },
18121 'bpmn:DataOutput': function(parentGfx, element) {
18122 var arrowPathData = pathMap.getRawPath('DATA_ARROW');
18123
18124 // page
18125 var elementObject = renderer('bpmn:DataObject')(parentGfx, element);
18126
18127 /* output arrow path */ drawPath(parentGfx, arrowPathData, {
18128 strokeWidth: 1,
18129 fill: 'black'
18130 });
18131
18132 return elementObject;
18133 },
18134 'bpmn:DataStoreReference': function(parentGfx, element) {
18135 var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', {
18136 xScaleFactor: 1,
18137 yScaleFactor: 1,
18138 containerWidth: element.width,
18139 containerHeight: element.height,
18140 position: {
18141 mx: 0,
18142 my: 0.133
18143 }
18144 });
18145
18146 var elementStore = drawPath(parentGfx, DATA_STORE_PATH, {
18147 strokeWidth: 2,
18148 fill: getFillColor(element, defaultFillColor),
18149 fillOpacity: DEFAULT_FILL_OPACITY,
18150 stroke: getStrokeColor(element, defaultStrokeColor)
18151 });
18152
18153 return elementStore;
18154 },
18155 'bpmn:BoundaryEvent': function(parentGfx, element) {
18156
18157 var semantic = getSemantic(element),
18158 cancel = semantic.cancelActivity;
18159
18160 var attrs = {
18161 strokeWidth: 1,
18162 fill: getFillColor(element, defaultFillColor),
18163 stroke: getStrokeColor(element, defaultStrokeColor)
18164 };
18165
18166 if (!cancel) {
18167 attrs.strokeDasharray = '6';
18168 attrs.strokeLinecap = 'round';
18169 }
18170
18171 // apply fillOpacity
18172 var outerAttrs = assign({}, attrs, {
18173 fillOpacity: 1
18174 });
18175
18176 // apply no-fill
18177 var innerAttrs = assign({}, attrs, {
18178 fill: 'none'
18179 });
18180
18181 var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs);
18182
18183 /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs);
18184
18185 renderEventContent(element, parentGfx);
18186
18187 return outer;
18188 },
18189 'bpmn:Group': function(parentGfx, element) {
18190
18191 var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
18192 stroke: getStrokeColor(element, defaultStrokeColor),
18193 strokeWidth: 1,
18194 strokeDasharray: '8,3,1,3',
18195 fill: 'none',
18196 pointerEvents: 'none'
18197 });
18198
18199 return group;
18200 },
18201 'label': function(parentGfx, element) {
18202 return renderExternalLabel(parentGfx, element);
18203 },
18204 'bpmn:TextAnnotation': function(parentGfx, element) {
18205 var style = {
18206 'fill': 'none',
18207 'stroke': 'none'
18208 };
18209
18210 var textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style);
18211
18212 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
18213 xScaleFactor: 1,
18214 yScaleFactor: 1,
18215 containerWidth: element.width,
18216 containerHeight: element.height,
18217 position: {
18218 mx: 0.0,
18219 my: 0.0
18220 }
18221 });
18222
18223 drawPath(parentGfx, textPathData, {
18224 stroke: getStrokeColor(element, defaultStrokeColor)
18225 });
18226
18227 var text = getSemantic(element).text || '';
18228 renderLabel(parentGfx, text, {
18229 box: element,
18230 align: 'left-top',
18231 padding: 5,
18232 style: {
18233 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
18234 }
18235 });
18236
18237 return textElement;
18238 },
18239 'ParticipantMultiplicityMarker': function(parentGfx, element) {
18240 var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
18241 xScaleFactor: 1,
18242 yScaleFactor: 1,
18243 containerWidth: element.width,
18244 containerHeight: element.height,
18245 position: {
18246 mx: ((element.width / 2) / element.width),
18247 my: (element.height - 15) / element.height
18248 }
18249 });
18250
18251 drawMarker('participant-multiplicity', parentGfx, markerPath, {
18252 strokeWidth: 2,
18253 fill: getFillColor(element, defaultFillColor),
18254 stroke: getStrokeColor(element, defaultStrokeColor)
18255 });
18256 },
18257 'SubProcessMarker': function(parentGfx, element) {
18258 var markerRect = drawRect(parentGfx, 14, 14, 0, {
18259 strokeWidth: 1,
18260 fill: getFillColor(element, defaultFillColor),
18261 stroke: getStrokeColor(element, defaultStrokeColor)
18262 });
18263
18264 // Process marker is placed in the middle of the box
18265 // therefore fixed values can be used here
18266 translate(markerRect, element.width / 2 - 7.5, element.height - 20);
18267
18268 var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
18269 xScaleFactor: 1.5,
18270 yScaleFactor: 1.5,
18271 containerWidth: element.width,
18272 containerHeight: element.height,
18273 position: {
18274 mx: (element.width / 2 - 7.5) / element.width,
18275 my: (element.height - 20) / element.height
18276 }
18277 });
18278
18279 drawMarker('sub-process', parentGfx, markerPath, {
18280 fill: getFillColor(element, defaultFillColor),
18281 stroke: getStrokeColor(element, defaultStrokeColor)
18282 });
18283 },
18284 'ParallelMarker': function(parentGfx, element, position) {
18285 var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
18286 xScaleFactor: 1,
18287 yScaleFactor: 1,
18288 containerWidth: element.width,
18289 containerHeight: element.height,
18290 position: {
18291 mx: ((element.width / 2 + position.parallel) / element.width),
18292 my: (element.height - 20) / element.height
18293 }
18294 });
18295
18296 drawMarker('parallel', parentGfx, markerPath, {
18297 fill: getFillColor(element, defaultFillColor),
18298 stroke: getStrokeColor(element, defaultStrokeColor)
18299 });
18300 },
18301 'SequentialMarker': function(parentGfx, element, position) {
18302 var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
18303 xScaleFactor: 1,
18304 yScaleFactor: 1,
18305 containerWidth: element.width,
18306 containerHeight: element.height,
18307 position: {
18308 mx: ((element.width / 2 + position.seq) / element.width),
18309 my: (element.height - 19) / element.height
18310 }
18311 });
18312
18313 drawMarker('sequential', parentGfx, markerPath, {
18314 fill: getFillColor(element, defaultFillColor),
18315 stroke: getStrokeColor(element, defaultStrokeColor)
18316 });
18317 },
18318 'CompensationMarker': function(parentGfx, element, position) {
18319 var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
18320 xScaleFactor: 1,
18321 yScaleFactor: 1,
18322 containerWidth: element.width,
18323 containerHeight: element.height,
18324 position: {
18325 mx: ((element.width / 2 + position.compensation) / element.width),
18326 my: (element.height - 13) / element.height
18327 }
18328 });
18329
18330 drawMarker('compensation', parentGfx, markerMath, {
18331 strokeWidth: 1,
18332 fill: getFillColor(element, defaultFillColor),
18333 stroke: getStrokeColor(element, defaultStrokeColor)
18334 });
18335 },
18336 'LoopMarker': function(parentGfx, element, position) {
18337 var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
18338 xScaleFactor: 1,
18339 yScaleFactor: 1,
18340 containerWidth: element.width,
18341 containerHeight: element.height,
18342 position: {
18343 mx: ((element.width / 2 + position.loop) / element.width),
18344 my: (element.height - 7) / element.height
18345 }
18346 });
18347
18348 drawMarker('loop', parentGfx, markerPath, {
18349 strokeWidth: 1,
18350 fill: getFillColor(element, defaultFillColor),
18351 stroke: getStrokeColor(element, defaultStrokeColor),
18352 strokeLinecap: 'round',
18353 strokeMiterlimit: 0.5
18354 });
18355 },
18356 'AdhocMarker': function(parentGfx, element, position) {
18357 var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
18358 xScaleFactor: 1,
18359 yScaleFactor: 1,
18360 containerWidth: element.width,
18361 containerHeight: element.height,
18362 position: {
18363 mx: ((element.width / 2 + position.adhoc) / element.width),
18364 my: (element.height - 15) / element.height
18365 }
18366 });
18367
18368 drawMarker('adhoc', parentGfx, markerPath, {
18369 strokeWidth: 1,
18370 fill: getStrokeColor(element, defaultStrokeColor),
18371 stroke: getStrokeColor(element, defaultStrokeColor)
18372 });
18373 }
18374 };
18375
18376 function attachTaskMarkers(parentGfx, element, taskMarkers) {
18377 var obj = getSemantic(element);
18378
18379 var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1;
18380 var position;
18381
18382 if (subprocess) {
18383 position = {
18384 seq: -21,
18385 parallel: -22,
18386 compensation: -42,
18387 loop: -18,
18388 adhoc: 10
18389 };
18390 } else {
18391 position = {
18392 seq: -3,
18393 parallel: -6,
18394 compensation: -27,
18395 loop: 0,
18396 adhoc: 10
18397 };
18398 }
18399
18400 forEach(taskMarkers, function(marker) {
18401 renderer(marker)(parentGfx, element, position);
18402 });
18403
18404 if (obj.isForCompensation) {
18405 renderer('CompensationMarker')(parentGfx, element, position);
18406 }
18407
18408 if (obj.$type === 'bpmn:AdHocSubProcess') {
18409 renderer('AdhocMarker')(parentGfx, element, position);
18410 }
18411
18412 var loopCharacteristics = obj.loopCharacteristics,
18413 isSequential = loopCharacteristics && loopCharacteristics.isSequential;
18414
18415 if (loopCharacteristics) {
18416
18417 if (isSequential === undefined) {
18418 renderer('LoopMarker')(parentGfx, element, position);
18419 }
18420
18421 if (isSequential === false) {
18422 renderer('ParallelMarker')(parentGfx, element, position);
18423 }
18424
18425 if (isSequential === true) {
18426 renderer('SequentialMarker')(parentGfx, element, position);
18427 }
18428 }
18429 }
18430
18431 function renderDataItemCollection(parentGfx, element) {
18432
18433 var yPosition = (element.height - 18) / element.height;
18434
18435 var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
18436 xScaleFactor: 1,
18437 yScaleFactor: 1,
18438 containerWidth: element.width,
18439 containerHeight: element.height,
18440 position: {
18441 mx: 0.33,
18442 my: yPosition
18443 }
18444 });
18445
18446 /* collection path */ drawPath(parentGfx, pathData, {
18447 strokeWidth: 2
18448 });
18449 }
18450
18451
18452 // extension API, use at your own risk
18453 this._drawPath = drawPath;
18454
18455 }
18456
18457
18458 inherits_browser(BpmnRenderer, BaseRenderer);
18459
18460 BpmnRenderer.$inject = [
18461 'config.bpmnRenderer',
18462 'eventBus',
18463 'styles',
18464 'pathMap',
18465 'canvas',
18466 'textRenderer'
18467 ];
18468
18469
18470 BpmnRenderer.prototype.canRender = function(element) {
18471 return is$1(element, 'bpmn:BaseElement');
18472 };
18473
18474 BpmnRenderer.prototype.drawShape = function(parentGfx, element) {
18475 var type = element.type;
18476 var h = this.handlers[type];
18477
18478 /* jshint -W040 */
18479 return h(parentGfx, element);
18480 };
18481
18482 BpmnRenderer.prototype.drawConnection = function(parentGfx, element) {
18483 var type = element.type;
18484 var h = this.handlers[type];
18485
18486 /* jshint -W040 */
18487 return h(parentGfx, element);
18488 };
18489
18490 BpmnRenderer.prototype.getShapePath = function(element) {
18491
18492 if (is$1(element, 'bpmn:Event')) {
18493 return getCirclePath(element);
18494 }
18495
18496 if (is$1(element, 'bpmn:Activity')) {
18497 return getRoundRectPath(element, TASK_BORDER_RADIUS);
18498 }
18499
18500 if (is$1(element, 'bpmn:Gateway')) {
18501 return getDiamondPath(element);
18502 }
18503
18504 return getRectPath(element);
18505 };
18506
18507 var DEFAULT_BOX_PADDING = 0;
18508
18509 var DEFAULT_LABEL_SIZE = {
18510 width: 150,
18511 height: 50
18512 };
18513
18514
18515 function parseAlign(align) {
18516
18517 var parts = align.split('-');
18518
18519 return {
18520 horizontal: parts[0] || 'center',
18521 vertical: parts[1] || 'top'
18522 };
18523 }
18524
18525 function parsePadding(padding) {
18526
18527 if (isObject(padding)) {
18528 return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding);
18529 } else {
18530 return {
18531 top: padding,
18532 left: padding,
18533 right: padding,
18534 bottom: padding
18535 };
18536 }
18537 }
18538
18539 function getTextBBox(text, fakeText) {
18540
18541 fakeText.textContent = text;
18542
18543 var textBBox;
18544
18545 try {
18546 var bbox,
18547 emptyLine = text === '';
18548
18549 // add dummy text, when line is empty to
18550 // determine correct height
18551 fakeText.textContent = emptyLine ? 'dummy' : text;
18552
18553 textBBox = fakeText.getBBox();
18554
18555 // take text rendering related horizontal
18556 // padding into account
18557 bbox = {
18558 width: textBBox.width + textBBox.x * 2,
18559 height: textBBox.height
18560 };
18561
18562 if (emptyLine) {
18563
18564 // correct width
18565 bbox.width = 0;
18566 }
18567
18568 return bbox;
18569 } catch (e) {
18570 return { width: 0, height: 0 };
18571 }
18572 }
18573
18574
18575 /**
18576 * Layout the next line and return the layouted element.
18577 *
18578 * Alters the lines passed.
18579 *
18580 * @param {Array<string>} lines
18581 * @return {Object} the line descriptor, an object { width, height, text }
18582 */
18583 function layoutNext(lines, maxWidth, fakeText) {
18584
18585 var originalLine = lines.shift(),
18586 fitLine = originalLine;
18587
18588 var textBBox;
18589
18590 for (;;) {
18591 textBBox = getTextBBox(fitLine, fakeText);
18592
18593 textBBox.width = fitLine ? textBBox.width : 0;
18594
18595 // try to fit
18596 if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) {
18597 return fit(lines, fitLine, originalLine, textBBox);
18598 }
18599
18600 fitLine = shortenLine(fitLine, textBBox.width, maxWidth);
18601 }
18602 }
18603
18604 function fit(lines, fitLine, originalLine, textBBox) {
18605 if (fitLine.length < originalLine.length) {
18606 var remainder = originalLine.slice(fitLine.length).trim();
18607
18608 lines.unshift(remainder);
18609 }
18610
18611 return {
18612 width: textBBox.width,
18613 height: textBBox.height,
18614 text: fitLine
18615 };
18616 }
18617
18618 var SOFT_BREAK = '\u00AD';
18619
18620
18621 /**
18622 * Shortens a line based on spacing and hyphens.
18623 * Returns the shortened result on success.
18624 *
18625 * @param {string} line
18626 * @param {number} maxLength the maximum characters of the string
18627 * @return {string} the shortened string
18628 */
18629 function semanticShorten(line, maxLength) {
18630
18631 var parts = line.split(/(\s|-|\u00AD)/g),
18632 part,
18633 shortenedParts = [],
18634 length = 0;
18635
18636 // try to shorten via break chars
18637 if (parts.length > 1) {
18638
18639 while ((part = parts.shift())) {
18640 if (part.length + length < maxLength) {
18641 shortenedParts.push(part);
18642 length += part.length;
18643 } else {
18644
18645 // remove previous part, too if hyphen does not fit anymore
18646 if (part === '-' || part === SOFT_BREAK) {
18647 shortenedParts.pop();
18648 }
18649
18650 break;
18651 }
18652 }
18653 }
18654
18655 var last = shortenedParts[shortenedParts.length - 1];
18656
18657 // translate trailing soft break to actual hyphen
18658 if (last && last === SOFT_BREAK) {
18659 shortenedParts[shortenedParts.length - 1] = '-';
18660 }
18661
18662 return shortenedParts.join('');
18663 }
18664
18665
18666 function shortenLine(line, width, maxWidth) {
18667 var length = Math.max(line.length * (maxWidth / width), 1);
18668
18669 // try to shorten semantically (i.e. based on spaces and hyphens)
18670 var shortenedLine = semanticShorten(line, length);
18671
18672 if (!shortenedLine) {
18673
18674 // force shorten by cutting the long word
18675 shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1));
18676 }
18677
18678 return shortenedLine;
18679 }
18680
18681
18682 function getHelperSvg() {
18683 var helperSvg = document.getElementById('helper-svg');
18684
18685 if (!helperSvg) {
18686 helperSvg = create('svg');
18687
18688 attr$1(helperSvg, {
18689 id: 'helper-svg',
18690 width: 0,
18691 height: 0,
18692 style: 'visibility: hidden; position: fixed'
18693 });
18694
18695 document.body.appendChild(helperSvg);
18696 }
18697
18698 return helperSvg;
18699 }
18700
18701
18702 /**
18703 * Creates a new label utility
18704 *
18705 * @param {Object} config
18706 * @param {Dimensions} config.size
18707 * @param {number} config.padding
18708 * @param {Object} config.style
18709 * @param {string} config.align
18710 */
18711 function Text(config) {
18712
18713 this._config = assign({}, {
18714 size: DEFAULT_LABEL_SIZE,
18715 padding: DEFAULT_BOX_PADDING,
18716 style: {},
18717 align: 'center-top'
18718 }, config || {});
18719 }
18720
18721 /**
18722 * Returns the layouted text as an SVG element.
18723 *
18724 * @param {string} text
18725 * @param {Object} options
18726 *
18727 * @return {SVGElement}
18728 */
18729 Text.prototype.createText = function(text, options) {
18730 return this.layoutText(text, options).element;
18731 };
18732
18733 /**
18734 * Returns a labels layouted dimensions.
18735 *
18736 * @param {string} text to layout
18737 * @param {Object} options
18738 *
18739 * @return {Dimensions}
18740 */
18741 Text.prototype.getDimensions = function(text, options) {
18742 return this.layoutText(text, options).dimensions;
18743 };
18744
18745 /**
18746 * Creates and returns a label and its bounding box.
18747 *
18748 * @method Text#createText
18749 *
18750 * @param {string} text the text to render on the label
18751 * @param {Object} options
18752 * @param {string} options.align how to align in the bounding box.
18753 * Any of { 'center-middle', 'center-top' },
18754 * defaults to 'center-top'.
18755 * @param {string} options.style style to be applied to the text
18756 * @param {boolean} options.fitBox indicates if box will be recalculated to
18757 * fit text
18758 *
18759 * @return {Object} { element, dimensions }
18760 */
18761 Text.prototype.layoutText = function(text, options) {
18762 var box = assign({}, this._config.size, options.box),
18763 style = assign({}, this._config.style, options.style),
18764 align = parseAlign(options.align || this._config.align),
18765 padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding),
18766 fitBox = options.fitBox || false;
18767
18768 var lineHeight = getLineHeight(style);
18769
18770 // we split text by lines and normalize
18771 // {soft break} + {line break} => { line break }
18772 var lines = text.split(/\u00AD?\r?\n/),
18773 layouted = [];
18774
18775 var maxWidth = box.width - padding.left - padding.right;
18776
18777 // ensure correct rendering by attaching helper text node to invisible SVG
18778 var helperText = create('text');
18779 attr$1(helperText, { x: 0, y: 0 });
18780 attr$1(helperText, style);
18781
18782 var helperSvg = getHelperSvg();
18783
18784 append(helperSvg, helperText);
18785
18786 while (lines.length) {
18787 layouted.push(layoutNext(lines, maxWidth, helperText));
18788 }
18789
18790 if (align.vertical === 'middle') {
18791 padding.top = padding.bottom = 0;
18792 }
18793
18794 var totalHeight = reduce(layouted, function(sum, line, idx) {
18795 return sum + (lineHeight || line.height);
18796 }, 0) + padding.top + padding.bottom;
18797
18798 var maxLineWidth = reduce(layouted, function(sum, line, idx) {
18799 return line.width > sum ? line.width : sum;
18800 }, 0);
18801
18802 // the y position of the next line
18803 var y = padding.top;
18804
18805 if (align.vertical === 'middle') {
18806 y += (box.height - totalHeight) / 2;
18807 }
18808
18809 // magic number initial offset
18810 y -= (lineHeight || layouted[0].height) / 4;
18811
18812
18813 var textElement = create('text');
18814
18815 attr$1(textElement, style);
18816
18817 // layout each line taking into account that parent
18818 // shape might resize to fit text size
18819 forEach(layouted, function(line) {
18820
18821 var x;
18822
18823 y += (lineHeight || line.height);
18824
18825 switch (align.horizontal) {
18826 case 'left':
18827 x = padding.left;
18828 break;
18829
18830 case 'right':
18831 x = ((fitBox ? maxLineWidth : maxWidth)
18832 - padding.right - line.width);
18833 break;
18834
18835 default:
18836
18837 // aka center
18838 x = Math.max((((fitBox ? maxLineWidth : maxWidth)
18839 - line.width) / 2 + padding.left), 0);
18840 }
18841
18842 var tspan = create('tspan');
18843 attr$1(tspan, { x: x, y: y });
18844
18845 tspan.textContent = line.text;
18846
18847 append(textElement, tspan);
18848 });
18849
18850 remove$1(helperText);
18851
18852 var dimensions = {
18853 width: maxLineWidth,
18854 height: totalHeight
18855 };
18856
18857 return {
18858 dimensions: dimensions,
18859 element: textElement
18860 };
18861 };
18862
18863
18864 function getLineHeight(style) {
18865 if ('fontSize' in style && 'lineHeight' in style) {
18866 return style.lineHeight * parseInt(style.fontSize, 10);
18867 }
18868 }
18869
18870 var DEFAULT_FONT_SIZE = 12;
18871 var LINE_HEIGHT_RATIO = 1.2;
18872
18873 var MIN_TEXT_ANNOTATION_HEIGHT = 30;
18874
18875
18876 function TextRenderer(config) {
18877
18878 var defaultStyle = assign({
18879 fontFamily: 'Arial, sans-serif',
18880 fontSize: DEFAULT_FONT_SIZE,
18881 fontWeight: 'normal',
18882 lineHeight: LINE_HEIGHT_RATIO
18883 }, config && config.defaultStyle || {});
18884
18885 var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;
18886
18887 var externalStyle = assign({}, defaultStyle, {
18888 fontSize: fontSize
18889 }, config && config.externalStyle || {});
18890
18891 var textUtil = new Text({
18892 style: defaultStyle
18893 });
18894
18895 /**
18896 * Get the new bounds of an externally rendered,
18897 * layouted label.
18898 *
18899 * @param {Bounds} bounds
18900 * @param {string} text
18901 *
18902 * @return {Bounds}
18903 */
18904 this.getExternalLabelBounds = function(bounds, text) {
18905
18906 var layoutedDimensions = textUtil.getDimensions(text, {
18907 box: {
18908 width: 90,
18909 height: 30,
18910 x: bounds.width / 2 + bounds.x,
18911 y: bounds.height / 2 + bounds.y
18912 },
18913 style: externalStyle
18914 });
18915
18916 // resize label shape to fit label text
18917 return {
18918 x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
18919 y: Math.round(bounds.y),
18920 width: Math.ceil(layoutedDimensions.width),
18921 height: Math.ceil(layoutedDimensions.height)
18922 };
18923
18924 };
18925
18926 /**
18927 * Get the new bounds of text annotation.
18928 *
18929 * @param {Bounds} bounds
18930 * @param {string} text
18931 *
18932 * @return {Bounds}
18933 */
18934 this.getTextAnnotationBounds = function(bounds, text) {
18935
18936 var layoutedDimensions = textUtil.getDimensions(text, {
18937 box: bounds,
18938 style: defaultStyle,
18939 align: 'left-top',
18940 padding: 5
18941 });
18942
18943 return {
18944 x: bounds.x,
18945 y: bounds.y,
18946 width: bounds.width,
18947 height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
18948 };
18949 };
18950
18951 /**
18952 * Create a layouted text element.
18953 *
18954 * @param {string} text
18955 * @param {Object} [options]
18956 *
18957 * @return {SVGElement} rendered text
18958 */
18959 this.createText = function(text, options) {
18960 return textUtil.createText(text, options || {});
18961 };
18962
18963 /**
18964 * Get default text style.
18965 */
18966 this.getDefaultStyle = function() {
18967 return defaultStyle;
18968 };
18969
18970 /**
18971 * Get the external text style.
18972 */
18973 this.getExternalStyle = function() {
18974 return externalStyle;
18975 };
18976
18977 }
18978
18979 TextRenderer.$inject = [
18980 'config.textRenderer'
18981 ];
18982
18983 /**
18984 * Map containing SVG paths needed by BpmnRenderer.
18985 */
18986
18987 function PathMap() {
18988
18989 /**
18990 * Contains a map of path elements
18991 *
18992 * <h1>Path definition</h1>
18993 * A parameterized path is defined like this:
18994 * <pre>
18995 * 'GATEWAY_PARALLEL': {
18996 * d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
18997 '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
18998 * height: 17.5,
18999 * width: 17.5,
19000 * heightElements: [2.5, 7.5],
19001 * widthElements: [2.5, 7.5]
19002 * }
19003 * </pre>
19004 * <p>It's important to specify a correct <b>height and width</b> for the path as the scaling
19005 * is based on the ratio between the specified height and width in this object and the
19006 * height and width that is set as scale target (Note x,y coordinates will be scaled with
19007 * individual ratios).</p>
19008 * <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled.
19009 * The scaling is based on the computed ratios.
19010 * Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using
19011 * the computed ratio coefficient.
19012 * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
19013 * <ul>
19014 * <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
19015 * <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
19016 * </ul>
19017 * The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
19018 * </p>
19019 */
19020 this.pathMap = {
19021 'EVENT_MESSAGE': {
19022 d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
19023 height: 36,
19024 width: 36,
19025 heightElements: [6, 14],
19026 widthElements: [10.5, 21]
19027 },
19028 'EVENT_SIGNAL': {
19029 d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
19030 height: 36,
19031 width: 36,
19032 heightElements: [18],
19033 widthElements: [10, 20]
19034 },
19035 'EVENT_ESCALATION': {
19036 d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
19037 height: 36,
19038 width: 36,
19039 heightElements: [20, 7],
19040 widthElements: [8]
19041 },
19042 'EVENT_CONDITIONAL': {
19043 d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
19044 'M {e.x2},{e.y3} l {e.x0},0 ' +
19045 'M {e.x2},{e.y4} l {e.x0},0 ' +
19046 'M {e.x2},{e.y5} l {e.x0},0 ' +
19047 'M {e.x2},{e.y6} l {e.x0},0 ' +
19048 'M {e.x2},{e.y7} l {e.x0},0 ' +
19049 'M {e.x2},{e.y8} l {e.x0},0 ',
19050 height: 36,
19051 width: 36,
19052 heightElements: [8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5],
19053 widthElements: [10.5, 14.5, 12.5]
19054 },
19055 'EVENT_LINK': {
19056 d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z',
19057 height: 36,
19058 width: 36,
19059 heightElements: [4.4375, 6.75, 7.8125],
19060 widthElements: [9.84375, 13.5]
19061 },
19062 'EVENT_ERROR': {
19063 d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z',
19064 height: 36,
19065 width: 36,
19066 heightElements: [0.023, 8.737, 8.151, 16.564, 10.591, 8.714],
19067 widthElements: [0.085, 6.672, 6.97, 4.273, 5.337, 6.636]
19068 },
19069 'EVENT_CANCEL_45': {
19070 d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
19071 '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
19072 height: 36,
19073 width: 36,
19074 heightElements: [4.75, 8.5],
19075 widthElements: [4.75, 8.5]
19076 },
19077 'EVENT_COMPENSATION': {
19078 d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z',
19079 height: 36,
19080 width: 36,
19081 heightElements: [6.5, 13, 0.4, 6.1],
19082 widthElements: [9, 9.3, 8.7]
19083 },
19084 'EVENT_TIMER_WH': {
19085 d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
19086 height: 36,
19087 width: 36,
19088 heightElements: [10, 2],
19089 widthElements: [3, 7]
19090 },
19091 'EVENT_TIMER_LINE': {
19092 d: 'M {mx},{my} ' +
19093 'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
19094 height: 36,
19095 width: 36,
19096 heightElements: [10, 3],
19097 widthElements: [0, 0]
19098 },
19099 'EVENT_MULTIPLE': {
19100 d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
19101 height: 36,
19102 width: 36,
19103 heightElements: [6.28099, 12.56199],
19104 widthElements: [3.1405, 9.42149, 12.56198]
19105 },
19106 'EVENT_PARALLEL_MULTIPLE': {
19107 d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
19108 '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
19109 height: 36,
19110 width: 36,
19111 heightElements: [2.56228, 7.68683],
19112 widthElements: [2.56228, 7.68683]
19113 },
19114 'GATEWAY_EXCLUSIVE': {
19115 d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
19116 '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
19117 '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
19118 height: 17.5,
19119 width: 17.5,
19120 heightElements: [8.5, 6.5312, -6.5312, -8.5],
19121 widthElements: [6.5, -6.5, 3, -3, 5, -5]
19122 },
19123 'GATEWAY_PARALLEL': {
19124 d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
19125 '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
19126 height: 30,
19127 width: 30,
19128 heightElements: [5, 12.5],
19129 widthElements: [5, 12.5]
19130 },
19131 'GATEWAY_EVENT_BASED': {
19132 d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
19133 height: 11,
19134 width: 11,
19135 heightElements: [-6, 6, 12, -12],
19136 widthElements: [9, -3, -12]
19137 },
19138 'GATEWAY_COMPLEX': {
19139 d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' +
19140 '{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' +
19141 '{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' +
19142 '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
19143 height: 17.125,
19144 width: 17.125,
19145 heightElements: [4.875, 3.4375, 2.125, 3],
19146 widthElements: [3.4375, 2.125, 4.875, 3]
19147 },
19148 'DATA_OBJECT_PATH': {
19149 d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0',
19150 height: 61,
19151 width: 51,
19152 heightElements: [10, 50, 60],
19153 widthElements: [10, 40, 50, 60]
19154 },
19155 'DATA_OBJECT_COLLECTION_PATH': {
19156 d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
19157 height: 10,
19158 width: 10,
19159 heightElements: [],
19160 widthElements: []
19161 },
19162 'DATA_ARROW': {
19163 d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
19164 height: 61,
19165 width: 51,
19166 heightElements: [],
19167 widthElements: []
19168 },
19169 'DATA_STORE': {
19170 d:'m {mx},{my} ' +
19171 'l 0,{e.y2} ' +
19172 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
19173 'l 0,-{e.y2} ' +
19174 'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
19175 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
19176 'm -{e.x2},{e.y0}' +
19177 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
19178 'm -{e.x2},{e.y0}' +
19179 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0',
19180 height: 61,
19181 width: 61,
19182 heightElements: [7, 10, 45],
19183 widthElements: [2, 58, 60]
19184 },
19185 'TEXT_ANNOTATION': {
19186 d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
19187 height: 30,
19188 width: 10,
19189 heightElements: [30],
19190 widthElements: [10]
19191 },
19192 'MARKER_SUB_PROCESS': {
19193 d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
19194 height: 10,
19195 width: 10,
19196 heightElements: [],
19197 widthElements: []
19198 },
19199 'MARKER_PARALLEL': {
19200 d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
19201 height: 10,
19202 width: 10,
19203 heightElements: [],
19204 widthElements: []
19205 },
19206 'MARKER_SEQUENTIAL': {
19207 d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
19208 height: 10,
19209 width: 10,
19210 heightElements: [],
19211 widthElements: []
19212 },
19213 'MARKER_COMPENSATION': {
19214 d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z',
19215 height: 10,
19216 width: 21,
19217 heightElements: [],
19218 widthElements: []
19219 },
19220 'MARKER_LOOP': {
19221 d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
19222 '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
19223 '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
19224 'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
19225 height: 13.9,
19226 width: 13.7,
19227 heightElements: [],
19228 widthElements: []
19229 },
19230 'MARKER_ADHOC': {
19231 d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' +
19232 '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
19233 '1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' +
19234 '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
19235 '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
19236 height: 4,
19237 width: 15,
19238 heightElements: [],
19239 widthElements: []
19240 },
19241 'TASK_TYPE_SEND': {
19242 d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
19243 height: 14,
19244 width: 21,
19245 heightElements: [6, 14],
19246 widthElements: [10.5, 21]
19247 },
19248 'TASK_TYPE_SCRIPT': {
19249 d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
19250 'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
19251 'm -7,-12 l 5,0 ' +
19252 'm -4.5,3 l 4.5,0 ' +
19253 'm -3,3 l 5,0' +
19254 'm -4,3 l 5,0',
19255 height: 15,
19256 width: 12.6,
19257 heightElements: [6, 14],
19258 widthElements: [10.5, 21]
19259 },
19260 'TASK_TYPE_USER_1': {
19261 d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
19262 '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
19263 '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
19264 'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
19265 'm -8,6 l 0,5.5 m 11,0 l 0,-5'
19266 },
19267 'TASK_TYPE_USER_2': {
19268 d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
19269 '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
19270 },
19271 'TASK_TYPE_USER_3': {
19272 d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' +
19273 '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
19274 '-4.20799998,3.36699999 -4.20699998,4.34799999 z'
19275 },
19276 'TASK_TYPE_MANUAL': {
19277 d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' +
19278 '-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' +
19279 '0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' +
19280 '-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' +
19281 '0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' +
19282 '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
19283 '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
19284 '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
19285 '-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' +
19286 '-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' +
19287 '0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' +
19288 '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
19289 },
19290 'TASK_TYPE_INSTANTIATING_SEND': {
19291 d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6'
19292 },
19293 'TASK_TYPE_SERVICE': {
19294 d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
19295 '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
19296 '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
19297 'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
19298 '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
19299 '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
19300 'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' +
19301 '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
19302 'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
19303 'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
19304 '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
19305 'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
19306 'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
19307 '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
19308 '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
19309 },
19310 'TASK_TYPE_SERVICE_FILL': {
19311 d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
19312 '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
19313 '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
19314 },
19315 'TASK_TYPE_BUSINESS_RULE_HEADER': {
19316 d: 'm {mx},{my} 0,4 20,0 0,-4 z'
19317 },
19318 'TASK_TYPE_BUSINESS_RULE_MAIN': {
19319 d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
19320 'm 0,8 l 20,0 ' +
19321 'm -13,-4 l 0,8'
19322 },
19323 'MESSAGE_FLOW_MARKER': {
19324 d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6'
19325 }
19326 };
19327
19328 this.getRawPath = function getRawPath(pathId) {
19329 return this.pathMap[pathId].d;
19330 };
19331
19332 /**
19333 * Scales the path to the given height and width.
19334 * <h1>Use case</h1>
19335 * <p>Use case is to scale the content of elements (event, gateways) based
19336 * on the element bounding box's size.
19337 * </p>
19338 * <h1>Why not transform</h1>
19339 * <p>Scaling a path with transform() will also scale the stroke and IE does not support
19340 * the option 'non-scaling-stroke' to prevent this.
19341 * Also there are use cases where only some parts of a path should be
19342 * scaled.</p>
19343 *
19344 * @param {string} pathId The ID of the path.
19345 * @param {Object} param <p>
19346 * Example param object scales the path to 60% size of the container (data.width, data.height).
19347 * <pre>
19348 * {
19349 * xScaleFactor: 0.6,
19350 * yScaleFactor:0.6,
19351 * containerWidth: data.width,
19352 * containerHeight: data.height,
19353 * position: {
19354 * mx: 0.46,
19355 * my: 0.2,
19356 * }
19357 * }
19358 * </pre>
19359 * <ul>
19360 * <li>targetpathwidth = xScaleFactor * containerWidth</li>
19361 * <li>targetpathheight = yScaleFactor * containerHeight</li>
19362 * <li>Position is used to set the starting coordinate of the path. M is computed:
19363 * <ul>
19364 * <li>position.x * containerWidth</li>
19365 * <li>position.y * containerHeight</li>
19366 * </ul>
19367 * Center of the container <pre> position: {
19368 * mx: 0.5,
19369 * my: 0.5,
19370 * }</pre>
19371 * Upper left corner of the container
19372 * <pre> position: {
19373 * mx: 0.0,
19374 * my: 0.0,
19375 * }</pre>
19376 * </li>
19377 * </ul>
19378 * </p>
19379 *
19380 */
19381 this.getScaledPath = function getScaledPath(pathId, param) {
19382 var rawPath = this.pathMap[pathId];
19383
19384 // positioning
19385 // compute the start point of the path
19386 var mx, my;
19387
19388 if (param.abspos) {
19389 mx = param.abspos.x;
19390 my = param.abspos.y;
19391 } else {
19392 mx = param.containerWidth * param.position.mx;
19393 my = param.containerHeight * param.position.my;
19394 }
19395
19396 var coordinates = {}; // map for the scaled coordinates
19397 if (param.position) {
19398
19399 // path
19400 var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
19401 var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;
19402
19403
19404 // Apply height ratio
19405 for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
19406 coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
19407 }
19408
19409 // Apply width ratio
19410 for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
19411 coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
19412 }
19413 }
19414
19415 // Apply value to raw path
19416 var path = format(
19417 rawPath.d, {
19418 mx: mx,
19419 my: my,
19420 e: coordinates
19421 }
19422 );
19423 return path;
19424 };
19425 }
19426
19427 // helpers //////////////////////
19428
19429 // copied from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
19430 var tokenRegex = /\{([^}]+)\}/g,
19431 objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties
19432
19433 function replacer(all, key, obj) {
19434 var res = obj;
19435 key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
19436 name = name || quotedName;
19437 if (res) {
19438 if (name in res) {
19439 res = res[name];
19440 }
19441 typeof res == 'function' && isFunc && (res = res());
19442 }
19443 });
19444 res = (res == null || res == obj ? all : res) + '';
19445
19446 return res;
19447 }
19448
19449 function format(str, obj) {
19450 return String(str).replace(tokenRegex, function(all, key) {
19451 return replacer(all, key, obj);
19452 });
19453 }
19454
19455 var DrawModule$1 = {
19456 __init__: [ 'bpmnRenderer' ],
19457 bpmnRenderer: [ 'type', BpmnRenderer ],
19458 textRenderer: [ 'type', TextRenderer ],
19459 pathMap: [ 'type', PathMap ]
19460 };
19461
19462 /**
19463 * A simple translation stub to be used for multi-language support
19464 * in diagrams. Can be easily replaced with a more sophisticated
19465 * solution.
19466 *
19467 * @example
19468 *
19469 * // use it inside any diagram component by injecting `translate`.
19470 *
19471 * function MyService(translate) {
19472 * alert(translate('HELLO {you}', { you: 'You!' }));
19473 * }
19474 *
19475 * @param {string} template to interpolate
19476 * @param {Object} [replacements] a map with substitutes
19477 *
19478 * @return {string} the translated string
19479 */
19480 function translate$1(template, replacements) {
19481
19482 replacements = replacements || {};
19483
19484 return template.replace(/{([^}]+)}/g, function(_, key) {
19485 return replacements[key] || '{' + key + '}';
19486 });
19487 }
19488
19489 var translate$2 = {
19490 translate: [ 'value', translate$1 ]
19491 };
19492
19493 var DEFAULT_LABEL_SIZE$1 = {
19494 width: 90,
19495 height: 20
19496 };
19497
19498 var FLOW_LABEL_INDENT = 15;
19499
19500
19501 /**
19502 * Returns true if the given semantic has an external label
19503 *
19504 * @param {BpmnElement} semantic
19505 * @return {boolean} true if has label
19506 */
19507 function isLabelExternal(semantic) {
19508 return is$1(semantic, 'bpmn:Event') ||
19509 is$1(semantic, 'bpmn:Gateway') ||
19510 is$1(semantic, 'bpmn:DataStoreReference') ||
19511 is$1(semantic, 'bpmn:DataObjectReference') ||
19512 is$1(semantic, 'bpmn:DataInput') ||
19513 is$1(semantic, 'bpmn:DataOutput') ||
19514 is$1(semantic, 'bpmn:SequenceFlow') ||
19515 is$1(semantic, 'bpmn:MessageFlow') ||
19516 is$1(semantic, 'bpmn:Group');
19517 }
19518
19519 /**
19520 * Returns true if the given element has an external label
19521 *
19522 * @param {djs.model.shape} element
19523 * @return {boolean} true if has label
19524 */
19525 function hasExternalLabel(element) {
19526 return isLabel(element.label);
19527 }
19528
19529 /**
19530 * Get the position for sequence flow labels
19531 *
19532 * @param {Array<Point>} waypoints
19533 * @return {Point} the label position
19534 */
19535 function getFlowLabelPosition(waypoints) {
19536
19537 // get the waypoints mid
19538 var mid = waypoints.length / 2 - 1;
19539
19540 var first = waypoints[Math.floor(mid)];
19541 var second = waypoints[Math.ceil(mid + 0.01)];
19542
19543 // get position
19544 var position = getWaypointsMid(waypoints);
19545
19546 // calculate angle
19547 var angle = Math.atan((second.y - first.y) / (second.x - first.x));
19548
19549 var x = position.x,
19550 y = position.y;
19551
19552 if (Math.abs(angle) < Math.PI / 2) {
19553 y -= FLOW_LABEL_INDENT;
19554 } else {
19555 x += FLOW_LABEL_INDENT;
19556 }
19557
19558 return { x: x, y: y };
19559 }
19560
19561
19562 /**
19563 * Get the middle of a number of waypoints
19564 *
19565 * @param {Array<Point>} waypoints
19566 * @return {Point} the mid point
19567 */
19568 function getWaypointsMid(waypoints) {
19569
19570 var mid = waypoints.length / 2 - 1;
19571
19572 var first = waypoints[Math.floor(mid)];
19573 var second = waypoints[Math.ceil(mid + 0.01)];
19574
19575 return {
19576 x: first.x + (second.x - first.x) / 2,
19577 y: first.y + (second.y - first.y) / 2
19578 };
19579 }
19580
19581
19582 function getExternalLabelMid(element) {
19583
19584 if (element.waypoints) {
19585 return getFlowLabelPosition(element.waypoints);
19586 } else if (is$1(element, 'bpmn:Group')) {
19587 return {
19588 x: element.x + element.width / 2,
19589 y: element.y + DEFAULT_LABEL_SIZE$1.height / 2
19590 };
19591 } else {
19592 return {
19593 x: element.x + element.width / 2,
19594 y: element.y + element.height + DEFAULT_LABEL_SIZE$1.height / 2
19595 };
19596 }
19597 }
19598
19599
19600 /**
19601 * Returns the bounds of an elements label, parsed from the elements DI or
19602 * generated from its bounds.
19603 *
19604 * @param {BpmnElement} semantic
19605 * @param {djs.model.Base} element
19606 */
19607 function getExternalLabelBounds(semantic, element) {
19608
19609 var mid,
19610 size,
19611 bounds,
19612 di = semantic.di,
19613 label = di.label;
19614
19615 if (label && label.bounds) {
19616 bounds = label.bounds;
19617
19618 size = {
19619 width: Math.max(DEFAULT_LABEL_SIZE$1.width, bounds.width),
19620 height: bounds.height
19621 };
19622
19623 mid = {
19624 x: bounds.x + bounds.width / 2,
19625 y: bounds.y + bounds.height / 2
19626 };
19627 } else {
19628
19629 mid = getExternalLabelMid(element);
19630
19631 size = DEFAULT_LABEL_SIZE$1;
19632 }
19633
19634 return assign({
19635 x: mid.x - size.width / 2,
19636 y: mid.y - size.height / 2
19637 }, size);
19638 }
19639
19640 function isLabel(element) {
19641 return element && !!element.labelTarget;
19642 }
19643
19644 function elementData(semantic, attrs) {
19645 return assign({
19646 id: semantic.id,
19647 type: semantic.$type,
19648 businessObject: semantic
19649 }, attrs);
19650 }
19651
19652 function getWaypoints(bo, source, target) {
19653
19654 var waypoints = bo.di.waypoint;
19655
19656 if (!waypoints || waypoints.length < 2) {
19657 return [ getMid(source), getMid(target) ];
19658 }
19659
19660 return waypoints.map(function(p) {
19661 return { x: p.x, y: p.y };
19662 });
19663 }
19664
19665 function notYetDrawn(translate, semantic, refSemantic, property) {
19666 return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
19667 element: elementToString(refSemantic),
19668 referenced: elementToString(semantic),
19669 property: property
19670 }));
19671 }
19672
19673
19674 /**
19675 * An importer that adds bpmn elements to the canvas
19676 *
19677 * @param {EventBus} eventBus
19678 * @param {Canvas} canvas
19679 * @param {ElementFactory} elementFactory
19680 * @param {ElementRegistry} elementRegistry
19681 * @param {Function} translate
19682 * @param {TextRenderer} textRenderer
19683 */
19684 function BpmnImporter(
19685 eventBus, canvas, elementFactory,
19686 elementRegistry, translate, textRenderer) {
19687
19688 this._eventBus = eventBus;
19689 this._canvas = canvas;
19690 this._elementFactory = elementFactory;
19691 this._elementRegistry = elementRegistry;
19692 this._translate = translate;
19693 this._textRenderer = textRenderer;
19694 }
19695
19696 BpmnImporter.$inject = [
19697 'eventBus',
19698 'canvas',
19699 'elementFactory',
19700 'elementRegistry',
19701 'translate',
19702 'textRenderer'
19703 ];
19704
19705
19706 /**
19707 * Add bpmn element (semantic) to the canvas onto the
19708 * specified parent shape.
19709 */
19710 BpmnImporter.prototype.add = function(semantic, parentElement) {
19711
19712 var di = semantic.di,
19713 element,
19714 translate = this._translate,
19715 hidden;
19716
19717 var parentIndex;
19718
19719 // ROOT ELEMENT
19720 // handle the special case that we deal with a
19721 // invisible root element (process or collaboration)
19722 if (is$1(di, 'bpmndi:BPMNPlane')) {
19723
19724 // add a virtual element (not being drawn)
19725 element = this._elementFactory.createRoot(elementData(semantic));
19726
19727 this._canvas.setRootElement(element);
19728 }
19729
19730 // SHAPE
19731 else if (is$1(di, 'bpmndi:BPMNShape')) {
19732
19733 var collapsed = !isExpanded(semantic),
19734 isFrame = isFrameElement$1(semantic);
19735 hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
19736
19737 var bounds = semantic.di.bounds;
19738
19739 element = this._elementFactory.createShape(elementData(semantic, {
19740 collapsed: collapsed,
19741 hidden: hidden,
19742 x: Math.round(bounds.x),
19743 y: Math.round(bounds.y),
19744 width: Math.round(bounds.width),
19745 height: Math.round(bounds.height),
19746 isFrame: isFrame
19747 }));
19748
19749 if (is$1(semantic, 'bpmn:BoundaryEvent')) {
19750 this._attachBoundary(semantic, element);
19751 }
19752
19753 // insert lanes behind other flow nodes (cf. #727)
19754 if (is$1(semantic, 'bpmn:Lane')) {
19755 parentIndex = 0;
19756 }
19757
19758 if (is$1(semantic, 'bpmn:DataStoreReference')) {
19759
19760 // check whether data store is inside our outside of its semantic parent
19761 if (!isPointInsideBBox$1(parentElement, getMid(bounds))) {
19762 parentElement = this._canvas.getRootElement();
19763 }
19764 }
19765
19766 this._canvas.addShape(element, parentElement, parentIndex);
19767 }
19768
19769 // CONNECTION
19770 else if (is$1(di, 'bpmndi:BPMNEdge')) {
19771
19772 var source = this._getSource(semantic),
19773 target = this._getTarget(semantic);
19774
19775 hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
19776
19777 element = this._elementFactory.createConnection(elementData(semantic, {
19778 hidden: hidden,
19779 source: source,
19780 target: target,
19781 waypoints: getWaypoints(semantic, source, target)
19782 }));
19783
19784 if (is$1(semantic, 'bpmn:DataAssociation')) {
19785
19786 // render always on top; this ensures DataAssociations
19787 // are rendered correctly across different "hacks" people
19788 // love to model such as cross participant / sub process
19789 // associations
19790 parentElement = null;
19791 }
19792
19793 // insert sequence flows behind other flow nodes (cf. #727)
19794 if (is$1(semantic, 'bpmn:SequenceFlow')) {
19795 parentIndex = 0;
19796 }
19797
19798 this._canvas.addConnection(element, parentElement, parentIndex);
19799 } else {
19800 throw new Error(translate('unknown di {di} for element {semantic}', {
19801 di: elementToString(di),
19802 semantic: elementToString(semantic)
19803 }));
19804 }
19805
19806 // (optional) LABEL
19807 if (isLabelExternal(semantic) && getLabel(element)) {
19808 this.addLabel(semantic, element);
19809 }
19810
19811
19812 this._eventBus.fire('bpmnElement.added', { element: element });
19813
19814 return element;
19815 };
19816
19817
19818 /**
19819 * Attach the boundary element to the given host
19820 *
19821 * @param {ModdleElement} boundarySemantic
19822 * @param {djs.model.Base} boundaryElement
19823 */
19824 BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
19825 var translate = this._translate;
19826 var hostSemantic = boundarySemantic.attachedToRef;
19827
19828 if (!hostSemantic) {
19829 throw new Error(translate('missing {semantic}#attachedToRef', {
19830 semantic: elementToString(boundarySemantic)
19831 }));
19832 }
19833
19834 var host = this._elementRegistry.get(hostSemantic.id),
19835 attachers = host && host.attachers;
19836
19837 if (!host) {
19838 throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
19839 }
19840
19841 // wire element.host <> host.attachers
19842 boundaryElement.host = host;
19843
19844 if (!attachers) {
19845 host.attachers = attachers = [];
19846 }
19847
19848 if (attachers.indexOf(boundaryElement) === -1) {
19849 attachers.push(boundaryElement);
19850 }
19851 };
19852
19853
19854 /**
19855 * add label for an element
19856 */
19857 BpmnImporter.prototype.addLabel = function(semantic, element) {
19858 var bounds,
19859 text,
19860 label;
19861
19862 bounds = getExternalLabelBounds(semantic, element);
19863
19864 text = getLabel(element);
19865
19866 if (text) {
19867
19868 // get corrected bounds from actual layouted text
19869 bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
19870 }
19871
19872 label = this._elementFactory.createLabel(elementData(semantic, {
19873 id: semantic.id + '_label',
19874 labelTarget: element,
19875 type: 'label',
19876 hidden: element.hidden || !getLabel(element),
19877 x: Math.round(bounds.x),
19878 y: Math.round(bounds.y),
19879 width: Math.round(bounds.width),
19880 height: Math.round(bounds.height)
19881 }));
19882
19883 return this._canvas.addShape(label, element.parent);
19884 };
19885
19886 /**
19887 * Return the drawn connection end based on the given side.
19888 *
19889 * @throws {Error} if the end is not yet drawn
19890 */
19891 BpmnImporter.prototype._getEnd = function(semantic, side) {
19892
19893 var element,
19894 refSemantic,
19895 type = semantic.$type,
19896 translate = this._translate;
19897
19898 refSemantic = semantic[side + 'Ref'];
19899
19900 // handle mysterious isMany DataAssociation#sourceRef
19901 if (side === 'source' && type === 'bpmn:DataInputAssociation') {
19902 refSemantic = refSemantic && refSemantic[0];
19903 }
19904
19905 // fix source / target for DataInputAssociation / DataOutputAssociation
19906 if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
19907 side === 'target' && type === 'bpmn:DataInputAssociation') {
19908
19909 refSemantic = semantic.$parent;
19910 }
19911
19912 element = refSemantic && this._getElement(refSemantic);
19913
19914 if (element) {
19915 return element;
19916 }
19917
19918 if (refSemantic) {
19919 throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
19920 } else {
19921 throw new Error(translate('{semantic}#{side} Ref not specified', {
19922 semantic: elementToString(semantic),
19923 side: side
19924 }));
19925 }
19926 };
19927
19928 BpmnImporter.prototype._getSource = function(semantic) {
19929 return this._getEnd(semantic, 'source');
19930 };
19931
19932 BpmnImporter.prototype._getTarget = function(semantic) {
19933 return this._getEnd(semantic, 'target');
19934 };
19935
19936
19937 BpmnImporter.prototype._getElement = function(semantic) {
19938 return this._elementRegistry.get(semantic.id);
19939 };
19940
19941
19942 // helpers ////////////////////
19943
19944 function isPointInsideBBox$1(bbox, point) {
19945 var x = point.x,
19946 y = point.y;
19947
19948 return x >= bbox.x &&
19949 x <= bbox.x + bbox.width &&
19950 y >= bbox.y &&
19951 y <= bbox.y + bbox.height;
19952 }
19953
19954 function isFrameElement$1(semantic) {
19955 return is$1(semantic, 'bpmn:Group');
19956 }
19957
19958 var ImportModule = {
19959 __depends__: [
19960 translate$2
19961 ],
19962 bpmnImporter: [ 'type', BpmnImporter ]
19963 };
19964
19965 var CoreModule$1 = {
19966 __depends__: [
19967 DrawModule$1,
19968 ImportModule
19969 ]
19970 };
19971
19972 function __stopPropagation(event) {
19973 if (!event || typeof event.stopPropagation !== 'function') {
19974 return;
19975 }
19976
19977 event.stopPropagation();
19978 }
19979
19980
19981 function getOriginal(event) {
19982 return event.originalEvent || event.srcEvent;
19983 }
19984
19985
19986 function stopPropagation(event, immediate) {
19987 __stopPropagation(event);
19988 __stopPropagation(getOriginal(event));
19989 }
19990
19991
19992 function toPoint(event) {
19993
19994 if (event.pointers && event.pointers.length) {
19995 event = event.pointers[0];
19996 }
19997
19998 if (event.touches && event.touches.length) {
19999 event = event.touches[0];
20000 }
20001
20002 return event ? {
20003 x: event.clientX,
20004 y: event.clientY
20005 } : null;
20006 }
20007
20008 function isMac() {
20009 return (/mac/i).test(navigator.platform);
20010 }
20011
20012 function isButton(event, button) {
20013 return (getOriginal(event) || event).button === button;
20014 }
20015
20016 function isPrimaryButton(event) {
20017
20018 // button === 0 -> left áka primary mouse button
20019 return isButton(event, 0);
20020 }
20021
20022 function isAuxiliaryButton(event) {
20023
20024 // button === 1 -> auxiliary áka wheel button
20025 return isButton(event, 1);
20026 }
20027
20028 function hasPrimaryModifier(event) {
20029 var originalEvent = getOriginal(event) || event;
20030
20031 if (!isPrimaryButton(event)) {
20032 return false;
20033 }
20034
20035 // Use cmd as primary modifier key for mac OS
20036 if (isMac()) {
20037 return originalEvent.metaKey;
20038 } else {
20039 return originalEvent.ctrlKey;
20040 }
20041 }
20042
20043
20044 function hasSecondaryModifier(event) {
20045 var originalEvent = getOriginal(event) || event;
20046
20047 return isPrimaryButton(event) && originalEvent.shiftKey;
20048 }
20049
20050 function allowAll(event) { return true; }
20051
20052 function allowPrimaryAndAuxiliary(event) {
20053 return isPrimaryButton(event) || isAuxiliaryButton(event);
20054 }
20055
20056 var LOW_PRIORITY = 500;
20057
20058
20059 /**
20060 * A plugin that provides interaction events for diagram elements.
20061 *
20062 * It emits the following events:
20063 *
20064 * * element.click
20065 * * element.contextmenu
20066 * * element.dblclick
20067 * * element.hover
20068 * * element.mousedown
20069 * * element.mousemove
20070 * * element.mouseup
20071 * * element.out
20072 *
20073 * Each event is a tuple { element, gfx, originalEvent }.
20074 *
20075 * Canceling the event via Event#preventDefault()
20076 * prevents the original DOM operation.
20077 *
20078 * @param {EventBus} eventBus
20079 */
20080 function InteractionEvents(eventBus, elementRegistry, styles) {
20081
20082 var self = this;
20083
20084 /**
20085 * Fire an interaction event.
20086 *
20087 * @param {string} type local event name, e.g. element.click.
20088 * @param {DOMEvent} event native event
20089 * @param {djs.model.Base} [element] the diagram element to emit the event on;
20090 * defaults to the event target
20091 */
20092 function fire(type, event, element) {
20093
20094 if (isIgnored(type, event)) {
20095 return;
20096 }
20097
20098 var target, gfx, returnValue;
20099
20100 if (!element) {
20101 target = event.delegateTarget || event.target;
20102
20103 if (target) {
20104 gfx = target;
20105 element = elementRegistry.get(gfx);
20106 }
20107 } else {
20108 gfx = elementRegistry.getGraphics(element);
20109 }
20110
20111 if (!gfx || !element) {
20112 return;
20113 }
20114
20115 returnValue = eventBus.fire(type, {
20116 element: element,
20117 gfx: gfx,
20118 originalEvent: event
20119 });
20120
20121 if (returnValue === false) {
20122 event.stopPropagation();
20123 event.preventDefault();
20124 }
20125 }
20126
20127 // TODO(nikku): document this
20128 var handlers = {};
20129
20130 function mouseHandler(localEventName) {
20131 return handlers[localEventName];
20132 }
20133
20134 function isIgnored(localEventName, event) {
20135
20136 var filter = ignoredFilters[localEventName] || isPrimaryButton;
20137
20138 // only react on left mouse button interactions
20139 // except for interaction events that are enabled
20140 // for secundary mouse button
20141 return !filter(event);
20142 }
20143
20144 var bindings = {
20145 click: 'element.click',
20146 contextmenu: 'element.contextmenu',
20147 dblclick: 'element.dblclick',
20148 mousedown: 'element.mousedown',
20149 mousemove: 'element.mousemove',
20150 mouseover: 'element.hover',
20151 mouseout: 'element.out',
20152 mouseup: 'element.mouseup',
20153 };
20154
20155 var ignoredFilters = {
20156 'element.contextmenu': allowAll,
20157 'element.mousedown': allowPrimaryAndAuxiliary,
20158 'element.mouseup': allowPrimaryAndAuxiliary,
20159 'element.click': allowPrimaryAndAuxiliary,
20160 'element.dblclick': allowPrimaryAndAuxiliary
20161 };
20162
20163
20164 // manual event trigger //////////
20165
20166 /**
20167 * Trigger an interaction event (based on a native dom event)
20168 * on the target shape or connection.
20169 *
20170 * @param {string} eventName the name of the triggered DOM event
20171 * @param {MouseEvent} event
20172 * @param {djs.model.Base} targetElement
20173 */
20174 function triggerMouseEvent(eventName, event, targetElement) {
20175
20176 // i.e. element.mousedown...
20177 var localEventName = bindings[eventName];
20178
20179 if (!localEventName) {
20180 throw new Error('unmapped DOM event name <' + eventName + '>');
20181 }
20182
20183 return fire(localEventName, event, targetElement);
20184 }
20185
20186
20187 var ELEMENT_SELECTOR = 'svg, .djs-element';
20188
20189 // event handling ///////
20190
20191 function registerEvent(node, event, localEvent, ignoredFilter) {
20192
20193 var handler = handlers[localEvent] = function(event) {
20194 fire(localEvent, event);
20195 };
20196
20197 if (ignoredFilter) {
20198 ignoredFilters[localEvent] = ignoredFilter;
20199 }
20200
20201 handler.$delegate = delegate.bind(node, ELEMENT_SELECTOR, event, handler);
20202 }
20203
20204 function unregisterEvent(node, event, localEvent) {
20205
20206 var handler = mouseHandler(localEvent);
20207
20208 if (!handler) {
20209 return;
20210 }
20211
20212 delegate.unbind(node, event, handler.$delegate);
20213 }
20214
20215 function registerEvents(svg) {
20216 forEach(bindings, function(val, key) {
20217 registerEvent(svg, key, val);
20218 });
20219 }
20220
20221 function unregisterEvents(svg) {
20222 forEach(bindings, function(val, key) {
20223 unregisterEvent(svg, key, val);
20224 });
20225 }
20226
20227 eventBus.on('canvas.destroy', function(event) {
20228 unregisterEvents(event.svg);
20229 });
20230
20231 eventBus.on('canvas.init', function(event) {
20232 registerEvents(event.svg);
20233 });
20234
20235
20236 // hit box updating ////////////////
20237
20238 eventBus.on([ 'shape.added', 'connection.added' ], function(event) {
20239 var element = event.element,
20240 gfx = event.gfx;
20241
20242 eventBus.fire('interactionEvents.createHit', { element: element, gfx: gfx });
20243 });
20244
20245 // Update djs-hit on change.
20246 // A low priortity is necessary, because djs-hit of labels has to be updated
20247 // after the label bounds have been updated in the renderer.
20248 eventBus.on([
20249 'shape.changed',
20250 'connection.changed'
20251 ], LOW_PRIORITY, function(event) {
20252
20253 var element = event.element,
20254 gfx = event.gfx;
20255
20256 eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx });
20257 });
20258
20259 eventBus.on('interactionEvents.createHit', LOW_PRIORITY, function(event) {
20260 var element = event.element,
20261 gfx = event.gfx;
20262
20263 self.createDefaultHit(element, gfx);
20264 });
20265
20266 eventBus.on('interactionEvents.updateHit', function(event) {
20267 var element = event.element,
20268 gfx = event.gfx;
20269
20270 self.updateDefaultHit(element, gfx);
20271 });
20272
20273
20274 // hit styles ////////////
20275
20276 var STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-stroke');
20277
20278 var CLICK_STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-click-stroke');
20279
20280 var ALL_HIT_STYLE = createHitStyle('djs-hit djs-hit-all');
20281
20282 var HIT_TYPES = {
20283 'all': ALL_HIT_STYLE,
20284 'click-stroke': CLICK_STROKE_HIT_STYLE,
20285 'stroke': STROKE_HIT_STYLE
20286 };
20287
20288 function createHitStyle(classNames, attrs) {
20289
20290 attrs = assign({
20291 stroke: 'white',
20292 strokeWidth: 15
20293 }, attrs || {});
20294
20295 return styles.cls(classNames, [ 'no-fill', 'no-border' ], attrs);
20296 }
20297
20298
20299 // style helpers ///////////////
20300
20301 function applyStyle(hit, type) {
20302
20303 var attrs = HIT_TYPES[type];
20304
20305 if (!attrs) {
20306 throw new Error('invalid hit type <' + type + '>');
20307 }
20308
20309 attr$1(hit, attrs);
20310
20311 return hit;
20312 }
20313
20314 function appendHit(gfx, hit) {
20315 append(gfx, hit);
20316 }
20317
20318
20319 // API
20320
20321 /**
20322 * Remove hints on the given graphics.
20323 *
20324 * @param {SVGElement} gfx
20325 */
20326 this.removeHits = function(gfx) {
20327 var hits = all('.djs-hit', gfx);
20328
20329 forEach(hits, remove$1);
20330 };
20331
20332 /**
20333 * Create default hit for the given element.
20334 *
20335 * @param {djs.model.Base} element
20336 * @param {SVGElement} gfx
20337 *
20338 * @return {SVGElement} created hit
20339 */
20340 this.createDefaultHit = function(element, gfx) {
20341 var waypoints = element.waypoints,
20342 isFrame = element.isFrame,
20343 boxType;
20344
20345 if (waypoints) {
20346 return this.createWaypointsHit(gfx, waypoints);
20347 } else {
20348
20349 boxType = isFrame ? 'stroke' : 'all';
20350
20351 return this.createBoxHit(gfx, boxType, {
20352 width: element.width,
20353 height: element.height
20354 });
20355 }
20356 };
20357
20358 /**
20359 * Create hits for the given waypoints.
20360 *
20361 * @param {SVGElement} gfx
20362 * @param {Array<Point>} waypoints
20363 *
20364 * @return {SVGElement}
20365 */
20366 this.createWaypointsHit = function(gfx, waypoints) {
20367
20368 var hit = createLine(waypoints);
20369
20370 applyStyle(hit, 'stroke');
20371
20372 appendHit(gfx, hit);
20373
20374 return hit;
20375 };
20376
20377 /**
20378 * Create hits for a box.
20379 *
20380 * @param {SVGElement} gfx
20381 * @param {string} hitType
20382 * @param {Object} attrs
20383 *
20384 * @return {SVGElement}
20385 */
20386 this.createBoxHit = function(gfx, type, attrs) {
20387
20388 attrs = assign({
20389 x: 0,
20390 y: 0
20391 }, attrs);
20392
20393 var hit = create('rect');
20394
20395 applyStyle(hit, type);
20396
20397 attr$1(hit, attrs);
20398
20399 appendHit(gfx, hit);
20400
20401 return hit;
20402 };
20403
20404 /**
20405 * Update default hit of the element.
20406 *
20407 * @param {djs.model.Base} element
20408 * @param {SVGElement} gfx
20409 *
20410 * @return {SVGElement} updated hit
20411 */
20412 this.updateDefaultHit = function(element, gfx) {
20413
20414 var hit = query('.djs-hit', gfx);
20415
20416 if (!hit) {
20417 return;
20418 }
20419
20420 if (element.waypoints) {
20421 updateLine(hit, element.waypoints);
20422 } else {
20423 attr$1(hit, {
20424 width: element.width,
20425 height: element.height
20426 });
20427 }
20428
20429 return hit;
20430 };
20431
20432 this.fire = fire;
20433
20434 this.triggerMouseEvent = triggerMouseEvent;
20435
20436 this.mouseHandler = mouseHandler;
20437
20438 this.registerEvent = registerEvent;
20439 this.unregisterEvent = unregisterEvent;
20440 }
20441
20442
20443 InteractionEvents.$inject = [
20444 'eventBus',
20445 'elementRegistry',
20446 'styles'
20447 ];
20448
20449
20450 /**
20451 * An event indicating that the mouse hovered over an element
20452 *
20453 * @event element.hover
20454 *
20455 * @type {Object}
20456 * @property {djs.model.Base} element
20457 * @property {SVGElement} gfx
20458 * @property {Event} originalEvent
20459 */
20460
20461 /**
20462 * An event indicating that the mouse has left an element
20463 *
20464 * @event element.out
20465 *
20466 * @type {Object}
20467 * @property {djs.model.Base} element
20468 * @property {SVGElement} gfx
20469 * @property {Event} originalEvent
20470 */
20471
20472 /**
20473 * An event indicating that the mouse has clicked an element
20474 *
20475 * @event element.click
20476 *
20477 * @type {Object}
20478 * @property {djs.model.Base} element
20479 * @property {SVGElement} gfx
20480 * @property {Event} originalEvent
20481 */
20482
20483 /**
20484 * An event indicating that the mouse has double clicked an element
20485 *
20486 * @event element.dblclick
20487 *
20488 * @type {Object}
20489 * @property {djs.model.Base} element
20490 * @property {SVGElement} gfx
20491 * @property {Event} originalEvent
20492 */
20493
20494 /**
20495 * An event indicating that the mouse has gone down on an element.
20496 *
20497 * @event element.mousedown
20498 *
20499 * @type {Object}
20500 * @property {djs.model.Base} element
20501 * @property {SVGElement} gfx
20502 * @property {Event} originalEvent
20503 */
20504
20505 /**
20506 * An event indicating that the mouse has gone up on an element.
20507 *
20508 * @event element.mouseup
20509 *
20510 * @type {Object}
20511 * @property {djs.model.Base} element
20512 * @property {SVGElement} gfx
20513 * @property {Event} originalEvent
20514 */
20515
20516 /**
20517 * An event indicating that the context menu action is triggered
20518 * via mouse or touch controls.
20519 *
20520 * @event element.contextmenu
20521 *
20522 * @type {Object}
20523 * @property {djs.model.Base} element
20524 * @property {SVGElement} gfx
20525 * @property {Event} originalEvent
20526 */
20527
20528 var InteractionEventsModule = {
20529 __init__: [ 'interactionEvents' ],
20530 interactionEvents: [ 'type', InteractionEvents ]
20531 };
20532
20533 var LOW_PRIORITY$1 = 500;
20534
20535
20536 /**
20537 * @class
20538 *
20539 * A plugin that adds an outline to shapes and connections that may be activated and styled
20540 * via CSS classes.
20541 *
20542 * @param {EventBus} eventBus
20543 * @param {Styles} styles
20544 * @param {ElementRegistry} elementRegistry
20545 */
20546 function Outline(eventBus, styles, elementRegistry) {
20547
20548 this.offset = 6;
20549
20550 var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]);
20551
20552 var self = this;
20553
20554 function createOutline(gfx, bounds) {
20555 var outline = create('rect');
20556
20557 attr$1(outline, assign({
20558 x: 10,
20559 y: 10,
20560 width: 100,
20561 height: 100
20562 }, OUTLINE_STYLE));
20563
20564 append(gfx, outline);
20565
20566 return outline;
20567 }
20568
20569 // A low priortity is necessary, because outlines of labels have to be updated
20570 // after the label bounds have been updated in the renderer.
20571 eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$1, function(event) {
20572 var element = event.element,
20573 gfx = event.gfx;
20574
20575 var outline = query('.djs-outline', gfx);
20576
20577 if (!outline) {
20578 outline = createOutline(gfx);
20579 }
20580
20581 self.updateShapeOutline(outline, element);
20582 });
20583
20584 eventBus.on([ 'connection.added', 'connection.changed' ], function(event) {
20585 var element = event.element,
20586 gfx = event.gfx;
20587
20588 var outline = query('.djs-outline', gfx);
20589
20590 if (!outline) {
20591 outline = createOutline(gfx);
20592 }
20593
20594 self.updateConnectionOutline(outline, element);
20595 });
20596 }
20597
20598
20599 /**
20600 * Updates the outline of a shape respecting the dimension of the
20601 * element and an outline offset.
20602 *
20603 * @param {SVGElement} outline
20604 * @param {djs.model.Base} element
20605 */
20606 Outline.prototype.updateShapeOutline = function(outline, element) {
20607
20608 attr$1(outline, {
20609 x: -this.offset,
20610 y: -this.offset,
20611 width: element.width + this.offset * 2,
20612 height: element.height + this.offset * 2
20613 });
20614
20615 };
20616
20617
20618 /**
20619 * Updates the outline of a connection respecting the bounding box of
20620 * the connection and an outline offset.
20621 *
20622 * @param {SVGElement} outline
20623 * @param {djs.model.Base} element
20624 */
20625 Outline.prototype.updateConnectionOutline = function(outline, connection) {
20626
20627 var bbox = getBBox(connection);
20628
20629 attr$1(outline, {
20630 x: bbox.x - this.offset,
20631 y: bbox.y - this.offset,
20632 width: bbox.width + this.offset * 2,
20633 height: bbox.height + this.offset * 2
20634 });
20635
20636 };
20637
20638
20639 Outline.$inject = ['eventBus', 'styles', 'elementRegistry'];
20640
20641 var OutlineModule = {
20642 __init__: [ 'outline' ],
20643 outline: [ 'type', Outline ]
20644 };
20645
20646 /**
20647 * A service that offers the current selection in a diagram.
20648 * Offers the api to control the selection, too.
20649 *
20650 * @class
20651 *
20652 * @param {EventBus} eventBus the event bus
20653 */
20654 function Selection(eventBus) {
20655
20656 this._eventBus = eventBus;
20657
20658 this._selectedElements = [];
20659
20660 var self = this;
20661
20662 eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
20663 var element = e.element;
20664 self.deselect(element);
20665 });
20666
20667 eventBus.on([ 'diagram.clear' ], function(e) {
20668 self.select(null);
20669 });
20670 }
20671
20672 Selection.$inject = [ 'eventBus' ];
20673
20674
20675 Selection.prototype.deselect = function(element) {
20676 var selectedElements = this._selectedElements;
20677
20678 var idx = selectedElements.indexOf(element);
20679
20680 if (idx !== -1) {
20681 var oldSelection = selectedElements.slice();
20682
20683 selectedElements.splice(idx, 1);
20684
20685 this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
20686 }
20687 };
20688
20689
20690 Selection.prototype.get = function() {
20691 return this._selectedElements;
20692 };
20693
20694 Selection.prototype.isSelected = function(element) {
20695 return this._selectedElements.indexOf(element) !== -1;
20696 };
20697
20698
20699 /**
20700 * This method selects one or more elements on the diagram.
20701 *
20702 * By passing an additional add parameter you can decide whether or not the element(s)
20703 * should be added to the already existing selection or not.
20704 *
20705 * @method Selection#select
20706 *
20707 * @param {Object|Object[]} elements element or array of elements to be selected
20708 * @param {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false
20709 */
20710 Selection.prototype.select = function(elements, add) {
20711 var selectedElements = this._selectedElements,
20712 oldSelection = selectedElements.slice();
20713
20714 if (!isArray(elements)) {
20715 elements = elements ? [ elements ] : [];
20716 }
20717
20718 // selection may be cleared by passing an empty array or null
20719 // to the method
20720 if (add) {
20721 forEach(elements, function(element) {
20722 if (selectedElements.indexOf(element) !== -1) {
20723
20724 // already selected
20725 return;
20726 } else {
20727 selectedElements.push(element);
20728 }
20729 });
20730 } else {
20731 this._selectedElements = selectedElements = elements.slice();
20732 }
20733
20734 this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
20735 };
20736
20737 var MARKER_HOVER = 'hover',
20738 MARKER_SELECTED = 'selected';
20739
20740
20741 /**
20742 * A plugin that adds a visible selection UI to shapes and connections
20743 * by appending the <code>hover</code> and <code>selected</code> classes to them.
20744 *
20745 * @class
20746 *
20747 * Makes elements selectable, too.
20748 *
20749 * @param {EventBus} events
20750 * @param {SelectionService} selection
20751 * @param {Canvas} canvas
20752 */
20753 function SelectionVisuals(events, canvas, selection, styles) {
20754
20755 this._multiSelectionBox = null;
20756
20757 function addMarker(e, cls) {
20758 canvas.addMarker(e, cls);
20759 }
20760
20761 function removeMarker(e, cls) {
20762 canvas.removeMarker(e, cls);
20763 }
20764
20765 events.on('element.hover', function(event) {
20766 addMarker(event.element, MARKER_HOVER);
20767 });
20768
20769 events.on('element.out', function(event) {
20770 removeMarker(event.element, MARKER_HOVER);
20771 });
20772
20773 events.on('selection.changed', function(event) {
20774
20775 function deselect(s) {
20776 removeMarker(s, MARKER_SELECTED);
20777 }
20778
20779 function select(s) {
20780 addMarker(s, MARKER_SELECTED);
20781 }
20782
20783 var oldSelection = event.oldSelection,
20784 newSelection = event.newSelection;
20785
20786 forEach(oldSelection, function(e) {
20787 if (newSelection.indexOf(e) === -1) {
20788 deselect(e);
20789 }
20790 });
20791
20792 forEach(newSelection, function(e) {
20793 if (oldSelection.indexOf(e) === -1) {
20794 select(e);
20795 }
20796 });
20797 });
20798 }
20799
20800 SelectionVisuals.$inject = [
20801 'eventBus',
20802 'canvas',
20803 'selection',
20804 'styles'
20805 ];
20806
20807 function SelectionBehavior(eventBus, selection, canvas, elementRegistry) {
20808
20809 // Select elements on create
20810 eventBus.on('create.end', 500, function(event) {
20811 var context = event.context,
20812 canExecute = context.canExecute,
20813 elements = context.elements,
20814 hints = context.hints || {},
20815 autoSelect = hints.autoSelect;
20816
20817 if (canExecute) {
20818 if (autoSelect === false) {
20819
20820 // Select no elements
20821 return;
20822 }
20823
20824 if (isArray(autoSelect)) {
20825 selection.select(autoSelect);
20826 } else {
20827
20828 // Select all elements by default
20829 selection.select(elements.filter(isShown));
20830 }
20831 }
20832 });
20833
20834 // Select connection targets on connect
20835 eventBus.on('connect.end', 500, function(event) {
20836 var context = event.context,
20837 canExecute = context.canExecute,
20838 hover = context.hover;
20839
20840 if (canExecute && hover) {
20841 selection.select(hover);
20842 }
20843 });
20844
20845 // Select shapes on move
20846 eventBus.on('shape.move.end', 500, function(event) {
20847 var previousSelection = event.previousSelection || [];
20848
20849 var shape = elementRegistry.get(event.context.shape.id);
20850
20851 // Always select main shape on move
20852 var isSelected = find(previousSelection, function(selectedShape) {
20853 return shape.id === selectedShape.id;
20854 });
20855
20856 if (!isSelected) {
20857 selection.select(shape);
20858 }
20859 });
20860
20861 // Select elements on click
20862 eventBus.on('element.click', function(event) {
20863
20864 if (!isPrimaryButton(event)) {
20865 return;
20866 }
20867
20868 var element = event.element;
20869
20870 if (element === canvas.getRootElement()) {
20871 element = null;
20872 }
20873
20874 var isSelected = selection.isSelected(element),
20875 isMultiSelect = selection.get().length > 1;
20876
20877 // Add to selection if CTRL or SHIFT pressed
20878 var add = hasPrimaryModifier(event) || hasSecondaryModifier(event);
20879
20880 if (isSelected && isMultiSelect) {
20881 if (add) {
20882
20883 // Deselect element
20884 return selection.deselect(element);
20885 } else {
20886
20887 // Select element only
20888 return selection.select(element);
20889 }
20890 } else if (!isSelected) {
20891
20892 // Select element
20893 selection.select(element, add);
20894 } else {
20895
20896 // Deselect element
20897 selection.deselect(element);
20898 }
20899 });
20900 }
20901
20902 SelectionBehavior.$inject = [
20903 'eventBus',
20904 'selection',
20905 'canvas',
20906 'elementRegistry'
20907 ];
20908
20909
20910 function isShown(element) {
20911 return !element.hidden;
20912 }
20913
20914 var SelectionModule = {
20915 __init__: [ 'selectionVisuals', 'selectionBehavior' ],
20916 __depends__: [
20917 InteractionEventsModule,
20918 OutlineModule
20919 ],
20920 selection: [ 'type', Selection ],
20921 selectionVisuals: [ 'type', SelectionVisuals ],
20922 selectionBehavior: [ 'type', SelectionBehavior ]
20923 };
20924
20925 /**
20926 * Util that provides unique IDs.
20927 *
20928 * @class djs.util.IdGenerator
20929 * @constructor
20930 * @memberOf djs.util
20931 *
20932 * The ids can be customized via a given prefix and contain a random value to avoid collisions.
20933 *
20934 * @param {string} prefix a prefix to prepend to generated ids (for better readability)
20935 */
20936 function IdGenerator(prefix) {
20937
20938 this._counter = 0;
20939 this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-';
20940 }
20941
20942 /**
20943 * Returns a next unique ID.
20944 *
20945 * @method djs.util.IdGenerator#next
20946 *
20947 * @returns {string} the id
20948 */
20949 IdGenerator.prototype.next = function() {
20950 return this._prefix + (++this._counter);
20951 };
20952
20953 // document wide unique overlay ids
20954 var ids = new IdGenerator('ov');
20955
20956 var LOW_PRIORITY$2 = 500;
20957
20958
20959 /**
20960 * A service that allows users to attach overlays to diagram elements.
20961 *
20962 * The overlay service will take care of overlay positioning during updates.
20963 *
20964 * @example
20965 *
20966 * // add a pink badge on the top left of the shape
20967 * overlays.add(someShape, {
20968 * position: {
20969 * top: -5,
20970 * left: -5
20971 * },
20972 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
20973 * });
20974 *
20975 * // or add via shape id
20976 *
20977 * overlays.add('some-element-id', {
20978 * position: {
20979 * top: -5,
20980 * left: -5
20981 * }
20982 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
20983 * });
20984 *
20985 * // or add with optional type
20986 *
20987 * overlays.add(someShape, 'badge', {
20988 * position: {
20989 * top: -5,
20990 * left: -5
20991 * }
20992 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
20993 * });
20994 *
20995 *
20996 * // remove an overlay
20997 *
20998 * var id = overlays.add(...);
20999 * overlays.remove(id);
21000 *
21001 *
21002 * You may configure overlay defaults during tool by providing a `config` module
21003 * with `overlays.defaults` as an entry:
21004 *
21005 * {
21006 * overlays: {
21007 * defaults: {
21008 * show: {
21009 * minZoom: 0.7,
21010 * maxZoom: 5.0
21011 * },
21012 * scale: {
21013 * min: 1
21014 * }
21015 * }
21016 * }
21017 *
21018 * @param {Object} config
21019 * @param {EventBus} eventBus
21020 * @param {Canvas} canvas
21021 * @param {ElementRegistry} elementRegistry
21022 */
21023 function Overlays(config, eventBus, canvas, elementRegistry) {
21024
21025 this._eventBus = eventBus;
21026 this._canvas = canvas;
21027 this._elementRegistry = elementRegistry;
21028
21029 this._ids = ids;
21030
21031 this._overlayDefaults = assign({
21032
21033 // no show constraints
21034 show: null,
21035
21036 // always scale
21037 scale: true
21038 }, config && config.defaults);
21039
21040 /**
21041 * Mapping overlayId -> overlay
21042 */
21043 this._overlays = {};
21044
21045 /**
21046 * Mapping elementId -> overlay container
21047 */
21048 this._overlayContainers = [];
21049
21050 // root html element for all overlays
21051 this._overlayRoot = createRoot(canvas.getContainer());
21052
21053 this._init();
21054 }
21055
21056
21057 Overlays.$inject = [
21058 'config.overlays',
21059 'eventBus',
21060 'canvas',
21061 'elementRegistry'
21062 ];
21063
21064
21065 /**
21066 * Returns the overlay with the specified id or a list of overlays
21067 * for an element with a given type.
21068 *
21069 * @example
21070 *
21071 * // return the single overlay with the given id
21072 * overlays.get('some-id');
21073 *
21074 * // return all overlays for the shape
21075 * overlays.get({ element: someShape });
21076 *
21077 * // return all overlays on shape with type 'badge'
21078 * overlays.get({ element: someShape, type: 'badge' });
21079 *
21080 * // shape can also be specified as id
21081 * overlays.get({ element: 'element-id', type: 'badge' });
21082 *
21083 *
21084 * @param {Object} search
21085 * @param {string} [search.id]
21086 * @param {string|djs.model.Base} [search.element]
21087 * @param {string} [search.type]
21088 *
21089 * @return {Object|Array<Object>} the overlay(s)
21090 */
21091 Overlays.prototype.get = function(search) {
21092
21093 if (isString(search)) {
21094 search = { id: search };
21095 }
21096
21097 if (isString(search.element)) {
21098 search.element = this._elementRegistry.get(search.element);
21099 }
21100
21101 if (search.element) {
21102 var container = this._getOverlayContainer(search.element, true);
21103
21104 // return a list of overlays when searching by element (+type)
21105 if (container) {
21106 return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice();
21107 } else {
21108 return [];
21109 }
21110 } else
21111 if (search.type) {
21112 return filter(this._overlays, matchPattern({ type: search.type }));
21113 } else {
21114
21115 // return single element when searching by id
21116 return search.id ? this._overlays[search.id] : null;
21117 }
21118 };
21119
21120 /**
21121 * Adds a HTML overlay to an element.
21122 *
21123 * @param {string|djs.model.Base} element attach overlay to this shape
21124 * @param {string} [type] optional type to assign to the overlay
21125 * @param {Object} overlay the overlay configuration
21126 *
21127 * @param {string|DOMElement} overlay.html html element to use as an overlay
21128 * @param {Object} [overlay.show] show configuration
21129 * @param {number} [overlay.show.minZoom] minimal zoom level to show the overlay
21130 * @param {number} [overlay.show.maxZoom] maximum zoom level to show the overlay
21131 * @param {Object} overlay.position where to attach the overlay
21132 * @param {number} [overlay.position.left] relative to element bbox left attachment
21133 * @param {number} [overlay.position.top] relative to element bbox top attachment
21134 * @param {number} [overlay.position.bottom] relative to element bbox bottom attachment
21135 * @param {number} [overlay.position.right] relative to element bbox right attachment
21136 * @param {boolean|Object} [overlay.scale=true] false to preserve the same size regardless of
21137 * diagram zoom
21138 * @param {number} [overlay.scale.min]
21139 * @param {number} [overlay.scale.max]
21140 *
21141 * @return {string} id that may be used to reference the overlay for update or removal
21142 */
21143 Overlays.prototype.add = function(element, type, overlay) {
21144
21145 if (isObject(type)) {
21146 overlay = type;
21147 type = null;
21148 }
21149
21150 if (!element.id) {
21151 element = this._elementRegistry.get(element);
21152 }
21153
21154 if (!overlay.position) {
21155 throw new Error('must specifiy overlay position');
21156 }
21157
21158 if (!overlay.html) {
21159 throw new Error('must specifiy overlay html');
21160 }
21161
21162 if (!element) {
21163 throw new Error('invalid element specified');
21164 }
21165
21166 var id = this._ids.next();
21167
21168 overlay = assign({}, this._overlayDefaults, overlay, {
21169 id: id,
21170 type: type,
21171 element: element,
21172 html: overlay.html
21173 });
21174
21175 this._addOverlay(overlay);
21176
21177 return id;
21178 };
21179
21180
21181 /**
21182 * Remove an overlay with the given id or all overlays matching the given filter.
21183 *
21184 * @see Overlays#get for filter options.
21185 *
21186 * @param {string} [id]
21187 * @param {Object} [filter]
21188 */
21189 Overlays.prototype.remove = function(filter) {
21190
21191 var overlays = this.get(filter) || [];
21192
21193 if (!isArray(overlays)) {
21194 overlays = [ overlays ];
21195 }
21196
21197 var self = this;
21198
21199 forEach(overlays, function(overlay) {
21200
21201 var container = self._getOverlayContainer(overlay.element, true);
21202
21203 if (overlay) {
21204 remove(overlay.html);
21205 remove(overlay.htmlContainer);
21206
21207 delete overlay.htmlContainer;
21208 delete overlay.element;
21209
21210 delete self._overlays[overlay.id];
21211 }
21212
21213 if (container) {
21214 var idx = container.overlays.indexOf(overlay);
21215 if (idx !== -1) {
21216 container.overlays.splice(idx, 1);
21217 }
21218 }
21219 });
21220
21221 };
21222
21223
21224 Overlays.prototype.show = function() {
21225 setVisible(this._overlayRoot);
21226 };
21227
21228
21229 Overlays.prototype.hide = function() {
21230 setVisible(this._overlayRoot, false);
21231 };
21232
21233 Overlays.prototype.clear = function() {
21234 this._overlays = {};
21235
21236 this._overlayContainers = [];
21237
21238 clear(this._overlayRoot);
21239 };
21240
21241 Overlays.prototype._updateOverlayContainer = function(container) {
21242 var element = container.element,
21243 html = container.html;
21244
21245 // update container left,top according to the elements x,y coordinates
21246 // this ensures we can attach child elements relative to this container
21247
21248 var x = element.x,
21249 y = element.y;
21250
21251 if (element.waypoints) {
21252 var bbox = getBBox(element);
21253 x = bbox.x;
21254 y = bbox.y;
21255 }
21256
21257 setPosition(html, x, y);
21258
21259 attr(container.html, 'data-container-id', element.id);
21260 };
21261
21262
21263 Overlays.prototype._updateOverlay = function(overlay) {
21264
21265 var position = overlay.position,
21266 htmlContainer = overlay.htmlContainer,
21267 element = overlay.element;
21268
21269 // update overlay html relative to shape because
21270 // it is already positioned on the element
21271
21272 // update relative
21273 var left = position.left,
21274 top = position.top;
21275
21276 if (position.right !== undefined) {
21277
21278 var width;
21279
21280 if (element.waypoints) {
21281 width = getBBox(element).width;
21282 } else {
21283 width = element.width;
21284 }
21285
21286 left = position.right * -1 + width;
21287 }
21288
21289 if (position.bottom !== undefined) {
21290
21291 var height;
21292
21293 if (element.waypoints) {
21294 height = getBBox(element).height;
21295 } else {
21296 height = element.height;
21297 }
21298
21299 top = position.bottom * -1 + height;
21300 }
21301
21302 setPosition(htmlContainer, left || 0, top || 0);
21303 };
21304
21305
21306 Overlays.prototype._createOverlayContainer = function(element) {
21307 var html = domify('<div class="djs-overlays" style="position: absolute" />');
21308
21309 this._overlayRoot.appendChild(html);
21310
21311 var container = {
21312 html: html,
21313 element: element,
21314 overlays: []
21315 };
21316
21317 this._updateOverlayContainer(container);
21318
21319 this._overlayContainers.push(container);
21320
21321 return container;
21322 };
21323
21324
21325 Overlays.prototype._updateRoot = function(viewbox) {
21326 var scale = viewbox.scale || 1;
21327
21328 var matrix = 'matrix(' +
21329 [
21330 scale,
21331 0,
21332 0,
21333 scale,
21334 -1 * viewbox.x * scale,
21335 -1 * viewbox.y * scale
21336 ].join(',') +
21337 ')';
21338
21339 setTransform(this._overlayRoot, matrix);
21340 };
21341
21342
21343 Overlays.prototype._getOverlayContainer = function(element, raw) {
21344 var container = find(this._overlayContainers, function(c) {
21345 return c.element === element;
21346 });
21347
21348
21349 if (!container && !raw) {
21350 return this._createOverlayContainer(element);
21351 }
21352
21353 return container;
21354 };
21355
21356
21357 Overlays.prototype._addOverlay = function(overlay) {
21358
21359 var id = overlay.id,
21360 element = overlay.element,
21361 html = overlay.html,
21362 htmlContainer,
21363 overlayContainer;
21364
21365 // unwrap jquery (for those who need it)
21366 if (html.get && html.constructor.prototype.jquery) {
21367 html = html.get(0);
21368 }
21369
21370 // create proper html elements from
21371 // overlay HTML strings
21372 if (isString(html)) {
21373 html = domify(html);
21374 }
21375
21376 overlayContainer = this._getOverlayContainer(element);
21377
21378 htmlContainer = domify('<div class="djs-overlay" data-overlay-id="' + id + '" style="position: absolute">');
21379
21380 htmlContainer.appendChild(html);
21381
21382 if (overlay.type) {
21383 classes(htmlContainer).add('djs-overlay-' + overlay.type);
21384 }
21385
21386 overlay.htmlContainer = htmlContainer;
21387
21388 overlayContainer.overlays.push(overlay);
21389 overlayContainer.html.appendChild(htmlContainer);
21390
21391 this._overlays[id] = overlay;
21392
21393 this._updateOverlay(overlay);
21394 this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
21395 };
21396
21397
21398 Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) {
21399 var show = overlay.show,
21400 minZoom = show && show.minZoom,
21401 maxZoom = show && show.maxZoom,
21402 htmlContainer = overlay.htmlContainer,
21403 visible = true;
21404
21405 if (show) {
21406 if (
21407 (isDefined(minZoom) && minZoom > viewbox.scale) ||
21408 (isDefined(maxZoom) && maxZoom < viewbox.scale)
21409 ) {
21410 visible = false;
21411 }
21412
21413 setVisible(htmlContainer, visible);
21414 }
21415
21416 this._updateOverlayScale(overlay, viewbox);
21417 };
21418
21419
21420 Overlays.prototype._updateOverlayScale = function(overlay, viewbox) {
21421 var shouldScale = overlay.scale,
21422 minScale,
21423 maxScale,
21424 htmlContainer = overlay.htmlContainer;
21425
21426 var scale, transform = '';
21427
21428 if (shouldScale !== true) {
21429
21430 if (shouldScale === false) {
21431 minScale = 1;
21432 maxScale = 1;
21433 } else {
21434 minScale = shouldScale.min;
21435 maxScale = shouldScale.max;
21436 }
21437
21438 if (isDefined(minScale) && viewbox.scale < minScale) {
21439 scale = (1 / viewbox.scale || 1) * minScale;
21440 }
21441
21442 if (isDefined(maxScale) && viewbox.scale > maxScale) {
21443 scale = (1 / viewbox.scale || 1) * maxScale;
21444 }
21445 }
21446
21447 if (isDefined(scale)) {
21448 transform = 'scale(' + scale + ',' + scale + ')';
21449 }
21450
21451 setTransform(htmlContainer, transform);
21452 };
21453
21454
21455 Overlays.prototype._updateOverlaysVisibilty = function(viewbox) {
21456
21457 var self = this;
21458
21459 forEach(this._overlays, function(overlay) {
21460 self._updateOverlayVisibilty(overlay, viewbox);
21461 });
21462 };
21463
21464
21465 Overlays.prototype._init = function() {
21466
21467 var eventBus = this._eventBus;
21468
21469 var self = this;
21470
21471
21472 // scroll/zoom integration
21473
21474 function updateViewbox(viewbox) {
21475 self._updateRoot(viewbox);
21476 self._updateOverlaysVisibilty(viewbox);
21477
21478 self.show();
21479 }
21480
21481 eventBus.on('canvas.viewbox.changing', function(event) {
21482 self.hide();
21483 });
21484
21485 eventBus.on('canvas.viewbox.changed', function(event) {
21486 updateViewbox(event.viewbox);
21487 });
21488
21489
21490 // remove integration
21491
21492 eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
21493 var element = e.element;
21494 var overlays = self.get({ element: element });
21495
21496 forEach(overlays, function(o) {
21497 self.remove(o.id);
21498 });
21499
21500 var container = self._getOverlayContainer(element);
21501
21502 if (container) {
21503 remove(container.html);
21504 var i = self._overlayContainers.indexOf(container);
21505 if (i !== -1) {
21506 self._overlayContainers.splice(i, 1);
21507 }
21508 }
21509 });
21510
21511
21512 // move integration
21513
21514 eventBus.on('element.changed', LOW_PRIORITY$2, function(e) {
21515 var element = e.element;
21516
21517 var container = self._getOverlayContainer(element, true);
21518
21519 if (container) {
21520 forEach(container.overlays, function(overlay) {
21521 self._updateOverlay(overlay);
21522 });
21523
21524 self._updateOverlayContainer(container);
21525 }
21526 });
21527
21528
21529 // marker integration, simply add them on the overlays as classes, too.
21530
21531 eventBus.on('element.marker.update', function(e) {
21532 var container = self._getOverlayContainer(e.element, true);
21533 if (container) {
21534 classes(container.html)[e.add ? 'add' : 'remove'](e.marker);
21535 }
21536 });
21537
21538
21539 // clear overlays with diagram
21540
21541 eventBus.on('diagram.clear', this.clear, this);
21542 };
21543
21544
21545
21546 // helpers /////////////////////////////
21547
21548 function createRoot(parentNode) {
21549 var root = domify(
21550 '<div class="djs-overlay-container" style="position: absolute; width: 0; height: 0;" />'
21551 );
21552
21553 parentNode.insertBefore(root, parentNode.firstChild);
21554
21555 return root;
21556 }
21557
21558 function setPosition(el, x, y) {
21559 assign(el.style, { left: x + 'px', top: y + 'px' });
21560 }
21561
21562 function setVisible(el, visible) {
21563 el.style.display = visible === false ? 'none' : '';
21564 }
21565
21566 function setTransform(el, transform) {
21567
21568 el.style['transform-origin'] = 'top left';
21569
21570 [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
21571 el.style[prefix + 'transform'] = transform;
21572 });
21573 }
21574
21575 var OverlaysModule = {
21576 __init__: [ 'overlays' ],
21577 overlays: [ 'type', Overlays ]
21578 };
21579
21580 /**
21581 * A viewer for BPMN 2.0 diagrams.
21582 *
21583 * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
21584 * additional features.
21585 *
21586 *
21587 * ## Extending the Viewer
21588 *
21589 * In order to extend the viewer pass extension modules to bootstrap via the
21590 * `additionalModules` option. An extension module is an object that exposes
21591 * named services.
21592 *
21593 * The following example depicts the integration of a simple
21594 * logging component that integrates with interaction events:
21595 *
21596 *
21597 * ```javascript
21598 *
21599 * // logging component
21600 * function InteractionLogger(eventBus) {
21601 * eventBus.on('element.hover', function(event) {
21602 * console.log()
21603 * })
21604 * }
21605 *
21606 * InteractionLogger.$inject = [ 'eventBus' ]; // minification save
21607 *
21608 * // extension module
21609 * var extensionModule = {
21610 * __init__: [ 'interactionLogger' ],
21611 * interactionLogger: [ 'type', InteractionLogger ]
21612 * };
21613 *
21614 * // extend the viewer
21615 * var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
21616 * bpmnViewer.importXML(...);
21617 * ```
21618 *
21619 * @param {Object} [options] configuration options to pass to the viewer
21620 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
21621 * @param {string|number} [options.width] the width of the viewer
21622 * @param {string|number} [options.height] the height of the viewer
21623 * @param {Object} [options.moddleExtensions] extension packages to provide
21624 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
21625 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
21626 */
21627 function Viewer(options) {
21628 BaseViewer.call(this, options);
21629 }
21630
21631 inherits_browser(Viewer, BaseViewer);
21632
21633 // modules the viewer is composed of
21634 Viewer.prototype._modules = [
21635 CoreModule$1,
21636 translate$2,
21637 SelectionModule,
21638 OverlaysModule
21639 ];
21640
21641 // default moddle extensions the viewer is composed of
21642 Viewer.prototype._moddleExtensions = {};
21643
21644 /**
21645 * Returns true if event was triggered with any modifier
21646 * @param {KeyboardEvent} event
21647 */
21648 function hasModifier(event) {
21649 return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey);
21650 }
21651
21652 /**
21653 * @param {KeyboardEvent} event
21654 */
21655 function isCmd(event) {
21656
21657 // ensure we don't react to AltGr
21658 // (mapped to CTRL + ALT)
21659 if (event.altKey) {
21660 return false;
21661 }
21662
21663 return event.ctrlKey || event.metaKey;
21664 }
21665
21666 /**
21667 * Checks if key pressed is one of provided keys.
21668 *
21669 * @param {string|Array<string>} keys
21670 * @param {KeyboardEvent} event
21671 */
21672 function isKey(keys, event) {
21673 keys = isArray(keys) ? keys : [ keys ];
21674
21675 return keys.indexOf(event.key) !== -1 || keys.indexOf(event.keyCode) !== -1;
21676 }
21677
21678 /**
21679 * @param {KeyboardEvent} event
21680 */
21681 function isShift(event) {
21682 return event.shiftKey;
21683 }
21684
21685 var KEYDOWN_EVENT = 'keyboard.keydown',
21686 KEYUP_EVENT = 'keyboard.keyup';
21687
21688 var DEFAULT_PRIORITY$1 = 1000;
21689
21690
21691 /**
21692 * A keyboard abstraction that may be activated and
21693 * deactivated by users at will, consuming key events
21694 * and triggering diagram actions.
21695 *
21696 * For keys pressed down, keyboard fires `keyboard.keydown` event.
21697 * The event context contains one field which is `KeyboardEvent` event.
21698 *
21699 * The implementation fires the following key events that allow
21700 * other components to hook into key handling:
21701 *
21702 * - keyboard.bind
21703 * - keyboard.unbind
21704 * - keyboard.init
21705 * - keyboard.destroy
21706 *
21707 * All events contain one field which is node.
21708 *
21709 * A default binding for the keyboard may be specified via the
21710 * `keyboard.bindTo` configuration option.
21711 *
21712 * @param {Config} config
21713 * @param {EventBus} eventBus
21714 */
21715 function Keyboard(config, eventBus) {
21716 var self = this;
21717
21718 this._config = config || {};
21719 this._eventBus = eventBus;
21720
21721 this._keydownHandler = this._keydownHandler.bind(this);
21722 this._keyupHandler = this._keyupHandler.bind(this);
21723
21724 // properly clean dom registrations
21725 eventBus.on('diagram.destroy', function() {
21726 self._fire('destroy');
21727
21728 self.unbind();
21729 });
21730
21731 eventBus.on('diagram.init', function() {
21732 self._fire('init');
21733 });
21734
21735 eventBus.on('attach', function() {
21736 if (config && config.bindTo) {
21737 self.bind(config.bindTo);
21738 }
21739 });
21740
21741 eventBus.on('detach', function() {
21742 self.unbind();
21743 });
21744 }
21745
21746 Keyboard.$inject = [
21747 'config.keyboard',
21748 'eventBus'
21749 ];
21750
21751 Keyboard.prototype._keydownHandler = function(event) {
21752 this._keyHandler(event, KEYDOWN_EVENT);
21753 };
21754
21755 Keyboard.prototype._keyupHandler = function(event) {
21756 this._keyHandler(event, KEYUP_EVENT);
21757 };
21758
21759 Keyboard.prototype._keyHandler = function(event, type) {
21760 var target = event.target,
21761 eventBusResult;
21762
21763 if (isInput(target)) {
21764 return;
21765 }
21766
21767 var context = {
21768 keyEvent: event
21769 };
21770
21771 eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context);
21772
21773 if (eventBusResult) {
21774 event.preventDefault();
21775 }
21776 };
21777
21778 Keyboard.prototype.bind = function(node) {
21779
21780 // make sure that the keyboard is only bound once to the DOM
21781 this.unbind();
21782
21783 this._node = node;
21784
21785 // bind key events
21786 componentEvent.bind(node, 'keydown', this._keydownHandler, true);
21787 componentEvent.bind(node, 'keyup', this._keyupHandler, true);
21788
21789 this._fire('bind');
21790 };
21791
21792 Keyboard.prototype.getBinding = function() {
21793 return this._node;
21794 };
21795
21796 Keyboard.prototype.unbind = function() {
21797 var node = this._node;
21798
21799 if (node) {
21800 this._fire('unbind');
21801
21802 // unbind key events
21803 componentEvent.unbind(node, 'keydown', this._keydownHandler, true);
21804 componentEvent.unbind(node, 'keyup', this._keyupHandler, true);
21805 }
21806
21807 this._node = null;
21808 };
21809
21810 Keyboard.prototype._fire = function(event) {
21811 this._eventBus.fire('keyboard.' + event, { node: this._node });
21812 };
21813
21814 /**
21815 * Add a listener function that is notified with `KeyboardEvent` whenever
21816 * the keyboard is bound and the user presses a key. If no priority is
21817 * provided, the default value of 1000 is used.
21818 *
21819 * @param {number} [priority]
21820 * @param {Function} listener
21821 * @param {string} type
21822 */
21823 Keyboard.prototype.addListener = function(priority, listener, type) {
21824 if (isFunction(priority)) {
21825 type = listener;
21826 listener = priority;
21827 priority = DEFAULT_PRIORITY$1;
21828 }
21829
21830 this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
21831 };
21832
21833 Keyboard.prototype.removeListener = function(listener, type) {
21834 this._eventBus.off(type || KEYDOWN_EVENT, listener);
21835 };
21836
21837 Keyboard.prototype.hasModifier = hasModifier;
21838 Keyboard.prototype.isCmd = isCmd;
21839 Keyboard.prototype.isShift = isShift;
21840 Keyboard.prototype.isKey = isKey;
21841
21842
21843
21844 // helpers ///////
21845
21846 function isInput(target) {
21847 return target && (matchesSelector(target, 'input, textarea') || target.contentEditable === 'true');
21848 }
21849
21850 var LOW_PRIORITY$3 = 500;
21851
21852 var KEYCODE_C = 67;
21853 var KEYCODE_V = 86;
21854 var KEYCODE_Y = 89;
21855 var KEYCODE_Z = 90;
21856
21857 var KEYS_COPY = ['c', 'C', KEYCODE_C ];
21858 var KEYS_PASTE = [ 'v', 'V', KEYCODE_V ];
21859 var KEYS_REDO = [ 'y', 'Y', KEYCODE_Y ];
21860 var KEYS_UNDO = [ 'z', 'Z', KEYCODE_Z ];
21861
21862
21863 /**
21864 * Adds default keyboard bindings.
21865 *
21866 * This does not pull in any features will bind only actions that
21867 * have previously been registered against the editorActions component.
21868 *
21869 * @param {EventBus} eventBus
21870 * @param {Keyboard} keyboard
21871 */
21872 function KeyboardBindings(eventBus, keyboard) {
21873
21874 var self = this;
21875
21876 eventBus.on('editorActions.init', LOW_PRIORITY$3, function(event) {
21877
21878 var editorActions = event.editorActions;
21879
21880 self.registerBindings(keyboard, editorActions);
21881 });
21882 }
21883
21884 KeyboardBindings.$inject = [
21885 'eventBus',
21886 'keyboard'
21887 ];
21888
21889
21890 /**
21891 * Register available keyboard bindings.
21892 *
21893 * @param {Keyboard} keyboard
21894 * @param {EditorActions} editorActions
21895 */
21896 KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
21897
21898 /**
21899 * Add keyboard binding if respective editor action
21900 * is registered.
21901 *
21902 * @param {string} action name
21903 * @param {Function} fn that implements the key binding
21904 */
21905 function addListener(action, fn) {
21906
21907 if (editorActions.isRegistered(action)) {
21908 keyboard.addListener(fn);
21909 }
21910 }
21911
21912
21913 // undo
21914 // (CTRL|CMD) + Z
21915 addListener('undo', function(context) {
21916
21917 var event = context.keyEvent;
21918
21919 if (isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event)) {
21920 editorActions.trigger('undo');
21921
21922 return true;
21923 }
21924 });
21925
21926 // redo
21927 // CTRL + Y
21928 // CMD + SHIFT + Z
21929 addListener('redo', function(context) {
21930
21931 var event = context.keyEvent;
21932
21933 if (isCmd(event) && (isKey(KEYS_REDO, event) || (isKey(KEYS_UNDO, event) && isShift(event)))) {
21934 editorActions.trigger('redo');
21935
21936 return true;
21937 }
21938 });
21939
21940 // copy
21941 // CTRL/CMD + C
21942 addListener('copy', function(context) {
21943
21944 var event = context.keyEvent;
21945
21946 if (isCmd(event) && isKey(KEYS_COPY, event)) {
21947 editorActions.trigger('copy');
21948
21949 return true;
21950 }
21951 });
21952
21953 // paste
21954 // CTRL/CMD + V
21955 addListener('paste', function(context) {
21956
21957 var event = context.keyEvent;
21958
21959 if (isCmd(event) && isKey(KEYS_PASTE, event)) {
21960 editorActions.trigger('paste');
21961
21962 return true;
21963 }
21964 });
21965
21966 // zoom in one step
21967 // CTRL/CMD + +
21968 addListener('stepZoom', function(context) {
21969
21970 var event = context.keyEvent;
21971
21972 // quirk: it has to be triggered by `=` as well to work on international keyboard layout
21973 // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
21974 if (isKey([ '+', 'Add', '=' ], event) && isCmd(event)) {
21975 editorActions.trigger('stepZoom', { value: 1 });
21976
21977 return true;
21978 }
21979 });
21980
21981 // zoom out one step
21982 // CTRL + -
21983 addListener('stepZoom', function(context) {
21984
21985 var event = context.keyEvent;
21986
21987 if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) {
21988 editorActions.trigger('stepZoom', { value: -1 });
21989
21990 return true;
21991 }
21992 });
21993
21994 // zoom to the default level
21995 // CTRL + 0
21996 addListener('zoom', function(context) {
21997
21998 var event = context.keyEvent;
21999
22000 if (isKey('0', event) && isCmd(event)) {
22001 editorActions.trigger('zoom', { value: 1 });
22002
22003 return true;
22004 }
22005 });
22006
22007 // delete selected element
22008 // DEL
22009 addListener('removeSelection', function(context) {
22010
22011 var event = context.keyEvent;
22012
22013 if (isKey(['Backspace', 'Delete', 'Del' ], event)) {
22014 editorActions.trigger('removeSelection');
22015
22016 return true;
22017 }
22018 });
22019 };
22020
22021 var KeyboardModule = {
22022 __init__: [ 'keyboard', 'keyboardBindings' ],
22023 keyboard: [ 'type', Keyboard ],
22024 keyboardBindings: [ 'type', KeyboardBindings ]
22025 };
22026
22027 var DEFAULT_CONFIG = {
22028 moveSpeed: 50,
22029 moveSpeedAccelerated: 200
22030 };
22031
22032
22033 /**
22034 * A feature that allows users to move the canvas using the keyboard.
22035 *
22036 * @param {Object} config
22037 * @param {number} [config.moveSpeed=50]
22038 * @param {number} [config.moveSpeedAccelerated=200]
22039 * @param {Keyboard} keyboard
22040 * @param {Canvas} canvas
22041 */
22042 function KeyboardMove(
22043 config,
22044 keyboard,
22045 canvas
22046 ) {
22047
22048 var self = this;
22049
22050 this._config = assign({}, DEFAULT_CONFIG, config || {});
22051
22052 keyboard.addListener(arrowsListener);
22053
22054
22055 function arrowsListener(context) {
22056
22057 var event = context.keyEvent,
22058 config = self._config;
22059
22060 if (!keyboard.isCmd(event)) {
22061 return;
22062 }
22063
22064 if (keyboard.isKey([
22065 'ArrowLeft', 'Left',
22066 'ArrowUp', 'Up',
22067 'ArrowDown', 'Down',
22068 'ArrowRight', 'Right'
22069 ], event)) {
22070
22071 var speed = (
22072 keyboard.isShift(event) ?
22073 config.moveSpeedAccelerated :
22074 config.moveSpeed
22075 );
22076
22077 var direction;
22078
22079 switch (event.key) {
22080 case 'ArrowLeft':
22081 case 'Left':
22082 direction = 'left';
22083 break;
22084 case 'ArrowUp':
22085 case 'Up':
22086 direction = 'up';
22087 break;
22088 case 'ArrowRight':
22089 case 'Right':
22090 direction = 'right';
22091 break;
22092 case 'ArrowDown':
22093 case 'Down':
22094 direction = 'down';
22095 break;
22096 }
22097
22098 self.moveCanvas({
22099 speed: speed,
22100 direction: direction
22101 });
22102
22103 return true;
22104 }
22105 }
22106
22107 this.moveCanvas = function(opts) {
22108
22109 var dx = 0,
22110 dy = 0,
22111 speed = opts.speed;
22112
22113 var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1);
22114
22115 switch (opts.direction) {
22116 case 'left': // Left
22117 dx = actualSpeed;
22118 break;
22119 case 'up': // Up
22120 dy = actualSpeed;
22121 break;
22122 case 'right': // Right
22123 dx = -actualSpeed;
22124 break;
22125 case 'down': // Down
22126 dy = -actualSpeed;
22127 break;
22128 }
22129
22130 canvas.scroll({
22131 dx: dx,
22132 dy: dy
22133 });
22134 };
22135
22136 }
22137
22138
22139 KeyboardMove.$inject = [
22140 'config.keyboardMove',
22141 'keyboard',
22142 'canvas'
22143 ];
22144
22145 var KeyboardMoveModule = {
22146 __depends__: [
22147 KeyboardModule
22148 ],
22149 __init__: [ 'keyboardMove' ],
22150 keyboardMove: [ 'type', KeyboardMove ]
22151 };
22152
22153 var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/;
22154
22155
22156 function set$1(mode) {
22157 var classes$1 = classes(document.body);
22158
22159 classes$1.removeMatching(CURSOR_CLS_PATTERN);
22160
22161 if (mode) {
22162 classes$1.add('djs-cursor-' + mode);
22163 }
22164 }
22165
22166 function unset() {
22167 set$1(null);
22168 }
22169
22170 var TRAP_PRIORITY = 5000;
22171
22172 /**
22173 * Installs a click trap that prevents a ghost click following a dragging operation.
22174 *
22175 * @return {Function} a function to immediately remove the installed trap.
22176 */
22177 function install(eventBus, eventName) {
22178
22179 eventName = eventName || 'element.click';
22180
22181 function trap() {
22182 return false;
22183 }
22184
22185 eventBus.once(eventName, TRAP_PRIORITY, trap);
22186
22187 return function() {
22188 eventBus.off(eventName, trap);
22189 };
22190 }
22191
22192 function center(bounds) {
22193 return {
22194 x: bounds.x + (bounds.width / 2),
22195 y: bounds.y + (bounds.height / 2)
22196 };
22197 }
22198
22199
22200 function delta(a, b) {
22201 return {
22202 x: a.x - b.x,
22203 y: a.y - b.y
22204 };
22205 }
22206
22207 var THRESHOLD = 15;
22208
22209
22210 /**
22211 * Move the canvas via mouse.
22212 *
22213 * @param {EventBus} eventBus
22214 * @param {Canvas} canvas
22215 */
22216 function MoveCanvas(eventBus, canvas) {
22217
22218 var context;
22219
22220
22221 // listen for move on element mouse down;
22222 // allow others to hook into the event before us though
22223 // (dragging / element moving will do this)
22224 eventBus.on('element.mousedown', 500, function(e) {
22225 return handleStart(e.originalEvent);
22226 });
22227
22228
22229 function handleMove(event) {
22230
22231 var start = context.start,
22232 button = context.button,
22233 position = toPoint(event),
22234 delta$1 = delta(position, start);
22235
22236 if (!context.dragging && length(delta$1) > THRESHOLD) {
22237 context.dragging = true;
22238
22239 if (button === 0) {
22240 install(eventBus);
22241 }
22242
22243 set$1('grab');
22244 }
22245
22246 if (context.dragging) {
22247
22248 var lastPosition = context.last || context.start;
22249
22250 delta$1 = delta(position, lastPosition);
22251
22252 canvas.scroll({
22253 dx: delta$1.x,
22254 dy: delta$1.y
22255 });
22256
22257 context.last = position;
22258 }
22259
22260 // prevent select
22261 event.preventDefault();
22262 }
22263
22264
22265 function handleEnd(event) {
22266 componentEvent.unbind(document, 'mousemove', handleMove);
22267 componentEvent.unbind(document, 'mouseup', handleEnd);
22268
22269 context = null;
22270
22271 unset();
22272 }
22273
22274 function handleStart(event) {
22275
22276 // event is already handled by '.djs-draggable'
22277 if (closest(event.target, '.djs-draggable')) {
22278 return;
22279 }
22280
22281 var button = event.button;
22282
22283 // reject right mouse button or modifier key
22284 if (button >= 2 || event.ctrlKey || event.shiftKey || event.altKey) {
22285 return;
22286 }
22287
22288 context = {
22289 button: button,
22290 start: toPoint(event)
22291 };
22292
22293 componentEvent.bind(document, 'mousemove', handleMove);
22294 componentEvent.bind(document, 'mouseup', handleEnd);
22295
22296 // we've handled the event
22297 return true;
22298 }
22299
22300 this.isActive = function() {
22301 return !!context;
22302 };
22303
22304 }
22305
22306
22307 MoveCanvas.$inject = [
22308 'eventBus',
22309 'canvas'
22310 ];
22311
22312
22313
22314 // helpers ///////
22315
22316 function length(point) {
22317 return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
22318 }
22319
22320 var MoveCanvasModule = {
22321 __init__: [ 'moveCanvas' ],
22322 moveCanvas: [ 'type', MoveCanvas ]
22323 };
22324
22325 /**
22326 * Get the logarithm of x with base 10
22327 * @param {Integer} value
22328 */
22329 function log10(x) {
22330 return Math.log(x) / Math.log(10);
22331 }
22332
22333 /**
22334 * Get step size for given range and number of steps.
22335 *
22336 * @param {Object} range
22337 * @param {number} range.min
22338 * @param {number} range.max
22339 */
22340 function getStepSize(range, steps) {
22341
22342 var minLinearRange = log10(range.min),
22343 maxLinearRange = log10(range.max);
22344
22345 var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);
22346
22347 return absoluteLinearRange / steps;
22348 }
22349
22350 function cap(range, scale) {
22351 return Math.max(range.min, Math.min(range.max, scale));
22352 }
22353
22354 var sign = Math.sign || function(n) {
22355 return n >= 0 ? 1 : -1;
22356 };
22357
22358 var RANGE = { min: 0.2, max: 4 },
22359 NUM_STEPS = 10;
22360
22361 var DELTA_THRESHOLD = 0.1;
22362
22363 var DEFAULT_SCALE = 0.75;
22364
22365 /**
22366 * An implementation of zooming and scrolling within the
22367 * {@link Canvas} via the mouse wheel.
22368 *
22369 * Mouse wheel zooming / scrolling may be disabled using
22370 * the {@link toggle(enabled)} method.
22371 *
22372 * @param {Object} [config]
22373 * @param {boolean} [config.enabled=true] default enabled state
22374 * @param {number} [config.scale=.75] scroll sensivity
22375 * @param {EventBus} eventBus
22376 * @param {Canvas} canvas
22377 */
22378 function ZoomScroll(config, eventBus, canvas) {
22379
22380 config = config || {};
22381
22382 this._enabled = false;
22383
22384 this._canvas = canvas;
22385 this._container = canvas._container;
22386
22387 this._handleWheel = bind(this._handleWheel, this);
22388
22389 this._totalDelta = 0;
22390 this._scale = config.scale || DEFAULT_SCALE;
22391
22392 var self = this;
22393
22394 eventBus.on('canvas.init', function(e) {
22395 self._init(config.enabled !== false);
22396 });
22397 }
22398
22399 ZoomScroll.$inject = [
22400 'config.zoomScroll',
22401 'eventBus',
22402 'canvas'
22403 ];
22404
22405 ZoomScroll.prototype.scroll = function scroll(delta) {
22406 this._canvas.scroll(delta);
22407 };
22408
22409
22410 ZoomScroll.prototype.reset = function reset() {
22411 this._canvas.zoom('fit-viewport');
22412 };
22413
22414 /**
22415 * Zoom depending on delta.
22416 *
22417 * @param {number} delta
22418 * @param {Object} position
22419 */
22420 ZoomScroll.prototype.zoom = function zoom(delta, position) {
22421
22422 // zoom with half the step size of stepZoom
22423 var stepSize = getStepSize(RANGE, NUM_STEPS * 2);
22424
22425 // add until threshold reached
22426 this._totalDelta += delta;
22427
22428 if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) {
22429 this._zoom(delta, position, stepSize);
22430
22431 // reset
22432 this._totalDelta = 0;
22433 }
22434 };
22435
22436
22437 ZoomScroll.prototype._handleWheel = function handleWheel(event) {
22438
22439 // event is already handled by '.djs-scrollable'
22440 if (closest(event.target, '.djs-scrollable', true)) {
22441 return;
22442 }
22443
22444 var element = this._container;
22445
22446 event.preventDefault();
22447
22448 // pinch to zoom is mapped to wheel + ctrlKey = true
22449 // in modern browsers (!)
22450
22451 var isZoom = event.ctrlKey;
22452
22453 var isHorizontalScroll = event.shiftKey;
22454
22455 var factor = -1 * this._scale,
22456 delta;
22457
22458 if (isZoom) {
22459 factor *= event.deltaMode === 0 ? 0.020 : 0.32;
22460 } else {
22461 factor *= event.deltaMode === 0 ? 1.0 : 16.0;
22462 }
22463
22464 if (isZoom) {
22465 var elementRect = element.getBoundingClientRect();
22466
22467 var offset = {
22468 x: event.clientX - elementRect.left,
22469 y: event.clientY - elementRect.top
22470 };
22471
22472 delta = (
22473 Math.sqrt(
22474 Math.pow(event.deltaY, 2) +
22475 Math.pow(event.deltaX, 2)
22476 ) * sign(event.deltaY) * factor
22477 );
22478
22479 // zoom in relative to diagram {x,y} coordinates
22480 this.zoom(delta, offset);
22481 } else {
22482
22483 if (isHorizontalScroll) {
22484 delta = {
22485 dx: factor * event.deltaY,
22486 dy: 0
22487 };
22488 } else {
22489 delta = {
22490 dx: factor * event.deltaX,
22491 dy: factor * event.deltaY
22492 };
22493 }
22494
22495 this.scroll(delta);
22496 }
22497 };
22498
22499 /**
22500 * Zoom with fixed step size.
22501 *
22502 * @param {number} delta - Zoom delta (1 for zooming in, -1 for out).
22503 * @param {Object} position
22504 */
22505 ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) {
22506
22507 var stepSize = getStepSize(RANGE, NUM_STEPS);
22508
22509 this._zoom(delta, position, stepSize);
22510 };
22511
22512
22513 /**
22514 * Zoom in/out given a step size.
22515 *
22516 * @param {number} delta
22517 * @param {Object} position
22518 * @param {number} stepSize
22519 */
22520 ZoomScroll.prototype._zoom = function(delta, position, stepSize) {
22521 var canvas = this._canvas;
22522
22523 var direction = delta > 0 ? 1 : -1;
22524
22525 var currentLinearZoomLevel = log10(canvas.zoom());
22526
22527 // snap to a proximate zoom step
22528 var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize;
22529
22530 // increase or decrease one zoom step in the given direction
22531 newLinearZoomLevel += stepSize * direction;
22532
22533 // calculate the absolute logarithmic zoom level based on the linear zoom level
22534 // (e.g. 2 for an absolute x2 zoom)
22535 var newLogZoomLevel = Math.pow(10, newLinearZoomLevel);
22536
22537 canvas.zoom(cap(RANGE, newLogZoomLevel), position);
22538 };
22539
22540
22541 /**
22542 * Toggle the zoom scroll ability via mouse wheel.
22543 *
22544 * @param {boolean} [newEnabled] new enabled state
22545 */
22546 ZoomScroll.prototype.toggle = function toggle(newEnabled) {
22547
22548 var element = this._container;
22549 var handleWheel = this._handleWheel;
22550
22551 var oldEnabled = this._enabled;
22552
22553 if (typeof newEnabled === 'undefined') {
22554 newEnabled = !oldEnabled;
22555 }
22556
22557 // only react on actual changes
22558 if (oldEnabled !== newEnabled) {
22559
22560 // add or remove wheel listener based on
22561 // changed enabled state
22562 componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false);
22563 }
22564
22565 this._enabled = newEnabled;
22566
22567 return newEnabled;
22568 };
22569
22570
22571 ZoomScroll.prototype._init = function(newEnabled) {
22572 this.toggle(newEnabled);
22573 };
22574
22575 var ZoomScrollModule = {
22576 __init__: [ 'zoomScroll' ],
22577 zoomScroll: [ 'type', ZoomScroll ]
22578 };
22579
22580 /**
22581 * A viewer that includes mouse navigation facilities
22582 *
22583 * @param {Object} options
22584 */
22585 function NavigatedViewer(options) {
22586 Viewer.call(this, options);
22587 }
22588
22589 inherits_browser(NavigatedViewer, Viewer);
22590
22591
22592 NavigatedViewer.prototype._navigationModules = [
22593 KeyboardMoveModule,
22594 MoveCanvasModule,
22595 ZoomScrollModule
22596 ];
22597
22598 NavigatedViewer.prototype._modules = [].concat(
22599 Viewer.prototype._modules,
22600 NavigatedViewer.prototype._navigationModules
22601 );
22602
22603 /*! Hammer.JS - v2.0.7 - 2016-04-22
22604 * http://hammerjs.github.io/
22605 *
22606 * Copyright (c) 2016 Jorik Tangelder;
22607 * Licensed under the MIT license */
22608
22609 var hammer = createCommonjsModule(function (module) {
22610 (function(window, document, exportName, undefined$1) {
22611
22612 var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
22613 var TEST_ELEMENT = document.createElement('div');
22614
22615 var TYPE_FUNCTION = 'function';
22616
22617 var round = Math.round;
22618 var abs = Math.abs;
22619 var now = Date.now;
22620
22621 /**
22622 * set a timeout with a given scope
22623 * @param {Function} fn
22624 * @param {Number} timeout
22625 * @param {Object} context
22626 * @returns {number}
22627 */
22628 function setTimeoutContext(fn, timeout, context) {
22629 return setTimeout(bindFn(fn, context), timeout);
22630 }
22631
22632 /**
22633 * if the argument is an array, we want to execute the fn on each entry
22634 * if it aint an array we don't want to do a thing.
22635 * this is used by all the methods that accept a single and array argument.
22636 * @param {*|Array} arg
22637 * @param {String} fn
22638 * @param {Object} [context]
22639 * @returns {Boolean}
22640 */
22641 function invokeArrayArg(arg, fn, context) {
22642 if (Array.isArray(arg)) {
22643 each(arg, context[fn], context);
22644 return true;
22645 }
22646 return false;
22647 }
22648
22649 /**
22650 * walk objects and arrays
22651 * @param {Object} obj
22652 * @param {Function} iterator
22653 * @param {Object} context
22654 */
22655 function each(obj, iterator, context) {
22656 var i;
22657
22658 if (!obj) {
22659 return;
22660 }
22661
22662 if (obj.forEach) {
22663 obj.forEach(iterator, context);
22664 } else if (obj.length !== undefined$1) {
22665 i = 0;
22666 while (i < obj.length) {
22667 iterator.call(context, obj[i], i, obj);
22668 i++;
22669 }
22670 } else {
22671 for (i in obj) {
22672 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
22673 }
22674 }
22675 }
22676
22677 /**
22678 * wrap a method with a deprecation warning and stack trace
22679 * @param {Function} method
22680 * @param {String} name
22681 * @param {String} message
22682 * @returns {Function} A new function wrapping the supplied method.
22683 */
22684 function deprecate(method, name, message) {
22685 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
22686 return function() {
22687 var e = new Error('get-stack-trace');
22688 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
22689 .replace(/^\s+at\s+/gm, '')
22690 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
22691
22692 var log = window.console && (window.console.warn || window.console.log);
22693 if (log) {
22694 log.call(window.console, deprecationMessage, stack);
22695 }
22696 return method.apply(this, arguments);
22697 };
22698 }
22699
22700 /**
22701 * extend object.
22702 * means that properties in dest will be overwritten by the ones in src.
22703 * @param {Object} target
22704 * @param {...Object} objects_to_assign
22705 * @returns {Object} target
22706 */
22707 var assign;
22708 if (typeof Object.assign !== 'function') {
22709 assign = function assign(target) {
22710 if (target === undefined$1 || target === null) {
22711 throw new TypeError('Cannot convert undefined or null to object');
22712 }
22713
22714 var output = Object(target);
22715 for (var index = 1; index < arguments.length; index++) {
22716 var source = arguments[index];
22717 if (source !== undefined$1 && source !== null) {
22718 for (var nextKey in source) {
22719 if (source.hasOwnProperty(nextKey)) {
22720 output[nextKey] = source[nextKey];
22721 }
22722 }
22723 }
22724 }
22725 return output;
22726 };
22727 } else {
22728 assign = Object.assign;
22729 }
22730
22731 /**
22732 * extend object.
22733 * means that properties in dest will be overwritten by the ones in src.
22734 * @param {Object} dest
22735 * @param {Object} src
22736 * @param {Boolean} [merge=false]
22737 * @returns {Object} dest
22738 */
22739 var extend = deprecate(function extend(dest, src, merge) {
22740 var keys = Object.keys(src);
22741 var i = 0;
22742 while (i < keys.length) {
22743 if (!merge || (merge && dest[keys[i]] === undefined$1)) {
22744 dest[keys[i]] = src[keys[i]];
22745 }
22746 i++;
22747 }
22748 return dest;
22749 }, 'extend', 'Use `assign`.');
22750
22751 /**
22752 * merge the values from src in the dest.
22753 * means that properties that exist in dest will not be overwritten by src
22754 * @param {Object} dest
22755 * @param {Object} src
22756 * @returns {Object} dest
22757 */
22758 var merge = deprecate(function merge(dest, src) {
22759 return extend(dest, src, true);
22760 }, 'merge', 'Use `assign`.');
22761
22762 /**
22763 * simple class inheritance
22764 * @param {Function} child
22765 * @param {Function} base
22766 * @param {Object} [properties]
22767 */
22768 function inherit(child, base, properties) {
22769 var baseP = base.prototype,
22770 childP;
22771
22772 childP = child.prototype = Object.create(baseP);
22773 childP.constructor = child;
22774 childP._super = baseP;
22775
22776 if (properties) {
22777 assign(childP, properties);
22778 }
22779 }
22780
22781 /**
22782 * simple function bind
22783 * @param {Function} fn
22784 * @param {Object} context
22785 * @returns {Function}
22786 */
22787 function bindFn(fn, context) {
22788 return function boundFn() {
22789 return fn.apply(context, arguments);
22790 };
22791 }
22792
22793 /**
22794 * let a boolean value also be a function that must return a boolean
22795 * this first item in args will be used as the context
22796 * @param {Boolean|Function} val
22797 * @param {Array} [args]
22798 * @returns {Boolean}
22799 */
22800 function boolOrFn(val, args) {
22801 if (typeof val == TYPE_FUNCTION) {
22802 return val.apply(args ? args[0] || undefined$1 : undefined$1, args);
22803 }
22804 return val;
22805 }
22806
22807 /**
22808 * use the val2 when val1 is undefined
22809 * @param {*} val1
22810 * @param {*} val2
22811 * @returns {*}
22812 */
22813 function ifUndefined(val1, val2) {
22814 return (val1 === undefined$1) ? val2 : val1;
22815 }
22816
22817 /**
22818 * addEventListener with multiple events at once
22819 * @param {EventTarget} target
22820 * @param {String} types
22821 * @param {Function} handler
22822 */
22823 function addEventListeners(target, types, handler) {
22824 each(splitStr(types), function(type) {
22825 target.addEventListener(type, handler, false);
22826 });
22827 }
22828
22829 /**
22830 * removeEventListener with multiple events at once
22831 * @param {EventTarget} target
22832 * @param {String} types
22833 * @param {Function} handler
22834 */
22835 function removeEventListeners(target, types, handler) {
22836 each(splitStr(types), function(type) {
22837 target.removeEventListener(type, handler, false);
22838 });
22839 }
22840
22841 /**
22842 * find if a node is in the given parent
22843 * @method hasParent
22844 * @param {HTMLElement} node
22845 * @param {HTMLElement} parent
22846 * @return {Boolean} found
22847 */
22848 function hasParent(node, parent) {
22849 while (node) {
22850 if (node == parent) {
22851 return true;
22852 }
22853 node = node.parentNode;
22854 }
22855 return false;
22856 }
22857
22858 /**
22859 * small indexOf wrapper
22860 * @param {String} str
22861 * @param {String} find
22862 * @returns {Boolean} found
22863 */
22864 function inStr(str, find) {
22865 return str.indexOf(find) > -1;
22866 }
22867
22868 /**
22869 * split string on whitespace
22870 * @param {String} str
22871 * @returns {Array} words
22872 */
22873 function splitStr(str) {
22874 return str.trim().split(/\s+/g);
22875 }
22876
22877 /**
22878 * find if a array contains the object using indexOf or a simple polyFill
22879 * @param {Array} src
22880 * @param {String} find
22881 * @param {String} [findByKey]
22882 * @return {Boolean|Number} false when not found, or the index
22883 */
22884 function inArray(src, find, findByKey) {
22885 if (src.indexOf && !findByKey) {
22886 return src.indexOf(find);
22887 } else {
22888 var i = 0;
22889 while (i < src.length) {
22890 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
22891 return i;
22892 }
22893 i++;
22894 }
22895 return -1;
22896 }
22897 }
22898
22899 /**
22900 * convert array-like objects to real arrays
22901 * @param {Object} obj
22902 * @returns {Array}
22903 */
22904 function toArray(obj) {
22905 return Array.prototype.slice.call(obj, 0);
22906 }
22907
22908 /**
22909 * unique array with objects based on a key (like 'id') or just by the array's value
22910 * @param {Array} src [{id:1},{id:2},{id:1}]
22911 * @param {String} [key]
22912 * @param {Boolean} [sort=False]
22913 * @returns {Array} [{id:1},{id:2}]
22914 */
22915 function uniqueArray(src, key, sort) {
22916 var results = [];
22917 var values = [];
22918 var i = 0;
22919
22920 while (i < src.length) {
22921 var val = key ? src[i][key] : src[i];
22922 if (inArray(values, val) < 0) {
22923 results.push(src[i]);
22924 }
22925 values[i] = val;
22926 i++;
22927 }
22928
22929 if (sort) {
22930 if (!key) {
22931 results = results.sort();
22932 } else {
22933 results = results.sort(function sortUniqueArray(a, b) {
22934 return a[key] > b[key];
22935 });
22936 }
22937 }
22938
22939 return results;
22940 }
22941
22942 /**
22943 * get the prefixed property
22944 * @param {Object} obj
22945 * @param {String} property
22946 * @returns {String|Undefined} prefixed
22947 */
22948 function prefixed(obj, property) {
22949 var prefix, prop;
22950 var camelProp = property[0].toUpperCase() + property.slice(1);
22951
22952 var i = 0;
22953 while (i < VENDOR_PREFIXES.length) {
22954 prefix = VENDOR_PREFIXES[i];
22955 prop = (prefix) ? prefix + camelProp : property;
22956
22957 if (prop in obj) {
22958 return prop;
22959 }
22960 i++;
22961 }
22962 return undefined$1;
22963 }
22964
22965 /**
22966 * get a unique id
22967 * @returns {number} uniqueId
22968 */
22969 var _uniqueId = 1;
22970 function uniqueId() {
22971 return _uniqueId++;
22972 }
22973
22974 /**
22975 * get the window object of an element
22976 * @param {HTMLElement} element
22977 * @returns {DocumentView|Window}
22978 */
22979 function getWindowForElement(element) {
22980 var doc = element.ownerDocument || element;
22981 return (doc.defaultView || doc.parentWindow || window);
22982 }
22983
22984 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
22985
22986 var SUPPORT_TOUCH = ('ontouchstart' in window);
22987 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined$1;
22988 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
22989
22990 var INPUT_TYPE_TOUCH = 'touch';
22991 var INPUT_TYPE_PEN = 'pen';
22992 var INPUT_TYPE_MOUSE = 'mouse';
22993 var INPUT_TYPE_KINECT = 'kinect';
22994
22995 var COMPUTE_INTERVAL = 25;
22996
22997 var INPUT_START = 1;
22998 var INPUT_MOVE = 2;
22999 var INPUT_END = 4;
23000 var INPUT_CANCEL = 8;
23001
23002 var DIRECTION_NONE = 1;
23003 var DIRECTION_LEFT = 2;
23004 var DIRECTION_RIGHT = 4;
23005 var DIRECTION_UP = 8;
23006 var DIRECTION_DOWN = 16;
23007
23008 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
23009 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
23010 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
23011
23012 var PROPS_XY = ['x', 'y'];
23013 var PROPS_CLIENT_XY = ['clientX', 'clientY'];
23014
23015 /**
23016 * create new input type manager
23017 * @param {Manager} manager
23018 * @param {Function} callback
23019 * @returns {Input}
23020 * @constructor
23021 */
23022 function Input(manager, callback) {
23023 var self = this;
23024 this.manager = manager;
23025 this.callback = callback;
23026 this.element = manager.element;
23027 this.target = manager.options.inputTarget;
23028
23029 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
23030 // so when disabled the input events are completely bypassed.
23031 this.domHandler = function(ev) {
23032 if (boolOrFn(manager.options.enable, [manager])) {
23033 self.handler(ev);
23034 }
23035 };
23036
23037 this.init();
23038
23039 }
23040
23041 Input.prototype = {
23042 /**
23043 * should handle the inputEvent data and trigger the callback
23044 * @virtual
23045 */
23046 handler: function() { },
23047
23048 /**
23049 * bind the events
23050 */
23051 init: function() {
23052 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
23053 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
23054 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
23055 },
23056
23057 /**
23058 * unbind the events
23059 */
23060 destroy: function() {
23061 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
23062 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
23063 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
23064 }
23065 };
23066
23067 /**
23068 * create new input type manager
23069 * called by the Manager constructor
23070 * @param {Hammer} manager
23071 * @returns {Input}
23072 */
23073 function createInputInstance(manager) {
23074 var Type;
23075 var inputClass = manager.options.inputClass;
23076
23077 if (inputClass) {
23078 Type = inputClass;
23079 } else if (SUPPORT_POINTER_EVENTS) {
23080 Type = PointerEventInput;
23081 } else if (SUPPORT_ONLY_TOUCH) {
23082 Type = TouchInput;
23083 } else if (!SUPPORT_TOUCH) {
23084 Type = MouseInput;
23085 } else {
23086 Type = TouchMouseInput;
23087 }
23088 return new (Type)(manager, inputHandler);
23089 }
23090
23091 /**
23092 * handle input events
23093 * @param {Manager} manager
23094 * @param {String} eventType
23095 * @param {Object} input
23096 */
23097 function inputHandler(manager, eventType, input) {
23098 var pointersLen = input.pointers.length;
23099 var changedPointersLen = input.changedPointers.length;
23100 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
23101 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
23102
23103 input.isFirst = !!isFirst;
23104 input.isFinal = !!isFinal;
23105
23106 if (isFirst) {
23107 manager.session = {};
23108 }
23109
23110 // source event is the normalized value of the domEvents
23111 // like 'touchstart, mouseup, pointerdown'
23112 input.eventType = eventType;
23113
23114 // compute scale, rotation etc
23115 computeInputData(manager, input);
23116
23117 // emit secret event
23118 manager.emit('hammer.input', input);
23119
23120 manager.recognize(input);
23121 manager.session.prevInput = input;
23122 }
23123
23124 /**
23125 * extend the data with some usable properties like scale, rotate, velocity etc
23126 * @param {Object} manager
23127 * @param {Object} input
23128 */
23129 function computeInputData(manager, input) {
23130 var session = manager.session;
23131 var pointers = input.pointers;
23132 var pointersLength = pointers.length;
23133
23134 // store the first input to calculate the distance and direction
23135 if (!session.firstInput) {
23136 session.firstInput = simpleCloneInputData(input);
23137 }
23138
23139 // to compute scale and rotation we need to store the multiple touches
23140 if (pointersLength > 1 && !session.firstMultiple) {
23141 session.firstMultiple = simpleCloneInputData(input);
23142 } else if (pointersLength === 1) {
23143 session.firstMultiple = false;
23144 }
23145
23146 var firstInput = session.firstInput;
23147 var firstMultiple = session.firstMultiple;
23148 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
23149
23150 var center = input.center = getCenter(pointers);
23151 input.timeStamp = now();
23152 input.deltaTime = input.timeStamp - firstInput.timeStamp;
23153
23154 input.angle = getAngle(offsetCenter, center);
23155 input.distance = getDistance(offsetCenter, center);
23156
23157 computeDeltaXY(session, input);
23158 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
23159
23160 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
23161 input.overallVelocityX = overallVelocity.x;
23162 input.overallVelocityY = overallVelocity.y;
23163 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
23164
23165 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
23166 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
23167
23168 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
23169 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
23170
23171 computeIntervalInputData(session, input);
23172
23173 // find the correct target
23174 var target = manager.element;
23175 if (hasParent(input.srcEvent.target, target)) {
23176 target = input.srcEvent.target;
23177 }
23178 input.target = target;
23179 }
23180
23181 function computeDeltaXY(session, input) {
23182 var center = input.center;
23183 var offset = session.offsetDelta || {};
23184 var prevDelta = session.prevDelta || {};
23185 var prevInput = session.prevInput || {};
23186
23187 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
23188 prevDelta = session.prevDelta = {
23189 x: prevInput.deltaX || 0,
23190 y: prevInput.deltaY || 0
23191 };
23192
23193 offset = session.offsetDelta = {
23194 x: center.x,
23195 y: center.y
23196 };
23197 }
23198
23199 input.deltaX = prevDelta.x + (center.x - offset.x);
23200 input.deltaY = prevDelta.y + (center.y - offset.y);
23201 }
23202
23203 /**
23204 * velocity is calculated every x ms
23205 * @param {Object} session
23206 * @param {Object} input
23207 */
23208 function computeIntervalInputData(session, input) {
23209 var last = session.lastInterval || input,
23210 deltaTime = input.timeStamp - last.timeStamp,
23211 velocity, velocityX, velocityY, direction;
23212
23213 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined$1)) {
23214 var deltaX = input.deltaX - last.deltaX;
23215 var deltaY = input.deltaY - last.deltaY;
23216
23217 var v = getVelocity(deltaTime, deltaX, deltaY);
23218 velocityX = v.x;
23219 velocityY = v.y;
23220 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
23221 direction = getDirection(deltaX, deltaY);
23222
23223 session.lastInterval = input;
23224 } else {
23225 // use latest velocity info if it doesn't overtake a minimum period
23226 velocity = last.velocity;
23227 velocityX = last.velocityX;
23228 velocityY = last.velocityY;
23229 direction = last.direction;
23230 }
23231
23232 input.velocity = velocity;
23233 input.velocityX = velocityX;
23234 input.velocityY = velocityY;
23235 input.direction = direction;
23236 }
23237
23238 /**
23239 * create a simple clone from the input used for storage of firstInput and firstMultiple
23240 * @param {Object} input
23241 * @returns {Object} clonedInputData
23242 */
23243 function simpleCloneInputData(input) {
23244 // make a simple copy of the pointers because we will get a reference if we don't
23245 // we only need clientXY for the calculations
23246 var pointers = [];
23247 var i = 0;
23248 while (i < input.pointers.length) {
23249 pointers[i] = {
23250 clientX: round(input.pointers[i].clientX),
23251 clientY: round(input.pointers[i].clientY)
23252 };
23253 i++;
23254 }
23255
23256 return {
23257 timeStamp: now(),
23258 pointers: pointers,
23259 center: getCenter(pointers),
23260 deltaX: input.deltaX,
23261 deltaY: input.deltaY
23262 };
23263 }
23264
23265 /**
23266 * get the center of all the pointers
23267 * @param {Array} pointers
23268 * @return {Object} center contains `x` and `y` properties
23269 */
23270 function getCenter(pointers) {
23271 var pointersLength = pointers.length;
23272
23273 // no need to loop when only one touch
23274 if (pointersLength === 1) {
23275 return {
23276 x: round(pointers[0].clientX),
23277 y: round(pointers[0].clientY)
23278 };
23279 }
23280
23281 var x = 0, y = 0, i = 0;
23282 while (i < pointersLength) {
23283 x += pointers[i].clientX;
23284 y += pointers[i].clientY;
23285 i++;
23286 }
23287
23288 return {
23289 x: round(x / pointersLength),
23290 y: round(y / pointersLength)
23291 };
23292 }
23293
23294 /**
23295 * calculate the velocity between two points. unit is in px per ms.
23296 * @param {Number} deltaTime
23297 * @param {Number} x
23298 * @param {Number} y
23299 * @return {Object} velocity `x` and `y`
23300 */
23301 function getVelocity(deltaTime, x, y) {
23302 return {
23303 x: x / deltaTime || 0,
23304 y: y / deltaTime || 0
23305 };
23306 }
23307
23308 /**
23309 * get the direction between two points
23310 * @param {Number} x
23311 * @param {Number} y
23312 * @return {Number} direction
23313 */
23314 function getDirection(x, y) {
23315 if (x === y) {
23316 return DIRECTION_NONE;
23317 }
23318
23319 if (abs(x) >= abs(y)) {
23320 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
23321 }
23322 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
23323 }
23324
23325 /**
23326 * calculate the absolute distance between two points
23327 * @param {Object} p1 {x, y}
23328 * @param {Object} p2 {x, y}
23329 * @param {Array} [props] containing x and y keys
23330 * @return {Number} distance
23331 */
23332 function getDistance(p1, p2, props) {
23333 if (!props) {
23334 props = PROPS_XY;
23335 }
23336 var x = p2[props[0]] - p1[props[0]],
23337 y = p2[props[1]] - p1[props[1]];
23338
23339 return Math.sqrt((x * x) + (y * y));
23340 }
23341
23342 /**
23343 * calculate the angle between two coordinates
23344 * @param {Object} p1
23345 * @param {Object} p2
23346 * @param {Array} [props] containing x and y keys
23347 * @return {Number} angle
23348 */
23349 function getAngle(p1, p2, props) {
23350 if (!props) {
23351 props = PROPS_XY;
23352 }
23353 var x = p2[props[0]] - p1[props[0]],
23354 y = p2[props[1]] - p1[props[1]];
23355 return Math.atan2(y, x) * 180 / Math.PI;
23356 }
23357
23358 /**
23359 * calculate the rotation degrees between two pointersets
23360 * @param {Array} start array of pointers
23361 * @param {Array} end array of pointers
23362 * @return {Number} rotation
23363 */
23364 function getRotation(start, end) {
23365 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
23366 }
23367
23368 /**
23369 * calculate the scale factor between two pointersets
23370 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
23371 * @param {Array} start array of pointers
23372 * @param {Array} end array of pointers
23373 * @return {Number} scale
23374 */
23375 function getScale(start, end) {
23376 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
23377 }
23378
23379 var MOUSE_INPUT_MAP = {
23380 mousedown: INPUT_START,
23381 mousemove: INPUT_MOVE,
23382 mouseup: INPUT_END
23383 };
23384
23385 var MOUSE_ELEMENT_EVENTS = 'mousedown';
23386 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
23387
23388 /**
23389 * Mouse events input
23390 * @constructor
23391 * @extends Input
23392 */
23393 function MouseInput() {
23394 this.evEl = MOUSE_ELEMENT_EVENTS;
23395 this.evWin = MOUSE_WINDOW_EVENTS;
23396
23397 this.pressed = false; // mousedown state
23398
23399 Input.apply(this, arguments);
23400 }
23401
23402 inherit(MouseInput, Input, {
23403 /**
23404 * handle mouse events
23405 * @param {Object} ev
23406 */
23407 handler: function MEhandler(ev) {
23408 var eventType = MOUSE_INPUT_MAP[ev.type];
23409
23410 // on start we want to have the left mouse button down
23411 if (eventType & INPUT_START && ev.button === 0) {
23412 this.pressed = true;
23413 }
23414
23415 if (eventType & INPUT_MOVE && ev.which !== 1) {
23416 eventType = INPUT_END;
23417 }
23418
23419 // mouse must be down
23420 if (!this.pressed) {
23421 return;
23422 }
23423
23424 if (eventType & INPUT_END) {
23425 this.pressed = false;
23426 }
23427
23428 this.callback(this.manager, eventType, {
23429 pointers: [ev],
23430 changedPointers: [ev],
23431 pointerType: INPUT_TYPE_MOUSE,
23432 srcEvent: ev
23433 });
23434 }
23435 });
23436
23437 var POINTER_INPUT_MAP = {
23438 pointerdown: INPUT_START,
23439 pointermove: INPUT_MOVE,
23440 pointerup: INPUT_END,
23441 pointercancel: INPUT_CANCEL,
23442 pointerout: INPUT_CANCEL
23443 };
23444
23445 // in IE10 the pointer types is defined as an enum
23446 var IE10_POINTER_TYPE_ENUM = {
23447 2: INPUT_TYPE_TOUCH,
23448 3: INPUT_TYPE_PEN,
23449 4: INPUT_TYPE_MOUSE,
23450 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
23451 };
23452
23453 var POINTER_ELEMENT_EVENTS = 'pointerdown';
23454 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
23455
23456 // IE10 has prefixed support, and case-sensitive
23457 if (window.MSPointerEvent && !window.PointerEvent) {
23458 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
23459 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
23460 }
23461
23462 /**
23463 * Pointer events input
23464 * @constructor
23465 * @extends Input
23466 */
23467 function PointerEventInput() {
23468 this.evEl = POINTER_ELEMENT_EVENTS;
23469 this.evWin = POINTER_WINDOW_EVENTS;
23470
23471 Input.apply(this, arguments);
23472
23473 this.store = (this.manager.session.pointerEvents = []);
23474 }
23475
23476 inherit(PointerEventInput, Input, {
23477 /**
23478 * handle mouse events
23479 * @param {Object} ev
23480 */
23481 handler: function PEhandler(ev) {
23482 var store = this.store;
23483 var removePointer = false;
23484
23485 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
23486 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
23487 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
23488
23489 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
23490
23491 // get index of the event in the store
23492 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
23493
23494 // start and mouse must be down
23495 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
23496 if (storeIndex < 0) {
23497 store.push(ev);
23498 storeIndex = store.length - 1;
23499 }
23500 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
23501 removePointer = true;
23502 }
23503
23504 // it not found, so the pointer hasn't been down (so it's probably a hover)
23505 if (storeIndex < 0) {
23506 return;
23507 }
23508
23509 // update the event in the store
23510 store[storeIndex] = ev;
23511
23512 this.callback(this.manager, eventType, {
23513 pointers: store,
23514 changedPointers: [ev],
23515 pointerType: pointerType,
23516 srcEvent: ev
23517 });
23518
23519 if (removePointer) {
23520 // remove from the store
23521 store.splice(storeIndex, 1);
23522 }
23523 }
23524 });
23525
23526 var SINGLE_TOUCH_INPUT_MAP = {
23527 touchstart: INPUT_START,
23528 touchmove: INPUT_MOVE,
23529 touchend: INPUT_END,
23530 touchcancel: INPUT_CANCEL
23531 };
23532
23533 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
23534 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
23535
23536 /**
23537 * Touch events input
23538 * @constructor
23539 * @extends Input
23540 */
23541 function SingleTouchInput() {
23542 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
23543 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
23544 this.started = false;
23545
23546 Input.apply(this, arguments);
23547 }
23548
23549 inherit(SingleTouchInput, Input, {
23550 handler: function TEhandler(ev) {
23551 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
23552
23553 // should we handle the touch events?
23554 if (type === INPUT_START) {
23555 this.started = true;
23556 }
23557
23558 if (!this.started) {
23559 return;
23560 }
23561
23562 var touches = normalizeSingleTouches.call(this, ev, type);
23563
23564 // when done, reset the started state
23565 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
23566 this.started = false;
23567 }
23568
23569 this.callback(this.manager, type, {
23570 pointers: touches[0],
23571 changedPointers: touches[1],
23572 pointerType: INPUT_TYPE_TOUCH,
23573 srcEvent: ev
23574 });
23575 }
23576 });
23577
23578 /**
23579 * @this {TouchInput}
23580 * @param {Object} ev
23581 * @param {Number} type flag
23582 * @returns {undefined|Array} [all, changed]
23583 */
23584 function normalizeSingleTouches(ev, type) {
23585 var all = toArray(ev.touches);
23586 var changed = toArray(ev.changedTouches);
23587
23588 if (type & (INPUT_END | INPUT_CANCEL)) {
23589 all = uniqueArray(all.concat(changed), 'identifier', true);
23590 }
23591
23592 return [all, changed];
23593 }
23594
23595 var TOUCH_INPUT_MAP = {
23596 touchstart: INPUT_START,
23597 touchmove: INPUT_MOVE,
23598 touchend: INPUT_END,
23599 touchcancel: INPUT_CANCEL
23600 };
23601
23602 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
23603
23604 /**
23605 * Multi-user touch events input
23606 * @constructor
23607 * @extends Input
23608 */
23609 function TouchInput() {
23610 this.evTarget = TOUCH_TARGET_EVENTS;
23611 this.targetIds = {};
23612
23613 Input.apply(this, arguments);
23614 }
23615
23616 inherit(TouchInput, Input, {
23617 handler: function MTEhandler(ev) {
23618 var type = TOUCH_INPUT_MAP[ev.type];
23619 var touches = getTouches.call(this, ev, type);
23620 if (!touches) {
23621 return;
23622 }
23623
23624 this.callback(this.manager, type, {
23625 pointers: touches[0],
23626 changedPointers: touches[1],
23627 pointerType: INPUT_TYPE_TOUCH,
23628 srcEvent: ev
23629 });
23630 }
23631 });
23632
23633 /**
23634 * @this {TouchInput}
23635 * @param {Object} ev
23636 * @param {Number} type flag
23637 * @returns {undefined|Array} [all, changed]
23638 */
23639 function getTouches(ev, type) {
23640 var allTouches = toArray(ev.touches);
23641 var targetIds = this.targetIds;
23642
23643 // when there is only one touch, the process can be simplified
23644 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
23645 targetIds[allTouches[0].identifier] = true;
23646 return [allTouches, allTouches];
23647 }
23648
23649 var i,
23650 targetTouches,
23651 changedTouches = toArray(ev.changedTouches),
23652 changedTargetTouches = [],
23653 target = this.target;
23654
23655 // get target touches from touches
23656 targetTouches = allTouches.filter(function(touch) {
23657 return hasParent(touch.target, target);
23658 });
23659
23660 // collect touches
23661 if (type === INPUT_START) {
23662 i = 0;
23663 while (i < targetTouches.length) {
23664 targetIds[targetTouches[i].identifier] = true;
23665 i++;
23666 }
23667 }
23668
23669 // filter changed touches to only contain touches that exist in the collected target ids
23670 i = 0;
23671 while (i < changedTouches.length) {
23672 if (targetIds[changedTouches[i].identifier]) {
23673 changedTargetTouches.push(changedTouches[i]);
23674 }
23675
23676 // cleanup removed touches
23677 if (type & (INPUT_END | INPUT_CANCEL)) {
23678 delete targetIds[changedTouches[i].identifier];
23679 }
23680 i++;
23681 }
23682
23683 if (!changedTargetTouches.length) {
23684 return;
23685 }
23686
23687 return [
23688 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
23689 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
23690 changedTargetTouches
23691 ];
23692 }
23693
23694 /**
23695 * Combined touch and mouse input
23696 *
23697 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
23698 * This because touch devices also emit mouse events while doing a touch.
23699 *
23700 * @constructor
23701 * @extends Input
23702 */
23703
23704 var DEDUP_TIMEOUT = 2500;
23705 var DEDUP_DISTANCE = 25;
23706
23707 function TouchMouseInput() {
23708 Input.apply(this, arguments);
23709
23710 var handler = bindFn(this.handler, this);
23711 this.touch = new TouchInput(this.manager, handler);
23712 this.mouse = new MouseInput(this.manager, handler);
23713
23714 this.primaryTouch = null;
23715 this.lastTouches = [];
23716 }
23717
23718 inherit(TouchMouseInput, Input, {
23719 /**
23720 * handle mouse and touch events
23721 * @param {Hammer} manager
23722 * @param {String} inputEvent
23723 * @param {Object} inputData
23724 */
23725 handler: function TMEhandler(manager, inputEvent, inputData) {
23726 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
23727 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
23728
23729 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
23730 return;
23731 }
23732
23733 // when we're in a touch event, record touches to de-dupe synthetic mouse event
23734 if (isTouch) {
23735 recordTouches.call(this, inputEvent, inputData);
23736 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
23737 return;
23738 }
23739
23740 this.callback(manager, inputEvent, inputData);
23741 },
23742
23743 /**
23744 * remove the event listeners
23745 */
23746 destroy: function destroy() {
23747 this.touch.destroy();
23748 this.mouse.destroy();
23749 }
23750 });
23751
23752 function recordTouches(eventType, eventData) {
23753 if (eventType & INPUT_START) {
23754 this.primaryTouch = eventData.changedPointers[0].identifier;
23755 setLastTouch.call(this, eventData);
23756 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
23757 setLastTouch.call(this, eventData);
23758 }
23759 }
23760
23761 function setLastTouch(eventData) {
23762 var touch = eventData.changedPointers[0];
23763
23764 if (touch.identifier === this.primaryTouch) {
23765 var lastTouch = {x: touch.clientX, y: touch.clientY};
23766 this.lastTouches.push(lastTouch);
23767 var lts = this.lastTouches;
23768 var removeLastTouch = function() {
23769 var i = lts.indexOf(lastTouch);
23770 if (i > -1) {
23771 lts.splice(i, 1);
23772 }
23773 };
23774 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
23775 }
23776 }
23777
23778 function isSyntheticEvent(eventData) {
23779 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
23780 for (var i = 0; i < this.lastTouches.length; i++) {
23781 var t = this.lastTouches[i];
23782 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
23783 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
23784 return true;
23785 }
23786 }
23787 return false;
23788 }
23789
23790 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
23791 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined$1;
23792
23793 // magical touchAction value
23794 var TOUCH_ACTION_COMPUTE = 'compute';
23795 var TOUCH_ACTION_AUTO = 'auto';
23796 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
23797 var TOUCH_ACTION_NONE = 'none';
23798 var TOUCH_ACTION_PAN_X = 'pan-x';
23799 var TOUCH_ACTION_PAN_Y = 'pan-y';
23800 var TOUCH_ACTION_MAP = getTouchActionProps();
23801
23802 /**
23803 * Touch Action
23804 * sets the touchAction property or uses the js alternative
23805 * @param {Manager} manager
23806 * @param {String} value
23807 * @constructor
23808 */
23809 function TouchAction(manager, value) {
23810 this.manager = manager;
23811 this.set(value);
23812 }
23813
23814 TouchAction.prototype = {
23815 /**
23816 * set the touchAction value on the element or enable the polyfill
23817 * @param {String} value
23818 */
23819 set: function(value) {
23820 // find out the touch-action by the event handlers
23821 if (value == TOUCH_ACTION_COMPUTE) {
23822 value = this.compute();
23823 }
23824
23825 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
23826 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
23827 }
23828 this.actions = value.toLowerCase().trim();
23829 },
23830
23831 /**
23832 * just re-set the touchAction value
23833 */
23834 update: function() {
23835 this.set(this.manager.options.touchAction);
23836 },
23837
23838 /**
23839 * compute the value for the touchAction property based on the recognizer's settings
23840 * @returns {String} value
23841 */
23842 compute: function() {
23843 var actions = [];
23844 each(this.manager.recognizers, function(recognizer) {
23845 if (boolOrFn(recognizer.options.enable, [recognizer])) {
23846 actions = actions.concat(recognizer.getTouchAction());
23847 }
23848 });
23849 return cleanTouchActions(actions.join(' '));
23850 },
23851
23852 /**
23853 * this method is called on each input cycle and provides the preventing of the browser behavior
23854 * @param {Object} input
23855 */
23856 preventDefaults: function(input) {
23857 var srcEvent = input.srcEvent;
23858 var direction = input.offsetDirection;
23859
23860 // if the touch action did prevented once this session
23861 if (this.manager.session.prevented) {
23862 srcEvent.preventDefault();
23863 return;
23864 }
23865
23866 var actions = this.actions;
23867 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
23868 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
23869 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
23870
23871 if (hasNone) {
23872 //do not prevent defaults if this is a tap gesture
23873
23874 var isTapPointer = input.pointers.length === 1;
23875 var isTapMovement = input.distance < 2;
23876 var isTapTouchTime = input.deltaTime < 250;
23877
23878 if (isTapPointer && isTapMovement && isTapTouchTime) {
23879 return;
23880 }
23881 }
23882
23883 if (hasPanX && hasPanY) {
23884 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
23885 return;
23886 }
23887
23888 if (hasNone ||
23889 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
23890 (hasPanX && direction & DIRECTION_VERTICAL)) {
23891 return this.preventSrc(srcEvent);
23892 }
23893 },
23894
23895 /**
23896 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
23897 * @param {Object} srcEvent
23898 */
23899 preventSrc: function(srcEvent) {
23900 this.manager.session.prevented = true;
23901 srcEvent.preventDefault();
23902 }
23903 };
23904
23905 /**
23906 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
23907 * @param {String} actions
23908 * @returns {*}
23909 */
23910 function cleanTouchActions(actions) {
23911 // none
23912 if (inStr(actions, TOUCH_ACTION_NONE)) {
23913 return TOUCH_ACTION_NONE;
23914 }
23915
23916 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
23917 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
23918
23919 // if both pan-x and pan-y are set (different recognizers
23920 // for different directions, e.g. horizontal pan but vertical swipe?)
23921 // we need none (as otherwise with pan-x pan-y combined none of these
23922 // recognizers will work, since the browser would handle all panning
23923 if (hasPanX && hasPanY) {
23924 return TOUCH_ACTION_NONE;
23925 }
23926
23927 // pan-x OR pan-y
23928 if (hasPanX || hasPanY) {
23929 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
23930 }
23931
23932 // manipulation
23933 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
23934 return TOUCH_ACTION_MANIPULATION;
23935 }
23936
23937 return TOUCH_ACTION_AUTO;
23938 }
23939
23940 function getTouchActionProps() {
23941 if (!NATIVE_TOUCH_ACTION) {
23942 return false;
23943 }
23944 var touchMap = {};
23945 var cssSupports = window.CSS && window.CSS.supports;
23946 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
23947
23948 // If css.supports is not supported but there is native touch-action assume it supports
23949 // all values. This is the case for IE 10 and 11.
23950 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
23951 });
23952 return touchMap;
23953 }
23954
23955 /**
23956 * Recognizer flow explained; *
23957 * All recognizers have the initial state of POSSIBLE when a input session starts.
23958 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
23959 * Example session for mouse-input: mousedown -> mousemove -> mouseup
23960 *
23961 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
23962 * which determines with state it should be.
23963 *
23964 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
23965 * POSSIBLE to give it another change on the next cycle.
23966 *
23967 * Possible
23968 * |
23969 * +-----+---------------+
23970 * | |
23971 * +-----+-----+ |
23972 * | | |
23973 * Failed Cancelled |
23974 * +-------+------+
23975 * | |
23976 * Recognized Began
23977 * |
23978 * Changed
23979 * |
23980 * Ended/Recognized
23981 */
23982 var STATE_POSSIBLE = 1;
23983 var STATE_BEGAN = 2;
23984 var STATE_CHANGED = 4;
23985 var STATE_ENDED = 8;
23986 var STATE_RECOGNIZED = STATE_ENDED;
23987 var STATE_CANCELLED = 16;
23988 var STATE_FAILED = 32;
23989
23990 /**
23991 * Recognizer
23992 * Every recognizer needs to extend from this class.
23993 * @constructor
23994 * @param {Object} options
23995 */
23996 function Recognizer(options) {
23997 this.options = assign({}, this.defaults, options || {});
23998
23999 this.id = uniqueId();
24000
24001 this.manager = null;
24002
24003 // default is enable true
24004 this.options.enable = ifUndefined(this.options.enable, true);
24005
24006 this.state = STATE_POSSIBLE;
24007
24008 this.simultaneous = {};
24009 this.requireFail = [];
24010 }
24011
24012 Recognizer.prototype = {
24013 /**
24014 * @virtual
24015 * @type {Object}
24016 */
24017 defaults: {},
24018
24019 /**
24020 * set options
24021 * @param {Object} options
24022 * @return {Recognizer}
24023 */
24024 set: function(options) {
24025 assign(this.options, options);
24026
24027 // also update the touchAction, in case something changed about the directions/enabled state
24028 this.manager && this.manager.touchAction.update();
24029 return this;
24030 },
24031
24032 /**
24033 * recognize simultaneous with an other recognizer.
24034 * @param {Recognizer} otherRecognizer
24035 * @returns {Recognizer} this
24036 */
24037 recognizeWith: function(otherRecognizer) {
24038 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
24039 return this;
24040 }
24041
24042 var simultaneous = this.simultaneous;
24043 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
24044 if (!simultaneous[otherRecognizer.id]) {
24045 simultaneous[otherRecognizer.id] = otherRecognizer;
24046 otherRecognizer.recognizeWith(this);
24047 }
24048 return this;
24049 },
24050
24051 /**
24052 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
24053 * @param {Recognizer} otherRecognizer
24054 * @returns {Recognizer} this
24055 */
24056 dropRecognizeWith: function(otherRecognizer) {
24057 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
24058 return this;
24059 }
24060
24061 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
24062 delete this.simultaneous[otherRecognizer.id];
24063 return this;
24064 },
24065
24066 /**
24067 * recognizer can only run when an other is failing
24068 * @param {Recognizer} otherRecognizer
24069 * @returns {Recognizer} this
24070 */
24071 requireFailure: function(otherRecognizer) {
24072 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
24073 return this;
24074 }
24075
24076 var requireFail = this.requireFail;
24077 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
24078 if (inArray(requireFail, otherRecognizer) === -1) {
24079 requireFail.push(otherRecognizer);
24080 otherRecognizer.requireFailure(this);
24081 }
24082 return this;
24083 },
24084
24085 /**
24086 * drop the requireFailure link. it does not remove the link on the other recognizer.
24087 * @param {Recognizer} otherRecognizer
24088 * @returns {Recognizer} this
24089 */
24090 dropRequireFailure: function(otherRecognizer) {
24091 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
24092 return this;
24093 }
24094
24095 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
24096 var index = inArray(this.requireFail, otherRecognizer);
24097 if (index > -1) {
24098 this.requireFail.splice(index, 1);
24099 }
24100 return this;
24101 },
24102
24103 /**
24104 * has require failures boolean
24105 * @returns {boolean}
24106 */
24107 hasRequireFailures: function() {
24108 return this.requireFail.length > 0;
24109 },
24110
24111 /**
24112 * if the recognizer can recognize simultaneous with an other recognizer
24113 * @param {Recognizer} otherRecognizer
24114 * @returns {Boolean}
24115 */
24116 canRecognizeWith: function(otherRecognizer) {
24117 return !!this.simultaneous[otherRecognizer.id];
24118 },
24119
24120 /**
24121 * You should use `tryEmit` instead of `emit` directly to check
24122 * that all the needed recognizers has failed before emitting.
24123 * @param {Object} input
24124 */
24125 emit: function(input) {
24126 var self = this;
24127 var state = this.state;
24128
24129 function emit(event) {
24130 self.manager.emit(event, input);
24131 }
24132
24133 // 'panstart' and 'panmove'
24134 if (state < STATE_ENDED) {
24135 emit(self.options.event + stateStr(state));
24136 }
24137
24138 emit(self.options.event); // simple 'eventName' events
24139
24140 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
24141 emit(input.additionalEvent);
24142 }
24143
24144 // panend and pancancel
24145 if (state >= STATE_ENDED) {
24146 emit(self.options.event + stateStr(state));
24147 }
24148 },
24149
24150 /**
24151 * Check that all the require failure recognizers has failed,
24152 * if true, it emits a gesture event,
24153 * otherwise, setup the state to FAILED.
24154 * @param {Object} input
24155 */
24156 tryEmit: function(input) {
24157 if (this.canEmit()) {
24158 return this.emit(input);
24159 }
24160 // it's failing anyway
24161 this.state = STATE_FAILED;
24162 },
24163
24164 /**
24165 * can we emit?
24166 * @returns {boolean}
24167 */
24168 canEmit: function() {
24169 var i = 0;
24170 while (i < this.requireFail.length) {
24171 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
24172 return false;
24173 }
24174 i++;
24175 }
24176 return true;
24177 },
24178
24179 /**
24180 * update the recognizer
24181 * @param {Object} inputData
24182 */
24183 recognize: function(inputData) {
24184 // make a new copy of the inputData
24185 // so we can change the inputData without messing up the other recognizers
24186 var inputDataClone = assign({}, inputData);
24187
24188 // is is enabled and allow recognizing?
24189 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
24190 this.reset();
24191 this.state = STATE_FAILED;
24192 return;
24193 }
24194
24195 // reset when we've reached the end
24196 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
24197 this.state = STATE_POSSIBLE;
24198 }
24199
24200 this.state = this.process(inputDataClone);
24201
24202 // the recognizer has recognized a gesture
24203 // so trigger an event
24204 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
24205 this.tryEmit(inputDataClone);
24206 }
24207 },
24208
24209 /**
24210 * return the state of the recognizer
24211 * the actual recognizing happens in this method
24212 * @virtual
24213 * @param {Object} inputData
24214 * @returns {Const} STATE
24215 */
24216 process: function(inputData) { }, // jshint ignore:line
24217
24218 /**
24219 * return the preferred touch-action
24220 * @virtual
24221 * @returns {Array}
24222 */
24223 getTouchAction: function() { },
24224
24225 /**
24226 * called when the gesture isn't allowed to recognize
24227 * like when another is being recognized or it is disabled
24228 * @virtual
24229 */
24230 reset: function() { }
24231 };
24232
24233 /**
24234 * get a usable string, used as event postfix
24235 * @param {Const} state
24236 * @returns {String} state
24237 */
24238 function stateStr(state) {
24239 if (state & STATE_CANCELLED) {
24240 return 'cancel';
24241 } else if (state & STATE_ENDED) {
24242 return 'end';
24243 } else if (state & STATE_CHANGED) {
24244 return 'move';
24245 } else if (state & STATE_BEGAN) {
24246 return 'start';
24247 }
24248 return '';
24249 }
24250
24251 /**
24252 * direction cons to string
24253 * @param {Const} direction
24254 * @returns {String}
24255 */
24256 function directionStr(direction) {
24257 if (direction == DIRECTION_DOWN) {
24258 return 'down';
24259 } else if (direction == DIRECTION_UP) {
24260 return 'up';
24261 } else if (direction == DIRECTION_LEFT) {
24262 return 'left';
24263 } else if (direction == DIRECTION_RIGHT) {
24264 return 'right';
24265 }
24266 return '';
24267 }
24268
24269 /**
24270 * get a recognizer by name if it is bound to a manager
24271 * @param {Recognizer|String} otherRecognizer
24272 * @param {Recognizer} recognizer
24273 * @returns {Recognizer}
24274 */
24275 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
24276 var manager = recognizer.manager;
24277 if (manager) {
24278 return manager.get(otherRecognizer);
24279 }
24280 return otherRecognizer;
24281 }
24282
24283 /**
24284 * This recognizer is just used as a base for the simple attribute recognizers.
24285 * @constructor
24286 * @extends Recognizer
24287 */
24288 function AttrRecognizer() {
24289 Recognizer.apply(this, arguments);
24290 }
24291
24292 inherit(AttrRecognizer, Recognizer, {
24293 /**
24294 * @namespace
24295 * @memberof AttrRecognizer
24296 */
24297 defaults: {
24298 /**
24299 * @type {Number}
24300 * @default 1
24301 */
24302 pointers: 1
24303 },
24304
24305 /**
24306 * Used to check if it the recognizer receives valid input, like input.distance > 10.
24307 * @memberof AttrRecognizer
24308 * @param {Object} input
24309 * @returns {Boolean} recognized
24310 */
24311 attrTest: function(input) {
24312 var optionPointers = this.options.pointers;
24313 return optionPointers === 0 || input.pointers.length === optionPointers;
24314 },
24315
24316 /**
24317 * Process the input and return the state for the recognizer
24318 * @memberof AttrRecognizer
24319 * @param {Object} input
24320 * @returns {*} State
24321 */
24322 process: function(input) {
24323 var state = this.state;
24324 var eventType = input.eventType;
24325
24326 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
24327 var isValid = this.attrTest(input);
24328
24329 // on cancel input and we've recognized before, return STATE_CANCELLED
24330 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
24331 return state | STATE_CANCELLED;
24332 } else if (isRecognized || isValid) {
24333 if (eventType & INPUT_END) {
24334 return state | STATE_ENDED;
24335 } else if (!(state & STATE_BEGAN)) {
24336 return STATE_BEGAN;
24337 }
24338 return state | STATE_CHANGED;
24339 }
24340 return STATE_FAILED;
24341 }
24342 });
24343
24344 /**
24345 * Pan
24346 * Recognized when the pointer is down and moved in the allowed direction.
24347 * @constructor
24348 * @extends AttrRecognizer
24349 */
24350 function PanRecognizer() {
24351 AttrRecognizer.apply(this, arguments);
24352
24353 this.pX = null;
24354 this.pY = null;
24355 }
24356
24357 inherit(PanRecognizer, AttrRecognizer, {
24358 /**
24359 * @namespace
24360 * @memberof PanRecognizer
24361 */
24362 defaults: {
24363 event: 'pan',
24364 threshold: 10,
24365 pointers: 1,
24366 direction: DIRECTION_ALL
24367 },
24368
24369 getTouchAction: function() {
24370 var direction = this.options.direction;
24371 var actions = [];
24372 if (direction & DIRECTION_HORIZONTAL) {
24373 actions.push(TOUCH_ACTION_PAN_Y);
24374 }
24375 if (direction & DIRECTION_VERTICAL) {
24376 actions.push(TOUCH_ACTION_PAN_X);
24377 }
24378 return actions;
24379 },
24380
24381 directionTest: function(input) {
24382 var options = this.options;
24383 var hasMoved = true;
24384 var distance = input.distance;
24385 var direction = input.direction;
24386 var x = input.deltaX;
24387 var y = input.deltaY;
24388
24389 // lock to axis?
24390 if (!(direction & options.direction)) {
24391 if (options.direction & DIRECTION_HORIZONTAL) {
24392 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
24393 hasMoved = x != this.pX;
24394 distance = Math.abs(input.deltaX);
24395 } else {
24396 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
24397 hasMoved = y != this.pY;
24398 distance = Math.abs(input.deltaY);
24399 }
24400 }
24401 input.direction = direction;
24402 return hasMoved && distance > options.threshold && direction & options.direction;
24403 },
24404
24405 attrTest: function(input) {
24406 return AttrRecognizer.prototype.attrTest.call(this, input) &&
24407 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
24408 },
24409
24410 emit: function(input) {
24411
24412 this.pX = input.deltaX;
24413 this.pY = input.deltaY;
24414
24415 var direction = directionStr(input.direction);
24416
24417 if (direction) {
24418 input.additionalEvent = this.options.event + direction;
24419 }
24420 this._super.emit.call(this, input);
24421 }
24422 });
24423
24424 /**
24425 * Pinch
24426 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
24427 * @constructor
24428 * @extends AttrRecognizer
24429 */
24430 function PinchRecognizer() {
24431 AttrRecognizer.apply(this, arguments);
24432 }
24433
24434 inherit(PinchRecognizer, AttrRecognizer, {
24435 /**
24436 * @namespace
24437 * @memberof PinchRecognizer
24438 */
24439 defaults: {
24440 event: 'pinch',
24441 threshold: 0,
24442 pointers: 2
24443 },
24444
24445 getTouchAction: function() {
24446 return [TOUCH_ACTION_NONE];
24447 },
24448
24449 attrTest: function(input) {
24450 return this._super.attrTest.call(this, input) &&
24451 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
24452 },
24453
24454 emit: function(input) {
24455 if (input.scale !== 1) {
24456 var inOut = input.scale < 1 ? 'in' : 'out';
24457 input.additionalEvent = this.options.event + inOut;
24458 }
24459 this._super.emit.call(this, input);
24460 }
24461 });
24462
24463 /**
24464 * Press
24465 * Recognized when the pointer is down for x ms without any movement.
24466 * @constructor
24467 * @extends Recognizer
24468 */
24469 function PressRecognizer() {
24470 Recognizer.apply(this, arguments);
24471
24472 this._timer = null;
24473 this._input = null;
24474 }
24475
24476 inherit(PressRecognizer, Recognizer, {
24477 /**
24478 * @namespace
24479 * @memberof PressRecognizer
24480 */
24481 defaults: {
24482 event: 'press',
24483 pointers: 1,
24484 time: 251, // minimal time of the pointer to be pressed
24485 threshold: 9 // a minimal movement is ok, but keep it low
24486 },
24487
24488 getTouchAction: function() {
24489 return [TOUCH_ACTION_AUTO];
24490 },
24491
24492 process: function(input) {
24493 var options = this.options;
24494 var validPointers = input.pointers.length === options.pointers;
24495 var validMovement = input.distance < options.threshold;
24496 var validTime = input.deltaTime > options.time;
24497
24498 this._input = input;
24499
24500 // we only allow little movement
24501 // and we've reached an end event, so a tap is possible
24502 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
24503 this.reset();
24504 } else if (input.eventType & INPUT_START) {
24505 this.reset();
24506 this._timer = setTimeoutContext(function() {
24507 this.state = STATE_RECOGNIZED;
24508 this.tryEmit();
24509 }, options.time, this);
24510 } else if (input.eventType & INPUT_END) {
24511 return STATE_RECOGNIZED;
24512 }
24513 return STATE_FAILED;
24514 },
24515
24516 reset: function() {
24517 clearTimeout(this._timer);
24518 },
24519
24520 emit: function(input) {
24521 if (this.state !== STATE_RECOGNIZED) {
24522 return;
24523 }
24524
24525 if (input && (input.eventType & INPUT_END)) {
24526 this.manager.emit(this.options.event + 'up', input);
24527 } else {
24528 this._input.timeStamp = now();
24529 this.manager.emit(this.options.event, this._input);
24530 }
24531 }
24532 });
24533
24534 /**
24535 * Rotate
24536 * Recognized when two or more pointer are moving in a circular motion.
24537 * @constructor
24538 * @extends AttrRecognizer
24539 */
24540 function RotateRecognizer() {
24541 AttrRecognizer.apply(this, arguments);
24542 }
24543
24544 inherit(RotateRecognizer, AttrRecognizer, {
24545 /**
24546 * @namespace
24547 * @memberof RotateRecognizer
24548 */
24549 defaults: {
24550 event: 'rotate',
24551 threshold: 0,
24552 pointers: 2
24553 },
24554
24555 getTouchAction: function() {
24556 return [TOUCH_ACTION_NONE];
24557 },
24558
24559 attrTest: function(input) {
24560 return this._super.attrTest.call(this, input) &&
24561 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
24562 }
24563 });
24564
24565 /**
24566 * Swipe
24567 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
24568 * @constructor
24569 * @extends AttrRecognizer
24570 */
24571 function SwipeRecognizer() {
24572 AttrRecognizer.apply(this, arguments);
24573 }
24574
24575 inherit(SwipeRecognizer, AttrRecognizer, {
24576 /**
24577 * @namespace
24578 * @memberof SwipeRecognizer
24579 */
24580 defaults: {
24581 event: 'swipe',
24582 threshold: 10,
24583 velocity: 0.3,
24584 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
24585 pointers: 1
24586 },
24587
24588 getTouchAction: function() {
24589 return PanRecognizer.prototype.getTouchAction.call(this);
24590 },
24591
24592 attrTest: function(input) {
24593 var direction = this.options.direction;
24594 var velocity;
24595
24596 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
24597 velocity = input.overallVelocity;
24598 } else if (direction & DIRECTION_HORIZONTAL) {
24599 velocity = input.overallVelocityX;
24600 } else if (direction & DIRECTION_VERTICAL) {
24601 velocity = input.overallVelocityY;
24602 }
24603
24604 return this._super.attrTest.call(this, input) &&
24605 direction & input.offsetDirection &&
24606 input.distance > this.options.threshold &&
24607 input.maxPointers == this.options.pointers &&
24608 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
24609 },
24610
24611 emit: function(input) {
24612 var direction = directionStr(input.offsetDirection);
24613 if (direction) {
24614 this.manager.emit(this.options.event + direction, input);
24615 }
24616
24617 this.manager.emit(this.options.event, input);
24618 }
24619 });
24620
24621 /**
24622 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
24623 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
24624 * a single tap.
24625 *
24626 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
24627 * multi-taps being recognized.
24628 * @constructor
24629 * @extends Recognizer
24630 */
24631 function TapRecognizer() {
24632 Recognizer.apply(this, arguments);
24633
24634 // previous time and center,
24635 // used for tap counting
24636 this.pTime = false;
24637 this.pCenter = false;
24638
24639 this._timer = null;
24640 this._input = null;
24641 this.count = 0;
24642 }
24643
24644 inherit(TapRecognizer, Recognizer, {
24645 /**
24646 * @namespace
24647 * @memberof PinchRecognizer
24648 */
24649 defaults: {
24650 event: 'tap',
24651 pointers: 1,
24652 taps: 1,
24653 interval: 300, // max time between the multi-tap taps
24654 time: 250, // max time of the pointer to be down (like finger on the screen)
24655 threshold: 9, // a minimal movement is ok, but keep it low
24656 posThreshold: 10 // a multi-tap can be a bit off the initial position
24657 },
24658
24659 getTouchAction: function() {
24660 return [TOUCH_ACTION_MANIPULATION];
24661 },
24662
24663 process: function(input) {
24664 var options = this.options;
24665
24666 var validPointers = input.pointers.length === options.pointers;
24667 var validMovement = input.distance < options.threshold;
24668 var validTouchTime = input.deltaTime < options.time;
24669
24670 this.reset();
24671
24672 if ((input.eventType & INPUT_START) && (this.count === 0)) {
24673 return this.failTimeout();
24674 }
24675
24676 // we only allow little movement
24677 // and we've reached an end event, so a tap is possible
24678 if (validMovement && validTouchTime && validPointers) {
24679 if (input.eventType != INPUT_END) {
24680 return this.failTimeout();
24681 }
24682
24683 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
24684 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
24685
24686 this.pTime = input.timeStamp;
24687 this.pCenter = input.center;
24688
24689 if (!validMultiTap || !validInterval) {
24690 this.count = 1;
24691 } else {
24692 this.count += 1;
24693 }
24694
24695 this._input = input;
24696
24697 // if tap count matches we have recognized it,
24698 // else it has began recognizing...
24699 var tapCount = this.count % options.taps;
24700 if (tapCount === 0) {
24701 // no failing requirements, immediately trigger the tap event
24702 // or wait as long as the multitap interval to trigger
24703 if (!this.hasRequireFailures()) {
24704 return STATE_RECOGNIZED;
24705 } else {
24706 this._timer = setTimeoutContext(function() {
24707 this.state = STATE_RECOGNIZED;
24708 this.tryEmit();
24709 }, options.interval, this);
24710 return STATE_BEGAN;
24711 }
24712 }
24713 }
24714 return STATE_FAILED;
24715 },
24716
24717 failTimeout: function() {
24718 this._timer = setTimeoutContext(function() {
24719 this.state = STATE_FAILED;
24720 }, this.options.interval, this);
24721 return STATE_FAILED;
24722 },
24723
24724 reset: function() {
24725 clearTimeout(this._timer);
24726 },
24727
24728 emit: function() {
24729 if (this.state == STATE_RECOGNIZED) {
24730 this._input.tapCount = this.count;
24731 this.manager.emit(this.options.event, this._input);
24732 }
24733 }
24734 });
24735
24736 /**
24737 * Simple way to create a manager with a default set of recognizers.
24738 * @param {HTMLElement} element
24739 * @param {Object} [options]
24740 * @constructor
24741 */
24742 function Hammer(element, options) {
24743 options = options || {};
24744 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
24745 return new Manager(element, options);
24746 }
24747
24748 /**
24749 * @const {string}
24750 */
24751 Hammer.VERSION = '2.0.7';
24752
24753 /**
24754 * default settings
24755 * @namespace
24756 */
24757 Hammer.defaults = {
24758 /**
24759 * set if DOM events are being triggered.
24760 * But this is slower and unused by simple implementations, so disabled by default.
24761 * @type {Boolean}
24762 * @default false
24763 */
24764 domEvents: false,
24765
24766 /**
24767 * The value for the touchAction property/fallback.
24768 * When set to `compute` it will magically set the correct value based on the added recognizers.
24769 * @type {String}
24770 * @default compute
24771 */
24772 touchAction: TOUCH_ACTION_COMPUTE,
24773
24774 /**
24775 * @type {Boolean}
24776 * @default true
24777 */
24778 enable: true,
24779
24780 /**
24781 * EXPERIMENTAL FEATURE -- can be removed/changed
24782 * Change the parent input target element.
24783 * If Null, then it is being set the to main element.
24784 * @type {Null|EventTarget}
24785 * @default null
24786 */
24787 inputTarget: null,
24788
24789 /**
24790 * force an input class
24791 * @type {Null|Function}
24792 * @default null
24793 */
24794 inputClass: null,
24795
24796 /**
24797 * Default recognizer setup when calling `Hammer()`
24798 * When creating a new Manager these will be skipped.
24799 * @type {Array}
24800 */
24801 preset: [
24802 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
24803 [RotateRecognizer, {enable: false}],
24804 [PinchRecognizer, {enable: false}, ['rotate']],
24805 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
24806 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
24807 [TapRecognizer],
24808 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
24809 [PressRecognizer]
24810 ],
24811
24812 /**
24813 * Some CSS properties can be used to improve the working of Hammer.
24814 * Add them to this method and they will be set when creating a new Manager.
24815 * @namespace
24816 */
24817 cssProps: {
24818 /**
24819 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
24820 * @type {String}
24821 * @default 'none'
24822 */
24823 userSelect: 'none',
24824
24825 /**
24826 * Disable the Windows Phone grippers when pressing an element.
24827 * @type {String}
24828 * @default 'none'
24829 */
24830 touchSelect: 'none',
24831
24832 /**
24833 * Disables the default callout shown when you touch and hold a touch target.
24834 * On iOS, when you touch and hold a touch target such as a link, Safari displays
24835 * a callout containing information about the link. This property allows you to disable that callout.
24836 * @type {String}
24837 * @default 'none'
24838 */
24839 touchCallout: 'none',
24840
24841 /**
24842 * Specifies whether zooming is enabled. Used by IE10>
24843 * @type {String}
24844 * @default 'none'
24845 */
24846 contentZooming: 'none',
24847
24848 /**
24849 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
24850 * @type {String}
24851 * @default 'none'
24852 */
24853 userDrag: 'none',
24854
24855 /**
24856 * Overrides the highlight color shown when the user taps a link or a JavaScript
24857 * clickable element in iOS. This property obeys the alpha value, if specified.
24858 * @type {String}
24859 * @default 'rgba(0,0,0,0)'
24860 */
24861 tapHighlightColor: 'rgba(0,0,0,0)'
24862 }
24863 };
24864
24865 var STOP = 1;
24866 var FORCED_STOP = 2;
24867
24868 /**
24869 * Manager
24870 * @param {HTMLElement} element
24871 * @param {Object} [options]
24872 * @constructor
24873 */
24874 function Manager(element, options) {
24875 this.options = assign({}, Hammer.defaults, options || {});
24876
24877 this.options.inputTarget = this.options.inputTarget || element;
24878
24879 this.handlers = {};
24880 this.session = {};
24881 this.recognizers = [];
24882 this.oldCssProps = {};
24883
24884 this.element = element;
24885 this.input = createInputInstance(this);
24886 this.touchAction = new TouchAction(this, this.options.touchAction);
24887
24888 toggleCssProps(this, true);
24889
24890 each(this.options.recognizers, function(item) {
24891 var recognizer = this.add(new (item[0])(item[1]));
24892 item[2] && recognizer.recognizeWith(item[2]);
24893 item[3] && recognizer.requireFailure(item[3]);
24894 }, this);
24895 }
24896
24897 Manager.prototype = {
24898 /**
24899 * set options
24900 * @param {Object} options
24901 * @returns {Manager}
24902 */
24903 set: function(options) {
24904 assign(this.options, options);
24905
24906 // Options that need a little more setup
24907 if (options.touchAction) {
24908 this.touchAction.update();
24909 }
24910 if (options.inputTarget) {
24911 // Clean up existing event listeners and reinitialize
24912 this.input.destroy();
24913 this.input.target = options.inputTarget;
24914 this.input.init();
24915 }
24916 return this;
24917 },
24918
24919 /**
24920 * stop recognizing for this session.
24921 * This session will be discarded, when a new [input]start event is fired.
24922 * When forced, the recognizer cycle is stopped immediately.
24923 * @param {Boolean} [force]
24924 */
24925 stop: function(force) {
24926 this.session.stopped = force ? FORCED_STOP : STOP;
24927 },
24928
24929 /**
24930 * run the recognizers!
24931 * called by the inputHandler function on every movement of the pointers (touches)
24932 * it walks through all the recognizers and tries to detect the gesture that is being made
24933 * @param {Object} inputData
24934 */
24935 recognize: function(inputData) {
24936 var session = this.session;
24937 if (session.stopped) {
24938 return;
24939 }
24940
24941 // run the touch-action polyfill
24942 this.touchAction.preventDefaults(inputData);
24943
24944 var recognizer;
24945 var recognizers = this.recognizers;
24946
24947 // this holds the recognizer that is being recognized.
24948 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
24949 // if no recognizer is detecting a thing, it is set to `null`
24950 var curRecognizer = session.curRecognizer;
24951
24952 // reset when the last recognizer is recognized
24953 // or when we're in a new session
24954 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
24955 curRecognizer = session.curRecognizer = null;
24956 }
24957
24958 var i = 0;
24959 while (i < recognizers.length) {
24960 recognizer = recognizers[i];
24961
24962 // find out if we are allowed try to recognize the input for this one.
24963 // 1. allow if the session is NOT forced stopped (see the .stop() method)
24964 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
24965 // that is being recognized.
24966 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
24967 // this can be setup with the `recognizeWith()` method on the recognizer.
24968 if (session.stopped !== FORCED_STOP && ( // 1
24969 !curRecognizer || recognizer == curRecognizer || // 2
24970 recognizer.canRecognizeWith(curRecognizer))) { // 3
24971 recognizer.recognize(inputData);
24972 } else {
24973 recognizer.reset();
24974 }
24975
24976 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
24977 // current active recognizer. but only if we don't already have an active recognizer
24978 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
24979 curRecognizer = session.curRecognizer = recognizer;
24980 }
24981 i++;
24982 }
24983 },
24984
24985 /**
24986 * get a recognizer by its event name.
24987 * @param {Recognizer|String} recognizer
24988 * @returns {Recognizer|Null}
24989 */
24990 get: function(recognizer) {
24991 if (recognizer instanceof Recognizer) {
24992 return recognizer;
24993 }
24994
24995 var recognizers = this.recognizers;
24996 for (var i = 0; i < recognizers.length; i++) {
24997 if (recognizers[i].options.event == recognizer) {
24998 return recognizers[i];
24999 }
25000 }
25001 return null;
25002 },
25003
25004 /**
25005 * add a recognizer to the manager
25006 * existing recognizers with the same event name will be removed
25007 * @param {Recognizer} recognizer
25008 * @returns {Recognizer|Manager}
25009 */
25010 add: function(recognizer) {
25011 if (invokeArrayArg(recognizer, 'add', this)) {
25012 return this;
25013 }
25014
25015 // remove existing
25016 var existing = this.get(recognizer.options.event);
25017 if (existing) {
25018 this.remove(existing);
25019 }
25020
25021 this.recognizers.push(recognizer);
25022 recognizer.manager = this;
25023
25024 this.touchAction.update();
25025 return recognizer;
25026 },
25027
25028 /**
25029 * remove a recognizer by name or instance
25030 * @param {Recognizer|String} recognizer
25031 * @returns {Manager}
25032 */
25033 remove: function(recognizer) {
25034 if (invokeArrayArg(recognizer, 'remove', this)) {
25035 return this;
25036 }
25037
25038 recognizer = this.get(recognizer);
25039
25040 // let's make sure this recognizer exists
25041 if (recognizer) {
25042 var recognizers = this.recognizers;
25043 var index = inArray(recognizers, recognizer);
25044
25045 if (index !== -1) {
25046 recognizers.splice(index, 1);
25047 this.touchAction.update();
25048 }
25049 }
25050
25051 return this;
25052 },
25053
25054 /**
25055 * bind event
25056 * @param {String} events
25057 * @param {Function} handler
25058 * @returns {EventEmitter} this
25059 */
25060 on: function(events, handler) {
25061 if (events === undefined$1) {
25062 return;
25063 }
25064 if (handler === undefined$1) {
25065 return;
25066 }
25067
25068 var handlers = this.handlers;
25069 each(splitStr(events), function(event) {
25070 handlers[event] = handlers[event] || [];
25071 handlers[event].push(handler);
25072 });
25073 return this;
25074 },
25075
25076 /**
25077 * unbind event, leave emit blank to remove all handlers
25078 * @param {String} events
25079 * @param {Function} [handler]
25080 * @returns {EventEmitter} this
25081 */
25082 off: function(events, handler) {
25083 if (events === undefined$1) {
25084 return;
25085 }
25086
25087 var handlers = this.handlers;
25088 each(splitStr(events), function(event) {
25089 if (!handler) {
25090 delete handlers[event];
25091 } else {
25092 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
25093 }
25094 });
25095 return this;
25096 },
25097
25098 /**
25099 * emit event to the listeners
25100 * @param {String} event
25101 * @param {Object} data
25102 */
25103 emit: function(event, data) {
25104 // we also want to trigger dom events
25105 if (this.options.domEvents) {
25106 triggerDomEvent(event, data);
25107 }
25108
25109 // no handlers, so skip it all
25110 var handlers = this.handlers[event] && this.handlers[event].slice();
25111 if (!handlers || !handlers.length) {
25112 return;
25113 }
25114
25115 data.type = event;
25116 data.preventDefault = function() {
25117 data.srcEvent.preventDefault();
25118 };
25119
25120 var i = 0;
25121 while (i < handlers.length) {
25122 handlers[i](data);
25123 i++;
25124 }
25125 },
25126
25127 /**
25128 * destroy the manager and unbinds all events
25129 * it doesn't unbind dom events, that is the user own responsibility
25130 */
25131 destroy: function() {
25132 this.element && toggleCssProps(this, false);
25133
25134 this.handlers = {};
25135 this.session = {};
25136 this.input.destroy();
25137 this.element = null;
25138 }
25139 };
25140
25141 /**
25142 * add/remove the css properties as defined in manager.options.cssProps
25143 * @param {Manager} manager
25144 * @param {Boolean} add
25145 */
25146 function toggleCssProps(manager, add) {
25147 var element = manager.element;
25148 if (!element.style) {
25149 return;
25150 }
25151 var prop;
25152 each(manager.options.cssProps, function(value, name) {
25153 prop = prefixed(element.style, name);
25154 if (add) {
25155 manager.oldCssProps[prop] = element.style[prop];
25156 element.style[prop] = value;
25157 } else {
25158 element.style[prop] = manager.oldCssProps[prop] || '';
25159 }
25160 });
25161 if (!add) {
25162 manager.oldCssProps = {};
25163 }
25164 }
25165
25166 /**
25167 * trigger dom event
25168 * @param {String} event
25169 * @param {Object} data
25170 */
25171 function triggerDomEvent(event, data) {
25172 var gestureEvent = document.createEvent('Event');
25173 gestureEvent.initEvent(event, true, true);
25174 gestureEvent.gesture = data;
25175 data.target.dispatchEvent(gestureEvent);
25176 }
25177
25178 assign(Hammer, {
25179 INPUT_START: INPUT_START,
25180 INPUT_MOVE: INPUT_MOVE,
25181 INPUT_END: INPUT_END,
25182 INPUT_CANCEL: INPUT_CANCEL,
25183
25184 STATE_POSSIBLE: STATE_POSSIBLE,
25185 STATE_BEGAN: STATE_BEGAN,
25186 STATE_CHANGED: STATE_CHANGED,
25187 STATE_ENDED: STATE_ENDED,
25188 STATE_RECOGNIZED: STATE_RECOGNIZED,
25189 STATE_CANCELLED: STATE_CANCELLED,
25190 STATE_FAILED: STATE_FAILED,
25191
25192 DIRECTION_NONE: DIRECTION_NONE,
25193 DIRECTION_LEFT: DIRECTION_LEFT,
25194 DIRECTION_RIGHT: DIRECTION_RIGHT,
25195 DIRECTION_UP: DIRECTION_UP,
25196 DIRECTION_DOWN: DIRECTION_DOWN,
25197 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
25198 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
25199 DIRECTION_ALL: DIRECTION_ALL,
25200
25201 Manager: Manager,
25202 Input: Input,
25203 TouchAction: TouchAction,
25204
25205 TouchInput: TouchInput,
25206 MouseInput: MouseInput,
25207 PointerEventInput: PointerEventInput,
25208 TouchMouseInput: TouchMouseInput,
25209 SingleTouchInput: SingleTouchInput,
25210
25211 Recognizer: Recognizer,
25212 AttrRecognizer: AttrRecognizer,
25213 Tap: TapRecognizer,
25214 Pan: PanRecognizer,
25215 Swipe: SwipeRecognizer,
25216 Pinch: PinchRecognizer,
25217 Rotate: RotateRecognizer,
25218 Press: PressRecognizer,
25219
25220 on: addEventListeners,
25221 off: removeEventListeners,
25222 each: each,
25223 merge: merge,
25224 extend: extend,
25225 assign: assign,
25226 inherit: inherit,
25227 bindFn: bindFn,
25228 prefixed: prefixed
25229 });
25230
25231 // this prevents errors when Hammer is loaded in the presence of an AMD
25232 // style loader but by script tag, not by the loader.
25233 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
25234 freeGlobal.Hammer = Hammer;
25235
25236 if (typeof undefined$1 === 'function' && undefined$1.amd) {
25237 undefined$1(function() {
25238 return Hammer;
25239 });
25240 } else if ( module.exports) {
25241 module.exports = Hammer;
25242 } else {
25243 window[exportName] = Hammer;
25244 }
25245
25246 })(window, document, 'Hammer');
25247 });
25248
25249 var MIN_ZOOM = 0.2,
25250 MAX_ZOOM = 4;
25251
25252 var mouseEvents = [
25253 'mousedown',
25254 'mouseup',
25255 'mouseover',
25256 'mouseout',
25257 'click',
25258 'dblclick'
25259 ];
25260
25261 function get$1(service, injector) {
25262 return injector.get(service, false);
25263 }
25264
25265 function stopEvent(event) {
25266
25267 event.preventDefault();
25268
25269 if (typeof event.stopPropagation === 'function') {
25270 event.stopPropagation();
25271 } else if (event.srcEvent && typeof event.srcEvent.stopPropagation === 'function') {
25272
25273 // iPhone & iPad
25274 event.srcEvent.stopPropagation();
25275 }
25276
25277 if (typeof event.stopImmediatePropagation === 'function') {
25278 event.stopImmediatePropagation();
25279 }
25280 }
25281
25282
25283 function createTouchRecognizer(node) {
25284
25285 function stopMouse(event) {
25286
25287 forEach(mouseEvents, function(e) {
25288 componentEvent.bind(node, e, stopEvent, true);
25289 });
25290 }
25291
25292 function allowMouse(event) {
25293 setTimeout(function() {
25294 forEach(mouseEvents, function(e) {
25295 componentEvent.unbind(node, e, stopEvent, true);
25296 });
25297 }, 500);
25298 }
25299
25300 componentEvent.bind(node, 'touchstart', stopMouse, true);
25301 componentEvent.bind(node, 'touchend', allowMouse, true);
25302 componentEvent.bind(node, 'touchcancel', allowMouse, true);
25303
25304 // A touch event recognizer that handles
25305 // touch events only (we know, we can already handle
25306 // mouse events out of the box)
25307
25308 var recognizer = new hammer.Manager(node, {
25309 inputClass: hammer.TouchInput,
25310 recognizers: [],
25311 domEvents: true
25312 });
25313
25314
25315 var tap = new hammer.Tap();
25316 var pan = new hammer.Pan({ threshold: 10 });
25317 var press = new hammer.Press();
25318 var pinch = new hammer.Pinch();
25319
25320 var doubleTap = new hammer.Tap({ event: 'doubletap', taps: 2 });
25321
25322 pinch.requireFailure(pan);
25323 pinch.requireFailure(press);
25324
25325 recognizer.add([ pan, press, pinch, doubleTap, tap ]);
25326
25327 recognizer.reset = function(force) {
25328 var recognizers = this.recognizers,
25329 session = this.session;
25330
25331 if (session.stopped) {
25332 return;
25333 }
25334
25335 recognizer.stop(force);
25336
25337 setTimeout(function() {
25338 var i, r;
25339 for (i = 0; (r = recognizers[i]); i++) {
25340 r.reset();
25341 r.state = 8; // FAILED STATE
25342 }
25343
25344 session.curRecognizer = null;
25345 }, 0);
25346 };
25347
25348 recognizer.on('hammer.input', function(event) {
25349 if (event.srcEvent.defaultPrevented) {
25350 recognizer.reset(true);
25351 }
25352 });
25353
25354 return recognizer;
25355 }
25356
25357 /**
25358 * A plugin that provides touch events for elements.
25359 *
25360 * @param {EventBus} eventBus
25361 * @param {InteractionEvents} interactionEvents
25362 */
25363 function TouchInteractionEvents(
25364 injector, canvas, eventBus,
25365 elementRegistry, interactionEvents) {
25366
25367 // optional integrations
25368 var dragging = get$1('dragging', injector),
25369 move = get$1('move', injector),
25370 contextPad = get$1('contextPad', injector),
25371 palette = get$1('palette', injector);
25372
25373 // the touch recognizer
25374 var recognizer;
25375
25376 function handler(type) {
25377
25378 return function(event) {
25379
25380 interactionEvents.fire(type, event);
25381 };
25382 }
25383
25384 function getGfx(target) {
25385 var node = closest(target, 'svg, .djs-element', true);
25386 return node;
25387 }
25388
25389 function initEvents(svg) {
25390
25391 // touch recognizer
25392 recognizer = createTouchRecognizer(svg);
25393
25394 recognizer.on('doubletap', handler('element.dblclick'));
25395
25396 recognizer.on('tap', handler('element.click'));
25397
25398 function startGrabCanvas(event) {
25399
25400 var lx = 0, ly = 0;
25401
25402 function update(e) {
25403
25404 var dx = e.deltaX - lx,
25405 dy = e.deltaY - ly;
25406
25407 canvas.scroll({ dx: dx, dy: dy });
25408
25409 lx = e.deltaX;
25410 ly = e.deltaY;
25411 }
25412
25413 function end(e) {
25414 recognizer.off('panmove', update);
25415 recognizer.off('panend', end);
25416 recognizer.off('pancancel', end);
25417 }
25418
25419 recognizer.on('panmove', update);
25420 recognizer.on('panend', end);
25421 recognizer.on('pancancel', end);
25422 }
25423
25424 function startGrab(event) {
25425
25426 var gfx = getGfx(event.target),
25427 element = gfx && elementRegistry.get(gfx);
25428
25429 // recognizer
25430 if (move && canvas.getRootElement() !== element) {
25431 return move.start(event, element, true);
25432 } else {
25433 startGrabCanvas();
25434 }
25435 }
25436
25437 function startZoom(e) {
25438
25439 var zoom = canvas.zoom(),
25440 mid = e.center;
25441
25442 function update(e) {
25443
25444 var ratio = 1 - (1 - e.scale) / 1.50,
25445 newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom));
25446
25447 canvas.zoom(newZoom, mid);
25448
25449 stopEvent(e);
25450 }
25451
25452 function end(e) {
25453 recognizer.off('pinchmove', update);
25454 recognizer.off('pinchend', end);
25455 recognizer.off('pinchcancel', end);
25456
25457 recognizer.reset(true);
25458 }
25459
25460 recognizer.on('pinchmove', update);
25461 recognizer.on('pinchend', end);
25462 recognizer.on('pinchcancel', end);
25463 }
25464
25465 recognizer.on('panstart', startGrab);
25466 recognizer.on('press', startGrab);
25467
25468 recognizer.on('pinchstart', startZoom);
25469 }
25470
25471 if (dragging) {
25472
25473 // simulate hover during dragging
25474 eventBus.on('drag.move', function(event) {
25475
25476 var originalEvent = event.originalEvent;
25477
25478 if (!originalEvent || originalEvent instanceof MouseEvent) {
25479 return;
25480 }
25481
25482 var position = toPoint(originalEvent);
25483
25484 // this gets really expensive ...
25485 var node = document.elementFromPoint(position.x, position.y),
25486 gfx = getGfx(node),
25487 element = gfx && elementRegistry.get(gfx);
25488
25489 if (element !== event.hover) {
25490 if (event.hover) {
25491 dragging.out(event);
25492 }
25493
25494 if (element) {
25495 dragging.hover({ element: element, gfx: gfx });
25496
25497 event.hover = element;
25498 event.hoverGfx = gfx;
25499 }
25500 }
25501 });
25502 }
25503
25504 if (contextPad) {
25505
25506 eventBus.on('contextPad.create', function(event) {
25507 var node = event.pad.html;
25508
25509 // touch recognizer
25510 var padRecognizer = createTouchRecognizer(node);
25511
25512 padRecognizer.on('panstart', function(event) {
25513 contextPad.trigger('dragstart', event, true);
25514 });
25515
25516 padRecognizer.on('press', function(event) {
25517 contextPad.trigger('dragstart', event, true);
25518 });
25519
25520 padRecognizer.on('tap', function(event) {
25521 contextPad.trigger('click', event);
25522 });
25523 });
25524 }
25525
25526 if (palette) {
25527 eventBus.on('palette.create', function(event) {
25528 var node = event.container;
25529
25530 // touch recognizer
25531 var padRecognizer = createTouchRecognizer(node);
25532
25533 padRecognizer.on('panstart', function(event) {
25534 palette.trigger('dragstart', event, true);
25535 });
25536
25537 padRecognizer.on('press', function(event) {
25538 palette.trigger('dragstart', event, true);
25539 });
25540
25541 padRecognizer.on('tap', function(event) {
25542 palette.trigger('click', event);
25543 });
25544 });
25545 }
25546
25547 eventBus.on('canvas.init', function(event) {
25548 initEvents(event.svg);
25549 });
25550 }
25551
25552
25553 TouchInteractionEvents.$inject = [
25554 'injector',
25555 'canvas',
25556 'eventBus',
25557 'elementRegistry',
25558 'interactionEvents',
25559 'touchFix'
25560 ];
25561
25562 function TouchFix(canvas, eventBus) {
25563
25564 var self = this;
25565
25566 eventBus.on('canvas.init', function(e) {
25567 self.addBBoxMarker(e.svg);
25568 });
25569 }
25570
25571 TouchFix.$inject = [ 'canvas', 'eventBus' ];
25572
25573
25574 /**
25575 * Safari mobile (iOS 7) does not fire touchstart event in <SVG> element
25576 * if there is no shape between 0,0 and viewport elements origin.
25577 *
25578 * So touchstart event is only fired when the <g class="viewport"> element was hit.
25579 * Putting an element over and below the 'viewport' fixes that behavior.
25580 */
25581 TouchFix.prototype.addBBoxMarker = function(svg) {
25582
25583 var markerStyle = {
25584 fill: 'none',
25585 class: 'outer-bound-marker'
25586 };
25587
25588 var rect1 = create('rect');
25589 attr$1(rect1, {
25590 x: -10000,
25591 y: 10000,
25592 width: 10,
25593 height: 10
25594 });
25595 attr$1(rect1, markerStyle);
25596
25597 append(svg, rect1);
25598
25599 var rect2 = create('rect');
25600 attr$1(rect2, {
25601 x: 10000,
25602 y: 10000,
25603 width: 10,
25604 height: 10
25605 });
25606 attr$1(rect2, markerStyle);
25607
25608 append(svg, rect2);
25609 };
25610
25611 var TouchModule = {
25612 __depends__: [ InteractionEventsModule ],
25613 __init__: [ 'touchInteractionEvents' ],
25614 touchInteractionEvents: [ 'type', TouchInteractionEvents ],
25615 touchFix: [ 'type', TouchFix ]
25616 };
25617
25618 var TouchModule$1 = {
25619 __depends__: [
25620 TouchModule
25621 ]
25622 };
25623
25624 function last(arr) {
25625 return arr && arr[arr.length - 1];
25626 }
25627
25628 function sortTopOrMiddle(element) {
25629 return element.y;
25630 }
25631
25632 function sortLeftOrCenter(element) {
25633 return element.x;
25634 }
25635
25636 /**
25637 * Sorting functions for different types of alignment
25638 *
25639 * @type {Object}
25640 *
25641 * @return {Function}
25642 */
25643 var ALIGNMENT_SORTING = {
25644 left: sortLeftOrCenter,
25645 center: sortLeftOrCenter,
25646 right: function(element) {
25647 return element.x + element.width;
25648 },
25649 top: sortTopOrMiddle,
25650 middle: sortTopOrMiddle,
25651 bottom: function(element) {
25652 return element.y + element.height;
25653 }
25654 };
25655
25656
25657 function AlignElements(modeling) {
25658 this._modeling = modeling;
25659 }
25660
25661 AlignElements.$inject = [ 'modeling' ];
25662
25663
25664 /**
25665 * Get the relevant "axis" and "dimension" related to the current type of alignment
25666 *
25667 * @param {string} type left|right|center|top|bottom|middle
25668 *
25669 * @return {Object} { axis, dimension }
25670 */
25671 AlignElements.prototype._getOrientationDetails = function(type) {
25672 var vertical = [ 'top', 'bottom', 'middle' ],
25673 axis = 'x',
25674 dimension = 'width';
25675
25676 if (vertical.indexOf(type) !== -1) {
25677 axis = 'y';
25678 dimension = 'height';
25679 }
25680
25681 return {
25682 axis: axis,
25683 dimension: dimension
25684 };
25685 };
25686
25687 AlignElements.prototype._isType = function(type, types) {
25688 return types.indexOf(type) !== -1;
25689 };
25690
25691 /**
25692 * Get a point on the relevant axis where elements should align to
25693 *
25694 * @param {string} type left|right|center|top|bottom|middle
25695 * @param {Array} sortedElements
25696 *
25697 * @return {Object}
25698 */
25699 AlignElements.prototype._alignmentPosition = function(type, sortedElements) {
25700 var orientation = this._getOrientationDetails(type),
25701 axis = orientation.axis,
25702 dimension = orientation.dimension,
25703 alignment = {},
25704 centers = {},
25705 hasSharedCenters = false,
25706 centeredElements,
25707 firstElement,
25708 lastElement;
25709
25710 function getMiddleOrTop(first, last) {
25711 return Math.round((first[axis] + last[axis] + last[dimension]) / 2);
25712 }
25713
25714 if (this._isType(type, [ 'left', 'top' ])) {
25715 alignment[type] = sortedElements[0][axis];
25716
25717 } else if (this._isType(type, [ 'right', 'bottom' ])) {
25718 lastElement = last(sortedElements);
25719
25720 alignment[type] = lastElement[axis] + lastElement[dimension];
25721
25722 } else if (this._isType(type, [ 'center', 'middle' ])) {
25723
25724 // check if there is a center shared by more than one shape
25725 // if not, just take the middle of the range
25726 forEach(sortedElements, function(element) {
25727 var center = element[axis] + Math.round(element[dimension] / 2);
25728
25729 if (centers[center]) {
25730 centers[center].elements.push(element);
25731 } else {
25732 centers[center] = {
25733 elements: [ element ],
25734 center: center
25735 };
25736 }
25737 });
25738
25739 centeredElements = sortBy(centers, function(center) {
25740 if (center.elements.length > 1) {
25741 hasSharedCenters = true;
25742 }
25743
25744 return center.elements.length;
25745 });
25746
25747 if (hasSharedCenters) {
25748 alignment[type] = last(centeredElements).center;
25749
25750 return alignment;
25751 }
25752
25753 firstElement = sortedElements[0];
25754
25755 sortedElements = sortBy(sortedElements, function(element) {
25756 return element[axis] + element[dimension];
25757 });
25758
25759 lastElement = last(sortedElements);
25760
25761 alignment[type] = getMiddleOrTop(firstElement, lastElement);
25762 }
25763
25764 return alignment;
25765 };
25766
25767 /**
25768 * Executes the alignment of a selection of elements
25769 *
25770 * @param {Array} elements [description]
25771 * @param {string} type left|right|center|top|bottom|middle
25772 */
25773 AlignElements.prototype.trigger = function(elements, type) {
25774 var modeling = this._modeling;
25775
25776 var filteredElements = filter(elements, function(element) {
25777 return !(element.waypoints || element.host || element.labelTarget);
25778 });
25779
25780 if (filteredElements.length < 2) {
25781 return;
25782 }
25783
25784 var sortFn = ALIGNMENT_SORTING[type];
25785
25786 var sortedElements = sortBy(filteredElements, sortFn);
25787
25788 var alignment = this._alignmentPosition(type, sortedElements);
25789
25790 modeling.alignElements(sortedElements, alignment);
25791 };
25792
25793 var AlignElementsModule = {
25794 __init__: [ 'alignElements' ],
25795 alignElements: [ 'type', AlignElements ]
25796 };
25797
25798 // padding to detect element placement
25799 var PLACEMENT_DETECTION_PAD = 10;
25800
25801 var DEFAULT_DISTANCE = 50;
25802
25803 var DEFAULT_MAX_DISTANCE = 250;
25804
25805
25806 /**
25807 * Get free position starting from given position.
25808 *
25809 * @param {djs.model.Shape} source
25810 * @param {djs.model.Shape} element
25811 * @param {Point} position
25812 * @param {Function} getNextPosition
25813 *
25814 * @return {Point}
25815 */
25816 function findFreePosition(source, element, position, getNextPosition) {
25817 var connectedAtPosition;
25818
25819 while ((connectedAtPosition = getConnectedAtPosition(source, position, element))) {
25820 position = getNextPosition(element, position, connectedAtPosition);
25821 }
25822
25823 return position;
25824 }
25825
25826 /**
25827 * Returns function that returns next position.
25828 *
25829 * @param {Object} nextPositionDirection
25830 * @param {Object} [nextPositionDirection.x]
25831 * @param {Object} [nextPositionDirection.y]
25832 *
25833 * @returns {Function}
25834 */
25835 function generateGetNextPosition(nextPositionDirection) {
25836 return function(element, previousPosition, connectedAtPosition) {
25837 var nextPosition = {
25838 x: previousPosition.x,
25839 y: previousPosition.y
25840 };
25841
25842 [ 'x', 'y' ].forEach(function(axis) {
25843
25844 var nextPositionDirectionForAxis = nextPositionDirection[ axis ];
25845
25846 if (!nextPositionDirectionForAxis) {
25847 return;
25848 }
25849
25850 var dimension = axis === 'x' ? 'width' : 'height';
25851
25852 var margin = nextPositionDirectionForAxis.margin,
25853 minDistance = nextPositionDirectionForAxis.minDistance;
25854
25855 if (margin < 0) {
25856 nextPosition[ axis ] = Math.min(
25857 connectedAtPosition[ axis ] + margin - element[ dimension ] / 2,
25858 previousPosition[ axis ] - minDistance + margin
25859 );
25860 } else {
25861 nextPosition[ axis ] = Math.max(
25862 connectedAtPosition[ axis ] + connectedAtPosition[ dimension ] + margin + element[ dimension ] / 2,
25863 previousPosition[ axis ] + minDistance + margin
25864 );
25865 }
25866 });
25867
25868 return nextPosition;
25869 };
25870 }
25871
25872 /**
25873 * Return target at given position, if defined.
25874 *
25875 * This takes connected elements from host and attachers
25876 * into account, too.
25877 */
25878 function getConnectedAtPosition(source, position, element) {
25879
25880 var bounds = {
25881 x: position.x - (element.width / 2),
25882 y: position.y - (element.height / 2),
25883 width: element.width,
25884 height: element.height
25885 };
25886
25887 var closure = getAutoPlaceClosure(source);
25888
25889 return find(closure, function(target) {
25890
25891 if (target === element) {
25892 return false;
25893 }
25894
25895 var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
25896
25897 return orientation === 'intersect';
25898 });
25899 }
25900
25901 /**
25902 * Compute optimal distance between source and target based on existing connections to and from source.
25903 * Assumes left-to-right and top-to-down modeling.
25904 *
25905 * @param {djs.model.Shape} source
25906 * @param {Object} [hints]
25907 * @param {number} [hints.defaultDistance]
25908 * @param {string} [hints.direction]
25909 * @param {Function} [hints.filter]
25910 * @param {Function} [hints.getWeight]
25911 * @param {number} [hints.maxDistance]
25912 * @param {string} [hints.reference]
25913 *
25914 * @return {number}
25915 */
25916 function getConnectedDistance(source, hints) {
25917 if (!hints) {
25918 hints = {};
25919 }
25920
25921 // targets > sources by default
25922 function getDefaultWeight(connection) {
25923 return connection.source === source ? 1 : -1;
25924 }
25925
25926 var defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE,
25927 direction = hints.direction || 'e',
25928 filter = hints.filter,
25929 getWeight = hints.getWeight || getDefaultWeight,
25930 maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE,
25931 reference = hints.reference || 'start';
25932
25933 if (!filter) {
25934 filter = noneFilter;
25935 }
25936
25937 function getDistance(a, b) {
25938 if (direction === 'n') {
25939 if (reference === 'start') {
25940 return asTRBL(a).top - asTRBL(b).bottom;
25941 } else if (reference === 'center') {
25942 return asTRBL(a).top - getMid(b).y;
25943 } else {
25944 return asTRBL(a).top - asTRBL(b).top;
25945 }
25946 } else if (direction === 'w') {
25947 if (reference === 'start') {
25948 return asTRBL(a).left - asTRBL(b).right;
25949 } else if (reference === 'center') {
25950 return asTRBL(a).left - getMid(b).x;
25951 } else {
25952 return asTRBL(a).left - asTRBL(b).left;
25953 }
25954 } else if (direction === 's') {
25955 if (reference === 'start') {
25956 return asTRBL(b).top - asTRBL(a).bottom;
25957 } else if (reference === 'center') {
25958 return getMid(b).y - asTRBL(a).bottom;
25959 } else {
25960 return asTRBL(b).bottom - asTRBL(a).bottom;
25961 }
25962 } else {
25963 if (reference === 'start') {
25964 return asTRBL(b).left - asTRBL(a).right;
25965 } else if (reference === 'center') {
25966 return getMid(b).x - asTRBL(a).right;
25967 } else {
25968 return asTRBL(b).right - asTRBL(a).right;
25969 }
25970 }
25971 }
25972
25973 var sourcesDistances = source.incoming
25974 .filter(filter)
25975 .map(function(connection) {
25976 var weight = getWeight(connection);
25977
25978 var distance = weight < 0
25979 ? getDistance(connection.source, source)
25980 : getDistance(source, connection.source);
25981
25982 return {
25983 id: connection.source.id,
25984 distance: distance,
25985 weight: weight
25986 };
25987 });
25988
25989 var targetsDistances = source.outgoing
25990 .filter(filter)
25991 .map(function(connection) {
25992 var weight = getWeight(connection);
25993
25994 var distance = weight > 0
25995 ? getDistance(source, connection.target)
25996 : getDistance(connection.target, source);
25997
25998 return {
25999 id: connection.target.id,
26000 distance: distance,
26001 weight: weight
26002 };
26003 });
26004
26005 var distances = sourcesDistances.concat(targetsDistances).reduce(function(accumulator, currentValue) {
26006 accumulator[ currentValue.id + '__weight_' + currentValue.weight ] = currentValue;
26007
26008 return accumulator;
26009 }, {});
26010
26011 var distancesGrouped = reduce(distances, function(accumulator, currentValue) {
26012 var distance = currentValue.distance,
26013 weight = currentValue.weight;
26014
26015 if (distance < 0 || distance > maxDistance) {
26016 return accumulator;
26017 }
26018
26019 if (!accumulator[ String(distance) ]) {
26020 accumulator[ String(distance) ] = 0;
26021 }
26022
26023 accumulator[ String(distance) ] += 1 * weight;
26024
26025 if (!accumulator.distance || accumulator[ accumulator.distance ] < accumulator[ String(distance) ]) {
26026 accumulator.distance = distance;
26027 }
26028
26029 return accumulator;
26030 }, {});
26031
26032 return distancesGrouped.distance || defaultDistance;
26033 }
26034
26035 /**
26036 * Returns all connected elements around the given source.
26037 *
26038 * This includes:
26039 *
26040 * - connected elements
26041 * - host connected elements
26042 * - attachers connected elements
26043 *
26044 * @param {djs.model.Shape} source
26045 *
26046 * @return {Array<djs.model.Shape>}
26047 */
26048 function getAutoPlaceClosure(source) {
26049
26050 var allConnected = getConnected(source);
26051
26052 if (source.host) {
26053 allConnected = allConnected.concat(getConnected(source.host));
26054 }
26055
26056 if (source.attachers) {
26057 allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
26058 return shapes.concat(getConnected(attacher));
26059 }, []));
26060 }
26061
26062 return allConnected;
26063 }
26064
26065 function getConnected(element) {
26066 return getTargets(element).concat(getSources(element));
26067 }
26068
26069 function getSources(shape) {
26070 return shape.incoming.map(function(connection) {
26071 return connection.source;
26072 });
26073 }
26074
26075 function getTargets(shape) {
26076 return shape.outgoing.map(function(connection) {
26077 return connection.target;
26078 });
26079 }
26080
26081 function noneFilter() {
26082 return true;
26083 }
26084
26085 var LOW_PRIORITY$4 = 100;
26086
26087
26088 /**
26089 * A service that places elements connected to existing ones
26090 * to an appropriate position in an _automated_ fashion.
26091 *
26092 * @param {EventBus} eventBus
26093 * @param {Modeling} modeling
26094 */
26095 function AutoPlace(eventBus, modeling, canvas) {
26096
26097 eventBus.on('autoPlace', LOW_PRIORITY$4, function(context) {
26098 var shape = context.shape,
26099 source = context.source;
26100
26101 return getNewShapePosition(source, shape);
26102 });
26103
26104 eventBus.on('autoPlace.end', function(event) {
26105 canvas.scrollToElement(event.shape);
26106 });
26107
26108 /**
26109 * Append shape to source at appropriate position.
26110 *
26111 * @param {djs.model.Shape} source
26112 * @param {djs.model.Shape} shape
26113 *
26114 * @return {djs.model.Shape} appended shape
26115 */
26116 this.append = function(source, shape, hints) {
26117
26118 eventBus.fire('autoPlace.start', {
26119 source: source,
26120 shape: shape
26121 });
26122
26123 // allow others to provide the position
26124 var position = eventBus.fire('autoPlace', {
26125 source: source,
26126 shape: shape
26127 });
26128
26129 var newShape = modeling.appendShape(source, shape, position, source.parent, hints);
26130
26131 eventBus.fire('autoPlace.end', {
26132 source: source,
26133 shape: newShape
26134 });
26135
26136 return newShape;
26137 };
26138
26139 }
26140
26141 AutoPlace.$inject = [
26142 'eventBus',
26143 'modeling',
26144 'canvas'
26145 ];
26146
26147 // helpers //////////
26148
26149 /**
26150 * Find the new position for the target element to
26151 * connect to source.
26152 *
26153 * @param {djs.model.Shape} source
26154 * @param {djs.model.Shape} element
26155 * @param {Object} [hints]
26156 * @param {Object} [hints.defaultDistance]
26157 *
26158 * @returns {Point}
26159 */
26160 function getNewShapePosition(source, element, hints) {
26161 if (!hints) {
26162 hints = {};
26163 }
26164
26165 var distance = hints.defaultDistance || DEFAULT_DISTANCE;
26166
26167 var sourceMid = getMid(source),
26168 sourceTrbl = asTRBL(source);
26169
26170 // simply put element right next to source
26171 return {
26172 x: sourceTrbl.right + distance + element.width / 2,
26173 y: sourceMid.y
26174 };
26175 }
26176
26177 /**
26178 * Select element after auto placement.
26179 *
26180 * @param {EventBus} eventBus
26181 * @param {Selection} selection
26182 */
26183 function AutoPlaceSelectionBehavior(eventBus, selection) {
26184
26185 eventBus.on('autoPlace.end', 500, function(e) {
26186 selection.select(e.shape);
26187 });
26188
26189 }
26190
26191 AutoPlaceSelectionBehavior.$inject = [
26192 'eventBus',
26193 'selection'
26194 ];
26195
26196 var AutoPlaceModule = {
26197 __init__: [ 'autoPlaceSelectionBehavior' ],
26198 autoPlace: [ 'type', AutoPlace ],
26199 autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
26200 };
26201
26202 /**
26203 * Return true if element has any of the given types.
26204 *
26205 * @param {djs.model.Base} element
26206 * @param {Array<string>} types
26207 *
26208 * @return {boolean}
26209 */
26210 function isAny(element, types) {
26211 return some(types, function(t) {
26212 return is$1(element, t);
26213 });
26214 }
26215
26216
26217 /**
26218 * Return the parent of the element with any of the given types.
26219 *
26220 * @param {djs.model.Base} element
26221 * @param {string|Array<string>} anyType
26222 *
26223 * @return {djs.model.Base}
26224 */
26225 function getParent$1(element, anyType) {
26226
26227 if (typeof anyType === 'string') {
26228 anyType = [ anyType ];
26229 }
26230
26231 while ((element = element.parent)) {
26232 if (isAny(element, anyType)) {
26233 return element;
26234 }
26235 }
26236
26237 return null;
26238 }
26239
26240 /**
26241 * Find the new position for the target element to
26242 * connect to source.
26243 *
26244 * @param {djs.model.Shape} source
26245 * @param {djs.model.Shape} element
26246 *
26247 * @return {Point}
26248 */
26249 function getNewShapePosition$1(source, element) {
26250
26251 if (is$1(element, 'bpmn:TextAnnotation')) {
26252 return getTextAnnotationPosition(source, element);
26253 }
26254
26255 if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
26256 return getDataElementPosition(source, element);
26257 }
26258
26259 if (is$1(element, 'bpmn:FlowNode')) {
26260 return getFlowNodePosition(source, element);
26261 }
26262 }
26263
26264 /**
26265 * Always try to place element right of source;
26266 * compute actual distance from previous nodes in flow.
26267 */
26268 function getFlowNodePosition(source, element) {
26269
26270 var sourceTrbl = asTRBL(source);
26271 var sourceMid = getMid(source);
26272
26273 var horizontalDistance = getConnectedDistance(source, {
26274 filter: function(connection) {
26275 return is$1(connection, 'bpmn:SequenceFlow');
26276 }
26277 });
26278
26279 var margin = 30,
26280 minDistance = 80,
26281 orientation = 'left';
26282
26283 if (is$1(source, 'bpmn:BoundaryEvent')) {
26284 orientation = getOrientation(source, source.host, -25);
26285
26286 if (orientation.indexOf('top') !== -1) {
26287 margin *= -1;
26288 }
26289 }
26290
26291 var position = {
26292 x: sourceTrbl.right + horizontalDistance + element.width / 2,
26293 y: sourceMid.y + getVerticalDistance(orientation, minDistance)
26294 };
26295
26296 var nextPositionDirection = {
26297 y: {
26298 margin: margin,
26299 minDistance: minDistance
26300 }
26301 };
26302
26303 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
26304 }
26305
26306
26307 function getVerticalDistance(orientation, minDistance) {
26308 if (orientation.indexOf('top') != -1) {
26309 return -1 * minDistance;
26310 } else if (orientation.indexOf('bottom') != -1) {
26311 return minDistance;
26312 } else {
26313 return 0;
26314 }
26315 }
26316
26317
26318 /**
26319 * Always try to place text annotations top right of source.
26320 */
26321 function getTextAnnotationPosition(source, element) {
26322
26323 var sourceTrbl = asTRBL(source);
26324
26325 var position = {
26326 x: sourceTrbl.right + element.width / 2,
26327 y: sourceTrbl.top - 50 - element.height / 2
26328 };
26329
26330 var nextPositionDirection = {
26331 y: {
26332 margin: -30,
26333 minDistance: 20
26334 }
26335 };
26336
26337 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
26338 }
26339
26340
26341 /**
26342 * Always put element bottom right of source.
26343 */
26344 function getDataElementPosition(source, element) {
26345
26346 var sourceTrbl = asTRBL(source);
26347
26348 var position = {
26349 x: sourceTrbl.right - 10 + element.width / 2,
26350 y: sourceTrbl.bottom + 40 + element.width / 2
26351 };
26352
26353 var nextPositionDirection = {
26354 x: {
26355 margin: 30,
26356 minDistance: 30
26357 }
26358 };
26359
26360 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
26361 }
26362
26363 /**
26364 * BPMN auto-place behavior.
26365 *
26366 * @param {EventBus} eventBus
26367 */
26368 function AutoPlace$1(eventBus) {
26369 eventBus.on('autoPlace', function(context) {
26370 var shape = context.shape,
26371 source = context.source;
26372
26373 return getNewShapePosition$1(source, shape);
26374 });
26375 }
26376
26377 AutoPlace$1.$inject = [ 'eventBus' ];
26378
26379 var AutoPlaceModule$1 = {
26380 __depends__: [ AutoPlaceModule ],
26381 __init__: [ 'bpmnAutoPlace' ],
26382 bpmnAutoPlace: [ 'type', AutoPlace$1 ]
26383 };
26384
26385 var DEFAULT_PRIORITY$2 = 1000;
26386
26387 /**
26388 * A utility that can be used to plug-in into the command execution for
26389 * extension and/or validation.
26390 *
26391 * @param {EventBus} eventBus
26392 *
26393 * @example
26394 *
26395 * import inherits from 'inherits';
26396 *
26397 * import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
26398 *
26399 * function CommandLogger(eventBus) {
26400 * CommandInterceptor.call(this, eventBus);
26401 *
26402 * this.preExecute(function(event) {
26403 * console.log('command pre-execute', event);
26404 * });
26405 * }
26406 *
26407 * inherits(CommandLogger, CommandInterceptor);
26408 *
26409 */
26410 function CommandInterceptor(eventBus) {
26411 this._eventBus = eventBus;
26412 }
26413
26414 CommandInterceptor.$inject = [ 'eventBus' ];
26415
26416 function unwrapEvent(fn, that) {
26417 return function(event) {
26418 return fn.call(that || null, event.context, event.command, event);
26419 };
26420 }
26421
26422 /**
26423 * Register an interceptor for a command execution
26424 *
26425 * @param {string|Array<string>} [events] list of commands to register on
26426 * @param {string} [hook] command hook, i.e. preExecute, executed to listen on
26427 * @param {number} [priority] the priority on which to hook into the execution
26428 * @param {Function} handlerFn interceptor to be invoked with (event)
26429 * @param {boolean} unwrap if true, unwrap the event and pass (context, command, event) to the
26430 * listener instead
26431 * @param {Object} [that] Pass context (`this`) to the handler function
26432 */
26433 CommandInterceptor.prototype.on = function(events, hook, priority, handlerFn, unwrap, that) {
26434
26435 if (isFunction(hook) || isNumber(hook)) {
26436 that = unwrap;
26437 unwrap = handlerFn;
26438 handlerFn = priority;
26439 priority = hook;
26440 hook = null;
26441 }
26442
26443 if (isFunction(priority)) {
26444 that = unwrap;
26445 unwrap = handlerFn;
26446 handlerFn = priority;
26447 priority = DEFAULT_PRIORITY$2;
26448 }
26449
26450 if (isObject(unwrap)) {
26451 that = unwrap;
26452 unwrap = false;
26453 }
26454
26455 if (!isFunction(handlerFn)) {
26456 throw new Error('handlerFn must be a function');
26457 }
26458
26459 if (!isArray(events)) {
26460 events = [ events ];
26461 }
26462
26463 var eventBus = this._eventBus;
26464
26465 forEach(events, function(event) {
26466
26467 // concat commandStack(.event)?(.hook)?
26468 var fullEvent = [ 'commandStack', event, hook ].filter(function(e) { return e; }).join('.');
26469
26470 eventBus.on(fullEvent, priority, unwrap ? unwrapEvent(handlerFn, that) : handlerFn, that);
26471 });
26472 };
26473
26474
26475 var hooks = [
26476 'canExecute',
26477 'preExecute',
26478 'preExecuted',
26479 'execute',
26480 'executed',
26481 'postExecute',
26482 'postExecuted',
26483 'revert',
26484 'reverted'
26485 ];
26486
26487 /*
26488 * Install hook shortcuts
26489 *
26490 * This will generate the CommandInterceptor#(preExecute|...|reverted) methods
26491 * which will in term forward to CommandInterceptor#on.
26492 */
26493 forEach(hooks, function(hook) {
26494
26495 /**
26496 * {canExecute|preExecute|preExecuted|execute|executed|postExecute|postExecuted|revert|reverted}
26497 *
26498 * A named hook for plugging into the command execution
26499 *
26500 * @param {string|Array<string>} [events] list of commands to register on
26501 * @param {number} [priority] the priority on which to hook into the execution
26502 * @param {Function} handlerFn interceptor to be invoked with (event)
26503 * @param {boolean} [unwrap=false] if true, unwrap the event and pass (context, command, event) to the
26504 * listener instead
26505 * @param {Object} [that] Pass context (`this`) to the handler function
26506 */
26507 CommandInterceptor.prototype[hook] = function(events, priority, handlerFn, unwrap, that) {
26508
26509 if (isFunction(events) || isNumber(events)) {
26510 that = unwrap;
26511 unwrap = handlerFn;
26512 handlerFn = priority;
26513 priority = events;
26514 events = null;
26515 }
26516
26517 this.on(events, hook, priority, handlerFn, unwrap, that);
26518 };
26519 });
26520
26521 /**
26522 * An auto resize component that takes care of expanding a parent element
26523 * if child elements are created or moved close the parents edge.
26524 *
26525 * @param {EventBus} eventBus
26526 * @param {ElementRegistry} elementRegistry
26527 * @param {Modeling} modeling
26528 * @param {Rules} rules
26529 */
26530 function AutoResize(eventBus, elementRegistry, modeling, rules) {
26531
26532 CommandInterceptor.call(this, eventBus);
26533
26534 this._elementRegistry = elementRegistry;
26535 this._modeling = modeling;
26536 this._rules = rules;
26537
26538 var self = this;
26539
26540 this.postExecuted([ 'shape.create' ], function(event) {
26541 var context = event.context,
26542 hints = context.hints || {},
26543 shape = context.shape,
26544 parent = context.parent || context.newParent;
26545
26546 if (hints.autoResize === false) {
26547 return;
26548 }
26549
26550 self._expand([ shape ], parent);
26551 });
26552
26553 this.postExecuted([ 'elements.move' ], function(event) {
26554 var context = event.context,
26555 elements = flatten(values(context.closure.topLevel)),
26556 hints = context.hints;
26557
26558 var autoResize = hints ? hints.autoResize : true;
26559
26560 if (autoResize === false) {
26561 return;
26562 }
26563
26564 var expandings = groupBy(elements, function(element) {
26565 return element.parent.id;
26566 });
26567
26568 forEach(expandings, function(elements, parentId) {
26569
26570 // optionally filter elements to be considered when resizing
26571 if (isArray(autoResize)) {
26572 elements = elements.filter(function(element) {
26573 return find(autoResize, matchPattern({ id: element.id }));
26574 });
26575 }
26576
26577 self._expand(elements, parentId);
26578 });
26579 });
26580
26581 this.postExecuted([ 'shape.toggleCollapse' ], function(event) {
26582 var context = event.context,
26583 hints = context.hints,
26584 shape = context.shape;
26585
26586 if (hints && hints.autoResize === false) {
26587 return;
26588 }
26589
26590 if (shape.collapsed) {
26591 return;
26592 }
26593
26594 self._expand(shape.children || [], shape);
26595 });
26596
26597 this.postExecuted([ 'shape.resize' ], function(event) {
26598 var context = event.context,
26599 hints = context.hints,
26600 shape = context.shape,
26601 parent = shape.parent;
26602
26603 if (hints && hints.autoResize === false) {
26604 return;
26605 }
26606
26607 if (parent) {
26608 self._expand([ shape ], parent);
26609 }
26610 });
26611
26612 }
26613
26614 AutoResize.$inject = [
26615 'eventBus',
26616 'elementRegistry',
26617 'modeling',
26618 'rules'
26619 ];
26620
26621 inherits_browser(AutoResize, CommandInterceptor);
26622
26623
26624 /**
26625 * Calculate the new bounds of the target shape, given
26626 * a number of elements have been moved or added into the parent.
26627 *
26628 * This method considers the current size, the added elements as well as
26629 * the provided padding for the new bounds.
26630 *
26631 * @param {Array<djs.model.Shape>} elements
26632 * @param {djs.model.Shape} target
26633 */
26634 AutoResize.prototype._getOptimalBounds = function(elements, target) {
26635
26636 var offset = this.getOffset(target),
26637 padding = this.getPadding(target);
26638
26639 var elementsTrbl = asTRBL(getBBox(elements)),
26640 targetTrbl = asTRBL(target);
26641
26642 var newTrbl = {};
26643
26644 if (elementsTrbl.top - targetTrbl.top < padding.top) {
26645 newTrbl.top = elementsTrbl.top - offset.top;
26646 }
26647
26648 if (elementsTrbl.left - targetTrbl.left < padding.left) {
26649 newTrbl.left = elementsTrbl.left - offset.left;
26650 }
26651
26652 if (targetTrbl.right - elementsTrbl.right < padding.right) {
26653 newTrbl.right = elementsTrbl.right + offset.right;
26654 }
26655
26656 if (targetTrbl.bottom - elementsTrbl.bottom < padding.bottom) {
26657 newTrbl.bottom = elementsTrbl.bottom + offset.bottom;
26658 }
26659
26660 return asBounds(assign({}, targetTrbl, newTrbl));
26661 };
26662
26663
26664 /**
26665 * Expand the target shape respecting rules, offset and padding
26666 *
26667 * @param {Array<djs.model.Shape>} elements
26668 * @param {djs.model.Shape|string} target|targetId
26669 */
26670 AutoResize.prototype._expand = function(elements, target) {
26671
26672 if (typeof target === 'string') {
26673 target = this._elementRegistry.get(target);
26674 }
26675
26676 var allowed = this._rules.allowed('element.autoResize', {
26677 elements: elements,
26678 target: target
26679 });
26680
26681 if (!allowed) {
26682 return;
26683 }
26684
26685 // calculate the new bounds
26686 var newBounds = this._getOptimalBounds(elements, target);
26687
26688 if (!boundsChanged(newBounds, target)) {
26689 return;
26690 }
26691
26692 var resizeDirections = getResizeDirections(pick(target, [ 'x', 'y', 'width', 'height' ]), newBounds);
26693
26694 // resize the parent shape
26695 this.resize(target, newBounds, {
26696 autoResize: resizeDirections
26697 });
26698
26699 var parent = target.parent;
26700
26701 // recursively expand parent elements
26702 if (parent) {
26703 this._expand([ target ], parent);
26704 }
26705 };
26706
26707
26708 /**
26709 * Get the amount to expand the given shape in each direction.
26710 *
26711 * @param {djs.model.Shape} shape
26712 *
26713 * @return {TRBL}
26714 */
26715 AutoResize.prototype.getOffset = function(shape) {
26716 return { top: 60, bottom: 60, left: 100, right: 100 };
26717 };
26718
26719
26720 /**
26721 * Get the activation threshold for each side for which
26722 * resize triggers.
26723 *
26724 * @param {djs.model.Shape} shape
26725 *
26726 * @return {TRBL}
26727 */
26728 AutoResize.prototype.getPadding = function(shape) {
26729 return { top: 2, bottom: 2, left: 15, right: 15 };
26730 };
26731
26732
26733 /**
26734 * Perform the actual resize operation.
26735 *
26736 * @param {djs.model.Shape} shape
26737 * @param {Bounds} newBounds
26738 * @param {Object} [hints]
26739 * @param {string} [hints.autoResize]
26740 */
26741 AutoResize.prototype.resize = function(shape, newBounds, hints) {
26742 this._modeling.resizeShape(shape, newBounds, null, hints);
26743 };
26744
26745
26746 function boundsChanged(newBounds, oldBounds) {
26747 return (
26748 newBounds.x !== oldBounds.x ||
26749 newBounds.y !== oldBounds.y ||
26750 newBounds.width !== oldBounds.width ||
26751 newBounds.height !== oldBounds.height
26752 );
26753 }
26754
26755 /**
26756 * Get directions of resize as {n|w|s|e} e.g. "nw".
26757 *
26758 * @param {Bounds} oldBounds
26759 * @param {Bounds} newBounds
26760 *
26761 * @returns {string} Resize directions as {n|w|s|e}.
26762 */
26763 function getResizeDirections(oldBounds, newBounds) {
26764 var directions = '';
26765
26766 oldBounds = asTRBL(oldBounds);
26767 newBounds = asTRBL(newBounds);
26768
26769 if (oldBounds.top > newBounds.top) {
26770 directions = directions.concat('n');
26771 }
26772
26773 if (oldBounds.right < newBounds.right) {
26774 directions = directions.concat('w');
26775 }
26776
26777 if (oldBounds.bottom < newBounds.bottom) {
26778 directions = directions.concat('s');
26779 }
26780
26781 if (oldBounds.left > newBounds.left) {
26782 directions = directions.concat('e');
26783 }
26784
26785 return directions;
26786 }
26787
26788 /**
26789 * Sub class of the AutoResize module which implements a BPMN
26790 * specific resize function.
26791 */
26792 function BpmnAutoResize(injector) {
26793
26794 injector.invoke(AutoResize, this);
26795 }
26796
26797 BpmnAutoResize.$inject = [
26798 'injector'
26799 ];
26800
26801 inherits_browser(BpmnAutoResize, AutoResize);
26802
26803
26804 /**
26805 * Resize shapes and lanes.
26806 *
26807 * @param {djs.model.Shape} target
26808 * @param {Bounds} newBounds
26809 * @param {Object} hints
26810 */
26811 BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {
26812
26813 if (is$1(target, 'bpmn:Participant')) {
26814 this._modeling.resizeLane(target, newBounds, null, hints);
26815 } else {
26816 this._modeling.resizeShape(target, newBounds, null, hints);
26817 }
26818 };
26819
26820 /**
26821 * A basic provider that may be extended to implement modeling rules.
26822 *
26823 * Extensions should implement the init method to actually add their custom
26824 * modeling checks. Checks may be added via the #addRule(action, fn) method.
26825 *
26826 * @param {EventBus} eventBus
26827 */
26828 function RuleProvider(eventBus) {
26829 CommandInterceptor.call(this, eventBus);
26830
26831 this.init();
26832 }
26833
26834 RuleProvider.$inject = [ 'eventBus' ];
26835
26836 inherits_browser(RuleProvider, CommandInterceptor);
26837
26838
26839 /**
26840 * Adds a modeling rule for the given action, implemented through
26841 * a callback function.
26842 *
26843 * The function will receive the modeling specific action context
26844 * to perform its check. It must return `false` to disallow the
26845 * action from happening or `true` to allow the action.
26846 *
26847 * A rule provider may pass over the evaluation to lower priority
26848 * rules by returning return nothing (or <code>undefined</code>).
26849 *
26850 * @example
26851 *
26852 * ResizableRules.prototype.init = function() {
26853 *
26854 * \/**
26855 * * Return `true`, `false` or nothing to denote
26856 * * _allowed_, _not allowed_ and _continue evaluating_.
26857 * *\/
26858 * this.addRule('shape.resize', function(context) {
26859 *
26860 * var shape = context.shape;
26861 *
26862 * if (!context.newBounds) {
26863 * // check general resizability
26864 * if (!shape.resizable) {
26865 * return false;
26866 * }
26867 *
26868 * // not returning anything (read: undefined)
26869 * // will continue the evaluation of other rules
26870 * // (with lower priority)
26871 * return;
26872 * } else {
26873 * // element must have minimum size of 10*10 points
26874 * return context.newBounds.width > 10 && context.newBounds.height > 10;
26875 * }
26876 * });
26877 * };
26878 *
26879 * @param {string|Array<string>} actions the identifier for the modeling action to check
26880 * @param {number} [priority] the priority at which this rule is being applied
26881 * @param {Function} fn the callback function that performs the actual check
26882 */
26883 RuleProvider.prototype.addRule = function(actions, priority, fn) {
26884
26885 var self = this;
26886
26887 if (typeof actions === 'string') {
26888 actions = [ actions ];
26889 }
26890
26891 actions.forEach(function(action) {
26892
26893 self.canExecute(action, priority, function(context, action, event) {
26894 return fn(context);
26895 }, true);
26896 });
26897 };
26898
26899 /**
26900 * Implement this method to add new rules during provider initialization.
26901 */
26902 RuleProvider.prototype.init = function() {};
26903
26904 /**
26905 * This is a base rule provider for the element.autoResize rule.
26906 */
26907 function AutoResizeProvider(eventBus) {
26908
26909 RuleProvider.call(this, eventBus);
26910
26911 var self = this;
26912
26913 this.addRule('element.autoResize', function(context) {
26914 return self.canResize(context.elements, context.target);
26915 });
26916 }
26917
26918 AutoResizeProvider.$inject = [ 'eventBus' ];
26919
26920 inherits_browser(AutoResizeProvider, RuleProvider);
26921
26922 /**
26923 * Needs to be implemented by sub classes to allow actual auto resize
26924 *
26925 * @param {Array<djs.model.Shape>} elements
26926 * @param {djs.model.Shape} target
26927 *
26928 * @return {boolean}
26929 */
26930 AutoResizeProvider.prototype.canResize = function(elements, target) {
26931 return false;
26932 };
26933
26934 /**
26935 * This module is a provider for automatically resizing parent BPMN elements
26936 */
26937 function BpmnAutoResizeProvider(eventBus, modeling) {
26938 AutoResizeProvider.call(this, eventBus);
26939
26940 this._modeling = modeling;
26941 }
26942
26943 inherits_browser(BpmnAutoResizeProvider, AutoResizeProvider);
26944
26945 BpmnAutoResizeProvider.$inject = [
26946 'eventBus',
26947 'modeling'
26948 ];
26949
26950
26951 /**
26952 * Check if the given target can be expanded
26953 *
26954 * @param {djs.model.Shape} target
26955 *
26956 * @return {boolean}
26957 */
26958 BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {
26959
26960 if (!is$1(target, 'bpmn:Participant') && !is$1(target, 'bpmn:Lane') && !(is$1(target, 'bpmn:SubProcess'))) {
26961 return false;
26962 }
26963
26964 var canResize = true;
26965
26966 forEach(elements, function(element) {
26967
26968 if (is$1(element, 'bpmn:Lane') || element.labelTarget) {
26969 canResize = false;
26970 return;
26971 }
26972 });
26973
26974 return canResize;
26975 };
26976
26977 var AutoResizeModule = {
26978 __init__: [
26979 'bpmnAutoResize',
26980 'bpmnAutoResizeProvider'
26981 ],
26982 bpmnAutoResize: [ 'type', BpmnAutoResize ],
26983 bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
26984 };
26985
26986 var HIGH_PRIORITY = 1500;
26987
26988
26989 /**
26990 * Browsers may swallow certain events (hover, out ...) if users are to
26991 * fast with the mouse.
26992 *
26993 * @see http://stackoverflow.com/questions/7448468/why-cant-i-reliably-capture-a-mouseout-event
26994 *
26995 * The fix implemented in this component ensure that we
26996 *
26997 * 1) have a hover state after a successful drag.move event
26998 * 2) have an out event when dragging leaves an element
26999 *
27000 * @param {ElementRegistry} elementRegistry
27001 * @param {EventBus} eventBus
27002 * @param {Injector} injector
27003 */
27004 function HoverFix(elementRegistry, eventBus, injector) {
27005
27006 var self = this;
27007
27008 var dragging = injector.get('dragging', false);
27009
27010 /**
27011 * Make sure we are god damn hovering!
27012 *
27013 * @param {Event} dragging event
27014 */
27015 function ensureHover(event) {
27016
27017 if (event.hover) {
27018 return;
27019 }
27020
27021 var originalEvent = event.originalEvent;
27022
27023 var gfx = self._findTargetGfx(originalEvent);
27024
27025 var element = gfx && elementRegistry.get(gfx);
27026
27027 if (gfx && element) {
27028
27029 // 1) cancel current mousemove
27030 event.stopPropagation();
27031
27032 // 2) emit fake hover for new target
27033 dragging.hover({ element: element, gfx: gfx });
27034
27035 // 3) re-trigger move event
27036 dragging.move(originalEvent);
27037 }
27038 }
27039
27040
27041 if (dragging) {
27042
27043 /**
27044 * We wait for a specific sequence of events before
27045 * emitting a fake drag.hover event.
27046 *
27047 * Event Sequence:
27048 *
27049 * drag.start
27050 * drag.move >> ensure we are hovering
27051 */
27052 eventBus.on('drag.start', function(event) {
27053
27054 eventBus.once('drag.move', HIGH_PRIORITY, function(event) {
27055
27056 ensureHover(event);
27057
27058 });
27059
27060 });
27061 }
27062
27063
27064 /**
27065 * We make sure that element.out is always fired, even if the
27066 * browser swallows an element.out event.
27067 *
27068 * Event sequence:
27069 *
27070 * element.hover
27071 * (element.out >> sometimes swallowed)
27072 * element.hover >> ensure we fired element.out
27073 */
27074 (function() {
27075 var hoverGfx;
27076 var hover;
27077
27078 eventBus.on('element.hover', function(event) {
27079
27080 // (1) remember current hover element
27081 hoverGfx = event.gfx;
27082 hover = event.element;
27083 });
27084
27085 eventBus.on('element.hover', HIGH_PRIORITY, function(event) {
27086
27087 // (3) am I on an element still?
27088 if (hover) {
27089
27090 // (4) that is a problem, gotta "simulate the out"
27091 eventBus.fire('element.out', {
27092 element: hover,
27093 gfx: hoverGfx
27094 });
27095 }
27096
27097 });
27098
27099 eventBus.on('element.out', function() {
27100
27101 // (2) unset hover state if we correctly outed us *GG*
27102 hoverGfx = null;
27103 hover = null;
27104 });
27105
27106 })();
27107
27108 this._findTargetGfx = function(event) {
27109 var position,
27110 target;
27111
27112 if (!(event instanceof MouseEvent)) {
27113 return;
27114 }
27115
27116 position = toPoint(event);
27117
27118 // damn expensive operation, ouch!
27119 target = document.elementFromPoint(position.x, position.y);
27120
27121 return getGfx(target);
27122 };
27123
27124 }
27125
27126 HoverFix.$inject = [
27127 'elementRegistry',
27128 'eventBus',
27129 'injector'
27130 ];
27131
27132
27133 // helpers /////////////////////
27134
27135 function getGfx(target) {
27136 return closest(target, 'svg, .djs-element', true);
27137 }
27138
27139 var HoverFixModule = {
27140 __init__: [
27141 'hoverFix'
27142 ],
27143 hoverFix: [ 'type', HoverFix ],
27144 };
27145
27146 var round$1 = Math.round;
27147
27148 var DRAG_ACTIVE_CLS = 'djs-drag-active';
27149
27150
27151 function preventDefault(event) {
27152 event.preventDefault();
27153 }
27154
27155 function isTouchEvent(event) {
27156
27157 // check for TouchEvent being available first
27158 // (i.e. not available on desktop Firefox)
27159 return typeof TouchEvent !== 'undefined' && event instanceof TouchEvent;
27160 }
27161
27162 function getLength(point) {
27163 return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
27164 }
27165
27166 /**
27167 * A helper that fires canvas localized drag events and realizes
27168 * the general "drag-and-drop" look and feel.
27169 *
27170 * Calling {@link Dragging#activate} activates dragging on a canvas.
27171 *
27172 * It provides the following:
27173 *
27174 * * emits life cycle events, namespaced with a prefix assigned
27175 * during dragging activation
27176 * * sets and restores the cursor
27177 * * sets and restores the selection if elements still exist
27178 * * ensures there can be only one drag operation active at a time
27179 *
27180 * Dragging may be canceled manually by calling {@link Dragging#cancel}
27181 * or by pressing ESC.
27182 *
27183 *
27184 * ## Life-cycle events
27185 *
27186 * Dragging can be in three different states, off, initialized
27187 * and active.
27188 *
27189 * (1) off: no dragging operation is in progress
27190 * (2) initialized: a new drag operation got initialized but not yet
27191 * started (i.e. because of no initial move)
27192 * (3) started: dragging is in progress
27193 *
27194 * Eventually dragging will be off again after a drag operation has
27195 * been ended or canceled via user click or ESC key press.
27196 *
27197 * To indicate transitions between these states dragging emits generic
27198 * life-cycle events with the `drag.` prefix _and_ events namespaced
27199 * to a prefix choosen by a user during drag initialization.
27200 *
27201 * The following events are emitted (appropriately prefixed) via
27202 * the {@link EventBus}.
27203 *
27204 * * `init`
27205 * * `start`
27206 * * `move`
27207 * * `end`
27208 * * `ended` (dragging already in off state)
27209 * * `cancel` (only if previously started)
27210 * * `canceled` (dragging already in off state, only if previously started)
27211 * * `cleanup`
27212 *
27213 *
27214 * @example
27215 *
27216 * function MyDragComponent(eventBus, dragging) {
27217 *
27218 * eventBus.on('mydrag.start', function(event) {
27219 * console.log('yes, we start dragging');
27220 * });
27221 *
27222 * eventBus.on('mydrag.move', function(event) {
27223 * console.log('canvas local coordinates', event.x, event.y, event.dx, event.dy);
27224 *
27225 * // local drag data is passed with the event
27226 * event.context.foo; // "BAR"
27227 *
27228 * // the original mouse event, too
27229 * event.originalEvent; // MouseEvent(...)
27230 * });
27231 *
27232 * eventBus.on('element.click', function(event) {
27233 * dragging.init(event, 'mydrag', {
27234 * cursor: 'grabbing',
27235 * data: {
27236 * context: {
27237 * foo: "BAR"
27238 * }
27239 * }
27240 * });
27241 * });
27242 * }
27243 */
27244 function Dragging(eventBus, canvas, selection, elementRegistry) {
27245
27246 var defaultOptions = {
27247 threshold: 5,
27248 trapClick: true
27249 };
27250
27251 // the currently active drag operation
27252 // dragging is active as soon as this context exists.
27253 //
27254 // it is visually _active_ only when a context.active flag is set to true.
27255 var context;
27256
27257 /* convert a global event into local coordinates */
27258 function toLocalPoint(globalPosition) {
27259
27260 var viewbox = canvas.viewbox();
27261
27262 var clientRect = canvas._container.getBoundingClientRect();
27263
27264 return {
27265 x: viewbox.x + (globalPosition.x - clientRect.left) / viewbox.scale,
27266 y: viewbox.y + (globalPosition.y - clientRect.top) / viewbox.scale
27267 };
27268 }
27269
27270 // helpers
27271
27272 function fire(type, dragContext) {
27273 dragContext = dragContext || context;
27274
27275 var event = eventBus.createEvent(
27276 assign(
27277 {},
27278 dragContext.payload,
27279 dragContext.data,
27280 { isTouch: dragContext.isTouch }
27281 )
27282 );
27283
27284 // default integration
27285 if (eventBus.fire('drag.' + type, event) === false) {
27286 return false;
27287 }
27288
27289 return eventBus.fire(dragContext.prefix + '.' + type, event);
27290 }
27291
27292 function restoreSelection(previousSelection) {
27293 var existingSelection = previousSelection.filter(function(element) {
27294 return elementRegistry.get(element.id);
27295 });
27296
27297 existingSelection.length && selection.select(existingSelection);
27298 }
27299
27300 // event listeners
27301
27302 function move(event, activate) {
27303 var payload = context.payload,
27304 displacement = context.displacement;
27305
27306 var globalStart = context.globalStart,
27307 globalCurrent = toPoint(event),
27308 globalDelta = delta(globalCurrent, globalStart);
27309
27310 var localStart = context.localStart,
27311 localCurrent = toLocalPoint(globalCurrent),
27312 localDelta = delta(localCurrent, localStart);
27313
27314
27315 // activate context explicitly or once threshold is reached
27316 if (!context.active && (activate || getLength(globalDelta) > context.threshold)) {
27317
27318 // fire start event with original
27319 // starting coordinates
27320
27321 assign(payload, {
27322 x: round$1(localStart.x + displacement.x),
27323 y: round$1(localStart.y + displacement.y),
27324 dx: 0,
27325 dy: 0
27326 }, { originalEvent: event });
27327
27328 if (false === fire('start')) {
27329 return cancel();
27330 }
27331
27332 context.active = true;
27333
27334 // unset selection and remember old selection
27335 // the previous (old) selection will always passed
27336 // with the event via the event.previousSelection property
27337 if (!context.keepSelection) {
27338 payload.previousSelection = selection.get();
27339 selection.select(null);
27340 }
27341
27342 // allow custom cursor
27343 if (context.cursor) {
27344 set$1(context.cursor);
27345 }
27346
27347 // indicate dragging via marker on root element
27348 canvas.addMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);
27349 }
27350
27351 stopPropagation(event);
27352
27353 if (context.active) {
27354
27355 // update payload with actual coordinates
27356 assign(payload, {
27357 x: round$1(localCurrent.x + displacement.x),
27358 y: round$1(localCurrent.y + displacement.y),
27359 dx: round$1(localDelta.x),
27360 dy: round$1(localDelta.y)
27361 }, { originalEvent: event });
27362
27363 // emit move event
27364 fire('move');
27365 }
27366 }
27367
27368 function end(event) {
27369 var previousContext,
27370 returnValue = true;
27371
27372 if (context.active) {
27373
27374 if (event) {
27375 context.payload.originalEvent = event;
27376
27377 // suppress original event (click, ...)
27378 // because we just ended a drag operation
27379 stopPropagation(event);
27380 }
27381
27382 // implementations may stop restoring the
27383 // original state (selections, ...) by preventing the
27384 // end events default action
27385 returnValue = fire('end');
27386 }
27387
27388 if (returnValue === false) {
27389 fire('rejected');
27390 }
27391
27392 previousContext = cleanup(returnValue !== true);
27393
27394 // last event to be fired when all drag operations are done
27395 // at this point in time no drag operation is in progress anymore
27396 fire('ended', previousContext);
27397 }
27398
27399
27400 // cancel active drag operation if the user presses
27401 // the ESC key on the keyboard
27402
27403 function checkCancel(event) {
27404
27405 if (event.which === 27) {
27406 preventDefault(event);
27407
27408 cancel();
27409 }
27410 }
27411
27412
27413 // prevent ghost click that might occur after a finished
27414 // drag and drop session
27415
27416 function trapClickAndEnd(event) {
27417
27418 var untrap;
27419
27420 // trap the click in case we are part of an active
27421 // drag operation. This will effectively prevent
27422 // the ghost click that cannot be canceled otherwise.
27423 if (context.active) {
27424
27425 untrap = install(eventBus);
27426
27427 // remove trap after minimal delay
27428 setTimeout(untrap, 400);
27429
27430 // prevent default action (click)
27431 preventDefault(event);
27432 }
27433
27434 end(event);
27435 }
27436
27437 function trapTouch(event) {
27438 move(event);
27439 }
27440
27441 // update the drag events hover (djs.model.Base) and hoverGfx (Snap<SVGElement>)
27442 // properties during hover and out and fire {prefix}.hover and {prefix}.out properties
27443 // respectively
27444
27445 function hover(event) {
27446 var payload = context.payload;
27447
27448 payload.hoverGfx = event.gfx;
27449 payload.hover = event.element;
27450
27451 fire('hover');
27452 }
27453
27454 function out(event) {
27455 fire('out');
27456
27457 var payload = context.payload;
27458
27459 payload.hoverGfx = null;
27460 payload.hover = null;
27461 }
27462
27463
27464 // life-cycle methods
27465
27466 function cancel(restore) {
27467 var previousContext;
27468
27469 if (!context) {
27470 return;
27471 }
27472
27473 var wasActive = context.active;
27474
27475 if (wasActive) {
27476 fire('cancel');
27477 }
27478
27479 previousContext = cleanup(restore);
27480
27481 if (wasActive) {
27482
27483 // last event to be fired when all drag operations are done
27484 // at this point in time no drag operation is in progress anymore
27485 fire('canceled', previousContext);
27486 }
27487 }
27488
27489 function cleanup(restore) {
27490 var previousContext,
27491 endDrag;
27492
27493 fire('cleanup');
27494
27495 // reset cursor
27496 unset();
27497
27498 if (context.trapClick) {
27499 endDrag = trapClickAndEnd;
27500 } else {
27501 endDrag = end;
27502 }
27503
27504 // reset dom listeners
27505 componentEvent.unbind(document, 'mousemove', move);
27506
27507 componentEvent.unbind(document, 'dragstart', preventDefault);
27508 componentEvent.unbind(document, 'selectstart', preventDefault);
27509
27510 componentEvent.unbind(document, 'mousedown', endDrag, true);
27511 componentEvent.unbind(document, 'mouseup', endDrag, true);
27512
27513 componentEvent.unbind(document, 'keyup', checkCancel);
27514
27515 componentEvent.unbind(document, 'touchstart', trapTouch, true);
27516 componentEvent.unbind(document, 'touchcancel', cancel, true);
27517 componentEvent.unbind(document, 'touchmove', move, true);
27518 componentEvent.unbind(document, 'touchend', end, true);
27519
27520 eventBus.off('element.hover', hover);
27521 eventBus.off('element.out', out);
27522
27523 // remove drag marker on root element
27524 canvas.removeMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);
27525
27526 // restore selection, unless it has changed
27527 var previousSelection = context.payload.previousSelection;
27528
27529 if (restore !== false && previousSelection && !selection.get().length) {
27530 restoreSelection(previousSelection);
27531 }
27532
27533 previousContext = context;
27534
27535 context = null;
27536
27537 return previousContext;
27538 }
27539
27540 /**
27541 * Initialize a drag operation.
27542 *
27543 * If `localPosition` is given, drag events will be emitted
27544 * relative to it.
27545 *
27546 * @param {MouseEvent|TouchEvent} [event]
27547 * @param {Point} [localPosition] actual diagram local position this drag operation should start at
27548 * @param {string} prefix
27549 * @param {Object} [options]
27550 */
27551 function init(event, relativeTo, prefix, options) {
27552
27553 // only one drag operation may be active, at a time
27554 if (context) {
27555 cancel(false);
27556 }
27557
27558 if (typeof relativeTo === 'string') {
27559 options = prefix;
27560 prefix = relativeTo;
27561 relativeTo = null;
27562 }
27563
27564 options = assign({}, defaultOptions, options || {});
27565
27566 var data = options.data || {},
27567 originalEvent,
27568 globalStart,
27569 localStart,
27570 endDrag,
27571 isTouch;
27572
27573 if (options.trapClick) {
27574 endDrag = trapClickAndEnd;
27575 } else {
27576 endDrag = end;
27577 }
27578
27579 if (event) {
27580 originalEvent = getOriginal(event) || event;
27581 globalStart = toPoint(event);
27582
27583 stopPropagation(event);
27584
27585 // prevent default browser dragging behavior
27586 if (originalEvent.type === 'dragstart') {
27587 preventDefault(originalEvent);
27588 }
27589 } else {
27590 originalEvent = null;
27591 globalStart = { x: 0, y: 0 };
27592 }
27593
27594 localStart = toLocalPoint(globalStart);
27595
27596 if (!relativeTo) {
27597 relativeTo = localStart;
27598 }
27599
27600 isTouch = isTouchEvent(originalEvent);
27601
27602 context = assign({
27603 prefix: prefix,
27604 data: data,
27605 payload: {},
27606 globalStart: globalStart,
27607 displacement: delta(relativeTo, localStart),
27608 localStart: localStart,
27609 isTouch: isTouch
27610 }, options);
27611
27612 // skip dom registration if trigger
27613 // is set to manual (during testing)
27614 if (!options.manual) {
27615
27616 // add dom listeners
27617
27618 if (isTouch) {
27619 componentEvent.bind(document, 'touchstart', trapTouch, true);
27620 componentEvent.bind(document, 'touchcancel', cancel, true);
27621 componentEvent.bind(document, 'touchmove', move, true);
27622 componentEvent.bind(document, 'touchend', end, true);
27623 } else {
27624
27625 // assume we use the mouse to interact per default
27626 componentEvent.bind(document, 'mousemove', move);
27627
27628 // prevent default browser drag and text selection behavior
27629 componentEvent.bind(document, 'dragstart', preventDefault);
27630 componentEvent.bind(document, 'selectstart', preventDefault);
27631
27632 componentEvent.bind(document, 'mousedown', endDrag, true);
27633 componentEvent.bind(document, 'mouseup', endDrag, true);
27634 }
27635
27636 componentEvent.bind(document, 'keyup', checkCancel);
27637
27638 eventBus.on('element.hover', hover);
27639 eventBus.on('element.out', out);
27640 }
27641
27642 fire('init');
27643
27644 if (options.autoActivate) {
27645 move(event, true);
27646 }
27647 }
27648
27649 // cancel on diagram destruction
27650 eventBus.on('diagram.destroy', cancel);
27651
27652
27653 // API
27654
27655 this.init = init;
27656 this.move = move;
27657 this.hover = hover;
27658 this.out = out;
27659 this.end = end;
27660
27661 this.cancel = cancel;
27662
27663 // for introspection
27664
27665 this.context = function() {
27666 return context;
27667 };
27668
27669 this.setOptions = function(options) {
27670 assign(defaultOptions, options);
27671 };
27672 }
27673
27674 Dragging.$inject = [
27675 'eventBus',
27676 'canvas',
27677 'selection',
27678 'elementRegistry'
27679 ];
27680
27681 var DraggingModule = {
27682 __depends__: [
27683 HoverFixModule,
27684 SelectionModule,
27685 ],
27686 dragging: [ 'type', Dragging ],
27687 };
27688
27689 /**
27690 * Initiates canvas scrolling if current cursor point is close to a border.
27691 * Cancelled when current point moves back inside the scrolling borders
27692 * or cancelled manually.
27693 *
27694 * Default options :
27695 * scrollThresholdIn: [ 20, 20, 20, 20 ],
27696 * scrollThresholdOut: [ 0, 0, 0, 0 ],
27697 * scrollRepeatTimeout: 15,
27698 * scrollStep: 10
27699 *
27700 * Threshold order:
27701 * [ left, top, right, bottom ]
27702 */
27703 function AutoScroll(config, eventBus, canvas) {
27704
27705 this._canvas = canvas;
27706
27707 this._opts = assign({
27708 scrollThresholdIn: [ 20, 20, 20, 20 ],
27709 scrollThresholdOut: [ 0, 0, 0, 0 ],
27710 scrollRepeatTimeout: 15,
27711 scrollStep: 10
27712 }, config);
27713
27714 var self = this;
27715
27716 eventBus.on('drag.move', function(e) {
27717 var point = self._toBorderPoint(e);
27718
27719 self.startScroll(point);
27720 });
27721
27722 eventBus.on([ 'drag.cleanup' ], function() {
27723 self.stopScroll();
27724 });
27725 }
27726
27727 AutoScroll.$inject = [
27728 'config.autoScroll',
27729 'eventBus',
27730 'canvas'
27731 ];
27732
27733
27734 /**
27735 * Starts scrolling loop.
27736 * Point is given in global scale in canvas container box plane.
27737 *
27738 * @param {Object} point { x: X, y: Y }
27739 */
27740 AutoScroll.prototype.startScroll = function(point) {
27741
27742 var canvas = this._canvas;
27743 var opts = this._opts;
27744 var self = this;
27745
27746 var clientRect = canvas.getContainer().getBoundingClientRect();
27747
27748 var diff = [
27749 point.x,
27750 point.y,
27751 clientRect.width - point.x,
27752 clientRect.height - point.y
27753 ];
27754
27755 this.stopScroll();
27756
27757 var dx = 0,
27758 dy = 0;
27759
27760 for (var i = 0; i < 4; i++) {
27761 if (between(diff[i], opts.scrollThresholdOut[i], opts.scrollThresholdIn[i])) {
27762 if (i === 0) {
27763 dx = opts.scrollStep;
27764 } else if (i == 1) {
27765 dy = opts.scrollStep;
27766 } else if (i == 2) {
27767 dx = -opts.scrollStep;
27768 } else if (i == 3) {
27769 dy = -opts.scrollStep;
27770 }
27771 }
27772 }
27773
27774 if (dx !== 0 || dy !== 0) {
27775 canvas.scroll({ dx: dx, dy: dy });
27776
27777 this._scrolling = setTimeout(function() {
27778 self.startScroll(point);
27779 }, opts.scrollRepeatTimeout);
27780 }
27781 };
27782
27783 function between(val, start, end) {
27784 if (start < val && val < end) {
27785 return true;
27786 }
27787
27788 return false;
27789 }
27790
27791
27792 /**
27793 * Stops scrolling loop.
27794 */
27795 AutoScroll.prototype.stopScroll = function() {
27796 clearTimeout(this._scrolling);
27797 };
27798
27799
27800 /**
27801 * Overrides defaults options.
27802 *
27803 * @param {Object} options
27804 */
27805 AutoScroll.prototype.setOptions = function(options) {
27806 this._opts = assign({}, this._opts, options);
27807 };
27808
27809
27810 /**
27811 * Converts event to a point in canvas container plane in global scale.
27812 *
27813 * @param {Event} event
27814 * @return {Point}
27815 */
27816 AutoScroll.prototype._toBorderPoint = function(event) {
27817 var clientRect = this._canvas._container.getBoundingClientRect();
27818
27819 var globalPosition = toPoint(event.originalEvent);
27820
27821 return {
27822 x: globalPosition.x - clientRect.left,
27823 y: globalPosition.y - clientRect.top
27824 };
27825 };
27826
27827 var AutoScrollModule = {
27828 __depends__: [
27829 DraggingModule,
27830 ],
27831 __init__: [ 'autoScroll' ],
27832 autoScroll: [ 'type', AutoScroll ]
27833 };
27834
27835 /**
27836 * A service that provides rules for certain diagram actions.
27837 *
27838 * The default implementation will hook into the {@link CommandStack}
27839 * to perform the actual rule evaluation. Make sure to provide the
27840 * `commandStack` service with this module if you plan to use it.
27841 *
27842 * Together with this implementation you may use the {@link RuleProvider}
27843 * to implement your own rule checkers.
27844 *
27845 * This module is ment to be easily replaced, thus the tiny foot print.
27846 *
27847 * @param {Injector} injector
27848 */
27849 function Rules(injector) {
27850 this._commandStack = injector.get('commandStack', false);
27851 }
27852
27853 Rules.$inject = [ 'injector' ];
27854
27855
27856 /**
27857 * Returns whether or not a given modeling action can be executed
27858 * in the specified context.
27859 *
27860 * This implementation will respond with allow unless anyone
27861 * objects.
27862 *
27863 * @param {string} action the action to be checked
27864 * @param {Object} [context] the context to check the action in
27865 *
27866 * @return {boolean} returns true, false or null depending on whether the
27867 * operation is allowed, not allowed or should be ignored.
27868 */
27869 Rules.prototype.allowed = function(action, context) {
27870 var allowed = true;
27871
27872 var commandStack = this._commandStack;
27873
27874 if (commandStack) {
27875 allowed = commandStack.canExecute(action, context);
27876 }
27877
27878 // map undefined to true, i.e. no rules
27879 return allowed === undefined ? true : allowed;
27880 };
27881
27882 var RulesModule = {
27883 __init__: [ 'rules' ],
27884 rules: [ 'type', Rules ]
27885 };
27886
27887 var round$2 = Math.round,
27888 max = Math.max;
27889
27890
27891 function circlePath(center, r) {
27892 var x = center.x,
27893 y = center.y;
27894
27895 return [
27896 ['M', x, y],
27897 ['m', 0, -r],
27898 ['a', r, r, 0, 1, 1, 0, 2 * r],
27899 ['a', r, r, 0, 1, 1, 0, -2 * r],
27900 ['z']
27901 ];
27902 }
27903
27904 function linePath(points) {
27905 var segments = [];
27906
27907 points.forEach(function(p, idx) {
27908 segments.push([ idx === 0 ? 'M' : 'L', p.x, p.y ]);
27909 });
27910
27911 return segments;
27912 }
27913
27914
27915 var INTERSECTION_THRESHOLD = 10;
27916
27917 function getBendpointIntersection(waypoints, reference) {
27918
27919 var i, w;
27920
27921 for (i = 0; (w = waypoints[i]); i++) {
27922
27923 if (pointDistance(w, reference) <= INTERSECTION_THRESHOLD) {
27924 return {
27925 point: waypoints[i],
27926 bendpoint: true,
27927 index: i
27928 };
27929 }
27930 }
27931
27932 return null;
27933 }
27934
27935 function getPathIntersection(waypoints, reference) {
27936
27937 var intersections = intersect(circlePath(reference, INTERSECTION_THRESHOLD), linePath(waypoints));
27938
27939 var a = intersections[0],
27940 b = intersections[intersections.length - 1],
27941 idx;
27942
27943 if (!a) {
27944
27945 // no intersection
27946 return null;
27947 }
27948
27949 if (a !== b) {
27950
27951 if (a.segment2 !== b.segment2) {
27952
27953 // we use the bendpoint in between both segments
27954 // as the intersection point
27955
27956 idx = max(a.segment2, b.segment2) - 1;
27957
27958 return {
27959 point: waypoints[idx],
27960 bendpoint: true,
27961 index: idx
27962 };
27963 }
27964
27965 return {
27966 point: {
27967 x: (round$2(a.x + b.x) / 2),
27968 y: (round$2(a.y + b.y) / 2)
27969 },
27970 index: a.segment2
27971 };
27972 }
27973
27974 return {
27975 point: {
27976 x: round$2(a.x),
27977 y: round$2(a.y)
27978 },
27979 index: a.segment2
27980 };
27981 }
27982
27983 /**
27984 * Returns the closest point on the connection towards a given reference point.
27985 *
27986 * @param {Array<Point>} waypoints
27987 * @param {Point} reference
27988 *
27989 * @return {Object} intersection data (segment, point)
27990 */
27991 function getApproxIntersection(waypoints, reference) {
27992 return getBendpointIntersection(waypoints, reference) || getPathIntersection(waypoints, reference);
27993 }
27994
27995 var BENDPOINT_CLS = 'djs-bendpoint';
27996 var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger';
27997
27998 function toCanvasCoordinates(canvas, event) {
27999
28000 var position = toPoint(event),
28001 clientRect = canvas._container.getBoundingClientRect(),
28002 offset;
28003
28004 // canvas relative position
28005
28006 offset = {
28007 x: clientRect.left,
28008 y: clientRect.top
28009 };
28010
28011 // update actual event payload with canvas relative measures
28012
28013 var viewbox = canvas.viewbox();
28014
28015 return {
28016 x: viewbox.x + (position.x - offset.x) / viewbox.scale,
28017 y: viewbox.y + (position.y - offset.y) / viewbox.scale
28018 };
28019 }
28020
28021 function getConnectionIntersection(canvas, waypoints, event) {
28022 var localPosition = toCanvasCoordinates(canvas, event),
28023 intersection = getApproxIntersection(waypoints, localPosition);
28024
28025 return intersection;
28026 }
28027
28028 function addBendpoint(parentGfx, cls) {
28029 var groupGfx = create('g');
28030 classes$1(groupGfx).add(BENDPOINT_CLS);
28031
28032 append(parentGfx, groupGfx);
28033
28034 var visual = create('circle');
28035 attr$1(visual, {
28036 cx: 0,
28037 cy: 0,
28038 r: 4
28039 });
28040 classes$1(visual).add('djs-visual');
28041
28042 append(groupGfx, visual);
28043
28044 var hit = create('circle');
28045 attr$1(hit, {
28046 cx: 0,
28047 cy: 0,
28048 r: 10
28049 });
28050 classes$1(hit).add('djs-hit');
28051
28052 append(groupGfx, hit);
28053
28054 if (cls) {
28055 classes$1(groupGfx).add(cls);
28056 }
28057
28058 return groupGfx;
28059 }
28060
28061 function createParallelDragger(parentGfx, segmentStart, segmentEnd, alignment) {
28062 var draggerGfx = create('g');
28063
28064 append(parentGfx, draggerGfx);
28065
28066 var width = 14,
28067 height = 3,
28068 padding = 11,
28069 hitWidth = calculateHitWidth(segmentStart, segmentEnd, alignment),
28070 hitHeight = height + padding;
28071
28072 var visual = create('rect');
28073 attr$1(visual, {
28074 x: -width / 2,
28075 y: -height / 2,
28076 width: width,
28077 height: height
28078 });
28079 classes$1(visual).add('djs-visual');
28080
28081 append(draggerGfx, visual);
28082
28083 var hit = create('rect');
28084 attr$1(hit, {
28085 x: -hitWidth / 2,
28086 y: -hitHeight / 2,
28087 width: hitWidth,
28088 height: hitHeight
28089 });
28090 classes$1(hit).add('djs-hit');
28091
28092 append(draggerGfx, hit);
28093
28094 rotate(draggerGfx, alignment === 'v' ? 90 : 0);
28095
28096 return draggerGfx;
28097 }
28098
28099
28100 function addSegmentDragger(parentGfx, segmentStart, segmentEnd) {
28101
28102 var groupGfx = create('g'),
28103 mid = getMidPoint(segmentStart, segmentEnd),
28104 alignment = pointsAligned(segmentStart, segmentEnd);
28105
28106 append(parentGfx, groupGfx);
28107
28108 createParallelDragger(groupGfx, segmentStart, segmentEnd, alignment);
28109
28110 classes$1(groupGfx).add(SEGMENT_DRAGGER_CLS);
28111 classes$1(groupGfx).add(alignment === 'h' ? 'horizontal' : 'vertical');
28112
28113 translate(groupGfx, mid.x, mid.y);
28114
28115 return groupGfx;
28116 }
28117
28118 /**
28119 * Calculates region for segment move which is 2/3 of the full segment length
28120 * @param {number} segmentLength
28121 *
28122 * @return {number}
28123 */
28124 function calculateSegmentMoveRegion(segmentLength) {
28125 return Math.abs(Math.round(segmentLength * 2 / 3));
28126 }
28127
28128 // helper //////////
28129
28130 function calculateHitWidth(segmentStart, segmentEnd, alignment) {
28131 var segmentLengthXAxis = segmentEnd.x - segmentStart.x,
28132 segmentLengthYAxis = segmentEnd.y - segmentStart.y;
28133
28134 return alignment === 'h' ?
28135 calculateSegmentMoveRegion(segmentLengthXAxis) :
28136 calculateSegmentMoveRegion(segmentLengthYAxis);
28137 }
28138
28139 /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
28140
28141 var css_escape = createCommonjsModule(function (module, exports) {
28142 (function(root, factory) {
28143 // https://github.com/umdjs/umd/blob/master/returnExports.js
28144 {
28145 // For Node.js.
28146 module.exports = factory(root);
28147 }
28148 }(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : commonjsGlobal, function(root) {
28149
28150 if (root.CSS && root.CSS.escape) {
28151 return root.CSS.escape;
28152 }
28153
28154 // https://drafts.csswg.org/cssom/#serialize-an-identifier
28155 var cssEscape = function(value) {
28156 if (arguments.length == 0) {
28157 throw new TypeError('`CSS.escape` requires an argument.');
28158 }
28159 var string = String(value);
28160 var length = string.length;
28161 var index = -1;
28162 var codeUnit;
28163 var result = '';
28164 var firstCodeUnit = string.charCodeAt(0);
28165 while (++index < length) {
28166 codeUnit = string.charCodeAt(index);
28167 // Note: there’s no need to special-case astral symbols, surrogate
28168 // pairs, or lone surrogates.
28169
28170 // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
28171 // (U+FFFD).
28172 if (codeUnit == 0x0000) {
28173 result += '\uFFFD';
28174 continue;
28175 }
28176
28177 if (
28178 // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
28179 // U+007F, […]
28180 (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
28181 // If the character is the first character and is in the range [0-9]
28182 // (U+0030 to U+0039), […]
28183 (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
28184 // If the character is the second character and is in the range [0-9]
28185 // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
28186 (
28187 index == 1 &&
28188 codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
28189 firstCodeUnit == 0x002D
28190 )
28191 ) {
28192 // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
28193 result += '\\' + codeUnit.toString(16) + ' ';
28194 continue;
28195 }
28196
28197 if (
28198 // If the character is the first character and is a `-` (U+002D), and
28199 // there is no second character, […]
28200 index == 0 &&
28201 length == 1 &&
28202 codeUnit == 0x002D
28203 ) {
28204 result += '\\' + string.charAt(index);
28205 continue;
28206 }
28207
28208 // If the character is not handled by one of the above rules and is
28209 // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
28210 // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
28211 // U+005A), or [a-z] (U+0061 to U+007A), […]
28212 if (
28213 codeUnit >= 0x0080 ||
28214 codeUnit == 0x002D ||
28215 codeUnit == 0x005F ||
28216 codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
28217 codeUnit >= 0x0041 && codeUnit <= 0x005A ||
28218 codeUnit >= 0x0061 && codeUnit <= 0x007A
28219 ) {
28220 // the character itself
28221 result += string.charAt(index);
28222 continue;
28223 }
28224
28225 // Otherwise, the escaped character.
28226 // https://drafts.csswg.org/cssom/#escape-a-character
28227 result += '\\' + string.charAt(index);
28228
28229 }
28230 return result;
28231 };
28232
28233 if (!root.CSS) {
28234 root.CSS = {};
28235 }
28236
28237 root.CSS.escape = cssEscape;
28238 return cssEscape;
28239
28240 }));
28241 });
28242
28243 var HTML_ESCAPE_MAP = {
28244 '&': '&amp;',
28245 '<': '&lt;',
28246 '>': '&gt;',
28247 '"': '&quot;',
28248 '\'': '&#39;'
28249 };
28250
28251 function escapeHTML(str) {
28252 str = '' + str;
28253
28254 return str && str.replace(/[&<>"']/g, function(match) {
28255 return HTML_ESCAPE_MAP[match];
28256 });
28257 }
28258
28259 /**
28260 * A service that adds editable bendpoints to connections.
28261 */
28262 function Bendpoints(
28263 eventBus, canvas, interactionEvents,
28264 bendpointMove, connectionSegmentMove) {
28265
28266 /**
28267 * Returns true if intersection point is inside middle region of segment, adjusted by
28268 * optional threshold
28269 */
28270 function isIntersectionMiddle(intersection, waypoints, treshold) {
28271 var idx = intersection.index,
28272 p = intersection.point,
28273 p0, p1, mid, aligned, xDelta, yDelta;
28274
28275 if (idx <= 0 || intersection.bendpoint) {
28276 return false;
28277 }
28278
28279 p0 = waypoints[idx - 1];
28280 p1 = waypoints[idx];
28281 mid = getMidPoint(p0, p1),
28282 aligned = pointsAligned(p0, p1);
28283 xDelta = Math.abs(p.x - mid.x);
28284 yDelta = Math.abs(p.y - mid.y);
28285
28286 return aligned && xDelta <= treshold && yDelta <= treshold;
28287 }
28288
28289 /**
28290 * Calculates the threshold from a connection's middle which fits the two-third-region
28291 */
28292 function calculateIntersectionThreshold(connection, intersection) {
28293 var waypoints = connection.waypoints,
28294 relevantSegment, alignment, segmentLength, threshold;
28295
28296 if (intersection.index <= 0 || intersection.bendpoint) {
28297 return null;
28298 }
28299
28300 // segment relative to connection intersection
28301 relevantSegment = {
28302 start: waypoints[intersection.index - 1],
28303 end: waypoints[intersection.index]
28304 };
28305
28306 alignment = pointsAligned(relevantSegment.start, relevantSegment.end);
28307
28308 if (!alignment) {
28309 return null;
28310 }
28311
28312 if (alignment === 'h') {
28313 segmentLength = relevantSegment.end.x - relevantSegment.start.x;
28314 } else {
28315 segmentLength = relevantSegment.end.y - relevantSegment.start.y;
28316 }
28317
28318 // calculate threshold relative to 2/3 of segment length
28319 threshold = calculateSegmentMoveRegion(segmentLength) / 2;
28320
28321 return threshold;
28322 }
28323
28324 function activateBendpointMove(event, connection) {
28325 var waypoints = connection.waypoints,
28326 intersection = getConnectionIntersection(canvas, waypoints, event),
28327 threshold;
28328
28329 if (!intersection) {
28330 return;
28331 }
28332
28333 threshold = calculateIntersectionThreshold(connection, intersection);
28334
28335 if (isIntersectionMiddle(intersection, waypoints, threshold)) {
28336 connectionSegmentMove.start(event, connection, intersection.index);
28337 } else {
28338 bendpointMove.start(event, connection, intersection.index, !intersection.bendpoint);
28339 }
28340
28341 // we've handled the event
28342 return true;
28343 }
28344
28345 function bindInteractionEvents(node, eventName, element) {
28346
28347 componentEvent.bind(node, eventName, function(event) {
28348 interactionEvents.triggerMouseEvent(eventName, event, element);
28349 event.stopPropagation();
28350 });
28351 }
28352
28353 function getBendpointsContainer(element, create$1) {
28354
28355 var layer = canvas.getLayer('overlays'),
28356 gfx = query('.djs-bendpoints[data-element-id="' + css_escape(element.id) + '"]', layer);
28357
28358 if (!gfx && create$1) {
28359 gfx = create('g');
28360 attr$1(gfx, { 'data-element-id': element.id });
28361 classes$1(gfx).add('djs-bendpoints');
28362
28363 append(layer, gfx);
28364
28365 bindInteractionEvents(gfx, 'mousedown', element);
28366 bindInteractionEvents(gfx, 'click', element);
28367 bindInteractionEvents(gfx, 'dblclick', element);
28368 }
28369
28370 return gfx;
28371 }
28372
28373 function getSegmentDragger(idx, parentGfx) {
28374 return query(
28375 '.djs-segment-dragger[data-segment-idx="' + idx + '"]',
28376 parentGfx
28377 );
28378 }
28379
28380 function createBendpoints(gfx, connection) {
28381 connection.waypoints.forEach(function(p, idx) {
28382 var bendpoint = addBendpoint(gfx);
28383
28384 append(gfx, bendpoint);
28385
28386 translate(bendpoint, p.x, p.y);
28387 });
28388
28389 // add floating bendpoint
28390 addBendpoint(gfx, 'floating');
28391 }
28392
28393 function createSegmentDraggers(gfx, connection) {
28394
28395 var waypoints = connection.waypoints;
28396
28397 var segmentStart,
28398 segmentEnd,
28399 segmentDraggerGfx;
28400
28401 for (var i = 1; i < waypoints.length; i++) {
28402
28403 segmentStart = waypoints[i - 1];
28404 segmentEnd = waypoints[i];
28405
28406 if (pointsAligned(segmentStart, segmentEnd)) {
28407 segmentDraggerGfx = addSegmentDragger(gfx, segmentStart, segmentEnd);
28408
28409 attr$1(segmentDraggerGfx, { 'data-segment-idx': i });
28410
28411 bindInteractionEvents(segmentDraggerGfx, 'mousemove', connection);
28412 }
28413 }
28414 }
28415
28416 function clearBendpoints(gfx) {
28417 forEach(all('.' + BENDPOINT_CLS, gfx), function(node) {
28418 remove$1(node);
28419 });
28420 }
28421
28422 function clearSegmentDraggers(gfx) {
28423 forEach(all('.' + SEGMENT_DRAGGER_CLS, gfx), function(node) {
28424 remove$1(node);
28425 });
28426 }
28427
28428 function addHandles(connection) {
28429
28430 var gfx = getBendpointsContainer(connection);
28431
28432 if (!gfx) {
28433 gfx = getBendpointsContainer(connection, true);
28434
28435 createBendpoints(gfx, connection);
28436 createSegmentDraggers(gfx, connection);
28437 }
28438
28439 return gfx;
28440 }
28441
28442 function updateHandles(connection) {
28443
28444 var gfx = getBendpointsContainer(connection);
28445
28446 if (gfx) {
28447 clearSegmentDraggers(gfx);
28448 clearBendpoints(gfx);
28449 createSegmentDraggers(gfx, connection);
28450 createBendpoints(gfx, connection);
28451 }
28452 }
28453
28454 function updateFloatingBendpointPosition(parentGfx, intersection) {
28455 var floating = query('.floating', parentGfx),
28456 point = intersection.point;
28457
28458 if (!floating) {
28459 return;
28460 }
28461
28462 translate(floating, point.x, point.y);
28463
28464 }
28465
28466 function updateSegmentDraggerPosition(parentGfx, intersection, waypoints) {
28467
28468 var draggerGfx = getSegmentDragger(intersection.index, parentGfx),
28469 segmentStart = waypoints[intersection.index - 1],
28470 segmentEnd = waypoints[intersection.index],
28471 point = intersection.point,
28472 mid = getMidPoint(segmentStart, segmentEnd),
28473 alignment = pointsAligned(segmentStart, segmentEnd),
28474 draggerVisual, relativePosition;
28475
28476 if (!draggerGfx) {
28477 return;
28478 }
28479
28480 draggerVisual = getDraggerVisual(draggerGfx);
28481
28482 relativePosition = {
28483 x: point.x - mid.x,
28484 y: point.y - mid.y
28485 };
28486
28487 if (alignment === 'v') {
28488
28489 // rotate position
28490 relativePosition = {
28491 x: relativePosition.y,
28492 y: relativePosition.x
28493 };
28494 }
28495
28496 translate(draggerVisual, relativePosition.x, relativePosition.y);
28497 }
28498
28499 eventBus.on('connection.changed', function(event) {
28500 updateHandles(event.element);
28501 });
28502
28503 eventBus.on('connection.remove', function(event) {
28504 var gfx = getBendpointsContainer(event.element);
28505
28506 if (gfx) {
28507 remove$1(gfx);
28508 }
28509 });
28510
28511 eventBus.on('element.marker.update', function(event) {
28512
28513 var element = event.element,
28514 bendpointsGfx;
28515
28516 if (!element.waypoints) {
28517 return;
28518 }
28519
28520 bendpointsGfx = addHandles(element);
28521
28522 if (event.add) {
28523 classes$1(bendpointsGfx).add(event.marker);
28524 } else {
28525 classes$1(bendpointsGfx).remove(event.marker);
28526 }
28527 });
28528
28529 eventBus.on('element.mousemove', function(event) {
28530
28531 var element = event.element,
28532 waypoints = element.waypoints,
28533 bendpointsGfx,
28534 intersection;
28535
28536 if (waypoints) {
28537 bendpointsGfx = getBendpointsContainer(element, true);
28538
28539 intersection = getConnectionIntersection(canvas, waypoints, event.originalEvent);
28540
28541 if (!intersection) {
28542 return;
28543 }
28544
28545 updateFloatingBendpointPosition(bendpointsGfx, intersection);
28546
28547 if (!intersection.bendpoint) {
28548 updateSegmentDraggerPosition(bendpointsGfx, intersection, waypoints);
28549 }
28550
28551 }
28552 });
28553
28554 eventBus.on('element.mousedown', function(event) {
28555
28556 if (!isPrimaryButton(event)) {
28557 return;
28558 }
28559
28560 var originalEvent = event.originalEvent,
28561 element = event.element;
28562
28563 if (!element.waypoints) {
28564 return;
28565 }
28566
28567 return activateBendpointMove(originalEvent, element);
28568 });
28569
28570 eventBus.on('selection.changed', function(event) {
28571 var newSelection = event.newSelection,
28572 primary = newSelection[0];
28573
28574 if (primary && primary.waypoints) {
28575 addHandles(primary);
28576 }
28577 });
28578
28579 eventBus.on('element.hover', function(event) {
28580 var element = event.element;
28581
28582 if (element.waypoints) {
28583 addHandles(element);
28584 interactionEvents.registerEvent(event.gfx, 'mousemove', 'element.mousemove');
28585 }
28586 });
28587
28588 eventBus.on('element.out', function(event) {
28589 interactionEvents.unregisterEvent(event.gfx, 'mousemove', 'element.mousemove');
28590 });
28591
28592 // update bendpoint container data attribute on element ID change
28593 eventBus.on('element.updateId', function(context) {
28594 var element = context.element,
28595 newId = context.newId;
28596
28597 if (element.waypoints) {
28598 var bendpointContainer = getBendpointsContainer(element);
28599
28600 if (bendpointContainer) {
28601 attr$1(bendpointContainer, { 'data-element-id': newId });
28602 }
28603 }
28604 });
28605
28606 // API
28607
28608 this.addHandles = addHandles;
28609 this.updateHandles = updateHandles;
28610 this.getBendpointsContainer = getBendpointsContainer;
28611 this.getSegmentDragger = getSegmentDragger;
28612 }
28613
28614 Bendpoints.$inject = [
28615 'eventBus',
28616 'canvas',
28617 'interactionEvents',
28618 'bendpointMove',
28619 'connectionSegmentMove'
28620 ];
28621
28622
28623
28624 // helper /////////////
28625
28626 function getDraggerVisual(draggerGfx) {
28627 return query('.djs-visual', draggerGfx);
28628 }
28629
28630 var round$3 = Math.round;
28631
28632 var RECONNECT_START = 'reconnectStart',
28633 RECONNECT_END = 'reconnectEnd',
28634 UPDATE_WAYPOINTS = 'updateWaypoints';
28635
28636
28637 /**
28638 * Move bendpoints through drag and drop to add/remove bendpoints or reconnect connection.
28639 */
28640 function BendpointMove(injector, eventBus, canvas, dragging, rules, modeling) {
28641 this._injector = injector;
28642
28643 this.start = function(event, connection, bendpointIndex, insert) {
28644 var gfx = canvas.getGraphics(connection),
28645 source = connection.source,
28646 target = connection.target,
28647 waypoints = connection.waypoints,
28648 type;
28649
28650 if (!insert && bendpointIndex === 0) {
28651 type = RECONNECT_START;
28652 } else
28653 if (!insert && bendpointIndex === waypoints.length - 1) {
28654 type = RECONNECT_END;
28655 } else {
28656 type = UPDATE_WAYPOINTS;
28657 }
28658
28659 var command = type === UPDATE_WAYPOINTS ? 'connection.updateWaypoints' : 'connection.reconnect';
28660
28661 var allowed = rules.allowed(command, {
28662 connection: connection,
28663 source: source,
28664 target: target
28665 });
28666
28667 if (allowed === false) {
28668 allowed = rules.allowed(command, {
28669 connection: connection,
28670 source: target,
28671 target: source
28672 });
28673 }
28674
28675 if (allowed === false) {
28676 return;
28677 }
28678
28679 dragging.init(event, 'bendpoint.move', {
28680 data: {
28681 connection: connection,
28682 connectionGfx: gfx,
28683 context: {
28684 allowed: allowed,
28685 bendpointIndex: bendpointIndex,
28686 connection: connection,
28687 source: source,
28688 target: target,
28689 insert: insert,
28690 type: type
28691 }
28692 }
28693 });
28694 };
28695
28696 eventBus.on('bendpoint.move.hover', function(event) {
28697 var context = event.context,
28698 connection = context.connection,
28699 source = connection.source,
28700 target = connection.target,
28701 hover = event.hover,
28702 type = context.type;
28703
28704 // cache hover state
28705 context.hover = hover;
28706
28707 var allowed;
28708
28709 if (!hover) {
28710 return;
28711 }
28712
28713 var command = type === UPDATE_WAYPOINTS ? 'connection.updateWaypoints' : 'connection.reconnect';
28714
28715 allowed = context.allowed = rules.allowed(command, {
28716 connection: connection,
28717 source: type === RECONNECT_START ? hover : source,
28718 target: type === RECONNECT_END ? hover : target
28719 });
28720
28721 if (allowed) {
28722 context.source = type === RECONNECT_START ? hover : source;
28723 context.target = type === RECONNECT_END ? hover : target;
28724
28725 return;
28726 }
28727
28728 if (allowed === false) {
28729 allowed = context.allowed = rules.allowed(command, {
28730 connection: connection,
28731 source: type === RECONNECT_END ? hover : target,
28732 target: type === RECONNECT_START ? hover : source
28733 });
28734 }
28735
28736 if (allowed) {
28737 context.source = type === RECONNECT_END ? hover : target;
28738 context.target = type === RECONNECT_START ? hover : source;
28739 }
28740 });
28741
28742 eventBus.on([ 'bendpoint.move.out', 'bendpoint.move.cleanup' ], function(event) {
28743 var context = event.context,
28744 type = context.type;
28745
28746 context.hover = null;
28747 context.source = null;
28748 context.target = null;
28749
28750 if (type !== UPDATE_WAYPOINTS) {
28751 context.allowed = false;
28752 }
28753 });
28754
28755 eventBus.on('bendpoint.move.end', function(event) {
28756 var context = event.context,
28757 allowed = context.allowed,
28758 bendpointIndex = context.bendpointIndex,
28759 connection = context.connection,
28760 insert = context.insert,
28761 newWaypoints = connection.waypoints.slice(),
28762 source = context.source,
28763 target = context.target,
28764 type = context.type,
28765 hints = context.hints || {};
28766
28767 // ensure integer values (important if zoom level was > 1 during move)
28768 var docking = {
28769 x: round$3(event.x),
28770 y: round$3(event.y)
28771 };
28772
28773 if (!allowed) {
28774 return false;
28775 }
28776
28777 if (type === UPDATE_WAYPOINTS) {
28778 if (insert) {
28779
28780 // insert new bendpoint
28781 newWaypoints.splice(bendpointIndex, 0, docking);
28782 } else {
28783
28784 // swap previous waypoint with moved one
28785 newWaypoints[bendpointIndex] = docking;
28786 }
28787
28788 // pass hints about actual moved bendpoint
28789 // useful for connection/label layout
28790 hints.bendpointMove = {
28791 insert: insert,
28792 bendpointIndex: bendpointIndex
28793 };
28794
28795 newWaypoints = this.cropWaypoints(connection, newWaypoints);
28796
28797 modeling.updateWaypoints(connection, filterRedundantWaypoints(newWaypoints), hints);
28798 } else {
28799 if (type === RECONNECT_START) {
28800 hints.docking = 'source';
28801
28802 if (isReverse(context)) {
28803 hints.docking = 'target';
28804
28805 hints.newWaypoints = newWaypoints.reverse();
28806 }
28807 } else if (type === RECONNECT_END) {
28808 hints.docking = 'target';
28809
28810 if (isReverse(context)) {
28811 hints.docking = 'source';
28812
28813 hints.newWaypoints = newWaypoints.reverse();
28814 }
28815 }
28816
28817 modeling.reconnect(connection, source, target, docking, hints);
28818 }
28819 }, this);
28820 }
28821
28822 BendpointMove.$inject = [
28823 'injector',
28824 'eventBus',
28825 'canvas',
28826 'dragging',
28827 'rules',
28828 'modeling'
28829 ];
28830
28831 BendpointMove.prototype.cropWaypoints = function(connection, newWaypoints) {
28832 var connectionDocking = this._injector.get('connectionDocking', false);
28833
28834 if (!connectionDocking) {
28835 return newWaypoints;
28836 }
28837
28838 var waypoints = connection.waypoints;
28839
28840 connection.waypoints = newWaypoints;
28841
28842 connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
28843
28844 newWaypoints = connection.waypoints;
28845
28846 connection.waypoints = waypoints;
28847
28848 return newWaypoints;
28849 };
28850
28851
28852 // helpers //////////
28853
28854 function isReverse(context) {
28855 var hover = context.hover,
28856 source = context.source,
28857 target = context.target,
28858 type = context.type;
28859
28860 if (type === RECONNECT_START) {
28861 return hover && target && hover === target && source !== target;
28862 }
28863
28864 if (type === RECONNECT_END) {
28865 return hover && source && hover === source && source !== target;
28866 }
28867 }
28868
28869 var RECONNECT_START$1 = 'reconnectStart',
28870 RECONNECT_END$1 = 'reconnectEnd',
28871 UPDATE_WAYPOINTS$1 = 'updateWaypoints';
28872
28873 var MARKER_OK = 'connect-ok',
28874 MARKER_NOT_OK = 'connect-not-ok',
28875 MARKER_CONNECT_HOVER = 'connect-hover',
28876 MARKER_CONNECT_UPDATING = 'djs-updating',
28877 MARKER_ELEMENT_HIDDEN = 'djs-element-hidden';
28878
28879 var HIGH_PRIORITY$1 = 1100;
28880
28881 /**
28882 * Preview connection while moving bendpoints.
28883 */
28884 function BendpointMovePreview(bendpointMove, injector, eventBus, canvas) {
28885 this._injector = injector;
28886
28887 var connectionPreview = injector.get('connectionPreview', false);
28888
28889 eventBus.on('bendpoint.move.start', function(event) {
28890 var context = event.context,
28891 bendpointIndex = context.bendpointIndex,
28892 connection = context.connection,
28893 insert = context.insert,
28894 waypoints = connection.waypoints,
28895 newWaypoints = waypoints.slice();
28896
28897 context.waypoints = waypoints;
28898
28899 if (insert) {
28900
28901 // insert placeholder for new bendpoint
28902 newWaypoints.splice(bendpointIndex, 0, { x: event.x, y: event.y });
28903 }
28904
28905 connection.waypoints = newWaypoints;
28906
28907 // add dragger gfx
28908 var draggerGfx = context.draggerGfx = addBendpoint(canvas.getLayer('overlays'));
28909
28910 classes$1(draggerGfx).add('djs-dragging');
28911
28912 canvas.addMarker(connection, MARKER_ELEMENT_HIDDEN);
28913 canvas.addMarker(connection, MARKER_CONNECT_UPDATING);
28914 });
28915
28916 eventBus.on('bendpoint.move.hover', function(event) {
28917 var context = event.context,
28918 allowed = context.allowed,
28919 hover = context.hover,
28920 type = context.type;
28921
28922 if (hover) {
28923 canvas.addMarker(hover, MARKER_CONNECT_HOVER);
28924
28925 if (type === UPDATE_WAYPOINTS$1) {
28926 return;
28927 }
28928
28929 if (allowed) {
28930 canvas.removeMarker(hover, MARKER_NOT_OK);
28931 canvas.addMarker(hover, MARKER_OK);
28932 } else if (allowed === false) {
28933 canvas.removeMarker(hover, MARKER_OK);
28934 canvas.addMarker(hover, MARKER_NOT_OK);
28935 }
28936 }
28937 });
28938
28939 eventBus.on([
28940 'bendpoint.move.out',
28941 'bendpoint.move.cleanup'
28942 ], HIGH_PRIORITY$1, function(event) {
28943 var context = event.context,
28944 hover = context.hover,
28945 target = context.target;
28946
28947 if (hover) {
28948 canvas.removeMarker(hover, MARKER_CONNECT_HOVER);
28949 canvas.removeMarker(hover, target ? MARKER_OK : MARKER_NOT_OK);
28950 }
28951 });
28952
28953 eventBus.on('bendpoint.move.move', function(event) {
28954 var context = event.context,
28955 allowed = context.allowed,
28956 bendpointIndex = context.bendpointIndex,
28957 draggerGfx = context.draggerGfx,
28958 hover = context.hover,
28959 type = context.type,
28960 connection = context.connection,
28961 source = connection.source,
28962 target = connection.target,
28963 newWaypoints = connection.waypoints.slice(),
28964 bendpoint = { x: event.x, y: event.y },
28965 hints = context.hints || {},
28966 drawPreviewHints = {};
28967
28968 if (connectionPreview) {
28969 if (hints.connectionStart) {
28970 drawPreviewHints.connectionStart = hints.connectionStart;
28971 }
28972
28973 if (hints.connectionEnd) {
28974 drawPreviewHints.connectionEnd = hints.connectionEnd;
28975 }
28976
28977
28978 if (type === RECONNECT_START$1) {
28979 if (isReverse(context)) {
28980 drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;
28981
28982 drawPreviewHints.source = target;
28983 drawPreviewHints.target = hover || source;
28984
28985 newWaypoints = newWaypoints.reverse();
28986 } else {
28987 drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;
28988
28989 drawPreviewHints.source = hover || source;
28990 drawPreviewHints.target = target;
28991 }
28992 } else if (type === RECONNECT_END$1) {
28993 if (isReverse(context)) {
28994 drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;
28995
28996 drawPreviewHints.source = hover || target;
28997 drawPreviewHints.target = source;
28998
28999 newWaypoints = newWaypoints.reverse();
29000 } else {
29001 drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;
29002
29003 drawPreviewHints.source = source;
29004 drawPreviewHints.target = hover || target;
29005 }
29006
29007 } else {
29008 drawPreviewHints.noCropping = true;
29009 drawPreviewHints.noLayout = true;
29010 newWaypoints[ bendpointIndex ] = bendpoint;
29011 }
29012
29013 if (type === UPDATE_WAYPOINTS$1) {
29014 newWaypoints = bendpointMove.cropWaypoints(connection, newWaypoints);
29015 }
29016
29017 drawPreviewHints.waypoints = newWaypoints;
29018
29019 connectionPreview.drawPreview(context, allowed, drawPreviewHints);
29020 }
29021
29022 translate(draggerGfx, event.x, event.y);
29023 }, this);
29024
29025 eventBus.on([
29026 'bendpoint.move.end',
29027 'bendpoint.move.cancel'
29028 ], HIGH_PRIORITY$1, function(event) {
29029 var context = event.context,
29030 connection = context.connection,
29031 draggerGfx = context.draggerGfx,
29032 hover = context.hover,
29033 target = context.target,
29034 waypoints = context.waypoints;
29035
29036 connection.waypoints = waypoints;
29037
29038 // remove dragger gfx
29039 remove$1(draggerGfx);
29040
29041 canvas.removeMarker(connection, MARKER_CONNECT_UPDATING);
29042 canvas.removeMarker(connection, MARKER_ELEMENT_HIDDEN);
29043
29044 if (hover) {
29045 canvas.removeMarker(hover, MARKER_OK);
29046 canvas.removeMarker(hover, target ? MARKER_OK : MARKER_NOT_OK);
29047 }
29048
29049 if (connectionPreview) {
29050 connectionPreview.cleanUp(context);
29051 }
29052 });
29053 }
29054
29055 BendpointMovePreview.$inject = [
29056 'bendpointMove',
29057 'injector',
29058 'eventBus',
29059 'canvas'
29060 ];
29061
29062 var MARKER_CONNECT_HOVER$1 = 'connect-hover',
29063 MARKER_CONNECT_UPDATING$1 = 'djs-updating';
29064
29065
29066 function axisAdd(point, axis, delta) {
29067 return axisSet(point, axis, point[axis] + delta);
29068 }
29069
29070 function axisSet(point, axis, value) {
29071 return {
29072 x: (axis === 'x' ? value : point.x),
29073 y: (axis === 'y' ? value : point.y)
29074 };
29075 }
29076
29077 function axisFenced(position, segmentStart, segmentEnd, axis) {
29078
29079 var maxValue = Math.max(segmentStart[axis], segmentEnd[axis]),
29080 minValue = Math.min(segmentStart[axis], segmentEnd[axis]);
29081
29082 var padding = 20;
29083
29084 var fencedValue = Math.min(Math.max(minValue + padding, position[axis]), maxValue - padding);
29085
29086 return axisSet(segmentStart, axis, fencedValue);
29087 }
29088
29089 function flipAxis(axis) {
29090 return axis === 'x' ? 'y' : 'x';
29091 }
29092
29093 /**
29094 * Get the docking point on the given element.
29095 *
29096 * Compute a reasonable docking, if non exists.
29097 *
29098 * @param {Point} point
29099 * @param {djs.model.Shape} referenceElement
29100 * @param {string} moveAxis (x|y)
29101 *
29102 * @return {Point}
29103 */
29104 function getDocking(point, referenceElement, moveAxis) {
29105
29106 var referenceMid,
29107 inverseAxis;
29108
29109 if (point.original) {
29110 return point.original;
29111 } else {
29112 referenceMid = getMid(referenceElement);
29113 inverseAxis = flipAxis(moveAxis);
29114
29115 return axisSet(point, inverseAxis, referenceMid[inverseAxis]);
29116 }
29117 }
29118
29119 /**
29120 * A component that implements moving of bendpoints
29121 */
29122 function ConnectionSegmentMove(
29123 injector, eventBus, canvas,
29124 dragging, graphicsFactory, modeling) {
29125
29126 // optional connection docking integration
29127 var connectionDocking = injector.get('connectionDocking', false);
29128
29129
29130 // API
29131
29132 this.start = function(event, connection, idx) {
29133
29134 var context,
29135 gfx = canvas.getGraphics(connection),
29136 segmentStartIndex = idx - 1,
29137 segmentEndIndex = idx,
29138 waypoints = connection.waypoints,
29139 segmentStart = waypoints[segmentStartIndex],
29140 segmentEnd = waypoints[segmentEndIndex],
29141 intersection = getConnectionIntersection(canvas, waypoints, event),
29142 direction, axis, dragPosition;
29143
29144 direction = pointsAligned(segmentStart, segmentEnd);
29145
29146 // do not move diagonal connection
29147 if (!direction) {
29148 return;
29149 }
29150
29151 // the axis where we are going to move things
29152 axis = direction === 'v' ? 'x' : 'y';
29153
29154 if (segmentStartIndex === 0) {
29155 segmentStart = getDocking(segmentStart, connection.source, axis);
29156 }
29157
29158 if (segmentEndIndex === waypoints.length - 1) {
29159 segmentEnd = getDocking(segmentEnd, connection.target, axis);
29160 }
29161
29162 if (intersection) {
29163 dragPosition = intersection.point;
29164 } else {
29165
29166 // set to segment center as default
29167 dragPosition = {
29168 x: (segmentStart.x + segmentEnd.x) / 2,
29169 y: (segmentStart.y + segmentEnd.y) / 2
29170 };
29171 }
29172
29173 context = {
29174 connection: connection,
29175 segmentStartIndex: segmentStartIndex,
29176 segmentEndIndex: segmentEndIndex,
29177 segmentStart: segmentStart,
29178 segmentEnd: segmentEnd,
29179 axis: axis,
29180 dragPosition: dragPosition
29181 };
29182
29183 dragging.init(event, dragPosition, 'connectionSegment.move', {
29184 cursor: axis === 'x' ? 'resize-ew' : 'resize-ns',
29185 data: {
29186 connection: connection,
29187 connectionGfx: gfx,
29188 context: context
29189 }
29190 });
29191 };
29192
29193 /**
29194 * Crop connection if connection cropping is provided.
29195 *
29196 * @param {Connection} connection
29197 * @param {Array<Point>} newWaypoints
29198 *
29199 * @return {Array<Point>} cropped connection waypoints
29200 */
29201 function cropConnection(connection, newWaypoints) {
29202
29203 // crop connection, if docking service is provided only
29204 if (!connectionDocking) {
29205 return newWaypoints;
29206 }
29207
29208 var oldWaypoints = connection.waypoints,
29209 croppedWaypoints;
29210
29211 // temporary set new waypoints
29212 connection.waypoints = newWaypoints;
29213
29214 croppedWaypoints = connectionDocking.getCroppedWaypoints(connection);
29215
29216 // restore old waypoints
29217 connection.waypoints = oldWaypoints;
29218
29219 return croppedWaypoints;
29220 }
29221
29222 // DRAGGING IMPLEMENTATION
29223
29224 function redrawConnection(data) {
29225 graphicsFactory.update('connection', data.connection, data.connectionGfx);
29226 }
29227
29228 function updateDragger(context, segmentOffset, event) {
29229
29230 var newWaypoints = context.newWaypoints,
29231 segmentStartIndex = context.segmentStartIndex + segmentOffset,
29232 segmentStart = newWaypoints[segmentStartIndex],
29233 segmentEndIndex = context.segmentEndIndex + segmentOffset,
29234 segmentEnd = newWaypoints[segmentEndIndex],
29235 axis = flipAxis(context.axis);
29236
29237 // make sure the dragger does not move
29238 // outside the connection
29239 var draggerPosition = axisFenced(event, segmentStart, segmentEnd, axis);
29240
29241 // update dragger
29242 translate(context.draggerGfx, draggerPosition.x, draggerPosition.y);
29243 }
29244
29245 /**
29246 * Filter waypoints for redundant ones (i.e. on the same axis).
29247 * Returns the filtered waypoints and the offset related to the segment move.
29248 *
29249 * @param {Array<Point>} waypoints
29250 * @param {Integer} segmentStartIndex of moved segment start
29251 *
29252 * @return {Object} { filteredWaypoints, segmentOffset }
29253 */
29254 function filterRedundantWaypoints(waypoints, segmentStartIndex) {
29255
29256 var segmentOffset = 0;
29257
29258 var filteredWaypoints = waypoints.filter(function(r, idx) {
29259 if (pointsOnLine(waypoints[idx - 1], waypoints[idx + 1], r)) {
29260
29261 // remove point and increment offset
29262 segmentOffset = idx <= segmentStartIndex ? segmentOffset - 1 : segmentOffset;
29263 return false;
29264 }
29265
29266 // dont remove point
29267 return true;
29268 });
29269
29270 return {
29271 waypoints: filteredWaypoints,
29272 segmentOffset: segmentOffset
29273 };
29274 }
29275
29276 eventBus.on('connectionSegment.move.start', function(event) {
29277
29278 var context = event.context,
29279 connection = event.connection,
29280 layer = canvas.getLayer('overlays');
29281
29282 context.originalWaypoints = connection.waypoints.slice();
29283
29284 // add dragger gfx
29285 context.draggerGfx = addSegmentDragger(layer, context.segmentStart, context.segmentEnd);
29286 classes$1(context.draggerGfx).add('djs-dragging');
29287
29288 canvas.addMarker(connection, MARKER_CONNECT_UPDATING$1);
29289 });
29290
29291 eventBus.on('connectionSegment.move.move', function(event) {
29292
29293 var context = event.context,
29294 connection = context.connection,
29295 segmentStartIndex = context.segmentStartIndex,
29296 segmentEndIndex = context.segmentEndIndex,
29297 segmentStart = context.segmentStart,
29298 segmentEnd = context.segmentEnd,
29299 axis = context.axis;
29300
29301 var newWaypoints = context.originalWaypoints.slice(),
29302 newSegmentStart = axisAdd(segmentStart, axis, event['d' + axis]),
29303 newSegmentEnd = axisAdd(segmentEnd, axis, event['d' + axis]);
29304
29305 // original waypoint count and added / removed
29306 // from start waypoint delta. We use the later
29307 // to retrieve the updated segmentStartIndex / segmentEndIndex
29308 var waypointCount = newWaypoints.length,
29309 segmentOffset = 0;
29310
29311 // move segment start / end by axis delta
29312 newWaypoints[segmentStartIndex] = newSegmentStart;
29313 newWaypoints[segmentEndIndex] = newSegmentEnd;
29314
29315 var sourceToSegmentOrientation,
29316 targetToSegmentOrientation;
29317
29318 // handle first segment
29319 if (segmentStartIndex < 2) {
29320 sourceToSegmentOrientation = getOrientation(connection.source, newSegmentStart);
29321
29322 // first bendpoint, remove first segment if intersecting
29323 if (segmentStartIndex === 1) {
29324
29325 if (sourceToSegmentOrientation === 'intersect') {
29326 newWaypoints.shift();
29327 newWaypoints[0] = newSegmentStart;
29328 segmentOffset--;
29329 }
29330 }
29331
29332 // docking point, add segment if not intersecting anymore
29333 else {
29334 if (sourceToSegmentOrientation !== 'intersect') {
29335 newWaypoints.unshift(segmentStart);
29336 segmentOffset++;
29337 }
29338 }
29339 }
29340
29341 // handle last segment
29342 if (segmentEndIndex > waypointCount - 3) {
29343 targetToSegmentOrientation = getOrientation(connection.target, newSegmentEnd);
29344
29345 // last bendpoint, remove last segment if intersecting
29346 if (segmentEndIndex === waypointCount - 2) {
29347
29348 if (targetToSegmentOrientation === 'intersect') {
29349 newWaypoints.pop();
29350 newWaypoints[newWaypoints.length - 1] = newSegmentEnd;
29351 }
29352 }
29353
29354 // last bendpoint, remove last segment if intersecting
29355 else {
29356 if (targetToSegmentOrientation !== 'intersect') {
29357 newWaypoints.push(segmentEnd);
29358 }
29359 }
29360 }
29361
29362 // update connection waypoints
29363 context.newWaypoints = connection.waypoints = cropConnection(connection, newWaypoints);
29364
29365 // update dragger position
29366 updateDragger(context, segmentOffset, event);
29367
29368 // save segmentOffset in context
29369 context.newSegmentStartIndex = segmentStartIndex + segmentOffset;
29370
29371 // redraw connection
29372 redrawConnection(event);
29373 });
29374
29375 eventBus.on('connectionSegment.move.hover', function(event) {
29376
29377 event.context.hover = event.hover;
29378 canvas.addMarker(event.hover, MARKER_CONNECT_HOVER$1);
29379 });
29380
29381 eventBus.on([
29382 'connectionSegment.move.out',
29383 'connectionSegment.move.cleanup'
29384 ], function(event) {
29385
29386 // remove connect marker
29387 // if it was added
29388 var hover = event.context.hover;
29389
29390 if (hover) {
29391 canvas.removeMarker(hover, MARKER_CONNECT_HOVER$1);
29392 }
29393 });
29394
29395 eventBus.on('connectionSegment.move.cleanup', function(event) {
29396
29397 var context = event.context,
29398 connection = context.connection;
29399
29400 // remove dragger gfx
29401 if (context.draggerGfx) {
29402 remove$1(context.draggerGfx);
29403 }
29404
29405 canvas.removeMarker(connection, MARKER_CONNECT_UPDATING$1);
29406 });
29407
29408 eventBus.on([
29409 'connectionSegment.move.cancel',
29410 'connectionSegment.move.end'
29411 ], function(event) {
29412 var context = event.context,
29413 connection = context.connection;
29414
29415 connection.waypoints = context.originalWaypoints;
29416
29417 redrawConnection(event);
29418 });
29419
29420 eventBus.on('connectionSegment.move.end', function(event) {
29421
29422 var context = event.context,
29423 connection = context.connection,
29424 newWaypoints = context.newWaypoints,
29425 newSegmentStartIndex = context.newSegmentStartIndex;
29426
29427 // ensure we have actual pixel values bendpoint
29428 // coordinates (important when zoom level was > 1 during move)
29429 newWaypoints = newWaypoints.map(function(p) {
29430 return {
29431 original: p.original,
29432 x: Math.round(p.x),
29433 y: Math.round(p.y)
29434 };
29435 });
29436
29437 // apply filter redunant waypoints
29438 var filtered = filterRedundantWaypoints(newWaypoints, newSegmentStartIndex);
29439
29440 // get filtered waypoints
29441 var filteredWaypoints = filtered.waypoints,
29442 croppedWaypoints = cropConnection(connection, filteredWaypoints),
29443 segmentOffset = filtered.segmentOffset;
29444
29445 var hints = {
29446 segmentMove: {
29447 segmentStartIndex: context.segmentStartIndex,
29448 newSegmentStartIndex: newSegmentStartIndex + segmentOffset
29449 }
29450 };
29451
29452 modeling.updateWaypoints(connection, croppedWaypoints, hints);
29453 });
29454 }
29455
29456 ConnectionSegmentMove.$inject = [
29457 'injector',
29458 'eventBus',
29459 'canvas',
29460 'dragging',
29461 'graphicsFactory',
29462 'modeling'
29463 ];
29464
29465 var abs$1 = Math.abs,
29466 round$4 = Math.round;
29467
29468
29469 /**
29470 * Snap value to a collection of reference values.
29471 *
29472 * @param {number} value
29473 * @param {Array<number>} values
29474 * @param {number} [tolerance=10]
29475 *
29476 * @return {number} the value we snapped to or null, if none snapped
29477 */
29478 function snapTo(value, values, tolerance) {
29479 tolerance = tolerance === undefined ? 10 : tolerance;
29480
29481 var idx, snapValue;
29482
29483 for (idx = 0; idx < values.length; idx++) {
29484 snapValue = values[idx];
29485
29486 if (abs$1(snapValue - value) <= tolerance) {
29487 return snapValue;
29488 }
29489 }
29490 }
29491
29492
29493 function topLeft(bounds) {
29494 return {
29495 x: bounds.x,
29496 y: bounds.y
29497 };
29498 }
29499
29500 function bottomRight(bounds) {
29501 return {
29502 x: bounds.x + bounds.width,
29503 y: bounds.y + bounds.height
29504 };
29505 }
29506
29507 function mid(bounds, defaultValue) {
29508
29509 if (!bounds || isNaN(bounds.x) || isNaN(bounds.y)) {
29510 return defaultValue;
29511 }
29512
29513 return {
29514 x: round$4(bounds.x + bounds.width / 2),
29515 y: round$4(bounds.y + bounds.height / 2)
29516 };
29517 }
29518
29519
29520 /**
29521 * Retrieve the snap state of the given event.
29522 *
29523 * @param {Event} event
29524 * @param {string} axis
29525 *
29526 * @return {boolean} the snapped state
29527 *
29528 */
29529 function isSnapped(event, axis) {
29530 var snapped = event.snapped;
29531
29532 if (!snapped) {
29533 return false;
29534 }
29535
29536 if (typeof axis === 'string') {
29537 return snapped[axis];
29538 }
29539
29540 return snapped.x && snapped.y;
29541 }
29542
29543
29544 /**
29545 * Set the given event as snapped.
29546 *
29547 * This method may change the x and/or y position of the shape
29548 * from the given event!
29549 *
29550 * @param {Event} event
29551 * @param {string} axis
29552 * @param {number|boolean} value
29553 *
29554 * @return {number} old value
29555 */
29556 function setSnapped(event, axis, value) {
29557 if (typeof axis !== 'string') {
29558 throw new Error('axis must be in [x, y]');
29559 }
29560
29561 if (typeof value !== 'number' && value !== false) {
29562 throw new Error('value must be Number or false');
29563 }
29564
29565 var delta,
29566 previousValue = event[axis];
29567
29568 var snapped = event.snapped = (event.snapped || {});
29569
29570
29571 if (value === false) {
29572 snapped[axis] = false;
29573 } else {
29574 snapped[axis] = true;
29575
29576 delta = value - previousValue;
29577
29578 event[axis] += delta;
29579 event['d' + axis] += delta;
29580 }
29581
29582 return previousValue;
29583 }
29584
29585 /**
29586 * Get children of a shape.
29587 *
29588 * @param {djs.model.Shape} parent
29589 *
29590 * @returns {Array<djs.model.Shape|djs.model.Connection>}
29591 */
29592 function getChildren$1(parent) {
29593 return parent.children || [];
29594 }
29595
29596 var abs$2= Math.abs,
29597 round$5 = Math.round;
29598
29599 var TOLERANCE = 10;
29600
29601
29602 function BendpointSnapping(eventBus) {
29603
29604 function snapTo(values, value) {
29605
29606 if (isArray(values)) {
29607 var i = values.length;
29608
29609 while (i--) if (abs$2(values[i] - value) <= TOLERANCE) {
29610 return values[i];
29611 }
29612 } else {
29613 values = +values;
29614 var rem = value % values;
29615
29616 if (rem < TOLERANCE) {
29617 return value - rem;
29618 }
29619
29620 if (rem > values - TOLERANCE) {
29621 return value - rem + values;
29622 }
29623 }
29624
29625 return value;
29626 }
29627
29628 function mid(element) {
29629 if (element.width) {
29630 return {
29631 x: round$5(element.width / 2 + element.x),
29632 y: round$5(element.height / 2 + element.y)
29633 };
29634 }
29635 }
29636
29637 // connection segment snapping //////////////////////
29638
29639 function getConnectionSegmentSnaps(context) {
29640
29641 var snapPoints = context.snapPoints,
29642 connection = context.connection,
29643 waypoints = connection.waypoints,
29644 segmentStart = context.segmentStart,
29645 segmentStartIndex = context.segmentStartIndex,
29646 segmentEnd = context.segmentEnd,
29647 segmentEndIndex = context.segmentEndIndex,
29648 axis = context.axis;
29649
29650 if (snapPoints) {
29651 return snapPoints;
29652 }
29653
29654 var referenceWaypoints = [
29655 waypoints[segmentStartIndex - 1],
29656 segmentStart,
29657 segmentEnd,
29658 waypoints[segmentEndIndex + 1]
29659 ];
29660
29661 if (segmentStartIndex < 2) {
29662 referenceWaypoints.unshift(mid(connection.source));
29663 }
29664
29665 if (segmentEndIndex > waypoints.length - 3) {
29666 referenceWaypoints.unshift(mid(connection.target));
29667 }
29668
29669 context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };
29670
29671 forEach(referenceWaypoints, function(p) {
29672
29673 // we snap on existing bendpoints only,
29674 // not placeholders that are inserted during add
29675 if (p) {
29676 p = p.original || p;
29677
29678 if (axis === 'y') {
29679 snapPoints.horizontal.push(p.y);
29680 }
29681
29682 if (axis === 'x') {
29683 snapPoints.vertical.push(p.x);
29684 }
29685 }
29686 });
29687
29688 return snapPoints;
29689 }
29690
29691 eventBus.on('connectionSegment.move.move', 1500, function(event) {
29692 var context = event.context,
29693 snapPoints = getConnectionSegmentSnaps(context),
29694 x = event.x,
29695 y = event.y,
29696 sx, sy;
29697
29698 if (!snapPoints) {
29699 return;
29700 }
29701
29702 // snap
29703 sx = snapTo(snapPoints.vertical, x);
29704 sy = snapTo(snapPoints.horizontal, y);
29705
29706
29707 // correction x/y
29708 var cx = (x - sx),
29709 cy = (y - sy);
29710
29711 // update delta
29712 assign(event, {
29713 dx: event.dx - cx,
29714 dy: event.dy - cy,
29715 x: sx,
29716 y: sy
29717 });
29718
29719 // only set snapped if actually snapped
29720 if (cx || snapPoints.vertical.indexOf(x) !== -1) {
29721 setSnapped(event, 'x', sx);
29722 }
29723
29724 if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
29725 setSnapped(event, 'y', sy);
29726 }
29727 });
29728
29729
29730 // bendpoint snapping //////////////////////
29731
29732 function getBendpointSnaps(context) {
29733
29734 var snapPoints = context.snapPoints,
29735 waypoints = context.connection.waypoints,
29736 bendpointIndex = context.bendpointIndex;
29737
29738 if (snapPoints) {
29739 return snapPoints;
29740 }
29741
29742 var referenceWaypoints = [ waypoints[bendpointIndex - 1], waypoints[bendpointIndex + 1] ];
29743
29744 context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };
29745
29746 forEach(referenceWaypoints, function(p) {
29747
29748 // we snap on existing bendpoints only,
29749 // not placeholders that are inserted during add
29750 if (p) {
29751 p = p.original || p;
29752
29753 snapPoints.horizontal.push(p.y);
29754 snapPoints.vertical.push(p.x);
29755 }
29756 });
29757
29758 return snapPoints;
29759 }
29760
29761
29762 eventBus.on([ 'bendpoint.move.move', 'bendpoint.move.end' ], 1500, function(event) {
29763
29764 var context = event.context,
29765 snapPoints = getBendpointSnaps(context),
29766 hover = context.hover,
29767 hoverMid = hover && mid(hover),
29768 x = event.x,
29769 y = event.y,
29770 sx, sy;
29771
29772 if (!snapPoints) {
29773 return;
29774 }
29775
29776 // snap to hover mid
29777 sx = snapTo(hoverMid ? snapPoints.vertical.concat([ hoverMid.x ]) : snapPoints.vertical, x);
29778 sy = snapTo(hoverMid ? snapPoints.horizontal.concat([ hoverMid.y ]) : snapPoints.horizontal, y);
29779
29780 // correction x/y
29781 var cx = (x - sx),
29782 cy = (y - sy);
29783
29784 // update delta
29785 assign(event, {
29786 dx: event.dx - cx,
29787 dy: event.dy - cy,
29788 x: event.x - cx,
29789 y: event.y - cy
29790 });
29791
29792 // only set snapped if actually snapped
29793 if (cx || snapPoints.vertical.indexOf(x) !== -1) {
29794 setSnapped(event, 'x', sx);
29795 }
29796
29797 if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
29798 setSnapped(event, 'y', sy);
29799 }
29800 });
29801 }
29802
29803
29804 BendpointSnapping.$inject = [ 'eventBus' ];
29805
29806 var BendpointsModule = {
29807 __depends__: [
29808 DraggingModule,
29809 RulesModule
29810 ],
29811 __init__: [ 'bendpoints', 'bendpointSnapping', 'bendpointMovePreview' ],
29812 bendpoints: [ 'type', Bendpoints ],
29813 bendpointMove: [ 'type', BendpointMove ],
29814 bendpointMovePreview: [ 'type', BendpointMovePreview ],
29815 connectionSegmentMove: [ 'type', ConnectionSegmentMove ],
29816 bendpointSnapping: [ 'type', BendpointSnapping ]
29817 };
29818
29819 function Connect(eventBus, dragging, modeling, rules) {
29820
29821 // rules
29822
29823 function canConnect(source, target) {
29824 return rules.allowed('connection.create', {
29825 source: source,
29826 target: target
29827 });
29828 }
29829
29830 function canConnectReverse(source, target) {
29831 return canConnect(target, source);
29832 }
29833
29834
29835 // event handlers
29836
29837 eventBus.on('connect.hover', function(event) {
29838 var context = event.context,
29839 start = context.start,
29840 hover = event.hover,
29841 canExecute;
29842
29843 // cache hover state
29844 context.hover = hover;
29845
29846 canExecute = context.canExecute = canConnect(start, hover);
29847
29848 // ignore hover
29849 if (isNil(canExecute)) {
29850 return;
29851 }
29852
29853 if (canExecute !== false) {
29854 context.source = start;
29855 context.target = hover;
29856
29857 return;
29858 }
29859
29860 canExecute = context.canExecute = canConnectReverse(start, hover);
29861
29862 // ignore hover
29863 if (isNil(canExecute)) {
29864 return;
29865 }
29866
29867 if (canExecute !== false) {
29868 context.source = hover;
29869 context.target = start;
29870 }
29871 });
29872
29873 eventBus.on([ 'connect.out', 'connect.cleanup' ], function(event) {
29874 var context = event.context;
29875
29876 context.hover = null;
29877 context.source = null;
29878 context.target = null;
29879
29880 context.canExecute = false;
29881 });
29882
29883 eventBus.on('connect.end', function(event) {
29884 var context = event.context,
29885 canExecute = context.canExecute,
29886 connectionStart = context.connectionStart,
29887 connectionEnd = {
29888 x: event.x,
29889 y: event.y
29890 },
29891 source = context.source,
29892 target = context.target;
29893
29894 if (!canExecute) {
29895 return false;
29896 }
29897
29898 var attrs = null,
29899 hints = {
29900 connectionStart: isReverse$1(context) ? connectionEnd : connectionStart,
29901 connectionEnd: isReverse$1(context) ? connectionStart : connectionEnd
29902 };
29903
29904 if (isObject(canExecute)) {
29905 attrs = canExecute;
29906 }
29907
29908 modeling.connect(source, target, attrs, hints);
29909 });
29910
29911
29912 // API
29913
29914 /**
29915 * Start connect operation.
29916 *
29917 * @param {DOMEvent} event
29918 * @param {djs.model.Base} start
29919 * @param {Point} [connectionStart]
29920 * @param {boolean} [autoActivate=false]
29921 */
29922 this.start = function(event, start, connectionStart, autoActivate) {
29923 if (!isObject(connectionStart)) {
29924 autoActivate = connectionStart;
29925 connectionStart = getMid(start);
29926 }
29927
29928 dragging.init(event, 'connect', {
29929 autoActivate: autoActivate,
29930 data: {
29931 shape: start,
29932 context: {
29933 start: start,
29934 connectionStart: connectionStart
29935 }
29936 }
29937 });
29938 };
29939 }
29940
29941 Connect.$inject = [
29942 'eventBus',
29943 'dragging',
29944 'modeling',
29945 'rules'
29946 ];
29947
29948
29949 // helpers //////////
29950
29951 function isReverse$1(context) {
29952 var hover = context.hover,
29953 source = context.source,
29954 target = context.target;
29955
29956 return hover && source && hover === source && source !== target;
29957 }
29958
29959 var HIGH_PRIORITY$2 = 1100,
29960 LOW_PRIORITY$5 = 900;
29961
29962 var MARKER_OK$1 = 'connect-ok',
29963 MARKER_NOT_OK$1 = 'connect-not-ok';
29964
29965 /**
29966 * Shows connection preview during connect.
29967 *
29968 * @param {didi.Injector} injector
29969 * @param {EventBus} eventBus
29970 * @param {Canvas} canvas
29971 */
29972 function ConnectPreview(injector, eventBus, canvas) {
29973 var connectionPreview = injector.get('connectionPreview', false);
29974
29975 connectionPreview && eventBus.on('connect.move', function(event) {
29976 var context = event.context,
29977 canConnect = context.canExecute,
29978 hover = context.hover,
29979 source = context.source,
29980 start = context.start,
29981 startPosition = context.startPosition,
29982 target = context.target,
29983 connectionStart = context.connectionStart || startPosition,
29984 connectionEnd = context.connectionEnd || {
29985 x: event.x,
29986 y: event.y
29987 },
29988 previewStart = connectionStart,
29989 previewEnd = connectionEnd;
29990
29991 if (isReverse$1(context)) {
29992 previewStart = connectionEnd;
29993 previewEnd = connectionStart;
29994 }
29995
29996 connectionPreview.drawPreview(context, canConnect, {
29997 source: source || start,
29998 target: target || hover,
29999 connectionStart: previewStart,
30000 connectionEnd: previewEnd
30001 });
30002 });
30003
30004 eventBus.on('connect.hover', LOW_PRIORITY$5, function(event) {
30005 var context = event.context,
30006 hover = event.hover,
30007 canExecute = context.canExecute;
30008
30009 // ignore hover
30010 if (canExecute === null) {
30011 return;
30012 }
30013
30014 canvas.addMarker(hover, canExecute ? MARKER_OK$1 : MARKER_NOT_OK$1);
30015 });
30016
30017 eventBus.on([
30018 'connect.out',
30019 'connect.cleanup'
30020 ], HIGH_PRIORITY$2, function(event) {
30021 var hover = event.hover;
30022
30023 if (hover) {
30024 canvas.removeMarker(hover, MARKER_OK$1);
30025 canvas.removeMarker(hover, MARKER_NOT_OK$1);
30026 }
30027 });
30028
30029 connectionPreview && eventBus.on('connect.cleanup', function(event) {
30030 connectionPreview.cleanUp(event.context);
30031 });
30032 }
30033
30034 ConnectPreview.$inject = [
30035 'injector',
30036 'eventBus',
30037 'canvas'
30038 ];
30039
30040 var ConnectModule = {
30041 __depends__: [
30042 SelectionModule,
30043 RulesModule,
30044 DraggingModule
30045 ],
30046 __init__: [
30047 'connectPreview'
30048 ],
30049 connect: [ 'type', Connect ],
30050 connectPreview: [ 'type', ConnectPreview ]
30051 };
30052
30053 var MARKER_CONNECTION_PREVIEW = 'djs-connection-preview';
30054
30055 /**
30056 * Draws connection preview. Optionally, this can use layouter and connection docking to draw
30057 * better looking previews.
30058 *
30059 * @param {didi.Injector} injector
30060 * @param {Canvas} canvas
30061 * @param {GraphicsFactory} graphicsFactory
30062 * @param {ElementFactory} elementFactory
30063 */
30064 function ConnectionPreview(
30065 injector,
30066 canvas,
30067 graphicsFactory,
30068 elementFactory
30069 ) {
30070 this._canvas = canvas;
30071 this._graphicsFactory = graphicsFactory;
30072 this._elementFactory = elementFactory;
30073
30074 // optional components
30075 this._connectionDocking = injector.get('connectionDocking', false);
30076 this._layouter = injector.get('layouter', false);
30077 }
30078
30079 ConnectionPreview.$inject = [
30080 'injector',
30081 'canvas',
30082 'graphicsFactory',
30083 'elementFactory'
30084 ];
30085
30086 /**
30087 * Draw connection preview.
30088 *
30089 * Provide at least one of <source, connectionStart> and <target, connectionEnd> to create a preview.
30090 * In the clean up stage, call `connectionPreview#cleanUp` with the context to remove preview.
30091 *
30092 * @param {Object} context
30093 * @param {Object|boolean} canConnect
30094 * @param {Object} hints
30095 * @param {djs.model.shape} [hints.source] source element
30096 * @param {djs.model.shape} [hints.target] target element
30097 * @param {Point} [hints.connectionStart] connection preview start
30098 * @param {Point} [hints.connectionEnd] connection preview end
30099 * @param {Array<Point>} [hints.waypoints] provided waypoints for preview
30100 * @param {boolean} [hints.noLayout] true if preview should not be laid out
30101 * @param {boolean} [hints.noCropping] true if preview should not be cropped
30102 * @param {boolean} [hints.noNoop] true if simple connection should not be drawn
30103 */
30104 ConnectionPreview.prototype.drawPreview = function(context, canConnect, hints) {
30105
30106 hints = hints || {};
30107
30108 var connectionPreviewGfx = context.connectionPreviewGfx,
30109 getConnection = context.getConnection,
30110 source = hints.source,
30111 target = hints.target,
30112 waypoints = hints.waypoints,
30113 connectionStart = hints.connectionStart,
30114 connectionEnd = hints.connectionEnd,
30115 noLayout = hints.noLayout,
30116 noCropping = hints.noCropping,
30117 noNoop = hints.noNoop,
30118 connection;
30119
30120 var self = this;
30121
30122 if (!connectionPreviewGfx) {
30123 connectionPreviewGfx = context.connectionPreviewGfx = this.createConnectionPreviewGfx();
30124 }
30125
30126 clear$1(connectionPreviewGfx);
30127
30128 if (!getConnection) {
30129 getConnection = context.getConnection = cacheReturnValues(function(canConnect, source, target) {
30130 return self.getConnection(canConnect, source, target);
30131 });
30132 }
30133
30134 if (canConnect) {
30135 connection = getConnection(canConnect, source, target);
30136 }
30137
30138 if (!connection) {
30139 !noNoop && this.drawNoopPreview(connectionPreviewGfx, hints);
30140 return;
30141 }
30142
30143 connection.waypoints = waypoints || [];
30144
30145 // optional layout
30146 if (this._layouter && !noLayout) {
30147 connection.waypoints = this._layouter.layoutConnection(connection, {
30148 source: source,
30149 target: target,
30150 connectionStart: connectionStart,
30151 connectionEnd: connectionEnd,
30152 waypoints: hints.waypoints || connection.waypoints
30153 });
30154 }
30155
30156 // fallback if no waypoints were provided nor created with layouter
30157 if (!connection.waypoints || !connection.waypoints.length) {
30158 connection.waypoints = [
30159 source ? getMid(source) : connectionStart,
30160 target ? getMid(target) : connectionEnd
30161 ];
30162 }
30163
30164 // optional cropping
30165 if (this._connectionDocking && (source || target) && !noCropping) {
30166 connection.waypoints = this._connectionDocking.getCroppedWaypoints(connection, source, target);
30167 }
30168
30169 this._graphicsFactory.drawConnection(connectionPreviewGfx, connection);
30170 };
30171
30172 /**
30173 * Draw simple connection between source and target or provided points.
30174 *
30175 * @param {SVGElement} connectionPreviewGfx container for the connection
30176 * @param {Object} hints
30177 * @param {djs.model.shape} [hints.source] source element
30178 * @param {djs.model.shape} [hints.target] target element
30179 * @param {Point} [hints.connectionStart] required if source is not provided
30180 * @param {Point} [hints.connectionEnd] required if target is not provided
30181 */
30182 ConnectionPreview.prototype.drawNoopPreview = function(connectionPreviewGfx, hints) {
30183 var source = hints.source,
30184 target = hints.target,
30185 start = hints.connectionStart || getMid(source),
30186 end = hints.connectionEnd || getMid(target);
30187
30188 var waypoints = this.cropWaypoints(start, end, source, target);
30189
30190 var connection = this.createNoopConnection(waypoints[0], waypoints[1]);
30191
30192 append(connectionPreviewGfx, connection);
30193 };
30194
30195 /**
30196 * Return cropped waypoints.
30197 *
30198 * @param {Point} start
30199 * @param {Point} end
30200 * @param {djs.model.shape} source
30201 * @param {djs.model.shape} target
30202 *
30203 * @returns {Array}
30204 */
30205 ConnectionPreview.prototype.cropWaypoints = function(start, end, source, target) {
30206 var graphicsFactory = this._graphicsFactory,
30207 sourcePath = source && graphicsFactory.getShapePath(source),
30208 targetPath = target && graphicsFactory.getShapePath(target),
30209 connectionPath = graphicsFactory.getConnectionPath({ waypoints: [ start, end ] });
30210
30211 start = (source && getElementLineIntersection(sourcePath, connectionPath, true)) || start;
30212 end = (target && getElementLineIntersection(targetPath, connectionPath, false)) || end;
30213
30214 return [ start, end ];
30215 };
30216
30217 /**
30218 * Remove connection preview container if it exists.
30219 *
30220 * @param {Object} [context]
30221 * @param {SVGElement} [context.connectionPreviewGfx] preview container
30222 */
30223 ConnectionPreview.prototype.cleanUp = function(context) {
30224 if (context && context.connectionPreviewGfx) {
30225 remove$1(context.connectionPreviewGfx);
30226 }
30227 };
30228
30229 /**
30230 * Get connection that connects source and target.
30231 *
30232 * @param {Object|boolean} canConnect
30233 *
30234 * @returns {djs.model.connection}
30235 */
30236 ConnectionPreview.prototype.getConnection = function(canConnect) {
30237 var attrs = ensureConnectionAttrs(canConnect);
30238
30239 return this._elementFactory.createConnection(attrs);
30240 };
30241
30242
30243 /**
30244 * Add and return preview graphics.
30245 *
30246 * @returns {SVGElement}
30247 */
30248 ConnectionPreview.prototype.createConnectionPreviewGfx = function() {
30249 var gfx = create('g');
30250
30251 attr$1(gfx, {
30252 pointerEvents: 'none'
30253 });
30254
30255 classes$1(gfx).add(MARKER_CONNECTION_PREVIEW);
30256
30257 append(this._canvas.getDefaultLayer(), gfx);
30258
30259 return gfx;
30260 };
30261
30262 /**
30263 * Create and return simple connection.
30264 *
30265 * @param {Point} start
30266 * @param {Point} end
30267 *
30268 * @returns {SVGElement}
30269 */
30270 ConnectionPreview.prototype.createNoopConnection = function(start, end) {
30271 var connection = create('polyline');
30272
30273 attr$1(connection, {
30274 'stroke': '#333',
30275 'strokeDasharray': [ 1 ],
30276 'strokeWidth': 2,
30277 'pointer-events': 'none'
30278 });
30279
30280 attr$1(connection, { 'points': [ start.x, start.y, end.x, end.y ] });
30281
30282 return connection;
30283 };
30284
30285 // helpers //////////
30286
30287 /**
30288 * Returns function that returns cached return values referenced by stringified first argument.
30289 *
30290 * @param {Function} fn
30291 *
30292 * @return {Function}
30293 */
30294 function cacheReturnValues(fn) {
30295 var returnValues = {};
30296
30297 /**
30298 * Return cached return value referenced by stringified first argument.
30299 *
30300 * @returns {*}
30301 */
30302 return function(firstArgument) {
30303 var key = JSON.stringify(firstArgument);
30304
30305 var returnValue = returnValues[key];
30306
30307 if (!returnValue) {
30308 returnValue = returnValues[key] = fn.apply(null, arguments);
30309 }
30310
30311 return returnValue;
30312 };
30313 }
30314
30315 /**
30316 * Ensure connection attributes is object.
30317 *
30318 * @param {Object|boolean} canConnect
30319 *
30320 * @returns {Object}
30321 */
30322 function ensureConnectionAttrs(canConnect) {
30323 if (isObject(canConnect)) {
30324 return canConnect;
30325 } else {
30326 return {};
30327 }
30328 }
30329
30330 var ConnectionPreviewModule = {
30331 __init__: [ 'connectionPreview' ],
30332 connectionPreview: [ 'type', ConnectionPreview ]
30333 };
30334
30335 var min = Math.min,
30336 max$1 = Math.max;
30337
30338 function preventDefault$1(e) {
30339 e.preventDefault();
30340 }
30341
30342 function stopPropagation$1(e) {
30343 e.stopPropagation();
30344 }
30345
30346 function isTextNode(node) {
30347 return node.nodeType === Node.TEXT_NODE;
30348 }
30349
30350 function toArray(nodeList) {
30351 return [].slice.call(nodeList);
30352 }
30353
30354 /**
30355 * Initializes a container for a content editable div.
30356 *
30357 * Structure:
30358 *
30359 * container
30360 * parent
30361 * content
30362 * resize-handle
30363 *
30364 * @param {object} options
30365 * @param {DOMElement} options.container The DOM element to append the contentContainer to
30366 * @param {Function} options.keyHandler Handler for key events
30367 * @param {Function} options.resizeHandler Handler for resize events
30368 */
30369 function TextBox(options) {
30370 this.container = options.container;
30371
30372 this.parent = domify(
30373 '<div class="djs-direct-editing-parent">' +
30374 '<div class="djs-direct-editing-content" contenteditable="true"></div>' +
30375 '</div>'
30376 );
30377
30378 this.content = query('[contenteditable]', this.parent);
30379
30380 this.keyHandler = options.keyHandler || function() {};
30381 this.resizeHandler = options.resizeHandler || function() {};
30382
30383 this.autoResize = bind(this.autoResize, this);
30384 this.handlePaste = bind(this.handlePaste, this);
30385 }
30386
30387
30388 /**
30389 * Create a text box with the given position, size, style and text content
30390 *
30391 * @param {Object} bounds
30392 * @param {Number} bounds.x absolute x position
30393 * @param {Number} bounds.y absolute y position
30394 * @param {Number} [bounds.width] fixed width value
30395 * @param {Number} [bounds.height] fixed height value
30396 * @param {Number} [bounds.maxWidth] maximum width value
30397 * @param {Number} [bounds.maxHeight] maximum height value
30398 * @param {Number} [bounds.minWidth] minimum width value
30399 * @param {Number} [bounds.minHeight] minimum height value
30400 * @param {Object} [style]
30401 * @param {String} value text content
30402 *
30403 * @return {DOMElement} The created content DOM element
30404 */
30405 TextBox.prototype.create = function(bounds, style, value, options) {
30406 var self = this;
30407
30408 var parent = this.parent,
30409 content = this.content,
30410 container = this.container;
30411
30412 options = this.options = options || {};
30413
30414 style = this.style = style || {};
30415
30416 var parentStyle = pick(style, [
30417 'width',
30418 'height',
30419 'maxWidth',
30420 'maxHeight',
30421 'minWidth',
30422 'minHeight',
30423 'left',
30424 'top',
30425 'backgroundColor',
30426 'position',
30427 'overflow',
30428 'border',
30429 'wordWrap',
30430 'textAlign',
30431 'outline',
30432 'transform'
30433 ]);
30434
30435 assign(parent.style, {
30436 width: bounds.width + 'px',
30437 height: bounds.height + 'px',
30438 maxWidth: bounds.maxWidth + 'px',
30439 maxHeight: bounds.maxHeight + 'px',
30440 minWidth: bounds.minWidth + 'px',
30441 minHeight: bounds.minHeight + 'px',
30442 left: bounds.x + 'px',
30443 top: bounds.y + 'px',
30444 backgroundColor: '#ffffff',
30445 position: 'absolute',
30446 overflow: 'visible',
30447 border: '1px solid #ccc',
30448 boxSizing: 'border-box',
30449 wordWrap: 'normal',
30450 textAlign: 'center',
30451 outline: 'none'
30452 }, parentStyle);
30453
30454 var contentStyle = pick(style, [
30455 'fontFamily',
30456 'fontSize',
30457 'fontWeight',
30458 'lineHeight',
30459 'padding',
30460 'paddingTop',
30461 'paddingRight',
30462 'paddingBottom',
30463 'paddingLeft'
30464 ]);
30465
30466 assign(content.style, {
30467 boxSizing: 'border-box',
30468 width: '100%',
30469 outline: 'none',
30470 wordWrap: 'break-word'
30471 }, contentStyle);
30472
30473 if (options.centerVertically) {
30474 assign(content.style, {
30475 position: 'absolute',
30476 top: '50%',
30477 transform: 'translate(0, -50%)'
30478 }, contentStyle);
30479 }
30480
30481 content.innerText = value;
30482
30483 componentEvent.bind(content, 'keydown', this.keyHandler);
30484 componentEvent.bind(content, 'mousedown', stopPropagation$1);
30485 componentEvent.bind(content, 'paste', self.handlePaste);
30486
30487 if (options.autoResize) {
30488 componentEvent.bind(content, 'input', this.autoResize);
30489 }
30490
30491 if (options.resizable) {
30492 this.resizable(style);
30493 }
30494
30495 container.appendChild(parent);
30496
30497 // set selection to end of text
30498 this.setSelection(content.lastChild, content.lastChild && content.lastChild.length);
30499
30500 return parent;
30501 };
30502
30503 /**
30504 * Intercept paste events to remove formatting from pasted text.
30505 */
30506 TextBox.prototype.handlePaste = function(e) {
30507 var options = this.options,
30508 style = this.style;
30509
30510 e.preventDefault();
30511
30512 var text;
30513
30514 if (e.clipboardData) {
30515
30516 // Chrome, Firefox, Safari
30517 text = e.clipboardData.getData('text/plain');
30518 } else {
30519
30520 // Internet Explorer
30521 text = window.clipboardData.getData('Text');
30522 }
30523
30524 this.insertText(text);
30525
30526 if (options.autoResize) {
30527 var hasResized = this.autoResize(style);
30528
30529 if (hasResized) {
30530 this.resizeHandler(hasResized);
30531 }
30532 }
30533 };
30534
30535 TextBox.prototype.insertText = function(text) {
30536 text = normalizeEndOfLineSequences(text);
30537
30538 // insertText command not supported by Internet Explorer
30539 var success = document.execCommand('insertText', false, text);
30540
30541 if (success) {
30542 return;
30543 }
30544
30545 this._insertTextIE(text);
30546 };
30547
30548 TextBox.prototype._insertTextIE = function(text) {
30549
30550 // Internet Explorer
30551 var range = this.getSelection(),
30552 startContainer = range.startContainer,
30553 endContainer = range.endContainer,
30554 startOffset = range.startOffset,
30555 endOffset = range.endOffset,
30556 commonAncestorContainer = range.commonAncestorContainer;
30557
30558 var childNodesArray = toArray(commonAncestorContainer.childNodes);
30559
30560 var container,
30561 offset;
30562
30563 if (isTextNode(commonAncestorContainer)) {
30564 var containerTextContent = startContainer.textContent;
30565
30566 startContainer.textContent =
30567 containerTextContent.substring(0, startOffset)
30568 + text
30569 + containerTextContent.substring(endOffset);
30570
30571 container = startContainer;
30572 offset = startOffset + text.length;
30573
30574 } else if (startContainer === this.content && endContainer === this.content) {
30575 var textNode = document.createTextNode(text);
30576
30577 this.content.insertBefore(textNode, childNodesArray[startOffset]);
30578
30579 container = textNode;
30580 offset = textNode.textContent.length;
30581 } else {
30582 var startContainerChildIndex = childNodesArray.indexOf(startContainer),
30583 endContainerChildIndex = childNodesArray.indexOf(endContainer);
30584
30585 childNodesArray.forEach(function(childNode, index) {
30586
30587 if (index === startContainerChildIndex) {
30588 childNode.textContent =
30589 startContainer.textContent.substring(0, startOffset) +
30590 text +
30591 endContainer.textContent.substring(endOffset);
30592 } else if (index > startContainerChildIndex && index <= endContainerChildIndex) {
30593 remove(childNode);
30594 }
30595 });
30596
30597 container = startContainer;
30598 offset = startOffset + text.length;
30599 }
30600
30601 if (container && offset !== undefined) {
30602
30603 // is necessary in Internet Explorer
30604 setTimeout(function() {
30605 self.setSelection(container, offset);
30606 });
30607 }
30608 };
30609
30610 /**
30611 * Automatically resize element vertically to fit its content.
30612 */
30613 TextBox.prototype.autoResize = function() {
30614 var parent = this.parent,
30615 content = this.content;
30616
30617 var fontSize = parseInt(this.style.fontSize) || 12;
30618
30619 if (content.scrollHeight > parent.offsetHeight ||
30620 content.scrollHeight < parent.offsetHeight - fontSize) {
30621 var bounds = parent.getBoundingClientRect();
30622
30623 var height = content.scrollHeight;
30624 parent.style.height = height + 'px';
30625
30626 this.resizeHandler({
30627 width: bounds.width,
30628 height: bounds.height,
30629 dx: 0,
30630 dy: height - bounds.height
30631 });
30632 }
30633 };
30634
30635 /**
30636 * Make an element resizable by adding a resize handle.
30637 */
30638 TextBox.prototype.resizable = function() {
30639 var self = this;
30640
30641 var parent = this.parent,
30642 resizeHandle = this.resizeHandle;
30643
30644 var minWidth = parseInt(this.style.minWidth) || 0,
30645 minHeight = parseInt(this.style.minHeight) || 0,
30646 maxWidth = parseInt(this.style.maxWidth) || Infinity,
30647 maxHeight = parseInt(this.style.maxHeight) || Infinity;
30648
30649 if (!resizeHandle) {
30650 resizeHandle = this.resizeHandle = domify(
30651 '<div class="djs-direct-editing-resize-handle"></div>'
30652 );
30653
30654 var startX, startY, startWidth, startHeight;
30655
30656 var onMouseDown = function(e) {
30657 preventDefault$1(e);
30658 stopPropagation$1(e);
30659
30660 startX = e.clientX;
30661 startY = e.clientY;
30662
30663 var bounds = parent.getBoundingClientRect();
30664
30665 startWidth = bounds.width;
30666 startHeight = bounds.height;
30667
30668 componentEvent.bind(document, 'mousemove', onMouseMove);
30669 componentEvent.bind(document, 'mouseup', onMouseUp);
30670 };
30671
30672 var onMouseMove = function(e) {
30673 preventDefault$1(e);
30674 stopPropagation$1(e);
30675
30676 var newWidth = min(max$1(startWidth + e.clientX - startX, minWidth), maxWidth);
30677 var newHeight = min(max$1(startHeight + e.clientY - startY, minHeight), maxHeight);
30678
30679 parent.style.width = newWidth + 'px';
30680 parent.style.height = newHeight + 'px';
30681
30682 self.resizeHandler({
30683 width: startWidth,
30684 height: startHeight,
30685 dx: e.clientX - startX,
30686 dy: e.clientY - startY
30687 });
30688 };
30689
30690 var onMouseUp = function(e) {
30691 preventDefault$1(e);
30692 stopPropagation$1(e);
30693
30694 componentEvent.unbind(document,'mousemove', onMouseMove, false);
30695 componentEvent.unbind(document, 'mouseup', onMouseUp, false);
30696 };
30697
30698 componentEvent.bind(resizeHandle, 'mousedown', onMouseDown);
30699 }
30700
30701 assign(resizeHandle.style, {
30702 position: 'absolute',
30703 bottom: '0px',
30704 right: '0px',
30705 cursor: 'nwse-resize',
30706 width: '0',
30707 height: '0',
30708 borderTop: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent',
30709 borderRight: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
30710 borderBottom: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
30711 borderLeft: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent'
30712 });
30713
30714 parent.appendChild(resizeHandle);
30715 };
30716
30717
30718 /**
30719 * Clear content and style of the textbox, unbind listeners and
30720 * reset CSS style.
30721 */
30722 TextBox.prototype.destroy = function() {
30723 var parent = this.parent,
30724 content = this.content,
30725 resizeHandle = this.resizeHandle;
30726
30727 // clear content
30728 content.innerText = '';
30729
30730 // clear styles
30731 parent.removeAttribute('style');
30732 content.removeAttribute('style');
30733
30734 componentEvent.unbind(content, 'keydown', this.keyHandler);
30735 componentEvent.unbind(content, 'mousedown', stopPropagation$1);
30736 componentEvent.unbind(content, 'input', this.autoResize);
30737 componentEvent.unbind(content, 'paste', this.handlePaste);
30738
30739 if (resizeHandle) {
30740 resizeHandle.removeAttribute('style');
30741
30742 remove(resizeHandle);
30743 }
30744
30745 remove(parent);
30746 };
30747
30748
30749 TextBox.prototype.getValue = function() {
30750 return this.content.innerText.trim();
30751 };
30752
30753
30754 TextBox.prototype.getSelection = function() {
30755 var selection = window.getSelection(),
30756 range = selection.getRangeAt(0);
30757
30758 return range;
30759 };
30760
30761
30762 TextBox.prototype.setSelection = function(container, offset) {
30763 var range = document.createRange();
30764
30765 if (container === null) {
30766 range.selectNodeContents(this.content);
30767 } else {
30768 range.setStart(container, offset);
30769 range.setEnd(container, offset);
30770 }
30771
30772 var selection = window.getSelection();
30773
30774 selection.removeAllRanges();
30775 selection.addRange(range);
30776 };
30777
30778 // helpers //////////
30779
30780 function normalizeEndOfLineSequences(string) {
30781 return string.replace(/\r\n|\r|\n/g, '\n');
30782 }
30783
30784 /**
30785 * A direct editing component that allows users
30786 * to edit an elements text directly in the diagram
30787 *
30788 * @param {EventBus} eventBus the event bus
30789 */
30790 function DirectEditing(eventBus, canvas) {
30791
30792 this._eventBus = eventBus;
30793
30794 this._providers = [];
30795 this._textbox = new TextBox({
30796 container: canvas.getContainer(),
30797 keyHandler: bind(this._handleKey, this),
30798 resizeHandler: bind(this._handleResize, this)
30799 });
30800 }
30801
30802 DirectEditing.$inject = [ 'eventBus', 'canvas' ];
30803
30804
30805 /**
30806 * Register a direct editing provider
30807
30808 * @param {Object} provider the provider, must expose an #activate(element) method that returns
30809 * an activation context ({ bounds: {x, y, width, height }, text }) if
30810 * direct editing is available for the given element.
30811 * Additionally the provider must expose a #update(element, value) method
30812 * to receive direct editing updates.
30813 */
30814 DirectEditing.prototype.registerProvider = function(provider) {
30815 this._providers.push(provider);
30816 };
30817
30818
30819 /**
30820 * Returns true if direct editing is currently active
30821 *
30822 * @return {Boolean}
30823 */
30824 DirectEditing.prototype.isActive = function() {
30825 return !!this._active;
30826 };
30827
30828
30829 /**
30830 * Cancel direct editing, if it is currently active
30831 */
30832 DirectEditing.prototype.cancel = function() {
30833 if (!this._active) {
30834 return;
30835 }
30836
30837 this._fire('cancel');
30838 this.close();
30839 };
30840
30841
30842 DirectEditing.prototype._fire = function(event, context) {
30843 this._eventBus.fire('directEditing.' + event, context || { active: this._active });
30844 };
30845
30846 DirectEditing.prototype.close = function() {
30847 this._textbox.destroy();
30848
30849 this._fire('deactivate');
30850
30851 this._active = null;
30852
30853 this.resizable = undefined;
30854 };
30855
30856
30857 DirectEditing.prototype.complete = function() {
30858
30859 var active = this._active;
30860
30861 if (!active) {
30862 return;
30863 }
30864
30865 var containerBounds,
30866 previousBounds = active.context.bounds,
30867 newBounds = this.$textbox.getBoundingClientRect(),
30868 newText = this.getValue(),
30869 previousText = active.context.text;
30870
30871 if (
30872 newText !== previousText ||
30873 newBounds.height !== previousBounds.height ||
30874 newBounds.width !== previousBounds.width
30875 ) {
30876 containerBounds = this._textbox.container.getBoundingClientRect();
30877
30878 active.provider.update(active.element, newText, active.context.text, {
30879 x: newBounds.left - containerBounds.left,
30880 y: newBounds.top - containerBounds.top,
30881 width: newBounds.width,
30882 height: newBounds.height
30883 });
30884 }
30885
30886 this._fire('complete');
30887
30888 this.close();
30889 };
30890
30891
30892 DirectEditing.prototype.getValue = function() {
30893 return this._textbox.getValue();
30894 };
30895
30896
30897 DirectEditing.prototype._handleKey = function(e) {
30898
30899 // stop bubble
30900 e.stopPropagation();
30901
30902 var key = e.keyCode || e.charCode;
30903
30904 // ESC
30905 if (key === 27) {
30906 e.preventDefault();
30907 return this.cancel();
30908 }
30909
30910 // Enter
30911 if (key === 13 && !e.shiftKey) {
30912 e.preventDefault();
30913 return this.complete();
30914 }
30915 };
30916
30917
30918 DirectEditing.prototype._handleResize = function(event) {
30919 this._fire('resize', event);
30920 };
30921
30922
30923 /**
30924 * Activate direct editing on the given element
30925 *
30926 * @param {Object} ElementDescriptor the descriptor for a shape or connection
30927 * @return {Boolean} true if the activation was possible
30928 */
30929 DirectEditing.prototype.activate = function(element) {
30930 if (this.isActive()) {
30931 this.cancel();
30932 }
30933
30934 // the direct editing context
30935 var context;
30936
30937 var provider = find(this._providers, function(p) {
30938 return (context = p.activate(element)) ? p : null;
30939 });
30940
30941 // check if activation took place
30942 if (context) {
30943 this.$textbox = this._textbox.create(
30944 context.bounds,
30945 context.style,
30946 context.text,
30947 context.options
30948 );
30949
30950 this._active = {
30951 element: element,
30952 context: context,
30953 provider: provider
30954 };
30955
30956 if (context.options && context.options.resizable) {
30957 this.resizable = true;
30958 }
30959
30960 this._fire('activate');
30961 }
30962
30963 return !!context;
30964 };
30965
30966 var DirectEditingModule = {
30967 __depends__: [
30968 InteractionEventsModule
30969 ],
30970 __init__: [ 'directEditing' ],
30971 directEditing: [ 'type', DirectEditing ]
30972 };
30973
30974 var entrySelector = '.entry';
30975
30976 var DEFAULT_PRIORITY$3 = 1000;
30977
30978
30979 /**
30980 * A context pad that displays element specific, contextual actions next
30981 * to a diagram element.
30982 *
30983 * @param {Object} config
30984 * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
30985 * @param {number} [config.scale.min]
30986 * @param {number} [config.scale.max]
30987 * @param {EventBus} eventBus
30988 * @param {Overlays} overlays
30989 */
30990 function ContextPad(config, eventBus, overlays) {
30991
30992 this._eventBus = eventBus;
30993 this._overlays = overlays;
30994
30995 var scale = isDefined(config && config.scale) ? config.scale : {
30996 min: 1,
30997 max: 1.5
30998 };
30999
31000 this._overlaysConfig = {
31001 position: {
31002 right: -9,
31003 top: -6
31004 },
31005 scale: scale
31006 };
31007
31008 this._current = null;
31009
31010 this._init();
31011 }
31012
31013 ContextPad.$inject = [
31014 'config.contextPad',
31015 'eventBus',
31016 'overlays'
31017 ];
31018
31019
31020 /**
31021 * Registers events needed for interaction with other components
31022 */
31023 ContextPad.prototype._init = function() {
31024
31025 var eventBus = this._eventBus;
31026
31027 var self = this;
31028
31029 eventBus.on('selection.changed', function(e) {
31030
31031 var selection = e.newSelection;
31032
31033 if (selection.length === 1) {
31034 self.open(selection[0]);
31035 } else {
31036 self.close();
31037 }
31038 });
31039
31040 eventBus.on('elements.delete', function(event) {
31041 var elements = event.elements;
31042
31043 forEach(elements, function(e) {
31044 if (self.isOpen(e)) {
31045 self.close();
31046 }
31047 });
31048 });
31049
31050 eventBus.on('element.changed', function(event) {
31051 var element = event.element,
31052 current = self._current;
31053
31054 // force reopen if element for which we are currently opened changed
31055 if (current && current.element === element) {
31056 self.open(element, true);
31057 }
31058 });
31059 };
31060
31061
31062 /**
31063 * Register a provider with the context pad
31064 *
31065 * @param {number} [priority=1000]
31066 * @param {ContextPadProvider} provider
31067 *
31068 * @example
31069 * const contextPadProvider = {
31070 * getContextPadEntries: function(element) {
31071 * return function(entries) {
31072 * return {
31073 * ...entries,
31074 * 'entry-1': {
31075 * label: 'My Entry',
31076 * action: function() { alert("I have been clicked!"); }
31077 * }
31078 * };
31079 * }
31080 * }
31081 * };
31082 *
31083 * contextPad.registerProvider(800, contextPadProvider);
31084 */
31085 ContextPad.prototype.registerProvider = function(priority, provider) {
31086 if (!provider) {
31087 provider = priority;
31088 priority = DEFAULT_PRIORITY$3;
31089 }
31090
31091 this._eventBus.on('contextPad.getProviders', priority, function(event) {
31092 event.providers.push(provider);
31093 });
31094 };
31095
31096
31097 /**
31098 * Returns the context pad entries for a given element
31099 *
31100 * @param {djs.element.Base} element
31101 *
31102 * @return {Array<ContextPadEntryDescriptor>} list of entries
31103 */
31104 ContextPad.prototype.getEntries = function(element) {
31105 var providers = this._getProviders();
31106
31107 var entries = {};
31108
31109 // loop through all providers and their entries.
31110 // group entries by id so that overriding an entry is possible
31111 forEach(providers, function(provider) {
31112 var entriesOrUpdater = provider.getContextPadEntries(element);
31113
31114 if (isFunction(entriesOrUpdater)) {
31115 entries = entriesOrUpdater(entries);
31116 } else {
31117 forEach(entriesOrUpdater, function(entry, id) {
31118 entries[id] = entry;
31119 });
31120 }
31121 });
31122
31123 return entries;
31124 };
31125
31126
31127 /**
31128 * Trigger an action available on the opened context pad
31129 *
31130 * @param {string} action
31131 * @param {Event} event
31132 * @param {boolean} [autoActivate=false]
31133 */
31134 ContextPad.prototype.trigger = function(action, event, autoActivate) {
31135
31136 var element = this._current.element,
31137 entries = this._current.entries,
31138 entry,
31139 handler,
31140 originalEvent,
31141 button = event.delegateTarget || event.target;
31142
31143 if (!button) {
31144 return event.preventDefault();
31145 }
31146
31147 entry = entries[attr(button, 'data-action')];
31148 handler = entry.action;
31149
31150 originalEvent = event.originalEvent || event;
31151
31152 // simple action (via callback function)
31153 if (isFunction(handler)) {
31154 if (action === 'click') {
31155 return handler(originalEvent, element, autoActivate);
31156 }
31157 } else {
31158 if (handler[action]) {
31159 return handler[action](originalEvent, element, autoActivate);
31160 }
31161 }
31162
31163 // silence other actions
31164 event.preventDefault();
31165 };
31166
31167
31168 /**
31169 * Open the context pad for the given element
31170 *
31171 * @param {djs.model.Base} element
31172 * @param {boolean} force if true, force reopening the context pad
31173 */
31174 ContextPad.prototype.open = function(element, force) {
31175 if (!force && this.isOpen(element)) {
31176 return;
31177 }
31178
31179 this.close();
31180 this._updateAndOpen(element);
31181 };
31182
31183 ContextPad.prototype._getProviders = function() {
31184
31185 var event = this._eventBus.createEvent({
31186 type: 'contextPad.getProviders',
31187 providers: []
31188 });
31189
31190 this._eventBus.fire(event);
31191
31192 return event.providers;
31193 };
31194
31195 ContextPad.prototype._updateAndOpen = function(element) {
31196
31197 var entries = this.getEntries(element),
31198 pad = this.getPad(element),
31199 html = pad.html;
31200
31201 forEach(entries, function(entry, id) {
31202 var grouping = entry.group || 'default',
31203 control = domify(entry.html || '<div class="entry" draggable="true"></div>'),
31204 container;
31205
31206 attr(control, 'data-action', id);
31207
31208 container = query('[data-group=' + grouping + ']', html);
31209 if (!container) {
31210 container = domify('<div class="group" data-group="' + grouping + '"></div>');
31211 html.appendChild(container);
31212 }
31213
31214 container.appendChild(control);
31215
31216 if (entry.className) {
31217 addClasses(control, entry.className);
31218 }
31219
31220 if (entry.title) {
31221 attr(control, 'title', entry.title);
31222 }
31223
31224 if (entry.imageUrl) {
31225 control.appendChild(domify('<img src="' + entry.imageUrl + '">'));
31226 }
31227 });
31228
31229 classes(html).add('open');
31230
31231 this._current = {
31232 element: element,
31233 pad: pad,
31234 entries: entries
31235 };
31236
31237 this._eventBus.fire('contextPad.open', { current: this._current });
31238 };
31239
31240
31241 ContextPad.prototype.getPad = function(element) {
31242 if (this.isOpen()) {
31243 return this._current.pad;
31244 }
31245
31246 var self = this;
31247
31248 var overlays = this._overlays;
31249
31250 var html = domify('<div class="djs-context-pad"></div>');
31251
31252 var overlaysConfig = assign({
31253 html: html
31254 }, this._overlaysConfig);
31255
31256 delegate.bind(html, entrySelector, 'click', function(event) {
31257 self.trigger('click', event);
31258 });
31259
31260 delegate.bind(html, entrySelector, 'dragstart', function(event) {
31261 self.trigger('dragstart', event);
31262 });
31263
31264 // stop propagation of mouse events
31265 componentEvent.bind(html, 'mousedown', function(event) {
31266 event.stopPropagation();
31267 });
31268
31269 this._overlayId = overlays.add(element, 'context-pad', overlaysConfig);
31270
31271 var pad = overlays.get(this._overlayId);
31272
31273 this._eventBus.fire('contextPad.create', { element: element, pad: pad });
31274
31275 return pad;
31276 };
31277
31278
31279 /**
31280 * Close the context pad
31281 */
31282 ContextPad.prototype.close = function() {
31283 if (!this.isOpen()) {
31284 return;
31285 }
31286
31287 this._overlays.remove(this._overlayId);
31288
31289 this._overlayId = null;
31290
31291 this._eventBus.fire('contextPad.close', { current: this._current });
31292
31293 this._current = null;
31294 };
31295
31296 /**
31297 * Check if pad is open. If element is given, will check
31298 * if pad is opened with given element.
31299 *
31300 * @param {Element} element
31301 * @return {boolean}
31302 */
31303 ContextPad.prototype.isOpen = function(element) {
31304 return !!this._current && (!element ? true : this._current.element === element);
31305 };
31306
31307
31308
31309
31310 // helpers //////////////////////
31311
31312 function addClasses(element, classNames) {
31313
31314 var classes$1 = classes(element);
31315
31316 var actualClassNames = isArray(classNames) ? classNames : classNames.split(/\s+/g);
31317 actualClassNames.forEach(function(cls) {
31318 classes$1.add(cls);
31319 });
31320 }
31321
31322 var ContextPadModule = {
31323 __depends__: [
31324 InteractionEventsModule,
31325 OverlaysModule
31326 ],
31327 contextPad: [ 'type', ContextPad ]
31328 };
31329
31330 var MARKER_TYPES = [
31331 'marker-start',
31332 'marker-mid',
31333 'marker-end'
31334 ];
31335
31336 var NODES_CAN_HAVE_MARKER = [
31337 'circle',
31338 'ellipse',
31339 'line',
31340 'path',
31341 'polygon',
31342 'polyline',
31343 'rect'
31344 ];
31345
31346
31347 /**
31348 * Adds support for previews of moving/resizing elements.
31349 */
31350 function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
31351 this._elementRegistry = elementRegistry;
31352 this._canvas = canvas;
31353 this._styles = styles;
31354
31355 this._clonedMarkers = {};
31356
31357 var self = this;
31358
31359 eventBus.on('drag.cleanup', function() {
31360 forEach(self._clonedMarkers, function(clonedMarker) {
31361 remove$1(clonedMarker);
31362 });
31363
31364 self._clonedMarkers = {};
31365 });
31366 }
31367
31368 PreviewSupport.$inject = [
31369 'elementRegistry',
31370 'eventBus',
31371 'canvas',
31372 'styles'
31373 ];
31374
31375
31376 /**
31377 * Returns graphics of an element.
31378 *
31379 * @param {djs.model.Base} element
31380 *
31381 * @return {SVGElement}
31382 */
31383 PreviewSupport.prototype.getGfx = function(element) {
31384 return this._elementRegistry.getGraphics(element);
31385 };
31386
31387 /**
31388 * Adds a move preview of a given shape to a given svg group.
31389 *
31390 * @param {djs.model.Base} element
31391 * @param {SVGElement} group
31392 * @param {SVGElement} [gfx]
31393 *
31394 * @return {SVGElement} dragger
31395 */
31396 PreviewSupport.prototype.addDragger = function(element, group, gfx) {
31397 gfx = gfx || this.getGfx(element);
31398
31399 var dragger = clone(gfx);
31400 var bbox = gfx.getBoundingClientRect();
31401
31402 this._cloneMarkers(getVisual(dragger));
31403
31404 attr$1(dragger, this._styles.cls('djs-dragger', [], {
31405 x: bbox.top,
31406 y: bbox.left
31407 }));
31408
31409 append(group, dragger);
31410
31411 return dragger;
31412 };
31413
31414 /**
31415 * Adds a resize preview of a given shape to a given svg group.
31416 *
31417 * @param {djs.model.Base} element
31418 * @param {SVGElement} group
31419 *
31420 * @return {SVGElement} frame
31421 */
31422 PreviewSupport.prototype.addFrame = function(shape, group) {
31423
31424 var frame = create('rect', {
31425 class: 'djs-resize-overlay',
31426 width: shape.width,
31427 height: shape.height,
31428 x: shape.x,
31429 y: shape.y
31430 });
31431
31432 append(group, frame);
31433
31434 return frame;
31435 };
31436
31437 /**
31438 * Clone all markers referenced by a node and its child nodes.
31439 *
31440 * @param {SVGElement} gfx
31441 */
31442 PreviewSupport.prototype._cloneMarkers = function(gfx) {
31443 var self = this;
31444
31445 if (gfx.childNodes) {
31446
31447 // TODO: use forEach once we drop PhantomJS
31448 for (var i = 0; i < gfx.childNodes.length; i++) {
31449
31450 // recursively clone markers of child nodes
31451 self._cloneMarkers(gfx.childNodes[ i ]);
31452 }
31453 }
31454
31455 if (!canHaveMarker(gfx)) {
31456 return;
31457 }
31458
31459 MARKER_TYPES.forEach(function(markerType) {
31460 if (attr$1(gfx, markerType)) {
31461 var marker = getMarker(gfx, markerType, self._canvas.getContainer());
31462
31463 self._cloneMarker(gfx, marker, markerType);
31464 }
31465 });
31466 };
31467
31468 /**
31469 * Clone marker referenced by an element.
31470 *
31471 * @param {SVGElement} gfx
31472 * @param {SVGElement} marker
31473 * @param {string} markerType
31474 */
31475 PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) {
31476 var markerId = marker.id;
31477
31478 var clonedMarker = this._clonedMarkers[ markerId ];
31479
31480 if (!clonedMarker) {
31481 clonedMarker = clone(marker);
31482
31483 var clonedMarkerId = markerId + '-clone';
31484
31485 clonedMarker.id = clonedMarkerId;
31486
31487 classes$1(clonedMarker)
31488 .add('djs-dragger')
31489 .add('djs-dragger-marker');
31490
31491 this._clonedMarkers[ markerId ] = clonedMarker;
31492
31493 var defs = query('defs', this._canvas._svg);
31494
31495 if (!defs) {
31496 defs = create('defs');
31497
31498 append(this._canvas._svg, defs);
31499 }
31500
31501 append(defs, clonedMarker);
31502 }
31503
31504 var reference = idToReference(this._clonedMarkers[ markerId ].id);
31505
31506 attr$1(gfx, markerType, reference);
31507 };
31508
31509 // helpers //////////
31510
31511 /**
31512 * Get marker of given type referenced by node.
31513 *
31514 * @param {Node} node
31515 * @param {string} markerType
31516 * @param {Node} [parentNode]
31517 *
31518 * @param {Node}
31519 */
31520 function getMarker(node, markerType, parentNode) {
31521 var id = referenceToId(attr$1(node, markerType));
31522
31523 return query('marker#' + id, parentNode || document);
31524 }
31525
31526 /**
31527 * Get ID of fragment within current document from its functional IRI reference.
31528 * References may use single or double quotes.
31529 *
31530 * @param {string} reference
31531 *
31532 * @returns {string}
31533 */
31534 function referenceToId(reference) {
31535 return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
31536 }
31537
31538 /**
31539 * Get functional IRI reference for given ID of fragment within current document.
31540 *
31541 * @param {string} id
31542 *
31543 * @returns {string}
31544 */
31545 function idToReference(id) {
31546 return 'url(#' + id + ')';
31547 }
31548
31549 /**
31550 * Check wether node type can have marker attributes.
31551 *
31552 * @param {Node} node
31553 *
31554 * @returns {boolean}
31555 */
31556 function canHaveMarker(node) {
31557 return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
31558 }
31559
31560 var PreviewSupportModule = {
31561 __init__: [ 'previewSupport' ],
31562 previewSupport: [ 'type', PreviewSupport ]
31563 };
31564
31565 var MARKER_OK$2 = 'drop-ok',
31566 MARKER_NOT_OK$2 = 'drop-not-ok',
31567 MARKER_ATTACH = 'attach-ok',
31568 MARKER_NEW_PARENT = 'new-parent';
31569
31570 var PREFIX = 'create';
31571
31572 var HIGH_PRIORITY$3 = 2000;
31573
31574
31575 /**
31576 * Create new elements through drag and drop.
31577 *
31578 * @param {Canvas} canvas
31579 * @param {Dragging} dragging
31580 * @param {EventBus} eventBus
31581 * @param {Modeling} modeling
31582 * @param {Rules} rules
31583 */
31584 function Create(
31585 canvas,
31586 dragging,
31587 eventBus,
31588 modeling,
31589 rules
31590 ) {
31591
31592 // rules //////////
31593
31594 /**
31595 * Check wether elements can be created.
31596 *
31597 * @param {Array<djs.model.Base>} elements
31598 * @param {djs.model.Base} target
31599 * @param {Point} position
31600 * @param {djs.model.Base} [source]
31601 *
31602 * @returns {boolean|null|Object}
31603 */
31604 function canCreate(elements, target, position, source, hints) {
31605 if (!target) {
31606 return false;
31607 }
31608
31609 // ignore child elements and external labels
31610 elements = filter(elements, function(element) {
31611 var labelTarget = element.labelTarget;
31612
31613 return !element.parent && !(isLabel$1(element) && elements.indexOf(labelTarget) !== -1);
31614 });
31615
31616 var shape = find(elements, function(element) {
31617 return !isConnection(element);
31618 });
31619
31620 var attach = false,
31621 connect = false,
31622 create = false;
31623
31624 // (1) attaching single shapes
31625 if (isSingleShape(elements)) {
31626 attach = rules.allowed('shape.attach', {
31627 position: position,
31628 shape: shape,
31629 target: target
31630 });
31631 }
31632
31633 if (!attach) {
31634
31635 // (2) creating elements
31636 if (isSingleShape(elements)) {
31637 create = rules.allowed('shape.create', {
31638 position: position,
31639 shape: shape,
31640 source: source,
31641 target: target
31642 });
31643 } else {
31644 create = rules.allowed('elements.create', {
31645 elements: elements,
31646 position: position,
31647 target: target
31648 });
31649 }
31650
31651 }
31652
31653 var connectionTarget = hints.connectionTarget;
31654
31655 // (3) appending single shapes
31656 if (create || attach) {
31657 if (shape && source) {
31658 connect = rules.allowed('connection.create', {
31659 source: connectionTarget === source ? shape : source,
31660 target: connectionTarget === source ? source : shape,
31661 hints: {
31662 targetParent: target,
31663 targetAttach: attach
31664 }
31665 });
31666 }
31667
31668 return {
31669 attach: attach,
31670 connect: connect
31671 };
31672 }
31673
31674 // ignore wether or not elements can be created
31675 if (create === null || attach === null) {
31676 return null;
31677 }
31678
31679 return false;
31680 }
31681
31682 function setMarker(element, marker) {
31683 [ MARKER_ATTACH, MARKER_OK$2, MARKER_NOT_OK$2, MARKER_NEW_PARENT ].forEach(function(m) {
31684
31685 if (m === marker) {
31686 canvas.addMarker(element, m);
31687 } else {
31688 canvas.removeMarker(element, m);
31689 }
31690 });
31691 }
31692
31693 // event handling //////////
31694
31695 eventBus.on([ 'create.move', 'create.hover' ], function(event) {
31696 var context = event.context,
31697 elements = context.elements,
31698 hover = event.hover,
31699 source = context.source,
31700 hints = context.hints || {};
31701
31702 if (!hover) {
31703 context.canExecute = false;
31704 context.target = null;
31705
31706 return;
31707 }
31708
31709 ensureConstraints(event);
31710
31711 var position = {
31712 x: event.x,
31713 y: event.y
31714 };
31715
31716 var canExecute = context.canExecute = hover && canCreate(elements, hover, position, source, hints);
31717
31718 if (hover && canExecute !== null) {
31719 context.target = hover;
31720
31721 if (canExecute && canExecute.attach) {
31722 setMarker(hover, MARKER_ATTACH);
31723 } else {
31724 setMarker(hover, canExecute ? MARKER_NEW_PARENT : MARKER_NOT_OK$2);
31725 }
31726 }
31727 });
31728
31729 eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) {
31730 var hover = event.hover;
31731
31732 if (hover) {
31733 setMarker(hover, null);
31734 }
31735 });
31736
31737 eventBus.on('create.end', function(event) {
31738 var context = event.context,
31739 source = context.source,
31740 shape = context.shape,
31741 elements = context.elements,
31742 target = context.target,
31743 canExecute = context.canExecute,
31744 attach = canExecute && canExecute.attach,
31745 connect = canExecute && canExecute.connect,
31746 hints = context.hints || {};
31747
31748 if (canExecute === false || !target) {
31749 return false;
31750 }
31751
31752 ensureConstraints(event);
31753
31754 var position = {
31755 x: event.x,
31756 y: event.y
31757 };
31758
31759 if (connect) {
31760 shape = modeling.appendShape(source, shape, position, target, {
31761 attach: attach,
31762 connection: connect === true ? {} : connect,
31763 connectionTarget: hints.connectionTarget
31764 });
31765 } else {
31766 elements = modeling.createElements(elements, position, target, assign({}, hints, {
31767 attach: attach
31768 }));
31769
31770 // update shape
31771 shape = find(elements, function(element) {
31772 return !isConnection(element);
31773 });
31774 }
31775
31776 // update elements and shape
31777 assign(context, {
31778 elements: elements,
31779 shape: shape
31780 });
31781
31782 assign(event, {
31783 elements: elements,
31784 shape: shape
31785 });
31786 });
31787
31788 function cancel() {
31789 var context = dragging.context();
31790
31791 if (context && context.prefix === PREFIX) {
31792 dragging.cancel();
31793 }
31794 }
31795
31796 // cancel on <elements.changed> that is not result of <drag.end>
31797 eventBus.on('create.init', function() {
31798 eventBus.on('elements.changed', cancel);
31799
31800 eventBus.once([ 'create.cancel', 'create.end' ], HIGH_PRIORITY$3, function() {
31801 eventBus.off('elements.changed', cancel);
31802 });
31803 });
31804
31805 // API //////////
31806
31807 this.start = function(event, elements, context) {
31808 if (!isArray(elements)) {
31809 elements = [ elements ];
31810 }
31811
31812 var shape = find(elements, function(element) {
31813 return !isConnection(element);
31814 });
31815
31816 if (!shape) {
31817
31818 // at least one shape is required
31819 return;
31820 }
31821
31822 context = assign({
31823 elements: elements,
31824 hints: {},
31825 shape: shape
31826 }, context || {});
31827
31828 // make sure each element has x and y
31829 forEach(elements, function(element) {
31830 if (!isNumber(element.x)) {
31831 element.x = 0;
31832 }
31833
31834 if (!isNumber(element.y)) {
31835 element.y = 0;
31836 }
31837 });
31838
31839 var bbox = getBBox(elements);
31840
31841 // center elements around cursor
31842 forEach(elements, function(element) {
31843 if (isConnection(element)) {
31844 element.waypoints = map(element.waypoints, function(waypoint) {
31845 return {
31846 x: waypoint.x - bbox.x - bbox.width / 2,
31847 y: waypoint.y - bbox.y - bbox.height / 2
31848 };
31849 });
31850 }
31851
31852 assign(element, {
31853 x: element.x - bbox.x - bbox.width / 2,
31854 y: element.y - bbox.y - bbox.height / 2
31855 });
31856 });
31857
31858 dragging.init(event, PREFIX, {
31859 cursor: 'grabbing',
31860 autoActivate: true,
31861 data: {
31862 shape: shape,
31863 elements: elements,
31864 context: context
31865 }
31866 });
31867 };
31868 }
31869
31870 Create.$inject = [
31871 'canvas',
31872 'dragging',
31873 'eventBus',
31874 'modeling',
31875 'rules'
31876 ];
31877
31878 // helpers //////////
31879
31880 function ensureConstraints(event) {
31881 var context = event.context,
31882 createConstraints = context.createConstraints;
31883
31884 if (!createConstraints) {
31885 return;
31886 }
31887
31888 if (createConstraints.left) {
31889 event.x = Math.max(event.x, createConstraints.left);
31890 }
31891
31892 if (createConstraints.right) {
31893 event.x = Math.min(event.x, createConstraints.right);
31894 }
31895
31896 if (createConstraints.top) {
31897 event.y = Math.max(event.y, createConstraints.top);
31898 }
31899
31900 if (createConstraints.bottom) {
31901 event.y = Math.min(event.y, createConstraints.bottom);
31902 }
31903 }
31904
31905 function isConnection(element) {
31906 return !!element.waypoints;
31907 }
31908
31909 function isSingleShape(elements) {
31910 return elements && elements.length === 1 && !isConnection(elements[0]);
31911 }
31912
31913 function isLabel$1(element) {
31914 return !!element.labelTarget;
31915 }
31916
31917 var LOW_PRIORITY$6 = 750;
31918
31919
31920 function CreatePreview(
31921 canvas,
31922 eventBus,
31923 graphicsFactory,
31924 previewSupport,
31925 styles
31926 ) {
31927 function createDragGroup(elements) {
31928 var dragGroup = create('g');
31929
31930 attr$1(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
31931
31932 var childrenGfx = create('g');
31933
31934 elements.forEach(function(element) {
31935
31936 // create graphics
31937 var gfx;
31938
31939 if (element.hidden) {
31940 return;
31941 }
31942
31943 if (element.waypoints) {
31944 gfx = graphicsFactory._createContainer('connection', childrenGfx);
31945
31946 graphicsFactory.drawConnection(getVisual(gfx), element);
31947 } else {
31948 gfx = graphicsFactory._createContainer('shape', childrenGfx);
31949
31950 graphicsFactory.drawShape(getVisual(gfx), element);
31951
31952 translate(gfx, element.x, element.y);
31953 }
31954
31955 // add preview
31956 previewSupport.addDragger(element, dragGroup, gfx);
31957 });
31958
31959 return dragGroup;
31960 }
31961
31962 eventBus.on('create.move', LOW_PRIORITY$6, function(event) {
31963
31964 var hover = event.hover,
31965 context = event.context,
31966 elements = context.elements,
31967 dragGroup = context.dragGroup;
31968
31969 // lazily create previews
31970 if (!dragGroup) {
31971 dragGroup = context.dragGroup = createDragGroup(elements);
31972 }
31973
31974 var defaultLayer;
31975
31976 if (hover) {
31977 if (!dragGroup.parentNode) {
31978 defaultLayer = canvas.getDefaultLayer();
31979
31980 append(defaultLayer, dragGroup);
31981 }
31982
31983 translate(dragGroup, event.x, event.y);
31984 } else {
31985 remove$1(dragGroup);
31986 }
31987 });
31988
31989 eventBus.on('create.cleanup', function(event) {
31990 var context = event.context,
31991 dragGroup = context.dragGroup;
31992
31993 if (dragGroup) {
31994 remove$1(dragGroup);
31995 }
31996 });
31997 }
31998
31999 CreatePreview.$inject = [
32000 'canvas',
32001 'eventBus',
32002 'graphicsFactory',
32003 'previewSupport',
32004 'styles'
32005 ];
32006
32007 var CreateModule = {
32008 __depends__: [
32009 DraggingModule,
32010 PreviewSupportModule,
32011 RulesModule,
32012 SelectionModule
32013 ],
32014 __init__: [
32015 'create',
32016 'createPreview'
32017 ],
32018 create: [ 'type', Create ],
32019 createPreview: [ 'type', CreatePreview ]
32020 };
32021
32022 var DATA_REF = 'data-id';
32023
32024 var CLOSE_EVENTS = [
32025 'contextPad.close',
32026 'canvas.viewbox.changing',
32027 'commandStack.changed'
32028 ];
32029
32030 var DEFAULT_PRIORITY$4 = 1000;
32031
32032
32033 /**
32034 * A popup menu that can be used to display a list of actions anywhere in the canvas.
32035 *
32036 * @param {Object} config
32037 * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
32038 * @param {number} [config.scale.min]
32039 * @param {number} [config.scale.max]
32040 * @param {EventBus} eventBus
32041 * @param {Canvas} canvas
32042 *
32043 * @class
32044 * @constructor
32045 */
32046 function PopupMenu(config, eventBus, canvas) {
32047
32048 var scale = isDefined(config && config.scale) ? config.scale : {
32049 min: 1,
32050 max: 1.5
32051 };
32052
32053 this._config = {
32054 scale: scale
32055 };
32056
32057 this._eventBus = eventBus;
32058 this._canvas = canvas;
32059 this._providers = {};
32060 this._current = {};
32061 }
32062
32063 PopupMenu.$inject = [
32064 'config.popupMenu',
32065 'eventBus',
32066 'canvas'
32067 ];
32068
32069 /**
32070 * Registers a popup menu provider
32071 *
32072 * @param {string} id
32073 * @param {number} [priority=1000]
32074 * @param {Object} provider
32075 *
32076 * @example
32077 * const popupMenuProvider = {
32078 * getPopupMenuEntries: function(element) {
32079 * return {
32080 * 'entry-1': {
32081 * label: 'My Entry',
32082 * action: function() { alert("I have been clicked!"); }
32083 * }
32084 * }
32085 * }
32086 * };
32087 *
32088 * popupMenu.registerProvider('myMenuID', popupMenuProvider);
32089 */
32090 PopupMenu.prototype.registerProvider = function(id, priority, provider) {
32091 if (!provider) {
32092 provider = priority;
32093 priority = DEFAULT_PRIORITY$4;
32094 }
32095
32096 this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
32097 event.providers.push(provider);
32098 });
32099 };
32100
32101 /**
32102 * Determine if the popup menu has entries.
32103 *
32104 * @return {boolean} true if empty
32105 */
32106 PopupMenu.prototype.isEmpty = function(element, providerId) {
32107 if (!element) {
32108 throw new Error('element parameter is missing');
32109 }
32110
32111 if (!providerId) {
32112 throw new Error('providerId parameter is missing');
32113 }
32114
32115 var providers = this._getProviders(providerId);
32116
32117 if (!providers) {
32118 return true;
32119 }
32120
32121 var entries = this._getEntries(element, providers),
32122 headerEntries = this._getHeaderEntries(element, providers);
32123
32124 var hasEntries = size(entries) > 0,
32125 hasHeaderEntries = headerEntries && size(headerEntries) > 0;
32126
32127 return !hasEntries && !hasHeaderEntries;
32128 };
32129
32130
32131 /**
32132 * Create entries and open popup menu at given position
32133 *
32134 * @param {Object} element
32135 * @param {string} id provider id
32136 * @param {Object} position
32137 *
32138 * @return {Object} popup menu instance
32139 */
32140 PopupMenu.prototype.open = function(element, id, position) {
32141
32142 var providers = this._getProviders(id);
32143
32144 if (!element) {
32145 throw new Error('Element is missing');
32146 }
32147
32148 if (!providers || !providers.length) {
32149 throw new Error('No registered providers for: ' + id);
32150 }
32151
32152 if (!position) {
32153 throw new Error('the position argument is missing');
32154 }
32155
32156 if (this.isOpen()) {
32157 this.close();
32158 }
32159
32160 this._emit('open');
32161
32162 var current = this._current = {
32163 className: id,
32164 element: element,
32165 position: position
32166 };
32167
32168 var entries = this._getEntries(element, providers),
32169 headerEntries = this._getHeaderEntries(element, providers);
32170
32171 current.entries = assign({}, entries, headerEntries);
32172
32173 current.container = this._createContainer();
32174
32175 if (size(headerEntries)) {
32176 current.container.appendChild(
32177 this._createEntries(headerEntries, 'djs-popup-header')
32178 );
32179 }
32180
32181 if (size(entries)) {
32182 current.container.appendChild(
32183 this._createEntries(entries, 'djs-popup-body')
32184 );
32185 }
32186
32187 var canvas = this._canvas,
32188 parent = canvas.getContainer();
32189
32190 this._attachContainer(current.container, parent, position.cursor);
32191 this._bindAutoClose();
32192 };
32193
32194
32195 /**
32196 * Removes the popup menu and unbinds the event handlers.
32197 */
32198 PopupMenu.prototype.close = function() {
32199
32200 if (!this.isOpen()) {
32201 return;
32202 }
32203
32204 this._emit('close');
32205
32206 this._unbindAutoClose();
32207 remove(this._current.container);
32208 this._current.container = null;
32209 };
32210
32211
32212 /**
32213 * Determine if an open popup menu exist.
32214 *
32215 * @return {boolean} true if open
32216 */
32217 PopupMenu.prototype.isOpen = function() {
32218 return !!this._current.container;
32219 };
32220
32221
32222 /**
32223 * Trigger an action associated with an entry.
32224 *
32225 * @param {Object} event
32226 *
32227 * @return the result of the action callback, if any
32228 */
32229 PopupMenu.prototype.trigger = function(event) {
32230
32231 // silence other actions
32232 event.preventDefault();
32233
32234 var element = event.delegateTarget || event.target,
32235 entryId = attr(element, DATA_REF);
32236
32237 var entry = this._getEntry(entryId);
32238
32239 if (entry.action) {
32240 return entry.action.call(null, event, entry);
32241 }
32242 };
32243
32244 PopupMenu.prototype._getProviders = function(id) {
32245
32246 var event = this._eventBus.createEvent({
32247 type: 'popupMenu.getProviders.' + id,
32248 providers: []
32249 });
32250
32251 this._eventBus.fire(event);
32252
32253 return event.providers;
32254 };
32255
32256 PopupMenu.prototype._getEntries = function(element, providers) {
32257
32258 var entries = {};
32259
32260 forEach(providers, function(provider) {
32261
32262 // handle legacy method
32263 if (!provider.getPopupMenuEntries) {
32264 forEach(provider.getEntries(element), function(entry) {
32265 var id = entry.id;
32266
32267 if (!id) {
32268 throw new Error('every entry must have the id property set');
32269 }
32270
32271 entries[id] = omit(entry, [ 'id' ]);
32272 });
32273
32274 return;
32275 }
32276
32277 var entriesOrUpdater = provider.getPopupMenuEntries(element);
32278
32279 if (isFunction(entriesOrUpdater)) {
32280 entries = entriesOrUpdater(entries);
32281 } else {
32282 forEach(entriesOrUpdater, function(entry, id) {
32283 entries[id] = entry;
32284 });
32285 }
32286 });
32287
32288 return entries;
32289 };
32290
32291 PopupMenu.prototype._getHeaderEntries = function(element, providers) {
32292
32293 var entries = {};
32294
32295 forEach(providers, function(provider) {
32296
32297 // handle legacy method
32298 if (!provider.getPopupMenuHeaderEntries) {
32299 if (!provider.getHeaderEntries) {
32300 return;
32301 }
32302
32303 forEach(provider.getHeaderEntries(element), function(entry) {
32304 var id = entry.id;
32305
32306 if (!id) {
32307 throw new Error('every entry must have the id property set');
32308 }
32309
32310 entries[id] = omit(entry, [ 'id' ]);
32311 });
32312
32313 return;
32314 }
32315
32316 var entriesOrUpdater = provider.getPopupMenuHeaderEntries(element);
32317
32318 if (isFunction(entriesOrUpdater)) {
32319 entries = entriesOrUpdater(entries);
32320 } else {
32321 forEach(entriesOrUpdater, function(entry, id) {
32322 entries[id] = entry;
32323 });
32324 }
32325 });
32326
32327 return entries;
32328
32329
32330 };
32331
32332 /**
32333 * Gets an entry instance (either entry or headerEntry) by id.
32334 *
32335 * @param {string} entryId
32336 *
32337 * @return {Object} entry instance
32338 */
32339 PopupMenu.prototype._getEntry = function(entryId) {
32340
32341 var entry = this._current.entries[entryId];
32342
32343 if (!entry) {
32344 throw new Error('entry not found');
32345 }
32346
32347 return entry;
32348 };
32349
32350 PopupMenu.prototype._emit = function(eventName) {
32351 this._eventBus.fire('popupMenu.' + eventName);
32352 };
32353
32354 /**
32355 * Creates the popup menu container.
32356 *
32357 * @return {Object} a DOM container
32358 */
32359 PopupMenu.prototype._createContainer = function() {
32360 var container = domify('<div class="djs-popup">'),
32361 position = this._current.position,
32362 className = this._current.className;
32363
32364 assign(container.style, {
32365 position: 'absolute',
32366 left: position.x + 'px',
32367 top: position.y + 'px',
32368 visibility: 'hidden'
32369 });
32370
32371 classes(container).add(className);
32372
32373 return container;
32374 };
32375
32376
32377 /**
32378 * Attaches the container to the DOM.
32379 *
32380 * @param {Object} container
32381 * @param {Object} parent
32382 */
32383 PopupMenu.prototype._attachContainer = function(container, parent, cursor) {
32384 var self = this;
32385
32386 // Event handler
32387 delegate.bind(container, '.entry' ,'click', function(event) {
32388 self.trigger(event);
32389 });
32390
32391 this._updateScale(container);
32392
32393 // Attach to DOM
32394 parent.appendChild(container);
32395
32396 if (cursor) {
32397 this._assureIsInbounds(container, cursor);
32398 }
32399 };
32400
32401
32402 /**
32403 * Updates popup style.transform with respect to the config and zoom level.
32404 *
32405 * @method _updateScale
32406 *
32407 * @param {Object} container
32408 */
32409 PopupMenu.prototype._updateScale = function(container) {
32410 var zoom = this._canvas.zoom();
32411
32412 var scaleConfig = this._config.scale,
32413 minScale,
32414 maxScale,
32415 scale = zoom;
32416
32417 if (scaleConfig !== true) {
32418
32419 if (scaleConfig === false) {
32420 minScale = 1;
32421 maxScale = 1;
32422 } else {
32423 minScale = scaleConfig.min;
32424 maxScale = scaleConfig.max;
32425 }
32426
32427 if (isDefined(minScale) && zoom < minScale) {
32428 scale = minScale;
32429 }
32430
32431 if (isDefined(maxScale) && zoom > maxScale) {
32432 scale = maxScale;
32433 }
32434
32435 }
32436
32437 setTransform$1(container, 'scale(' + scale + ')');
32438 };
32439
32440
32441 /**
32442 * Make sure that the menu is always fully shown
32443 *
32444 * @method function
32445 *
32446 * @param {Object} container
32447 * @param {Position} cursor {x, y}
32448 */
32449 PopupMenu.prototype._assureIsInbounds = function(container, cursor) {
32450 var canvas = this._canvas,
32451 clientRect = canvas._container.getBoundingClientRect();
32452
32453 var containerX = container.offsetLeft,
32454 containerY = container.offsetTop,
32455 containerWidth = container.scrollWidth,
32456 containerHeight = container.scrollHeight,
32457 overAxis = {},
32458 left, top;
32459
32460 var cursorPosition = {
32461 x: cursor.x - clientRect.left,
32462 y: cursor.y - clientRect.top
32463 };
32464
32465 if (containerX + containerWidth > clientRect.width) {
32466 overAxis.x = true;
32467 }
32468
32469 if (containerY + containerHeight > clientRect.height) {
32470 overAxis.y = true;
32471 }
32472
32473 if (overAxis.x && overAxis.y) {
32474 left = cursorPosition.x - containerWidth + 'px';
32475 top = cursorPosition.y - containerHeight + 'px';
32476 } else if (overAxis.x) {
32477 left = cursorPosition.x - containerWidth + 'px';
32478 top = cursorPosition.y + 'px';
32479 } else if (overAxis.y && cursorPosition.y < containerHeight) {
32480 left = cursorPosition.x + 'px';
32481 top = 10 + 'px';
32482 } else if (overAxis.y) {
32483 left = cursorPosition.x + 'px';
32484 top = cursorPosition.y - containerHeight + 'px';
32485 }
32486
32487 assign(container.style, { left: left, top: top }, { visibility: 'visible', 'z-index': 1000 });
32488 };
32489
32490
32491 /**
32492 * Creates a list of entries and returns them as a DOM container.
32493 *
32494 * @param {Array<Object>} entries an array of entry objects
32495 * @param {string} className the class name of the entry container
32496 *
32497 * @return {Object} a DOM container
32498 */
32499 PopupMenu.prototype._createEntries = function(entries, className) {
32500
32501 var entriesContainer = domify('<div>'),
32502 self = this;
32503
32504 classes(entriesContainer).add(className);
32505
32506 forEach(entries, function(entry, id) {
32507 var entryContainer = self._createEntry(entry, id);
32508 entriesContainer.appendChild(entryContainer);
32509 });
32510
32511 return entriesContainer;
32512 };
32513
32514
32515 /**
32516 * Creates a single entry and returns it as a DOM container.
32517 *
32518 * @param {Object} entry
32519 *
32520 * @return {Object} a DOM container
32521 */
32522 PopupMenu.prototype._createEntry = function(entry, id) {
32523
32524 var entryContainer = domify('<div>'),
32525 entryClasses = classes(entryContainer);
32526
32527 entryClasses.add('entry');
32528
32529 if (entry.className) {
32530 entry.className.split(' ').forEach(function(className) {
32531 entryClasses.add(className);
32532 });
32533 }
32534
32535 attr(entryContainer, DATA_REF, id);
32536
32537 if (entry.label) {
32538 var label = domify('<span>');
32539 label.textContent = entry.label;
32540 entryContainer.appendChild(label);
32541 }
32542
32543 if (entry.imageUrl) {
32544 entryContainer.appendChild(domify('<img src="' + entry.imageUrl + '" />'));
32545 }
32546
32547 if (entry.active === true) {
32548 entryClasses.add('active');
32549 }
32550
32551 if (entry.disabled === true) {
32552 entryClasses.add('disabled');
32553 }
32554
32555 if (entry.title) {
32556 entryContainer.title = entry.title;
32557 }
32558
32559 return entryContainer;
32560 };
32561
32562
32563 /**
32564 * Set up listener to close popup automatically on certain events.
32565 */
32566 PopupMenu.prototype._bindAutoClose = function() {
32567 this._eventBus.once(CLOSE_EVENTS, this.close, this);
32568 };
32569
32570
32571 /**
32572 * Remove the auto-closing listener.
32573 */
32574 PopupMenu.prototype._unbindAutoClose = function() {
32575 this._eventBus.off(CLOSE_EVENTS, this.close, this);
32576 };
32577
32578
32579
32580 // helpers /////////////////////////////
32581
32582 function setTransform$1(element, transform) {
32583 element.style['transform-origin'] = 'top left';
32584
32585 [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
32586 element.style[prefix + 'transform'] = transform;
32587 });
32588 }
32589
32590 var PopupMenuModule = {
32591 __init__: [ 'popupMenu' ],
32592 popupMenu: [ 'type', PopupMenu ]
32593 };
32594
32595 /**
32596 * A clip board stub
32597 */
32598 function Clipboard() {}
32599
32600
32601 Clipboard.prototype.get = function() {
32602 return this._data;
32603 };
32604
32605 Clipboard.prototype.set = function(data) {
32606 this._data = data;
32607 };
32608
32609 Clipboard.prototype.clear = function() {
32610 var data = this._data;
32611
32612 delete this._data;
32613
32614 return data;
32615 };
32616
32617 Clipboard.prototype.isEmpty = function() {
32618 return !this._data;
32619 };
32620
32621 var ClipboardModule = {
32622 clipboard: [ 'type', Clipboard ]
32623 };
32624
32625 function Mouse(eventBus) {
32626 var self = this;
32627
32628 this._lastMoveEvent = null;
32629
32630 function setLastMoveEvent(mousemoveEvent) {
32631 self._lastMoveEvent = mousemoveEvent;
32632 }
32633
32634 eventBus.on('canvas.init', function(context) {
32635 var svg = self._svg = context.svg;
32636
32637 svg.addEventListener('mousemove', setLastMoveEvent);
32638 });
32639
32640 eventBus.on('canvas.destroy', function() {
32641 self._lastMouseEvent = null;
32642
32643 self._svg.removeEventListener('mousemove', setLastMoveEvent);
32644 });
32645 }
32646
32647 Mouse.$inject = [ 'eventBus' ];
32648
32649 Mouse.prototype.getLastMoveEvent = function() {
32650 return this._lastMoveEvent || createMoveEvent(0, 0);
32651 };
32652
32653 // helpers //////////
32654
32655 function createMoveEvent(x, y) {
32656 var event = document.createEvent('MouseEvent');
32657
32658 var screenX = x,
32659 screenY = y,
32660 clientX = x,
32661 clientY = y;
32662
32663 if (event.initMouseEvent) {
32664 event.initMouseEvent(
32665 'mousemove',
32666 true,
32667 true,
32668 window,
32669 0,
32670 screenX,
32671 screenY,
32672 clientX,
32673 clientY,
32674 false,
32675 false,
32676 false,
32677 false,
32678 0,
32679 null
32680 );
32681 }
32682
32683 return event;
32684 }
32685
32686 var MouseModule = {
32687 __init__: [ 'mouse' ],
32688 mouse: [ 'type', Mouse ]
32689 };
32690
32691 /**
32692 * @typedef {Function} <copyPaste.canCopyElements> listener
32693 *
32694 * @param {Object} context
32695 * @param {Array<djs.model.Base>} context.elements
32696 *
32697 * @returns {Array<djs.model.Base>|boolean} - Return elements to be copied or false to disallow
32698 * copying.
32699 */
32700
32701 /**
32702 * @typedef {Function} <copyPaste.copyElement> listener
32703 *
32704 * @param {Object} context
32705 * @param {Object} context.descriptor
32706 * @param {djs.model.Base} context.element
32707 * @param {Array<djs.model.Base>} context.elements
32708 */
32709
32710 /**
32711 * @typedef {Function} <copyPaste.elementsCopied> listener
32712 *
32713 * @param {Object} context
32714 * @param {Object} context.elements
32715 * @param {Object} context.tree
32716 */
32717
32718 /**
32719 * @typedef {Function} <copyPaste.pasteElement> listener
32720 *
32721 * @param {Object} context
32722 * @param {Object} context.cache - Already created elements.
32723 * @param {Object} context.descriptor
32724 */
32725
32726 /**
32727 * @typedef {Function} <copyPaste.pasteElements> listener
32728 *
32729 * @param {Object} context
32730 * @param {Object} context.hints - Add hints before pasting.
32731 */
32732
32733 /**
32734 * Copy and paste elements.
32735 *
32736 * @param {Canvas} canvas
32737 * @param {Create} create
32738 * @param {Clipboard} clipboard
32739 * @param {ElementFactory} elementFactory
32740 * @param {EventBus} eventBus
32741 * @param {Modeling} modeling
32742 * @param {Mouse} mouse
32743 * @param {Rules} rules
32744 */
32745 function CopyPaste(
32746 canvas,
32747 create,
32748 clipboard,
32749 elementFactory,
32750 eventBus,
32751 modeling,
32752 mouse,
32753 rules
32754 ) {
32755
32756 this._canvas = canvas;
32757 this._create = create;
32758 this._clipboard = clipboard;
32759 this._elementFactory = elementFactory;
32760 this._eventBus = eventBus;
32761 this._modeling = modeling;
32762 this._mouse = mouse;
32763 this._rules = rules;
32764
32765 eventBus.on('copyPaste.copyElement', function(context) {
32766 var descriptor = context.descriptor,
32767 element = context.element,
32768 elements = context.elements;
32769
32770 // default priority (priority = 1)
32771 descriptor.priority = 1;
32772
32773 descriptor.id = element.id;
32774
32775 var parentCopied = find(elements, function(e) {
32776 return e === element.parent;
32777 });
32778
32779 // do NOT reference parent if parent wasn't copied
32780 if (parentCopied) {
32781 descriptor.parent = element.parent.id;
32782 }
32783
32784 // attachers (priority = 2)
32785 if (isAttacher(element)) {
32786 descriptor.priority = 2;
32787
32788 descriptor.host = element.host.id;
32789 }
32790
32791 // connections (priority = 3)
32792 if (isConnection$1(element)) {
32793 descriptor.priority = 3;
32794
32795 descriptor.source = element.source.id;
32796 descriptor.target = element.target.id;
32797
32798 descriptor.waypoints = copyWaypoints(element);
32799 }
32800
32801 // labels (priority = 4)
32802 if (isLabel$2(element)) {
32803 descriptor.priority = 4;
32804
32805 descriptor.labelTarget = element.labelTarget.id;
32806 }
32807
32808 forEach([ 'x', 'y', 'width', 'height' ], function(property) {
32809 if (isNumber(element[ property ])) {
32810 descriptor[ property ] = element[ property ];
32811 }
32812 });
32813
32814 descriptor.hidden = element.hidden;
32815 descriptor.collapsed = element.collapsed;
32816
32817 });
32818
32819 eventBus.on('copyPaste.pasteElements', function(context) {
32820 var hints = context.hints;
32821
32822 assign(hints, {
32823 createElementsBehavior: false
32824 });
32825 });
32826 }
32827
32828 CopyPaste.$inject = [
32829 'canvas',
32830 'create',
32831 'clipboard',
32832 'elementFactory',
32833 'eventBus',
32834 'modeling',
32835 'mouse',
32836 'rules'
32837 ];
32838
32839
32840 /**
32841 * Copy elements.
32842 *
32843 * @param {Array<djs.model.Base>} elements
32844 *
32845 * @returns {Object}
32846 */
32847 CopyPaste.prototype.copy = function(elements) {
32848 var allowed,
32849 tree;
32850
32851 if (!isArray(elements)) {
32852 elements = elements ? [ elements ] : [];
32853 }
32854
32855 allowed = this._eventBus.fire('copyPaste.canCopyElements', {
32856 elements: elements
32857 });
32858
32859 if (allowed === false) {
32860 tree = {};
32861 } else {
32862 tree = this.createTree(isArray(allowed) ? allowed : elements);
32863 }
32864
32865 // we set an empty tree, selection of elements
32866 // to copy was empty.
32867 this._clipboard.set(tree);
32868
32869 this._eventBus.fire('copyPaste.elementsCopied', {
32870 elements: elements,
32871 tree: tree
32872 });
32873
32874 return tree;
32875 };
32876
32877 /**
32878 * Paste elements.
32879 *
32880 * @param {Object} [context]
32881 * @param {djs.model.base} [context.element] - Parent.
32882 * @param {Point} [context.point] - Position.
32883 * @param {Object} [context.hints] - Hints.
32884 */
32885 CopyPaste.prototype.paste = function(context) {
32886 var tree = this._clipboard.get();
32887
32888 if (this._clipboard.isEmpty()) {
32889 return;
32890 }
32891
32892 var hints = context && context.hints || {};
32893
32894 this._eventBus.fire('copyPaste.pasteElements', {
32895 hints: hints
32896 });
32897
32898 var elements = this._createElements(tree);
32899
32900 // paste directly
32901 if (context && context.element && context.point) {
32902 return this._paste(elements, context.element, context.point, hints);
32903 }
32904
32905 this._create.start(this._mouse.getLastMoveEvent(), elements, {
32906 hints: hints || {}
32907 });
32908 };
32909
32910 /**
32911 * Paste elements directly.
32912 *
32913 * @param {Array<djs.model.Base>} elements
32914 * @param {djs.model.base} target
32915 * @param {Point} position
32916 * @param {Object} [hints]
32917 */
32918 CopyPaste.prototype._paste = function(elements, target, position, hints) {
32919
32920 // make sure each element has x and y
32921 forEach(elements, function(element) {
32922 if (!isNumber(element.x)) {
32923 element.x = 0;
32924 }
32925
32926 if (!isNumber(element.y)) {
32927 element.y = 0;
32928 }
32929 });
32930
32931 var bbox = getBBox(elements);
32932
32933 // center elements around cursor
32934 forEach(elements, function(element) {
32935 if (isConnection$1(element)) {
32936 element.waypoints = map(element.waypoints, function(waypoint) {
32937 return {
32938 x: waypoint.x - bbox.x - bbox.width / 2,
32939 y: waypoint.y - bbox.y - bbox.height / 2
32940 };
32941 });
32942 }
32943
32944 assign(element, {
32945 x: element.x - bbox.x - bbox.width / 2,
32946 y: element.y - bbox.y - bbox.height / 2
32947 });
32948 });
32949
32950 return this._modeling.createElements(elements, position, target, assign({}, hints));
32951 };
32952
32953 /**
32954 * Create elements from tree.
32955 */
32956 CopyPaste.prototype._createElements = function(tree) {
32957 var self = this;
32958
32959 var eventBus = this._eventBus;
32960
32961 var cache = {};
32962
32963 var elements = [];
32964
32965 forEach(tree, function(branch, depth) {
32966
32967 // sort by priority
32968 branch = sortBy(branch, 'priority');
32969
32970 forEach(branch, function(descriptor) {
32971
32972 // remove priority
32973 var attrs = assign({}, omit(descriptor, [ 'priority' ]));
32974
32975 if (cache[ descriptor.parent ]) {
32976 attrs.parent = cache[ descriptor.parent ];
32977 } else {
32978 delete attrs.parent;
32979 }
32980
32981 eventBus.fire('copyPaste.pasteElement', {
32982 cache: cache,
32983 descriptor: attrs
32984 });
32985
32986 var element;
32987
32988 if (isConnection$1(attrs)) {
32989 attrs.source = cache[ descriptor.source ];
32990 attrs.target = cache[ descriptor.target ];
32991
32992 element = cache[ descriptor.id ] = self.createConnection(attrs);
32993
32994 elements.push(element);
32995
32996 return;
32997 }
32998
32999 if (isLabel$2(attrs)) {
33000 attrs.labelTarget = cache[ attrs.labelTarget ];
33001
33002 element = cache[ descriptor.id ] = self.createLabel(attrs);
33003
33004 elements.push(element);
33005
33006 return;
33007 }
33008
33009 if (attrs.host) {
33010 attrs.host = cache[ attrs.host ];
33011 }
33012
33013 element = cache[ descriptor.id ] = self.createShape(attrs);
33014
33015 elements.push(element);
33016 });
33017
33018 });
33019
33020 return elements;
33021 };
33022
33023 CopyPaste.prototype.createConnection = function(attrs) {
33024 var connection = this._elementFactory.createConnection(omit(attrs, [ 'id' ]));
33025
33026 return connection;
33027 };
33028
33029 CopyPaste.prototype.createLabel = function(attrs) {
33030 var label = this._elementFactory.createLabel(omit(attrs, [ 'id' ]));
33031
33032 return label;
33033 };
33034
33035 CopyPaste.prototype.createShape = function(attrs) {
33036 var shape = this._elementFactory.createShape(omit(attrs, [ 'id' ]));
33037
33038 return shape;
33039 };
33040
33041 /**
33042 * Check wether element has relations to other elements e.g. attachers, labels and connections.
33043 *
33044 * @param {Object} element
33045 * @param {Array<djs.model.Base>} elements
33046 *
33047 * @returns {boolean}
33048 */
33049 CopyPaste.prototype.hasRelations = function(element, elements) {
33050 var labelTarget,
33051 source,
33052 target;
33053
33054 if (isConnection$1(element)) {
33055 source = find(elements, matchPattern({ id: element.source.id }));
33056 target = find(elements, matchPattern({ id: element.target.id }));
33057
33058 if (!source || !target) {
33059 return false;
33060 }
33061 }
33062
33063 if (isLabel$2(element)) {
33064 labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));
33065
33066 if (!labelTarget) {
33067 return false;
33068 }
33069 }
33070
33071 return true;
33072 };
33073
33074 /**
33075 * Create a tree-like structure from elements.
33076 *
33077 * @example
33078 * tree: {
33079 * 0: [
33080 * { id: 'Shape_1', priority: 1, ... },
33081 * { id: 'Shape_2', priority: 1, ... },
33082 * { id: 'Connection_1', source: 'Shape_1', target: 'Shape_2', priority: 3, ... },
33083 * ...
33084 * ],
33085 * 1: [
33086 * { id: 'Shape_3', parent: 'Shape1', priority: 1, ... },
33087 * ...
33088 * ]
33089 * };
33090 *
33091 * @param {Array<djs.model.base>} elements
33092 *
33093 * @return {Object}
33094 */
33095 CopyPaste.prototype.createTree = function(elements) {
33096 var rules = this._rules,
33097 self = this;
33098
33099 var tree = {},
33100 elementsData = [];
33101
33102 var parents = getParents(elements);
33103
33104 function canCopy(element, elements) {
33105 return rules.allowed('element.copy', {
33106 element: element,
33107 elements: elements
33108 });
33109 }
33110
33111 function addElementData(element, depth) {
33112
33113 // (1) check wether element has already been added
33114 var foundElementData = find(elementsData, function(elementsData) {
33115 return element === elementsData.element;
33116 });
33117
33118 // (2) add element if not already added
33119 if (!foundElementData) {
33120 elementsData.push({
33121 element: element,
33122 depth: depth
33123 });
33124
33125 return;
33126 }
33127
33128 // (3) update depth
33129 if (foundElementData.depth < depth) {
33130 elementsData = removeElementData(foundElementData, elementsData);
33131
33132 elementsData.push({
33133 element: foundElementData.element,
33134 depth: depth
33135 });
33136 }
33137 }
33138
33139 function removeElementData(elementData, elementsData) {
33140 var index = elementsData.indexOf(elementData);
33141
33142 if (index !== -1) {
33143 elementsData.splice(index, 1);
33144 }
33145
33146 return elementsData;
33147 }
33148
33149 // (1) add elements
33150 eachElement(parents, function(element, _index, depth) {
33151
33152 // do NOT add external labels directly
33153 if (isLabel$2(element)) {
33154 return;
33155 }
33156
33157 // always copy external labels
33158 forEach(element.labels, function(label) {
33159 addElementData(label, depth);
33160 });
33161
33162 function addRelatedElements(elements) {
33163 elements && elements.length && forEach(elements, function(element) {
33164
33165 // add external labels
33166 forEach(element.labels, function(label) {
33167 addElementData(label, depth);
33168 });
33169
33170 addElementData(element, depth);
33171 });
33172 }
33173
33174 forEach([ element.attachers, element.incoming, element.outgoing ], addRelatedElements);
33175
33176 addElementData(element, depth);
33177
33178 return element.children;
33179 });
33180
33181 elements = map(elementsData, function(elementData) {
33182 return elementData.element;
33183 });
33184
33185 // (2) copy elements
33186 elementsData = map(elementsData, function(elementData) {
33187 elementData.descriptor = {};
33188
33189 self._eventBus.fire('copyPaste.copyElement', {
33190 descriptor: elementData.descriptor,
33191 element: elementData.element,
33192 elements: elements
33193 });
33194
33195 return elementData;
33196 });
33197
33198 // (3) sort elements by priority
33199 elementsData = sortBy(elementsData, function(elementData) {
33200 return elementData.descriptor.priority;
33201 });
33202
33203 elements = map(elementsData, function(elementData) {
33204 return elementData.element;
33205 });
33206
33207 // (4) create tree
33208 forEach(elementsData, function(elementData) {
33209 var depth = elementData.depth;
33210
33211 if (!self.hasRelations(elementData.element, elements)) {
33212 removeElement(elementData.element, elements);
33213
33214 return;
33215 }
33216
33217 if (!canCopy(elementData.element, elements)) {
33218 removeElement(elementData.element, elements);
33219
33220 return;
33221 }
33222
33223 if (!tree[depth]) {
33224 tree[depth] = [];
33225 }
33226
33227 tree[depth].push(elementData.descriptor);
33228 });
33229
33230 return tree;
33231 };
33232
33233 // helpers //////////
33234
33235 function isAttacher(element) {
33236 return !!element.host;
33237 }
33238
33239 function isConnection$1(element) {
33240 return !!element.waypoints;
33241 }
33242
33243 function isLabel$2(element) {
33244 return !!element.labelTarget;
33245 }
33246
33247 function copyWaypoints(element) {
33248 return map(element.waypoints, function(waypoint) {
33249
33250 waypoint = copyWaypoint(waypoint);
33251
33252 if (waypoint.original) {
33253 waypoint.original = copyWaypoint(waypoint.original);
33254 }
33255
33256 return waypoint;
33257 });
33258 }
33259
33260 function copyWaypoint(waypoint) {
33261 return assign({}, waypoint);
33262 }
33263
33264 function removeElement(element, elements) {
33265 var index = elements.indexOf(element);
33266
33267 if (index === -1) {
33268 return elements;
33269 }
33270
33271 return elements.splice(index, 1);
33272 }
33273
33274 var CopyPasteModule = {
33275 __depends__: [
33276 ClipboardModule,
33277 CreateModule,
33278 MouseModule,
33279 RulesModule
33280 ],
33281 __init__: [ 'copyPaste' ],
33282 copyPaste: [ 'type', CopyPaste ]
33283 };
33284
33285 function copyProperties(source, target, properties) {
33286 if (!isArray(properties)) {
33287 properties = [ properties ];
33288 }
33289
33290 forEach(properties, function(property) {
33291 if (!isUndefined(source[property])) {
33292 target[property] = source[property];
33293 }
33294 });
33295 }
33296
33297 function removeProperties(element, properties) {
33298 if (!isArray(properties)) {
33299 properties = [ properties ];
33300 }
33301
33302 forEach(properties, function(property) {
33303 if (element[property]) {
33304 delete element[property];
33305 }
33306 });
33307 }
33308
33309 var LOW_PRIORITY$7 = 750;
33310
33311
33312 function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {
33313
33314 eventBus.on('copyPaste.copyElement', LOW_PRIORITY$7, function(context) {
33315 var descriptor = context.descriptor,
33316 element = context.element;
33317
33318 var businessObject = descriptor.oldBusinessObject = getBusinessObject(element);
33319
33320 descriptor.type = element.type;
33321
33322 copyProperties(businessObject, descriptor, 'name');
33323
33324 descriptor.di = {};
33325
33326 // colors will be set to DI
33327 copyProperties(businessObject.di, descriptor.di, [
33328 'fill',
33329 'stroke',
33330 'background-color',
33331 'border-color',
33332 'color'
33333 ]);
33334
33335 copyProperties(businessObject.di, descriptor, 'isExpanded');
33336
33337 if (isLabel$3(descriptor)) {
33338 return descriptor;
33339 }
33340
33341 // default sequence flow
33342 if (businessObject.default) {
33343 descriptor.default = businessObject.default.id;
33344 }
33345 });
33346
33347 eventBus.on('moddleCopy.canCopyProperty', function(context) {
33348 var parent = context.parent,
33349 property = context.property,
33350 propertyName = context.propertyName,
33351 bpmnProcess;
33352
33353 if (
33354 propertyName === 'processRef' &&
33355 is$1(parent, 'bpmn:Participant') &&
33356 is$1(property, 'bpmn:Process')
33357 ) {
33358 bpmnProcess = bpmnFactory.create('bpmn:Process');
33359
33360 // return copy of process
33361 return moddleCopy.copyElement(property, bpmnProcess);
33362 }
33363 });
33364
33365 var references;
33366
33367 function resolveReferences(descriptor, cache) {
33368 var businessObject = getBusinessObject(descriptor);
33369
33370 // default sequence flows
33371 if (descriptor.default) {
33372
33373 // relationship cannot be resolved immediately
33374 references[ descriptor.default ] = {
33375 element: businessObject,
33376 property: 'default'
33377 };
33378 }
33379
33380 // boundary events
33381 if (descriptor.host) {
33382
33383 // relationship can be resolved immediately
33384 getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]);
33385 }
33386
33387 references = omit(references, reduce(references, function(array, reference, key) {
33388 var element = reference.element,
33389 property = reference.property;
33390
33391 if (key === descriptor.id) {
33392 element[ property ] = businessObject;
33393
33394 array.push(descriptor.id);
33395 }
33396
33397 return array;
33398 }, []));
33399 }
33400
33401 eventBus.on('copyPaste.pasteElements', function() {
33402 references = {};
33403 });
33404
33405 eventBus.on('copyPaste.pasteElement', function(context) {
33406 var cache = context.cache,
33407 descriptor = context.descriptor,
33408 oldBusinessObject = descriptor.oldBusinessObject,
33409 newBusinessObject;
33410
33411 // do NOT copy business object if external label
33412 if (isLabel$3(descriptor)) {
33413 descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
33414
33415 return;
33416 }
33417
33418 newBusinessObject = bpmnFactory.create(oldBusinessObject.$type);
33419
33420 descriptor.businessObject = moddleCopy.copyElement(
33421 oldBusinessObject,
33422 newBusinessObject
33423 );
33424
33425 // resolve references e.g. default sequence flow
33426 resolveReferences(descriptor, cache);
33427
33428 copyProperties(descriptor, newBusinessObject, [
33429 'isExpanded',
33430 'name'
33431 ]);
33432
33433 removeProperties(descriptor, 'oldBusinessObject');
33434 });
33435
33436 }
33437
33438
33439 BpmnCopyPaste.$inject = [
33440 'bpmnFactory',
33441 'eventBus',
33442 'moddleCopy'
33443 ];
33444
33445 // helpers //////////
33446
33447 function isLabel$3(element) {
33448 return !!element.labelTarget;
33449 }
33450
33451 var DISALLOWED_PROPERTIES = [
33452 'artifacts',
33453 'dataInputAssociations',
33454 'dataOutputAssociations',
33455 'default',
33456 'flowElements',
33457 'lanes',
33458 'incoming',
33459 'outgoing'
33460 ];
33461
33462 /**
33463 * @typedef {Function} <moddleCopy.canCopyProperties> listener
33464 *
33465 * @param {Object} context
33466 * @param {Array<string>} context.propertyNames
33467 * @param {ModdleElement} context.sourceElement
33468 * @param {ModdleElement} context.targetElement
33469 *
33470 * @returns {Array<string>|boolean} - Return properties to be copied or false to disallow
33471 * copying.
33472 */
33473
33474 /**
33475 * @typedef {Function} <moddleCopy.canCopyProperty> listener
33476 *
33477 * @param {Object} context
33478 * @param {ModdleElement} context.parent
33479 * @param {*} context.property
33480 * @param {string} context.propertyName
33481 *
33482 * @returns {*|boolean} - Return copied property or false to disallow
33483 * copying.
33484 */
33485
33486 /**
33487 * @typedef {Function} <moddleCopy.canSetCopiedProperty> listener
33488 *
33489 * @param {Object} context
33490 * @param {ModdleElement} context.parent
33491 * @param {*} context.property
33492 * @param {string} context.propertyName
33493 *
33494 * @returns {boolean} - Return false to disallow
33495 * setting copied property.
33496 */
33497
33498 /**
33499 * Utility for copying model properties from source element to target element.
33500 *
33501 * @param {EventBus} eventBus
33502 * @param {BpmnFactory} bpmnFactory
33503 * @param {BpmnModdle} moddle
33504 */
33505 function ModdleCopy(eventBus, bpmnFactory, moddle) {
33506 this._bpmnFactory = bpmnFactory;
33507 this._eventBus = eventBus;
33508 this._moddle = moddle;
33509
33510 // copy extension elements last
33511 eventBus.on('moddleCopy.canCopyProperties', function(context) {
33512 var propertyNames = context.propertyNames;
33513
33514 if (!propertyNames || !propertyNames.length) {
33515 return;
33516 }
33517
33518 return sortBy(propertyNames, function(propertyName) {
33519 return propertyName === 'extensionElements';
33520 });
33521 });
33522
33523 // default check whether property can be copied
33524 eventBus.on('moddleCopy.canCopyProperty', function(context) {
33525 var parent = context.parent,
33526 parentDescriptor = isObject(parent) && parent.$descriptor,
33527 propertyName = context.propertyName;
33528
33529 if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) {
33530
33531 // disallow copying property
33532 return false;
33533 }
33534
33535 if (propertyName &&
33536 parentDescriptor &&
33537 !find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {
33538
33539 // disallow copying property
33540 return false;
33541 }
33542 });
33543
33544 // do NOT allow to copy empty extension elements
33545 eventBus.on('moddleCopy.canSetCopiedProperty', function(context) {
33546 var property = context.property;
33547
33548 if (is$2(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {
33549
33550 // disallow setting copied property
33551 return false;
33552 }
33553 });
33554 }
33555
33556 ModdleCopy.$inject = [
33557 'eventBus',
33558 'bpmnFactory',
33559 'moddle'
33560 ];
33561
33562 /**
33563 * Copy model properties of source element to target element.
33564 *
33565 * @param {ModdleElement} sourceElement
33566 * @param {ModdleElement} targetElement
33567 * @param {Array<string>} [propertyNames]
33568 *
33569 * @param {ModdleElement}
33570 */
33571 ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames) {
33572 var self = this;
33573
33574 if (propertyNames && !isArray(propertyNames)) {
33575 propertyNames = [ propertyNames ];
33576 }
33577
33578 propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);
33579
33580 var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
33581 propertyNames: propertyNames,
33582 sourceElement: sourceElement,
33583 targetElement: targetElement
33584 });
33585
33586 if (canCopyProperties === false) {
33587 return targetElement;
33588 }
33589
33590 if (isArray(canCopyProperties)) {
33591 propertyNames = canCopyProperties;
33592 }
33593
33594 // copy properties
33595 forEach(propertyNames, function(propertyName) {
33596 var sourceProperty;
33597
33598 if (has(sourceElement, propertyName)) {
33599 sourceProperty = sourceElement.get(propertyName);
33600 }
33601
33602 var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName);
33603
33604 var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', {
33605 parent: targetElement,
33606 property: copiedProperty,
33607 propertyName: propertyName
33608 });
33609
33610 if (canSetProperty === false) {
33611 return;
33612 }
33613
33614 if (isDefined(copiedProperty)) {
33615 targetElement.set(propertyName, copiedProperty);
33616 }
33617 });
33618
33619 return targetElement;
33620 };
33621
33622 /**
33623 * Copy model property.
33624 *
33625 * @param {*} property
33626 * @param {ModdleElement} parent
33627 * @param {string} propertyName
33628 *
33629 * @returns {*}
33630 */
33631 ModdleCopy.prototype.copyProperty = function(property, parent, propertyName) {
33632 var self = this;
33633
33634 // allow others to copy property
33635 var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
33636 parent: parent,
33637 property: property,
33638 propertyName: propertyName
33639 });
33640
33641 // return if copying is NOT allowed
33642 if (copiedProperty === false) {
33643 return;
33644 }
33645
33646 if (copiedProperty) {
33647 if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
33648 copiedProperty.$parent = parent;
33649 }
33650
33651 return copiedProperty;
33652 }
33653
33654 var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);
33655
33656 // do NOT copy Ids and references
33657 if (propertyDescriptor.isId || propertyDescriptor.isReference) {
33658 return;
33659 }
33660
33661 // copy arrays
33662 if (isArray(property)) {
33663 return reduce(property, function(childProperties, childProperty) {
33664
33665 // recursion
33666 copiedProperty = self.copyProperty(childProperty, parent, propertyName);
33667
33668 // copying might NOT be allowed
33669 if (copiedProperty) {
33670 copiedProperty.$parent = parent;
33671
33672 return childProperties.concat(copiedProperty);
33673 }
33674
33675 return childProperties;
33676 }, []);
33677 }
33678
33679 // copy model elements
33680 if (isObject(property) && property.$type) {
33681 if (this._moddle.getElementDescriptor(property).isGeneric) {
33682 return;
33683 }
33684
33685 copiedProperty = self._bpmnFactory.create(property.$type);
33686
33687 copiedProperty.$parent = parent;
33688
33689 // recursion
33690 copiedProperty = self.copyElement(property, copiedProperty);
33691
33692 return copiedProperty;
33693 }
33694
33695 // copy primitive properties
33696 return property;
33697 };
33698
33699 // helpers //////////
33700
33701 function getPropertyNames(descriptor, keepDefaultProperties) {
33702 return reduce(descriptor.properties, function(properties, property) {
33703
33704 if (keepDefaultProperties && property.default) {
33705 return properties;
33706 }
33707
33708 return properties.concat(property.name);
33709 }, []);
33710 }
33711
33712 function is$2(element, type) {
33713 return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type);
33714 }
33715
33716 var CopyPasteModule$1 = {
33717 __depends__: [
33718 CopyPasteModule
33719 ],
33720 __init__: [ 'bpmnCopyPaste', 'moddleCopy' ],
33721 bpmnCopyPaste: [ 'type', BpmnCopyPaste ],
33722 moddleCopy: [ 'type', ModdleCopy ]
33723 };
33724
33725 var round$6 = Math.round;
33726
33727 /**
33728 * Service that allow replacing of elements.
33729 */
33730 function Replace(modeling) {
33731
33732 this._modeling = modeling;
33733 }
33734
33735 Replace.$inject = [ 'modeling' ];
33736
33737 /**
33738 * @param {Element} oldElement - Element to be replaced
33739 * @param {Object} newElementData - Containing information about the new element,
33740 * for example the new bounds and type.
33741 * @param {Object} options - Custom options that will be attached to the context. It can be used to inject data
33742 * that is needed in the command chain. For example it could be used in
33743 * eventbus.on('commandStack.shape.replace.postExecute') to change shape attributes after
33744 * shape creation.
33745 */
33746 Replace.prototype.replaceElement = function(oldElement, newElementData, options) {
33747
33748 if (oldElement.waypoints) {
33749
33750 // TODO(nikku): we do not replace connections, yet
33751 return null;
33752 }
33753
33754 var modeling = this._modeling;
33755
33756 var width = newElementData.width || oldElement.width,
33757 height = newElementData.height || oldElement.height,
33758 x = newElementData.x || oldElement.x,
33759 y = newElementData.y || oldElement.y,
33760 centerX = round$6(x + width / 2),
33761 centerY = round$6(y + height / 2);
33762
33763 // modeling API requires center coordinates,
33764 // account for that when handling shape bounds
33765
33766 return modeling.replaceShape(
33767 oldElement,
33768 assign(
33769 {},
33770 newElementData,
33771 {
33772 x: centerX,
33773 y: centerY,
33774 width: width,
33775 height: height
33776 }
33777 ),
33778 options
33779 );
33780 };
33781
33782 var ReplaceModule = {
33783 __init__: [ 'replace' ],
33784 replace: [ 'type', Replace ]
33785 };
33786
33787 function copyProperties$1(source, target, properties) {
33788 if (!isArray(properties)) {
33789 properties = [ properties ];
33790 }
33791
33792 forEach(properties, function(property) {
33793 if (!isUndefined(source[property])) {
33794 target[property] = source[property];
33795 }
33796 });
33797 }
33798
33799 var CUSTOM_PROPERTIES = [
33800 'cancelActivity',
33801 'instantiate',
33802 'eventGatewayType',
33803 'triggeredByEvent',
33804 'isInterrupting'
33805 ];
33806
33807
33808 function toggeling(element, target) {
33809
33810 var oldCollapsed = (
33811 element && has(element, 'collapsed') ? element.collapsed : !isExpanded(element)
33812 );
33813
33814 var targetCollapsed;
33815
33816 if (target && (has(target, 'collapsed') || has(target, 'isExpanded'))) {
33817
33818 // property is explicitly set so use it
33819 targetCollapsed = (
33820 has(target, 'collapsed') ? target.collapsed : !target.isExpanded
33821 );
33822 } else {
33823
33824 // keep old state
33825 targetCollapsed = oldCollapsed;
33826 }
33827
33828 if (oldCollapsed !== targetCollapsed) {
33829 element.collapsed = oldCollapsed;
33830 return true;
33831 }
33832
33833 return false;
33834 }
33835
33836
33837
33838 /**
33839 * This module takes care of replacing BPMN elements
33840 */
33841 function BpmnReplace(
33842 bpmnFactory,
33843 elementFactory,
33844 moddleCopy,
33845 modeling,
33846 replace,
33847 rules,
33848 selection
33849 ) {
33850
33851 /**
33852 * Prepares a new business object for the replacement element
33853 * and triggers the replace operation.
33854 *
33855 * @param {djs.model.Base} element
33856 * @param {Object} target
33857 * @param {Object} [hints]
33858 *
33859 * @return {djs.model.Base} the newly created element
33860 */
33861 function replaceElement(element, target, hints) {
33862
33863 hints = hints || {};
33864
33865 var type = target.type,
33866 oldBusinessObject = element.businessObject;
33867
33868 if (isSubProcess(oldBusinessObject)) {
33869 if (type === 'bpmn:SubProcess') {
33870 if (toggeling(element, target)) {
33871
33872 // expanding or collapsing process
33873 modeling.toggleCollapse(element);
33874
33875 return element;
33876 }
33877 }
33878 }
33879
33880 var newBusinessObject = bpmnFactory.create(type);
33881
33882 var newElement = {
33883 type: type,
33884 businessObject: newBusinessObject
33885 };
33886
33887 var elementProps = getPropertyNames(oldBusinessObject.$descriptor),
33888 newElementProps = getPropertyNames(newBusinessObject.$descriptor, true),
33889 copyProps = intersection(elementProps, newElementProps);
33890
33891 // initialize special properties defined in target definition
33892 assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));
33893
33894 var properties = filter(copyProps, function(propertyName) {
33895
33896 // copying event definitions, unless we replace
33897 if (propertyName === 'eventDefinitions') {
33898 return hasEventDefinition$1(element, target.eventDefinitionType);
33899 }
33900
33901 // retain loop characteristics if the target element
33902 // is not an event sub process
33903 if (propertyName === 'loopCharacteristics') {
33904 return !isEventSubProcess(newBusinessObject);
33905 }
33906
33907 // so the applied properties from 'target' don't get lost
33908 if (has(newBusinessObject, propertyName)) {
33909 return false;
33910 }
33911
33912 if (propertyName === 'processRef' && target.isExpanded === false) {
33913 return false;
33914 }
33915
33916 if (propertyName === 'triggeredByEvent') {
33917 return false;
33918 }
33919
33920 return true;
33921 });
33922
33923 newBusinessObject = moddleCopy.copyElement(
33924 oldBusinessObject,
33925 newBusinessObject,
33926 properties
33927 );
33928
33929 // initialize custom BPMN extensions
33930 if (target.eventDefinitionType) {
33931
33932 // only initialize with new eventDefinition
33933 // if we did not set an event definition yet,
33934 // i.e. because we copied it
33935 if (!hasEventDefinition$1(newBusinessObject, target.eventDefinitionType)) {
33936 newElement.eventDefinitionType = target.eventDefinitionType;
33937 newElement.eventDefinitionAttrs = target.eventDefinitionAttrs;
33938 }
33939 }
33940
33941 if (is$1(oldBusinessObject, 'bpmn:Activity')) {
33942
33943 if (isSubProcess(oldBusinessObject)) {
33944
33945 // no toggeling, so keep old state
33946 newElement.isExpanded = isExpanded(oldBusinessObject);
33947 }
33948
33949 // else if property is explicitly set, use it
33950 else if (target && has(target, 'isExpanded')) {
33951 newElement.isExpanded = target.isExpanded;
33952 }
33953
33954 // TODO: need also to respect min/max Size
33955 // copy size, from an expanded subprocess to an expanded alternative subprocess
33956 // except bpmn:Task, because Task is always expanded
33957 if ((isExpanded(oldBusinessObject) && !is$1(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
33958 newElement.width = element.width;
33959 newElement.height = element.height;
33960 }
33961 }
33962
33963 // remove children if not expanding sub process
33964 if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
33965 hints.moveChildren = false;
33966 }
33967
33968 // transform collapsed/expanded pools
33969 if (is$1(oldBusinessObject, 'bpmn:Participant')) {
33970
33971 // create expanded pool
33972 if (target.isExpanded === true) {
33973 newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
33974 } else {
33975
33976 // remove children when transforming to collapsed pool
33977 hints.moveChildren = false;
33978 }
33979
33980 // apply same width and default height
33981 newElement.width = element.width;
33982 newElement.height = elementFactory._getDefaultSize(newBusinessObject).height;
33983 }
33984
33985 if (!rules.allowed('shape.resize', { shape: newBusinessObject })) {
33986 newElement.height = elementFactory._getDefaultSize(newBusinessObject).height;
33987 newElement.width = elementFactory._getDefaultSize(newBusinessObject).width;
33988 }
33989
33990 newBusinessObject.name = oldBusinessObject.name;
33991
33992 // retain default flow's reference between inclusive <-> exclusive gateways and activities
33993 if (
33994 isAny(oldBusinessObject, [
33995 'bpmn:ExclusiveGateway',
33996 'bpmn:InclusiveGateway',
33997 'bpmn:Activity'
33998 ]) &&
33999 isAny(newBusinessObject, [
34000 'bpmn:ExclusiveGateway',
34001 'bpmn:InclusiveGateway',
34002 'bpmn:Activity'
34003 ])
34004 ) {
34005 newBusinessObject.default = oldBusinessObject.default;
34006 }
34007
34008 if (
34009 target.host &&
34010 !is$1(oldBusinessObject, 'bpmn:BoundaryEvent') &&
34011 is$1(newBusinessObject, 'bpmn:BoundaryEvent')
34012 ) {
34013 newElement.host = target.host;
34014 }
34015
34016 // The DataStoreReference element is 14px wider than the DataObjectReference element
34017 // This ensures that they stay centered on the x axis when replaced
34018 if (
34019 newElement.type === 'bpmn:DataStoreReference' ||
34020 newElement.type === 'bpmn:DataObjectReference'
34021 ) {
34022 newElement.x = element.x + (element.width - newElement.width) / 2;
34023 }
34024
34025 newElement.di = {};
34026
34027 // colors will be set to DI
34028 copyProperties$1(oldBusinessObject.di, newElement.di, [
34029 'fill',
34030 'stroke',
34031 'background-color',
34032 'border-color',
34033 'color'
34034 ]);
34035
34036 newElement = replace.replaceElement(element, newElement, hints);
34037
34038 if (hints.select !== false) {
34039 selection.select(newElement);
34040 }
34041
34042 return newElement;
34043 }
34044
34045 this.replaceElement = replaceElement;
34046 }
34047
34048 BpmnReplace.$inject = [
34049 'bpmnFactory',
34050 'elementFactory',
34051 'moddleCopy',
34052 'modeling',
34053 'replace',
34054 'rules',
34055 'selection'
34056 ];
34057
34058
34059 function isSubProcess(bo) {
34060 return is$1(bo, 'bpmn:SubProcess');
34061 }
34062
34063 function hasEventDefinition$1(element, type) {
34064
34065 var bo = getBusinessObject(element);
34066
34067 return type && bo.get('eventDefinitions').some(function(definition) {
34068 return is$1(definition, type);
34069 });
34070 }
34071
34072 /**
34073 * Compute intersection between two arrays.
34074 */
34075 function intersection(a1, a2) {
34076 return a1.filter(function(el) {
34077 return a2.indexOf(el) !== -1;
34078 });
34079 }
34080
34081 var ReplaceModule$1 = {
34082 __depends__: [
34083 CopyPasteModule$1,
34084 ReplaceModule,
34085 SelectionModule
34086 ],
34087 bpmnReplace: [ 'type', BpmnReplace ]
34088 };
34089
34090 /**
34091 * Returns true, if an element is from a different type
34092 * than a target definition. Takes into account the type,
34093 * event definition type and triggeredByEvent property.
34094 *
34095 * @param {djs.model.Base} element
34096 *
34097 * @return {boolean}
34098 */
34099 function isDifferentType(element) {
34100
34101 return function(entry) {
34102 var target = entry.target;
34103
34104 var businessObject = getBusinessObject(element),
34105 eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0];
34106
34107 var isTypeEqual = businessObject.$type === target.type;
34108
34109 var isEventDefinitionEqual = (
34110 (eventDefinition && eventDefinition.$type) === target.eventDefinitionType
34111 );
34112
34113 var isTriggeredByEventEqual = (
34114 businessObject.triggeredByEvent === target.triggeredByEvent
34115 );
34116
34117 var isExpandedEqual = (
34118 target.isExpanded === undefined ||
34119 target.isExpanded === isExpanded(businessObject)
34120 );
34121
34122 return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
34123 };
34124 }
34125
34126 var START_EVENT = [
34127 {
34128 label: 'Start Event',
34129 actionName: 'replace-with-none-start',
34130 className: 'bpmn-icon-start-event-none',
34131 target: {
34132 type: 'bpmn:StartEvent'
34133 }
34134 },
34135 {
34136 label: 'Intermediate Throw Event',
34137 actionName: 'replace-with-none-intermediate-throwing',
34138 className: 'bpmn-icon-intermediate-event-none',
34139 target: {
34140 type: 'bpmn:IntermediateThrowEvent'
34141 }
34142 },
34143 {
34144 label: 'End Event',
34145 actionName: 'replace-with-none-end',
34146 className: 'bpmn-icon-end-event-none',
34147 target: {
34148 type: 'bpmn:EndEvent'
34149 }
34150 },
34151 {
34152 label: 'Message Start Event',
34153 actionName: 'replace-with-message-start',
34154 className: 'bpmn-icon-start-event-message',
34155 target: {
34156 type: 'bpmn:StartEvent',
34157 eventDefinitionType: 'bpmn:MessageEventDefinition'
34158 }
34159 },
34160 {
34161 label: 'Timer Start Event',
34162 actionName: 'replace-with-timer-start',
34163 className: 'bpmn-icon-start-event-timer',
34164 target: {
34165 type: 'bpmn:StartEvent',
34166 eventDefinitionType: 'bpmn:TimerEventDefinition'
34167 }
34168 },
34169 {
34170 label: 'Conditional Start Event',
34171 actionName: 'replace-with-conditional-start',
34172 className: 'bpmn-icon-start-event-condition',
34173 target: {
34174 type: 'bpmn:StartEvent',
34175 eventDefinitionType: 'bpmn:ConditionalEventDefinition'
34176 }
34177 },
34178 {
34179 label: 'Signal Start Event',
34180 actionName: 'replace-with-signal-start',
34181 className: 'bpmn-icon-start-event-signal',
34182 target: {
34183 type: 'bpmn:StartEvent',
34184 eventDefinitionType: 'bpmn:SignalEventDefinition'
34185 }
34186 }
34187 ];
34188
34189 var START_EVENT_SUB_PROCESS = [
34190 {
34191 label: 'Start Event',
34192 actionName: 'replace-with-none-start',
34193 className: 'bpmn-icon-start-event-none',
34194 target: {
34195 type: 'bpmn:StartEvent'
34196 }
34197 },
34198 {
34199 label: 'Intermediate Throw Event',
34200 actionName: 'replace-with-none-intermediate-throwing',
34201 className: 'bpmn-icon-intermediate-event-none',
34202 target: {
34203 type: 'bpmn:IntermediateThrowEvent'
34204 }
34205 },
34206 {
34207 label: 'End Event',
34208 actionName: 'replace-with-none-end',
34209 className: 'bpmn-icon-end-event-none',
34210 target: {
34211 type: 'bpmn:EndEvent'
34212 }
34213 }
34214 ];
34215
34216 var INTERMEDIATE_EVENT = [
34217 {
34218 label: 'Start Event',
34219 actionName: 'replace-with-none-start',
34220 className: 'bpmn-icon-start-event-none',
34221 target: {
34222 type: 'bpmn:StartEvent'
34223 }
34224 },
34225 {
34226 label: 'Intermediate Throw Event',
34227 actionName: 'replace-with-none-intermediate-throw',
34228 className: 'bpmn-icon-intermediate-event-none',
34229 target: {
34230 type: 'bpmn:IntermediateThrowEvent'
34231 }
34232 },
34233 {
34234 label: 'End Event',
34235 actionName: 'replace-with-none-end',
34236 className: 'bpmn-icon-end-event-none',
34237 target: {
34238 type: 'bpmn:EndEvent'
34239 }
34240 },
34241 {
34242 label: 'Message Intermediate Catch Event',
34243 actionName: 'replace-with-message-intermediate-catch',
34244 className: 'bpmn-icon-intermediate-event-catch-message',
34245 target: {
34246 type: 'bpmn:IntermediateCatchEvent',
34247 eventDefinitionType: 'bpmn:MessageEventDefinition'
34248 }
34249 },
34250 {
34251 label: 'Message Intermediate Throw Event',
34252 actionName: 'replace-with-message-intermediate-throw',
34253 className: 'bpmn-icon-intermediate-event-throw-message',
34254 target: {
34255 type: 'bpmn:IntermediateThrowEvent',
34256 eventDefinitionType: 'bpmn:MessageEventDefinition'
34257 }
34258 },
34259 {
34260 label: 'Timer Intermediate Catch Event',
34261 actionName: 'replace-with-timer-intermediate-catch',
34262 className: 'bpmn-icon-intermediate-event-catch-timer',
34263 target: {
34264 type: 'bpmn:IntermediateCatchEvent',
34265 eventDefinitionType: 'bpmn:TimerEventDefinition'
34266 }
34267 },
34268 {
34269 label: 'Escalation Intermediate Throw Event',
34270 actionName: 'replace-with-escalation-intermediate-throw',
34271 className: 'bpmn-icon-intermediate-event-throw-escalation',
34272 target: {
34273 type: 'bpmn:IntermediateThrowEvent',
34274 eventDefinitionType: 'bpmn:EscalationEventDefinition'
34275 }
34276 },
34277 {
34278 label: 'Conditional Intermediate Catch Event',
34279 actionName: 'replace-with-conditional-intermediate-catch',
34280 className: 'bpmn-icon-intermediate-event-catch-condition',
34281 target: {
34282 type: 'bpmn:IntermediateCatchEvent',
34283 eventDefinitionType: 'bpmn:ConditionalEventDefinition'
34284 }
34285 },
34286 {
34287 label: 'Link Intermediate Catch Event',
34288 actionName: 'replace-with-link-intermediate-catch',
34289 className: 'bpmn-icon-intermediate-event-catch-link',
34290 target: {
34291 type: 'bpmn:IntermediateCatchEvent',
34292 eventDefinitionType: 'bpmn:LinkEventDefinition',
34293 eventDefinitionAttrs: {
34294 name: ''
34295 }
34296 }
34297 },
34298 {
34299 label: 'Link Intermediate Throw Event',
34300 actionName: 'replace-with-link-intermediate-throw',
34301 className: 'bpmn-icon-intermediate-event-throw-link',
34302 target: {
34303 type: 'bpmn:IntermediateThrowEvent',
34304 eventDefinitionType: 'bpmn:LinkEventDefinition',
34305 eventDefinitionAttrs: {
34306 name: ''
34307 }
34308 }
34309 },
34310 {
34311 label: 'Compensation Intermediate Throw Event',
34312 actionName: 'replace-with-compensation-intermediate-throw',
34313 className: 'bpmn-icon-intermediate-event-throw-compensation',
34314 target: {
34315 type: 'bpmn:IntermediateThrowEvent',
34316 eventDefinitionType: 'bpmn:CompensateEventDefinition'
34317 }
34318 },
34319 {
34320 label: 'Signal Intermediate Catch Event',
34321 actionName: 'replace-with-signal-intermediate-catch',
34322 className: 'bpmn-icon-intermediate-event-catch-signal',
34323 target: {
34324 type: 'bpmn:IntermediateCatchEvent',
34325 eventDefinitionType: 'bpmn:SignalEventDefinition'
34326 }
34327 },
34328 {
34329 label: 'Signal Intermediate Throw Event',
34330 actionName: 'replace-with-signal-intermediate-throw',
34331 className: 'bpmn-icon-intermediate-event-throw-signal',
34332 target: {
34333 type: 'bpmn:IntermediateThrowEvent',
34334 eventDefinitionType: 'bpmn:SignalEventDefinition'
34335 }
34336 }
34337 ];
34338
34339 var END_EVENT = [
34340 {
34341 label: 'Start Event',
34342 actionName: 'replace-with-none-start',
34343 className: 'bpmn-icon-start-event-none',
34344 target: {
34345 type: 'bpmn:StartEvent'
34346 }
34347 },
34348 {
34349 label: 'Intermediate Throw Event',
34350 actionName: 'replace-with-none-intermediate-throw',
34351 className: 'bpmn-icon-intermediate-event-none',
34352 target: {
34353 type: 'bpmn:IntermediateThrowEvent'
34354 }
34355 },
34356 {
34357 label: 'End Event',
34358 actionName: 'replace-with-none-end',
34359 className: 'bpmn-icon-end-event-none',
34360 target: {
34361 type: 'bpmn:EndEvent'
34362 }
34363 },
34364 {
34365 label: 'Message End Event',
34366 actionName: 'replace-with-message-end',
34367 className: 'bpmn-icon-end-event-message',
34368 target: {
34369 type: 'bpmn:EndEvent',
34370 eventDefinitionType: 'bpmn:MessageEventDefinition'
34371 }
34372 },
34373 {
34374 label: 'Escalation End Event',
34375 actionName: 'replace-with-escalation-end',
34376 className: 'bpmn-icon-end-event-escalation',
34377 target: {
34378 type: 'bpmn:EndEvent',
34379 eventDefinitionType: 'bpmn:EscalationEventDefinition'
34380 }
34381 },
34382 {
34383 label: 'Error End Event',
34384 actionName: 'replace-with-error-end',
34385 className: 'bpmn-icon-end-event-error',
34386 target: {
34387 type: 'bpmn:EndEvent',
34388 eventDefinitionType: 'bpmn:ErrorEventDefinition'
34389 }
34390 },
34391 {
34392 label: 'Cancel End Event',
34393 actionName: 'replace-with-cancel-end',
34394 className: 'bpmn-icon-end-event-cancel',
34395 target: {
34396 type: 'bpmn:EndEvent',
34397 eventDefinitionType: 'bpmn:CancelEventDefinition'
34398 }
34399 },
34400 {
34401 label: 'Compensation End Event',
34402 actionName: 'replace-with-compensation-end',
34403 className: 'bpmn-icon-end-event-compensation',
34404 target: {
34405 type: 'bpmn:EndEvent',
34406 eventDefinitionType: 'bpmn:CompensateEventDefinition'
34407 }
34408 },
34409 {
34410 label: 'Signal End Event',
34411 actionName: 'replace-with-signal-end',
34412 className: 'bpmn-icon-end-event-signal',
34413 target: {
34414 type: 'bpmn:EndEvent',
34415 eventDefinitionType: 'bpmn:SignalEventDefinition'
34416 }
34417 },
34418 {
34419 label: 'Terminate End Event',
34420 actionName: 'replace-with-terminate-end',
34421 className: 'bpmn-icon-end-event-terminate',
34422 target: {
34423 type: 'bpmn:EndEvent',
34424 eventDefinitionType: 'bpmn:TerminateEventDefinition'
34425 }
34426 }
34427 ];
34428
34429 var GATEWAY = [
34430 {
34431 label: 'Exclusive Gateway',
34432 actionName: 'replace-with-exclusive-gateway',
34433 className: 'bpmn-icon-gateway-xor',
34434 target: {
34435 type: 'bpmn:ExclusiveGateway'
34436 }
34437 },
34438 {
34439 label: 'Parallel Gateway',
34440 actionName: 'replace-with-parallel-gateway',
34441 className: 'bpmn-icon-gateway-parallel',
34442 target: {
34443 type: 'bpmn:ParallelGateway'
34444 }
34445 },
34446 {
34447 label: 'Inclusive Gateway',
34448 actionName: 'replace-with-inclusive-gateway',
34449 className: 'bpmn-icon-gateway-or',
34450 target: {
34451 type: 'bpmn:InclusiveGateway'
34452 }
34453 },
34454 {
34455 label: 'Complex Gateway',
34456 actionName: 'replace-with-complex-gateway',
34457 className: 'bpmn-icon-gateway-complex',
34458 target: {
34459 type: 'bpmn:ComplexGateway'
34460 }
34461 },
34462 {
34463 label: 'Event based Gateway',
34464 actionName: 'replace-with-event-based-gateway',
34465 className: 'bpmn-icon-gateway-eventbased',
34466 target: {
34467 type: 'bpmn:EventBasedGateway',
34468 instantiate: false,
34469 eventGatewayType: 'Exclusive'
34470 }
34471 }
34472
34473 // Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194
34474 // {
34475 // label: 'Event based instantiating Gateway',
34476 // actionName: 'replace-with-exclusive-event-based-gateway',
34477 // className: 'bpmn-icon-exclusive-event-based',
34478 // target: {
34479 // type: 'bpmn:EventBasedGateway'
34480 // },
34481 // options: {
34482 // businessObject: { instantiate: true, eventGatewayType: 'Exclusive' }
34483 // }
34484 // },
34485 // {
34486 // label: 'Parallel Event based instantiating Gateway',
34487 // actionName: 'replace-with-parallel-event-based-instantiate-gateway',
34488 // className: 'bpmn-icon-parallel-event-based-instantiate-gateway',
34489 // target: {
34490 // type: 'bpmn:EventBasedGateway'
34491 // },
34492 // options: {
34493 // businessObject: { instantiate: true, eventGatewayType: 'Parallel' }
34494 // }
34495 // }
34496 ];
34497
34498 var SUBPROCESS_EXPANDED = [
34499 {
34500 label: 'Transaction',
34501 actionName: 'replace-with-transaction',
34502 className: 'bpmn-icon-transaction',
34503 target: {
34504 type: 'bpmn:Transaction',
34505 isExpanded: true
34506 }
34507 },
34508 {
34509 label: 'Event Sub Process',
34510 actionName: 'replace-with-event-subprocess',
34511 className: 'bpmn-icon-event-subprocess-expanded',
34512 target: {
34513 type: 'bpmn:SubProcess',
34514 triggeredByEvent: true,
34515 isExpanded: true
34516 }
34517 },
34518 {
34519 label: 'Sub Process (collapsed)',
34520 actionName: 'replace-with-collapsed-subprocess',
34521 className: 'bpmn-icon-subprocess-collapsed',
34522 target: {
34523 type: 'bpmn:SubProcess',
34524 isExpanded: false
34525 }
34526 }
34527 ];
34528
34529 var TRANSACTION = [
34530 {
34531 label: 'Sub Process',
34532 actionName: 'replace-with-subprocess',
34533 className: 'bpmn-icon-subprocess-expanded',
34534 target: {
34535 type: 'bpmn:SubProcess',
34536 isExpanded: true
34537 }
34538 },
34539 {
34540 label: 'Event Sub Process',
34541 actionName: 'replace-with-event-subprocess',
34542 className: 'bpmn-icon-event-subprocess-expanded',
34543 target: {
34544 type: 'bpmn:SubProcess',
34545 triggeredByEvent: true,
34546 isExpanded: true
34547 }
34548 }
34549 ];
34550
34551 var EVENT_SUB_PROCESS = [
34552 {
34553 label: 'Sub Process',
34554 actionName: 'replace-with-subprocess',
34555 className: 'bpmn-icon-subprocess-expanded',
34556 target: {
34557 type: 'bpmn:SubProcess',
34558 isExpanded: true
34559 }
34560 },
34561 {
34562 label: 'Transaction',
34563 actionName: 'replace-with-transaction',
34564 className: 'bpmn-icon-transaction',
34565 target: {
34566 type: 'bpmn:Transaction',
34567 isExpanded: true
34568 }
34569 }
34570 ];
34571
34572 var TASK = [
34573 {
34574 label: 'Task',
34575 actionName: 'replace-with-task',
34576 className: 'bpmn-icon-task',
34577 target: {
34578 type: 'bpmn:Task'
34579 }
34580 },
34581 {
34582 label: 'Send Task',
34583 actionName: 'replace-with-send-task',
34584 className: 'bpmn-icon-send',
34585 target: {
34586 type: 'bpmn:SendTask'
34587 }
34588 },
34589 {
34590 label: 'Receive Task',
34591 actionName: 'replace-with-receive-task',
34592 className: 'bpmn-icon-receive',
34593 target: {
34594 type: 'bpmn:ReceiveTask'
34595 }
34596 },
34597 {
34598 label: 'User Task',
34599 actionName: 'replace-with-user-task',
34600 className: 'bpmn-icon-user',
34601 target: {
34602 type: 'bpmn:UserTask'
34603 }
34604 },
34605 {
34606 label: 'Manual Task',
34607 actionName: 'replace-with-manual-task',
34608 className: 'bpmn-icon-manual',
34609 target: {
34610 type: 'bpmn:ManualTask'
34611 }
34612 },
34613 {
34614 label: 'Business Rule Task',
34615 actionName: 'replace-with-rule-task',
34616 className: 'bpmn-icon-business-rule',
34617 target: {
34618 type: 'bpmn:BusinessRuleTask'
34619 }
34620 },
34621 {
34622 label: 'Service Task',
34623 actionName: 'replace-with-service-task',
34624 className: 'bpmn-icon-service',
34625 target: {
34626 type: 'bpmn:ServiceTask'
34627 }
34628 },
34629 {
34630 label: 'Script Task',
34631 actionName: 'replace-with-script-task',
34632 className: 'bpmn-icon-script',
34633 target: {
34634 type: 'bpmn:ScriptTask'
34635 }
34636 },
34637 {
34638 label: 'Call Activity',
34639 actionName: 'replace-with-call-activity',
34640 className: 'bpmn-icon-call-activity',
34641 target: {
34642 type: 'bpmn:CallActivity'
34643 }
34644 },
34645 {
34646 label: 'Sub Process (collapsed)',
34647 actionName: 'replace-with-collapsed-subprocess',
34648 className: 'bpmn-icon-subprocess-collapsed',
34649 target: {
34650 type: 'bpmn:SubProcess',
34651 isExpanded: false
34652 }
34653 },
34654 {
34655 label: 'Sub Process (expanded)',
34656 actionName: 'replace-with-expanded-subprocess',
34657 className: 'bpmn-icon-subprocess-expanded',
34658 target: {
34659 type: 'bpmn:SubProcess',
34660 isExpanded: true
34661 }
34662 }
34663 ];
34664
34665 var DATA_OBJECT_REFERENCE = [
34666 {
34667 label: 'Data Store Reference',
34668 actionName: 'replace-with-data-store-reference',
34669 className: 'bpmn-icon-data-store',
34670 target: {
34671 type: 'bpmn:DataStoreReference'
34672 }
34673 }
34674 ];
34675
34676 var DATA_STORE_REFERENCE = [
34677 {
34678 label: 'Data Object Reference',
34679 actionName: 'replace-with-data-object-reference',
34680 className: 'bpmn-icon-data-object',
34681 target: {
34682 type: 'bpmn:DataObjectReference'
34683 }
34684 }
34685 ];
34686
34687 var BOUNDARY_EVENT = [
34688 {
34689 label: 'Message Boundary Event',
34690 actionName: 'replace-with-message-boundary',
34691 className: 'bpmn-icon-intermediate-event-catch-message',
34692 target: {
34693 type: 'bpmn:BoundaryEvent',
34694 eventDefinitionType: 'bpmn:MessageEventDefinition'
34695 }
34696 },
34697 {
34698 label: 'Timer Boundary Event',
34699 actionName: 'replace-with-timer-boundary',
34700 className: 'bpmn-icon-intermediate-event-catch-timer',
34701 target: {
34702 type: 'bpmn:BoundaryEvent',
34703 eventDefinitionType: 'bpmn:TimerEventDefinition'
34704 }
34705 },
34706 {
34707 label: 'Escalation Boundary Event',
34708 actionName: 'replace-with-escalation-boundary',
34709 className: 'bpmn-icon-intermediate-event-catch-escalation',
34710 target: {
34711 type: 'bpmn:BoundaryEvent',
34712 eventDefinitionType: 'bpmn:EscalationEventDefinition'
34713 }
34714 },
34715 {
34716 label: 'Conditional Boundary Event',
34717 actionName: 'replace-with-conditional-boundary',
34718 className: 'bpmn-icon-intermediate-event-catch-condition',
34719 target: {
34720 type: 'bpmn:BoundaryEvent',
34721 eventDefinitionType: 'bpmn:ConditionalEventDefinition'
34722 }
34723 },
34724 {
34725 label: 'Error Boundary Event',
34726 actionName: 'replace-with-error-boundary',
34727 className: 'bpmn-icon-intermediate-event-catch-error',
34728 target: {
34729 type: 'bpmn:BoundaryEvent',
34730 eventDefinitionType: 'bpmn:ErrorEventDefinition'
34731 }
34732 },
34733 {
34734 label: 'Cancel Boundary Event',
34735 actionName: 'replace-with-cancel-boundary',
34736 className: 'bpmn-icon-intermediate-event-catch-cancel',
34737 target: {
34738 type: 'bpmn:BoundaryEvent',
34739 eventDefinitionType: 'bpmn:CancelEventDefinition'
34740 }
34741 },
34742 {
34743 label: 'Signal Boundary Event',
34744 actionName: 'replace-with-signal-boundary',
34745 className: 'bpmn-icon-intermediate-event-catch-signal',
34746 target: {
34747 type: 'bpmn:BoundaryEvent',
34748 eventDefinitionType: 'bpmn:SignalEventDefinition'
34749 }
34750 },
34751 {
34752 label: 'Compensation Boundary Event',
34753 actionName: 'replace-with-compensation-boundary',
34754 className: 'bpmn-icon-intermediate-event-catch-compensation',
34755 target: {
34756 type: 'bpmn:BoundaryEvent',
34757 eventDefinitionType: 'bpmn:CompensateEventDefinition'
34758 }
34759 },
34760 {
34761 label: 'Message Boundary Event (non-interrupting)',
34762 actionName: 'replace-with-non-interrupting-message-boundary',
34763 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message',
34764 target: {
34765 type: 'bpmn:BoundaryEvent',
34766 eventDefinitionType: 'bpmn:MessageEventDefinition',
34767 cancelActivity: false
34768 }
34769 },
34770 {
34771 label: 'Timer Boundary Event (non-interrupting)',
34772 actionName: 'replace-with-non-interrupting-timer-boundary',
34773 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer',
34774 target: {
34775 type: 'bpmn:BoundaryEvent',
34776 eventDefinitionType: 'bpmn:TimerEventDefinition',
34777 cancelActivity: false
34778 }
34779 },
34780 {
34781 label: 'Escalation Boundary Event (non-interrupting)',
34782 actionName: 'replace-with-non-interrupting-escalation-boundary',
34783 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation',
34784 target: {
34785 type: 'bpmn:BoundaryEvent',
34786 eventDefinitionType: 'bpmn:EscalationEventDefinition',
34787 cancelActivity: false
34788 }
34789 },
34790 {
34791 label: 'Conditional Boundary Event (non-interrupting)',
34792 actionName: 'replace-with-non-interrupting-conditional-boundary',
34793 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition',
34794 target: {
34795 type: 'bpmn:BoundaryEvent',
34796 eventDefinitionType: 'bpmn:ConditionalEventDefinition',
34797 cancelActivity: false
34798 }
34799 },
34800 {
34801 label: 'Signal Boundary Event (non-interrupting)',
34802 actionName: 'replace-with-non-interrupting-signal-boundary',
34803 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal',
34804 target: {
34805 type: 'bpmn:BoundaryEvent',
34806 eventDefinitionType: 'bpmn:SignalEventDefinition',
34807 cancelActivity: false
34808 }
34809 }
34810 ];
34811
34812 var EVENT_SUB_PROCESS_START_EVENT = [
34813 {
34814 label: 'Message Start Event',
34815 actionName: 'replace-with-message-start',
34816 className: 'bpmn-icon-start-event-message',
34817 target: {
34818 type: 'bpmn:StartEvent',
34819 eventDefinitionType: 'bpmn:MessageEventDefinition'
34820 }
34821 },
34822 {
34823 label: 'Timer Start Event',
34824 actionName: 'replace-with-timer-start',
34825 className: 'bpmn-icon-start-event-timer',
34826 target: {
34827 type: 'bpmn:StartEvent',
34828 eventDefinitionType: 'bpmn:TimerEventDefinition'
34829 }
34830 },
34831 {
34832 label: 'Conditional Start Event',
34833 actionName: 'replace-with-conditional-start',
34834 className: 'bpmn-icon-start-event-condition',
34835 target: {
34836 type: 'bpmn:StartEvent',
34837 eventDefinitionType: 'bpmn:ConditionalEventDefinition'
34838 }
34839 },
34840 {
34841 label: 'Signal Start Event',
34842 actionName: 'replace-with-signal-start',
34843 className: 'bpmn-icon-start-event-signal',
34844 target: {
34845 type: 'bpmn:StartEvent',
34846 eventDefinitionType: 'bpmn:SignalEventDefinition'
34847 }
34848 },
34849 {
34850 label: 'Error Start Event',
34851 actionName: 'replace-with-error-start',
34852 className: 'bpmn-icon-start-event-error',
34853 target: {
34854 type: 'bpmn:StartEvent',
34855 eventDefinitionType: 'bpmn:ErrorEventDefinition'
34856 }
34857 },
34858 {
34859 label: 'Escalation Start Event',
34860 actionName: 'replace-with-escalation-start',
34861 className: 'bpmn-icon-start-event-escalation',
34862 target: {
34863 type: 'bpmn:StartEvent',
34864 eventDefinitionType: 'bpmn:EscalationEventDefinition'
34865 }
34866 },
34867 {
34868 label: 'Compensation Start Event',
34869 actionName: 'replace-with-compensation-start',
34870 className: 'bpmn-icon-start-event-compensation',
34871 target: {
34872 type: 'bpmn:StartEvent',
34873 eventDefinitionType: 'bpmn:CompensateEventDefinition'
34874 }
34875 },
34876 {
34877 label: 'Message Start Event (non-interrupting)',
34878 actionName: 'replace-with-non-interrupting-message-start',
34879 className: 'bpmn-icon-start-event-non-interrupting-message',
34880 target: {
34881 type: 'bpmn:StartEvent',
34882 eventDefinitionType: 'bpmn:MessageEventDefinition',
34883 isInterrupting: false
34884 }
34885 },
34886 {
34887 label: 'Timer Start Event (non-interrupting)',
34888 actionName: 'replace-with-non-interrupting-timer-start',
34889 className: 'bpmn-icon-start-event-non-interrupting-timer',
34890 target: {
34891 type: 'bpmn:StartEvent',
34892 eventDefinitionType: 'bpmn:TimerEventDefinition',
34893 isInterrupting: false
34894 }
34895 },
34896 {
34897 label: 'Conditional Start Event (non-interrupting)',
34898 actionName: 'replace-with-non-interrupting-conditional-start',
34899 className: 'bpmn-icon-start-event-non-interrupting-condition',
34900 target: {
34901 type: 'bpmn:StartEvent',
34902 eventDefinitionType: 'bpmn:ConditionalEventDefinition',
34903 isInterrupting: false
34904 }
34905 },
34906 {
34907 label: 'Signal Start Event (non-interrupting)',
34908 actionName: 'replace-with-non-interrupting-signal-start',
34909 className: 'bpmn-icon-start-event-non-interrupting-signal',
34910 target: {
34911 type: 'bpmn:StartEvent',
34912 eventDefinitionType: 'bpmn:SignalEventDefinition',
34913 isInterrupting: false
34914 }
34915 },
34916 {
34917 label: 'Escalation Start Event (non-interrupting)',
34918 actionName: 'replace-with-non-interrupting-escalation-start',
34919 className: 'bpmn-icon-start-event-non-interrupting-escalation',
34920 target: {
34921 type: 'bpmn:StartEvent',
34922 eventDefinitionType: 'bpmn:EscalationEventDefinition',
34923 isInterrupting: false
34924 }
34925 }
34926 ];
34927
34928 var SEQUENCE_FLOW = [
34929 {
34930 label: 'Sequence Flow',
34931 actionName: 'replace-with-sequence-flow',
34932 className: 'bpmn-icon-connection'
34933 },
34934 {
34935 label: 'Default Flow',
34936 actionName: 'replace-with-default-flow',
34937 className: 'bpmn-icon-default-flow'
34938 },
34939 {
34940 label: 'Conditional Flow',
34941 actionName: 'replace-with-conditional-flow',
34942 className: 'bpmn-icon-conditional-flow'
34943 }
34944 ];
34945
34946 var PARTICIPANT = [
34947 {
34948 label: 'Expanded Pool',
34949 actionName: 'replace-with-expanded-pool',
34950 className: 'bpmn-icon-participant',
34951 target: {
34952 type: 'bpmn:Participant',
34953 isExpanded: true
34954 }
34955 },
34956 {
34957 label: function(element) {
34958 var label = 'Empty Pool';
34959
34960 if (element.children && element.children.length) {
34961 label += ' (removes content)';
34962 }
34963
34964 return label;
34965 },
34966 actionName: 'replace-with-collapsed-pool',
34967
34968 // TODO(@janstuemmel): maybe design new icon
34969 className: 'bpmn-icon-lane',
34970 target: {
34971 type: 'bpmn:Participant',
34972 isExpanded: false
34973 }
34974 }
34975 ];
34976
34977 /**
34978 * This module is an element agnostic replace menu provider for the popup menu.
34979 */
34980 function ReplaceMenuProvider(
34981 bpmnFactory, popupMenu, modeling, moddle,
34982 bpmnReplace, rules, translate) {
34983
34984 this._bpmnFactory = bpmnFactory;
34985 this._popupMenu = popupMenu;
34986 this._modeling = modeling;
34987 this._moddle = moddle;
34988 this._bpmnReplace = bpmnReplace;
34989 this._rules = rules;
34990 this._translate = translate;
34991
34992 this.register();
34993 }
34994
34995 ReplaceMenuProvider.$inject = [
34996 'bpmnFactory',
34997 'popupMenu',
34998 'modeling',
34999 'moddle',
35000 'bpmnReplace',
35001 'rules',
35002 'translate'
35003 ];
35004
35005
35006 /**
35007 * Register replace menu provider in the popup menu
35008 */
35009 ReplaceMenuProvider.prototype.register = function() {
35010 this._popupMenu.registerProvider('bpmn-replace', this);
35011 };
35012
35013
35014 /**
35015 * Get all entries from replaceOptions for the given element and apply filters
35016 * on them. Get for example only elements, which are different from the current one.
35017 *
35018 * @param {djs.model.Base} element
35019 *
35020 * @return {Array<Object>} a list of menu entry items
35021 */
35022 ReplaceMenuProvider.prototype.getEntries = function(element) {
35023
35024 var businessObject = element.businessObject;
35025
35026 var rules = this._rules;
35027
35028 var entries;
35029
35030 if (!rules.allowed('shape.replace', { element: element })) {
35031 return [];
35032 }
35033
35034 var differentType = isDifferentType(element);
35035
35036 if (is$1(businessObject, 'bpmn:DataObjectReference')) {
35037 return this._createEntries(element, DATA_OBJECT_REFERENCE);
35038 }
35039
35040 if (is$1(businessObject, 'bpmn:DataStoreReference')) {
35041 return this._createEntries(element, DATA_STORE_REFERENCE);
35042 }
35043
35044 // start events outside sub processes
35045 if (is$1(businessObject, 'bpmn:StartEvent') && !is$1(businessObject.$parent, 'bpmn:SubProcess')) {
35046
35047 entries = filter(START_EVENT, differentType);
35048
35049 return this._createEntries(element, entries);
35050 }
35051
35052 // expanded/collapsed pools
35053 if (is$1(businessObject, 'bpmn:Participant')) {
35054
35055 entries = filter(PARTICIPANT, function(entry) {
35056 return isExpanded(businessObject) !== entry.target.isExpanded;
35057 });
35058
35059 return this._createEntries(element, entries);
35060 }
35061
35062 // start events inside event sub processes
35063 if (is$1(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
35064 entries = filter(EVENT_SUB_PROCESS_START_EVENT, function(entry) {
35065
35066 var target = entry.target;
35067
35068 var isInterrupting = target.isInterrupting !== false;
35069
35070 var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;
35071
35072 // filters elements which types and event definition are equal but have have different interrupting types
35073 return differentType(entry) || !differentType(entry) && !isInterruptingEqual;
35074
35075 });
35076
35077 return this._createEntries(element, entries);
35078 }
35079
35080 // start events inside sub processes
35081 if (is$1(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
35082 && is$1(businessObject.$parent, 'bpmn:SubProcess')) {
35083 entries = filter(START_EVENT_SUB_PROCESS, differentType);
35084
35085 return this._createEntries(element, entries);
35086 }
35087
35088 // end events
35089 if (is$1(businessObject, 'bpmn:EndEvent')) {
35090
35091 entries = filter(END_EVENT, function(entry) {
35092 var target = entry.target;
35093
35094 // hide cancel end events outside transactions
35095 if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is$1(businessObject.$parent, 'bpmn:Transaction')) {
35096 return false;
35097 }
35098
35099 return differentType(entry);
35100 });
35101
35102 return this._createEntries(element, entries);
35103 }
35104
35105 // boundary events
35106 if (is$1(businessObject, 'bpmn:BoundaryEvent')) {
35107
35108 entries = filter(BOUNDARY_EVENT, function(entry) {
35109
35110 var target = entry.target;
35111
35112 if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
35113 !is$1(businessObject.attachedToRef, 'bpmn:Transaction')) {
35114 return false;
35115 }
35116 var cancelActivity = target.cancelActivity !== false;
35117
35118 var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
35119
35120 return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
35121 });
35122
35123 return this._createEntries(element, entries);
35124 }
35125
35126 // intermediate events
35127 if (is$1(businessObject, 'bpmn:IntermediateCatchEvent') ||
35128 is$1(businessObject, 'bpmn:IntermediateThrowEvent')) {
35129
35130 entries = filter(INTERMEDIATE_EVENT, differentType);
35131
35132 return this._createEntries(element, entries);
35133 }
35134
35135 // gateways
35136 if (is$1(businessObject, 'bpmn:Gateway')) {
35137
35138 entries = filter(GATEWAY, differentType);
35139
35140 return this._createEntries(element, entries);
35141 }
35142
35143 // transactions
35144 if (is$1(businessObject, 'bpmn:Transaction')) {
35145
35146 entries = filter(TRANSACTION, differentType);
35147
35148 return this._createEntries(element, entries);
35149 }
35150
35151 // expanded event sub processes
35152 if (isEventSubProcess(businessObject) && isExpanded(businessObject)) {
35153
35154 entries = filter(EVENT_SUB_PROCESS, differentType);
35155
35156 return this._createEntries(element, entries);
35157 }
35158
35159 // expanded sub processes
35160 if (is$1(businessObject, 'bpmn:SubProcess') && isExpanded(businessObject)) {
35161
35162 entries = filter(SUBPROCESS_EXPANDED, differentType);
35163
35164 return this._createEntries(element, entries);
35165 }
35166
35167 // collapsed ad hoc sub processes
35168 if (is$1(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(businessObject)) {
35169
35170 entries = filter(TASK, function(entry) {
35171
35172 var target = entry.target;
35173
35174 var isTargetSubProcess = target.type === 'bpmn:SubProcess';
35175
35176 var isTargetExpanded = target.isExpanded === true;
35177
35178 return isDifferentType(element) && (!isTargetSubProcess || isTargetExpanded);
35179 });
35180
35181 return this._createEntries(element, entries);
35182 }
35183
35184 // sequence flows
35185 if (is$1(businessObject, 'bpmn:SequenceFlow')) {
35186 return this._createSequenceFlowEntries(element, SEQUENCE_FLOW);
35187 }
35188
35189 // flow nodes
35190 if (is$1(businessObject, 'bpmn:FlowNode')) {
35191 entries = filter(TASK, differentType);
35192
35193 // collapsed SubProcess can not be replaced with itself
35194 if (is$1(businessObject, 'bpmn:SubProcess') && !isExpanded(businessObject)) {
35195 entries = filter(entries, function(entry) {
35196 return entry.label !== 'Sub Process (collapsed)';
35197 });
35198 }
35199
35200 return this._createEntries(element, entries);
35201 }
35202
35203 return [];
35204 };
35205
35206
35207 /**
35208 * Get a list of header items for the given element. This includes buttons
35209 * for multi instance markers and for the ad hoc marker.
35210 *
35211 * @param {djs.model.Base} element
35212 *
35213 * @return {Array<Object>} a list of menu entry items
35214 */
35215 ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
35216
35217 var headerEntries = [];
35218
35219 if (is$1(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
35220 headerEntries = headerEntries.concat(this._getLoopEntries(element));
35221 }
35222
35223 if (is$1(element, 'bpmn:DataObjectReference')) {
35224 headerEntries = headerEntries.concat(this._getDataObjectIsCollection(element));
35225 }
35226
35227 if (is$1(element, 'bpmn:Participant')) {
35228 headerEntries = headerEntries.concat(this._getParticipantMultiplicity(element));
35229 }
35230
35231 if (is$1(element, 'bpmn:SubProcess') &&
35232 !is$1(element, 'bpmn:Transaction') &&
35233 !isEventSubProcess(element)) {
35234 headerEntries.push(this._getAdHocEntry(element));
35235 }
35236
35237 return headerEntries;
35238 };
35239
35240
35241 /**
35242 * Creates an array of menu entry objects for a given element and filters the replaceOptions
35243 * according to a filter function.
35244 *
35245 * @param {djs.model.Base} element
35246 * @param {Object} replaceOptions
35247 *
35248 * @return {Array<Object>} a list of menu items
35249 */
35250 ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
35251 var menuEntries = [];
35252
35253 var self = this;
35254
35255 forEach(replaceOptions, function(definition) {
35256 var entry = self._createMenuEntry(definition, element);
35257
35258 menuEntries.push(entry);
35259 });
35260
35261 return menuEntries;
35262 };
35263
35264 /**
35265 * Creates an array of menu entry objects for a given sequence flow.
35266 *
35267 * @param {djs.model.Base} element
35268 * @param {Object} replaceOptions
35269
35270 * @return {Array<Object>} a list of menu items
35271 */
35272 ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {
35273
35274 var businessObject = getBusinessObject(element);
35275
35276 var menuEntries = [];
35277
35278 var modeling = this._modeling,
35279 moddle = this._moddle;
35280
35281 var self = this;
35282
35283 forEach(replaceOptions, function(entry) {
35284
35285 switch (entry.actionName) {
35286 case 'replace-with-default-flow':
35287 if (businessObject.sourceRef.default !== businessObject &&
35288 (is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
35289 is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
35290 is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
35291 is$1(businessObject.sourceRef, 'bpmn:Activity'))) {
35292
35293 menuEntries.push(self._createMenuEntry(entry, element, function() {
35294 modeling.updateProperties(element.source, { default: businessObject });
35295 }));
35296 }
35297 break;
35298 case 'replace-with-conditional-flow':
35299 if (!businessObject.conditionExpression && is$1(businessObject.sourceRef, 'bpmn:Activity')) {
35300
35301 menuEntries.push(self._createMenuEntry(entry, element, function() {
35302 var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
35303
35304 modeling.updateProperties(element, { conditionExpression: conditionExpression });
35305 }));
35306 }
35307 break;
35308 default:
35309
35310 // default flows
35311 if (is$1(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
35312 return menuEntries.push(self._createMenuEntry(entry, element, function() {
35313 modeling.updateProperties(element, { conditionExpression: undefined });
35314 }));
35315 }
35316
35317 // conditional flows
35318 if ((is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
35319 is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
35320 is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
35321 is$1(businessObject.sourceRef, 'bpmn:Activity')) &&
35322 businessObject.sourceRef.default === businessObject) {
35323
35324 return menuEntries.push(self._createMenuEntry(entry, element, function() {
35325 modeling.updateProperties(element.source, { default: undefined });
35326 }));
35327 }
35328 }
35329 });
35330
35331 return menuEntries;
35332 };
35333
35334
35335 /**
35336 * Creates and returns a single menu entry item.
35337 *
35338 * @param {Object} definition a single replace options definition object
35339 * @param {djs.model.Base} element
35340 * @param {Function} [action] an action callback function which gets called when
35341 * the menu entry is being triggered.
35342 *
35343 * @return {Object} menu entry item
35344 */
35345 ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
35346 var translate = this._translate;
35347 var replaceElement = this._bpmnReplace.replaceElement;
35348
35349 var replaceAction = function() {
35350 return replaceElement(element, definition.target);
35351 };
35352
35353 var label = definition.label;
35354 if (label && typeof label === 'function') {
35355 label = label(element);
35356 }
35357
35358 action = action || replaceAction;
35359
35360 var menuEntry = {
35361 label: translate(label),
35362 className: definition.className,
35363 id: definition.actionName,
35364 action: action
35365 };
35366
35367 return menuEntry;
35368 };
35369
35370 /**
35371 * Get a list of menu items containing buttons for multi instance markers
35372 *
35373 * @param {djs.model.Base} element
35374 *
35375 * @return {Array<Object>} a list of menu items
35376 */
35377 ReplaceMenuProvider.prototype._getLoopEntries = function(element) {
35378
35379 var self = this;
35380 var translate = this._translate;
35381
35382 function toggleLoopEntry(event, entry) {
35383 var loopCharacteristics;
35384
35385 if (entry.active) {
35386 loopCharacteristics = undefined;
35387 } else {
35388 loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
35389
35390 if (entry.options.isSequential) {
35391 loopCharacteristics.isSequential = entry.options.isSequential;
35392 }
35393 }
35394 self._modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics });
35395 }
35396
35397 var businessObject = getBusinessObject(element),
35398 loopCharacteristics = businessObject.loopCharacteristics;
35399
35400 var isSequential,
35401 isLoop,
35402 isParallel;
35403
35404 if (loopCharacteristics) {
35405 isSequential = loopCharacteristics.isSequential;
35406 isLoop = loopCharacteristics.isSequential === undefined;
35407 isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
35408 }
35409
35410
35411 var loopEntries = [
35412 {
35413 id: 'toggle-parallel-mi',
35414 className: 'bpmn-icon-parallel-mi-marker',
35415 title: translate('Parallel Multi Instance'),
35416 active: isParallel,
35417 action: toggleLoopEntry,
35418 options: {
35419 loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
35420 isSequential: false
35421 }
35422 },
35423 {
35424 id: 'toggle-sequential-mi',
35425 className: 'bpmn-icon-sequential-mi-marker',
35426 title: translate('Sequential Multi Instance'),
35427 active: isSequential,
35428 action: toggleLoopEntry,
35429 options: {
35430 loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
35431 isSequential: true
35432 }
35433 },
35434 {
35435 id: 'toggle-loop',
35436 className: 'bpmn-icon-loop-marker',
35437 title: translate('Loop'),
35438 active: isLoop,
35439 action: toggleLoopEntry,
35440 options: {
35441 loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
35442 }
35443 }
35444 ];
35445 return loopEntries;
35446 };
35447
35448 /**
35449 * Get a list of menu items containing a button for the collection marker
35450 *
35451 * @param {djs.model.Base} element
35452 *
35453 * @return {Array<Object>} a list of menu items
35454 */
35455 ReplaceMenuProvider.prototype._getDataObjectIsCollection = function(element) {
35456
35457 var self = this;
35458 var translate = this._translate;
35459
35460 function toggleIsCollection(event, entry) {
35461 self._modeling.updateModdleProperties(
35462 element,
35463 dataObject,
35464 { isCollection: !entry.active });
35465 }
35466
35467 var dataObject = element.businessObject.dataObjectRef,
35468 isCollection = dataObject.isCollection;
35469
35470 var dataObjectEntries = [
35471 {
35472 id: 'toggle-is-collection',
35473 className: 'bpmn-icon-parallel-mi-marker',
35474 title: translate('Collection'),
35475 active: isCollection,
35476 action: toggleIsCollection,
35477 }
35478 ];
35479 return dataObjectEntries;
35480 };
35481
35482 /**
35483 * Get a list of menu items containing a button for the participant multiplicity marker
35484 *
35485 * @param {djs.model.Base} element
35486 *
35487 * @return {Array<Object>} a list of menu items
35488 */
35489 ReplaceMenuProvider.prototype._getParticipantMultiplicity = function(element) {
35490
35491 var self = this;
35492 var bpmnFactory = this._bpmnFactory;
35493 var translate = this._translate;
35494
35495 function toggleParticipantMultiplicity(event, entry) {
35496 var isActive = entry.active;
35497 var participantMultiplicity;
35498
35499 if (!isActive) {
35500 participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity');
35501 }
35502
35503 self._modeling.updateProperties(
35504 element,
35505 { participantMultiplicity: participantMultiplicity });
35506 }
35507
35508 var participantMultiplicity = element.businessObject.participantMultiplicity;
35509
35510 var participantEntries = [
35511 {
35512 id: 'toggle-participant-multiplicity',
35513 className: 'bpmn-icon-parallel-mi-marker',
35514 title: translate('Participant Multiplicity'),
35515 active: !!participantMultiplicity,
35516 action: toggleParticipantMultiplicity,
35517 }
35518 ];
35519 return participantEntries;
35520 };
35521
35522
35523 /**
35524 * Get the menu items containing a button for the ad hoc marker
35525 *
35526 * @param {djs.model.Base} element
35527 *
35528 * @return {Object} a menu item
35529 */
35530 ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
35531 var translate = this._translate;
35532 var businessObject = getBusinessObject(element);
35533
35534 var isAdHoc = is$1(businessObject, 'bpmn:AdHocSubProcess');
35535
35536 var replaceElement = this._bpmnReplace.replaceElement;
35537
35538 var adHocEntry = {
35539 id: 'toggle-adhoc',
35540 className: 'bpmn-icon-ad-hoc-marker',
35541 title: translate('Ad-hoc'),
35542 active: isAdHoc,
35543 action: function(event, entry) {
35544 if (isAdHoc) {
35545 return replaceElement(element, { type: 'bpmn:SubProcess' }, {
35546 autoResize: false,
35547 layoutConnection: false
35548 });
35549 } else {
35550 return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, {
35551 autoResize: false,
35552 layoutConnection: false
35553 });
35554 }
35555 }
35556 };
35557
35558 return adHocEntry;
35559 };
35560
35561 var PopupMenuModule$1 = {
35562 __depends__: [
35563 PopupMenuModule,
35564 ReplaceModule$1
35565 ],
35566 __init__: [ 'replaceMenuProvider' ],
35567 replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
35568 };
35569
35570 var max$2 = Math.max,
35571 min$1 = Math.min;
35572
35573 var DEFAULT_CHILD_BOX_PADDING = 20;
35574
35575
35576 /**
35577 * Substract a TRBL from another
35578 *
35579 * @param {TRBL} trblA
35580 * @param {TRBL} trblB
35581 *
35582 * @return {TRBL}
35583 */
35584 function substractTRBL(trblA, trblB) {
35585 return {
35586 top: trblA.top - trblB.top,
35587 right: trblA.right - trblB.right,
35588 bottom: trblA.bottom - trblB.bottom,
35589 left: trblA.left - trblB.left
35590 };
35591 }
35592
35593 /**
35594 * Resize the given bounds by the specified delta from a given anchor point.
35595 *
35596 * @param {Bounds} bounds the bounding box that should be resized
35597 * @param {string} direction in which the element is resized (nw, ne, se, sw)
35598 * @param {Point} delta of the resize operation
35599 *
35600 * @return {Bounds} resized bounding box
35601 */
35602 function resizeBounds(bounds, direction, delta) {
35603 var dx = delta.x,
35604 dy = delta.y;
35605
35606 var newBounds = {
35607 x: bounds.x,
35608 y: bounds.y,
35609 width: bounds.width,
35610 height: bounds.height
35611 };
35612
35613 if (direction.indexOf('n') !== -1) {
35614 newBounds.y = bounds.y + dy;
35615 newBounds.height = bounds.height - dy;
35616 } else if (direction.indexOf('s') !== -1) {
35617 newBounds.height = bounds.height + dy;
35618 }
35619
35620 if (direction.indexOf('e') !== -1) {
35621 newBounds.width = bounds.width + dx;
35622 } else if (direction.indexOf('w') !== -1) {
35623 newBounds.x = bounds.x + dx;
35624 newBounds.width = bounds.width - dx;
35625 }
35626
35627 return newBounds;
35628 }
35629
35630
35631 /**
35632 * Resize the given bounds by applying the passed
35633 * { top, right, bottom, left } delta.
35634 *
35635 * @param {Bounds} bounds
35636 * @param {TRBL} trblResize
35637 *
35638 * @return {Bounds}
35639 */
35640 function resizeTRBL(bounds, resize) {
35641 return {
35642 x: bounds.x + (resize.left || 0),
35643 y: bounds.y + (resize.top || 0),
35644 width: bounds.width - (resize.left || 0) + (resize.right || 0),
35645 height: bounds.height - (resize.top || 0) + (resize.bottom || 0)
35646 };
35647 }
35648
35649
35650 function applyConstraints(attr, trbl, resizeConstraints) {
35651
35652 var value = trbl[attr],
35653 minValue = resizeConstraints.min && resizeConstraints.min[attr],
35654 maxValue = resizeConstraints.max && resizeConstraints.max[attr];
35655
35656 if (isNumber(minValue)) {
35657 value = (/top|left/.test(attr) ? min$1 : max$2)(value, minValue);
35658 }
35659
35660 if (isNumber(maxValue)) {
35661 value = (/top|left/.test(attr) ? max$2 : min$1)(value, maxValue);
35662 }
35663
35664 return value;
35665 }
35666
35667 function ensureConstraints$1(currentBounds, resizeConstraints) {
35668
35669 if (!resizeConstraints) {
35670 return currentBounds;
35671 }
35672
35673 var currentTrbl = asTRBL(currentBounds);
35674
35675 return asBounds({
35676 top: applyConstraints('top', currentTrbl, resizeConstraints),
35677 right: applyConstraints('right', currentTrbl, resizeConstraints),
35678 bottom: applyConstraints('bottom', currentTrbl, resizeConstraints),
35679 left: applyConstraints('left', currentTrbl, resizeConstraints)
35680 });
35681 }
35682
35683
35684 function getMinResizeBounds(direction, currentBounds, minDimensions, childrenBounds) {
35685
35686 var currentBox = asTRBL(currentBounds);
35687
35688 var minBox = {
35689 top: /n/.test(direction) ? currentBox.bottom - minDimensions.height : currentBox.top,
35690 left: /w/.test(direction) ? currentBox.right - minDimensions.width : currentBox.left,
35691 bottom: /s/.test(direction) ? currentBox.top + minDimensions.height : currentBox.bottom,
35692 right: /e/.test(direction) ? currentBox.left + minDimensions.width : currentBox.right
35693 };
35694
35695 var childrenBox = childrenBounds ? asTRBL(childrenBounds) : minBox;
35696
35697 var combinedBox = {
35698 top: min$1(minBox.top, childrenBox.top),
35699 left: min$1(minBox.left, childrenBox.left),
35700 bottom: max$2(minBox.bottom, childrenBox.bottom),
35701 right: max$2(minBox.right, childrenBox.right)
35702 };
35703
35704 return asBounds(combinedBox);
35705 }
35706
35707 function asPadding(mayBePadding, defaultValue) {
35708 if (typeof mayBePadding !== 'undefined') {
35709 return mayBePadding;
35710 } else {
35711 return DEFAULT_CHILD_BOX_PADDING;
35712 }
35713 }
35714
35715 function addPadding(bbox, padding) {
35716 var left, right, top, bottom;
35717
35718 if (typeof padding === 'object') {
35719 left = asPadding(padding.left);
35720 right = asPadding(padding.right);
35721 top = asPadding(padding.top);
35722 bottom = asPadding(padding.bottom);
35723 } else {
35724 left = right = top = bottom = asPadding(padding);
35725 }
35726
35727 return {
35728 x: bbox.x - left,
35729 y: bbox.y - top,
35730 width: bbox.width + left + right,
35731 height: bbox.height + top + bottom
35732 };
35733 }
35734
35735
35736 /**
35737 * Is the given element part of the resize
35738 * targets min boundary box?
35739 *
35740 * This is the default implementation which excludes
35741 * connections and labels.
35742 *
35743 * @param {djs.model.Base} element
35744 */
35745 function isBBoxChild(element) {
35746
35747 // exclude connections
35748 if (element.waypoints) {
35749 return false;
35750 }
35751
35752 // exclude labels
35753 if (element.type === 'label') {
35754 return false;
35755 }
35756
35757 return true;
35758 }
35759
35760 /**
35761 * Return children bounding computed from a shapes children
35762 * or a list of prefiltered children.
35763 *
35764 * @param {djs.model.Shape|Array<djs.model.Shape>} shapeOrChildren
35765 * @param {number|Object} padding
35766 *
35767 * @return {Bounds}
35768 */
35769 function computeChildrenBBox(shapeOrChildren, padding) {
35770
35771 var elements;
35772
35773 // compute based on shape
35774 if (shapeOrChildren.length === undefined) {
35775
35776 // grab all the children that are part of the
35777 // parents children box
35778 elements = filter(shapeOrChildren.children, isBBoxChild);
35779
35780 } else {
35781 elements = shapeOrChildren;
35782 }
35783
35784 if (elements.length) {
35785 return addPadding(getBBox(elements), padding);
35786 }
35787 }
35788
35789 var abs$3 = Math.abs;
35790
35791
35792 function getTRBLResize(oldBounds, newBounds) {
35793 return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds));
35794 }
35795
35796
35797 var LANE_PARENTS = [
35798 'bpmn:Participant',
35799 'bpmn:Process',
35800 'bpmn:SubProcess'
35801 ];
35802
35803 var LANE_INDENTATION = 30;
35804
35805
35806 /**
35807 * Collect all lane shapes in the given paren
35808 *
35809 * @param {djs.model.Shape} shape
35810 * @param {Array<djs.model.Base>} [collectedShapes]
35811 *
35812 * @return {Array<djs.model.Base>}
35813 */
35814 function collectLanes(shape, collectedShapes) {
35815
35816 collectedShapes = collectedShapes || [];
35817
35818 shape.children.filter(function(s) {
35819 if (is$1(s, 'bpmn:Lane')) {
35820 collectLanes(s, collectedShapes);
35821
35822 collectedShapes.push(s);
35823 }
35824 });
35825
35826 return collectedShapes;
35827 }
35828
35829
35830 /**
35831 * Return the lane children of the given element.
35832 *
35833 * @param {djs.model.Shape} shape
35834 *
35835 * @return {Array<djs.model.Shape>}
35836 */
35837 function getChildLanes(shape) {
35838 return shape.children.filter(function(c) {
35839 return is$1(c, 'bpmn:Lane');
35840 });
35841 }
35842
35843
35844 /**
35845 * Return the root element containing the given lane shape
35846 *
35847 * @param {djs.model.Shape} shape
35848 *
35849 * @return {djs.model.Shape}
35850 */
35851 function getLanesRoot(shape) {
35852 return getParent$1(shape, LANE_PARENTS) || shape;
35853 }
35854
35855
35856 /**
35857 * Compute the required resize operations for lanes
35858 * adjacent to the given shape, assuming it will be
35859 * resized to the given new bounds.
35860 *
35861 * @param {djs.model.Shape} shape
35862 * @param {Bounds} newBounds
35863 *
35864 * @return {Array<Object>}
35865 */
35866 function computeLanesResize(shape, newBounds) {
35867
35868 var rootElement = getLanesRoot(shape);
35869
35870 var initialShapes = is$1(rootElement, 'bpmn:Process') ? [] : [ rootElement ];
35871
35872 var allLanes = collectLanes(rootElement, initialShapes),
35873 shapeTrbl = asTRBL(shape),
35874 shapeNewTrbl = asTRBL(newBounds),
35875 trblResize = getTRBLResize(shape, newBounds),
35876 resizeNeeded = [];
35877
35878 allLanes.forEach(function(other) {
35879
35880 if (other === shape) {
35881 return;
35882 }
35883
35884 var topResize = 0,
35885 rightResize = trblResize.right,
35886 bottomResize = 0,
35887 leftResize = trblResize.left;
35888
35889 var otherTrbl = asTRBL(other);
35890
35891 if (trblResize.top) {
35892 if (abs$3(otherTrbl.bottom - shapeTrbl.top) < 10) {
35893 bottomResize = shapeNewTrbl.top - otherTrbl.bottom;
35894 }
35895
35896 if (abs$3(otherTrbl.top - shapeTrbl.top) < 5) {
35897 topResize = shapeNewTrbl.top - otherTrbl.top;
35898 }
35899 }
35900
35901 if (trblResize.bottom) {
35902 if (abs$3(otherTrbl.top - shapeTrbl.bottom) < 10) {
35903 topResize = shapeNewTrbl.bottom - otherTrbl.top;
35904 }
35905
35906 if (abs$3(otherTrbl.bottom - shapeTrbl.bottom) < 5) {
35907 bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom;
35908 }
35909 }
35910
35911 if (topResize || rightResize || bottomResize || leftResize) {
35912
35913 resizeNeeded.push({
35914 shape: other,
35915 newBounds: resizeTRBL(other, {
35916 top: topResize,
35917 right: rightResize,
35918 bottom: bottomResize,
35919 left: leftResize
35920 })
35921 });
35922 }
35923
35924 });
35925
35926 return resizeNeeded;
35927 }
35928
35929 /**
35930 * A provider for BPMN 2.0 elements context pad
35931 */
35932 function ContextPadProvider(
35933 config, injector, eventBus,
35934 contextPad, modeling, elementFactory,
35935 connect, create, popupMenu,
35936 canvas, rules, translate) {
35937
35938 config = config || {};
35939
35940 contextPad.registerProvider(this);
35941
35942 this._contextPad = contextPad;
35943
35944 this._modeling = modeling;
35945
35946 this._elementFactory = elementFactory;
35947 this._connect = connect;
35948 this._create = create;
35949 this._popupMenu = popupMenu;
35950 this._canvas = canvas;
35951 this._rules = rules;
35952 this._translate = translate;
35953
35954 if (config.autoPlace !== false) {
35955 this._autoPlace = injector.get('autoPlace', false);
35956 }
35957
35958 eventBus.on('create.end', 250, function(event) {
35959 var context = event.context,
35960 shape = context.shape;
35961
35962 if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
35963 return;
35964 }
35965
35966 var entries = contextPad.getEntries(shape);
35967
35968 if (entries.replace) {
35969 entries.replace.action.click(event, shape);
35970 }
35971 });
35972 }
35973
35974 ContextPadProvider.$inject = [
35975 'config.contextPad',
35976 'injector',
35977 'eventBus',
35978 'contextPad',
35979 'modeling',
35980 'elementFactory',
35981 'connect',
35982 'create',
35983 'popupMenu',
35984 'canvas',
35985 'rules',
35986 'translate'
35987 ];
35988
35989
35990 ContextPadProvider.prototype.getContextPadEntries = function(element) {
35991
35992 var contextPad = this._contextPad,
35993 modeling = this._modeling,
35994
35995 elementFactory = this._elementFactory,
35996 connect = this._connect,
35997 create = this._create,
35998 popupMenu = this._popupMenu,
35999 canvas = this._canvas,
36000 rules = this._rules,
36001 autoPlace = this._autoPlace,
36002 translate = this._translate;
36003
36004 var actions = {};
36005
36006 if (element.type === 'label') {
36007 return actions;
36008 }
36009
36010 var businessObject = element.businessObject;
36011
36012 function startConnect(event, element) {
36013 connect.start(event, element);
36014 }
36015
36016 function removeElement(e) {
36017 modeling.removeElements([ element ]);
36018 }
36019
36020 function getReplaceMenuPosition(element) {
36021
36022 var Y_OFFSET = 5;
36023
36024 var diagramContainer = canvas.getContainer(),
36025 pad = contextPad.getPad(element).html;
36026
36027 var diagramRect = diagramContainer.getBoundingClientRect(),
36028 padRect = pad.getBoundingClientRect();
36029
36030 var top = padRect.top - diagramRect.top;
36031 var left = padRect.left - diagramRect.left;
36032
36033 var pos = {
36034 x: left,
36035 y: top + padRect.height + Y_OFFSET
36036 };
36037
36038 return pos;
36039 }
36040
36041
36042 /**
36043 * Create an append action
36044 *
36045 * @param {string} type
36046 * @param {string} className
36047 * @param {string} [title]
36048 * @param {Object} [options]
36049 *
36050 * @return {Object} descriptor
36051 */
36052 function appendAction(type, className, title, options) {
36053
36054 if (typeof title !== 'string') {
36055 options = title;
36056 title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
36057 }
36058
36059 function appendStart(event, element) {
36060
36061 var shape = elementFactory.createShape(assign({ type: type }, options));
36062 create.start(event, shape, {
36063 source: element
36064 });
36065 }
36066
36067
36068 var append = autoPlace ? function(event, element) {
36069 var shape = elementFactory.createShape(assign({ type: type }, options));
36070
36071 autoPlace.append(element, shape);
36072 } : appendStart;
36073
36074
36075 return {
36076 group: 'model',
36077 className: className,
36078 title: title,
36079 action: {
36080 dragstart: appendStart,
36081 click: append
36082 }
36083 };
36084 }
36085
36086 function splitLaneHandler(count) {
36087
36088 return function(event, element) {
36089
36090 // actual split
36091 modeling.splitLane(element, count);
36092
36093 // refresh context pad after split to
36094 // get rid of split icons
36095 contextPad.open(element, true);
36096 };
36097 }
36098
36099
36100 if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(businessObject)) {
36101
36102 var childLanes = getChildLanes(element);
36103
36104 assign(actions, {
36105 'lane-insert-above': {
36106 group: 'lane-insert-above',
36107 className: 'bpmn-icon-lane-insert-above',
36108 title: translate('Add Lane above'),
36109 action: {
36110 click: function(event, element) {
36111 modeling.addLane(element, 'top');
36112 }
36113 }
36114 }
36115 });
36116
36117 if (childLanes.length < 2) {
36118
36119 if (element.height >= 120) {
36120 assign(actions, {
36121 'lane-divide-two': {
36122 group: 'lane-divide',
36123 className: 'bpmn-icon-lane-divide-two',
36124 title: translate('Divide into two Lanes'),
36125 action: {
36126 click: splitLaneHandler(2)
36127 }
36128 }
36129 });
36130 }
36131
36132 if (element.height >= 180) {
36133 assign(actions, {
36134 'lane-divide-three': {
36135 group: 'lane-divide',
36136 className: 'bpmn-icon-lane-divide-three',
36137 title: translate('Divide into three Lanes'),
36138 action: {
36139 click: splitLaneHandler(3)
36140 }
36141 }
36142 });
36143 }
36144 }
36145
36146 assign(actions, {
36147 'lane-insert-below': {
36148 group: 'lane-insert-below',
36149 className: 'bpmn-icon-lane-insert-below',
36150 title: translate('Add Lane below'),
36151 action: {
36152 click: function(event, element) {
36153 modeling.addLane(element, 'bottom');
36154 }
36155 }
36156 }
36157 });
36158
36159 }
36160
36161 if (is$1(businessObject, 'bpmn:FlowNode')) {
36162
36163 if (is$1(businessObject, 'bpmn:EventBasedGateway')) {
36164
36165 assign(actions, {
36166 'append.receive-task': appendAction(
36167 'bpmn:ReceiveTask',
36168 'bpmn-icon-receive-task',
36169 translate('Append ReceiveTask')
36170 ),
36171 'append.message-intermediate-event': appendAction(
36172 'bpmn:IntermediateCatchEvent',
36173 'bpmn-icon-intermediate-event-catch-message',
36174 translate('Append MessageIntermediateCatchEvent'),
36175 { eventDefinitionType: 'bpmn:MessageEventDefinition' }
36176 ),
36177 'append.timer-intermediate-event': appendAction(
36178 'bpmn:IntermediateCatchEvent',
36179 'bpmn-icon-intermediate-event-catch-timer',
36180 translate('Append TimerIntermediateCatchEvent'),
36181 { eventDefinitionType: 'bpmn:TimerEventDefinition' }
36182 ),
36183 'append.condition-intermediate-event': appendAction(
36184 'bpmn:IntermediateCatchEvent',
36185 'bpmn-icon-intermediate-event-catch-condition',
36186 translate('Append ConditionIntermediateCatchEvent'),
36187 { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
36188 ),
36189 'append.signal-intermediate-event': appendAction(
36190 'bpmn:IntermediateCatchEvent',
36191 'bpmn-icon-intermediate-event-catch-signal',
36192 translate('Append SignalIntermediateCatchEvent'),
36193 { eventDefinitionType: 'bpmn:SignalEventDefinition' }
36194 )
36195 });
36196 } else
36197
36198 if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
36199
36200 assign(actions, {
36201 'append.compensation-activity':
36202 appendAction(
36203 'bpmn:Task',
36204 'bpmn-icon-task',
36205 translate('Append compensation activity'),
36206 {
36207 isForCompensation: true
36208 }
36209 )
36210 });
36211 } else
36212
36213 if (!is$1(businessObject, 'bpmn:EndEvent') &&
36214 !businessObject.isForCompensation &&
36215 !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
36216 !isEventSubProcess(businessObject)) {
36217
36218 assign(actions, {
36219 'append.end-event': appendAction(
36220 'bpmn:EndEvent',
36221 'bpmn-icon-end-event-none',
36222 translate('Append EndEvent')
36223 ),
36224 'append.gateway': appendAction(
36225 'bpmn:ExclusiveGateway',
36226 'bpmn-icon-gateway-none',
36227 translate('Append Gateway')
36228 ),
36229 'append.append-task': appendAction(
36230 'bpmn:Task',
36231 'bpmn-icon-task',
36232 translate('Append Task')
36233 ),
36234 'append.intermediate-event': appendAction(
36235 'bpmn:IntermediateThrowEvent',
36236 'bpmn-icon-intermediate-event-none',
36237 translate('Append Intermediate/Boundary Event')
36238 )
36239 });
36240 }
36241 }
36242
36243 if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
36244
36245 // Replace menu entry
36246 assign(actions, {
36247 'replace': {
36248 group: 'edit',
36249 className: 'bpmn-icon-screw-wrench',
36250 title: translate('Change type'),
36251 action: {
36252 click: function(event, element) {
36253
36254 var position = assign(getReplaceMenuPosition(element), {
36255 cursor: { x: event.x, y: event.y }
36256 });
36257
36258 popupMenu.open(element, 'bpmn-replace', position);
36259 }
36260 }
36261 }
36262 });
36263 }
36264
36265 if (
36266 isAny(businessObject, [
36267 'bpmn:FlowNode',
36268 'bpmn:InteractionNode',
36269 'bpmn:DataObjectReference',
36270 'bpmn:DataStoreReference',
36271 ])
36272 ) {
36273 assign(actions, {
36274 'append.text-annotation': appendAction(
36275 'bpmn:TextAnnotation',
36276 'bpmn-icon-text-annotation'
36277 ),
36278
36279 'connect': {
36280 group: 'connect',
36281 className: 'bpmn-icon-connection-multi',
36282 title: translate(
36283 'Connect using ' +
36284 (businessObject.isForCompensation
36285 ? ''
36286 : 'Sequence/MessageFlow or ') +
36287 'Association'
36288 ),
36289 action: {
36290 click: startConnect,
36291 dragstart: startConnect,
36292 },
36293 },
36294 });
36295 }
36296
36297 if (is$1(businessObject, 'bpmn:TextAnnotation')) {
36298 assign(actions, {
36299 'connect': {
36300 group: 'connect',
36301 className: 'bpmn-icon-connection-multi',
36302 title: translate('Connect using Association'),
36303 action: {
36304 click: startConnect,
36305 dragstart: startConnect,
36306 },
36307 },
36308 });
36309 }
36310
36311 if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
36312 assign(actions, {
36313 'connect': {
36314 group: 'connect',
36315 className: 'bpmn-icon-connection-multi',
36316 title: translate('Connect using DataInputAssociation'),
36317 action: {
36318 click: startConnect,
36319 dragstart: startConnect
36320 }
36321 }
36322 });
36323 }
36324
36325 if (is$1(businessObject, 'bpmn:Group')) {
36326 assign(actions, {
36327 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
36328 });
36329 }
36330
36331 // delete element entry, only show if allowed by rules
36332 var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
36333
36334 if (isArray(deleteAllowed)) {
36335
36336 // was the element returned as a deletion candidate?
36337 deleteAllowed = deleteAllowed[0] === element;
36338 }
36339
36340 if (deleteAllowed) {
36341 assign(actions, {
36342 'delete': {
36343 group: 'edit',
36344 className: 'bpmn-icon-trash',
36345 title: translate('Remove'),
36346 action: {
36347 click: removeElement
36348 }
36349 }
36350 });
36351 }
36352
36353 return actions;
36354 };
36355
36356
36357 // helpers /////////
36358
36359 function isEventType(eventBo, type, definition) {
36360
36361 var isType = eventBo.$instanceOf(type);
36362 var isDefinition = false;
36363
36364 var definitions = eventBo.eventDefinitions || [];
36365 forEach(definitions, function(def) {
36366 if (def.$type === definition) {
36367 isDefinition = true;
36368 }
36369 });
36370
36371 return isType && isDefinition;
36372 }
36373
36374 var ContextPadModule$1 = {
36375 __depends__: [
36376 DirectEditingModule,
36377 ContextPadModule,
36378 SelectionModule,
36379 ConnectModule,
36380 CreateModule,
36381 PopupMenuModule$1
36382 ],
36383 __init__: [ 'contextPadProvider' ],
36384 contextPadProvider: [ 'type', ContextPadProvider ]
36385 };
36386
36387 var AXIS_DIMENSIONS = {
36388 horizontal: [ 'x', 'width' ],
36389 vertical: [ 'y', 'height' ]
36390 };
36391
36392 var THRESHOLD$1 = 5;
36393
36394
36395 /**
36396 * Groups and filters elements and then trigger even distribution.
36397 */
36398 function DistributeElements(modeling) {
36399 this._modeling = modeling;
36400
36401 this._filters = [];
36402
36403 // register filter for filtering big elements
36404 this.registerFilter(function(elements, axis, dimension) {
36405 var elementsSize = 0,
36406 numOfShapes = 0,
36407 avgDimension;
36408
36409 forEach(elements, function(element) {
36410 if (element.waypoints || element.labelTarget) {
36411 return;
36412 }
36413
36414 elementsSize += element[dimension];
36415
36416 numOfShapes += 1;
36417 });
36418
36419 avgDimension = Math.round(elementsSize / numOfShapes);
36420
36421 return filter(elements, function(element) {
36422 return element[dimension] < (avgDimension + 50);
36423 });
36424 });
36425
36426 }
36427
36428 DistributeElements.$inject = [ 'modeling' ];
36429
36430
36431 /**
36432 * Registers filter functions that allow external parties to filter
36433 * out certain elements.
36434 *
36435 * @param {Function} filterFn
36436 */
36437 DistributeElements.prototype.registerFilter = function(filterFn) {
36438 if (typeof filterFn !== 'function') {
36439 throw new Error('the filter has to be a function');
36440 }
36441
36442 this._filters.push(filterFn);
36443 };
36444
36445 /**
36446 * Distributes the elements with a given orientation
36447 *
36448 * @param {Array} elements [description]
36449 * @param {string} orientation [description]
36450 */
36451 DistributeElements.prototype.trigger = function(elements, orientation) {
36452 var modeling = this._modeling;
36453
36454 var groups,
36455 distributableElements;
36456
36457 if (elements.length < 3) {
36458 return;
36459 }
36460
36461 this._setOrientation(orientation);
36462
36463 distributableElements = this._filterElements(elements);
36464
36465 groups = this._createGroups(distributableElements);
36466
36467 // nothing to distribute
36468 if (groups.length <= 2) {
36469 return;
36470 }
36471
36472 modeling.distributeElements(groups, this._axis, this._dimension);
36473
36474 return groups;
36475 };
36476
36477 /**
36478 * Filters the elements with provided filters by external parties
36479 *
36480 * @param {Array[Elements]} elements
36481 *
36482 * @return {Array[Elements]}
36483 */
36484 DistributeElements.prototype._filterElements = function(elements) {
36485 var filters = this._filters,
36486 axis = this._axis,
36487 dimension = this._dimension,
36488 distributableElements = [].concat(elements);
36489
36490 if (!filters.length) {
36491 return elements;
36492 }
36493
36494 forEach(filters, function(filterFn) {
36495 distributableElements = filterFn(distributableElements, axis, dimension);
36496 });
36497
36498 return distributableElements;
36499 };
36500
36501
36502 /**
36503 * Create range (min, max) groups. Also tries to group elements
36504 * together that share the same range.
36505 *
36506 * @example
36507 * var distributableElements = [
36508 * {
36509 * range: {
36510 * min: 100,
36511 * max: 200
36512 * },
36513 * elements: [ { id: 'shape1', .. }]
36514 * }
36515 * ]
36516 *
36517 * @param {Array} elements
36518 *
36519 * @return {Array[Objects]}
36520 */
36521 DistributeElements.prototype._createGroups = function(elements) {
36522 var rangeGroups = [],
36523 self = this,
36524 axis = this._axis,
36525 dimension = this._dimension;
36526
36527 if (!axis) {
36528 throw new Error('must have a defined "axis" and "dimension"');
36529 }
36530
36531 // sort by 'left->right' or 'top->bottom'
36532 var sortedElements = sortBy(elements, axis);
36533
36534 forEach(sortedElements, function(element, idx) {
36535 var elementRange = self._findRange(element, axis, dimension),
36536 range;
36537
36538 var previous = rangeGroups[rangeGroups.length - 1];
36539
36540 if (previous && self._hasIntersection(previous.range, elementRange)) {
36541 rangeGroups[rangeGroups.length - 1].elements.push(element);
36542 } else {
36543 range = { range: elementRange, elements: [ element ] };
36544
36545 rangeGroups.push(range);
36546 }
36547 });
36548
36549 return rangeGroups;
36550 };
36551
36552
36553 /**
36554 * Maps a direction to the according axis and dimension
36555 *
36556 * @param {string} direction 'horizontal' or 'vertical'
36557 */
36558 DistributeElements.prototype._setOrientation = function(direction) {
36559 var orientation = AXIS_DIMENSIONS[direction];
36560
36561 this._axis = orientation[0];
36562 this._dimension = orientation[1];
36563 };
36564
36565
36566 /**
36567 * Checks if the two ranges intercept each other
36568 *
36569 * @param {Object} rangeA {min, max}
36570 * @param {Object} rangeB {min, max}
36571 *
36572 * @return {boolean}
36573 */
36574 DistributeElements.prototype._hasIntersection = function(rangeA, rangeB) {
36575 return Math.max(rangeA.min, rangeA.max) >= Math.min(rangeB.min, rangeB.max) &&
36576 Math.min(rangeA.min, rangeA.max) <= Math.max(rangeB.min, rangeB.max);
36577 };
36578
36579
36580 /**
36581 * Returns the min and max values for an element
36582 *
36583 * @param {[type]} element [description]
36584 * @param {[type]} axis [description]
36585 * @param {[type]} dimension [description]
36586 *
36587 * @return {[type]} [description]
36588 */
36589 DistributeElements.prototype._findRange = function(element) {
36590 var axis = element[this._axis],
36591 dimension = element[this._dimension];
36592
36593 return {
36594 min: axis + THRESHOLD$1,
36595 max: axis + dimension - THRESHOLD$1
36596 };
36597 };
36598
36599 var DistributeElementsModule = {
36600 __init__: [ 'distributeElements' ],
36601 distributeElements: [ 'type', DistributeElements ]
36602 };
36603
36604 /**
36605 * Registers element exclude filters for elements that
36606 * currently do not support distribution.
36607 */
36608 function BpmnDistributeElements(distributeElements) {
36609
36610 distributeElements.registerFilter(function(elements) {
36611 return filter(elements, function(element) {
36612 var cannotDistribute = isAny(element, [
36613 'bpmn:Association',
36614 'bpmn:BoundaryEvent',
36615 'bpmn:DataInputAssociation',
36616 'bpmn:DataOutputAssociation',
36617 'bpmn:Lane',
36618 'bpmn:MessageFlow',
36619 'bpmn:Participant',
36620 'bpmn:SequenceFlow',
36621 'bpmn:TextAnnotation'
36622 ]);
36623
36624 return !(element.labelTarget || cannotDistribute);
36625 });
36626 });
36627 }
36628
36629 BpmnDistributeElements.$inject = [ 'distributeElements' ];
36630
36631 var DistributeElementsModule$1 = {
36632 __depends__: [
36633 DistributeElementsModule
36634 ],
36635 __init__: [ 'bpmnDistributeElements' ],
36636 bpmnDistributeElements: [ 'type', BpmnDistributeElements ]
36637 };
36638
36639 var NOT_REGISTERED_ERROR = 'is not a registered action',
36640 IS_REGISTERED_ERROR = 'is already registered';
36641
36642
36643 /**
36644 * An interface that provides access to modeling actions by decoupling
36645 * the one who requests the action to be triggered and the trigger itself.
36646 *
36647 * It's possible to add new actions by registering them with ´registerAction´
36648 * and likewise unregister existing ones with ´unregisterAction´.
36649 *
36650 *
36651 * ## Life-Cycle and configuration
36652 *
36653 * The editor actions will wait for diagram initialization before
36654 * registering default actions _and_ firing an `editorActions.init` event.
36655 *
36656 * Interested parties may listen to the `editorActions.init` event with
36657 * low priority to check, which actions got registered. Other components
36658 * may use the event to register their own actions via `registerAction`.
36659 *
36660 * @param {EventBus} eventBus
36661 * @param {Injector} injector
36662 */
36663 function EditorActions(eventBus, injector) {
36664
36665 // initialize actions
36666 this._actions = {};
36667
36668 var self = this;
36669
36670 eventBus.on('diagram.init', function() {
36671
36672 // all diagram modules got loaded; check which ones
36673 // are available and register the respective default actions
36674 self._registerDefaultActions(injector);
36675
36676 // ask interested parties to register available editor
36677 // actions on diagram initialization
36678 eventBus.fire('editorActions.init', {
36679 editorActions: self
36680 });
36681 });
36682
36683 }
36684
36685 EditorActions.$inject = [
36686 'eventBus',
36687 'injector'
36688 ];
36689
36690 /**
36691 * Register default actions.
36692 *
36693 * @param {Injector} injector
36694 */
36695 EditorActions.prototype._registerDefaultActions = function(injector) {
36696
36697 // (1) retrieve optional components to integrate with
36698
36699 var commandStack = injector.get('commandStack', false);
36700 var modeling = injector.get('modeling', false);
36701 var selection = injector.get('selection', false);
36702 var zoomScroll = injector.get('zoomScroll', false);
36703 var copyPaste = injector.get('copyPaste', false);
36704 var canvas = injector.get('canvas', false);
36705 var rules = injector.get('rules', false);
36706 var keyboardMove = injector.get('keyboardMove', false);
36707 var keyboardMoveSelection = injector.get('keyboardMoveSelection', false);
36708
36709 // (2) check components and register actions
36710
36711 if (commandStack) {
36712 this.register('undo', function() {
36713 commandStack.undo();
36714 });
36715
36716 this.register('redo', function() {
36717 commandStack.redo();
36718 });
36719 }
36720
36721 if (copyPaste && selection) {
36722 this.register('copy', function() {
36723 var selectedElements = selection.get();
36724
36725 copyPaste.copy(selectedElements);
36726 });
36727 }
36728
36729 if (copyPaste) {
36730 this.register('paste', function() {
36731 copyPaste.paste();
36732 });
36733 }
36734
36735 if (zoomScroll) {
36736 this.register('stepZoom', function(opts) {
36737 zoomScroll.stepZoom(opts.value);
36738 });
36739 }
36740
36741 if (canvas) {
36742 this.register('zoom', function(opts) {
36743 canvas.zoom(opts.value);
36744 });
36745 }
36746
36747 if (modeling && selection && rules) {
36748 this.register('removeSelection', function() {
36749
36750 var selectedElements = selection.get();
36751
36752 if (!selectedElements.length) {
36753 return;
36754 }
36755
36756 var allowed = rules.allowed('elements.delete', { elements: selectedElements }),
36757 removableElements;
36758
36759 if (allowed === false) {
36760 return;
36761 }
36762 else if (isArray(allowed)) {
36763 removableElements = allowed;
36764 }
36765 else {
36766 removableElements = selectedElements;
36767 }
36768
36769 if (removableElements.length) {
36770 modeling.removeElements(removableElements.slice());
36771 }
36772 });
36773 }
36774
36775 if (keyboardMove) {
36776 this.register('moveCanvas', function(opts) {
36777 keyboardMove.moveCanvas(opts);
36778 });
36779 }
36780
36781 if (keyboardMoveSelection) {
36782 this.register('moveSelection', function(opts) {
36783 keyboardMoveSelection.moveSelection(opts.direction, opts.accelerated);
36784 });
36785 }
36786
36787 };
36788
36789
36790 /**
36791 * Triggers a registered action
36792 *
36793 * @param {string} action
36794 * @param {Object} opts
36795 *
36796 * @return {Unknown} Returns what the registered listener returns
36797 */
36798 EditorActions.prototype.trigger = function(action, opts) {
36799 if (!this._actions[action]) {
36800 throw error$2(action, NOT_REGISTERED_ERROR);
36801 }
36802
36803 return this._actions[action](opts);
36804 };
36805
36806
36807 /**
36808 * Registers a collections of actions.
36809 * The key of the object will be the name of the action.
36810 *
36811 * @example
36812 * ´´´
36813 * var actions = {
36814 * spaceTool: function() {
36815 * spaceTool.activateSelection();
36816 * },
36817 * lassoTool: function() {
36818 * lassoTool.activateSelection();
36819 * }
36820 * ];
36821 *
36822 * editorActions.register(actions);
36823 *
36824 * editorActions.isRegistered('spaceTool'); // true
36825 * ´´´
36826 *
36827 * @param {Object} actions
36828 */
36829 EditorActions.prototype.register = function(actions, listener) {
36830 var self = this;
36831
36832 if (typeof actions === 'string') {
36833 return this._registerAction(actions, listener);
36834 }
36835
36836 forEach(actions, function(listener, action) {
36837 self._registerAction(action, listener);
36838 });
36839 };
36840
36841 /**
36842 * Registers a listener to an action key
36843 *
36844 * @param {string} action
36845 * @param {Function} listener
36846 */
36847 EditorActions.prototype._registerAction = function(action, listener) {
36848 if (this.isRegistered(action)) {
36849 throw error$2(action, IS_REGISTERED_ERROR);
36850 }
36851
36852 this._actions[action] = listener;
36853 };
36854
36855 /**
36856 * Unregister an existing action
36857 *
36858 * @param {string} action
36859 */
36860 EditorActions.prototype.unregister = function(action) {
36861 if (!this.isRegistered(action)) {
36862 throw error$2(action, NOT_REGISTERED_ERROR);
36863 }
36864
36865 this._actions[action] = undefined;
36866 };
36867
36868 /**
36869 * Returns the number of actions that are currently registered
36870 *
36871 * @return {number}
36872 */
36873 EditorActions.prototype.getActions = function() {
36874 return Object.keys(this._actions);
36875 };
36876
36877 /**
36878 * Checks wether the given action is registered
36879 *
36880 * @param {string} action
36881 *
36882 * @return {boolean}
36883 */
36884 EditorActions.prototype.isRegistered = function(action) {
36885 return !!this._actions[action];
36886 };
36887
36888
36889 function error$2(action, message) {
36890 return new Error(action + ' ' + message);
36891 }
36892
36893 var EditorActionsModule = {
36894 __init__: [ 'editorActions' ],
36895 editorActions: [ 'type', EditorActions ]
36896 };
36897
36898 /**
36899 * Registers and executes BPMN specific editor actions.
36900 *
36901 * @param {Injector} injector
36902 */
36903 function BpmnEditorActions(injector) {
36904 injector.invoke(EditorActions, this);
36905 }
36906
36907 inherits_browser(BpmnEditorActions, EditorActions);
36908
36909 BpmnEditorActions.$inject = [
36910 'injector'
36911 ];
36912
36913 /**
36914 * Register default actions.
36915 *
36916 * @param {Injector} injector
36917 */
36918 BpmnEditorActions.prototype._registerDefaultActions = function(injector) {
36919
36920 // (0) invoke super method
36921
36922 EditorActions.prototype._registerDefaultActions.call(this, injector);
36923
36924 // (1) retrieve optional components to integrate with
36925
36926 var canvas = injector.get('canvas', false);
36927 var elementRegistry = injector.get('elementRegistry', false);
36928 var selection = injector.get('selection', false);
36929 var spaceTool = injector.get('spaceTool', false);
36930 var lassoTool = injector.get('lassoTool', false);
36931 var handTool = injector.get('handTool', false);
36932 var globalConnect = injector.get('globalConnect', false);
36933 var distributeElements = injector.get('distributeElements', false);
36934 var alignElements = injector.get('alignElements', false);
36935 var directEditing = injector.get('directEditing', false);
36936 var searchPad = injector.get('searchPad', false);
36937 var modeling = injector.get('modeling', false);
36938
36939 // (2) check components and register actions
36940
36941 if (canvas && elementRegistry && selection) {
36942 this._registerAction('selectElements', function() {
36943
36944 // select all elements except for the invisible
36945 // root element
36946 var rootElement = canvas.getRootElement();
36947
36948 var elements = elementRegistry.filter(function(element) {
36949 return element !== rootElement;
36950 });
36951
36952 selection.select(elements);
36953
36954 return elements;
36955 });
36956 }
36957
36958 if (spaceTool) {
36959 this._registerAction('spaceTool', function() {
36960 spaceTool.toggle();
36961 });
36962 }
36963
36964 if (lassoTool) {
36965 this._registerAction('lassoTool', function() {
36966 lassoTool.toggle();
36967 });
36968 }
36969
36970 if (handTool) {
36971 this._registerAction('handTool', function() {
36972 handTool.toggle();
36973 });
36974 }
36975
36976 if (globalConnect) {
36977 this._registerAction('globalConnectTool', function() {
36978 globalConnect.toggle();
36979 });
36980 }
36981
36982 if (selection && distributeElements) {
36983 this._registerAction('distributeElements', function(opts) {
36984 var currentSelection = selection.get(),
36985 type = opts.type;
36986
36987 if (currentSelection.length) {
36988 distributeElements.trigger(currentSelection, type);
36989 }
36990 });
36991 }
36992
36993 if (selection && alignElements) {
36994 this._registerAction('alignElements', function(opts) {
36995 var currentSelection = selection.get(),
36996 aligneableElements = [],
36997 type = opts.type;
36998
36999 if (currentSelection.length) {
37000 aligneableElements = filter(currentSelection, function(element) {
37001 return !is$1(element, 'bpmn:Lane');
37002 });
37003
37004 alignElements.trigger(aligneableElements, type);
37005 }
37006 });
37007 }
37008
37009 if (selection && modeling) {
37010 this._registerAction('setColor', function(opts) {
37011 var currentSelection = selection.get();
37012
37013 if (currentSelection.length) {
37014 modeling.setColor(currentSelection, opts);
37015 }
37016 });
37017 }
37018
37019 if (selection && directEditing) {
37020 this._registerAction('directEditing', function() {
37021 var currentSelection = selection.get();
37022
37023 if (currentSelection.length) {
37024 directEditing.activate(currentSelection[0]);
37025 }
37026 });
37027 }
37028
37029 if (searchPad) {
37030 this._registerAction('find', function() {
37031 searchPad.toggle();
37032 });
37033 }
37034
37035 if (canvas && modeling) {
37036 this._registerAction('moveToOrigin', function() {
37037 var rootElement = canvas.getRootElement(),
37038 boundingBox,
37039 elements;
37040
37041 if (is$1(rootElement, 'bpmn:Collaboration')) {
37042 elements = elementRegistry.filter(function(element) {
37043 return is$1(element.parent, 'bpmn:Collaboration');
37044 });
37045 } else {
37046 elements = elementRegistry.filter(function(element) {
37047 return element !== rootElement && !is$1(element.parent, 'bpmn:SubProcess');
37048 });
37049 }
37050
37051 boundingBox = getBBox(elements);
37052
37053 modeling.moveElements(
37054 elements,
37055 { x: -boundingBox.x, y: -boundingBox.y },
37056 rootElement
37057 );
37058 });
37059 }
37060
37061 };
37062
37063 var EditorActionsModule$1 = {
37064 __depends__: [
37065 EditorActionsModule
37066 ],
37067 editorActions: [ 'type', BpmnEditorActions ]
37068 };
37069
37070 function BpmnGridSnapping(eventBus) {
37071 eventBus.on([
37072 'create.init',
37073 'shape.move.init'
37074 ], function(event) {
37075 var context = event.context,
37076 shape = event.shape;
37077
37078 if (isAny(shape, [
37079 'bpmn:Participant',
37080 'bpmn:SubProcess',
37081 'bpmn:TextAnnotation'
37082 ])) {
37083 if (!context.gridSnappingContext) {
37084 context.gridSnappingContext = {};
37085 }
37086
37087 context.gridSnappingContext.snapLocation = 'top-left';
37088 }
37089 });
37090 }
37091
37092 BpmnGridSnapping.$inject = [ 'eventBus' ];
37093
37094 var SPACING = 10;
37095
37096 function quantize(value, quantum, fn) {
37097 if (!fn) {
37098 fn = 'round';
37099 }
37100
37101 return Math[ fn ](value / quantum) * quantum;
37102 }
37103
37104 var LOWER_PRIORITY = 1200;
37105 var LOW_PRIORITY$8 = 800;
37106
37107 /**
37108 * Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints
37109 * and connection segments.
37110 */
37111 function GridSnapping(elementRegistry, eventBus, config) {
37112
37113 var active = !config || config.active !== false;
37114
37115 this._eventBus = eventBus;
37116
37117 var self = this;
37118
37119 eventBus.on('diagram.init', LOW_PRIORITY$8, function() {
37120 self.setActive(active);
37121 });
37122
37123 eventBus.on([
37124 'create.move',
37125 'create.end',
37126 'bendpoint.move.move',
37127 'bendpoint.move.end',
37128 'connect.move',
37129 'connect.end',
37130 'connectionSegment.move.move',
37131 'connectionSegment.move.end',
37132 'resize.move',
37133 'resize.end',
37134 'shape.move.move',
37135 'shape.move.end'
37136 ], LOWER_PRIORITY, function(event) {
37137 var originalEvent = event.originalEvent;
37138
37139 if (!self.active || (originalEvent && isCmd(originalEvent))) {
37140 return;
37141 }
37142
37143 var context = event.context,
37144 gridSnappingContext = context.gridSnappingContext;
37145
37146 if (!gridSnappingContext) {
37147 gridSnappingContext = context.gridSnappingContext = {};
37148 }
37149
37150 [ 'x', 'y' ].forEach(function(axis) {
37151 var options = {};
37152
37153 // allow snapping with offset
37154 var snapOffset = getSnapOffset(event, axis, elementRegistry);
37155
37156 if (snapOffset) {
37157 options.offset = snapOffset;
37158 }
37159
37160 // allow snapping with min and max
37161 var snapConstraints = getSnapConstraints(event, axis);
37162
37163 if (snapConstraints) {
37164 assign(options, snapConstraints);
37165 }
37166
37167 if (!isSnapped(event, axis)) {
37168 self.snapEvent(event, axis, options);
37169 }
37170 });
37171 });
37172 }
37173
37174 /**
37175 * Snap an events x or y with optional min, max and offset.
37176 *
37177 * @param {Object} event
37178 * @param {string} axis
37179 * @param {number} [options.min]
37180 * @param {number} [options.max]
37181 * @param {number} [options.offset]
37182 */
37183 GridSnapping.prototype.snapEvent = function(event, axis, options) {
37184 var snappedValue = this.snapValue(event[ axis ], options);
37185
37186 setSnapped(event, axis, snappedValue);
37187 };
37188
37189 /**
37190 * Expose grid spacing for third parties (i.e. extensions).
37191 *
37192 * @return {number} spacing of grid dots
37193 */
37194 GridSnapping.prototype.getGridSpacing = function() {
37195 return SPACING;
37196 };
37197
37198 /**
37199 * Snap value with optional min, max and offset.
37200 *
37201 * @param {number} value
37202 * @param {Object} options
37203 * @param {number} [options.min]
37204 * @param {number} [options.max]
37205 * @param {number} [options.offset]
37206 */
37207 GridSnapping.prototype.snapValue = function(value, options) {
37208 var offset = 0;
37209
37210 if (options && options.offset) {
37211 offset = options.offset;
37212 }
37213
37214 value += offset;
37215
37216 value = quantize(value, SPACING);
37217
37218 var min, max;
37219
37220 if (options && options.min) {
37221 min = options.min;
37222
37223 if (isNumber(min)) {
37224 min = quantize(min + offset, SPACING, 'ceil');
37225
37226 value = Math.max(value, min);
37227 }
37228 }
37229
37230 if (options && options.max) {
37231 max = options.max;
37232
37233 if (isNumber(max)) {
37234 max = quantize(max + offset, SPACING, 'floor');
37235
37236 value = Math.min(value, max);
37237 }
37238 }
37239
37240 value -= offset;
37241
37242 return value;
37243 };
37244
37245 GridSnapping.prototype.isActive = function() {
37246 return this.active;
37247 };
37248
37249 GridSnapping.prototype.setActive = function(active) {
37250 this.active = active;
37251
37252 this._eventBus.fire('gridSnapping.toggle', { active: active });
37253 };
37254
37255 GridSnapping.prototype.toggleActive = function() {
37256 this.setActive(!this.active);
37257 };
37258
37259 GridSnapping.$inject = [
37260 'elementRegistry',
37261 'eventBus',
37262 'config.gridSnapping'
37263 ];
37264
37265 // helpers //////////
37266
37267 /**
37268 * Get minimum and maximum snap constraints.
37269 * Constraints are cached.
37270 *
37271 * @param {Object} event
37272 * @param {Object} event.context
37273 * @param {string} axis
37274 *
37275 * @returns {boolean|Object}
37276 */
37277 function getSnapConstraints(event, axis) {
37278 var context = event.context,
37279 createConstraints = context.createConstraints,
37280 resizeConstraints = context.resizeConstraints || {},
37281 gridSnappingContext = context.gridSnappingContext,
37282 snapConstraints = gridSnappingContext.snapConstraints;
37283
37284 // cache snap constraints
37285 if (snapConstraints && snapConstraints[ axis ]) {
37286 return snapConstraints[ axis ];
37287 }
37288
37289 if (!snapConstraints) {
37290 snapConstraints = gridSnappingContext.snapConstraints = {};
37291 }
37292
37293 if (!snapConstraints[ axis ]) {
37294 snapConstraints[ axis ] = {};
37295 }
37296
37297 var direction = context.direction;
37298
37299 // create
37300 if (createConstraints) {
37301 if (isHorizontal(axis)) {
37302 snapConstraints.x.min = createConstraints.left;
37303 snapConstraints.x.max = createConstraints.right;
37304 } else {
37305 snapConstraints.y.min = createConstraints.top;
37306 snapConstraints.y.max = createConstraints.bottom;
37307 }
37308 }
37309
37310 // resize
37311 var minResizeConstraints = resizeConstraints.min,
37312 maxResizeConstraints = resizeConstraints.max;
37313
37314 if (minResizeConstraints) {
37315 if (isHorizontal(axis)) {
37316
37317 if (isWest(direction)) {
37318 snapConstraints.x.max = minResizeConstraints.left;
37319 } else {
37320 snapConstraints.x.min = minResizeConstraints.right;
37321 }
37322
37323 } else {
37324
37325 if (isNorth(direction)) {
37326 snapConstraints.y.max = minResizeConstraints.top;
37327 } else {
37328 snapConstraints.y.min = minResizeConstraints.bottom;
37329 }
37330
37331 }
37332 }
37333
37334 if (maxResizeConstraints) {
37335 if (isHorizontal(axis)) {
37336
37337 if (isWest(direction)) {
37338 snapConstraints.x.min = maxResizeConstraints.left;
37339 } else {
37340 snapConstraints.x.max = maxResizeConstraints.right;
37341 }
37342
37343 } else {
37344
37345 if (isNorth(direction)) {
37346 snapConstraints.y.min = maxResizeConstraints.top;
37347 } else {
37348 snapConstraints.y.max = maxResizeConstraints.bottom;
37349 }
37350
37351 }
37352 }
37353
37354 return snapConstraints[ axis ];
37355 }
37356
37357 /**
37358 * Get snap offset.
37359 * Offset is cached.
37360 *
37361 * @param {Object} event
37362 * @param {string} axis
37363 * @param {ElementRegistry} elementRegistry
37364 *
37365 * @returns {number}
37366 */
37367 function getSnapOffset(event, axis, elementRegistry) {
37368 var context = event.context,
37369 shape = event.shape,
37370 gridSnappingContext = context.gridSnappingContext,
37371 snapLocation = gridSnappingContext.snapLocation,
37372 snapOffset = gridSnappingContext.snapOffset;
37373
37374 // cache snap offset
37375 if (snapOffset && isNumber(snapOffset[ axis ])) {
37376 return snapOffset[ axis ];
37377 }
37378
37379 if (!snapOffset) {
37380 snapOffset = gridSnappingContext.snapOffset = {};
37381 }
37382
37383 if (!isNumber(snapOffset[ axis ])) {
37384 snapOffset[ axis ] = 0;
37385 }
37386
37387 if (!shape) {
37388 return snapOffset[ axis ];
37389 }
37390
37391 if (!elementRegistry.get(shape.id)) {
37392
37393 if (isHorizontal(axis)) {
37394 snapOffset[ axis ] += shape[ axis ] + shape.width / 2;
37395 } else {
37396 snapOffset[ axis ] += shape[ axis ] + shape.height / 2;
37397 }
37398 }
37399
37400 if (!snapLocation) {
37401 return snapOffset[ axis ];
37402 }
37403
37404 if (axis === 'x') {
37405 if (/left/.test(snapLocation)) {
37406 snapOffset[ axis ] -= shape.width / 2;
37407 } else if (/right/.test(snapLocation)) {
37408 snapOffset[ axis ] += shape.width / 2;
37409 }
37410 } else {
37411 if (/top/.test(snapLocation)) {
37412 snapOffset[ axis ] -= shape.height / 2;
37413 } else if (/bottom/.test(snapLocation)) {
37414 snapOffset[ axis ] += shape.height / 2;
37415 }
37416 }
37417
37418 return snapOffset[ axis ];
37419 }
37420
37421 function isHorizontal(axis) {
37422 return axis === 'x';
37423 }
37424
37425 function isNorth(direction) {
37426 return direction.indexOf('n') !== -1;
37427 }
37428
37429 function isWest(direction) {
37430 return direction.indexOf('w') !== -1;
37431 }
37432
37433 /**
37434 * Integrates resizing with grid snapping.
37435 */
37436 function ResizeBehavior(eventBus, gridSnapping) {
37437 CommandInterceptor.call(this, eventBus);
37438
37439 this._gridSnapping = gridSnapping;
37440
37441 var self = this;
37442
37443 this.preExecute('shape.resize', function(event) {
37444 var context = event.context,
37445 hints = context.hints || {},
37446 autoResize = hints.autoResize;
37447
37448 if (!autoResize) {
37449 return;
37450 }
37451
37452 var shape = context.shape,
37453 newBounds = context.newBounds;
37454
37455 if (isString(autoResize)) {
37456 context.newBounds = self.snapComplex(newBounds, autoResize);
37457 } else {
37458 context.newBounds = self.snapSimple(shape, newBounds);
37459 }
37460 });
37461 }
37462
37463 ResizeBehavior.$inject = [
37464 'eventBus',
37465 'gridSnapping',
37466 'modeling'
37467 ];
37468
37469 inherits_browser(ResizeBehavior, CommandInterceptor);
37470
37471 /**
37472 * Snap width and height in relation to center.
37473 *
37474 * @param {djs.model.shape} shape
37475 * @param {Bounds} newBounds
37476 *
37477 * @returns {Bounds} Snapped bounds.
37478 */
37479 ResizeBehavior.prototype.snapSimple = function(shape, newBounds) {
37480 var gridSnapping = this._gridSnapping;
37481
37482 newBounds.width = gridSnapping.snapValue(newBounds.width, {
37483 min: newBounds.width
37484 });
37485
37486 newBounds.height = gridSnapping.snapValue(newBounds.height, {
37487 min: newBounds.height
37488 });
37489
37490 newBounds.x = shape.x + (shape.width / 2) - (newBounds.width / 2);
37491 newBounds.y = shape.y + (shape.height / 2) - (newBounds.height / 2);
37492
37493 return newBounds;
37494 };
37495
37496 /**
37497 * Snap x, y, width and height according to given directions.
37498 *
37499 * @param {Bounds} newBounds
37500 * @param {string} directions - Directions as {n|w|s|e}.
37501 *
37502 * @returns {Bounds} Snapped bounds.
37503 */
37504 ResizeBehavior.prototype.snapComplex = function(newBounds, directions) {
37505 if (/w|e/.test(directions)) {
37506 newBounds = this.snapHorizontally(newBounds, directions);
37507 }
37508
37509 if (/n|s/.test(directions)) {
37510 newBounds = this.snapVertically(newBounds, directions);
37511 }
37512
37513 return newBounds;
37514 };
37515
37516 /**
37517 * Snap in one or both directions horizontally.
37518 *
37519 * @param {Bounds} newBounds
37520 * @param {string} directions - Directions as {n|w|s|e}.
37521 *
37522 * @returns {Bounds} Snapped bounds.
37523 */
37524 ResizeBehavior.prototype.snapHorizontally = function(newBounds, directions) {
37525 var gridSnapping = this._gridSnapping,
37526 west = /w/.test(directions),
37527 east = /e/.test(directions);
37528
37529 var snappedNewBounds = {};
37530
37531 snappedNewBounds.width = gridSnapping.snapValue(newBounds.width, {
37532 min: newBounds.width
37533 });
37534
37535 if (east) {
37536
37537 // handle <we>
37538 if (west) {
37539 snappedNewBounds.x = gridSnapping.snapValue(newBounds.x, {
37540 max: newBounds.x
37541 });
37542
37543 snappedNewBounds.width += gridSnapping.snapValue(newBounds.x - snappedNewBounds.x, {
37544 min: newBounds.x - snappedNewBounds.x
37545 });
37546 }
37547
37548 // handle <e>
37549 else {
37550 newBounds.x = newBounds.x + newBounds.width - snappedNewBounds.width;
37551 }
37552 }
37553
37554 // assign snapped x and width
37555 assign(newBounds, snappedNewBounds);
37556
37557 return newBounds;
37558 };
37559
37560 /**
37561 * Snap in one or both directions vertically.
37562 *
37563 * @param {Bounds} newBounds
37564 * @param {string} directions - Directions as {n|w|s|e}.
37565 *
37566 * @returns {Bounds} Snapped bounds.
37567 */
37568 ResizeBehavior.prototype.snapVertically = function(newBounds, directions) {
37569 var gridSnapping = this._gridSnapping,
37570 north = /n/.test(directions),
37571 south = /s/.test(directions);
37572
37573 var snappedNewBounds = {};
37574
37575 snappedNewBounds.height = gridSnapping.snapValue(newBounds.height, {
37576 min: newBounds.height
37577 });
37578
37579 if (north) {
37580
37581 // handle <ns>
37582 if (south) {
37583 snappedNewBounds.y = gridSnapping.snapValue(newBounds.y, {
37584 max: newBounds.y
37585 });
37586
37587 snappedNewBounds.height += gridSnapping.snapValue(newBounds.y - snappedNewBounds.y, {
37588 min: newBounds.y - snappedNewBounds.y
37589 });
37590 }
37591
37592 // handle <n>
37593 else {
37594 newBounds.y = newBounds.y + newBounds.height - snappedNewBounds.height;
37595 }
37596 }
37597
37598 // assign snapped y and height
37599 assign(newBounds, snappedNewBounds);
37600
37601 return newBounds;
37602 };
37603
37604 var HIGH_PRIORITY$4 = 2000;
37605
37606 /**
37607 * Integrates space tool with grid snapping.
37608 */
37609 function SpaceToolBehavior(eventBus, gridSnapping) {
37610 eventBus.on([
37611 'spaceTool.move',
37612 'spaceTool.end'
37613 ], HIGH_PRIORITY$4, function(event) {
37614 var context = event.context;
37615
37616 if (!context.initialized) {
37617 return;
37618 }
37619
37620 var axis = context.axis;
37621
37622 var snapped;
37623
37624 if (axis === 'x') {
37625
37626 // snap delta x to multiple of 10
37627 snapped = gridSnapping.snapValue(event.dx);
37628
37629 event.x = event.x + snapped - event.dx;
37630 event.dx = snapped;
37631 } else {
37632
37633 // snap delta y to multiple of 10
37634 snapped = gridSnapping.snapValue(event.dy);
37635
37636 event.y = event.y + snapped - event.dy;
37637 event.dy = snapped;
37638 }
37639 });
37640 }
37641
37642 SpaceToolBehavior.$inject = [
37643 'eventBus',
37644 'gridSnapping'
37645 ];
37646
37647 var GridSnappingBehaviorModule = {
37648 __init__: [
37649 'gridSnappingResizeBehavior',
37650 'gridSnappingSpaceToolBehavior'
37651 ],
37652 gridSnappingResizeBehavior: [ 'type', ResizeBehavior ],
37653 gridSnappingSpaceToolBehavior: [ 'type', SpaceToolBehavior ]
37654 };
37655
37656 var GridSnappingModule = {
37657 __depends__: [ GridSnappingBehaviorModule ],
37658 __init__: [ 'gridSnapping' ],
37659 gridSnapping: [ 'type', GridSnapping ]
37660 };
37661
37662 var HIGH_PRIORITY$5 = 2000;
37663
37664
37665 function AutoPlaceBehavior(eventBus, gridSnapping) {
37666 eventBus.on('autoPlace', HIGH_PRIORITY$5, function(context) {
37667 var source = context.source,
37668 sourceMid = getMid(source),
37669 shape = context.shape;
37670
37671 var position = getNewShapePosition$1(source, shape);
37672
37673 [ 'x', 'y' ].forEach(function(axis) {
37674 var options = {};
37675
37676 // do not snap if x/y equal
37677 if (position[ axis ] === sourceMid[ axis ]) {
37678 return;
37679 }
37680
37681 if (position[ axis ] > sourceMid[ axis ]) {
37682 options.min = position[ axis ];
37683 } else {
37684 options.max = position[ axis ];
37685 }
37686
37687 if (is$1(shape, 'bpmn:TextAnnotation')) {
37688
37689 if (isHorizontal$1(axis)) {
37690 options.offset = -shape.width / 2;
37691 } else {
37692 options.offset = -shape.height / 2;
37693 }
37694
37695 }
37696
37697 position[ axis ] = gridSnapping.snapValue(position[ axis ], options);
37698
37699 });
37700
37701 // must be returned to be considered by auto place
37702 return position;
37703 });
37704 }
37705
37706 AutoPlaceBehavior.$inject = [
37707 'eventBus',
37708 'gridSnapping'
37709 ];
37710
37711 // helpers //////////
37712
37713 function isHorizontal$1(axis) {
37714 return axis === 'x';
37715 }
37716
37717 var HIGHER_PRIORITY = 1750;
37718
37719
37720 function CreateParticipantBehavior(canvas, eventBus, gridSnapping) {
37721 eventBus.on([
37722 'create.start',
37723 'shape.move.start'
37724 ], HIGHER_PRIORITY, function(event) {
37725 var context = event.context,
37726 shape = context.shape,
37727 rootElement = canvas.getRootElement();
37728
37729 if (!is$1(shape, 'bpmn:Participant') ||
37730 !is$1(rootElement, 'bpmn:Process') ||
37731 !rootElement.children.length) {
37732 return;
37733 }
37734
37735 var createConstraints = context.createConstraints;
37736
37737 if (!createConstraints) {
37738 return;
37739 }
37740
37741 shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
37742 shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
37743 });
37744 }
37745
37746 CreateParticipantBehavior.$inject = [
37747 'canvas',
37748 'eventBus',
37749 'gridSnapping'
37750 ];
37751
37752 var HIGH_PRIORITY$6 = 3000;
37753
37754
37755 /**
37756 * Snaps connections with Manhattan layout.
37757 */
37758 function LayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
37759 CommandInterceptor.call(this, eventBus);
37760
37761 this._gridSnapping = gridSnapping;
37762
37763 var self = this;
37764
37765 this.postExecuted([
37766 'connection.create',
37767 'connection.layout'
37768 ], HIGH_PRIORITY$6, function(event) {
37769 var context = event.context,
37770 connection = context.connection,
37771 hints = context.hints || {},
37772 waypoints = connection.waypoints;
37773
37774 if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) {
37775 return;
37776 }
37777
37778 if (!hasMiddleSegments(waypoints)) {
37779 return;
37780 }
37781
37782 modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
37783 });
37784 }
37785
37786 LayoutConnectionBehavior.$inject = [
37787 'eventBus',
37788 'gridSnapping',
37789 'modeling'
37790 ];
37791
37792 inherits_browser(LayoutConnectionBehavior, CommandInterceptor);
37793
37794 /**
37795 * Snap middle segments of a given connection.
37796 *
37797 * @param {Array<Point>} waypoints
37798 *
37799 * @returns {Array<Point>}
37800 */
37801 LayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
37802 var gridSnapping = this._gridSnapping,
37803 snapped;
37804
37805 waypoints = waypoints.slice();
37806
37807 for (var i = 1; i < waypoints.length - 2; i++) {
37808
37809 snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);
37810
37811 waypoints[i] = snapped[0];
37812 waypoints[i + 1] = snapped[1];
37813 }
37814
37815 return waypoints;
37816 };
37817
37818
37819 // helpers //////////
37820
37821 /**
37822 * Check whether a connection has a middle segments.
37823 *
37824 * @param {Array} waypoints
37825 *
37826 * @returns {boolean}
37827 */
37828 function hasMiddleSegments(waypoints) {
37829 return waypoints.length > 3;
37830 }
37831
37832 /**
37833 * Check whether an alignment is horizontal.
37834 *
37835 * @param {string} aligned
37836 *
37837 * @returns {boolean}
37838 */
37839 function horizontallyAligned(aligned) {
37840 return aligned === 'h';
37841 }
37842
37843 /**
37844 * Check whether an alignment is vertical.
37845 *
37846 * @param {string} aligned
37847 *
37848 * @returns {boolean}
37849 */
37850 function verticallyAligned(aligned) {
37851 return aligned === 'v';
37852 }
37853
37854 /**
37855 * Get middle segments from a given connection.
37856 *
37857 * @param {Array} waypoints
37858 *
37859 * @returns {Array}
37860 */
37861 function snapSegment(gridSnapping, segmentStart, segmentEnd) {
37862
37863 var aligned = pointsAligned(segmentStart, segmentEnd);
37864
37865 var snapped = {};
37866
37867 if (horizontallyAligned(aligned)) {
37868
37869 // snap horizontally
37870 snapped.y = gridSnapping.snapValue(segmentStart.y);
37871 }
37872
37873 if (verticallyAligned(aligned)) {
37874
37875 // snap vertically
37876 snapped.x = gridSnapping.snapValue(segmentStart.x);
37877 }
37878
37879 if ('x' in snapped || 'y' in snapped) {
37880 segmentStart = assign({}, segmentStart, snapped);
37881 segmentEnd = assign({}, segmentEnd, snapped);
37882 }
37883
37884 return [ segmentStart, segmentEnd ];
37885 }
37886
37887 var GridSnappingBehaviorModule$1 = {
37888 __init__: [
37889 'gridSnappingAutoPlaceBehavior',
37890 'gridSnappingCreateParticipantBehavior',
37891 'gridSnappingLayoutConnectionBehavior',
37892 ],
37893 gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ],
37894 gridSnappingCreateParticipantBehavior: [ 'type', CreateParticipantBehavior ],
37895 gridSnappingLayoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ]
37896 };
37897
37898 var GridSnappingModule$1 = {
37899 __depends__: [
37900 GridSnappingModule,
37901 GridSnappingBehaviorModule$1
37902 ],
37903 __init__: [ 'bpmnGridSnapping' ],
37904 bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
37905 };
37906
37907 var LABEL_WIDTH = 30,
37908 LABEL_HEIGHT = 30;
37909
37910
37911 /**
37912 * BPMN-specific hit zones and interaction fixes.
37913 *
37914 * @param {EventBus} eventBus
37915 * @param {InteractionEvents} interactionEvents
37916 */
37917 function BpmnInteractionEvents(eventBus, interactionEvents) {
37918
37919 this._interactionEvents = interactionEvents;
37920
37921 var self = this;
37922
37923 eventBus.on([
37924 'interactionEvents.createHit',
37925 'interactionEvents.updateHit'
37926 ], function(context) {
37927 var element = context.element,
37928 gfx = context.gfx;
37929
37930 if (is$1(element, 'bpmn:Lane')) {
37931 return self.createParticipantHit(element, gfx);
37932 } else
37933
37934 if (is$1(element, 'bpmn:Participant')) {
37935 if (isExpanded(element)) {
37936 return self.createParticipantHit(element, gfx);
37937 } else {
37938 return self.createDefaultHit(element, gfx);
37939 }
37940 } else
37941
37942 if (is$1(element, 'bpmn:SubProcess')) {
37943 if (isExpanded(element)) {
37944 return self.createSubProcessHit(element, gfx);
37945 } else {
37946 return self.createDefaultHit(element, gfx);
37947 }
37948 }
37949 });
37950
37951 }
37952
37953 BpmnInteractionEvents.$inject = [
37954 'eventBus',
37955 'interactionEvents'
37956 ];
37957
37958
37959 BpmnInteractionEvents.prototype.createDefaultHit = function(element, gfx) {
37960 this._interactionEvents.removeHits(gfx);
37961
37962 this._interactionEvents.createDefaultHit(element, gfx);
37963
37964 // indicate that we created a hit
37965 return true;
37966 };
37967
37968 BpmnInteractionEvents.prototype.createParticipantHit = function(element, gfx) {
37969
37970 // remove existing hits
37971 this._interactionEvents.removeHits(gfx);
37972
37973 // add outline hit
37974 this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
37975 width: element.width,
37976 height: element.height
37977 });
37978
37979 // add label hit
37980 this._interactionEvents.createBoxHit(gfx, 'all', {
37981 width: LABEL_WIDTH,
37982 height: element.height
37983 });
37984
37985 // indicate that we created a hit
37986 return true;
37987 };
37988
37989 BpmnInteractionEvents.prototype.createSubProcessHit = function(element, gfx) {
37990
37991 // remove existing hits
37992 this._interactionEvents.removeHits(gfx);
37993
37994 // add outline hit
37995 this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
37996 width: element.width,
37997 height: element.height
37998 });
37999
38000 // add label hit
38001 this._interactionEvents.createBoxHit(gfx, 'all', {
38002 width: element.width,
38003 height: LABEL_HEIGHT
38004 });
38005
38006 // indicate that we created a hit
38007 return true;
38008 };
38009
38010 var InteractionEventsModule$1 = {
38011 __init__: [ 'bpmnInteractionEvents' ],
38012 bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ]
38013 };
38014
38015 /**
38016 * BPMN 2.0 specific keyboard bindings.
38017 *
38018 * @param {Injector} injector
38019 */
38020 function BpmnKeyboardBindings(injector) {
38021 injector.invoke(KeyboardBindings, this);
38022 }
38023
38024 inherits_browser(BpmnKeyboardBindings, KeyboardBindings);
38025
38026 BpmnKeyboardBindings.$inject = [
38027 'injector'
38028 ];
38029
38030
38031 /**
38032 * Register available keyboard bindings.
38033 *
38034 * @param {Keyboard} keyboard
38035 * @param {EditorActions} editorActions
38036 */
38037 BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
38038
38039 // inherit default bindings
38040 KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
38041
38042 /**
38043 * Add keyboard binding if respective editor action
38044 * is registered.
38045 *
38046 * @param {string} action name
38047 * @param {Function} fn that implements the key binding
38048 */
38049 function addListener(action, fn) {
38050
38051 if (editorActions.isRegistered(action)) {
38052 keyboard.addListener(fn);
38053 }
38054 }
38055
38056 // select all elements
38057 // CTRL + A
38058 addListener('selectElements', function(context) {
38059
38060 var event = context.keyEvent;
38061
38062 if (keyboard.isKey(['a', 'A'], event) && keyboard.isCmd(event)) {
38063 editorActions.trigger('selectElements');
38064
38065 return true;
38066 }
38067 });
38068
38069 // search labels
38070 // CTRL + F
38071 addListener('find', function(context) {
38072
38073 var event = context.keyEvent;
38074
38075 if (keyboard.isKey(['f', 'F'], event) && keyboard.isCmd(event)) {
38076 editorActions.trigger('find');
38077
38078 return true;
38079 }
38080 });
38081
38082 // activate space tool
38083 // S
38084 addListener('spaceTool', function(context) {
38085
38086 var event = context.keyEvent;
38087
38088 if (keyboard.hasModifier(event)) {
38089 return;
38090 }
38091
38092 if (keyboard.isKey(['s', 'S'], event)) {
38093 editorActions.trigger('spaceTool');
38094
38095 return true;
38096 }
38097 });
38098
38099 // activate lasso tool
38100 // L
38101 addListener('lassoTool', function(context) {
38102
38103 var event = context.keyEvent;
38104
38105 if (keyboard.hasModifier(event)) {
38106 return;
38107 }
38108
38109 if (keyboard.isKey(['l', 'L'], event)) {
38110 editorActions.trigger('lassoTool');
38111
38112 return true;
38113 }
38114 });
38115
38116 // activate hand tool
38117 // H
38118 addListener('handTool', function(context) {
38119
38120 var event = context.keyEvent;
38121
38122 if (keyboard.hasModifier(event)) {
38123 return;
38124 }
38125
38126 if (keyboard.isKey(['h', 'H'], event)) {
38127 editorActions.trigger('handTool');
38128
38129 return true;
38130 }
38131 });
38132
38133 // activate global connect tool
38134 // C
38135 addListener('globalConnectTool', function(context) {
38136
38137 var event = context.keyEvent;
38138
38139 if (keyboard.hasModifier(event)) {
38140 return;
38141 }
38142
38143 if (keyboard.isKey(['c', 'C'], event)) {
38144 editorActions.trigger('globalConnectTool');
38145
38146 return true;
38147 }
38148 });
38149
38150 // activate direct editing
38151 // E
38152 addListener('directEditing', function(context) {
38153
38154 var event = context.keyEvent;
38155
38156 if (keyboard.hasModifier(event)) {
38157 return;
38158 }
38159
38160 if (keyboard.isKey(['e', 'E'], event)) {
38161 editorActions.trigger('directEditing');
38162
38163 return true;
38164 }
38165 });
38166
38167 };
38168
38169 var KeyboardModule$1 = {
38170 __depends__: [
38171 KeyboardModule
38172 ],
38173 __init__: [ 'keyboardBindings' ],
38174 keyboardBindings: [ 'type', BpmnKeyboardBindings ]
38175 };
38176
38177 var DEFAULT_CONFIG$1 = {
38178 moveSpeed: 1,
38179 moveSpeedAccelerated: 10
38180 };
38181
38182 var HIGHER_PRIORITY$1 = 1500;
38183
38184 var LEFT = 'left';
38185 var UP = 'up';
38186 var RIGHT = 'right';
38187 var DOWN = 'down';
38188
38189 var KEY_TO_DIRECTION = {
38190 ArrowLeft: LEFT,
38191 Left: LEFT,
38192 ArrowUp: UP,
38193 Up: UP,
38194 ArrowRight: RIGHT,
38195 Right: RIGHT,
38196 ArrowDown: DOWN,
38197 Down: DOWN
38198 };
38199
38200 var DIRECTIONS_DELTA = {
38201 left: function(speed) {
38202 return {
38203 x: -speed,
38204 y: 0
38205 };
38206 },
38207 up: function(speed) {
38208 return {
38209 x: 0,
38210 y: -speed
38211 };
38212 },
38213 right: function(speed) {
38214 return {
38215 x: speed,
38216 y: 0
38217 };
38218 },
38219 down: function(speed) {
38220 return {
38221 x: 0,
38222 y: speed
38223 };
38224 }
38225 };
38226
38227
38228 /**
38229 * Enables to move selection with keyboard arrows.
38230 * Use with Shift for modified speed (default=1, with Shift=10).
38231 * Pressed Cmd/Ctrl turns the feature off.
38232 *
38233 * @param {Object} config
38234 * @param {number} [config.moveSpeed=1]
38235 * @param {number} [config.moveSpeedAccelerated=10]
38236 * @param {Keyboard} keyboard
38237 * @param {Modeling} modeling
38238 * @param {Selection} selection
38239 */
38240 function KeyboardMoveSelection(
38241 config,
38242 keyboard,
38243 modeling,
38244 rules,
38245 selection
38246 ) {
38247
38248 var self = this;
38249
38250 this._config = assign({}, DEFAULT_CONFIG$1, config || {});
38251
38252 keyboard.addListener(HIGHER_PRIORITY$1, function(event) {
38253
38254 var keyEvent = event.keyEvent;
38255
38256 var direction = KEY_TO_DIRECTION[keyEvent.key];
38257
38258 if (!direction) {
38259 return;
38260 }
38261
38262 if (keyboard.isCmd(keyEvent)) {
38263 return;
38264 }
38265
38266 var accelerated = keyboard.isShift(keyEvent);
38267
38268 self.moveSelection(direction, accelerated);
38269
38270 return true;
38271 });
38272
38273
38274 /**
38275 * Move selected elements in the given direction,
38276 * optionally specifying accelerated movement.
38277 *
38278 * @param {string} direction
38279 * @param {boolean} [accelerated=false]
38280 */
38281 this.moveSelection = function(direction, accelerated) {
38282
38283 var selectedElements = selection.get();
38284
38285 if (!selectedElements.length) {
38286 return;
38287 }
38288
38289 var speed = this._config[
38290 accelerated ?
38291 'moveSpeedAccelerated' :
38292 'moveSpeed'
38293 ];
38294
38295 var delta = DIRECTIONS_DELTA[direction](speed);
38296
38297 var canMove = rules.allowed('elements.move', {
38298 shapes: selectedElements
38299 });
38300
38301 if (canMove) {
38302 modeling.moveElements(selectedElements, delta);
38303 }
38304 };
38305
38306 }
38307
38308 KeyboardMoveSelection.$inject = [
38309 'config.keyboardMoveSelection',
38310 'keyboard',
38311 'modeling',
38312 'rules',
38313 'selection'
38314 ];
38315
38316 var KeyboardMoveSelectionModule = {
38317 __depends__: [
38318 KeyboardModule,
38319 SelectionModule
38320 ],
38321 __init__: [
38322 'keyboardMoveSelection'
38323 ],
38324 keyboardMoveSelection: [ 'type', KeyboardMoveSelection ]
38325 };
38326
38327 /**
38328 * Adds change support to the diagram, including
38329 *
38330 * <ul>
38331 * <li>redrawing shapes and connections on change</li>
38332 * </ul>
38333 *
38334 * @param {EventBus} eventBus
38335 * @param {Canvas} canvas
38336 * @param {ElementRegistry} elementRegistry
38337 * @param {GraphicsFactory} graphicsFactory
38338 */
38339 function ChangeSupport(
38340 eventBus, canvas, elementRegistry,
38341 graphicsFactory) {
38342
38343
38344 // redraw shapes / connections on change
38345
38346 eventBus.on('element.changed', function(event) {
38347
38348 var element = event.element;
38349
38350 // element might have been deleted and replaced by new element with same ID
38351 // thus check for parent of element except for root element
38352 if (element.parent || element === canvas.getRootElement()) {
38353 event.gfx = elementRegistry.getGraphics(element);
38354 }
38355
38356 // shape + gfx may have been deleted
38357 if (!event.gfx) {
38358 return;
38359 }
38360
38361 eventBus.fire(getType(element) + '.changed', event);
38362 });
38363
38364 eventBus.on('elements.changed', function(event) {
38365
38366 var elements = event.elements;
38367
38368 elements.forEach(function(e) {
38369 eventBus.fire('element.changed', { element: e });
38370 });
38371
38372 graphicsFactory.updateContainments(elements);
38373 });
38374
38375 eventBus.on('shape.changed', function(event) {
38376 graphicsFactory.update('shape', event.element, event.gfx);
38377 });
38378
38379 eventBus.on('connection.changed', function(event) {
38380 graphicsFactory.update('connection', event.element, event.gfx);
38381 });
38382 }
38383
38384 ChangeSupport.$inject = [
38385 'eventBus',
38386 'canvas',
38387 'elementRegistry',
38388 'graphicsFactory'
38389 ];
38390
38391 var ChangeSupportModule = {
38392 __init__: [ 'changeSupport'],
38393 changeSupport: [ 'type', ChangeSupport ]
38394 };
38395
38396 var DEFAULT_MIN_WIDTH = 10;
38397
38398
38399 /**
38400 * A component that provides resizing of shapes on the canvas.
38401 *
38402 * The following components are part of shape resize:
38403 *
38404 * * adding resize handles,
38405 * * creating a visual during resize
38406 * * checking resize rules
38407 * * committing a change once finished
38408 *
38409 *
38410 * ## Customizing
38411 *
38412 * It's possible to customize the resizing behaviour by intercepting 'resize.start'
38413 * and providing the following parameters through the 'context':
38414 *
38415 * * minDimensions ({ width, height }): minimum shape dimensions
38416 *
38417 * * childrenBoxPadding ({ left, top, bottom, right } || number):
38418 * gap between the minimum bounding box and the container
38419 *
38420 * f.ex:
38421 *
38422 * ```javascript
38423 * eventBus.on('resize.start', 1500, function(event) {
38424 * var context = event.context,
38425 *
38426 * context.minDimensions = { width: 140, height: 120 };
38427 *
38428 * // Passing general padding
38429 * context.childrenBoxPadding = 30;
38430 *
38431 * // Passing padding to a specific side
38432 * context.childrenBoxPadding.left = 20;
38433 * });
38434 * ```
38435 */
38436 function Resize(eventBus, rules, modeling, dragging) {
38437
38438 this._dragging = dragging;
38439 this._rules = rules;
38440
38441 var self = this;
38442
38443
38444 /**
38445 * Handle resize move by specified delta.
38446 *
38447 * @param {Object} context
38448 * @param {Point} delta
38449 */
38450 function handleMove(context, delta) {
38451
38452 var shape = context.shape,
38453 direction = context.direction,
38454 resizeConstraints = context.resizeConstraints,
38455 newBounds;
38456
38457 context.delta = delta;
38458
38459 newBounds = resizeBounds(shape, direction, delta);
38460
38461 // ensure constraints during resize
38462 context.newBounds = ensureConstraints$1(newBounds, resizeConstraints);
38463
38464 // update + cache executable state
38465 context.canExecute = self.canResize(context);
38466 }
38467
38468 /**
38469 * Handle resize start.
38470 *
38471 * @param {Object} context
38472 */
38473 function handleStart(context) {
38474
38475 var resizeConstraints = context.resizeConstraints,
38476
38477 // evaluate minBounds for backwards compatibility
38478 minBounds = context.minBounds;
38479
38480 if (resizeConstraints !== undefined) {
38481 return;
38482 }
38483
38484 if (minBounds === undefined) {
38485 minBounds = self.computeMinResizeBox(context);
38486 }
38487
38488 context.resizeConstraints = {
38489 min: asTRBL(minBounds)
38490 };
38491 }
38492
38493 /**
38494 * Handle resize end.
38495 *
38496 * @param {Object} context
38497 */
38498 function handleEnd(context) {
38499 var shape = context.shape,
38500 canExecute = context.canExecute,
38501 newBounds = context.newBounds;
38502
38503 if (canExecute) {
38504
38505 // ensure we have actual pixel values for new bounds
38506 // (important when zoom level was > 1 during move)
38507 newBounds = roundBounds(newBounds);
38508
38509 if (!boundsChanged$1(shape, newBounds)) {
38510
38511 // no resize necessary
38512 return;
38513 }
38514
38515 // perform the actual resize
38516 modeling.resizeShape(shape, newBounds);
38517 }
38518 }
38519
38520
38521 eventBus.on('resize.start', function(event) {
38522 handleStart(event.context);
38523 });
38524
38525 eventBus.on('resize.move', function(event) {
38526 var delta = {
38527 x: event.dx,
38528 y: event.dy
38529 };
38530
38531 handleMove(event.context, delta);
38532 });
38533
38534 eventBus.on('resize.end', function(event) {
38535 handleEnd(event.context);
38536 });
38537
38538 }
38539
38540
38541 Resize.prototype.canResize = function(context) {
38542 var rules = this._rules;
38543
38544 var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]);
38545
38546 return rules.allowed('shape.resize', ctx);
38547 };
38548
38549 /**
38550 * Activate a resize operation.
38551 *
38552 * You may specify additional contextual information and must specify a
38553 * resize direction during activation of the resize event.
38554 *
38555 * @param {MouseEvent} event
38556 * @param {djs.model.Shape} shape
38557 * @param {Object|string} contextOrDirection
38558 */
38559 Resize.prototype.activate = function(event, shape, contextOrDirection) {
38560 var dragging = this._dragging,
38561 context,
38562 direction;
38563
38564 if (typeof contextOrDirection === 'string') {
38565 contextOrDirection = {
38566 direction: contextOrDirection
38567 };
38568 }
38569
38570 context = assign({ shape: shape }, contextOrDirection);
38571
38572 direction = context.direction;
38573
38574 if (!direction) {
38575 throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)');
38576 }
38577
38578 dragging.init(event, getReferencePoint(shape, direction), 'resize', {
38579 autoActivate: true,
38580 cursor: getCursor(direction),
38581 data: {
38582 shape: shape,
38583 context: context
38584 }
38585 });
38586 };
38587
38588 Resize.prototype.computeMinResizeBox = function(context) {
38589 var shape = context.shape,
38590 direction = context.direction,
38591 minDimensions,
38592 childrenBounds;
38593
38594 minDimensions = context.minDimensions || {
38595 width: DEFAULT_MIN_WIDTH,
38596 height: DEFAULT_MIN_WIDTH
38597 };
38598
38599 // get children bounds
38600 childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding);
38601
38602 // get correct minimum bounds from given resize direction
38603 // basically ensures that the minBounds is max(childrenBounds, minDimensions)
38604 return getMinResizeBounds(direction, shape, minDimensions, childrenBounds);
38605 };
38606
38607
38608 Resize.$inject = [
38609 'eventBus',
38610 'rules',
38611 'modeling',
38612 'dragging'
38613 ];
38614
38615 // helpers //////////
38616
38617 function boundsChanged$1(shape, newBounds) {
38618 return shape.x !== newBounds.x ||
38619 shape.y !== newBounds.y ||
38620 shape.width !== newBounds.width ||
38621 shape.height !== newBounds.height;
38622 }
38623
38624 function getReferencePoint(shape, direction) {
38625 var mid = getMid(shape),
38626 trbl = asTRBL(shape);
38627
38628 var referencePoint = {
38629 x: mid.x,
38630 y: mid.y
38631 };
38632
38633 if (direction.indexOf('n') !== -1) {
38634 referencePoint.y = trbl.top;
38635 } else if (direction.indexOf('s') !== -1) {
38636 referencePoint.y = trbl.bottom;
38637 }
38638
38639 if (direction.indexOf('e') !== -1) {
38640 referencePoint.x = trbl.right;
38641 } else if (direction.indexOf('w') !== -1) {
38642 referencePoint.x = trbl.left;
38643 }
38644
38645 return referencePoint;
38646 }
38647
38648 function getCursor(direction) {
38649 var prefix = 'resize-';
38650
38651 if (direction === 'n' || direction === 's') {
38652 return prefix + 'ns';
38653 } else if (direction === 'e' || direction === 'w') {
38654 return prefix + 'ew';
38655 } else if (direction === 'nw' || direction === 'se') {
38656 return prefix + 'nwse';
38657 } else {
38658 return prefix + 'nesw';
38659 }
38660 }
38661
38662 var MARKER_RESIZING = 'djs-resizing',
38663 MARKER_RESIZE_NOT_OK = 'resize-not-ok';
38664
38665 var LOW_PRIORITY$9 = 500;
38666
38667
38668 /**
38669 * Provides previews for resizing shapes when resizing.
38670 *
38671 * @param {EventBus} eventBus
38672 * @param {Canvas} canvas
38673 * @param {PreviewSupport} previewSupport
38674 */
38675 function ResizePreview(eventBus, canvas, previewSupport) {
38676
38677 /**
38678 * Update resizer frame.
38679 *
38680 * @param {Object} context
38681 */
38682 function updateFrame(context) {
38683
38684 var shape = context.shape,
38685 bounds = context.newBounds,
38686 frame = context.frame;
38687
38688 if (!frame) {
38689 frame = context.frame = previewSupport.addFrame(shape, canvas.getDefaultLayer());
38690
38691 canvas.addMarker(shape, MARKER_RESIZING);
38692 }
38693
38694 if (bounds.width > 5) {
38695 attr$1(frame, { x: bounds.x, width: bounds.width });
38696 }
38697
38698 if (bounds.height > 5) {
38699 attr$1(frame, { y: bounds.y, height: bounds.height });
38700 }
38701
38702 if (context.canExecute) {
38703 classes$1(frame).remove(MARKER_RESIZE_NOT_OK);
38704 } else {
38705 classes$1(frame).add(MARKER_RESIZE_NOT_OK);
38706 }
38707 }
38708
38709 /**
38710 * Remove resizer frame.
38711 *
38712 * @param {Object} context
38713 */
38714 function removeFrame(context) {
38715 var shape = context.shape,
38716 frame = context.frame;
38717
38718 if (frame) {
38719 remove$1(context.frame);
38720 }
38721
38722 canvas.removeMarker(shape, MARKER_RESIZING);
38723 }
38724
38725 // add and update previews
38726 eventBus.on('resize.move', LOW_PRIORITY$9, function(event) {
38727 updateFrame(event.context);
38728 });
38729
38730 // remove previews
38731 eventBus.on('resize.cleanup', function(event) {
38732 removeFrame(event.context);
38733 });
38734
38735 }
38736
38737 ResizePreview.$inject = [
38738 'eventBus',
38739 'canvas',
38740 'previewSupport'
38741 ];
38742
38743 var HANDLE_OFFSET = -6,
38744 HANDLE_SIZE = 4,
38745 HANDLE_HIT_SIZE = 20;
38746
38747 var CLS_RESIZER = 'djs-resizer';
38748
38749 var directions = [ 'n', 'w', 's', 'e', 'nw', 'ne', 'se', 'sw' ];
38750
38751
38752 /**
38753 * This component is responsible for adding resize handles.
38754 *
38755 * @param {EventBus} eventBus
38756 * @param {Canvas} canvas
38757 * @param {Selection} selection
38758 * @param {Resize} resize
38759 */
38760 function ResizeHandles(eventBus, canvas, selection, resize) {
38761
38762 this._resize = resize;
38763 this._canvas = canvas;
38764
38765 var self = this;
38766
38767 eventBus.on('selection.changed', function(e) {
38768 var newSelection = e.newSelection;
38769
38770 // remove old selection markers
38771 self.removeResizers();
38772
38773 // add new selection markers ONLY if single selection
38774 if (newSelection.length === 1) {
38775 forEach(newSelection, bind(self.addResizer, self));
38776 }
38777 });
38778
38779 eventBus.on('shape.changed', function(e) {
38780 var shape = e.element;
38781
38782 if (selection.isSelected(shape)) {
38783 self.removeResizers();
38784
38785 self.addResizer(shape);
38786 }
38787 });
38788 }
38789
38790
38791 ResizeHandles.prototype.makeDraggable = function(element, gfx, direction) {
38792 var resize = this._resize;
38793
38794 function startResize(event) {
38795
38796 // only trigger on left mouse button
38797 if (isPrimaryButton(event)) {
38798 resize.activate(event, element, direction);
38799 }
38800 }
38801
38802 componentEvent.bind(gfx, 'mousedown', startResize);
38803 componentEvent.bind(gfx, 'touchstart', startResize);
38804 };
38805
38806
38807 ResizeHandles.prototype._createResizer = function(element, x, y, direction) {
38808 var resizersParent = this._getResizersParent();
38809
38810 var offset = getHandleOffset(direction);
38811
38812 var group = create('g');
38813
38814 classes$1(group).add(CLS_RESIZER);
38815 classes$1(group).add(CLS_RESIZER + '-' + element.id);
38816 classes$1(group).add(CLS_RESIZER + '-' + direction);
38817
38818 append(resizersParent, group);
38819
38820 var visual = create('rect');
38821
38822 attr$1(visual, {
38823 x: -HANDLE_SIZE / 2 + offset.x,
38824 y: -HANDLE_SIZE / 2 + offset.y,
38825 width: HANDLE_SIZE,
38826 height: HANDLE_SIZE
38827 });
38828
38829 classes$1(visual).add(CLS_RESIZER + '-visual');
38830
38831 append(group, visual);
38832
38833 var hit = create('rect');
38834
38835 attr$1(hit, {
38836 x: -HANDLE_HIT_SIZE / 2 + offset.x,
38837 y: -HANDLE_HIT_SIZE / 2 + offset.y,
38838 width: HANDLE_HIT_SIZE,
38839 height: HANDLE_HIT_SIZE
38840 });
38841
38842 classes$1(hit).add(CLS_RESIZER + '-hit');
38843
38844 append(group, hit);
38845
38846 transform$1(group, x, y);
38847
38848 return group;
38849 };
38850
38851 ResizeHandles.prototype.createResizer = function(element, direction) {
38852 var point = getReferencePoint(element, direction);
38853
38854 var resizer = this._createResizer(element, point.x, point.y, direction);
38855
38856 this.makeDraggable(element, resizer, direction);
38857 };
38858
38859 // resize handles implementation ///////////////////////////////
38860
38861 /**
38862 * Add resizers for a given element.
38863 *
38864 * @param {djs.model.Shape} shape
38865 */
38866 ResizeHandles.prototype.addResizer = function(shape) {
38867 var self = this;
38868
38869 var resize = this._resize;
38870
38871 if (!resize.canResize({ shape: shape })) {
38872 return;
38873 }
38874
38875 forEach(directions, function(direction) {
38876 self.createResizer(shape, direction);
38877 });
38878 };
38879
38880 /**
38881 * Remove all resizers
38882 */
38883 ResizeHandles.prototype.removeResizers = function() {
38884 var resizersParent = this._getResizersParent();
38885
38886 clear$1(resizersParent);
38887 };
38888
38889 ResizeHandles.prototype._getResizersParent = function() {
38890 return this._canvas.getLayer('resizers');
38891 };
38892
38893 ResizeHandles.$inject = [
38894 'eventBus',
38895 'canvas',
38896 'selection',
38897 'resize'
38898 ];
38899
38900 // helpers //////////
38901
38902 function getHandleOffset(direction) {
38903 var offset = {
38904 x: 0,
38905 y: 0
38906 };
38907
38908 if (direction.indexOf('e') !== -1) {
38909 offset.x = -HANDLE_OFFSET;
38910 } else if (direction.indexOf('w') !== -1) {
38911 offset.x = HANDLE_OFFSET;
38912 }
38913
38914 if (direction.indexOf('s') !== -1) {
38915 offset.y = -HANDLE_OFFSET;
38916 } else if (direction.indexOf('n') !== -1) {
38917 offset.y = HANDLE_OFFSET;
38918 }
38919
38920 return offset;
38921 }
38922
38923 var ResizeModule = {
38924 __depends__: [
38925 RulesModule,
38926 DraggingModule,
38927 PreviewSupportModule
38928 ],
38929 __init__: [
38930 'resize',
38931 'resizePreview',
38932 'resizeHandles'
38933 ],
38934 resize: [ 'type', Resize ],
38935 resizePreview: [ 'type', ResizePreview ],
38936 resizeHandles: [ 'type', ResizeHandles ]
38937 };
38938
38939 /**
38940 * Creates a new bpmn:CategoryValue inside a new bpmn:Category
38941 *
38942 * @param {ModdleElement} definitions
38943 * @param {BpmnFactory} bpmnFactory
38944 *
38945 * @return {ModdleElement} categoryValue.
38946 */
38947 function createCategoryValue(definitions, bpmnFactory) {
38948 var categoryValue = bpmnFactory.create('bpmn:CategoryValue'),
38949 category = bpmnFactory.create('bpmn:Category', {
38950 categoryValue: [ categoryValue ]
38951 });
38952
38953 // add to correct place
38954 add$1(definitions.get('rootElements'), category);
38955 getBusinessObject(category).$parent = definitions;
38956 getBusinessObject(categoryValue).$parent = category;
38957
38958 return categoryValue;
38959
38960 }
38961
38962 function LabelEditingProvider(
38963 eventBus, bpmnFactory, canvas, directEditing,
38964 modeling, resizeHandles, textRenderer) {
38965
38966 this._bpmnFactory = bpmnFactory;
38967 this._canvas = canvas;
38968 this._modeling = modeling;
38969 this._textRenderer = textRenderer;
38970
38971 directEditing.registerProvider(this);
38972
38973 // listen to dblclick on non-root elements
38974 eventBus.on('element.dblclick', function(event) {
38975 activateDirectEdit(event.element, true);
38976 });
38977
38978 // complete on followup canvas operation
38979 eventBus.on([
38980 'autoPlace.start',
38981 'canvas.viewbox.changing',
38982 'drag.init',
38983 'element.mousedown',
38984 'popupMenu.open'
38985 ], function(event) {
38986
38987 if (directEditing.isActive()) {
38988 directEditing.complete();
38989 }
38990 });
38991
38992 // cancel on command stack changes
38993 eventBus.on([ 'commandStack.changed' ], function(e) {
38994 if (directEditing.isActive()) {
38995 directEditing.cancel();
38996 }
38997 });
38998
38999
39000 eventBus.on('directEditing.activate', function(event) {
39001 resizeHandles.removeResizers();
39002 });
39003
39004 eventBus.on('create.end', 500, function(event) {
39005
39006 var context = event.context,
39007 element = context.shape,
39008 canExecute = event.context.canExecute,
39009 isTouch = event.isTouch;
39010
39011 // TODO(nikku): we need to find a way to support the
39012 // direct editing on mobile devices; right now this will
39013 // break for desworkflowediting on mobile devices
39014 // as it breaks the user interaction workflow
39015
39016 // TODO(nre): we should temporarily focus the edited element
39017 // here and release the focused viewport after the direct edit
39018 // operation is finished
39019 if (isTouch) {
39020 return;
39021 }
39022
39023 if (!canExecute) {
39024 return;
39025 }
39026
39027 if (context.hints && context.hints.createElementsBehavior === false) {
39028 return;
39029 }
39030
39031 activateDirectEdit(element);
39032 });
39033
39034 eventBus.on('autoPlace.end', 500, function(event) {
39035 activateDirectEdit(event.shape);
39036 });
39037
39038
39039 function activateDirectEdit(element, force) {
39040 if (force ||
39041 isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Group' ]) ||
39042 isCollapsedSubProcess(element)) {
39043
39044 directEditing.activate(element);
39045 }
39046 }
39047
39048 }
39049
39050 LabelEditingProvider.$inject = [
39051 'eventBus',
39052 'bpmnFactory',
39053 'canvas',
39054 'directEditing',
39055 'modeling',
39056 'resizeHandles',
39057 'textRenderer'
39058 ];
39059
39060
39061 /**
39062 * Activate direct editing for activities and text annotations.
39063 *
39064 * @param {djs.model.Base} element
39065 *
39066 * @return {Object} an object with properties bounds (position and size), text and options
39067 */
39068 LabelEditingProvider.prototype.activate = function(element) {
39069
39070 // text
39071 var text = getLabel(element);
39072
39073 if (text === undefined) {
39074 return;
39075 }
39076
39077 var context = {
39078 text: text
39079 };
39080
39081 // bounds
39082 var bounds = this.getEditingBBox(element);
39083
39084 assign(context, bounds);
39085
39086 var options = {};
39087
39088 // tasks
39089 if (
39090 isAny(element, [
39091 'bpmn:Task',
39092 'bpmn:Participant',
39093 'bpmn:Lane',
39094 'bpmn:CallActivity'
39095 ]) ||
39096 isCollapsedSubProcess(element)
39097 ) {
39098 assign(options, {
39099 centerVertically: true
39100 });
39101 }
39102
39103 // external labels
39104 if (isLabelExternal(element)) {
39105 assign(options, {
39106 autoResize: true
39107 });
39108 }
39109
39110 // text annotations
39111 if (is$1(element, 'bpmn:TextAnnotation')) {
39112 assign(options, {
39113 resizable: true,
39114 autoResize: true
39115 });
39116 }
39117
39118 assign(context, {
39119 options: options
39120 });
39121
39122 return context;
39123 };
39124
39125
39126 /**
39127 * Get the editing bounding box based on the element's size and position
39128 *
39129 * @param {djs.model.Base} element
39130 *
39131 * @return {Object} an object containing information about position
39132 * and size (fixed or minimum and/or maximum)
39133 */
39134 LabelEditingProvider.prototype.getEditingBBox = function(element) {
39135 var canvas = this._canvas;
39136
39137 var target = element.label || element;
39138
39139 var bbox = canvas.getAbsoluteBBox(target);
39140
39141 var mid = {
39142 x: bbox.x + bbox.width / 2,
39143 y: bbox.y + bbox.height / 2
39144 };
39145
39146 // default position
39147 var bounds = { x: bbox.x, y: bbox.y };
39148
39149 var zoom = canvas.zoom();
39150
39151 var defaultStyle = this._textRenderer.getDefaultStyle(),
39152 externalStyle = this._textRenderer.getExternalStyle();
39153
39154 // take zoom into account
39155 var externalFontSize = externalStyle.fontSize * zoom,
39156 externalLineHeight = externalStyle.lineHeight,
39157 defaultFontSize = defaultStyle.fontSize * zoom,
39158 defaultLineHeight = defaultStyle.lineHeight;
39159
39160 var style = {
39161 fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
39162 fontWeight: this._textRenderer.getDefaultStyle().fontWeight
39163 };
39164
39165 // adjust for expanded pools AND lanes
39166 if (is$1(element, 'bpmn:Lane') || isExpandedPool(element)) {
39167
39168 assign(bounds, {
39169 width: bbox.height,
39170 height: 30 * zoom,
39171 x: bbox.x - bbox.height / 2 + (15 * zoom),
39172 y: mid.y - (30 * zoom) / 2
39173 });
39174
39175 assign(style, {
39176 fontSize: defaultFontSize + 'px',
39177 lineHeight: defaultLineHeight,
39178 paddingTop: (7 * zoom) + 'px',
39179 paddingBottom: (7 * zoom) + 'px',
39180 paddingLeft: (5 * zoom) + 'px',
39181 paddingRight: (5 * zoom) + 'px',
39182 transform: 'rotate(-90deg)'
39183 });
39184 }
39185
39186
39187 // internal labels for tasks and collapsed call activities,
39188 // sub processes and participants
39189 if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity']) ||
39190 isCollapsedPool(element) ||
39191 isCollapsedSubProcess(element)) {
39192
39193 assign(bounds, {
39194 width: bbox.width,
39195 height: bbox.height
39196 });
39197
39198 assign(style, {
39199 fontSize: defaultFontSize + 'px',
39200 lineHeight: defaultLineHeight,
39201 paddingTop: (7 * zoom) + 'px',
39202 paddingBottom: (7 * zoom) + 'px',
39203 paddingLeft: (5 * zoom) + 'px',
39204 paddingRight: (5 * zoom) + 'px'
39205 });
39206 }
39207
39208
39209 // internal labels for expanded sub processes
39210 if (isExpandedSubProcess(element)) {
39211 assign(bounds, {
39212 width: bbox.width,
39213 x: bbox.x
39214 });
39215
39216 assign(style, {
39217 fontSize: defaultFontSize + 'px',
39218 lineHeight: defaultLineHeight,
39219 paddingTop: (7 * zoom) + 'px',
39220 paddingBottom: (7 * zoom) + 'px',
39221 paddingLeft: (5 * zoom) + 'px',
39222 paddingRight: (5 * zoom) + 'px'
39223 });
39224 }
39225
39226 var width = 90 * zoom,
39227 paddingTop = 7 * zoom,
39228 paddingBottom = 4 * zoom;
39229
39230 // external labels for events, data elements, gateways, groups and connections
39231 if (target.labelTarget) {
39232 assign(bounds, {
39233 width: width,
39234 height: bbox.height + paddingTop + paddingBottom,
39235 x: mid.x - width / 2,
39236 y: bbox.y - paddingTop
39237 });
39238
39239 assign(style, {
39240 fontSize: externalFontSize + 'px',
39241 lineHeight: externalLineHeight,
39242 paddingTop: paddingTop + 'px',
39243 paddingBottom: paddingBottom + 'px'
39244 });
39245 }
39246
39247 // external label not yet created
39248 if (isLabelExternal(target)
39249 && !hasExternalLabel(target)
39250 && !isLabel(target)) {
39251
39252 var externalLabelMid = getExternalLabelMid(element);
39253
39254 var absoluteBBox = canvas.getAbsoluteBBox({
39255 x: externalLabelMid.x,
39256 y: externalLabelMid.y,
39257 width: 0,
39258 height: 0
39259 });
39260
39261 var height = externalFontSize + paddingTop + paddingBottom;
39262
39263 assign(bounds, {
39264 width: width,
39265 height: height,
39266 x: absoluteBBox.x - width / 2,
39267 y: absoluteBBox.y - height / 2
39268 });
39269
39270 assign(style, {
39271 fontSize: externalFontSize + 'px',
39272 lineHeight: externalLineHeight,
39273 paddingTop: paddingTop + 'px',
39274 paddingBottom: paddingBottom + 'px'
39275 });
39276 }
39277
39278 // text annotations
39279 if (is$1(element, 'bpmn:TextAnnotation')) {
39280 assign(bounds, {
39281 width: bbox.width,
39282 height: bbox.height,
39283 minWidth: 30 * zoom,
39284 minHeight: 10 * zoom
39285 });
39286
39287 assign(style, {
39288 textAlign: 'left',
39289 paddingTop: (5 * zoom) + 'px',
39290 paddingBottom: (7 * zoom) + 'px',
39291 paddingLeft: (7 * zoom) + 'px',
39292 paddingRight: (5 * zoom) + 'px',
39293 fontSize: defaultFontSize + 'px',
39294 lineHeight: defaultLineHeight
39295 });
39296 }
39297
39298 return { bounds: bounds, style: style };
39299 };
39300
39301
39302 LabelEditingProvider.prototype.update = function(
39303 element, newLabel,
39304 activeContextText, bounds) {
39305
39306 var newBounds,
39307 bbox;
39308
39309 if (is$1(element, 'bpmn:TextAnnotation')) {
39310
39311 bbox = this._canvas.getAbsoluteBBox(element);
39312
39313 newBounds = {
39314 x: element.x,
39315 y: element.y,
39316 width: element.width / bbox.width * bounds.width,
39317 height: element.height / bbox.height * bounds.height
39318 };
39319 }
39320
39321 if (is$1(element, 'bpmn:Group')) {
39322
39323 var businessObject = getBusinessObject(element);
39324
39325 // initialize categoryValue if not existing
39326 if (!businessObject.categoryValueRef) {
39327
39328 var rootElement = this._canvas.getRootElement(),
39329 definitions = getBusinessObject(rootElement).$parent;
39330
39331 var categoryValue = createCategoryValue(definitions, this._bpmnFactory);
39332
39333 getBusinessObject(element).categoryValueRef = categoryValue;
39334 }
39335
39336 }
39337
39338 if (isEmptyText(newLabel)) {
39339 newLabel = null;
39340 }
39341
39342 this._modeling.updateLabel(element, newLabel, newBounds);
39343 };
39344
39345
39346
39347 // helpers //////////////////////
39348
39349 function isCollapsedSubProcess(element) {
39350 return is$1(element, 'bpmn:SubProcess') && !isExpanded(element);
39351 }
39352
39353 function isExpandedSubProcess(element) {
39354 return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
39355 }
39356
39357 function isCollapsedPool(element) {
39358 return is$1(element, 'bpmn:Participant') && !isExpanded(element);
39359 }
39360
39361 function isExpandedPool(element) {
39362 return is$1(element, 'bpmn:Participant') && isExpanded(element);
39363 }
39364
39365 function isEmptyText(label) {
39366 return !label || !label.trim();
39367 }
39368
39369 var MARKER_HIDDEN = 'djs-element-hidden',
39370 MARKER_LABEL_HIDDEN = 'djs-label-hidden';
39371
39372
39373 function LabelEditingPreview(
39374 eventBus, canvas, elementRegistry,
39375 pathMap) {
39376
39377 var self = this;
39378
39379 var defaultLayer = canvas.getDefaultLayer();
39380
39381 var element, absoluteElementBBox, gfx;
39382
39383 eventBus.on('directEditing.activate', function(context) {
39384 var activeProvider = context.active;
39385
39386 element = activeProvider.element.label || activeProvider.element;
39387
39388 // text annotation
39389 if (is$1(element, 'bpmn:TextAnnotation')) {
39390 absoluteElementBBox = canvas.getAbsoluteBBox(element);
39391
39392 gfx = create('g');
39393
39394 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
39395 xScaleFactor: 1,
39396 yScaleFactor: 1,
39397 containerWidth: element.width,
39398 containerHeight: element.height,
39399 position: {
39400 mx: 0.0,
39401 my: 0.0
39402 }
39403 });
39404
39405 var path = self.path = create('path');
39406
39407 attr$1(path, {
39408 d: textPathData,
39409 strokeWidth: 2,
39410 stroke: getStrokeColor$1(element)
39411 });
39412
39413 append(gfx, path);
39414
39415 append(defaultLayer, gfx);
39416
39417 translate(gfx, element.x, element.y);
39418 }
39419
39420 if (is$1(element, 'bpmn:TextAnnotation') ||
39421 element.labelTarget) {
39422 canvas.addMarker(element, MARKER_HIDDEN);
39423 } else if (is$1(element, 'bpmn:Task') ||
39424 is$1(element, 'bpmn:CallActivity') ||
39425 is$1(element, 'bpmn:SubProcess') ||
39426 is$1(element, 'bpmn:Participant')) {
39427 canvas.addMarker(element, MARKER_LABEL_HIDDEN);
39428 }
39429 });
39430
39431 eventBus.on('directEditing.resize', function(context) {
39432
39433 // text annotation
39434 if (is$1(element, 'bpmn:TextAnnotation')) {
39435 var height = context.height,
39436 dy = context.dy;
39437
39438 var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
39439
39440 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
39441 xScaleFactor: 1,
39442 yScaleFactor: 1,
39443 containerWidth: element.width,
39444 containerHeight: newElementHeight,
39445 position: {
39446 mx: 0.0,
39447 my: 0.0
39448 }
39449 });
39450
39451 attr$1(self.path, {
39452 d: textPathData
39453 });
39454 }
39455 });
39456
39457 eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
39458 var activeProvider = context.active;
39459
39460 if (activeProvider) {
39461 canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
39462 canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
39463 }
39464
39465 element = undefined;
39466 absoluteElementBBox = undefined;
39467
39468 if (gfx) {
39469 remove$1(gfx);
39470
39471 gfx = undefined;
39472 }
39473 });
39474 }
39475
39476 LabelEditingPreview.$inject = [
39477 'eventBus',
39478 'canvas',
39479 'elementRegistry',
39480 'pathMap'
39481 ];
39482
39483
39484 // helpers ///////////////////
39485
39486 function getStrokeColor$1(element, defaultColor) {
39487 var bo = getBusinessObject(element);
39488
39489 return bo.di.get('stroke') || defaultColor || 'black';
39490 }
39491
39492 var LabelEditingModule = {
39493 __depends__: [
39494 ChangeSupportModule,
39495 ResizeModule,
39496 DirectEditingModule
39497 ],
39498 __init__: [
39499 'labelEditingProvider',
39500 'labelEditingPreview'
39501 ],
39502 labelEditingProvider: [ 'type', LabelEditingProvider ],
39503 labelEditingPreview: [ 'type', LabelEditingPreview ]
39504 };
39505
39506 var ALIGNMENTS = [
39507 'top',
39508 'bottom',
39509 'left',
39510 'right'
39511 ];
39512
39513 var ELEMENT_LABEL_DISTANCE$1 = 10;
39514
39515 /**
39516 * A component that makes sure that external labels are added
39517 * together with respective elements and properly updated (DI wise)
39518 * during move.
39519 *
39520 * @param {EventBus} eventBus
39521 * @param {Modeling} modeling
39522 */
39523 function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
39524
39525 CommandInterceptor.call(this, eventBus);
39526
39527 this.postExecuted([
39528 'connection.create',
39529 'connection.layout',
39530 'connection.updateWaypoints'
39531 ], function(event) {
39532 var context = event.context,
39533 connection = context.connection,
39534 source = connection.source,
39535 target = connection.target,
39536 hints = context.hints || {};
39537
39538 if (hints.createElementsBehavior !== false) {
39539 checkLabelAdjustment(source);
39540 checkLabelAdjustment(target);
39541 }
39542 });
39543
39544
39545 this.postExecuted([
39546 'label.create'
39547 ], function(event) {
39548 var context = event.context,
39549 shape = context.shape,
39550 hints = context.hints || {};
39551
39552 if (hints.createElementsBehavior !== false) {
39553 checkLabelAdjustment(shape.labelTarget);
39554 }
39555 });
39556
39557
39558 this.postExecuted([
39559 'elements.create'
39560 ], function(event) {
39561 var context = event.context,
39562 elements = context.elements,
39563 hints = context.hints || {};
39564
39565 if (hints.createElementsBehavior !== false) {
39566 elements.forEach(function(element) {
39567 checkLabelAdjustment(element);
39568 });
39569 }
39570 });
39571
39572 function checkLabelAdjustment(element) {
39573
39574 // skip non-existing labels
39575 if (!hasExternalLabel(element)) {
39576 return;
39577 }
39578
39579 var optimalPosition = getOptimalPosition(element);
39580
39581 // no optimal position found
39582 if (!optimalPosition) {
39583 return;
39584 }
39585
39586 adjustLabelPosition(element, optimalPosition);
39587 }
39588
39589 function adjustLabelPosition(element, orientation) {
39590
39591 var elementMid = getMid(element),
39592 label = element.label,
39593 labelMid = getMid(label);
39594
39595 // ignore labels that are being created
39596 if (!label.parent) {
39597 return;
39598 }
39599
39600 var elementTrbl = asTRBL(element);
39601
39602 var newLabelMid;
39603
39604 switch (orientation) {
39605 case 'top':
39606 newLabelMid = {
39607 x: elementMid.x,
39608 y: elementTrbl.top - ELEMENT_LABEL_DISTANCE$1 - label.height / 2
39609 };
39610
39611 break;
39612
39613 case 'left':
39614
39615 newLabelMid = {
39616 x: elementTrbl.left - ELEMENT_LABEL_DISTANCE$1 - label.width / 2,
39617 y: elementMid.y
39618 };
39619
39620 break;
39621
39622 case 'bottom':
39623
39624 newLabelMid = {
39625 x: elementMid.x,
39626 y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE$1 + label.height / 2
39627 };
39628
39629 break;
39630
39631 case 'right':
39632
39633 newLabelMid = {
39634 x: elementTrbl.right + ELEMENT_LABEL_DISTANCE$1 + label.width / 2,
39635 y: elementMid.y
39636 };
39637
39638 break;
39639 }
39640
39641 var delta$1 = delta(newLabelMid, labelMid);
39642
39643 modeling.moveShape(label, delta$1);
39644 }
39645
39646 }
39647
39648 inherits_browser(AdaptiveLabelPositioningBehavior, CommandInterceptor);
39649
39650 AdaptiveLabelPositioningBehavior.$inject = [
39651 'eventBus',
39652 'modeling'
39653 ];
39654
39655
39656 // helpers //////////////////////
39657
39658 /**
39659 * Return alignments which are taken by a boundary's host element
39660 *
39661 * @param {Shape} element
39662 *
39663 * @return {Array<string>}
39664 */
39665 function getTakenHostAlignments(element) {
39666
39667 var hostElement = element.host,
39668 elementMid = getMid(element),
39669 hostOrientation = getOrientation(elementMid, hostElement);
39670
39671 var freeAlignments;
39672
39673 // check whether there is a multi-orientation, e.g. 'top-left'
39674 if (hostOrientation.indexOf('-') >= 0) {
39675 freeAlignments = hostOrientation.split('-');
39676 } else {
39677 freeAlignments = [ hostOrientation ];
39678 }
39679
39680 var takenAlignments = ALIGNMENTS.filter(function(alignment) {
39681
39682 return freeAlignments.indexOf(alignment) === -1;
39683 });
39684
39685 return takenAlignments;
39686
39687 }
39688
39689 /**
39690 * Return alignments which are taken by related connections
39691 *
39692 * @param {Shape} element
39693 *
39694 * @return {Array<string>}
39695 */
39696 function getTakenConnectionAlignments(element) {
39697
39698 var elementMid = getMid(element);
39699
39700 var takenAlignments = [].concat(
39701 element.incoming.map(function(c) {
39702 return c.waypoints[c.waypoints.length - 2 ];
39703 }),
39704 element.outgoing.map(function(c) {
39705 return c.waypoints[1];
39706 })
39707 ).map(function(point) {
39708 return getApproximateOrientation(elementMid, point);
39709 });
39710
39711 return takenAlignments;
39712 }
39713
39714 /**
39715 * Return the optimal label position around an element
39716 * or _undefined_, if none was found.
39717 *
39718 * @param {Shape} element
39719 *
39720 * @return {string} positioning identifier
39721 */
39722 function getOptimalPosition(element) {
39723
39724 var labelMid = getMid(element.label);
39725
39726 var elementMid = getMid(element);
39727
39728 var labelOrientation = getApproximateOrientation(elementMid, labelMid);
39729
39730 if (!isAligned(labelOrientation)) {
39731 return;
39732 }
39733
39734 var takenAlignments = getTakenConnectionAlignments(element);
39735
39736 if (element.host) {
39737 var takenHostAlignments = getTakenHostAlignments(element);
39738
39739 takenAlignments = takenAlignments.concat(takenHostAlignments);
39740 }
39741
39742 var freeAlignments = ALIGNMENTS.filter(function(alignment) {
39743
39744 return takenAlignments.indexOf(alignment) === -1;
39745 });
39746
39747 // NOTHING TO DO; label already aligned a.O.K.
39748 if (freeAlignments.indexOf(labelOrientation) !== -1) {
39749 return;
39750 }
39751
39752 return freeAlignments[0];
39753 }
39754
39755 function getApproximateOrientation(p0, p1) {
39756 return getOrientation(p1, p0, 5);
39757 }
39758
39759 function isAligned(orientation) {
39760 return ALIGNMENTS.indexOf(orientation) !== -1;
39761 }
39762
39763 function AppendBehavior(eventBus, elementFactory, bpmnRules) {
39764
39765 CommandInterceptor.call(this, eventBus);
39766
39767 // assign correct shape position unless already set
39768
39769 this.preExecute('shape.append', function(context) {
39770
39771 var source = context.source,
39772 shape = context.shape;
39773
39774 if (!context.position) {
39775
39776 if (is$1(shape, 'bpmn:TextAnnotation')) {
39777 context.position = {
39778 x: source.x + source.width / 2 + 75,
39779 y: source.y - (50) - shape.height / 2
39780 };
39781 } else {
39782 context.position = {
39783 x: source.x + source.width + 80 + shape.width / 2,
39784 y: source.y + source.height / 2
39785 };
39786 }
39787 }
39788 }, true);
39789 }
39790
39791 inherits_browser(AppendBehavior, CommandInterceptor);
39792
39793 AppendBehavior.$inject = [
39794 'eventBus',
39795 'elementFactory',
39796 'bpmnRules'
39797 ];
39798
39799 function AssociationBehavior(injector, modeling) {
39800 injector.invoke(CommandInterceptor, this);
39801
39802 this.postExecute('shape.move', function(context) {
39803 var newParent = context.newParent,
39804 shape = context.shape;
39805
39806 var associations = filter(shape.incoming.concat(shape.outgoing), function(connection) {
39807 return is$1(connection, 'bpmn:Association');
39808 });
39809
39810 forEach(associations, function(association) {
39811 modeling.moveConnection(association, { x: 0, y: 0 }, newParent);
39812 });
39813 }, true);
39814 }
39815
39816 inherits_browser(AssociationBehavior, CommandInterceptor);
39817
39818 AssociationBehavior.$inject = [
39819 'injector',
39820 'modeling'
39821 ];
39822
39823 var LOW_PRIORITY$a = 500;
39824
39825
39826 /**
39827 * Replace intermediate event with boundary event when creating or moving results in attached event.
39828 */
39829 function AttachEventBehavior(bpmnReplace, injector) {
39830 injector.invoke(CommandInterceptor, this);
39831
39832 this._bpmnReplace = bpmnReplace;
39833
39834 var self = this;
39835
39836 this.postExecuted('elements.create', LOW_PRIORITY$a, function(context) {
39837 var elements = context.elements;
39838
39839 elements = elements.filter(function(shape) {
39840 var host = shape.host;
39841
39842 return shouldReplace(shape, host);
39843 });
39844
39845 if (elements.length !== 1) {
39846 return;
39847 }
39848
39849 elements.map(function(element) {
39850 return elements.indexOf(element);
39851 }).forEach(function(index) {
39852 var host = elements[ index ];
39853
39854 context.elements[ index ] = self.replaceShape(elements[ index ], host);
39855 });
39856 }, true);
39857
39858
39859 this.preExecute('elements.move', LOW_PRIORITY$a, function(context) {
39860 var shapes = context.shapes,
39861 host = context.newHost;
39862
39863 if (shapes.length !== 1) {
39864 return;
39865 }
39866
39867 var shape = shapes[0];
39868
39869 if (shouldReplace(shape, host)) {
39870 context.shapes = [ self.replaceShape(shape, host) ];
39871 }
39872 }, true);
39873 }
39874
39875 AttachEventBehavior.$inject = [
39876 'bpmnReplace',
39877 'injector'
39878 ];
39879
39880 inherits_browser(AttachEventBehavior, CommandInterceptor);
39881
39882 AttachEventBehavior.prototype.replaceShape = function(shape, host) {
39883 var eventDefinition = getEventDefinition(shape);
39884
39885 var boundaryEvent = {
39886 type: 'bpmn:BoundaryEvent',
39887 host: host
39888 };
39889
39890 if (eventDefinition) {
39891 boundaryEvent.eventDefinitionType = eventDefinition.$type;
39892 }
39893
39894 return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
39895 };
39896
39897
39898 // helpers //////////
39899
39900 function getEventDefinition(element) {
39901 var businessObject = getBusinessObject(element),
39902 eventDefinitions = businessObject.eventDefinitions;
39903
39904 return eventDefinitions && eventDefinitions[0];
39905 }
39906
39907 function shouldReplace(shape, host) {
39908 return !isLabel(shape) &&
39909 isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host;
39910 }
39911
39912 var HIGH_PRIORITY$7 = 2000;
39913
39914
39915 /**
39916 * BPMN specific boundary event behavior
39917 */
39918 function BoundaryEventBehavior(eventBus, moddle, modeling) {
39919
39920 CommandInterceptor.call(this, eventBus);
39921
39922 function getBoundaryEvents(element) {
39923 return filter(element.attachers, function(attacher) {
39924 return is$1(attacher, 'bpmn:BoundaryEvent');
39925 });
39926 }
39927
39928 // remove after connecting to event-based gateway
39929 this.postExecute('connection.create', function(event) {
39930 var source = event.context.source,
39931 target = event.context.target,
39932 boundaryEvents = getBoundaryEvents(target);
39933
39934 if (
39935 is$1(source, 'bpmn:EventBasedGateway') &&
39936 is$1(target, 'bpmn:ReceiveTask') &&
39937 boundaryEvents.length > 0
39938 ) {
39939 modeling.removeElements(boundaryEvents);
39940 }
39941
39942 });
39943
39944 // remove after replacing connected gateway with event-based gateway
39945 this.postExecute('connection.reconnect', function(event) {
39946 var oldSource = event.context.oldSource,
39947 newSource = event.context.newSource;
39948
39949 if (is$1(oldSource, 'bpmn:Gateway') &&
39950 is$1(newSource, 'bpmn:EventBasedGateway')) {
39951 forEach(newSource.outgoing, function(connection) {
39952 var target = connection.target,
39953 attachedboundaryEvents = getBoundaryEvents(target);
39954
39955 if (is$1(target, 'bpmn:ReceiveTask') &&
39956 attachedboundaryEvents.length > 0) {
39957 modeling.removeElements(attachedboundaryEvents);
39958 }
39959 });
39960 }
39961 });
39962
39963 // copy reference to root element on replace
39964 eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY$7, function(context) {
39965 var parent = context.parent,
39966 property = context.property,
39967 propertyName = context.propertyName;
39968
39969 var propertyDescriptor = moddle.getPropertyDescriptor(parent, propertyName);
39970
39971 if (propertyDescriptor && propertyDescriptor.isReference && is$1(property, 'bpmn:RootElement')) {
39972 parent.set(propertyName, property);
39973 }
39974 });
39975 }
39976
39977 BoundaryEventBehavior.$inject = [
39978 'eventBus',
39979 'moddle',
39980 'modeling'
39981 ];
39982
39983 inherits_browser(BoundaryEventBehavior, CommandInterceptor);
39984
39985 var LOW_PRIORITY$b = 500;
39986
39987
39988 /**
39989 * Add referenced root elements (error, escalation, message, signal) if they don't exist.
39990 * Copy referenced root elements on copy & paste.
39991 */
39992 function RootElementReferenceBehavior(
39993 bpmnjs, eventBus, injector, moddleCopy, bpmnFactory
39994 ) {
39995 injector.invoke(CommandInterceptor, this);
39996
39997 function canHaveRootElementReference(element) {
39998 return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) ||
39999 hasAnyEventDefinition(element, [
40000 'bpmn:ErrorEventDefinition',
40001 'bpmn:EscalationEventDefinition',
40002 'bpmn:MessageEventDefinition',
40003 'bpmn:SignalEventDefinition'
40004 ]);
40005 }
40006
40007 function hasRootElement(rootElement) {
40008 var definitions = bpmnjs.getDefinitions(),
40009 rootElements = definitions.get('rootElements');
40010
40011 return !!find(rootElements, matchPattern({ id: rootElement.id }));
40012 }
40013
40014 function getRootElementReferencePropertyName(eventDefinition) {
40015 if (is$1(eventDefinition, 'bpmn:ErrorEventDefinition')) {
40016 return 'errorRef';
40017 } else if (is$1(eventDefinition, 'bpmn:EscalationEventDefinition')) {
40018 return 'escalationRef';
40019 } else if (is$1(eventDefinition, 'bpmn:MessageEventDefinition')) {
40020 return 'messageRef';
40021 } else if (is$1(eventDefinition, 'bpmn:SignalEventDefinition')) {
40022 return 'signalRef';
40023 }
40024 }
40025
40026 function getRootElement(businessObject) {
40027 if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
40028 return businessObject.get('messageRef');
40029 }
40030
40031 var eventDefinitions = businessObject.get('eventDefinitions'),
40032 eventDefinition = eventDefinitions[ 0 ];
40033
40034 return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
40035 }
40036
40037 function setRootElement(businessObject, rootElement) {
40038 if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
40039 return businessObject.set('messageRef', rootElement);
40040 }
40041
40042 var eventDefinitions = businessObject.get('eventDefinitions'),
40043 eventDefinition = eventDefinitions[ 0 ];
40044
40045 return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement);
40046 }
40047
40048 // create shape
40049 this.executed('shape.create', function(context) {
40050 var shape = context.shape;
40051
40052 if (!canHaveRootElementReference(shape)) {
40053 return;
40054 }
40055
40056 var businessObject = getBusinessObject(shape),
40057 rootElement = getRootElement(businessObject),
40058 rootElements;
40059
40060 if (rootElement && !hasRootElement(rootElement)) {
40061 rootElements = bpmnjs.getDefinitions().get('rootElements');
40062
40063 // add root element
40064 add$1(rootElements, rootElement);
40065
40066 context.addedRootElement = rootElement;
40067 }
40068 }, true);
40069
40070 this.reverted('shape.create', function(context) {
40071 var addedRootElement = context.addedRootElement;
40072
40073 if (!addedRootElement) {
40074 return;
40075 }
40076
40077 var rootElements = bpmnjs.getDefinitions().get('rootElements');
40078
40079 // remove root element
40080 remove$2(rootElements, addedRootElement);
40081 }, true);
40082
40083 eventBus.on('copyPaste.copyElement', function(context) {
40084 var descriptor = context.descriptor,
40085 element = context.element;
40086
40087 if (!canHaveRootElementReference(element)) {
40088 return;
40089 }
40090
40091 var businessObject = getBusinessObject(element),
40092 rootElement = getRootElement(businessObject);
40093
40094 if (rootElement) {
40095 descriptor.referencedRootElement = rootElement;
40096 }
40097 });
40098
40099 eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$b, function(context) {
40100 var descriptor = context.descriptor,
40101 businessObject = descriptor.businessObject;
40102
40103 if (!canHaveRootElementReference(businessObject)) {
40104 return;
40105 }
40106
40107 var referencedRootElement = descriptor.referencedRootElement;
40108
40109 if (!referencedRootElement) {
40110 return;
40111 }
40112
40113 if (!hasRootElement(referencedRootElement)) {
40114 referencedRootElement = moddleCopy.copyElement(
40115 referencedRootElement,
40116 bpmnFactory.create(referencedRootElement.$type)
40117 );
40118 }
40119
40120 setRootElement(businessObject, referencedRootElement);
40121 });
40122 }
40123
40124 RootElementReferenceBehavior.$inject = [
40125 'bpmnjs',
40126 'eventBus',
40127 'injector',
40128 'moddleCopy',
40129 'bpmnFactory'
40130 ];
40131
40132 inherits_browser(RootElementReferenceBehavior, CommandInterceptor);
40133
40134 // helpers //////////
40135
40136 function hasAnyEventDefinition(element, types) {
40137 if (!isArray(types)) {
40138 types = [ types ];
40139 }
40140
40141 return some(types, function(type) {
40142 return hasEventDefinition(element, type);
40143 });
40144 }
40145
40146 function CreateBehavior(injector) {
40147 injector.invoke(CommandInterceptor, this);
40148
40149 this.preExecute('shape.create', 1500, function(event) {
40150 var context = event.context,
40151 parent = context.parent,
40152 shape = context.shape;
40153
40154 if (is$1(parent, 'bpmn:Lane') && !is$1(shape, 'bpmn:Lane')) {
40155 context.parent = getParent$1(parent, 'bpmn:Participant');
40156 }
40157 });
40158
40159 }
40160
40161
40162 CreateBehavior.$inject = [ 'injector' ];
40163
40164 inherits_browser(CreateBehavior, CommandInterceptor);
40165
40166 var HIGH_PRIORITY$8 = 1500;
40167 var HIGHEST_PRIORITY = 2000;
40168
40169
40170 /**
40171 * Correct hover targets in certain situations to improve diagram interaction.
40172 *
40173 * @param {ElementRegistry} elementRegistry
40174 * @param {EventBus} eventBus
40175 * @param {Canvas} canvas
40176 */
40177 function FixHoverBehavior(elementRegistry, eventBus, canvas) {
40178
40179 eventBus.on([
40180 'create.hover',
40181 'create.move',
40182 'create.out',
40183 'create.end',
40184 'shape.move.hover',
40185 'shape.move.move',
40186 'shape.move.out',
40187 'shape.move.end'
40188 ], HIGH_PRIORITY$8, function(event) {
40189 var context = event.context,
40190 shape = context.shape || event.shape,
40191 hover = event.hover;
40192
40193 // ensure elements are not dropped onto a bpmn:Lane but onto
40194 // the underlying bpmn:Participant
40195 if (is$1(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
40196 event.hover = getLanesRoot(hover);
40197 event.hoverGfx = elementRegistry.getGraphics(event.hover);
40198 }
40199
40200 var rootElement = canvas.getRootElement();
40201
40202 // ensure bpmn:Group and label elements are dropped
40203 // always onto the root
40204 if (hover !== rootElement && (shape.labelTarget || is$1(shape, 'bpmn:Group'))) {
40205 event.hover = rootElement;
40206 event.hoverGfx = elementRegistry.getGraphics(event.hover);
40207 }
40208 });
40209
40210 eventBus.on([
40211 'connect.hover',
40212 'connect.out',
40213 'connect.end',
40214 'connect.cleanup',
40215 'global-connect.hover',
40216 'global-connect.out',
40217 'global-connect.end',
40218 'global-connect.cleanup'
40219 ], HIGH_PRIORITY$8, function(event) {
40220 var hover = event.hover;
40221
40222 // ensure connections start/end on bpmn:Participant,
40223 // not the underlying bpmn:Lane
40224 if (is$1(hover, 'bpmn:Lane')) {
40225 event.hover = getLanesRoot(hover) || hover;
40226 event.hoverGfx = elementRegistry.getGraphics(event.hover);
40227 }
40228 });
40229
40230
40231 eventBus.on([
40232 'bendpoint.move.hover'
40233 ], HIGH_PRIORITY$8, function(event) {
40234 var context = event.context,
40235 hover = event.hover,
40236 type = context.type;
40237
40238 // ensure reconnect start/end on bpmn:Participant,
40239 // not the underlying bpmn:Lane
40240 if (is$1(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
40241 event.hover = getLanesRoot(hover) || hover;
40242 event.hoverGfx = elementRegistry.getGraphics(event.hover);
40243 }
40244 });
40245
40246
40247 eventBus.on([
40248 'connect.start'
40249 ], HIGH_PRIORITY$8, function(event) {
40250 var context = event.context,
40251 start = context.start;
40252
40253 // ensure connect start on bpmn:Participant,
40254 // not the underlying bpmn:Lane
40255 if (is$1(start, 'bpmn:Lane')) {
40256 context.start = getLanesRoot(start) || start;
40257 }
40258 });
40259
40260
40261 // allow movement of participants from lanes
40262 eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
40263 var shape = event.shape;
40264
40265 if (is$1(shape, 'bpmn:Lane')) {
40266 event.shape = getLanesRoot(shape) || shape;
40267 }
40268 });
40269
40270 }
40271
40272 FixHoverBehavior.$inject = [
40273 'elementRegistry',
40274 'eventBus',
40275 'canvas'
40276 ];
40277
40278 /**
40279 * BPMN specific create data object behavior
40280 */
40281 function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) {
40282
40283 CommandInterceptor.call(this, eventBus);
40284
40285 this.preExecute('shape.create', function(event) {
40286
40287 var context = event.context,
40288 shape = context.shape;
40289
40290 if (is$1(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
40291
40292 // create a DataObject every time a DataObjectReference is created
40293 var dataObject = bpmnFactory.create('bpmn:DataObject');
40294
40295 // set the reference to the DataObject
40296 shape.businessObject.dataObjectRef = dataObject;
40297 }
40298 });
40299
40300 }
40301
40302 CreateDataObjectBehavior.$inject = [
40303 'eventBus',
40304 'bpmnFactory',
40305 'moddle'
40306 ];
40307
40308 inherits_browser(CreateDataObjectBehavior, CommandInterceptor);
40309
40310 var HORIZONTAL_PARTICIPANT_PADDING = 20,
40311 VERTICAL_PARTICIPANT_PADDING = 20;
40312
40313 var PARTICIPANT_BORDER_WIDTH = 30;
40314
40315 var HIGH_PRIORITY$9 = 2000;
40316
40317
40318 /**
40319 * BPMN-specific behavior for creating participants.
40320 */
40321 function CreateParticipantBehavior$1(canvas, eventBus, modeling) {
40322 CommandInterceptor.call(this, eventBus);
40323
40324 // fit participant
40325 eventBus.on([
40326 'create.start',
40327 'shape.move.start'
40328 ], HIGH_PRIORITY$9, function(event) {
40329 var context = event.context,
40330 shape = context.shape,
40331 rootElement = canvas.getRootElement();
40332
40333 if (!is$1(shape, 'bpmn:Participant') ||
40334 !is$1(rootElement, 'bpmn:Process') ||
40335 !rootElement.children.length) {
40336 return;
40337 }
40338
40339 // ignore connections, groups and labels
40340 var children = rootElement.children.filter(function(element) {
40341 return !is$1(element, 'bpmn:Group') &&
40342 !isLabel(element) &&
40343 !isConnection$2(element);
40344 });
40345
40346 // ensure for available children to calculate bounds
40347 if (!children.length) {
40348 return;
40349 }
40350
40351 var childrenBBox = getBBox(children);
40352
40353 var participantBounds = getParticipantBounds(shape, childrenBBox);
40354
40355 // assign width and height
40356 assign(shape, participantBounds);
40357
40358 // assign create constraints
40359 context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
40360 });
40361
40362 // force hovering process when creating first participant
40363 eventBus.on('create.start', HIGH_PRIORITY$9, function(event) {
40364 var context = event.context,
40365 shape = context.shape,
40366 rootElement = canvas.getRootElement(),
40367 rootElementGfx = canvas.getGraphics(rootElement);
40368
40369 function ensureHoveringProcess(event) {
40370 event.element = rootElement;
40371 event.gfx = rootElementGfx;
40372 }
40373
40374 if (is$1(shape, 'bpmn:Participant') && is$1(rootElement, 'bpmn:Process')) {
40375 eventBus.on('element.hover', HIGH_PRIORITY$9, ensureHoveringProcess);
40376
40377 eventBus.once('create.cleanup', function() {
40378 eventBus.off('element.hover', ensureHoveringProcess);
40379 });
40380 }
40381 });
40382
40383 function ensureCollaboration(context) {
40384 var parent = context.parent,
40385 collaboration;
40386
40387 var rootElement = canvas.getRootElement();
40388
40389 if (is$1(rootElement, 'bpmn:Collaboration')) {
40390 collaboration = rootElement;
40391 } else {
40392
40393 // update root element by making collaboration
40394 collaboration = modeling.makeCollaboration();
40395
40396 // re-use process when creating first participant
40397 context.process = parent;
40398 }
40399
40400 context.parent = collaboration;
40401 }
40402
40403 // turn process into collaboration before adding participant
40404 this.preExecute('shape.create', function(context) {
40405 var parent = context.parent,
40406 shape = context.shape;
40407
40408 if (is$1(shape, 'bpmn:Participant') && is$1(parent, 'bpmn:Process')) {
40409 ensureCollaboration(context);
40410 }
40411 }, true);
40412
40413 this.execute('shape.create', function(context) {
40414 var process = context.process,
40415 shape = context.shape;
40416
40417 if (process) {
40418 context.oldProcessRef = shape.businessObject.processRef;
40419
40420 // re-use process when creating first participant
40421 shape.businessObject.processRef = process.businessObject;
40422 }
40423 }, true);
40424
40425 this.revert('shape.create', function(context) {
40426 var process = context.process,
40427 shape = context.shape;
40428
40429 if (process) {
40430
40431 // re-use process when creating first participant
40432 shape.businessObject.processRef = context.oldProcessRef;
40433 }
40434 }, true);
40435
40436 this.postExecute('shape.create', function(context) {
40437 var process = context.process,
40438 shape = context.shape;
40439
40440 if (process) {
40441
40442 // move children from process to participant
40443 var processChildren = process.children.slice();
40444
40445 modeling.moveElements(processChildren, { x: 0, y: 0 }, shape);
40446 }
40447
40448 }, true);
40449
40450 // turn process into collaboration when creating participants
40451 this.preExecute('elements.create', HIGH_PRIORITY$9, function(context) {
40452 var elements = context.elements,
40453 parent = context.parent,
40454 participant;
40455
40456 var hasParticipants = findParticipant(elements);
40457
40458 if (hasParticipants && is$1(parent, 'bpmn:Process')) {
40459 ensureCollaboration(context);
40460
40461 participant = findParticipant(elements);
40462
40463 context.oldProcessRef = participant.businessObject.processRef;
40464
40465 // re-use process when creating first participant
40466 participant.businessObject.processRef = parent.businessObject;
40467 }
40468 }, true);
40469
40470 this.revert('elements.create', function(context) {
40471 var elements = context.elements,
40472 process = context.process,
40473 participant;
40474
40475 if (process) {
40476 participant = findParticipant(elements);
40477
40478 // re-use process when creating first participant
40479 participant.businessObject.processRef = context.oldProcessRef;
40480 }
40481 }, true);
40482
40483 this.postExecute('elements.create', function(context) {
40484 var elements = context.elements,
40485 process = context.process,
40486 participant;
40487
40488 if (process) {
40489 participant = findParticipant(elements);
40490
40491 // move children from process to first participant
40492 var processChildren = process.children.slice();
40493
40494 modeling.moveElements(processChildren, { x: 0, y: 0 }, participant);
40495 }
40496
40497 }, true);
40498
40499 }
40500
40501 CreateParticipantBehavior$1.$inject = [
40502 'canvas',
40503 'eventBus',
40504 'modeling'
40505 ];
40506
40507 inherits_browser(CreateParticipantBehavior$1, CommandInterceptor);
40508
40509 // helpers //////////
40510
40511 function getParticipantBounds(shape, childrenBBox) {
40512 childrenBBox = {
40513 width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
40514 height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
40515 };
40516
40517 var width = Math.max(shape.width, childrenBBox.width),
40518 height = Math.max(shape.height, childrenBBox.height);
40519
40520 return {
40521 x: -width / 2,
40522 y: -height / 2,
40523 width: width,
40524 height: height
40525 };
40526 }
40527
40528 function getParticipantCreateConstraints(shape, childrenBBox) {
40529 childrenBBox = asTRBL(childrenBBox);
40530
40531 return {
40532 bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
40533 left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
40534 top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
40535 right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
40536 };
40537 }
40538
40539 function isConnection$2(element) {
40540 return !!element.waypoints;
40541 }
40542
40543 function findParticipant(elements) {
40544 return find(elements, function(element) {
40545 return is$1(element, 'bpmn:Participant');
40546 });
40547 }
40548
40549 var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
40550
40551
40552 /**
40553 * This behavior makes sure we always set a fake
40554 * DataInputAssociation#targetRef as demanded by the BPMN 2.0
40555 * XSD schema.
40556 *
40557 * The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
40558 * which is created on the fly and cleaned up afterwards if not needed
40559 * anymore.
40560 *
40561 * @param {EventBus} eventBus
40562 * @param {BpmnFactory} bpmnFactory
40563 */
40564 function DataInputAssociationBehavior(eventBus, bpmnFactory) {
40565
40566 CommandInterceptor.call(this, eventBus);
40567
40568
40569 this.executed([
40570 'connection.create',
40571 'connection.delete',
40572 'connection.move',
40573 'connection.reconnect'
40574 ], ifDataInputAssociation(fixTargetRef));
40575
40576 this.reverted([
40577 'connection.create',
40578 'connection.delete',
40579 'connection.move',
40580 'connection.reconnect'
40581 ], ifDataInputAssociation(fixTargetRef));
40582
40583
40584 function usesTargetRef(element, targetRef, removedConnection) {
40585
40586 var inputAssociations = element.get('dataInputAssociations');
40587
40588 return find(inputAssociations, function(association) {
40589 return association !== removedConnection &&
40590 association.targetRef === targetRef;
40591 });
40592 }
40593
40594 function getTargetRef(element, create) {
40595
40596 var properties = element.get('properties');
40597
40598 var targetRefProp = find(properties, function(p) {
40599 return p.name === TARGET_REF_PLACEHOLDER_NAME;
40600 });
40601
40602 if (!targetRefProp && create) {
40603 targetRefProp = bpmnFactory.create('bpmn:Property', {
40604 name: TARGET_REF_PLACEHOLDER_NAME
40605 });
40606
40607 add$1(properties, targetRefProp);
40608 }
40609
40610 return targetRefProp;
40611 }
40612
40613 function cleanupTargetRef(element, connection) {
40614
40615 var targetRefProp = getTargetRef(element);
40616
40617 if (!targetRefProp) {
40618 return;
40619 }
40620
40621 if (!usesTargetRef(element, targetRefProp, connection)) {
40622 remove$2(element.get('properties'), targetRefProp);
40623 }
40624 }
40625
40626 /**
40627 * Make sure targetRef is set to a valid property or
40628 * `null` if the connection is detached.
40629 *
40630 * @param {Event} event
40631 */
40632 function fixTargetRef(event) {
40633
40634 var context = event.context,
40635 connection = context.connection,
40636 connectionBo = connection.businessObject,
40637 target = connection.target,
40638 targetBo = target && target.businessObject,
40639 newTarget = context.newTarget,
40640 newTargetBo = newTarget && newTarget.businessObject,
40641 oldTarget = context.oldTarget || context.target,
40642 oldTargetBo = oldTarget && oldTarget.businessObject;
40643
40644 var dataAssociation = connection.businessObject,
40645 targetRefProp;
40646
40647 if (oldTargetBo && oldTargetBo !== targetBo) {
40648 cleanupTargetRef(oldTargetBo, connectionBo);
40649 }
40650
40651 if (newTargetBo && newTargetBo !== targetBo) {
40652 cleanupTargetRef(newTargetBo, connectionBo);
40653 }
40654
40655 if (targetBo) {
40656 targetRefProp = getTargetRef(targetBo, true);
40657 dataAssociation.targetRef = targetRefProp;
40658 } else {
40659 dataAssociation.targetRef = null;
40660 }
40661 }
40662 }
40663
40664 DataInputAssociationBehavior.$inject = [
40665 'eventBus',
40666 'bpmnFactory'
40667 ];
40668
40669 inherits_browser(DataInputAssociationBehavior, CommandInterceptor);
40670
40671
40672 /**
40673 * Only call the given function when the event
40674 * touches a bpmn:DataInputAssociation.
40675 *
40676 * @param {Function} fn
40677 * @return {Function}
40678 */
40679 function ifDataInputAssociation(fn) {
40680
40681 return function(event) {
40682 var context = event.context,
40683 connection = context.connection;
40684
40685 if (is$1(connection, 'bpmn:DataInputAssociation')) {
40686 return fn(event);
40687 }
40688 };
40689 }
40690
40691 function UpdateSemanticParentHandler(bpmnUpdater) {
40692 this._bpmnUpdater = bpmnUpdater;
40693 }
40694
40695 UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ];
40696
40697
40698 UpdateSemanticParentHandler.prototype.execute = function(context) {
40699 var dataStoreBo = context.dataStoreBo,
40700 newSemanticParent = context.newSemanticParent,
40701 newDiParent = context.newDiParent;
40702
40703 context.oldSemanticParent = dataStoreBo.$parent;
40704 context.oldDiParent = dataStoreBo.di.$parent;
40705
40706 // update semantic parent
40707 this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);
40708
40709 // update DI parent
40710 this._bpmnUpdater.updateDiParent(dataStoreBo.di, newDiParent);
40711 };
40712
40713 UpdateSemanticParentHandler.prototype.revert = function(context) {
40714 var dataStoreBo = context.dataStoreBo,
40715 oldSemanticParent = context.oldSemanticParent,
40716 oldDiParent = context.oldDiParent;
40717
40718 // update semantic parent
40719 this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);
40720
40721 // update DI parent
40722 this._bpmnUpdater.updateDiParent(dataStoreBo.di, oldDiParent);
40723 };
40724
40725 /**
40726 * BPMN specific data store behavior
40727 */
40728 function DataStoreBehavior(
40729 canvas, commandStack, elementRegistry,
40730 eventBus) {
40731
40732 CommandInterceptor.call(this, eventBus);
40733
40734 commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);
40735
40736 function getFirstParticipantWithProcessRef() {
40737 return elementRegistry.filter(function(element) {
40738 return is$1(element, 'bpmn:Participant') && getBusinessObject(element).processRef;
40739 })[0];
40740 }
40741
40742 function getDataStores(element) {
40743 return element.children.filter(function(child) {
40744 return is$1(child, 'bpmn:DataStoreReference') && !child.labelTarget;
40745 });
40746 }
40747
40748 function updateDataStoreParent(dataStore, newDataStoreParent) {
40749 var dataStoreBo = dataStore.businessObject || dataStore;
40750
40751 newDataStoreParent = newDataStoreParent || getFirstParticipantWithProcessRef();
40752
40753 if (newDataStoreParent) {
40754 var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;
40755
40756 commandStack.execute('dataStore.updateContainment', {
40757 dataStoreBo: dataStoreBo,
40758 newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
40759 newDiParent: newDataStoreParentBo.di
40760 });
40761 }
40762 }
40763
40764
40765 // disable auto-resize for data stores
40766 this.preExecute('shape.create', function(event) {
40767
40768 var context = event.context,
40769 shape = context.shape;
40770
40771 if (is$1(shape, 'bpmn:DataStoreReference') &&
40772 shape.type !== 'label') {
40773
40774 if (!context.hints) {
40775 context.hints = {};
40776 }
40777
40778 // prevent auto resizing
40779 context.hints.autoResize = false;
40780 }
40781 });
40782
40783
40784 // disable auto-resize for data stores
40785 this.preExecute('elements.move', function(event) {
40786 var context = event.context,
40787 shapes = context.shapes;
40788
40789 var dataStoreReferences = shapes.filter(function(shape) {
40790 return is$1(shape, 'bpmn:DataStoreReference');
40791 });
40792
40793 if (dataStoreReferences.length) {
40794 if (!context.hints) {
40795 context.hints = {};
40796 }
40797
40798 // prevent auto resizing for data store references
40799 context.hints.autoResize = shapes.filter(function(shape) {
40800 return !is$1(shape, 'bpmn:DataStoreReference');
40801 });
40802 }
40803 });
40804
40805
40806 // update parent on data store created
40807 this.postExecute('shape.create', function(event) {
40808 var context = event.context,
40809 shape = context.shape,
40810 parent = shape.parent;
40811
40812
40813 if (is$1(shape, 'bpmn:DataStoreReference') &&
40814 shape.type !== 'label' &&
40815 is$1(parent, 'bpmn:Collaboration')) {
40816
40817 updateDataStoreParent(shape);
40818 }
40819 });
40820
40821
40822 // update parent on data store moved
40823 this.postExecute('shape.move', function(event) {
40824 var context = event.context,
40825 shape = context.shape,
40826 oldParent = context.oldParent,
40827 parent = shape.parent;
40828
40829 if (is$1(oldParent, 'bpmn:Collaboration')) {
40830
40831 // do nothing if not necessary
40832 return;
40833 }
40834
40835 if (is$1(shape, 'bpmn:DataStoreReference') &&
40836 shape.type !== 'label' &&
40837 is$1(parent, 'bpmn:Collaboration')) {
40838
40839 var participant = is$1(oldParent, 'bpmn:Participant') ?
40840 oldParent :
40841 getAncestor(oldParent, 'bpmn:Participant');
40842
40843 updateDataStoreParent(shape, participant);
40844 }
40845 });
40846
40847
40848 // update data store parents on participant or subprocess deleted
40849 this.postExecute('shape.delete', function(event) {
40850 var context = event.context,
40851 shape = context.shape,
40852 rootElement = canvas.getRootElement();
40853
40854 if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
40855 && is$1(rootElement, 'bpmn:Collaboration')) {
40856 getDataStores(rootElement)
40857 .filter(function(dataStore) {
40858 return isDescendant(dataStore, shape);
40859 })
40860 .forEach(function(dataStore) {
40861 updateDataStoreParent(dataStore);
40862 });
40863 }
40864 });
40865
40866 // update data store parents on collaboration -> process
40867 this.postExecute('canvas.updateRoot', function(event) {
40868 var context = event.context,
40869 oldRoot = context.oldRoot,
40870 newRoot = context.newRoot;
40871
40872 var dataStores = getDataStores(oldRoot);
40873
40874 dataStores.forEach(function(dataStore) {
40875
40876 if (is$1(newRoot, 'bpmn:Process')) {
40877 updateDataStoreParent(dataStore, newRoot);
40878 }
40879
40880 });
40881 });
40882 }
40883
40884 DataStoreBehavior.$inject = [
40885 'canvas',
40886 'commandStack',
40887 'elementRegistry',
40888 'eventBus',
40889 ];
40890
40891 inherits_browser(DataStoreBehavior, CommandInterceptor);
40892
40893
40894 // helpers //////////
40895
40896 function isDescendant(descendant, ancestor) {
40897 var descendantBo = descendant.businessObject || descendant,
40898 ancestorBo = ancestor.businessObject || ancestor;
40899
40900 while (descendantBo.$parent) {
40901 if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
40902 return true;
40903 }
40904
40905 descendantBo = descendantBo.$parent;
40906 }
40907
40908 return false;
40909 }
40910
40911 function getAncestor(element, type) {
40912
40913 while (element.parent) {
40914 if (is$1(element.parent, type)) {
40915 return element.parent;
40916 }
40917
40918 element = element.parent;
40919 }
40920 }
40921
40922 var LOW_PRIORITY$c = 500;
40923
40924
40925 /**
40926 * BPMN specific delete lane behavior
40927 */
40928 function DeleteLaneBehavior(eventBus, modeling, spaceTool) {
40929
40930 CommandInterceptor.call(this, eventBus);
40931
40932
40933 function compensateLaneDelete(shape, oldParent) {
40934
40935 var siblings = getChildLanes(oldParent);
40936
40937 var topAffected = [];
40938 var bottomAffected = [];
40939
40940 eachElement(siblings, function(element) {
40941
40942 if (element.y > shape.y) {
40943 bottomAffected.push(element);
40944 } else {
40945 topAffected.push(element);
40946 }
40947
40948 return element.children;
40949 });
40950
40951 if (!siblings.length) {
40952 return;
40953 }
40954
40955 var offset;
40956
40957 if (bottomAffected.length && topAffected.length) {
40958 offset = shape.height / 2;
40959 } else {
40960 offset = shape.height;
40961 }
40962
40963 var topAdjustments,
40964 bottomAdjustments;
40965
40966 if (topAffected.length) {
40967 topAdjustments = spaceTool.calculateAdjustments(
40968 topAffected, 'y', offset, shape.y - 10);
40969
40970 spaceTool.makeSpace(
40971 topAdjustments.movingShapes,
40972 topAdjustments.resizingShapes,
40973 { x: 0, y: offset }, 's');
40974 }
40975
40976 if (bottomAffected.length) {
40977 bottomAdjustments = spaceTool.calculateAdjustments(
40978 bottomAffected, 'y', -offset, shape.y + shape.height + 10);
40979
40980 spaceTool.makeSpace(
40981 bottomAdjustments.movingShapes,
40982 bottomAdjustments.resizingShapes,
40983 { x: 0, y: -offset }, 'n');
40984 }
40985 }
40986
40987
40988 /**
40989 * Adjust sizes of other lanes after lane deletion
40990 */
40991 this.postExecuted('shape.delete', LOW_PRIORITY$c, function(event) {
40992
40993 var context = event.context,
40994 hints = context.hints,
40995 shape = context.shape,
40996 oldParent = context.oldParent;
40997
40998 // only compensate lane deletes
40999 if (!is$1(shape, 'bpmn:Lane')) {
41000 return;
41001 }
41002
41003 // compensate root deletes only
41004 if (hints && hints.nested) {
41005 return;
41006 }
41007
41008 compensateLaneDelete(shape, oldParent);
41009 });
41010 }
41011
41012 DeleteLaneBehavior.$inject = [
41013 'eventBus',
41014 'modeling',
41015 'spaceTool'
41016 ];
41017
41018 inherits_browser(DeleteLaneBehavior, CommandInterceptor);
41019
41020 var LOW_PRIORITY$d = 500;
41021
41022
41023 /**
41024 * Replace boundary event with intermediate event when creating or moving results in detached event.
41025 */
41026 function DetachEventBehavior(bpmnReplace, injector) {
41027 injector.invoke(CommandInterceptor, this);
41028
41029 this._bpmnReplace = bpmnReplace;
41030
41031 var self = this;
41032
41033 this.postExecuted('elements.create', LOW_PRIORITY$d, function(context) {
41034 var elements = context.elements;
41035
41036 elements.filter(function(shape) {
41037 var host = shape.host;
41038
41039 return shouldReplace$1(shape, host);
41040 }).map(function(shape) {
41041 return elements.indexOf(shape);
41042 }).forEach(function(index) {
41043 context.elements[ index ] = self.replaceShape(elements[ index ]);
41044 });
41045 }, true);
41046
41047 this.preExecute('elements.move', LOW_PRIORITY$d, function(context) {
41048 var shapes = context.shapes,
41049 newHost = context.newHost;
41050
41051 shapes.forEach(function(shape, index) {
41052 var host = shape.host;
41053
41054 if (shouldReplace$1(shape, includes(shapes, host) ? host : newHost)) {
41055 shapes[ index ] = self.replaceShape(shape);
41056 }
41057 });
41058 }, true);
41059 }
41060
41061 DetachEventBehavior.$inject = [
41062 'bpmnReplace',
41063 'injector'
41064 ];
41065
41066 inherits_browser(DetachEventBehavior, CommandInterceptor);
41067
41068 DetachEventBehavior.prototype.replaceShape = function(shape) {
41069 var eventDefinition = getEventDefinition$1(shape),
41070 intermediateEvent;
41071
41072 if (eventDefinition) {
41073 intermediateEvent = {
41074 type: 'bpmn:IntermediateCatchEvent',
41075 eventDefinitionType: eventDefinition.$type
41076 };
41077 } else {
41078 intermediateEvent = {
41079 type: 'bpmn:IntermediateThrowEvent'
41080 };
41081 }
41082
41083 return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
41084 };
41085
41086
41087 // helpers //////////
41088
41089 function getEventDefinition$1(element) {
41090 var businessObject = getBusinessObject(element),
41091 eventDefinitions = businessObject.eventDefinitions;
41092
41093 return eventDefinitions && eventDefinitions[0];
41094 }
41095
41096 function shouldReplace$1(shape, host) {
41097 return !isLabel(shape) && is$1(shape, 'bpmn:BoundaryEvent') && !host;
41098 }
41099
41100 function includes(array, item) {
41101 return array.indexOf(item) !== -1;
41102 }
41103
41104 function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
41105
41106 CommandInterceptor.call(this, eventBus);
41107
41108 /**
41109 * Reconnect start / end of a connection after
41110 * dropping an element on a flow.
41111 */
41112
41113 function insertShape(shape, targetFlow, positionOrBounds) {
41114 var waypoints = targetFlow.waypoints,
41115 waypointsBefore,
41116 waypointsAfter,
41117 dockingPoint,
41118 source,
41119 target,
41120 incomingConnection,
41121 outgoingConnection,
41122 oldOutgoing = shape.outgoing.slice(),
41123 oldIncoming = shape.incoming.slice();
41124
41125 var mid;
41126
41127 if (isNumber(positionOrBounds.width)) {
41128 mid = getMid(positionOrBounds);
41129 } else {
41130 mid = positionOrBounds;
41131 }
41132
41133 var intersection = getApproxIntersection(waypoints, mid);
41134
41135 if (intersection) {
41136 waypointsBefore = waypoints.slice(0, intersection.index);
41137 waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
41138
41139 // due to inaccuracy intersection might have been found
41140 if (!waypointsBefore.length || !waypointsAfter.length) {
41141 return;
41142 }
41143
41144 dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid;
41145
41146 // if last waypointBefore is inside shape's bounds, ignore docking point
41147 if (!isPointInsideBBox$2(shape, waypointsBefore[waypointsBefore.length-1])) {
41148 waypointsBefore.push(copy(dockingPoint));
41149 }
41150
41151 // if first waypointAfter is inside shape's bounds, ignore docking point
41152 if (!isPointInsideBBox$2(shape, waypointsAfter[0])) {
41153 waypointsAfter.unshift(copy(dockingPoint));
41154 }
41155 }
41156
41157 source = targetFlow.source;
41158 target = targetFlow.target;
41159
41160 if (bpmnRules.canConnect(source, shape, targetFlow)) {
41161
41162 // reconnect source -> inserted shape
41163 modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid);
41164
41165 incomingConnection = targetFlow;
41166 }
41167
41168 if (bpmnRules.canConnect(shape, target, targetFlow)) {
41169
41170 if (!incomingConnection) {
41171
41172 // reconnect inserted shape -> end
41173 modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid);
41174
41175 outgoingConnection = targetFlow;
41176 } else {
41177 outgoingConnection = modeling.connect(
41178 shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
41179 );
41180 }
41181 }
41182
41183 var duplicateConnections = [].concat(
41184
41185 incomingConnection && filter(oldIncoming, function(connection) {
41186 return connection.source === incomingConnection.source;
41187 }) || [],
41188
41189 outgoingConnection && filter(oldOutgoing, function(connection) {
41190 return connection.target === outgoingConnection.target;
41191 }) || []
41192 );
41193
41194 if (duplicateConnections.length) {
41195 modeling.removeElements(duplicateConnections);
41196 }
41197 }
41198
41199 this.preExecute('elements.move', function(context) {
41200
41201 var newParent = context.newParent,
41202 shapes = context.shapes,
41203 delta = context.delta,
41204 shape = shapes[0];
41205
41206 if (!shape || !newParent) {
41207 return;
41208 }
41209
41210 // if the new parent is a connection,
41211 // change it to the new parent's parent
41212 if (newParent && newParent.waypoints) {
41213 context.newParent = newParent = newParent.parent;
41214 }
41215
41216 var shapeMid = getMid(shape);
41217 var newShapeMid = {
41218 x: shapeMid.x + delta.x,
41219 y: shapeMid.y + delta.y
41220 };
41221
41222 // find a connection which intersects with the
41223 // element's mid point
41224 var connection = find(newParent.children, function(element) {
41225 var canInsert = bpmnRules.canInsert(shapes, element);
41226
41227 return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
41228 });
41229
41230 if (connection) {
41231 context.targetFlow = connection;
41232 context.position = newShapeMid;
41233 }
41234
41235 }, true);
41236
41237 this.postExecuted('elements.move', function(context) {
41238
41239 var shapes = context.shapes,
41240 targetFlow = context.targetFlow,
41241 position = context.position;
41242
41243 if (targetFlow) {
41244 insertShape(shapes[0], targetFlow, position);
41245 }
41246
41247 }, true);
41248
41249 this.preExecute('shape.create', function(context) {
41250
41251 var parent = context.parent,
41252 shape = context.shape;
41253
41254 if (bpmnRules.canInsert(shape, parent)) {
41255 context.targetFlow = parent;
41256 context.parent = parent.parent;
41257 }
41258 }, true);
41259
41260 this.postExecuted('shape.create', function(context) {
41261
41262 var shape = context.shape,
41263 targetFlow = context.targetFlow,
41264 positionOrBounds = context.position;
41265
41266 if (targetFlow) {
41267 insertShape(shape, targetFlow, positionOrBounds);
41268 }
41269 }, true);
41270 }
41271
41272 inherits_browser(DropOnFlowBehavior, CommandInterceptor);
41273
41274 DropOnFlowBehavior.$inject = [
41275 'eventBus',
41276 'bpmnRules',
41277 'modeling'
41278 ];
41279
41280
41281 // helpers /////////////////////
41282
41283 function isPointInsideBBox$2(bbox, point) {
41284 var x = point.x,
41285 y = point.y;
41286
41287 return x >= bbox.x &&
41288 x <= bbox.x + bbox.width &&
41289 y >= bbox.y &&
41290 y <= bbox.y + bbox.height;
41291 }
41292
41293 function copy(obj) {
41294 return assign({}, obj);
41295 }
41296
41297 function EventBasedGatewayBehavior(eventBus, modeling) {
41298
41299 CommandInterceptor.call(this, eventBus);
41300
41301 /**
41302 * Remove existing sequence flows of event-based target before connecting
41303 * from event-based gateway.
41304 */
41305 this.preExecuted('connection.create', function(event) {
41306
41307 var context = event.context,
41308 source = context.source,
41309 target = context.target,
41310 existingIncomingConnections = target.incoming.slice();
41311
41312 if (context.hints && context.hints.createElementsBehavior === false) {
41313 return;
41314 }
41315
41316 if (
41317 is$1(source, 'bpmn:EventBasedGateway') &&
41318 target.incoming.length
41319 ) {
41320
41321 existingIncomingConnections.filter(isSequenceFlow)
41322 .forEach(function(sequenceFlow) {
41323 modeling.removeConnection(sequenceFlow);
41324 });
41325 }
41326 });
41327
41328 /**
41329 * After replacing shape with event-based gateway, remove incoming sequence
41330 * flows of event-based targets which do not belong to event-based gateway
41331 * source.
41332 */
41333 this.preExecuted('shape.replace', function(event) {
41334
41335 var newShape = event.context.newShape,
41336 newShapeTargets,
41337 newShapeTargetsIncomingSequenceFlows;
41338
41339 if (!is$1(newShape, 'bpmn:EventBasedGateway')) {
41340 return;
41341 }
41342
41343 newShapeTargets = newShape.outgoing.filter(isSequenceFlow)
41344 .map(function(sequenceFlow) {
41345 return sequenceFlow.target;
41346 });
41347
41348 newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) {
41349 var incomingSequenceFlows = target.incoming.filter(isSequenceFlow);
41350
41351 return sequenceFlows.concat(incomingSequenceFlows);
41352 }, []);
41353
41354 newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) {
41355 if (sequenceFlow.source !== newShape) {
41356 modeling.removeConnection(sequenceFlow);
41357 }
41358 });
41359 });
41360 }
41361
41362 EventBasedGatewayBehavior.$inject = [
41363 'eventBus',
41364 'modeling'
41365 ];
41366
41367 inherits_browser(EventBasedGatewayBehavior, CommandInterceptor);
41368
41369
41370
41371 // helpers //////////////////////
41372
41373 function isSequenceFlow(connection) {
41374 return is$1(connection, 'bpmn:SequenceFlow');
41375 }
41376
41377 var HIGH_PRIORITY$a = 2000;
41378
41379
41380 /**
41381 * BPMN specific Group behavior
41382 */
41383 function GroupBehavior(
41384 bpmnFactory,
41385 canvas,
41386 elementRegistry,
41387 eventBus,
41388 injector,
41389 moddleCopy
41390 ) {
41391 injector.invoke(CommandInterceptor, this);
41392
41393 /**
41394 * Gets process definitions
41395 *
41396 * @return {ModdleElement} definitions
41397 */
41398 function getDefinitions() {
41399 var rootElement = canvas.getRootElement(),
41400 businessObject = getBusinessObject(rootElement);
41401
41402 return businessObject.$parent;
41403 }
41404
41405 /**
41406 * Removes a referenced category value for a given group shape
41407 *
41408 * @param {djs.model.Shape} shape
41409 */
41410 function removeReferencedCategoryValue(shape) {
41411
41412 var businessObject = getBusinessObject(shape),
41413 categoryValue = businessObject.categoryValueRef;
41414
41415 if (!categoryValue) {
41416 return;
41417 }
41418
41419 var category = categoryValue.$parent;
41420
41421 if (!categoryValue) {
41422 return;
41423 }
41424
41425 remove$2(category.categoryValue, categoryValue);
41426
41427 // cleanup category if it is empty
41428 if (category && !category.categoryValue.length) {
41429 removeCategory(category);
41430 }
41431 }
41432
41433 /**
41434 * Removes a given category from the definitions
41435 *
41436 * @param {ModdleElement} category
41437 */
41438 function removeCategory(category) {
41439
41440 var definitions = getDefinitions();
41441
41442 remove$2(definitions.get('rootElements'), category);
41443 }
41444
41445 /**
41446 * Returns all group element in the current registry
41447 *
41448 * @return {Array<djs.model.shape>} a list of group shapes
41449 */
41450 function getGroupElements() {
41451 return elementRegistry.filter(function(e) {
41452 return is$1(e, 'bpmn:Group');
41453 });
41454 }
41455
41456 /**
41457 * Returns true if given categoryValue is referenced in one of the given elements
41458 *
41459 * @param {Array<djs.model.shape>} elements
41460 * @param {ModdleElement} categoryValue
41461 * @return {boolean}
41462 */
41463 function isReferenced(elements, categoryValue) {
41464 return elements.some(function(e) {
41465
41466 var businessObject = getBusinessObject(e);
41467
41468 return businessObject.categoryValueRef
41469 && businessObject.categoryValueRef === categoryValue;
41470 });
41471 }
41472
41473 /**
41474 * remove referenced category + value when group was deleted
41475 */
41476 this.executed('shape.delete', function(event) {
41477
41478 var context = event.context,
41479 shape = context.shape;
41480
41481 if (is$1(shape, 'bpmn:Group')) {
41482
41483 var businessObject = getBusinessObject(shape),
41484 categoryValueRef = businessObject.categoryValueRef,
41485 groupElements = getGroupElements();
41486
41487 if (!isReferenced(groupElements, categoryValueRef)) {
41488 removeReferencedCategoryValue(shape);
41489 }
41490 }
41491 });
41492
41493 /**
41494 * re-attach removed category
41495 */
41496 this.reverted('shape.delete', function(event) {
41497
41498 var context = event.context,
41499 shape = context.shape;
41500
41501 if (is$1(shape, 'bpmn:Group')) {
41502
41503 var businessObject = getBusinessObject(shape),
41504 categoryValueRef = businessObject.categoryValueRef,
41505 definitions = getDefinitions(),
41506 category = categoryValueRef ? categoryValueRef.$parent : null;
41507
41508 add$1(category.get('categoryValue'), categoryValueRef);
41509 add$1(definitions.get('rootElements'), category);
41510 }
41511 });
41512
41513 /**
41514 * create new category + value when group was created
41515 */
41516 this.execute('shape.create', function(event) {
41517 var context = event.context,
41518 shape = context.shape,
41519 businessObject = getBusinessObject(shape);
41520
41521 if (is$1(businessObject, 'bpmn:Group') && !businessObject.categoryValueRef) {
41522
41523 var definitions = getDefinitions(),
41524 categoryValue = createCategoryValue(definitions, bpmnFactory);
41525
41526 // link the reference to the Group
41527 businessObject.categoryValueRef = categoryValue;
41528 }
41529 });
41530
41531
41532 this.revert('shape.create', function(event) {
41533
41534 var context = event.context,
41535 shape = context.shape;
41536
41537 if (is$1(shape, 'bpmn:Group')) {
41538 removeReferencedCategoryValue(shape);
41539
41540 delete getBusinessObject(shape).categoryValueRef;
41541
41542 }
41543 });
41544
41545 // copy bpmn:CategoryValue when copying element
41546 eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY$a, function(context) {
41547 var property = context.property,
41548 categoryValue;
41549
41550 if (is$1(property, 'bpmn:CategoryValue')) {
41551 categoryValue = createCategoryValue(getDefinitions(), bpmnFactory);
41552
41553 // return copy of category
41554 return moddleCopy.copyElement(property, categoryValue);
41555 }
41556 });
41557
41558 }
41559
41560 GroupBehavior.$inject = [
41561 'bpmnFactory',
41562 'canvas',
41563 'elementRegistry',
41564 'eventBus',
41565 'injector',
41566 'moddleCopy'
41567 ];
41568
41569 inherits_browser(GroupBehavior, CommandInterceptor);
41570
41571 /**
41572 * Returns the intersection between two line segments a and b.
41573 *
41574 * @param {Point} l1s
41575 * @param {Point} l1e
41576 * @param {Point} l2s
41577 * @param {Point} l2e
41578 *
41579 * @return {Point}
41580 */
41581 function lineIntersect(l1s, l1e, l2s, l2e) {
41582
41583 // if the lines intersect, the result contains the x and y of the
41584 // intersection (treating the lines as infinite) and booleans for
41585 // whether line segment 1 or line segment 2 contain the point
41586 var denominator, a, b, c, numerator;
41587
41588 denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
41589
41590 if (denominator == 0) {
41591 return null;
41592 }
41593
41594 a = l1s.y - l2s.y;
41595 b = l1s.x - l2s.x;
41596 numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
41597
41598 c = numerator / denominator;
41599
41600 // if we cast these lines infinitely in
41601 // both directions, they intersect here
41602 return {
41603 x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
41604 y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
41605 };
41606 }
41607
41608 /**
41609 * Fix broken dockings after DI imports.
41610 *
41611 * @param {EventBus} eventBus
41612 */
41613 function ImportDockingFix(eventBus) {
41614
41615 function adjustDocking(startPoint, nextPoint, elementMid) {
41616
41617 var elementTop = {
41618 x: elementMid.x,
41619 y: elementMid.y - 50
41620 };
41621
41622 var elementLeft = {
41623 x: elementMid.x - 50,
41624 y: elementMid.y
41625 };
41626
41627 var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
41628 horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
41629
41630 // original is horizontal or vertical center cross intersection
41631 var centerIntersect;
41632
41633 if (verticalIntersect && horizontalIntersect) {
41634 if (getDistance(verticalIntersect, elementMid) > getDistance(horizontalIntersect, elementMid)) {
41635 centerIntersect = horizontalIntersect;
41636 } else {
41637 centerIntersect = verticalIntersect;
41638 }
41639 } else {
41640 centerIntersect = verticalIntersect || horizontalIntersect;
41641 }
41642
41643 startPoint.original = centerIntersect;
41644 }
41645
41646 function fixDockings(connection) {
41647 var waypoints = connection.waypoints;
41648
41649 adjustDocking(
41650 waypoints[0],
41651 waypoints[1],
41652 getMid(connection.source)
41653 );
41654
41655 adjustDocking(
41656 waypoints[waypoints.length - 1],
41657 waypoints[waypoints.length - 2],
41658 getMid(connection.target)
41659 );
41660 }
41661
41662 eventBus.on('bpmnElement.added', function(e) {
41663
41664 var element = e.element;
41665
41666 if (element.waypoints) {
41667 fixDockings(element);
41668 }
41669 });
41670 }
41671
41672 ImportDockingFix.$inject = [
41673 'eventBus'
41674 ];
41675
41676
41677 // helpers //////////////////////
41678
41679 function getDistance(p1, p2) {
41680 return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
41681 }
41682
41683 /**
41684 * A component that makes sure that each created or updated
41685 * Pool and Lane is assigned an isHorizontal property set to true.
41686 *
41687 * @param {EventBus} eventBus
41688 */
41689 function IsHorizontalFix(eventBus) {
41690
41691 CommandInterceptor.call(this, eventBus);
41692
41693 var elementTypesToUpdate = [
41694 'bpmn:Participant',
41695 'bpmn:Lane'
41696 ];
41697
41698 this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
41699 var bo = getBusinessObject(event.context.shape);
41700
41701 if (isAny(bo, elementTypesToUpdate) && !bo.di.get('isHorizontal')) {
41702
41703 // set attribute directly to avoid modeling#updateProperty side effects
41704 bo.di.set('isHorizontal', true);
41705 }
41706 });
41707
41708 }
41709
41710 IsHorizontalFix.$inject = [ 'eventBus' ];
41711
41712 inherits_browser(IsHorizontalFix, CommandInterceptor);
41713
41714 /**
41715 * Returns the length of a vector
41716 *
41717 * @param {Vector}
41718 * @return {Float}
41719 */
41720 function vectorLength(v) {
41721 return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
41722 }
41723
41724
41725 /**
41726 * Calculates the angle between a line a the yAxis
41727 *
41728 * @param {Array}
41729 * @return {Float}
41730 */
41731 function getAngle(line) {
41732
41733 // return value is between 0, 180 and -180, -0
41734 // @janstuemmel: maybe replace return a/b with b/a
41735 return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
41736 }
41737
41738
41739 /**
41740 * Rotates a vector by a given angle
41741 *
41742 * @param {Vector}
41743 * @param {Float} Angle in radians
41744 * @return {Vector}
41745 */
41746 function rotateVector(vector, angle) {
41747 return (!angle) ? vector : {
41748 x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
41749 y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
41750 };
41751 }
41752
41753
41754 /**
41755 * Solves a 2D equation system
41756 * a + r*b = c, where a,b,c are 2D vectors
41757 *
41758 * @param {Vector}
41759 * @param {Vector}
41760 * @param {Vector}
41761 * @return {Float}
41762 */
41763 function solveLambaSystem(a, b, c) {
41764
41765 // the 2d system
41766 var system = [
41767 { n: a[0] - c[0], lambda: b[0] },
41768 { n: a[1] - c[1], lambda: b[1] }
41769 ];
41770
41771 // solve
41772 var n = system[0].n * b[0] + system[1].n * b[1],
41773 l = system[0].lambda * b[0] + system[1].lambda * b[1];
41774
41775 return -n/l;
41776 }
41777
41778
41779 /**
41780 * Position of perpendicular foot
41781 *
41782 * @param {Point}
41783 * @param [ {Point}, {Point} ] line defined through two points
41784 * @return {Point} the perpendicular foot position
41785 */
41786 function perpendicularFoot(point, line) {
41787
41788 var a = line[0], b = line[1];
41789
41790 // relative position of b from a
41791 var bd = { x: b.x - a.x, y: b.y - a.y };
41792
41793 // solve equation system to the parametrized vectors param real value
41794 var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);
41795
41796 return { x: a.x + r*bd.x, y: a.y + r*bd.y };
41797 }
41798
41799
41800 /**
41801 * Calculates the distance between a point and a line
41802 *
41803 * @param {Point}
41804 * @param [ {Point}, {Point} ] line defined through two points
41805 * @return {Float} distance
41806 */
41807 function getDistancePointLine(point, line) {
41808
41809 var pfPoint = perpendicularFoot(point, line);
41810
41811 // distance vector
41812 var connectionVector = {
41813 x: pfPoint.x - point.x,
41814 y: pfPoint.y - point.y
41815 };
41816
41817 return vectorLength(connectionVector);
41818 }
41819
41820
41821 /**
41822 * Calculates the distance between two points
41823 *
41824 * @param {Point}
41825 * @param {Point}
41826 * @return {Float} distance
41827 */
41828 function getDistancePointPoint(point1, point2) {
41829
41830 return vectorLength({
41831 x: point1.x - point2.x,
41832 y: point1.y - point2.y
41833 });
41834 }
41835
41836 var sqrt = Math.sqrt,
41837 min$2 = Math.min,
41838 max$3 = Math.max,
41839 abs$4 = Math.abs;
41840
41841 /**
41842 * Calculate the square (power to two) of a number.
41843 *
41844 * @param {number} n
41845 *
41846 * @return {number}
41847 */
41848 function sq(n) {
41849 return Math.pow(n, 2);
41850 }
41851
41852 /**
41853 * Get distance between two points.
41854 *
41855 * @param {Point} p1
41856 * @param {Point} p2
41857 *
41858 * @return {number}
41859 */
41860 function getDistance$1(p1, p2) {
41861 return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
41862 }
41863
41864 /**
41865 * Return the attachment of the given point on the specified line.
41866 *
41867 * The attachment is either a bendpoint (attached to the given point)
41868 * or segment (attached to a location on a line segment) attachment:
41869 *
41870 * ```javascript
41871 * var pointAttachment = {
41872 * type: 'bendpoint',
41873 * bendpointIndex: 3,
41874 * position: { x: 10, y: 10 } // the attach point on the line
41875 * };
41876 *
41877 * var segmentAttachment = {
41878 * type: 'segment',
41879 * segmentIndex: 2,
41880 * relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
41881 * position: { x: 10, y: 10 } // the attach point on the line
41882 * };
41883 * ```
41884 *
41885 * @param {Point} point
41886 * @param {Array<Point>} line
41887 *
41888 * @return {Object} attachment
41889 */
41890 function getAttachment(point, line) {
41891
41892 var idx = 0,
41893 segmentStart,
41894 segmentEnd,
41895 segmentStartDistance,
41896 segmentEndDistance,
41897 attachmentPosition,
41898 minDistance,
41899 intersections,
41900 attachment,
41901 attachmentDistance,
41902 closestAttachmentDistance,
41903 closestAttachment;
41904
41905 for (idx = 0; idx < line.length - 1; idx++) {
41906
41907 segmentStart = line[idx];
41908 segmentEnd = line[idx + 1];
41909
41910 if (pointsEqual(segmentStart, segmentEnd)) {
41911 intersections = [ segmentStart ];
41912 } else {
41913 segmentStartDistance = getDistance$1(point, segmentStart);
41914 segmentEndDistance = getDistance$1(point, segmentEnd);
41915
41916 minDistance = min$2(segmentStartDistance, segmentEndDistance);
41917
41918 intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
41919 }
41920
41921 if (intersections.length < 1) {
41922 throw new Error('expected between [1, 2] circle -> line intersections');
41923 }
41924
41925 // one intersection -> bendpoint attachment
41926 if (intersections.length === 1) {
41927 attachment = {
41928 type: 'bendpoint',
41929 position: intersections[0],
41930 segmentIndex: idx,
41931 bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
41932 };
41933 }
41934
41935 // two intersections -> segment attachment
41936 if (intersections.length === 2) {
41937
41938 attachmentPosition = mid$1(intersections[0], intersections[1]);
41939
41940 attachment = {
41941 type: 'segment',
41942 position: attachmentPosition,
41943 segmentIndex: idx,
41944 relativeLocation: getDistance$1(segmentStart, attachmentPosition) / getDistance$1(segmentStart, segmentEnd)
41945 };
41946 }
41947
41948 attachmentDistance = getDistance$1(attachment.position, point);
41949
41950 if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
41951 closestAttachment = attachment;
41952 closestAttachmentDistance = attachmentDistance;
41953 }
41954 }
41955
41956 return closestAttachment;
41957 }
41958
41959 /**
41960 * Gets the intersection between a circle and a line segment.
41961 *
41962 * @param {Point} s1 segment start
41963 * @param {Point} s2 segment end
41964 * @param {Point} cc circle center
41965 * @param {number} cr circle radius
41966 *
41967 * @return {Array<Point>} intersections
41968 */
41969 function getCircleSegmentIntersections(s1, s2, cc, cr) {
41970
41971 var baX = s2.x - s1.x;
41972 var baY = s2.y - s1.y;
41973 var caX = cc.x - s1.x;
41974 var caY = cc.y - s1.y;
41975
41976 var a = baX * baX + baY * baY;
41977 var bBy2 = baX * caX + baY * caY;
41978 var c = caX * caX + caY * caY - cr * cr;
41979
41980 var pBy2 = bBy2 / a;
41981 var q = c / a;
41982
41983 var disc = pBy2 * pBy2 - q;
41984
41985 // check against negative value to work around
41986 // negative, very close to zero results (-4e-15)
41987 // being produced in some environments
41988 if (disc < 0 && disc > -0.000001) {
41989 disc = 0;
41990 }
41991
41992 if (disc < 0) {
41993 return [];
41994 }
41995
41996 // if disc == 0 ... dealt with later
41997 var tmpSqrt = sqrt(disc);
41998 var abScalingFactor1 = -pBy2 + tmpSqrt;
41999 var abScalingFactor2 = -pBy2 - tmpSqrt;
42000
42001 var i1 = {
42002 x: s1.x - baX * abScalingFactor1,
42003 y: s1.y - baY * abScalingFactor1
42004 };
42005
42006 if (disc === 0) { // abScalingFactor1 == abScalingFactor2
42007 return [ i1 ];
42008 }
42009
42010 var i2 = {
42011 x: s1.x - baX * abScalingFactor2,
42012 y: s1.y - baY * abScalingFactor2
42013 };
42014
42015 // return only points on line segment
42016 return [ i1, i2 ].filter(function(p) {
42017 return isPointInSegment(p, s1, s2);
42018 });
42019 }
42020
42021
42022 function isPointInSegment(p, segmentStart, segmentEnd) {
42023 return (
42024 fenced(p.x, segmentStart.x, segmentEnd.x) &&
42025 fenced(p.y, segmentStart.y, segmentEnd.y)
42026 );
42027 }
42028
42029 function fenced(n, rangeStart, rangeEnd) {
42030
42031 // use matching threshold to work around
42032 // precision errors in intersection computation
42033
42034 return (
42035 n >= min$2(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
42036 n <= max$3(rangeStart, rangeEnd) + EQUAL_THRESHOLD
42037 );
42038 }
42039
42040 /**
42041 * Calculate mid of two points.
42042 *
42043 * @param {Point} p1
42044 * @param {Point} p2
42045 *
42046 * @return {Point}
42047 */
42048 function mid$1(p1, p2) {
42049
42050 return {
42051 x: (p1.x + p2.x) / 2,
42052 y: (p1.y + p2.y) / 2
42053 };
42054 }
42055
42056 var EQUAL_THRESHOLD = 0.1;
42057
42058 function pointsEqual(p1, p2) {
42059
42060 return (
42061 abs$4(p1.x - p2.x) <= EQUAL_THRESHOLD &&
42062 abs$4(p1.y - p2.y) <= EQUAL_THRESHOLD
42063 );
42064 }
42065
42066 function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
42067
42068 var index = attachment.segmentIndex;
42069
42070 var offset = newWaypoints.length - oldWaypoints.length;
42071
42072 // segmentMove happened
42073 if (hints.segmentMove) {
42074
42075 var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
42076 newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
42077
42078 // if label was on moved segment return new segment index
42079 if (index === oldSegmentStartIndex) {
42080 return newSegmentStartIndex;
42081 }
42082
42083 // label is after new segment index
42084 if (index >= newSegmentStartIndex) {
42085 return (index+offset < newSegmentStartIndex) ? newSegmentStartIndex : index+offset;
42086 }
42087
42088 // if label is before new segment index
42089 return index;
42090 }
42091
42092 // bendpointMove happened
42093 if (hints.bendpointMove) {
42094
42095 var insert = hints.bendpointMove.insert,
42096 bendpointIndex = hints.bendpointMove.bendpointIndex,
42097 newIndex;
42098
42099 // waypoints length didnt change
42100 if (offset === 0) {
42101 return index;
42102 }
42103
42104 // label behind new/removed bendpoint
42105 if (index >= bendpointIndex) {
42106 newIndex = insert ? index + 1 : index - 1;
42107 }
42108
42109 // label before new/removed bendpoint
42110 if (index < bendpointIndex) {
42111
42112 newIndex = index;
42113
42114 // decide label should take right or left segment
42115 if (insert && attachment.type !== 'bendpoint' && bendpointIndex-1 === index) {
42116
42117 var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
42118
42119 if (rel < attachment.relativeLocation) {
42120 newIndex++;
42121 }
42122 }
42123 }
42124
42125 return newIndex;
42126 }
42127
42128 // start/end changed
42129 if (offset === 0) {
42130 return index;
42131 }
42132
42133 if (hints.connectionStart) {
42134 return (index === 0) ? 0 : null;
42135 }
42136
42137 if (hints.connectionEnd) {
42138 return (index === oldWaypoints.length - 2) ? newWaypoints.length - 2 : null;
42139 }
42140
42141 // if nothing fits, return null
42142 return null;
42143 }
42144
42145
42146 /**
42147 * Calculate the required adjustment (move delta) for the given label
42148 * after the connection waypoints got updated.
42149 *
42150 * @param {djs.model.Label} label
42151 * @param {Array<Point>} newWaypoints
42152 * @param {Array<Point>} oldWaypoints
42153 * @param {Object} hints
42154 *
42155 * @return {Point} delta
42156 */
42157 function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
42158
42159 var x = 0,
42160 y = 0;
42161
42162 var labelPosition = getLabelMid(label);
42163
42164 // get closest attachment
42165 var attachment = getAttachment(labelPosition, oldWaypoints),
42166 oldLabelLineIndex = attachment.segmentIndex,
42167 newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
42168
42169 if (newLabelLineIndex === null) {
42170 return { x: x, y: y };
42171 }
42172
42173 // should never happen
42174 // TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
42175 if (newLabelLineIndex < 0 ||
42176 newLabelLineIndex > newWaypoints.length - 2) {
42177 return { x: x, y: y };
42178 }
42179
42180 var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
42181 newLabelLine = getLine(newWaypoints, newLabelLineIndex),
42182 oldFoot = attachment.position;
42183
42184 var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
42185 angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
42186
42187 // special rule if label on bendpoint
42188 if (attachment.type === 'bendpoint') {
42189
42190 var offset = newWaypoints.length - oldWaypoints.length,
42191 oldBendpointIndex = attachment.bendpointIndex,
42192 oldBendpoint = oldWaypoints[oldBendpointIndex];
42193
42194 // bendpoint position hasn't changed, return same position
42195 if (newWaypoints.indexOf(oldBendpoint) !== -1) {
42196 return { x: x, y: y };
42197 }
42198
42199 // new bendpoint and old bendpoint have same index, then just return the offset
42200 if (offset === 0) {
42201 var newBendpoint = newWaypoints[oldBendpointIndex];
42202
42203 return {
42204 x: newBendpoint.x - attachment.position.x,
42205 y: newBendpoint.y - attachment.position.y
42206 };
42207 }
42208
42209 // if bendpoints get removed
42210 if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
42211 relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
42212 }
42213 }
42214
42215 var newFoot = {
42216 x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
42217 y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
42218 };
42219
42220 // the rotated vector to label
42221 var newLabelVector = rotateVector({
42222 x: labelPosition.x - oldFoot.x,
42223 y: labelPosition.y - oldFoot.y
42224 }, angleDelta);
42225
42226 // the new relative position
42227 x = newFoot.x + newLabelVector.x - labelPosition.x;
42228 y = newFoot.y + newLabelVector.y - labelPosition.y;
42229
42230 return roundPoint({
42231 x: x,
42232 y: y
42233 });
42234 }
42235
42236
42237 // HELPERS //////////////////////
42238
42239 function relativePositionMidWaypoint(waypoints, idx) {
42240
42241 var distanceSegment1 = getDistancePointPoint(waypoints[idx-1], waypoints[idx]),
42242 distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx+1]);
42243
42244 var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
42245
42246 return relativePosition;
42247 }
42248
42249 function getLabelMid(label) {
42250 return {
42251 x: label.x + label.width / 2,
42252 y: label.y + label.height / 2
42253 };
42254 }
42255
42256 function getAngleDelta(l1, l2) {
42257 var a1 = getAngle(l1),
42258 a2 = getAngle(l2);
42259 return a2 - a1;
42260 }
42261
42262 function getLine(waypoints, idx) {
42263 return [ waypoints[idx], waypoints[idx+1] ];
42264 }
42265
42266 function getRelativeFootPosition(line, foot) {
42267
42268 var length = getDistancePointPoint(line[0], line[1]),
42269 lengthToFoot = getDistancePointPoint(line[0], foot);
42270
42271 return length === 0 ? 0 : lengthToFoot / length;
42272 }
42273
42274 /**
42275 * Calculates the absolute point relative to the new element's position
42276 *
42277 * @param {point} point [absolute]
42278 * @param {bounds} oldBounds
42279 * @param {bounds} newBounds
42280 *
42281 * @return {point} point [absolute]
42282 */
42283 function getNewAttachPoint(point, oldBounds, newBounds) {
42284 var oldCenter = center(oldBounds),
42285 newCenter = center(newBounds),
42286 oldDelta = delta(point, oldCenter);
42287
42288 var newDelta = {
42289 x: oldDelta.x * (newBounds.width / oldBounds.width),
42290 y: oldDelta.y * (newBounds.height / oldBounds.height)
42291 };
42292
42293 return roundPoint({
42294 x: newCenter.x + newDelta.x,
42295 y: newCenter.y + newDelta.y
42296 });
42297 }
42298
42299
42300 /**
42301 * Calculates the shape's delta relative to a new position
42302 * of a certain element's bounds
42303 *
42304 * @param {djs.model.Shape} point [absolute]
42305 * @param {bounds} oldBounds
42306 * @param {bounds} newBounds
42307 *
42308 * @return {delta} delta
42309 */
42310 function getNewAttachShapeDelta(shape, oldBounds, newBounds) {
42311 var shapeCenter = center(shape),
42312 oldCenter = center(oldBounds),
42313 newCenter = center(newBounds),
42314 shapeDelta = delta(shape, shapeCenter),
42315 oldCenterDelta = delta(shapeCenter, oldCenter),
42316 stickyPositionDelta = getStickyPositionDelta(shapeCenter, oldBounds, newBounds);
42317
42318 if (stickyPositionDelta) {
42319 return stickyPositionDelta;
42320 }
42321
42322 var newCenterDelta = {
42323 x: oldCenterDelta.x * (newBounds.width / oldBounds.width),
42324 y: oldCenterDelta.y * (newBounds.height / oldBounds.height)
42325 };
42326
42327 var newShapeCenter = {
42328 x: newCenter.x + newCenterDelta.x,
42329 y: newCenter.y + newCenterDelta.y
42330 };
42331
42332 return roundPoint({
42333 x: newShapeCenter.x + shapeDelta.x - shape.x,
42334 y: newShapeCenter.y + shapeDelta.y - shape.y
42335 });
42336 }
42337
42338 function getStickyPositionDelta(oldShapeCenter, oldBounds, newBounds) {
42339 var oldTRBL = asTRBL(oldBounds),
42340 newTRBL = asTRBL(newBounds);
42341
42342 if (isMoved(oldTRBL, newTRBL)) {
42343 return null;
42344 }
42345
42346 var oldOrientation = getOrientation(oldBounds, oldShapeCenter),
42347 stickyPositionDelta,
42348 newShapeCenter,
42349 newOrientation;
42350
42351 if (oldOrientation === 'top') {
42352 stickyPositionDelta = {
42353 x: 0,
42354 y: newTRBL.bottom - oldTRBL.bottom
42355 };
42356 } else if (oldOrientation === 'bottom') {
42357 stickyPositionDelta = {
42358 x: 0,
42359 y: newTRBL.top - oldTRBL.top
42360 };
42361 } else if (oldOrientation === 'right') {
42362 stickyPositionDelta = {
42363 x: newTRBL.left - oldTRBL.left,
42364 y: 0
42365 };
42366 } else if (oldOrientation === 'left') {
42367 stickyPositionDelta = {
42368 x: newTRBL.right - oldTRBL.right,
42369 y: 0
42370 };
42371 } else {
42372
42373 // fallback to proportional movement for corner-placed attachments
42374 return null;
42375 }
42376
42377 newShapeCenter = {
42378 x: oldShapeCenter.x + stickyPositionDelta.x,
42379 y: oldShapeCenter.y + stickyPositionDelta.y
42380 };
42381
42382 newOrientation = getOrientation(newBounds, newShapeCenter);
42383
42384 if (newOrientation !== oldOrientation) {
42385
42386 // fallback to proportional movement if orientation would otherwise change
42387 return null;
42388 }
42389
42390 return stickyPositionDelta;
42391 }
42392
42393 function isMoved(oldTRBL, newTRBL) {
42394 return isHorizontallyMoved(oldTRBL, newTRBL) || isVerticallyMoved(oldTRBL, newTRBL);
42395 }
42396
42397 function isHorizontallyMoved(oldTRBL, newTRBL) {
42398 return oldTRBL.right !== newTRBL.right && oldTRBL.left !== newTRBL.left;
42399 }
42400
42401 function isVerticallyMoved(oldTRBL, newTRBL) {
42402 return oldTRBL.top !== newTRBL.top && oldTRBL.bottom !== newTRBL.bottom;
42403 }
42404
42405 var DEFAULT_LABEL_DIMENSIONS = {
42406 width: 90,
42407 height: 20
42408 };
42409
42410 var NAME_PROPERTY = 'name';
42411 var TEXT_PROPERTY = 'text';
42412
42413 /**
42414 * A component that makes sure that external labels are added
42415 * together with respective elements and properly updated (DI wise)
42416 * during move.
42417 *
42418 * @param {EventBus} eventBus
42419 * @param {Modeling} modeling
42420 * @param {BpmnFactory} bpmnFactory
42421 * @param {TextRenderer} textRenderer
42422 */
42423 function LabelBehavior(
42424 eventBus, modeling, bpmnFactory,
42425 textRenderer) {
42426
42427 CommandInterceptor.call(this, eventBus);
42428
42429 // update label if name property was updated
42430 this.postExecute('element.updateProperties', function(e) {
42431 var context = e.context,
42432 element = context.element,
42433 properties = context.properties;
42434
42435 if (NAME_PROPERTY in properties) {
42436 modeling.updateLabel(element, properties[NAME_PROPERTY]);
42437 }
42438
42439 if (TEXT_PROPERTY in properties
42440 && is$1(element, 'bpmn:TextAnnotation')) {
42441
42442 var newBounds = textRenderer.getTextAnnotationBounds(
42443 {
42444 x: element.x,
42445 y: element.y,
42446 width: element.width,
42447 height: element.height
42448 },
42449 properties[TEXT_PROPERTY] || ''
42450 );
42451
42452 modeling.updateLabel(element, properties.text, newBounds);
42453 }
42454 });
42455
42456 // create label shape after shape/connection was created
42457 this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
42458 var context = e.context,
42459 hints = context.hints || {};
42460
42461 if (hints.createElementsBehavior === false) {
42462 return;
42463 }
42464
42465 var element = context.shape || context.connection,
42466 businessObject = element.businessObject;
42467
42468 if (isLabel(element) || !isLabelExternal(element)) {
42469 return;
42470 }
42471
42472 // only create label if attribute available
42473 if (!getLabel(element)) {
42474 return;
42475 }
42476
42477 var labelCenter = getExternalLabelMid(element);
42478
42479 // we don't care about x and y
42480 var labelDimensions = textRenderer.getExternalLabelBounds(
42481 DEFAULT_LABEL_DIMENSIONS,
42482 getLabel(element)
42483 );
42484
42485 modeling.createLabel(element, labelCenter, {
42486 id: businessObject.id + '_label',
42487 businessObject: businessObject,
42488 width: labelDimensions.width,
42489 height: labelDimensions.height
42490 });
42491 });
42492
42493 // update label after label shape was deleted
42494 this.postExecute('shape.delete', function(event) {
42495 var context = event.context,
42496 labelTarget = context.labelTarget,
42497 hints = context.hints || {};
42498
42499 // check if label
42500 if (labelTarget && hints.unsetLabel !== false) {
42501 modeling.updateLabel(labelTarget, null, null, { removeShape: false });
42502 }
42503 });
42504
42505 // update di information on label creation
42506 this.postExecute([ 'label.create' ], function(event) {
42507
42508 var context = event.context,
42509 element = context.shape,
42510 businessObject,
42511 di;
42512
42513 // we want to trigger on real labels only
42514 if (!element.labelTarget) {
42515 return;
42516 }
42517
42518 // we want to trigger on BPMN elements only
42519 if (!is$1(element.labelTarget || element, 'bpmn:BaseElement')) {
42520 return;
42521 }
42522
42523 businessObject = element.businessObject,
42524 di = businessObject.di;
42525
42526
42527 if (!di.label) {
42528 di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
42529 bounds: bpmnFactory.create('dc:Bounds')
42530 });
42531 }
42532
42533 assign(di.label.bounds, {
42534 x: element.x,
42535 y: element.y,
42536 width: element.width,
42537 height: element.height
42538 });
42539 });
42540
42541 function getVisibleLabelAdjustment(event) {
42542
42543 var context = event.context,
42544 connection = context.connection,
42545 label = connection.label,
42546 hints = assign({}, context.hints),
42547 newWaypoints = context.newWaypoints || connection.waypoints,
42548 oldWaypoints = context.oldWaypoints;
42549
42550
42551 if (typeof hints.startChanged === 'undefined') {
42552 hints.startChanged = !!hints.connectionStart;
42553 }
42554
42555 if (typeof hints.endChanged === 'undefined') {
42556 hints.endChanged = !!hints.connectionEnd;
42557 }
42558
42559 return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
42560 }
42561
42562 this.postExecute([
42563 'connection.layout',
42564 'connection.updateWaypoints'
42565 ], function(event) {
42566 var context = event.context,
42567 hints = context.hints || {};
42568
42569 if (hints.labelBehavior === false) {
42570 return;
42571 }
42572
42573 var connection = context.connection,
42574 label = connection.label,
42575 labelAdjustment;
42576
42577 // handle missing label as well as the case
42578 // that the label parent does not exist (yet),
42579 // because it is being pasted / created via multi element create
42580 //
42581 // Cf. https://github.com/bpmn-io/bpmn-js/pull/1227
42582 if (!label || !label.parent) {
42583 return;
42584 }
42585
42586 labelAdjustment = getVisibleLabelAdjustment(event);
42587
42588 modeling.moveShape(label, labelAdjustment);
42589 });
42590
42591
42592 // keep label position on shape replace
42593 this.postExecute([ 'shape.replace' ], function(event) {
42594 var context = event.context,
42595 newShape = context.newShape,
42596 oldShape = context.oldShape;
42597
42598 var businessObject = getBusinessObject(newShape);
42599
42600 if (businessObject
42601 && isLabelExternal(businessObject)
42602 && oldShape.label
42603 && newShape.label) {
42604 newShape.label.x = oldShape.label.x;
42605 newShape.label.y = oldShape.label.y;
42606 }
42607 });
42608
42609
42610 // move external label after resizing
42611 this.postExecute('shape.resize', function(event) {
42612
42613 var context = event.context,
42614 shape = context.shape,
42615 newBounds = context.newBounds,
42616 oldBounds = context.oldBounds;
42617
42618 if (hasExternalLabel(shape)) {
42619
42620 var label = shape.label,
42621 labelMid = getMid(label),
42622 edges = asEdges(oldBounds);
42623
42624 // get nearest border point to label as reference point
42625 var referencePoint = getReferencePoint$1(labelMid, edges);
42626
42627 var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
42628
42629 modeling.moveShape(label, delta);
42630
42631 }
42632
42633 });
42634
42635 }
42636
42637 inherits_browser(LabelBehavior, CommandInterceptor);
42638
42639 LabelBehavior.$inject = [
42640 'eventBus',
42641 'modeling',
42642 'bpmnFactory',
42643 'textRenderer'
42644 ];
42645
42646 // helpers //////////////////////
42647
42648 /**
42649 * Calculates a reference point delta relative to a new position
42650 * of a certain element's bounds
42651 *
42652 * @param {Point} point
42653 * @param {Bounds} oldBounds
42654 * @param {Bounds} newBounds
42655 *
42656 * @return {Delta} delta
42657 */
42658 function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
42659
42660 var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
42661
42662 return roundPoint(delta(newReferencePoint, referencePoint));
42663 }
42664
42665 /**
42666 * Generates the nearest point (reference point) for a given point
42667 * onto given set of lines
42668 *
42669 * @param {Array<Point, Point>} lines
42670 * @param {Point} point
42671 *
42672 * @param {Point}
42673 */
42674 function getReferencePoint$1(point, lines) {
42675
42676 if (!lines.length) {
42677 return;
42678 }
42679
42680 var nearestLine = getNearestLine(point, lines);
42681
42682 return perpendicularFoot(point, nearestLine);
42683 }
42684
42685 /**
42686 * Convert the given bounds to a lines array containing all edges
42687 *
42688 * @param {Bounds|Point} bounds
42689 *
42690 * @return Array<Point>
42691 */
42692 function asEdges(bounds) {
42693 return [
42694 [ // top
42695 {
42696 x: bounds.x,
42697 y: bounds.y
42698 },
42699 {
42700 x: bounds.x + (bounds.width || 0),
42701 y: bounds.y
42702 }
42703 ],
42704 [ // right
42705 {
42706 x: bounds.x + (bounds.width || 0),
42707 y: bounds.y
42708 },
42709 {
42710 x: bounds.x + (bounds.width || 0),
42711 y: bounds.y + (bounds.height || 0)
42712 }
42713 ],
42714 [ // bottom
42715 {
42716 x: bounds.x,
42717 y: bounds.y + (bounds.height || 0)
42718 },
42719 {
42720 x: bounds.x + (bounds.width || 0),
42721 y: bounds.y + (bounds.height || 0)
42722 }
42723 ],
42724 [ // left
42725 {
42726 x: bounds.x,
42727 y: bounds.y
42728 },
42729 {
42730 x: bounds.x,
42731 y: bounds.y + (bounds.height || 0)
42732 }
42733 ]
42734 ];
42735 }
42736
42737 /**
42738 * Returns the nearest line for a given point by distance
42739 * @param {Point} point
42740 * @param Array<Point> lines
42741 *
42742 * @return Array<Point>
42743 */
42744 function getNearestLine(point, lines) {
42745
42746 var distances = lines.map(function(l) {
42747 return {
42748 line: l,
42749 distance: getDistancePointLine(point, l)
42750 };
42751 });
42752
42753 var sorted = sortBy(distances, 'distance');
42754
42755 return sorted[0].line;
42756 }
42757
42758 function getResizedSourceAnchor(connection, shape, oldBounds) {
42759
42760 var waypoints = safeGetWaypoints(connection),
42761 waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
42762 oldAnchor = waypoints[0];
42763
42764 // new anchor is the last waypoint enclosed be resized source
42765 if (waypointsInsideNewBounds.length) {
42766 return waypointsInsideNewBounds[ waypointsInsideNewBounds.length - 1 ];
42767 }
42768
42769 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
42770 }
42771
42772
42773 function getResizedTargetAnchor(connection, shape, oldBounds) {
42774
42775 var waypoints = safeGetWaypoints(connection),
42776 waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
42777 oldAnchor = waypoints[waypoints.length - 1];
42778
42779 // new anchor is the first waypoint enclosed be resized target
42780 if (waypointsInsideNewBounds.length) {
42781 return waypointsInsideNewBounds[ 0 ];
42782 }
42783
42784 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
42785 }
42786
42787
42788 function getMovedSourceAnchor(connection, source, moveDelta) {
42789
42790 var waypoints = safeGetWaypoints(connection),
42791 oldBounds = subtract(source, moveDelta),
42792 oldAnchor = waypoints[ 0 ];
42793
42794 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, source);
42795 }
42796
42797
42798 function getMovedTargetAnchor(connection, target, moveDelta) {
42799
42800 var waypoints = safeGetWaypoints(connection),
42801 oldBounds = subtract(target, moveDelta),
42802 oldAnchor = waypoints[ waypoints.length - 1 ];
42803
42804 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, target);
42805 }
42806
42807
42808 // helpers //////////////////////
42809
42810 function subtract(bounds, delta) {
42811 return {
42812 x: bounds.x - delta.x,
42813 y: bounds.y - delta.y,
42814 width: bounds.width,
42815 height: bounds.height
42816 };
42817 }
42818
42819
42820 /**
42821 * Return waypoints of given connection; throw if non exists (should not happen!!).
42822 *
42823 * @param {Connection} connection
42824 *
42825 * @return {Array<Point>}
42826 */
42827 function safeGetWaypoints(connection) {
42828
42829 var waypoints = connection.waypoints;
42830
42831 if (!waypoints.length) {
42832 throw new Error('connection#' + connection.id + ': no waypoints');
42833 }
42834
42835 return waypoints;
42836 }
42837
42838 function getWaypointsInsideBounds(waypoints, bounds) {
42839 var originalWaypoints = map(waypoints, getOriginal$1);
42840
42841 return filter(originalWaypoints, function(waypoint) {
42842 return isInsideBounds(waypoint, bounds);
42843 });
42844 }
42845
42846 /**
42847 * Checks if point is inside bounds, incl. edges.
42848 *
42849 * @param {Point} point
42850 * @param {Bounds} bounds
42851 */
42852 function isInsideBounds(point, bounds) {
42853 return getOrientation(bounds, point, 1) === 'intersect';
42854 }
42855
42856 function getOriginal$1(point) {
42857 return point.original || point;
42858 }
42859
42860 /**
42861 * BPMN-specific message flow behavior.
42862 */
42863 function MessageFlowBehavior(eventBus, modeling) {
42864
42865 CommandInterceptor.call(this, eventBus);
42866
42867 this.postExecute('shape.replace', function(context) {
42868 var oldShape = context.oldShape,
42869 newShape = context.newShape;
42870
42871 if (!isParticipantCollapse(oldShape, newShape)) {
42872 return;
42873 }
42874
42875 var messageFlows = getMessageFlows(oldShape);
42876
42877 messageFlows.incoming.forEach(function(incoming) {
42878 var anchor = getResizedTargetAnchor(incoming, newShape, oldShape);
42879
42880 modeling.reconnectEnd(incoming, newShape, anchor);
42881 });
42882
42883 messageFlows.outgoing.forEach(function(outgoing) {
42884 var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape);
42885
42886 modeling.reconnectStart(outgoing, newShape, anchor);
42887 });
42888 }, true);
42889
42890 }
42891
42892 MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ];
42893
42894 inherits_browser(MessageFlowBehavior, CommandInterceptor);
42895
42896 // helpers //////////
42897
42898 function isParticipantCollapse(oldShape, newShape) {
42899 return is$1(oldShape, 'bpmn:Participant')
42900 && isExpanded(oldShape)
42901 && is$1(newShape, 'bpmn:Participant')
42902 && !isExpanded(newShape);
42903 }
42904
42905 function getMessageFlows(parent) {
42906 var elements = selfAndAllChildren([ parent ], false);
42907
42908 var incoming = [],
42909 outgoing = [];
42910
42911 elements.forEach(function(element) {
42912 if (element === parent) {
42913 return;
42914 }
42915
42916 element.incoming.forEach(function(connection) {
42917 if (is$1(connection, 'bpmn:MessageFlow')) {
42918 incoming.push(connection);
42919 }
42920 });
42921
42922 element.outgoing.forEach(function(connection) {
42923 if (is$1(connection, 'bpmn:MessageFlow')) {
42924 outgoing.push(connection);
42925 }
42926 });
42927 }, []);
42928
42929 return {
42930 incoming: incoming,
42931 outgoing: outgoing
42932 };
42933 }
42934
42935 var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants';
42936
42937 function ModelingFeedback(eventBus, tooltips, translate) {
42938
42939 function showError(position, message, timeout) {
42940 tooltips.add({
42941 position: {
42942 x: position.x + 5,
42943 y: position.y + 5
42944 },
42945 type: 'error',
42946 timeout: timeout || 2000,
42947 html: '<div>' + message + '</div>'
42948 });
42949 }
42950
42951 eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
42952 var context = event.context,
42953 shape = context.shape,
42954 target = context.target;
42955
42956 if (is$1(target, 'bpmn:Collaboration') && is$1(shape, 'bpmn:FlowNode')) {
42957 showError(event, translate(COLLAB_ERR_MSG));
42958 }
42959 });
42960
42961 }
42962
42963 ModelingFeedback.$inject = [
42964 'eventBus',
42965 'tooltips',
42966 'translate'
42967 ];
42968
42969 function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {
42970
42971 CommandInterceptor.call(this, eventBus);
42972
42973 var dragging = injector.get('dragging', false);
42974
42975 function fixConnection(connection) {
42976
42977 var source = connection.source,
42978 target = connection.target,
42979 parent = connection.parent;
42980
42981 // do not do anything if connection
42982 // is already deleted (may happen due to other
42983 // behaviors plugged-in before)
42984 if (!parent) {
42985 return;
42986 }
42987
42988 var replacementType,
42989 remove;
42990
42991 /**
42992 * Check if incoming or outgoing connections
42993 * can stay or could be substituted with an
42994 * appropriate replacement.
42995 *
42996 * This holds true for SequenceFlow <> MessageFlow.
42997 */
42998
42999 if (is$1(connection, 'bpmn:SequenceFlow')) {
43000 if (!bpmnRules.canConnectSequenceFlow(source, target)) {
43001 remove = true;
43002 }
43003
43004 if (bpmnRules.canConnectMessageFlow(source, target)) {
43005 replacementType = 'bpmn:MessageFlow';
43006 }
43007 }
43008
43009 // transform message flows into sequence flows, if possible
43010
43011 if (is$1(connection, 'bpmn:MessageFlow')) {
43012
43013 if (!bpmnRules.canConnectMessageFlow(source, target)) {
43014 remove = true;
43015 }
43016
43017 if (bpmnRules.canConnectSequenceFlow(source, target)) {
43018 replacementType = 'bpmn:SequenceFlow';
43019 }
43020 }
43021
43022 if (is$1(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
43023 remove = true;
43024 }
43025
43026
43027 // remove invalid connection,
43028 // unless it has been removed already
43029 if (remove) {
43030 modeling.removeConnection(connection);
43031 }
43032
43033 // replace SequenceFlow <> MessageFlow
43034
43035 if (replacementType) {
43036 modeling.connect(source, target, {
43037 type: replacementType,
43038 waypoints: connection.waypoints.slice()
43039 });
43040 }
43041 }
43042
43043 function replaceReconnectedConnection(event) {
43044
43045 var context = event.context,
43046 connection = context.connection,
43047 source = context.newSource || connection.source,
43048 target = context.newTarget || connection.target,
43049 allowed,
43050 replacement;
43051
43052 allowed = bpmnRules.canConnect(source, target);
43053
43054 if (!allowed || allowed.type === connection.type) {
43055 return;
43056 }
43057
43058 replacement = modeling.connect(source, target, {
43059 type: allowed.type,
43060 waypoints: connection.waypoints.slice()
43061 });
43062
43063 // remove old connection
43064 modeling.removeConnection(connection);
43065
43066 // replace connection in context to reconnect end/start
43067 context.connection = replacement;
43068
43069 if (dragging) {
43070 cleanDraggingSelection(connection, replacement);
43071 }
43072 }
43073
43074 // monkey-patch selection saved in dragging in order to re-select it when operation is finished
43075 function cleanDraggingSelection(oldConnection, newConnection) {
43076 var context = dragging.context(),
43077 previousSelection = context && context.payload.previousSelection,
43078 index;
43079
43080 // do nothing if not dragging or no selection was present
43081 if (!previousSelection || !previousSelection.length) {
43082 return;
43083 }
43084
43085 index = previousSelection.indexOf(oldConnection);
43086
43087 if (index === -1) {
43088 return;
43089 }
43090
43091 previousSelection.splice(index, 1, newConnection);
43092 }
43093
43094 // lifecycle hooks
43095
43096 this.postExecuted('elements.move', function(context) {
43097
43098 var closure = context.closure,
43099 allConnections = closure.allConnections;
43100
43101 forEach(allConnections, fixConnection);
43102 }, true);
43103
43104 this.preExecute('connection.reconnect', replaceReconnectedConnection);
43105
43106 this.postExecuted('element.updateProperties', function(event) {
43107 var context = event.context,
43108 properties = context.properties,
43109 element = context.element,
43110 businessObject = element.businessObject,
43111 connection;
43112
43113 // remove condition on change to default
43114 if (properties.default) {
43115 connection = find(
43116 element.outgoing,
43117 matchPattern({ id: element.businessObject.default.id })
43118 );
43119
43120 if (connection) {
43121 modeling.updateProperties(connection, { conditionExpression: undefined });
43122 }
43123 }
43124
43125 // remove default from source on change to conditional
43126 if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
43127 modeling.updateProperties(element.source, { default: undefined });
43128 }
43129 });
43130 }
43131
43132 inherits_browser(ReplaceConnectionBehavior, CommandInterceptor);
43133
43134 ReplaceConnectionBehavior.$inject = [
43135 'eventBus',
43136 'modeling',
43137 'bpmnRules',
43138 'injector'
43139 ];
43140
43141 /**
43142 * BPMN specific remove behavior
43143 */
43144 function RemoveParticipantBehavior(eventBus, modeling) {
43145
43146 CommandInterceptor.call(this, eventBus);
43147
43148
43149 /**
43150 * morph collaboration diagram into process diagram
43151 * after the last participant has been removed
43152 */
43153
43154 this.preExecute('shape.delete', function(context) {
43155
43156 var shape = context.shape,
43157 parent = shape.parent;
43158
43159 // activate the behavior if the shape to be removed
43160 // is a participant
43161 if (is$1(shape, 'bpmn:Participant')) {
43162 context.collaborationRoot = parent;
43163 }
43164 }, true);
43165
43166 this.postExecute('shape.delete', function(context) {
43167
43168 var collaborationRoot = context.collaborationRoot;
43169
43170 if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {
43171
43172 // replace empty collaboration with process diagram
43173 modeling.makeProcess();
43174 }
43175 }, true);
43176
43177 }
43178
43179 RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];
43180
43181 inherits_browser(RemoveParticipantBehavior, CommandInterceptor);
43182
43183 /**
43184 * BPMN-specific replace behavior.
43185 */
43186 function ReplaceElementBehaviour(
43187 bpmnReplace,
43188 bpmnRules,
43189 elementRegistry,
43190 injector,
43191 modeling,
43192 selection
43193 ) {
43194 injector.invoke(CommandInterceptor, this);
43195
43196 this._bpmnReplace = bpmnReplace;
43197 this._elementRegistry = elementRegistry;
43198 this._selection = selection;
43199
43200 // replace elements on create, e.g. during copy-paste
43201 this.postExecuted([ 'elements.create' ], 500, function(event) {
43202 var context = event.context,
43203 target = context.parent,
43204 elements = context.elements;
43205
43206 var canReplace = bpmnRules.canReplace(elements, target);
43207
43208 if (canReplace) {
43209 this.replaceElements(elements, canReplace.replacements);
43210 }
43211 }, this);
43212
43213 // replace elements on move
43214 this.postExecuted([ 'elements.move' ], 500, function(event) {
43215 var context = event.context,
43216 target = context.newParent,
43217 newHost = context.newHost,
43218 elements = [];
43219
43220 forEach(context.closure.topLevel, function(topLevelElements) {
43221 if (isEventSubProcess(topLevelElements)) {
43222 elements = elements.concat(topLevelElements.children);
43223 } else {
43224 elements = elements.concat(topLevelElements);
43225 }
43226 });
43227
43228 // set target to host if attaching
43229 if (elements.length === 1 && newHost) {
43230 target = newHost;
43231 }
43232
43233 var canReplace = bpmnRules.canReplace(elements, target);
43234
43235 if (canReplace) {
43236 this.replaceElements(elements, canReplace.replacements, newHost);
43237 }
43238 }, this);
43239
43240 // update attachments on host replace
43241 this.postExecute([ 'shape.replace' ], 1500, function(e) {
43242 var context = e.context,
43243 oldShape = context.oldShape,
43244 newShape = context.newShape,
43245 attachers = oldShape.attachers,
43246 canReplace;
43247
43248 if (attachers && attachers.length) {
43249 canReplace = bpmnRules.canReplace(attachers, newShape);
43250
43251 this.replaceElements(attachers, canReplace.replacements);
43252 }
43253
43254 }, this);
43255
43256 // keep ID on shape replace
43257 this.postExecuted([ 'shape.replace' ], 1500, function(e) {
43258 var context = e.context,
43259 oldShape = context.oldShape,
43260 newShape = context.newShape;
43261
43262 modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
43263 modeling.updateProperties(newShape, { id: oldShape.id });
43264 });
43265 }
43266
43267 inherits_browser(ReplaceElementBehaviour, CommandInterceptor);
43268
43269 ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) {
43270 var elementRegistry = this._elementRegistry,
43271 bpmnReplace = this._bpmnReplace,
43272 selection = this._selection;
43273
43274 forEach(newElements, function(replacement) {
43275 var newElement = {
43276 type: replacement.newElementType
43277 };
43278
43279 var oldElement = elementRegistry.get(replacement.oldElementId);
43280
43281 var idx = elements.indexOf(oldElement);
43282
43283 elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
43284 });
43285
43286 if (newElements) {
43287 selection.select(elements);
43288 }
43289 };
43290
43291 ReplaceElementBehaviour.$inject = [
43292 'bpmnReplace',
43293 'bpmnRules',
43294 'elementRegistry',
43295 'injector',
43296 'modeling',
43297 'selection'
43298 ];
43299
43300 var abs$5 = Math.abs,
43301 min$3 = Math.min,
43302 max$4 = Math.max;
43303
43304
43305 function addToTrbl(trbl, attr, value, choice) {
43306 var current = trbl[attr];
43307
43308 // make sure to set the value if it does not exist
43309 // or apply the correct value by comparing against
43310 // choice(value, currentValue)
43311 trbl[attr] = current === undefined ? value : choice(value, current);
43312 }
43313
43314 function addMin(trbl, attr, value) {
43315 return addToTrbl(trbl, attr, value, min$3);
43316 }
43317
43318 function addMax(trbl, attr, value) {
43319 return addToTrbl(trbl, attr, value, max$4);
43320 }
43321
43322 var LANE_RIGHT_PADDING = 20,
43323 LANE_LEFT_PADDING = 50,
43324 LANE_TOP_PADDING = 20,
43325 LANE_BOTTOM_PADDING = 20;
43326
43327
43328 function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
43329 var lanesRoot = getLanesRoot(laneShape);
43330
43331 var isFirst = true,
43332 isLast = true;
43333
43334 // max top/bottom size for lanes
43335 var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);
43336
43337 var laneTrbl = asTRBL(laneShape);
43338
43339 var maxTrbl = {},
43340 minTrbl = {};
43341
43342 if (/e/.test(resizeDirection)) {
43343 minTrbl.right = laneTrbl.left + LANE_MIN_DIMENSIONS.width;
43344 } else
43345 if (/w/.test(resizeDirection)) {
43346 minTrbl.left = laneTrbl.right - LANE_MIN_DIMENSIONS.width;
43347 }
43348
43349 allLanes.forEach(function(other) {
43350
43351 var otherTrbl = asTRBL(other);
43352
43353 if (/n/.test(resizeDirection)) {
43354
43355 if (otherTrbl.top < (laneTrbl.top - 10)) {
43356 isFirst = false;
43357 }
43358
43359 // max top size (based on next element)
43360 if (balanced && abs$5(laneTrbl.top - otherTrbl.bottom) < 10) {
43361 addMax(maxTrbl, 'top', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
43362 }
43363
43364 // min top size (based on self or nested element)
43365 if (abs$5(laneTrbl.top - otherTrbl.top) < 5) {
43366 addMin(minTrbl, 'top', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
43367 }
43368 }
43369
43370 if (/s/.test(resizeDirection)) {
43371
43372 if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
43373 isLast = false;
43374 }
43375
43376 // max bottom size (based on previous element)
43377 if (balanced && abs$5(laneTrbl.bottom - otherTrbl.top) < 10) {
43378 addMin(maxTrbl, 'bottom', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
43379 }
43380
43381 // min bottom size (based on self or nested element)
43382 if (abs$5(laneTrbl.bottom - otherTrbl.bottom) < 5) {
43383 addMax(minTrbl, 'bottom', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
43384 }
43385 }
43386 });
43387
43388 // max top/bottom/left/right size based on flow nodes
43389 var flowElements = lanesRoot.children.filter(function(s) {
43390 return !s.hidden && !s.waypoints && (is$1(s, 'bpmn:FlowElement') || is$1(s, 'bpmn:Artifact'));
43391 });
43392
43393 flowElements.forEach(function(flowElement) {
43394
43395 var flowElementTrbl = asTRBL(flowElement);
43396
43397 if (isFirst && /n/.test(resizeDirection)) {
43398 addMin(minTrbl, 'top', flowElementTrbl.top - LANE_TOP_PADDING);
43399 }
43400
43401 if (/e/.test(resizeDirection)) {
43402 addMax(minTrbl, 'right', flowElementTrbl.right + LANE_RIGHT_PADDING);
43403 }
43404
43405 if (isLast && /s/.test(resizeDirection)) {
43406 addMax(minTrbl, 'bottom', flowElementTrbl.bottom + LANE_BOTTOM_PADDING);
43407 }
43408
43409 if (/w/.test(resizeDirection)) {
43410 addMin(minTrbl, 'left', flowElementTrbl.left - LANE_LEFT_PADDING);
43411 }
43412 });
43413
43414 return {
43415 min: minTrbl,
43416 max: maxTrbl
43417 };
43418 }
43419
43420 var HIGH_PRIORITY$b = 1500;
43421
43422 var LANE_MIN_DIMENSIONS = { width: 300, height: 60 };
43423
43424 var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 };
43425
43426 var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 };
43427
43428 var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };
43429
43430
43431 /**
43432 * Set minimum bounds/resize constraints on resize.
43433 *
43434 * @param {EventBus} eventBus
43435 */
43436 function ResizeBehavior$1(eventBus) {
43437 eventBus.on('resize.start', HIGH_PRIORITY$b, function(event) {
43438 var context = event.context,
43439 shape = context.shape,
43440 direction = context.direction,
43441 balanced = context.balanced;
43442
43443 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
43444 context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
43445 }
43446
43447 if (is$1(shape, 'bpmn:Participant')) {
43448 context.minDimensions = PARTICIPANT_MIN_DIMENSIONS;
43449 }
43450
43451 if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
43452 context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
43453 }
43454
43455 if (is$1(shape, 'bpmn:TextAnnotation')) {
43456 context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
43457 }
43458 });
43459 }
43460
43461 ResizeBehavior$1.$inject = [ 'eventBus' ];
43462
43463 var SLIGHTLY_HIGHER_PRIORITY = 1001;
43464
43465
43466 /**
43467 * Invoke {@link Modeling#resizeLane} instead of
43468 * {@link Modeling#resizeShape} when resizing a Lane
43469 * or Participant shape.
43470 */
43471 function ResizeLaneBehavior(eventBus, modeling) {
43472
43473 eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
43474 var context = event.context,
43475 shape = context.shape;
43476
43477 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
43478
43479 // should we resize the opposite lane(s) in
43480 // order to compensate for the resize operation?
43481 context.balanced = !hasPrimaryModifier(event);
43482 }
43483 });
43484
43485 /**
43486 * Intercept resize end and call resize lane function instead.
43487 */
43488 eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
43489 var context = event.context,
43490 shape = context.shape,
43491 canExecute = context.canExecute,
43492 newBounds = context.newBounds;
43493
43494 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
43495
43496 if (canExecute) {
43497
43498 // ensure we have actual pixel values for new bounds
43499 // (important when zoom level was > 1 during move)
43500 newBounds = roundBounds(newBounds);
43501
43502 // perform the actual resize
43503 modeling.resizeLane(shape, newBounds, context.balanced);
43504 }
43505
43506 // stop propagation
43507 return false;
43508 }
43509 });
43510 }
43511
43512 ResizeLaneBehavior.$inject = [
43513 'eventBus',
43514 'modeling'
43515 ];
43516
43517 function RemoveElementBehavior(eventBus, bpmnRules, modeling) {
43518
43519 CommandInterceptor.call(this, eventBus);
43520
43521 /**
43522 * Combine sequence flows when deleting an element
43523 * if there is one incoming and one outgoing
43524 * sequence flow
43525 */
43526 this.preExecute('shape.delete', function(e) {
43527
43528 var shape = e.context.shape;
43529
43530 // only handle [a] -> [shape] -> [b] patterns
43531 if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
43532 return;
43533 }
43534
43535 var inConnection = shape.incoming[0],
43536 outConnection = shape.outgoing[0];
43537
43538 // only handle sequence flows
43539 if (!is$1(inConnection, 'bpmn:SequenceFlow') || !is$1(outConnection, 'bpmn:SequenceFlow')) {
43540 return;
43541 }
43542
43543 if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {
43544
43545 // compute new, combined waypoints
43546 var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);
43547
43548 modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
43549 }
43550 });
43551
43552 }
43553
43554 inherits_browser(RemoveElementBehavior, CommandInterceptor);
43555
43556 RemoveElementBehavior.$inject = [
43557 'eventBus',
43558 'bpmnRules',
43559 'modeling'
43560 ];
43561
43562
43563 // helpers //////////////////////
43564
43565 function getDocking$1(point) {
43566 return point.original || point;
43567 }
43568
43569
43570 function getNewWaypoints(inWaypoints, outWaypoints) {
43571
43572 var intersection = lineIntersect(
43573 getDocking$1(inWaypoints[inWaypoints.length - 2]),
43574 getDocking$1(inWaypoints[inWaypoints.length - 1]),
43575 getDocking$1(outWaypoints[1]),
43576 getDocking$1(outWaypoints[0]));
43577
43578 if (intersection) {
43579 return [].concat(
43580 inWaypoints.slice(0, inWaypoints.length - 1),
43581 [ intersection ],
43582 outWaypoints.slice(1));
43583 } else {
43584 return [
43585 getDocking$1(inWaypoints[0]),
43586 getDocking$1(outWaypoints[outWaypoints.length - 1])
43587 ];
43588 }
43589 }
43590
43591 var max$5 = Math.max;
43592
43593
43594 function SpaceToolBehavior$1(eventBus) {
43595 eventBus.on('spaceTool.getMinDimensions', function(context) {
43596 var shapes = context.shapes,
43597 axis = context.axis,
43598 start = context.start,
43599 minDimensions = {};
43600
43601 forEach(shapes, function(shape) {
43602 var id = shape.id;
43603
43604 if (is$1(shape, 'bpmn:Participant')) {
43605
43606 if (isHorizontal$2(axis)) {
43607 minDimensions[ id ] = PARTICIPANT_MIN_DIMENSIONS;
43608 } else {
43609 minDimensions[ id ] = {
43610 width: PARTICIPANT_MIN_DIMENSIONS.width,
43611 height: getParticipantMinHeight(shape, start)
43612 };
43613 }
43614
43615 }
43616
43617 if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
43618 minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS;
43619 }
43620
43621 if (is$1(shape, 'bpmn:TextAnnotation')) {
43622 minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS;
43623 }
43624 });
43625
43626 return minDimensions;
43627 });
43628 }
43629
43630 SpaceToolBehavior$1.$inject = [ 'eventBus' ];
43631
43632
43633 // helpers //////////
43634 function isHorizontal$2(axis) {
43635 return axis === 'x';
43636 }
43637
43638 /**
43639 * Get minimum height for participant taking lanes into account.
43640 *
43641 * @param {<djs.model.Shape>} participant
43642 * @param {number} start
43643 *
43644 * @returns {Object}
43645 */
43646 function getParticipantMinHeight(participant, start) {
43647 var lanesMinHeight;
43648
43649 if (!hasChildLanes(participant)) {
43650 return PARTICIPANT_MIN_DIMENSIONS.height;
43651 }
43652
43653 lanesMinHeight = getLanesMinHeight(participant, start);
43654
43655 return max$5(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight);
43656 }
43657
43658 function hasChildLanes(element) {
43659 return !!getChildLanes(element).length;
43660 }
43661
43662 function getLanesMinHeight(participant, resizeStart) {
43663 var lanes = getChildLanes(participant),
43664 resizedLane;
43665
43666 // find the nested lane which is currently resized
43667 resizedLane = findResizedLane(lanes, resizeStart);
43668
43669 // resized lane cannot shrink below the minimum height
43670 // but remaining lanes' dimensions are kept intact
43671 return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height;
43672 }
43673
43674 /**
43675 * Find nested lane which is currently resized.
43676 *
43677 * @param {Array<djs.model.Shape>} lanes
43678 * @param {number} resizeStart
43679 */
43680 function findResizedLane(lanes, resizeStart) {
43681 var i, lane, childLanes;
43682
43683 for (i = 0; i < lanes.length; i++) {
43684 lane = lanes[i];
43685
43686 // resizing current lane or a lane nested
43687 if (resizeStart >= lane.y && resizeStart <= lane.y + lane.height) {
43688 childLanes = getChildLanes(lane);
43689
43690 // a nested lane is resized
43691 if (childLanes.length) {
43692 return findResizedLane(childLanes, resizeStart);
43693 }
43694
43695 // current lane is the resized one
43696 return lane;
43697 }
43698 }
43699 }
43700
43701 /**
43702 * Add start event replacing element with expanded sub process.
43703 *
43704 * @param {Injector} injector
43705 * @param {Modeling} modeling
43706 */
43707 function SubProcessStartEventBehavior(injector, modeling) {
43708 injector.invoke(CommandInterceptor, this);
43709
43710 this.postExecuted('shape.replace', function(event) {
43711 var oldShape = event.context.oldShape,
43712 newShape = event.context.newShape;
43713
43714 if (
43715 !is$1(newShape, 'bpmn:SubProcess') ||
43716 !is$1(oldShape, 'bpmn:Task') ||
43717 !isExpanded(newShape)
43718 ) {
43719 return;
43720 }
43721
43722 var position = getStartEventPosition(newShape);
43723
43724 modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
43725 });
43726 }
43727
43728 SubProcessStartEventBehavior.$inject = [
43729 'injector',
43730 'modeling'
43731 ];
43732
43733 inherits_browser(SubProcessStartEventBehavior, CommandInterceptor);
43734
43735 // helpers //////////
43736
43737 function getStartEventPosition(shape) {
43738 return {
43739 x: shape.x + shape.width / 6,
43740 y: shape.y + shape.height / 2
43741 };
43742 }
43743
43744 var LOW_PRIORITY$e = 500;
43745
43746
43747 function ToggleElementCollapseBehaviour(
43748 eventBus, elementFactory, modeling,
43749 resize) {
43750
43751 CommandInterceptor.call(this, eventBus);
43752
43753
43754 function hideEmptyLabels(children) {
43755 if (children.length) {
43756 children.forEach(function(child) {
43757 if (child.type === 'label' && !child.businessObject.name) {
43758 child.hidden = true;
43759 }
43760 });
43761 }
43762 }
43763
43764 function expandedBounds(shape, defaultSize) {
43765 var children = shape.children,
43766 newBounds = defaultSize,
43767 visibleElements,
43768 visibleBBox;
43769
43770 visibleElements = filterVisible(children).concat([ shape ]);
43771
43772 visibleBBox = computeChildrenBBox(visibleElements);
43773
43774 if (visibleBBox) {
43775
43776 // center to visibleBBox with max(defaultSize, childrenBounds)
43777 newBounds.width = Math.max(visibleBBox.width, newBounds.width);
43778 newBounds.height = Math.max(visibleBBox.height, newBounds.height);
43779
43780 newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
43781 newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
43782 } else {
43783
43784 // center to collapsed shape with defaultSize
43785 newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
43786 newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
43787 }
43788
43789 return newBounds;
43790 }
43791
43792 function collapsedBounds(shape, defaultSize) {
43793
43794 return {
43795 x: shape.x + (shape.width - defaultSize.width) / 2,
43796 y: shape.y + (shape.height - defaultSize.height) / 2,
43797 width: defaultSize.width,
43798 height: defaultSize.height
43799 };
43800 }
43801
43802 this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY$e, function(e) {
43803
43804 var context = e.context,
43805 shape = context.shape;
43806
43807 if (!is$1(shape, 'bpmn:SubProcess')) {
43808 return;
43809 }
43810
43811 if (!shape.collapsed) {
43812
43813 // all children got made visible through djs, hide empty labels
43814 hideEmptyLabels(shape.children);
43815
43816 // remove collapsed marker
43817 getBusinessObject(shape).di.isExpanded = true;
43818 } else {
43819
43820 // place collapsed marker
43821 getBusinessObject(shape).di.isExpanded = false;
43822 }
43823 });
43824
43825 this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY$e, function(e) {
43826
43827 var context = e.context;
43828 var shape = context.shape;
43829
43830
43831 // revert removing/placing collapsed marker
43832 if (!shape.collapsed) {
43833 getBusinessObject(shape).di.isExpanded = true;
43834
43835 } else {
43836 getBusinessObject(shape).di.isExpanded = false;
43837 }
43838 });
43839
43840 this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY$e, function(e) {
43841 var shape = e.context.shape,
43842 defaultSize = elementFactory._getDefaultSize(shape),
43843 newBounds;
43844
43845 if (shape.collapsed) {
43846
43847 // resize to default size of collapsed shapes
43848 newBounds = collapsedBounds(shape, defaultSize);
43849 } else {
43850
43851 // resize to bounds of max(visible children, defaultSize)
43852 newBounds = expandedBounds(shape, defaultSize);
43853 }
43854
43855 modeling.resizeShape(shape, newBounds, null, {
43856 autoResize: shape.collapsed ? false : 'nwse'
43857 });
43858 });
43859
43860 }
43861
43862
43863 inherits_browser(ToggleElementCollapseBehaviour, CommandInterceptor);
43864
43865 ToggleElementCollapseBehaviour.$inject = [
43866 'eventBus',
43867 'elementFactory',
43868 'modeling'
43869 ];
43870
43871
43872 // helpers //////////////////////
43873
43874 function filterVisible(elements) {
43875 return elements.filter(function(e) {
43876 return !e.hidden;
43877 });
43878 }
43879
43880 /**
43881 * Unclaims model IDs on element deletion.
43882 *
43883 * @param {Canvas} canvas
43884 * @param {Injector} injector
43885 * @param {Moddle} moddle
43886 * @param {Modeling} modeling
43887 */
43888 function UnclaimIdBehavior(canvas, injector, moddle, modeling) {
43889 injector.invoke(CommandInterceptor, this);
43890
43891 this.preExecute('shape.delete', function(event) {
43892 var context = event.context,
43893 shape = context.shape,
43894 shapeBo = shape.businessObject;
43895
43896 if (isLabel(shape)) {
43897 return;
43898 }
43899
43900 if (is$1(shape, 'bpmn:Participant') && isExpanded(shape)) {
43901 moddle.ids.unclaim(shapeBo.processRef.id);
43902 }
43903
43904 modeling.unclaimId(shapeBo.id, shapeBo);
43905 });
43906
43907
43908 this.preExecute('connection.delete', function(event) {
43909 var context = event.context,
43910 connection = context.connection,
43911 connectionBo = connection.businessObject;
43912
43913 modeling.unclaimId(connectionBo.id, connectionBo);
43914 });
43915
43916 this.preExecute('canvas.updateRoot', function() {
43917 var rootElement = canvas.getRootElement(),
43918 rootElementBo = rootElement.businessObject;
43919
43920 moddle.ids.unclaim(rootElementBo.id);
43921 });
43922 }
43923
43924 inherits_browser(UnclaimIdBehavior, CommandInterceptor);
43925
43926 UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];
43927
43928 var LOW_PRIORITY$f = 500,
43929 HIGH_PRIORITY$c = 5000;
43930
43931
43932 /**
43933 * BPMN specific delete lane behavior
43934 */
43935 function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) {
43936
43937 CommandInterceptor.call(this, eventBus);
43938
43939 /**
43940 * Ok, this is it:
43941 *
43942 * We have to update the Lane#flowNodeRefs _and_
43943 * FlowNode#lanes with every FlowNode move/resize and
43944 * Lane move/resize.
43945 *
43946 * We want to group that stuff to recompute containments
43947 * as efficient as possible.
43948 *
43949 * Yea!
43950 */
43951
43952 // the update context
43953 var context;
43954
43955
43956 function initContext() {
43957 context = context || new UpdateContext();
43958 context.enter();
43959
43960 return context;
43961 }
43962
43963 function getContext() {
43964 if (!context) {
43965 throw new Error(translate('out of bounds release'));
43966 }
43967
43968 return context;
43969 }
43970
43971 function releaseContext() {
43972
43973 if (!context) {
43974 throw new Error(translate('out of bounds release'));
43975 }
43976
43977 var triggerUpdate = context.leave();
43978
43979 if (triggerUpdate) {
43980 modeling.updateLaneRefs(context.flowNodes, context.lanes);
43981
43982 context = null;
43983 }
43984
43985 return triggerUpdate;
43986 }
43987
43988
43989 var laneRefUpdateEvents = [
43990 'spaceTool',
43991 'lane.add',
43992 'lane.resize',
43993 'lane.split',
43994 'elements.create',
43995 'elements.delete',
43996 'elements.move',
43997 'shape.create',
43998 'shape.delete',
43999 'shape.move',
44000 'shape.resize'
44001 ];
44002
44003
44004 // listen to a lot of stuff to group lane updates
44005
44006 this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY$c, function(event) {
44007 initContext();
44008 });
44009
44010 this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY$f, function(event) {
44011 releaseContext();
44012 });
44013
44014
44015 // Mark flow nodes + lanes that need an update
44016
44017 this.preExecute([
44018 'shape.create',
44019 'shape.move',
44020 'shape.delete',
44021 'shape.resize'
44022 ], function(event) {
44023
44024 var context = event.context,
44025 shape = context.shape;
44026
44027 var updateContext = getContext();
44028
44029 // no need to update labels
44030 if (shape.labelTarget) {
44031 return;
44032 }
44033
44034 if (is$1(shape, 'bpmn:Lane')) {
44035 updateContext.addLane(shape);
44036 }
44037
44038 if (is$1(shape, 'bpmn:FlowNode')) {
44039 updateContext.addFlowNode(shape);
44040 }
44041 });
44042 }
44043
44044 UpdateFlowNodeRefsBehavior.$inject = [
44045 'eventBus',
44046 'modeling' ,
44047 'translate'
44048 ];
44049
44050 inherits_browser(UpdateFlowNodeRefsBehavior, CommandInterceptor);
44051
44052
44053 function UpdateContext() {
44054
44055 this.flowNodes = [];
44056 this.lanes = [];
44057
44058 this.counter = 0;
44059
44060 this.addLane = function(lane) {
44061 this.lanes.push(lane);
44062 };
44063
44064 this.addFlowNode = function(flowNode) {
44065 this.flowNodes.push(flowNode);
44066 };
44067
44068 this.enter = function() {
44069 this.counter++;
44070 };
44071
44072 this.leave = function() {
44073 this.counter--;
44074
44075 return !this.counter;
44076 };
44077 }
44078
44079 /**
44080 * A behavior that unsets the Default property of
44081 * sequence flow source on element delete, if the
44082 * removed element is the Gateway or Task's default flow.
44083 *
44084 * @param {EventBus} eventBus
44085 * @param {Modeling} modeling
44086 */
44087 function DeleteSequenceFlowBehavior(eventBus, modeling) {
44088
44089 CommandInterceptor.call(this, eventBus);
44090
44091
44092 this.preExecute('connection.delete', function(event) {
44093 var context = event.context,
44094 connection = context.connection,
44095 source = connection.source;
44096
44097 if (isDefaultFlow(connection, source)) {
44098 modeling.updateProperties(source, {
44099 'default': null
44100 });
44101 }
44102 });
44103 }
44104
44105 inherits_browser(DeleteSequenceFlowBehavior, CommandInterceptor);
44106
44107 DeleteSequenceFlowBehavior.$inject = [
44108 'eventBus',
44109 'modeling'
44110 ];
44111
44112
44113 // helpers //////////////////////
44114
44115 function isDefaultFlow(connection, source) {
44116
44117 if (!is$1(connection, 'bpmn:SequenceFlow')) {
44118 return false;
44119 }
44120
44121 var sourceBo = getBusinessObject(source),
44122 sequenceFlow = getBusinessObject(connection);
44123
44124 return sourceBo.get('default') === sequenceFlow;
44125 }
44126
44127 var BehaviorModule = {
44128 __init__: [
44129 'adaptiveLabelPositioningBehavior',
44130 'appendBehavior',
44131 'associationBehavior',
44132 'attachEventBehavior',
44133 'boundaryEventBehavior',
44134 'rootElementReferenceBehavior',
44135 'createBehavior',
44136 'fixHoverBehavior',
44137 'createDataObjectBehavior',
44138 'createParticipantBehavior',
44139 'dataStoreBehavior',
44140 'dataInputAssociationBehavior',
44141 'deleteLaneBehavior',
44142 'detachEventBehavior',
44143 'dropOnFlowBehavior',
44144 'eventBasedGatewayBehavior',
44145 'groupBehavior',
44146 'importDockingFix',
44147 'isHorizontalFix',
44148 'labelBehavior',
44149 'messageFlowBehavior',
44150 'modelingFeedback',
44151 'removeElementBehavior',
44152 'removeParticipantBehavior',
44153 'replaceConnectionBehavior',
44154 'replaceElementBehaviour',
44155 'resizeBehavior',
44156 'resizeLaneBehavior',
44157 'toggleElementCollapseBehaviour',
44158 'spaceToolBehavior',
44159 'subProcessStartEventBehavior',
44160 'unclaimIdBehavior',
44161 'unsetDefaultFlowBehavior',
44162 'updateFlowNodeRefsBehavior'
44163 ],
44164 adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
44165 appendBehavior: [ 'type', AppendBehavior ],
44166 associationBehavior: [ 'type', AssociationBehavior ],
44167 attachEventBehavior: [ 'type', AttachEventBehavior ],
44168 boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
44169 rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
44170 createBehavior: [ 'type', CreateBehavior ],
44171 fixHoverBehavior: [ 'type', FixHoverBehavior ],
44172 createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
44173 createParticipantBehavior: [ 'type', CreateParticipantBehavior$1 ],
44174 dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
44175 dataStoreBehavior: [ 'type', DataStoreBehavior ],
44176 deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
44177 detachEventBehavior: [ 'type', DetachEventBehavior ],
44178 dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
44179 eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
44180 groupBehavior: [ 'type', GroupBehavior ],
44181 importDockingFix: [ 'type', ImportDockingFix ],
44182 isHorizontalFix: [ 'type', IsHorizontalFix ],
44183 labelBehavior: [ 'type', LabelBehavior ],
44184 messageFlowBehavior: [ 'type', MessageFlowBehavior ],
44185 modelingFeedback: [ 'type', ModelingFeedback ],
44186 replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
44187 removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
44188 replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
44189 resizeBehavior: [ 'type', ResizeBehavior$1 ],
44190 resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
44191 removeElementBehavior: [ 'type', RemoveElementBehavior ],
44192 toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
44193 spaceToolBehavior: [ 'type', SpaceToolBehavior$1 ],
44194 subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
44195 unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
44196 updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
44197 unsetDefaultFlowBehavior: [ 'type', DeleteSequenceFlowBehavior ]
44198 };
44199
44200 function getBoundaryAttachment(position, targetBounds) {
44201
44202 var orientation = getOrientation(position, targetBounds, -15);
44203
44204 if (orientation !== 'intersect') {
44205 return orientation;
44206 } else {
44207 return null;
44208 }
44209 }
44210
44211 /**
44212 * BPMN specific modeling rule
44213 */
44214 function BpmnRules(eventBus) {
44215 RuleProvider.call(this, eventBus);
44216 }
44217
44218 inherits_browser(BpmnRules, RuleProvider);
44219
44220 BpmnRules.$inject = [ 'eventBus' ];
44221
44222 BpmnRules.prototype.init = function() {
44223
44224 this.addRule('connection.start', function(context) {
44225 var source = context.source;
44226
44227 return canStartConnection(source);
44228 });
44229
44230 this.addRule('connection.create', function(context) {
44231 var source = context.source,
44232 target = context.target,
44233 hints = context.hints || {},
44234 targetParent = hints.targetParent,
44235 targetAttach = hints.targetAttach;
44236
44237 // don't allow incoming connections on
44238 // newly created boundary events
44239 // to boundary events
44240 if (targetAttach) {
44241 return false;
44242 }
44243
44244 // temporarily set target parent for scoping
44245 // checks to work
44246 if (targetParent) {
44247 target.parent = targetParent;
44248 }
44249
44250 try {
44251 return canConnect(source, target);
44252 } finally {
44253
44254 // unset temporary target parent
44255 if (targetParent) {
44256 target.parent = null;
44257 }
44258 }
44259 });
44260
44261 this.addRule('connection.reconnect', function(context) {
44262
44263 var connection = context.connection,
44264 source = context.source,
44265 target = context.target;
44266
44267 return canConnect(source, target, connection);
44268 });
44269
44270 this.addRule('connection.updateWaypoints', function(context) {
44271 return {
44272 type: context.connection.type
44273 };
44274 });
44275
44276 this.addRule('shape.resize', function(context) {
44277
44278 var shape = context.shape,
44279 newBounds = context.newBounds;
44280
44281 return canResize(shape, newBounds);
44282 });
44283
44284 this.addRule('elements.create', function(context) {
44285 var elements = context.elements,
44286 position = context.position,
44287 target = context.target;
44288
44289 if (isConnection$3(target) && !canInsert(elements, target)) {
44290 return false;
44291 }
44292
44293 return every(elements, function(element) {
44294 if (isConnection$3(element)) {
44295 return canConnect(element.source, element.target, element);
44296 }
44297
44298 if (element.host) {
44299 return canAttach(element, element.host, null, position);
44300 }
44301
44302 return canCreate(element, target, null);
44303 });
44304 });
44305
44306 this.addRule('elements.move', function(context) {
44307
44308 var target = context.target,
44309 shapes = context.shapes,
44310 position = context.position;
44311
44312 return canAttach(shapes, target, null, position) ||
44313 canReplace(shapes, target, position) ||
44314 canMove(shapes, target) ||
44315 canInsert(shapes, target);
44316 });
44317
44318 this.addRule('shape.create', function(context) {
44319 return canCreate(
44320 context.shape,
44321 context.target,
44322 context.source,
44323 context.position
44324 );
44325 });
44326
44327 this.addRule('shape.attach', function(context) {
44328
44329 return canAttach(
44330 context.shape,
44331 context.target,
44332 null,
44333 context.position
44334 );
44335 });
44336
44337 this.addRule('element.copy', function(context) {
44338 var element = context.element,
44339 elements = context.elements;
44340
44341 return canCopy(elements, element);
44342 });
44343 };
44344
44345 BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
44346
44347 BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
44348
44349 BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
44350
44351 BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
44352
44353 BpmnRules.prototype.canMove = canMove;
44354
44355 BpmnRules.prototype.canAttach = canAttach;
44356
44357 BpmnRules.prototype.canReplace = canReplace;
44358
44359 BpmnRules.prototype.canDrop = canDrop;
44360
44361 BpmnRules.prototype.canInsert = canInsert;
44362
44363 BpmnRules.prototype.canCreate = canCreate;
44364
44365 BpmnRules.prototype.canConnect = canConnect;
44366
44367 BpmnRules.prototype.canResize = canResize;
44368
44369 BpmnRules.prototype.canCopy = canCopy;
44370
44371 /**
44372 * Utility functions for rule checking
44373 */
44374
44375 /**
44376 * Checks if given element can be used for starting connection.
44377 *
44378 * @param {Element} source
44379 * @return {boolean}
44380 */
44381 function canStartConnection(element) {
44382 if (nonExistingOrLabel(element)) {
44383 return null;
44384 }
44385
44386 return isAny(element, [
44387 'bpmn:FlowNode',
44388 'bpmn:InteractionNode',
44389 'bpmn:DataObjectReference',
44390 'bpmn:DataStoreReference',
44391 'bpmn:Group',
44392 'bpmn:TextAnnotation'
44393 ]);
44394 }
44395
44396 function nonExistingOrLabel(element) {
44397 return !element || isLabel(element);
44398 }
44399
44400 function isSame(a, b) {
44401 return a === b;
44402 }
44403
44404 function getOrganizationalParent(element) {
44405
44406 do {
44407 if (is$1(element, 'bpmn:Process')) {
44408 return getBusinessObject(element);
44409 }
44410
44411 if (is$1(element, 'bpmn:Participant')) {
44412 return (
44413 getBusinessObject(element).processRef ||
44414 getBusinessObject(element)
44415 );
44416 }
44417 } while ((element = element.parent));
44418
44419 }
44420
44421 function isTextAnnotation(element) {
44422 return is$1(element, 'bpmn:TextAnnotation');
44423 }
44424
44425 function isGroup(element) {
44426 return is$1(element, 'bpmn:Group') && !element.labelTarget;
44427 }
44428
44429 function isCompensationBoundary(element) {
44430 return is$1(element, 'bpmn:BoundaryEvent') &&
44431 hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
44432 }
44433
44434 function isForCompensation(e) {
44435 return getBusinessObject(e).isForCompensation;
44436 }
44437
44438 function isSameOrganization(a, b) {
44439 var parentA = getOrganizationalParent(a),
44440 parentB = getOrganizationalParent(b);
44441
44442 return parentA === parentB;
44443 }
44444
44445 function isMessageFlowSource(element) {
44446 return (
44447 is$1(element, 'bpmn:InteractionNode') &&
44448 !is$1(element, 'bpmn:BoundaryEvent') && (
44449 !is$1(element, 'bpmn:Event') || (
44450 is$1(element, 'bpmn:ThrowEvent') &&
44451 hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
44452 )
44453 )
44454 );
44455 }
44456
44457 function isMessageFlowTarget(element) {
44458 return (
44459 is$1(element, 'bpmn:InteractionNode') &&
44460 !isForCompensation(element) && (
44461 !is$1(element, 'bpmn:Event') || (
44462 is$1(element, 'bpmn:CatchEvent') &&
44463 hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
44464 )
44465 ) && !(
44466 is$1(element, 'bpmn:BoundaryEvent') &&
44467 !hasEventDefinition$2(element, 'bpmn:MessageEventDefinition')
44468 )
44469 );
44470 }
44471
44472 function getScopeParent(element) {
44473
44474 var parent = element;
44475
44476 while ((parent = parent.parent)) {
44477
44478 if (is$1(parent, 'bpmn:FlowElementsContainer')) {
44479 return getBusinessObject(parent);
44480 }
44481
44482 if (is$1(parent, 'bpmn:Participant')) {
44483 return getBusinessObject(parent).processRef;
44484 }
44485 }
44486
44487 return null;
44488 }
44489
44490 function isSameScope(a, b) {
44491 var scopeParentA = getScopeParent(a),
44492 scopeParentB = getScopeParent(b);
44493
44494 return scopeParentA === scopeParentB;
44495 }
44496
44497 function hasEventDefinition$2(element, eventDefinition) {
44498 var bo = getBusinessObject(element);
44499
44500 return !!find(bo.eventDefinitions || [], function(definition) {
44501 return is$1(definition, eventDefinition);
44502 });
44503 }
44504
44505 function hasEventDefinitionOrNone(element, eventDefinition) {
44506 var bo = getBusinessObject(element);
44507
44508 return (bo.eventDefinitions || []).every(function(definition) {
44509 return is$1(definition, eventDefinition);
44510 });
44511 }
44512
44513 function isSequenceFlowSource(element) {
44514 return (
44515 is$1(element, 'bpmn:FlowNode') &&
44516 !is$1(element, 'bpmn:EndEvent') &&
44517 !isEventSubProcess(element) &&
44518 !(is$1(element, 'bpmn:IntermediateThrowEvent') &&
44519 hasEventDefinition$2(element, 'bpmn:LinkEventDefinition')
44520 ) &&
44521 !isCompensationBoundary(element) &&
44522 !isForCompensation(element)
44523 );
44524 }
44525
44526 function isSequenceFlowTarget(element) {
44527 return (
44528 is$1(element, 'bpmn:FlowNode') &&
44529 !is$1(element, 'bpmn:StartEvent') &&
44530 !is$1(element, 'bpmn:BoundaryEvent') &&
44531 !isEventSubProcess(element) &&
44532 !(is$1(element, 'bpmn:IntermediateCatchEvent') &&
44533 hasEventDefinition$2(element, 'bpmn:LinkEventDefinition')
44534 ) &&
44535 !isForCompensation(element)
44536 );
44537 }
44538
44539 function isEventBasedTarget(element) {
44540 return (
44541 is$1(element, 'bpmn:ReceiveTask') || (
44542 is$1(element, 'bpmn:IntermediateCatchEvent') && (
44543 hasEventDefinition$2(element, 'bpmn:MessageEventDefinition') ||
44544 hasEventDefinition$2(element, 'bpmn:TimerEventDefinition') ||
44545 hasEventDefinition$2(element, 'bpmn:ConditionalEventDefinition') ||
44546 hasEventDefinition$2(element, 'bpmn:SignalEventDefinition')
44547 )
44548 )
44549 );
44550 }
44551
44552 function isConnection$3(element) {
44553 return element.waypoints;
44554 }
44555
44556 function getParents$1(element) {
44557
44558 var parents = [];
44559
44560 while (element) {
44561 element = element.parent;
44562
44563 if (element) {
44564 parents.push(element);
44565 }
44566 }
44567
44568 return parents;
44569 }
44570
44571 function isParent(possibleParent, element) {
44572 var allParents = getParents$1(element);
44573 return allParents.indexOf(possibleParent) !== -1;
44574 }
44575
44576 function canConnect(source, target, connection) {
44577
44578 if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
44579 return null;
44580 }
44581
44582 if (!is$1(connection, 'bpmn:DataAssociation')) {
44583
44584 if (canConnectMessageFlow(source, target)) {
44585 return { type: 'bpmn:MessageFlow' };
44586 }
44587
44588 if (canConnectSequenceFlow(source, target)) {
44589 return { type: 'bpmn:SequenceFlow' };
44590 }
44591 }
44592
44593 var connectDataAssociation = canConnectDataAssociation(source, target);
44594
44595 if (connectDataAssociation) {
44596 return connectDataAssociation;
44597 }
44598
44599 if (isCompensationBoundary(source) && isForCompensation(target)) {
44600 return {
44601 type: 'bpmn:Association',
44602 associationDirection: 'One'
44603 };
44604 }
44605
44606 if (canConnectAssociation(source, target)) {
44607
44608 return {
44609 type: 'bpmn:Association'
44610 };
44611 }
44612
44613 return false;
44614 }
44615
44616 /**
44617 * Can an element be dropped into the target element
44618 *
44619 * @return {boolean}
44620 */
44621 function canDrop(element, target, position) {
44622
44623 // can move labels and groups everywhere
44624 if (isLabel(element) || isGroup(element)) {
44625 return true;
44626 }
44627
44628
44629 // disallow to create elements on collapsed pools
44630 if (is$1(target, 'bpmn:Participant') && !isExpanded(target)) {
44631 return false;
44632 }
44633
44634 // allow to create new participants on
44635 // existing collaboration and process diagrams
44636 if (is$1(element, 'bpmn:Participant')) {
44637 return is$1(target, 'bpmn:Process') || is$1(target, 'bpmn:Collaboration');
44638 }
44639
44640 // allow moving DataInput / DataOutput within its original container only
44641 if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
44642
44643 if (element.parent) {
44644 return target === element.parent;
44645 }
44646 }
44647
44648 // allow creating lanes on participants and other lanes only
44649 if (is$1(element, 'bpmn:Lane')) {
44650 return is$1(target, 'bpmn:Participant') || is$1(target, 'bpmn:Lane');
44651 }
44652
44653 // disallow dropping boundary events which cannot replace with intermediate event
44654 if (is$1(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
44655 return false;
44656 }
44657
44658 // drop flow elements onto flow element containers
44659 // and participants
44660 if (is$1(element, 'bpmn:FlowElement') && !is$1(element, 'bpmn:DataStoreReference')) {
44661 if (is$1(target, 'bpmn:FlowElementsContainer')) {
44662 return isExpanded(target);
44663 }
44664
44665 return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
44666 }
44667
44668 // disallow dropping data store reference if there is no process to append to
44669 if (is$1(element, 'bpmn:DataStoreReference') && is$1(target, 'bpmn:Collaboration')) {
44670 return some(getBusinessObject(target).get('participants'), function(participant) {
44671 return !!participant.get('processRef');
44672 });
44673 }
44674
44675 // account for the fact that data associations are always
44676 // rendered and moved to top (Process or Collaboration level)
44677 //
44678 // artifacts may be placed wherever, too
44679 if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
44680 return isAny(target, [
44681 'bpmn:Collaboration',
44682 'bpmn:Lane',
44683 'bpmn:Participant',
44684 'bpmn:Process',
44685 'bpmn:SubProcess' ]);
44686 }
44687
44688 if (is$1(element, 'bpmn:MessageFlow')) {
44689 return is$1(target, 'bpmn:Collaboration')
44690 || element.source.parent == target
44691 || element.target.parent == target;
44692 }
44693
44694 return false;
44695 }
44696
44697 function isDroppableBoundaryEvent(event) {
44698 return getBusinessObject(event).cancelActivity && (
44699 hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
44700 );
44701 }
44702
44703 function isBoundaryEvent(element) {
44704 return !isLabel(element) && is$1(element, 'bpmn:BoundaryEvent');
44705 }
44706
44707 function isLane(element) {
44708 return is$1(element, 'bpmn:Lane');
44709 }
44710
44711 /**
44712 * We treat IntermediateThrowEvents as boundary events during create,
44713 * this must be reflected in the rules.
44714 */
44715 function isBoundaryCandidate(element) {
44716 if (isBoundaryEvent(element)) {
44717 return true;
44718 }
44719
44720 if (is$1(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
44721 return true;
44722 }
44723
44724 return (
44725 is$1(element, 'bpmn:IntermediateCatchEvent') &&
44726 hasCommonBoundaryIntermediateEventDefinition(element)
44727 );
44728 }
44729
44730 function hasNoEventDefinition(element) {
44731 var bo = getBusinessObject(element);
44732
44733 return bo && !(bo.eventDefinitions && bo.eventDefinitions.length);
44734 }
44735
44736 function hasCommonBoundaryIntermediateEventDefinition(element) {
44737 return hasOneOfEventDefinitions(element, [
44738 'bpmn:MessageEventDefinition',
44739 'bpmn:TimerEventDefinition',
44740 'bpmn:SignalEventDefinition',
44741 'bpmn:ConditionalEventDefinition'
44742 ]);
44743 }
44744
44745 function hasOneOfEventDefinitions(element, eventDefinitions) {
44746 return eventDefinitions.some(function(definition) {
44747 return hasEventDefinition$2(element, definition);
44748 });
44749 }
44750
44751 function isReceiveTaskAfterEventBasedGateway(element) {
44752 return (
44753 is$1(element, 'bpmn:ReceiveTask') &&
44754 find(element.incoming, function(incoming) {
44755 return is$1(incoming.source, 'bpmn:EventBasedGateway');
44756 })
44757 );
44758 }
44759
44760
44761 function canAttach(elements, target, source, position) {
44762
44763 if (!Array.isArray(elements)) {
44764 elements = [ elements ];
44765 }
44766
44767 // only (re-)attach one element at a time
44768 if (elements.length !== 1) {
44769 return false;
44770 }
44771
44772 var element = elements[0];
44773
44774 // do not attach labels
44775 if (isLabel(element)) {
44776 return false;
44777 }
44778
44779 // only handle boundary events
44780 if (!isBoundaryCandidate(element)) {
44781 return false;
44782 }
44783
44784 // disallow drop on event sub processes
44785 if (isEventSubProcess(target)) {
44786 return false;
44787 }
44788
44789 // only allow drop on non compensation activities
44790 if (!is$1(target, 'bpmn:Activity') || isForCompensation(target)) {
44791 return false;
44792 }
44793
44794 // only attach to subprocess border
44795 if (position && !getBoundaryAttachment(position, target)) {
44796 return false;
44797 }
44798
44799 // do not attach on receive tasks after event based gateways
44800 if (isReceiveTaskAfterEventBasedGateway(target)) {
44801 return false;
44802 }
44803
44804 return 'attach';
44805 }
44806
44807
44808 /**
44809 * Defines how to replace elements for a given target.
44810 *
44811 * Returns an array containing all elements which will be replaced.
44812 *
44813 * @example
44814 *
44815 * [{ id: 'IntermediateEvent_2',
44816 * type: 'bpmn:StartEvent'
44817 * },
44818 * { id: 'IntermediateEvent_5',
44819 * type: 'bpmn:EndEvent'
44820 * }]
44821 *
44822 * @param {Array} elements
44823 * @param {Object} target
44824 *
44825 * @return {Object} an object containing all elements which have to be replaced
44826 */
44827 function canReplace(elements, target, position) {
44828
44829 if (!target) {
44830 return false;
44831 }
44832
44833 var canExecute = {
44834 replacements: []
44835 };
44836
44837 forEach(elements, function(element) {
44838
44839 if (!isEventSubProcess(target)) {
44840
44841 if (is$1(element, 'bpmn:StartEvent') &&
44842 element.type !== 'label' &&
44843 canDrop(element, target)) {
44844
44845 // replace a non-interrupting start event by a blank interrupting start event
44846 // when the target is not an event sub process
44847 if (!isInterrupting(element)) {
44848 canExecute.replacements.push({
44849 oldElementId: element.id,
44850 newElementType: 'bpmn:StartEvent'
44851 });
44852 }
44853
44854 // replace an error/escalation/compensate start event by a blank interrupting start event
44855 // when the target is not an event sub process
44856 if (hasErrorEventDefinition(element) ||
44857 hasEscalationEventDefinition(element) ||
44858 hasCompensateEventDefinition(element)) {
44859 canExecute.replacements.push({
44860 oldElementId: element.id,
44861 newElementType: 'bpmn:StartEvent'
44862 });
44863 }
44864
44865 // replace a typed start event by a blank interrupting start event
44866 // when the target is a sub process but not an event sub process
44867 if (hasOneOfEventDefinitions(element,
44868 [
44869 'bpmn:MessageEventDefinition',
44870 'bpmn:TimerEventDefinition',
44871 'bpmn:SignalEventDefinition',
44872 'bpmn:ConditionalEventDefinition'
44873 ]) &&
44874 is$1(target, 'bpmn:SubProcess')) {
44875 canExecute.replacements.push({
44876 oldElementId: element.id,
44877 newElementType: 'bpmn:StartEvent'
44878 });
44879 }
44880 }
44881 }
44882
44883 if (!is$1(target, 'bpmn:Transaction')) {
44884 if (hasEventDefinition$2(element, 'bpmn:CancelEventDefinition') &&
44885 element.type !== 'label') {
44886
44887 if (is$1(element, 'bpmn:EndEvent') && canDrop(element, target)) {
44888 canExecute.replacements.push({
44889 oldElementId: element.id,
44890 newElementType: 'bpmn:EndEvent'
44891 });
44892 }
44893
44894 if (is$1(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
44895 canExecute.replacements.push({
44896 oldElementId: element.id,
44897 newElementType: 'bpmn:BoundaryEvent'
44898 });
44899 }
44900 }
44901 }
44902 });
44903
44904 return canExecute.replacements.length ? canExecute : false;
44905 }
44906
44907 function canMove(elements, target) {
44908
44909 // do not move selection containing lanes
44910 if (some(elements, isLane)) {
44911 return false;
44912 }
44913
44914 // allow default move check to start move operation
44915 if (!target) {
44916 return true;
44917 }
44918
44919 return elements.every(function(element) {
44920 return canDrop(element, target);
44921 });
44922 }
44923
44924 function canCreate(shape, target, source, position) {
44925
44926 if (!target) {
44927 return false;
44928 }
44929
44930 if (isLabel(shape) || isGroup(shape)) {
44931 return true;
44932 }
44933
44934 if (isSame(source, target)) {
44935 return false;
44936 }
44937
44938 // ensure we do not drop the element
44939 // into source
44940 if (source && isParent(source, target)) {
44941 return false;
44942 }
44943
44944 return canDrop(shape, target) || canInsert(shape, target);
44945 }
44946
44947 function canResize(shape, newBounds) {
44948 if (is$1(shape, 'bpmn:SubProcess')) {
44949 return (
44950 isExpanded(shape) && (
44951 !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
44952 )
44953 );
44954 }
44955
44956 if (is$1(shape, 'bpmn:Lane')) {
44957 return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
44958 }
44959
44960 if (is$1(shape, 'bpmn:Participant')) {
44961 return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
44962 }
44963
44964 if (isTextAnnotation(shape)) {
44965 return true;
44966 }
44967
44968 if (isGroup(shape)) {
44969 return true;
44970 }
44971
44972 return false;
44973 }
44974
44975 /**
44976 * Check, whether one side of the relationship
44977 * is a text annotation.
44978 */
44979 function isOneTextAnnotation(source, target) {
44980
44981 var sourceTextAnnotation = isTextAnnotation(source),
44982 targetTextAnnotation = isTextAnnotation(target);
44983
44984 return (
44985 (sourceTextAnnotation || targetTextAnnotation) &&
44986 (sourceTextAnnotation !== targetTextAnnotation)
44987 );
44988 }
44989
44990
44991 function canConnectAssociation(source, target) {
44992
44993 // do not connect connections
44994 if (isConnection$3(source) || isConnection$3(target)) {
44995 return false;
44996 }
44997
44998 // compensation boundary events are exception
44999 if (isCompensationBoundary(source) && isForCompensation(target)) {
45000 return true;
45001 }
45002
45003 // don't connect parent <-> child
45004 if (isParent(target, source) || isParent(source, target)) {
45005 return false;
45006 }
45007
45008 // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
45009 if (isOneTextAnnotation(source, target)) {
45010 return true;
45011 }
45012
45013 // can connect associations where we can connect
45014 // data associations, too (!)
45015 return !!canConnectDataAssociation(source, target);
45016 }
45017
45018 function canConnectMessageFlow(source, target) {
45019
45020 // during connect user might move mouse out of canvas
45021 // https://github.com/bpmn-io/bpmn-js/issues/1033
45022 if (getRootElement(source) && !getRootElement(target)) {
45023 return false;
45024 }
45025
45026 return (
45027 isMessageFlowSource(source) &&
45028 isMessageFlowTarget(target) &&
45029 !isSameOrganization(source, target)
45030 );
45031 }
45032
45033 function canConnectSequenceFlow(source, target) {
45034
45035 if (
45036 isEventBasedTarget(target) &&
45037 target.incoming.length > 0 &&
45038 areOutgoingEventBasedGatewayConnections(target.incoming) &&
45039 !is$1(source, 'bpmn:EventBasedGateway')
45040 ) {
45041 return false;
45042 }
45043
45044 return isSequenceFlowSource(source) &&
45045 isSequenceFlowTarget(target) &&
45046 isSameScope(source, target) &&
45047 !(is$1(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
45048 }
45049
45050
45051 function canConnectDataAssociation(source, target) {
45052
45053 if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
45054 isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
45055 return { type: 'bpmn:DataInputAssociation' };
45056 }
45057
45058 if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
45059 isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
45060 return { type: 'bpmn:DataOutputAssociation' };
45061 }
45062
45063 return false;
45064 }
45065
45066 function canInsert(shape, flow, position) {
45067
45068 if (!flow) {
45069 return false;
45070 }
45071
45072 if (Array.isArray(shape)) {
45073 if (shape.length !== 1) {
45074 return false;
45075 }
45076
45077 shape = shape[0];
45078 }
45079
45080 if (flow.source === shape ||
45081 flow.target === shape) {
45082 return false;
45083 }
45084
45085 // return true if we can drop on the
45086 // underlying flow parent
45087 //
45088 // at this point we are not really able to talk
45089 // about connection rules (yet)
45090
45091 return (
45092 isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
45093 !isLabel(flow) &&
45094 is$1(shape, 'bpmn:FlowNode') &&
45095 !is$1(shape, 'bpmn:BoundaryEvent') &&
45096 canDrop(shape, flow.parent));
45097 }
45098
45099 function includes$1(elements, element) {
45100 return (elements && element) && elements.indexOf(element) !== -1;
45101 }
45102
45103 function canCopy(elements, element) {
45104 if (isLabel(element)) {
45105 return true;
45106 }
45107
45108 if (is$1(element, 'bpmn:Lane') && !includes$1(elements, element.parent)) {
45109 return false;
45110 }
45111
45112 return true;
45113 }
45114
45115 function isOutgoingEventBasedGatewayConnection(connection) {
45116
45117 if (connection && connection.source) {
45118 return is$1(connection.source, 'bpmn:EventBasedGateway');
45119 }
45120 }
45121
45122 function areOutgoingEventBasedGatewayConnections(connections) {
45123 connections = connections || [];
45124
45125 return connections.some(isOutgoingEventBasedGatewayConnection);
45126 }
45127
45128 function getRootElement(element) {
45129 return getParent$1(element, 'bpmn:Process') || getParent$1(element, 'bpmn:Collaboration');
45130 }
45131
45132 var RulesModule$1 = {
45133 __depends__: [
45134 RulesModule
45135 ],
45136 __init__: [ 'bpmnRules' ],
45137 bpmnRules: [ 'type', BpmnRules ]
45138 };
45139
45140 var HIGH_PRIORITY$d = 2000;
45141
45142 function BpmnDiOrdering(eventBus, canvas) {
45143
45144 eventBus.on('saveXML.start', HIGH_PRIORITY$d, orderDi);
45145
45146 function orderDi() {
45147 var root = canvas.getRootElement(),
45148 rootDi = getBusinessObject(root).di,
45149 elements,
45150 diElements;
45151
45152 elements = selfAndAllChildren([ root ], false);
45153
45154 // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane
45155 elements = filter(elements, function(element) {
45156 return element !== root && !element.labelTarget;
45157 });
45158
45159 diElements = map(elements, getDi);
45160
45161 rootDi.set('planeElement', diElements);
45162 }
45163 }
45164
45165 BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ];
45166
45167 var DiOrderingModule = {
45168 __init__: [
45169 'bpmnDiOrdering'
45170 ],
45171 bpmnDiOrdering: [ 'type', BpmnDiOrdering ]
45172 };
45173
45174 /**
45175 * An abstract provider that allows modelers to implement a custom
45176 * ordering of diagram elements on the canvas.
45177 *
45178 * It makes sure that the order is always preserved during element
45179 * creation and move operations.
45180 *
45181 * In order to use this behavior, inherit from it and override
45182 * the method {@link OrderingProvider#getOrdering}.
45183 *
45184 * @example
45185 *
45186 * ```javascript
45187 * function CustomOrderingProvider(eventBus) {
45188 * OrderingProvider.call(this, eventBus);
45189 *
45190 * this.getOrdering = function(element, newParent) {
45191 * // always insert elements at the front
45192 * // when moving
45193 * return {
45194 * index: 0,
45195 * parent: newParent
45196 * };
45197 * };
45198 * }
45199 * ```
45200 *
45201 * @param {EventBus} eventBus
45202 */
45203 function OrderingProvider(eventBus) {
45204
45205 CommandInterceptor.call(this, eventBus);
45206
45207
45208 var self = this;
45209
45210 this.preExecute([ 'shape.create', 'connection.create' ], function(event) {
45211
45212 var context = event.context,
45213 element = context.shape || context.connection,
45214 parent = context.parent;
45215
45216 var ordering = self.getOrdering(element, parent);
45217
45218 if (ordering) {
45219
45220 if (ordering.parent !== undefined) {
45221 context.parent = ordering.parent;
45222 }
45223
45224 context.parentIndex = ordering.index;
45225 }
45226 });
45227
45228 this.preExecute([ 'shape.move', 'connection.move' ], function(event) {
45229
45230 var context = event.context,
45231 element = context.shape || context.connection,
45232 parent = context.newParent || element.parent;
45233
45234 var ordering = self.getOrdering(element, parent);
45235
45236 if (ordering) {
45237
45238 if (ordering.parent !== undefined) {
45239 context.newParent = ordering.parent;
45240 }
45241
45242 context.newParentIndex = ordering.index;
45243 }
45244 });
45245 }
45246
45247 /**
45248 * Return a custom ordering of the element, both in terms
45249 * of parent element and index in the new parent.
45250 *
45251 * Implementors of this method must return an object with
45252 * `parent` _and_ `index` in it.
45253 *
45254 * @param {djs.model.Base} element
45255 * @param {djs.model.Shape} newParent
45256 *
45257 * @return {Object} ordering descriptor
45258 */
45259 OrderingProvider.prototype.getOrdering = function(element, newParent) {
45260 return null;
45261 };
45262
45263 inherits_browser(OrderingProvider, CommandInterceptor);
45264
45265 /**
45266 * a simple ordering provider that makes sure:
45267 *
45268 * (0) labels and groups are rendered always on top
45269 * (1) elements are ordered by a {level} property
45270 */
45271 function BpmnOrderingProvider(eventBus, canvas, translate) {
45272
45273 OrderingProvider.call(this, eventBus);
45274
45275 var orders = [
45276 { type: 'bpmn:SubProcess', order: { level: 6 } },
45277 {
45278 type: 'bpmn:SequenceFlow',
45279 order: {
45280 level: 3,
45281 containers: [
45282 'bpmn:Participant',
45283 'bpmn:FlowElementsContainer'
45284 ]
45285 }
45286 },
45287
45288 // handle DataAssociation(s) like message flows and render them always on top
45289 {
45290 type: 'bpmn:DataAssociation',
45291 order: {
45292 level: 9,
45293 containers: [
45294 'bpmn:Collaboration',
45295 'bpmn:Process'
45296 ]
45297 }
45298 },
45299 {
45300 type: 'bpmn:MessageFlow', order: {
45301 level: 9,
45302 containers: [ 'bpmn:Collaboration' ]
45303 }
45304 },
45305 {
45306 type: 'bpmn:Association',
45307 order: {
45308 level: 6,
45309 containers: [
45310 'bpmn:Participant',
45311 'bpmn:FlowElementsContainer',
45312 'bpmn:Collaboration'
45313 ]
45314 }
45315 },
45316 { type: 'bpmn:BoundaryEvent', order: { level: 8 } },
45317 {
45318 type: 'bpmn:Group',
45319 order: {
45320 level: 10,
45321 containers: [
45322 'bpmn:Collaboration',
45323 'bpmn:Process'
45324 ]
45325 }
45326 },
45327 { type: 'bpmn:FlowElement', order: { level: 5 } },
45328 { type: 'bpmn:Participant', order: { level: -2 } },
45329 { type: 'bpmn:Lane', order: { level: -1 } }
45330 ];
45331
45332 function computeOrder(element) {
45333 if (element.labelTarget) {
45334 return { level: 10 };
45335 }
45336
45337 var entry = find(orders, function(o) {
45338 return isAny(element, [ o.type ]);
45339 });
45340
45341 return entry && entry.order || { level: 1 };
45342 }
45343
45344 function getOrder(element) {
45345
45346 var order = element.order;
45347
45348 if (!order) {
45349 element.order = order = computeOrder(element);
45350 }
45351
45352 return order;
45353 }
45354
45355 function findActualParent(element, newParent, containers) {
45356
45357 var actualParent = newParent;
45358
45359 while (actualParent) {
45360
45361 if (isAny(actualParent, containers)) {
45362 break;
45363 }
45364
45365 actualParent = actualParent.parent;
45366 }
45367
45368 if (!actualParent) {
45369 throw new Error(translate('no parent for {element} in {parent}', {
45370 element: element.id,
45371 parent: newParent.id
45372 }));
45373 }
45374
45375 return actualParent;
45376 }
45377
45378 this.getOrdering = function(element, newParent) {
45379
45380 // render labels always on top
45381 if (element.labelTarget) {
45382 return {
45383 parent: canvas.getRootElement(),
45384 index: -1
45385 };
45386 }
45387
45388 var elementOrder = getOrder(element);
45389
45390
45391 if (elementOrder.containers) {
45392 newParent = findActualParent(element, newParent, elementOrder.containers);
45393 }
45394
45395
45396 var currentIndex = newParent.children.indexOf(element);
45397
45398 var insertIndex = findIndex(newParent.children, function(child) {
45399
45400 // do not compare with labels, they are created
45401 // in the wrong order (right after elements) during import and
45402 // mess up the positioning.
45403 if (!element.labelTarget && child.labelTarget) {
45404 return false;
45405 }
45406
45407 return elementOrder.level < getOrder(child).level;
45408 });
45409
45410
45411 // if the element is already in the child list at
45412 // a smaller index, we need to adjust the insert index.
45413 // this takes into account that the element is being removed
45414 // before being re-inserted
45415 if (insertIndex !== -1) {
45416 if (currentIndex !== -1 && currentIndex < insertIndex) {
45417 insertIndex -= 1;
45418 }
45419 }
45420
45421 return {
45422 index: insertIndex,
45423 parent: newParent
45424 };
45425 };
45426 }
45427
45428 BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ];
45429
45430 inherits_browser(BpmnOrderingProvider, OrderingProvider);
45431
45432 var OrderingModule = {
45433 __depends__: [
45434 translate$2
45435 ],
45436 __init__: [ 'bpmnOrderingProvider' ],
45437 bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
45438 };
45439
45440 /**
45441 * A service that offers un- and redoable execution of commands.
45442 *
45443 * The command stack is responsible for executing modeling actions
45444 * in a un- and redoable manner. To do this it delegates the actual
45445 * command execution to {@link CommandHandler}s.
45446 *
45447 * Command handlers provide {@link CommandHandler#execute(ctx)} and
45448 * {@link CommandHandler#revert(ctx)} methods to un- and redo a command
45449 * identified by a command context.
45450 *
45451 *
45452 * ## Life-Cycle events
45453 *
45454 * In the process the command stack fires a number of life-cycle events
45455 * that other components to participate in the command execution.
45456 *
45457 * * preExecute
45458 * * preExecuted
45459 * * execute
45460 * * executed
45461 * * postExecute
45462 * * postExecuted
45463 * * revert
45464 * * reverted
45465 *
45466 * A special event is used for validating, whether a command can be
45467 * performed prior to its execution.
45468 *
45469 * * canExecute
45470 *
45471 * Each of the events is fired as `commandStack.{eventName}` and
45472 * `commandStack.{commandName}.{eventName}`, respectively. This gives
45473 * components fine grained control on where to hook into.
45474 *
45475 * The event object fired transports `command`, the name of the
45476 * command and `context`, the command context.
45477 *
45478 *
45479 * ## Creating Command Handlers
45480 *
45481 * Command handlers should provide the {@link CommandHandler#execute(ctx)}
45482 * and {@link CommandHandler#revert(ctx)} methods to implement
45483 * redoing and undoing of a command.
45484 *
45485 * A command handler _must_ ensure undo is performed properly in order
45486 * not to break the undo chain. It must also return the shapes that
45487 * got changed during the `execute` and `revert` operations.
45488 *
45489 * Command handlers may execute other modeling operations (and thus
45490 * commands) in their `preExecute` and `postExecute` phases. The command
45491 * stack will properly group all commands together into a logical unit
45492 * that may be re- and undone atomically.
45493 *
45494 * Command handlers must not execute other commands from within their
45495 * core implementation (`execute`, `revert`).
45496 *
45497 *
45498 * ## Change Tracking
45499 *
45500 * During the execution of the CommandStack it will keep track of all
45501 * elements that have been touched during the command's execution.
45502 *
45503 * At the end of the CommandStack execution it will notify interested
45504 * components via an 'elements.changed' event with all the dirty
45505 * elements.
45506 *
45507 * The event can be picked up by components that are interested in the fact
45508 * that elements have been changed. One use case for this is updating
45509 * their graphical representation after moving / resizing or deletion.
45510 *
45511 * @see CommandHandler
45512 *
45513 * @param {EventBus} eventBus
45514 * @param {Injector} injector
45515 */
45516 function CommandStack(eventBus, injector) {
45517
45518 /**
45519 * A map of all registered command handlers.
45520 *
45521 * @type {Object}
45522 */
45523 this._handlerMap = {};
45524
45525 /**
45526 * A stack containing all re/undoable actions on the diagram
45527 *
45528 * @type {Array<Object>}
45529 */
45530 this._stack = [];
45531
45532 /**
45533 * The current index on the stack
45534 *
45535 * @type {number}
45536 */
45537 this._stackIdx = -1;
45538
45539 /**
45540 * Current active commandStack execution
45541 *
45542 * @type {Object}
45543 * @property {Object[]} actions
45544 * @property {Object[]} dirty
45545 * @property { 'undo' | 'redo' | 'clear' | 'execute' | null } trigger the cause of the current excecution
45546 */
45547 this._currentExecution = {
45548 actions: [],
45549 dirty: [],
45550 trigger: null
45551 };
45552
45553
45554 this._injector = injector;
45555 this._eventBus = eventBus;
45556
45557 this._uid = 1;
45558
45559 eventBus.on([
45560 'diagram.destroy',
45561 'diagram.clear'
45562 ], function() {
45563 this.clear(false);
45564 }, this);
45565 }
45566
45567 CommandStack.$inject = [ 'eventBus', 'injector' ];
45568
45569
45570 /**
45571 * Execute a command
45572 *
45573 * @param {string} command the command to execute
45574 * @param {Object} context the environment to execute the command in
45575 */
45576 CommandStack.prototype.execute = function(command, context) {
45577 if (!command) {
45578 throw new Error('command required');
45579 }
45580
45581 this._currentExecution.trigger = 'execute';
45582
45583 var action = { command: command, context: context };
45584
45585 this._pushAction(action);
45586 this._internalExecute(action);
45587 this._popAction(action);
45588 };
45589
45590
45591 /**
45592 * Ask whether a given command can be executed.
45593 *
45594 * Implementors may hook into the mechanism on two ways:
45595 *
45596 * * in event listeners:
45597 *
45598 * Users may prevent the execution via an event listener.
45599 * It must prevent the default action for `commandStack.(<command>.)canExecute` events.
45600 *
45601 * * in command handlers:
45602 *
45603 * If the method {@link CommandHandler#canExecute} is implemented in a handler
45604 * it will be called to figure out whether the execution is allowed.
45605 *
45606 * @param {string} command the command to execute
45607 * @param {Object} context the environment to execute the command in
45608 *
45609 * @return {boolean} true if the command can be executed
45610 */
45611 CommandStack.prototype.canExecute = function(command, context) {
45612
45613 var action = { command: command, context: context };
45614
45615 var handler = this._getHandler(command);
45616
45617 var result = this._fire(command, 'canExecute', action);
45618
45619 // handler#canExecute will only be called if no listener
45620 // decided on a result already
45621 if (result === undefined) {
45622 if (!handler) {
45623 return false;
45624 }
45625
45626 if (handler.canExecute) {
45627 result = handler.canExecute(context);
45628 }
45629 }
45630
45631 return result;
45632 };
45633
45634
45635 /**
45636 * Clear the command stack, erasing all undo / redo history
45637 */
45638 CommandStack.prototype.clear = function(emit) {
45639 this._stack.length = 0;
45640 this._stackIdx = -1;
45641
45642 if (emit !== false) {
45643 this._fire('changed', { trigger: 'clear' });
45644 }
45645 };
45646
45647
45648 /**
45649 * Undo last command(s)
45650 */
45651 CommandStack.prototype.undo = function() {
45652 var action = this._getUndoAction(),
45653 next;
45654
45655 if (action) {
45656 this._currentExecution.trigger = 'undo';
45657
45658 this._pushAction(action);
45659
45660 while (action) {
45661 this._internalUndo(action);
45662 next = this._getUndoAction();
45663
45664 if (!next || next.id !== action.id) {
45665 break;
45666 }
45667
45668 action = next;
45669 }
45670
45671 this._popAction();
45672 }
45673 };
45674
45675
45676 /**
45677 * Redo last command(s)
45678 */
45679 CommandStack.prototype.redo = function() {
45680 var action = this._getRedoAction(),
45681 next;
45682
45683 if (action) {
45684 this._currentExecution.trigger = 'redo';
45685
45686 this._pushAction(action);
45687
45688 while (action) {
45689 this._internalExecute(action, true);
45690 next = this._getRedoAction();
45691
45692 if (!next || next.id !== action.id) {
45693 break;
45694 }
45695
45696 action = next;
45697 }
45698
45699 this._popAction();
45700 }
45701 };
45702
45703
45704 /**
45705 * Register a handler instance with the command stack
45706 *
45707 * @param {string} command
45708 * @param {CommandHandler} handler
45709 */
45710 CommandStack.prototype.register = function(command, handler) {
45711 this._setHandler(command, handler);
45712 };
45713
45714
45715 /**
45716 * Register a handler type with the command stack
45717 * by instantiating it and injecting its dependencies.
45718 *
45719 * @param {string} command
45720 * @param {Function} a constructor for a {@link CommandHandler}
45721 */
45722 CommandStack.prototype.registerHandler = function(command, handlerCls) {
45723
45724 if (!command || !handlerCls) {
45725 throw new Error('command and handlerCls must be defined');
45726 }
45727
45728 var handler = this._injector.instantiate(handlerCls);
45729 this.register(command, handler);
45730 };
45731
45732 CommandStack.prototype.canUndo = function() {
45733 return !!this._getUndoAction();
45734 };
45735
45736 CommandStack.prototype.canRedo = function() {
45737 return !!this._getRedoAction();
45738 };
45739
45740 // stack access //////////////////////
45741
45742 CommandStack.prototype._getRedoAction = function() {
45743 return this._stack[this._stackIdx + 1];
45744 };
45745
45746
45747 CommandStack.prototype._getUndoAction = function() {
45748 return this._stack[this._stackIdx];
45749 };
45750
45751
45752 // internal functionality //////////////////////
45753
45754 CommandStack.prototype._internalUndo = function(action) {
45755 var self = this;
45756
45757 var command = action.command,
45758 context = action.context;
45759
45760 var handler = this._getHandler(command);
45761
45762 // guard against illegal nested command stack invocations
45763 this._atomicDo(function() {
45764 self._fire(command, 'revert', action);
45765
45766 if (handler.revert) {
45767 self._markDirty(handler.revert(context));
45768 }
45769
45770 self._revertedAction(action);
45771
45772 self._fire(command, 'reverted', action);
45773 });
45774 };
45775
45776
45777 CommandStack.prototype._fire = function(command, qualifier, event) {
45778 if (arguments.length < 3) {
45779 event = qualifier;
45780 qualifier = null;
45781 }
45782
45783 var names = qualifier ? [ command + '.' + qualifier, qualifier ] : [ command ],
45784 i, name, result;
45785
45786 event = this._eventBus.createEvent(event);
45787
45788 for (i = 0; (name = names[i]); i++) {
45789 result = this._eventBus.fire('commandStack.' + name, event);
45790
45791 if (event.cancelBubble) {
45792 break;
45793 }
45794 }
45795
45796 return result;
45797 };
45798
45799 CommandStack.prototype._createId = function() {
45800 return this._uid++;
45801 };
45802
45803 CommandStack.prototype._atomicDo = function(fn) {
45804
45805 var execution = this._currentExecution;
45806
45807 execution.atomic = true;
45808
45809 try {
45810 fn();
45811 } finally {
45812 execution.atomic = false;
45813 }
45814 };
45815
45816 CommandStack.prototype._internalExecute = function(action, redo) {
45817 var self = this;
45818
45819 var command = action.command,
45820 context = action.context;
45821
45822 var handler = this._getHandler(command);
45823
45824 if (!handler) {
45825 throw new Error('no command handler registered for <' + command + '>');
45826 }
45827
45828 this._pushAction(action);
45829
45830 if (!redo) {
45831 this._fire(command, 'preExecute', action);
45832
45833 if (handler.preExecute) {
45834 handler.preExecute(context);
45835 }
45836
45837 this._fire(command, 'preExecuted', action);
45838 }
45839
45840 // guard against illegal nested command stack invocations
45841 this._atomicDo(function() {
45842
45843 self._fire(command, 'execute', action);
45844
45845 if (handler.execute) {
45846
45847 // actual execute + mark return results as dirty
45848 self._markDirty(handler.execute(context));
45849 }
45850
45851 // log to stack
45852 self._executedAction(action, redo);
45853
45854 self._fire(command, 'executed', action);
45855 });
45856
45857 if (!redo) {
45858 this._fire(command, 'postExecute', action);
45859
45860 if (handler.postExecute) {
45861 handler.postExecute(context);
45862 }
45863
45864 this._fire(command, 'postExecuted', action);
45865 }
45866
45867 this._popAction(action);
45868 };
45869
45870
45871 CommandStack.prototype._pushAction = function(action) {
45872
45873 var execution = this._currentExecution,
45874 actions = execution.actions;
45875
45876 var baseAction = actions[0];
45877
45878 if (execution.atomic) {
45879 throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')');
45880 }
45881
45882 if (!action.id) {
45883 action.id = (baseAction && baseAction.id) || this._createId();
45884 }
45885
45886 actions.push(action);
45887 };
45888
45889
45890 CommandStack.prototype._popAction = function() {
45891 var execution = this._currentExecution,
45892 trigger = execution.trigger,
45893 actions = execution.actions,
45894 dirty = execution.dirty;
45895
45896 actions.pop();
45897
45898 if (!actions.length) {
45899 this._eventBus.fire('elements.changed', { elements: uniqueBy('id', dirty.reverse()) });
45900
45901 dirty.length = 0;
45902
45903 this._fire('changed', { trigger: trigger });
45904
45905 execution.trigger = null;
45906 }
45907 };
45908
45909
45910 CommandStack.prototype._markDirty = function(elements) {
45911 var execution = this._currentExecution;
45912
45913 if (!elements) {
45914 return;
45915 }
45916
45917 elements = isArray(elements) ? elements : [ elements ];
45918
45919 execution.dirty = execution.dirty.concat(elements);
45920 };
45921
45922
45923 CommandStack.prototype._executedAction = function(action, redo) {
45924 var stackIdx = ++this._stackIdx;
45925
45926 if (!redo) {
45927 this._stack.splice(stackIdx, this._stack.length, action);
45928 }
45929 };
45930
45931
45932 CommandStack.prototype._revertedAction = function(action) {
45933 this._stackIdx--;
45934 };
45935
45936
45937 CommandStack.prototype._getHandler = function(command) {
45938 return this._handlerMap[command];
45939 };
45940
45941 CommandStack.prototype._setHandler = function(command, handler) {
45942 if (!command || !handler) {
45943 throw new Error('command and handler required');
45944 }
45945
45946 if (this._handlerMap[command]) {
45947 throw new Error('overriding handler for command <' + command + '>');
45948 }
45949
45950 this._handlerMap[command] = handler;
45951 };
45952
45953 var CommandModule = {
45954 commandStack: [ 'type', CommandStack ]
45955 };
45956
45957 // document wide unique tooltip ids
45958 var ids$1 = new IdGenerator('tt');
45959
45960
45961 function createRoot$1(parentNode) {
45962 var root = domify(
45963 '<div class="djs-tooltip-container" style="position: absolute; width: 0; height: 0;" />'
45964 );
45965
45966 parentNode.insertBefore(root, parentNode.firstChild);
45967
45968 return root;
45969 }
45970
45971
45972 function setPosition$1(el, x, y) {
45973 assign(el.style, { left: x + 'px', top: y + 'px' });
45974 }
45975
45976 function setVisible$1(el, visible) {
45977 el.style.display = visible === false ? 'none' : '';
45978 }
45979
45980
45981 var tooltipClass = 'djs-tooltip',
45982 tooltipSelector = '.' + tooltipClass;
45983
45984 /**
45985 * A service that allows users to render tool tips on the diagram.
45986 *
45987 * The tooltip service will take care of updating the tooltip positioning
45988 * during navigation + zooming.
45989 *
45990 * @example
45991 *
45992 * ```javascript
45993 *
45994 * // add a pink badge on the top left of the shape
45995 * tooltips.add({
45996 * position: {
45997 * x: 50,
45998 * y: 100
45999 * },
46000 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
46001 * });
46002 *
46003 * // or with optional life span
46004 * tooltips.add({
46005 * position: {
46006 * top: -5,
46007 * left: -5
46008 * },
46009 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>',
46010 * ttl: 2000
46011 * });
46012 *
46013 * // remove a tool tip
46014 * var id = tooltips.add(...);
46015 * tooltips.remove(id);
46016 * ```
46017 *
46018 * @param {EventBus} eventBus
46019 * @param {Canvas} canvas
46020 */
46021 function Tooltips(eventBus, canvas) {
46022
46023 this._eventBus = eventBus;
46024 this._canvas = canvas;
46025
46026 this._ids = ids$1;
46027
46028 this._tooltipDefaults = {
46029 show: {
46030 minZoom: 0.7,
46031 maxZoom: 5.0
46032 }
46033 };
46034
46035 /**
46036 * Mapping tooltipId -> tooltip
46037 */
46038 this._tooltips = {};
46039
46040 // root html element for all tooltips
46041 this._tooltipRoot = createRoot$1(canvas.getContainer());
46042
46043
46044 var self = this;
46045
46046 delegate.bind(this._tooltipRoot, tooltipSelector, 'mousedown', function(event) {
46047 event.stopPropagation();
46048 });
46049
46050 delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseover', function(event) {
46051 self.trigger('mouseover', event);
46052 });
46053
46054 delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseout', function(event) {
46055 self.trigger('mouseout', event);
46056 });
46057
46058 this._init();
46059 }
46060
46061
46062 Tooltips.$inject = [ 'eventBus', 'canvas' ];
46063
46064
46065 /**
46066 * Adds a HTML tooltip to the diagram
46067 *
46068 * @param {Object} tooltip the tooltip configuration
46069 *
46070 * @param {string|DOMElement} tooltip.html html element to use as an tooltip
46071 * @param {Object} [tooltip.show] show configuration
46072 * @param {number} [tooltip.show.minZoom] minimal zoom level to show the tooltip
46073 * @param {number} [tooltip.show.maxZoom] maximum zoom level to show the tooltip
46074 * @param {Object} tooltip.position where to attach the tooltip
46075 * @param {number} [tooltip.position.left] relative to element bbox left attachment
46076 * @param {number} [tooltip.position.top] relative to element bbox top attachment
46077 * @param {number} [tooltip.position.bottom] relative to element bbox bottom attachment
46078 * @param {number} [tooltip.position.right] relative to element bbox right attachment
46079 * @param {number} [tooltip.timeout=-1]
46080 *
46081 * @return {string} id that may be used to reference the tooltip for update or removal
46082 */
46083 Tooltips.prototype.add = function(tooltip) {
46084
46085 if (!tooltip.position) {
46086 throw new Error('must specifiy tooltip position');
46087 }
46088
46089 if (!tooltip.html) {
46090 throw new Error('must specifiy tooltip html');
46091 }
46092
46093 var id = this._ids.next();
46094
46095 tooltip = assign({}, this._tooltipDefaults, tooltip, {
46096 id: id
46097 });
46098
46099 this._addTooltip(tooltip);
46100
46101 if (tooltip.timeout) {
46102 this.setTimeout(tooltip);
46103 }
46104
46105 return id;
46106 };
46107
46108 Tooltips.prototype.trigger = function(action, event) {
46109
46110 var node = event.delegateTarget || event.target;
46111
46112 var tooltip = this.get(attr(node, 'data-tooltip-id'));
46113
46114 if (!tooltip) {
46115 return;
46116 }
46117
46118 if (action === 'mouseover' && tooltip.timeout) {
46119 this.clearTimeout(tooltip);
46120 }
46121
46122 if (action === 'mouseout' && tooltip.timeout) {
46123
46124 // cut timeout after mouse out
46125 tooltip.timeout = 1000;
46126
46127 this.setTimeout(tooltip);
46128 }
46129 };
46130
46131 /**
46132 * Get a tooltip with the given id
46133 *
46134 * @param {string} id
46135 */
46136 Tooltips.prototype.get = function(id) {
46137
46138 if (typeof id !== 'string') {
46139 id = id.id;
46140 }
46141
46142 return this._tooltips[id];
46143 };
46144
46145 Tooltips.prototype.clearTimeout = function(tooltip) {
46146
46147 tooltip = this.get(tooltip);
46148
46149 if (!tooltip) {
46150 return;
46151 }
46152
46153 var removeTimer = tooltip.removeTimer;
46154
46155 if (removeTimer) {
46156 clearTimeout(removeTimer);
46157 tooltip.removeTimer = null;
46158 }
46159 };
46160
46161 Tooltips.prototype.setTimeout = function(tooltip) {
46162
46163 tooltip = this.get(tooltip);
46164
46165 if (!tooltip) {
46166 return;
46167 }
46168
46169 this.clearTimeout(tooltip);
46170
46171 var self = this;
46172
46173 tooltip.removeTimer = setTimeout(function() {
46174 self.remove(tooltip);
46175 }, tooltip.timeout);
46176 };
46177
46178 /**
46179 * Remove an tooltip with the given id
46180 *
46181 * @param {string} id
46182 */
46183 Tooltips.prototype.remove = function(id) {
46184
46185 var tooltip = this.get(id);
46186
46187 if (tooltip) {
46188 remove(tooltip.html);
46189 remove(tooltip.htmlContainer);
46190
46191 delete tooltip.htmlContainer;
46192
46193 delete this._tooltips[tooltip.id];
46194 }
46195 };
46196
46197
46198 Tooltips.prototype.show = function() {
46199 setVisible$1(this._tooltipRoot);
46200 };
46201
46202
46203 Tooltips.prototype.hide = function() {
46204 setVisible$1(this._tooltipRoot, false);
46205 };
46206
46207
46208 Tooltips.prototype._updateRoot = function(viewbox) {
46209 var a = viewbox.scale || 1;
46210 var d = viewbox.scale || 1;
46211
46212 var matrix = 'matrix(' + a + ',0,0,' + d + ',' + (-1 * viewbox.x * a) + ',' + (-1 * viewbox.y * d) + ')';
46213
46214 this._tooltipRoot.style.transform = matrix;
46215 this._tooltipRoot.style['-ms-transform'] = matrix;
46216 };
46217
46218
46219 Tooltips.prototype._addTooltip = function(tooltip) {
46220
46221 var id = tooltip.id,
46222 html = tooltip.html,
46223 htmlContainer,
46224 tooltipRoot = this._tooltipRoot;
46225
46226 // unwrap jquery (for those who need it)
46227 if (html.get && html.constructor.prototype.jquery) {
46228 html = html.get(0);
46229 }
46230
46231 // create proper html elements from
46232 // tooltip HTML strings
46233 if (isString(html)) {
46234 html = domify(html);
46235 }
46236
46237 htmlContainer = domify('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '" style="position: absolute">');
46238
46239 htmlContainer.appendChild(html);
46240
46241 if (tooltip.type) {
46242 classes(htmlContainer).add('djs-tooltip-' + tooltip.type);
46243 }
46244
46245 if (tooltip.className) {
46246 classes(htmlContainer).add(tooltip.className);
46247 }
46248
46249 tooltip.htmlContainer = htmlContainer;
46250
46251 tooltipRoot.appendChild(htmlContainer);
46252
46253 this._tooltips[id] = tooltip;
46254
46255 this._updateTooltip(tooltip);
46256 };
46257
46258
46259 Tooltips.prototype._updateTooltip = function(tooltip) {
46260
46261 var position = tooltip.position,
46262 htmlContainer = tooltip.htmlContainer;
46263
46264 // update overlay html based on tooltip x, y
46265
46266 setPosition$1(htmlContainer, position.x, position.y);
46267 };
46268
46269
46270 Tooltips.prototype._updateTooltipVisibilty = function(viewbox) {
46271
46272 forEach(this._tooltips, function(tooltip) {
46273 var show = tooltip.show,
46274 htmlContainer = tooltip.htmlContainer,
46275 visible = true;
46276
46277 if (show) {
46278 if (show.minZoom > viewbox.scale ||
46279 show.maxZoom < viewbox.scale) {
46280 visible = false;
46281 }
46282
46283 setVisible$1(htmlContainer, visible);
46284 }
46285 });
46286 };
46287
46288 Tooltips.prototype._init = function() {
46289
46290 var self = this;
46291
46292 // scroll/zoom integration
46293
46294 function updateViewbox(viewbox) {
46295 self._updateRoot(viewbox);
46296 self._updateTooltipVisibilty(viewbox);
46297
46298 self.show();
46299 }
46300
46301 this._eventBus.on('canvas.viewbox.changing', function(event) {
46302 self.hide();
46303 });
46304
46305 this._eventBus.on('canvas.viewbox.changed', function(event) {
46306 updateViewbox(event.viewbox);
46307 });
46308 };
46309
46310 var TooltipsModule = {
46311 __init__: [ 'tooltips' ],
46312 tooltips: [ 'type', Tooltips ]
46313 };
46314
46315 /**
46316 * Remove from the beginning of a collection until it is empty.
46317 *
46318 * This is a null-safe operation that ensures elements
46319 * are being removed from the given collection until the
46320 * collection is empty.
46321 *
46322 * The implementation deals with the fact that a remove operation
46323 * may touch, i.e. remove multiple elements in the collection
46324 * at a time.
46325 *
46326 * @param {Array<Object>} [collection]
46327 * @param {Function} removeFn
46328 *
46329 * @return {Array<Object>} the cleared collection
46330 */
46331 function saveClear(collection, removeFn) {
46332
46333 if (typeof removeFn !== 'function') {
46334 throw new Error('removeFn iterator must be a function');
46335 }
46336
46337 if (!collection) {
46338 return;
46339 }
46340
46341 var e;
46342
46343 while ((e = collection[0])) {
46344 removeFn(e);
46345 }
46346
46347 return collection;
46348 }
46349
46350 var LOW_PRIORITY$g = 250,
46351 HIGH_PRIORITY$e = 1400;
46352
46353
46354 /**
46355 * A handler that makes sure labels are properly moved with
46356 * their label targets.
46357 *
46358 * @param {didi.Injector} injector
46359 * @param {EventBus} eventBus
46360 * @param {Modeling} modeling
46361 */
46362 function LabelSupport(injector, eventBus, modeling) {
46363
46364 CommandInterceptor.call(this, eventBus);
46365
46366 var movePreview = injector.get('movePreview', false);
46367
46368 // remove labels from the collection that are being
46369 // moved with other elements anyway
46370 eventBus.on('shape.move.start', HIGH_PRIORITY$e, function(e) {
46371
46372 var context = e.context,
46373 shapes = context.shapes,
46374 validatedShapes = context.validatedShapes;
46375
46376 context.shapes = removeLabels(shapes);
46377 context.validatedShapes = removeLabels(validatedShapes);
46378 });
46379
46380 // add labels to visual's group
46381 movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$g, function(e) {
46382
46383 var context = e.context,
46384 shapes = context.shapes;
46385
46386 var labels = [];
46387
46388 forEach(shapes, function(element) {
46389
46390 forEach(element.labels, function(label) {
46391
46392 if (!label.hidden && context.shapes.indexOf(label) === -1) {
46393 labels.push(label);
46394 }
46395
46396 if (element.labelTarget) {
46397 labels.push(element);
46398 }
46399 });
46400 });
46401
46402 forEach(labels, function(label) {
46403 movePreview.makeDraggable(context, label, true);
46404 });
46405
46406 });
46407
46408 // add all labels to move closure
46409 this.preExecuted('elements.move', HIGH_PRIORITY$e, function(e) {
46410 var context = e.context,
46411 closure = context.closure,
46412 enclosedElements = closure.enclosedElements;
46413
46414 var enclosedLabels = [];
46415
46416 // find labels that are not part of
46417 // move closure yet and add them
46418 forEach(enclosedElements, function(element) {
46419 forEach(element.labels, function(label) {
46420
46421 if (!enclosedElements[label.id]) {
46422 enclosedLabels.push(label);
46423 }
46424 });
46425 });
46426
46427 closure.addAll(enclosedLabels);
46428 });
46429
46430
46431 this.preExecute([
46432 'connection.delete',
46433 'shape.delete'
46434 ], function(e) {
46435
46436 var context = e.context,
46437 element = context.connection || context.shape;
46438
46439 saveClear(element.labels, function(label) {
46440 modeling.removeShape(label, { nested: true });
46441 });
46442 });
46443
46444
46445 this.execute('shape.delete', function(e) {
46446
46447 var context = e.context,
46448 shape = context.shape,
46449 labelTarget = shape.labelTarget;
46450
46451 // unset labelTarget
46452 if (labelTarget) {
46453 context.labelTargetIndex = indexOf$1(labelTarget.labels, shape);
46454 context.labelTarget = labelTarget;
46455
46456 shape.labelTarget = null;
46457 }
46458 });
46459
46460 this.revert('shape.delete', function(e) {
46461
46462 var context = e.context,
46463 shape = context.shape,
46464 labelTarget = context.labelTarget,
46465 labelTargetIndex = context.labelTargetIndex;
46466
46467 // restore labelTarget
46468 if (labelTarget) {
46469 add$1(labelTarget.labels, shape, labelTargetIndex);
46470
46471 shape.labelTarget = labelTarget;
46472 }
46473 });
46474
46475 }
46476
46477 inherits_browser(LabelSupport, CommandInterceptor);
46478
46479 LabelSupport.$inject = [
46480 'injector',
46481 'eventBus',
46482 'modeling'
46483 ];
46484
46485
46486 /**
46487 * Return a filtered list of elements that do not
46488 * contain attached elements with hosts being part
46489 * of the selection.
46490 *
46491 * @param {Array<djs.model.Base>} elements
46492 *
46493 * @return {Array<djs.model.Base>} filtered
46494 */
46495 function removeLabels(elements) {
46496
46497 return filter(elements, function(element) {
46498
46499 // filter out labels that are move together
46500 // with their label targets
46501 return elements.indexOf(element.labelTarget) === -1;
46502 });
46503 }
46504
46505 var LabelSupportModule = {
46506 __init__: [ 'labelSupport'],
46507 labelSupport: [ 'type', LabelSupport ]
46508 };
46509
46510 var LOW_PRIORITY$h = 251,
46511 HIGH_PRIORITY$f = 1401;
46512
46513 var MARKER_ATTACH$1 = 'attach-ok';
46514
46515
46516 /**
46517 * Adds the notion of attached elements to the modeler.
46518 *
46519 * Optionally depends on `diagram-js/lib/features/move` to render
46520 * the attached elements during move preview.
46521 *
46522 * Optionally depends on `diagram-js/lib/features/label-support`
46523 * to render attached labels during move preview.
46524 *
46525 * @param {didi.Injector} injector
46526 * @param {EventBus} eventBus
46527 * @param {Canvas} canvas
46528 * @param {Rules} rules
46529 * @param {Modeling} modeling
46530 */
46531 function AttachSupport(injector, eventBus, canvas, rules, modeling) {
46532
46533 CommandInterceptor.call(this, eventBus);
46534
46535 var movePreview = injector.get('movePreview', false);
46536
46537
46538 // remove all the attached elements from the shapes to be validated
46539 // add all the attached shapes to the overall list of moved shapes
46540 eventBus.on('shape.move.start', HIGH_PRIORITY$f, function(e) {
46541
46542 var context = e.context,
46543 shapes = context.shapes,
46544 validatedShapes = context.validatedShapes;
46545
46546 context.shapes = addAttached(shapes);
46547
46548 context.validatedShapes = removeAttached(validatedShapes);
46549 });
46550
46551 // add attachers to the visual's group
46552 movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$h, function(e) {
46553
46554 var context = e.context,
46555 shapes = context.shapes,
46556 attachers = getAttachers(shapes);
46557
46558 forEach(attachers, function(attacher) {
46559 movePreview.makeDraggable(context, attacher, true);
46560
46561 forEach(attacher.labels, function(label) {
46562 movePreview.makeDraggable(context, label, true);
46563 });
46564 });
46565 });
46566
46567 // add attach-ok marker to current host
46568 movePreview && eventBus.on('shape.move.start', function(event) {
46569 var context = event.context,
46570 shapes = context.shapes;
46571
46572 if (shapes.length !== 1) {
46573 return;
46574 }
46575
46576 var shape = shapes[0];
46577
46578 var host = shape.host;
46579
46580 if (host) {
46581 canvas.addMarker(host, MARKER_ATTACH$1);
46582
46583 eventBus.once([
46584 'shape.move.out',
46585 'shape.move.cleanup'
46586 ], function() {
46587 canvas.removeMarker(host, MARKER_ATTACH$1);
46588 });
46589 }
46590 });
46591
46592 // add all attachers to move closure
46593 this.preExecuted('elements.move', HIGH_PRIORITY$f, function(e) {
46594 var context = e.context,
46595 closure = context.closure,
46596 shapes = context.shapes,
46597 attachers = getAttachers(shapes);
46598
46599 forEach(attachers, function(attacher) {
46600 closure.add(attacher, closure.topLevel[attacher.host.id]);
46601 });
46602 });
46603
46604 // perform the attaching after shapes are done moving
46605 this.postExecuted('elements.move', function(e) {
46606
46607 var context = e.context,
46608 shapes = context.shapes,
46609 newHost = context.newHost,
46610 attachers;
46611
46612 // only single elements can be attached
46613 // multiply elements can be detached
46614 if (newHost && shapes.length !== 1) {
46615 return;
46616 }
46617
46618 if (newHost) {
46619 attachers = shapes;
46620 } else {
46621
46622 // find attachers moved without host
46623 attachers = filter(shapes, function(shape) {
46624 var host = shape.host;
46625
46626 return isAttacher$1(shape) && !includes$2(shapes, host);
46627 });
46628 }
46629
46630 forEach(attachers, function(attacher) {
46631 modeling.updateAttachment(attacher, newHost);
46632 });
46633 });
46634
46635 // ensure invalid attachment connections are removed
46636 this.postExecuted('elements.move', function(e) {
46637
46638 var shapes = e.context.shapes;
46639
46640 forEach(shapes, function(shape) {
46641
46642 forEach(shape.attachers, function(attacher) {
46643
46644 // remove invalid outgoing connections
46645 forEach(attacher.outgoing.slice(), function(connection) {
46646 var allowed = rules.allowed('connection.reconnect', {
46647 connection: connection,
46648 source: connection.source,
46649 target: connection.target
46650 });
46651
46652 if (!allowed) {
46653 modeling.removeConnection(connection);
46654 }
46655 });
46656
46657 // remove invalid incoming connections
46658 forEach(attacher.incoming.slice(), function(connection) {
46659 var allowed = rules.allowed('connection.reconnect', {
46660 connection: connection,
46661 source: connection.source,
46662 target: connection.target
46663 });
46664
46665 if (!allowed) {
46666 modeling.removeConnection(connection);
46667 }
46668 });
46669 });
46670 });
46671 });
46672
46673 this.postExecute('shape.create', function(e) {
46674 var context = e.context,
46675 shape = context.shape,
46676 host = context.host;
46677
46678 if (host) {
46679 modeling.updateAttachment(shape, host);
46680 }
46681 });
46682
46683 // update attachments if the host is replaced
46684 this.postExecute('shape.replace', function(e) {
46685
46686 var context = e.context,
46687 oldShape = context.oldShape,
46688 newShape = context.newShape;
46689
46690 // move the attachers to the new host
46691 saveClear(oldShape.attachers, function(attacher) {
46692 var allowed = rules.allowed('elements.move', {
46693 target: newShape,
46694 shapes: [attacher]
46695 });
46696
46697 if (allowed === 'attach') {
46698 modeling.updateAttachment(attacher, newShape);
46699 } else {
46700 modeling.removeShape(attacher);
46701 }
46702 });
46703
46704 // move attachers if new host has different size
46705 if (newShape.attachers.length) {
46706
46707 forEach(newShape.attachers, function(attacher) {
46708 var delta = getNewAttachShapeDelta(attacher, oldShape, newShape);
46709 modeling.moveShape(attacher, delta, attacher.parent);
46710 });
46711 }
46712
46713 });
46714
46715 // move shape on host resize
46716 this.postExecute('shape.resize', function(event) {
46717 var context = event.context,
46718 shape = context.shape,
46719 oldBounds = context.oldBounds,
46720 newBounds = context.newBounds,
46721 attachers = shape.attachers,
46722 hints = context.hints || {};
46723
46724 if (hints.attachSupport === false) {
46725 return;
46726 }
46727
46728 forEach(attachers, function(attacher) {
46729 var delta = getNewAttachShapeDelta(attacher, oldBounds, newBounds);
46730
46731 modeling.moveShape(attacher, delta, attacher.parent);
46732
46733 forEach(attacher.labels, function(label) {
46734 modeling.moveShape(label, delta, label.parent);
46735 });
46736 });
46737 });
46738
46739 // remove attachments
46740 this.preExecute('shape.delete', function(event) {
46741
46742 var shape = event.context.shape;
46743
46744 saveClear(shape.attachers, function(attacher) {
46745 modeling.removeShape(attacher);
46746 });
46747
46748 if (shape.host) {
46749 modeling.updateAttachment(shape, null);
46750 }
46751 });
46752 }
46753
46754 inherits_browser(AttachSupport, CommandInterceptor);
46755
46756 AttachSupport.$inject = [
46757 'injector',
46758 'eventBus',
46759 'canvas',
46760 'rules',
46761 'modeling'
46762 ];
46763
46764
46765 /**
46766 * Return attachers of the given shapes
46767 *
46768 * @param {Array<djs.model.Base>} shapes
46769 * @return {Array<djs.model.Base>}
46770 */
46771 function getAttachers(shapes) {
46772 return flatten(map(shapes, function(s) {
46773 return s.attachers || [];
46774 }));
46775 }
46776
46777 /**
46778 * Return a combined list of elements and
46779 * attachers.
46780 *
46781 * @param {Array<djs.model.Base>} elements
46782 * @return {Array<djs.model.Base>} filtered
46783 */
46784 function addAttached(elements) {
46785 var attachers = getAttachers(elements);
46786
46787 return unionBy('id', elements, attachers);
46788 }
46789
46790 /**
46791 * Return a filtered list of elements that do not
46792 * contain attached elements with hosts being part
46793 * of the selection.
46794 *
46795 * @param {Array<djs.model.Base>} elements
46796 *
46797 * @return {Array<djs.model.Base>} filtered
46798 */
46799 function removeAttached(elements) {
46800
46801 var ids = groupBy(elements, 'id');
46802
46803 return filter(elements, function(element) {
46804 while (element) {
46805
46806 // host in selection
46807 if (element.host && ids[element.host.id]) {
46808 return false;
46809 }
46810
46811 element = element.parent;
46812 }
46813
46814 return true;
46815 });
46816 }
46817
46818 function isAttacher$1(shape) {
46819 return !!shape.host;
46820 }
46821
46822 function includes$2(array, item) {
46823 return array.indexOf(item) !== -1;
46824 }
46825
46826 var AttachSupportModule = {
46827 __depends__: [
46828 RulesModule
46829 ],
46830 __init__: [ 'attachSupport' ],
46831 attachSupport: [ 'type', AttachSupport ]
46832 };
46833
46834 var LOW_PRIORITY$i = 250;
46835
46836 /**
46837 * The tool manager acts as middle-man between the available tool's and the Palette,
46838 * it takes care of making sure that the correct active state is set.
46839 *
46840 * @param {Object} eventBus
46841 * @param {Object} dragging
46842 */
46843 function ToolManager(eventBus, dragging) {
46844 this._eventBus = eventBus;
46845 this._dragging = dragging;
46846
46847 this._tools = [];
46848 this._active = null;
46849 }
46850
46851 ToolManager.$inject = [ 'eventBus', 'dragging' ];
46852
46853 ToolManager.prototype.registerTool = function(name, events) {
46854 var tools = this._tools;
46855
46856 if (!events) {
46857 throw new Error('A tool has to be registered with it\'s "events"');
46858 }
46859
46860 tools.push(name);
46861
46862 this.bindEvents(name, events);
46863 };
46864
46865 ToolManager.prototype.isActive = function(tool) {
46866 return tool && this._active === tool;
46867 };
46868
46869 ToolManager.prototype.length = function(tool) {
46870 return this._tools.length;
46871 };
46872
46873 ToolManager.prototype.setActive = function(tool) {
46874 var eventBus = this._eventBus;
46875
46876 if (this._active !== tool) {
46877 this._active = tool;
46878
46879 eventBus.fire('tool-manager.update', { tool: tool });
46880 }
46881 };
46882
46883 ToolManager.prototype.bindEvents = function(name, events) {
46884 var eventBus = this._eventBus,
46885 dragging = this._dragging;
46886
46887 var eventsToRegister = [];
46888
46889 eventBus.on(events.tool + '.init', function(event) {
46890 var context = event.context;
46891
46892 // Active tools that want to reactivate themselves must do this explicitly
46893 if (!context.reactivate && this.isActive(name)) {
46894 this.setActive(null);
46895
46896 dragging.cancel();
46897 return;
46898 }
46899
46900 this.setActive(name);
46901
46902 }, this);
46903
46904 // Todo[ricardo]: add test cases
46905 forEach(events, function(event) {
46906 eventsToRegister.push(event + '.ended');
46907 eventsToRegister.push(event + '.canceled');
46908 });
46909
46910 eventBus.on(eventsToRegister, LOW_PRIORITY$i, function(event) {
46911
46912 // We defer the de-activation of the tool to the .activate phase,
46913 // so we're able to check if we want to toggle off the current
46914 // active tool or switch to a new one
46915 if (!this._active) {
46916 return;
46917 }
46918
46919 if (isPaletteClick(event)) {
46920 return;
46921 }
46922
46923 this.setActive(null);
46924 }, this);
46925
46926 };
46927
46928
46929 // helpers ///////////////
46930
46931 /**
46932 * Check if a given event is a palette click event.
46933 *
46934 * @param {EventBus.Event} event
46935 *
46936 * @return {boolean}
46937 */
46938 function isPaletteClick(event) {
46939 var target = event.originalEvent && event.originalEvent.target;
46940
46941 return target && closest(target, '.group[data-group="tools"]');
46942 }
46943
46944 var ToolManagerModule = {
46945 __depends__: [
46946 DraggingModule
46947 ],
46948 __init__: [ 'toolManager' ],
46949 toolManager: [ 'type', ToolManager ]
46950 };
46951
46952 /**
46953 * Return direction given axis and delta.
46954 *
46955 * @param {string} axis
46956 * @param {number} delta
46957 *
46958 * @return {string}
46959 */
46960 function getDirection(axis, delta) {
46961
46962 if (axis === 'x') {
46963 if (delta > 0) {
46964 return 'e';
46965 }
46966
46967 if (delta < 0) {
46968 return 'w';
46969 }
46970 }
46971
46972 if (axis === 'y') {
46973 if (delta > 0) {
46974 return 's';
46975 }
46976
46977 if (delta < 0) {
46978 return 'n';
46979 }
46980 }
46981
46982 return null;
46983 }
46984
46985 /**
46986 * Returns connections whose waypoints are to be updated. Waypoints are to be updated if start
46987 * or end is to be moved or resized.
46988 *
46989 * @param {Array<djs.model.Shape} movingShapes
46990 * @param {Array<djs.model.Shape} resizingShapes
46991 *
46992 * @returns {Array<djs.model.Connection>}
46993 */
46994 function getWaypointsUpdatingConnections(movingShapes, resizingShapes) {
46995 var waypointsUpdatingConnections = [];
46996
46997 forEach(movingShapes.concat(resizingShapes), function(shape) {
46998 var incoming = shape.incoming,
46999 outgoing = shape.outgoing;
47000
47001 forEach(incoming.concat(outgoing), function(connection) {
47002 var source = connection.source,
47003 target = connection.target;
47004
47005 if (includes$3(movingShapes, source) ||
47006 includes$3(movingShapes, target) ||
47007 includes$3(resizingShapes, source) ||
47008 includes$3(resizingShapes, target)) {
47009
47010 if (!includes$3(waypointsUpdatingConnections, connection)) {
47011 waypointsUpdatingConnections.push(connection);
47012 }
47013 }
47014 });
47015 });
47016
47017 return waypointsUpdatingConnections;
47018 }
47019
47020 function includes$3(array, item) {
47021 return array.indexOf(item) !== -1;
47022 }
47023
47024 /**
47025 * Resize bounds.
47026 *
47027 * @param {Object} bounds
47028 * @param {number} bounds.x
47029 * @param {number} bounds.y
47030 * @param {number} bounds.width
47031 * @param {number} bounds.height
47032 * @param {string} direction
47033 * @param {Object} delta
47034 * @param {number} delta.x
47035 * @param {number} delta.y
47036 *
47037 * @return {Object}
47038 */
47039 function resizeBounds$1(bounds, direction, delta) {
47040 var x = bounds.x,
47041 y = bounds.y,
47042 width = bounds.width,
47043 height = bounds.height,
47044 dx = delta.x,
47045 dy = delta.y;
47046
47047 switch (direction) {
47048 case 'n':
47049 return {
47050 x: x,
47051 y: y + dy,
47052 width: width,
47053 height: height - dy
47054 };
47055 case 's':
47056 return {
47057 x: x,
47058 y: y,
47059 width: width,
47060 height: height + dy
47061 };
47062 case 'w':
47063 return {
47064 x: x + dx,
47065 y: y,
47066 width: width - dx,
47067 height: height
47068 };
47069 case 'e':
47070 return {
47071 x: x,
47072 y: y,
47073 width: width + dx,
47074 height: height
47075 };
47076 default:
47077 throw new Error('unknown direction: ' + direction);
47078 }
47079 }
47080
47081 var abs$6 = Math.abs,
47082 round$7 = Math.round;
47083
47084 var AXIS_TO_DIMENSION = {
47085 x: 'width',
47086 y: 'height'
47087 };
47088
47089 var CURSOR_CROSSHAIR = 'crosshair';
47090
47091 var DIRECTION_TO_TRBL = {
47092 n: 'top',
47093 w: 'left',
47094 s: 'bottom',
47095 e: 'right'
47096 };
47097
47098 var HIGH_PRIORITY$g = 1500;
47099
47100 var DIRECTION_TO_OPPOSITE = {
47101 n: 's',
47102 w: 'e',
47103 s: 'n',
47104 e: 'w'
47105 };
47106
47107 var PADDING = 20;
47108
47109
47110 /**
47111 * Add or remove space by moving and resizing elements.
47112 *
47113 * @param {Canvas} canvas
47114 * @param {Dragging} dragging
47115 * @param {EventBus} eventBus
47116 * @param {Modeling} modeling
47117 * @param {Rules} rules
47118 * @param {ToolManager} toolManager
47119 * @param {Mouse} mouse
47120 */
47121 function SpaceTool(
47122 canvas, dragging, eventBus,
47123 modeling, rules, toolManager,
47124 mouse) {
47125
47126 this._canvas = canvas;
47127 this._dragging = dragging;
47128 this._eventBus = eventBus;
47129 this._modeling = modeling;
47130 this._rules = rules;
47131 this._toolManager = toolManager;
47132 this._mouse = mouse;
47133
47134 var self = this;
47135
47136 toolManager.registerTool('space', {
47137 tool: 'spaceTool.selection',
47138 dragging: 'spaceTool'
47139 });
47140
47141 eventBus.on('spaceTool.selection.end', function(event) {
47142 eventBus.once('spaceTool.selection.ended', function() {
47143 self.activateMakeSpace(event.originalEvent);
47144 });
47145 });
47146
47147 eventBus.on('spaceTool.move', HIGH_PRIORITY$g , function(event) {
47148 var context = event.context,
47149 initialized = context.initialized;
47150
47151 if (!initialized) {
47152 initialized = context.initialized = self.init(event, context);
47153 }
47154
47155 if (initialized) {
47156 ensureConstraints$2(event);
47157 }
47158 });
47159
47160 eventBus.on('spaceTool.end', function(event) {
47161 var context = event.context,
47162 axis = context.axis,
47163 direction = context.direction,
47164 movingShapes = context.movingShapes,
47165 resizingShapes = context.resizingShapes,
47166 start = context.start;
47167
47168 if (!context.initialized) {
47169 return;
47170 }
47171
47172 ensureConstraints$2(event);
47173
47174 var delta = {
47175 x: 0,
47176 y: 0
47177 };
47178
47179 delta[ axis ] = round$7(event[ 'd' + axis ]);
47180
47181 self.makeSpace(movingShapes, resizingShapes, delta, direction, start);
47182
47183 eventBus.once('spaceTool.ended', function(event) {
47184
47185 // activate space tool selection after make space
47186 self.activateSelection(event.originalEvent, true, true);
47187 });
47188 });
47189 }
47190
47191 SpaceTool.$inject = [
47192 'canvas',
47193 'dragging',
47194 'eventBus',
47195 'modeling',
47196 'rules',
47197 'toolManager',
47198 'mouse'
47199 ];
47200
47201 /**
47202 * Activate space tool selection.
47203 *
47204 * @param {Object} event
47205 * @param {boolean} autoActivate
47206 */
47207 SpaceTool.prototype.activateSelection = function(event, autoActivate, reactivate) {
47208 this._dragging.init(event, 'spaceTool.selection', {
47209 autoActivate: autoActivate,
47210 cursor: CURSOR_CROSSHAIR,
47211 data: {
47212 context: {
47213 reactivate: reactivate
47214 }
47215 },
47216 trapClick: false
47217 });
47218 };
47219
47220 /**
47221 * Activate space tool make space.
47222 *
47223 * @param {MouseEvent} event
47224 */
47225 SpaceTool.prototype.activateMakeSpace = function(event) {
47226 this._dragging.init(event, 'spaceTool', {
47227 autoActivate: true,
47228 cursor: CURSOR_CROSSHAIR,
47229 data: {
47230 context: {}
47231 }
47232 });
47233 };
47234
47235 /**
47236 * Make space.
47237 *
47238 * @param {Array<djs.model.Shape>} movingShapes
47239 * @param {Array<djs.model.Shape>} resizingShapes
47240 * @param {Object} delta
47241 * @param {number} delta.x
47242 * @param {number} delta.y
47243 * @param {string} direction
47244 * @param {number} start
47245 */
47246 SpaceTool.prototype.makeSpace = function(movingShapes, resizingShapes, delta, direction, start) {
47247 return this._modeling.createSpace(movingShapes, resizingShapes, delta, direction, start);
47248 };
47249
47250 /**
47251 * Initialize make space and return true if that was successful.
47252 *
47253 * @param {Object} event
47254 * @param {Object} context
47255 *
47256 * @return {boolean}
47257 */
47258 SpaceTool.prototype.init = function(event, context) {
47259 var axis = abs$6(event.dx) > abs$6(event.dy) ? 'x' : 'y',
47260 delta = event[ 'd' + axis ],
47261 start = event[ axis ] - delta;
47262
47263 if (abs$6(delta) < 5) {
47264 return false;
47265 }
47266
47267 // invert delta to remove space when moving left
47268 if (delta < 0) {
47269 delta *= -1;
47270 }
47271
47272 // invert delta to add/remove space when removing/adding space if modifier key is pressed
47273 if (hasPrimaryModifier(event)) {
47274 delta *= -1;
47275 }
47276
47277 var direction = getDirection(axis, delta);
47278
47279 var root = this._canvas.getRootElement();
47280
47281 var children = selfAndAllChildren(root, true);
47282
47283 var elements = this.calculateAdjustments(children, axis, delta, start);
47284
47285 var minDimensions = this._eventBus.fire('spaceTool.getMinDimensions', {
47286 axis: axis,
47287 direction: direction,
47288 shapes: elements.resizingShapes,
47289 start: start
47290 });
47291
47292 var spaceToolConstraints = getSpaceToolConstraints(elements, axis, direction, start, minDimensions);
47293
47294 assign(
47295 context,
47296 elements,
47297 {
47298 axis: axis,
47299 direction: direction,
47300 spaceToolConstraints: spaceToolConstraints,
47301 start: start
47302 }
47303 );
47304
47305 set$1('resize-' + (axis === 'x' ? 'ew' : 'ns'));
47306
47307 return true;
47308 };
47309
47310 /**
47311 * Get elements to be moved and resized.
47312 *
47313 * @param {Array<djs.model.Shape>} elements
47314 * @param {string} axis
47315 * @param {number} delta
47316 * @param {number} start
47317 *
47318 * @return {Object}
47319 */
47320 SpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {
47321 var rules = this._rules;
47322
47323 var movingShapes = [],
47324 resizingShapes = [];
47325
47326 forEach(elements, function(element) {
47327 if (!element.parent || isConnection$4(element)) {
47328 return;
47329 }
47330
47331 var shapeStart = element[ axis ],
47332 shapeEnd = shapeStart + element[ AXIS_TO_DIMENSION[ axis ] ];
47333
47334 // shape to be moved
47335 if ((delta > 0 && shapeStart > start) || (delta < 0 && shapeEnd < start)) {
47336 return movingShapes.push(element);
47337 }
47338
47339 // shape to be resized
47340 if (shapeStart < start &&
47341 shapeEnd > start &&
47342 rules.allowed('shape.resize', { shape: element })
47343 ) {
47344
47345 return resizingShapes.push(element);
47346 }
47347 });
47348
47349 return {
47350 movingShapes: movingShapes,
47351 resizingShapes: resizingShapes
47352 };
47353 };
47354
47355 SpaceTool.prototype.toggle = function() {
47356
47357 if (this.isActive()) {
47358 return this._dragging.cancel();
47359 }
47360
47361 var mouseEvent = this._mouse.getLastMoveEvent();
47362
47363 this.activateSelection(mouseEvent, !!mouseEvent);
47364 };
47365
47366 SpaceTool.prototype.isActive = function() {
47367 var context = this._dragging.context();
47368
47369 return context && /^spaceTool/.test(context.prefix);
47370 };
47371
47372 // helpers //////////
47373
47374 function addPadding$1(trbl) {
47375 return {
47376 top: trbl.top - PADDING,
47377 right: trbl.right + PADDING,
47378 bottom: trbl.bottom + PADDING,
47379 left: trbl.left - PADDING
47380 };
47381 }
47382
47383 function ensureConstraints$2(event) {
47384 var context = event.context,
47385 spaceToolConstraints = context.spaceToolConstraints;
47386
47387 if (!spaceToolConstraints) {
47388 return;
47389 }
47390
47391 var x, y;
47392
47393 if (isNumber(spaceToolConstraints.left)) {
47394 x = Math.max(event.x, spaceToolConstraints.left);
47395
47396 event.dx = event.dx + x - event.x;
47397 event.x = x;
47398 }
47399
47400 if (isNumber(spaceToolConstraints.right)) {
47401 x = Math.min(event.x, spaceToolConstraints.right);
47402
47403 event.dx = event.dx + x - event.x;
47404 event.x = x;
47405 }
47406
47407 if (isNumber(spaceToolConstraints.top)) {
47408 y = Math.max(event.y, spaceToolConstraints.top);
47409
47410 event.dy = event.dy + y - event.y;
47411 event.y = y;
47412 }
47413
47414 if (isNumber(spaceToolConstraints.bottom)) {
47415 y = Math.min(event.y, spaceToolConstraints.bottom);
47416
47417 event.dy = event.dy + y - event.y;
47418 event.y = y;
47419 }
47420 }
47421
47422 function getSpaceToolConstraints(elements, axis, direction, start, minDimensions) {
47423 var movingShapes = elements.movingShapes,
47424 resizingShapes = elements.resizingShapes;
47425
47426 if (!resizingShapes.length) {
47427 return;
47428 }
47429
47430 var spaceToolConstraints = {},
47431 min,
47432 max;
47433
47434 forEach(resizingShapes, function(resizingShape) {
47435 var resizingShapeBBox = asTRBL(resizingShape);
47436
47437 // find children that are not moving or resizing
47438 var nonMovingResizingChildren = filter(resizingShape.children, function(child) {
47439 return !isConnection$4(child) &&
47440 !isLabel$4(child) &&
47441 !includes$4(movingShapes, child) &&
47442 !includes$4(resizingShapes, child);
47443 });
47444
47445 // find children that are moving
47446 var movingChildren = filter(resizingShape.children, function(child) {
47447 return !isConnection$4(child) && !isLabel$4(child) && includes$4(movingShapes, child);
47448 });
47449
47450 var minOrMax,
47451 nonMovingResizingChildrenBBox,
47452 movingChildrenBBox;
47453
47454 if (nonMovingResizingChildren.length) {
47455 nonMovingResizingChildrenBBox = addPadding$1(asTRBL(getBBox(nonMovingResizingChildren)));
47456
47457 minOrMax = start -
47458 resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] +
47459 nonMovingResizingChildrenBBox[ DIRECTION_TO_TRBL[ direction ] ];
47460
47461 if (direction === 'n') {
47462 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47463 } else if (direction === 'w') {
47464 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47465 } else if (direction === 's') {
47466 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47467 } else if (direction === 'e') {
47468 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47469 }
47470 }
47471
47472 if (movingChildren.length) {
47473 movingChildrenBBox = addPadding$1(asTRBL(getBBox(movingChildren)));
47474
47475 minOrMax = start -
47476 movingChildrenBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] +
47477 resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ];
47478
47479 if (direction === 'n') {
47480 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47481 } else if (direction === 'w') {
47482 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47483 } else if (direction === 's') {
47484 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47485 } else if (direction === 'e') {
47486 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47487 }
47488 }
47489
47490 var resizingShapeMinDimensions = minDimensions && minDimensions[ resizingShape.id ];
47491
47492 if (resizingShapeMinDimensions) {
47493 if (direction === 'n') {
47494 minOrMax = start +
47495 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
47496 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
47497
47498 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47499 } else if (direction === 'w') {
47500 minOrMax = start +
47501 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
47502 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
47503
47504 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
47505 } else if (direction === 's') {
47506 minOrMax = start -
47507 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
47508 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
47509
47510 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47511 } else if (direction === 'e') {
47512 minOrMax = start -
47513 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
47514 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
47515
47516 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
47517 }
47518 }
47519 });
47520
47521 return spaceToolConstraints;
47522 }
47523
47524 function includes$4(array, item) {
47525 return array.indexOf(item) !== -1;
47526 }
47527
47528 function isConnection$4(element) {
47529 return !!element.waypoints;
47530 }
47531
47532 function isLabel$4(element) {
47533 return !!element.labelTarget;
47534 }
47535
47536 var MARKER_DRAGGING = 'djs-dragging',
47537 MARKER_RESIZING$1 = 'djs-resizing';
47538
47539 var LOW_PRIORITY$j = 250;
47540
47541 var max$6 = Math.max;
47542
47543
47544 /**
47545 * Provides previews for selecting/moving/resizing shapes when creating/removing space.
47546 *
47547 * @param {EventBus} eventBus
47548 * @param {ElementRegistry} elementRegistry
47549 * @param {Canvas} canvas
47550 * @param {Styles} styles
47551 */
47552 function SpaceToolPreview(
47553 eventBus, elementRegistry, canvas,
47554 styles, previewSupport) {
47555
47556 function addPreviewGfx(collection, dragGroup) {
47557 forEach(collection, function(element) {
47558 previewSupport.addDragger(element, dragGroup);
47559
47560 canvas.addMarker(element, MARKER_DRAGGING);
47561 });
47562 }
47563
47564 // add crosshair
47565 eventBus.on('spaceTool.selection.start', function(event) {
47566 var space = canvas.getLayer('space'),
47567 context = event.context;
47568
47569 var orientation = {
47570 x: 'M 0,-10000 L 0,10000',
47571 y: 'M -10000,0 L 10000,0'
47572 };
47573
47574 var crosshairGroup = create('g');
47575 attr$1(crosshairGroup, styles.cls('djs-crosshair-group', [ 'no-events' ]));
47576
47577 append(space, crosshairGroup);
47578
47579 // horizontal path
47580 var pathX = create('path');
47581 attr$1(pathX, 'd', orientation.x);
47582 classes$1(pathX).add('djs-crosshair');
47583
47584 append(crosshairGroup, pathX);
47585
47586 // vertical path
47587 var pathY = create('path');
47588 attr$1(pathY, 'd', orientation.y);
47589 classes$1(pathY).add('djs-crosshair');
47590
47591 append(crosshairGroup, pathY);
47592
47593 context.crosshairGroup = crosshairGroup;
47594 });
47595
47596 // update crosshair
47597 eventBus.on('spaceTool.selection.move', function(event) {
47598 var crosshairGroup = event.context.crosshairGroup;
47599
47600 translate(crosshairGroup, event.x, event.y);
47601 });
47602
47603 // remove crosshair
47604 eventBus.on('spaceTool.selection.cleanup', function(event) {
47605 var context = event.context,
47606 crosshairGroup = context.crosshairGroup;
47607
47608 if (crosshairGroup) {
47609 remove$1(crosshairGroup);
47610 }
47611 });
47612
47613 // add and update move/resize previews
47614 eventBus.on('spaceTool.move', LOW_PRIORITY$j, function(event) {
47615
47616 var context = event.context,
47617 line = context.line,
47618 axis = context.axis,
47619 movingShapes = context.movingShapes,
47620 resizingShapes = context.resizingShapes;
47621
47622 if (!context.initialized) {
47623 return;
47624 }
47625
47626 if (!context.dragGroup) {
47627 var spaceLayer = canvas.getLayer('space');
47628
47629 line = create('path');
47630 attr$1(line, 'd', 'M0,0 L0,0');
47631 classes$1(line).add('djs-crosshair');
47632
47633 append(spaceLayer, line);
47634
47635 context.line = line;
47636
47637 var dragGroup = create('g');
47638 attr$1(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
47639
47640 append(canvas.getDefaultLayer(), dragGroup);
47641
47642 // shapes
47643 addPreviewGfx(movingShapes, dragGroup);
47644
47645 // connections
47646 var movingConnections = context.movingConnections = elementRegistry.filter(function(element) {
47647 var sourceIsMoving = false;
47648
47649 forEach(movingShapes, function(shape) {
47650 forEach(shape.outgoing, function(connection) {
47651 if (element === connection) {
47652 sourceIsMoving = true;
47653 }
47654 });
47655 });
47656
47657 var targetIsMoving = false;
47658
47659 forEach(movingShapes, function(shape) {
47660 forEach(shape.incoming, function(connection) {
47661 if (element === connection) {
47662 targetIsMoving = true;
47663 }
47664 });
47665 });
47666
47667 var sourceIsResizing = false;
47668
47669 forEach(resizingShapes, function(shape) {
47670 forEach(shape.outgoing, function(connection) {
47671 if (element === connection) {
47672 sourceIsResizing = true;
47673 }
47674 });
47675 });
47676
47677 var targetIsResizing = false;
47678
47679 forEach(resizingShapes, function(shape) {
47680 forEach(shape.incoming, function(connection) {
47681 if (element === connection) {
47682 targetIsResizing = true;
47683 }
47684 });
47685 });
47686
47687 return isConnection$5(element)
47688 && (sourceIsMoving || sourceIsResizing)
47689 && (targetIsMoving || targetIsResizing);
47690 });
47691
47692
47693 addPreviewGfx(movingConnections, dragGroup);
47694
47695 context.dragGroup = dragGroup;
47696 }
47697
47698 if (!context.frameGroup) {
47699 var frameGroup = create('g');
47700 attr$1(frameGroup, styles.cls('djs-frame-group', [ 'no-events' ]));
47701
47702 append(canvas.getDefaultLayer(), frameGroup);
47703
47704 var frames = [];
47705
47706 forEach(resizingShapes, function(shape) {
47707 var frame = previewSupport.addFrame(shape, frameGroup);
47708
47709 var initialBounds = frame.getBBox();
47710
47711 frames.push({
47712 element: frame,
47713 initialBounds: initialBounds
47714 });
47715
47716 canvas.addMarker(shape, MARKER_RESIZING$1);
47717 });
47718
47719 context.frameGroup = frameGroup;
47720 context.frames = frames;
47721 }
47722
47723 var orientation = {
47724 x: 'M' + event.x + ', -10000 L' + event.x + ', 10000',
47725 y: 'M -10000, ' + event.y + ' L 10000, ' + event.y
47726 };
47727
47728 attr$1(line, { d: orientation[ axis ] });
47729
47730 var opposite = { x: 'y', y: 'x' };
47731 var delta = { x: event.dx, y: event.dy };
47732 delta[ opposite[ context.axis ] ] = 0;
47733
47734 // update move previews
47735 translate(context.dragGroup, delta.x, delta.y);
47736
47737 // update resize previews
47738 forEach(context.frames, function(frame) {
47739 var element = frame.element,
47740 initialBounds = frame.initialBounds,
47741 width,
47742 height;
47743
47744 if (context.direction === 'e') {
47745 attr$1(element, {
47746 width: max$6(initialBounds.width + delta.x, 5)
47747 });
47748 } else {
47749 width = max$6(initialBounds.width - delta.x, 5);
47750
47751 attr$1(element, {
47752 width: width,
47753 x: initialBounds.x + initialBounds.width - width
47754 });
47755 }
47756
47757 if (context.direction === 's') {
47758 attr$1(element, {
47759 height: max$6(initialBounds.height + delta.y, 5)
47760 });
47761 } else {
47762 height = max$6(initialBounds.height - delta.y, 5);
47763
47764 attr$1(element, {
47765 height: height,
47766 y: initialBounds.y + initialBounds.height - height
47767 });
47768 }
47769 });
47770
47771 });
47772
47773 // remove move/resize previews
47774 eventBus.on('spaceTool.cleanup', function(event) {
47775
47776 var context = event.context,
47777 movingShapes = context.movingShapes,
47778 movingConnections = context.movingConnections,
47779 resizingShapes = context.resizingShapes,
47780 line = context.line,
47781 dragGroup = context.dragGroup,
47782 frameGroup = context.frameGroup;
47783
47784 // moving shapes
47785 forEach(movingShapes, function(shape) {
47786 canvas.removeMarker(shape, MARKER_DRAGGING);
47787 });
47788
47789 // moving connections
47790 forEach(movingConnections, function(connection) {
47791 canvas.removeMarker(connection, MARKER_DRAGGING);
47792 });
47793
47794 if (dragGroup) {
47795 remove$1(line);
47796 remove$1(dragGroup);
47797 }
47798
47799 forEach(resizingShapes, function(shape) {
47800 canvas.removeMarker(shape, MARKER_RESIZING$1);
47801 });
47802
47803 if (frameGroup) {
47804 remove$1(frameGroup);
47805 }
47806 });
47807 }
47808
47809 SpaceToolPreview.$inject = [
47810 'eventBus',
47811 'elementRegistry',
47812 'canvas',
47813 'styles',
47814 'previewSupport'
47815 ];
47816
47817
47818 // helpers //////////////////////
47819
47820 /**
47821 * Checks if an element is a connection.
47822 */
47823 function isConnection$5(element) {
47824 return element.waypoints;
47825 }
47826
47827 var SpaceToolModule = {
47828 __init__: ['spaceToolPreview'],
47829 __depends__: [
47830 DraggingModule,
47831 RulesModule,
47832 ToolManagerModule,
47833 PreviewSupportModule,
47834 MouseModule
47835 ],
47836 spaceTool: ['type', SpaceTool ],
47837 spaceToolPreview: ['type', SpaceToolPreview ]
47838 };
47839
47840 function BpmnFactory(moddle) {
47841 this._model = moddle;
47842 }
47843
47844 BpmnFactory.$inject = [ 'moddle' ];
47845
47846
47847 BpmnFactory.prototype._needsId = function(element) {
47848 return isAny(element, [
47849 'bpmn:RootElement',
47850 'bpmn:FlowElement',
47851 'bpmn:MessageFlow',
47852 'bpmn:DataAssociation',
47853 'bpmn:Artifact',
47854 'bpmn:Participant',
47855 'bpmn:Lane',
47856 'bpmn:LaneSet',
47857 'bpmn:Process',
47858 'bpmn:Collaboration',
47859 'bpmndi:BPMNShape',
47860 'bpmndi:BPMNEdge',
47861 'bpmndi:BPMNDiagram',
47862 'bpmndi:BPMNPlane',
47863 'bpmn:Property',
47864 'bpmn:CategoryValue'
47865 ]);
47866 };
47867
47868 BpmnFactory.prototype._ensureId = function(element) {
47869
47870 // generate semantic ids for elements
47871 // bpmn:SequenceFlow -> SequenceFlow_ID
47872 var prefix;
47873
47874 if (is$1(element, 'bpmn:Activity')) {
47875 prefix = 'Activity';
47876 } else if (is$1(element, 'bpmn:Event')) {
47877 prefix = 'Event';
47878 } else if (is$1(element, 'bpmn:Gateway')) {
47879 prefix = 'Gateway';
47880 } else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) {
47881 prefix = 'Flow';
47882 } else {
47883 prefix = (element.$type || '').replace(/^[^:]*:/g, '');
47884 }
47885
47886 prefix += '_';
47887
47888 if (!element.id && this._needsId(element)) {
47889 element.id = this._model.ids.nextPrefixed(prefix, element);
47890 }
47891 };
47892
47893
47894 BpmnFactory.prototype.create = function(type, attrs) {
47895 var element = this._model.create(type, attrs || {});
47896
47897 this._ensureId(element);
47898
47899 return element;
47900 };
47901
47902
47903 BpmnFactory.prototype.createDiLabel = function() {
47904 return this.create('bpmndi:BPMNLabel', {
47905 bounds: this.createDiBounds()
47906 });
47907 };
47908
47909
47910 BpmnFactory.prototype.createDiShape = function(semantic, bounds, attrs) {
47911
47912 return this.create('bpmndi:BPMNShape', assign({
47913 bpmnElement: semantic,
47914 bounds: this.createDiBounds(bounds)
47915 }, attrs));
47916 };
47917
47918
47919 BpmnFactory.prototype.createDiBounds = function(bounds) {
47920 return this.create('dc:Bounds', bounds);
47921 };
47922
47923
47924 BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
47925 var self = this;
47926
47927 return map(waypoints, function(pos) {
47928 return self.createDiWaypoint(pos);
47929 });
47930 };
47931
47932 BpmnFactory.prototype.createDiWaypoint = function(point) {
47933 return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
47934 };
47935
47936
47937 BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) {
47938 return this.create('bpmndi:BPMNEdge', assign({
47939 bpmnElement: semantic
47940 }, attrs));
47941 };
47942
47943 BpmnFactory.prototype.createDiPlane = function(semantic) {
47944 return this.create('bpmndi:BPMNPlane', {
47945 bpmnElement: semantic
47946 });
47947 };
47948
47949 /**
47950 * A handler responsible for updating the underlying BPMN 2.0 XML + DI
47951 * once changes on the diagram happen
47952 */
47953 function BpmnUpdater(
47954 eventBus, bpmnFactory, connectionDocking,
47955 translate) {
47956
47957 CommandInterceptor.call(this, eventBus);
47958
47959 this._bpmnFactory = bpmnFactory;
47960 this._translate = translate;
47961
47962 var self = this;
47963
47964
47965
47966 // connection cropping //////////////////////
47967
47968 // crop connection ends during create/update
47969 function cropConnection(e) {
47970 var context = e.context,
47971 hints = context.hints || {},
47972 connection;
47973
47974 if (!context.cropped && hints.createElementsBehavior !== false) {
47975 connection = context.connection;
47976 connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
47977 context.cropped = true;
47978 }
47979 }
47980
47981 this.executed([
47982 'connection.layout',
47983 'connection.create'
47984 ], cropConnection);
47985
47986 this.reverted([ 'connection.layout' ], function(e) {
47987 delete e.context.cropped;
47988 });
47989
47990
47991
47992 // BPMN + DI update //////////////////////
47993
47994
47995 // update parent
47996 function updateParent(e) {
47997 var context = e.context;
47998
47999 self.updateParent(context.shape || context.connection, context.oldParent);
48000 }
48001
48002 function reverseUpdateParent(e) {
48003 var context = e.context;
48004
48005 var element = context.shape || context.connection,
48006
48007 // oldParent is the (old) new parent, because we are undoing
48008 oldParent = context.parent || context.newParent;
48009
48010 self.updateParent(element, oldParent);
48011 }
48012
48013 this.executed([
48014 'shape.move',
48015 'shape.create',
48016 'shape.delete',
48017 'connection.create',
48018 'connection.move',
48019 'connection.delete'
48020 ], ifBpmn(updateParent));
48021
48022 this.reverted([
48023 'shape.move',
48024 'shape.create',
48025 'shape.delete',
48026 'connection.create',
48027 'connection.move',
48028 'connection.delete'
48029 ], ifBpmn(reverseUpdateParent));
48030
48031 /*
48032 * ## Updating Parent
48033 *
48034 * When morphing a Process into a Collaboration or vice-versa,
48035 * make sure that both the *semantic* and *di* parent of each element
48036 * is updated.
48037 *
48038 */
48039 function updateRoot(event) {
48040 var context = event.context,
48041 oldRoot = context.oldRoot,
48042 children = oldRoot.children;
48043
48044 forEach(children, function(child) {
48045 if (is$1(child, 'bpmn:BaseElement')) {
48046 self.updateParent(child);
48047 }
48048 });
48049 }
48050
48051 this.executed([ 'canvas.updateRoot' ], updateRoot);
48052 this.reverted([ 'canvas.updateRoot' ], updateRoot);
48053
48054
48055 // update bounds
48056 function updateBounds(e) {
48057 var shape = e.context.shape;
48058
48059 if (!is$1(shape, 'bpmn:BaseElement')) {
48060 return;
48061 }
48062
48063 self.updateBounds(shape);
48064 }
48065
48066 this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
48067
48068 // exclude labels because they're handled separately during shape.changed
48069 if (event.context.shape.type === 'label') {
48070 return;
48071 }
48072
48073 updateBounds(event);
48074 }));
48075
48076 this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
48077
48078 // exclude labels because they're handled separately during shape.changed
48079 if (event.context.shape.type === 'label') {
48080 return;
48081 }
48082
48083 updateBounds(event);
48084 }));
48085
48086 // Handle labels separately. This is necessary, because the label bounds have to be updated
48087 // every time its shape changes, not only on move, create and resize.
48088 eventBus.on('shape.changed', function(event) {
48089 if (event.element.type === 'label') {
48090 updateBounds({ context: { shape: event.element } });
48091 }
48092 });
48093
48094 // attach / detach connection
48095 function updateConnection(e) {
48096 self.updateConnection(e.context);
48097 }
48098
48099 this.executed([
48100 'connection.create',
48101 'connection.move',
48102 'connection.delete',
48103 'connection.reconnect'
48104 ], ifBpmn(updateConnection));
48105
48106 this.reverted([
48107 'connection.create',
48108 'connection.move',
48109 'connection.delete',
48110 'connection.reconnect'
48111 ], ifBpmn(updateConnection));
48112
48113
48114 // update waypoints
48115 function updateConnectionWaypoints(e) {
48116 self.updateConnectionWaypoints(e.context.connection);
48117 }
48118
48119 this.executed([
48120 'connection.layout',
48121 'connection.move',
48122 'connection.updateWaypoints',
48123 ], ifBpmn(updateConnectionWaypoints));
48124
48125 this.reverted([
48126 'connection.layout',
48127 'connection.move',
48128 'connection.updateWaypoints',
48129 ], ifBpmn(updateConnectionWaypoints));
48130
48131 // update conditional/default flows
48132 this.executed('connection.reconnect', ifBpmn(function(event) {
48133 var context = event.context,
48134 connection = context.connection,
48135 oldSource = context.oldSource,
48136 newSource = context.newSource,
48137 connectionBo = getBusinessObject(connection),
48138 oldSourceBo = getBusinessObject(oldSource),
48139 newSourceBo = getBusinessObject(newSource);
48140
48141 // remove condition from connection on reconnect to new source
48142 // if new source can NOT have condional sequence flow
48143 if (connectionBo.conditionExpression && !isAny(newSourceBo, [
48144 'bpmn:Activity',
48145 'bpmn:ExclusiveGateway',
48146 'bpmn:InclusiveGateway'
48147 ])) {
48148 context.oldConditionExpression = connectionBo.conditionExpression;
48149
48150 delete connectionBo.conditionExpression;
48151 }
48152
48153 // remove default from old source flow on reconnect to new source
48154 // if source changed
48155 if (oldSource !== newSource && oldSourceBo.default === connectionBo) {
48156 context.oldDefault = oldSourceBo.default;
48157
48158 delete oldSourceBo.default;
48159 }
48160 }));
48161
48162 this.reverted('connection.reconnect', ifBpmn(function(event) {
48163 var context = event.context,
48164 connection = context.connection,
48165 oldSource = context.oldSource,
48166 newSource = context.newSource,
48167 connectionBo = getBusinessObject(connection),
48168 oldSourceBo = getBusinessObject(oldSource),
48169 newSourceBo = getBusinessObject(newSource);
48170
48171 // add condition to connection on revert reconnect to new source
48172 if (context.oldConditionExpression) {
48173 connectionBo.conditionExpression = context.oldConditionExpression;
48174 }
48175
48176 // add default to old source on revert reconnect to new source
48177 if (context.oldDefault) {
48178 oldSourceBo.default = context.oldDefault;
48179
48180 delete newSourceBo.default;
48181 }
48182 }));
48183
48184 // update attachments
48185 function updateAttachment(e) {
48186 self.updateAttachment(e.context);
48187 }
48188
48189 this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
48190 this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
48191 }
48192
48193 inherits_browser(BpmnUpdater, CommandInterceptor);
48194
48195 BpmnUpdater.$inject = [
48196 'eventBus',
48197 'bpmnFactory',
48198 'connectionDocking',
48199 'translate'
48200 ];
48201
48202
48203 // implementation //////////////////////
48204
48205 BpmnUpdater.prototype.updateAttachment = function(context) {
48206
48207 var shape = context.shape,
48208 businessObject = shape.businessObject,
48209 host = shape.host;
48210
48211 businessObject.attachedToRef = host && host.businessObject;
48212 };
48213
48214 BpmnUpdater.prototype.updateParent = function(element, oldParent) {
48215
48216 // do not update BPMN 2.0 label parent
48217 if (element instanceof Label) {
48218 return;
48219 }
48220
48221 // data stores in collaborations are handled separately by DataStoreBehavior
48222 if (is$1(element, 'bpmn:DataStoreReference') &&
48223 element.parent &&
48224 is$1(element.parent, 'bpmn:Collaboration')) {
48225 return;
48226 }
48227
48228 var parentShape = element.parent;
48229
48230 var businessObject = element.businessObject,
48231 parentBusinessObject = parentShape && parentShape.businessObject,
48232 parentDi = parentBusinessObject && parentBusinessObject.di;
48233
48234 if (is$1(element, 'bpmn:FlowNode')) {
48235 this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
48236 }
48237
48238 if (is$1(element, 'bpmn:DataOutputAssociation')) {
48239 if (element.source) {
48240 parentBusinessObject = element.source.businessObject;
48241 } else {
48242 parentBusinessObject = null;
48243 }
48244 }
48245
48246 if (is$1(element, 'bpmn:DataInputAssociation')) {
48247 if (element.target) {
48248 parentBusinessObject = element.target.businessObject;
48249 } else {
48250 parentBusinessObject = null;
48251 }
48252 }
48253
48254 this.updateSemanticParent(businessObject, parentBusinessObject);
48255
48256 if (is$1(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
48257 this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
48258 }
48259
48260 this.updateDiParent(businessObject.di, parentDi);
48261 };
48262
48263
48264 BpmnUpdater.prototype.updateBounds = function(shape) {
48265
48266 var di = shape.businessObject.di;
48267
48268 var target = (shape instanceof Label) ? this._getLabel(di) : di;
48269
48270 var bounds = target.bounds;
48271
48272 if (!bounds) {
48273 bounds = this._bpmnFactory.createDiBounds();
48274 target.set('bounds', bounds);
48275 }
48276
48277 assign(bounds, {
48278 x: shape.x,
48279 y: shape.y,
48280 width: shape.width,
48281 height: shape.height
48282 });
48283 };
48284
48285 BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
48286
48287 if (oldContainment === newContainment) {
48288 return;
48289 }
48290
48291 var oldRefs, newRefs;
48292
48293 if (is$1 (oldContainment, 'bpmn:Lane')) {
48294 oldRefs = oldContainment.get('flowNodeRef');
48295 remove$2(oldRefs, businessObject);
48296 }
48297
48298 if (is$1(newContainment, 'bpmn:Lane')) {
48299 newRefs = newContainment.get('flowNodeRef');
48300 add$1(newRefs, businessObject);
48301 }
48302 };
48303
48304
48305 // update existing sourceElement and targetElement di information
48306 BpmnUpdater.prototype.updateDiConnection = function(di, newSource, newTarget) {
48307
48308 if (di.sourceElement && di.sourceElement.bpmnElement !== newSource) {
48309 di.sourceElement = newSource && newSource.di;
48310 }
48311
48312 if (di.targetElement && di.targetElement.bpmnElement !== newTarget) {
48313 di.targetElement = newTarget && newTarget.di;
48314 }
48315
48316 };
48317
48318
48319 BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
48320
48321 if (parentDi && !is$1(parentDi, 'bpmndi:BPMNPlane')) {
48322 parentDi = parentDi.$parent;
48323 }
48324
48325 if (di.$parent === parentDi) {
48326 return;
48327 }
48328
48329 var planeElements = (parentDi || di.$parent).get('planeElement');
48330
48331 if (parentDi) {
48332 planeElements.push(di);
48333 di.$parent = parentDi;
48334 } else {
48335 remove$2(planeElements, di);
48336 di.$parent = null;
48337 }
48338 };
48339
48340 function getDefinitions(element) {
48341 while (element && !is$1(element, 'bpmn:Definitions')) {
48342 element = element.$parent;
48343 }
48344
48345 return element;
48346 }
48347
48348 BpmnUpdater.prototype.getLaneSet = function(container) {
48349
48350 var laneSet, laneSets;
48351
48352 // bpmn:Lane
48353 if (is$1(container, 'bpmn:Lane')) {
48354 laneSet = container.childLaneSet;
48355
48356 if (!laneSet) {
48357 laneSet = this._bpmnFactory.create('bpmn:LaneSet');
48358 container.childLaneSet = laneSet;
48359 laneSet.$parent = container;
48360 }
48361
48362 return laneSet;
48363 }
48364
48365 // bpmn:Participant
48366 if (is$1(container, 'bpmn:Participant')) {
48367 container = container.processRef;
48368 }
48369
48370 // bpmn:FlowElementsContainer
48371 laneSets = container.get('laneSets');
48372 laneSet = laneSets[0];
48373
48374 if (!laneSet) {
48375 laneSet = this._bpmnFactory.create('bpmn:LaneSet');
48376 laneSet.$parent = container;
48377 laneSets.push(laneSet);
48378 }
48379
48380 return laneSet;
48381 };
48382
48383 BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
48384
48385 var containment,
48386 translate = this._translate;
48387
48388 if (businessObject.$parent === newParent) {
48389 return;
48390 }
48391
48392 if (is$1(businessObject, 'bpmn:DataInput') || is$1(businessObject, 'bpmn:DataOutput')) {
48393
48394 if (is$1(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
48395 newParent = newParent.processRef;
48396 }
48397
48398 // already in correct ioSpecification
48399 if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
48400 return;
48401 }
48402 }
48403
48404 if (is$1(businessObject, 'bpmn:Lane')) {
48405
48406 if (newParent) {
48407 newParent = this.getLaneSet(newParent);
48408 }
48409
48410 containment = 'lanes';
48411 } else
48412
48413 if (is$1(businessObject, 'bpmn:FlowElement')) {
48414
48415 if (newParent) {
48416
48417 if (is$1(newParent, 'bpmn:Participant')) {
48418 newParent = newParent.processRef;
48419 } else
48420
48421 if (is$1(newParent, 'bpmn:Lane')) {
48422 do {
48423
48424 // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
48425 newParent = newParent.$parent.$parent;
48426 } while (is$1(newParent, 'bpmn:Lane'));
48427
48428 }
48429 }
48430
48431 containment = 'flowElements';
48432
48433 } else
48434
48435 if (is$1(businessObject, 'bpmn:Artifact')) {
48436
48437 while (newParent &&
48438 !is$1(newParent, 'bpmn:Process') &&
48439 !is$1(newParent, 'bpmn:SubProcess') &&
48440 !is$1(newParent, 'bpmn:Collaboration')) {
48441
48442 if (is$1(newParent, 'bpmn:Participant')) {
48443 newParent = newParent.processRef;
48444 break;
48445 } else {
48446 newParent = newParent.$parent;
48447 }
48448 }
48449
48450 containment = 'artifacts';
48451 } else
48452
48453 if (is$1(businessObject, 'bpmn:MessageFlow')) {
48454 containment = 'messageFlows';
48455
48456 } else
48457
48458 if (is$1(businessObject, 'bpmn:Participant')) {
48459 containment = 'participants';
48460
48461 // make sure the participants process is properly attached / detached
48462 // from the XML document
48463
48464 var process = businessObject.processRef,
48465 definitions;
48466
48467 if (process) {
48468 definitions = getDefinitions(businessObject.$parent || newParent);
48469
48470 if (businessObject.$parent) {
48471 remove$2(definitions.get('rootElements'), process);
48472 process.$parent = null;
48473 }
48474
48475 if (newParent) {
48476 add$1(definitions.get('rootElements'), process);
48477 process.$parent = definitions;
48478 }
48479 }
48480 } else
48481
48482 if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
48483 containment = 'dataOutputAssociations';
48484 } else
48485
48486 if (is$1(businessObject, 'bpmn:DataInputAssociation')) {
48487 containment = 'dataInputAssociations';
48488 }
48489
48490 if (!containment) {
48491 throw new Error(translate(
48492 'no parent for {element} in {parent}',
48493 {
48494 element: businessObject.id,
48495 parent: newParent.id
48496 }
48497 ));
48498 }
48499
48500 var children;
48501
48502 if (businessObject.$parent) {
48503
48504 // remove from old parent
48505 children = businessObject.$parent.get(containment);
48506 remove$2(children, businessObject);
48507 }
48508
48509 if (!newParent) {
48510 businessObject.$parent = null;
48511 } else {
48512
48513 // add to new parent
48514 children = newParent.get(containment);
48515 children.push(businessObject);
48516 businessObject.$parent = newParent;
48517 }
48518
48519 if (visualParent) {
48520 var diChildren = visualParent.get(containment);
48521
48522 remove$2(children, businessObject);
48523
48524 if (newParent) {
48525
48526 if (!diChildren) {
48527 diChildren = [];
48528 newParent.set(containment, diChildren);
48529 }
48530
48531 diChildren.push(businessObject);
48532 }
48533 }
48534 };
48535
48536
48537 BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
48538 connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
48539 };
48540
48541
48542 BpmnUpdater.prototype.updateConnection = function(context) {
48543
48544 var connection = context.connection,
48545 businessObject = getBusinessObject(connection),
48546 newSource = getBusinessObject(connection.source),
48547 newTarget = getBusinessObject(connection.target),
48548 visualParent;
48549
48550 if (!is$1(businessObject, 'bpmn:DataAssociation')) {
48551
48552 var inverseSet = is$1(businessObject, 'bpmn:SequenceFlow');
48553
48554 if (businessObject.sourceRef !== newSource) {
48555 if (inverseSet) {
48556 remove$2(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
48557
48558 if (newSource && newSource.get('outgoing')) {
48559 newSource.get('outgoing').push(businessObject);
48560 }
48561 }
48562
48563 businessObject.sourceRef = newSource;
48564 }
48565
48566 if (businessObject.targetRef !== newTarget) {
48567 if (inverseSet) {
48568 remove$2(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
48569
48570 if (newTarget && newTarget.get('incoming')) {
48571 newTarget.get('incoming').push(businessObject);
48572 }
48573 }
48574
48575 businessObject.targetRef = newTarget;
48576 }
48577 } else
48578
48579 if (is$1(businessObject, 'bpmn:DataInputAssociation')) {
48580
48581 // handle obnoxious isMsome sourceRef
48582 businessObject.get('sourceRef')[0] = newSource;
48583
48584 visualParent = context.parent || context.newParent || newTarget;
48585
48586 this.updateSemanticParent(businessObject, newTarget, visualParent);
48587 } else
48588
48589 if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
48590 visualParent = context.parent || context.newParent || newSource;
48591
48592 this.updateSemanticParent(businessObject, newSource, visualParent);
48593
48594 // targetRef = new target
48595 businessObject.targetRef = newTarget;
48596 }
48597
48598 this.updateConnectionWaypoints(connection);
48599
48600 this.updateDiConnection(businessObject.di, newSource, newTarget);
48601 };
48602
48603
48604 // helpers //////////////////////
48605
48606 BpmnUpdater.prototype._getLabel = function(di) {
48607 if (!di.label) {
48608 di.label = this._bpmnFactory.createDiLabel();
48609 }
48610
48611 return di.label;
48612 };
48613
48614
48615 /**
48616 * Make sure the event listener is only called
48617 * if the touched element is a BPMN element.
48618 *
48619 * @param {Function} fn
48620 * @return {Function} guarded function
48621 */
48622 function ifBpmn(fn) {
48623
48624 return function(event) {
48625
48626 var context = event.context,
48627 element = context.shape || context.connection;
48628
48629 if (is$1(element, 'bpmn:BaseElement')) {
48630 fn(event);
48631 }
48632 };
48633 }
48634
48635 /**
48636 * A bpmn-aware factory for diagram-js shapes
48637 */
48638 function ElementFactory$1(bpmnFactory, moddle, translate) {
48639 ElementFactory.call(this);
48640
48641 this._bpmnFactory = bpmnFactory;
48642 this._moddle = moddle;
48643 this._translate = translate;
48644 }
48645
48646 inherits_browser(ElementFactory$1, ElementFactory);
48647
48648 ElementFactory$1.$inject = [
48649 'bpmnFactory',
48650 'moddle',
48651 'translate'
48652 ];
48653
48654 ElementFactory$1.prototype.baseCreate = ElementFactory.prototype.create;
48655
48656 ElementFactory$1.prototype.create = function(elementType, attrs) {
48657
48658 // no special magic for labels,
48659 // we assume their businessObjects have already been created
48660 // and wired via attrs
48661 if (elementType === 'label') {
48662 return this.baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE$1, attrs));
48663 }
48664
48665 return this.createBpmnElement(elementType, attrs);
48666 };
48667
48668 ElementFactory$1.prototype.createBpmnElement = function(elementType, attrs) {
48669 var size,
48670 translate = this._translate;
48671
48672 attrs = attrs || {};
48673
48674 var businessObject = attrs.businessObject;
48675
48676 if (!businessObject) {
48677 if (!attrs.type) {
48678 throw new Error(translate('no shape type specified'));
48679 }
48680
48681 businessObject = this._bpmnFactory.create(attrs.type);
48682 }
48683
48684 if (!businessObject.di) {
48685 if (elementType === 'root') {
48686 businessObject.di = this._bpmnFactory.createDiPlane(businessObject, [], {
48687 id: businessObject.id + '_di'
48688 });
48689 } else
48690 if (elementType === 'connection') {
48691 businessObject.di = this._bpmnFactory.createDiEdge(businessObject, [], {
48692 id: businessObject.id + '_di'
48693 });
48694 } else {
48695 businessObject.di = this._bpmnFactory.createDiShape(businessObject, {}, {
48696 id: businessObject.id + '_di'
48697 });
48698 }
48699 }
48700
48701 if (is$1(businessObject, 'bpmn:Group')) {
48702 attrs = assign({
48703 isFrame: true
48704 }, attrs);
48705 }
48706
48707 if (attrs.di) {
48708 assign(businessObject.di, attrs.di);
48709
48710 delete attrs.di;
48711 }
48712
48713 applyAttributes(businessObject, attrs, [
48714 'processRef',
48715 'isInterrupting',
48716 'associationDirection',
48717 'isForCompensation'
48718 ]);
48719
48720 if (attrs.isExpanded) {
48721 applyAttribute(businessObject.di, attrs, 'isExpanded');
48722 }
48723
48724 if (is$1(businessObject, 'bpmn:ExclusiveGateway')) {
48725 businessObject.di.isMarkerVisible = true;
48726 }
48727
48728 var eventDefinitions,
48729 newEventDefinition;
48730
48731 if (attrs.eventDefinitionType) {
48732 eventDefinitions = businessObject.get('eventDefinitions') || [];
48733 newEventDefinition = this._bpmnFactory.create(attrs.eventDefinitionType, attrs.eventDefinitionAttrs);
48734
48735 if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
48736 newEventDefinition.condition = this._bpmnFactory.create('bpmn:FormalExpression');
48737 }
48738
48739 eventDefinitions.push(newEventDefinition);
48740
48741 newEventDefinition.$parent = businessObject;
48742 businessObject.eventDefinitions = eventDefinitions;
48743
48744 delete attrs.eventDefinitionType;
48745 }
48746
48747 size = this._getDefaultSize(businessObject);
48748
48749 attrs = assign({
48750 businessObject: businessObject,
48751 id: businessObject.id
48752 }, size, attrs);
48753
48754 return this.baseCreate(elementType, attrs);
48755 };
48756
48757
48758 ElementFactory$1.prototype._getDefaultSize = function(semantic) {
48759
48760 if (is$1(semantic, 'bpmn:SubProcess')) {
48761
48762 if (isExpanded(semantic)) {
48763 return { width: 350, height: 200 };
48764 } else {
48765 return { width: 100, height: 80 };
48766 }
48767 }
48768
48769 if (is$1(semantic, 'bpmn:Task')) {
48770 return { width: 100, height: 80 };
48771 }
48772
48773 if (is$1(semantic, 'bpmn:Gateway')) {
48774 return { width: 50, height: 50 };
48775 }
48776
48777 if (is$1(semantic, 'bpmn:Event')) {
48778 return { width: 36, height: 36 };
48779 }
48780
48781 if (is$1(semantic, 'bpmn:Participant')) {
48782 if (isExpanded(semantic)) {
48783 return { width: 600, height: 250 };
48784 } else {
48785 return { width: 400, height: 60 };
48786 }
48787 }
48788
48789 if (is$1(semantic, 'bpmn:Lane')) {
48790 return { width: 400, height: 100 };
48791 }
48792
48793 if (is$1(semantic, 'bpmn:DataObjectReference')) {
48794 return { width: 36, height: 50 };
48795 }
48796
48797 if (is$1(semantic, 'bpmn:DataStoreReference')) {
48798 return { width: 50, height: 50 };
48799 }
48800
48801 if (is$1(semantic, 'bpmn:TextAnnotation')) {
48802 return { width: 100, height: 30 };
48803 }
48804
48805 if (is$1(semantic, 'bpmn:Group')) {
48806 return { width: 300, height: 300 };
48807 }
48808
48809 return { width: 100, height: 80 };
48810 };
48811
48812
48813 /**
48814 * Create participant.
48815 *
48816 * @param {boolean|Object} [attrs] attrs
48817 *
48818 * @returns {djs.model.Shape}
48819 */
48820 ElementFactory$1.prototype.createParticipantShape = function(attrs) {
48821
48822 if (!isObject(attrs)) {
48823 attrs = { isExpanded: attrs };
48824 }
48825
48826 attrs = assign({ type: 'bpmn:Participant' }, attrs || {});
48827
48828 // participants are expanded by default
48829 if (attrs.isExpanded !== false) {
48830 attrs.processRef = this._bpmnFactory.create('bpmn:Process');
48831 }
48832
48833 return this.createShape(attrs);
48834 };
48835
48836
48837 // helpers //////////////////////
48838
48839 /**
48840 * Apply attributes from a map to the given element,
48841 * remove attribute from the map on application.
48842 *
48843 * @param {Base} element
48844 * @param {Object} attrs (in/out map of attributes)
48845 * @param {Array<string>} attributeNames name of attributes to apply
48846 */
48847 function applyAttributes(element, attrs, attributeNames) {
48848
48849 forEach(attributeNames, function(property) {
48850 if (attrs[property] !== undefined) {
48851 applyAttribute(element, attrs, property);
48852 }
48853 });
48854 }
48855
48856 /**
48857 * Apply named property to element and drain it from the attrs
48858 * collection.
48859 *
48860 * @param {Base} element
48861 * @param {Object} attrs (in/out map of attributes)
48862 * @param {string} attributeName to apply
48863 */
48864 function applyAttribute(element, attrs, attributeName) {
48865 element[attributeName] = attrs[attributeName];
48866
48867 delete attrs[attributeName];
48868 }
48869
48870 /**
48871 * A handler that align elements in a certain way.
48872 *
48873 */
48874 function AlignElements$1(modeling, canvas) {
48875 this._modeling = modeling;
48876 this._canvas = canvas;
48877 }
48878
48879 AlignElements$1.$inject = [ 'modeling', 'canvas' ];
48880
48881
48882 AlignElements$1.prototype.preExecute = function(context) {
48883 var modeling = this._modeling;
48884
48885 var elements = context.elements,
48886 alignment = context.alignment;
48887
48888
48889 forEach(elements, function(element) {
48890 var delta = {
48891 x: 0,
48892 y: 0
48893 };
48894
48895 if (alignment.left) {
48896 delta.x = alignment.left - element.x;
48897
48898 } else if (alignment.right) {
48899 delta.x = (alignment.right - element.width) - element.x;
48900
48901 } else if (alignment.center) {
48902 delta.x = (alignment.center - Math.round(element.width / 2)) - element.x;
48903
48904 } else if (alignment.top) {
48905 delta.y = alignment.top - element.y;
48906
48907 } else if (alignment.bottom) {
48908 delta.y = (alignment.bottom - element.height) - element.y;
48909
48910 } else if (alignment.middle) {
48911 delta.y = (alignment.middle - Math.round(element.height / 2)) - element.y;
48912 }
48913
48914 modeling.moveElements([ element ], delta, element.parent);
48915 });
48916 };
48917
48918 AlignElements$1.prototype.postExecute = function(context) {
48919
48920 };
48921
48922 /**
48923 * A handler that implements reversible appending of shapes
48924 * to a source shape.
48925 *
48926 * @param {canvas} Canvas
48927 * @param {elementFactory} ElementFactory
48928 * @param {modeling} Modeling
48929 */
48930 function AppendShapeHandler(modeling) {
48931 this._modeling = modeling;
48932 }
48933
48934 AppendShapeHandler.$inject = [ 'modeling' ];
48935
48936
48937 // api //////////////////////
48938
48939
48940 /**
48941 * Creates a new shape
48942 *
48943 * @param {Object} context
48944 * @param {ElementDescriptor} context.shape the new shape
48945 * @param {ElementDescriptor} context.source the source object
48946 * @param {ElementDescriptor} context.parent the parent object
48947 * @param {Point} context.position position of the new element
48948 */
48949 AppendShapeHandler.prototype.preExecute = function(context) {
48950
48951 var source = context.source;
48952
48953 if (!source) {
48954 throw new Error('source required');
48955 }
48956
48957 var target = context.target || source.parent,
48958 shape = context.shape,
48959 hints = context.hints || {};
48960
48961 shape = context.shape =
48962 this._modeling.createShape(
48963 shape,
48964 context.position,
48965 target, { attach: hints.attach });
48966
48967 context.shape = shape;
48968 };
48969
48970 AppendShapeHandler.prototype.postExecute = function(context) {
48971 var hints = context.hints || {};
48972
48973 if (!existsConnection(context.source, context.shape)) {
48974
48975 // create connection
48976 if (hints.connectionTarget === context.source) {
48977 this._modeling.connect(context.shape, context.source, context.connection);
48978 } else {
48979 this._modeling.connect(context.source, context.shape, context.connection);
48980 }
48981 }
48982 };
48983
48984
48985 function existsConnection(source, target) {
48986 return some(source.outgoing, function(c) {
48987 return c.target === target;
48988 });
48989 }
48990
48991 function CreateConnectionHandler(canvas, layouter) {
48992 this._canvas = canvas;
48993 this._layouter = layouter;
48994 }
48995
48996 CreateConnectionHandler.$inject = [ 'canvas', 'layouter' ];
48997
48998
48999 // api //////////////////////
49000
49001
49002 /**
49003 * Appends a shape to a target shape
49004 *
49005 * @param {Object} context
49006 * @param {djs.element.Base} context.source the source object
49007 * @param {djs.element.Base} context.target the parent object
49008 * @param {Point} context.position position of the new element
49009 */
49010 CreateConnectionHandler.prototype.execute = function(context) {
49011
49012 var connection = context.connection,
49013 source = context.source,
49014 target = context.target,
49015 parent = context.parent,
49016 parentIndex = context.parentIndex,
49017 hints = context.hints;
49018
49019 if (!source || !target) {
49020 throw new Error('source and target required');
49021 }
49022
49023 if (!parent) {
49024 throw new Error('parent required');
49025 }
49026
49027 connection.source = source;
49028 connection.target = target;
49029
49030 if (!connection.waypoints) {
49031 connection.waypoints = this._layouter.layoutConnection(connection, hints);
49032 }
49033
49034 // add connection
49035 this._canvas.addConnection(connection, parent, parentIndex);
49036
49037 return connection;
49038 };
49039
49040 CreateConnectionHandler.prototype.revert = function(context) {
49041 var connection = context.connection;
49042
49043 this._canvas.removeConnection(connection);
49044
49045 connection.source = null;
49046 connection.target = null;
49047
49048 return connection;
49049 };
49050
49051 var round$8 = Math.round;
49052
49053 function CreateElementsHandler(modeling) {
49054 this._modeling = modeling;
49055 }
49056
49057 CreateElementsHandler.$inject = [
49058 'modeling'
49059 ];
49060
49061 CreateElementsHandler.prototype.preExecute = function(context) {
49062 var elements = context.elements,
49063 parent = context.parent,
49064 parentIndex = context.parentIndex,
49065 position = context.position,
49066 hints = context.hints;
49067
49068 var modeling = this._modeling;
49069
49070 // make sure each element has x and y
49071 forEach(elements, function(element) {
49072 if (!isNumber(element.x)) {
49073 element.x = 0;
49074 }
49075
49076 if (!isNumber(element.y)) {
49077 element.y = 0;
49078 }
49079 });
49080
49081 var bbox = getBBox(elements);
49082
49083 // center elements around position
49084 forEach(elements, function(element) {
49085 if (isConnection$6(element)) {
49086 element.waypoints = map(element.waypoints, function(waypoint) {
49087 return {
49088 x: round$8(waypoint.x - bbox.x - bbox.width / 2 + position.x),
49089 y: round$8(waypoint.y - bbox.y - bbox.height / 2 + position.y)
49090 };
49091 });
49092 }
49093
49094 assign(element, {
49095 x: round$8(element.x - bbox.x - bbox.width / 2 + position.x),
49096 y: round$8(element.y - bbox.y - bbox.height / 2 + position.y)
49097 });
49098 });
49099
49100 var parents = getParents(elements);
49101
49102 var cache = {};
49103
49104 forEach(elements, function(element) {
49105 if (isConnection$6(element)) {
49106 cache[ element.id ] = isNumber(parentIndex) ?
49107 modeling.createConnection(
49108 cache[ element.source.id ],
49109 cache[ element.target.id ],
49110 parentIndex,
49111 element,
49112 element.parent || parent,
49113 hints
49114 ) :
49115 modeling.createConnection(
49116 cache[ element.source.id ],
49117 cache[ element.target.id ],
49118 element,
49119 element.parent || parent,
49120 hints
49121 );
49122
49123 return;
49124 }
49125
49126 var createShapeHints = assign({}, hints);
49127
49128 if (parents.indexOf(element) === -1) {
49129 createShapeHints.autoResize = false;
49130 }
49131
49132 cache[ element.id ] = isNumber(parentIndex) ?
49133 modeling.createShape(
49134 element,
49135 pick(element, [ 'x', 'y', 'width', 'height' ]),
49136 element.parent || parent,
49137 parentIndex,
49138 createShapeHints
49139 ) :
49140 modeling.createShape(
49141 element,
49142 pick(element, [ 'x', 'y', 'width', 'height' ]),
49143 element.parent || parent,
49144 createShapeHints
49145 );
49146 });
49147
49148 context.elements = values(cache);
49149 };
49150
49151 // helpers //////////
49152
49153 function isConnection$6(element) {
49154 return !!element.waypoints;
49155 }
49156
49157 var round$9 = Math.round;
49158
49159
49160 /**
49161 * A handler that implements reversible addition of shapes.
49162 *
49163 * @param {canvas} Canvas
49164 */
49165 function CreateShapeHandler(canvas) {
49166 this._canvas = canvas;
49167 }
49168
49169 CreateShapeHandler.$inject = [ 'canvas' ];
49170
49171
49172 // api //////////////////////
49173
49174
49175 /**
49176 * Appends a shape to a target shape
49177 *
49178 * @param {Object} context
49179 * @param {djs.model.Base} context.parent the parent object
49180 * @param {Point} context.position position of the new element
49181 */
49182 CreateShapeHandler.prototype.execute = function(context) {
49183
49184 var shape = context.shape,
49185 positionOrBounds = context.position,
49186 parent = context.parent,
49187 parentIndex = context.parentIndex;
49188
49189 if (!parent) {
49190 throw new Error('parent required');
49191 }
49192
49193 if (!positionOrBounds) {
49194 throw new Error('position required');
49195 }
49196
49197 // (1) add at event center position _or_ at given bounds
49198 if (positionOrBounds.width !== undefined) {
49199 assign(shape, positionOrBounds);
49200 } else {
49201 assign(shape, {
49202 x: positionOrBounds.x - round$9(shape.width / 2),
49203 y: positionOrBounds.y - round$9(shape.height / 2)
49204 });
49205 }
49206
49207 // (2) add to canvas
49208 this._canvas.addShape(shape, parent, parentIndex);
49209
49210 return shape;
49211 };
49212
49213
49214 /**
49215 * Undo append by removing the shape
49216 */
49217 CreateShapeHandler.prototype.revert = function(context) {
49218
49219 var shape = context.shape;
49220
49221 // (3) remove form canvas
49222 this._canvas.removeShape(shape);
49223
49224 return shape;
49225 };
49226
49227 /**
49228 * A handler that attaches a label to a given target shape.
49229 *
49230 * @param {Canvas} canvas
49231 */
49232 function CreateLabelHandler(canvas) {
49233 CreateShapeHandler.call(this, canvas);
49234 }
49235
49236 inherits_browser(CreateLabelHandler, CreateShapeHandler);
49237
49238 CreateLabelHandler.$inject = [ 'canvas' ];
49239
49240
49241 // api //////////////////////
49242
49243
49244 var originalExecute = CreateShapeHandler.prototype.execute;
49245
49246 /**
49247 * Appends a label to a target shape.
49248 *
49249 * @method CreateLabelHandler#execute
49250 *
49251 * @param {Object} context
49252 * @param {ElementDescriptor} context.target the element the label is attached to
49253 * @param {ElementDescriptor} context.parent the parent object
49254 * @param {Point} context.position position of the new element
49255 */
49256 CreateLabelHandler.prototype.execute = function(context) {
49257
49258 var label = context.shape;
49259
49260 ensureValidDimensions(label);
49261
49262 label.labelTarget = context.labelTarget;
49263
49264 return originalExecute.call(this, context);
49265 };
49266
49267 var originalRevert = CreateShapeHandler.prototype.revert;
49268
49269 /**
49270 * Undo append by removing the shape
49271 */
49272 CreateLabelHandler.prototype.revert = function(context) {
49273 context.shape.labelTarget = null;
49274
49275 return originalRevert.call(this, context);
49276 };
49277
49278
49279 // helpers //////////////////////
49280
49281 function ensureValidDimensions(label) {
49282
49283 // make sure a label has valid { width, height } dimensions
49284 [ 'width', 'height' ].forEach(function(prop) {
49285 if (typeof label[prop] === 'undefined') {
49286 label[prop] = 0;
49287 }
49288 });
49289 }
49290
49291 /**
49292 * A handler that implements reversible deletion of Connections.
49293 */
49294 function DeleteConnectionHandler(canvas, modeling) {
49295 this._canvas = canvas;
49296 this._modeling = modeling;
49297 }
49298
49299 DeleteConnectionHandler.$inject = [
49300 'canvas',
49301 'modeling'
49302 ];
49303
49304
49305 DeleteConnectionHandler.prototype.execute = function(context) {
49306
49307 var connection = context.connection,
49308 parent = connection.parent;
49309
49310 context.parent = parent;
49311
49312 // remember containment
49313 context.parentIndex = indexOf$1(parent.children, connection);
49314
49315 context.source = connection.source;
49316 context.target = connection.target;
49317
49318 this._canvas.removeConnection(connection);
49319
49320 connection.source = null;
49321 connection.target = null;
49322
49323 return connection;
49324 };
49325
49326 /**
49327 * Command revert implementation.
49328 */
49329 DeleteConnectionHandler.prototype.revert = function(context) {
49330
49331 var connection = context.connection,
49332 parent = context.parent,
49333 parentIndex = context.parentIndex;
49334
49335 connection.source = context.source;
49336 connection.target = context.target;
49337
49338 // restore containment
49339 add$1(parent.children, connection, parentIndex);
49340
49341 this._canvas.addConnection(connection, parent);
49342
49343 return connection;
49344 };
49345
49346 function DeleteElementsHandler(modeling, elementRegistry) {
49347 this._modeling = modeling;
49348 this._elementRegistry = elementRegistry;
49349 }
49350
49351 DeleteElementsHandler.$inject = [
49352 'modeling',
49353 'elementRegistry'
49354 ];
49355
49356
49357 DeleteElementsHandler.prototype.postExecute = function(context) {
49358
49359 var modeling = this._modeling,
49360 elementRegistry = this._elementRegistry,
49361 elements = context.elements;
49362
49363 forEach(elements, function(element) {
49364
49365 // element may have been removed with previous
49366 // remove operations already (e.g. in case of nesting)
49367 if (!elementRegistry.get(element.id)) {
49368 return;
49369 }
49370
49371 if (element.waypoints) {
49372 modeling.removeConnection(element);
49373 } else {
49374 modeling.removeShape(element);
49375 }
49376 });
49377 };
49378
49379 /**
49380 * A handler that implements reversible deletion of shapes.
49381 *
49382 */
49383 function DeleteShapeHandler(canvas, modeling) {
49384 this._canvas = canvas;
49385 this._modeling = modeling;
49386 }
49387
49388 DeleteShapeHandler.$inject = [ 'canvas', 'modeling' ];
49389
49390
49391 /**
49392 * - Remove connections
49393 * - Remove all direct children
49394 */
49395 DeleteShapeHandler.prototype.preExecute = function(context) {
49396
49397 var modeling = this._modeling;
49398
49399 var shape = context.shape;
49400
49401 // remove connections
49402 saveClear(shape.incoming, function(connection) {
49403
49404 // To make sure that the connection isn't removed twice
49405 // For example if a container is removed
49406 modeling.removeConnection(connection, { nested: true });
49407 });
49408
49409 saveClear(shape.outgoing, function(connection) {
49410 modeling.removeConnection(connection, { nested: true });
49411 });
49412
49413 // remove child shapes and connections
49414 saveClear(shape.children, function(child) {
49415 if (isConnection$7(child)) {
49416 modeling.removeConnection(child, { nested: true });
49417 } else {
49418 modeling.removeShape(child, { nested: true });
49419 }
49420 });
49421 };
49422
49423 /**
49424 * Remove shape and remember the parent
49425 */
49426 DeleteShapeHandler.prototype.execute = function(context) {
49427 var canvas = this._canvas;
49428
49429 var shape = context.shape,
49430 oldParent = shape.parent;
49431
49432 context.oldParent = oldParent;
49433
49434 // remove containment
49435 context.oldParentIndex = indexOf$1(oldParent.children, shape);
49436
49437 // remove shape
49438 canvas.removeShape(shape);
49439
49440 return shape;
49441 };
49442
49443
49444 /**
49445 * Command revert implementation
49446 */
49447 DeleteShapeHandler.prototype.revert = function(context) {
49448
49449 var canvas = this._canvas;
49450
49451 var shape = context.shape,
49452 oldParent = context.oldParent,
49453 oldParentIndex = context.oldParentIndex;
49454
49455 // restore containment
49456 add$1(oldParent.children, shape, oldParentIndex);
49457
49458 canvas.addShape(shape, oldParent);
49459
49460 return shape;
49461 };
49462
49463 function isConnection$7(element) {
49464 return element.waypoints;
49465 }
49466
49467 /**
49468 * A handler that distributes elements evenly.
49469 */
49470 function DistributeElements$1(modeling) {
49471 this._modeling = modeling;
49472 }
49473
49474 DistributeElements$1.$inject = [ 'modeling' ];
49475
49476 var OFF_AXIS = {
49477 x: 'y',
49478 y: 'x'
49479 };
49480
49481 DistributeElements$1.prototype.preExecute = function(context) {
49482 var modeling = this._modeling;
49483
49484 var groups = context.groups,
49485 axis = context.axis,
49486 dimension = context.dimension;
49487
49488 function updateRange(group, element) {
49489 group.range.min = Math.min(element[axis], group.range.min);
49490 group.range.max = Math.max(element[axis] + element[dimension], group.range.max);
49491 }
49492
49493 function center(element) {
49494 return element[axis] + element[dimension] / 2;
49495 }
49496
49497 function lastIdx(arr) {
49498 return arr.length - 1;
49499 }
49500
49501 function rangeDiff(range) {
49502 return range.max - range.min;
49503 }
49504
49505 function centerElement(refCenter, element) {
49506 var delta = { y: 0 };
49507
49508 delta[axis] = refCenter - center(element);
49509
49510 if (delta[axis]) {
49511
49512 delta[OFF_AXIS[axis]] = 0;
49513
49514 modeling.moveElements([ element ], delta, element.parent);
49515 }
49516 }
49517
49518 var firstGroup = groups[0],
49519 lastGroupIdx = lastIdx(groups),
49520 lastGroup = groups[ lastGroupIdx ];
49521
49522 var margin,
49523 spaceInBetween,
49524 groupsSize = 0; // the size of each range
49525
49526 forEach(groups, function(group, idx) {
49527 var sortedElements,
49528 refElem,
49529 refCenter;
49530
49531 if (group.elements.length < 2) {
49532 if (idx && idx !== groups.length - 1) {
49533 updateRange(group, group.elements[0]);
49534
49535 groupsSize += rangeDiff(group.range);
49536 }
49537 return;
49538 }
49539
49540 sortedElements = sortBy(group.elements, axis);
49541
49542 refElem = sortedElements[0];
49543
49544 if (idx === lastGroupIdx) {
49545 refElem = sortedElements[lastIdx(sortedElements)];
49546 }
49547
49548 refCenter = center(refElem);
49549
49550 // wanna update the ranges after the shapes have been centered
49551 group.range = null;
49552
49553 forEach(sortedElements, function(element) {
49554
49555 centerElement(refCenter, element);
49556
49557 if (group.range === null) {
49558 group.range = {
49559 min: element[axis],
49560 max: element[axis] + element[dimension]
49561 };
49562
49563 return;
49564 }
49565
49566 // update group's range after centering the range elements
49567 updateRange(group, element);
49568 });
49569
49570 if (idx && idx !== groups.length - 1) {
49571 groupsSize += rangeDiff(group.range);
49572 }
49573 });
49574
49575 spaceInBetween = Math.abs(lastGroup.range.min - firstGroup.range.max);
49576
49577 margin = Math.round((spaceInBetween - groupsSize) / (groups.length - 1));
49578
49579 if (margin < groups.length - 1) {
49580 return;
49581 }
49582
49583 forEach(groups, function(group, groupIdx) {
49584 var delta = {},
49585 prevGroup;
49586
49587 if (group === firstGroup || group === lastGroup) {
49588 return;
49589 }
49590
49591 prevGroup = groups[groupIdx - 1];
49592
49593 group.range.max = 0;
49594
49595 forEach(group.elements, function(element, idx) {
49596 delta[OFF_AXIS[axis]] = 0;
49597 delta[axis] = (prevGroup.range.max - element[axis]) + margin;
49598
49599 if (group.range.min !== element[axis]) {
49600 delta[axis] += element[axis] - group.range.min;
49601 }
49602
49603 if (delta[axis]) {
49604 modeling.moveElements([ element ], delta, element.parent);
49605 }
49606
49607 group.range.max = Math.max(element[axis] + element[dimension], idx ? group.range.max : 0);
49608 });
49609 });
49610 };
49611
49612 DistributeElements$1.prototype.postExecute = function(context) {
49613
49614 };
49615
49616 /**
49617 * A handler that implements reversible moving of shapes.
49618 */
49619 function LayoutConnectionHandler(layouter, canvas) {
49620 this._layouter = layouter;
49621 this._canvas = canvas;
49622 }
49623
49624 LayoutConnectionHandler.$inject = [ 'layouter', 'canvas' ];
49625
49626 LayoutConnectionHandler.prototype.execute = function(context) {
49627
49628 var connection = context.connection;
49629
49630 var oldWaypoints = connection.waypoints;
49631
49632 assign(context, {
49633 oldWaypoints: oldWaypoints
49634 });
49635
49636 connection.waypoints = this._layouter.layoutConnection(connection, context.hints);
49637
49638 return connection;
49639 };
49640
49641 LayoutConnectionHandler.prototype.revert = function(context) {
49642
49643 var connection = context.connection;
49644
49645 connection.waypoints = context.oldWaypoints;
49646
49647 return connection;
49648 };
49649
49650 /**
49651 * A handler that implements reversible moving of connections.
49652 *
49653 * The handler differs from the layout connection handler in a sense
49654 * that it preserves the connection layout.
49655 */
49656 function MoveConnectionHandler() { }
49657
49658
49659 MoveConnectionHandler.prototype.execute = function(context) {
49660
49661 var connection = context.connection,
49662 delta = context.delta;
49663
49664 var newParent = context.newParent || connection.parent,
49665 newParentIndex = context.newParentIndex,
49666 oldParent = connection.parent;
49667
49668 // save old parent in context
49669 context.oldParent = oldParent;
49670 context.oldParentIndex = remove$2(oldParent.children, connection);
49671
49672 // add to new parent at position
49673 add$1(newParent.children, connection, newParentIndex);
49674
49675 // update parent
49676 connection.parent = newParent;
49677
49678 // update waypoint positions
49679 forEach(connection.waypoints, function(p) {
49680 p.x += delta.x;
49681 p.y += delta.y;
49682
49683 if (p.original) {
49684 p.original.x += delta.x;
49685 p.original.y += delta.y;
49686 }
49687 });
49688
49689 return connection;
49690 };
49691
49692 MoveConnectionHandler.prototype.revert = function(context) {
49693
49694 var connection = context.connection,
49695 newParent = connection.parent,
49696 oldParent = context.oldParent,
49697 oldParentIndex = context.oldParentIndex,
49698 delta = context.delta;
49699
49700 // remove from newParent
49701 remove$2(newParent.children, connection);
49702
49703 // restore previous location in old parent
49704 add$1(oldParent.children, connection, oldParentIndex);
49705
49706 // restore parent
49707 connection.parent = oldParent;
49708
49709 // revert to old waypoint positions
49710 forEach(connection.waypoints, function(p) {
49711 p.x -= delta.x;
49712 p.y -= delta.y;
49713
49714 if (p.original) {
49715 p.original.x -= delta.x;
49716 p.original.y -= delta.y;
49717 }
49718 });
49719
49720 return connection;
49721 };
49722
49723 function MoveClosure() {
49724
49725 this.allShapes = {};
49726 this.allConnections = {};
49727
49728 this.enclosedElements = {};
49729 this.enclosedConnections = {};
49730
49731 this.topLevel = {};
49732 }
49733
49734
49735 MoveClosure.prototype.add = function(element, isTopLevel) {
49736 return this.addAll([ element ], isTopLevel);
49737 };
49738
49739
49740 MoveClosure.prototype.addAll = function(elements, isTopLevel) {
49741
49742 var newClosure = getClosure(elements, !!isTopLevel, this);
49743
49744 assign(this, newClosure);
49745
49746 return this;
49747 };
49748
49749 /**
49750 * A helper that is able to carry out serialized move
49751 * operations on multiple elements.
49752 *
49753 * @param {Modeling} modeling
49754 */
49755 function MoveHelper(modeling) {
49756 this._modeling = modeling;
49757 }
49758
49759 /**
49760 * Move the specified elements and all children by the given delta.
49761 *
49762 * This moves all enclosed connections, too and layouts all affected
49763 * external connections.
49764 *
49765 * @param {Array<djs.model.Base>} elements
49766 * @param {Point} delta
49767 * @param {djs.model.Base} newParent applied to the first level of shapes
49768 *
49769 * @return {Array<djs.model.Base>} list of touched elements
49770 */
49771 MoveHelper.prototype.moveRecursive = function(elements, delta, newParent) {
49772 if (!elements) {
49773 return [];
49774 } else {
49775 return this.moveClosure(this.getClosure(elements), delta, newParent);
49776 }
49777 };
49778
49779 /**
49780 * Move the given closure of elmements.
49781 *
49782 * @param {Object} closure
49783 * @param {Point} delta
49784 * @param {djs.model.Base} [newParent]
49785 * @param {djs.model.Base} [newHost]
49786 */
49787 MoveHelper.prototype.moveClosure = function(closure, delta, newParent, newHost, primaryShape) {
49788 var modeling = this._modeling;
49789
49790 var allShapes = closure.allShapes,
49791 allConnections = closure.allConnections,
49792 enclosedConnections = closure.enclosedConnections,
49793 topLevel = closure.topLevel,
49794 keepParent = false;
49795
49796 if (primaryShape && primaryShape.parent === newParent) {
49797 keepParent = true;
49798 }
49799
49800 // move all shapes
49801 forEach(allShapes, function(shape) {
49802
49803 // move the element according to the given delta
49804 modeling.moveShape(shape, delta, topLevel[shape.id] && !keepParent && newParent, {
49805 recurse: false,
49806 layout: false
49807 });
49808 });
49809
49810 // move all child connections / layout external connections
49811 forEach(allConnections, function(c) {
49812
49813 var sourceMoved = !!allShapes[c.source.id],
49814 targetMoved = !!allShapes[c.target.id];
49815
49816 if (enclosedConnections[c.id] && sourceMoved && targetMoved) {
49817 modeling.moveConnection(c, delta, topLevel[c.id] && !keepParent && newParent);
49818 } else {
49819 modeling.layoutConnection(c, {
49820 connectionStart: sourceMoved && getMovedSourceAnchor(c, c.source, delta),
49821 connectionEnd: targetMoved && getMovedTargetAnchor(c, c.target, delta)
49822 });
49823 }
49824 });
49825 };
49826
49827 /**
49828 * Returns the closure for the selected elements
49829 *
49830 * @param {Array<djs.model.Base>} elements
49831 * @return {MoveClosure} closure
49832 */
49833 MoveHelper.prototype.getClosure = function(elements) {
49834 return new MoveClosure().addAll(elements, true);
49835 };
49836
49837 /**
49838 * A handler that implements reversible moving of shapes.
49839 */
49840 function MoveElementsHandler(modeling) {
49841 this._helper = new MoveHelper(modeling);
49842 }
49843
49844 MoveElementsHandler.$inject = [ 'modeling' ];
49845
49846 MoveElementsHandler.prototype.preExecute = function(context) {
49847 context.closure = this._helper.getClosure(context.shapes);
49848 };
49849
49850 MoveElementsHandler.prototype.postExecute = function(context) {
49851
49852 var hints = context.hints,
49853 primaryShape;
49854
49855 if (hints && hints.primaryShape) {
49856 primaryShape = hints.primaryShape;
49857 hints.oldParent = primaryShape.parent;
49858 }
49859
49860 this._helper.moveClosure(
49861 context.closure,
49862 context.delta,
49863 context.newParent,
49864 context.newHost,
49865 primaryShape
49866 );
49867 };
49868
49869 /**
49870 * A handler that implements reversible moving of shapes.
49871 */
49872 function MoveShapeHandler(modeling) {
49873 this._modeling = modeling;
49874
49875 this._helper = new MoveHelper(modeling);
49876 }
49877
49878 MoveShapeHandler.$inject = [ 'modeling' ];
49879
49880
49881 MoveShapeHandler.prototype.execute = function(context) {
49882
49883 var shape = context.shape,
49884 delta = context.delta,
49885 newParent = context.newParent || shape.parent,
49886 newParentIndex = context.newParentIndex,
49887 oldParent = shape.parent;
49888
49889 context.oldBounds = pick(shape, [ 'x', 'y', 'width', 'height']);
49890
49891 // save old parent in context
49892 context.oldParent = oldParent;
49893 context.oldParentIndex = remove$2(oldParent.children, shape);
49894
49895 // add to new parent at position
49896 add$1(newParent.children, shape, newParentIndex);
49897
49898 // update shape parent + position
49899 assign(shape, {
49900 parent: newParent,
49901 x: shape.x + delta.x,
49902 y: shape.y + delta.y
49903 });
49904
49905 return shape;
49906 };
49907
49908 MoveShapeHandler.prototype.postExecute = function(context) {
49909
49910 var shape = context.shape,
49911 delta = context.delta,
49912 hints = context.hints;
49913
49914 var modeling = this._modeling;
49915
49916 if (hints.layout !== false) {
49917
49918 forEach(shape.incoming, function(c) {
49919 modeling.layoutConnection(c, {
49920 connectionEnd: getMovedTargetAnchor(c, shape, delta)
49921 });
49922 });
49923
49924 forEach(shape.outgoing, function(c) {
49925 modeling.layoutConnection(c, {
49926 connectionStart: getMovedSourceAnchor(c, shape, delta)
49927 });
49928 });
49929 }
49930
49931 if (hints.recurse !== false) {
49932 this.moveChildren(context);
49933 }
49934 };
49935
49936 MoveShapeHandler.prototype.revert = function(context) {
49937
49938 var shape = context.shape,
49939 oldParent = context.oldParent,
49940 oldParentIndex = context.oldParentIndex,
49941 delta = context.delta;
49942
49943 // restore previous location in old parent
49944 add$1(oldParent.children, shape, oldParentIndex);
49945
49946 // revert to old position and parent
49947 assign(shape, {
49948 parent: oldParent,
49949 x: shape.x - delta.x,
49950 y: shape.y - delta.y
49951 });
49952
49953 return shape;
49954 };
49955
49956 MoveShapeHandler.prototype.moveChildren = function(context) {
49957
49958 var delta = context.delta,
49959 shape = context.shape;
49960
49961 this._helper.moveRecursive(shape.children, delta, null);
49962 };
49963
49964 MoveShapeHandler.prototype.getNewParent = function(context) {
49965 return context.newParent || context.shape.parent;
49966 };
49967
49968 /**
49969 * Reconnect connection handler
49970 */
49971 function ReconnectConnectionHandler(modeling) {
49972 this._modeling = modeling;
49973 }
49974
49975 ReconnectConnectionHandler.$inject = [ 'modeling' ];
49976
49977 ReconnectConnectionHandler.prototype.execute = function(context) {
49978 var newSource = context.newSource,
49979 newTarget = context.newTarget,
49980 connection = context.connection,
49981 dockingOrPoints = context.dockingOrPoints;
49982
49983 if (!newSource && !newTarget) {
49984 throw new Error('newSource or newTarget required');
49985 }
49986
49987 if (isArray(dockingOrPoints)) {
49988 context.oldWaypoints = connection.waypoints;
49989 connection.waypoints = dockingOrPoints;
49990 }
49991
49992 if (newSource) {
49993 context.oldSource = connection.source;
49994 connection.source = newSource;
49995 }
49996
49997 if (newTarget) {
49998 context.oldTarget = connection.target;
49999 connection.target = newTarget;
50000 }
50001
50002 return connection;
50003 };
50004
50005 ReconnectConnectionHandler.prototype.postExecute = function(context) {
50006 var connection = context.connection,
50007 newSource = context.newSource,
50008 newTarget = context.newTarget,
50009 dockingOrPoints = context.dockingOrPoints,
50010 hints = context.hints || {};
50011
50012 var layoutConnectionHints = {};
50013
50014 if (hints.connectionStart) {
50015 layoutConnectionHints.connectionStart = hints.connectionStart;
50016 }
50017
50018 if (hints.connectionEnd) {
50019 layoutConnectionHints.connectionEnd = hints.connectionEnd;
50020 }
50021
50022 if (hints.layoutConnection === false) {
50023 return;
50024 }
50025
50026 if (newSource && (!newTarget || hints.docking === 'source')) {
50027 layoutConnectionHints.connectionStart = layoutConnectionHints.connectionStart
50028 || getDocking$2(isArray(dockingOrPoints) ? dockingOrPoints[ 0 ] : dockingOrPoints);
50029 }
50030
50031 if (newTarget && (!newSource || hints.docking === 'target')) {
50032 layoutConnectionHints.connectionEnd = layoutConnectionHints.connectionEnd
50033 || getDocking$2(isArray(dockingOrPoints) ? dockingOrPoints[ dockingOrPoints.length - 1 ] : dockingOrPoints);
50034 }
50035
50036 if (hints.newWaypoints) {
50037 layoutConnectionHints.waypoints = hints.newWaypoints;
50038 }
50039
50040 this._modeling.layoutConnection(connection, layoutConnectionHints);
50041 };
50042
50043 ReconnectConnectionHandler.prototype.revert = function(context) {
50044 var oldSource = context.oldSource,
50045 oldTarget = context.oldTarget,
50046 oldWaypoints = context.oldWaypoints,
50047 connection = context.connection;
50048
50049 if (oldSource) {
50050 connection.source = oldSource;
50051 }
50052
50053 if (oldTarget) {
50054 connection.target = oldTarget;
50055 }
50056
50057 if (oldWaypoints) {
50058 connection.waypoints = oldWaypoints;
50059 }
50060
50061 return connection;
50062 };
50063
50064
50065
50066 // helpers //////////
50067
50068 function getDocking$2(point) {
50069 return point.original || point;
50070 }
50071
50072 /**
50073 * Replace shape by adding new shape and removing old shape. Incoming and outgoing connections will
50074 * be kept if possible.
50075 *
50076 * @class
50077 * @constructor
50078 *
50079 * @param {Modeling} modeling
50080 * @param {Rules} rules
50081 */
50082 function ReplaceShapeHandler(modeling, rules) {
50083 this._modeling = modeling;
50084 this._rules = rules;
50085 }
50086
50087 ReplaceShapeHandler.$inject = [ 'modeling', 'rules' ];
50088
50089
50090 /**
50091 * Add new shape.
50092 *
50093 * @param {Object} context
50094 * @param {djs.model.Shape} context.oldShape
50095 * @param {Object} context.newData
50096 * @param {string} context.newData.type
50097 * @param {number} context.newData.x
50098 * @param {number} context.newData.y
50099 * @param {Object} [hints]
50100 */
50101 ReplaceShapeHandler.prototype.preExecute = function(context) {
50102 var self = this,
50103 modeling = this._modeling,
50104 rules = this._rules;
50105
50106 var oldShape = context.oldShape,
50107 newData = context.newData,
50108 hints = context.hints || {},
50109 newShape;
50110
50111 function canReconnect(source, target, connection) {
50112 return rules.allowed('connection.reconnect', {
50113 connection: connection,
50114 source: source,
50115 target: target
50116 });
50117 }
50118
50119 // (1) add new shape at given position
50120 var position = {
50121 x: newData.x,
50122 y: newData.y
50123 };
50124
50125 var oldBounds = {
50126 x: oldShape.x,
50127 y: oldShape.y,
50128 width: oldShape.width,
50129 height: oldShape.height
50130 };
50131
50132 newShape = context.newShape =
50133 context.newShape ||
50134 self.createShape(newData, position, oldShape.parent, hints);
50135
50136 // (2) update host
50137 if (oldShape.host) {
50138 modeling.updateAttachment(newShape, oldShape.host);
50139 }
50140
50141 // (3) adopt all children from old shape
50142 var children;
50143
50144 if (hints.moveChildren !== false) {
50145 children = oldShape.children.slice();
50146
50147 modeling.moveElements(children, { x: 0, y: 0 }, newShape, hints);
50148 }
50149
50150 // (4) reconnect connections to new shape if possible
50151 var incoming = oldShape.incoming.slice(),
50152 outgoing = oldShape.outgoing.slice();
50153
50154 forEach(incoming, function(connection) {
50155 var source = connection.source,
50156 allowed = canReconnect(source, newShape, connection);
50157
50158 if (allowed) {
50159 self.reconnectEnd(
50160 connection, newShape,
50161 getResizedTargetAnchor(connection, newShape, oldBounds),
50162 hints
50163 );
50164 }
50165 });
50166
50167 forEach(outgoing, function(connection) {
50168 var target = connection.target,
50169 allowed = canReconnect(newShape, target, connection);
50170
50171 if (allowed) {
50172 self.reconnectStart(
50173 connection, newShape,
50174 getResizedSourceAnchor(connection, newShape, oldBounds),
50175 hints
50176 );
50177 }
50178 });
50179 };
50180
50181
50182 /**
50183 * Remove old shape.
50184 */
50185 ReplaceShapeHandler.prototype.postExecute = function(context) {
50186 var oldShape = context.oldShape;
50187
50188 this._modeling.removeShape(oldShape);
50189 };
50190
50191
50192 ReplaceShapeHandler.prototype.execute = function(context) {};
50193
50194
50195 ReplaceShapeHandler.prototype.revert = function(context) {};
50196
50197
50198 ReplaceShapeHandler.prototype.createShape = function(shape, position, target, hints) {
50199 return this._modeling.createShape(shape, position, target, hints);
50200 };
50201
50202
50203 ReplaceShapeHandler.prototype.reconnectStart = function(connection, newSource, dockingPoint, hints) {
50204 this._modeling.reconnectStart(connection, newSource, dockingPoint, hints);
50205 };
50206
50207
50208 ReplaceShapeHandler.prototype.reconnectEnd = function(connection, newTarget, dockingPoint, hints) {
50209 this._modeling.reconnectEnd(connection, newTarget, dockingPoint, hints);
50210 };
50211
50212 /**
50213 * A handler that implements reversible resizing of shapes.
50214 *
50215 * @param {Modeling} modeling
50216 */
50217 function ResizeShapeHandler(modeling) {
50218 this._modeling = modeling;
50219 }
50220
50221 ResizeShapeHandler.$inject = [ 'modeling' ];
50222
50223 /**
50224 * {
50225 * shape: {....}
50226 * newBounds: {
50227 * width: 20,
50228 * height: 40,
50229 * x: 5,
50230 * y: 10
50231 * }
50232 *
50233 * }
50234 */
50235 ResizeShapeHandler.prototype.execute = function(context) {
50236 var shape = context.shape,
50237 newBounds = context.newBounds,
50238 minBounds = context.minBounds;
50239
50240 if (newBounds.x === undefined || newBounds.y === undefined ||
50241 newBounds.width === undefined || newBounds.height === undefined) {
50242 throw new Error('newBounds must have {x, y, width, height} properties');
50243 }
50244
50245 if (minBounds && (newBounds.width < minBounds.width
50246 || newBounds.height < minBounds.height)) {
50247 throw new Error('width and height cannot be less than minimum height and width');
50248 } else if (!minBounds
50249 && newBounds.width < 10 || newBounds.height < 10) {
50250 throw new Error('width and height cannot be less than 10px');
50251 }
50252
50253 // save old bbox in context
50254 context.oldBounds = {
50255 width: shape.width,
50256 height: shape.height,
50257 x: shape.x,
50258 y: shape.y
50259 };
50260
50261 // update shape
50262 assign(shape, {
50263 width: newBounds.width,
50264 height: newBounds.height,
50265 x: newBounds.x,
50266 y: newBounds.y
50267 });
50268
50269 return shape;
50270 };
50271
50272 ResizeShapeHandler.prototype.postExecute = function(context) {
50273 var modeling = this._modeling;
50274
50275 var shape = context.shape,
50276 oldBounds = context.oldBounds,
50277 hints = context.hints || {};
50278
50279 if (hints.layout === false) {
50280 return;
50281 }
50282
50283 forEach(shape.incoming, function(c) {
50284 modeling.layoutConnection(c, {
50285 connectionEnd: getResizedTargetAnchor(c, shape, oldBounds)
50286 });
50287 });
50288
50289 forEach(shape.outgoing, function(c) {
50290 modeling.layoutConnection(c, {
50291 connectionStart: getResizedSourceAnchor(c, shape, oldBounds)
50292 });
50293 });
50294
50295 };
50296
50297 ResizeShapeHandler.prototype.revert = function(context) {
50298
50299 var shape = context.shape,
50300 oldBounds = context.oldBounds;
50301
50302 // restore previous bbox
50303 assign(shape, {
50304 width: oldBounds.width,
50305 height: oldBounds.height,
50306 x: oldBounds.x,
50307 y: oldBounds.y
50308 });
50309
50310 return shape;
50311 };
50312
50313 /**
50314 * Add or remove space by moving and resizing shapes and updating connection waypoints.
50315 */
50316 function SpaceToolHandler(modeling) {
50317 this._modeling = modeling;
50318 }
50319
50320 SpaceToolHandler.$inject = [ 'modeling' ];
50321
50322 SpaceToolHandler.prototype.preExecute = function(context) {
50323 var delta = context.delta,
50324 direction = context.direction,
50325 movingShapes = context.movingShapes,
50326 resizingShapes = context.resizingShapes,
50327 start = context.start,
50328 oldBounds = {};
50329
50330 // (1) move shapes
50331 this.moveShapes(movingShapes, delta);
50332
50333 // (2a) save old bounds of resized shapes
50334 forEach(resizingShapes, function(shape) {
50335 oldBounds[shape.id] = getBounds(shape);
50336 });
50337
50338 // (2b) resize shapes
50339 this.resizeShapes(resizingShapes, delta, direction);
50340
50341 // (3) update connection waypoints
50342 this.updateConnectionWaypoints(
50343 getWaypointsUpdatingConnections(movingShapes, resizingShapes),
50344 delta,
50345 direction,
50346 start,
50347 movingShapes,
50348 resizingShapes,
50349 oldBounds
50350 );
50351 };
50352
50353 SpaceToolHandler.prototype.execute = function() {};
50354 SpaceToolHandler.prototype.revert = function() {};
50355
50356 SpaceToolHandler.prototype.moveShapes = function(shapes, delta) {
50357 var self = this;
50358
50359 forEach(shapes, function(element) {
50360 self._modeling.moveShape(element, delta, null, {
50361 autoResize: false,
50362 layout: false,
50363 recurse: false
50364 });
50365 });
50366 };
50367
50368 SpaceToolHandler.prototype.resizeShapes = function(shapes, delta, direction) {
50369 var self = this;
50370
50371 forEach(shapes, function(shape) {
50372 var newBounds = resizeBounds$1(shape, direction, delta);
50373
50374 self._modeling.resizeShape(shape, newBounds, null, {
50375 attachSupport: false,
50376 autoResize: false,
50377 layout: false
50378 });
50379 });
50380 };
50381
50382 /**
50383 * Update connections waypoints according to the rules:
50384 * 1. Both source and target are moved/resized => move waypoints by the delta
50385 * 2. Only one of source and target is moved/resized => re-layout connection with moved start/end
50386 */
50387 SpaceToolHandler.prototype.updateConnectionWaypoints = function(
50388 connections,
50389 delta,
50390 direction,
50391 start,
50392 movingShapes,
50393 resizingShapes,
50394 oldBounds
50395 ) {
50396 var self = this,
50397 affectedShapes = movingShapes.concat(resizingShapes);
50398
50399 forEach(connections, function(connection) {
50400 var source = connection.source,
50401 target = connection.target,
50402 waypoints = copyWaypoints$1(connection),
50403 axis = getAxisFromDirection(direction),
50404 layoutHints = {
50405 labelBehavior: false
50406 };
50407
50408 if (includes$5(affectedShapes, source) && includes$5(affectedShapes, target)) {
50409
50410 // move waypoints
50411 waypoints = map(waypoints, function(waypoint) {
50412 if (shouldMoveWaypoint(waypoint, start, direction)) {
50413
50414 // move waypoint
50415 waypoint[ axis ] = waypoint[ axis ] + delta[ axis ];
50416 }
50417
50418 if (waypoint.original && shouldMoveWaypoint(waypoint.original, start, direction)) {
50419
50420 // move waypoint original
50421 waypoint.original[ axis ] = waypoint.original[ axis ] + delta[ axis ];
50422 }
50423
50424 return waypoint;
50425 });
50426
50427 self._modeling.updateWaypoints(connection, waypoints, {
50428 labelBehavior: false
50429 });
50430 } else if (includes$5(affectedShapes, source) || includes$5(affectedShapes, target)) {
50431
50432 // re-layout connection with moved start/end
50433 if (includes$5(movingShapes, source)) {
50434 layoutHints.connectionStart = getMovedSourceAnchor(connection, source, delta);
50435 } else if (includes$5(movingShapes, target)) {
50436 layoutHints.connectionEnd = getMovedTargetAnchor(connection, target, delta);
50437 } else if (includes$5(resizingShapes, source)) {
50438 layoutHints.connectionStart = getResizedSourceAnchor(
50439 connection, source, oldBounds[source.id]
50440 );
50441 } else if (includes$5(resizingShapes, target)) {
50442 layoutHints.connectionEnd = getResizedTargetAnchor(
50443 connection, target, oldBounds[target.id]
50444 );
50445 }
50446
50447 self._modeling.layoutConnection(connection, layoutHints);
50448 }
50449 });
50450 };
50451
50452
50453 // helpers //////////
50454
50455 function copyWaypoint$1(waypoint) {
50456 return assign({}, waypoint);
50457 }
50458
50459 function copyWaypoints$1(connection) {
50460 return map(connection.waypoints, function(waypoint) {
50461
50462 waypoint = copyWaypoint$1(waypoint);
50463
50464 if (waypoint.original) {
50465 waypoint.original = copyWaypoint$1(waypoint.original);
50466 }
50467
50468 return waypoint;
50469 });
50470 }
50471
50472 function getAxisFromDirection(direction) {
50473 switch (direction) {
50474 case 'n':
50475 return 'y';
50476 case 'w':
50477 return 'x';
50478 case 's':
50479 return 'y';
50480 case 'e':
50481 return 'x';
50482 }
50483 }
50484
50485 function shouldMoveWaypoint(waypoint, start, direction) {
50486 var relevantAxis = getAxisFromDirection(direction);
50487
50488 if (/e|s/.test(direction)) {
50489 return waypoint[ relevantAxis ] > start;
50490 } else if (/n|w/.test(direction)) {
50491 return waypoint[ relevantAxis ] < start;
50492 }
50493 }
50494
50495 function includes$5(array, item) {
50496 return array.indexOf(item) !== -1;
50497 }
50498
50499 function getBounds(shape) {
50500 return {
50501 x: shape.x,
50502 y: shape.y,
50503 height: shape.height,
50504 width: shape.width
50505 };
50506 }
50507
50508 /**
50509 * A handler that toggles the collapsed state of an element
50510 * and the visibility of all its children.
50511 *
50512 * @param {Modeling} modeling
50513 */
50514 function ToggleShapeCollapseHandler(modeling) {
50515 this._modeling = modeling;
50516 }
50517
50518 ToggleShapeCollapseHandler.$inject = [ 'modeling' ];
50519
50520
50521 ToggleShapeCollapseHandler.prototype.execute = function(context) {
50522
50523 var shape = context.shape,
50524 children = shape.children;
50525
50526 // recursively remember previous visibility of children
50527 context.oldChildrenVisibility = getElementsVisibilityRecursive(children);
50528
50529 // toggle state
50530 shape.collapsed = !shape.collapsed;
50531
50532 // recursively hide/show children
50533 var result = setHiddenRecursive(children, shape.collapsed);
50534
50535 return [shape].concat(result);
50536 };
50537
50538
50539 ToggleShapeCollapseHandler.prototype.revert = function(context) {
50540
50541 var shape = context.shape,
50542 oldChildrenVisibility = context.oldChildrenVisibility;
50543
50544 var children = shape.children;
50545
50546 // recursively set old visability of children
50547 var result = restoreVisibilityRecursive(children, oldChildrenVisibility);
50548
50549 // retoggle state
50550 shape.collapsed = !shape.collapsed;
50551
50552 return [shape].concat(result);
50553 };
50554
50555
50556 // helpers //////////////////////
50557
50558 /**
50559 * Return a map { elementId -> hiddenState}.
50560 *
50561 * @param {Array<djs.model.Shape>} elements
50562 *
50563 * @return {Object}
50564 */
50565 function getElementsVisibilityRecursive(elements) {
50566
50567 var result = {};
50568
50569 forEach(elements, function(element) {
50570 result[element.id] = element.hidden;
50571
50572 if (element.children) {
50573 result = assign({}, result, getElementsVisibilityRecursive(element.children));
50574 }
50575 });
50576
50577 return result;
50578 }
50579
50580
50581 function setHiddenRecursive(elements, newHidden) {
50582 var result = [];
50583 forEach(elements, function(element) {
50584 element.hidden = newHidden;
50585
50586 result = result.concat(element);
50587
50588 if (element.children) {
50589 result = result.concat(setHiddenRecursive(element.children, element.collapsed || newHidden));
50590 }
50591 });
50592
50593 return result;
50594 }
50595
50596 function restoreVisibilityRecursive(elements, lastState) {
50597 var result = [];
50598 forEach(elements, function(element) {
50599 element.hidden = lastState[element.id];
50600
50601 result = result.concat(element);
50602
50603 if (element.children) {
50604 result = result.concat(restoreVisibilityRecursive(element.children, lastState));
50605 }
50606 });
50607
50608 return result;
50609 }
50610
50611 /**
50612 * A handler that implements reversible attaching/detaching of shapes.
50613 */
50614 function UpdateAttachmentHandler(modeling) {
50615 this._modeling = modeling;
50616 }
50617
50618 UpdateAttachmentHandler.$inject = [ 'modeling' ];
50619
50620
50621 UpdateAttachmentHandler.prototype.execute = function(context) {
50622 var shape = context.shape,
50623 newHost = context.newHost,
50624 oldHost = shape.host;
50625
50626 // (0) detach from old host
50627 context.oldHost = oldHost;
50628 context.attacherIdx = removeAttacher(oldHost, shape);
50629
50630 // (1) attach to new host
50631 addAttacher(newHost, shape);
50632
50633 // (2) update host
50634 shape.host = newHost;
50635
50636 return shape;
50637 };
50638
50639 UpdateAttachmentHandler.prototype.revert = function(context) {
50640 var shape = context.shape,
50641 newHost = context.newHost,
50642 oldHost = context.oldHost,
50643 attacherIdx = context.attacherIdx;
50644
50645 // (2) update host
50646 shape.host = oldHost;
50647
50648 // (1) attach to new host
50649 removeAttacher(newHost, shape);
50650
50651 // (0) detach from old host
50652 addAttacher(oldHost, shape, attacherIdx);
50653
50654 return shape;
50655 };
50656
50657
50658 function removeAttacher(host, attacher) {
50659
50660 // remove attacher from host
50661 return remove$2(host && host.attachers, attacher);
50662 }
50663
50664 function addAttacher(host, attacher, idx) {
50665
50666 if (!host) {
50667 return;
50668 }
50669
50670 var attachers = host.attachers;
50671
50672 if (!attachers) {
50673 host.attachers = attachers = [];
50674 }
50675
50676 add$1(attachers, attacher, idx);
50677 }
50678
50679 function UpdateWaypointsHandler() { }
50680
50681 UpdateWaypointsHandler.prototype.execute = function(context) {
50682
50683 var connection = context.connection,
50684 newWaypoints = context.newWaypoints;
50685
50686 context.oldWaypoints = connection.waypoints;
50687
50688 connection.waypoints = newWaypoints;
50689
50690 return connection;
50691 };
50692
50693 UpdateWaypointsHandler.prototype.revert = function(context) {
50694
50695 var connection = context.connection,
50696 oldWaypoints = context.oldWaypoints;
50697
50698 connection.waypoints = oldWaypoints;
50699
50700 return connection;
50701 };
50702
50703 /**
50704 * The basic modeling entry point.
50705 *
50706 * @param {EventBus} eventBus
50707 * @param {ElementFactory} elementFactory
50708 * @param {CommandStack} commandStack
50709 */
50710 function Modeling(eventBus, elementFactory, commandStack) {
50711 this._eventBus = eventBus;
50712 this._elementFactory = elementFactory;
50713 this._commandStack = commandStack;
50714
50715 var self = this;
50716
50717 eventBus.on('diagram.init', function() {
50718
50719 // register modeling handlers
50720 self.registerHandlers(commandStack);
50721 });
50722 }
50723
50724 Modeling.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ];
50725
50726
50727 Modeling.prototype.getHandlers = function() {
50728 return {
50729 'shape.append': AppendShapeHandler,
50730 'shape.create': CreateShapeHandler,
50731 'shape.delete': DeleteShapeHandler,
50732 'shape.move': MoveShapeHandler,
50733 'shape.resize': ResizeShapeHandler,
50734 'shape.replace': ReplaceShapeHandler,
50735 'shape.toggleCollapse': ToggleShapeCollapseHandler,
50736
50737 'spaceTool': SpaceToolHandler,
50738
50739 'label.create': CreateLabelHandler,
50740
50741 'connection.create': CreateConnectionHandler,
50742 'connection.delete': DeleteConnectionHandler,
50743 'connection.move': MoveConnectionHandler,
50744 'connection.layout': LayoutConnectionHandler,
50745
50746 'connection.updateWaypoints': UpdateWaypointsHandler,
50747
50748 'connection.reconnect': ReconnectConnectionHandler,
50749
50750 'elements.create': CreateElementsHandler,
50751 'elements.move': MoveElementsHandler,
50752 'elements.delete': DeleteElementsHandler,
50753
50754 'elements.distribute': DistributeElements$1,
50755 'elements.align': AlignElements$1,
50756
50757 'element.updateAttachment': UpdateAttachmentHandler
50758 };
50759 };
50760
50761 /**
50762 * Register handlers with the command stack
50763 *
50764 * @param {CommandStack} commandStack
50765 */
50766 Modeling.prototype.registerHandlers = function(commandStack) {
50767 forEach(this.getHandlers(), function(handler, id) {
50768 commandStack.registerHandler(id, handler);
50769 });
50770 };
50771
50772
50773 // modeling helpers //////////////////////
50774
50775 Modeling.prototype.moveShape = function(shape, delta, newParent, newParentIndex, hints) {
50776
50777 if (typeof newParentIndex === 'object') {
50778 hints = newParentIndex;
50779 newParentIndex = null;
50780 }
50781
50782 var context = {
50783 shape: shape,
50784 delta: delta,
50785 newParent: newParent,
50786 newParentIndex: newParentIndex,
50787 hints: hints || {}
50788 };
50789
50790 this._commandStack.execute('shape.move', context);
50791 };
50792
50793
50794 /**
50795 * Update the attachment of the given shape.
50796 *
50797 * @param {djs.mode.Base} shape
50798 * @param {djs.model.Base} [newHost]
50799 */
50800 Modeling.prototype.updateAttachment = function(shape, newHost) {
50801 var context = {
50802 shape: shape,
50803 newHost: newHost
50804 };
50805
50806 this._commandStack.execute('element.updateAttachment', context);
50807 };
50808
50809
50810 /**
50811 * Move a number of shapes to a new target, either setting it as
50812 * the new parent or attaching it.
50813 *
50814 * @param {Array<djs.mode.Base>} shapes
50815 * @param {Point} delta
50816 * @param {djs.model.Base} [target]
50817 * @param {Object} [hints]
50818 * @param {boolean} [hints.attach=false]
50819 */
50820 Modeling.prototype.moveElements = function(shapes, delta, target, hints) {
50821
50822 hints = hints || {};
50823
50824 var attach = hints.attach;
50825
50826 var newParent = target,
50827 newHost;
50828
50829 if (attach === true) {
50830 newHost = target;
50831 newParent = target.parent;
50832 } else
50833
50834 if (attach === false) {
50835 newHost = null;
50836 }
50837
50838 var context = {
50839 shapes: shapes,
50840 delta: delta,
50841 newParent: newParent,
50842 newHost: newHost,
50843 hints: hints
50844 };
50845
50846 this._commandStack.execute('elements.move', context);
50847 };
50848
50849
50850 Modeling.prototype.moveConnection = function(connection, delta, newParent, newParentIndex, hints) {
50851
50852 if (typeof newParentIndex === 'object') {
50853 hints = newParentIndex;
50854 newParentIndex = undefined;
50855 }
50856
50857 var context = {
50858 connection: connection,
50859 delta: delta,
50860 newParent: newParent,
50861 newParentIndex: newParentIndex,
50862 hints: hints || {}
50863 };
50864
50865 this._commandStack.execute('connection.move', context);
50866 };
50867
50868
50869 Modeling.prototype.layoutConnection = function(connection, hints) {
50870 var context = {
50871 connection: connection,
50872 hints: hints || {}
50873 };
50874
50875 this._commandStack.execute('connection.layout', context);
50876 };
50877
50878
50879 /**
50880 * Create connection.
50881 *
50882 * @param {djs.model.Base} source
50883 * @param {djs.model.Base} target
50884 * @param {number} [parentIndex]
50885 * @param {Object|djs.model.Connection} connection
50886 * @param {djs.model.Base} parent
50887 * @param {Object} hints
50888 *
50889 * @return {djs.model.Connection} the created connection.
50890 */
50891 Modeling.prototype.createConnection = function(source, target, parentIndex, connection, parent, hints) {
50892
50893 if (typeof parentIndex === 'object') {
50894 hints = parent;
50895 parent = connection;
50896 connection = parentIndex;
50897 parentIndex = undefined;
50898 }
50899
50900 connection = this._create('connection', connection);
50901
50902 var context = {
50903 source: source,
50904 target: target,
50905 parent: parent,
50906 parentIndex: parentIndex,
50907 connection: connection,
50908 hints: hints
50909 };
50910
50911 this._commandStack.execute('connection.create', context);
50912
50913 return context.connection;
50914 };
50915
50916
50917 /**
50918 * Create a shape at the specified position.
50919 *
50920 * @param {djs.model.Shape|Object} shape
50921 * @param {Point} position
50922 * @param {djs.model.Shape|djs.model.Root} target
50923 * @param {number} [parentIndex] position in parents children list
50924 * @param {Object} [hints]
50925 * @param {boolean} [hints.attach] whether to attach to target or become a child
50926 *
50927 * @return {djs.model.Shape} the created shape
50928 */
50929 Modeling.prototype.createShape = function(shape, position, target, parentIndex, hints) {
50930
50931 if (typeof parentIndex !== 'number') {
50932 hints = parentIndex;
50933 parentIndex = undefined;
50934 }
50935
50936 hints = hints || {};
50937
50938 var attach = hints.attach,
50939 parent,
50940 host;
50941
50942 shape = this._create('shape', shape);
50943
50944 if (attach) {
50945 parent = target.parent;
50946 host = target;
50947 } else {
50948 parent = target;
50949 }
50950
50951 var context = {
50952 position: position,
50953 shape: shape,
50954 parent: parent,
50955 parentIndex: parentIndex,
50956 host: host,
50957 hints: hints
50958 };
50959
50960 this._commandStack.execute('shape.create', context);
50961
50962 return context.shape;
50963 };
50964
50965
50966 Modeling.prototype.createElements = function(elements, position, parent, parentIndex, hints) {
50967 if (!isArray(elements)) {
50968 elements = [ elements ];
50969 }
50970
50971 if (typeof parentIndex !== 'number') {
50972 hints = parentIndex;
50973 parentIndex = undefined;
50974 }
50975
50976 hints = hints || {};
50977
50978 var context = {
50979 position: position,
50980 elements: elements,
50981 parent: parent,
50982 parentIndex: parentIndex,
50983 hints: hints
50984 };
50985
50986 this._commandStack.execute('elements.create', context);
50987
50988 return context.elements;
50989 };
50990
50991
50992 Modeling.prototype.createLabel = function(labelTarget, position, label, parent) {
50993
50994 label = this._create('label', label);
50995
50996 var context = {
50997 labelTarget: labelTarget,
50998 position: position,
50999 parent: parent || labelTarget.parent,
51000 shape: label
51001 };
51002
51003 this._commandStack.execute('label.create', context);
51004
51005 return context.shape;
51006 };
51007
51008
51009 /**
51010 * Append shape to given source, drawing a connection
51011 * between source and the newly created shape.
51012 *
51013 * @param {djs.model.Shape} source
51014 * @param {djs.model.Shape|Object} shape
51015 * @param {Point} position
51016 * @param {djs.model.Shape} target
51017 * @param {Object} [hints]
51018 * @param {boolean} [hints.attach]
51019 * @param {djs.model.Connection|Object} [hints.connection]
51020 * @param {djs.model.Base} [hints.connectionParent]
51021 *
51022 * @return {djs.model.Shape} the newly created shape
51023 */
51024 Modeling.prototype.appendShape = function(source, shape, position, target, hints) {
51025
51026 hints = hints || {};
51027
51028 shape = this._create('shape', shape);
51029
51030 var context = {
51031 source: source,
51032 position: position,
51033 target: target,
51034 shape: shape,
51035 connection: hints.connection,
51036 connectionParent: hints.connectionParent,
51037 hints: hints
51038 };
51039
51040 this._commandStack.execute('shape.append', context);
51041
51042 return context.shape;
51043 };
51044
51045
51046 Modeling.prototype.removeElements = function(elements) {
51047 var context = {
51048 elements: elements
51049 };
51050
51051 this._commandStack.execute('elements.delete', context);
51052 };
51053
51054
51055 Modeling.prototype.distributeElements = function(groups, axis, dimension) {
51056 var context = {
51057 groups: groups,
51058 axis: axis,
51059 dimension: dimension
51060 };
51061
51062 this._commandStack.execute('elements.distribute', context);
51063 };
51064
51065
51066 Modeling.prototype.removeShape = function(shape, hints) {
51067 var context = {
51068 shape: shape,
51069 hints: hints || {}
51070 };
51071
51072 this._commandStack.execute('shape.delete', context);
51073 };
51074
51075
51076 Modeling.prototype.removeConnection = function(connection, hints) {
51077 var context = {
51078 connection: connection,
51079 hints: hints || {}
51080 };
51081
51082 this._commandStack.execute('connection.delete', context);
51083 };
51084
51085 Modeling.prototype.replaceShape = function(oldShape, newShape, hints) {
51086 var context = {
51087 oldShape: oldShape,
51088 newData: newShape,
51089 hints: hints || {}
51090 };
51091
51092 this._commandStack.execute('shape.replace', context);
51093
51094 return context.newShape;
51095 };
51096
51097 Modeling.prototype.alignElements = function(elements, alignment) {
51098 var context = {
51099 elements: elements,
51100 alignment: alignment
51101 };
51102
51103 this._commandStack.execute('elements.align', context);
51104 };
51105
51106 Modeling.prototype.resizeShape = function(shape, newBounds, minBounds, hints) {
51107 var context = {
51108 shape: shape,
51109 newBounds: newBounds,
51110 minBounds: minBounds,
51111 hints: hints
51112 };
51113
51114 this._commandStack.execute('shape.resize', context);
51115 };
51116
51117 Modeling.prototype.createSpace = function(movingShapes, resizingShapes, delta, direction, start) {
51118 var context = {
51119 delta: delta,
51120 direction: direction,
51121 movingShapes: movingShapes,
51122 resizingShapes: resizingShapes,
51123 start: start
51124 };
51125
51126 this._commandStack.execute('spaceTool', context);
51127 };
51128
51129 Modeling.prototype.updateWaypoints = function(connection, newWaypoints, hints) {
51130 var context = {
51131 connection: connection,
51132 newWaypoints: newWaypoints,
51133 hints: hints || {}
51134 };
51135
51136 this._commandStack.execute('connection.updateWaypoints', context);
51137 };
51138
51139 Modeling.prototype.reconnect = function(connection, source, target, dockingOrPoints, hints) {
51140 var context = {
51141 connection: connection,
51142 newSource: source,
51143 newTarget: target,
51144 dockingOrPoints: dockingOrPoints,
51145 hints: hints || {}
51146 };
51147
51148 this._commandStack.execute('connection.reconnect', context);
51149 };
51150
51151 Modeling.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) {
51152 if (!hints) {
51153 hints = {};
51154 }
51155
51156 this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign(hints, {
51157 docking: 'source'
51158 }));
51159 };
51160
51161 Modeling.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) {
51162 if (!hints) {
51163 hints = {};
51164 }
51165
51166 this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign(hints, {
51167 docking: 'target'
51168 }));
51169 };
51170
51171 Modeling.prototype.connect = function(source, target, attrs, hints) {
51172 return this.createConnection(source, target, attrs || {}, source.parent, hints);
51173 };
51174
51175 Modeling.prototype._create = function(type, attrs) {
51176 if (attrs instanceof Base) {
51177 return attrs;
51178 } else {
51179 return this._elementFactory.create(type, attrs);
51180 }
51181 };
51182
51183 Modeling.prototype.toggleCollapse = function(shape, hints) {
51184 var context = {
51185 shape: shape,
51186 hints: hints || {}
51187 };
51188
51189 this._commandStack.execute('shape.toggleCollapse', context);
51190 };
51191
51192 var require$$0 = /*@__PURE__*/getAugmentedNamespace(index_esm);
51193
51194 var require$$1 = /*@__PURE__*/getAugmentedNamespace(ModelUtil);
51195
51196 var reduce$1 = require$$0.reduce,
51197 keys$1 = require$$0.keys,
51198 forEach$1 = require$$0.forEach,
51199 is$3 = require$$1.is,
51200 getBusinessObject$1 = require$$1.getBusinessObject;
51201
51202
51203 function UpdateModdlePropertiesHandler(elementRegistry) {
51204 this._elementRegistry = elementRegistry;
51205 }
51206
51207 UpdateModdlePropertiesHandler.$inject = ['elementRegistry'];
51208
51209 var UpdateModdlePropertiesHandler_1 = UpdateModdlePropertiesHandler;
51210
51211
51212 UpdateModdlePropertiesHandler.prototype.execute = function(context) {
51213
51214 var element = context.element,
51215 moddleElement = context.moddleElement,
51216 properties = context.properties;
51217
51218 if (!moddleElement) {
51219 throw new Error('<moddleElement> required');
51220 }
51221
51222 var changed = context.changed || this.getVisualReferences(moddleElement).concat(element);
51223 var oldProperties = context.oldProperties || getModdleProperties(moddleElement, keys$1(properties));
51224
51225 setModdleProperties(moddleElement, properties);
51226
51227 context.oldProperties = oldProperties;
51228 context.changed = changed;
51229
51230 return changed;
51231 };
51232
51233 UpdateModdlePropertiesHandler.prototype.revert = function(context) {
51234 var oldProperties = context.oldProperties,
51235 moddleElement = context.moddleElement,
51236 changed = context.changed;
51237
51238 setModdleProperties(moddleElement, oldProperties);
51239
51240 return changed;
51241 };
51242
51243 /**
51244 * Return visual references of given moddle element within the diagram.
51245 *
51246 * @param {ModdleElement} moddleElement
51247 *
51248 * @return {Array<djs.model.Element>}
51249 */
51250 UpdateModdlePropertiesHandler.prototype.getVisualReferences = function(moddleElement) {
51251
51252 var elementRegistry = this._elementRegistry;
51253
51254 if (is$3(moddleElement, 'bpmn:DataObject')) {
51255 return getAllDataObjectReferences(moddleElement, elementRegistry);
51256 }
51257
51258 return [];
51259 };
51260
51261
51262 // helpers /////////////////
51263
51264 function getModdleProperties(moddleElement, propertyNames) {
51265 return reduce$1(propertyNames, function(result, key) {
51266 result[key] = moddleElement.get(key);
51267 return result;
51268 }, {});
51269 }
51270
51271 function setModdleProperties(moddleElement, properties) {
51272 forEach$1(properties, function(value, key) {
51273 moddleElement.set(key, value);
51274 });
51275 }
51276
51277 function getAllDataObjectReferences(dataObject, elementRegistry) {
51278 return elementRegistry.filter(function(element) {
51279 return (
51280 is$3(element, 'bpmn:DataObjectReference') &&
51281 getBusinessObject$1(element).dataObjectRef === dataObject
51282 );
51283 });
51284 }
51285
51286 var DEFAULT_FLOW = 'default',
51287 ID = 'id',
51288 DI = 'di';
51289
51290 var NULL_DIMENSIONS = {
51291 width: 0,
51292 height: 0
51293 };
51294
51295 /**
51296 * A handler that implements a BPMN 2.0 property update.
51297 *
51298 * This should be used to set simple properties on elements with
51299 * an underlying BPMN business object.
51300 *
51301 * Use respective diagram-js provided handlers if you would
51302 * like to perform automated modeling.
51303 */
51304 function UpdatePropertiesHandler(
51305 elementRegistry, moddle, translate,
51306 modeling, textRenderer) {
51307
51308 this._elementRegistry = elementRegistry;
51309 this._moddle = moddle;
51310 this._translate = translate;
51311 this._modeling = modeling;
51312 this._textRenderer = textRenderer;
51313 }
51314
51315 UpdatePropertiesHandler.$inject = [
51316 'elementRegistry',
51317 'moddle',
51318 'translate',
51319 'modeling',
51320 'textRenderer'
51321 ];
51322
51323
51324 // api //////////////////////
51325
51326 /**
51327 * Updates a BPMN element with a list of new properties
51328 *
51329 * @param {Object} context
51330 * @param {djs.model.Base} context.element the element to update
51331 * @param {Object} context.properties a list of properties to set on the element's
51332 * businessObject (the BPMN model element)
51333 *
51334 * @return {Array<djs.model.Base>} the updated element
51335 */
51336 UpdatePropertiesHandler.prototype.execute = function(context) {
51337
51338 var element = context.element,
51339 changed = [ element ],
51340 translate = this._translate;
51341
51342 if (!element) {
51343 throw new Error(translate('element required'));
51344 }
51345
51346 var elementRegistry = this._elementRegistry,
51347 ids = this._moddle.ids;
51348
51349 var businessObject = element.businessObject,
51350 properties = unwrapBusinessObjects(context.properties),
51351 oldProperties = context.oldProperties || getProperties(businessObject, properties);
51352
51353 if (isIdChange(properties, businessObject)) {
51354 ids.unclaim(businessObject[ID]);
51355
51356 elementRegistry.updateId(element, properties[ID]);
51357
51358 ids.claim(properties[ID], businessObject);
51359 }
51360
51361 // correctly indicate visual changes on default flow updates
51362 if (DEFAULT_FLOW in properties) {
51363
51364 if (properties[DEFAULT_FLOW]) {
51365 changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
51366 }
51367
51368 if (businessObject[DEFAULT_FLOW]) {
51369 changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
51370 }
51371 }
51372
51373 // update properties
51374 setProperties(businessObject, properties);
51375
51376 // store old values
51377 context.oldProperties = oldProperties;
51378 context.changed = changed;
51379
51380 // indicate changed on objects affected by the update
51381 return changed;
51382 };
51383
51384
51385 UpdatePropertiesHandler.prototype.postExecute = function(context) {
51386 var element = context.element,
51387 label = element.label;
51388
51389 var text = label && getBusinessObject(label).name;
51390
51391 if (!text) {
51392 return;
51393 }
51394
51395 // get layouted text bounds and resize external
51396 // external label accordingly
51397 var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);
51398
51399 this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
51400 };
51401
51402 /**
51403 * Reverts the update on a BPMN elements properties.
51404 *
51405 * @param {Object} context
51406 *
51407 * @return {djs.model.Base} the updated element
51408 */
51409 UpdatePropertiesHandler.prototype.revert = function(context) {
51410
51411 var element = context.element,
51412 properties = context.properties,
51413 oldProperties = context.oldProperties,
51414 businessObject = element.businessObject,
51415 elementRegistry = this._elementRegistry,
51416 ids = this._moddle.ids;
51417
51418 // update properties
51419 setProperties(businessObject, oldProperties);
51420
51421 if (isIdChange(properties, businessObject)) {
51422 ids.unclaim(properties[ID]);
51423
51424 elementRegistry.updateId(element, oldProperties[ID]);
51425
51426 ids.claim(oldProperties[ID], businessObject);
51427 }
51428
51429 return context.changed;
51430 };
51431
51432
51433 function isIdChange(properties, businessObject) {
51434 return ID in properties && properties[ID] !== businessObject[ID];
51435 }
51436
51437
51438 function getProperties(businessObject, properties) {
51439 var propertyNames = keys(properties);
51440
51441 return reduce(propertyNames, function(result, key) {
51442
51443 // handle DI separately
51444 if (key !== DI) {
51445 result[key] = businessObject.get(key);
51446 } else {
51447 result[key] = getDiProperties(businessObject.di, keys(properties.di));
51448 }
51449
51450 return result;
51451 }, {});
51452 }
51453
51454
51455 function getDiProperties(di, propertyNames) {
51456 return reduce(propertyNames, function(result, key) {
51457 result[key] = di.get(key);
51458
51459 return result;
51460 }, {});
51461 }
51462
51463
51464 function setProperties(businessObject, properties) {
51465 forEach(properties, function(value, key) {
51466
51467 if (key !== DI) {
51468 businessObject.set(key, value);
51469 } else {
51470
51471 // only update, if businessObject.di exists
51472 if (businessObject.di) {
51473 setDiProperties(businessObject.di, value);
51474 }
51475 }
51476 });
51477 }
51478
51479
51480 function setDiProperties(di, properties) {
51481 forEach(properties, function(value, key) {
51482 di.set(key, value);
51483 });
51484 }
51485
51486
51487 var referencePropertyNames = [ 'default' ];
51488
51489 /**
51490 * Make sure we unwrap the actual business object
51491 * behind diagram element that may have been
51492 * passed as arguments.
51493 *
51494 * @param {Object} properties
51495 *
51496 * @return {Object} unwrappedProps
51497 */
51498 function unwrapBusinessObjects(properties) {
51499
51500 var unwrappedProps = assign({}, properties);
51501
51502 referencePropertyNames.forEach(function(name) {
51503 if (name in properties) {
51504 unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
51505 }
51506 });
51507
51508 return unwrappedProps;
51509 }
51510
51511 function UpdateCanvasRootHandler(canvas, modeling) {
51512 this._canvas = canvas;
51513 this._modeling = modeling;
51514 }
51515
51516 UpdateCanvasRootHandler.$inject = [
51517 'canvas',
51518 'modeling'
51519 ];
51520
51521
51522 UpdateCanvasRootHandler.prototype.execute = function(context) {
51523
51524 var canvas = this._canvas;
51525
51526 var newRoot = context.newRoot,
51527 newRootBusinessObject = newRoot.businessObject,
51528 oldRoot = canvas.getRootElement(),
51529 oldRootBusinessObject = oldRoot.businessObject,
51530 bpmnDefinitions = oldRootBusinessObject.$parent,
51531 diPlane = oldRootBusinessObject.di;
51532
51533 // (1) replace process old <> new root
51534 canvas.setRootElement(newRoot, true);
51535
51536 // (2) update root elements
51537 add$1(bpmnDefinitions.rootElements, newRootBusinessObject);
51538 newRootBusinessObject.$parent = bpmnDefinitions;
51539
51540 remove$2(bpmnDefinitions.rootElements, oldRootBusinessObject);
51541 oldRootBusinessObject.$parent = null;
51542
51543 // (3) wire di
51544 oldRootBusinessObject.di = null;
51545
51546 diPlane.bpmnElement = newRootBusinessObject;
51547 newRootBusinessObject.di = diPlane;
51548
51549 context.oldRoot = oldRoot;
51550
51551 // TODO(nikku): return changed elements?
51552 // return [ newRoot, oldRoot ];
51553 };
51554
51555
51556 UpdateCanvasRootHandler.prototype.revert = function(context) {
51557
51558 var canvas = this._canvas;
51559
51560 var newRoot = context.newRoot,
51561 newRootBusinessObject = newRoot.businessObject,
51562 oldRoot = context.oldRoot,
51563 oldRootBusinessObject = oldRoot.businessObject,
51564 bpmnDefinitions = newRootBusinessObject.$parent,
51565 diPlane = newRootBusinessObject.di;
51566
51567 // (1) replace process old <> new root
51568 canvas.setRootElement(oldRoot, true);
51569
51570 // (2) update root elements
51571 remove$2(bpmnDefinitions.rootElements, newRootBusinessObject);
51572 newRootBusinessObject.$parent = null;
51573
51574 add$1(bpmnDefinitions.rootElements, oldRootBusinessObject);
51575 oldRootBusinessObject.$parent = bpmnDefinitions;
51576
51577 // (3) wire di
51578 newRootBusinessObject.di = null;
51579
51580 diPlane.bpmnElement = oldRootBusinessObject;
51581 oldRootBusinessObject.di = diPlane;
51582
51583 // TODO(nikku): return changed elements?
51584 // return [ newRoot, oldRoot ];
51585 };
51586
51587 /**
51588 * A handler that allows us to add a new lane
51589 * above or below an existing one.
51590 *
51591 * @param {Modeling} modeling
51592 * @param {SpaceTool} spaceTool
51593 */
51594 function AddLaneHandler(modeling, spaceTool) {
51595 this._modeling = modeling;
51596 this._spaceTool = spaceTool;
51597 }
51598
51599 AddLaneHandler.$inject = [
51600 'modeling',
51601 'spaceTool'
51602 ];
51603
51604
51605 AddLaneHandler.prototype.preExecute = function(context) {
51606
51607 var spaceTool = this._spaceTool,
51608 modeling = this._modeling;
51609
51610 var shape = context.shape,
51611 location = context.location;
51612
51613 var lanesRoot = getLanesRoot(shape);
51614
51615 var isRoot = lanesRoot === shape,
51616 laneParent = isRoot ? shape : shape.parent;
51617
51618 var existingChildLanes = getChildLanes(laneParent);
51619
51620 // (0) add a lane if we currently got none and are adding to root
51621 if (!existingChildLanes.length) {
51622 modeling.createShape({ type: 'bpmn:Lane' }, {
51623 x: shape.x + LANE_INDENTATION,
51624 y: shape.y,
51625 width: shape.width - LANE_INDENTATION,
51626 height: shape.height
51627 }, laneParent);
51628 }
51629
51630 // (1) collect affected elements to create necessary space
51631 var allAffected = [];
51632
51633 eachElement(lanesRoot, function(element) {
51634 allAffected.push(element);
51635
51636 // handle element labels in the diagram root
51637 if (element.label) {
51638 allAffected.push(element.label);
51639 }
51640
51641 if (element === shape) {
51642 return [];
51643 }
51644
51645 return filter(element.children, function(c) {
51646 return c !== shape;
51647 });
51648 });
51649
51650 var offset = location === 'top' ? -120 : 120,
51651 lanePosition = location === 'top' ? shape.y : shape.y + shape.height,
51652 spacePos = lanePosition + (location === 'top' ? 10 : -10),
51653 direction = location === 'top' ? 'n' : 's';
51654
51655 var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
51656
51657 spaceTool.makeSpace(
51658 adjustments.movingShapes,
51659 adjustments.resizingShapes,
51660 { x: 0, y: offset },
51661 direction,
51662 spacePos
51663 );
51664
51665 // (2) create new lane at open space
51666 context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, {
51667 x: shape.x + (isRoot ? LANE_INDENTATION : 0),
51668 y: lanePosition - (location === 'top' ? 120 : 0),
51669 width: shape.width - (isRoot ? LANE_INDENTATION : 0),
51670 height: 120
51671 }, laneParent);
51672 };
51673
51674 /**
51675 * A handler that splits a lane into a number of sub-lanes,
51676 * creating new sub lanes, if necessary.
51677 *
51678 * @param {Modeling} modeling
51679 */
51680 function SplitLaneHandler(modeling, translate) {
51681 this._modeling = modeling;
51682 this._translate = translate;
51683 }
51684
51685 SplitLaneHandler.$inject = [
51686 'modeling',
51687 'translate'
51688 ];
51689
51690
51691 SplitLaneHandler.prototype.preExecute = function(context) {
51692
51693 var modeling = this._modeling,
51694 translate = this._translate;
51695
51696 var shape = context.shape,
51697 newLanesCount = context.count;
51698
51699 var childLanes = getChildLanes(shape),
51700 existingLanesCount = childLanes.length;
51701
51702 if (existingLanesCount > newLanesCount) {
51703 throw new Error(translate('more than {count} child lanes', { count: newLanesCount }));
51704 }
51705
51706 var newLanesHeight = Math.round(shape.height / newLanesCount);
51707
51708 // Iterate from top to bottom in child lane order,
51709 // resizing existing lanes and creating new ones
51710 // so that they split the parent proportionally.
51711 //
51712 // Due to rounding related errors, the bottom lane
51713 // needs to take up all the remaining space.
51714 var laneY,
51715 laneHeight,
51716 laneBounds,
51717 newLaneAttrs,
51718 idx;
51719
51720 for (idx = 0; idx < newLanesCount; idx++) {
51721
51722 laneY = shape.y + idx * newLanesHeight;
51723
51724 // if bottom lane
51725 if (idx === newLanesCount - 1) {
51726 laneHeight = shape.height - (newLanesHeight * idx);
51727 } else {
51728 laneHeight = newLanesHeight;
51729 }
51730
51731 laneBounds = {
51732 x: shape.x + LANE_INDENTATION,
51733 y: laneY,
51734 width: shape.width - LANE_INDENTATION,
51735 height: laneHeight
51736 };
51737
51738 if (idx < existingLanesCount) {
51739
51740 // resize existing lane
51741 modeling.resizeShape(childLanes[idx], laneBounds);
51742 } else {
51743
51744 // create a new lane at position
51745 newLaneAttrs = {
51746 type: 'bpmn:Lane'
51747 };
51748
51749 modeling.createShape(newLaneAttrs, laneBounds, shape);
51750 }
51751 }
51752 };
51753
51754 /**
51755 * A handler that resizes a lane.
51756 *
51757 * @param {Modeling} modeling
51758 */
51759 function ResizeLaneHandler(modeling, spaceTool) {
51760 this._modeling = modeling;
51761 this._spaceTool = spaceTool;
51762 }
51763
51764 ResizeLaneHandler.$inject = [
51765 'modeling',
51766 'spaceTool'
51767 ];
51768
51769
51770 ResizeLaneHandler.prototype.preExecute = function(context) {
51771
51772 var shape = context.shape,
51773 newBounds = context.newBounds,
51774 balanced = context.balanced;
51775
51776 if (balanced !== false) {
51777 this.resizeBalanced(shape, newBounds);
51778 } else {
51779 this.resizeSpace(shape, newBounds);
51780 }
51781 };
51782
51783
51784 /**
51785 * Resize balanced, adjusting next / previous lane sizes.
51786 *
51787 * @param {djs.model.Shape} shape
51788 * @param {Bounds} newBounds
51789 */
51790 ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) {
51791
51792 var modeling = this._modeling;
51793
51794 var resizeNeeded = computeLanesResize(shape, newBounds);
51795
51796 // resize the lane
51797 modeling.resizeShape(shape, newBounds);
51798
51799 // resize other lanes as needed
51800 resizeNeeded.forEach(function(r) {
51801 modeling.resizeShape(r.shape, r.newBounds);
51802 });
51803 };
51804
51805
51806 /**
51807 * Resize, making actual space and moving below / above elements.
51808 *
51809 * @param {djs.model.Shape} shape
51810 * @param {Bounds} newBounds
51811 */
51812 ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) {
51813 var spaceTool = this._spaceTool;
51814
51815 var shapeTrbl = asTRBL(shape),
51816 newTrbl = asTRBL(newBounds);
51817
51818 var trblDiff = substractTRBL(newTrbl, shapeTrbl);
51819
51820 var lanesRoot = getLanesRoot(shape);
51821
51822 var allAffected = [],
51823 allLanes = [];
51824
51825 eachElement(lanesRoot, function(element) {
51826 allAffected.push(element);
51827
51828 if (is$1(element, 'bpmn:Lane') || is$1(element, 'bpmn:Participant')) {
51829 allLanes.push(element);
51830 }
51831
51832 return element.children;
51833 });
51834
51835 var change,
51836 spacePos,
51837 direction,
51838 offset,
51839 adjustments;
51840
51841 if (trblDiff.bottom || trblDiff.top) {
51842
51843 change = trblDiff.bottom || trblDiff.top;
51844 spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10);
51845 direction = trblDiff.bottom ? 's' : 'n';
51846
51847 offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change;
51848
51849 adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
51850
51851 spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction);
51852 }
51853
51854
51855 if (trblDiff.left || trblDiff.right) {
51856
51857 change = trblDiff.right || trblDiff.left;
51858 spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100);
51859 direction = trblDiff.right ? 'e' : 'w';
51860
51861 offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change;
51862
51863 adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos);
51864
51865 spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction);
51866 }
51867 };
51868
51869 var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
51870 LANES_ATTR = 'lanes';
51871
51872
51873 /**
51874 * A handler that updates lane refs on changed elements
51875 */
51876 function UpdateFlowNodeRefsHandler(elementRegistry) {
51877 this._elementRegistry = elementRegistry;
51878 }
51879
51880 UpdateFlowNodeRefsHandler.$inject = [
51881 'elementRegistry'
51882 ];
51883
51884
51885 UpdateFlowNodeRefsHandler.prototype.computeUpdates = function(flowNodeShapes, laneShapes) {
51886
51887 var handledNodes = [];
51888
51889 var updates = [];
51890
51891 var participantCache = {};
51892
51893 var allFlowNodeShapes = [];
51894
51895 function isInLaneShape(element, laneShape) {
51896
51897 var laneTrbl = asTRBL(laneShape);
51898
51899 var elementMid = {
51900 x: element.x + element.width / 2,
51901 y: element.y + element.height / 2
51902 };
51903
51904 return elementMid.x > laneTrbl.left &&
51905 elementMid.x < laneTrbl.right &&
51906 elementMid.y > laneTrbl.top &&
51907 elementMid.y < laneTrbl.bottom;
51908 }
51909
51910 function addFlowNodeShape(flowNodeShape) {
51911 if (handledNodes.indexOf(flowNodeShape) === -1) {
51912 allFlowNodeShapes.push(flowNodeShape);
51913 handledNodes.push(flowNodeShape);
51914 }
51915 }
51916
51917 function getAllLaneShapes(flowNodeShape) {
51918
51919 var root = getLanesRoot(flowNodeShape);
51920
51921 if (!participantCache[root.id]) {
51922 participantCache[root.id] = collectLanes(root);
51923 }
51924
51925 return participantCache[root.id];
51926 }
51927
51928 function getNewLanes(flowNodeShape) {
51929 if (!flowNodeShape.parent) {
51930 return [];
51931 }
51932
51933 var allLaneShapes = getAllLaneShapes(flowNodeShape);
51934
51935 return allLaneShapes.filter(function(l) {
51936 return isInLaneShape(flowNodeShape, l);
51937 }).map(function(shape) {
51938 return shape.businessObject;
51939 });
51940 }
51941
51942 laneShapes.forEach(function(laneShape) {
51943 var root = getLanesRoot(laneShape);
51944
51945 if (!root || handledNodes.indexOf(root) !== -1) {
51946 return;
51947 }
51948
51949 var children = root.children.filter(function(c) {
51950 return is$1(c, 'bpmn:FlowNode');
51951 });
51952
51953 children.forEach(addFlowNodeShape);
51954
51955 handledNodes.push(root);
51956 });
51957
51958 flowNodeShapes.forEach(addFlowNodeShape);
51959
51960
51961 allFlowNodeShapes.forEach(function(flowNodeShape) {
51962
51963 var flowNode = flowNodeShape.businessObject;
51964
51965 var lanes = flowNode.get(LANES_ATTR),
51966 remove = lanes.slice(),
51967 add = getNewLanes(flowNodeShape);
51968
51969 updates.push({ flowNode: flowNode, remove: remove, add: add });
51970 });
51971
51972 laneShapes.forEach(function(laneShape) {
51973
51974 var lane = laneShape.businessObject;
51975
51976 // lane got removed XX-)
51977 if (!laneShape.parent) {
51978 lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) {
51979 updates.push({ flowNode: flowNode, remove: [ lane ], add: [] });
51980 });
51981 }
51982 });
51983
51984 return updates;
51985 };
51986
51987 UpdateFlowNodeRefsHandler.prototype.execute = function(context) {
51988
51989 var updates = context.updates;
51990
51991 if (!updates) {
51992 updates = context.updates = this.computeUpdates(context.flowNodeShapes, context.laneShapes);
51993 }
51994
51995
51996 updates.forEach(function(update) {
51997
51998 var flowNode = update.flowNode,
51999 lanes = flowNode.get(LANES_ATTR);
52000
52001 // unwire old
52002 update.remove.forEach(function(oldLane) {
52003 remove$2(lanes, oldLane);
52004 remove$2(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
52005 });
52006
52007 // wire new
52008 update.add.forEach(function(newLane) {
52009 add$1(lanes, newLane);
52010 add$1(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
52011 });
52012 });
52013
52014 // TODO(nikku): return changed elements
52015 // return [ ... ];
52016 };
52017
52018
52019 UpdateFlowNodeRefsHandler.prototype.revert = function(context) {
52020
52021 var updates = context.updates;
52022
52023 updates.forEach(function(update) {
52024
52025 var flowNode = update.flowNode,
52026 lanes = flowNode.get(LANES_ATTR);
52027
52028 // unwire new
52029 update.add.forEach(function(newLane) {
52030 remove$2(lanes, newLane);
52031 remove$2(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
52032 });
52033
52034 // wire old
52035 update.remove.forEach(function(oldLane) {
52036 add$1(lanes, oldLane);
52037 add$1(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
52038 });
52039 });
52040
52041 // TODO(nikku): return changed elements
52042 // return [ ... ];
52043 };
52044
52045 function IdClaimHandler(moddle) {
52046 this._moddle = moddle;
52047 }
52048
52049 IdClaimHandler.$inject = [ 'moddle' ];
52050
52051
52052 IdClaimHandler.prototype.execute = function(context) {
52053 var ids = this._moddle.ids,
52054 id = context.id,
52055 element = context.element,
52056 claiming = context.claiming;
52057
52058 if (claiming) {
52059 ids.claim(id, element);
52060 } else {
52061 ids.unclaim(id);
52062 }
52063 };
52064
52065 /**
52066 * Command revert implementation.
52067 */
52068 IdClaimHandler.prototype.revert = function(context) {
52069 var ids = this._moddle.ids,
52070 id = context.id,
52071 element = context.element,
52072 claiming = context.claiming;
52073
52074 if (claiming) {
52075 ids.unclaim(id);
52076 } else {
52077 ids.claim(id, element);
52078 }
52079 };
52080
52081 var DEFAULT_COLORS = {
52082 fill: undefined,
52083 stroke: undefined
52084 };
52085
52086
52087 function SetColorHandler(commandStack) {
52088 this._commandStack = commandStack;
52089
52090 this._normalizeColor = function(color) {
52091
52092 // Remove color for falsy values.
52093 if (!color) {
52094 return undefined;
52095 }
52096
52097 if (isString(color)) {
52098 var hexColor = colorToHex(color);
52099
52100 if (hexColor) {
52101 return hexColor;
52102 }
52103 }
52104
52105 throw new Error('invalid color value: ' + color);
52106 };
52107 }
52108
52109 SetColorHandler.$inject = [
52110 'commandStack'
52111 ];
52112
52113
52114 SetColorHandler.prototype.postExecute = function(context) {
52115 var elements = context.elements,
52116 colors = context.colors || DEFAULT_COLORS;
52117
52118 var self = this;
52119
52120 var di = {};
52121
52122 if ('fill' in colors) {
52123 assign(di, {
52124 'background-color': this._normalizeColor(colors.fill) });
52125 }
52126
52127 if ('stroke' in colors) {
52128 assign(di, {
52129 'border-color': this._normalizeColor(colors.stroke) });
52130 }
52131
52132 forEach(elements, function(element) {
52133 var assignedDi = isConnection$8(element) ? pick(di, [ 'border-color' ]) : di;
52134
52135 // TODO @barmac: remove once we drop bpmn.io properties
52136 ensureLegacySupport(assignedDi);
52137
52138 self._commandStack.execute('element.updateProperties', {
52139 element: element,
52140 properties: {
52141 di: assignedDi
52142 }
52143 });
52144 });
52145
52146 };
52147
52148 /**
52149 * Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names and for colors
52150 * with alpha less than 1.0. This depends on `<canvas>` serialization of the `context.fillStyle`.
52151 * Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
52152 *
52153 * @example
52154 * ```js
52155 * var color = 'fuchsia';
52156 * console.log(colorToHex(color));
52157 * // "#ff00ff"
52158 * color = 'rgba(1,2,3,0.4)';
52159 * console.log(colorToHex(color));
52160 * // null
52161 * ```
52162 *
52163 * @param {string} color
52164 * @returns {string|null}
52165 */
52166 function colorToHex(color) {
52167 var context = document.createElement('canvas').getContext('2d');
52168
52169 // (0) Start with transparent to account for browser default values.
52170 context.fillStyle = 'transparent';
52171
52172 // (1) Assign color so that it's serialized.
52173 context.fillStyle = color;
52174
52175 // (2) Return null for non-hex serialization result.
52176 return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null;
52177 }
52178
52179 function isConnection$8(element) {
52180 return !!element.waypoints;
52181 }
52182
52183 /**
52184 * Add legacy properties if required.
52185 * @param {{ 'border-color': string?, 'background-color': string? }} di
52186 */
52187 function ensureLegacySupport(di) {
52188 if ('border-color' in di) {
52189 di.stroke = di['border-color'];
52190 }
52191
52192 if ('background-color' in di) {
52193 di.fill = di['background-color'];
52194 }
52195 }
52196
52197 var NULL_DIMENSIONS$1 = {
52198 width: 0,
52199 height: 0
52200 };
52201
52202
52203 /**
52204 * A handler that updates the text of a BPMN element.
52205 */
52206 function UpdateLabelHandler(modeling, textRenderer) {
52207
52208 /**
52209 * Set the label and return the changed elements.
52210 *
52211 * Element parameter can be label itself or connection (i.e. sequence flow).
52212 *
52213 * @param {djs.model.Base} element
52214 * @param {string} text
52215 */
52216 function setText(element, text) {
52217
52218 // external label if present
52219 var label = element.label || element;
52220
52221 var labelTarget = element.labelTarget || element;
52222
52223 setLabel(label, text);
52224
52225 return [ label, labelTarget ];
52226 }
52227
52228 function preExecute(ctx) {
52229 var element = ctx.element,
52230 businessObject = element.businessObject,
52231 newLabel = ctx.newLabel;
52232
52233 if (!isLabel(element)
52234 && isLabelExternal(element)
52235 && !hasExternalLabel(element)
52236 && !isEmptyText$1(newLabel)) {
52237
52238 // create label
52239 var paddingTop = 7;
52240
52241 var labelCenter = getExternalLabelMid(element);
52242
52243 labelCenter = {
52244 x: labelCenter.x,
52245 y: labelCenter.y + paddingTop
52246 };
52247
52248 modeling.createLabel(element, labelCenter, {
52249 id: businessObject.id + '_label',
52250 businessObject: businessObject
52251 });
52252 }
52253 }
52254
52255 function execute(ctx) {
52256 ctx.oldLabel = getLabel(ctx.element);
52257 return setText(ctx.element, ctx.newLabel);
52258 }
52259
52260 function revert(ctx) {
52261 return setText(ctx.element, ctx.oldLabel);
52262 }
52263
52264 function postExecute(ctx) {
52265 var element = ctx.element,
52266 label = element.label || element,
52267 newLabel = ctx.newLabel,
52268 newBounds = ctx.newBounds,
52269 hints = ctx.hints || {};
52270
52271 // ignore internal labels for elements except text annotations
52272 if (!isLabel(label) && !is$1(label, 'bpmn:TextAnnotation')) {
52273 return;
52274 }
52275
52276 if (isLabel(label) && isEmptyText$1(newLabel)) {
52277
52278 if (hints.removeShape !== false) {
52279 modeling.removeShape(label, { unsetLabel: false });
52280 }
52281
52282 return;
52283 }
52284
52285 var text = getLabel(label);
52286
52287 // resize element based on label _or_ pre-defined bounds
52288 if (typeof newBounds === 'undefined') {
52289 newBounds = textRenderer.getExternalLabelBounds(label, text);
52290 }
52291
52292 // setting newBounds to false or _null_ will
52293 // disable the postExecute resize operation
52294 if (newBounds) {
52295 modeling.resizeShape(label, newBounds, NULL_DIMENSIONS$1);
52296 }
52297 }
52298
52299 // API
52300
52301 this.preExecute = preExecute;
52302 this.execute = execute;
52303 this.revert = revert;
52304 this.postExecute = postExecute;
52305 }
52306
52307 UpdateLabelHandler.$inject = [
52308 'modeling',
52309 'textRenderer'
52310 ];
52311
52312
52313 // helpers ///////////////////////
52314
52315 function isEmptyText$1(label) {
52316 return !label || !label.trim();
52317 }
52318
52319 /**
52320 * BPMN 2.0 modeling features activator
52321 *
52322 * @param {EventBus} eventBus
52323 * @param {ElementFactory} elementFactory
52324 * @param {CommandStack} commandStack
52325 * @param {BpmnRules} bpmnRules
52326 */
52327 function Modeling$1(
52328 eventBus, elementFactory, commandStack,
52329 bpmnRules) {
52330
52331 Modeling.call(this, eventBus, elementFactory, commandStack);
52332
52333 this._bpmnRules = bpmnRules;
52334 }
52335
52336 inherits_browser(Modeling$1, Modeling);
52337
52338 Modeling$1.$inject = [
52339 'eventBus',
52340 'elementFactory',
52341 'commandStack',
52342 'bpmnRules'
52343 ];
52344
52345
52346 Modeling$1.prototype.getHandlers = function() {
52347 var handlers = Modeling.prototype.getHandlers.call(this);
52348
52349 handlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler_1;
52350 handlers['element.updateProperties'] = UpdatePropertiesHandler;
52351 handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
52352 handlers['lane.add'] = AddLaneHandler;
52353 handlers['lane.resize'] = ResizeLaneHandler;
52354 handlers['lane.split'] = SplitLaneHandler;
52355 handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
52356 handlers['id.updateClaim'] = IdClaimHandler;
52357 handlers['element.setColor'] = SetColorHandler;
52358 handlers['element.updateLabel'] = UpdateLabelHandler;
52359
52360 return handlers;
52361 };
52362
52363
52364 Modeling$1.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
52365 this._commandStack.execute('element.updateLabel', {
52366 element: element,
52367 newLabel: newLabel,
52368 newBounds: newBounds,
52369 hints: hints || {}
52370 });
52371 };
52372
52373
52374 Modeling$1.prototype.connect = function(source, target, attrs, hints) {
52375
52376 var bpmnRules = this._bpmnRules;
52377
52378 if (!attrs) {
52379 attrs = bpmnRules.canConnect(source, target);
52380 }
52381
52382 if (!attrs) {
52383 return;
52384 }
52385
52386 return this.createConnection(source, target, attrs, source.parent, hints);
52387 };
52388
52389
52390 Modeling$1.prototype.updateModdleProperties = function(element, moddleElement, properties) {
52391 this._commandStack.execute('element.updateModdleProperties', {
52392 element: element,
52393 moddleElement: moddleElement,
52394 properties: properties
52395 });
52396 };
52397
52398 Modeling$1.prototype.updateProperties = function(element, properties) {
52399 this._commandStack.execute('element.updateProperties', {
52400 element: element,
52401 properties: properties
52402 });
52403 };
52404
52405 Modeling$1.prototype.resizeLane = function(laneShape, newBounds, balanced) {
52406 this._commandStack.execute('lane.resize', {
52407 shape: laneShape,
52408 newBounds: newBounds,
52409 balanced: balanced
52410 });
52411 };
52412
52413 Modeling$1.prototype.addLane = function(targetLaneShape, location) {
52414 var context = {
52415 shape: targetLaneShape,
52416 location: location
52417 };
52418
52419 this._commandStack.execute('lane.add', context);
52420
52421 return context.newLane;
52422 };
52423
52424 Modeling$1.prototype.splitLane = function(targetLane, count) {
52425 this._commandStack.execute('lane.split', {
52426 shape: targetLane,
52427 count: count
52428 });
52429 };
52430
52431 /**
52432 * Transform the current diagram into a collaboration.
52433 *
52434 * @return {djs.model.Root} the new root element
52435 */
52436 Modeling$1.prototype.makeCollaboration = function() {
52437
52438 var collaborationElement = this._create('root', {
52439 type: 'bpmn:Collaboration'
52440 });
52441
52442 var context = {
52443 newRoot: collaborationElement
52444 };
52445
52446 this._commandStack.execute('canvas.updateRoot', context);
52447
52448 return collaborationElement;
52449 };
52450
52451 Modeling$1.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {
52452
52453 this._commandStack.execute('lane.updateRefs', {
52454 flowNodeShapes: flowNodeShapes,
52455 laneShapes: laneShapes
52456 });
52457 };
52458
52459 /**
52460 * Transform the current diagram into a process.
52461 *
52462 * @return {djs.model.Root} the new root element
52463 */
52464 Modeling$1.prototype.makeProcess = function() {
52465
52466 var processElement = this._create('root', {
52467 type: 'bpmn:Process'
52468 });
52469
52470 var context = {
52471 newRoot: processElement
52472 };
52473
52474 this._commandStack.execute('canvas.updateRoot', context);
52475 };
52476
52477
52478 Modeling$1.prototype.claimId = function(id, moddleElement) {
52479 this._commandStack.execute('id.updateClaim', {
52480 id: id,
52481 element: moddleElement,
52482 claiming: true
52483 });
52484 };
52485
52486
52487 Modeling$1.prototype.unclaimId = function(id, moddleElement) {
52488 this._commandStack.execute('id.updateClaim', {
52489 id: id,
52490 element: moddleElement
52491 });
52492 };
52493
52494 Modeling$1.prototype.setColor = function(elements, colors) {
52495 if (!elements.length) {
52496 elements = [ elements ];
52497 }
52498
52499 this._commandStack.execute('element.setColor', {
52500 elements: elements,
52501 colors: colors
52502 });
52503 };
52504
52505 /**
52506 * A base connection layouter implementation
52507 * that layouts the connection by directly connecting
52508 * mid(source) + mid(target).
52509 */
52510 function BaseLayouter() {}
52511
52512
52513 /**
52514 * Return the new layouted waypoints for the given connection.
52515 *
52516 * The connection passed is still unchanged; you may figure out about
52517 * the new connection start / end via the layout hints provided.
52518 *
52519 * @param {djs.model.Connection} connection
52520 * @param {Object} [hints]
52521 * @param {Point} [hints.connectionStart]
52522 * @param {Point} [hints.connectionEnd]
52523 * @param {Point} [hints.source]
52524 * @param {Point} [hints.target]
52525 *
52526 * @return {Array<Point>} the layouted connection waypoints
52527 */
52528 BaseLayouter.prototype.layoutConnection = function(connection, hints) {
52529
52530 hints = hints || {};
52531
52532 return [
52533 hints.connectionStart || getMid(hints.source || connection.source),
52534 hints.connectionEnd || getMid(hints.target || connection.target)
52535 ];
52536 };
52537
52538 var MIN_SEGMENT_LENGTH = 20,
52539 POINT_ORIENTATION_PADDING = 5;
52540
52541 var round$a = Math.round;
52542
52543 var INTERSECTION_THRESHOLD$1 = 20,
52544 ORIENTATION_THRESHOLD = {
52545 'h:h': 20,
52546 'v:v': 20,
52547 'h:v': -10,
52548 'v:h': -10
52549 };
52550
52551 function needsTurn(orientation, startDirection) {
52552 return !{
52553 t: /top/,
52554 r: /right/,
52555 b: /bottom/,
52556 l: /left/,
52557 h: /./,
52558 v: /./
52559 }[startDirection].test(orientation);
52560 }
52561
52562 function canLayoutStraight(direction, targetOrientation) {
52563 return {
52564 t: /top/,
52565 r: /right/,
52566 b: /bottom/,
52567 l: /left/,
52568 h: /left|right/,
52569 v: /top|bottom/
52570 }[direction].test(targetOrientation);
52571 }
52572
52573 function getSegmentBendpoints(a, b, directions) {
52574 var orientation = getOrientation(b, a, POINT_ORIENTATION_PADDING);
52575
52576 var startDirection = directions.split(':')[0];
52577
52578 var xmid = round$a((b.x - a.x) / 2 + a.x),
52579 ymid = round$a((b.y - a.y) / 2 + a.y);
52580
52581 var segmentEnd, segmentDirections;
52582
52583 var layoutStraight = canLayoutStraight(startDirection, orientation),
52584 layoutHorizontal = /h|r|l/.test(startDirection),
52585 layoutTurn = false;
52586
52587 var turnNextDirections = false;
52588
52589 if (layoutStraight) {
52590 segmentEnd = layoutHorizontal ? { x: xmid, y: a.y } : { x: a.x, y: ymid };
52591
52592 segmentDirections = layoutHorizontal ? 'h:h' : 'v:v';
52593 } else {
52594 layoutTurn = needsTurn(orientation, startDirection);
52595
52596 segmentDirections = layoutHorizontal ? 'h:v' : 'v:h';
52597
52598 if (layoutTurn) {
52599
52600 if (layoutHorizontal) {
52601 turnNextDirections = ymid === a.y;
52602
52603 segmentEnd = {
52604 x: a.x + MIN_SEGMENT_LENGTH * (/l/.test(startDirection) ? -1 : 1),
52605 y: turnNextDirections ? ymid + MIN_SEGMENT_LENGTH : ymid
52606 };
52607 } else {
52608 turnNextDirections = xmid === a.x;
52609
52610 segmentEnd = {
52611 x: turnNextDirections ? xmid + MIN_SEGMENT_LENGTH : xmid,
52612 y: a.y + MIN_SEGMENT_LENGTH * (/t/.test(startDirection) ? -1 : 1)
52613 };
52614 }
52615
52616 } else {
52617 segmentEnd = {
52618 x: xmid,
52619 y: ymid
52620 };
52621 }
52622 }
52623
52624 return {
52625 waypoints: getBendpoints(a, segmentEnd, segmentDirections).concat(segmentEnd),
52626 directions: segmentDirections,
52627 turnNextDirections: turnNextDirections
52628 };
52629 }
52630
52631 function getStartSegment(a, b, directions) {
52632 return getSegmentBendpoints(a, b, directions);
52633 }
52634
52635 function getEndSegment(a, b, directions) {
52636 var invertedSegment = getSegmentBendpoints(b, a, invertDirections(directions));
52637
52638 return {
52639 waypoints: invertedSegment.waypoints.slice().reverse(),
52640 directions: invertDirections(invertedSegment.directions),
52641 turnNextDirections: invertedSegment.turnNextDirections
52642 };
52643 }
52644
52645 function getMidSegment(startSegment, endSegment) {
52646
52647 var startDirection = startSegment.directions.split(':')[1],
52648 endDirection = endSegment.directions.split(':')[0];
52649
52650 if (startSegment.turnNextDirections) {
52651 startDirection = startDirection == 'h' ? 'v' : 'h';
52652 }
52653
52654 if (endSegment.turnNextDirections) {
52655 endDirection = endDirection == 'h' ? 'v' : 'h';
52656 }
52657
52658 var directions = startDirection + ':' + endDirection;
52659
52660 var bendpoints = getBendpoints(
52661 startSegment.waypoints[startSegment.waypoints.length - 1],
52662 endSegment.waypoints[0],
52663 directions
52664 );
52665
52666 return {
52667 waypoints: bendpoints,
52668 directions: directions
52669 };
52670 }
52671
52672 function invertDirections(directions) {
52673 return directions.split(':').reverse().join(':');
52674 }
52675
52676 /**
52677 * Handle simple layouts with maximum two bendpoints.
52678 */
52679 function getSimpleBendpoints(a, b, directions) {
52680
52681 var xmid = round$a((b.x - a.x) / 2 + a.x),
52682 ymid = round$a((b.y - a.y) / 2 + a.y);
52683
52684 // one point, right or left from a
52685 if (directions === 'h:v') {
52686 return [ { x: b.x, y: a.y } ];
52687 }
52688
52689 // one point, above or below a
52690 if (directions === 'v:h') {
52691 return [ { x: a.x, y: b.y } ];
52692 }
52693
52694 // vertical segment between a and b
52695 if (directions === 'h:h') {
52696 return [
52697 { x: xmid, y: a.y },
52698 { x: xmid, y: b.y }
52699 ];
52700 }
52701
52702 // horizontal segment between a and b
52703 if (directions === 'v:v') {
52704 return [
52705 { x: a.x, y: ymid },
52706 { x: b.x, y: ymid }
52707 ];
52708 }
52709
52710 throw new Error('invalid directions: can only handle varians of [hv]:[hv]');
52711 }
52712
52713
52714 /**
52715 * Returns the mid points for a manhattan connection between two points.
52716 *
52717 * @example h:h (horizontal:horizontal)
52718 *
52719 * [a]----[x]
52720 * |
52721 * [x]----[b]
52722 *
52723 * @example h:v (horizontal:vertical)
52724 *
52725 * [a]----[x]
52726 * |
52727 * [b]
52728 *
52729 * @example h:r (horizontal:right)
52730 *
52731 * [a]----[x]
52732 * |
52733 * [b]-[x]
52734 *
52735 * @param {Point} a
52736 * @param {Point} b
52737 * @param {string} directions
52738 *
52739 * @return {Array<Point>}
52740 */
52741 function getBendpoints(a, b, directions) {
52742 directions = directions || 'h:h';
52743
52744 if (!isValidDirections(directions)) {
52745 throw new Error(
52746 'unknown directions: <' + directions + '>: ' +
52747 'must be specified as <start>:<end> ' +
52748 'with start/end in { h,v,t,r,b,l }'
52749 );
52750 }
52751
52752 // compute explicit directions, involving trbl dockings
52753 // using a three segmented layouting algorithm
52754 if (isExplicitDirections(directions)) {
52755 var startSegment = getStartSegment(a, b, directions),
52756 endSegment = getEndSegment(a, b, directions),
52757 midSegment = getMidSegment(startSegment, endSegment);
52758
52759 return [].concat(
52760 startSegment.waypoints,
52761 midSegment.waypoints,
52762 endSegment.waypoints
52763 );
52764 }
52765
52766 // handle simple [hv]:[hv] cases that can be easily computed
52767 return getSimpleBendpoints(a, b, directions);
52768 }
52769
52770 /**
52771 * Create a connection between the two points according
52772 * to the manhattan layout (only horizontal and vertical) edges.
52773 *
52774 * @param {Point} a
52775 * @param {Point} b
52776 *
52777 * @param {string} [directions='h:h'] specifies manhattan directions for each point as {adirection}:{bdirection}.
52778 A directionfor a point is either `h` (horizontal) or `v` (vertical)
52779 *
52780 * @return {Array<Point>}
52781 */
52782 function connectPoints(a, b, directions) {
52783
52784 var points = getBendpoints(a, b, directions);
52785
52786 points.unshift(a);
52787 points.push(b);
52788
52789 return withoutRedundantPoints(points);
52790 }
52791
52792
52793 /**
52794 * Connect two rectangles using a manhattan layouted connection.
52795 *
52796 * @param {Bounds} source source rectangle
52797 * @param {Bounds} target target rectangle
52798 * @param {Point} [start] source docking
52799 * @param {Point} [end] target docking
52800 *
52801 * @param {Object} [hints]
52802 * @param {string} [hints.preserveDocking=source] preserve docking on selected side
52803 * @param {Array<string>} [hints.preferredLayouts]
52804 * @param {Point|boolean} [hints.connectionStart] whether the start changed
52805 * @param {Point|boolean} [hints.connectionEnd] whether the end changed
52806 *
52807 * @return {Array<Point>} connection points
52808 */
52809 function connectRectangles(source, target, start, end, hints) {
52810
52811 var preferredLayouts = hints && hints.preferredLayouts || [];
52812
52813 var preferredLayout = without(preferredLayouts, 'straight')[0] || 'h:h';
52814
52815 var threshold = ORIENTATION_THRESHOLD[preferredLayout] || 0;
52816
52817 var orientation = getOrientation(source, target, threshold);
52818
52819 var directions = getDirections(orientation, preferredLayout);
52820
52821 start = start || getMid(source);
52822 end = end || getMid(target);
52823
52824 var directionSplit = directions.split(':');
52825
52826 // compute actual docking points for start / end
52827 // this ensures we properly layout only parts of the
52828 // connection that lies in between the two rectangles
52829 var startDocking = getDockingPoint(start, source, directionSplit[0], invertOrientation(orientation)),
52830 endDocking = getDockingPoint(end, target, directionSplit[1], orientation);
52831
52832 return connectPoints(startDocking, endDocking, directions);
52833 }
52834
52835
52836 /**
52837 * Repair the connection between two rectangles, of which one has been updated.
52838 *
52839 * @param {Bounds} source
52840 * @param {Bounds} target
52841 * @param {Point} [start]
52842 * @param {Point} [end]
52843 * @param {Array<Point>} [waypoints]
52844 * @param {Object} [hints]
52845 * @param {Array<string>} [hints.preferredLayouts] list of preferred layouts
52846 * @param {boolean} [hints.connectionStart]
52847 * @param {boolean} [hints.connectionEnd]
52848 *
52849 * @return {Array<Point>} repaired waypoints
52850 */
52851 function repairConnection(source, target, start, end, waypoints, hints) {
52852
52853 if (isArray(start)) {
52854 waypoints = start;
52855 hints = end;
52856
52857 start = getMid(source);
52858 end = getMid(target);
52859 }
52860
52861 hints = assign({ preferredLayouts: [] }, hints);
52862 waypoints = waypoints || [];
52863
52864 var preferredLayouts = hints.preferredLayouts,
52865 preferStraight = preferredLayouts.indexOf('straight') !== -1,
52866 repairedWaypoints;
52867
52868 // just layout non-existing or simple connections
52869 // attempt to render straight lines, if required
52870
52871 // attempt to layout a straight line
52872 repairedWaypoints = preferStraight && tryLayoutStraight(source, target, start, end, hints);
52873
52874 if (repairedWaypoints) {
52875 return repairedWaypoints;
52876 }
52877
52878 // try to layout from end
52879 repairedWaypoints = hints.connectionEnd && tryRepairConnectionEnd(target, source, end, waypoints);
52880
52881 if (repairedWaypoints) {
52882 return repairedWaypoints;
52883 }
52884
52885 // try to layout from start
52886 repairedWaypoints = hints.connectionStart && tryRepairConnectionStart(source, target, start, waypoints);
52887
52888 if (repairedWaypoints) {
52889 return repairedWaypoints;
52890 }
52891
52892 // or whether nothing seems to have changed
52893 if (!hints.connectionStart && !hints.connectionEnd && waypoints && waypoints.length) {
52894 return waypoints;
52895 }
52896
52897 // simply reconnect if nothing else worked
52898 return connectRectangles(source, target, start, end, hints);
52899 }
52900
52901
52902 function inRange(a, start, end) {
52903 return a >= start && a <= end;
52904 }
52905
52906 function isInRange(axis, a, b) {
52907 var size = {
52908 x: 'width',
52909 y: 'height'
52910 };
52911
52912 return inRange(a[axis], b[axis], b[axis] + b[size[axis]]);
52913 }
52914
52915 /**
52916 * Layout a straight connection
52917 *
52918 * @param {Bounds} source
52919 * @param {Bounds} target
52920 * @param {Point} start
52921 * @param {Point} end
52922 * @param {Object} [hints]
52923 *
52924 * @return {Array<Point>|null} waypoints if straight layout worked
52925 */
52926 function tryLayoutStraight(source, target, start, end, hints) {
52927 var axis = {},
52928 primaryAxis,
52929 orientation;
52930
52931 orientation = getOrientation(source, target);
52932
52933 // only layout a straight connection if shapes are
52934 // horizontally or vertically aligned
52935 if (!/^(top|bottom|left|right)$/.test(orientation)) {
52936 return null;
52937 }
52938
52939 if (/top|bottom/.test(orientation)) {
52940 primaryAxis = 'x';
52941 }
52942
52943 if (/left|right/.test(orientation)) {
52944 primaryAxis = 'y';
52945 }
52946
52947 if (hints.preserveDocking === 'target') {
52948
52949 if (!isInRange(primaryAxis, end, source)) {
52950 return null;
52951 }
52952
52953 axis[primaryAxis] = end[primaryAxis];
52954
52955 return [
52956 {
52957 x: axis.x !== undefined ? axis.x : start.x,
52958 y: axis.y !== undefined ? axis.y : start.y,
52959 original: {
52960 x: axis.x !== undefined ? axis.x : start.x,
52961 y: axis.y !== undefined ? axis.y : start.y
52962 }
52963 },
52964 {
52965 x: end.x,
52966 y: end.y
52967 }
52968 ];
52969
52970 } else {
52971
52972 if (!isInRange(primaryAxis, start, target)) {
52973 return null;
52974 }
52975
52976 axis[primaryAxis] = start[primaryAxis];
52977
52978 return [
52979 {
52980 x: start.x,
52981 y: start.y
52982 },
52983 {
52984 x: axis.x !== undefined ? axis.x : end.x,
52985 y: axis.y !== undefined ? axis.y : end.y,
52986 original: {
52987 x: axis.x !== undefined ? axis.x : end.x,
52988 y: axis.y !== undefined ? axis.y : end.y
52989 }
52990 }
52991 ];
52992 }
52993
52994 }
52995
52996 /**
52997 * Repair a connection from start.
52998 *
52999 * @param {Bounds} moved
53000 * @param {Bounds} other
53001 * @param {Point} newDocking
53002 * @param {Array<Point>} points originalPoints from moved to other
53003 *
53004 * @return {Array<Point>|null} the repaired points between the two rectangles
53005 */
53006 function tryRepairConnectionStart(moved, other, newDocking, points) {
53007 return _tryRepairConnectionSide(moved, other, newDocking, points);
53008 }
53009
53010 /**
53011 * Repair a connection from end.
53012 *
53013 * @param {Bounds} moved
53014 * @param {Bounds} other
53015 * @param {Point} newDocking
53016 * @param {Array<Point>} points originalPoints from moved to other
53017 *
53018 * @return {Array<Point>|null} the repaired points between the two rectangles
53019 */
53020 function tryRepairConnectionEnd(moved, other, newDocking, points) {
53021 var waypoints = points.slice().reverse();
53022
53023 waypoints = _tryRepairConnectionSide(moved, other, newDocking, waypoints);
53024
53025 return waypoints ? waypoints.reverse() : null;
53026 }
53027
53028 /**
53029 * Repair a connection from one side that moved.
53030 *
53031 * @param {Bounds} moved
53032 * @param {Bounds} other
53033 * @param {Point} newDocking
53034 * @param {Array<Point>} points originalPoints from moved to other
53035 *
53036 * @return {Array<Point>} the repaired points between the two rectangles
53037 */
53038 function _tryRepairConnectionSide(moved, other, newDocking, points) {
53039
53040 function needsRelayout(points) {
53041 if (points.length < 3) {
53042 return true;
53043 }
53044
53045 if (points.length > 4) {
53046 return false;
53047 }
53048
53049 // relayout if two points overlap
53050 // this is most likely due to
53051 return !!find(points, function(p, idx) {
53052 var q = points[idx - 1];
53053
53054 return q && pointDistance(p, q) < 3;
53055 });
53056 }
53057
53058 function repairBendpoint(candidate, oldPeer, newPeer) {
53059
53060 var alignment = pointsAligned(oldPeer, candidate);
53061
53062 switch (alignment) {
53063 case 'v':
53064
53065 // repair horizontal alignment
53066 return { x: newPeer.x, y: candidate.y };
53067 case 'h':
53068
53069 // repair vertical alignment
53070 return { x: candidate.x, y: newPeer.y };
53071 }
53072
53073 return { x: candidate.x, y: candidate. y };
53074 }
53075
53076 function removeOverlapping(points, a, b) {
53077 var i;
53078
53079 for (i = points.length - 2; i !== 0; i--) {
53080
53081 // intersects (?) break, remove all bendpoints up to this one and relayout
53082 if (pointInRect(points[i], a, INTERSECTION_THRESHOLD$1) ||
53083 pointInRect(points[i], b, INTERSECTION_THRESHOLD$1)) {
53084
53085 // return sliced old connection
53086 return points.slice(i);
53087 }
53088 }
53089
53090 return points;
53091 }
53092
53093 // (0) only repair what has layoutable bendpoints
53094
53095 // (1) if only one bendpoint and on shape moved onto other shapes axis
53096 // (horizontally / vertically), relayout
53097
53098 if (needsRelayout(points)) {
53099 return null;
53100 }
53101
53102 var oldDocking = points[0],
53103 newPoints = points.slice(),
53104 slicedPoints;
53105
53106 // (2) repair only last line segment and only if it was layouted before
53107
53108 newPoints[0] = newDocking;
53109 newPoints[1] = repairBendpoint(newPoints[1], oldDocking, newDocking);
53110
53111
53112 // (3) if shape intersects with any bendpoint after repair,
53113 // remove all segments up to this bendpoint and repair from there
53114 slicedPoints = removeOverlapping(newPoints, moved, other);
53115
53116 if (slicedPoints !== newPoints) {
53117 newPoints = _tryRepairConnectionSide(moved, other, newDocking, slicedPoints);
53118 }
53119
53120 // (4) do NOT repair if repaired bendpoints are aligned
53121 if (newPoints && pointsAligned(newPoints)) {
53122 return null;
53123 }
53124
53125 return newPoints;
53126 }
53127
53128
53129 /**
53130 * Returns the manhattan directions connecting two rectangles
53131 * with the given orientation.
53132 *
53133 * Will always return the default layout, if it is specific
53134 * regarding sides already (trbl).
53135 *
53136 * @example
53137 *
53138 * getDirections('top'); // -> 'v:v'
53139 * getDirections('intersect'); // -> 't:t'
53140 *
53141 * getDirections('top-right', 'v:h'); // -> 'v:h'
53142 * getDirections('top-right', 'h:h'); // -> 'h:h'
53143 *
53144 *
53145 * @param {string} orientation
53146 * @param {string} defaultLayout
53147 *
53148 * @return {string}
53149 */
53150 function getDirections(orientation, defaultLayout) {
53151
53152 // don't override specific trbl directions
53153 if (isExplicitDirections(defaultLayout)) {
53154 return defaultLayout;
53155 }
53156
53157 switch (orientation) {
53158 case 'intersect':
53159 return 't:t';
53160
53161 case 'top':
53162 case 'bottom':
53163 return 'v:v';
53164
53165 case 'left':
53166 case 'right':
53167 return 'h:h';
53168
53169 // 'top-left'
53170 // 'top-right'
53171 // 'bottom-left'
53172 // 'bottom-right'
53173 default:
53174 return defaultLayout;
53175 }
53176 }
53177
53178 function isValidDirections(directions) {
53179 return directions && /^h|v|t|r|b|l:h|v|t|r|b|l$/.test(directions);
53180 }
53181
53182 function isExplicitDirections(directions) {
53183 return directions && /t|r|b|l/.test(directions);
53184 }
53185
53186 function invertOrientation(orientation) {
53187 return {
53188 'top': 'bottom',
53189 'bottom': 'top',
53190 'left': 'right',
53191 'right': 'left',
53192 'top-left': 'bottom-right',
53193 'bottom-right': 'top-left',
53194 'top-right': 'bottom-left',
53195 'bottom-left': 'top-right',
53196 }[orientation];
53197 }
53198
53199 function getDockingPoint(point, rectangle, dockingDirection, targetOrientation) {
53200
53201 // ensure we end up with a specific docking direction
53202 // based on the targetOrientation, if <h|v> is being passed
53203
53204 if (dockingDirection === 'h') {
53205 dockingDirection = /left/.test(targetOrientation) ? 'l' : 'r';
53206 }
53207
53208 if (dockingDirection === 'v') {
53209 dockingDirection = /top/.test(targetOrientation) ? 't' : 'b';
53210 }
53211
53212 if (dockingDirection === 't') {
53213 return { original: point, x: point.x, y: rectangle.y };
53214 }
53215
53216 if (dockingDirection === 'r') {
53217 return { original: point, x: rectangle.x + rectangle.width, y: point.y };
53218 }
53219
53220 if (dockingDirection === 'b') {
53221 return { original: point, x: point.x, y: rectangle.y + rectangle.height };
53222 }
53223
53224 if (dockingDirection === 'l') {
53225 return { original: point, x: rectangle.x, y: point.y };
53226 }
53227
53228 throw new Error('unexpected dockingDirection: <' + dockingDirection + '>');
53229 }
53230
53231
53232 /**
53233 * Return list of waypoints with redundant ones filtered out.
53234 *
53235 * @example
53236 *
53237 * Original points:
53238 *
53239 * [x] ----- [x] ------ [x]
53240 * |
53241 * [x] ----- [x] - [x]
53242 *
53243 * Filtered:
53244 *
53245 * [x] ---------------- [x]
53246 * |
53247 * [x] ----------- [x]
53248 *
53249 * @param {Array<Point>} waypoints
53250 *
53251 * @return {Array<Point>}
53252 */
53253 function withoutRedundantPoints(waypoints) {
53254 return waypoints.reduce(function(points, p, idx) {
53255
53256 var previous = points[points.length - 1],
53257 next = waypoints[idx + 1];
53258
53259 if (!pointsOnLine(previous, next, p, 0)) {
53260 points.push(p);
53261 }
53262
53263 return points;
53264 }, []);
53265 }
53266
53267 var ATTACH_ORIENTATION_PADDING = -10,
53268 BOUNDARY_TO_HOST_THRESHOLD = 40;
53269
53270 var oppositeOrientationMapping = {
53271 'top': 'bottom',
53272 'top-right': 'bottom-left',
53273 'top-left': 'bottom-right',
53274 'right': 'left',
53275 'bottom': 'top',
53276 'bottom-right': 'top-left',
53277 'bottom-left': 'top-right',
53278 'left': 'right'
53279 };
53280
53281 var orientationDirectionMapping = {
53282 top: 't',
53283 right: 'r',
53284 bottom: 'b',
53285 left: 'l'
53286 };
53287
53288
53289 function BpmnLayouter() {}
53290
53291 inherits_browser(BpmnLayouter, BaseLayouter);
53292
53293
53294 BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
53295 if (!hints) {
53296 hints = {};
53297 }
53298
53299 var source = hints.source || connection.source,
53300 target = hints.target || connection.target,
53301 waypoints = hints.waypoints || connection.waypoints,
53302 connectionStart = hints.connectionStart,
53303 connectionEnd = hints.connectionEnd;
53304
53305 var manhattanOptions,
53306 updatedWaypoints;
53307
53308 if (!connectionStart) {
53309 connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source);
53310 }
53311
53312 if (!connectionEnd) {
53313 connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
53314 }
53315
53316 // TODO(nikku): support vertical modeling
53317 // and invert preferredLayouts accordingly
53318
53319 if (is$1(connection, 'bpmn:Association') ||
53320 is$1(connection, 'bpmn:DataAssociation')) {
53321
53322 if (waypoints && !isCompensationAssociation(source, target)) {
53323 return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]);
53324 }
53325 }
53326
53327 if (is$1(connection, 'bpmn:MessageFlow')) {
53328 manhattanOptions = getMessageFlowManhattanOptions(source, target);
53329 } else if (is$1(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {
53330
53331 // layout all connection between flow elements h:h, except for
53332 // (1) outgoing of boundary events -> layout based on attach orientation and target orientation
53333 // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
53334 // (3) loops
53335 if (source === target) {
53336 manhattanOptions = {
53337 preferredLayouts: getLoopPreferredLayout(source, connection)
53338 };
53339 } else if (is$1(source, 'bpmn:BoundaryEvent')) {
53340 manhattanOptions = {
53341 preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd)
53342 };
53343 } else if (isExpandedSubProcess$1(source) || isExpandedSubProcess$1(target)) {
53344 manhattanOptions = getSubProcessManhattanOptions(source);
53345 } else if (is$1(source, 'bpmn:Gateway')) {
53346 manhattanOptions = {
53347 preferredLayouts: [ 'v:h' ]
53348 };
53349 } else if (is$1(target, 'bpmn:Gateway')) {
53350 manhattanOptions = {
53351 preferredLayouts: [ 'h:v' ]
53352 };
53353 } else {
53354 manhattanOptions = {
53355 preferredLayouts: [ 'h:h' ]
53356 };
53357 }
53358 }
53359
53360 if (manhattanOptions) {
53361 manhattanOptions = assign(manhattanOptions, hints);
53362
53363 updatedWaypoints = withoutRedundantPoints(repairConnection(
53364 source,
53365 target,
53366 connectionStart,
53367 connectionEnd,
53368 waypoints,
53369 manhattanOptions
53370 ));
53371 }
53372
53373 return updatedWaypoints || [ connectionStart, connectionEnd ];
53374 };
53375
53376
53377 // helpers //////////
53378
53379 function getAttachOrientation(attachedElement) {
53380 var hostElement = attachedElement.host;
53381
53382 return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING);
53383 }
53384
53385 function getMessageFlowManhattanOptions(source, target) {
53386 return {
53387 preferredLayouts: [ 'straight', 'v:v' ],
53388 preserveDocking: getMessageFlowPreserveDocking(source, target)
53389 };
53390 }
53391
53392 function getMessageFlowPreserveDocking(source, target) {
53393
53394 // (1) docking element connected to participant has precedence
53395 if (is$1(target, 'bpmn:Participant')) {
53396 return 'source';
53397 }
53398
53399 if (is$1(source, 'bpmn:Participant')) {
53400 return 'target';
53401 }
53402
53403 // (2) docking element connected to expanded sub-process has precedence
53404 if (isExpandedSubProcess$1(target)) {
53405 return 'source';
53406 }
53407
53408 if (isExpandedSubProcess$1(source)) {
53409 return 'target';
53410 }
53411
53412 // (3) docking event has precedence
53413 if (is$1(target, 'bpmn:Event')) {
53414 return 'target';
53415 }
53416
53417 if (is$1(source, 'bpmn:Event')) {
53418 return 'source';
53419 }
53420
53421 return null;
53422 }
53423
53424 function getSubProcessManhattanOptions(source) {
53425 return {
53426 preferredLayouts: [ 'straight', 'h:h' ],
53427 preserveDocking: getSubProcessPreserveDocking(source)
53428 };
53429 }
53430
53431 function getSubProcessPreserveDocking(source) {
53432 return isExpandedSubProcess$1(source) ? 'target' : 'source';
53433 }
53434
53435 function getConnectionDocking(point, shape) {
53436 return point ? (point.original || point) : getMid(shape);
53437 }
53438
53439 function isCompensationAssociation(source, target) {
53440 return is$1(target, 'bpmn:Activity') &&
53441 is$1(source, 'bpmn:BoundaryEvent') &&
53442 target.businessObject.isForCompensation;
53443 }
53444
53445 function isExpandedSubProcess$1(element) {
53446 return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
53447 }
53448
53449 function isSame$1(a, b) {
53450 return a === b;
53451 }
53452
53453 function isAnyOrientation(orientation, orientations) {
53454 return orientations.indexOf(orientation) !== -1;
53455 }
53456
53457 function getHorizontalOrientation(orientation) {
53458 var matches = /right|left/.exec(orientation);
53459
53460 return matches && matches[0];
53461 }
53462
53463 function getVerticalOrientation(orientation) {
53464 var matches = /top|bottom/.exec(orientation);
53465
53466 return matches && matches[0];
53467 }
53468
53469 function isOppositeOrientation(a, b) {
53470 return oppositeOrientationMapping[a] === b;
53471 }
53472
53473 function isOppositeHorizontalOrientation(a, b) {
53474 var horizontalOrientation = getHorizontalOrientation(a);
53475
53476 var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
53477
53478 return b.indexOf(oppositeHorizontalOrientation) !== -1;
53479 }
53480
53481 function isOppositeVerticalOrientation(a, b) {
53482 var verticalOrientation = getVerticalOrientation(a);
53483
53484 var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
53485
53486 return b.indexOf(oppositeVerticalOrientation) !== -1;
53487 }
53488
53489 function isHorizontalOrientation(orientation) {
53490 return orientation === 'right' || orientation === 'left';
53491 }
53492
53493 function getLoopPreferredLayout(source, connection) {
53494 var waypoints = connection.waypoints;
53495
53496 var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);
53497
53498 if (orientation === 'top') {
53499 return [ 't:r' ];
53500 } else if (orientation === 'right') {
53501 return [ 'r:b' ];
53502 } else if (orientation === 'left') {
53503 return [ 'l:t' ];
53504 }
53505
53506 return [ 'b:l' ];
53507 }
53508
53509 function getBoundaryEventPreferredLayouts(source, target, end) {
53510 var sourceMid = getMid(source),
53511 targetMid = getMid(target),
53512 attachOrientation = getAttachOrientation(source),
53513 sourceLayout,
53514 targetLayout;
53515
53516 var isLoop = isSame$1(source.host, target);
53517
53518 var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
53519
53520 var targetOrientation = getOrientation(targetMid, sourceMid, {
53521 x: source.width / 2 + target.width / 2,
53522 y: source.height / 2 + target.height / 2
53523 });
53524
53525 if (isLoop) {
53526 return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
53527 }
53528
53529 // source layout
53530 sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);
53531
53532 // target layout
53533 targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);
53534
53535 return [ sourceLayout + ':' + targetLayout ];
53536 }
53537
53538 function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
53539 var orientation = attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation),
53540 sourceLayout = orientationDirectionMapping[ orientation ],
53541 targetLayout;
53542
53543 if (attachedToSide) {
53544 if (isHorizontalOrientation(attachOrientation)) {
53545 targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
53546 } else {
53547 targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
53548 }
53549 } else {
53550 targetLayout = 'v';
53551 }
53552
53553 return [ sourceLayout + ':' + targetLayout ];
53554 }
53555
53556 function shouldConnectToSameSide(axis, source, target, end) {
53557 var threshold = BOUNDARY_TO_HOST_THRESHOLD;
53558
53559 return !(
53560 areCloseOnAxis(axis, end, target, threshold) ||
53561 areCloseOnAxis(axis, end, {
53562 x: target.x + target.width,
53563 y: target.y + target.height
53564 }, threshold) ||
53565 areCloseOnAxis(axis, end, getMid(source), threshold)
53566 );
53567 }
53568
53569 function areCloseOnAxis(axis, a, b, threshold) {
53570 return Math.abs(a[ axis ] - b[ axis ]) < threshold;
53571 }
53572
53573 function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {
53574
53575 // attached to either top, right, bottom or left side
53576 if (attachedToSide) {
53577 return orientationDirectionMapping[ attachOrientation ];
53578 }
53579
53580 // attached to either top-right, top-left, bottom-right or bottom-left corner
53581
53582 // same vertical or opposite horizontal orientation
53583 if (isSame$1(
53584 getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
53585 ) || isOppositeOrientation(
53586 getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
53587 )) {
53588 return orientationDirectionMapping[ getVerticalOrientation(attachOrientation) ];
53589 }
53590
53591 // fallback
53592 return orientationDirectionMapping[ getHorizontalOrientation(attachOrientation) ];
53593 }
53594
53595 function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {
53596
53597 // attached to either top, right, bottom or left side
53598 if (attachedToSide) {
53599 if (isHorizontalOrientation(attachOrientation)) {
53600
53601 // orientation is right or left
53602
53603 // opposite horizontal orientation or same orientation
53604 if (
53605 isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
53606 isSame$1(attachOrientation, targetOrientation)
53607 ) {
53608 return 'h';
53609 }
53610
53611 // fallback
53612 return 'v';
53613 } else {
53614
53615 // orientation is top or bottom
53616
53617 // opposite vertical orientation or same orientation
53618 if (
53619 isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
53620 isSame$1(attachOrientation, targetOrientation)
53621 ) {
53622 return 'v';
53623 }
53624
53625 // fallback
53626 return 'h';
53627 }
53628 }
53629
53630 // attached to either top-right, top-left, bottom-right or bottom-left corner
53631
53632 // orientation is right, left
53633 // or same vertical orientation but also right or left
53634 if (isHorizontalOrientation(targetOrientation) ||
53635 (isSame$1(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
53636 getHorizontalOrientation(targetOrientation))) {
53637 return 'h';
53638 } else {
53639 return 'v';
53640 }
53641 }
53642
53643 function dockingToPoint(docking) {
53644
53645 // use the dockings actual point and
53646 // retain the original docking
53647 return assign({ original: docking.point.original || docking.point }, docking.actual);
53648 }
53649
53650
53651 /**
53652 * A {@link ConnectionDocking} that crops connection waypoints based on
53653 * the path(s) of the connection source and target.
53654 *
53655 * @param {djs.core.ElementRegistry} elementRegistry
53656 */
53657 function CroppingConnectionDocking(elementRegistry, graphicsFactory) {
53658 this._elementRegistry = elementRegistry;
53659 this._graphicsFactory = graphicsFactory;
53660 }
53661
53662 CroppingConnectionDocking.$inject = [ 'elementRegistry', 'graphicsFactory' ];
53663
53664
53665 /**
53666 * @inheritDoc ConnectionDocking#getCroppedWaypoints
53667 */
53668 CroppingConnectionDocking.prototype.getCroppedWaypoints = function(connection, source, target) {
53669
53670 source = source || connection.source;
53671 target = target || connection.target;
53672
53673 var sourceDocking = this.getDockingPoint(connection, source, true),
53674 targetDocking = this.getDockingPoint(connection, target);
53675
53676 var croppedWaypoints = connection.waypoints.slice(sourceDocking.idx + 1, targetDocking.idx);
53677
53678 croppedWaypoints.unshift(dockingToPoint(sourceDocking));
53679 croppedWaypoints.push(dockingToPoint(targetDocking));
53680
53681 return croppedWaypoints;
53682 };
53683
53684 /**
53685 * Return the connection docking point on the specified shape
53686 *
53687 * @inheritDoc ConnectionDocking#getDockingPoint
53688 */
53689 CroppingConnectionDocking.prototype.getDockingPoint = function(connection, shape, dockStart) {
53690
53691 var waypoints = connection.waypoints,
53692 dockingIdx,
53693 dockingPoint,
53694 croppedPoint;
53695
53696 dockingIdx = dockStart ? 0 : waypoints.length - 1;
53697 dockingPoint = waypoints[dockingIdx];
53698
53699 croppedPoint = this._getIntersection(shape, connection, dockStart);
53700
53701 return {
53702 point: dockingPoint,
53703 actual: croppedPoint || dockingPoint,
53704 idx: dockingIdx
53705 };
53706 };
53707
53708
53709 // helpers //////////////////////
53710
53711 CroppingConnectionDocking.prototype._getIntersection = function(shape, connection, takeFirst) {
53712
53713 var shapePath = this._getShapePath(shape),
53714 connectionPath = this._getConnectionPath(connection);
53715
53716 return getElementLineIntersection(shapePath, connectionPath, takeFirst);
53717 };
53718
53719 CroppingConnectionDocking.prototype._getConnectionPath = function(connection) {
53720 return this._graphicsFactory.getConnectionPath(connection);
53721 };
53722
53723 CroppingConnectionDocking.prototype._getShapePath = function(shape) {
53724 return this._graphicsFactory.getShapePath(shape);
53725 };
53726
53727 CroppingConnectionDocking.prototype._getGfx = function(element) {
53728 return this._elementRegistry.getGraphics(element);
53729 };
53730
53731 var ModelingModule = {
53732 __init__: [
53733 'modeling',
53734 'bpmnUpdater'
53735 ],
53736 __depends__: [
53737 BehaviorModule,
53738 RulesModule$1,
53739 DiOrderingModule,
53740 OrderingModule,
53741 ReplaceModule$1,
53742 CommandModule,
53743 TooltipsModule,
53744 LabelSupportModule,
53745 AttachSupportModule,
53746 SelectionModule,
53747 ChangeSupportModule,
53748 SpaceToolModule
53749 ],
53750 bpmnFactory: [ 'type', BpmnFactory ],
53751 bpmnUpdater: [ 'type', BpmnUpdater ],
53752 elementFactory: [ 'type', ElementFactory$1 ],
53753 modeling: [ 'type', Modeling$1 ],
53754 layouter: [ 'type', BpmnLayouter ],
53755 connectionDocking: [ 'type', CroppingConnectionDocking ]
53756 };
53757
53758 var LOW_PRIORITY$k = 500,
53759 MEDIUM_PRIORITY = 1250,
53760 HIGH_PRIORITY$h = 1500;
53761
53762 var round$b = Math.round;
53763
53764 function mid$2(element) {
53765 return {
53766 x: element.x + round$b(element.width / 2),
53767 y: element.y + round$b(element.height / 2)
53768 };
53769 }
53770
53771 /**
53772 * A plugin that makes shapes draggable / droppable.
53773 *
53774 * @param {EventBus} eventBus
53775 * @param {Dragging} dragging
53776 * @param {Modeling} modeling
53777 * @param {Selection} selection
53778 * @param {Rules} rules
53779 */
53780 function MoveEvents(
53781 eventBus, dragging, modeling,
53782 selection, rules) {
53783
53784 // rules
53785
53786 function canMove(shapes, delta, position, target) {
53787
53788 return rules.allowed('elements.move', {
53789 shapes: shapes,
53790 delta: delta,
53791 position: position,
53792 target: target
53793 });
53794 }
53795
53796
53797 // move events
53798
53799 // assign a high priority to this handler to setup the environment
53800 // others may hook up later, e.g. at default priority and modify
53801 // the move environment.
53802 //
53803 // This sets up the context with
53804 //
53805 // * shape: the primary shape being moved
53806 // * shapes: a list of shapes to be moved
53807 // * validatedShapes: a list of shapes that are being checked
53808 // against the rules before and during move
53809 //
53810 eventBus.on('shape.move.start', HIGH_PRIORITY$h, function(event) {
53811
53812 var context = event.context,
53813 shape = event.shape,
53814 shapes = selection.get().slice();
53815
53816 // move only single shape if the dragged element
53817 // is not part of the current selection
53818 if (shapes.indexOf(shape) === -1) {
53819 shapes = [ shape ];
53820 }
53821
53822 // ensure we remove nested elements in the collection
53823 // and add attachers for a proper dragger
53824 shapes = removeNested(shapes);
53825
53826 // attach shapes to drag context
53827 assign(context, {
53828 shapes: shapes,
53829 validatedShapes: shapes,
53830 shape: shape
53831 });
53832 });
53833
53834
53835 // assign a high priority to this handler to setup the environment
53836 // others may hook up later, e.g. at default priority and modify
53837 // the move environment
53838 //
53839 eventBus.on('shape.move.start', MEDIUM_PRIORITY, function(event) {
53840
53841 var context = event.context,
53842 validatedShapes = context.validatedShapes,
53843 canExecute;
53844
53845 canExecute = context.canExecute = canMove(validatedShapes);
53846
53847 // check if we can move the elements
53848 if (!canExecute) {
53849 return false;
53850 }
53851 });
53852
53853 // assign a low priority to this handler
53854 // to let others modify the move event before we update
53855 // the context
53856 //
53857 eventBus.on('shape.move.move', LOW_PRIORITY$k, function(event) {
53858
53859 var context = event.context,
53860 validatedShapes = context.validatedShapes,
53861 hover = event.hover,
53862 delta = { x: event.dx, y: event.dy },
53863 position = { x: event.x, y: event.y },
53864 canExecute;
53865
53866 // check if we can move the elements
53867 canExecute = canMove(validatedShapes, delta, position, hover);
53868
53869 context.delta = delta;
53870 context.canExecute = canExecute;
53871
53872 // simply ignore move over
53873 if (canExecute === null) {
53874 context.target = null;
53875
53876 return;
53877 }
53878
53879 context.target = hover;
53880 });
53881
53882 eventBus.on('shape.move.end', function(event) {
53883
53884 var context = event.context;
53885
53886 var delta = context.delta,
53887 canExecute = context.canExecute,
53888 isAttach = canExecute === 'attach',
53889 shapes = context.shapes;
53890
53891 if (canExecute === false) {
53892 return false;
53893 }
53894
53895 // ensure we have actual pixel values deltas
53896 // (important when zoom level was > 1 during move)
53897 delta.x = round$b(delta.x);
53898 delta.y = round$b(delta.y);
53899
53900 if (delta.x === 0 && delta.y === 0) {
53901
53902 // didn't move
53903 return;
53904 }
53905
53906 modeling.moveElements(shapes, delta, context.target, {
53907 primaryShape: context.shape,
53908 attach: isAttach
53909 });
53910 });
53911
53912
53913 // move activation
53914
53915 eventBus.on('element.mousedown', function(event) {
53916
53917 if (!isPrimaryButton(event)) {
53918 return;
53919 }
53920
53921 var originalEvent = getOriginal(event);
53922
53923 if (!originalEvent) {
53924 throw new Error('must supply DOM mousedown event');
53925 }
53926
53927 return start(originalEvent, event.element);
53928 });
53929
53930 /**
53931 * Start move.
53932 *
53933 * @param {MouseEvent} event
53934 * @param {djs.model.Shape} shape
53935 * @param {boolean} [activate]
53936 * @param {Object} [context]
53937 */
53938 function start(event, element, activate, context) {
53939 if (isObject(activate)) {
53940 context = activate;
53941 activate = false;
53942 }
53943
53944 // do not move connections or the root element
53945 if (element.waypoints || !element.parent) {
53946 return;
53947 }
53948
53949 var referencePoint = mid$2(element);
53950
53951 dragging.init(event, referencePoint, 'shape.move', {
53952 cursor: 'grabbing',
53953 autoActivate: activate,
53954 data: {
53955 shape: element,
53956 context: context || {}
53957 }
53958 });
53959
53960 // we've handled the event
53961 return true;
53962 }
53963
53964 // API
53965
53966 this.start = start;
53967 }
53968
53969 MoveEvents.$inject = [
53970 'eventBus',
53971 'dragging',
53972 'modeling',
53973 'selection',
53974 'rules'
53975 ];
53976
53977
53978 /**
53979 * Return a filtered list of elements that do not contain
53980 * those nested into others.
53981 *
53982 * @param {Array<djs.model.Base>} elements
53983 *
53984 * @return {Array<djs.model.Base>} filtered
53985 */
53986 function removeNested(elements) {
53987
53988 var ids = groupBy(elements, 'id');
53989
53990 return filter(elements, function(element) {
53991 while ((element = element.parent)) {
53992
53993 // parent in selection
53994 if (ids[element.id]) {
53995 return false;
53996 }
53997 }
53998
53999 return true;
54000 });
54001 }
54002
54003 var LOW_PRIORITY$l = 499;
54004
54005 var MARKER_DRAGGING$1 = 'djs-dragging',
54006 MARKER_OK$3 = 'drop-ok',
54007 MARKER_NOT_OK$3 = 'drop-not-ok',
54008 MARKER_NEW_PARENT$1 = 'new-parent',
54009 MARKER_ATTACH$2 = 'attach-ok';
54010
54011
54012 /**
54013 * Provides previews for moving shapes when moving.
54014 *
54015 * @param {EventBus} eventBus
54016 * @param {ElementRegistry} elementRegistry
54017 * @param {Canvas} canvas
54018 * @param {Styles} styles
54019 */
54020 function MovePreview(
54021 eventBus, canvas, styles, previewSupport) {
54022
54023 function getVisualDragShapes(shapes) {
54024 var elements = getAllDraggedElements(shapes);
54025
54026 var filteredElements = removeEdges(elements);
54027
54028 return filteredElements;
54029 }
54030
54031 function getAllDraggedElements(shapes) {
54032 var allShapes = selfAndAllChildren(shapes, true);
54033
54034 var allConnections = map(allShapes, function(shape) {
54035 return (shape.incoming || []).concat(shape.outgoing || []);
54036 });
54037
54038 return flatten(allShapes.concat(allConnections));
54039 }
54040
54041 /**
54042 * Sets drop marker on an element.
54043 */
54044 function setMarker(element, marker) {
54045
54046 [ MARKER_ATTACH$2, MARKER_OK$3, MARKER_NOT_OK$3, MARKER_NEW_PARENT$1 ].forEach(function(m) {
54047
54048 if (m === marker) {
54049 canvas.addMarker(element, m);
54050 } else {
54051 canvas.removeMarker(element, m);
54052 }
54053 });
54054 }
54055
54056 /**
54057 * Make an element draggable.
54058 *
54059 * @param {Object} context
54060 * @param {djs.model.Base} element
54061 * @param {boolean} addMarker
54062 */
54063 function makeDraggable(context, element, addMarker) {
54064
54065 previewSupport.addDragger(element, context.dragGroup);
54066
54067 if (addMarker) {
54068 canvas.addMarker(element, MARKER_DRAGGING$1);
54069 }
54070
54071 if (context.allDraggedElements) {
54072 context.allDraggedElements.push(element);
54073 } else {
54074 context.allDraggedElements = [ element ];
54075 }
54076 }
54077
54078 // assign a low priority to this handler
54079 // to let others modify the move context before
54080 // we draw things
54081 eventBus.on('shape.move.start', LOW_PRIORITY$l, function(event) {
54082 var context = event.context,
54083 dragShapes = context.shapes,
54084 allDraggedElements = context.allDraggedElements;
54085
54086 var visuallyDraggedShapes = getVisualDragShapes(dragShapes);
54087
54088 if (!context.dragGroup) {
54089 var dragGroup = create('g');
54090
54091 attr$1(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
54092
54093 var defaultLayer = canvas.getDefaultLayer();
54094
54095 append(defaultLayer, dragGroup);
54096
54097 context.dragGroup = dragGroup;
54098 }
54099
54100 // add previews
54101 visuallyDraggedShapes.forEach(function(shape) {
54102 previewSupport.addDragger(shape, context.dragGroup);
54103 });
54104
54105 // cache all dragged elements / gfx
54106 // so that we can quickly undo their state changes later
54107 if (!allDraggedElements) {
54108 allDraggedElements = getAllDraggedElements(dragShapes);
54109 } else {
54110 allDraggedElements = flatten([
54111 allDraggedElements,
54112 getAllDraggedElements(dragShapes)
54113 ]);
54114 }
54115
54116 // add dragging marker
54117 forEach(allDraggedElements, function(e) {
54118 canvas.addMarker(e, MARKER_DRAGGING$1);
54119 });
54120
54121 context.allDraggedElements = allDraggedElements;
54122
54123 // determine, if any of the dragged elements have different parents
54124 context.differentParents = haveDifferentParents(dragShapes);
54125 });
54126
54127 // update previews
54128 eventBus.on('shape.move.move', LOW_PRIORITY$l, function(event) {
54129
54130 var context = event.context,
54131 dragGroup = context.dragGroup,
54132 target = context.target,
54133 parent = context.shape.parent,
54134 canExecute = context.canExecute;
54135
54136 if (target) {
54137 if (canExecute === 'attach') {
54138 setMarker(target, MARKER_ATTACH$2);
54139 } else if (context.canExecute && target && target.id !== parent.id) {
54140 setMarker(target, MARKER_NEW_PARENT$1);
54141 } else {
54142 setMarker(target, context.canExecute ? MARKER_OK$3 : MARKER_NOT_OK$3);
54143 }
54144 }
54145
54146 translate(dragGroup, event.dx, event.dy);
54147 });
54148
54149 eventBus.on([ 'shape.move.out', 'shape.move.cleanup' ], function(event) {
54150 var context = event.context,
54151 target = context.target;
54152
54153 if (target) {
54154 setMarker(target, null);
54155 }
54156 });
54157
54158 // remove previews
54159 eventBus.on('shape.move.cleanup', function(event) {
54160
54161 var context = event.context,
54162 allDraggedElements = context.allDraggedElements,
54163 dragGroup = context.dragGroup;
54164
54165
54166 // remove dragging marker
54167 forEach(allDraggedElements, function(e) {
54168 canvas.removeMarker(e, MARKER_DRAGGING$1);
54169 });
54170
54171 if (dragGroup) {
54172 remove$1(dragGroup);
54173 }
54174 });
54175
54176
54177 // API //////////////////////
54178
54179 /**
54180 * Make an element draggable.
54181 *
54182 * @param {Object} context
54183 * @param {djs.model.Base} element
54184 * @param {boolean} addMarker
54185 */
54186 this.makeDraggable = makeDraggable;
54187 }
54188
54189 MovePreview.$inject = [
54190 'eventBus',
54191 'canvas',
54192 'styles',
54193 'previewSupport'
54194 ];
54195
54196
54197 // helpers //////////////////////
54198
54199 /**
54200 * returns elements minus all connections
54201 * where source or target is not elements
54202 */
54203 function removeEdges(elements) {
54204
54205 var filteredElements = filter(elements, function(element) {
54206
54207 if (!isConnection$9(element)) {
54208 return true;
54209 } else {
54210
54211 return (
54212 find(elements, matchPattern({ id: element.source.id })) &&
54213 find(elements, matchPattern({ id: element.target.id }))
54214 );
54215 }
54216 });
54217
54218 return filteredElements;
54219 }
54220
54221 function haveDifferentParents(elements) {
54222 return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) !== 1;
54223 }
54224
54225 /**
54226 * Checks if an element is a connection.
54227 */
54228 function isConnection$9(element) {
54229 return element.waypoints;
54230 }
54231
54232 var MoveModule = {
54233 __depends__: [
54234 InteractionEventsModule,
54235 SelectionModule,
54236 OutlineModule,
54237 RulesModule,
54238 DraggingModule,
54239 PreviewSupportModule
54240 ],
54241 __init__: [
54242 'move',
54243 'movePreview'
54244 ],
54245 move: [ 'type', MoveEvents ],
54246 movePreview: [ 'type', MovePreview ]
54247 };
54248
54249 var TOGGLE_SELECTOR = '.djs-palette-toggle',
54250 ENTRY_SELECTOR = '.entry',
54251 ELEMENT_SELECTOR = TOGGLE_SELECTOR + ', ' + ENTRY_SELECTOR;
54252
54253 var PALETTE_OPEN_CLS = 'open',
54254 PALETTE_TWO_COLUMN_CLS = 'two-column';
54255
54256 var DEFAULT_PRIORITY$5 = 1000;
54257
54258
54259 /**
54260 * A palette containing modeling elements.
54261 */
54262 function Palette(eventBus, canvas) {
54263
54264 this._eventBus = eventBus;
54265 this._canvas = canvas;
54266
54267 var self = this;
54268
54269 eventBus.on('tool-manager.update', function(event) {
54270 var tool = event.tool;
54271
54272 self.updateToolHighlight(tool);
54273 });
54274
54275 eventBus.on('i18n.changed', function() {
54276 self._update();
54277 });
54278
54279 eventBus.on('diagram.init', function() {
54280
54281 self._diagramInitialized = true;
54282
54283 self._rebuild();
54284 });
54285 }
54286
54287 Palette.$inject = [ 'eventBus', 'canvas' ];
54288
54289
54290 /**
54291 * Register a provider with the palette
54292 *
54293 * @param {number} [priority=1000]
54294 * @param {PaletteProvider} provider
54295 *
54296 * @example
54297 * const paletteProvider = {
54298 * getPaletteEntries: function() {
54299 * return function(entries) {
54300 * return {
54301 * ...entries,
54302 * 'entry-1': {
54303 * label: 'My Entry',
54304 * action: function() { alert("I have been clicked!"); }
54305 * }
54306 * };
54307 * }
54308 * }
54309 * };
54310 *
54311 * palette.registerProvider(800, paletteProvider);
54312 */
54313 Palette.prototype.registerProvider = function(priority, provider) {
54314 if (!provider) {
54315 provider = priority;
54316 priority = DEFAULT_PRIORITY$5;
54317 }
54318
54319 this._eventBus.on('palette.getProviders', priority, function(event) {
54320 event.providers.push(provider);
54321 });
54322
54323 this._rebuild();
54324 };
54325
54326
54327 /**
54328 * Returns the palette entries
54329 *
54330 * @return {Object<string, PaletteEntryDescriptor>} map of entries
54331 */
54332 Palette.prototype.getEntries = function() {
54333 var providers = this._getProviders();
54334
54335 return providers.reduce(addPaletteEntries, {});
54336 };
54337
54338 Palette.prototype._rebuild = function() {
54339
54340 if (!this._diagramInitialized) {
54341 return;
54342 }
54343
54344 var providers = this._getProviders();
54345
54346 if (!providers.length) {
54347 return;
54348 }
54349
54350 if (!this._container) {
54351 this._init();
54352 }
54353
54354 this._update();
54355 };
54356
54357 /**
54358 * Initialize
54359 */
54360 Palette.prototype._init = function() {
54361
54362 var self = this;
54363
54364 var eventBus = this._eventBus;
54365
54366 var parentContainer = this._getParentContainer();
54367
54368 var container = this._container = domify(Palette.HTML_MARKUP);
54369
54370 parentContainer.appendChild(container);
54371
54372 delegate.bind(container, ELEMENT_SELECTOR, 'click', function(event) {
54373
54374 var target = event.delegateTarget;
54375
54376 if (matchesSelector(target, TOGGLE_SELECTOR)) {
54377 return self.toggle();
54378 }
54379
54380 self.trigger('click', event);
54381 });
54382
54383 // prevent drag propagation
54384 componentEvent.bind(container, 'mousedown', function(event) {
54385 event.stopPropagation();
54386 });
54387
54388 // prevent drag propagation
54389 delegate.bind(container, ENTRY_SELECTOR, 'dragstart', function(event) {
54390 self.trigger('dragstart', event);
54391 });
54392
54393 eventBus.on('canvas.resized', this._layoutChanged, this);
54394
54395 eventBus.fire('palette.create', {
54396 container: container
54397 });
54398 };
54399
54400 Palette.prototype._getProviders = function(id) {
54401
54402 var event = this._eventBus.createEvent({
54403 type: 'palette.getProviders',
54404 providers: []
54405 });
54406
54407 this._eventBus.fire(event);
54408
54409 return event.providers;
54410 };
54411
54412 /**
54413 * Update palette state.
54414 *
54415 * @param {Object} [state] { open, twoColumn }
54416 */
54417 Palette.prototype._toggleState = function(state) {
54418
54419 state = state || {};
54420
54421 var parent = this._getParentContainer(),
54422 container = this._container;
54423
54424 var eventBus = this._eventBus;
54425
54426 var twoColumn;
54427
54428 var cls = classes(container);
54429
54430 if ('twoColumn' in state) {
54431 twoColumn = state.twoColumn;
54432 } else {
54433 twoColumn = this._needsCollapse(parent.clientHeight, this._entries || {});
54434 }
54435
54436 // always update two column
54437 cls.toggle(PALETTE_TWO_COLUMN_CLS, twoColumn);
54438
54439 if ('open' in state) {
54440 cls.toggle(PALETTE_OPEN_CLS, state.open);
54441 }
54442
54443 eventBus.fire('palette.changed', {
54444 twoColumn: twoColumn,
54445 open: this.isOpen()
54446 });
54447 };
54448
54449 Palette.prototype._update = function() {
54450
54451 var entriesContainer = query('.djs-palette-entries', this._container),
54452 entries = this._entries = this.getEntries();
54453
54454 clear(entriesContainer);
54455
54456 forEach(entries, function(entry, id) {
54457
54458 var grouping = entry.group || 'default';
54459
54460 var container = query('[data-group=' + grouping + ']', entriesContainer);
54461 if (!container) {
54462 container = domify('<div class="group" data-group="' + grouping + '"></div>');
54463 entriesContainer.appendChild(container);
54464 }
54465
54466 var html = entry.html || (
54467 entry.separator ?
54468 '<hr class="separator" />' :
54469 '<div class="entry" draggable="true"></div>');
54470
54471
54472 var control = domify(html);
54473 container.appendChild(control);
54474
54475 if (!entry.separator) {
54476 attr(control, 'data-action', id);
54477
54478 if (entry.title) {
54479 attr(control, 'title', entry.title);
54480 }
54481
54482 if (entry.className) {
54483 addClasses$1(control, entry.className);
54484 }
54485
54486 if (entry.imageUrl) {
54487 control.appendChild(domify('<img src="' + entry.imageUrl + '">'));
54488 }
54489 }
54490 });
54491
54492 // open after update
54493 this.open();
54494 };
54495
54496
54497 /**
54498 * Trigger an action available on the palette
54499 *
54500 * @param {string} action
54501 * @param {Event} event
54502 */
54503 Palette.prototype.trigger = function(action, event, autoActivate) {
54504 var entries = this._entries,
54505 entry,
54506 handler,
54507 originalEvent,
54508 button = event.delegateTarget || event.target;
54509
54510 if (!button) {
54511 return event.preventDefault();
54512 }
54513
54514 entry = entries[attr(button, 'data-action')];
54515
54516 // when user clicks on the palette and not on an action
54517 if (!entry) {
54518 return;
54519 }
54520
54521 handler = entry.action;
54522
54523 originalEvent = event.originalEvent || event;
54524
54525 // simple action (via callback function)
54526 if (isFunction(handler)) {
54527 if (action === 'click') {
54528 handler(originalEvent, autoActivate);
54529 }
54530 } else {
54531 if (handler[action]) {
54532 handler[action](originalEvent, autoActivate);
54533 }
54534 }
54535
54536 // silence other actions
54537 event.preventDefault();
54538 };
54539
54540 Palette.prototype._layoutChanged = function() {
54541 this._toggleState({});
54542 };
54543
54544 /**
54545 * Do we need to collapse to two columns?
54546 *
54547 * @param {number} availableHeight
54548 * @param {Object} entries
54549 *
54550 * @return {boolean}
54551 */
54552 Palette.prototype._needsCollapse = function(availableHeight, entries) {
54553
54554 // top margin + bottom toggle + bottom margin
54555 // implementors must override this method if they
54556 // change the palette styles
54557 var margin = 20 + 10 + 20;
54558
54559 var entriesHeight = Object.keys(entries).length * 46;
54560
54561 return availableHeight < entriesHeight + margin;
54562 };
54563
54564 /**
54565 * Close the palette
54566 */
54567 Palette.prototype.close = function() {
54568
54569 this._toggleState({
54570 open: false,
54571 twoColumn: false
54572 });
54573 };
54574
54575
54576 /**
54577 * Open the palette
54578 */
54579 Palette.prototype.open = function() {
54580 this._toggleState({ open: true });
54581 };
54582
54583
54584 Palette.prototype.toggle = function(open) {
54585 if (this.isOpen()) {
54586 this.close();
54587 } else {
54588 this.open();
54589 }
54590 };
54591
54592 Palette.prototype.isActiveTool = function(tool) {
54593 return tool && this._activeTool === tool;
54594 };
54595
54596 Palette.prototype.updateToolHighlight = function(name) {
54597 var entriesContainer,
54598 toolsContainer;
54599
54600 if (!this._toolsContainer) {
54601 entriesContainer = query('.djs-palette-entries', this._container);
54602
54603 this._toolsContainer = query('[data-group=tools]', entriesContainer);
54604 }
54605
54606 toolsContainer = this._toolsContainer;
54607
54608 forEach(toolsContainer.children, function(tool) {
54609 var actionName = tool.getAttribute('data-action');
54610
54611 if (!actionName) {
54612 return;
54613 }
54614
54615 var toolClasses = classes(tool);
54616
54617 actionName = actionName.replace('-tool', '');
54618
54619 if (toolClasses.contains('entry') && actionName === name) {
54620 toolClasses.add('highlighted-entry');
54621 } else {
54622 toolClasses.remove('highlighted-entry');
54623 }
54624 });
54625 };
54626
54627
54628 /**
54629 * Return true if the palette is opened.
54630 *
54631 * @example
54632 *
54633 * palette.open();
54634 *
54635 * if (palette.isOpen()) {
54636 * // yes, we are open
54637 * }
54638 *
54639 * @return {boolean} true if palette is opened
54640 */
54641 Palette.prototype.isOpen = function() {
54642 return classes(this._container).has(PALETTE_OPEN_CLS);
54643 };
54644
54645 /**
54646 * Get container the palette lives in.
54647 *
54648 * @return {Element}
54649 */
54650 Palette.prototype._getParentContainer = function() {
54651 return this._canvas.getContainer();
54652 };
54653
54654
54655 /* markup definition */
54656
54657 Palette.HTML_MARKUP =
54658 '<div class="djs-palette">' +
54659 '<div class="djs-palette-entries"></div>' +
54660 '<div class="djs-palette-toggle"></div>' +
54661 '</div>';
54662
54663
54664 // helpers //////////////////////
54665
54666 function addClasses$1(element, classNames) {
54667
54668 var classes$1 = classes(element);
54669
54670 var actualClassNames = isArray(classNames) ? classNames : classNames.split(/\s+/g);
54671 actualClassNames.forEach(function(cls) {
54672 classes$1.add(cls);
54673 });
54674 }
54675
54676 function addPaletteEntries(entries, provider) {
54677
54678 var entriesOrUpdater = provider.getPaletteEntries();
54679
54680 if (isFunction(entriesOrUpdater)) {
54681 return entriesOrUpdater(entries);
54682 }
54683
54684 forEach(entriesOrUpdater, function(entry, id) {
54685 entries[id] = entry;
54686 });
54687
54688 return entries;
54689 }
54690
54691 var PaletteModule = {
54692 __init__: [ 'palette' ],
54693 palette: [ 'type', Palette ]
54694 };
54695
54696 var LASSO_TOOL_CURSOR = 'crosshair';
54697
54698
54699 function LassoTool(
54700 eventBus, canvas, dragging,
54701 elementRegistry, selection, toolManager,
54702 mouse) {
54703
54704 this._selection = selection;
54705 this._dragging = dragging;
54706 this._mouse = mouse;
54707
54708 var self = this;
54709
54710 // lasso visuals implementation
54711
54712 /**
54713 * A helper that realizes the selection box visual
54714 */
54715 var visuals = {
54716
54717 create: function(context) {
54718 var container = canvas.getDefaultLayer(),
54719 frame;
54720
54721 frame = context.frame = create('rect');
54722 attr$1(frame, {
54723 class: 'djs-lasso-overlay',
54724 width: 1,
54725 height: 1,
54726 x: 0,
54727 y: 0
54728 });
54729
54730 append(container, frame);
54731 },
54732
54733 update: function(context) {
54734 var frame = context.frame,
54735 bbox = context.bbox;
54736
54737 attr$1(frame, {
54738 x: bbox.x,
54739 y: bbox.y,
54740 width: bbox.width,
54741 height: bbox.height
54742 });
54743 },
54744
54745 remove: function(context) {
54746
54747 if (context.frame) {
54748 remove$1(context.frame);
54749 }
54750 }
54751 };
54752
54753 toolManager.registerTool('lasso', {
54754 tool: 'lasso.selection',
54755 dragging: 'lasso'
54756 });
54757
54758 eventBus.on('lasso.selection.end', function(event) {
54759 var target = event.originalEvent.target;
54760
54761 // only reactive on diagram click
54762 // on some occasions, event.hover is not set and we have to check if the target is an svg
54763 if (!event.hover && !(target instanceof SVGElement)) {
54764 return;
54765 }
54766
54767 eventBus.once('lasso.selection.ended', function() {
54768 self.activateLasso(event.originalEvent, true);
54769 });
54770 });
54771
54772 // lasso interaction implementation
54773
54774 eventBus.on('lasso.end', function(event) {
54775
54776 var bbox = toBBox(event);
54777
54778 var elements = elementRegistry.filter(function(element) {
54779 return element;
54780 });
54781
54782 self.select(elements, bbox);
54783 });
54784
54785 eventBus.on('lasso.start', function(event) {
54786
54787 var context = event.context;
54788
54789 context.bbox = toBBox(event);
54790 visuals.create(context);
54791 });
54792
54793 eventBus.on('lasso.move', function(event) {
54794
54795 var context = event.context;
54796
54797 context.bbox = toBBox(event);
54798 visuals.update(context);
54799 });
54800
54801 eventBus.on('lasso.cleanup', function(event) {
54802
54803 var context = event.context;
54804
54805 visuals.remove(context);
54806 });
54807
54808
54809 // event integration
54810
54811 eventBus.on('element.mousedown', 1500, function(event) {
54812
54813 if (!hasSecondaryModifier(event)) {
54814 return;
54815 }
54816
54817 self.activateLasso(event.originalEvent);
54818
54819 // we've handled the event
54820 return true;
54821 });
54822 }
54823
54824 LassoTool.$inject = [
54825 'eventBus',
54826 'canvas',
54827 'dragging',
54828 'elementRegistry',
54829 'selection',
54830 'toolManager',
54831 'mouse'
54832 ];
54833
54834
54835 LassoTool.prototype.activateLasso = function(event, autoActivate) {
54836
54837 this._dragging.init(event, 'lasso', {
54838 autoActivate: autoActivate,
54839 cursor: LASSO_TOOL_CURSOR,
54840 data: {
54841 context: {}
54842 }
54843 });
54844 };
54845
54846 LassoTool.prototype.activateSelection = function(event, autoActivate) {
54847
54848 this._dragging.init(event, 'lasso.selection', {
54849 trapClick: false,
54850 autoActivate: autoActivate,
54851 cursor: LASSO_TOOL_CURSOR,
54852 data: {
54853 context: {}
54854 }
54855 });
54856 };
54857
54858 LassoTool.prototype.select = function(elements, bbox) {
54859 var selectedElements = getEnclosedElements(elements, bbox);
54860
54861 this._selection.select(values(selectedElements));
54862 };
54863
54864 LassoTool.prototype.toggle = function() {
54865 if (this.isActive()) {
54866 return this._dragging.cancel();
54867 }
54868
54869 var mouseEvent = this._mouse.getLastMoveEvent();
54870
54871 this.activateSelection(mouseEvent, !!mouseEvent);
54872 };
54873
54874 LassoTool.prototype.isActive = function() {
54875 var context = this._dragging.context();
54876
54877 return context && /^lasso/.test(context.prefix);
54878 };
54879
54880
54881
54882 function toBBox(event) {
54883
54884 var start = {
54885
54886 x: event.x - event.dx,
54887 y: event.y - event.dy
54888 };
54889
54890 var end = {
54891 x: event.x,
54892 y: event.y
54893 };
54894
54895 var bbox;
54896
54897 if ((start.x <= end.x && start.y < end.y) ||
54898 (start.x < end.x && start.y <= end.y)) {
54899
54900 bbox = {
54901 x: start.x,
54902 y: start.y,
54903 width: end.x - start.x,
54904 height: end.y - start.y
54905 };
54906 } else if ((start.x >= end.x && start.y < end.y) ||
54907 (start.x > end.x && start.y <= end.y)) {
54908
54909 bbox = {
54910 x: end.x,
54911 y: start.y,
54912 width: start.x - end.x,
54913 height: end.y - start.y
54914 };
54915 } else if ((start.x <= end.x && start.y > end.y) ||
54916 (start.x < end.x && start.y >= end.y)) {
54917
54918 bbox = {
54919 x: start.x,
54920 y: end.y,
54921 width: end.x - start.x,
54922 height: start.y - end.y
54923 };
54924 } else if ((start.x >= end.x && start.y > end.y) ||
54925 (start.x > end.x && start.y >= end.y)) {
54926
54927 bbox = {
54928 x: end.x,
54929 y: end.y,
54930 width: start.x - end.x,
54931 height: start.y - end.y
54932 };
54933 } else {
54934
54935 bbox = {
54936 x: end.x,
54937 y: end.y,
54938 width: 0,
54939 height: 0
54940 };
54941 }
54942 return bbox;
54943 }
54944
54945 var LassoToolModule = {
54946 __depends__: [
54947 ToolManagerModule,
54948 MouseModule
54949 ],
54950 __init__: [ 'lassoTool' ],
54951 lassoTool: [ 'type', LassoTool ]
54952 };
54953
54954 var HIGH_PRIORITY$i = 1500;
54955 var HAND_CURSOR = 'grab';
54956
54957
54958 function HandTool(
54959 eventBus, canvas, dragging,
54960 injector, toolManager, mouse) {
54961
54962 this._dragging = dragging;
54963 this._mouse = mouse;
54964
54965 var self = this,
54966 keyboard = injector.get('keyboard', false);
54967
54968 toolManager.registerTool('hand', {
54969 tool: 'hand',
54970 dragging: 'hand.move'
54971 });
54972
54973 eventBus.on('element.mousedown', HIGH_PRIORITY$i, function(event) {
54974
54975 if (!hasPrimaryModifier(event)) {
54976 return;
54977 }
54978
54979 self.activateMove(event.originalEvent, true);
54980
54981 return false;
54982 });
54983
54984 keyboard && keyboard.addListener(HIGH_PRIORITY$i, function(e) {
54985 if (!isSpace(e.keyEvent) || self.isActive()) {
54986 return;
54987 }
54988
54989 var mouseEvent = self._mouse.getLastMoveEvent();
54990
54991 self.activateMove(mouseEvent, !!mouseEvent);
54992 }, 'keyboard.keydown');
54993
54994 keyboard && keyboard.addListener(HIGH_PRIORITY$i, function(e) {
54995 if (!isSpace(e.keyEvent) || !self.isActive()) {
54996 return;
54997 }
54998
54999 self.toggle();
55000 }, 'keyboard.keyup');
55001
55002 eventBus.on('hand.end', function(event) {
55003 var target = event.originalEvent.target;
55004
55005 // only reactive on diagram click
55006 // on some occasions, event.hover is not set and we have to check if the target is an svg
55007 if (!event.hover && !(target instanceof SVGElement)) {
55008 return false;
55009 }
55010
55011 eventBus.once('hand.ended', function() {
55012 self.activateMove(event.originalEvent, { reactivate: true });
55013 });
55014
55015 });
55016
55017 eventBus.on('hand.move.move', function(event) {
55018 var scale = canvas.viewbox().scale;
55019
55020 canvas.scroll({
55021 dx: event.dx * scale,
55022 dy: event.dy * scale
55023 });
55024 });
55025
55026 eventBus.on('hand.move.end', function(event) {
55027 var context = event.context,
55028 reactivate = context.reactivate;
55029
55030 // Don't reactivate if the user is using the keyboard keybinding
55031 if (!hasPrimaryModifier(event) && reactivate) {
55032
55033 eventBus.once('hand.move.ended', function(event) {
55034 self.activateHand(event.originalEvent, true, true);
55035 });
55036
55037 }
55038
55039 return false;
55040 });
55041
55042 }
55043
55044 HandTool.$inject = [
55045 'eventBus',
55046 'canvas',
55047 'dragging',
55048 'injector',
55049 'toolManager',
55050 'mouse'
55051 ];
55052
55053
55054 HandTool.prototype.activateMove = function(event, autoActivate, context) {
55055 if (typeof autoActivate === 'object') {
55056 context = autoActivate;
55057 autoActivate = false;
55058 }
55059
55060 this._dragging.init(event, 'hand.move', {
55061 autoActivate: autoActivate,
55062 cursor: HAND_CURSOR,
55063 data: {
55064 context: context || {}
55065 }
55066 });
55067 };
55068
55069 HandTool.prototype.activateHand = function(event, autoActivate, reactivate) {
55070 this._dragging.init(event, 'hand', {
55071 trapClick: false,
55072 autoActivate: autoActivate,
55073 cursor: HAND_CURSOR,
55074 data: {
55075 context: {
55076 reactivate: reactivate
55077 }
55078 }
55079 });
55080 };
55081
55082 HandTool.prototype.toggle = function() {
55083 if (this.isActive()) {
55084 return this._dragging.cancel();
55085 }
55086
55087 var mouseEvent = this._mouse.getLastMoveEvent();
55088
55089 this.activateHand(mouseEvent, !!mouseEvent);
55090 };
55091
55092 HandTool.prototype.isActive = function() {
55093 var context = this._dragging.context();
55094
55095 if (context) {
55096 return /^(hand|hand\.move)$/.test(context.prefix);
55097 }
55098
55099 return false;
55100 };
55101
55102 // helpers //////////
55103
55104 function isSpace(keyEvent) {
55105 return isKey(' ', keyEvent);
55106 }
55107
55108 var HandToolModule = {
55109 __depends__: [
55110 ToolManagerModule,
55111 MouseModule
55112 ],
55113 __init__: [ 'handTool' ],
55114 handTool: [ 'type', HandTool ]
55115 };
55116
55117 var MARKER_OK$4 = 'connect-ok',
55118 MARKER_NOT_OK$4 = 'connect-not-ok';
55119
55120 /**
55121 * @class
55122 * @constructor
55123 *
55124 * @param {EventBus} eventBus
55125 * @param {Dragging} dragging
55126 * @param {Connect} connect
55127 * @param {Canvas} canvas
55128 * @param {ToolManager} toolManager
55129 * @param {Rules} rules
55130 * @param {Mouse} mouse
55131 */
55132 function GlobalConnect(
55133 eventBus, dragging, connect,
55134 canvas, toolManager, rules,
55135 mouse) {
55136
55137 var self = this;
55138
55139 this._dragging = dragging;
55140 this._rules = rules;
55141 this._mouse = mouse;
55142
55143 toolManager.registerTool('global-connect', {
55144 tool: 'global-connect',
55145 dragging: 'global-connect.drag'
55146 });
55147
55148 eventBus.on('global-connect.hover', function(event) {
55149 var context = event.context,
55150 startTarget = event.hover;
55151
55152 var canStartConnect = context.canStartConnect = self.canStartConnect(startTarget);
55153
55154 // simply ignore hover
55155 if (canStartConnect === null) {
55156 return;
55157 }
55158
55159 context.startTarget = startTarget;
55160
55161 canvas.addMarker(startTarget, canStartConnect ? MARKER_OK$4 : MARKER_NOT_OK$4);
55162 });
55163
55164
55165 eventBus.on([ 'global-connect.out', 'global-connect.cleanup' ], function(event) {
55166 var startTarget = event.context.startTarget,
55167 canStartConnect = event.context.canStartConnect;
55168
55169 if (startTarget) {
55170 canvas.removeMarker(startTarget, canStartConnect ? MARKER_OK$4 : MARKER_NOT_OK$4);
55171 }
55172 });
55173
55174
55175 eventBus.on([ 'global-connect.ended' ], function(event) {
55176 var context = event.context,
55177 startTarget = context.startTarget,
55178 startPosition = {
55179 x: event.x,
55180 y: event.y
55181 };
55182
55183 var canStartConnect = self.canStartConnect(startTarget);
55184
55185 if (!canStartConnect) {
55186 return;
55187 }
55188
55189 eventBus.once('element.out', function() {
55190 eventBus.once([ 'connect.ended', 'connect.canceled' ], function() {
55191 eventBus.fire('global-connect.drag.ended');
55192 });
55193
55194 connect.start(null, startTarget, startPosition);
55195 });
55196
55197 return false;
55198 });
55199 }
55200
55201 GlobalConnect.$inject = [
55202 'eventBus',
55203 'dragging',
55204 'connect',
55205 'canvas',
55206 'toolManager',
55207 'rules',
55208 'mouse'
55209 ];
55210
55211 /**
55212 * Initiates tool activity.
55213 */
55214 GlobalConnect.prototype.start = function(event, autoActivate) {
55215 this._dragging.init(event, 'global-connect', {
55216 autoActivate: autoActivate,
55217 trapClick: false,
55218 data: {
55219 context: {}
55220 }
55221 });
55222 };
55223
55224 GlobalConnect.prototype.toggle = function() {
55225
55226 if (this.isActive()) {
55227 return this._dragging.cancel();
55228 }
55229
55230 var mouseEvent = this._mouse.getLastMoveEvent();
55231
55232 return this.start(mouseEvent, !!mouseEvent);
55233 };
55234
55235 GlobalConnect.prototype.isActive = function() {
55236 var context = this._dragging.context();
55237
55238 return context && /^global-connect/.test(context.prefix);
55239 };
55240
55241 /**
55242 * Check if source shape can initiate connection.
55243 *
55244 * @param {Shape} startTarget
55245 * @return {boolean}
55246 */
55247 GlobalConnect.prototype.canStartConnect = function(startTarget) {
55248 return this._rules.allowed('connection.start', { source: startTarget });
55249 };
55250
55251 var GlobalConnectModule = {
55252 __depends__: [
55253 ConnectModule,
55254 RulesModule,
55255 DraggingModule,
55256 ToolManagerModule,
55257 MouseModule
55258 ],
55259 globalConnect: [ 'type', GlobalConnect ]
55260 };
55261
55262 /**
55263 * A palette provider for BPMN 2.0 elements.
55264 */
55265 function PaletteProvider(
55266 palette, create, elementFactory,
55267 spaceTool, lassoTool, handTool,
55268 globalConnect, translate) {
55269
55270 this._palette = palette;
55271 this._create = create;
55272 this._elementFactory = elementFactory;
55273 this._spaceTool = spaceTool;
55274 this._lassoTool = lassoTool;
55275 this._handTool = handTool;
55276 this._globalConnect = globalConnect;
55277 this._translate = translate;
55278
55279 palette.registerProvider(this);
55280 }
55281
55282 PaletteProvider.$inject = [
55283 'palette',
55284 'create',
55285 'elementFactory',
55286 'spaceTool',
55287 'lassoTool',
55288 'handTool',
55289 'globalConnect',
55290 'translate'
55291 ];
55292
55293
55294 PaletteProvider.prototype.getPaletteEntries = function(element) {
55295
55296 var actions = {},
55297 create = this._create,
55298 elementFactory = this._elementFactory,
55299 spaceTool = this._spaceTool,
55300 lassoTool = this._lassoTool,
55301 handTool = this._handTool,
55302 globalConnect = this._globalConnect,
55303 translate = this._translate;
55304
55305 function createAction(type, group, className, title, options) {
55306
55307 function createListener(event) {
55308 var shape = elementFactory.createShape(assign({ type: type }, options));
55309
55310 if (options) {
55311 shape.businessObject.di.isExpanded = options.isExpanded;
55312 }
55313
55314 create.start(event, shape);
55315 }
55316
55317 var shortType = type.replace(/^bpmn:/, '');
55318
55319 return {
55320 group: group,
55321 className: className,
55322 title: title || translate('Create {type}', { type: shortType }),
55323 action: {
55324 dragstart: createListener,
55325 click: createListener
55326 }
55327 };
55328 }
55329
55330 function createSubprocess(event) {
55331 var subProcess = elementFactory.createShape({
55332 type: 'bpmn:SubProcess',
55333 x: 0,
55334 y: 0,
55335 isExpanded: true
55336 });
55337
55338 var startEvent = elementFactory.createShape({
55339 type: 'bpmn:StartEvent',
55340 x: 40,
55341 y: 82,
55342 parent: subProcess
55343 });
55344
55345 create.start(event, [ subProcess, startEvent ], {
55346 hints: {
55347 autoSelect: [ startEvent ]
55348 }
55349 });
55350 }
55351
55352 function createParticipant(event) {
55353 create.start(event, elementFactory.createParticipantShape());
55354 }
55355
55356 assign(actions, {
55357 'hand-tool': {
55358 group: 'tools',
55359 className: 'bpmn-icon-hand-tool',
55360 title: translate('Activate the hand tool'),
55361 action: {
55362 click: function(event) {
55363 handTool.activateHand(event);
55364 }
55365 }
55366 },
55367 'lasso-tool': {
55368 group: 'tools',
55369 className: 'bpmn-icon-lasso-tool',
55370 title: translate('Activate the lasso tool'),
55371 action: {
55372 click: function(event) {
55373 lassoTool.activateSelection(event);
55374 }
55375 }
55376 },
55377 'space-tool': {
55378 group: 'tools',
55379 className: 'bpmn-icon-space-tool',
55380 title: translate('Activate the create/remove space tool'),
55381 action: {
55382 click: function(event) {
55383 spaceTool.activateSelection(event);
55384 }
55385 }
55386 },
55387 'global-connect-tool': {
55388 group: 'tools',
55389 className: 'bpmn-icon-connection-multi',
55390 title: translate('Activate the global connect tool'),
55391 action: {
55392 click: function(event) {
55393 globalConnect.start(event);
55394 }
55395 }
55396 },
55397 'tool-separator': {
55398 group: 'tools',
55399 separator: true
55400 },
55401 'create.start-event': createAction(
55402 'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
55403 translate('Create StartEvent')
55404 ),
55405 'create.intermediate-event': createAction(
55406 'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
55407 translate('Create Intermediate/Boundary Event')
55408 ),
55409 'create.end-event': createAction(
55410 'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
55411 translate('Create EndEvent')
55412 ),
55413 'create.exclusive-gateway': createAction(
55414 'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none',
55415 translate('Create Gateway')
55416 ),
55417 'create.task': createAction(
55418 'bpmn:Task', 'activity', 'bpmn-icon-task',
55419 translate('Create Task')
55420 ),
55421 'create.data-object': createAction(
55422 'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
55423 translate('Create DataObjectReference')
55424 ),
55425 'create.data-store': createAction(
55426 'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
55427 translate('Create DataStoreReference')
55428 ),
55429 'create.subprocess-expanded': {
55430 group: 'activity',
55431 className: 'bpmn-icon-subprocess-expanded',
55432 title: translate('Create expanded SubProcess'),
55433 action: {
55434 dragstart: createSubprocess,
55435 click: createSubprocess
55436 }
55437 },
55438 'create.participant-expanded': {
55439 group: 'collaboration',
55440 className: 'bpmn-icon-participant',
55441 title: translate('Create Pool/Participant'),
55442 action: {
55443 dragstart: createParticipant,
55444 click: createParticipant
55445 }
55446 },
55447 'create.group': createAction(
55448 'bpmn:Group', 'artifact', 'bpmn-icon-group',
55449 translate('Create Group')
55450 ),
55451 });
55452
55453 return actions;
55454 };
55455
55456 var PaletteModule$1 = {
55457 __depends__: [
55458 PaletteModule,
55459 CreateModule,
55460 SpaceToolModule,
55461 LassoToolModule,
55462 HandToolModule,
55463 GlobalConnectModule,
55464 translate$2
55465 ],
55466 __init__: [ 'paletteProvider' ],
55467 paletteProvider: [ 'type', PaletteProvider ]
55468 };
55469
55470 var LOW_PRIORITY$m = 250;
55471
55472
55473 function BpmnReplacePreview(
55474 eventBus, elementRegistry, elementFactory,
55475 canvas, previewSupport) {
55476
55477 CommandInterceptor.call(this, eventBus);
55478
55479 /**
55480 * Replace the visuals of all elements in the context which can be replaced
55481 *
55482 * @param {Object} context
55483 */
55484 function replaceVisual(context) {
55485
55486 var replacements = context.canExecute.replacements;
55487
55488 forEach(replacements, function(replacement) {
55489
55490 var id = replacement.oldElementId;
55491
55492 var newElement = {
55493 type: replacement.newElementType
55494 };
55495
55496 // if the visual of the element is already replaced
55497 if (context.visualReplacements[id]) {
55498 return;
55499 }
55500
55501 var element = elementRegistry.get(id);
55502
55503 assign(newElement, { x: element.x, y: element.y });
55504
55505 // create a temporary shape
55506 var tempShape = elementFactory.createShape(newElement);
55507
55508 canvas.addShape(tempShape, element.parent);
55509
55510 // select the original SVG element related to the element and hide it
55511 var gfx = query('[data-element-id="' + css_escape(element.id) + '"]', context.dragGroup);
55512
55513 if (gfx) {
55514 attr$1(gfx, { display: 'none' });
55515 }
55516
55517 // clone the gfx of the temporary shape and add it to the drag group
55518 var dragger = previewSupport.addDragger(tempShape, context.dragGroup);
55519
55520 context.visualReplacements[id] = dragger;
55521
55522 canvas.removeShape(tempShape);
55523 });
55524 }
55525
55526 /**
55527 * Restore the original visuals of the previously replaced elements
55528 *
55529 * @param {Object} context
55530 */
55531 function restoreVisual(context) {
55532
55533 var visualReplacements = context.visualReplacements;
55534
55535 forEach(visualReplacements, function(dragger, id) {
55536
55537 var originalGfx = query('[data-element-id="' + css_escape(id) + '"]', context.dragGroup);
55538
55539 if (originalGfx) {
55540 attr$1(originalGfx, { display: 'inline' });
55541 }
55542
55543 dragger.remove();
55544
55545 if (visualReplacements[id]) {
55546 delete visualReplacements[id];
55547 }
55548 });
55549 }
55550
55551 eventBus.on('shape.move.move', LOW_PRIORITY$m, function(event) {
55552
55553 var context = event.context,
55554 canExecute = context.canExecute;
55555
55556 if (!context.visualReplacements) {
55557 context.visualReplacements = {};
55558 }
55559
55560 if (canExecute && canExecute.replacements) {
55561 replaceVisual(context);
55562 } else {
55563 restoreVisual(context);
55564 }
55565 });
55566 }
55567
55568 BpmnReplacePreview.$inject = [
55569 'eventBus',
55570 'elementRegistry',
55571 'elementFactory',
55572 'canvas',
55573 'previewSupport'
55574 ];
55575
55576 inherits_browser(BpmnReplacePreview, CommandInterceptor);
55577
55578 var ReplacePreviewModule = {
55579 __depends__: [
55580 PreviewSupportModule
55581 ],
55582 __init__: [ 'bpmnReplacePreview' ],
55583 bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
55584 };
55585
55586 var HIGHER_PRIORITY$2 = 1250;
55587
55588 var BOUNDARY_TO_HOST_THRESHOLD$1 = 40;
55589
55590 var TARGET_BOUNDS_PADDING = 20,
55591 TASK_BOUNDS_PADDING = 10;
55592
55593 var TARGET_CENTER_PADDING = 20;
55594
55595 var AXES = [ 'x', 'y' ];
55596
55597 var abs$7 = Math.abs;
55598
55599 /**
55600 * Snap during connect.
55601 *
55602 * @param {EventBus} eventBus
55603 */
55604 function BpmnConnectSnapping(eventBus) {
55605 eventBus.on([
55606 'connect.hover',
55607 'connect.move',
55608 'connect.end',
55609 ], HIGHER_PRIORITY$2, function(event) {
55610 var context = event.context,
55611 canExecute = context.canExecute,
55612 start = context.start,
55613 hover = context.hover,
55614 source = context.source,
55615 target = context.target;
55616
55617 // do NOT snap on CMD
55618 if (event.originalEvent && isCmd(event.originalEvent)) {
55619 return;
55620 }
55621
55622 if (!context.initialConnectionStart) {
55623 context.initialConnectionStart = context.connectionStart;
55624 }
55625
55626 // snap hover
55627 if (canExecute && hover) {
55628 snapToShape(event, hover, getTargetBoundsPadding(hover));
55629 }
55630
55631 if (hover && isAnyType(canExecute, [
55632 'bpmn:Association',
55633 'bpmn:DataInputAssociation',
55634 'bpmn:DataOutputAssociation',
55635 'bpmn:SequenceFlow'
55636 ])) {
55637 context.connectionStart = mid(start);
55638
55639 // snap hover
55640 if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) {
55641 snapToPosition(event, mid(hover));
55642 }
55643
55644 // snap hover
55645 if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) {
55646 snapToTargetMid(event, hover);
55647 }
55648
55649 // snap source and target
55650 if (is$1(source, 'bpmn:BoundaryEvent') && target === source.host) {
55651 snapBoundaryEventLoop(event);
55652 }
55653
55654 } else if (isType(canExecute, 'bpmn:MessageFlow')) {
55655
55656 if (is$1(start, 'bpmn:Event')) {
55657
55658 // snap start
55659 context.connectionStart = mid(start);
55660 }
55661
55662 if (is$1(hover, 'bpmn:Event')) {
55663
55664 // snap hover
55665 snapToPosition(event, mid(hover));
55666 }
55667
55668 } else {
55669
55670 // un-snap source
55671 context.connectionStart = context.initialConnectionStart;
55672 }
55673 });
55674 }
55675
55676 BpmnConnectSnapping.$inject = [ 'eventBus' ];
55677
55678
55679 // helpers //////////
55680
55681 // snap to target if event in target
55682 function snapToShape(event, target, padding) {
55683 AXES.forEach(function(axis) {
55684 var dimensionForAxis = getDimensionForAxis(axis, target);
55685
55686 if (event[ axis ] < target[ axis ] + padding) {
55687 setSnapped(event, axis, target[ axis ] + padding);
55688 } else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) {
55689 setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding);
55690 }
55691 });
55692 }
55693
55694 // snap to target mid if event in target mid
55695 function snapToTargetMid(event, target) {
55696 var targetMid = mid(target);
55697
55698 AXES.forEach(function(axis) {
55699 if (isMid(event, target, axis)) {
55700 setSnapped(event, axis, targetMid[ axis ]);
55701 }
55702 });
55703 }
55704
55705 // snap to prevent loop overlapping boundary event
55706 function snapBoundaryEventLoop(event) {
55707 var context = event.context,
55708 source = context.source,
55709 target = context.target;
55710
55711 if (isReverse$2(context)) {
55712 return;
55713 }
55714
55715 var sourceMid = mid(source),
55716 orientation = getOrientation(sourceMid, target, -10),
55717 axes = [];
55718
55719 if (/top|bottom/.test(orientation)) {
55720 axes.push('x');
55721 }
55722
55723 if (/left|right/.test(orientation)) {
55724 axes.push('y');
55725 }
55726
55727 axes.forEach(function(axis) {
55728 var coordinate = event[ axis ], newCoordinate;
55729
55730 if (abs$7(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD$1) {
55731 if (coordinate > sourceMid[ axis ]) {
55732 newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD$1;
55733 }
55734 else {
55735 newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD$1;
55736 }
55737
55738 setSnapped(event, axis, newCoordinate);
55739 }
55740 });
55741 }
55742
55743 function snapToPosition(event, position) {
55744 setSnapped(event, 'x', position.x);
55745 setSnapped(event, 'y', position.y);
55746 }
55747
55748 function isType(attrs, type) {
55749 return attrs && attrs.type === type;
55750 }
55751
55752 function isAnyType(attrs, types) {
55753 return some(types, function(type) {
55754 return isType(attrs, type);
55755 });
55756 }
55757
55758 function getDimensionForAxis(axis, element) {
55759 return axis === 'x' ? element.width : element.height;
55760 }
55761
55762 function getTargetBoundsPadding(target) {
55763 if (is$1(target, 'bpmn:Task')) {
55764 return TASK_BOUNDS_PADDING;
55765 } else {
55766 return TARGET_BOUNDS_PADDING;
55767 }
55768 }
55769
55770 function isMid(event, target, axis) {
55771 return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING
55772 && event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING;
55773 }
55774
55775 function isReverse$2(context) {
55776 var hover = context.hover,
55777 source = context.source;
55778
55779 return hover && source && hover === source;
55780 }
55781
55782 /**
55783 * A snap context, containing the (possibly incomplete)
55784 * mappings of drop targets (to identify the snapping)
55785 * to computed snap points.
55786 */
55787 function SnapContext() {
55788
55789 /**
55790 * Map<String, SnapPoints> mapping drop targets to
55791 * a list of possible snappings.
55792 *
55793 * @type {Object}
55794 */
55795 this._targets = {};
55796
55797 /**
55798 * Map<String, Point> initial positioning of element
55799 * regarding various snap directions.
55800 *
55801 * @type {Object}
55802 */
55803 this._snapOrigins = {};
55804
55805 /**
55806 * List of snap locations
55807 *
55808 * @type {Array<string>}
55809 */
55810 this._snapLocations = [];
55811
55812 /**
55813 * Map<String, Array<Point>> of default snapping locations
55814 *
55815 * @type {Object}
55816 */
55817 this._defaultSnaps = {};
55818 }
55819
55820
55821 SnapContext.prototype.getSnapOrigin = function(snapLocation) {
55822 return this._snapOrigins[snapLocation];
55823 };
55824
55825
55826 SnapContext.prototype.setSnapOrigin = function(snapLocation, initialValue) {
55827 this._snapOrigins[snapLocation] = initialValue;
55828
55829 if (this._snapLocations.indexOf(snapLocation) === -1) {
55830 this._snapLocations.push(snapLocation);
55831 }
55832 };
55833
55834
55835 SnapContext.prototype.addDefaultSnap = function(type, point) {
55836
55837 var snapValues = this._defaultSnaps[type];
55838
55839 if (!snapValues) {
55840 snapValues = this._defaultSnaps[type] = [];
55841 }
55842
55843 snapValues.push(point);
55844 };
55845
55846 /**
55847 * Return a number of initialized snaps, i.e. snap locations such as
55848 * top-left, mid, bottom-right and so forth.
55849 *
55850 * @return {Array<string>} snapLocations
55851 */
55852 SnapContext.prototype.getSnapLocations = function() {
55853 return this._snapLocations;
55854 };
55855
55856 /**
55857 * Set the snap locations for this context.
55858 *
55859 * The order of locations determines precedence.
55860 *
55861 * @param {Array<string>} snapLocations
55862 */
55863 SnapContext.prototype.setSnapLocations = function(snapLocations) {
55864 this._snapLocations = snapLocations;
55865 };
55866
55867 /**
55868 * Get snap points for a given target
55869 *
55870 * @param {Element|string} target
55871 */
55872 SnapContext.prototype.pointsForTarget = function(target) {
55873
55874 var targetId = target.id || target;
55875
55876 var snapPoints = this._targets[targetId];
55877
55878 if (!snapPoints) {
55879 snapPoints = this._targets[targetId] = new SnapPoints();
55880 snapPoints.initDefaults(this._defaultSnaps);
55881 }
55882
55883 return snapPoints;
55884 };
55885
55886
55887 /**
55888 * Creates the snap points and initializes them with the
55889 * given default values.
55890 *
55891 * @param {Object<string, Array<Point>>} [defaultPoints]
55892 */
55893 function SnapPoints(defaultSnaps) {
55894
55895 /**
55896 * Map<String, Map<(x|y), Array<number>>> mapping snap locations,
55897 * i.e. top-left, bottom-right, center to actual snap values.
55898 *
55899 * @type {Object}
55900 */
55901 this._snapValues = {};
55902 }
55903
55904 SnapPoints.prototype.add = function(snapLocation, point) {
55905
55906 var snapValues = this._snapValues[snapLocation];
55907
55908 if (!snapValues) {
55909 snapValues = this._snapValues[snapLocation] = { x: [], y: [] };
55910 }
55911
55912 if (snapValues.x.indexOf(point.x) === -1) {
55913 snapValues.x.push(point.x);
55914 }
55915
55916 if (snapValues.y.indexOf(point.y) === -1) {
55917 snapValues.y.push(point.y);
55918 }
55919 };
55920
55921
55922 SnapPoints.prototype.snap = function(point, snapLocation, axis, tolerance) {
55923 var snappingValues = this._snapValues[snapLocation];
55924
55925 return snappingValues && snapTo(point[axis], snappingValues[axis], tolerance);
55926 };
55927
55928 /**
55929 * Initialize a number of default snapping points.
55930 *
55931 * @param {Object} defaultSnaps
55932 */
55933 SnapPoints.prototype.initDefaults = function(defaultSnaps) {
55934
55935 var self = this;
55936
55937 forEach(defaultSnaps || {}, function(snapPoints, snapLocation) {
55938 forEach(snapPoints, function(point) {
55939 self.add(snapLocation, point);
55940 });
55941 });
55942 };
55943
55944 var HIGHER_PRIORITY$3 = 1250;
55945
55946
55947 /**
55948 * Snap during create and move.
55949 *
55950 * @param {EventBus} elementRegistry
55951 * @param {EventBus} eventBus
55952 * @param {Snapping} snapping
55953 */
55954 function CreateMoveSnapping(elementRegistry, eventBus, snapping) {
55955 var self = this;
55956
55957 this._elementRegistry = elementRegistry;
55958
55959 eventBus.on([
55960 'create.start',
55961 'shape.move.start'
55962 ], function(event) {
55963 self.initSnap(event);
55964 });
55965
55966 eventBus.on([
55967 'create.move',
55968 'create.end',
55969 'shape.move.move',
55970 'shape.move.end'
55971 ], HIGHER_PRIORITY$3, function(event) {
55972 var context = event.context,
55973 shape = context.shape,
55974 snapContext = context.snapContext,
55975 target = context.target;
55976
55977 if (event.originalEvent && isCmd(event.originalEvent)) {
55978 return;
55979 }
55980
55981 if (isSnapped(event) || !target) {
55982 return;
55983 }
55984
55985 var snapPoints = snapContext.pointsForTarget(target);
55986
55987 if (!snapPoints.initialized) {
55988 snapPoints = self.addSnapTargetPoints(snapPoints, shape, target);
55989
55990 snapPoints.initialized = true;
55991 }
55992
55993 snapping.snap(event, snapPoints);
55994 });
55995
55996 eventBus.on([
55997 'create.cleanup',
55998 'shape.move.cleanup'
55999 ], function() {
56000 snapping.hide();
56001 });
56002 }
56003
56004 CreateMoveSnapping.$inject = [
56005 'elementRegistry',
56006 'eventBus',
56007 'snapping'
56008 ];
56009
56010 CreateMoveSnapping.prototype.initSnap = function(event) {
56011 var elementRegistry = this._elementRegistry;
56012
56013 var context = event.context,
56014 shape = context.shape,
56015 snapContext = context.snapContext;
56016
56017 if (!snapContext) {
56018 snapContext = context.snapContext = new SnapContext();
56019 }
56020
56021 var shapeMid;
56022
56023 if (elementRegistry.get(shape.id)) {
56024
56025 // move
56026 shapeMid = mid(shape, event);
56027 } else {
56028
56029 // create
56030 shapeMid = {
56031 x: event.x + mid(shape).x,
56032 y: event.y + mid(shape).y
56033 };
56034 }
56035
56036 var shapeTopLeft = {
56037 x: shapeMid.x - shape.width / 2,
56038 y: shapeMid.y - shape.height / 2
56039 },
56040 shapeBottomRight = {
56041 x: shapeMid.x + shape.width / 2,
56042 y: shapeMid.y + shape.height / 2
56043 };
56044
56045 snapContext.setSnapOrigin('mid', {
56046 x: shapeMid.x - event.x,
56047 y: shapeMid.y - event.y
56048 });
56049
56050 // snap labels to mid only
56051 if (isLabel$5(shape)) {
56052 return snapContext;
56053 }
56054
56055 snapContext.setSnapOrigin('top-left', {
56056 x: shapeTopLeft.x - event.x,
56057 y: shapeTopLeft.y - event.y
56058 });
56059
56060 snapContext.setSnapOrigin('bottom-right', {
56061 x: shapeBottomRight.x - event.x,
56062 y: shapeBottomRight.y - event.y
56063 });
56064
56065 return snapContext;
56066 };
56067
56068 CreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
56069 var snapTargets = this.getSnapTargets(shape, target);
56070
56071 forEach(snapTargets, function(snapTarget) {
56072
56073 // handle labels
56074 if (isLabel$5(snapTarget)) {
56075
56076 if (isLabel$5(shape)) {
56077 snapPoints.add('mid', mid(snapTarget));
56078 }
56079
56080 return;
56081 }
56082
56083 // handle connections
56084 if (isConnection$a(snapTarget)) {
56085
56086 // ignore single segment connections
56087 if (snapTarget.waypoints.length < 3) {
56088 return;
56089 }
56090
56091 // ignore first and last waypoint
56092 var waypoints = snapTarget.waypoints.slice(1, -1);
56093
56094 forEach(waypoints, function(waypoint) {
56095 snapPoints.add('mid', waypoint);
56096 });
56097
56098 return;
56099 }
56100
56101 // handle shapes
56102 snapPoints.add('mid', mid(snapTarget));
56103 });
56104
56105 if (!isNumber(shape.x) || !isNumber(shape.y)) {
56106 return snapPoints;
56107 }
56108
56109 // snap to original position when moving
56110 if (this._elementRegistry.get(shape.id)) {
56111 snapPoints.add('mid', mid(shape));
56112 }
56113
56114 return snapPoints;
56115 };
56116
56117 CreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
56118 return getChildren$1(target).filter(function(child) {
56119 return !isHidden(child);
56120 });
56121 };
56122
56123 // helpers //////////
56124
56125 function isConnection$a(element) {
56126 return !!element.waypoints;
56127 }
56128
56129 function isHidden(element) {
56130 return !!element.hidden;
56131 }
56132
56133 function isLabel$5(element) {
56134 return !!element.labelTarget;
56135 }
56136
56137 var HIGH_PRIORITY$j = 1500;
56138
56139
56140 /**
56141 * Snap during create and move.
56142 *
56143 * @param {EventBus} eventBus
56144 * @param {Injector} injector
56145 */
56146 function BpmnCreateMoveSnapping(eventBus, injector) {
56147 injector.invoke(CreateMoveSnapping, this);
56148
56149 // creating first participant
56150 eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY$j, setSnappedIfConstrained);
56151
56152 // snap boundary events
56153 eventBus.on([
56154 'create.move',
56155 'create.end',
56156 'shape.move.move',
56157 'shape.move.end'
56158 ], HIGH_PRIORITY$j, function(event) {
56159 var context = event.context,
56160 canExecute = context.canExecute,
56161 target = context.target;
56162
56163 var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);
56164
56165 if (canAttach && !isSnapped(event)) {
56166 snapBoundaryEvent(event, target);
56167 }
56168 });
56169 }
56170
56171 inherits_browser(BpmnCreateMoveSnapping, CreateMoveSnapping);
56172
56173 BpmnCreateMoveSnapping.$inject = [
56174 'eventBus',
56175 'injector'
56176 ];
56177
56178 BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
56179 var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
56180
56181 var shape = event.shape;
56182
56183 var isMove = !!this._elementRegistry.get(shape.id);
56184
56185 // snap to docking points
56186 forEach(shape.outgoing, function(connection) {
56187 var docking = connection.waypoints[0];
56188
56189 docking = docking.original || docking;
56190
56191 snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
56192 });
56193
56194 forEach(shape.incoming, function(connection) {
56195 var docking = connection.waypoints[connection.waypoints.length - 1];
56196
56197 docking = docking.original || docking;
56198
56199 snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
56200 });
56201
56202 if (is$1(shape, 'bpmn:Participant')) {
56203
56204 // snap to borders with higher priority
56205 snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
56206 }
56207
56208 return snapContext;
56209 };
56210
56211 BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
56212 CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
56213
56214 var snapTargets = this.getSnapTargets(shape, target);
56215
56216 forEach(snapTargets, function(snapTarget) {
56217
56218 // handle TRBL alignment
56219 //
56220 // * with container elements
56221 // * with text annotations
56222 if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
56223 snapPoints.add('top-left', topLeft(snapTarget));
56224 snapPoints.add('bottom-right', bottomRight(snapTarget));
56225 }
56226 });
56227
56228 var elementRegistry = this._elementRegistry;
56229
56230 // snap to docking points if not create mode
56231 forEach(shape.incoming, function(connection) {
56232 if (elementRegistry.get(shape.id)) {
56233
56234 if (!includes$6(snapTargets, connection.source)) {
56235 snapPoints.add('mid', getMid(connection.source));
56236 }
56237
56238 var docking = connection.waypoints[0];
56239 snapPoints.add(connection.id + '-docking', docking.original || docking);
56240 }
56241 });
56242
56243 forEach(shape.outgoing, function(connection) {
56244 if (elementRegistry.get(shape.id)) {
56245
56246 if (!includes$6(snapTargets, connection.target)) {
56247 snapPoints.add('mid', getMid(connection.target));
56248 }
56249
56250 var docking = connection.waypoints[ connection.waypoints.length - 1 ];
56251
56252 snapPoints.add(connection.id + '-docking', docking.original || docking);
56253 }
56254 });
56255
56256 // add sequence flow parents as snap targets
56257 if (is$1(target, 'bpmn:SequenceFlow')) {
56258 snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
56259 }
56260
56261 return snapPoints;
56262 };
56263
56264 BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
56265 return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
56266 .filter(function(snapTarget) {
56267
56268 // do not snap to lanes
56269 return !is$1(snapTarget, 'bpmn:Lane');
56270 });
56271 };
56272
56273 // helpers //////////
56274
56275 function snapBoundaryEvent(event, target) {
56276 var targetTRBL = asTRBL(target);
56277
56278 var direction = getBoundaryAttachment(event, target);
56279
56280 var context = event.context,
56281 shape = context.shape;
56282
56283 var offset;
56284
56285 if (shape.parent) {
56286 offset = { x: 0, y: 0 };
56287 } else {
56288 offset = getMid(shape);
56289 }
56290
56291 if (/top/.test(direction)) {
56292 setSnapped(event, 'y', targetTRBL.top - offset.y);
56293 } else if (/bottom/.test(direction)) {
56294 setSnapped(event, 'y', targetTRBL.bottom - offset.y);
56295 }
56296
56297 if (/left/.test(direction)) {
56298 setSnapped(event, 'x', targetTRBL.left - offset.x);
56299 } else if (/right/.test(direction)) {
56300 setSnapped(event, 'x', targetTRBL.right - offset.x);
56301 }
56302 }
56303
56304 function areAll(elements, type) {
56305 return elements.every(function(el) {
56306 return is$1(el, type);
56307 });
56308 }
56309
56310 function isContainer(element) {
56311 if (is$1(element, 'bpmn:SubProcess') && isExpanded(element)) {
56312 return true;
56313 }
56314
56315 return is$1(element, 'bpmn:Participant');
56316 }
56317
56318
56319 function setSnappedIfConstrained(event) {
56320 var context = event.context,
56321 createConstraints = context.createConstraints;
56322
56323 if (!createConstraints) {
56324 return;
56325 }
56326
56327 var top = createConstraints.top,
56328 right = createConstraints.right,
56329 bottom = createConstraints.bottom,
56330 left = createConstraints.left;
56331
56332 if ((left && left >= event.x) || (right && right <= event.x)) {
56333 setSnapped(event, 'x', event.x);
56334 }
56335
56336 if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
56337 setSnapped(event, 'y', event.y);
56338 }
56339 }
56340
56341 function includes$6(array, value) {
56342 return array.indexOf(value) !== -1;
56343 }
56344
56345 function getDockingSnapOrigin(docking, isMove, event) {
56346 return isMove ? (
56347 {
56348 x: docking.x - event.x,
56349 y: docking.y - event.y
56350 }
56351 ) : {
56352 x: docking.x,
56353 y: docking.y
56354 };
56355 }
56356
56357 var HIGHER_PRIORITY$4 = 1250;
56358
56359
56360 /**
56361 * Snap during resize.
56362 *
56363 * @param {EventBus} eventBus
56364 * @param {Snapping} snapping
56365 */
56366 function ResizeSnapping(eventBus, snapping) {
56367 var self = this;
56368
56369 eventBus.on([ 'resize.start' ], function(event) {
56370 self.initSnap(event);
56371 });
56372
56373 eventBus.on([
56374 'resize.move',
56375 'resize.end',
56376 ], HIGHER_PRIORITY$4, function(event) {
56377 var context = event.context,
56378 shape = context.shape,
56379 parent = shape.parent,
56380 direction = context.direction,
56381 snapContext = context.snapContext;
56382
56383 if (event.originalEvent && isCmd(event.originalEvent)) {
56384 return;
56385 }
56386
56387 if (isSnapped(event)) {
56388 return;
56389 }
56390
56391 var snapPoints = snapContext.pointsForTarget(parent);
56392
56393 if (!snapPoints.initialized) {
56394 snapPoints = self.addSnapTargetPoints(snapPoints, shape, parent, direction);
56395
56396 snapPoints.initialized = true;
56397 }
56398
56399 if (isHorizontal$3(direction)) {
56400 setSnapped(event, 'x', event.x);
56401 }
56402
56403 if (isVertical(direction)) {
56404 setSnapped(event, 'y', event.y);
56405 }
56406
56407 snapping.snap(event, snapPoints);
56408 });
56409
56410 eventBus.on([ 'resize.cleanup' ], function() {
56411 snapping.hide();
56412 });
56413 }
56414
56415 ResizeSnapping.prototype.initSnap = function(event) {
56416 var context = event.context,
56417 shape = context.shape,
56418 direction = context.direction,
56419 snapContext = context.snapContext;
56420
56421 if (!snapContext) {
56422 snapContext = context.snapContext = new SnapContext();
56423 }
56424
56425 var snapOrigin = getSnapOrigin(shape, direction);
56426
56427 snapContext.setSnapOrigin('corner', {
56428 x: snapOrigin.x - event.x,
56429 y: snapOrigin.y - event.y
56430 });
56431
56432 return snapContext;
56433 };
56434
56435 ResizeSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target, direction) {
56436 var snapTargets = this.getSnapTargets(shape, target);
56437
56438 forEach(snapTargets, function(snapTarget) {
56439 snapPoints.add('corner', bottomRight(snapTarget));
56440 snapPoints.add('corner', topLeft(snapTarget));
56441 });
56442
56443 snapPoints.add('corner', getSnapOrigin(shape, direction));
56444
56445 return snapPoints;
56446 };
56447
56448 ResizeSnapping.$inject = [
56449 'eventBus',
56450 'snapping'
56451 ];
56452
56453 ResizeSnapping.prototype.getSnapTargets = function(shape, target) {
56454 return getChildren$1(target).filter(function(child) {
56455 return !isAttached(child, shape)
56456 && !isConnection$b(child)
56457 && !isHidden$1(child)
56458 && !isLabel$6(child);
56459 });
56460 };
56461
56462 // helpers //////////
56463
56464 function getSnapOrigin(shape, direction) {
56465 var mid = getMid(shape),
56466 trbl = asTRBL(shape);
56467
56468 var snapOrigin = {
56469 x: mid.x,
56470 y: mid.y
56471 };
56472
56473 if (direction.indexOf('n') !== -1) {
56474 snapOrigin.y = trbl.top;
56475 } else if (direction.indexOf('s') !== -1) {
56476 snapOrigin.y = trbl.bottom;
56477 }
56478
56479 if (direction.indexOf('e') !== -1) {
56480 snapOrigin.x = trbl.right;
56481 } else if (direction.indexOf('w') !== -1) {
56482 snapOrigin.x = trbl.left;
56483 }
56484
56485 return snapOrigin;
56486 }
56487
56488 function isAttached(element, host) {
56489 return element.host === host;
56490 }
56491
56492 function isConnection$b(element) {
56493 return !!element.waypoints;
56494 }
56495
56496 function isHidden$1(element) {
56497 return !!element.hidden;
56498 }
56499
56500 function isLabel$6(element) {
56501 return !!element.labelTarget;
56502 }
56503
56504 function isHorizontal$3(direction) {
56505 return direction === 'n' || direction === 's';
56506 }
56507
56508 function isVertical(direction) {
56509 return direction === 'e' || direction === 'w';
56510 }
56511
56512 var SNAP_TOLERANCE = 7;
56513
56514 var SNAP_LINE_HIDE_DELAY = 1000;
56515
56516
56517 /**
56518 * Generic snapping feature.
56519 *
56520 * @param {EventBus} eventBus
56521 * @param {Canvas} canvas
56522 */
56523 function Snapping(canvas) {
56524 this._canvas = canvas;
56525
56526 // delay hide by 1000 seconds since last snap
56527 this._asyncHide = debounce(bind(this.hide, this), SNAP_LINE_HIDE_DELAY);
56528 }
56529
56530 Snapping.$inject = [ 'canvas' ];
56531
56532 /**
56533 * Snap an event to given snap points.
56534 *
56535 * @param {Event} event
56536 * @param {SnapPoints} snapPoints
56537 */
56538 Snapping.prototype.snap = function(event, snapPoints) {
56539 var context = event.context,
56540 snapContext = context.snapContext,
56541 snapLocations = snapContext.getSnapLocations();
56542
56543 var snapping = {
56544 x: isSnapped(event, 'x'),
56545 y: isSnapped(event, 'y')
56546 };
56547
56548 forEach(snapLocations, function(location) {
56549 var snapOrigin = snapContext.getSnapOrigin(location);
56550
56551 var snapCurrent = {
56552 x: event.x + snapOrigin.x,
56553 y: event.y + snapOrigin.y
56554 };
56555
56556 // snap both axis if not snapped already
56557 forEach([ 'x', 'y' ], function(axis) {
56558 var locationSnapping;
56559
56560 if (!snapping[axis]) {
56561 locationSnapping = snapPoints.snap(snapCurrent, location, axis, SNAP_TOLERANCE);
56562
56563 if (locationSnapping !== undefined) {
56564 snapping[axis] = {
56565 value: locationSnapping,
56566 originValue: locationSnapping - snapOrigin[axis]
56567 };
56568 }
56569 }
56570 });
56571
56572 // no need to continue snapping
56573 if (snapping.x && snapping.y) {
56574 return false;
56575 }
56576 });
56577
56578 // show snap lines
56579 this.showSnapLine('vertical', snapping.x && snapping.x.value);
56580 this.showSnapLine('horizontal', snapping.y && snapping.y.value);
56581
56582 // snap event
56583 forEach([ 'x', 'y' ], function(axis) {
56584 var axisSnapping = snapping[axis];
56585
56586 if (isObject(axisSnapping)) {
56587 setSnapped(event, axis, axisSnapping.originValue);
56588 }
56589 });
56590 };
56591
56592 Snapping.prototype._createLine = function(orientation) {
56593 var root = this._canvas.getLayer('snap');
56594
56595 var line = create('path');
56596
56597 attr$1(line, { d: 'M0,0 L0,0' });
56598
56599 classes$1(line).add('djs-snap-line');
56600
56601 append(root, line);
56602
56603 return {
56604 update: function(position) {
56605
56606 if (!isNumber(position)) {
56607 attr$1(line, { display: 'none' });
56608 } else {
56609 if (orientation === 'horizontal') {
56610 attr$1(line, {
56611 d: 'M-100000,' + position + ' L+100000,' + position,
56612 display: ''
56613 });
56614 } else {
56615 attr$1(line, {
56616 d: 'M ' + position + ',-100000 L ' + position + ', +100000',
56617 display: ''
56618 });
56619 }
56620 }
56621 }
56622 };
56623 };
56624
56625 Snapping.prototype._createSnapLines = function() {
56626 this._snapLines = {
56627 horizontal: this._createLine('horizontal'),
56628 vertical: this._createLine('vertical')
56629 };
56630 };
56631
56632 Snapping.prototype.showSnapLine = function(orientation, position) {
56633
56634 var line = this.getSnapLine(orientation);
56635
56636 if (line) {
56637 line.update(position);
56638 }
56639
56640 this._asyncHide();
56641 };
56642
56643 Snapping.prototype.getSnapLine = function(orientation) {
56644 if (!this._snapLines) {
56645 this._createSnapLines();
56646 }
56647
56648 return this._snapLines[orientation];
56649 };
56650
56651 Snapping.prototype.hide = function() {
56652 forEach(this._snapLines, function(snapLine) {
56653 snapLine.update();
56654 });
56655 };
56656
56657 var SnappingModule = {
56658 __init__: [
56659 'createMoveSnapping',
56660 'resizeSnapping',
56661 'snapping'
56662 ],
56663 createMoveSnapping: [ 'type', CreateMoveSnapping ],
56664 resizeSnapping: [ 'type', ResizeSnapping ],
56665 snapping: [ 'type', Snapping ]
56666 };
56667
56668 var SnappingModule$1 = {
56669 __depends__: [ SnappingModule ],
56670 __init__: [
56671 'connectSnapping',
56672 'createMoveSnapping'
56673 ],
56674 connectSnapping: [ 'type', BpmnConnectSnapping ],
56675 createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
56676 };
56677
56678 /**
56679 * Provides searching infrastructure
56680 */
56681 function SearchPad(canvas, eventBus, overlays, selection) {
56682 this._open = false;
56683 this._results = [];
56684 this._eventMaps = [];
56685
56686 this._canvas = canvas;
56687 this._eventBus = eventBus;
56688 this._overlays = overlays;
56689 this._selection = selection;
56690
56691 // setup elements
56692 this._container = domify(SearchPad.BOX_HTML);
56693 this._searchInput = query(SearchPad.INPUT_SELECTOR, this._container);
56694 this._resultsContainer = query(SearchPad.RESULTS_CONTAINER_SELECTOR, this._container);
56695
56696 // attach search pad
56697 this._canvas.getContainer().appendChild(this._container);
56698
56699 // cleanup on destroy
56700 eventBus.on([ 'canvas.destroy', 'diagram.destroy' ], this.close, this);
56701 }
56702
56703
56704 SearchPad.$inject = [
56705 'canvas',
56706 'eventBus',
56707 'overlays',
56708 'selection'
56709 ];
56710
56711
56712 /**
56713 * Binds and keeps track of all event listereners
56714 */
56715 SearchPad.prototype._bindEvents = function() {
56716 var self = this;
56717
56718 function listen(el, selector, type, fn) {
56719 self._eventMaps.push({
56720 el: el,
56721 type: type,
56722 listener: delegate.bind(el, selector, type, fn)
56723 });
56724 }
56725
56726 // close search on clicking anywhere outside
56727 listen(document, 'html', 'click', function(e) {
56728 self.close();
56729 });
56730
56731 // stop event from propagating and closing search
56732 // focus on input
56733 listen(this._container, SearchPad.INPUT_SELECTOR, 'click', function(e) {
56734 e.stopPropagation();
56735 e.delegateTarget.focus();
56736 });
56737
56738 // preselect result on hover
56739 listen(this._container, SearchPad.RESULT_SELECTOR, 'mouseover', function(e) {
56740 e.stopPropagation();
56741 self._scrollToNode(e.delegateTarget);
56742 self._preselect(e.delegateTarget);
56743 });
56744
56745 // selects desired result on mouse click
56746 listen(this._container, SearchPad.RESULT_SELECTOR, 'click', function(e) {
56747 e.stopPropagation();
56748 self._select(e.delegateTarget);
56749 });
56750
56751 // prevent cursor in input from going left and right when using up/down to
56752 // navigate results
56753 listen(this._container, SearchPad.INPUT_SELECTOR, 'keydown', function(e) {
56754
56755 // up
56756 if (e.keyCode === 38) {
56757 e.preventDefault();
56758 }
56759
56760 // down
56761 if (e.keyCode === 40) {
56762 e.preventDefault();
56763 }
56764 });
56765
56766 // handle keyboard input
56767 listen(this._container, SearchPad.INPUT_SELECTOR, 'keyup', function(e) {
56768
56769 // escape
56770 if (e.keyCode === 27) {
56771 return self.close();
56772 }
56773
56774 // enter
56775 if (e.keyCode === 13) {
56776 var selected = self._getCurrentResult();
56777
56778 return selected ? self._select(selected) : self.close();
56779 }
56780
56781 // up
56782 if (e.keyCode === 38) {
56783 return self._scrollToDirection(true);
56784 }
56785
56786 // down
56787 if (e.keyCode === 40) {
56788 return self._scrollToDirection();
56789 }
56790
56791 // left && right
56792 // do not search while navigating text input
56793 if (e.keyCode === 37 || e.keyCode === 39) {
56794 return;
56795 }
56796
56797 // anything else
56798 self._search(e.delegateTarget.value);
56799 });
56800 };
56801
56802
56803 /**
56804 * Unbinds all previously established listeners
56805 */
56806 SearchPad.prototype._unbindEvents = function() {
56807 this._eventMaps.forEach(function(m) {
56808 delegate.unbind(m.el, m.type, m.listener);
56809 });
56810 };
56811
56812
56813 /**
56814 * Performs a search for the given pattern.
56815 *
56816 * @param {string} pattern
56817 */
56818 SearchPad.prototype._search = function(pattern) {
56819 var self = this;
56820
56821 this._clearResults();
56822
56823 // do not search on empty query
56824 if (!pattern || pattern === '') {
56825 return;
56826 }
56827
56828 var searchResults = this._searchProvider.find(pattern);
56829
56830 if (!searchResults.length) {
56831 return;
56832 }
56833
56834 // append new results
56835 searchResults.forEach(function(result) {
56836 var id = result.element.id;
56837 var node = self._createResultNode(result, id);
56838 self._results[id] = {
56839 element: result.element,
56840 node: node
56841 };
56842 });
56843
56844 // preselect first result
56845 var node = query(SearchPad.RESULT_SELECTOR, this._resultsContainer);
56846 this._scrollToNode(node);
56847 this._preselect(node);
56848 };
56849
56850
56851 /**
56852 * Navigate to the previous/next result. Defaults to next result.
56853 * @param {boolean} previous
56854 */
56855 SearchPad.prototype._scrollToDirection = function(previous) {
56856 var selected = this._getCurrentResult();
56857 if (!selected) {
56858 return;
56859 }
56860
56861 var node = previous ? selected.previousElementSibling : selected.nextElementSibling;
56862 if (node) {
56863 this._scrollToNode(node);
56864 this._preselect(node);
56865 }
56866 };
56867
56868
56869 /**
56870 * Scroll to the node if it is not visible.
56871 *
56872 * @param {Element} node
56873 */
56874 SearchPad.prototype._scrollToNode = function(node) {
56875 if (!node || node === this._getCurrentResult()) {
56876 return;
56877 }
56878
56879 var nodeOffset = node.offsetTop;
56880 var containerScroll = this._resultsContainer.scrollTop;
56881
56882 var bottomScroll = nodeOffset - this._resultsContainer.clientHeight + node.clientHeight;
56883
56884 if (nodeOffset < containerScroll) {
56885 this._resultsContainer.scrollTop = nodeOffset;
56886 } else if (containerScroll < bottomScroll) {
56887 this._resultsContainer.scrollTop = bottomScroll;
56888 }
56889 };
56890
56891
56892 /**
56893 * Clears all results data.
56894 */
56895 SearchPad.prototype._clearResults = function() {
56896 clear(this._resultsContainer);
56897
56898 this._results = [];
56899
56900 this._resetOverlay();
56901
56902 this._eventBus.fire('searchPad.cleared');
56903 };
56904
56905
56906 /**
56907 * Get currently selected result.
56908 *
56909 * @return {Element}
56910 */
56911 SearchPad.prototype._getCurrentResult = function() {
56912 return query(SearchPad.RESULT_SELECTED_SELECTOR, this._resultsContainer);
56913 };
56914
56915
56916 /**
56917 * Create result DOM element within results container
56918 * that corresponds to a search result.
56919 *
56920 * 'result' : one of the elements returned by SearchProvider
56921 * 'id' : id attribute value to assign to the new DOM node
56922 * return : created DOM element
56923 *
56924 * @param {SearchResult} result
56925 * @param {string} id
56926 * @return {Element}
56927 */
56928 SearchPad.prototype._createResultNode = function(result, id) {
56929 var node = domify(SearchPad.RESULT_HTML);
56930
56931 // create only if available
56932 if (result.primaryTokens.length > 0) {
56933 createInnerTextNode(node, result.primaryTokens, SearchPad.RESULT_PRIMARY_HTML);
56934 }
56935
56936 // secondary tokens (represent element ID) are allways available
56937 createInnerTextNode(node, result.secondaryTokens, SearchPad.RESULT_SECONDARY_HTML);
56938
56939 attr(node, SearchPad.RESULT_ID_ATTRIBUTE, id);
56940
56941 this._resultsContainer.appendChild(node);
56942
56943 return node;
56944 };
56945
56946
56947 /**
56948 * Register search element provider.
56949 *
56950 * SearchProvider.find - provides search function over own elements
56951 * (pattern) => [{ text: <String>, element: <Element>}, ...]
56952 *
56953 * @param {SearchProvider} provider
56954 */
56955 SearchPad.prototype.registerProvider = function(provider) {
56956 this._searchProvider = provider;
56957 };
56958
56959
56960 /**
56961 * Open search pad.
56962 */
56963 SearchPad.prototype.open = function() {
56964 if (!this._searchProvider) {
56965 throw new Error('no search provider registered');
56966 }
56967
56968 if (this.isOpen()) {
56969 return;
56970 }
56971
56972 this._bindEvents();
56973
56974 this._open = true;
56975
56976 classes(this._container).add('open');
56977
56978 this._searchInput.focus();
56979
56980 this._eventBus.fire('searchPad.opened');
56981 };
56982
56983
56984 /**
56985 * Close search pad.
56986 */
56987 SearchPad.prototype.close = function() {
56988 if (!this.isOpen()) {
56989 return;
56990 }
56991
56992 this._unbindEvents();
56993
56994 this._open = false;
56995
56996 classes(this._container).remove('open');
56997
56998 this._clearResults();
56999
57000 this._searchInput.value = '';
57001 this._searchInput.blur();
57002
57003 this._resetOverlay();
57004
57005 this._eventBus.fire('searchPad.closed');
57006 };
57007
57008
57009 /**
57010 * Toggles search pad on/off.
57011 */
57012 SearchPad.prototype.toggle = function() {
57013 this.isOpen() ? this.close() : this.open();
57014 };
57015
57016
57017 /**
57018 * Report state of search pad.
57019 */
57020 SearchPad.prototype.isOpen = function() {
57021 return this._open;
57022 };
57023
57024
57025 /**
57026 * Preselect result entry.
57027 *
57028 * @param {Element} element
57029 */
57030 SearchPad.prototype._preselect = function(node) {
57031 var selectedNode = this._getCurrentResult();
57032
57033 // already selected
57034 if (node === selectedNode) {
57035 return;
57036 }
57037
57038 // removing preselection from current node
57039 if (selectedNode) {
57040 classes(selectedNode).remove(SearchPad.RESULT_SELECTED_CLASS);
57041 }
57042
57043 var id = attr(node, SearchPad.RESULT_ID_ATTRIBUTE);
57044 var element = this._results[id].element;
57045
57046 classes(node).add(SearchPad.RESULT_SELECTED_CLASS);
57047
57048 this._resetOverlay(element);
57049
57050 this._canvas.scrollToElement(element, { top: 400 });
57051
57052 this._selection.select(element);
57053
57054 this._eventBus.fire('searchPad.preselected', element);
57055 };
57056
57057
57058 /**
57059 * Select result node.
57060 *
57061 * @param {Element} element
57062 */
57063 SearchPad.prototype._select = function(node) {
57064 var id = attr(node, SearchPad.RESULT_ID_ATTRIBUTE);
57065 var element = this._results[id].element;
57066
57067 this.close();
57068
57069 this._resetOverlay();
57070
57071 this._canvas.scrollToElement(element, { top: 400 });
57072
57073 this._selection.select(element);
57074
57075 this._eventBus.fire('searchPad.selected', element);
57076 };
57077
57078
57079 /**
57080 * Reset overlay removes and, optionally, set
57081 * overlay to a new element.
57082 *
57083 * @param {Element} element
57084 */
57085 SearchPad.prototype._resetOverlay = function(element) {
57086 if (this._overlayId) {
57087 this._overlays.remove(this._overlayId);
57088 }
57089
57090 if (element) {
57091 var box = getBBox(element);
57092 var overlay = constructOverlay(box);
57093 this._overlayId = this._overlays.add(element, overlay);
57094 }
57095 };
57096
57097
57098 /**
57099 * Construct overlay object for the given bounding box.
57100 *
57101 * @param {BoundingBox} box
57102 * @return {Object}
57103 */
57104 function constructOverlay(box) {
57105
57106 var offset = 6;
57107 var w = box.width + offset * 2;
57108 var h = box.height + offset * 2;
57109
57110 var styles = [
57111 'width: '+ w +'px',
57112 'height: '+ h + 'px'
57113 ].join('; ');
57114
57115 return {
57116 position: {
57117 bottom: h - offset,
57118 right: w - offset
57119 },
57120 show: true,
57121 html: '<div style="' + styles + '" class="' + SearchPad.OVERLAY_CLASS + '"></div>'
57122 };
57123 }
57124
57125
57126 /**
57127 * Creates and appends child node from result tokens and HTML template.
57128 *
57129 * @param {Element} node
57130 * @param {Array<Object>} tokens
57131 * @param {string} template
57132 */
57133 function createInnerTextNode(parentNode, tokens, template) {
57134 var text = createHtmlText(tokens);
57135 var childNode = domify(template);
57136 childNode.innerHTML = text;
57137 parentNode.appendChild(childNode);
57138 }
57139
57140 /**
57141 * Create internal HTML markup from result tokens.
57142 * Caters for highlighting pattern matched tokens.
57143 *
57144 * @param {Array<Object>} tokens
57145 * @return {string}
57146 */
57147 function createHtmlText(tokens) {
57148 var htmlText = '';
57149
57150 tokens.forEach(function(t) {
57151 if (t.matched) {
57152 htmlText += '<strong class="' + SearchPad.RESULT_HIGHLIGHT_CLASS + '">' + escapeHTML(t.matched) + '</strong>';
57153 } else {
57154 htmlText += escapeHTML(t.normal);
57155 }
57156 });
57157
57158 return htmlText !== '' ? htmlText : null;
57159 }
57160
57161
57162 /**
57163 * CONSTANTS
57164 */
57165 SearchPad.CONTAINER_SELECTOR = '.djs-search-container';
57166 SearchPad.INPUT_SELECTOR = '.djs-search-input input';
57167 SearchPad.RESULTS_CONTAINER_SELECTOR = '.djs-search-results';
57168 SearchPad.RESULT_SELECTOR = '.djs-search-result';
57169 SearchPad.RESULT_SELECTED_CLASS = 'djs-search-result-selected';
57170 SearchPad.RESULT_SELECTED_SELECTOR = '.' + SearchPad.RESULT_SELECTED_CLASS;
57171 SearchPad.RESULT_ID_ATTRIBUTE = 'data-result-id';
57172 SearchPad.RESULT_HIGHLIGHT_CLASS = 'djs-search-highlight';
57173 SearchPad.OVERLAY_CLASS = 'djs-search-overlay';
57174
57175 SearchPad.BOX_HTML =
57176 '<div class="djs-search-container djs-draggable djs-scrollable">' +
57177 '<div class="djs-search-input">' +
57178 '<input type="text"/>' +
57179 '</div>' +
57180 '<div class="djs-search-results"></div>' +
57181 '</div>';
57182
57183 SearchPad.RESULT_HTML =
57184 '<div class="djs-search-result"></div>';
57185
57186 SearchPad.RESULT_PRIMARY_HTML =
57187 '<div class="djs-search-result-primary"></div>';
57188
57189 SearchPad.RESULT_SECONDARY_HTML =
57190 '<p class="djs-search-result-secondary"></p>';
57191
57192 var SearchPadModule = {
57193 __depends__: [
57194 OverlaysModule,
57195 SelectionModule
57196 ],
57197 searchPad: [ 'type', SearchPad ]
57198 };
57199
57200 /**
57201 * Provides ability to search through BPMN elements
57202 */
57203 function BpmnSearchProvider(elementRegistry, searchPad, canvas) {
57204
57205 this._elementRegistry = elementRegistry;
57206 this._canvas = canvas;
57207
57208 searchPad.registerProvider(this);
57209 }
57210
57211 BpmnSearchProvider.$inject = [
57212 'elementRegistry',
57213 'searchPad',
57214 'canvas'
57215 ];
57216
57217
57218 /**
57219 * Finds all elements that match given pattern
57220 *
57221 * <Result> :
57222 * {
57223 * primaryTokens: <Array<Token>>,
57224 * secondaryTokens: <Array<Token>>,
57225 * element: <Element>
57226 * }
57227 *
57228 * <Token> :
57229 * {
57230 * normal|matched: <string>
57231 * }
57232 *
57233 * @param {string} pattern
57234 * @return {Array<Result>}
57235 */
57236 BpmnSearchProvider.prototype.find = function(pattern) {
57237 var rootElement = this._canvas.getRootElement();
57238
57239 var elements = this._elementRegistry.filter(function(element) {
57240 if (element.labelTarget) {
57241 return false;
57242 }
57243 return true;
57244 });
57245
57246 // do not include root element
57247 elements = filter(elements, function(element) {
57248 return element !== rootElement;
57249 });
57250
57251 elements = map(elements, function(element) {
57252 return {
57253 primaryTokens: matchAndSplit(getLabel(element), pattern),
57254 secondaryTokens: matchAndSplit(element.id, pattern),
57255 element: element
57256 };
57257 });
57258
57259 // exclude non-matched elements
57260 elements = filter(elements, function(element) {
57261 return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens);
57262 });
57263
57264 elements = sortBy(elements, function(element) {
57265 return getLabel(element.element) + element.element.id;
57266 });
57267
57268 return elements;
57269 };
57270
57271
57272 function hasMatched(tokens) {
57273 var matched = filter(tokens, function(t) {
57274 return !!t.matched;
57275 });
57276
57277 return matched.length > 0;
57278 }
57279
57280
57281 function matchAndSplit(text, pattern) {
57282 var tokens = [],
57283 originalText = text;
57284
57285 if (!text) {
57286 return tokens;
57287 }
57288
57289 text = text.toLowerCase();
57290 pattern = pattern.toLowerCase();
57291
57292 var i = text.indexOf(pattern);
57293
57294 if (i > -1) {
57295 if (i !== 0) {
57296 tokens.push({
57297 normal: originalText.substr(0, i)
57298 });
57299 }
57300
57301 tokens.push({
57302 matched: originalText.substr(i, pattern.length)
57303 });
57304
57305 if (pattern.length + i < text.length) {
57306 tokens.push({
57307 normal: originalText.substr(pattern.length + i, text.length)
57308 });
57309 }
57310 } else {
57311 tokens.push({
57312 normal: originalText
57313 });
57314 }
57315
57316 return tokens;
57317 }
57318
57319 var SearchModule = {
57320 __depends__: [
57321 SearchPadModule
57322 ],
57323 __init__: [ 'bpmnSearch'],
57324 bpmnSearch: [ 'type', BpmnSearchProvider ]
57325 };
57326
57327 var initialDiagram =
57328 '<?xml version="1.0" encoding="UTF-8"?>' +
57329 '<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
57330 'xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" ' +
57331 'xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" ' +
57332 'xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" ' +
57333 'targetNamespace="http://bpmn.io/schema/bpmn" ' +
57334 'id="Definitions_1">' +
57335 '<bpmn:process id="Process_1" isExecutable="false">' +
57336 '<bpmn:startEvent id="StartEvent_1"/>' +
57337 '</bpmn:process>' +
57338 '<bpmndi:BPMNDiagram id="BPMNDiagram_1">' +
57339 '<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">' +
57340 '<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">' +
57341 '<dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/>' +
57342 '</bpmndi:BPMNShape>' +
57343 '</bpmndi:BPMNPlane>' +
57344 '</bpmndi:BPMNDiagram>' +
57345 '</bpmn:definitions>';
57346
57347
57348 /**
57349 * A modeler for BPMN 2.0 diagrams.
57350 *
57351 *
57352 * ## Extending the Modeler
57353 *
57354 * In order to extend the viewer pass extension modules to bootstrap via the
57355 * `additionalModules` option. An extension module is an object that exposes
57356 * named services.
57357 *
57358 * The following example depicts the integration of a simple
57359 * logging component that integrates with interaction events:
57360 *
57361 *
57362 * ```javascript
57363 *
57364 * // logging component
57365 * function InteractionLogger(eventBus) {
57366 * eventBus.on('element.hover', function(event) {
57367 * console.log()
57368 * })
57369 * }
57370 *
57371 * InteractionLogger.$inject = [ 'eventBus' ]; // minification save
57372 *
57373 * // extension module
57374 * var extensionModule = {
57375 * __init__: [ 'interactionLogger' ],
57376 * interactionLogger: [ 'type', InteractionLogger ]
57377 * };
57378 *
57379 * // extend the viewer
57380 * var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
57381 * bpmnModeler.importXML(...);
57382 * ```
57383 *
57384 *
57385 * ## Customizing / Replacing Components
57386 *
57387 * You can replace individual diagram components by redefining them in override modules.
57388 * This works for all components, including those defined in the core.
57389 *
57390 * Pass in override modules via the `options.additionalModules` flag like this:
57391 *
57392 * ```javascript
57393 * function CustomContextPadProvider(contextPad) {
57394 *
57395 * contextPad.registerProvider(this);
57396 *
57397 * this.getContextPadEntries = function(element) {
57398 * // no entries, effectively disable the context pad
57399 * return {};
57400 * };
57401 * }
57402 *
57403 * CustomContextPadProvider.$inject = [ 'contextPad' ];
57404 *
57405 * var overrideModule = {
57406 * contextPadProvider: [ 'type', CustomContextPadProvider ]
57407 * };
57408 *
57409 * var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]});
57410 * ```
57411 *
57412 * @param {Object} [options] configuration options to pass to the viewer
57413 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
57414 * @param {string|number} [options.width] the width of the viewer
57415 * @param {string|number} [options.height] the height of the viewer
57416 * @param {Object} [options.moddleExtensions] extension packages to provide
57417 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
57418 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
57419 */
57420 function Modeler(options) {
57421 BaseModeler.call(this, options);
57422 }
57423
57424 inherits_browser(Modeler, BaseModeler);
57425
57426
57427 Modeler.Viewer = Viewer;
57428 Modeler.NavigatedViewer = NavigatedViewer;
57429
57430 /**
57431 * The createDiagram result.
57432 *
57433 * @typedef {Object} CreateDiagramResult
57434 *
57435 * @property {Array<string>} warnings
57436 */
57437
57438 /**
57439 * The createDiagram error.
57440 *
57441 * @typedef {Error} CreateDiagramError
57442 *
57443 * @property {Array<string>} warnings
57444 */
57445
57446 /**
57447 * Create a new diagram to start modeling.
57448 *
57449 * Returns {Promise<CreateDiagramResult, CreateDiagramError>}
57450 */
57451 Modeler.prototype.createDiagram = wrapForCompatibility(function createDiagram() {
57452 return this.importXML(initialDiagram);
57453 });
57454
57455
57456 Modeler.prototype._interactionModules = [
57457
57458 // non-modeling components
57459 KeyboardMoveModule,
57460 MoveCanvasModule,
57461 TouchModule$1,
57462 ZoomScrollModule
57463 ];
57464
57465 Modeler.prototype._modelingModules = [
57466
57467 // modeling components
57468 AlignElementsModule,
57469 AutoPlaceModule$1,
57470 AutoScrollModule,
57471 AutoResizeModule,
57472 BendpointsModule,
57473 ConnectModule,
57474 ConnectionPreviewModule,
57475 ContextPadModule$1,
57476 CopyPasteModule$1,
57477 CreateModule,
57478 DistributeElementsModule$1,
57479 EditorActionsModule$1,
57480 GridSnappingModule$1,
57481 InteractionEventsModule$1,
57482 KeyboardModule$1,
57483 KeyboardMoveSelectionModule,
57484 LabelEditingModule,
57485 ModelingModule,
57486 MoveModule,
57487 PaletteModule$1,
57488 ReplacePreviewModule,
57489 ResizeModule,
57490 SnappingModule$1,
57491 SearchModule
57492 ];
57493
57494
57495 // modules the modeler is composed of
57496 //
57497 // - viewer modules
57498 // - interaction modules
57499 // - modeling modules
57500
57501 Modeler.prototype._modules = [].concat(
57502 Viewer.prototype._modules,
57503 Modeler.prototype._interactionModules,
57504 Modeler.prototype._modelingModules
57505 );
57506
57507 return Modeler;
57508
57509})));