/*!
 * bpmn-js - bpmn-modeler v9.4.0
 *
 * Copyright (c) 2014-present, camunda Services GmbH
 *
 * Released under the bpmn.io license
 * http://bpmn.io/license
 *
 * Source Code: https://github.com/bpmn-io/bpmn-js
 *
 * Date: 2022-08-22
 */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BpmnJS = factory());
})(this, (function () { 'use strict';

  function e(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}));}

  function createCommonjsModule(fn, module) {
  	return module = { exports: {} }, fn(module, module.exports), module.exports;
  }

  var hat_1 = createCommonjsModule(function (module) {
  var hat = module.exports = function (bits, base) {
      if (!base) base = 16;
      if (bits === undefined) bits = 128;
      if (bits <= 0) return '0';
      
      var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
      for (var i = 2; digits === Infinity; i *= 2) {
          digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
      }
      
      var rem = digits - Math.floor(digits);
      
      var res = '';
      
      for (var i = 0; i < Math.floor(digits); i++) {
          var x = Math.floor(Math.random() * base).toString(base);
          res = x + res;
      }
      
      if (rem) {
          var b = Math.pow(base, rem);
          var x = Math.floor(Math.random() * b).toString(base);
          res = x + res;
      }
      
      var parsed = parseInt(res, base);
      if (parsed !== Infinity && parsed >= Math.pow(2, bits)) {
          return hat(bits, base)
      }
      else return res;
  };

  hat.rack = function (bits, base, expandBy) {
      var fn = function (data) {
          var iters = 0;
          do {
              if (iters ++ > 10) {
                  if (expandBy) bits += expandBy;
                  else throw new Error('too many ID collisions, use more bits')
              }
              
              var id = hat(bits, base);
          } while (Object.hasOwnProperty.call(hats, id));
          
          hats[id] = data;
          return id;
      };
      var hats = fn.hats = {};
      
      fn.get = function (id) {
          return fn.hats[id];
      };
      
      fn.set = function (id, value) {
          fn.hats[id] = value;
          return fn;
      };
      
      fn.bits = bits || 128;
      fn.base = base || 16;
      return fn;
  };
  });

  /**
   * Create a new id generator / cache instance.
   *
   * You may optionally provide a seed that is used internally.
   *
   * @param {Seed} seed
   */

  function Ids(seed) {
    if (!(this instanceof Ids)) {
      return new Ids(seed);
    }

    seed = seed || [128, 36, 1];
    this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed;
  }
  /**
   * Generate a next id.
   *
   * @param {Object} [element] element to bind the id to
   *
   * @return {String} id
   */

  Ids.prototype.next = function (element) {
    return this._seed(element || true);
  };
  /**
   * Generate a next id with a given prefix.
   *
   * @param {Object} [element] element to bind the id to
   *
   * @return {String} id
   */


  Ids.prototype.nextPrefixed = function (prefix, element) {
    var id;

    do {
      id = prefix + this.next(true);
    } while (this.assigned(id)); // claim {prefix}{random}


    this.claim(id, element); // return

    return id;
  };
  /**
   * Manually claim an existing id.
   *
   * @param {String} id
   * @param {String} [element] element the id is claimed by
   */


  Ids.prototype.claim = function (id, element) {
    this._seed.set(id, element || true);
  };
  /**
   * Returns true if the given id has already been assigned.
   *
   * @param  {String} id
   * @return {Boolean}
   */


  Ids.prototype.assigned = function (id) {
    return this._seed.get(id) || false;
  };
  /**
   * Unclaim an id.
   *
   * @param  {String} id the id to unclaim
   */


  Ids.prototype.unclaim = function (id) {
    delete this._seed.hats[id];
  };
  /**
   * Clear all claimed ids.
   */


  Ids.prototype.clear = function () {
    var hats = this._seed.hats,
        id;

    for (id in hats) {
      this.unclaim(id);
    }
  };

  /**
   * Flatten array, one level deep.
   *
   * @param {Array<?>} arr
   *
   * @return {Array<?>}
   */
  function flatten(arr) {
    return Array.prototype.concat.apply([], arr);
  }

  var nativeToString$1 = Object.prototype.toString;
  var nativeHasOwnProperty$1 = Object.prototype.hasOwnProperty;
  function isUndefined$2(obj) {
    return obj === undefined;
  }
  function isDefined(obj) {
    return obj !== undefined;
  }
  function isNil(obj) {
    return obj == null;
  }
  function isArray$3(obj) {
    return nativeToString$1.call(obj) === '[object Array]';
  }
  function isObject(obj) {
    return nativeToString$1.call(obj) === '[object Object]';
  }
  function isNumber(obj) {
    return nativeToString$1.call(obj) === '[object Number]';
  }
  function isFunction(obj) {
    var tag = nativeToString$1.call(obj);
    return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
  }
  function isString(obj) {
    return nativeToString$1.call(obj) === '[object String]';
  }
  /**
   * Ensure collection is an array.
   *
   * @param {Object} obj
   */

  function ensureArray(obj) {
    if (isArray$3(obj)) {
      return;
    }

    throw new Error('must supply array');
  }
  /**
   * Return true, if target owns a property with the given key.
   *
   * @param {Object} target
   * @param {String} key
   *
   * @return {Boolean}
   */

  function has$1(target, key) {
    return nativeHasOwnProperty$1.call(target, key);
  }

  /**
   * Find element in collection.
   *
   * @param  {Array|Object} collection
   * @param  {Function|Object} matcher
   *
   * @return {Object}
   */

  function find(collection, matcher) {
    matcher = toMatcher(matcher);
    var match;
    forEach$1(collection, function (val, key) {
      if (matcher(val, key)) {
        match = val;
        return false;
      }
    });
    return match;
  }
  /**
   * Find element index in collection.
   *
   * @param  {Array|Object} collection
   * @param  {Function} matcher
   *
   * @return {Object}
   */

  function findIndex(collection, matcher) {
    matcher = toMatcher(matcher);
    var idx = isArray$3(collection) ? -1 : undefined;
    forEach$1(collection, function (val, key) {
      if (matcher(val, key)) {
        idx = key;
        return false;
      }
    });
    return idx;
  }
  /**
   * Find element in collection.
   *
   * @param  {Array|Object} collection
   * @param  {Function} matcher
   *
   * @return {Array} result
   */

  function filter(collection, matcher) {
    var result = [];
    forEach$1(collection, function (val, key) {
      if (matcher(val, key)) {
        result.push(val);
      }
    });
    return result;
  }
  /**
   * Iterate over collection; returning something
   * (non-undefined) will stop iteration.
   *
   * @param  {Array|Object} collection
   * @param  {Function} iterator
   *
   * @return {Object} return result that stopped the iteration
   */

  function forEach$1(collection, iterator) {
    var val, result;

    if (isUndefined$2(collection)) {
      return;
    }

    var convertKey = isArray$3(collection) ? toNum$1 : identity$1;

    for (var key in collection) {
      if (has$1(collection, key)) {
        val = collection[key];
        result = iterator(val, convertKey(key));

        if (result === false) {
          return val;
        }
      }
    }
  }
  /**
   * Return collection without element.
   *
   * @param  {Array} arr
   * @param  {Function} matcher
   *
   * @return {Array}
   */

  function without(arr, matcher) {
    if (isUndefined$2(arr)) {
      return [];
    }

    ensureArray(arr);
    matcher = toMatcher(matcher);
    return arr.filter(function (el, idx) {
      return !matcher(el, idx);
    });
  }
  /**
   * Reduce collection, returning a single result.
   *
   * @param  {Object|Array} collection
   * @param  {Function} iterator
   * @param  {Any} result
   *
   * @return {Any} result returned from last iterator
   */

  function reduce(collection, iterator, result) {
    forEach$1(collection, function (value, idx) {
      result = iterator(result, value, idx);
    });
    return result;
  }
  /**
   * Return true if every element in the collection
   * matches the criteria.
   *
   * @param  {Object|Array} collection
   * @param  {Function} matcher
   *
   * @return {Boolean}
   */

  function every(collection, matcher) {
    return !!reduce(collection, function (matches, val, key) {
      return matches && matcher(val, key);
    }, true);
  }
  /**
   * Return true if some elements in the collection
   * match the criteria.
   *
   * @param  {Object|Array} collection
   * @param  {Function} matcher
   *
   * @return {Boolean}
   */

  function some(collection, matcher) {
    return !!find(collection, matcher);
  }
  /**
   * Transform a collection into another collection
   * by piping each member through the given fn.
   *
   * @param  {Object|Array}   collection
   * @param  {Function} fn
   *
   * @return {Array} transformed collection
   */

  function map(collection, fn) {
    var result = [];
    forEach$1(collection, function (val, key) {
      result.push(fn(val, key));
    });
    return result;
  }
  /**
   * Get the collections keys.
   *
   * @param  {Object|Array} collection
   *
   * @return {Array}
   */

  function keys(collection) {
    return collection && Object.keys(collection) || [];
  }
  /**
   * Shorthand for `keys(o).length`.
   *
   * @param  {Object|Array} collection
   *
   * @return {Number}
   */

  function size(collection) {
    return keys(collection).length;
  }
  /**
   * Get the values in the collection.
   *
   * @param  {Object|Array} collection
   *
   * @return {Array}
   */

  function values(collection) {
    return map(collection, function (val) {
      return val;
    });
  }
  /**
   * Group collection members by attribute.
   *
   * @param  {Object|Array} collection
   * @param  {Function} extractor
   *
   * @return {Object} map with { attrValue => [ a, b, c ] }
   */

  function groupBy(collection, extractor) {
    var grouped = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    extractor = toExtractor(extractor);
    forEach$1(collection, function (val) {
      var discriminator = extractor(val) || '_';
      var group = grouped[discriminator];

      if (!group) {
        group = grouped[discriminator] = [];
      }

      group.push(val);
    });
    return grouped;
  }
  function uniqueBy(extractor) {
    extractor = toExtractor(extractor);
    var grouped = {};

    for (var _len = arguments.length, collections = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      collections[_key - 1] = arguments[_key];
    }

    forEach$1(collections, function (c) {
      return groupBy(c, extractor, grouped);
    });
    var result = map(grouped, function (val, key) {
      return val[0];
    });
    return result;
  }
  var unionBy = uniqueBy;
  /**
   * Sort collection by criteria.
   *
   * @param  {Object|Array} collection
   * @param  {String|Function} extractor
   *
   * @return {Array}
   */

  function sortBy(collection, extractor) {
    extractor = toExtractor(extractor);
    var sorted = [];
    forEach$1(collection, function (value, key) {
      var disc = extractor(value, key);
      var entry = {
        d: disc,
        v: value
      };

      for (var idx = 0; idx < sorted.length; idx++) {
        var d = sorted[idx].d;

        if (disc < d) {
          sorted.splice(idx, 0, entry);
          return;
        }
      } // not inserted, append (!)


      sorted.push(entry);
    });
    return map(sorted, function (e) {
      return e.v;
    });
  }
  /**
   * Create an object pattern matcher.
   *
   * @example
   *
   * const matcher = matchPattern({ id: 1 });
   *
   * let element = find(elements, matcher);
   *
   * @param  {Object} pattern
   *
   * @return {Function} matcherFn
   */

  function matchPattern(pattern) {
    return function (el) {
      return every(pattern, function (val, key) {
        return el[key] === val;
      });
    };
  }

  function toExtractor(extractor) {
    return isFunction(extractor) ? extractor : function (e) {
      return e[extractor];
    };
  }

  function toMatcher(matcher) {
    return isFunction(matcher) ? matcher : function (e) {
      return e === matcher;
    };
  }

  function identity$1(arg) {
    return arg;
  }

  function toNum$1(arg) {
    return Number(arg);
  }

  /**
   * Debounce fn, calling it only once if the given time
   * elapsed between calls.
   *
   * Lodash-style the function exposes methods to `#clear`
   * and `#flush` to control internal behavior.
   *
   * @param  {Function} fn
   * @param  {Number} timeout
   *
   * @return {Function} debounced function
   */
  function debounce(fn, timeout) {
    var timer;
    var lastArgs;
    var lastThis;
    var lastNow;

    function fire(force) {
      var now = Date.now();
      var scheduledDiff = force ? 0 : lastNow + timeout - now;

      if (scheduledDiff > 0) {
        return schedule(scheduledDiff);
      }

      fn.apply(lastThis, lastArgs);
      clear();
    }

    function schedule(timeout) {
      timer = setTimeout(fire, timeout);
    }

    function clear() {
      if (timer) {
        clearTimeout(timer);
      }

      timer = lastNow = lastArgs = lastThis = undefined;
    }

    function flush() {
      if (timer) {
        fire(true);
      }

      clear();
    }

    function callback() {
      lastNow = Date.now();

      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      lastArgs = args;
      lastThis = this; // ensure an execution is scheduled

      if (!timer) {
        schedule(timeout);
      }
    }

    callback.flush = flush;
    callback.cancel = clear;
    return callback;
  }
  /**
   * Bind function against target <this>.
   *
   * @param  {Function} fn
   * @param  {Object}   target
   *
   * @return {Function} bound function
   */

  function bind(fn, target) {
    return fn.bind(target);
  }

  function _extends() {
    _extends = Object.assign || function (target) {
      for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];

        for (var key in source) {
          if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
          }
        }
      }

      return target;
    };

    return _extends.apply(this, arguments);
  }

  /**
   * Convenience wrapper for `Object.assign`.
   *
   * @param {Object} target
   * @param {...Object} others
   *
   * @return {Object} the target
   */

  function assign(target) {
    for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      others[_key - 1] = arguments[_key];
    }

    return _extends.apply(void 0, [target].concat(others));
  }
  /**
   * Pick given properties from the target object.
   *
   * @param {Object} target
   * @param {Array} properties
   *
   * @return {Object} target
   */

  function pick(target, properties) {
    var result = {};
    var obj = Object(target);
    forEach$1(properties, function (prop) {
      if (prop in obj) {
        result[prop] = target[prop];
      }
    });
    return result;
  }
  /**
   * Pick all target properties, excluding the given ones.
   *
   * @param {Object} target
   * @param {Array} properties
   *
   * @return {Object} target
   */

  function omit(target, properties) {
    var result = {};
    var obj = Object(target);
    forEach$1(obj, function (prop, key) {
      if (properties.indexOf(key) === -1) {
        result[key] = prop;
      }
    });
    return result;
  }

  /**
   * Flatten array, one level deep.
   *
   * @param {Array<?>} arr
   *
   * @return {Array<?>}
   */

  var nativeToString = Object.prototype.toString;
  var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
  function isUndefined$1(obj) {
    return obj === undefined;
  }
  function isArray$2(obj) {
    return nativeToString.call(obj) === '[object Array]';
  }
  /**
   * Return true, if target owns a property with the given key.
   *
   * @param {Object} target
   * @param {String} key
   *
   * @return {Boolean}
   */

  function has(target, key) {
    return nativeHasOwnProperty.call(target, key);
  }
  /**
   * Iterate over collection; returning something
   * (non-undefined) will stop iteration.
   *
   * @param  {Array|Object} collection
   * @param  {Function} iterator
   *
   * @return {Object} return result that stopped the iteration
   */

  function forEach(collection, iterator) {
    var val, result;

    if (isUndefined$1(collection)) {
      return;
    }

    var convertKey = isArray$2(collection) ? toNum : identity;

    for (var key in collection) {
      if (has(collection, key)) {
        val = collection[key];
        result = iterator(val, convertKey(key));

        if (result === false) {
          return val;
        }
      }
    }
  }

  function identity(arg) {
    return arg;
  }

  function toNum(arg) {
    return Number(arg);
  }

  /**
   * Assigns style attributes in a style-src compliant way.
   *
   * @param {Element} element
   * @param {...Object} styleSources
   *
   * @return {Element} the element
   */
  function assign$1(element) {
    var target = element.style;

    for (var _len = arguments.length, styleSources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      styleSources[_key - 1] = arguments[_key];
    }

    forEach(styleSources, function (style) {
      if (!style) {
        return;
      }

      forEach(style, function (value, key) {
        target[key] = value;
      });
    });

    return element;
  }

  /**
   * Set attribute `name` to `val`, or get attr `name`.
   *
   * @param {Element} el
   * @param {String} name
   * @param {String} [val]
   * @api public
   */
  function attr$1(el, name, val) {
    // get
    if (arguments.length == 2) {
      return el.getAttribute(name);
    }

    // remove
    if (val === null) {
      return el.removeAttribute(name);
    }

    // set
    el.setAttribute(name, val);

    return el;
  }

  var indexOf$1 = [].indexOf;

  var indexof = function(arr, obj){
    if (indexOf$1) return arr.indexOf(obj);
    for (var i = 0; i < arr.length; ++i) {
      if (arr[i] === obj) return i;
    }
    return -1;
  };

  /**
   * Taken from https://github.com/component/classes
   *
   * Without the component bits.
   */

  /**
   * Whitespace regexp.
   */

  var re$1 = /\s+/;

  /**
   * toString reference.
   */

  var toString$1 = Object.prototype.toString;

  /**
   * Wrap `el` in a `ClassList`.
   *
   * @param {Element} el
   * @return {ClassList}
   * @api public
   */

  function classes$1(el) {
    return new ClassList$1(el);
  }

  /**
   * Initialize a new ClassList for `el`.
   *
   * @param {Element} el
   * @api private
   */

  function ClassList$1(el) {
    if (!el || !el.nodeType) {
      throw new Error('A DOM element reference is required');
    }
    this.el = el;
    this.list = el.classList;
  }

  /**
   * Add class `name` if not already present.
   *
   * @param {String} name
   * @return {ClassList}
   * @api public
   */

  ClassList$1.prototype.add = function (name) {
    // classList
    if (this.list) {
      this.list.add(name);
      return this;
    }

    // fallback
    var arr = this.array();
    var i = indexof(arr, name);
    if (!~i) arr.push(name);
    this.el.className = arr.join(' ');
    return this;
  };

  /**
   * Remove class `name` when present, or
   * pass a regular expression to remove
   * any which match.
   *
   * @param {String|RegExp} name
   * @return {ClassList}
   * @api public
   */

  ClassList$1.prototype.remove = function (name) {
    if ('[object RegExp]' == toString$1.call(name)) {
      return this.removeMatching(name);
    }

    // classList
    if (this.list) {
      this.list.remove(name);
      return this;
    }

    // fallback
    var arr = this.array();
    var i = indexof(arr, name);
    if (~i) arr.splice(i, 1);
    this.el.className = arr.join(' ');
    return this;
  };

  /**
   * Remove all classes matching `re`.
   *
   * @param {RegExp} re
   * @return {ClassList}
   * @api private
   */

  ClassList$1.prototype.removeMatching = function (re) {
    var arr = this.array();
    for (var i = 0; i < arr.length; i++) {
      if (re.test(arr[i])) {
        this.remove(arr[i]);
      }
    }
    return this;
  };

  /**
   * Toggle class `name`, can force state via `force`.
   *
   * For browsers that support classList, but do not support `force` yet,
   * the mistake will be detected and corrected.
   *
   * @param {String} name
   * @param {Boolean} force
   * @return {ClassList}
   * @api public
   */

  ClassList$1.prototype.toggle = function (name, force) {
    // classList
    if (this.list) {
      if ('undefined' !== typeof force) {
        if (force !== this.list.toggle(name, force)) {
          this.list.toggle(name); // toggle again to correct
        }
      } else {
        this.list.toggle(name);
      }
      return this;
    }

    // fallback
    if ('undefined' !== typeof force) {
      if (!force) {
        this.remove(name);
      } else {
        this.add(name);
      }
    } else {
      if (this.has(name)) {
        this.remove(name);
      } else {
        this.add(name);
      }
    }

    return this;
  };

  /**
   * Return an array of classes.
   *
   * @return {Array}
   * @api public
   */

  ClassList$1.prototype.array = function () {
    var className = this.el.getAttribute('class') || '';
    var str = className.replace(/^\s+|\s+$/g, '');
    var arr = str.split(re$1);
    if ('' === arr[0]) arr.shift();
    return arr;
  };

  /**
   * Check if class `name` is present.
   *
   * @param {String} name
   * @return {ClassList}
   * @api public
   */

  ClassList$1.prototype.has = ClassList$1.prototype.contains = function (name) {
    return this.list ? this.list.contains(name) : !!~indexof(this.array(), name);
  };

  /**
   * Remove all children from the given element.
   */
  function clear$1(el) {

    var c;

    while (el.childNodes.length) {
      c = el.childNodes[0];
      el.removeChild(c);
    }

    return el;
  }

  var proto = typeof Element !== 'undefined' ? Element.prototype : {};
  var vendor = proto.matches
    || proto.matchesSelector
    || proto.webkitMatchesSelector
    || proto.mozMatchesSelector
    || proto.msMatchesSelector
    || proto.oMatchesSelector;

  var matchesSelector = match;

  /**
   * Match `el` to `selector`.
   *
   * @param {Element} el
   * @param {String} selector
   * @return {Boolean}
   * @api public
   */

  function match(el, selector) {
    if (!el || el.nodeType !== 1) return false;
    if (vendor) return vendor.call(el, selector);
    var nodes = el.parentNode.querySelectorAll(selector);
    for (var i = 0; i < nodes.length; i++) {
      if (nodes[i] == el) return true;
    }
    return false;
  }

  /**
   * Closest
   *
   * @param {Element} el
   * @param {String} selector
   * @param {Boolean} checkYourSelf (optional)
   */
  function closest (element, selector, checkYourSelf) {
    var currentElem = checkYourSelf ? element : element.parentNode;

    while (currentElem && currentElem.nodeType !== document.DOCUMENT_NODE && currentElem.nodeType !== document.DOCUMENT_FRAGMENT_NODE) {

      if (matchesSelector(currentElem, selector)) {
        return currentElem;
      }

      currentElem = currentElem.parentNode;
    }

    return matchesSelector(currentElem, selector) ? currentElem : null;
  }

  var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent',
      unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
      prefix$6 = bind$1 !== 'addEventListener' ? 'on' : '';

  /**
   * Bind `el` event `type` to `fn`.
   *
   * @param {Element} el
   * @param {String} type
   * @param {Function} fn
   * @param {Boolean} capture
   * @return {Function}
   * @api public
   */

  var bind_1 = function(el, type, fn, capture){
    el[bind$1](prefix$6 + type, fn, capture || false);
    return fn;
  };

  /**
   * Unbind `el` event `type`'s callback `fn`.
   *
   * @param {Element} el
   * @param {String} type
   * @param {Function} fn
   * @param {Boolean} capture
   * @return {Function}
   * @api public
   */

  var unbind_1 = function(el, type, fn, capture){
    el[unbind](prefix$6 + type, fn, capture || false);
    return fn;
  };

  var componentEvent = {
  	bind: bind_1,
  	unbind: unbind_1
  };

  /**
   * Module dependencies.
   */

  /**
   * Delegate event `type` to `selector`
   * and invoke `fn(e)`. A callback function
   * is returned which may be passed to `.unbind()`.
   *
   * @param {Element} el
   * @param {String} selector
   * @param {String} type
   * @param {Function} fn
   * @param {Boolean} capture
   * @return {Function}
   * @api public
   */

  // Some events don't bubble, so we want to bind to the capture phase instead
  // when delegating.
  var forceCaptureEvents = ['focus', 'blur'];

  function bind$2(el, selector, type, fn, capture) {
    if (forceCaptureEvents.indexOf(type) !== -1) {
      capture = true;
    }

    return componentEvent.bind(el, type, function (e) {
      var target = e.target || e.srcElement;
      e.delegateTarget = closest(target, selector, true);
      if (e.delegateTarget) {
        fn.call(el, e);
      }
    }, capture);
  }

  /**
   * Unbind event `type`'s callback `fn`.
   *
   * @param {Element} el
   * @param {String} type
   * @param {Function} fn
   * @param {Boolean} capture
   * @api public
   */
  function unbind$1(el, type, fn, capture) {
    if (forceCaptureEvents.indexOf(type) !== -1) {
      capture = true;
    }

    return componentEvent.unbind(el, type, fn, capture);
  }

  var delegate = {
    bind: bind$2,
    unbind: unbind$1
  };

  /**
   * Expose `parse`.
   */

  var domify = parse$1;

  /**
   * Tests for browser support.
   */

  var innerHTMLBug = false;
  var bugTestDiv;
  if (typeof document !== 'undefined') {
    bugTestDiv = document.createElement('div');
    // Setup
    bugTestDiv.innerHTML = '  <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
    // Make sure that link elements get serialized correctly by innerHTML
    // This requires a wrapper element in IE
    innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length;
    bugTestDiv = undefined;
  }

  /**
   * Wrap map from jquery.
   */

  var map$1 = {
    legend: [1, '<fieldset>', '</fieldset>'],
    tr: [2, '<table><tbody>', '</tbody></table>'],
    col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
    // for script/link/style tags to work in IE6-8, you have to wrap
    // in a div with a non-whitespace character in front, ha!
    _default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
  };

  map$1.td =
  map$1.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];

  map$1.option =
  map$1.optgroup = [1, '<select multiple="multiple">', '</select>'];

  map$1.thead =
  map$1.tbody =
  map$1.colgroup =
  map$1.caption =
  map$1.tfoot = [1, '<table>', '</table>'];

  map$1.polyline =
  map$1.ellipse =
  map$1.polygon =
  map$1.circle =
  map$1.text =
  map$1.line =
  map$1.path =
  map$1.rect =
  map$1.g = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];

  /**
   * Parse `html` and return a DOM Node instance, which could be a TextNode,
   * HTML DOM Node of some kind (<div> for example), or a DocumentFragment
   * instance, depending on the contents of the `html` string.
   *
   * @param {String} html - HTML string to "domify"
   * @param {Document} doc - The `document` instance to create the Node for
   * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
   * @api private
   */

  function parse$1(html, doc) {
    if ('string' != typeof html) throw new TypeError('String expected');

    // default to the global `document` object
    if (!doc) doc = document;

    // tag name
    var m = /<([\w:]+)/.exec(html);
    if (!m) return doc.createTextNode(html);

    html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace

    var tag = m[1];

    // body support
    if (tag == 'body') {
      var el = doc.createElement('html');
      el.innerHTML = html;
      return el.removeChild(el.lastChild);
    }

    // wrap map
    var wrap = map$1[tag] || map$1._default;
    var depth = wrap[0];
    var prefix = wrap[1];
    var suffix = wrap[2];
    var el = doc.createElement('div');
    el.innerHTML = prefix + html + suffix;
    while (depth--) el = el.lastChild;

    // one element
    if (el.firstChild == el.lastChild) {
      return el.removeChild(el.firstChild);
    }

    // several elements
    var fragment = doc.createDocumentFragment();
    while (el.firstChild) {
      fragment.appendChild(el.removeChild(el.firstChild));
    }

    return fragment;
  }

  function query(selector, el) {
    el = el || document;

    return el.querySelector(selector);
  }

  function all(selector, el) {
    el = el || document;

    return el.querySelectorAll(selector);
  }

  function remove$2(el) {
    el.parentNode && el.parentNode.removeChild(el);
  }

  function ensureImported(element, target) {

    if (element.ownerDocument !== target.ownerDocument) {
      try {
        // may fail on webkit
        return target.ownerDocument.importNode(element, true);
      } catch (e) {
        // ignore
      }
    }

    return element;
  }

  /**
   * appendTo utility
   */

  /**
   * Append a node to a target element and return the appended node.
   *
   * @param  {SVGElement} element
   * @param  {SVGElement} target
   *
   * @return {SVGElement} the appended node
   */
  function appendTo(element, target) {
    return target.appendChild(ensureImported(element, target));
  }

  /**
   * append utility
   */

  /**
   * Append a node to an element
   *
   * @param  {SVGElement} element
   * @param  {SVGElement} node
   *
   * @return {SVGElement} the element
   */
  function append(target, node) {
    appendTo(node, target);
    return target;
  }

  /**
   * attribute accessor utility
   */

  var LENGTH_ATTR = 2;

  var CSS_PROPERTIES = {
    'alignment-baseline': 1,
    'baseline-shift': 1,
    'clip': 1,
    'clip-path': 1,
    'clip-rule': 1,
    'color': 1,
    'color-interpolation': 1,
    'color-interpolation-filters': 1,
    'color-profile': 1,
    'color-rendering': 1,
    'cursor': 1,
    'direction': 1,
    'display': 1,
    'dominant-baseline': 1,
    'enable-background': 1,
    'fill': 1,
    'fill-opacity': 1,
    'fill-rule': 1,
    'filter': 1,
    'flood-color': 1,
    'flood-opacity': 1,
    'font': 1,
    'font-family': 1,
    'font-size': LENGTH_ATTR,
    'font-size-adjust': 1,
    'font-stretch': 1,
    'font-style': 1,
    'font-variant': 1,
    'font-weight': 1,
    'glyph-orientation-horizontal': 1,
    'glyph-orientation-vertical': 1,
    'image-rendering': 1,
    'kerning': 1,
    'letter-spacing': 1,
    'lighting-color': 1,
    'marker': 1,
    'marker-end': 1,
    'marker-mid': 1,
    'marker-start': 1,
    'mask': 1,
    'opacity': 1,
    'overflow': 1,
    'pointer-events': 1,
    'shape-rendering': 1,
    'stop-color': 1,
    'stop-opacity': 1,
    'stroke': 1,
    'stroke-dasharray': 1,
    'stroke-dashoffset': 1,
    'stroke-linecap': 1,
    'stroke-linejoin': 1,
    'stroke-miterlimit': 1,
    'stroke-opacity': 1,
    'stroke-width': LENGTH_ATTR,
    'text-anchor': 1,
    'text-decoration': 1,
    'text-rendering': 1,
    'unicode-bidi': 1,
    'visibility': 1,
    'word-spacing': 1,
    'writing-mode': 1
  };


  function getAttribute(node, name) {
    if (CSS_PROPERTIES[name]) {
      return node.style[name];
    } else {
      return node.getAttributeNS(null, name);
    }
  }

  function setAttribute(node, name, value) {
    var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

    var type = CSS_PROPERTIES[hyphenated];

    if (type) {
      // append pixel unit, unless present
      if (type === LENGTH_ATTR && typeof value === 'number') {
        value = String(value) + 'px';
      }

      node.style[hyphenated] = value;
    } else {
      node.setAttributeNS(null, name, value);
    }
  }

  function setAttributes(node, attrs) {

    var names = Object.keys(attrs), i, name;

    for (i = 0, name; (name = names[i]); i++) {
      setAttribute(node, name, attrs[name]);
    }
  }

  /**
   * Gets or sets raw attributes on a node.
   *
   * @param  {SVGElement} node
   * @param  {Object} [attrs]
   * @param  {String} [name]
   * @param  {String} [value]
   *
   * @return {String}
   */
  function attr(node, name, value) {
    if (typeof name === 'string') {
      if (value !== undefined) {
        setAttribute(node, name, value);
      } else {
        return getAttribute(node, name);
      }
    } else {
      setAttributes(node, name);
    }

    return node;
  }

  /**
   * Clear utility
   */
  function index(arr, obj) {
    if (arr.indexOf) {
      return arr.indexOf(obj);
    }


    for (var i = 0; i < arr.length; ++i) {
      if (arr[i] === obj) {
        return i;
      }
    }

    return -1;
  }

  var re = /\s+/;

  var toString = Object.prototype.toString;

  function defined(o) {
    return typeof o !== 'undefined';
  }

  /**
   * Wrap `el` in a `ClassList`.
   *
   * @param {Element} el
   * @return {ClassList}
   * @api public
   */

  function classes(el) {
    return new ClassList(el);
  }

  function ClassList(el) {
    if (!el || !el.nodeType) {
      throw new Error('A DOM element reference is required');
    }
    this.el = el;
    this.list = el.classList;
  }

  /**
   * Add class `name` if not already present.
   *
   * @param {String} name
   * @return {ClassList}
   * @api public
   */

  ClassList.prototype.add = function(name) {

    // classList
    if (this.list) {
      this.list.add(name);
      return this;
    }

    // fallback
    var arr = this.array();
    var i = index(arr, name);
    if (!~i) {
      arr.push(name);
    }

    if (defined(this.el.className.baseVal)) {
      this.el.className.baseVal = arr.join(' ');
    } else {
      this.el.className = arr.join(' ');
    }

    return this;
  };

  /**
   * Remove class `name` when present, or
   * pass a regular expression to remove
   * any which match.
   *
   * @param {String|RegExp} name
   * @return {ClassList}
   * @api public
   */

  ClassList.prototype.remove = function(name) {
    if ('[object RegExp]' === toString.call(name)) {
      return this.removeMatching(name);
    }

    // classList
    if (this.list) {
      this.list.remove(name);
      return this;
    }

    // fallback
    var arr = this.array();
    var i = index(arr, name);
    if (~i) {
      arr.splice(i, 1);
    }
    this.el.className.baseVal = arr.join(' ');
    return this;
  };

  /**
   * Remove all classes matching `re`.
   *
   * @param {RegExp} re
   * @return {ClassList}
   * @api private
   */

  ClassList.prototype.removeMatching = function(re) {
    var arr = this.array();
    for (var i = 0; i < arr.length; i++) {
      if (re.test(arr[i])) {
        this.remove(arr[i]);
      }
    }
    return this;
  };

  /**
   * Toggle class `name`, can force state via `force`.
   *
   * For browsers that support classList, but do not support `force` yet,
   * the mistake will be detected and corrected.
   *
   * @param {String} name
   * @param {Boolean} force
   * @return {ClassList}
   * @api public
   */

  ClassList.prototype.toggle = function(name, force) {
    // classList
    if (this.list) {
      if (defined(force)) {
        if (force !== this.list.toggle(name, force)) {
          this.list.toggle(name); // toggle again to correct
        }
      } else {
        this.list.toggle(name);
      }
      return this;
    }

    // fallback
    if (defined(force)) {
      if (!force) {
        this.remove(name);
      } else {
        this.add(name);
      }
    } else {
      if (this.has(name)) {
        this.remove(name);
      } else {
        this.add(name);
      }
    }

    return this;
  };

  /**
   * Return an array of classes.
   *
   * @return {Array}
   * @api public
   */

  ClassList.prototype.array = function() {
    var className = this.el.getAttribute('class') || '';
    var str = className.replace(/^\s+|\s+$/g, '');
    var arr = str.split(re);
    if ('' === arr[0]) {
      arr.shift();
    }
    return arr;
  };

  /**
   * Check if class `name` is present.
   *
   * @param {String} name
   * @return {ClassList}
   * @api public
   */

  ClassList.prototype.has =
  ClassList.prototype.contains = function(name) {
    return (
      this.list ?
        this.list.contains(name) :
        !! ~index(this.array(), name)
    );
  };

  function remove$1(element) {
    var parent = element.parentNode;

    if (parent) {
      parent.removeChild(element);
    }

    return element;
  }

  /**
   * Clear utility
   */

  /**
   * Removes all children from the given element
   *
   * @param  {DOMElement} element
   * @return {DOMElement} the element (for chaining)
   */
  function clear(element) {
    var child;

    while ((child = element.firstChild)) {
      remove$1(child);
    }

    return element;
  }

  function clone$1(element) {
    return element.cloneNode(true);
  }

  var ns = {
    svg: 'http://www.w3.org/2000/svg'
  };

  /**
   * DOM parsing utility
   */

  var SVG_START = '<svg xmlns="' + ns.svg + '"';

  function parse(svg) {

    var unwrap = false;

    // ensure we import a valid svg document
    if (svg.substring(0, 4) === '<svg') {
      if (svg.indexOf(ns.svg) === -1) {
        svg = SVG_START + svg.substring(4);
      }
    } else {
      // namespace svg
      svg = SVG_START + '>' + svg + '</svg>';
      unwrap = true;
    }

    var parsed = parseDocument(svg);

    if (!unwrap) {
      return parsed;
    }

    var fragment = document.createDocumentFragment();

    var parent = parsed.firstChild;

    while (parent.firstChild) {
      fragment.appendChild(parent.firstChild);
    }

    return fragment;
  }

  function parseDocument(svg) {

    var parser;

    // parse
    parser = new DOMParser();
    parser.async = false;

    return parser.parseFromString(svg, 'text/xml');
  }

  /**
   * Create utility for SVG elements
   */


  /**
   * Create a specific type from name or SVG markup.
   *
   * @param {String} name the name or markup of the element
   * @param {Object} [attrs] attributes to set on the element
   *
   * @returns {SVGElement}
   */
  function create$1(name, attrs) {
    var element;

    if (name.charAt(0) === '<') {
      element = parse(name).firstChild;
      element = document.importNode(element, true);
    } else {
      element = document.createElementNS(ns.svg, name);
    }

    if (attrs) {
      attr(element, attrs);
    }

    return element;
  }

  /**
   * Geometry helpers
   */

  // fake node used to instantiate svg geometry elements
  var node = null;

  function getNode() {
    if (node === null) {
      node = create$1('svg');
    }

    return node;
  }

  function extend$1(object, props) {
    var i, k, keys = Object.keys(props);

    for (i = 0; (k = keys[i]); i++) {
      object[k] = props[k];
    }

    return object;
  }

  /**
   * Create matrix via args.
   *
   * @example
   *
   * createMatrix({ a: 1, b: 1 });
   * createMatrix();
   * createMatrix(1, 2, 0, 0, 30, 20);
   *
   * @return {SVGMatrix}
   */
  function createMatrix(a, b, c, d, e, f) {
    var matrix = getNode().createSVGMatrix();

    switch (arguments.length) {
    case 0:
      return matrix;
    case 1:
      return extend$1(matrix, a);
    case 6:
      return extend$1(matrix, {
        a: a,
        b: b,
        c: c,
        d: d,
        e: e,
        f: f
      });
    }
  }

  function createTransform(matrix) {
    if (matrix) {
      return getNode().createSVGTransformFromMatrix(matrix);
    } else {
      return getNode().createSVGTransform();
    }
  }

  /**
   * Serialization util
   */

  var TEXT_ENTITIES = /([&<>]{1})/g;
  var ATTR_ENTITIES = /([\n\r"]{1})/g;

  var ENTITY_REPLACEMENT = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '\''
  };

  function escape$1(str, pattern) {

    function replaceFn(match, entity) {
      return ENTITY_REPLACEMENT[entity] || entity;
    }

    return str.replace(pattern, replaceFn);
  }

  function serialize(node, output) {

    var i, len, attrMap, attrNode, childNodes;

    switch (node.nodeType) {
    // TEXT
    case 3:
      // replace special XML characters
      output.push(escape$1(node.textContent, TEXT_ENTITIES));
      break;

    // ELEMENT
    case 1:
      output.push('<', node.tagName);

      if (node.hasAttributes()) {
        attrMap = node.attributes;
        for (i = 0, len = attrMap.length; i < len; ++i) {
          attrNode = attrMap.item(i);
          output.push(' ', attrNode.name, '="', escape$1(attrNode.value, ATTR_ENTITIES), '"');
        }
      }

      if (node.hasChildNodes()) {
        output.push('>');
        childNodes = node.childNodes;
        for (i = 0, len = childNodes.length; i < len; ++i) {
          serialize(childNodes.item(i), output);
        }
        output.push('</', node.tagName, '>');
      } else {
        output.push('/>');
      }
      break;

    // COMMENT
    case 8:
      output.push('<!--', escape$1(node.nodeValue, TEXT_ENTITIES), '-->');
      break;

    // CDATA
    case 4:
      output.push('<![CDATA[', node.nodeValue, ']]>');
      break;

    default:
      throw new Error('unable to handle node ' + node.nodeType);
    }

    return output;
  }

  /**
   * innerHTML like functionality for SVG elements.
   * based on innerSVG (https://code.google.com/p/innersvg)
   */


  function set$1(element, svg) {

    var parsed = parse(svg);

    // clear element contents
    clear(element);

    if (!svg) {
      return;
    }

    if (!isFragment(parsed)) {
      // extract <svg> from parsed document
      parsed = parsed.documentElement;
    }

    var nodes = slice$1(parsed.childNodes);

    // import + append each node
    for (var i = 0; i < nodes.length; i++) {
      appendTo(nodes[i], element);
    }

  }

  function get$1(element) {
    var child = element.firstChild,
        output = [];

    while (child) {
      serialize(child, output);
      child = child.nextSibling;
    }

    return output.join('');
  }

  function isFragment(node) {
    return node.nodeName === '#document-fragment';
  }

  function innerSVG(element, svg) {

    if (svg !== undefined) {

      try {
        set$1(element, svg);
      } catch (e) {
        throw new Error('error parsing SVG: ' + e.message);
      }

      return element;
    } else {
      return get$1(element);
    }
  }


  function slice$1(arr) {
    return Array.prototype.slice.call(arr);
  }

  /**
   * transform accessor utility
   */

  function wrapMatrix(transformList, transform) {
    if (transform instanceof SVGMatrix) {
      return transformList.createSVGTransformFromMatrix(transform);
    }

    return transform;
  }


  function setTransforms(transformList, transforms) {
    var i, t;

    transformList.clear();

    for (i = 0; (t = transforms[i]); i++) {
      transformList.appendItem(wrapMatrix(transformList, t));
    }
  }

  /**
   * Get or set the transforms on the given node.
   *
   * @param {SVGElement} node
   * @param  {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms]
   *
   * @return {SVGTransform} the consolidated transform
   */
  function transform$1(node, transforms) {
    var transformList = node.transform.baseVal;

    if (transforms) {

      if (!Array.isArray(transforms)) {
        transforms = [ transforms ];
      }

      setTransforms(transformList, transforms);
    }

    return transformList.consolidate();
  }

  var CLASS_PATTERN = /^class /;


  /**
   * @param {function} fn
   *
   * @return {boolean}
   */
  function isClass(fn) {
    return CLASS_PATTERN.test(fn.toString());
  }

  /**
   * @param {any} obj
   *
   * @return {boolean}
   */
  function isArray$1(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  }

  /**
   * @param {any} obj
   * @param {string} prop
   *
   * @return {boolean}
   */
  function hasOwnProp(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
  }

  /**
   * @typedef {import('./index').InjectAnnotated } InjectAnnotated
   */

  /**
   * @template T
   *
   * @params {[...string[], T] | ...string[], T} args
   *
   * @return {T & InjectAnnotated}
   */
  function annotate() {
    var args = Array.prototype.slice.call(arguments);

    if (args.length === 1 && isArray$1(args[0])) {
      args = args[0];
    }

    var fn = args.pop();

    fn.$inject = args;

    return fn;
  }


  // Current limitations:
  // - can't put into "function arg" comments
  // function /* (no parenthesis like this) */ (){}
  // function abc( /* xx (no parenthesis like this) */ a, b) {}
  //
  // Just put the comment before function or inside:
  // /* (((this is fine))) */ function(a, b) {}
  // function abc(a) { /* (((this is fine))) */}
  //
  // - can't reliably auto-annotate constructor; we'll match the
  // first constructor(...) pattern found which may be the one
  // of a nested class, too.

  var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m;
  var FN_ARGS = /^(?:async\s+)?(?:function\s*[^(]*)?(?:\(\s*([^)]*)\)|(\w+))/m;
  var FN_ARG = /\/\*([^*]*)\*\//m;

  /**
   * @param {unknown} fn
   *
   * @return {string[]}
   */
  function parseAnnotations(fn) {

    if (typeof fn !== 'function') {
      throw new Error('Cannot annotate "' + fn + '". Expected a function!');
    }

    var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS);

    // may parse class without constructor
    if (!match) {
      return [];
    }

    var args = match[1] || match[2];

    return args && args.split(',').map(function(arg) {
      var argMatch = arg.match(FN_ARG);
      return (argMatch && argMatch[1] || arg).trim();
    }) || [];
  }

  /**
   * @typedef { import('./index').ModuleDeclaration } ModuleDeclaration
   * @typedef { import('./index').ModuleDefinition } ModuleDefinition
   * @typedef { import('./index').InjectorContext } InjectorContext
   */

  /**
   * Create a new injector with the given modules.
   *
   * @param {ModuleDefinition[]} modules
   * @param {InjectorContext} [parent]
   */
  function Injector(modules, parent) {
    parent = parent || {
      get: function(name, strict) {
        currentlyResolving.push(name);

        if (strict === false) {
          return null;
        } else {
          throw error('No provider for "' + name + '"!');
        }
      }
    };

    var currentlyResolving = [];
    var providers = this._providers = Object.create(parent._providers || null);
    var instances = this._instances = Object.create(null);

    var self = instances.injector = this;

    var error = function(msg) {
      var stack = currentlyResolving.join(' -> ');
      currentlyResolving.length = 0;
      return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg);
    };

    /**
     * Return a named service.
     *
     * @param {string} name
     * @param {boolean} [strict=true] if false, resolve missing services to null
     *
     * @return {any}
     */
    function get(name, strict) {
      if (!providers[name] && name.indexOf('.') !== -1) {
        var parts = name.split('.');
        var pivot = get(parts.shift());

        while (parts.length) {
          pivot = pivot[parts.shift()];
        }

        return pivot;
      }

      if (hasOwnProp(instances, name)) {
        return instances[name];
      }

      if (hasOwnProp(providers, name)) {
        if (currentlyResolving.indexOf(name) !== -1) {
          currentlyResolving.push(name);
          throw error('Cannot resolve circular dependency!');
        }

        currentlyResolving.push(name);
        instances[name] = providers[name][0](providers[name][1]);
        currentlyResolving.pop();

        return instances[name];
      }

      return parent.get(name, strict);
    }

    function fnDef(fn, locals) {

      if (typeof locals === 'undefined') {
        locals = {};
      }

      if (typeof fn !== 'function') {
        if (isArray$1(fn)) {
          fn = annotate(fn.slice());
        } else {
          throw new Error('Cannot invoke "' + fn + '". Expected a function!');
        }
      }

      var inject = fn.$inject || parseAnnotations(fn);
      var dependencies = inject.map(function(dep) {
        if (hasOwnProp(locals, dep)) {
          return locals[dep];
        } else {
          return get(dep);
        }
      });

      return {
        fn: fn,
        dependencies: dependencies
      };
    }

    function instantiate(Type) {
      var def = fnDef(Type);

      var fn = def.fn,
          dependencies = def.dependencies;

      // instantiate var args constructor
      var Constructor = Function.prototype.bind.apply(fn, [ null ].concat(dependencies));

      return new Constructor();
    }

    function invoke(func, context, locals) {
      var def = fnDef(func, locals);

      var fn = def.fn,
          dependencies = def.dependencies;

      return fn.apply(context, dependencies);
    }

    /**
     * @param {Injector} childInjector
     *
     * @return {Function}
     */
    function createPrivateInjectorFactory(childInjector) {
      return annotate(function(key) {
        return childInjector.get(key);
      });
    }

    /**
     * @param {ModuleDefinition[]} modules
     * @param {string[]} [forceNewInstances]
     *
     * @return {Injector}
     */
    function createChild(modules, forceNewInstances) {
      if (forceNewInstances && forceNewInstances.length) {
        var fromParentModule = Object.create(null);
        var matchedScopes = Object.create(null);

        var privateInjectorsCache = [];
        var privateChildInjectors = [];
        var privateChildFactories = [];

        var provider;
        var cacheIdx;
        var privateChildInjector;
        var privateChildInjectorFactory;
        for (var name in providers) {
          provider = providers[name];

          if (forceNewInstances.indexOf(name) !== -1) {
            if (provider[2] === 'private') {
              cacheIdx = privateInjectorsCache.indexOf(provider[3]);
              if (cacheIdx === -1) {
                privateChildInjector = provider[3].createChild([], forceNewInstances);
                privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector);
                privateInjectorsCache.push(provider[3]);
                privateChildInjectors.push(privateChildInjector);
                privateChildFactories.push(privateChildInjectorFactory);
                fromParentModule[name] = [ privateChildInjectorFactory, name, 'private', privateChildInjector ];
              } else {
                fromParentModule[name] = [ privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx] ];
              }
            } else {
              fromParentModule[name] = [ provider[2], provider[1] ];
            }
            matchedScopes[name] = true;
          }

          if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) {
            /* jshint -W083 */
            forceNewInstances.forEach(function(scope) {
              if (provider[1].$scope.indexOf(scope) !== -1) {
                fromParentModule[name] = [ provider[2], provider[1] ];
                matchedScopes[scope] = true;
              }
            });
          }
        }

        forceNewInstances.forEach(function(scope) {
          if (!matchedScopes[scope]) {
            throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!');
          }
        });

        modules.unshift(fromParentModule);
      }

      return new Injector(modules, self);
    }

    var factoryMap = {
      factory: invoke,
      type: instantiate,
      value: function(value) {
        return value;
      }
    };

    /**
     * @param {ModuleDefinition} moduleDefinition
     * @param {Injector} injector
     */
    function createInitializer(moduleDefinition, injector) {

      var initializers = moduleDefinition.__init__ || [];

      return function() {
        initializers.forEach(function(initializer) {

          // eagerly resolve component (fn or string)
          if (typeof initializer === 'string') {
            injector.get(initializer);
          } else {
            injector.invoke(initializer);
          }
        });
      };
    }

    /**
     * @param {ModuleDefinition} moduleDefinition
     */
    function loadModule(moduleDefinition) {

      var moduleExports = moduleDefinition.__exports__;

      // private module
      if (moduleExports) {
        var nestedModules = moduleDefinition.__modules__;

        var clonedModule = Object.keys(moduleDefinition).reduce(function(clonedModule, key) {

          if (key !== '__exports__' && key !== '__modules__' && key !== '__init__' && key !== '__depends__') {
            clonedModule[key] = moduleDefinition[key];
          }

          return clonedModule;
        }, Object.create(null));

        var childModules = (nestedModules || []).concat(clonedModule);

        var privateInjector = createChild(childModules);
        var getFromPrivateInjector = annotate(function(key) {
          return privateInjector.get(key);
        });

        moduleExports.forEach(function(key) {
          providers[key] = [ getFromPrivateInjector, key, 'private', privateInjector ];
        });

        // ensure child injector initializes
        var initializers = (moduleDefinition.__init__ || []).slice();

        initializers.unshift(function() {
          privateInjector.init();
        });

        moduleDefinition = Object.assign({}, moduleDefinition, {
          __init__: initializers
        });

        return createInitializer(moduleDefinition, privateInjector);
      }

      // normal module
      Object.keys(moduleDefinition).forEach(function(key) {

        if (key === '__init__' || key === '__depends__') {
          return;
        }

        if (moduleDefinition[key][2] === 'private') {
          providers[key] = moduleDefinition[key];
          return;
        }

        var type = moduleDefinition[key][0];
        var value = moduleDefinition[key][1];

        providers[key] = [ factoryMap[type], arrayUnwrap(type, value), type ];
      });

      return createInitializer(moduleDefinition, self);
    }

    /**
     * @param {ModuleDefinition[]} moduleDefinitions
     * @param {ModuleDefinition} moduleDefinition
     *
     * @return {ModuleDefinition[]}
     */
    function resolveDependencies(moduleDefinitions, moduleDefinition) {

      if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
        return moduleDefinitions;
      }

      moduleDefinitions = (moduleDefinition.__depends__ || []).reduce(resolveDependencies, moduleDefinitions);

      if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
        return moduleDefinitions;
      }

      return moduleDefinitions.concat(moduleDefinition);
    }

    /**
     * @param {ModuleDefinition[]} moduleDefinitions
     *
     * @return { () => void } initializerFn
     */
    function bootstrap(moduleDefinitions) {

      var initializers = moduleDefinitions
        .reduce(resolveDependencies, [])
        .map(loadModule);

      var initialized = false;

      return function() {

        if (initialized) {
          return;
        }

        initialized = true;

        initializers.forEach(function(initializer) {
          return initializer();
        });
      };
    }

    // public API
    this.get = get;
    this.invoke = invoke;
    this.instantiate = instantiate;
    this.createChild = createChild;

    // setup
    this.init = bootstrap(modules);
  }


  // helpers ///////////////

  function arrayUnwrap(type, value) {
    if (type !== 'value' && isArray$1(value)) {
      value = annotate(value.slice());
    }

    return value;
  }

  var DEFAULT_RENDER_PRIORITY$1 = 1000;

  /**
   * The base implementation of shape and connection renderers.
   *
   * @param {EventBus} eventBus
   * @param {number} [renderPriority=1000]
   */
  function BaseRenderer(eventBus, renderPriority) {
    var self = this;

    renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY$1;

    eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) {
      var type = evt.type,
          element = context.element,
          visuals = context.gfx,
          attrs = context.attrs;

      if (self.canRender(element)) {
        if (type === 'render.shape') {
          return self.drawShape(visuals, element, attrs);
        } else {
          return self.drawConnection(visuals, element, attrs);
        }
      }
    });

    eventBus.on([ 'render.getShapePath', 'render.getConnectionPath' ], renderPriority, function(evt, element) {
      if (self.canRender(element)) {
        if (evt.type === 'render.getShapePath') {
          return self.getShapePath(element);
        } else {
          return self.getConnectionPath(element);
        }
      }
    });
  }

  /**
   * Should check whether *this* renderer can render
   * the element/connection.
   *
   * @param {element} element
   *
   * @returns {boolean}
   */
  BaseRenderer.prototype.canRender = function() {};

  /**
   * Provides the shape's snap svg element to be drawn on the `canvas`.
   *
   * @param {djs.Graphics} visuals
   * @param {Shape} shape
   *
   * @returns {Snap.svg} [returns a Snap.svg paper element ]
   */
  BaseRenderer.prototype.drawShape = function() {};

  /**
   * Provides the shape's snap svg element to be drawn on the `canvas`.
   *
   * @param {djs.Graphics} visuals
   * @param {Connection} connection
   *
   * @returns {Snap.svg} [returns a Snap.svg paper element ]
   */
  BaseRenderer.prototype.drawConnection = function() {};

  /**
   * Gets the SVG path of a shape that represents it's visual bounds.
   *
   * @param {Shape} shape
   *
   * @return {string} svg path
   */
  BaseRenderer.prototype.getShapePath = function() {};

  /**
   * Gets the SVG path of a connection that represents it's visual bounds.
   *
   * @param {Connection} connection
   *
   * @return {string} svg path
   */
  BaseRenderer.prototype.getConnectionPath = function() {};

  function componentsToPath(elements) {
    return elements.join(',').replace(/,?([A-z]),?/g, '$1');
  }

  function toSVGPoints(points) {
    var result = '';

    for (var i = 0, p; (p = points[i]); i++) {
      result += p.x + ',' + p.y + ' ';
    }

    return result;
  }

  function createLine(points, attrs) {

    var line = create$1('polyline');
    attr(line, { points: toSVGPoints(points) });

    if (attrs) {
      attr(line, attrs);
    }

    return line;
  }

  function updateLine(gfx, points) {
    attr(gfx, { points: toSVGPoints(points) });

    return gfx;
  }

  /**
   * @typedef { {x:number, y: number, width: number, height: number} } Bounds
   */

  /**
   * Get parent elements.
   *
   * @param {Array<djs.model.base>} elements
   *
   * @returns {Array<djs.model.Base>}
   */
  function getParents$1(elements) {

    // find elements that are not children of any other elements
    return filter(elements, function(element) {
      return !find(elements, function(e) {
        return e !== element && getParent$1(element, e);
      });
    });
  }


  function getParent$1(element, parent) {
    if (!parent) {
      return;
    }

    if (element === parent) {
      return parent;
    }

    if (!element.parent) {
      return;
    }

    return getParent$1(element.parent, parent);
  }


  /**
   * Adds an element to a collection and returns true if the
   * element was added.
   *
   * @param {Array<Object>} elements
   * @param {Object} e
   * @param {boolean} unique
   */
  function add$1(elements, e, unique) {
    var canAdd = !unique || elements.indexOf(e) === -1;

    if (canAdd) {
      elements.push(e);
    }

    return canAdd;
  }


  /**
   * Iterate over each element in a collection, calling the iterator function `fn`
   * with (element, index, recursionDepth).
   *
   * Recurse into all elements that are returned by `fn`.
   *
   * @param  {Object|Array<Object>} elements
   * @param  {Function} fn iterator function called with (element, index, recursionDepth)
   * @param  {number} [depth] maximum recursion depth
   */
  function eachElement(elements, fn, depth) {

    depth = depth || 0;

    if (!isArray$3(elements)) {
      elements = [ elements ];
    }

    forEach$1(elements, function(s, i) {
      var filter = fn(s, i, depth);

      if (isArray$3(filter) && filter.length) {
        eachElement(filter, fn, depth + 1);
      }
    });
  }


  /**
   * Collects self + child elements up to a given depth from a list of elements.
   *
   * @param  {djs.model.Base|Array<djs.model.Base>} elements the elements to select the children from
   * @param  {boolean} unique whether to return a unique result set (no duplicates)
   * @param  {number} maxDepth the depth to search through or -1 for infinite
   *
   * @return {Array<djs.model.Base>} found elements
   */
  function selfAndChildren(elements, unique, maxDepth) {
    var result = [],
        processedChildren = [];

    eachElement(elements, function(element, i, depth) {
      add$1(result, element, unique);

      var children = element.children;

      // max traversal depth not reached yet
      if (maxDepth === -1 || depth < maxDepth) {

        // children exist && children not yet processed
        if (children && add$1(processedChildren, children, unique)) {
          return children;
        }
      }
    });

    return result;
  }


  /**
   * Return self + ALL children for a number of elements
   *
   * @param  {Array<djs.model.Base>} elements to query
   * @param  {boolean} allowDuplicates to allow duplicates in the result set
   *
   * @return {Array<djs.model.Base>} the collected elements
   */
  function selfAndAllChildren(elements, allowDuplicates) {
    return selfAndChildren(elements, !allowDuplicates, -1);
  }


  /**
   * Gets the the closure for all selected elements,
   * their enclosed children and connections.
   *
   * @param {Array<djs.model.Base>} elements
   * @param {boolean} [isTopLevel=true]
   * @param {Object} [existingClosure]
   *
   * @return {Object} newClosure
   */
  function getClosure(elements, isTopLevel, closure) {

    if (isUndefined$2(isTopLevel)) {
      isTopLevel = true;
    }

    if (isObject(isTopLevel)) {
      closure = isTopLevel;
      isTopLevel = true;
    }


    closure = closure || {};

    var allShapes = copyObject(closure.allShapes),
        allConnections = copyObject(closure.allConnections),
        enclosedElements = copyObject(closure.enclosedElements),
        enclosedConnections = copyObject(closure.enclosedConnections);

    var topLevel = copyObject(
      closure.topLevel,
      isTopLevel && groupBy(elements, function(e) { return e.id; })
    );


    function handleConnection(c) {
      if (topLevel[c.source.id] && topLevel[c.target.id]) {
        topLevel[c.id] = [ c ];
      }

      // not enclosed as a child, but maybe logically
      // (connecting two moved elements?)
      if (allShapes[c.source.id] && allShapes[c.target.id]) {
        enclosedConnections[c.id] = enclosedElements[c.id] = c;
      }

      allConnections[c.id] = c;
    }

    function handleElement(element) {

      enclosedElements[element.id] = element;

      if (element.waypoints) {

        // remember connection
        enclosedConnections[element.id] = allConnections[element.id] = element;
      } else {

        // remember shape
        allShapes[element.id] = element;

        // remember all connections
        forEach$1(element.incoming, handleConnection);

        forEach$1(element.outgoing, handleConnection);

        // recurse into children
        return element.children;
      }
    }

    eachElement(elements, handleElement);

    return {
      allShapes: allShapes,
      allConnections: allConnections,
      topLevel: topLevel,
      enclosedConnections: enclosedConnections,
      enclosedElements: enclosedElements
    };
  }

  /**
   * Returns the surrounding bbox for all elements in
   * the array or the element primitive.
   *
   * @param {Array<djs.model.Shape>|djs.model.Shape} elements
   * @param {boolean} [stopRecursion=false]
   *
   * @return {Bounds}
   */
  function getBBox(elements, stopRecursion) {

    stopRecursion = !!stopRecursion;
    if (!isArray$3(elements)) {
      elements = [ elements ];
    }

    var minX,
        minY,
        maxX,
        maxY;

    forEach$1(elements, function(element) {

      // If element is a connection the bbox must be computed first
      var bbox = element;
      if (element.waypoints && !stopRecursion) {
        bbox = getBBox(element.waypoints, true);
      }

      var x = bbox.x,
          y = bbox.y,
          height = bbox.height || 0,
          width = bbox.width || 0;

      if (x < minX || minX === undefined) {
        minX = x;
      }
      if (y < minY || minY === undefined) {
        minY = y;
      }

      if ((x + width) > maxX || maxX === undefined) {
        maxX = x + width;
      }
      if ((y + height) > maxY || maxY === undefined) {
        maxY = y + height;
      }
    });

    return {
      x: minX,
      y: minY,
      height: maxY - minY,
      width: maxX - minX
    };
  }


  /**
   * Returns all elements that are enclosed from the bounding box.
   *
   *   * If bbox.(width|height) is not specified the method returns
   *     all elements with element.x/y > bbox.x/y
   *   * If only bbox.x or bbox.y is specified, method return all elements with
   *     e.x > bbox.x or e.y > bbox.y
   *
   * @param {Array<djs.model.Shape>} elements List of Elements to search through
   * @param {djs.model.Shape} bbox the enclosing bbox.
   *
   * @return {Array<djs.model.Shape>} enclosed elements
   */
  function getEnclosedElements(elements, bbox) {

    var filteredElements = {};

    forEach$1(elements, function(element) {

      var e = element;

      if (e.waypoints) {
        e = getBBox(e);
      }

      if (!isNumber(bbox.y) && (e.x > bbox.x)) {
        filteredElements[element.id] = element;
      }
      if (!isNumber(bbox.x) && (e.y > bbox.y)) {
        filteredElements[element.id] = element;
      }
      if (e.x > bbox.x && e.y > bbox.y) {
        if (isNumber(bbox.width) && isNumber(bbox.height) &&
            e.width + e.x < bbox.width + bbox.x &&
            e.height + e.y < bbox.height + bbox.y) {

          filteredElements[element.id] = element;
        } else if (!isNumber(bbox.width) || !isNumber(bbox.height)) {
          filteredElements[element.id] = element;
        }
      }
    });

    return filteredElements;
  }


  function getType(element) {

    if ('waypoints' in element) {
      return 'connection';
    }

    if ('x' in element) {
      return 'shape';
    }

    return 'root';
  }

  function isFrameElement$1(element) {

    return !!(element && element.isFrame);
  }

  // helpers ///////////////////////////////

  function copyObject(src1, src2) {
    return assign({}, src1 || {}, src2 || {});
  }

  // apply default renderer with lowest possible priority
  // so that it only kicks in if noone else could render
  var DEFAULT_RENDER_PRIORITY = 1;

  /**
   * The default renderer used for shapes and connections.
   *
   * @param {EventBus} eventBus
   * @param {Styles} styles
   */
  function DefaultRenderer(eventBus, styles) {

    //
    BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY);

    this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' });
    this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 });
    this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 });
  }

  e(DefaultRenderer, BaseRenderer);


  DefaultRenderer.prototype.canRender = function() {
    return true;
  };

  DefaultRenderer.prototype.drawShape = function drawShape(visuals, element, attrs) {
    var rect = create$1('rect');

    attr(rect, {
      x: 0,
      y: 0,
      width: element.width || 0,
      height: element.height || 0
    });

    if (isFrameElement$1(element)) {
      attr(rect, assign({}, this.FRAME_STYLE, attrs || {}));
    } else {
      attr(rect, assign({}, this.SHAPE_STYLE, attrs || {}));
    }

    append(visuals, rect);

    return rect;
  };

  DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection, attrs) {

    var line = createLine(connection.waypoints, assign({}, this.CONNECTION_STYLE, attrs || {}));
    append(visuals, line);

    return line;
  };

  DefaultRenderer.prototype.getShapePath = function getShapePath(shape) {

    var x = shape.x,
        y = shape.y,
        width = shape.width,
        height = shape.height;

    var shapePath = [
      [ 'M', x, y ],
      [ 'l', width, 0 ],
      [ 'l', 0, height ],
      [ 'l', -width, 0 ],
      [ 'z' ]
    ];

    return componentsToPath(shapePath);
  };

  DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) {
    var waypoints = connection.waypoints;

    var idx, point, connectionPath = [];

    for (idx = 0; (point = waypoints[idx]); idx++) {

      // take invisible docking into account
      // when creating the path
      point = point.original || point;

      connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]);
    }

    return componentsToPath(connectionPath);
  };


  DefaultRenderer.$inject = [ 'eventBus', 'styles' ];

  /**
   * A component that manages shape styles
   */
  function Styles() {

    var defaultTraits = {

      'no-fill': {
        fill: 'none'
      },
      'no-border': {
        strokeOpacity: 0.0
      },
      'no-events': {
        pointerEvents: 'none'
      }
    };

    var self = this;

    /**
     * Builds a style definition from a className, a list of traits and an object of additional attributes.
     *
     * @param  {string} className
     * @param  {Array<string>} traits
     * @param  {Object} additionalAttrs
     *
     * @return {Object} the style defintion
     */
    this.cls = function(className, traits, additionalAttrs) {
      var attrs = this.style(traits, additionalAttrs);

      return assign(attrs, { 'class': className });
    };

    /**
     * Builds a style definition from a list of traits and an object of additional attributes.
     *
     * @param  {Array<string>} traits
     * @param  {Object} additionalAttrs
     *
     * @return {Object} the style defintion
     */
    this.style = function(traits, additionalAttrs) {

      if (!isArray$3(traits) && !additionalAttrs) {
        additionalAttrs = traits;
        traits = [];
      }

      var attrs = reduce(traits, function(attrs, t) {
        return assign(attrs, defaultTraits[t] || {});
      }, {});

      return additionalAttrs ? assign(attrs, additionalAttrs) : attrs;
    };

    this.computeStyle = function(custom, traits, defaultStyles) {
      if (!isArray$3(traits)) {
        defaultStyles = traits;
        traits = [];
      }

      return self.style(traits || [], assign({}, defaultStyles, custom || {}));
    };
  }

  var DrawModule$1 = {
    __init__: [ 'defaultRenderer' ],
    defaultRenderer: [ 'type', DefaultRenderer ],
    styles: [ 'type', Styles ]
  };

  /**
   * Failsafe remove an element from a collection
   *
   * @param  {Array<Object>} [collection]
   * @param  {Object} [element]
   *
   * @return {number} the previous index of the element
   */
  function remove(collection, element) {

    if (!collection || !element) {
      return -1;
    }

    var idx = collection.indexOf(element);

    if (idx !== -1) {
      collection.splice(idx, 1);
    }

    return idx;
  }

  /**
   * Fail save add an element to the given connection, ensuring
   * it does not yet exist.
   *
   * @param {Array<Object>} collection
   * @param {Object} element
   * @param {number} idx
   */
  function add(collection, element, idx) {

    if (!collection || !element) {
      return;
    }

    if (typeof idx !== 'number') {
      idx = -1;
    }

    var currentIdx = collection.indexOf(element);

    if (currentIdx !== -1) {

      if (currentIdx === idx) {

        // nothing to do, position has not changed
        return;
      } else {

        if (idx !== -1) {

          // remove from current position
          collection.splice(currentIdx, 1);
        } else {

          // already exists in collection
          return;
        }
      }
    }

    if (idx !== -1) {

      // insert at specified position
      collection.splice(idx, 0, element);
    } else {

      // push to end
      collection.push(element);
    }
  }


  /**
   * Fail save get the index of an element in a collection.
   *
   * @param {Array<Object>} collection
   * @param {Object} element
   *
   * @return {number} the index or -1 if collection or element do
   *                  not exist or the element is not contained.
   */
  function indexOf(collection, element) {

    if (!collection || !element) {
      return -1;
    }

    return collection.indexOf(element);
  }

  /**
   * Computes the distance between two points
   *
   * @param  {Point}  p
   * @param  {Point}  q
   *
   * @return {number}  distance
   */
  function pointDistance(a, b) {
    if (!a || !b) {
      return -1;
    }

    return Math.sqrt(
      Math.pow(a.x - b.x, 2) +
      Math.pow(a.y - b.y, 2)
    );
  }


  /**
   * Returns true if the point r is on the line between p and q
   *
   * @param  {Point}  p
   * @param  {Point}  q
   * @param  {Point}  r
   * @param  {number} [accuracy=5] accuracy for points on line check (lower is better)
   *
   * @return {boolean}
   */
  function pointsOnLine(p, q, r, accuracy) {

    if (typeof accuracy === 'undefined') {
      accuracy = 5;
    }

    if (!p || !q || !r) {
      return false;
    }

    var val = (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x),
        dist = pointDistance(p, q);

    // @see http://stackoverflow.com/a/907491/412190
    return Math.abs(val / dist) <= accuracy;
  }


  var ALIGNED_THRESHOLD = 2;

  /**
   * Check whether two points are horizontally or vertically aligned.
   *
   * @param {Array<Point>|Point}
   * @param {Point}
   *
   * @return {string|boolean}
   */
  function pointsAligned(a, b) {
    var points;

    if (isArray$3(a)) {
      points = a;
    } else {
      points = [ a, b ];
    }

    if (pointsAlignedHorizontally(points)) {
      return 'h';
    }

    if (pointsAlignedVertically(points)) {
      return 'v';
    }

    return false;
  }

  function pointsAlignedHorizontally(a, b) {
    var points;

    if (isArray$3(a)) {
      points = a;
    } else {
      points = [ a, b ];
    }

    var firstPoint = points.slice().shift();

    return every(points, function(point) {
      return Math.abs(firstPoint.y - point.y) <= ALIGNED_THRESHOLD;
    });
  }

  function pointsAlignedVertically(a, b) {
    var points;

    if (isArray$3(a)) {
      points = a;
    } else {
      points = [ a, b ];
    }

    var firstPoint = points.slice().shift();

    return every(points, function(point) {
      return Math.abs(firstPoint.x - point.x) <= ALIGNED_THRESHOLD;
    });
  }



  /**
   * Returns true if the point p is inside the rectangle rect
   *
   * @param  {Point}  p
   * @param  {Rect} rect
   * @param  {number} tolerance
   *
   * @return {boolean}
   */
  function pointInRect(p, rect, tolerance) {
    tolerance = tolerance || 0;

    return p.x > rect.x - tolerance &&
           p.y > rect.y - tolerance &&
           p.x < rect.x + rect.width + tolerance &&
           p.y < rect.y + rect.height + tolerance;
  }

  /**
   * Returns a point in the middle of points p and q
   *
   * @param  {Point}  p
   * @param  {Point}  q
   *
   * @return {Point} middle point
   */
  function getMidPoint(p, q) {
    return {
      x: Math.round(p.x + ((q.x - p.x) / 2.0)),
      y: Math.round(p.y + ((q.y - p.y) / 2.0))
    };
  }

  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

  function getDefaultExportFromCjs (x) {
  	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
  }

  /**
   * This file contains source code adapted from Snap.svg (licensed Apache-2.0).
   *
   * @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js
   */

  /* eslint no-fallthrough: "off" */

  var p2s = /,?([a-z]),?/gi,
      toFloat = parseFloat,
      math = Math,
      PI = math.PI,
      mmin = math.min,
      mmax = math.max,
      pow = math.pow,
      abs$7 = math.abs,
      pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?[\s]*,?[\s]*)+)/ig,
      pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)[\s]*,?[\s]*/ig;

  var isArray = Array.isArray || function(o) { return o instanceof Array; };

  function hasProperty(obj, property) {
    return Object.prototype.hasOwnProperty.call(obj, property);
  }

  function clone(obj) {

    if (typeof obj == 'function' || Object(obj) !== obj) {
      return obj;
    }

    var res = new obj.constructor;

    for (var key in obj) {
      if (hasProperty(obj, key)) {
        res[key] = clone(obj[key]);
      }
    }

    return res;
  }

  function repush(array, item) {
    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
      return array.push(array.splice(i, 1)[0]);
    }
  }

  function cacher(f) {

    function newf() {

      var arg = Array.prototype.slice.call(arguments, 0),
          args = arg.join('\u2400'),
          cache = newf.cache = newf.cache || {},
          count = newf.count = newf.count || [];

      if (hasProperty(cache, args)) {
        repush(count, args);
        return cache[args];
      }

      count.length >= 1e3 && delete cache[count.shift()];
      count.push(args);
      cache[args] = f.apply(0, arg);

      return cache[args];
    }
    return newf;
  }

  function parsePathString(pathString) {

    if (!pathString) {
      return null;
    }

    var pth = paths(pathString);

    if (pth.arr) {
      return clone(pth.arr);
    }

    var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 },
        data = [];

    if (isArray(pathString) && isArray(pathString[0])) { // rough assumption
      data = clone(pathString);
    }

    if (!data.length) {

      String(pathString).replace(pathCommand, function(a, b, c) {
        var params = [],
            name = b.toLowerCase();

        c.replace(pathValues, function(a, b) {
          b && params.push(+b);
        });

        if (name == 'm' && params.length > 2) {
          data.push([b].concat(params.splice(0, 2)));
          name = 'l';
          b = b == 'm' ? 'l' : 'L';
        }

        while (params.length >= paramCounts[name]) {
          data.push([b].concat(params.splice(0, paramCounts[name])));
          if (!paramCounts[name]) {
            break;
          }
        }
      });
    }

    data.toString = paths.toString;
    pth.arr = clone(data);

    return data;
  }

  function paths(ps) {
    var p = paths.ps = paths.ps || {};

    if (p[ps]) {
      p[ps].sleep = 100;
    } else {
      p[ps] = {
        sleep: 100
      };
    }

    setTimeout(function() {
      for (var key in p) {
        if (hasProperty(p, key) && key != ps) {
          p[key].sleep--;
          !p[key].sleep && delete p[key];
        }
      }
    });

    return p[ps];
  }

  function rectBBox(x, y, width, height) {

    if (arguments.length === 1) {
      y = x.y;
      width = x.width;
      height = x.height;
      x = x.x;
    }

    return {
      x: x,
      y: y,
      width: width,
      height: height,
      x2: x + width,
      y2: y + height
    };
  }

  function pathToString() {
    return this.join(',').replace(p2s, '$1');
  }

  function pathClone(pathArray) {
    var res = clone(pathArray);
    res.toString = pathToString;
    return res;
  }

  function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
    var t1 = 1 - t,
        t13 = pow(t1, 3),
        t12 = pow(t1, 2),
        t2 = t * t,
        t3 = t2 * t,
        x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
        y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y;

    return {
      x: fixError(x),
      y: fixError(y)
    };
  }

  function bezierBBox(points) {

    var bbox = curveBBox.apply(null, points);

    return rectBBox(
      bbox.x0,
      bbox.y0,
      bbox.x1 - bbox.x0,
      bbox.y1 - bbox.y0
    );
  }

  function isPointInsideBBox$2(bbox, x, y) {
    return x >= bbox.x &&
      x <= bbox.x + bbox.width &&
      y >= bbox.y &&
      y <= bbox.y + bbox.height;
  }

  function isBBoxIntersect(bbox1, bbox2) {
    bbox1 = rectBBox(bbox1);
    bbox2 = rectBBox(bbox2);
    return isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y)
      || isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y)
      || isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y2)
      || isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y2)
      || isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y)
      || isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y)
      || isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y2)
      || isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y2)
      || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
          || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
      && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
          || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
  }

  function base3(t, p1, p2, p3, p4) {
    var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
        t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
    return t * t2 - 3 * p1 + 3 * p2;
  }

  function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {

    if (z == null) {
      z = 1;
    }

    z = z > 1 ? 1 : z < 0 ? 0 : z;

    var z2 = z / 2,
        n = 12,
        Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
        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],
        sum = 0;

    for (var i = 0; i < n; i++) {
      var ct = z2 * Tvalues[i] + z2,
          xbase = base3(ct, x1, x2, x3, x4),
          ybase = base3(ct, y1, y2, y3, y4),
          comb = xbase * xbase + ybase * ybase;

      sum += Cvalues[i] * math.sqrt(comb);
    }

    return z2 * sum;
  }


  function intersectLines(x1, y1, x2, y2, x3, y3, x4, y4) {

    if (
      mmax(x1, x2) < mmin(x3, x4) ||
        mmin(x1, x2) > mmax(x3, x4) ||
        mmax(y1, y2) < mmin(y3, y4) ||
        mmin(y1, y2) > mmax(y3, y4)
    ) {
      return;
    }

    var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
        ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
        denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

    if (!denominator) {
      return;
    }

    var px = fixError(nx / denominator),
        py = fixError(ny / denominator),
        px2 = +px.toFixed(2),
        py2 = +py.toFixed(2);

    if (
      px2 < +mmin(x1, x2).toFixed(2) ||
        px2 > +mmax(x1, x2).toFixed(2) ||
        px2 < +mmin(x3, x4).toFixed(2) ||
        px2 > +mmax(x3, x4).toFixed(2) ||
        py2 < +mmin(y1, y2).toFixed(2) ||
        py2 > +mmax(y1, y2).toFixed(2) ||
        py2 < +mmin(y3, y4).toFixed(2) ||
        py2 > +mmax(y3, y4).toFixed(2)
    ) {
      return;
    }

    return { x: px, y: py };
  }

  function fixError(number) {
    return Math.round(number * 100000000000) / 100000000000;
  }

  function findBezierIntersections(bez1, bez2, justCount) {
    var bbox1 = bezierBBox(bez1),
        bbox2 = bezierBBox(bez2);

    if (!isBBoxIntersect(bbox1, bbox2)) {
      return justCount ? 0 : [];
    }

    // As an optimization, lines will have only 1 segment

    var l1 = bezlen.apply(0, bez1),
        l2 = bezlen.apply(0, bez2),
        n1 = isLine(bez1) ? 1 : ~~(l1 / 5) || 1,
        n2 = isLine(bez2) ? 1 : ~~(l2 / 5) || 1,
        dots1 = [],
        dots2 = [],
        xy = {},
        res = justCount ? 0 : [];

    for (var i = 0; i < n1 + 1; i++) {
      var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
      dots1.push({ x: p.x, y: p.y, t: i / n1 });
    }

    for (i = 0; i < n2 + 1; i++) {
      p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
      dots2.push({ x: p.x, y: p.y, t: i / n2 });
    }

    for (i = 0; i < n1; i++) {

      for (var j = 0; j < n2; j++) {
        var di = dots1[i],
            di1 = dots1[i + 1],
            dj = dots2[j],
            dj1 = dots2[j + 1],
            ci = abs$7(di1.x - di.x) < .01 ? 'y' : 'x',
            cj = abs$7(dj1.x - dj.x) < .01 ? 'y' : 'x',
            is = intersectLines(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y),
            key;

        if (is) {
          key = is.x.toFixed(9) + '#' + is.y.toFixed(9);

          if (xy[key]) {
            continue;
          }

          xy[key] = true;

          var t1 = di.t + abs$7((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
              t2 = dj.t + abs$7((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);

          if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {

            if (justCount) {
              res++;
            } else {
              res.push({
                x: is.x,
                y: is.y,
                t1: t1,
                t2: t2
              });
            }
          }
        }
      }
    }

    return res;
  }


  /**
   * Find or counts the intersections between two SVG paths.
   *
   * Returns a number in counting mode and a list of intersections otherwise.
   *
   * A single intersection entry contains the intersection coordinates (x, y)
   * as well as additional information regarding the intersecting segments
   * on each path (segment1, segment2) and the relative location of the
   * intersection on these segments (t1, t2).
   *
   * The path may be an SVG path string or a list of path components
   * such as `[ [ 'M', 0, 10 ], [ 'L', 20, 0 ] ]`.
   *
   * @example
   *
   * var intersections = findPathIntersections(
   *   'M0,0L100,100',
   *   [ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ]
   * );
   *
   * // intersections = [
   * //   { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 }
   * // ]
   *
   * @param {String|Array<PathDef>} path1
   * @param {String|Array<PathDef>} path2
   * @param {Boolean} [justCount=false]
   *
   * @return {Array<Intersection>|Number}
   */
  function findPathIntersections(path1, path2, justCount) {
    path1 = pathToCurve(path1);
    path2 = pathToCurve(path2);

    var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
        res = justCount ? 0 : [];

    for (var i = 0, ii = path1.length; i < ii; i++) {
      var pi = path1[i];

      if (pi[0] == 'M') {
        x1 = x1m = pi[1];
        y1 = y1m = pi[2];
      } else {

        if (pi[0] == 'C') {
          bez1 = [x1, y1].concat(pi.slice(1));
          x1 = bez1[6];
          y1 = bez1[7];
        } else {
          bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
          x1 = x1m;
          y1 = y1m;
        }

        for (var j = 0, jj = path2.length; j < jj; j++) {
          var pj = path2[j];

          if (pj[0] == 'M') {
            x2 = x2m = pj[1];
            y2 = y2m = pj[2];
          } else {

            if (pj[0] == 'C') {
              bez2 = [x2, y2].concat(pj.slice(1));
              x2 = bez2[6];
              y2 = bez2[7];
            } else {
              bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
              x2 = x2m;
              y2 = y2m;
            }

            var intr = findBezierIntersections(bez1, bez2, justCount);

            if (justCount) {
              res += intr;
            } else {

              for (var k = 0, kk = intr.length; k < kk; k++) {
                intr[k].segment1 = i;
                intr[k].segment2 = j;
                intr[k].bez1 = bez1;
                intr[k].bez2 = bez2;
              }

              res = res.concat(intr);
            }
          }
        }
      }
    }

    return res;
  }


  function pathToAbsolute(pathArray) {
    var pth = paths(pathArray);

    if (pth.abs) {
      return pathClone(pth.abs);
    }

    if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption
      pathArray = parsePathString(pathArray);
    }

    if (!pathArray || !pathArray.length) {
      return [['M', 0, 0]];
    }

    var res = [],
        x = 0,
        y = 0,
        mx = 0,
        my = 0,
        start = 0,
        pa0;

    if (pathArray[0][0] == 'M') {
      x = +pathArray[0][1];
      y = +pathArray[0][2];
      mx = x;
      my = y;
      start++;
      res[0] = ['M', x, y];
    }

    for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
      res.push(r = []);
      pa = pathArray[i];
      pa0 = pa[0];

      if (pa0 != pa0.toUpperCase()) {
        r[0] = pa0.toUpperCase();

        switch (r[0]) {
        case 'A':
          r[1] = pa[1];
          r[2] = pa[2];
          r[3] = pa[3];
          r[4] = pa[4];
          r[5] = pa[5];
          r[6] = +pa[6] + x;
          r[7] = +pa[7] + y;
          break;
        case 'V':
          r[1] = +pa[1] + y;
          break;
        case 'H':
          r[1] = +pa[1] + x;
          break;
        case 'M':
          mx = +pa[1] + x;
          my = +pa[2] + y;
        default:
          for (var j = 1, jj = pa.length; j < jj; j++) {
            r[j] = +pa[j] + ((j % 2) ? x : y);
          }
        }
      } else {
        for (var k = 0, kk = pa.length; k < kk; k++) {
          r[k] = pa[k];
        }
      }
      pa0 = pa0.toUpperCase();

      switch (r[0]) {
      case 'Z':
        x = +mx;
        y = +my;
        break;
      case 'H':
        x = r[1];
        break;
      case 'V':
        y = r[1];
        break;
      case 'M':
        mx = r[r.length - 2];
        my = r[r.length - 1];
      default:
        x = r[r.length - 2];
        y = r[r.length - 1];
      }
    }

    res.toString = pathToString;
    pth.abs = pathClone(res);

    return res;
  }

  function isLine(bez) {
    return (
      bez[0] === bez[2] &&
      bez[1] === bez[3] &&
      bez[4] === bez[6] &&
      bez[5] === bez[7]
    );
  }

  function lineToCurve(x1, y1, x2, y2) {
    return [
      x1, y1, x2,
      y2, x2, y2
    ];
  }

  function qubicToCurve(x1, y1, ax, ay, x2, y2) {
    var _13 = 1 / 3,
        _23 = 2 / 3;

    return [
      _13 * x1 + _23 * ax,
      _13 * y1 + _23 * ay,
      _13 * x2 + _23 * ax,
      _13 * y2 + _23 * ay,
      x2,
      y2
    ];
  }

  function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {

    // for more information of where this math came from visit:
    // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
    var _120 = PI * 120 / 180,
        rad = PI / 180 * (+angle || 0),
        res = [],
        xy,
        rotate = cacher(function(x, y, rad) {
          var X = x * math.cos(rad) - y * math.sin(rad),
              Y = x * math.sin(rad) + y * math.cos(rad);

          return { x: X, y: Y };
        });

    if (!recursive) {
      xy = rotate(x1, y1, -rad);
      x1 = xy.x;
      y1 = xy.y;
      xy = rotate(x2, y2, -rad);
      x2 = xy.x;
      y2 = xy.y;

      var x = (x1 - x2) / 2,
          y = (y1 - y2) / 2;

      var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);

      if (h > 1) {
        h = math.sqrt(h);
        rx = h * rx;
        ry = h * ry;
      }

      var rx2 = rx * rx,
          ry2 = ry * ry,
          k = (large_arc_flag == sweep_flag ? -1 : 1) *
              math.sqrt(abs$7((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
          cx = k * rx * y / ry + (x1 + x2) / 2,
          cy = k * -ry * x / rx + (y1 + y2) / 2,
          f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
          f2 = math.asin(((y2 - cy) / ry).toFixed(9));

      f1 = x1 < cx ? PI - f1 : f1;
      f2 = x2 < cx ? PI - f2 : f2;
      f1 < 0 && (f1 = PI * 2 + f1);
      f2 < 0 && (f2 = PI * 2 + f2);

      if (sweep_flag && f1 > f2) {
        f1 = f1 - PI * 2;
      }
      if (!sweep_flag && f2 > f1) {
        f2 = f2 - PI * 2;
      }
    } else {
      f1 = recursive[0];
      f2 = recursive[1];
      cx = recursive[2];
      cy = recursive[3];
    }

    var df = f2 - f1;

    if (abs$7(df) > _120) {
      var f2old = f2,
          x2old = x2,
          y2old = y2;

      f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
      x2 = cx + rx * math.cos(f2);
      y2 = cy + ry * math.sin(f2);
      res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
    }

    df = f2 - f1;

    var c1 = math.cos(f1),
        s1 = math.sin(f1),
        c2 = math.cos(f2),
        s2 = math.sin(f2),
        t = math.tan(df / 4),
        hx = 4 / 3 * rx * t,
        hy = 4 / 3 * ry * t,
        m1 = [x1, y1],
        m2 = [x1 + hx * s1, y1 - hy * c1],
        m3 = [x2 + hx * s2, y2 - hy * c2],
        m4 = [x2, y2];

    m2[0] = 2 * m1[0] - m2[0];
    m2[1] = 2 * m1[1] - m2[1];

    if (recursive) {
      return [m2, m3, m4].concat(res);
    } else {
      res = [m2, m3, m4].concat(res).join().split(',');
      var newres = [];

      for (var i = 0, ii = res.length; i < ii; i++) {
        newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
      }

      return newres;
    }
  }

  // Returns bounding box of cubic bezier curve.
  // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
  // Original version: NISHIO Hirokazu
  // Modifications: https://github.com/timo22345
  function curveBBox(x0, y0, x1, y1, x2, y2, x3, y3) {
    var tvalues = [],
        bounds = [[], []],
        a, b, c, t, t1, t2, b2ac, sqrtb2ac;

    for (var i = 0; i < 2; ++i) {

      if (i == 0) {
        b = 6 * x0 - 12 * x1 + 6 * x2;
        a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
        c = 3 * x1 - 3 * x0;
      } else {
        b = 6 * y0 - 12 * y1 + 6 * y2;
        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
        c = 3 * y1 - 3 * y0;
      }

      if (abs$7(a) < 1e-12) {

        if (abs$7(b) < 1e-12) {
          continue;
        }

        t = -c / b;

        if (0 < t && t < 1) {
          tvalues.push(t);
        }

        continue;
      }

      b2ac = b * b - 4 * c * a;
      sqrtb2ac = math.sqrt(b2ac);

      if (b2ac < 0) {
        continue;
      }

      t1 = (-b + sqrtb2ac) / (2 * a);

      if (0 < t1 && t1 < 1) {
        tvalues.push(t1);
      }

      t2 = (-b - sqrtb2ac) / (2 * a);

      if (0 < t2 && t2 < 1) {
        tvalues.push(t2);
      }
    }

    var j = tvalues.length,
        jlen = j,
        mt;

    while (j--) {
      t = tvalues[j];
      mt = 1 - t;
      bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
      bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
    }

    bounds[0][jlen] = x0;
    bounds[1][jlen] = y0;
    bounds[0][jlen + 1] = x3;
    bounds[1][jlen + 1] = y3;
    bounds[0].length = bounds[1].length = jlen + 2;

    return {
      x0: mmin.apply(0, bounds[0]),
      y0: mmin.apply(0, bounds[1]),
      x1: mmax.apply(0, bounds[0]),
      y1: mmax.apply(0, bounds[1])
    };
  }

  function pathToCurve(path) {

    var pth = paths(path);

    // return cached curve, if existing
    if (pth.curve) {
      return pathClone(pth.curve);
    }

    var curvedPath = pathToAbsolute(path),
        attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null },
        processPath = function(path, d, pathCommand) {
          var nx, ny;

          if (!path) {
            return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
          }

          !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null);

          switch (path[0]) {
          case 'M':
            d.X = path[1];
            d.Y = path[2];
            break;
          case 'A':
            path = ['C'].concat(arcToCurve.apply(0, [d.x, d.y].concat(path.slice(1))));
            break;
          case 'S':
            if (pathCommand == 'C' || pathCommand == 'S') {

              // In 'S' case we have to take into account, if the previous command is C/S.
              nx = d.x * 2 - d.bx;

              // And reflect the previous
              ny = d.y * 2 - d.by;

              // command's control point relative to the current point.
            }
            else {

              // or some else or nothing
              nx = d.x;
              ny = d.y;
            }
            path = ['C', nx, ny].concat(path.slice(1));
            break;
          case 'T':
            if (pathCommand == 'Q' || pathCommand == 'T') {

              // In 'T' case we have to take into account, if the previous command is Q/T.
              d.qx = d.x * 2 - d.qx;

              // And make a reflection similar
              d.qy = d.y * 2 - d.qy;

              // to case 'S'.
            }
            else {

              // or something else or nothing
              d.qx = d.x;
              d.qy = d.y;
            }
            path = ['C'].concat(qubicToCurve(d.x, d.y, d.qx, d.qy, path[1], path[2]));
            break;
          case 'Q':
            d.qx = path[1];
            d.qy = path[2];
            path = ['C'].concat(qubicToCurve(d.x, d.y, path[1], path[2], path[3], path[4]));
            break;
          case 'L':
            path = ['C'].concat(lineToCurve(d.x, d.y, path[1], path[2]));
            break;
          case 'H':
            path = ['C'].concat(lineToCurve(d.x, d.y, path[1], d.y));
            break;
          case 'V':
            path = ['C'].concat(lineToCurve(d.x, d.y, d.x, path[1]));
            break;
          case 'Z':
            path = ['C'].concat(lineToCurve(d.x, d.y, d.X, d.Y));
            break;
          }

          return path;
        },

        fixArc = function(pp, i) {

          if (pp[i].length > 7) {
            pp[i].shift();
            var pi = pp[i];

            while (pi.length) {
              pathCommands[i] = 'A'; // if created multiple C:s, their original seg is saved
              pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
            }

            pp.splice(i, 1);
            ii = curvedPath.length;
          }
        },

        pathCommands = [], // path commands of original path p
        pfirst = '', // temporary holder for original path command
        pathCommand = ''; // holder for previous path command of original path

    for (var i = 0, ii = curvedPath.length; i < ii; i++) {
      curvedPath[i] && (pfirst = curvedPath[i][0]); // save current path command

      if (pfirst != 'C') // C is not saved yet, because it may be result of conversion
      {
        pathCommands[i] = pfirst; // Save current path command
        i && (pathCommand = pathCommands[i - 1]); // Get previous path command pathCommand
      }
      curvedPath[i] = processPath(curvedPath[i], attrs, pathCommand); // Previous path command is inputted to processPath

      if (pathCommands[i] != 'A' && pfirst == 'C') pathCommands[i] = 'C'; // A is the only command
      // which may produce multiple C:s
      // so we have to make sure that C is also C in original path

      fixArc(curvedPath, i); // fixArc adds also the right amount of A:s to pathCommands

      var seg = curvedPath[i],
          seglen = seg.length;

      attrs.x = seg[seglen - 2];
      attrs.y = seg[seglen - 1];
      attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
      attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
    }

    // cache curve
    pth.curve = pathClone(curvedPath);

    return curvedPath;
  }

  var intersect = findPathIntersections;

  function roundBounds(bounds) {
    return {
      x: Math.round(bounds.x),
      y: Math.round(bounds.y),
      width: Math.round(bounds.width),
      height: Math.round(bounds.height)
    };
  }


  function roundPoint(point) {

    return {
      x: Math.round(point.x),
      y: Math.round(point.y)
    };
  }


  /**
   * Convert the given bounds to a { top, left, bottom, right } descriptor.
   *
   * @param {Bounds|Point} bounds
   *
   * @return {Object}
   */
  function asTRBL(bounds) {
    return {
      top: bounds.y,
      right: bounds.x + (bounds.width || 0),
      bottom: bounds.y + (bounds.height || 0),
      left: bounds.x
    };
  }


  /**
   * Convert a { top, left, bottom, right } to an objects bounds.
   *
   * @param {Object} trbl
   *
   * @return {Bounds}
   */
  function asBounds(trbl) {
    return {
      x: trbl.left,
      y: trbl.top,
      width: trbl.right - trbl.left,
      height: trbl.bottom - trbl.top
    };
  }


  /**
   * Get the mid of the given bounds or point.
   *
   * @param {Bounds|Point} bounds
   *
   * @return {Point}
   */
  function getBoundsMid(bounds) {
    return roundPoint({
      x: bounds.x + (bounds.width || 0) / 2,
      y: bounds.y + (bounds.height || 0) / 2
    });
  }


  /**
   * Get the mid of the given Connection.
   *
   * @param {djs.Base.Connection} connection
   *
   * @return {Point}
   */
  function getConnectionMid(connection) {
    var waypoints = connection.waypoints;

    // calculate total length and length of each segment
    var parts = waypoints.reduce(function(parts, point, index) {

      var lastPoint = waypoints[index - 1];

      if (lastPoint) {
        var lastPart = parts[parts.length - 1];

        var startLength = lastPart && lastPart.endLength || 0;
        var length = distance(lastPoint, point);

        parts.push({
          start: lastPoint,
          end: point,
          startLength: startLength,
          endLength: startLength + length,
          length: length
        });
      }

      return parts;
    }, []);

    var totalLength = parts.reduce(function(length, part) {
      return length + part.length;
    }, 0);

    // find which segement contains middle point
    var midLength = totalLength / 2;

    var i = 0;
    var midSegment = parts[i];

    while (midSegment.endLength < midLength) {
      midSegment = parts[++i];
    }

    // calculate relative position on mid segment
    var segmentProgress = (midLength - midSegment.startLength) / midSegment.length;

    var midPoint = {
      x: midSegment.start.x + (midSegment.end.x - midSegment.start.x) * segmentProgress,
      y: midSegment.start.y + (midSegment.end.y - midSegment.start.y) * segmentProgress
    };

    return midPoint;
  }


  /**
   * Get the mid of the given Element.
   *
   * @param {djs.Base.Connection} connection
   *
   * @return {Point}
   */
  function getMid(element) {
    if (isConnection$f(element)) {
      return getConnectionMid(element);
    }

    return getBoundsMid(element);
  }

  // orientation utils //////////////////////

  /**
   * Get orientation of the given rectangle with respect to
   * the reference rectangle.
   *
   * A padding (positive or negative) may be passed to influence
   * horizontal / vertical orientation and intersection.
   *
   * @param {Bounds} rect
   * @param {Bounds} reference
   * @param {Point|number} padding
   *
   * @return {string} the orientation; one of top, top-left, left, ..., bottom, right or intersect.
   */
  function getOrientation(rect, reference, padding) {

    padding = padding || 0;

    // make sure we can use an object, too
    // for individual { x, y } padding
    if (!isObject(padding)) {
      padding = { x: padding, y: padding };
    }


    var rectOrientation = asTRBL(rect),
        referenceOrientation = asTRBL(reference);

    var top = rectOrientation.bottom + padding.y <= referenceOrientation.top,
        right = rectOrientation.left - padding.x >= referenceOrientation.right,
        bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom,
        left = rectOrientation.right + padding.x <= referenceOrientation.left;

    var vertical = top ? 'top' : (bottom ? 'bottom' : null),
        horizontal = left ? 'left' : (right ? 'right' : null);

    if (horizontal && vertical) {
      return vertical + '-' + horizontal;
    } else {
      return horizontal || vertical || 'intersect';
    }
  }


  // intersection utils //////////////////////

  /**
   * Get intersection between an element and a line path.
   *
   * @param {PathDef} elementPath
   * @param {PathDef} linePath
   * @param {boolean} cropStart crop from start or end
   *
   * @return {Point}
   */
  function getElementLineIntersection(elementPath, linePath, cropStart) {

    var intersections = getIntersections(elementPath, linePath);

    // recognize intersections
    // only one -> choose
    // two close together -> choose first
    // two or more distinct -> pull out appropriate one
    // none -> ok (fallback to point itself)
    if (intersections.length === 1) {
      return roundPoint(intersections[0]);
    } else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
      return roundPoint(intersections[0]);
    } else if (intersections.length > 1) {

      // sort by intersections based on connection segment +
      // distance from start
      intersections = sortBy(intersections, function(i) {
        var distance = Math.floor(i.t2 * 100) || 1;

        distance = 100 - distance;

        distance = (distance < 10 ? '0' : '') + distance;

        // create a sort string that makes sure we sort
        // line segment ASC + line segment position DESC (for cropStart)
        // line segment ASC + line segment position ASC (for cropEnd)
        return i.segment2 + '#' + distance;
      });

      return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
    }

    return null;
  }


  function getIntersections(a, b) {
    return intersect(a, b);
  }


  function filterRedundantWaypoints(waypoints) {

    // alter copy of waypoints, not original
    waypoints = waypoints.slice();

    var idx = 0,
        point,
        previousPoint,
        nextPoint;

    while (waypoints[idx]) {
      point = waypoints[idx];
      previousPoint = waypoints[idx - 1];
      nextPoint = waypoints[idx + 1];

      if (pointDistance(point, nextPoint) === 0 ||
          pointsOnLine(previousPoint, nextPoint, point)) {

        // remove point, if overlapping with {nextPoint}
        // or on line with {previousPoint} -> {point} -> {nextPoint}
        waypoints.splice(idx, 1);
      } else {
        idx++;
      }
    }

    return waypoints;
  }

  // helpers //////////////////////

  function distance(a, b) {
    return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
  }

  function isConnection$f(element) {
    return !!element.waypoints;
  }

  function round$b(number, resolution) {
    return Math.round(number * resolution) / resolution;
  }

  function ensurePx(number) {
    return isNumber(number) ? number + 'px' : number;
  }

  function findRoot(element) {
    while (element.parent) {
      element = element.parent;
    }

    return element;
  }

  /**
   * Creates a HTML container element for a SVG element with
   * the given configuration
   *
   * @param  {Object} options
   * @return {HTMLElement} the container element
   */
  function createContainer(options) {

    options = assign({}, { width: '100%', height: '100%' }, options);

    var container = options.container || document.body;

    // create a <div> around the svg element with the respective size
    // this way we can always get the correct container size
    // (this is impossible for <svg> elements at the moment)
    var parent = document.createElement('div');
    parent.setAttribute('class', 'djs-container');

    assign$1(parent, {
      position: 'relative',
      overflow: 'hidden',
      width: ensurePx(options.width),
      height: ensurePx(options.height)
    });

    container.appendChild(parent);

    return parent;
  }

  function createGroup(parent, cls, childIndex) {
    var group = create$1('g');
    classes(group).add(cls);

    var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1;

    // must ensure second argument is node or _null_
    // cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
    parent.insertBefore(group, parent.childNodes[index] || null);

    return group;
  }

  var BASE_LAYER = 'base';

  // render plane contents behind utility layers
  var PLANE_LAYER_INDEX = 0;
  var UTILITY_LAYER_INDEX = 1;


  var REQUIRED_MODEL_ATTRS = {
    shape: [ 'x', 'y', 'width', 'height' ],
    connection: [ 'waypoints' ]
  };

  /**
   * The main drawing canvas.
   *
   * @class
   * @constructor
   *
   * @emits Canvas#canvas.init
   *
   * @param {Object} config
   * @param {EventBus} eventBus
   * @param {GraphicsFactory} graphicsFactory
   * @param {ElementRegistry} elementRegistry
   */
  function Canvas(config, eventBus, graphicsFactory, elementRegistry) {

    this._eventBus = eventBus;
    this._elementRegistry = elementRegistry;
    this._graphicsFactory = graphicsFactory;

    this._rootsIdx = 0;

    this._layers = {};
    this._planes = [];
    this._rootElement = null;

    this._init(config || {});
  }

  Canvas.$inject = [
    'config.canvas',
    'eventBus',
    'graphicsFactory',
    'elementRegistry'
  ];

  /**
   * Creates a <svg> element that is wrapped into a <div>.
   * This way we are always able to correctly figure out the size of the svg element
   * by querying the parent node.

   * (It is not possible to get the size of a svg element cross browser @ 2014-04-01)

   * <div class="djs-container" style="width: {desired-width}, height: {desired-height}">
   *   <svg width="100%" height="100%">
   *    ...
   *   </svg>
   * </div>
   */
  Canvas.prototype._init = function(config) {

    var eventBus = this._eventBus;

    // html container
    var container = this._container = createContainer(config);

    var svg = this._svg = create$1('svg');
    attr(svg, { width: '100%', height: '100%' });

    append(container, svg);

    var viewport = this._viewport = createGroup(svg, 'viewport');

    // debounce canvas.viewbox.changed events
    // for smoother diagram interaction
    if (config.deferUpdate !== false) {
      this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300);
    }

    eventBus.on('diagram.init', function() {

      /**
       * An event indicating that the canvas is ready to be drawn on.
       *
       * @memberOf Canvas
       *
       * @event canvas.init
       *
       * @type {Object}
       * @property {SVGElement} svg the created svg element
       * @property {SVGElement} viewport the direct parent of diagram elements and shapes
       */
      eventBus.fire('canvas.init', {
        svg: svg,
        viewport: viewport
      });

    }, this);

    // reset viewbox on shape changes to
    // recompute the viewbox
    eventBus.on([
      'shape.added',
      'connection.added',
      'shape.removed',
      'connection.removed',
      'elements.changed',
      'root.set'
    ], function() {
      delete this._cachedViewbox;
    }, this);

    eventBus.on('diagram.destroy', 500, this._destroy, this);
    eventBus.on('diagram.clear', 500, this._clear, this);
  };

  Canvas.prototype._destroy = function(emit) {
    this._eventBus.fire('canvas.destroy', {
      svg: this._svg,
      viewport: this._viewport
    });

    var parent = this._container.parentNode;

    if (parent) {
      parent.removeChild(this._container);
    }

    delete this._svg;
    delete this._container;
    delete this._layers;
    delete this._planes;
    delete this._rootElement;
    delete this._viewport;
  };

  Canvas.prototype._clear = function() {

    var self = this;

    var allElements = this._elementRegistry.getAll();

    // remove all elements
    allElements.forEach(function(element) {
      var type = getType(element);

      if (type === 'root') {
        self.removeRootElement(element);
      } else {
        self._removeElement(element, type);
      }
    });

    // remove all planes
    this._planes = [];
    this._rootElement = null;

    // force recomputation of view box
    delete this._cachedViewbox;
  };

  /**
   * Returns the default layer on which
   * all elements are drawn.
   *
   * @returns {SVGElement}
   */
  Canvas.prototype.getDefaultLayer = function() {
    return this.getLayer(BASE_LAYER, PLANE_LAYER_INDEX);
  };

  /**
   * Returns a layer that is used to draw elements
   * or annotations on it.
   *
   * Non-existing layers retrieved through this method
   * will be created. During creation, the optional index
   * may be used to create layers below or above existing layers.
   * A layer with a certain index is always created above all
   * existing layers with the same index.
   *
   * @param {string} name
   * @param {number} index
   *
   * @returns {SVGElement}
   */
  Canvas.prototype.getLayer = function(name, index) {

    if (!name) {
      throw new Error('must specify a name');
    }

    var layer = this._layers[name];

    if (!layer) {
      layer = this._layers[name] = this._createLayer(name, index);
    }

    // throw an error if layer creation / retrival is
    // requested on different index
    if (typeof index !== 'undefined' && layer.index !== index) {
      throw new Error('layer <' + name + '> already created at index <' + index + '>');
    }

    return layer.group;
  };

  /**
   * For a given index, return the number of layers that have a higher index and
   * are visible.
   *
   * This is used to determine the node a layer should be inserted at.
   *
   * @param {Number} index
   * @returns {Number}
   */
  Canvas.prototype._getChildIndex = function(index) {
    return reduce(this._layers, function(childIndex, layer) {
      if (layer.visible && index >= layer.index) {
        childIndex++;
      }

      return childIndex;
    }, 0);
  };

  /**
   * Creates a given layer and returns it.
   *
   * @param {string} name
   * @param {number} [index=0]
   *
   * @return {Object} layer descriptor with { index, group: SVGGroup }
   */
  Canvas.prototype._createLayer = function(name, index) {

    if (typeof index === 'undefined') {
      index = UTILITY_LAYER_INDEX;
    }

    var childIndex = this._getChildIndex(index);

    return {
      group: createGroup(this._viewport, 'layer-' + name, childIndex),
      index: index,
      visible: true
    };
  };


  /**
   * Shows a given layer.
   *
   * @param {String} layer
   * @returns {SVGElement}
   */
  Canvas.prototype.showLayer = function(name) {

    if (!name) {
      throw new Error('must specify a name');
    }

    var layer = this._layers[name];

    if (!layer) {
      throw new Error('layer <' + name + '> does not exist');
    }

    var viewport = this._viewport;
    var group = layer.group;
    var index = layer.index;

    if (layer.visible) {
      return group;
    }

    var childIndex = this._getChildIndex(index);

    viewport.insertBefore(group, viewport.childNodes[childIndex] || null);

    layer.visible = true;

    return group;
  };

  /**
   * Hides a given layer.
   *
   * @param {String} layer
   * @returns {SVGElement}
   */
  Canvas.prototype.hideLayer = function(name) {

    if (!name) {
      throw new Error('must specify a name');
    }

    var layer = this._layers[name];

    if (!layer) {
      throw new Error('layer <' + name + '> does not exist');
    }

    var group = layer.group;

    if (!layer.visible) {
      return group;
    }

    remove$1(group);

    layer.visible = false;

    return group;
  };


  Canvas.prototype._removeLayer = function(name) {

    var layer = this._layers[name];

    if (layer) {
      delete this._layers[name];

      remove$1(layer.group);
    }
  };

  /**
   * Returns the currently active layer. Can be null.
   *
   * @returns {SVGElement|null}
   */
  Canvas.prototype.getActiveLayer = function() {
    var plane = this._findPlaneForRoot(this.getRootElement());

    if (!plane) {
      return null;
    }

    return plane.layer;
  };


  /**
   * Returns the plane which contains the given element.
   *
   * @param {string|djs.model.Base} element
   *
   * @return {djs.model.Base} root for element
   */
  Canvas.prototype.findRoot = function(element) {
    if (typeof element === 'string') {
      element = this._elementRegistry.get(element);
    }

    if (!element) {
      return;
    }

    var plane = this._findPlaneForRoot(
      findRoot(element)
    ) || {};

    return plane.rootElement;
  };

  /**
   * Return a list of all root elements on the diagram.
   *
   * @return {djs.model.Root[]}
   */
  Canvas.prototype.getRootElements = function() {
    return this._planes.map(function(plane) {
      return plane.rootElement;
    });
  };

  Canvas.prototype._findPlaneForRoot = function(rootElement) {
    return find(this._planes, function(plane) {
      return plane.rootElement === rootElement;
    });
  };


  /**
   * Returns the html element that encloses the
   * drawing canvas.
   *
   * @return {DOMNode}
   */
  Canvas.prototype.getContainer = function() {
    return this._container;
  };


  // markers //////////////////////

  Canvas.prototype._updateMarker = function(element, marker, add) {
    var container;

    if (!element.id) {
      element = this._elementRegistry.get(element);
    }

    // we need to access all
    container = this._elementRegistry._elements[element.id];

    if (!container) {
      return;
    }

    forEach$1([ container.gfx, container.secondaryGfx ], function(gfx) {
      if (gfx) {

        // invoke either addClass or removeClass based on mode
        if (add) {
          classes(gfx).add(marker);
        } else {
          classes(gfx).remove(marker);
        }
      }
    });

    /**
     * An event indicating that a marker has been updated for an element
     *
     * @event element.marker.update
     * @type {Object}
     * @property {djs.model.Element} element the shape
     * @property {Object} gfx the graphical representation of the shape
     * @property {string} marker
     * @property {boolean} add true if the marker was added, false if it got removed
     */
    this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add });
  };


  /**
   * Adds a marker to an element (basically a css class).
   *
   * Fires the element.marker.update event, making it possible to
   * integrate extension into the marker life-cycle, too.
   *
   * @example
   * canvas.addMarker('foo', 'some-marker');
   *
   * var fooGfx = canvas.getGraphics('foo');
   *
   * fooGfx; // <g class="... some-marker"> ... </g>
   *
   * @param {string|djs.model.Base} element
   * @param {string} marker
   */
  Canvas.prototype.addMarker = function(element, marker) {
    this._updateMarker(element, marker, true);
  };


  /**
   * Remove a marker from an element.
   *
   * Fires the element.marker.update event, making it possible to
   * integrate extension into the marker life-cycle, too.
   *
   * @param  {string|djs.model.Base} element
   * @param  {string} marker
   */
  Canvas.prototype.removeMarker = function(element, marker) {
    this._updateMarker(element, marker, false);
  };

  /**
   * Check the existence of a marker on element.
   *
   * @param  {string|djs.model.Base} element
   * @param  {string} marker
   */
  Canvas.prototype.hasMarker = function(element, marker) {
    if (!element.id) {
      element = this._elementRegistry.get(element);
    }

    var gfx = this.getGraphics(element);

    return classes(gfx).has(marker);
  };

  /**
   * Toggles a marker on an element.
   *
   * Fires the element.marker.update event, making it possible to
   * integrate extension into the marker life-cycle, too.
   *
   * @param  {string|djs.model.Base} element
   * @param  {string} marker
   */
  Canvas.prototype.toggleMarker = function(element, marker) {
    if (this.hasMarker(element, marker)) {
      this.removeMarker(element, marker);
    } else {
      this.addMarker(element, marker);
    }
  };

  /**
   * Returns the current root element.
   *
   * Supports two different modes for handling root elements:
   *
   * 1. if no root element has been added before, an implicit root will be added
   * and returned. This is used in applications that don't require explicit
   * root elements.
   *
   * 2. when root elements have been added before calling `getRootElement`,
   * root elements can be null. This is used for applications that want to manage
   * root elements themselves.
   *
   * @returns {Object|djs.model.Root|null} rootElement.
   */
  Canvas.prototype.getRootElement = function() {
    var rootElement = this._rootElement;

    // can return null if root elements are present but none was set yet
    if (rootElement || this._planes.length) {
      return rootElement;
    }

    return this.setRootElement(this.addRootElement(null));
  };

  /**
   * Adds a given root element and returns it.
   *
   * @param {Object|djs.model.Root} rootElement
   *
   * @return {Object|djs.model.Root} rootElement
   */

  Canvas.prototype.addRootElement = function(rootElement) {
    var idx = this._rootsIdx++;

    if (!rootElement) {
      rootElement = {
        id: '__implicitroot_' + idx,
        children: [],
        isImplicit: true
      };
    }

    var layerName = rootElement.layer = 'root-' + idx;

    this._ensureValid('root', rootElement);

    var layer = this.getLayer(layerName, PLANE_LAYER_INDEX);

    this.hideLayer(layerName);

    this._addRoot(rootElement, layer);

    this._planes.push({
      rootElement: rootElement,
      layer: layer
    });

    return rootElement;
  };

  /**
   * Removes a given rootElement and returns it.
   *
   * @param {djs.model.Root|String} rootElement
   *
   * @return {Object|djs.model.Root} rootElement
   */
  Canvas.prototype.removeRootElement = function(rootElement) {

    if (typeof rootElement === 'string') {
      rootElement = this._elementRegistry.get(rootElement);
    }

    var plane = this._findPlaneForRoot(rootElement);

    if (!plane) {
      return;
    }

    // hook up life-cycle events
    this._removeRoot(rootElement);

    // clean up layer
    this._removeLayer(rootElement.layer);

    // clean up plane
    this._planes = this._planes.filter(function(plane) {
      return plane.rootElement !== rootElement;
    });

    // clean up active root
    if (this._rootElement === rootElement) {
      this._rootElement = null;
    }

    return rootElement;
  };


  // root element handling //////////////////////

  /**
   * Sets a given element as the new root element for the canvas
   * and returns the new root element.
   *
   * @param {Object|djs.model.Root} rootElement
   *
   * @return {Object|djs.model.Root} new root element
   */
  Canvas.prototype.setRootElement = function(rootElement, override) {

    if (isDefined(override)) {
      throw new Error('override not supported');
    }

    if (rootElement === this._rootElement) {
      return;
    }

    var plane;

    if (!rootElement) {
      throw new Error('rootElement required');
    }

    plane = this._findPlaneForRoot(rootElement);

    // give set add semantics for backwards compatibility
    if (!plane) {
      rootElement = this.addRootElement(rootElement);
    }

    this._setRoot(rootElement);

    return rootElement;
  };


  Canvas.prototype._removeRoot = function(element) {
    var elementRegistry = this._elementRegistry,
        eventBus = this._eventBus;

    // simulate element remove event sequence
    eventBus.fire('root.remove', { element: element });
    eventBus.fire('root.removed', { element: element });

    elementRegistry.remove(element);
  };


  Canvas.prototype._addRoot = function(element, gfx) {
    var elementRegistry = this._elementRegistry,
        eventBus = this._eventBus;

    // resemble element add event sequence
    eventBus.fire('root.add', { element: element });

    elementRegistry.add(element, gfx);

    eventBus.fire('root.added', { element: element, gfx: gfx });
  };


  Canvas.prototype._setRoot = function(rootElement, layer) {

    var currentRoot = this._rootElement;

    if (currentRoot) {

      // un-associate previous root element <svg>
      this._elementRegistry.updateGraphics(currentRoot, null, true);

      // hide previous layer
      this.hideLayer(currentRoot.layer);
    }

    if (rootElement) {

      if (!layer) {
        layer = this._findPlaneForRoot(rootElement).layer;
      }

      // associate element with <svg>
      this._elementRegistry.updateGraphics(rootElement, this._svg, true);

      // show root layer
      this.showLayer(rootElement.layer);
    }

    this._rootElement = rootElement;

    this._eventBus.fire('root.set', { element: rootElement });
  };

  // add functionality //////////////////////

  Canvas.prototype._ensureValid = function(type, element) {
    if (!element.id) {
      throw new Error('element must have an id');
    }

    if (this._elementRegistry.get(element.id)) {
      throw new Error('element <' + element.id + '> already exists');
    }

    var requiredAttrs = REQUIRED_MODEL_ATTRS[type];

    var valid = every(requiredAttrs, function(attr) {
      return typeof element[attr] !== 'undefined';
    });

    if (!valid) {
      throw new Error(
        'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type);
    }
  };

  Canvas.prototype._setParent = function(element, parent, parentIndex) {
    add(parent.children, element, parentIndex);
    element.parent = parent;
  };

  /**
   * Adds an element to the canvas.
   *
   * This wires the parent <-> child relationship between the element and
   * a explicitly specified parent or an implicit root element.
   *
   * During add it emits the events
   *
   *  * <{type}.add> (element, parent)
   *  * <{type}.added> (element, gfx)
   *
   * Extensions may hook into these events to perform their magic.
   *
   * @param {string} type
   * @param {Object|djs.model.Base} element
   * @param {Object|djs.model.Base} [parent]
   * @param {number} [parentIndex]
   *
   * @return {Object|djs.model.Base} the added element
   */
  Canvas.prototype._addElement = function(type, element, parent, parentIndex) {

    parent = parent || this.getRootElement();

    var eventBus = this._eventBus,
        graphicsFactory = this._graphicsFactory;

    this._ensureValid(type, element);

    eventBus.fire(type + '.add', { element: element, parent: parent });

    this._setParent(element, parent, parentIndex);

    // create graphics
    var gfx = graphicsFactory.create(type, element, parentIndex);

    this._elementRegistry.add(element, gfx);

    // update its visual
    graphicsFactory.update(type, element, gfx);

    eventBus.fire(type + '.added', { element: element, gfx: gfx });

    return element;
  };

  /**
   * Adds a shape to the canvas
   *
   * @param {Object|djs.model.Shape} shape to add to the diagram
   * @param {djs.model.Base} [parent]
   * @param {number} [parentIndex]
   *
   * @return {djs.model.Shape} the added shape
   */
  Canvas.prototype.addShape = function(shape, parent, parentIndex) {
    return this._addElement('shape', shape, parent, parentIndex);
  };

  /**
   * Adds a connection to the canvas
   *
   * @param {Object|djs.model.Connection} connection to add to the diagram
   * @param {djs.model.Base} [parent]
   * @param {number} [parentIndex]
   *
   * @return {djs.model.Connection} the added connection
   */
  Canvas.prototype.addConnection = function(connection, parent, parentIndex) {
    return this._addElement('connection', connection, parent, parentIndex);
  };


  /**
   * Internal remove element
   */
  Canvas.prototype._removeElement = function(element, type) {

    var elementRegistry = this._elementRegistry,
        graphicsFactory = this._graphicsFactory,
        eventBus = this._eventBus;

    element = elementRegistry.get(element.id || element);

    if (!element) {

      // element was removed already
      return;
    }

    eventBus.fire(type + '.remove', { element: element });

    graphicsFactory.remove(element);

    // unset parent <-> child relationship
    remove(element.parent && element.parent.children, element);
    element.parent = null;

    eventBus.fire(type + '.removed', { element: element });

    elementRegistry.remove(element);

    return element;
  };


  /**
   * Removes a shape from the canvas
   *
   * @param {string|djs.model.Shape} shape or shape id to be removed
   *
   * @return {djs.model.Shape} the removed shape
   */
  Canvas.prototype.removeShape = function(shape) {

    /**
     * An event indicating that a shape is about to be removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event shape.remove
     * @type {Object}
     * @property {djs.model.Shape} element the shape descriptor
     * @property {Object} gfx the graphical representation of the shape
     */

    /**
     * An event indicating that a shape has been removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event shape.removed
     * @type {Object}
     * @property {djs.model.Shape} element the shape descriptor
     * @property {Object} gfx the graphical representation of the shape
     */
    return this._removeElement(shape, 'shape');
  };


  /**
   * Removes a connection from the canvas
   *
   * @param {string|djs.model.Connection} connection or connection id to be removed
   *
   * @return {djs.model.Connection} the removed connection
   */
  Canvas.prototype.removeConnection = function(connection) {

    /**
     * An event indicating that a connection is about to be removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event connection.remove
     * @type {Object}
     * @property {djs.model.Connection} element the connection descriptor
     * @property {Object} gfx the graphical representation of the connection
     */

    /**
     * An event indicating that a connection has been removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event connection.removed
     * @type {Object}
     * @property {djs.model.Connection} element the connection descriptor
     * @property {Object} gfx the graphical representation of the connection
     */
    return this._removeElement(connection, 'connection');
  };


  /**
   * Return the graphical object underlaying a certain diagram element
   *
   * @param {string|djs.model.Base} element descriptor of the element
   * @param {boolean} [secondary=false] whether to return the secondary connected element
   *
   * @return {SVGElement}
   */
  Canvas.prototype.getGraphics = function(element, secondary) {
    return this._elementRegistry.getGraphics(element, secondary);
  };


  /**
   * Perform a viewbox update via a given change function.
   *
   * @param {Function} changeFn
   */
  Canvas.prototype._changeViewbox = function(changeFn) {

    // notify others of the upcoming viewbox change
    this._eventBus.fire('canvas.viewbox.changing');

    // perform actual change
    changeFn.apply(this);

    // reset the cached viewbox so that
    // a new get operation on viewbox or zoom
    // triggers a viewbox re-computation
    this._cachedViewbox = null;

    // notify others of the change; this step
    // may or may not be debounced
    this._viewboxChanged();
  };

  Canvas.prototype._viewboxChanged = function() {
    this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() });
  };


  /**
   * Gets or sets the view box of the canvas, i.e. the
   * area that is currently displayed.
   *
   * The getter may return a cached viewbox (if it is currently
   * changing). To force a recomputation, pass `false` as the first argument.
   *
   * @example
   *
   * canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 })
   *
   * // sets the visible area of the diagram to (100|100) -> (600|100)
   * // and and scales it according to the diagram width
   *
   * var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box.
   *
   * console.log(viewbox);
   * // {
   * //   inner: Dimensions,
   * //   outer: Dimensions,
   * //   scale,
   * //   x, y,
   * //   width, height
   * // }
   *
   * // if the current diagram is zoomed and scrolled, you may reset it to the
   * // default zoom via this method, too:
   *
   * var zoomedAndScrolledViewbox = canvas.viewbox();
   *
   * canvas.viewbox({
   *   x: 0,
   *   y: 0,
   *   width: zoomedAndScrolledViewbox.outer.width,
   *   height: zoomedAndScrolledViewbox.outer.height
   * });
   *
   * @param  {Object} [box] the new view box to set
   * @param  {number} box.x the top left X coordinate of the canvas visible in view box
   * @param  {number} box.y the top left Y coordinate of the canvas visible in view box
   * @param  {number} box.width the visible width
   * @param  {number} box.height
   *
   * @return {Object} the current view box
   */
  Canvas.prototype.viewbox = function(box) {

    if (box === undefined && this._cachedViewbox) {
      return this._cachedViewbox;
    }

    var viewport = this._viewport,
        innerBox,
        outerBox = this.getSize(),
        matrix,
        activeLayer,
        transform,
        scale,
        x, y;

    if (!box) {

      // compute the inner box based on the
      // diagrams active layer. This allows us to exclude
      // external components, such as overlays

      activeLayer = this._rootElement ? this.getActiveLayer() : null;
      innerBox = activeLayer && activeLayer.getBBox() || {};

      transform = transform$1(viewport);
      matrix = transform ? transform.matrix : createMatrix();
      scale = round$b(matrix.a, 1000);

      x = round$b(-matrix.e || 0, 1000);
      y = round$b(-matrix.f || 0, 1000);

      box = this._cachedViewbox = {
        x: x ? x / scale : 0,
        y: y ? y / scale : 0,
        width: outerBox.width / scale,
        height: outerBox.height / scale,
        scale: scale,
        inner: {
          width: innerBox.width || 0,
          height: innerBox.height || 0,
          x: innerBox.x || 0,
          y: innerBox.y || 0
        },
        outer: outerBox
      };

      return box;
    } else {

      this._changeViewbox(function() {
        scale = Math.min(outerBox.width / box.width, outerBox.height / box.height);

        var matrix = this._svg.createSVGMatrix()
          .scale(scale)
          .translate(-box.x, -box.y);

        transform$1(viewport, matrix);
      });
    }

    return box;
  };


  /**
   * Gets or sets the scroll of the canvas.
   *
   * @param {Object} [delta] the new scroll to apply.
   *
   * @param {number} [delta.dx]
   * @param {number} [delta.dy]
   */
  Canvas.prototype.scroll = function(delta) {

    var node = this._viewport;
    var matrix = node.getCTM();

    if (delta) {
      this._changeViewbox(function() {
        delta = assign({ dx: 0, dy: 0 }, delta || {});

        matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix);

        setCTM(node, matrix);
      });
    }

    return { x: matrix.e, y: matrix.f };
  };

  /**
   * Scrolls the viewbox to contain the given element.
   * Optionally specify a padding to be applied to the edges.
   *
   * @param {Object|String} [element] the element to scroll to.
   * @param {Object|Number} [padding=100] the padding to be applied. Can also specify top, bottom, left and right.
   *
   */
  Canvas.prototype.scrollToElement = function(element, padding) {
    var defaultPadding = 100;

    if (typeof element === 'string') {
      element = this._elementRegistry.get(element);
    }

    // set to correct rootElement
    var rootElement = this.findRoot(element);

    if (rootElement !== this.getRootElement()) {
      this.setRootElement(rootElement);
    }

    if (!padding) {
      padding = {};
    }
    if (typeof padding === 'number') {
      defaultPadding = padding;
    }

    padding = {
      top: padding.top || defaultPadding,
      right: padding.right || defaultPadding,
      bottom: padding.bottom || defaultPadding,
      left: padding.left || defaultPadding
    };

    var elementBounds = getBBox(element),
        elementTrbl = asTRBL(elementBounds),
        viewboxBounds = this.viewbox(),
        zoom = this.zoom(),
        dx, dy;

    // shrink viewboxBounds with padding
    viewboxBounds.y += padding.top / zoom;
    viewboxBounds.x += padding.left / zoom;
    viewboxBounds.width -= (padding.right + padding.left) / zoom;
    viewboxBounds.height -= (padding.bottom + padding.top) / zoom;

    var viewboxTrbl = asTRBL(viewboxBounds);

    var canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height;

    if (!canFit) {

      // top-left when element can't fit
      dx = elementBounds.x - viewboxBounds.x;
      dy = elementBounds.y - viewboxBounds.y;

    } else {

      var dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right),
          dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left),
          dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom),
          dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top);

      dx = dRight || dLeft;
      dy = dBottom || dTop;

    }

    this.scroll({ dx: -dx * zoom, dy: -dy * zoom });
  };

  /**
   * Gets or sets the current zoom of the canvas, optionally zooming
   * to the specified position.
   *
   * The getter may return a cached zoom level. Call it with `false` as
   * the first argument to force recomputation of the current level.
   *
   * @param {string|number} [newScale] the new zoom level, either a number, i.e. 0.9,
   *                                   or `fit-viewport` to adjust the size to fit the current viewport
   * @param {string|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null
   *
   * @return {number} the current scale
   */
  Canvas.prototype.zoom = function(newScale, center) {

    if (!newScale) {
      return this.viewbox(newScale).scale;
    }

    if (newScale === 'fit-viewport') {
      return this._fitViewport(center);
    }

    var outer,
        matrix;

    this._changeViewbox(function() {

      if (typeof center !== 'object') {
        outer = this.viewbox().outer;

        center = {
          x: outer.width / 2,
          y: outer.height / 2
        };
      }

      matrix = this._setZoom(newScale, center);
    });

    return round$b(matrix.a, 1000);
  };

  function setCTM(node, m) {
    var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')';
    node.setAttribute('transform', mstr);
  }

  Canvas.prototype._fitViewport = function(center) {

    var vbox = this.viewbox(),
        outer = vbox.outer,
        inner = vbox.inner,
        newScale,
        newViewbox;

    // display the complete diagram without zooming in.
    // instead of relying on internal zoom, we perform a
    // hard reset on the canvas viewbox to realize this
    //
    // if diagram does not need to be zoomed in, we focus it around
    // the diagram origin instead

    if (inner.x >= 0 &&
        inner.y >= 0 &&
        inner.x + inner.width <= outer.width &&
        inner.y + inner.height <= outer.height &&
        !center) {

      newViewbox = {
        x: 0,
        y: 0,
        width: Math.max(inner.width + inner.x, outer.width),
        height: Math.max(inner.height + inner.y, outer.height)
      };
    } else {

      newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height);
      newViewbox = {
        x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0),
        y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0),
        width: outer.width / newScale,
        height: outer.height / newScale
      };
    }

    this.viewbox(newViewbox);

    return this.viewbox(false).scale;
  };


  Canvas.prototype._setZoom = function(scale, center) {

    var svg = this._svg,
        viewport = this._viewport;

    var matrix = svg.createSVGMatrix();
    var point = svg.createSVGPoint();

    var centerPoint,
        originalPoint,
        currentMatrix,
        scaleMatrix,
        newMatrix;

    currentMatrix = viewport.getCTM();

    var currentScale = currentMatrix.a;

    if (center) {
      centerPoint = assign(point, center);

      // revert applied viewport transformations
      originalPoint = centerPoint.matrixTransform(currentMatrix.inverse());

      // create scale matrix
      scaleMatrix = matrix
        .translate(originalPoint.x, originalPoint.y)
        .scale(1 / currentScale * scale)
        .translate(-originalPoint.x, -originalPoint.y);

      newMatrix = currentMatrix.multiply(scaleMatrix);
    } else {
      newMatrix = matrix.scale(scale);
    }

    setCTM(this._viewport, newMatrix);

    return newMatrix;
  };


  /**
   * Returns the size of the canvas
   *
   * @return {Dimensions}
   */
  Canvas.prototype.getSize = function() {
    return {
      width: this._container.clientWidth,
      height: this._container.clientHeight
    };
  };


  /**
   * Return the absolute bounding box for the given element
   *
   * The absolute bounding box may be used to display overlays in the
   * callers (browser) coordinate system rather than the zoomed in/out
   * canvas coordinates.
   *
   * @param  {ElementDescriptor} element
   * @return {Bounds} the absolute bounding box
   */
  Canvas.prototype.getAbsoluteBBox = function(element) {
    var vbox = this.viewbox();
    var bbox;

    // connection
    // use svg bbox
    if (element.waypoints) {
      var gfx = this.getGraphics(element);

      bbox = gfx.getBBox();
    }

    // shapes
    // use data
    else {
      bbox = element;
    }

    var x = bbox.x * vbox.scale - vbox.x * vbox.scale;
    var y = bbox.y * vbox.scale - vbox.y * vbox.scale;

    var width = bbox.width * vbox.scale;
    var height = bbox.height * vbox.scale;

    return {
      x: x,
      y: y,
      width: width,
      height: height
    };
  };

  /**
   * Fires an event in order other modules can react to the
   * canvas resizing
   */
  Canvas.prototype.resized = function() {

    // force recomputation of view box
    delete this._cachedViewbox;

    this._eventBus.fire('canvas.resized');
  };

  var ELEMENT_ID = 'data-element-id';


  /**
   * @class
   *
   * A registry that keeps track of all shapes in the diagram.
   */
  function ElementRegistry(eventBus) {
    this._elements = {};

    this._eventBus = eventBus;
  }

  ElementRegistry.$inject = [ 'eventBus' ];

  /**
   * Register a pair of (element, gfx, (secondaryGfx)).
   *
   * @param {djs.model.Base} element
   * @param {SVGElement} gfx
   * @param {SVGElement} [secondaryGfx] optional other element to register, too
   */
  ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) {

    var id = element.id;

    this._validateId(id);

    // associate dom node with element
    attr(gfx, ELEMENT_ID, id);

    if (secondaryGfx) {
      attr(secondaryGfx, ELEMENT_ID, id);
    }

    this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx };
  };

  /**
   * Removes an element from the registry.
   *
   * @param {djs.model.Base} element
   */
  ElementRegistry.prototype.remove = function(element) {
    var elements = this._elements,
        id = element.id || element,
        container = id && elements[id];

    if (container) {

      // unset element id on gfx
      attr(container.gfx, ELEMENT_ID, '');

      if (container.secondaryGfx) {
        attr(container.secondaryGfx, ELEMENT_ID, '');
      }

      delete elements[id];
    }
  };

  /**
   * Update the id of an element
   *
   * @param {djs.model.Base} element
   * @param {string} newId
   */
  ElementRegistry.prototype.updateId = function(element, newId) {

    this._validateId(newId);

    if (typeof element === 'string') {
      element = this.get(element);
    }

    this._eventBus.fire('element.updateId', {
      element: element,
      newId: newId
    });

    var gfx = this.getGraphics(element),
        secondaryGfx = this.getGraphics(element, true);

    this.remove(element);

    element.id = newId;

    this.add(element, gfx, secondaryGfx);
  };

  /**
   * Update the graphics of an element
   *
   * @param {djs.model.Base} element
   * @param {SVGElement} gfx
   * @param {boolean} [secondary=false] whether to update the secondary connected element
   */
  ElementRegistry.prototype.updateGraphics = function(filter, gfx, secondary) {
    var id = filter.id || filter;

    var container = this._elements[id];

    if (secondary) {
      container.secondaryGfx = gfx;
    } else {
      container.gfx = gfx;
    }

    if (gfx) {
      attr(gfx, ELEMENT_ID, id);
    }

    return gfx;
  };

  /**
   * Return the model element for a given id or graphics.
   *
   * @example
   *
   * elementRegistry.get('SomeElementId_1');
   * elementRegistry.get(gfx);
   *
   *
   * @param {string|SVGElement} filter for selecting the element
   *
   * @return {djs.model.Base}
   */
  ElementRegistry.prototype.get = function(filter) {
    var id;

    if (typeof filter === 'string') {
      id = filter;
    } else {
      id = filter && attr(filter, ELEMENT_ID);
    }

    var container = this._elements[id];
    return container && container.element;
  };

  /**
   * Return all elements that match a given filter function.
   *
   * @param {Function} fn
   *
   * @return {Array<djs.model.Base>}
   */
  ElementRegistry.prototype.filter = function(fn) {

    var filtered = [];

    this.forEach(function(element, gfx) {
      if (fn(element, gfx)) {
        filtered.push(element);
      }
    });

    return filtered;
  };

  /**
   * Return the first element that satisfies the provided testing function.
   *
   * @param {Function} fn
   *
   * @return {djs.model.Base}
   */
  ElementRegistry.prototype.find = function(fn) {
    var map = this._elements,
        keys = Object.keys(map);

    for (var i = 0; i < keys.length; i++) {
      var id = keys[i],
          container = map[id],
          element = container.element,
          gfx = container.gfx;

      if (fn(element, gfx)) {
        return element;
      }
    }
  };

  /**
   * Return all rendered model elements.
   *
   * @return {Array<djs.model.Base>}
   */
  ElementRegistry.prototype.getAll = function() {
    return this.filter(function(e) { return e; });
  };

  /**
   * Iterate over all diagram elements.
   *
   * @param {Function} fn
   */
  ElementRegistry.prototype.forEach = function(fn) {

    var map = this._elements;

    Object.keys(map).forEach(function(id) {
      var container = map[id],
          element = container.element,
          gfx = container.gfx;

      return fn(element, gfx);
    });
  };

  /**
   * Return the graphical representation of an element or its id.
   *
   * @example
   * elementRegistry.getGraphics('SomeElementId_1');
   * elementRegistry.getGraphics(rootElement); // <g ...>
   *
   * elementRegistry.getGraphics(rootElement, true); // <svg ...>
   *
   *
   * @param {string|djs.model.Base} filter
   * @param {boolean} [secondary=false] whether to return the secondary connected element
   *
   * @return {SVGElement}
   */
  ElementRegistry.prototype.getGraphics = function(filter, secondary) {
    var id = filter.id || filter;

    var container = this._elements[id];
    return container && (secondary ? container.secondaryGfx : container.gfx);
  };

  /**
   * Validate the suitability of the given id and signals a problem
   * with an exception.
   *
   * @param {string} id
   *
   * @throws {Error} if id is empty or already assigned
   */
  ElementRegistry.prototype._validateId = function(id) {
    if (!id) {
      throw new Error('element must have an id');
    }

    if (this._elements[id]) {
      throw new Error('element with id ' + id + ' already added');
    }
  };

  var objectRefs = {exports: {}};

  var collection = {};

  /**
   * An empty collection stub. Use {@link RefsCollection.extend} to extend a
   * collection with ref semantics.
   *
   * @class RefsCollection
   */

  /**
   * Extends a collection with {@link Refs} aware methods
   *
   * @memberof RefsCollection
   * @static
   *
   * @param  {Array<Object>} collection
   * @param  {Refs} refs instance
   * @param  {Object} property represented by the collection
   * @param  {Object} target object the collection is attached to
   *
   * @return {RefsCollection<Object>} the extended array
   */
  function extend(collection, refs, property, target) {

    var inverseProperty = property.inverse;

    /**
     * Removes the given element from the array and returns it.
     *
     * @method RefsCollection#remove
     *
     * @param {Object} element the element to remove
     */
    Object.defineProperty(collection, 'remove', {
      value: function(element) {
        var idx = this.indexOf(element);
        if (idx !== -1) {
          this.splice(idx, 1);

          // unset inverse
          refs.unset(element, inverseProperty, target);
        }

        return element;
      }
    });

    /**
     * Returns true if the collection contains the given element
     *
     * @method RefsCollection#contains
     *
     * @param {Object} element the element to check for
     */
    Object.defineProperty(collection, 'contains', {
      value: function(element) {
        return this.indexOf(element) !== -1;
      }
    });

    /**
     * Adds an element to the array, unless it exists already (set semantics).
     *
     * @method RefsCollection#add
     *
     * @param {Object} element the element to add
     * @param {Number} optional index to add element to
     *                 (possibly moving other elements around)
     */
    Object.defineProperty(collection, 'add', {
      value: function(element, idx) {

        var currentIdx = this.indexOf(element);

        if (typeof idx === 'undefined') {

          if (currentIdx !== -1) {
            // element already in collection (!)
            return;
          }

          // add to end of array, as no idx is specified
          idx = this.length;
        }

        // handle already in collection
        if (currentIdx !== -1) {

          // remove element from currentIdx
          this.splice(currentIdx, 1);
        }

        // add element at idx
        this.splice(idx, 0, element);

        if (currentIdx === -1) {
          // set inverse, unless element was
          // in collection already
          refs.set(element, inverseProperty, target);
        }
      }
    });

    // a simple marker, identifying this element
    // as being a refs collection
    Object.defineProperty(collection, '__refs_collection', {
      value: true
    });

    return collection;
  }


  function isExtended(collection) {
    return collection.__refs_collection === true;
  }

  collection.extend = extend;

  collection.isExtended = isExtended;

  var Collection = collection;

  function hasOwnProperty$1(e, property) {
    return Object.prototype.hasOwnProperty.call(e, property.name || property);
  }

  function defineCollectionProperty(ref, property, target) {

    var collection = Collection.extend(target[property.name] || [], ref, property, target);

    Object.defineProperty(target, property.name, {
      enumerable: property.enumerable,
      value: collection
    });

    if (collection.length) {

      collection.forEach(function(o) {
        ref.set(o, property.inverse, target);
      });
    }
  }


  function defineProperty$1(ref, property, target) {

    var inverseProperty = property.inverse;

    var _value = target[property.name];

    Object.defineProperty(target, property.name, {
      configurable: property.configurable,
      enumerable: property.enumerable,

      get: function() {
        return _value;
      },

      set: function(value) {

        // return if we already performed all changes
        if (value === _value) {
          return;
        }

        var old = _value;

        // temporary set null
        _value = null;

        if (old) {
          ref.unset(old, inverseProperty, target);
        }

        // set new value
        _value = value;

        // set inverse value
        ref.set(_value, inverseProperty, target);
      }
    });

  }

  /**
   * Creates a new references object defining two inversly related
   * attribute descriptors a and b.
   *
   * <p>
   *   When bound to an object using {@link Refs#bind} the references
   *   get activated and ensure that add and remove operations are applied
   *   reversely, too.
   * </p>
   *
   * <p>
   *   For attributes represented as collections {@link Refs} provides the
   *   {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions
   *   that must be used to properly hook into the inverse change mechanism.
   * </p>
   *
   * @class Refs
   *
   * @classdesc A bi-directional reference between two attributes.
   *
   * @param {Refs.AttributeDescriptor} a property descriptor
   * @param {Refs.AttributeDescriptor} b property descriptor
   *
   * @example
   *
   * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' });
   *
   * var car = { name: 'toyota' };
   * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }];
   *
   * refs.bind(car, 'wheels');
   *
   * car.wheels // []
   * car.wheels.add(wheels[0]);
   * car.wheels.add(wheels[1]);
   *
   * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }]
   *
   * wheels[0].car // { name: 'toyota' };
   * car.wheels.remove(wheels[0]);
   *
   * wheels[0].car // undefined
   */
  function Refs$1(a, b) {

    if (!(this instanceof Refs$1)) {
      return new Refs$1(a, b);
    }

    // link
    a.inverse = b;
    b.inverse = a;

    this.props = {};
    this.props[a.name] = a;
    this.props[b.name] = b;
  }

  /**
   * Binds one side of a bi-directional reference to a
   * target object.
   *
   * @memberOf Refs
   *
   * @param  {Object} target
   * @param  {String} property
   */
  Refs$1.prototype.bind = function(target, property) {
    if (typeof property === 'string') {
      if (!this.props[property]) {
        throw new Error('no property <' + property + '> in ref');
      }
      property = this.props[property];
    }

    if (property.collection) {
      defineCollectionProperty(this, property, target);
    } else {
      defineProperty$1(this, property, target);
    }
  };

  Refs$1.prototype.ensureRefsCollection = function(target, property) {

    var collection = target[property.name];

    if (!Collection.isExtended(collection)) {
      defineCollectionProperty(this, property, target);
    }

    return collection;
  };

  Refs$1.prototype.ensureBound = function(target, property) {
    if (!hasOwnProperty$1(target, property)) {
      this.bind(target, property);
    }
  };

  Refs$1.prototype.unset = function(target, property, value) {

    if (target) {
      this.ensureBound(target, property);

      if (property.collection) {
        this.ensureRefsCollection(target, property).remove(value);
      } else {
        target[property.name] = undefined;
      }
    }
  };

  Refs$1.prototype.set = function(target, property, value) {

    if (target) {
      this.ensureBound(target, property);

      if (property.collection) {
        this.ensureRefsCollection(target, property).add(value);
      } else {
        target[property.name] = value;
      }
    }
  };

  var refs = Refs$1;

  (function (module) {
  	module.exports = refs;

  	module.exports.Collection = collection;
  } (objectRefs));

  var Refs = /*@__PURE__*/getDefaultExportFromCjs(objectRefs.exports);

  var parentRefs = new Refs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }),
      labelRefs = new Refs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }),
      attacherRefs = new Refs({ name: 'attachers', collection: true }, { name: 'host' }),
      outgoingRefs = new Refs({ name: 'outgoing', collection: true }, { name: 'source' }),
      incomingRefs = new Refs({ name: 'incoming', collection: true }, { name: 'target' });

  /**
   * @namespace djs.model
   */

  /**
   * @memberOf djs.model
   */

  /**
   * The basic graphical representation
   *
   * @class
   *
   * @abstract
   */
  function Base$1() {

    /**
     * The object that backs up the shape
     *
     * @name Base#businessObject
     * @type Object
     */
    Object.defineProperty(this, 'businessObject', {
      writable: true
    });


    /**
     * Single label support, will mapped to multi label array
     *
     * @name Base#label
     * @type Object
     */
    Object.defineProperty(this, 'label', {
      get: function() {
        return this.labels[0];
      },
      set: function(newLabel) {

        var label = this.label,
            labels = this.labels;

        if (!newLabel && label) {
          labels.remove(label);
        } else {
          labels.add(newLabel, 0);
        }
      }
    });

    /**
     * The parent shape
     *
     * @name Base#parent
     * @type Shape
     */
    parentRefs.bind(this, 'parent');

    /**
     * The list of labels
     *
     * @name Base#labels
     * @type Label
     */
    labelRefs.bind(this, 'labels');

    /**
     * The list of outgoing connections
     *
     * @name Base#outgoing
     * @type Array<Connection>
     */
    outgoingRefs.bind(this, 'outgoing');

    /**
     * The list of incoming connections
     *
     * @name Base#incoming
     * @type Array<Connection>
     */
    incomingRefs.bind(this, 'incoming');
  }


  /**
   * A graphical object
   *
   * @class
   * @constructor
   *
   * @extends Base
   */
  function Shape() {
    Base$1.call(this);

    /**
     * Indicates frame shapes
     *
     * @name Shape#isFrame
     * @type boolean
     */

    /**
     * The list of children
     *
     * @name Shape#children
     * @type Array<Base>
     */
    parentRefs.bind(this, 'children');

    /**
     * @name Shape#host
     * @type Shape
     */
    attacherRefs.bind(this, 'host');

    /**
     * @name Shape#attachers
     * @type Shape
     */
    attacherRefs.bind(this, 'attachers');
  }

  e(Shape, Base$1);


  /**
   * A root graphical object
   *
   * @class
   * @constructor
   *
   * @extends Shape
   */
  function Root() {
    Shape.call(this);
  }

  e(Root, Shape);


  /**
   * A label for an element
   *
   * @class
   * @constructor
   *
   * @extends Shape
   */
  function Label() {
    Shape.call(this);

    /**
     * The labeled element
     *
     * @name Label#labelTarget
     * @type Base
     */
    labelRefs.bind(this, 'labelTarget');
  }

  e(Label, Shape);


  /**
   * A connection between two elements
   *
   * @class
   * @constructor
   *
   * @extends Base
   */
  function Connection() {
    Base$1.call(this);

    /**
     * The element this connection originates from
     *
     * @name Connection#source
     * @type Base
     */
    outgoingRefs.bind(this, 'source');

    /**
     * The element this connection points to
     *
     * @name Connection#target
     * @type Base
     */
    incomingRefs.bind(this, 'target');
  }

  e(Connection, Base$1);


  var types$6 = {
    connection: Connection,
    shape: Shape,
    label: Label,
    root: Root
  };

  /**
   * Creates a new model element of the specified type
   *
   * @method create
   *
   * @example
   *
   * var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 });
   * var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 });
   *
   * var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] });
   *
   * @param  {string} type lower-cased model name
   * @param  {Object} attrs attributes to initialize the new model instance with
   *
   * @return {Base} the new model instance
   */
  function create(type, attrs) {
    var Type = types$6[type];
    if (!Type) {
      throw new Error('unknown type: <' + type + '>');
    }
    return assign(new Type(), attrs);
  }

  /**
   * A factory for diagram-js shapes
   */
  function ElementFactory$1() {
    this._uid = 12;
  }


  ElementFactory$1.prototype.createRoot = function(attrs) {
    return this.create('root', attrs);
  };

  ElementFactory$1.prototype.createLabel = function(attrs) {
    return this.create('label', attrs);
  };

  ElementFactory$1.prototype.createShape = function(attrs) {
    return this.create('shape', attrs);
  };

  ElementFactory$1.prototype.createConnection = function(attrs) {
    return this.create('connection', attrs);
  };

  /**
   * Create a model element with the given type and
   * a number of pre-set attributes.
   *
   * @param  {string} type
   * @param  {Object} attrs
   * @return {djs.model.Base} the newly created model instance
   */
  ElementFactory$1.prototype.create = function(type, attrs) {

    attrs = assign({}, attrs || {});

    if (!attrs.id) {
      attrs.id = type + '_' + (this._uid++);
    }

    return create(type, attrs);
  };

  var FN_REF = '__fn';

  var DEFAULT_PRIORITY$5 = 1000;

  var slice = Array.prototype.slice;

  /**
   * A general purpose event bus.
   *
   * This component is used to communicate across a diagram instance.
   * Other parts of a diagram can use it to listen to and broadcast events.
   *
   *
   * ## Registering for Events
   *
   * The event bus provides the {@link EventBus#on} and {@link EventBus#once}
   * methods to register for events. {@link EventBus#off} can be used to
   * remove event registrations. Listeners receive an instance of {@link Event}
   * as the first argument. It allows them to hook into the event execution.
   *
   * ```javascript
   *
   * // listen for event
   * eventBus.on('foo', function(event) {
   *
   *   // access event type
   *   event.type; // 'foo'
   *
   *   // stop propagation to other listeners
   *   event.stopPropagation();
   *
   *   // prevent event default
   *   event.preventDefault();
   * });
   *
   * // listen for event with custom payload
   * eventBus.on('bar', function(event, payload) {
   *   console.log(payload);
   * });
   *
   * // listen for event returning value
   * eventBus.on('foobar', function(event) {
   *
   *   // stop event propagation + prevent default
   *   return false;
   *
   *   // stop event propagation + return custom result
   *   return {
   *     complex: 'listening result'
   *   };
   * });
   *
   *
   * // listen with custom priority (default=1000, higher is better)
   * eventBus.on('priorityfoo', 1500, function(event) {
   *   console.log('invoked first!');
   * });
   *
   *
   * // listen for event and pass the context (`this`)
   * eventBus.on('foobar', function(event) {
   *   this.foo();
   * }, this);
   * ```
   *
   *
   * ## Emitting Events
   *
   * Events can be emitted via the event bus using {@link EventBus#fire}.
   *
   * ```javascript
   *
   * // false indicates that the default action
   * // was prevented by listeners
   * if (eventBus.fire('foo') === false) {
   *   console.log('default has been prevented!');
   * };
   *
   *
   * // custom args + return value listener
   * eventBus.on('sum', function(event, a, b) {
   *   return a + b;
   * });
   *
   * // you can pass custom arguments + retrieve result values.
   * var sum = eventBus.fire('sum', 1, 2);
   * console.log(sum); // 3
   * ```
   */
  function EventBus() {
    this._listeners = {};

    // cleanup on destroy on lowest priority to allow
    // message passing until the bitter end
    this.on('diagram.destroy', 1, this._destroy, this);
  }


  /**
   * Register an event listener for events with the given name.
   *
   * The callback will be invoked with `event, ...additionalArguments`
   * that have been passed to {@link EventBus#fire}.
   *
   * Returning false from a listener will prevent the events default action
   * (if any is specified). To stop an event from being processed further in
   * other listeners execute {@link Event#stopPropagation}.
   *
   * Returning anything but `undefined` from a listener will stop the listener propagation.
   *
   * @param {string|Array<string>} events
   * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
   * @param {Function} callback
   * @param {Object} [that] Pass context (`this`) to the callback
   */
  EventBus.prototype.on = function(events, priority, callback, that) {

    events = isArray$3(events) ? events : [ events ];

    if (isFunction(priority)) {
      that = callback;
      callback = priority;
      priority = DEFAULT_PRIORITY$5;
    }

    if (!isNumber(priority)) {
      throw new Error('priority must be a number');
    }

    var actualCallback = callback;

    if (that) {
      actualCallback = bind(callback, that);

      // make sure we remember and are able to remove
      // bound callbacks via {@link #off} using the original
      // callback
      actualCallback[FN_REF] = callback[FN_REF] || callback;
    }

    var self = this;

    events.forEach(function(e) {
      self._addListener(e, {
        priority: priority,
        callback: actualCallback,
        next: null
      });
    });
  };


  /**
   * Register an event listener that is executed only once.
   *
   * @param {string} event the event name to register for
   * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
   * @param {Function} callback the callback to execute
   * @param {Object} [that] Pass context (`this`) to the callback
   */
  EventBus.prototype.once = function(event, priority, callback, that) {
    var self = this;

    if (isFunction(priority)) {
      that = callback;
      callback = priority;
      priority = DEFAULT_PRIORITY$5;
    }

    if (!isNumber(priority)) {
      throw new Error('priority must be a number');
    }

    function wrappedCallback() {
      wrappedCallback.__isTomb = true;

      var result = callback.apply(that, arguments);

      self.off(event, wrappedCallback);

      return result;
    }

    // make sure we remember and are able to remove
    // bound callbacks via {@link #off} using the original
    // callback
    wrappedCallback[FN_REF] = callback;

    this.on(event, priority, wrappedCallback);
  };


  /**
   * Removes event listeners by event and callback.
   *
   * If no callback is given, all listeners for a given event name are being removed.
   *
   * @param {string|Array<string>} events
   * @param {Function} [callback]
   */
  EventBus.prototype.off = function(events, callback) {

    events = isArray$3(events) ? events : [ events ];

    var self = this;

    events.forEach(function(event) {
      self._removeListener(event, callback);
    });

  };


  /**
   * Create an EventBus event.
   *
   * @param {Object} data
   *
   * @return {Object} event, recognized by the eventBus
   */
  EventBus.prototype.createEvent = function(data) {
    var event = new InternalEvent();

    event.init(data);

    return event;
  };


  /**
   * Fires a named event.
   *
   * @example
   *
   * // fire event by name
   * events.fire('foo');
   *
   * // fire event object with nested type
   * var event = { type: 'foo' };
   * events.fire(event);
   *
   * // fire event with explicit type
   * var event = { x: 10, y: 20 };
   * events.fire('element.moved', event);
   *
   * // pass additional arguments to the event
   * events.on('foo', function(event, bar) {
   *   alert(bar);
   * });
   *
   * events.fire({ type: 'foo' }, 'I am bar!');
   *
   * @param {string} [name] the optional event name
   * @param {Object} [event] the event object
   * @param {...Object} additional arguments to be passed to the callback functions
   *
   * @return {boolean} the events return value, if specified or false if the
   *                   default action was prevented by listeners
   */
  EventBus.prototype.fire = function(type, data) {
    var event,
        firstListener,
        returnValue,
        args;

    args = slice.call(arguments);

    if (typeof type === 'object') {
      data = type;
      type = data.type;
    }

    if (!type) {
      throw new Error('no event type specified');
    }

    firstListener = this._listeners[type];

    if (!firstListener) {
      return;
    }

    // we make sure we fire instances of our home made
    // events here. We wrap them only once, though
    if (data instanceof InternalEvent) {

      // we are fine, we alread have an event
      event = data;
    } else {
      event = this.createEvent(data);
    }

    // ensure we pass the event as the first parameter
    args[0] = event;

    // original event type (in case we delegate)
    var originalType = event.type;

    // update event type before delegation
    if (type !== originalType) {
      event.type = type;
    }

    try {
      returnValue = this._invokeListeners(event, args, firstListener);
    } finally {

      // reset event type after delegation
      if (type !== originalType) {
        event.type = originalType;
      }
    }

    // set the return value to false if the event default
    // got prevented and no other return value exists
    if (returnValue === undefined && event.defaultPrevented) {
      returnValue = false;
    }

    return returnValue;
  };


  EventBus.prototype.handleError = function(error) {
    return this.fire('error', { error: error }) === false;
  };


  EventBus.prototype._destroy = function() {
    this._listeners = {};
  };

  EventBus.prototype._invokeListeners = function(event, args, listener) {

    var returnValue;

    while (listener) {

      // handle stopped propagation
      if (event.cancelBubble) {
        break;
      }

      returnValue = this._invokeListener(event, args, listener);

      listener = listener.next;
    }

    return returnValue;
  };

  EventBus.prototype._invokeListener = function(event, args, listener) {

    var returnValue;

    if (listener.callback.__isTomb) {
      return returnValue;
    }

    try {

      // returning false prevents the default action
      returnValue = invokeFunction(listener.callback, args);

      // stop propagation on return value
      if (returnValue !== undefined) {
        event.returnValue = returnValue;
        event.stopPropagation();
      }

      // prevent default on return false
      if (returnValue === false) {
        event.preventDefault();
      }
    } catch (error) {
      if (!this.handleError(error)) {
        console.error('unhandled error in event listener', error);

        throw error;
      }
    }

    return returnValue;
  };

  /*
   * Add new listener with a certain priority to the list
   * of listeners (for the given event).
   *
   * The semantics of listener registration / listener execution are
   * first register, first serve: New listeners will always be inserted
   * after existing listeners with the same priority.
   *
   * Example: Inserting two listeners with priority 1000 and 1300
   *
   *    * before: [ 1500, 1500, 1000, 1000 ]
   *    * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
   *
   * @param {string} event
   * @param {Object} listener { priority, callback }
   */
  EventBus.prototype._addListener = function(event, newListener) {

    var listener = this._getListeners(event),
        previousListener;

    // no prior listeners
    if (!listener) {
      this._setListeners(event, newListener);

      return;
    }

    // ensure we order listeners by priority from
    // 0 (high) to n > 0 (low)
    while (listener) {

      if (listener.priority < newListener.priority) {

        newListener.next = listener;

        if (previousListener) {
          previousListener.next = newListener;
        } else {
          this._setListeners(event, newListener);
        }

        return;
      }

      previousListener = listener;
      listener = listener.next;
    }

    // add new listener to back
    previousListener.next = newListener;
  };


  EventBus.prototype._getListeners = function(name) {
    return this._listeners[name];
  };

  EventBus.prototype._setListeners = function(name, listener) {
    this._listeners[name] = listener;
  };

  EventBus.prototype._removeListener = function(event, callback) {

    var listener = this._getListeners(event),
        nextListener,
        previousListener,
        listenerCallback;

    if (!callback) {

      // clear listeners
      this._setListeners(event, null);

      return;
    }

    while (listener) {

      nextListener = listener.next;

      listenerCallback = listener.callback;

      if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
        if (previousListener) {
          previousListener.next = nextListener;
        } else {

          // new first listener
          this._setListeners(event, nextListener);
        }
      }

      previousListener = listener;
      listener = nextListener;
    }
  };

  /**
   * A event that is emitted via the event bus.
   */
  function InternalEvent() { }

  InternalEvent.prototype.stopPropagation = function() {
    this.cancelBubble = true;
  };

  InternalEvent.prototype.preventDefault = function() {
    this.defaultPrevented = true;
  };

  InternalEvent.prototype.init = function(data) {
    assign(this, data || {});
  };


  /**
   * Invoke function. Be fast...
   *
   * @param {Function} fn
   * @param {Array<Object>} args
   *
   * @return {Any}
   */
  function invokeFunction(fn, args) {
    return fn.apply(null, args);
  }

  /**
   * SVGs for elements are generated by the {@link GraphicsFactory}.
   *
   * This utility gives quick access to the important semantic
   * parts of an element.
   */

  /**
   * Returns the visual part of a diagram element
   *
   * @param {Snap<SVGElement>} gfx
   *
   * @return {Snap<SVGElement>}
   */
  function getVisual(gfx) {
    return gfx.childNodes[0];
  }

  /**
   * Returns the children for a given diagram element.
   *
   * @param {Snap<SVGElement>} gfx
   * @return {Snap<SVGElement>}
   */
  function getChildren$1(gfx) {
    return gfx.parentNode.childNodes[1];
  }

  /**
   * @param {<SVGElement>} element
   * @param {number} x
   * @param {number} y
   * @param {number} angle
   * @param {number} amount
   */
  function transform(gfx, x, y, angle, amount) {
    var translate = createTransform();
    translate.setTranslate(x, y);

    var rotate = createTransform();
    rotate.setRotate(angle || 0, 0, 0);

    var scale = createTransform();
    scale.setScale(amount || 1, amount || 1);

    transform$1(gfx, [ translate, rotate, scale ]);
  }


  /**
   * @param {SVGElement} element
   * @param {number} x
   * @param {number} y
   */
  function translate$2(gfx, x, y) {
    var translate = createTransform();
    translate.setTranslate(x, y);

    transform$1(gfx, translate);
  }


  /**
   * @param {SVGElement} element
   * @param {number} angle
   */
  function rotate(gfx, angle) {
    var rotate = createTransform();
    rotate.setRotate(angle, 0, 0);

    transform$1(gfx, rotate);
  }

  /**
   * A factory that creates graphical elements
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   */
  function GraphicsFactory(eventBus, elementRegistry) {
    this._eventBus = eventBus;
    this._elementRegistry = elementRegistry;
  }

  GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ];


  GraphicsFactory.prototype._getChildrenContainer = function(element) {

    var gfx = this._elementRegistry.getGraphics(element);

    var childrenGfx;

    // root element
    if (!element.parent) {
      childrenGfx = gfx;
    } else {
      childrenGfx = getChildren$1(gfx);
      if (!childrenGfx) {
        childrenGfx = create$1('g');
        classes(childrenGfx).add('djs-children');

        append(gfx.parentNode, childrenGfx);
      }
    }

    return childrenGfx;
  };

  /**
   * Clears the graphical representation of the element and returns the
   * cleared visual (the <g class="djs-visual" /> element).
   */
  GraphicsFactory.prototype._clear = function(gfx) {
    var visual = getVisual(gfx);

    clear$1(visual);

    return visual;
  };

  /**
   * Creates a gfx container for shapes and connections
   *
   * The layout is as follows:
   *
   * <g class="djs-group">
   *
   *   <!-- the gfx -->
   *   <g class="djs-element djs-(shape|connection|frame)">
   *     <g class="djs-visual">
   *       <!-- the renderer draws in here -->
   *     </g>
   *
   *     <!-- extensions (overlays, click box, ...) goes here
   *   </g>
   *
   *   <!-- the gfx child nodes -->
   *   <g class="djs-children"></g>
   * </g>
   *
   * @param {string} type the type of the element, i.e. shape | connection
   * @param {SVGElement} [childrenGfx]
   * @param {number} [parentIndex] position to create container in parent
   * @param {boolean} [isFrame] is frame element
   *
   * @return {SVGElement}
   */
  GraphicsFactory.prototype._createContainer = function(
      type, childrenGfx, parentIndex, isFrame
  ) {
    var outerGfx = create$1('g');
    classes(outerGfx).add('djs-group');

    // insert node at position
    if (typeof parentIndex !== 'undefined') {
      prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]);
    } else {
      append(childrenGfx, outerGfx);
    }

    var gfx = create$1('g');
    classes(gfx).add('djs-element');
    classes(gfx).add('djs-' + type);

    if (isFrame) {
      classes(gfx).add('djs-frame');
    }

    append(outerGfx, gfx);

    // create visual
    var visual = create$1('g');
    classes(visual).add('djs-visual');

    append(gfx, visual);

    return gfx;
  };

  GraphicsFactory.prototype.create = function(type, element, parentIndex) {
    var childrenGfx = this._getChildrenContainer(element.parent);
    return this._createContainer(type, childrenGfx, parentIndex, isFrameElement$1(element));
  };

  GraphicsFactory.prototype.updateContainments = function(elements) {

    var self = this,
        elementRegistry = this._elementRegistry,
        parents;

    parents = reduce(elements, function(map, e) {

      if (e.parent) {
        map[e.parent.id] = e.parent;
      }

      return map;
    }, {});

    // update all parents of changed and reorganized their children
    // in the correct order (as indicated in our model)
    forEach$1(parents, function(parent) {

      var children = parent.children;

      if (!children) {
        return;
      }

      var childrenGfx = self._getChildrenContainer(parent);

      forEach$1(children.slice().reverse(), function(child) {
        var childGfx = elementRegistry.getGraphics(child);

        prependTo(childGfx.parentNode, childrenGfx);
      });
    });
  };

  GraphicsFactory.prototype.drawShape = function(visual, element) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.shape', { gfx: visual, element: element });
  };

  GraphicsFactory.prototype.getShapePath = function(element) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.getShapePath', element);
  };

  GraphicsFactory.prototype.drawConnection = function(visual, element) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.connection', { gfx: visual, element: element });
  };

  GraphicsFactory.prototype.getConnectionPath = function(waypoints) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.getConnectionPath', waypoints);
  };

  GraphicsFactory.prototype.update = function(type, element, gfx) {

    // do NOT update root element
    if (!element.parent) {
      return;
    }

    var visual = this._clear(gfx);

    // redraw
    if (type === 'shape') {
      this.drawShape(visual, element);

      // update positioning
      translate$2(gfx, element.x, element.y);
    } else
    if (type === 'connection') {
      this.drawConnection(visual, element);
    } else {
      throw new Error('unknown type: ' + type);
    }

    if (element.hidden) {
      attr(gfx, 'display', 'none');
    } else {
      attr(gfx, 'display', 'block');
    }
  };

  GraphicsFactory.prototype.remove = function(element) {
    var gfx = this._elementRegistry.getGraphics(element);

    // remove
    remove$1(gfx.parentNode);
  };


  // helpers //////////

  function prependTo(newNode, parentNode, siblingNode) {
    var node = siblingNode || parentNode.firstChild;

    // do not prepend node to itself to prevent IE from crashing
    // https://github.com/bpmn-io/bpmn-js/issues/746
    if (newNode === node) {
      return;
    }

    parentNode.insertBefore(newNode, node);
  }

  var CoreModule$1 = {
    __depends__: [ DrawModule$1 ],
    __init__: [ 'canvas' ],
    canvas: [ 'type', Canvas ],
    elementRegistry: [ 'type', ElementRegistry ],
    elementFactory: [ 'type', ElementFactory$1 ],
    eventBus: [ 'type', EventBus ],
    graphicsFactory: [ 'type', GraphicsFactory ]
  };

  /**
   * @typedef { import('didi').ModuleDeclaration } Module
   */

  /**
   * Bootstrap an injector from a list of modules, instantiating a number of default components
   *
   * @param {Array<Module>} modules
   *
   * @return {Injector} a injector to use to access the components
   */
  function bootstrap(modules) {
    var injector = new Injector(modules);

    injector.init();

    return injector;
  }

  /**
   * Creates an injector from passed options.
   *
   * @param {Object} options
   * @return {Injector}
   */
  function createInjector(options) {

    options = options || {};

    var configModule = {
      'config': [ 'value', options ]
    };

    var modules = [ configModule, CoreModule$1 ].concat(options.modules || []);

    return bootstrap(modules);
  }


  /**
   * The main diagram-js entry point that bootstraps the diagram with the given
   * configuration.
   *
   * To register extensions with the diagram, pass them as Array<Module> to the constructor.
   *
   * @class djs.Diagram
   * @memberOf djs
   * @constructor
   *
   * @example
   *
   * <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption>
   *
   * // plug-in implemenentation
   * function MyLoggingPlugin(eventBus) {
   *   eventBus.on('shape.added', function(event) {
   *     console.log('shape ', event.shape, ' was added to the diagram');
   *   });
   * }
   *
   * // export as module
   * export default {
   *   __init__: [ 'myLoggingPlugin' ],
   *     myLoggingPlugin: [ 'type', MyLoggingPlugin ]
   * };
   *
   *
   * // instantiate the diagram with the new plug-in
   *
   * import MyLoggingModule from 'path-to-my-logging-plugin';
   *
   * var diagram = new Diagram({
   *   modules: [
   *     MyLoggingModule
   *   ]
   * });
   *
   * diagram.invoke([ 'canvas', function(canvas) {
   *   // add shape to drawing canvas
   *   canvas.addShape({ x: 10, y: 10 });
   * });
   *
   * // 'shape ... was added to the diagram' logged to console
   *
   * @param {Object} options
   * @param {Array<Module>} [options.modules] external modules to instantiate with the diagram
   * @param {Injector} [injector] an (optional) injector to bootstrap the diagram with
   */
  function Diagram(options, injector) {

    // create injector unless explicitly specified
    this.injector = injector = injector || createInjector(options);

    // API

    /**
     * Resolves a diagram service
     *
     * @method Diagram#get
     *
     * @param {string} name the name of the diagram service to be retrieved
     * @param {boolean} [strict=true] if false, resolve missing services to null
     */
    this.get = injector.get;

    /**
     * Executes a function into which diagram services are injected
     *
     * @method Diagram#invoke
     *
     * @param {Function|Object[]} fn the function to resolve
     * @param {Object} locals a number of locals to use to resolve certain dependencies
     */
    this.invoke = injector.invoke;

    // init

    // indicate via event


    /**
     * An event indicating that all plug-ins are loaded.
     *
     * Use this event to fire other events to interested plug-ins
     *
     * @memberOf Diagram
     *
     * @event diagram.init
     *
     * @example
     *
     * eventBus.on('diagram.init', function() {
     *   eventBus.fire('my-custom-event', { foo: 'BAR' });
     * });
     *
     * @type {Object}
     */
    this.get('eventBus').fire('diagram.init');
  }


  /**
   * Destroys the diagram
   *
   * @method  Diagram#destroy
   */
  Diagram.prototype.destroy = function() {
    this.get('eventBus').fire('diagram.destroy');
  };

  /**
   * Clear the diagram, removing all contents.
   */
  Diagram.prototype.clear = function() {
    this.get('eventBus').fire('diagram.clear');
  };

  /**
   * Moddle base element.
   */
  function Base() { }

  Base.prototype.get = function(name) {
    return this.$model.properties.get(this, name);
  };

  Base.prototype.set = function(name, value) {
    this.$model.properties.set(this, name, value);
  };

  /**
   * A model element factory.
   *
   * @param {Moddle} model
   * @param {Properties} properties
   */
  function Factory(model, properties) {
    this.model = model;
    this.properties = properties;
  }


  Factory.prototype.createType = function(descriptor) {

    var model = this.model;

    var props = this.properties,
        prototype = Object.create(Base.prototype);

    // initialize default values
    forEach$1(descriptor.properties, function(p) {
      if (!p.isMany && p.default !== undefined) {
        prototype[p.name] = p.default;
      }
    });

    props.defineModel(prototype, model);
    props.defineDescriptor(prototype, descriptor);

    var name = descriptor.ns.name;

    /**
     * The new type constructor
     */
    function ModdleElement(attrs) {
      props.define(this, '$type', { value: name, enumerable: true });
      props.define(this, '$attrs', { value: {} });
      props.define(this, '$parent', { writable: true });

      forEach$1(attrs, bind(function(val, key) {
        this.set(key, val);
      }, this));
    }

    ModdleElement.prototype = prototype;

    ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;

    // static links
    props.defineModel(ModdleElement, model);
    props.defineDescriptor(ModdleElement, descriptor);

    return ModdleElement;
  };

  /**
   * Built-in moddle types
   */
  var BUILTINS = {
    String: true,
    Boolean: true,
    Integer: true,
    Real: true,
    Element: true
  };

  /**
   * Converters for built in types from string representations
   */
  var TYPE_CONVERTERS = {
    String: function(s) { return s; },
    Boolean: function(s) { return s === 'true'; },
    Integer: function(s) { return parseInt(s, 10); },
    Real: function(s) { return parseFloat(s); }
  };

  /**
   * Convert a type to its real representation
   */
  function coerceType(type, value) {

    var converter = TYPE_CONVERTERS[type];

    if (converter) {
      return converter(value);
    } else {
      return value;
    }
  }

  /**
   * Return whether the given type is built-in
   */
  function isBuiltIn(type) {
    return !!BUILTINS[type];
  }

  /**
   * Return whether the given type is simple
   */
  function isSimple(type) {
    return !!TYPE_CONVERTERS[type];
  }

  /**
   * Parses a namespaced attribute name of the form (ns:)localName to an object,
   * given a default prefix to assume in case no explicit namespace is given.
   *
   * @param {String} name
   * @param {String} [defaultPrefix] the default prefix to take, if none is present.
   *
   * @return {Object} the parsed name
   */
  function parseName(name, defaultPrefix) {
    var parts = name.split(/:/),
        localName, prefix;

    // no prefix (i.e. only local name)
    if (parts.length === 1) {
      localName = name;
      prefix = defaultPrefix;
    } else
    // prefix + local name
    if (parts.length === 2) {
      localName = parts[1];
      prefix = parts[0];
    } else {
      throw new Error('expected <prefix:localName> or <localName>, got ' + name);
    }

    name = (prefix ? prefix + ':' : '') + localName;

    return {
      name: name,
      prefix: prefix,
      localName: localName
    };
  }

  /**
   * A utility to build element descriptors.
   */
  function DescriptorBuilder(nameNs) {
    this.ns = nameNs;
    this.name = nameNs.name;
    this.allTypes = [];
    this.allTypesByName = {};
    this.properties = [];
    this.propertiesByName = {};
  }


  DescriptorBuilder.prototype.build = function() {
    return pick(this, [
      'ns',
      'name',
      'allTypes',
      'allTypesByName',
      'properties',
      'propertiesByName',
      'bodyProperty',
      'idProperty'
    ]);
  };

  /**
   * Add property at given index.
   *
   * @param {Object} p
   * @param {Number} [idx]
   * @param {Boolean} [validate=true]
   */
  DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {

    if (typeof idx === 'boolean') {
      validate = idx;
      idx = undefined;
    }

    this.addNamedProperty(p, validate !== false);

    var properties = this.properties;

    if (idx !== undefined) {
      properties.splice(idx, 0, p);
    } else {
      properties.push(p);
    }
  };


  DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
    var oldNameNs = oldProperty.ns;

    var props = this.properties,
        propertiesByName = this.propertiesByName,
        rename = oldProperty.name !== newProperty.name;

    if (oldProperty.isId) {
      if (!newProperty.isId) {
        throw new Error(
          'property <' + newProperty.ns.name + '> must be id property ' +
          'to refine <' + oldProperty.ns.name + '>');
      }

      this.setIdProperty(newProperty, false);
    }

    if (oldProperty.isBody) {

      if (!newProperty.isBody) {
        throw new Error(
          'property <' + newProperty.ns.name + '> must be body property ' +
          'to refine <' + oldProperty.ns.name + '>');
      }

      // TODO: Check compatibility
      this.setBodyProperty(newProperty, false);
    }

    // validate existence and get location of old property
    var idx = props.indexOf(oldProperty);
    if (idx === -1) {
      throw new Error('property <' + oldNameNs.name + '> not found in property list');
    }

    // remove old property
    props.splice(idx, 1);

    // replacing the named property is intentional
    //
    //  * validate only if this is a "rename" operation
    //  * add at specific index unless we "replace"
    //
    this.addProperty(newProperty, replace ? undefined : idx, rename);

    // make new property available under old name
    propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
  };


  DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {

    var nsPrefix = p.ns.prefix;
    var parts = targetPropertyName.split('#');

    var name = parseName(parts[0], nsPrefix);
    var attrName = parseName(parts[1], name.prefix).name;

    var redefinedProperty = this.propertiesByName[attrName];
    if (!redefinedProperty) {
      throw new Error('refined property <' + attrName + '> not found');
    } else {
      this.replaceProperty(redefinedProperty, p, replace);
    }

    delete p.redefines;
  };

  DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
    var ns = p.ns,
        propsByName = this.propertiesByName;

    if (validate) {
      this.assertNotDefined(p, ns.name);
      this.assertNotDefined(p, ns.localName);
    }

    propsByName[ns.name] = propsByName[ns.localName] = p;
  };

  DescriptorBuilder.prototype.removeNamedProperty = function(p) {
    var ns = p.ns,
        propsByName = this.propertiesByName;

    delete propsByName[ns.name];
    delete propsByName[ns.localName];
  };

  DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {

    if (validate && this.bodyProperty) {
      throw new Error(
        'body property defined multiple times ' +
        '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
    }

    this.bodyProperty = p;
  };

  DescriptorBuilder.prototype.setIdProperty = function(p, validate) {

    if (validate && this.idProperty) {
      throw new Error(
        'id property defined multiple times ' +
        '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
    }

    this.idProperty = p;
  };

  DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
    var propertyName = p.name,
        definedProperty = this.propertiesByName[propertyName];

    if (definedProperty) {
      throw new Error(
        'property <' + propertyName + '> already defined; ' +
        'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
        '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
    }
  };

  DescriptorBuilder.prototype.hasProperty = function(name) {
    return this.propertiesByName[name];
  };

  DescriptorBuilder.prototype.addTrait = function(t, inherited) {

    var typesByName = this.allTypesByName,
        types = this.allTypes;

    var typeName = t.name;

    if (typeName in typesByName) {
      return;
    }

    forEach$1(t.properties, bind(function(p) {

      // clone property to allow extensions
      p = assign({}, p, {
        name: p.ns.localName,
        inherited: inherited
      });

      Object.defineProperty(p, 'definedBy', {
        value: t
      });

      var replaces = p.replaces,
          redefines = p.redefines;

      // add replace/redefine support
      if (replaces || redefines) {
        this.redefineProperty(p, replaces || redefines, replaces);
      } else {
        if (p.isBody) {
          this.setBodyProperty(p);
        }
        if (p.isId) {
          this.setIdProperty(p);
        }
        this.addProperty(p);
      }
    }, this));

    types.push(t);
    typesByName[typeName] = t;
  };

  /**
   * A registry of Moddle packages.
   *
   * @param {Array<Package>} packages
   * @param {Properties} properties
   */
  function Registry(packages, properties) {
    this.packageMap = {};
    this.typeMap = {};

    this.packages = [];

    this.properties = properties;

    forEach$1(packages, bind(this.registerPackage, this));
  }


  Registry.prototype.getPackage = function(uriOrPrefix) {
    return this.packageMap[uriOrPrefix];
  };

  Registry.prototype.getPackages = function() {
    return this.packages;
  };


  Registry.prototype.registerPackage = function(pkg) {

    // copy package
    pkg = assign({}, pkg);

    var pkgMap = this.packageMap;

    ensureAvailable(pkgMap, pkg, 'prefix');
    ensureAvailable(pkgMap, pkg, 'uri');

    // register types
    forEach$1(pkg.types, bind(function(descriptor) {
      this.registerType(descriptor, pkg);
    }, this));

    pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
    this.packages.push(pkg);
  };


  /**
   * Register a type from a specific package with us
   */
  Registry.prototype.registerType = function(type, pkg) {

    type = assign({}, type, {
      superClass: (type.superClass || []).slice(),
      extends: (type.extends || []).slice(),
      properties: (type.properties || []).slice(),
      meta: assign((type.meta || {}))
    });

    var ns = parseName(type.name, pkg.prefix),
        name = ns.name,
        propertiesByName = {};

    // parse properties
    forEach$1(type.properties, bind(function(p) {

      // namespace property names
      var propertyNs = parseName(p.name, ns.prefix),
          propertyName = propertyNs.name;

      // namespace property types
      if (!isBuiltIn(p.type)) {
        p.type = parseName(p.type, propertyNs.prefix).name;
      }

      assign(p, {
        ns: propertyNs,
        name: propertyName
      });

      propertiesByName[propertyName] = p;
    }, this));

    // update ns + name
    assign(type, {
      ns: ns,
      name: name,
      propertiesByName: propertiesByName
    });

    forEach$1(type.extends, bind(function(extendsName) {
      var extended = this.typeMap[extendsName];

      extended.traits = extended.traits || [];
      extended.traits.push(name);
    }, this));

    // link to package
    this.definePackage(type, pkg);

    // register
    this.typeMap[name] = type;
  };


  /**
   * Traverse the type hierarchy from bottom to top,
   * calling iterator with (type, inherited) for all elements in
   * the inheritance chain.
   *
   * @param {Object} nsName
   * @param {Function} iterator
   * @param {Boolean} [trait=false]
   */
  Registry.prototype.mapTypes = function(nsName, iterator, trait) {

    var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name];

    var self = this;

    /**
     * Traverse the selected trait.
     *
     * @param {String} cls
     */
    function traverseTrait(cls) {
      return traverseSuper(cls, true);
    }

    /**
     * Traverse the selected super type or trait
     *
     * @param {String} cls
     * @param {Boolean} [trait=false]
     */
    function traverseSuper(cls, trait) {
      var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
      self.mapTypes(parentNs, iterator, trait);
    }

    if (!type) {
      throw new Error('unknown type <' + nsName.name + '>');
    }

    forEach$1(type.superClass, trait ? traverseTrait : traverseSuper);

    // call iterator with (type, inherited=!trait)
    iterator(type, !trait);

    forEach$1(type.traits, traverseTrait);
  };


  /**
   * Returns the effective descriptor for a type.
   *
   * @param  {String} type the namespaced name (ns:localName) of the type
   *
   * @return {Descriptor} the resulting effective descriptor
   */
  Registry.prototype.getEffectiveDescriptor = function(name) {

    var nsName = parseName(name);

    var builder = new DescriptorBuilder(nsName);

    this.mapTypes(nsName, function(type, inherited) {
      builder.addTrait(type, inherited);
    });

    var descriptor = builder.build();

    // define package link
    this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);

    return descriptor;
  };


  Registry.prototype.definePackage = function(target, pkg) {
    this.properties.define(target, '$pkg', { value: pkg });
  };



  ///////// helpers ////////////////////////////

  function ensureAvailable(packageMap, pkg, identifierKey) {

    var value = pkg[identifierKey];

    if (value in packageMap) {
      throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
    }
  }

  /**
   * A utility that gets and sets properties of model elements.
   *
   * @param {Model} model
   */
  function Properties(model) {
    this.model = model;
  }


  /**
   * Sets a named property on the target element.
   * If the value is undefined, the property gets deleted.
   *
   * @param {Object} target
   * @param {String} name
   * @param {Object} value
   */
  Properties.prototype.set = function(target, name, value) {

    var property = this.model.getPropertyDescriptor(target, name);

    var propertyName = property && property.name;

    if (isUndefined(value)) {
      // unset the property, if the specified value is undefined;
      // delete from $attrs (for extensions) or the target itself
      if (property) {
        delete target[propertyName];
      } else {
        delete target.$attrs[name];
      }
    } else {
      // set the property, defining well defined properties on the fly
      // or simply updating them in target.$attrs (for extensions)
      if (property) {
        if (propertyName in target) {
          target[propertyName] = value;
        } else {
          defineProperty(target, property, value);
        }
      } else {
        target.$attrs[name] = value;
      }
    }
  };

  /**
   * Returns the named property of the given element
   *
   * @param  {Object} target
   * @param  {String} name
   *
   * @return {Object}
   */
  Properties.prototype.get = function(target, name) {

    var property = this.model.getPropertyDescriptor(target, name);

    if (!property) {
      return target.$attrs[name];
    }

    var propertyName = property.name;

    // check if access to collection property and lazily initialize it
    if (!target[propertyName] && property.isMany) {
      defineProperty(target, property, []);
    }

    return target[propertyName];
  };


  /**
   * Define a property on the target element
   *
   * @param  {Object} target
   * @param  {String} name
   * @param  {Object} options
   */
  Properties.prototype.define = function(target, name, options) {

    if (!options.writable) {

      var value = options.value;

      // use getters for read-only variables to support ES6 proxies
      // cf. https://github.com/bpmn-io/internal-docs/issues/386
      options = assign({}, options, {
        get: function() { return value; }
      });

      delete options.value;
    }

    Object.defineProperty(target, name, options);
  };


  /**
   * Define the descriptor for an element
   */
  Properties.prototype.defineDescriptor = function(target, descriptor) {
    this.define(target, '$descriptor', { value: descriptor });
  };

  /**
   * Define the model for an element
   */
  Properties.prototype.defineModel = function(target, model) {
    this.define(target, '$model', { value: model });
  };


  function isUndefined(val) {
    return typeof val === 'undefined';
  }

  function defineProperty(target, property, value) {
    Object.defineProperty(target, property.name, {
      enumerable: !property.isReference,
      writable: true,
      value: value,
      configurable: true
    });
  }

  //// Moddle implementation /////////////////////////////////////////////////

  /**
   * @class Moddle
   *
   * A model that can be used to create elements of a specific type.
   *
   * @example
   *
   * var Moddle = require('moddle');
   *
   * var pkg = {
   *   name: 'mypackage',
   *   prefix: 'my',
   *   types: [
   *     { name: 'Root' }
   *   ]
   * };
   *
   * var moddle = new Moddle([pkg]);
   *
   * @param {Array<Package>} packages the packages to contain
   */
  function Moddle(packages) {

    this.properties = new Properties(this);

    this.factory = new Factory(this, this.properties);
    this.registry = new Registry(packages, this.properties);

    this.typeCache = {};
  }


  /**
   * Create an instance of the specified type.
   *
   * @method Moddle#create
   *
   * @example
   *
   * var foo = moddle.create('my:Foo');
   * var bar = moddle.create('my:Bar', { id: 'BAR_1' });
   *
   * @param  {String|Object} descriptor the type descriptor or name know to the model
   * @param  {Object} attrs   a number of attributes to initialize the model instance with
   * @return {Object}         model instance
   */
  Moddle.prototype.create = function(descriptor, attrs) {
    var Type = this.getType(descriptor);

    if (!Type) {
      throw new Error('unknown type <' + descriptor + '>');
    }

    return new Type(attrs);
  };


  /**
   * Returns the type representing a given descriptor
   *
   * @method Moddle#getType
   *
   * @example
   *
   * var Foo = moddle.getType('my:Foo');
   * var foo = new Foo({ 'id' : 'FOO_1' });
   *
   * @param  {String|Object} descriptor the type descriptor or name know to the model
   * @return {Object}         the type representing the descriptor
   */
  Moddle.prototype.getType = function(descriptor) {

    var cache = this.typeCache;

    var name = isString(descriptor) ? descriptor : descriptor.ns.name;

    var type = cache[name];

    if (!type) {
      descriptor = this.registry.getEffectiveDescriptor(name);
      type = cache[name] = this.factory.createType(descriptor);
    }

    return type;
  };


  /**
   * Creates an any-element type to be used within model instances.
   *
   * This can be used to create custom elements that lie outside the meta-model.
   * The created element contains all the meta-data required to serialize it
   * as part of meta-model elements.
   *
   * @method Moddle#createAny
   *
   * @example
   *
   * var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
   *   value: 'bar'
   * });
   *
   * var container = moddle.create('my:Container', 'http://my', {
   *   any: [ foo ]
   * });
   *
   * // go ahead and serialize the stuff
   *
   *
   * @param  {String} name  the name of the element
   * @param  {String} nsUri the namespace uri of the element
   * @param  {Object} [properties] a map of properties to initialize the instance with
   * @return {Object} the any type instance
   */
  Moddle.prototype.createAny = function(name, nsUri, properties) {

    var nameNs = parseName(name);

    var element = {
      $type: name,
      $instanceOf: function(type) {
        return type === this.$type;
      }
    };

    var descriptor = {
      name: name,
      isGeneric: true,
      ns: {
        prefix: nameNs.prefix,
        localName: nameNs.localName,
        uri: nsUri
      }
    };

    this.properties.defineDescriptor(element, descriptor);
    this.properties.defineModel(element, this);
    this.properties.define(element, '$parent', { enumerable: false, writable: true });
    this.properties.define(element, '$instanceOf', { enumerable: false, writable: true });

    forEach$1(properties, function(a, key) {
      if (isObject(a) && a.value !== undefined) {
        element[a.name] = a.value;
      } else {
        element[key] = a;
      }
    });

    return element;
  };

  /**
   * Returns a registered package by uri or prefix
   *
   * @return {Object} the package
   */
  Moddle.prototype.getPackage = function(uriOrPrefix) {
    return this.registry.getPackage(uriOrPrefix);
  };

  /**
   * Returns a snapshot of all known packages
   *
   * @return {Object} the package
   */
  Moddle.prototype.getPackages = function() {
    return this.registry.getPackages();
  };

  /**
   * Returns the descriptor for an element
   */
  Moddle.prototype.getElementDescriptor = function(element) {
    return element.$descriptor;
  };

  /**
   * Returns true if the given descriptor or instance
   * represents the given type.
   *
   * May be applied to this, if element is omitted.
   */
  Moddle.prototype.hasType = function(element, type) {
    if (type === undefined) {
      type = element;
      element = this;
    }

    var descriptor = element.$model.getElementDescriptor(element);

    return (type in descriptor.allTypesByName);
  };

  /**
   * Returns the descriptor of an elements named property
   */
  Moddle.prototype.getPropertyDescriptor = function(element, property) {
    return this.getElementDescriptor(element).propertiesByName[property];
  };

  /**
   * Returns a mapped type's descriptor
   */
  Moddle.prototype.getTypeDescriptor = function(type) {
    return this.registry.typeMap[type];
  };

  var fromCharCode = String.fromCharCode;

  var hasOwnProperty = Object.prototype.hasOwnProperty;

  var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig;

  var ENTITY_MAPPING = {
    'amp': '&',
    'apos': '\'',
    'gt': '>',
    'lt': '<',
    'quot': '"'
  };

  // map UPPERCASE variants of supported special chars
  Object.keys(ENTITY_MAPPING).forEach(function(k) {
    ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k];
  });


  function replaceEntities(_, d, x, z) {

    // reserved names, i.e. &nbsp;
    if (z) {
      if (hasOwnProperty.call(ENTITY_MAPPING, z)) {
        return ENTITY_MAPPING[z];
      } else {

        // fall back to original value
        return '&' + z + ';';
      }
    }

    // decimal encoded char
    if (d) {
      return fromCharCode(d);
    }

    // hex encoded char
    return fromCharCode(parseInt(x, 16));
  }


  /**
   * A basic entity decoder that can decode a minimal
   * sub-set of reserved names (&amp;) as well as
   * hex (&#xaaf;) and decimal (&#1231;) encoded characters.
   *
   * @param {string} str
   *
   * @return {string} decoded string
   */
  function decodeEntities(s) {
    if (s.length > 3 && s.indexOf('&') !== -1) {
      return s.replace(ENTITY_PATTERN, replaceEntities);
    }

    return s;
  }

  var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance';
  var XSI_PREFIX = 'xsi';
  var XSI_TYPE$1 = 'xsi:type';

  var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';

  function error$2(msg) {
    return new Error(msg);
  }

  function missingNamespaceForPrefix(prefix) {
    return 'missing namespace for prefix <' + prefix + '>';
  }

  function getter(getFn) {
    return {
      'get': getFn,
      'enumerable': true
    };
  }

  function cloneNsMatrix(nsMatrix) {
    var clone = {}, key;
    for (key in nsMatrix) {
      clone[key] = nsMatrix[key];
    }
    return clone;
  }

  function uriPrefix(prefix) {
    return prefix + '$uri';
  }

  function buildNsMatrix(nsUriToPrefix) {
    var nsMatrix = {},
        uri,
        prefix;

    for (uri in nsUriToPrefix) {
      prefix = nsUriToPrefix[uri];
      nsMatrix[prefix] = prefix;
      nsMatrix[uriPrefix(prefix)] = uri;
    }

    return nsMatrix;
  }

  function noopGetContext() {
    return { 'line': 0, 'column': 0 };
  }

  function throwFunc(err) {
    throw err;
  }

  /**
   * Creates a new parser with the given options.
   *
   * @constructor
   *
   * @param  {!Object<string, ?>=} options
   */
  function Parser(options) {

    if (!this) {
      return new Parser(options);
    }

    var proxy = options && options['proxy'];

    var onText,
        onOpenTag,
        onCloseTag,
        onCDATA,
        onError = throwFunc,
        onWarning,
        onComment,
        onQuestion,
        onAttention;

    var getContext = noopGetContext;

    /**
     * Do we need to parse the current elements attributes for namespaces?
     *
     * @type {boolean}
     */
    var maybeNS = false;

    /**
     * Do we process namespaces at all?
     *
     * @type {boolean}
     */
    var isNamespace = false;

    /**
     * The caught error returned on parse end
     *
     * @type {Error}
     */
    var returnError = null;

    /**
     * Should we stop parsing?
     *
     * @type {boolean}
     */
    var parseStop = false;

    /**
     * A map of { uri: prefix } used by the parser.
     *
     * This map will ensure we can normalize prefixes during processing;
     * for each uri, only one prefix will be exposed to the handlers.
     *
     * @type {!Object<string, string>}}
     */
    var nsUriToPrefix;

    /**
     * Handle parse error.
     *
     * @param  {string|Error} err
     */
    function handleError(err) {
      if (!(err instanceof Error)) {
        err = error$2(err);
      }

      returnError = err;

      onError(err, getContext);
    }

    /**
     * Handle parse error.
     *
     * @param  {string|Error} err
     */
    function handleWarning(err) {

      if (!onWarning) {
        return;
      }

      if (!(err instanceof Error)) {
        err = error$2(err);
      }

      onWarning(err, getContext);
    }

    /**
     * Register parse listener.
     *
     * @param  {string}   name
     * @param  {Function} cb
     *
     * @return {Parser}
     */
    this['on'] = function(name, cb) {

      if (typeof cb !== 'function') {
        throw error$2('required args <name, cb>');
      }

      switch (name) {
      case 'openTag': onOpenTag = cb; break;
      case 'text': onText = cb; break;
      case 'closeTag': onCloseTag = cb; break;
      case 'error': onError = cb; break;
      case 'warn': onWarning = cb; break;
      case 'cdata': onCDATA = cb; break;
      case 'attention': onAttention = cb; break; // <!XXXXX zzzz="eeee">
      case 'question': onQuestion = cb; break; // <? ....  ?>
      case 'comment': onComment = cb; break;
      default:
        throw error$2('unsupported event: ' + name);
      }

      return this;
    };

    /**
     * Set the namespace to prefix mapping.
     *
     * @example
     *
     * parser.ns({
     *   'http://foo': 'foo',
     *   'http://bar': 'bar'
     * });
     *
     * @param  {!Object<string, string>} nsMap
     *
     * @return {Parser}
     */
    this['ns'] = function(nsMap) {

      if (typeof nsMap === 'undefined') {
        nsMap = {};
      }

      if (typeof nsMap !== 'object') {
        throw error$2('required args <nsMap={}>');
      }

      var _nsUriToPrefix = {}, k;

      for (k in nsMap) {
        _nsUriToPrefix[k] = nsMap[k];
      }

      // FORCE default mapping for schema instance
      _nsUriToPrefix[XSI_URI] = XSI_PREFIX;

      isNamespace = true;
      nsUriToPrefix = _nsUriToPrefix;

      return this;
    };

    /**
     * Parse xml string.
     *
     * @param  {string} xml
     *
     * @return {Error} returnError, if not thrown
     */
    this['parse'] = function(xml) {
      if (typeof xml !== 'string') {
        throw error$2('required args <xml=string>');
      }

      returnError = null;

      parse(xml);

      getContext = noopGetContext;
      parseStop = false;

      return returnError;
    };

    /**
     * Stop parsing.
     */
    this['stop'] = function() {
      parseStop = true;
    };

    /**
     * Parse string, invoking configured listeners on element.
     *
     * @param  {string} xml
     */
    function parse(xml) {
      var nsMatrixStack = isNamespace ? [] : null,
          nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null,
          _nsMatrix,
          nodeStack = [],
          anonymousNsCount = 0,
          tagStart = false,
          tagEnd = false,
          i = 0, j = 0,
          x, y, q, w, v,
          xmlns,
          elementName,
          _elementName,
          elementProxy
          ;

      var attrsString = '',
          attrsStart = 0,
          cachedAttrs // false = parsed with errors, null = needs parsing
          ;

      /**
       * Parse attributes on demand and returns the parsed attributes.
       *
       * Return semantics: (1) `false` on attribute parse error,
       * (2) object hash on extracted attrs.
       *
       * @return {boolean|Object}
       */
      function getAttrs() {
        if (cachedAttrs !== null) {
          return cachedAttrs;
        }

        var nsUri,
            nsUriPrefix,
            nsName,
            defaultAlias = isNamespace && nsMatrix['xmlns'],
            attrList = isNamespace && maybeNS ? [] : null,
            i = attrsStart,
            s = attrsString,
            l = s.length,
            hasNewMatrix,
            newalias,
            value,
            alias,
            name,
            attrs = {},
            seenAttrs = {},
            skipAttr,
            w,
            j;

        parseAttr:
        for (; i < l; i++) {
          skipAttr = false;
          w = s.charCodeAt(i);

          if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v}
            continue;
          }

          // wait for non whitespace character
          if (w < 65 || w > 122 || (w > 90 && w < 97)) {
            if (w !== 95 && w !== 58) { // char 95"_" 58":"
              handleWarning('illegal first char attribute name');
              skipAttr = true;
            }
          }

          // parse attribute name
          for (j = i + 1; j < l; j++) {
            w = s.charCodeAt(j);

            if (
              w > 96 && w < 123 ||
              w > 64 && w < 91 ||
              w > 47 && w < 59 ||
              w === 46 || // '.'
              w === 45 || // '-'
              w === 95 // '_'
            ) {
              continue;
            }

            // unexpected whitespace
            if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
              handleWarning('missing attribute value');
              i = j;

              continue parseAttr;
            }

            // expected "="
            if (w === 61) { // "=" == 61
              break;
            }

            handleWarning('illegal attribute name char');
            skipAttr = true;
          }

          name = s.substring(i, j);

          if (name === 'xmlns:xmlns') {
            handleWarning('illegal declaration of xmlns');
            skipAttr = true;
          }

          w = s.charCodeAt(j + 1);

          if (w === 34) { // '"'
            j = s.indexOf('"', i = j + 2);

            if (j === -1) {
              j = s.indexOf('\'', i);

              if (j !== -1) {
                handleWarning('attribute value quote missmatch');
                skipAttr = true;
              }
            }

          } else if (w === 39) { // "'"
            j = s.indexOf('\'', i = j + 2);

            if (j === -1) {
              j = s.indexOf('"', i);

              if (j !== -1) {
                handleWarning('attribute value quote missmatch');
                skipAttr = true;
              }
            }

          } else {
            handleWarning('missing attribute value quotes');
            skipAttr = true;

            // skip to next space
            for (j = j + 1; j < l; j++) {
              w = s.charCodeAt(j + 1);

              if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
                break;
              }
            }

          }

          if (j === -1) {
            handleWarning('missing closing quotes');

            j = l;
            skipAttr = true;
          }

          if (!skipAttr) {
            value = s.substring(i, j);
          }

          i = j;

          // ensure SPACE follows attribute
          // skip illegal content otherwise
          // example a="b"c
          for (; j + 1 < l; j++) {
            w = s.charCodeAt(j + 1);

            if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
              break;
            }

            // FIRST ILLEGAL CHAR
            if (i === j) {
              handleWarning('illegal character after attribute end');
              skipAttr = true;
            }
          }

          // advance cursor to next attribute
          i = j + 1;

          if (skipAttr) {
            continue parseAttr;
          }

          // check attribute re-declaration
          if (name in seenAttrs) {
            handleWarning('attribute <' + name + '> already defined');
            continue;
          }

          seenAttrs[name] = true;

          if (!isNamespace) {
            attrs[name] = value;
            continue;
          }

          // try to extract namespace information
          if (maybeNS) {
            newalias = (
              name === 'xmlns'
                ? 'xmlns'
                : (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:')
                  ? name.substr(6)
                  : null
            );

            // handle xmlns(:alias) assignment
            if (newalias !== null) {
              nsUri = decodeEntities(value);
              nsUriPrefix = uriPrefix(newalias);

              alias = nsUriToPrefix[nsUri];

              if (!alias) {

                // no prefix defined or prefix collision
                if (
                  (newalias === 'xmlns') ||
                  (nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri)
                ) {

                  // alocate free ns prefix
                  do {
                    alias = 'ns' + (anonymousNsCount++);
                  } while (typeof nsMatrix[alias] !== 'undefined');
                } else {
                  alias = newalias;
                }

                nsUriToPrefix[nsUri] = alias;
              }

              if (nsMatrix[newalias] !== alias) {
                if (!hasNewMatrix) {
                  nsMatrix = cloneNsMatrix(nsMatrix);
                  hasNewMatrix = true;
                }

                nsMatrix[newalias] = alias;
                if (newalias === 'xmlns') {
                  nsMatrix[uriPrefix(alias)] = nsUri;
                  defaultAlias = alias;
                }

                nsMatrix[nsUriPrefix] = nsUri;
              }

              // expose xmlns(:asd)="..." in attributes
              attrs[name] = value;
              continue;
            }

            // collect attributes until all namespace
            // declarations are processed
            attrList.push(name, value);
            continue;

          } /** end if (maybeNs) */

          // handle attributes on element without
          // namespace declarations
          w = name.indexOf(':');
          if (w === -1) {
            attrs[name] = value;
            continue;
          }

          // normalize ns attribute name
          if (!(nsName = nsMatrix[name.substring(0, w)])) {
            handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
            continue;
          }

          name = defaultAlias === nsName
            ? name.substr(w + 1)
            : nsName + name.substr(w);

          // end: normalize ns attribute name

          // normalize xsi:type ns attribute value
          if (name === XSI_TYPE$1) {
            w = value.indexOf(':');

            if (w !== -1) {
              nsName = value.substring(0, w);

              // handle default prefixes, i.e. xs:String gracefully
              nsName = nsMatrix[nsName] || nsName;
              value = nsName + value.substring(w);
            } else {
              value = defaultAlias + ':' + value;
            }
          }

          // end: normalize xsi:type ns attribute value

          attrs[name] = value;
        }


        // handle deferred, possibly namespaced attributes
        if (maybeNS) {

          // normalize captured attributes
          for (i = 0, l = attrList.length; i < l; i++) {

            name = attrList[i++];
            value = attrList[i];

            w = name.indexOf(':');

            if (w !== -1) {

              // normalize ns attribute name
              if (!(nsName = nsMatrix[name.substring(0, w)])) {
                handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
                continue;
              }

              name = defaultAlias === nsName
                ? name.substr(w + 1)
                : nsName + name.substr(w);

              // end: normalize ns attribute name

              // normalize xsi:type ns attribute value
              if (name === XSI_TYPE$1) {
                w = value.indexOf(':');

                if (w !== -1) {
                  nsName = value.substring(0, w);

                  // handle default prefixes, i.e. xs:String gracefully
                  nsName = nsMatrix[nsName] || nsName;
                  value = nsName + value.substring(w);
                } else {
                  value = defaultAlias + ':' + value;
                }
              }

              // end: normalize xsi:type ns attribute value
            }

            attrs[name] = value;
          }

          // end: normalize captured attributes
        }

        return cachedAttrs = attrs;
      }

      /**
       * Extract the parse context { line, column, part }
       * from the current parser position.
       *
       * @return {Object} parse context
       */
      function getParseContext() {
        var splitsRe = /(\r\n|\r|\n)/g;

        var line = 0;
        var column = 0;
        var startOfLine = 0;
        var endOfLine = j;
        var match;
        var data;

        while (i >= startOfLine) {

          match = splitsRe.exec(xml);

          if (!match) {
            break;
          }

          // end of line = (break idx + break chars)
          endOfLine = match[0].length + match.index;

          if (endOfLine > i) {
            break;
          }

          // advance to next line
          line += 1;

          startOfLine = endOfLine;
        }

        // EOF errors
        if (i == -1) {
          column = endOfLine;
          data = xml.substring(j);
        } else

        // start errors
        if (j === 0) {
          data = xml.substring(j, i);
        }

        // other errors
        else {
          column = i - startOfLine;
          data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1));
        }

        return {
          'data': data,
          'line': line,
          'column': column
        };
      }

      getContext = getParseContext;


      if (proxy) {
        elementProxy = Object.create({}, {
          'name': getter(function() {
            return elementName;
          }),
          'originalName': getter(function() {
            return _elementName;
          }),
          'attrs': getter(getAttrs),
          'ns': getter(function() {
            return nsMatrix;
          })
        });
      }

      // actual parse logic
      while (j !== -1) {

        if (xml.charCodeAt(j) === 60) { // "<"
          i = j;
        } else {
          i = xml.indexOf('<', j);
        }

        // parse end
        if (i === -1) {
          if (nodeStack.length) {
            return handleError('unexpected end of file');
          }

          if (j === 0) {
            return handleError('missing start tag');
          }

          if (j < xml.length) {
            if (xml.substring(j).trim()) {
              handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
            }
          }

          return;
        }

        // parse text
        if (j !== i) {

          if (nodeStack.length) {
            if (onText) {
              onText(xml.substring(j, i), decodeEntities, getContext);

              if (parseStop) {
                return;
              }
            }
          } else {
            if (xml.substring(j, i).trim()) {
              handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);

              if (parseStop) {
                return;
              }
            }
          }
        }

        w = xml.charCodeAt(i+1);

        // parse comments + CDATA
        if (w === 33) { // "!"
          q = xml.charCodeAt(i+2);

          // CDATA section
          if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "["
            j = xml.indexOf(']]>', i);
            if (j === -1) {
              return handleError('unclosed cdata');
            }

            if (onCDATA) {
              onCDATA(xml.substring(i + 9, j), getContext);
              if (parseStop) {
                return;
              }
            }

            j += 3;
            continue;
          }

          // comment
          if (q === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-"
            j = xml.indexOf('-->', i);
            if (j === -1) {
              return handleError('unclosed comment');
            }


            if (onComment) {
              onComment(xml.substring(i + 4, j), decodeEntities, getContext);
              if (parseStop) {
                return;
              }
            }

            j += 3;
            continue;
          }
        }

        // parse question <? ... ?>
        if (w === 63) { // "?"
          j = xml.indexOf('?>', i);
          if (j === -1) {
            return handleError('unclosed question');
          }

          if (onQuestion) {
            onQuestion(xml.substring(i, j + 2), getContext);
            if (parseStop) {
              return;
            }
          }

          j += 2;
          continue;
        }

        // find matching closing tag for attention or standard tags
        // for that we must skip through attribute values
        // (enclosed in single or double quotes)
        for (x = i + 1; ; x++) {
          v = xml.charCodeAt(x);
          if (isNaN(v)) {
            j = -1;
            return handleError('unclosed tag');
          }

          // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"
          // skips the quoted string
          // (double quotes) does not appear in a literal enclosed by (double quotes)
          // (single quote) does not appear in a literal enclosed by (single quote)
          if (v === 34) { //  '"'
            q = xml.indexOf('"', x + 1);
            x = q !== -1 ? q : x;
          } else if (v === 39) { // "'"
            q = xml.indexOf("'", x + 1);
            x = q !== -1 ? q : x;
          } else if (v === 62) { // '>'
            j = x;
            break;
          }
        }


        // parse attention <! ...>
        // previously comment and CDATA have already been parsed
        if (w === 33) { // "!"

          if (onAttention) {
            onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
            if (parseStop) {
              return;
            }
          }

          j += 1;
          continue;
        }

        // don't process attributes;
        // there are none
        cachedAttrs = {};

        // if (xml.charCodeAt(i+1) === 47) { // </...
        if (w === 47) { // </...
          tagStart = false;
          tagEnd = true;

          if (!nodeStack.length) {
            return handleError('missing open tag');
          }

          // verify open <-> close tag match
          x = elementName = nodeStack.pop();
          q = i + 2 + x.length;

          if (xml.substring(i + 2, q) !== x) {
            return handleError('closing tag mismatch');
          }

          // verify chars in close tag
          for (; q < j; q++) {
            w = xml.charCodeAt(q);

            if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space
              continue;
            }

            return handleError('close tag');
          }

        } else {
          if (xml.charCodeAt(j - 1) === 47) { // .../>
            x = elementName = xml.substring(i + 1, j - 1);

            tagStart = true;
            tagEnd = true;

          } else {
            x = elementName = xml.substring(i + 1, j);

            tagStart = true;
            tagEnd = false;
          }

          if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":"
            return handleError('illegal first char nodeName');
          }

          for (q = 1, y = x.length; q < y; q++) {
            w = x.charCodeAt(q);

            if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) {
              continue;
            }

            if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space
              elementName = x.substring(0, q);

              // maybe there are attributes
              cachedAttrs = null;
              break;
            }

            return handleError('invalid nodeName');
          }

          if (!tagEnd) {
            nodeStack.push(elementName);
          }
        }

        if (isNamespace) {

          _nsMatrix = nsMatrix;

          if (tagStart) {

            // remember old namespace
            // unless we're self-closing
            if (!tagEnd) {
              nsMatrixStack.push(_nsMatrix);
            }

            if (cachedAttrs === null) {

              // quick check, whether there may be namespace
              // declarations on the node; if that is the case
              // we need to eagerly parse the node attributes
              if ((maybeNS = x.indexOf('xmlns', q) !== -1)) {
                attrsStart = q;
                attrsString = x;

                getAttrs();

                maybeNS = false;
              }
            }
          }

          _elementName = elementName;

          w = elementName.indexOf(':');
          if (w !== -1) {
            xmlns = nsMatrix[elementName.substring(0, w)];

            // prefix given; namespace must exist
            if (!xmlns) {
              return handleError('missing namespace on <' + _elementName + '>');
            }

            elementName = elementName.substr(w + 1);
          } else {
            xmlns = nsMatrix['xmlns'];

            // if no default namespace is defined,
            // we'll import the element as anonymous.
            //
            // it is up to users to correct that to the document defined
            // targetNamespace, or whatever their undersanding of the
            // XML spec mandates.
          }

          // adjust namespace prefixs as configured
          if (xmlns) {
            elementName = xmlns + ':' + elementName;
          }

        }

        if (tagStart) {
          attrsStart = q;
          attrsString = x;

          if (onOpenTag) {
            if (proxy) {
              onOpenTag(elementProxy, decodeEntities, tagEnd, getContext);
            } else {
              onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext);
            }

            if (parseStop) {
              return;
            }
          }

        }

        if (tagEnd) {

          if (onCloseTag) {
            onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext);

            if (parseStop) {
              return;
            }
          }

          // restore old namespace
          if (isNamespace) {
            if (!tagStart) {
              nsMatrix = nsMatrixStack.pop();
            } else {
              nsMatrix = _nsMatrix;
            }
          }
        }

        j += 1;
      }
    } /** end parse */

  }

  function hasLowerCaseAlias(pkg) {
    return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
  }

  var DEFAULT_NS_MAP = {
    'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
    'xml': 'http://www.w3.org/XML/1998/namespace'
  };

  var XSI_TYPE = 'xsi:type';

  function serializeFormat(element) {
    return element.xml && element.xml.serialize;
  }

  function serializeAsType(element) {
    return serializeFormat(element) === XSI_TYPE;
  }

  function serializeAsProperty(element) {
    return serializeFormat(element) === 'property';
  }

  function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  function aliasToName(aliasNs, pkg) {

    if (!hasLowerCaseAlias(pkg)) {
      return aliasNs.name;
    }

    return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
  }

  function prefixedToName(nameNs, pkg) {

    var name = nameNs.name,
        localName = nameNs.localName;

    var typePrefix = pkg.xml && pkg.xml.typePrefix;

    if (typePrefix && localName.indexOf(typePrefix) === 0) {
      return nameNs.prefix + ':' + localName.slice(typePrefix.length);
    } else {
      return name;
    }
  }

  function normalizeXsiTypeName(name, model) {

    var nameNs = parseName(name);
    var pkg = model.getPackage(nameNs.prefix);

    return prefixedToName(nameNs, pkg);
  }

  function error$1(message) {
    return new Error(message);
  }

  /**
   * Get the moddle descriptor for a given instance or type.
   *
   * @param  {ModdleElement|Function} element
   *
   * @return {Object} the moddle descriptor
   */
  function getModdleDescriptor(element) {
    return element.$descriptor;
  }


  /**
   * A parse context.
   *
   * @class
   *
   * @param {Object} options
   * @param {ElementHandler} options.rootHandler the root handler for parsing a document
   * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
   */
  function Context(options) {

    /**
     * @property {ElementHandler} rootHandler
     */

    /**
     * @property {Boolean} lax
     */

    assign(this, options);

    this.elementsById = {};
    this.references = [];
    this.warnings = [];

    /**
     * Add an unresolved reference.
     *
     * @param {Object} reference
     */
    this.addReference = function(reference) {
      this.references.push(reference);
    };

    /**
     * Add a processed element.
     *
     * @param {ModdleElement} element
     */
    this.addElement = function(element) {

      if (!element) {
        throw error$1('expected element');
      }

      var elementsById = this.elementsById;

      var descriptor = getModdleDescriptor(element);

      var idProperty = descriptor.idProperty,
          id;

      if (idProperty) {
        id = element.get(idProperty.name);

        if (id) {

          // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
          if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
            throw new Error('illegal ID <' + id + '>');
          }

          if (elementsById[id]) {
            throw error$1('duplicate ID <' + id + '>');
          }

          elementsById[id] = element;
        }
      }
    };

    /**
     * Add an import warning.
     *
     * @param {Object} warning
     * @param {String} warning.message
     * @param {Error} [warning.error]
     */
    this.addWarning = function(warning) {
      this.warnings.push(warning);
    };
  }

  function BaseHandler() {}

  BaseHandler.prototype.handleEnd = function() {};
  BaseHandler.prototype.handleText = function() {};
  BaseHandler.prototype.handleNode = function() {};


  /**
   * A simple pass through handler that does nothing except for
   * ignoring all input it receives.
   *
   * This is used to ignore unknown elements and
   * attributes.
   */
  function NoopHandler() { }

  NoopHandler.prototype = Object.create(BaseHandler.prototype);

  NoopHandler.prototype.handleNode = function() {
    return this;
  };

  function BodyHandler() {}

  BodyHandler.prototype = Object.create(BaseHandler.prototype);

  BodyHandler.prototype.handleText = function(text) {
    this.body = (this.body || '') + text;
  };

  function ReferenceHandler(property, context) {
    this.property = property;
    this.context = context;
  }

  ReferenceHandler.prototype = Object.create(BodyHandler.prototype);

  ReferenceHandler.prototype.handleNode = function(node) {

    if (this.element) {
      throw error$1('expected no sub nodes');
    } else {
      this.element = this.createReference(node);
    }

    return this;
  };

  ReferenceHandler.prototype.handleEnd = function() {
    this.element.id = this.body;
  };

  ReferenceHandler.prototype.createReference = function(node) {
    return {
      property: this.property.ns.name,
      id: ''
    };
  };

  function ValueHandler(propertyDesc, element) {
    this.element = element;
    this.propertyDesc = propertyDesc;
  }

  ValueHandler.prototype = Object.create(BodyHandler.prototype);

  ValueHandler.prototype.handleEnd = function() {

    var value = this.body || '',
        element = this.element,
        propertyDesc = this.propertyDesc;

    value = coerceType(propertyDesc.type, value);

    if (propertyDesc.isMany) {
      element.get(propertyDesc.name).push(value);
    } else {
      element.set(propertyDesc.name, value);
    }
  };


  function BaseElementHandler() {}

  BaseElementHandler.prototype = Object.create(BodyHandler.prototype);

  BaseElementHandler.prototype.handleNode = function(node) {
    var parser = this,
        element = this.element;

    if (!element) {
      element = this.element = this.createElement(node);

      this.context.addElement(element);
    } else {
      parser = this.handleChild(node);
    }

    return parser;
  };

  /**
   * @class Reader.ElementHandler
   *
   */
  function ElementHandler(model, typeName, context) {
    this.model = model;
    this.type = model.getType(typeName);
    this.context = context;
  }

  ElementHandler.prototype = Object.create(BaseElementHandler.prototype);

  ElementHandler.prototype.addReference = function(reference) {
    this.context.addReference(reference);
  };

  ElementHandler.prototype.handleText = function(text) {

    var element = this.element,
        descriptor = getModdleDescriptor(element),
        bodyProperty = descriptor.bodyProperty;

    if (!bodyProperty) {
      throw error$1('unexpected body text <' + text + '>');
    }

    BodyHandler.prototype.handleText.call(this, text);
  };

  ElementHandler.prototype.handleEnd = function() {

    var value = this.body,
        element = this.element,
        descriptor = getModdleDescriptor(element),
        bodyProperty = descriptor.bodyProperty;

    if (bodyProperty && value !== undefined) {
      value = coerceType(bodyProperty.type, value);
      element.set(bodyProperty.name, value);
    }
  };

  /**
   * Create an instance of the model from the given node.
   *
   * @param  {Element} node the xml node
   */
  ElementHandler.prototype.createElement = function(node) {
    var attributes = node.attributes,
        Type = this.type,
        descriptor = getModdleDescriptor(Type),
        context = this.context,
        instance = new Type({}),
        model = this.model,
        propNameNs;

    forEach$1(attributes, function(value, name) {

      var prop = descriptor.propertiesByName[name],
          values;

      if (prop && prop.isReference) {

        if (!prop.isMany) {
          context.addReference({
            element: instance,
            property: prop.ns.name,
            id: value
          });
        } else {

          // IDREFS: parse references as whitespace-separated list
          values = value.split(' ');

          forEach$1(values, function(v) {
            context.addReference({
              element: instance,
              property: prop.ns.name,
              id: v
            });
          });
        }

      } else {
        if (prop) {
          value = coerceType(prop.type, value);
        } else
        if (name !== 'xmlns') {
          propNameNs = parseName(name, descriptor.ns.prefix);

          // check whether attribute is defined in a well-known namespace
          // if that is the case we emit a warning to indicate potential misuse
          if (model.getPackage(propNameNs.prefix)) {

            context.addWarning({
              message: 'unknown attribute <' + name + '>',
              element: instance,
              property: name,
              value: value
            });
          }
        }

        instance.set(name, value);
      }
    });

    return instance;
  };

  ElementHandler.prototype.getPropertyForNode = function(node) {

    var name = node.name;
    var nameNs = parseName(name);

    var type = this.type,
        model = this.model,
        descriptor = getModdleDescriptor(type);

    var propertyName = nameNs.name,
        property = descriptor.propertiesByName[propertyName],
        elementTypeName,
        elementType;

    // search for properties by name first

    if (property && !property.isAttr) {

      if (serializeAsType(property)) {
        elementTypeName = node.attributes[XSI_TYPE];

        // xsi type is optional, if it does not exists the
        // default type is assumed
        if (elementTypeName) {

          // take possible type prefixes from XML
          // into account, i.e.: xsi:type="t{ActualType}"
          elementTypeName = normalizeXsiTypeName(elementTypeName, model);

          elementType = model.getType(elementTypeName);

          return assign({}, property, {
            effectiveType: getModdleDescriptor(elementType).name
          });
        }
      }

      // search for properties by name first
      return property;
    }

    var pkg = model.getPackage(nameNs.prefix);

    if (pkg) {
      elementTypeName = aliasToName(nameNs, pkg);
      elementType = model.getType(elementTypeName);

      // search for collection members later
      property = find(descriptor.properties, function(p) {
        return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
      });

      if (property) {
        return assign({}, property, {
          effectiveType: getModdleDescriptor(elementType).name
        });
      }
    } else {

      // parse unknown element (maybe extension)
      property = find(descriptor.properties, function(p) {
        return !p.isReference && !p.isAttribute && p.type === 'Element';
      });

      if (property) {
        return property;
      }
    }

    throw error$1('unrecognized element <' + nameNs.name + '>');
  };

  ElementHandler.prototype.toString = function() {
    return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
  };

  ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
    return new ValueHandler(propertyDesc, element);
  };

  ElementHandler.prototype.referenceHandler = function(propertyDesc) {
    return new ReferenceHandler(propertyDesc, this.context);
  };

  ElementHandler.prototype.handler = function(type) {
    if (type === 'Element') {
      return new GenericElementHandler(this.model, type, this.context);
    } else {
      return new ElementHandler(this.model, type, this.context);
    }
  };

  /**
   * Handle the child element parsing
   *
   * @param  {Element} node the xml node
   */
  ElementHandler.prototype.handleChild = function(node) {
    var propertyDesc, type, element, childHandler;

    propertyDesc = this.getPropertyForNode(node);
    element = this.element;

    type = propertyDesc.effectiveType || propertyDesc.type;

    if (isSimple(type)) {
      return this.valueHandler(propertyDesc, element);
    }

    if (propertyDesc.isReference) {
      childHandler = this.referenceHandler(propertyDesc).handleNode(node);
    } else {
      childHandler = this.handler(type).handleNode(node);
    }

    var newElement = childHandler.element;

    // child handles may decide to skip elements
    // by not returning anything
    if (newElement !== undefined) {

      if (propertyDesc.isMany) {
        element.get(propertyDesc.name).push(newElement);
      } else {
        element.set(propertyDesc.name, newElement);
      }

      if (propertyDesc.isReference) {
        assign(newElement, {
          element: element
        });

        this.context.addReference(newElement);
      } else {

        // establish child -> parent relationship
        newElement.$parent = element;
      }
    }

    return childHandler;
  };

  /**
   * An element handler that performs special validation
   * to ensure the node it gets initialized with matches
   * the handlers type (namespace wise).
   *
   * @param {Moddle} model
   * @param {String} typeName
   * @param {Context} context
   */
  function RootElementHandler(model, typeName, context) {
    ElementHandler.call(this, model, typeName, context);
  }

  RootElementHandler.prototype = Object.create(ElementHandler.prototype);

  RootElementHandler.prototype.createElement = function(node) {

    var name = node.name,
        nameNs = parseName(name),
        model = this.model,
        type = this.type,
        pkg = model.getPackage(nameNs.prefix),
        typeName = pkg && aliasToName(nameNs, pkg) || name;

    // verify the correct namespace if we parse
    // the first element in the handler tree
    //
    // this ensures we don't mistakenly import wrong namespace elements
    if (!type.hasType(typeName)) {
      throw error$1('unexpected element <' + node.originalName + '>');
    }

    return ElementHandler.prototype.createElement.call(this, node);
  };


  function GenericElementHandler(model, typeName, context) {
    this.model = model;
    this.context = context;
  }

  GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);

  GenericElementHandler.prototype.createElement = function(node) {

    var name = node.name,
        ns = parseName(name),
        prefix = ns.prefix,
        uri = node.ns[prefix + '$uri'],
        attributes = node.attributes;

    return this.model.createAny(name, uri, attributes);
  };

  GenericElementHandler.prototype.handleChild = function(node) {

    var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
        element = this.element;

    var newElement = handler.element,
        children;

    if (newElement !== undefined) {
      children = element.$children = element.$children || [];
      children.push(newElement);

      // establish child -> parent relationship
      newElement.$parent = element;
    }

    return handler;
  };

  GenericElementHandler.prototype.handleEnd = function() {
    if (this.body) {
      this.element.$body = this.body;
    }
  };

  /**
   * A reader for a meta-model
   *
   * @param {Object} options
   * @param {Model} options.model used to read xml files
   * @param {Boolean} options.lax whether to make parse errors warnings
   */
  function Reader(options) {

    if (options instanceof Moddle) {
      options = {
        model: options
      };
    }

    assign(this, { lax: false }, options);
  }

  /**
   * The fromXML result.
   *
   * @typedef {Object} ParseResult
   *
   * @property {ModdleElement} rootElement
   * @property {Array<Object>} references
   * @property {Array<Error>} warnings
   * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
   */

  /**
   * The fromXML result.
   *
   * @typedef {Error} ParseError
   *
   * @property {Array<Error>} warnings
   */

  /**
   * Parse the given XML into a moddle document tree.
   *
   * @param {String} xml
   * @param {ElementHandler|Object} options or rootHandler
   *
   * @returns {Promise<ParseResult, ParseError>}
   */
  Reader.prototype.fromXML = function(xml, options, done) {

    var rootHandler = options.rootHandler;

    if (options instanceof ElementHandler) {

      // root handler passed via (xml, { rootHandler: ElementHandler }, ...)
      rootHandler = options;
      options = {};
    } else {
      if (typeof options === 'string') {

        // rootHandler passed via (xml, 'someString', ...)
        rootHandler = this.handler(options);
        options = {};
      } else if (typeof rootHandler === 'string') {

        // rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
        rootHandler = this.handler(rootHandler);
      }
    }

    var model = this.model,
        lax = this.lax;

    var context = new Context(assign({}, options, { rootHandler: rootHandler })),
        parser = new Parser({ proxy: true }),
        stack = createStack();

    rootHandler.context = context;

    // push root handler
    stack.push(rootHandler);


    /**
     * Handle error.
     *
     * @param  {Error} err
     * @param  {Function} getContext
     * @param  {boolean} lax
     *
     * @return {boolean} true if handled
     */
    function handleError(err, getContext, lax) {

      var ctx = getContext();

      var line = ctx.line,
          column = ctx.column,
          data = ctx.data;

      // we receive the full context data here,
      // for elements trim down the information
      // to the tag name, only
      if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
        data = data.slice(0, data.indexOf(' ')) + '>';
      }

      var message =
        'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' +
          'line: ' + line + '\n\t' +
          'column: ' + column + '\n\t' +
          'nested error: ' + err.message;

      if (lax) {
        context.addWarning({
          message: message,
          error: err
        });

        return true;
      } else {
        throw error$1(message);
      }
    }

    function handleWarning(err, getContext) {

      // just like handling errors in <lax=true> mode
      return handleError(err, getContext, true);
    }

    /**
     * Resolve collected references on parse end.
     */
    function resolveReferences() {

      var elementsById = context.elementsById;
      var references = context.references;

      var i, r;

      for (i = 0; (r = references[i]); i++) {
        var element = r.element;
        var reference = elementsById[r.id];
        var property = getModdleDescriptor(element).propertiesByName[r.property];

        if (!reference) {
          context.addWarning({
            message: 'unresolved reference <' + r.id + '>',
            element: r.element,
            property: r.property,
            value: r.id
          });
        }

        if (property.isMany) {
          var collection = element.get(property.name),
              idx = collection.indexOf(r);

          // we replace an existing place holder (idx != -1) or
          // append to the collection instead
          if (idx === -1) {
            idx = collection.length;
          }

          if (!reference) {

            // remove unresolvable reference
            collection.splice(idx, 1);
          } else {

            // add or update reference in collection
            collection[idx] = reference;
          }
        } else {
          element.set(property.name, reference);
        }
      }
    }

    function handleClose() {
      stack.pop().handleEnd();
    }

    var PREAMBLE_START_PATTERN = /^<\?xml /i;

    var ENCODING_PATTERN = / encoding="([^"]+)"/i;

    var UTF_8_PATTERN = /^utf-8$/i;

    function handleQuestion(question) {

      if (!PREAMBLE_START_PATTERN.test(question)) {
        return;
      }

      var match = ENCODING_PATTERN.exec(question);
      var encoding = match && match[1];

      if (!encoding || UTF_8_PATTERN.test(encoding)) {
        return;
      }

      context.addWarning({
        message:
          'unsupported document encoding <' + encoding + '>, ' +
          'falling back to UTF-8'
      });
    }

    function handleOpen(node, getContext) {
      var handler = stack.peek();

      try {
        stack.push(handler.handleNode(node));
      } catch (err) {

        if (handleError(err, getContext, lax)) {
          stack.push(new NoopHandler());
        }
      }
    }

    function handleCData(text, getContext) {

      try {
        stack.peek().handleText(text);
      } catch (err) {
        handleWarning(err, getContext);
      }
    }

    function handleText(text, getContext) {

      // strip whitespace only nodes, i.e. before
      // <!CDATA[ ... ]> sections and in between tags

      if (!text.trim()) {
        return;
      }

      handleCData(text, getContext);
    }

    var uriMap = model.getPackages().reduce(function(uriMap, p) {
      uriMap[p.uri] = p.prefix;

      return uriMap;
    }, {
      'http://www.w3.org/XML/1998/namespace': 'xml' // add default xml ns
    });
    parser
      .ns(uriMap)
      .on('openTag', function(obj, decodeStr, selfClosing, getContext) {

        // gracefully handle unparsable attributes (attrs=false)
        var attrs = obj.attrs || {};

        var decodedAttrs = Object.keys(attrs).reduce(function(d, key) {
          var value = decodeStr(attrs[key]);

          d[key] = value;

          return d;
        }, {});

        var node = {
          name: obj.name,
          originalName: obj.originalName,
          attributes: decodedAttrs,
          ns: obj.ns
        };

        handleOpen(node, getContext);
      })
      .on('question', handleQuestion)
      .on('closeTag', handleClose)
      .on('cdata', handleCData)
      .on('text', function(text, decodeEntities, getContext) {
        handleText(decodeEntities(text), getContext);
      })
      .on('error', handleError)
      .on('warn', handleWarning);

    // async XML parsing to make sure the execution environment
    // (node or brower) is kept responsive and that certain optimization
    // strategies can kick in.
    return new Promise(function(resolve, reject) {

      var err;

      try {
        parser.parse(xml);

        resolveReferences();
      } catch (e) {
        err = e;
      }

      var rootElement = rootHandler.element;

      if (!err && !rootElement) {
        err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
      }

      var warnings = context.warnings;
      var references = context.references;
      var elementsById = context.elementsById;

      if (err) {
        err.warnings = warnings;

        return reject(err);
      } else {
        return resolve({
          rootElement: rootElement,
          elementsById: elementsById,
          references: references,
          warnings: warnings
        });
      }
    });
  };

  Reader.prototype.handler = function(name) {
    return new RootElementHandler(this.model, name);
  };


  // helpers //////////////////////////

  function createStack() {
    var stack = [];

    Object.defineProperty(stack, 'peek', {
      value: function() {
        return this[this.length - 1];
      }
    });

    return stack;
  }

  var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n';

  var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g;
  var ESCAPE_CHARS = /<|>|&/g;


  function Namespaces(parent) {

    var prefixMap = {};
    var uriMap = {};
    var used = {};

    var wellknown = [];
    var custom = [];

    // API

    this.byUri = function(uri) {
      return uriMap[uri] || (
        parent && parent.byUri(uri)
      );
    };

    this.add = function(ns, isWellknown) {

      uriMap[ns.uri] = ns;

      if (isWellknown) {
        wellknown.push(ns);
      } else {
        custom.push(ns);
      }

      this.mapPrefix(ns.prefix, ns.uri);
    };

    this.uriByPrefix = function(prefix) {
      return prefixMap[prefix || 'xmlns'];
    };

    this.mapPrefix = function(prefix, uri) {
      prefixMap[prefix || 'xmlns'] = uri;
    };

    this.getNSKey = function(ns) {
      return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri;
    };

    this.logUsed = function(ns) {

      var uri = ns.uri;
      var nsKey = this.getNSKey(ns);

      used[nsKey] = this.byUri(uri);

      // Inform parent recursively about the usage of this NS
      if (parent) {
        parent.logUsed(ns);
      }
    };

    this.getUsed = function(ns) {

      function isUsed(ns) {
        var nsKey = self.getNSKey(ns);

        return used[nsKey];
      }

      var self = this;

      var allNs = [].concat(wellknown, custom);

      return allNs.filter(isUsed);
    };

  }

  function lower(string) {
    return string.charAt(0).toLowerCase() + string.slice(1);
  }

  function nameToAlias(name, pkg) {
    if (hasLowerCaseAlias(pkg)) {
      return lower(name);
    } else {
      return name;
    }
  }

  function inherits(ctor, superCtor) {
    ctor.super_ = superCtor;
    ctor.prototype = Object.create(superCtor.prototype, {
      constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
  }

  function nsName(ns) {
    if (isString(ns)) {
      return ns;
    } else {
      return (ns.prefix ? ns.prefix + ':' : '') + ns.localName;
    }
  }

  function getNsAttrs(namespaces) {

    return namespaces.getUsed().filter(function(ns) {

      // do not serialize built in <xml> namespace
      return ns.prefix !== 'xml';
    }).map(function(ns) {
      var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : '');
      return { name: name, value: ns.uri };
    });

  }

  function getElementNs(ns, descriptor) {
    if (descriptor.isGeneric) {
      return assign({ localName: descriptor.ns.localName }, ns);
    } else {
      return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns);
    }
  }

  function getPropertyNs(ns, descriptor) {
    return assign({ localName: descriptor.ns.localName }, ns);
  }

  function getSerializableProperties(element) {
    var descriptor = element.$descriptor;

    return filter(descriptor.properties, function(p) {
      var name = p.name;

      if (p.isVirtual) {
        return false;
      }

      // do not serialize defaults
      if (!has$1(element, name)) {
        return false;
      }

      var value = element[name];

      // do not serialize default equals
      if (value === p.default) {
        return false;
      }

      // do not serialize null properties
      if (value === null) {
        return false;
      }

      return p.isMany ? value.length : true;
    });
  }

  var ESCAPE_ATTR_MAP = {
    '\n': '#10',
    '\n\r': '#10',
    '"': '#34',
    '\'': '#39',
    '<': '#60',
    '>': '#62',
    '&': '#38'
  };

  var ESCAPE_MAP = {
    '<': 'lt',
    '>': 'gt',
    '&': 'amp'
  };

  function escape(str, charPattern, replaceMap) {

    // ensure we are handling strings here
    str = isString(str) ? str : '' + str;

    return str.replace(charPattern, function(s) {
      return '&' + replaceMap[s] + ';';
    });
  }

  /**
   * Escape a string attribute to not contain any bad values (line breaks, '"', ...)
   *
   * @param {String} str the string to escape
   * @return {String} the escaped string
   */
  function escapeAttr(str) {
    return escape(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
  }

  function escapeBody(str) {
    return escape(str, ESCAPE_CHARS, ESCAPE_MAP);
  }

  function filterAttributes(props) {
    return filter(props, function(p) { return p.isAttr; });
  }

  function filterContained(props) {
    return filter(props, function(p) { return !p.isAttr; });
  }


  function ReferenceSerializer(tagName) {
    this.tagName = tagName;
  }

  ReferenceSerializer.prototype.build = function(element) {
    this.element = element;
    return this;
  };

  ReferenceSerializer.prototype.serializeTo = function(writer) {
    writer
      .appendIndent()
      .append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>')
      .appendNewLine();
  };

  function BodySerializer() {}

  BodySerializer.prototype.serializeValue =
  BodySerializer.prototype.serializeTo = function(writer) {
    writer.append(
      this.escape
        ? escapeBody(this.value)
        : this.value
    );
  };

  BodySerializer.prototype.build = function(prop, value) {
    this.value = value;

    if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) {
      this.escape = true;
    }

    return this;
  };

  function ValueSerializer(tagName) {
    this.tagName = tagName;
  }

  inherits(ValueSerializer, BodySerializer);

  ValueSerializer.prototype.serializeTo = function(writer) {

    writer
      .appendIndent()
      .append('<' + this.tagName + '>');

    this.serializeValue(writer);

    writer
      .append('</' + this.tagName + '>')
      .appendNewLine();
  };

  function ElementSerializer(parent, propertyDescriptor) {
    this.body = [];
    this.attrs = [];

    this.parent = parent;
    this.propertyDescriptor = propertyDescriptor;
  }

  ElementSerializer.prototype.build = function(element) {
    this.element = element;

    var elementDescriptor = element.$descriptor,
        propertyDescriptor = this.propertyDescriptor;

    var otherAttrs,
        properties;

    var isGeneric = elementDescriptor.isGeneric;

    if (isGeneric) {
      otherAttrs = this.parseGeneric(element);
    } else {
      otherAttrs = this.parseNsAttributes(element);
    }

    if (propertyDescriptor) {
      this.ns = this.nsPropertyTagName(propertyDescriptor);
    } else {
      this.ns = this.nsTagName(elementDescriptor);
    }

    // compute tag name
    this.tagName = this.addTagName(this.ns);

    if (!isGeneric) {
      properties = getSerializableProperties(element);

      this.parseAttributes(filterAttributes(properties));
      this.parseContainments(filterContained(properties));
    }

    this.parseGenericAttributes(element, otherAttrs);

    return this;
  };

  ElementSerializer.prototype.nsTagName = function(descriptor) {
    var effectiveNs = this.logNamespaceUsed(descriptor.ns);
    return getElementNs(effectiveNs, descriptor);
  };

  ElementSerializer.prototype.nsPropertyTagName = function(descriptor) {
    var effectiveNs = this.logNamespaceUsed(descriptor.ns);
    return getPropertyNs(effectiveNs, descriptor);
  };

  ElementSerializer.prototype.isLocalNs = function(ns) {
    return ns.uri === this.ns.uri;
  };

  /**
   * Get the actual ns attribute name for the given element.
   *
   * @param {Object} element
   * @param {Boolean} [element.inherited=false]
   *
   * @return {Object} nsName
   */
  ElementSerializer.prototype.nsAttributeName = function(element) {

    var ns;

    if (isString(element)) {
      ns = parseName(element);
    } else {
      ns = element.ns;
    }

    // return just local name for inherited attributes
    if (element.inherited) {
      return { localName: ns.localName };
    }

    // parse + log effective ns
    var effectiveNs = this.logNamespaceUsed(ns);

    // LOG ACTUAL namespace use
    this.getNamespaces().logUsed(effectiveNs);

    // strip prefix if same namespace like parent
    if (this.isLocalNs(effectiveNs)) {
      return { localName: ns.localName };
    } else {
      return assign({ localName: ns.localName }, effectiveNs);
    }
  };

  ElementSerializer.prototype.parseGeneric = function(element) {

    var self = this,
        body = this.body;

    var attributes = [];

    forEach$1(element, function(val, key) {

      var nonNsAttr;

      if (key === '$body') {
        body.push(new BodySerializer().build({ type: 'String' }, val));
      } else
      if (key === '$children') {
        forEach$1(val, function(child) {
          body.push(new ElementSerializer(self).build(child));
        });
      } else
      if (key.indexOf('$') !== 0) {
        nonNsAttr = self.parseNsAttribute(element, key, val);

        if (nonNsAttr) {
          attributes.push({ name: key, value: val });
        }
      }
    });

    return attributes;
  };

  ElementSerializer.prototype.parseNsAttribute = function(element, name, value) {
    var model = element.$model;

    var nameNs = parseName(name);

    var ns;

    // parse xmlns:foo="http://foo.bar"
    if (nameNs.prefix === 'xmlns') {
      ns = { prefix: nameNs.localName, uri: value };
    }

    // parse xmlns="http://foo.bar"
    if (!nameNs.prefix && nameNs.localName === 'xmlns') {
      ns = { uri: value };
    }

    if (!ns) {
      return {
        name: name,
        value: value
      };
    }

    if (model && model.getPackage(value)) {

      // register well known namespace
      this.logNamespace(ns, true, true);
    } else {

      // log custom namespace directly as used
      var actualNs = this.logNamespaceUsed(ns, true);

      this.getNamespaces().logUsed(actualNs);
    }
  };


  /**
   * Parse namespaces and return a list of left over generic attributes
   *
   * @param  {Object} element
   * @return {Array<Object>}
   */
  ElementSerializer.prototype.parseNsAttributes = function(element, attrs) {
    var self = this;

    var genericAttrs = element.$attrs;

    var attributes = [];

    // parse namespace attributes first
    // and log them. push non namespace attributes to a list
    // and process them later
    forEach$1(genericAttrs, function(value, name) {

      var nonNsAttr = self.parseNsAttribute(element, name, value);

      if (nonNsAttr) {
        attributes.push(nonNsAttr);
      }
    });

    return attributes;
  };

  ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) {

    var self = this;

    forEach$1(attributes, function(attr) {

      // do not serialize xsi:type attribute
      // it is set manually based on the actual implementation type
      if (attr.name === XSI_TYPE) {
        return;
      }

      try {
        self.addAttribute(self.nsAttributeName(attr.name), attr.value);
      } catch (e) {
        console.warn(
          'missing namespace information for ',
          attr.name, '=', attr.value, 'on', element,
          e);
      }
    });
  };

  ElementSerializer.prototype.parseContainments = function(properties) {

    var self = this,
        body = this.body,
        element = this.element;

    forEach$1(properties, function(p) {
      var value = element.get(p.name),
          isReference = p.isReference,
          isMany = p.isMany;

      if (!isMany) {
        value = [ value ];
      }

      if (p.isBody) {
        body.push(new BodySerializer().build(p, value[0]));
      } else
      if (isSimple(p.type)) {
        forEach$1(value, function(v) {
          body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
        });
      } else
      if (isReference) {
        forEach$1(value, function(v) {
          body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v));
        });
      } else {

        // allow serialization via type
        // rather than element name
        var asType = serializeAsType(p),
            asProperty = serializeAsProperty(p);

        forEach$1(value, function(v) {
          var serializer;

          if (asType) {
            serializer = new TypeSerializer(self, p);
          } else
          if (asProperty) {
            serializer = new ElementSerializer(self, p);
          } else {
            serializer = new ElementSerializer(self);
          }

          body.push(serializer.build(v));
        });
      }
    });
  };

  ElementSerializer.prototype.getNamespaces = function(local) {

    var namespaces = this.namespaces,
        parent = this.parent,
        parentNamespaces;

    if (!namespaces) {
      parentNamespaces = parent && parent.getNamespaces();

      if (local || !parentNamespaces) {
        this.namespaces = namespaces = new Namespaces(parentNamespaces);
      } else {
        namespaces = parentNamespaces;
      }
    }

    return namespaces;
  };

  ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) {
    var namespaces = this.getNamespaces(local);

    var nsUri = ns.uri,
        nsPrefix = ns.prefix;

    var existing = namespaces.byUri(nsUri);

    if (!existing || local) {
      namespaces.add(ns, wellknown);
    }

    namespaces.mapPrefix(nsPrefix, nsUri);

    return ns;
  };

  ElementSerializer.prototype.logNamespaceUsed = function(ns, local) {
    var element = this.element,
        model = element.$model,
        namespaces = this.getNamespaces(local);

    // ns may be
    //
    //   * prefix only
    //   * prefix:uri
    //   * localName only

    var prefix = ns.prefix,
        uri = ns.uri,
        newPrefix, idx,
        wellknownUri;

    // handle anonymous namespaces (elementForm=unqualified), cf. #23
    if (!prefix && !uri) {
      return { localName: ns.localName };
    }

    wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri;

    uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);

    if (!uri) {
      throw new Error('no namespace uri given for prefix <' + prefix + '>');
    }

    ns = namespaces.byUri(uri);

    if (!ns) {
      newPrefix = prefix;
      idx = 1;

      // find a prefix that is not mapped yet
      while (namespaces.uriByPrefix(newPrefix)) {
        newPrefix = prefix + '_' + idx++;
      }

      ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri);
    }

    if (prefix) {
      namespaces.mapPrefix(prefix, uri);
    }

    return ns;
  };

  ElementSerializer.prototype.parseAttributes = function(properties) {
    var self = this,
        element = this.element;

    forEach$1(properties, function(p) {

      var value = element.get(p.name);

      if (p.isReference) {

        if (!p.isMany) {
          value = value.id;
        }
        else {
          var values = [];
          forEach$1(value, function(v) {
            values.push(v.id);
          });

          // IDREFS is a whitespace-separated list of references.
          value = values.join(' ');
        }

      }

      self.addAttribute(self.nsAttributeName(p), value);
    });
  };

  ElementSerializer.prototype.addTagName = function(nsTagName) {
    var actualNs = this.logNamespaceUsed(nsTagName);

    this.getNamespaces().logUsed(actualNs);

    return nsName(nsTagName);
  };

  ElementSerializer.prototype.addAttribute = function(name, value) {
    var attrs = this.attrs;

    if (isString(value)) {
      value = escapeAttr(value);
    }

    // de-duplicate attributes
    // https://github.com/bpmn-io/moddle-xml/issues/66
    var idx = findIndex(attrs, function(element) {
      return (
        element.name.localName === name.localName &&
        element.name.uri === name.uri &&
        element.name.prefix === name.prefix
      );
    });

    var attr = { name: name, value: value };

    if (idx !== -1) {
      attrs.splice(idx, 1, attr);
    } else {
      attrs.push(attr);
    }
  };

  ElementSerializer.prototype.serializeAttributes = function(writer) {
    var attrs = this.attrs,
        namespaces = this.namespaces;

    if (namespaces) {
      attrs = getNsAttrs(namespaces).concat(attrs);
    }

    forEach$1(attrs, function(a) {
      writer
        .append(' ')
        .append(nsName(a.name)).append('="').append(a.value).append('"');
    });
  };

  ElementSerializer.prototype.serializeTo = function(writer) {
    var firstBody = this.body[0],
        indent = firstBody && firstBody.constructor !== BodySerializer;

    writer
      .appendIndent()
      .append('<' + this.tagName);

    this.serializeAttributes(writer);

    writer.append(firstBody ? '>' : ' />');

    if (firstBody) {

      if (indent) {
        writer
          .appendNewLine()
          .indent();
      }

      forEach$1(this.body, function(b) {
        b.serializeTo(writer);
      });

      if (indent) {
        writer
          .unindent()
          .appendIndent();
      }

      writer.append('</' + this.tagName + '>');
    }

    writer.appendNewLine();
  };

  /**
   * A serializer for types that handles serialization of data types
   */
  function TypeSerializer(parent, propertyDescriptor) {
    ElementSerializer.call(this, parent, propertyDescriptor);
  }

  inherits(TypeSerializer, ElementSerializer);

  TypeSerializer.prototype.parseNsAttributes = function(element) {

    // extracted attributes
    var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element);

    var descriptor = element.$descriptor;

    // only serialize xsi:type if necessary
    if (descriptor.name === this.propertyDescriptor.type) {
      return attributes;
    }

    var typeNs = this.typeNs = this.nsTagName(descriptor);
    this.getNamespaces().logUsed(this.typeNs);

    // add xsi:type attribute to represent the elements
    // actual type

    var pkg = element.$model.getPackage(typeNs.uri),
        typePrefix = (pkg.xml && pkg.xml.typePrefix) || '';

    this.addAttribute(
      this.nsAttributeName(XSI_TYPE),
      (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName
    );

    return attributes;
  };

  TypeSerializer.prototype.isLocalNs = function(ns) {
    return ns.uri === (this.typeNs || this.ns).uri;
  };

  function SavingWriter() {
    this.value = '';

    this.write = function(str) {
      this.value += str;
    };
  }

  function FormatingWriter(out, format) {

    var indent = [''];

    this.append = function(str) {
      out.write(str);

      return this;
    };

    this.appendNewLine = function() {
      if (format) {
        out.write('\n');
      }

      return this;
    };

    this.appendIndent = function() {
      if (format) {
        out.write(indent.join('  '));
      }

      return this;
    };

    this.indent = function() {
      indent.push('');
      return this;
    };

    this.unindent = function() {
      indent.pop();
      return this;
    };
  }

  /**
   * A writer for meta-model backed document trees
   *
   * @param {Object} options output options to pass into the writer
   */
  function Writer(options) {

    options = assign({ format: false, preamble: true }, options || {});

    function toXML(tree, writer) {
      var internalWriter = writer || new SavingWriter();
      var formatingWriter = new FormatingWriter(internalWriter, options.format);

      if (options.preamble) {
        formatingWriter.append(XML_PREAMBLE);
      }

      new ElementSerializer().build(tree).serializeTo(formatingWriter);

      if (!writer) {
        return internalWriter.value;
      }
    }

    return {
      toXML: toXML
    };
  }

  /**
   * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files.
   *
   * @class BpmnModdle
   * @extends Moddle
   *
   * @param {Object|Array} packages to use for instantiating the model
   * @param {Object} [options] additional options to pass over
   */
  function BpmnModdle(packages, options) {
    Moddle.call(this, packages, options);
  }

  BpmnModdle.prototype = Object.create(Moddle.prototype);

  /**
   * The fromXML result.
   *
   * @typedef {Object} ParseResult
   *
   * @property {ModdleElement} rootElement
   * @property {Array<Object>} references
   * @property {Array<Error>} warnings
   * @property {Object} elementsById - a mapping containing each ID -> ModdleElement
   */

  /**
   * The fromXML error.
   *
   * @typedef {Error} ParseError
   *
   * @property {Array<Error>} warnings
   */

  /**
   * Instantiates a BPMN model tree from a given xml string.
   *
   * @param {String}   xmlStr
   * @param {String}   [typeName='bpmn:Definitions'] name of the root element
   * @param {Object}   [options]  options to pass to the underlying reader
   *
   * @returns {Promise<ParseResult, ParseError>}
   */
  BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options) {

    if (!isString(typeName)) {
      options = typeName;
      typeName = 'bpmn:Definitions';
    }

    var reader = new Reader(assign({ model: this, lax: true }, options));
    var rootHandler = reader.handler(typeName);

    return reader.fromXML(xmlStr, rootHandler);
  };


  /**
   * The toXML result.
   *
   * @typedef {Object} SerializationResult
   *
   * @property {String} xml
   */

  /**
   * Serializes a BPMN 2.0 object tree to XML.
   *
   * @param {String}   element    the root element, typically an instance of `bpmn:Definitions`
   * @param {Object}   [options]  to pass to the underlying writer
   *
   * @returns {Promise<SerializationResult, Error>}
   */
  BpmnModdle.prototype.toXML = function(element, options) {

    var writer = new Writer(options);

    return new Promise(function(resolve, reject) {
      try {
        var result = writer.toXML(element);

        return resolve({
          xml: result
        });
      } catch (err) {
        return reject(err);
      }
    });
  };

  var name$5 = "BPMN20";
  var uri$5 = "http://www.omg.org/spec/BPMN/20100524/MODEL";
  var prefix$5 = "bpmn";
  var associations$5 = [
  ];
  var types$5 = [
  	{
  		name: "Interface",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "operations",
  				type: "Operation",
  				isMany: true
  			},
  			{
  				name: "implementationRef",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Operation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "inMessageRef",
  				type: "Message",
  				isReference: true
  			},
  			{
  				name: "outMessageRef",
  				type: "Message",
  				isReference: true
  			},
  			{
  				name: "errorRef",
  				type: "Error",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "implementationRef",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "EndPoint",
  		superClass: [
  			"RootElement"
  		]
  	},
  	{
  		name: "Auditing",
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "GlobalTask",
  		superClass: [
  			"CallableElement"
  		],
  		properties: [
  			{
  				name: "resources",
  				type: "ResourceRole",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "Monitoring",
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "Performer",
  		superClass: [
  			"ResourceRole"
  		]
  	},
  	{
  		name: "Process",
  		superClass: [
  			"FlowElementsContainer",
  			"CallableElement"
  		],
  		properties: [
  			{
  				name: "processType",
  				type: "ProcessType",
  				isAttr: true
  			},
  			{
  				name: "isClosed",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "auditing",
  				type: "Auditing"
  			},
  			{
  				name: "monitoring",
  				type: "Monitoring"
  			},
  			{
  				name: "properties",
  				type: "Property",
  				isMany: true
  			},
  			{
  				name: "laneSets",
  				isMany: true,
  				replaces: "FlowElementsContainer#laneSets",
  				type: "LaneSet"
  			},
  			{
  				name: "flowElements",
  				isMany: true,
  				replaces: "FlowElementsContainer#flowElements",
  				type: "FlowElement"
  			},
  			{
  				name: "artifacts",
  				type: "Artifact",
  				isMany: true
  			},
  			{
  				name: "resources",
  				type: "ResourceRole",
  				isMany: true
  			},
  			{
  				name: "correlationSubscriptions",
  				type: "CorrelationSubscription",
  				isMany: true
  			},
  			{
  				name: "supports",
  				type: "Process",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "definitionalCollaborationRef",
  				type: "Collaboration",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "isExecutable",
  				isAttr: true,
  				type: "Boolean"
  			}
  		]
  	},
  	{
  		name: "LaneSet",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "lanes",
  				type: "Lane",
  				isMany: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Lane",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "partitionElementRef",
  				type: "BaseElement",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "partitionElement",
  				type: "BaseElement"
  			},
  			{
  				name: "flowNodeRef",
  				type: "FlowNode",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "childLaneSet",
  				type: "LaneSet",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		]
  	},
  	{
  		name: "GlobalManualTask",
  		superClass: [
  			"GlobalTask"
  		]
  	},
  	{
  		name: "ManualTask",
  		superClass: [
  			"Task"
  		]
  	},
  	{
  		name: "UserTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "renderings",
  				type: "Rendering",
  				isMany: true
  			},
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Rendering",
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "HumanPerformer",
  		superClass: [
  			"Performer"
  		]
  	},
  	{
  		name: "PotentialOwner",
  		superClass: [
  			"HumanPerformer"
  		]
  	},
  	{
  		name: "GlobalUserTask",
  		superClass: [
  			"GlobalTask"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "renderings",
  				type: "Rendering",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "Gateway",
  		isAbstract: true,
  		superClass: [
  			"FlowNode"
  		],
  		properties: [
  			{
  				name: "gatewayDirection",
  				type: "GatewayDirection",
  				"default": "Unspecified",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "EventBasedGateway",
  		superClass: [
  			"Gateway"
  		],
  		properties: [
  			{
  				name: "instantiate",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "eventGatewayType",
  				type: "EventBasedGatewayType",
  				isAttr: true,
  				"default": "Exclusive"
  			}
  		]
  	},
  	{
  		name: "ComplexGateway",
  		superClass: [
  			"Gateway"
  		],
  		properties: [
  			{
  				name: "activationCondition",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "default",
  				type: "SequenceFlow",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ExclusiveGateway",
  		superClass: [
  			"Gateway"
  		],
  		properties: [
  			{
  				name: "default",
  				type: "SequenceFlow",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "InclusiveGateway",
  		superClass: [
  			"Gateway"
  		],
  		properties: [
  			{
  				name: "default",
  				type: "SequenceFlow",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ParallelGateway",
  		superClass: [
  			"Gateway"
  		]
  	},
  	{
  		name: "RootElement",
  		isAbstract: true,
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "Relationship",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "type",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "direction",
  				type: "RelationshipDirection",
  				isAttr: true
  			},
  			{
  				name: "source",
  				isMany: true,
  				isReference: true,
  				type: "Element"
  			},
  			{
  				name: "target",
  				isMany: true,
  				isReference: true,
  				type: "Element"
  			}
  		]
  	},
  	{
  		name: "BaseElement",
  		isAbstract: true,
  		properties: [
  			{
  				name: "id",
  				isAttr: true,
  				type: "String",
  				isId: true
  			},
  			{
  				name: "documentation",
  				type: "Documentation",
  				isMany: true
  			},
  			{
  				name: "extensionDefinitions",
  				type: "ExtensionDefinition",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "extensionElements",
  				type: "ExtensionElements"
  			}
  		]
  	},
  	{
  		name: "Extension",
  		properties: [
  			{
  				name: "mustUnderstand",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "definition",
  				type: "ExtensionDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ExtensionDefinition",
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "extensionAttributeDefinitions",
  				type: "ExtensionAttributeDefinition",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "ExtensionAttributeDefinition",
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "type",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isReference",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "extensionDefinition",
  				type: "ExtensionDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ExtensionElements",
  		properties: [
  			{
  				name: "valueRef",
  				isAttr: true,
  				isReference: true,
  				type: "Element"
  			},
  			{
  				name: "values",
  				type: "Element",
  				isMany: true
  			},
  			{
  				name: "extensionAttributeDefinition",
  				type: "ExtensionAttributeDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Documentation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "text",
  				type: "String",
  				isBody: true
  			},
  			{
  				name: "textFormat",
  				"default": "text/plain",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Event",
  		isAbstract: true,
  		superClass: [
  			"FlowNode",
  			"InteractionNode"
  		],
  		properties: [
  			{
  				name: "properties",
  				type: "Property",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "IntermediateCatchEvent",
  		superClass: [
  			"CatchEvent"
  		]
  	},
  	{
  		name: "IntermediateThrowEvent",
  		superClass: [
  			"ThrowEvent"
  		]
  	},
  	{
  		name: "EndEvent",
  		superClass: [
  			"ThrowEvent"
  		]
  	},
  	{
  		name: "StartEvent",
  		superClass: [
  			"CatchEvent"
  		],
  		properties: [
  			{
  				name: "isInterrupting",
  				"default": true,
  				isAttr: true,
  				type: "Boolean"
  			}
  		]
  	},
  	{
  		name: "ThrowEvent",
  		isAbstract: true,
  		superClass: [
  			"Event"
  		],
  		properties: [
  			{
  				name: "dataInputs",
  				type: "DataInput",
  				isMany: true
  			},
  			{
  				name: "dataInputAssociations",
  				type: "DataInputAssociation",
  				isMany: true
  			},
  			{
  				name: "inputSet",
  				type: "InputSet"
  			},
  			{
  				name: "eventDefinitions",
  				type: "EventDefinition",
  				isMany: true
  			},
  			{
  				name: "eventDefinitionRef",
  				type: "EventDefinition",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CatchEvent",
  		isAbstract: true,
  		superClass: [
  			"Event"
  		],
  		properties: [
  			{
  				name: "parallelMultiple",
  				isAttr: true,
  				type: "Boolean",
  				"default": false
  			},
  			{
  				name: "dataOutputs",
  				type: "DataOutput",
  				isMany: true
  			},
  			{
  				name: "dataOutputAssociations",
  				type: "DataOutputAssociation",
  				isMany: true
  			},
  			{
  				name: "outputSet",
  				type: "OutputSet"
  			},
  			{
  				name: "eventDefinitions",
  				type: "EventDefinition",
  				isMany: true
  			},
  			{
  				name: "eventDefinitionRef",
  				type: "EventDefinition",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "BoundaryEvent",
  		superClass: [
  			"CatchEvent"
  		],
  		properties: [
  			{
  				name: "cancelActivity",
  				"default": true,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "attachedToRef",
  				type: "Activity",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "EventDefinition",
  		isAbstract: true,
  		superClass: [
  			"RootElement"
  		]
  	},
  	{
  		name: "CancelEventDefinition",
  		superClass: [
  			"EventDefinition"
  		]
  	},
  	{
  		name: "ErrorEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "errorRef",
  				type: "Error",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "TerminateEventDefinition",
  		superClass: [
  			"EventDefinition"
  		]
  	},
  	{
  		name: "EscalationEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "escalationRef",
  				type: "Escalation",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Escalation",
  		properties: [
  			{
  				name: "structureRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "escalationCode",
  				isAttr: true,
  				type: "String"
  			}
  		],
  		superClass: [
  			"RootElement"
  		]
  	},
  	{
  		name: "CompensateEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "waitForCompletion",
  				isAttr: true,
  				type: "Boolean",
  				"default": true
  			},
  			{
  				name: "activityRef",
  				type: "Activity",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "TimerEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "timeDate",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "timeCycle",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "timeDuration",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		]
  	},
  	{
  		name: "LinkEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "target",
  				type: "LinkEventDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "source",
  				type: "LinkEventDefinition",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "MessageEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "messageRef",
  				type: "Message",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "operationRef",
  				type: "Operation",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ConditionalEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "condition",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		]
  	},
  	{
  		name: "SignalEventDefinition",
  		superClass: [
  			"EventDefinition"
  		],
  		properties: [
  			{
  				name: "signalRef",
  				type: "Signal",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Signal",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "structureRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ImplicitThrowEvent",
  		superClass: [
  			"ThrowEvent"
  		]
  	},
  	{
  		name: "DataState",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ItemAwareElement",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "itemSubjectRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "dataState",
  				type: "DataState"
  			}
  		]
  	},
  	{
  		name: "DataAssociation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "sourceRef",
  				type: "ItemAwareElement",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "targetRef",
  				type: "ItemAwareElement",
  				isReference: true
  			},
  			{
  				name: "transformation",
  				type: "FormalExpression",
  				xml: {
  					serialize: "property"
  				}
  			},
  			{
  				name: "assignment",
  				type: "Assignment",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "DataInput",
  		superClass: [
  			"ItemAwareElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isCollection",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "inputSetRef",
  				type: "InputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "inputSetWithOptional",
  				type: "InputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "inputSetWithWhileExecuting",
  				type: "InputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "DataOutput",
  		superClass: [
  			"ItemAwareElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isCollection",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "outputSetRef",
  				type: "OutputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "outputSetWithOptional",
  				type: "OutputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "outputSetWithWhileExecuting",
  				type: "OutputSet",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "InputSet",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "dataInputRefs",
  				type: "DataInput",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "optionalInputRefs",
  				type: "DataInput",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "whileExecutingInputRefs",
  				type: "DataInput",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "outputSetRefs",
  				type: "OutputSet",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "OutputSet",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "dataOutputRefs",
  				type: "DataOutput",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "inputSetRefs",
  				type: "InputSet",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "optionalOutputRefs",
  				type: "DataOutput",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "whileExecutingOutputRefs",
  				type: "DataOutput",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Property",
  		superClass: [
  			"ItemAwareElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "DataInputAssociation",
  		superClass: [
  			"DataAssociation"
  		]
  	},
  	{
  		name: "DataOutputAssociation",
  		superClass: [
  			"DataAssociation"
  		]
  	},
  	{
  		name: "InputOutputSpecification",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "dataInputs",
  				type: "DataInput",
  				isMany: true
  			},
  			{
  				name: "dataOutputs",
  				type: "DataOutput",
  				isMany: true
  			},
  			{
  				name: "inputSets",
  				type: "InputSet",
  				isMany: true
  			},
  			{
  				name: "outputSets",
  				type: "OutputSet",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "DataObject",
  		superClass: [
  			"FlowElement",
  			"ItemAwareElement"
  		],
  		properties: [
  			{
  				name: "isCollection",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			}
  		]
  	},
  	{
  		name: "InputOutputBinding",
  		properties: [
  			{
  				name: "inputDataRef",
  				type: "InputSet",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "outputDataRef",
  				type: "OutputSet",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "operationRef",
  				type: "Operation",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Assignment",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "from",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "to",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		]
  	},
  	{
  		name: "DataStore",
  		superClass: [
  			"RootElement",
  			"ItemAwareElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "capacity",
  				isAttr: true,
  				type: "Integer"
  			},
  			{
  				name: "isUnlimited",
  				"default": true,
  				isAttr: true,
  				type: "Boolean"
  			}
  		]
  	},
  	{
  		name: "DataStoreReference",
  		superClass: [
  			"ItemAwareElement",
  			"FlowElement"
  		],
  		properties: [
  			{
  				name: "dataStoreRef",
  				type: "DataStore",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "DataObjectReference",
  		superClass: [
  			"ItemAwareElement",
  			"FlowElement"
  		],
  		properties: [
  			{
  				name: "dataObjectRef",
  				type: "DataObject",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ConversationLink",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "sourceRef",
  				type: "InteractionNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "targetRef",
  				type: "InteractionNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ConversationAssociation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "innerConversationNodeRef",
  				type: "ConversationNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "outerConversationNodeRef",
  				type: "ConversationNode",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CallConversation",
  		superClass: [
  			"ConversationNode"
  		],
  		properties: [
  			{
  				name: "calledCollaborationRef",
  				type: "Collaboration",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "participantAssociations",
  				type: "ParticipantAssociation",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "Conversation",
  		superClass: [
  			"ConversationNode"
  		]
  	},
  	{
  		name: "SubConversation",
  		superClass: [
  			"ConversationNode"
  		],
  		properties: [
  			{
  				name: "conversationNodes",
  				type: "ConversationNode",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "ConversationNode",
  		isAbstract: true,
  		superClass: [
  			"InteractionNode",
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "participantRef",
  				type: "Participant",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "messageFlowRefs",
  				type: "MessageFlow",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "correlationKeys",
  				type: "CorrelationKey",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "GlobalConversation",
  		superClass: [
  			"Collaboration"
  		]
  	},
  	{
  		name: "PartnerEntity",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "participantRef",
  				type: "Participant",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "PartnerRole",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "participantRef",
  				type: "Participant",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CorrelationProperty",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "correlationPropertyRetrievalExpression",
  				type: "CorrelationPropertyRetrievalExpression",
  				isMany: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "type",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Error",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "structureRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "errorCode",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "CorrelationKey",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "correlationPropertyRef",
  				type: "CorrelationProperty",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Expression",
  		superClass: [
  			"BaseElement"
  		],
  		isAbstract: false,
  		properties: [
  			{
  				name: "body",
  				isBody: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "FormalExpression",
  		superClass: [
  			"Expression"
  		],
  		properties: [
  			{
  				name: "language",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "evaluatesToTypeRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Message",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "itemRef",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ItemDefinition",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "itemKind",
  				type: "ItemKind",
  				isAttr: true
  			},
  			{
  				name: "structureRef",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isCollection",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "import",
  				type: "Import",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "FlowElement",
  		isAbstract: true,
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "auditing",
  				type: "Auditing"
  			},
  			{
  				name: "monitoring",
  				type: "Monitoring"
  			},
  			{
  				name: "categoryValueRef",
  				type: "CategoryValue",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "SequenceFlow",
  		superClass: [
  			"FlowElement"
  		],
  		properties: [
  			{
  				name: "isImmediate",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "conditionExpression",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "sourceRef",
  				type: "FlowNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "targetRef",
  				type: "FlowNode",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "FlowElementsContainer",
  		isAbstract: true,
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "laneSets",
  				type: "LaneSet",
  				isMany: true
  			},
  			{
  				name: "flowElements",
  				type: "FlowElement",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "CallableElement",
  		isAbstract: true,
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "ioSpecification",
  				type: "InputOutputSpecification",
  				xml: {
  					serialize: "property"
  				}
  			},
  			{
  				name: "supportedInterfaceRef",
  				type: "Interface",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "ioBinding",
  				type: "InputOutputBinding",
  				isMany: true,
  				xml: {
  					serialize: "property"
  				}
  			}
  		]
  	},
  	{
  		name: "FlowNode",
  		isAbstract: true,
  		superClass: [
  			"FlowElement"
  		],
  		properties: [
  			{
  				name: "incoming",
  				type: "SequenceFlow",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "outgoing",
  				type: "SequenceFlow",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "lanes",
  				type: "Lane",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CorrelationPropertyRetrievalExpression",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "messagePath",
  				type: "FormalExpression"
  			},
  			{
  				name: "messageRef",
  				type: "Message",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CorrelationPropertyBinding",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "dataPath",
  				type: "FormalExpression"
  			},
  			{
  				name: "correlationPropertyRef",
  				type: "CorrelationProperty",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Resource",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "resourceParameters",
  				type: "ResourceParameter",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "ResourceParameter",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isRequired",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "type",
  				type: "ItemDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "CorrelationSubscription",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "correlationKeyRef",
  				type: "CorrelationKey",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "correlationPropertyBinding",
  				type: "CorrelationPropertyBinding",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "MessageFlow",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "sourceRef",
  				type: "InteractionNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "targetRef",
  				type: "InteractionNode",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "messageRef",
  				type: "Message",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "MessageFlowAssociation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "innerMessageFlowRef",
  				type: "MessageFlow",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "outerMessageFlowRef",
  				type: "MessageFlow",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "InteractionNode",
  		isAbstract: true,
  		properties: [
  			{
  				name: "incomingConversationLinks",
  				type: "ConversationLink",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "outgoingConversationLinks",
  				type: "ConversationLink",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Participant",
  		superClass: [
  			"InteractionNode",
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "interfaceRef",
  				type: "Interface",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "participantMultiplicity",
  				type: "ParticipantMultiplicity"
  			},
  			{
  				name: "endPointRefs",
  				type: "EndPoint",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "processRef",
  				type: "Process",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ParticipantAssociation",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "innerParticipantRef",
  				type: "Participant",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "outerParticipantRef",
  				type: "Participant",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ParticipantMultiplicity",
  		properties: [
  			{
  				name: "minimum",
  				"default": 0,
  				isAttr: true,
  				type: "Integer"
  			},
  			{
  				name: "maximum",
  				"default": 1,
  				isAttr: true,
  				type: "Integer"
  			}
  		],
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "Collaboration",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "isClosed",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "participants",
  				type: "Participant",
  				isMany: true
  			},
  			{
  				name: "messageFlows",
  				type: "MessageFlow",
  				isMany: true
  			},
  			{
  				name: "artifacts",
  				type: "Artifact",
  				isMany: true
  			},
  			{
  				name: "conversations",
  				type: "ConversationNode",
  				isMany: true
  			},
  			{
  				name: "conversationAssociations",
  				type: "ConversationAssociation"
  			},
  			{
  				name: "participantAssociations",
  				type: "ParticipantAssociation",
  				isMany: true
  			},
  			{
  				name: "messageFlowAssociations",
  				type: "MessageFlowAssociation",
  				isMany: true
  			},
  			{
  				name: "correlationKeys",
  				type: "CorrelationKey",
  				isMany: true
  			},
  			{
  				name: "choreographyRef",
  				type: "Choreography",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "conversationLinks",
  				type: "ConversationLink",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "ChoreographyActivity",
  		isAbstract: true,
  		superClass: [
  			"FlowNode"
  		],
  		properties: [
  			{
  				name: "participantRef",
  				type: "Participant",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "initiatingParticipantRef",
  				type: "Participant",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "correlationKeys",
  				type: "CorrelationKey",
  				isMany: true
  			},
  			{
  				name: "loopType",
  				type: "ChoreographyLoopType",
  				"default": "None",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "CallChoreography",
  		superClass: [
  			"ChoreographyActivity"
  		],
  		properties: [
  			{
  				name: "calledChoreographyRef",
  				type: "Choreography",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "participantAssociations",
  				type: "ParticipantAssociation",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "SubChoreography",
  		superClass: [
  			"ChoreographyActivity",
  			"FlowElementsContainer"
  		],
  		properties: [
  			{
  				name: "artifacts",
  				type: "Artifact",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "ChoreographyTask",
  		superClass: [
  			"ChoreographyActivity"
  		],
  		properties: [
  			{
  				name: "messageFlowRef",
  				type: "MessageFlow",
  				isMany: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Choreography",
  		superClass: [
  			"Collaboration",
  			"FlowElementsContainer"
  		]
  	},
  	{
  		name: "GlobalChoreographyTask",
  		superClass: [
  			"Choreography"
  		],
  		properties: [
  			{
  				name: "initiatingParticipantRef",
  				type: "Participant",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "TextAnnotation",
  		superClass: [
  			"Artifact"
  		],
  		properties: [
  			{
  				name: "text",
  				type: "String"
  			},
  			{
  				name: "textFormat",
  				"default": "text/plain",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Group",
  		superClass: [
  			"Artifact"
  		],
  		properties: [
  			{
  				name: "categoryValueRef",
  				type: "CategoryValue",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Association",
  		superClass: [
  			"Artifact"
  		],
  		properties: [
  			{
  				name: "associationDirection",
  				type: "AssociationDirection",
  				isAttr: true
  			},
  			{
  				name: "sourceRef",
  				type: "BaseElement",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "targetRef",
  				type: "BaseElement",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "Category",
  		superClass: [
  			"RootElement"
  		],
  		properties: [
  			{
  				name: "categoryValue",
  				type: "CategoryValue",
  				isMany: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Artifact",
  		isAbstract: true,
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "CategoryValue",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "categorizedFlowElements",
  				type: "FlowElement",
  				isMany: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "value",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Activity",
  		isAbstract: true,
  		superClass: [
  			"FlowNode"
  		],
  		properties: [
  			{
  				name: "isForCompensation",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "default",
  				type: "SequenceFlow",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "ioSpecification",
  				type: "InputOutputSpecification",
  				xml: {
  					serialize: "property"
  				}
  			},
  			{
  				name: "boundaryEventRefs",
  				type: "BoundaryEvent",
  				isMany: true,
  				isReference: true
  			},
  			{
  				name: "properties",
  				type: "Property",
  				isMany: true
  			},
  			{
  				name: "dataInputAssociations",
  				type: "DataInputAssociation",
  				isMany: true
  			},
  			{
  				name: "dataOutputAssociations",
  				type: "DataOutputAssociation",
  				isMany: true
  			},
  			{
  				name: "startQuantity",
  				"default": 1,
  				isAttr: true,
  				type: "Integer"
  			},
  			{
  				name: "resources",
  				type: "ResourceRole",
  				isMany: true
  			},
  			{
  				name: "completionQuantity",
  				"default": 1,
  				isAttr: true,
  				type: "Integer"
  			},
  			{
  				name: "loopCharacteristics",
  				type: "LoopCharacteristics"
  			}
  		]
  	},
  	{
  		name: "ServiceTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "operationRef",
  				type: "Operation",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "SubProcess",
  		superClass: [
  			"Activity",
  			"FlowElementsContainer",
  			"InteractionNode"
  		],
  		properties: [
  			{
  				name: "triggeredByEvent",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "artifacts",
  				type: "Artifact",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "LoopCharacteristics",
  		isAbstract: true,
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "MultiInstanceLoopCharacteristics",
  		superClass: [
  			"LoopCharacteristics"
  		],
  		properties: [
  			{
  				name: "isSequential",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "behavior",
  				type: "MultiInstanceBehavior",
  				"default": "All",
  				isAttr: true
  			},
  			{
  				name: "loopCardinality",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "loopDataInputRef",
  				type: "ItemAwareElement",
  				isReference: true
  			},
  			{
  				name: "loopDataOutputRef",
  				type: "ItemAwareElement",
  				isReference: true
  			},
  			{
  				name: "inputDataItem",
  				type: "DataInput",
  				xml: {
  					serialize: "property"
  				}
  			},
  			{
  				name: "outputDataItem",
  				type: "DataOutput",
  				xml: {
  					serialize: "property"
  				}
  			},
  			{
  				name: "complexBehaviorDefinition",
  				type: "ComplexBehaviorDefinition",
  				isMany: true
  			},
  			{
  				name: "completionCondition",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "oneBehaviorEventRef",
  				type: "EventDefinition",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "noneBehaviorEventRef",
  				type: "EventDefinition",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "StandardLoopCharacteristics",
  		superClass: [
  			"LoopCharacteristics"
  		],
  		properties: [
  			{
  				name: "testBefore",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "loopCondition",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "loopMaximum",
  				type: "Integer",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "CallActivity",
  		superClass: [
  			"Activity",
  			"InteractionNode"
  		],
  		properties: [
  			{
  				name: "calledElement",
  				type: "String",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "Task",
  		superClass: [
  			"Activity",
  			"InteractionNode"
  		]
  	},
  	{
  		name: "SendTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "operationRef",
  				type: "Operation",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "messageRef",
  				type: "Message",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ReceiveTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "instantiate",
  				"default": false,
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "operationRef",
  				type: "Operation",
  				isAttr: true,
  				isReference: true
  			},
  			{
  				name: "messageRef",
  				type: "Message",
  				isAttr: true,
  				isReference: true
  			}
  		]
  	},
  	{
  		name: "ScriptTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "scriptFormat",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "script",
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "BusinessRuleTask",
  		superClass: [
  			"Task"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "AdHocSubProcess",
  		superClass: [
  			"SubProcess"
  		],
  		properties: [
  			{
  				name: "completionCondition",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "ordering",
  				type: "AdHocOrdering",
  				isAttr: true
  			},
  			{
  				name: "cancelRemainingInstances",
  				"default": true,
  				isAttr: true,
  				type: "Boolean"
  			}
  		]
  	},
  	{
  		name: "Transaction",
  		superClass: [
  			"SubProcess"
  		],
  		properties: [
  			{
  				name: "protocol",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "method",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "GlobalScriptTask",
  		superClass: [
  			"GlobalTask"
  		],
  		properties: [
  			{
  				name: "scriptLanguage",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "script",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "GlobalBusinessRuleTask",
  		superClass: [
  			"GlobalTask"
  		],
  		properties: [
  			{
  				name: "implementation",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ComplexBehaviorDefinition",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "condition",
  				type: "FormalExpression"
  			},
  			{
  				name: "event",
  				type: "ImplicitThrowEvent"
  			}
  		]
  	},
  	{
  		name: "ResourceRole",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "resourceRef",
  				type: "Resource",
  				isReference: true
  			},
  			{
  				name: "resourceParameterBindings",
  				type: "ResourceParameterBinding",
  				isMany: true
  			},
  			{
  				name: "resourceAssignmentExpression",
  				type: "ResourceAssignmentExpression"
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ResourceParameterBinding",
  		properties: [
  			{
  				name: "expression",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			},
  			{
  				name: "parameterRef",
  				type: "ResourceParameter",
  				isAttr: true,
  				isReference: true
  			}
  		],
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "ResourceAssignmentExpression",
  		properties: [
  			{
  				name: "expression",
  				type: "Expression",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		],
  		superClass: [
  			"BaseElement"
  		]
  	},
  	{
  		name: "Import",
  		properties: [
  			{
  				name: "importType",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "location",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "namespace",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Definitions",
  		superClass: [
  			"BaseElement"
  		],
  		properties: [
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "targetNamespace",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "expressionLanguage",
  				"default": "http://www.w3.org/1999/XPath",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "typeLanguage",
  				"default": "http://www.w3.org/2001/XMLSchema",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "imports",
  				type: "Import",
  				isMany: true
  			},
  			{
  				name: "extensions",
  				type: "Extension",
  				isMany: true
  			},
  			{
  				name: "rootElements",
  				type: "RootElement",
  				isMany: true
  			},
  			{
  				name: "diagrams",
  				isMany: true,
  				type: "bpmndi:BPMNDiagram"
  			},
  			{
  				name: "exporter",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "relationships",
  				type: "Relationship",
  				isMany: true
  			},
  			{
  				name: "exporterVersion",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	}
  ];
  var enumerations$3 = [
  	{
  		name: "ProcessType",
  		literalValues: [
  			{
  				name: "None"
  			},
  			{
  				name: "Public"
  			},
  			{
  				name: "Private"
  			}
  		]
  	},
  	{
  		name: "GatewayDirection",
  		literalValues: [
  			{
  				name: "Unspecified"
  			},
  			{
  				name: "Converging"
  			},
  			{
  				name: "Diverging"
  			},
  			{
  				name: "Mixed"
  			}
  		]
  	},
  	{
  		name: "EventBasedGatewayType",
  		literalValues: [
  			{
  				name: "Parallel"
  			},
  			{
  				name: "Exclusive"
  			}
  		]
  	},
  	{
  		name: "RelationshipDirection",
  		literalValues: [
  			{
  				name: "None"
  			},
  			{
  				name: "Forward"
  			},
  			{
  				name: "Backward"
  			},
  			{
  				name: "Both"
  			}
  		]
  	},
  	{
  		name: "ItemKind",
  		literalValues: [
  			{
  				name: "Physical"
  			},
  			{
  				name: "Information"
  			}
  		]
  	},
  	{
  		name: "ChoreographyLoopType",
  		literalValues: [
  			{
  				name: "None"
  			},
  			{
  				name: "Standard"
  			},
  			{
  				name: "MultiInstanceSequential"
  			},
  			{
  				name: "MultiInstanceParallel"
  			}
  		]
  	},
  	{
  		name: "AssociationDirection",
  		literalValues: [
  			{
  				name: "None"
  			},
  			{
  				name: "One"
  			},
  			{
  				name: "Both"
  			}
  		]
  	},
  	{
  		name: "MultiInstanceBehavior",
  		literalValues: [
  			{
  				name: "None"
  			},
  			{
  				name: "One"
  			},
  			{
  				name: "All"
  			},
  			{
  				name: "Complex"
  			}
  		]
  	},
  	{
  		name: "AdHocOrdering",
  		literalValues: [
  			{
  				name: "Parallel"
  			},
  			{
  				name: "Sequential"
  			}
  		]
  	}
  ];
  var xml$1 = {
  	tagAlias: "lowerCase",
  	typePrefix: "t"
  };
  var BpmnPackage = {
  	name: name$5,
  	uri: uri$5,
  	prefix: prefix$5,
  	associations: associations$5,
  	types: types$5,
  	enumerations: enumerations$3,
  	xml: xml$1
  };

  var name$4 = "BPMNDI";
  var uri$4 = "http://www.omg.org/spec/BPMN/20100524/DI";
  var prefix$4 = "bpmndi";
  var types$4 = [
  	{
  		name: "BPMNDiagram",
  		properties: [
  			{
  				name: "plane",
  				type: "BPMNPlane",
  				redefines: "di:Diagram#rootElement"
  			},
  			{
  				name: "labelStyle",
  				type: "BPMNLabelStyle",
  				isMany: true
  			}
  		],
  		superClass: [
  			"di:Diagram"
  		]
  	},
  	{
  		name: "BPMNPlane",
  		properties: [
  			{
  				name: "bpmnElement",
  				isAttr: true,
  				isReference: true,
  				type: "bpmn:BaseElement",
  				redefines: "di:DiagramElement#modelElement"
  			}
  		],
  		superClass: [
  			"di:Plane"
  		]
  	},
  	{
  		name: "BPMNShape",
  		properties: [
  			{
  				name: "bpmnElement",
  				isAttr: true,
  				isReference: true,
  				type: "bpmn:BaseElement",
  				redefines: "di:DiagramElement#modelElement"
  			},
  			{
  				name: "isHorizontal",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "isExpanded",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "isMarkerVisible",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "label",
  				type: "BPMNLabel"
  			},
  			{
  				name: "isMessageVisible",
  				isAttr: true,
  				type: "Boolean"
  			},
  			{
  				name: "participantBandKind",
  				type: "ParticipantBandKind",
  				isAttr: true
  			},
  			{
  				name: "choreographyActivityShape",
  				type: "BPMNShape",
  				isAttr: true,
  				isReference: true
  			}
  		],
  		superClass: [
  			"di:LabeledShape"
  		]
  	},
  	{
  		name: "BPMNEdge",
  		properties: [
  			{
  				name: "label",
  				type: "BPMNLabel"
  			},
  			{
  				name: "bpmnElement",
  				isAttr: true,
  				isReference: true,
  				type: "bpmn:BaseElement",
  				redefines: "di:DiagramElement#modelElement"
  			},
  			{
  				name: "sourceElement",
  				isAttr: true,
  				isReference: true,
  				type: "di:DiagramElement",
  				redefines: "di:Edge#source"
  			},
  			{
  				name: "targetElement",
  				isAttr: true,
  				isReference: true,
  				type: "di:DiagramElement",
  				redefines: "di:Edge#target"
  			},
  			{
  				name: "messageVisibleKind",
  				type: "MessageVisibleKind",
  				isAttr: true,
  				"default": "initiating"
  			}
  		],
  		superClass: [
  			"di:LabeledEdge"
  		]
  	},
  	{
  		name: "BPMNLabel",
  		properties: [
  			{
  				name: "labelStyle",
  				type: "BPMNLabelStyle",
  				isAttr: true,
  				isReference: true,
  				redefines: "di:DiagramElement#style"
  			}
  		],
  		superClass: [
  			"di:Label"
  		]
  	},
  	{
  		name: "BPMNLabelStyle",
  		properties: [
  			{
  				name: "font",
  				type: "dc:Font"
  			}
  		],
  		superClass: [
  			"di:Style"
  		]
  	}
  ];
  var enumerations$2 = [
  	{
  		name: "ParticipantBandKind",
  		literalValues: [
  			{
  				name: "top_initiating"
  			},
  			{
  				name: "middle_initiating"
  			},
  			{
  				name: "bottom_initiating"
  			},
  			{
  				name: "top_non_initiating"
  			},
  			{
  				name: "middle_non_initiating"
  			},
  			{
  				name: "bottom_non_initiating"
  			}
  		]
  	},
  	{
  		name: "MessageVisibleKind",
  		literalValues: [
  			{
  				name: "initiating"
  			},
  			{
  				name: "non_initiating"
  			}
  		]
  	}
  ];
  var associations$4 = [
  ];
  var BpmnDiPackage = {
  	name: name$4,
  	uri: uri$4,
  	prefix: prefix$4,
  	types: types$4,
  	enumerations: enumerations$2,
  	associations: associations$4
  };

  var name$3 = "DC";
  var uri$3 = "http://www.omg.org/spec/DD/20100524/DC";
  var prefix$3 = "dc";
  var types$3 = [
  	{
  		name: "Boolean"
  	},
  	{
  		name: "Integer"
  	},
  	{
  		name: "Real"
  	},
  	{
  		name: "String"
  	},
  	{
  		name: "Font",
  		properties: [
  			{
  				name: "name",
  				type: "String",
  				isAttr: true
  			},
  			{
  				name: "size",
  				type: "Real",
  				isAttr: true
  			},
  			{
  				name: "isBold",
  				type: "Boolean",
  				isAttr: true
  			},
  			{
  				name: "isItalic",
  				type: "Boolean",
  				isAttr: true
  			},
  			{
  				name: "isUnderline",
  				type: "Boolean",
  				isAttr: true
  			},
  			{
  				name: "isStrikeThrough",
  				type: "Boolean",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "Point",
  		properties: [
  			{
  				name: "x",
  				type: "Real",
  				"default": "0",
  				isAttr: true
  			},
  			{
  				name: "y",
  				type: "Real",
  				"default": "0",
  				isAttr: true
  			}
  		]
  	},
  	{
  		name: "Bounds",
  		properties: [
  			{
  				name: "x",
  				type: "Real",
  				"default": "0",
  				isAttr: true
  			},
  			{
  				name: "y",
  				type: "Real",
  				"default": "0",
  				isAttr: true
  			},
  			{
  				name: "width",
  				type: "Real",
  				isAttr: true
  			},
  			{
  				name: "height",
  				type: "Real",
  				isAttr: true
  			}
  		]
  	}
  ];
  var associations$3 = [
  ];
  var DcPackage = {
  	name: name$3,
  	uri: uri$3,
  	prefix: prefix$3,
  	types: types$3,
  	associations: associations$3
  };

  var name$2 = "DI";
  var uri$2 = "http://www.omg.org/spec/DD/20100524/DI";
  var prefix$2 = "di";
  var types$2 = [
  	{
  		name: "DiagramElement",
  		isAbstract: true,
  		properties: [
  			{
  				name: "id",
  				isAttr: true,
  				isId: true,
  				type: "String"
  			},
  			{
  				name: "extension",
  				type: "Extension"
  			},
  			{
  				name: "owningDiagram",
  				type: "Diagram",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "owningElement",
  				type: "DiagramElement",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "modelElement",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true,
  				type: "Element"
  			},
  			{
  				name: "style",
  				type: "Style",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "ownedElement",
  				type: "DiagramElement",
  				isReadOnly: true,
  				isMany: true,
  				isVirtual: true
  			}
  		]
  	},
  	{
  		name: "Node",
  		isAbstract: true,
  		superClass: [
  			"DiagramElement"
  		]
  	},
  	{
  		name: "Edge",
  		isAbstract: true,
  		superClass: [
  			"DiagramElement"
  		],
  		properties: [
  			{
  				name: "source",
  				type: "DiagramElement",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "target",
  				type: "DiagramElement",
  				isReadOnly: true,
  				isVirtual: true,
  				isReference: true
  			},
  			{
  				name: "waypoint",
  				isUnique: false,
  				isMany: true,
  				type: "dc:Point",
  				xml: {
  					serialize: "xsi:type"
  				}
  			}
  		]
  	},
  	{
  		name: "Diagram",
  		isAbstract: true,
  		properties: [
  			{
  				name: "id",
  				isAttr: true,
  				isId: true,
  				type: "String"
  			},
  			{
  				name: "rootElement",
  				type: "DiagramElement",
  				isReadOnly: true,
  				isVirtual: true
  			},
  			{
  				name: "name",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "documentation",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "resolution",
  				isAttr: true,
  				type: "Real"
  			},
  			{
  				name: "ownedStyle",
  				type: "Style",
  				isReadOnly: true,
  				isMany: true,
  				isVirtual: true
  			}
  		]
  	},
  	{
  		name: "Shape",
  		isAbstract: true,
  		superClass: [
  			"Node"
  		],
  		properties: [
  			{
  				name: "bounds",
  				type: "dc:Bounds"
  			}
  		]
  	},
  	{
  		name: "Plane",
  		isAbstract: true,
  		superClass: [
  			"Node"
  		],
  		properties: [
  			{
  				name: "planeElement",
  				type: "DiagramElement",
  				subsettedProperty: "DiagramElement-ownedElement",
  				isMany: true
  			}
  		]
  	},
  	{
  		name: "LabeledEdge",
  		isAbstract: true,
  		superClass: [
  			"Edge"
  		],
  		properties: [
  			{
  				name: "ownedLabel",
  				type: "Label",
  				isReadOnly: true,
  				subsettedProperty: "DiagramElement-ownedElement",
  				isMany: true,
  				isVirtual: true
  			}
  		]
  	},
  	{
  		name: "LabeledShape",
  		isAbstract: true,
  		superClass: [
  			"Shape"
  		],
  		properties: [
  			{
  				name: "ownedLabel",
  				type: "Label",
  				isReadOnly: true,
  				subsettedProperty: "DiagramElement-ownedElement",
  				isMany: true,
  				isVirtual: true
  			}
  		]
  	},
  	{
  		name: "Label",
  		isAbstract: true,
  		superClass: [
  			"Node"
  		],
  		properties: [
  			{
  				name: "bounds",
  				type: "dc:Bounds"
  			}
  		]
  	},
  	{
  		name: "Style",
  		isAbstract: true,
  		properties: [
  			{
  				name: "id",
  				isAttr: true,
  				isId: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "Extension",
  		properties: [
  			{
  				name: "values",
  				isMany: true,
  				type: "Element"
  			}
  		]
  	}
  ];
  var associations$2 = [
  ];
  var xml = {
  	tagAlias: "lowerCase"
  };
  var DiPackage = {
  	name: name$2,
  	uri: uri$2,
  	prefix: prefix$2,
  	types: types$2,
  	associations: associations$2,
  	xml: xml
  };

  var name$1 = "bpmn.io colors for BPMN";
  var uri$1 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
  var prefix$1 = "bioc";
  var types$1 = [
  	{
  		name: "ColoredShape",
  		"extends": [
  			"bpmndi:BPMNShape"
  		],
  		properties: [
  			{
  				name: "stroke",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "fill",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ColoredEdge",
  		"extends": [
  			"bpmndi:BPMNEdge"
  		],
  		properties: [
  			{
  				name: "stroke",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "fill",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	}
  ];
  var enumerations$1 = [
  ];
  var associations$1 = [
  ];
  var BiocPackage = {
  	name: name$1,
  	uri: uri$1,
  	prefix: prefix$1,
  	types: types$1,
  	enumerations: enumerations$1,
  	associations: associations$1
  };

  var name = "BPMN in Color";
  var uri = "http://www.omg.org/spec/BPMN/non-normative/color/1.0";
  var prefix = "color";
  var types = [
  	{
  		name: "ColoredLabel",
  		"extends": [
  			"bpmndi:BPMNLabel"
  		],
  		properties: [
  			{
  				name: "color",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ColoredShape",
  		"extends": [
  			"bpmndi:BPMNShape"
  		],
  		properties: [
  			{
  				name: "background-color",
  				isAttr: true,
  				type: "String"
  			},
  			{
  				name: "border-color",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	},
  	{
  		name: "ColoredEdge",
  		"extends": [
  			"bpmndi:BPMNEdge"
  		],
  		properties: [
  			{
  				name: "border-color",
  				isAttr: true,
  				type: "String"
  			}
  		]
  	}
  ];
  var enumerations = [
  ];
  var associations = [
  ];
  var BpmnInColorPackage = {
  	name: name,
  	uri: uri,
  	prefix: prefix,
  	types: types,
  	enumerations: enumerations,
  	associations: associations
  };

  var packages = {
    bpmn: BpmnPackage,
    bpmndi: BpmnDiPackage,
    dc: DcPackage,
    di: DiPackage,
    bioc: BiocPackage,
    color: BpmnInColorPackage
  };

  function simple(additionalPackages, options) {
    var pks = assign({}, packages, additionalPackages);

    return new BpmnModdle(pks, options);
  }

  function elementToString(e) {
    if (!e) {
      return '<null>';
    }

    return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
  }

  // TODO(nikku): remove with future bpmn-js version

  /**
   * Wraps APIs to check:
   *
   * 1) If a callback is passed -> Warn users about callback deprecation.
   * 2) If Promise class is implemented in current environment.
   *
   * @private
   */
  function wrapForCompatibility(api) {

    return function() {

      if (!window.Promise) {
        throw new Error('Promises is not supported in this environment. Please polyfill Promise.');
      }

      var argLen = arguments.length;
      if (argLen >= 1 && isFunction(arguments[argLen - 1])) {

        var callback = arguments[argLen - 1];

        console.warn(new Error(
          'Passing callbacks to ' + api.name + ' is deprecated and will be removed in a future major release. ' +
          'Please switch to promises: https://bpmn.io/l/moving-to-promises.html'
        ));

        var argsWithoutCallback = Array.prototype.slice.call(arguments, 0, -1);

        api.apply(this, argsWithoutCallback).then(function(result) {

          var firstKey = Object.keys(result)[0];

          // The APIs we are wrapping all resolve a single item depending on the API.
          // For instance, importXML resolves { warnings } and saveXML returns { xml }.
          // That's why we can call the callback with the first item of result.
          return callback(null, result[firstKey]);

          // Passing a second paramter instead of catch because we don't want to
          // catch errors thrown by callback().
        }, function(err) {

          return callback(err, err.warnings);
        });
      } else {

        return api.apply(this, arguments);
      }
    };
  }


  // TODO(nikku): remove with future bpmn-js version

  var DI_ERROR_MESSAGE = 'Tried to access di from the businessObject. The di is available through the diagram element only. For more information, see https://github.com/bpmn-io/bpmn-js/issues/1472';

  function ensureCompatDiRef(businessObject) {

    // bpmnElement can have multiple independent DIs
    if (!has$1(businessObject, 'di')) {
      Object.defineProperty(businessObject, 'di', {
        enumerable: false,
        get: function() {
          throw new Error(DI_ERROR_MESSAGE);
        }
      });
    }
  }

  /**
   * Returns true if an element has the given meta-model type
   *
   * @param  {ModdleElement}  element
   * @param  {string}         type
   *
   * @return {boolean}
   */
  function is$2(element, type) {
    return element.$instanceOf(type);
  }


  /**
   * Find a suitable display candidate for definitions where the DI does not
   * correctly specify one.
   */
  function findDisplayCandidate(definitions) {
    return find(definitions.rootElements, function(e) {
      return is$2(e, 'bpmn:Process') || is$2(e, 'bpmn:Collaboration');
    });
  }


  function BpmnTreeWalker(handler, translate) {

    // list of containers already walked
    var handledElements = {};

    // list of elements to handle deferred to ensure
    // prerequisites are drawn
    var deferred = [];

    var diMap = {};

    // Helpers //////////////////////

    function contextual(fn, ctx) {
      return function(e) {
        fn(e, ctx);
      };
    }

    function handled(element) {
      handledElements[element.id] = element;
    }

    function isHandled(element) {
      return handledElements[element.id];
    }

    function visit(element, ctx) {

      var gfx = element.gfx;

      // avoid multiple rendering of elements
      if (gfx) {
        throw new Error(
          translate('already rendered {element}', { element: elementToString(element) })
        );
      }

      // call handler
      return handler.element(element, diMap[element.id], ctx);
    }

    function visitRoot(element, diagram) {
      return handler.root(element, diMap[element.id], diagram);
    }

    function visitIfDi(element, ctx) {

      try {
        var gfx = diMap[element.id] && visit(element, ctx);

        handled(element);

        return gfx;
      } catch (e) {
        logError(e.message, { element: element, error: e });

        console.error(translate('failed to import {element}', { element: elementToString(element) }));
        console.error(e);
      }
    }

    function logError(message, context) {
      handler.error(message, context);
    }

    // DI handling //////////////////////

    function registerDi(di) {
      var bpmnElement = di.bpmnElement;

      if (bpmnElement) {
        if (diMap[bpmnElement.id]) {
          logError(
            translate('multiple DI elements defined for {element}', {
              element: elementToString(bpmnElement)
            }),
            { element: bpmnElement }
          );
        } else {
          diMap[bpmnElement.id] = di;

          ensureCompatDiRef(bpmnElement);
        }
      } else {
        logError(
          translate('no bpmnElement referenced in {element}', {
            element: elementToString(di)
          }),
          { element: di }
        );
      }
    }

    function handleDiagram(diagram) {
      handlePlane(diagram.plane);
    }

    function handlePlane(plane) {
      registerDi(plane);

      forEach$1(plane.planeElement, handlePlaneElement);
    }

    function handlePlaneElement(planeElement) {
      registerDi(planeElement);
    }


    // Semantic handling //////////////////////

    /**
     * Handle definitions and return the rendered diagram (if any)
     *
     * @param {ModdleElement} definitions to walk and import
     * @param {ModdleElement} [diagram] specific diagram to import and display
     *
     * @throws {Error} if no diagram to display could be found
     */
    function handleDefinitions(definitions, diagram) {

      // make sure we walk the correct bpmnElement

      var diagrams = definitions.diagrams;

      if (diagram && diagrams.indexOf(diagram) === -1) {
        throw new Error(translate('diagram not part of bpmn:Definitions'));
      }

      if (!diagram && diagrams && diagrams.length) {
        diagram = diagrams[0];
      }

      // no diagram -> nothing to import
      if (!diagram) {
        throw new Error(translate('no diagram to display'));
      }

      // load DI from selected diagram only
      diMap = {};
      handleDiagram(diagram);


      var plane = diagram.plane;

      if (!plane) {
        throw new Error(translate(
          'no plane for {element}',
          { element: elementToString(diagram) }
        ));
      }

      var rootElement = plane.bpmnElement;

      // ensure we default to a suitable display candidate (process or collaboration),
      // even if non is specified in DI
      if (!rootElement) {
        rootElement = findDisplayCandidate(definitions);

        if (!rootElement) {
          throw new Error(translate('no process or collaboration to display'));
        } else {

          logError(
            translate('correcting missing bpmnElement on {plane} to {rootElement}', {
              plane: elementToString(plane),
              rootElement: elementToString(rootElement)
            })
          );

          // correct DI on the fly
          plane.bpmnElement = rootElement;
          registerDi(plane);
        }
      }


      var ctx = visitRoot(rootElement, plane);

      if (is$2(rootElement, 'bpmn:Process') || is$2(rootElement, 'bpmn:SubProcess')) {
        handleProcess(rootElement, ctx);
      } else if (is$2(rootElement, 'bpmn:Collaboration')) {
        handleCollaboration(rootElement, ctx);

        // force drawing of everything not yet drawn that is part of the target DI
        handleUnhandledProcesses(definitions.rootElements, ctx);
      } else {
        throw new Error(
          translate('unsupported bpmnElement for {plane}: {rootElement}', {
            plane: elementToString(plane),
            rootElement: elementToString(rootElement)
          })
        );
      }

      // handle all deferred elements
      handleDeferred();
    }

    function handleDeferred() {

      var fn;

      // drain deferred until empty
      while (deferred.length) {
        fn = deferred.shift();

        fn();
      }
    }

    function handleProcess(process, context) {
      handleFlowElementsContainer(process, context);
      handleIoSpecification(process.ioSpecification, context);

      handleArtifacts(process.artifacts, context);

      // log process handled
      handled(process);
    }

    function handleUnhandledProcesses(rootElements, ctx) {

      // walk through all processes that have not yet been drawn and draw them
      // if they contain lanes with DI information.
      // we do this to pass the free-floating lane test cases in the MIWG test suite
      var processes = filter(rootElements, function(e) {
        return !isHandled(e) && is$2(e, 'bpmn:Process') && e.laneSets;
      });

      processes.forEach(contextual(handleProcess, ctx));
    }

    function handleMessageFlow(messageFlow, context) {
      visitIfDi(messageFlow, context);
    }

    function handleMessageFlows(messageFlows, context) {
      forEach$1(messageFlows, contextual(handleMessageFlow, context));
    }

    function handleDataAssociation(association, context) {
      visitIfDi(association, context);
    }

    function handleDataInput(dataInput, context) {
      visitIfDi(dataInput, context);
    }

    function handleDataOutput(dataOutput, context) {
      visitIfDi(dataOutput, context);
    }

    function handleArtifact(artifact, context) {

      // bpmn:TextAnnotation
      // bpmn:Group
      // bpmn:Association

      visitIfDi(artifact, context);
    }

    function handleArtifacts(artifacts, context) {

      forEach$1(artifacts, function(e) {
        if (is$2(e, 'bpmn:Association')) {
          deferred.push(function() {
            handleArtifact(e, context);
          });
        } else {
          handleArtifact(e, context);
        }
      });
    }

    function handleIoSpecification(ioSpecification, context) {

      if (!ioSpecification) {
        return;
      }

      forEach$1(ioSpecification.dataInputs, contextual(handleDataInput, context));
      forEach$1(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
    }

    function handleSubProcess(subProcess, context) {
      handleFlowElementsContainer(subProcess, context);
      handleArtifacts(subProcess.artifacts, context);
    }

    function handleFlowNode(flowNode, context) {
      var childCtx = visitIfDi(flowNode, context);

      if (is$2(flowNode, 'bpmn:SubProcess')) {
        handleSubProcess(flowNode, childCtx || context);
      }

      if (is$2(flowNode, 'bpmn:Activity')) {
        handleIoSpecification(flowNode.ioSpecification, context);
      }

      // defer handling of associations
      // affected types:
      //
      //   * bpmn:Activity
      //   * bpmn:ThrowEvent
      //   * bpmn:CatchEvent
      //
      deferred.push(function() {
        forEach$1(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
        forEach$1(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
      });
    }

    function handleSequenceFlow(sequenceFlow, context) {
      visitIfDi(sequenceFlow, context);
    }

    function handleDataElement(dataObject, context) {
      visitIfDi(dataObject, context);
    }

    function handleLane(lane, context) {

      deferred.push(function() {

        var newContext = visitIfDi(lane, context);

        if (lane.childLaneSet) {
          handleLaneSet(lane.childLaneSet, newContext || context);
        }

        wireFlowNodeRefs(lane);
      });
    }

    function handleLaneSet(laneSet, context) {
      forEach$1(laneSet.lanes, contextual(handleLane, context));
    }

    function handleLaneSets(laneSets, context) {
      forEach$1(laneSets, contextual(handleLaneSet, context));
    }

    function handleFlowElementsContainer(container, context) {
      handleFlowElements(container.flowElements, context);

      if (container.laneSets) {
        handleLaneSets(container.laneSets, context);
      }
    }

    function handleFlowElements(flowElements, context) {
      forEach$1(flowElements, function(e) {
        if (is$2(e, 'bpmn:SequenceFlow')) {
          deferred.push(function() {
            handleSequenceFlow(e, context);
          });
        } else if (is$2(e, 'bpmn:BoundaryEvent')) {
          deferred.unshift(function() {
            handleFlowNode(e, context);
          });
        } else if (is$2(e, 'bpmn:FlowNode')) {
          handleFlowNode(e, context);
        } else if (is$2(e, 'bpmn:DataObject')) ; else if (is$2(e, 'bpmn:DataStoreReference')) {
          handleDataElement(e, context);
        } else if (is$2(e, 'bpmn:DataObjectReference')) {
          handleDataElement(e, context);
        } else {
          logError(
            translate('unrecognized flowElement {element} in context {context}', {
              element: elementToString(e),
              context: (context ? elementToString(context.businessObject) : 'null')
            }),
            { element: e, context: context }
          );
        }
      });
    }

    function handleParticipant(participant, context) {
      var newCtx = visitIfDi(participant, context);

      var process = participant.processRef;
      if (process) {
        handleProcess(process, newCtx || context);
      }
    }

    function handleCollaboration(collaboration, context) {

      forEach$1(collaboration.participants, contextual(handleParticipant, context));

      handleArtifacts(collaboration.artifacts, context);

      // handle message flows latest in the process
      deferred.push(function() {
        handleMessageFlows(collaboration.messageFlows, context);
      });
    }


    function wireFlowNodeRefs(lane) {

      // wire the virtual flowNodeRefs <-> relationship
      forEach$1(lane.flowNodeRef, function(flowNode) {
        var lanes = flowNode.get('lanes');

        if (lanes) {
          lanes.push(lane);
        }
      });
    }

    // API //////////////////////

    return {
      handleDeferred: handleDeferred,
      handleDefinitions: handleDefinitions,
      handleSubProcess: handleSubProcess,
      registerDi: registerDi
    };
  }

  /**
   * Is an element of the given BPMN type?
   *
   * @param  {djs.model.Base|ModdleElement} element
   * @param  {string} type
   *
   * @return {boolean}
   */
  function is$1(element, type) {
    var bo = getBusinessObject(element);

    return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
  }


  /**
   * Return true if element has any of the given types.
   *
   * @param {djs.model.Base} element
   * @param {Array<string>} types
   *
   * @return {boolean}
   */
  function isAny(element, types) {
    return some(types, function(t) {
      return is$1(element, t);
    });
  }

  /**
   * Return the business object for a given element.
   *
   * @param  {djs.model.Base|ModdleElement} element
   *
   * @return {ModdleElement}
   */
  function getBusinessObject(element) {
    return (element && element.businessObject) || element;
  }

  /**
   * Return the di object for a given element.
   *
   * @param  {djs.model.Base} element
   *
   * @return {ModdleElement}
   */
  function getDi(element) {
    return element && element.di;
  }

  /**
   * The importBpmnDiagram result.
   *
   * @typedef {Object} ImportBPMNDiagramResult
   *
   * @property {Array<string>} warnings
   */

  /**
  * The importBpmnDiagram error.
  *
  * @typedef {Error} ImportBPMNDiagramError
  *
  * @property {Array<string>} warnings
  */

  /**
   * Import the definitions into a diagram.
   *
   * Errors and warnings are reported through the specified callback.
   *
   * @param  {djs.Diagram} diagram
   * @param  {ModdleElement<Definitions>} definitions
   * @param  {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered
   * (if not provided, the first one will be rendered)
   *
   * Returns {Promise<ImportBPMNDiagramResult, ImportBPMNDiagramError>}
   */
  function importBpmnDiagram(diagram, definitions, bpmnDiagram) {

    var importer,
        eventBus,
        translate,
        canvas;

    var error,
        warnings = [];

    /**
     * Walk the diagram semantically, importing (=drawing)
     * all elements you encounter.
     *
     * @param {ModdleElement<Definitions>} definitions
     * @param {ModdleElement<BPMNDiagram>} bpmnDiagram
     */
    function render(definitions, bpmnDiagram) {

      var visitor = {

        root: function(element, di) {
          return importer.add(element, di);
        },

        element: function(element, di, parentShape) {
          return importer.add(element, di, parentShape);
        },

        error: function(message, context) {
          warnings.push({ message: message, context: context });
        }
      };

      var walker = new BpmnTreeWalker(visitor, translate);


      bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]);

      var diagramsToImport = getDiagramsToImport(definitions, bpmnDiagram);

      if (!diagramsToImport) {
        throw new Error(translate('no diagram to display'));
      }

      // traverse BPMN 2.0 document model,
      // starting at definitions
      forEach$1(diagramsToImport, function(diagram) {
        walker.handleDefinitions(definitions, diagram);
      });

      var rootId = bpmnDiagram.plane.bpmnElement.id;

      // we do need to account for different ways we create root elements
      // each nested imported <root> do have the `_plane` suffix, while
      // the root <root> is found under the business object ID
      canvas.setRootElement(
        canvas.findRoot(rootId + '_plane') || canvas.findRoot(rootId)
      );
    }

    return new Promise(function(resolve, reject) {
      try {
        importer = diagram.get('bpmnImporter');
        eventBus = diagram.get('eventBus');
        translate = diagram.get('translate');
        canvas = diagram.get('canvas');

        eventBus.fire('import.render.start', { definitions: definitions });

        render(definitions, bpmnDiagram);

        eventBus.fire('import.render.complete', {
          error: error,
          warnings: warnings
        });

        return resolve({ warnings: warnings });
      } catch (e) {

        e.warnings = warnings;
        return reject(e);
      }
    });
  }

  /**
   * Returns all diagrams in the same hierarchy as the requested diagram.
   * Includes all parent and sub process diagrams.
   *
   * @param {Array} definitions
   * @param {Object} bpmnDiagram
   *
   * @returns {Array<Object>}
   */
  function getDiagramsToImport(definitions, bpmnDiagram) {
    if (!bpmnDiagram) {
      return;
    }

    var bpmnElement = bpmnDiagram.plane.bpmnElement,
        rootElement = bpmnElement;

    if (!is$1(bpmnElement, 'bpmn:Process') && !is$1(bpmnElement, 'bpmn:Collaboration')) {
      rootElement = findRootProcess(bpmnElement);
    }

    // in case the process is part of a collaboration, the plane references the
    // collaboration, not the process
    var collaboration;

    if (is$1(rootElement, 'bpmn:Collaboration')) {
      collaboration = rootElement;
    } else {
      collaboration = find(definitions.rootElements, function(element) {
        if (!is$1(element, 'bpmn:Collaboration')) {
          return;
        }

        return find(element.participants, function(participant) {
          return participant.processRef === rootElement;
        });
      });
    }

    var rootElements = [ rootElement ];

    // all collaboration processes can contain sub-diagrams
    if (collaboration) {
      rootElements = map(collaboration.participants, function(participant) {
        return participant.processRef;
      });

      rootElements.push(collaboration);
    }

    var allChildren = selfAndAllFlowElements(rootElements);

    // if we have multiple diagrams referencing the same element, we
    // use the first in the file
    var diagramsToImport = [ bpmnDiagram ];
    var handledElements = [ bpmnElement ];

    forEach$1(definitions.diagrams, function(diagram) {
      var businessObject = diagram.plane.bpmnElement;

      if (
        allChildren.indexOf(businessObject) !== -1 &&
        handledElements.indexOf(businessObject) === -1
      ) {
        diagramsToImport.push(diagram);
        handledElements.push(businessObject);
      }
    });


    return diagramsToImport;
  }

  function selfAndAllFlowElements(elements) {
    var result = [];

    forEach$1(elements, function(element) {
      if (!element) {
        return;
      }

      result.push(element);

      result = result.concat(selfAndAllFlowElements(element.flowElements));
    });

    return result;
  }

  function findRootProcess(element) {
    var parent = element;

    while (parent) {
      if (is$1(parent, 'bpmn:Process')) {
        return parent;
      }

      parent = parent.$parent;
    }
  }

  /**
   * This file must not be changed or exchanged.
   *
   * @see http://bpmn.io/license for more information.
   */


  // inlined ../../resources/logo.svg
  var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.02 5.57" width="53" height="21"><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>';

  var BPMNIO_IMG = BPMNIO_LOGO_SVG;

  var LOGO_STYLES = {
    verticalAlign: 'middle'
  };

  var LINK_STYLES = {
    'color': '#404040'
  };

  var LIGHTBOX_STYLES = {
    'zIndex': '1001',
    'position': 'fixed',
    'top': '0',
    'left': '0',
    'right': '0',
    'bottom': '0'
  };

  var BACKDROP_STYLES = {
    'width': '100%',
    'height': '100%',
    'background': 'rgba(40,40,40,0.2)'
  };

  var NOTICE_STYLES = {
    'position': 'absolute',
    'left': '50%',
    'top': '40%',
    'transform': 'translate(-50%)',
    'width': '260px',
    'padding': '10px',
    'background': 'white',
    'boxShadow': '0 1px 4px rgba(0,0,0,0.3)',
    'fontFamily': 'Helvetica, Arial, sans-serif',
    'fontSize': '14px',
    'display': 'flex',
    'lineHeight': '1.3'
  };

  var LIGHTBOX_MARKUP =
    '<div class="bjs-powered-by-lightbox">' +
      '<div class="backdrop"></div>' +
      '<div class="notice">' +
        '<a href="https://bpmn.io" target="_blank" rel="noopener" class="link">' +
          BPMNIO_IMG +
        '</a>' +
        '<span>' +
          'Web-based tooling for BPMN, DMN and CMMN diagrams ' +
          'powered by <a href="https://bpmn.io" target="_blank" rel="noopener">bpmn.io</a>.' +
        '</span>' +
      '</div>' +
    '</div>';


  var lightbox;

  function createLightbox() {
    lightbox = domify(LIGHTBOX_MARKUP);

    assign$1(lightbox, LIGHTBOX_STYLES);
    assign$1(query('svg', lightbox), LOGO_STYLES);
    assign$1(query('.backdrop', lightbox), BACKDROP_STYLES);
    assign$1(query('.notice', lightbox), NOTICE_STYLES);
    assign$1(query('.link', lightbox), LINK_STYLES, {
      'margin': '15px 20px 15px 10px',
      'alignSelf': 'center'
    });
  }

  function open() {

    if (!lightbox) {
      createLightbox();

      delegate.bind(lightbox, '.backdrop', 'click', function(event) {
        document.body.removeChild(lightbox);
      });
    }

    document.body.appendChild(lightbox);
  }

  /**
   * The code in the <project-logo></project-logo> area
   * must not be changed.
   *
   * @see http://bpmn.io/license for more information.
   */

  /**
   * A base viewer for BPMN 2.0 diagrams.
   *
   * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
   * bundles that include actual features.
   *
   * @param {Object} [options] configuration options to pass to the viewer
   * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
   * @param {string|number} [options.width] the width of the viewer
   * @param {string|number} [options.height] the height of the viewer
   * @param {Object} [options.moddleExtensions] extension packages to provide
   * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
   * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
   */
  function BaseViewer(options) {

    options = assign({}, DEFAULT_OPTIONS, options);

    this._moddle = this._createModdle(options);

    this._container = this._createContainer(options);

    /* <project-logo> */

    addProjectLogo(this._container);

    /* </project-logo> */

    this._init(this._container, this._moddle, options);
  }

  e(BaseViewer, Diagram);

  /**
  * The importXML result.
  *
  * @typedef {Object} ImportXMLResult
  *
  * @property {Array<string>} warnings
  */

  /**
  * The importXML error.
  *
  * @typedef {Error} ImportXMLError
  *
  * @property {Array<string>} warnings
  */

  /**
   * Parse and render a BPMN 2.0 diagram.
   *
   * Once finished the viewer reports back the result to the
   * provided callback function with (err, warnings).
   *
   * ## Life-Cycle Events
   *
   * During import the viewer will fire life-cycle events:
   *
   *   * import.parse.start (about to read model from xml)
   *   * import.parse.complete (model read; may have worked or not)
   *   * import.render.start (graphical import start)
   *   * import.render.complete (graphical import finished)
   *   * import.done (everything done)
   *
   * You can use these events to hook into the life-cycle.
   *
   * @param {string} xml the BPMN 2.0 xml
   * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
   *
   * Returns {Promise<ImportXMLResult, ImportXMLError>}
   */
  BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) {

    var self = this;

    function ParseCompleteEvent(data) {

      var event = self.get('eventBus').createEvent(data);

      // TODO(nikku): remove with future bpmn-js version
      Object.defineProperty(event, 'context', {
        enumerable: true,
        get: function() {

          console.warn(new Error(
            'import.parse.complete <context> is deprecated ' +
            'and will be removed in future library versions'
          ));

          return {
            warnings: data.warnings,
            references: data.references,
            elementsById: data.elementsById
          };
        }
      });

      return event;
    }

    return new Promise(function(resolve, reject) {

      // hook in pre-parse listeners +
      // allow xml manipulation
      xml = self._emit('import.parse.start', { xml: xml }) || xml;

      self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) {
        var definitions = result.rootElement;
        var references = result.references;
        var parseWarnings = result.warnings;
        var elementsById = result.elementsById;

        // hook in post parse listeners +
        // allow definitions manipulation
        definitions = self._emit('import.parse.complete', ParseCompleteEvent({
          error: null,
          definitions: definitions,
          elementsById: elementsById,
          references: references,
          warnings: parseWarnings
        })) || definitions;

        self.importDefinitions(definitions, bpmnDiagram).then(function(result) {
          var allWarnings = [].concat(parseWarnings, result.warnings || []);

          self._emit('import.done', { error: null, warnings: allWarnings });

          return resolve({ warnings: allWarnings });
        }).catch(function(err) {
          var allWarnings = [].concat(parseWarnings, err.warnings || []);

          self._emit('import.done', { error: err, warnings: allWarnings });

          return reject(addWarningsToError(err, allWarnings));
        });
      }).catch(function(err) {

        self._emit('import.parse.complete', {
          error: err
        });

        err = checkValidationError(err);

        self._emit('import.done', { error: err, warnings: err.warnings });

        return reject(err);
      });
    });
  });

  /**
  * The importDefinitions result.
  *
  * @typedef {Object} ImportDefinitionsResult
  *
  * @property {Array<string>} warnings
  */

  /**
  * The importDefinitions error.
  *
  * @typedef {Error} ImportDefinitionsError
  *
  * @property {Array<string>} warnings
  */

  /**
   * Import parsed definitions and render a BPMN 2.0 diagram.
   *
   * Once finished the viewer reports back the result to the
   * provided callback function with (err, warnings).
   *
   * ## Life-Cycle Events
   *
   * During import the viewer will fire life-cycle events:
   *
   *   * import.render.start (graphical import start)
   *   * import.render.complete (graphical import finished)
   *
   * You can use these events to hook into the life-cycle.
   *
   * @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
   * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
   *
   * Returns {Promise<ImportDefinitionsResult, ImportDefinitionsError>}
   */
  BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) {

    var self = this;

    return new Promise(function(resolve, reject) {

      self._setDefinitions(definitions);

      self.open(bpmnDiagram).then(function(result) {

        var warnings = result.warnings;

        return resolve({ warnings: warnings });
      }).catch(function(err) {

        return reject(err);
      });
    });
  });

  /**
   * The open result.
   *
   * @typedef {Object} OpenResult
   *
   * @property {Array<string>} warnings
   */

  /**
  * The open error.
  *
  * @typedef {Error} OpenError
  *
  * @property {Array<string>} warnings
  */

  /**
   * Open diagram of previously imported XML.
   *
   * Once finished the viewer reports back the result to the
   * provided callback function with (err, warnings).
   *
   * ## Life-Cycle Events
   *
   * During switch the viewer will fire life-cycle events:
   *
   *   * import.render.start (graphical import start)
   *   * import.render.complete (graphical import finished)
   *
   * You can use these events to hook into the life-cycle.
   *
   * @param {string|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
   *
   * Returns {Promise<OpenResult, OpenError>}
   */
  BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) {

    var definitions = this._definitions;
    var bpmnDiagram = bpmnDiagramOrId;

    var self = this;

    return new Promise(function(resolve, reject) {
      if (!definitions) {
        var err1 = new Error('no XML imported');

        return reject(addWarningsToError(err1, []));
      }

      if (typeof bpmnDiagramOrId === 'string') {
        bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);

        if (!bpmnDiagram) {
          var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');

          return reject(addWarningsToError(err2, []));
        }
      }

      // clear existing rendered diagram
      // catch synchronous exceptions during #clear()
      try {
        self.clear();
      } catch (error) {

        return reject(addWarningsToError(error, []));
      }

      // perform graphical import
      importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) {

        var warnings = result.warnings;

        return resolve({ warnings: warnings });
      }).catch(function(err) {

        return reject(err);
      });
    });
  });

  /**
   * The saveXML result.
   *
   * @typedef {Object} SaveXMLResult
   *
   * @property {string} xml
   */

  /**
   * Export the currently displayed BPMN 2.0 diagram as
   * a BPMN 2.0 XML document.
   *
   * ## Life-Cycle Events
   *
   * During XML saving the viewer will fire life-cycle events:
   *
   *   * saveXML.start (before serialization)
   *   * saveXML.serialized (after xml generation)
   *   * saveXML.done (everything done)
   *
   * You can use these events to hook into the life-cycle.
   *
   * @param {Object} [options] export options
   * @param {boolean} [options.format=false] output formatted XML
   * @param {boolean} [options.preamble=true] output preamble
   *
   * Returns {Promise<SaveXMLResult, Error>}
   */
  BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) {

    options = options || {};

    var self = this;

    var definitions = this._definitions;

    return new Promise(function(resolve) {

      if (!definitions) {
        return resolve({
          error: new Error('no definitions loaded')
        });
      }

      // allow to fiddle around with definitions
      definitions = self._emit('saveXML.start', {
        definitions: definitions
      }) || definitions;

      self._moddle.toXML(definitions, options).then(function(result) {

        var xml = result.xml;

        xml = self._emit('saveXML.serialized', {
          xml: xml
        }) || xml;

        return resolve({
          xml: xml
        });
      });
    }).catch(function(error) {
      return { error: error };
    }).then(function(result) {

      self._emit('saveXML.done', result);

      var error = result.error;

      if (error) {
        return Promise.reject(error);
      }

      return result;
    });
  });

  /**
   * The saveSVG result.
   *
   * @typedef {Object} SaveSVGResult
   *
   * @property {string} svg
   */

  /**
   * Export the currently displayed BPMN 2.0 diagram as
   * an SVG image.
   *
   * ## Life-Cycle Events
   *
   * During SVG saving the viewer will fire life-cycle events:
   *
   *   * saveSVG.start (before serialization)
   *   * saveSVG.done (everything done)
   *
   * You can use these events to hook into the life-cycle.
   *
   * @param {Object} [options]
   *
   * Returns {Promise<SaveSVGResult, Error>}
   */
  BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) {

    var self = this;

    return new Promise(function(resolve, reject) {

      self._emit('saveSVG.start');

      var svg, err;

      try {
        var canvas = self.get('canvas');

        var contentNode = canvas.getActiveLayer(),
            defsNode = query('defs', canvas._svg);

        var contents = innerSVG(contentNode),
            defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';

        var bbox = contentNode.getBBox();

        svg =
          '<?xml version="1.0" encoding="utf-8"?>\n' +
          '<!-- created with bpmn-js / http://bpmn.io -->\n' +
          '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
          '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
               'width="' + bbox.width + '" height="' + bbox.height + '" ' +
               'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
            defs + contents +
          '</svg>';
      } catch (e) {
        err = e;
      }

      self._emit('saveSVG.done', {
        error: err,
        svg: svg
      });

      if (!err) {
        return resolve({ svg: svg });
      }

      return reject(err);
    });
  });

  /**
   * Get a named diagram service.
   *
   * @example
   *
   * var elementRegistry = viewer.get('elementRegistry');
   * var startEventShape = elementRegistry.get('StartEvent_1');
   *
   * @param {string} name
   *
   * @return {Object} diagram service instance
   *
   * @method BaseViewer#get
   */

  /**
   * Invoke a function in the context of this viewer.
   *
   * @example
   *
   * viewer.invoke(function(elementRegistry) {
   *   var startEventShape = elementRegistry.get('StartEvent_1');
   * });
   *
   * @param {Function} fn to be invoked
   *
   * @return {Object} the functions return value
   *
   * @method BaseViewer#invoke
   */


  BaseViewer.prototype._setDefinitions = function(definitions) {
    this._definitions = definitions;
  };

  BaseViewer.prototype.getModules = function() {
    return this._modules;
  };

  /**
   * Remove all drawn elements from the viewer.
   *
   * After calling this method the viewer can still
   * be reused for opening another diagram.
   *
   * @method BaseViewer#clear
   */
  BaseViewer.prototype.clear = function() {
    if (!this.getDefinitions()) {

      // no diagram to clear
      return;
    }

    // remove drawn elements
    Diagram.prototype.clear.call(this);
  };

  /**
   * Destroy the viewer instance and remove all its
   * remainders from the document tree.
   */
  BaseViewer.prototype.destroy = function() {

    // diagram destroy
    Diagram.prototype.destroy.call(this);

    // dom detach
    remove$2(this._container);
  };

  /**
   * Register an event listener
   *
   * Remove a previously added listener via {@link #off(event, callback)}.
   *
   * @param {string} event
   * @param {number} [priority]
   * @param {Function} callback
   * @param {Object} [that]
   */
  BaseViewer.prototype.on = function(event, priority, callback, target) {
    return this.get('eventBus').on(event, priority, callback, target);
  };

  /**
   * De-register an event listener
   *
   * @param {string} event
   * @param {Function} callback
   */
  BaseViewer.prototype.off = function(event, callback) {
    this.get('eventBus').off(event, callback);
  };

  BaseViewer.prototype.attachTo = function(parentNode) {

    if (!parentNode) {
      throw new Error('parentNode required');
    }

    // ensure we detach from the
    // previous, old parent
    this.detach();

    // unwrap jQuery if provided
    if (parentNode.get && parentNode.constructor.prototype.jquery) {
      parentNode = parentNode.get(0);
    }

    if (typeof parentNode === 'string') {
      parentNode = query(parentNode);
    }

    parentNode.appendChild(this._container);

    this._emit('attach', {});

    this.get('canvas').resized();
  };

  BaseViewer.prototype.getDefinitions = function() {
    return this._definitions;
  };

  BaseViewer.prototype.detach = function() {

    var container = this._container,
        parentNode = container.parentNode;

    if (!parentNode) {
      return;
    }

    this._emit('detach', {});

    parentNode.removeChild(container);
  };

  BaseViewer.prototype._init = function(container, moddle, options) {

    var baseModules = options.modules || this.getModules(),
        additionalModules = options.additionalModules || [],
        staticModules = [
          {
            bpmnjs: [ 'value', this ],
            moddle: [ 'value', moddle ]
          }
        ];

    var diagramModules = [].concat(staticModules, baseModules, additionalModules);

    var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
      canvas: assign({}, options.canvas, { container: container }),
      modules: diagramModules
    });

    // invoke diagram constructor
    Diagram.call(this, diagramOptions);

    if (options && options.container) {
      this.attachTo(options.container);
    }
  };

  /**
   * Emit an event on the underlying {@link EventBus}
   *
   * @param  {string} type
   * @param  {Object} event
   *
   * @return {Object} event processing result (if any)
   */
  BaseViewer.prototype._emit = function(type, event) {
    return this.get('eventBus').fire(type, event);
  };

  BaseViewer.prototype._createContainer = function(options) {

    var container = domify('<div class="bjs-container"></div>');

    assign$1(container, {
      width: ensureUnit(options.width),
      height: ensureUnit(options.height),
      position: options.position
    });

    return container;
  };

  BaseViewer.prototype._createModdle = function(options) {
    var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);

    return new simple(moddleOptions);
  };

  BaseViewer.prototype._modules = [];

  // helpers ///////////////

  function addWarningsToError(err, warningsAry) {
    err.warnings = warningsAry;
    return err;
  }

  function checkValidationError(err) {

    // check if we can help the user by indicating wrong BPMN 2.0 xml
    // (in case he or the exporting tool did not get that right)

    var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
    var match = pattern.exec(err.message);

    if (match) {
      err.message =
        'unparsable content <' + match[1] + '> detected; ' +
        'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
    }

    return err;
  }

  var DEFAULT_OPTIONS = {
    width: '100%',
    height: '100%',
    position: 'relative'
  };


  /**
   * Ensure the passed argument is a proper unit (defaulting to px)
   */
  function ensureUnit(val) {
    return val + (isNumber(val) ? 'px' : '');
  }


  /**
   * Find BPMNDiagram in definitions by ID
   *
   * @param {ModdleElement<Definitions>} definitions
   * @param {string} diagramId
   *
   * @return {ModdleElement<BPMNDiagram>|null}
   */
  function findBPMNDiagram(definitions, diagramId) {
    if (!diagramId) {
      return null;
    }

    return find(definitions.diagrams, function(element) {
      return element.id === diagramId;
    }) || null;
  }

  /**
   * Adds the project logo to the diagram container as
   * required by the bpmn.io license.
   *
   * @see http://bpmn.io/license
   *
   * @param {Element} container
   */
  function addProjectLogo(container) {
    var img = BPMNIO_IMG;

    var linkMarkup =
      '<a href="http://bpmn.io" ' +
         'target="_blank" ' +
         'class="bjs-powered-by" ' +
         'title="Powered by bpmn.io" ' +
        '>' +
        img +
      '</a>';

    var linkElement = domify(linkMarkup);

    assign$1(query('svg', linkElement), LOGO_STYLES);
    assign$1(linkElement, LINK_STYLES, {
      position: 'absolute',
      bottom: '15px',
      right: '15px',
      zIndex: '100'
    });

    container.appendChild(linkElement);

    componentEvent.bind(linkElement, 'click', function(event) {
      open();

      event.preventDefault();
    });
  }

  /* </project-logo> */

  /**
   * A base modeler for BPMN 2.0 diagrams.
   *
   * Have a look at {@link Modeler} for a bundle that includes actual features.
   *
   * @param {Object} [options] configuration options to pass to the viewer
   * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
   * @param {string|number} [options.width] the width of the viewer
   * @param {string|number} [options.height] the height of the viewer
   * @param {Object} [options.moddleExtensions] extension packages to provide
   * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
   * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
   */
  function BaseModeler(options) {
    BaseViewer.call(this, options);

    // hook ID collection into the modeler
    this.on('import.parse.complete', function(event) {
      if (!event.error) {
        this._collectIds(event.definitions, event.elementsById);
      }
    }, this);

    this.on('diagram.destroy', function() {
      this.get('moddle').ids.clear();
    }, this);
  }

  e(BaseModeler, BaseViewer);


  /**
   * Create a moddle instance, attaching ids to it.
   *
   * @param {Object} options
   */
  BaseModeler.prototype._createModdle = function(options) {
    var moddle = BaseViewer.prototype._createModdle.call(this, options);

    // attach ids to moddle to be able to track
    // and validated ids in the BPMN 2.0 XML document
    // tree
    moddle.ids = new Ids([ 32, 36, 1 ]);

    return moddle;
  };

  /**
   * Collect ids processed during parsing of the
   * definitions object.
   *
   * @param {ModdleElement} definitions
   * @param {Context} context
   */
  BaseModeler.prototype._collectIds = function(definitions, elementsById) {

    var moddle = definitions.$model,
        ids = moddle.ids,
        id;

    // remove references from previous import
    ids.clear();

    for (id in elementsById) {
      ids.claim(id, elementsById[id]);
    }
  };

  function isExpanded(element, di) {

    if (is$1(element, 'bpmn:CallActivity')) {
      return false;
    }

    if (is$1(element, 'bpmn:SubProcess')) {
      di = di || getDi(element);

      if (di && is$1(di, 'bpmndi:BPMNPlane')) {
        return true;
      }

      return di && !!di.isExpanded;
    }

    if (is$1(element, 'bpmn:Participant')) {
      return !!getBusinessObject(element).processRef;
    }

    return true;
  }

  function isInterrupting(element) {
    return element && getBusinessObject(element).isInterrupting !== false;
  }

  function isEventSubProcess(element) {
    return element && !!getBusinessObject(element).triggeredByEvent;
  }

  function hasEventDefinition$2(element, eventType) {
    var bo = getBusinessObject(element),
        hasEventDefinition = false;

    if (bo.eventDefinitions) {
      forEach$1(bo.eventDefinitions, function(event) {
        if (is$1(event, eventType)) {
          hasEventDefinition = true;
        }
      });
    }

    return hasEventDefinition;
  }

  function hasErrorEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:ErrorEventDefinition');
  }

  function hasEscalationEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:EscalationEventDefinition');
  }

  function hasCompensateEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
  }

  function getLabelAttr(semantic) {
    if (
      is$1(semantic, 'bpmn:FlowElement') ||
      is$1(semantic, 'bpmn:Participant') ||
      is$1(semantic, 'bpmn:Lane') ||
      is$1(semantic, 'bpmn:SequenceFlow') ||
      is$1(semantic, 'bpmn:MessageFlow') ||
      is$1(semantic, 'bpmn:DataInput') ||
      is$1(semantic, 'bpmn:DataOutput')
    ) {
      return 'name';
    }

    if (is$1(semantic, 'bpmn:TextAnnotation')) {
      return 'text';
    }

    if (is$1(semantic, 'bpmn:Group')) {
      return 'categoryValueRef';
    }
  }

  function getCategoryValue(semantic) {
    var categoryValueRef = semantic['categoryValueRef'];

    if (!categoryValueRef) {
      return '';
    }


    return categoryValueRef.value || '';
  }

  function getLabel(element) {
    var semantic = element.businessObject,
        attr = getLabelAttr(semantic);

    if (attr) {

      if (attr === 'categoryValueRef') {

        return getCategoryValue(semantic);
      }

      return semantic[attr] || '';
    }
  }


  function setLabel(element, text, isExternal) {
    var semantic = element.businessObject,
        attr = getLabelAttr(semantic);

    if (attr) {

      if (attr === 'categoryValueRef') {
        semantic['categoryValueRef'].value = text;
      } else {
        semantic[attr] = text;
      }

    }

    return element;
  }

  var black = 'hsl(225, 10%, 15%)';

  // element utils //////////////////////

  /**
   * Checks if eventDefinition of the given element matches with semantic type.
   *
   * @return {boolean} true if element is of the given semantic type
   */
  function isTypedEvent(event, eventDefinitionType, filter) {

    function matches(definition, filter) {
      return every(filter, function(val, key) {

        // we want a == conversion here, to be able to catch
        // undefined == false and friends
        /* jshint -W116 */
        return definition[key] == val;
      });
    }

    return some(event.eventDefinitions, function(definition) {
      return definition.$type === eventDefinitionType && matches(event, filter);
    });
  }

  function isThrowEvent(event) {
    return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
  }

  function isCollection(element) {
    var dataObject = element.dataObjectRef;

    return element.isCollection || (dataObject && dataObject.isCollection);
  }

  function getSemantic(element) {
    return element.businessObject;
  }


  // color access //////////////////////

  function getFillColor(element, defaultColor) {
    var di = getDi(element);

    return di.get('color:background-color') || di.get('bioc:fill') || defaultColor || 'white';
  }

  function getStrokeColor$1(element, defaultColor) {
    var di = getDi(element);

    return di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black;
  }

  function getLabelColor(element, defaultColor, defaultStrokeColor) {
    var di = getDi(element),
        label = di.get('label');

    return label && label.get('color:color') || defaultColor ||
      getStrokeColor$1(element, defaultStrokeColor);
  }

  // cropping path customizations //////////////////////

  function getCirclePath(shape) {

    var cx = shape.x + shape.width / 2,
        cy = shape.y + shape.height / 2,
        radius = shape.width / 2;

    var circlePath = [
      [ 'M', cx, cy ],
      [ 'm', 0, -radius ],
      [ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ],
      [ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ],
      [ 'z' ]
    ];

    return componentsToPath(circlePath);
  }

  function getRoundRectPath(shape, borderRadius) {

    var x = shape.x,
        y = shape.y,
        width = shape.width,
        height = shape.height;

    var roundRectPath = [
      [ 'M', x + borderRadius, y ],
      [ 'l', width - borderRadius * 2, 0 ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ],
      [ 'l', 0, height - borderRadius * 2 ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ],
      [ 'l', borderRadius * 2 - width, 0 ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ],
      [ 'l', 0, borderRadius * 2 - height ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ],
      [ 'z' ]
    ];

    return componentsToPath(roundRectPath);
  }

  function getDiamondPath(shape) {

    var width = shape.width,
        height = shape.height,
        x = shape.x,
        y = shape.y,
        halfWidth = width / 2,
        halfHeight = height / 2;

    var diamondPath = [
      [ 'M', x + halfWidth, y ],
      [ 'l', halfWidth, halfHeight ],
      [ 'l', -halfWidth, halfHeight ],
      [ 'l', -halfWidth, -halfHeight ],
      [ 'z' ]
    ];

    return componentsToPath(diamondPath);
  }

  function getRectPath(shape) {
    var x = shape.x,
        y = shape.y,
        width = shape.width,
        height = shape.height;

    var rectPath = [
      [ 'M', x, y ],
      [ 'l', width, 0 ],
      [ 'l', 0, height ],
      [ 'l', -width, 0 ],
      [ 'z' ]
    ];

    return componentsToPath(rectPath);
  }

  var RENDERER_IDS = new Ids();

  var TASK_BORDER_RADIUS = 10;
  var INNER_OUTER_DIST = 3;

  var DEFAULT_FILL_OPACITY = .95,
      HIGH_FILL_OPACITY = .35;

  var ELEMENT_LABEL_DISTANCE$1 = 10;

  function BpmnRenderer(
      config, eventBus, styles, pathMap,
      canvas, textRenderer, priority) {

    BaseRenderer.call(this, eventBus, priority);

    var defaultFillColor = config && config.defaultFillColor,
        defaultStrokeColor = config && config.defaultStrokeColor,
        defaultLabelColor = config && config.defaultLabelColor;

    var rendererId = RENDERER_IDS.next();

    var markers = {};

    var computeStyle = styles.computeStyle;

    function addMarker(id, options) {
      var attrs = assign({
        fill: black,
        strokeWidth: 1,
        strokeLinecap: 'round',
        strokeDasharray: 'none'
      }, options.attrs);

      var ref = options.ref || { x: 0, y: 0 };

      var scale = options.scale || 1;

      // fix for safari / chrome / firefox bug not correctly
      // resetting stroke dash array
      if (attrs.strokeDasharray === 'none') {
        attrs.strokeDasharray = [ 10000, 1 ];
      }

      var marker = create$1('marker');

      attr(options.element, attrs);

      append(marker, options.element);

      attr(marker, {
        id: id,
        viewBox: '0 0 20 20',
        refX: ref.x,
        refY: ref.y,
        markerWidth: 20 * scale,
        markerHeight: 20 * scale,
        orient: 'auto'
      });

      var defs = query('defs', canvas._svg);

      if (!defs) {
        defs = create$1('defs');

        append(canvas._svg, defs);
      }

      append(defs, marker);

      markers[id] = marker;
    }

    function colorEscape(str) {

      // only allow characters and numbers
      return str.replace(/[^0-9a-zA-z]+/g, '_');
    }

    function marker(type, fill, stroke) {
      var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId;

      if (!markers[id]) {
        createMarker(id, type, fill, stroke);
      }

      return 'url(#' + id + ')';
    }

    function createMarker(id, type, fill, stroke) {

      if (type === 'sequenceflow-end') {
        var sequenceflowEnd = create$1('path');
        attr(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' });

        addMarker(id, {
          element: sequenceflowEnd,
          ref: { x: 11, y: 10 },
          scale: 0.5,
          attrs: {
            fill: stroke,
            stroke: stroke
          }
        });
      }

      if (type === 'messageflow-start') {
        var messageflowStart = create$1('circle');
        attr(messageflowStart, { cx: 6, cy: 6, r: 3.5 });

        addMarker(id, {
          element: messageflowStart,
          attrs: {
            fill: fill,
            stroke: stroke
          },
          ref: { x: 6, y: 6 }
        });
      }

      if (type === 'messageflow-end') {
        var messageflowEnd = create$1('path');
        attr(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' });

        addMarker(id, {
          element: messageflowEnd,
          attrs: {
            fill: fill,
            stroke: stroke,
            strokeLinecap: 'butt'
          },
          ref: { x: 8.5, y: 5 }
        });
      }

      if (type === 'association-start') {
        var associationStart = create$1('path');
        attr(associationStart, { d: 'M 11 5 L 1 10 L 11 15' });

        addMarker(id, {
          element: associationStart,
          attrs: {
            fill: 'none',
            stroke: stroke,
            strokeWidth: 1.5
          },
          ref: { x: 1, y: 10 },
          scale: 0.5
        });
      }

      if (type === 'association-end') {
        var associationEnd = create$1('path');
        attr(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' });

        addMarker(id, {
          element: associationEnd,
          attrs: {
            fill: 'none',
            stroke: stroke,
            strokeWidth: 1.5
          },
          ref: { x: 12, y: 10 },
          scale: 0.5
        });
      }

      if (type === 'conditional-flow-marker') {
        var conditionalflowMarker = create$1('path');
        attr(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' });

        addMarker(id, {
          element: conditionalflowMarker,
          attrs: {
            fill: fill,
            stroke: stroke
          },
          ref: { x: -1, y: 10 },
          scale: 0.5
        });
      }

      if (type === 'conditional-default-flow-marker') {
        var conditionaldefaultflowMarker = create$1('path');
        attr(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' });

        addMarker(id, {
          element: conditionaldefaultflowMarker,
          attrs: {
            stroke: stroke
          },
          ref: { x: 0, y: 10 },
          scale: 0.5
        });
      }
    }

    function drawCircle(parentGfx, width, height, offset, attrs) {

      if (isObject(offset)) {
        attrs = offset;
        offset = 0;
      }

      offset = offset || 0;

      attrs = computeStyle(attrs, {
        stroke: black,
        strokeWidth: 2,
        fill: 'white'
      });

      if (attrs.fill === 'none') {
        delete attrs.fillOpacity;
      }

      var cx = width / 2,
          cy = height / 2;

      var circle = create$1('circle');
      attr(circle, {
        cx: cx,
        cy: cy,
        r: Math.round((width + height) / 4 - offset)
      });
      attr(circle, attrs);

      append(parentGfx, circle);

      return circle;
    }

    function drawRect(parentGfx, width, height, r, offset, attrs) {

      if (isObject(offset)) {
        attrs = offset;
        offset = 0;
      }

      offset = offset || 0;

      attrs = computeStyle(attrs, {
        stroke: black,
        strokeWidth: 2,
        fill: 'white'
      });

      var rect = create$1('rect');
      attr(rect, {
        x: offset,
        y: offset,
        width: width - offset * 2,
        height: height - offset * 2,
        rx: r,
        ry: r
      });
      attr(rect, attrs);

      append(parentGfx, rect);

      return rect;
    }

    function drawDiamond(parentGfx, width, height, attrs) {

      var x_2 = width / 2;
      var y_2 = height / 2;

      var points = [ { x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 } ];

      var pointsString = points.map(function(point) {
        return point.x + ',' + point.y;
      }).join(' ');

      attrs = computeStyle(attrs, {
        stroke: black,
        strokeWidth: 2,
        fill: 'white'
      });

      var polygon = create$1('polygon');
      attr(polygon, {
        points: pointsString
      });
      attr(polygon, attrs);

      append(parentGfx, polygon);

      return polygon;
    }

    function drawLine(parentGfx, waypoints, attrs) {
      attrs = computeStyle(attrs, [ 'no-fill' ], {
        stroke: black,
        strokeWidth: 2,
        fill: 'none'
      });

      var line = createLine(waypoints, attrs);

      append(parentGfx, line);

      return line;
    }

    function drawPath(parentGfx, d, attrs) {

      attrs = computeStyle(attrs, [ 'no-fill' ], {
        strokeWidth: 2,
        stroke: black
      });

      var path = create$1('path');
      attr(path, { d: d });
      attr(path, attrs);

      append(parentGfx, path);

      return path;
    }

    function drawMarker(type, parentGfx, path, attrs) {
      return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs));
    }

    function renderer(type) {
      return handlers[type];
    }

    function as(type) {
      return function(parentGfx, element) {
        return renderer(type)(parentGfx, element);
      };
    }

    function renderEventContent(element, parentGfx) {

      var event = getSemantic(element);
      var isThrowing = isThrowEvent(event);

      if (event.eventDefinitions && event.eventDefinitions.length > 1) {
        if (event.parallelMultiple) {
          return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing);
        }
        else {
          return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing);
        }
      }

      if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) {
        return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) {
        return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) {
        return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element);
      }

      if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) {
        return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) {
        return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) {
        return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) {
        return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) {
        return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) {
        return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing);
      }

      if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) {
        return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing);
      }

      return null;
    }

    function renderLabel(parentGfx, label, options) {

      options = assign({
        size: {
          width: 100
        }
      }, options);

      var text = textRenderer.createText(label || '', options);

      classes(text).add('djs-label');

      append(parentGfx, text);

      return text;
    }

    function renderEmbeddedLabel(parentGfx, element, align) {
      var semantic = getSemantic(element);

      return renderLabel(parentGfx, semantic.name, {
        box: element,
        align: align,
        padding: 5,
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
        }
      });
    }

    function renderExternalLabel(parentGfx, element) {

      var box = {
        width: 90,
        height: 30,
        x: element.width / 2 + element.x,
        y: element.height / 2 + element.y
      };

      return renderLabel(parentGfx, getLabel(element), {
        box: box,
        fitBox: true,
        style: assign(
          {},
          textRenderer.getExternalStyle(),
          {
            fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
          }
        )
      });
    }

    function renderLaneLabel(parentGfx, text, element) {
      var textBox = renderLabel(parentGfx, text, {
        box: {
          height: 30,
          width: element.height
        },
        align: 'center-middle',
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
        }
      });

      var top = -1 * element.height;

      transform(textBox, 0, -top, 270);
    }

    function createPathFromConnection(connection) {
      var waypoints = connection.waypoints;

      var pathData = 'm  ' + waypoints[0].x + ',' + waypoints[0].y;
      for (var i = 1; i < waypoints.length; i++) {
        pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' ';
      }
      return pathData;
    }

    var handlers = this.handlers = {
      'bpmn:Event': function(parentGfx, element, attrs) {

        if (!('fillOpacity' in attrs)) {
          attrs.fillOpacity = DEFAULT_FILL_OPACITY;
        }

        return drawCircle(parentGfx, element.width, element.height, attrs);
      },
      'bpmn:StartEvent': function(parentGfx, element) {
        var attrs = {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        var semantic = getSemantic(element);

        if (!semantic.isInterrupting) {
          attrs = {
            strokeDasharray: '6',
            strokeLinecap: 'round',
            fill: getFillColor(element, defaultFillColor),
            stroke: getStrokeColor$1(element, defaultStrokeColor)
          };
        }

        var circle = renderer('bpmn:Event')(parentGfx, element, attrs);

        renderEventContent(element, parentGfx);

        return circle;
      },
      'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.235,
            my: 0.315
          }
        });

        var fill = isThrowing ? getStrokeColor$1(element, defaultStrokeColor) : getFillColor(element, defaultFillColor);
        var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor$1(element, defaultStrokeColor);

        var messagePath = drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: stroke
        });

        return messagePath;
      },
      'bpmn:TimerEventDefinition': function(parentGfx, element) {
        var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
          xScaleFactor: 0.75,
          yScaleFactor: 0.75,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.5,
            my: 0.5
          }
        });

        drawPath(parentGfx, pathData, {
          strokeWidth: 2,
          strokeLinecap: 'square',
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        for (var i = 0;i < 12; i++) {

          var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
            xScaleFactor: 0.75,
            yScaleFactor: 0.75,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.5,
              my: 0.5
            }
          });

          var width = element.width / 2;
          var height = element.height / 2;

          drawPath(parentGfx, linePathData, {
            strokeWidth: 1,
            strokeLinecap: 'square',
            transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')',
            stroke: getStrokeColor$1(element, defaultStrokeColor)
          });
        }

        return circle;
      },
      'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:ConditionalEventDefinition': function(parentGfx, event) {
        var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.222
          }
        });

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_LINK', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.57,
            my: 0.263
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_ERROR', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.2,
            my: 0.722
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
          xScaleFactor: 1.0,
          yScaleFactor: 1.0,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.638,
            my: -0.055
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        var path = drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });

        rotate(path, 45);

        return path;
      },
      'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.22,
            my: 0.5
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.222,
            my: 0.36
          }
        });

        var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: fill
        });
      },
      'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) {
        var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
          xScaleFactor: 1.2,
          yScaleFactor: 1.2,
          containerWidth: event.width,
          containerHeight: event.height,
          position: {
            mx: 0.458,
            my: 0.194
          }
        });

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor$1(event, defaultStrokeColor),
          stroke: getStrokeColor$1(event, defaultStrokeColor)
        });
      },
      'bpmn:EndEvent': function(parentGfx, element) {
        var circle = renderer('bpmn:Event')(parentGfx, element, {
          strokeWidth: 4,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        renderEventContent(element, parentGfx);

        return circle;
      },
      'bpmn:TerminateEventDefinition': function(parentGfx, element) {
        var circle = drawCircle(parentGfx, element.width, element.height, 8, {
          strokeWidth: 4,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return circle;
      },
      'bpmn:IntermediateEvent': function(parentGfx, element) {
        var outer = renderer('bpmn:Event')(parentGfx, element, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        /* inner */
        drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
          strokeWidth: 1,
          fill: getFillColor(element, 'none'),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        renderEventContent(element, parentGfx);

        return outer;
      },
      'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
      'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),

      'bpmn:Activity': function(parentGfx, element, attrs) {

        attrs = attrs || {};

        if (!('fillOpacity' in attrs)) {
          attrs.fillOpacity = DEFAULT_FILL_OPACITY;
        }

        return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs);
      },

      'bpmn:Task': function(parentGfx, element) {
        var attrs = {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);

        renderEmbeddedLabel(parentGfx, element, 'center-middle');
        attachTaskMarkers(parentGfx, element);

        return rect;
      },
      'bpmn:ServiceTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 12,
            y: 18
          }
        });

        /* service bg */ drawPath(parentGfx, pathDataBG, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', {
          abspos: {
            x: 17.2,
            y: 18
          }
        });

        /* service fill */ drawPath(parentGfx, fillPathData, {
          strokeWidth: 0,
          fill: getFillColor(element, defaultFillColor)
        });

        var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 17,
            y: 22
          }
        });

        /* service */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:UserTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var x = 15;
        var y = 12;

        var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
          abspos: {
            x: x,
            y: y
          }
        });

        /* user path */ drawPath(parentGfx, pathData, {
          strokeWidth: 0.5,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
          abspos: {
            x: x,
            y: y
          }
        });

        /* user2 path */ drawPath(parentGfx, pathData2, {
          strokeWidth: 0.5,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
          abspos: {
            x: x,
            y: y
          }
        });

        /* user3 path */ drawPath(parentGfx, pathData3, {
          strokeWidth: 0.5,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:ManualTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
          abspos: {
            x: 17,
            y: 15
          }
        });

        /* manual path */ drawPath(parentGfx, pathData, {
          strokeWidth: 0.5, // 0.25,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:SendTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: 21,
          containerHeight: 14,
          position: {
            mx: 0.285,
            my: 0.357
          }
        });

        /* send path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getFillColor(element, defaultFillColor)
        });

        return task;
      },
      'bpmn:ReceiveTask' : function(parentGfx, element) {
        var semantic = getSemantic(element);

        var task = renderer('bpmn:Task')(parentGfx, element);
        var pathData;

        if (semantic.instantiate) {
          drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 });

          pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
            abspos: {
              x: 7.77,
              y: 9.52
            }
          });
        } else {

          pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
            xScaleFactor: 0.9,
            yScaleFactor: 0.9,
            containerWidth: 21,
            containerHeight: 14,
            position: {
              mx: 0.3,
              my: 0.4
            }
          });
        }

        /* receive path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:ScriptTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
          abspos: {
            x: 15,
            y: 20
          }
        });

        /* script path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:BusinessRuleTask': function(parentGfx, element) {
        var task = renderer('bpmn:Task')(parentGfx, element);

        var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
          abspos: {
            x: 8,
            y: 8
          }
        });

        var businessHeaderPath = drawPath(parentGfx, headerPathData);
        attr(businessHeaderPath, {
          strokeWidth: 1,
          fill: getFillColor(element, '#aaaaaa'),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
          abspos: {
            x: 8,
            y: 8
          }
        });

        var businessPath = drawPath(parentGfx, headerData);
        attr(businessPath, {
          strokeWidth: 1,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return task;
      },
      'bpmn:SubProcess': function(parentGfx, element, attrs) {
        attrs = assign({
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        }, attrs);

        var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);

        var expanded = isExpanded(element);

        if (isEventSubProcess(element)) {
          attr(rect, {
            strokeDasharray: '1,2'
          });
        }

        renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle');

        if (expanded) {
          attachTaskMarkers(parentGfx, element);
        } else {
          attachTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ]);
        }

        return rect;
      },
      'bpmn:AdHocSubProcess': function(parentGfx, element) {
        return renderer('bpmn:SubProcess')(parentGfx, element);
      },
      'bpmn:Transaction': function(parentGfx, element) {
        var outer = renderer('bpmn:SubProcess')(parentGfx, element);

        var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        /* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs);

        return outer;
      },
      'bpmn:CallActivity': function(parentGfx, element) {
        return renderer('bpmn:SubProcess')(parentGfx, element, {
          strokeWidth: 5
        });
      },
      'bpmn:Participant': function(parentGfx, element) {

        var attrs = {
          fillOpacity: DEFAULT_FILL_OPACITY,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        var lane = renderer('bpmn:Lane')(parentGfx, element, attrs);

        var expandedPool = isExpanded(element);

        if (expandedPool) {
          drawLine(parentGfx, [
            { x: 30, y: 0 },
            { x: 30, y: element.height }
          ], {
            stroke: getStrokeColor$1(element, defaultStrokeColor)
          });
          var text = getSemantic(element).name;
          renderLaneLabel(parentGfx, text, element);
        } else {

          // Collapsed pool draw text inline
          var text2 = getSemantic(element).name;
          renderLabel(parentGfx, text2, {
            box: element, align: 'center-middle',
            style: {
              fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
            }
          });
        }

        var participantMultiplicity = !!(getSemantic(element).participantMultiplicity);

        if (participantMultiplicity) {
          renderer('ParticipantMultiplicityMarker')(parentGfx, element);
        }

        return lane;
      },
      'bpmn:Lane': function(parentGfx, element, attrs) {
        var rect = drawRect(parentGfx, element.width, element.height, 0, assign({
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: HIGH_FILL_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        }, attrs));

        var semantic = getSemantic(element);

        if (semantic.$type === 'bpmn:Lane') {
          var text = semantic.name;
          renderLaneLabel(parentGfx, text, element);
        }

        return rect;
      },
      'bpmn:InclusiveGateway': function(parentGfx, element) {
        var diamond = renderer('bpmn:Gateway')(parentGfx, element);

        /* circle path */
        drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
          strokeWidth: 2.5,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return diamond;
      },
      'bpmn:ExclusiveGateway': function(parentGfx, element) {
        var diamond = renderer('bpmn:Gateway')(parentGfx, element);

        var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
          xScaleFactor: 0.4,
          yScaleFactor: 0.4,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.32,
            my: 0.3
          }
        });

        if ((getDi(element).isMarkerVisible)) {
          drawPath(parentGfx, pathData, {
            strokeWidth: 1,
            fill: getStrokeColor$1(element, defaultStrokeColor),
            stroke: getStrokeColor$1(element, defaultStrokeColor)
          });
        }

        return diamond;
      },
      'bpmn:ComplexGateway': function(parentGfx, element) {
        var diamond = renderer('bpmn:Gateway')(parentGfx, element);

        var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
          xScaleFactor: 0.5,
          yScaleFactor:0.5,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.46,
            my: 0.26
          }
        });

        /* complex path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return diamond;
      },
      'bpmn:ParallelGateway': function(parentGfx, element) {
        var diamond = renderer('bpmn:Gateway')(parentGfx, element);

        var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
          xScaleFactor: 0.6,
          yScaleFactor:0.6,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.46,
            my: 0.2
          }
        });

        /* parallel path */ drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return diamond;
      },
      'bpmn:EventBasedGateway': function(parentGfx, element) {

        var semantic = getSemantic(element);

        var diamond = renderer('bpmn:Gateway')(parentGfx, element);

        /* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
          strokeWidth: 1,
          fill: 'none',
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var type = semantic.eventGatewayType;
        var instantiate = !!semantic.instantiate;

        function drawEvent() {

          var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
            xScaleFactor: 0.18,
            yScaleFactor: 0.18,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.36,
              my: 0.44
            }
          });

          var attrs = {
            strokeWidth: 2,
            fill: getFillColor(element, 'none'),
            stroke: getStrokeColor$1(element, defaultStrokeColor)
          };

          /* event path */ drawPath(parentGfx, pathData, attrs);
        }

        if (type === 'Parallel') {

          var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
            xScaleFactor: 0.4,
            yScaleFactor:0.4,
            containerWidth: element.width,
            containerHeight: element.height,
            position: {
              mx: 0.474,
              my: 0.296
            }
          });

          var parallelPath = drawPath(parentGfx, pathData);
          attr(parallelPath, {
            strokeWidth: 1,
            fill: 'none'
          });
        } else if (type === 'Exclusive') {

          if (!instantiate) {
            var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26);
            attr(innerCircle, {
              strokeWidth: 1,
              fill: 'none',
              stroke: getStrokeColor$1(element, defaultStrokeColor)
            });
          }

          drawEvent();
        }


        return diamond;
      },
      'bpmn:Gateway': function(parentGfx, element) {
        var attrs = {
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: DEFAULT_FILL_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        return drawDiamond(parentGfx, element.width, element.height, attrs);
      },
      'bpmn:SequenceFlow': function(parentGfx, element) {
        var pathData = createPathFromConnection(element);

        var fill = getFillColor(element, defaultFillColor),
            stroke = getStrokeColor$1(element, defaultStrokeColor);

        var attrs = {
          strokeLinejoin: 'round',
          markerEnd: marker('sequenceflow-end', fill, stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        var path = drawPath(parentGfx, pathData, attrs);

        var sequenceFlow = getSemantic(element);

        var source;

        if (element.source) {
          source = element.source.businessObject;

          // conditional flow marker
          if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) {
            attr(path, {
              markerStart: marker('conditional-flow-marker', fill, stroke)
            });
          }

          // default marker
          if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) &&
              source.default === sequenceFlow) {
            attr(path, {
              markerStart: marker('conditional-default-flow-marker', fill, stroke)
            });
          }
        }

        return path;
      },
      'bpmn:Association': function(parentGfx, element, attrs) {

        var semantic = getSemantic(element);

        var fill = getFillColor(element, defaultFillColor),
            stroke = getStrokeColor$1(element, defaultStrokeColor);

        attrs = assign({
          strokeDasharray: '0.5, 5',
          strokeLinecap: 'round',
          strokeLinejoin: 'round',
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        }, attrs || {});

        if (semantic.associationDirection === 'One' ||
            semantic.associationDirection === 'Both') {
          attrs.markerEnd = marker('association-end', fill, stroke);
        }

        if (semantic.associationDirection === 'Both') {
          attrs.markerStart = marker('association-start', fill, stroke);
        }

        return drawLine(parentGfx, element.waypoints, attrs);
      },
      'bpmn:DataInputAssociation': function(parentGfx, element) {
        var fill = getFillColor(element, defaultFillColor),
            stroke = getStrokeColor$1(element, defaultStrokeColor);

        return renderer('bpmn:Association')(parentGfx, element, {
          markerEnd: marker('association-end', fill, stroke)
        });
      },
      'bpmn:DataOutputAssociation': function(parentGfx, element) {
        var fill = getFillColor(element, defaultFillColor),
            stroke = getStrokeColor$1(element, defaultStrokeColor);

        return renderer('bpmn:Association')(parentGfx, element, {
          markerEnd: marker('association-end', fill, stroke)
        });
      },
      'bpmn:MessageFlow': function(parentGfx, element) {

        var semantic = getSemantic(element),
            di = getDi(element);

        var fill = getFillColor(element, defaultFillColor),
            stroke = getStrokeColor$1(element, defaultStrokeColor);

        var pathData = createPathFromConnection(element);

        var attrs = {
          markerEnd: marker('messageflow-end', fill, stroke),
          markerStart: marker('messageflow-start', fill, stroke),
          strokeDasharray: '10, 12',
          strokeLinecap: 'round',
          strokeLinejoin: 'round',
          strokeWidth: '1.5px',
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        var path = drawPath(parentGfx, pathData, attrs);

        if (semantic.messageRef) {
          var midPoint = path.getPointAtLength(path.getTotalLength() / 2);

          var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
            abspos: {
              x: midPoint.x,
              y: midPoint.y
            }
          });

          var messageAttrs = { strokeWidth: 1 };

          if (di.messageVisibleKind === 'initiating') {
            messageAttrs.fill = 'white';
            messageAttrs.stroke = black;
          } else {
            messageAttrs.fill = '#888';
            messageAttrs.stroke = 'white';
          }

          var message = drawPath(parentGfx, markerPathData, messageAttrs);

          var labelText = semantic.messageRef.name;
          var label = renderLabel(parentGfx, labelText, {
            align: 'center-top',
            fitBox: true,
            style: {
              fill: getStrokeColor$1(element, defaultLabelColor)
            }
          });

          var messageBounds = message.getBBox(),
              labelBounds = label.getBBox();

          var translateX = midPoint.x - labelBounds.width / 2,
              translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE$1;

          transform(label, translateX, translateY, 0);

        }

        return path;
      },
      'bpmn:DataObject': function(parentGfx, element) {
        var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.474,
            my: 0.296
          }
        });

        var elementObject = drawPath(parentGfx, pathData, {
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: DEFAULT_FILL_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var semantic = getSemantic(element);

        if (isCollection(semantic)) {
          renderDataItemCollection(parentGfx, element);
        }

        return elementObject;
      },
      'bpmn:DataObjectReference': as('bpmn:DataObject'),
      'bpmn:DataInput': function(parentGfx, element) {

        var arrowPathData = pathMap.getRawPath('DATA_ARROW');

        // page
        var elementObject = renderer('bpmn:DataObject')(parentGfx, element);

        /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 });

        return elementObject;
      },
      'bpmn:DataOutput': function(parentGfx, element) {
        var arrowPathData = pathMap.getRawPath('DATA_ARROW');

        // page
        var elementObject = renderer('bpmn:DataObject')(parentGfx, element);

        /* output arrow path */ drawPath(parentGfx, arrowPathData, {
          strokeWidth: 1,
          fill: black
        });

        return elementObject;
      },
      'bpmn:DataStoreReference': function(parentGfx, element) {
        var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0,
            my: 0.133
          }
        });

        var elementStore = drawPath(parentGfx, DATA_STORE_PATH, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor),
          fillOpacity: DEFAULT_FILL_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        return elementStore;
      },
      'bpmn:BoundaryEvent': function(parentGfx, element) {

        var semantic = getSemantic(element),
            cancel = semantic.cancelActivity;

        var attrs = {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        };

        if (!cancel) {
          attrs.strokeDasharray = '6';
          attrs.strokeLinecap = 'round';
        }

        // apply fillOpacity
        var outerAttrs = assign({}, attrs, {
          fillOpacity: 1
        });

        // apply no-fill
        var innerAttrs = assign({}, attrs, {
          fill: 'none'
        });

        var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs);

        /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs);

        renderEventContent(element, parentGfx);

        return outer;
      },
      'bpmn:Group': function(parentGfx, element) {

        var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
          stroke: getStrokeColor$1(element, defaultStrokeColor),
          strokeWidth: 1,
          strokeDasharray: '8,3,1,3',
          fill: 'none',
          pointerEvents: 'none'
        });

        return group;
      },
      'label': function(parentGfx, element) {
        return renderExternalLabel(parentGfx, element);
      },
      'bpmn:TextAnnotation': function(parentGfx, element) {
        var style = {
          'fill': 'none',
          'stroke': 'none'
        };

        var textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style);

        var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.0,
            my: 0.0
          }
        });

        drawPath(parentGfx, textPathData, {
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        var text = getSemantic(element).text || '';
        renderLabel(parentGfx, text, {
          box: element,
          align: 'left-top',
          padding: 5,
          style: {
            fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
          }
        });

        return textElement;
      },
      'ParticipantMultiplicityMarker': function(parentGfx, element) {
        var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2) / element.width),
            my: (element.height - 15) / element.height
          }
        });

        drawMarker('participant-multiplicity', parentGfx, markerPath, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      },
      'SubProcessMarker': function(parentGfx, element) {
        var markerRect = drawRect(parentGfx, 14, 14, 0, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });

        // Process marker is placed in the middle of the box
        // therefore fixed values can be used here
        translate$2(markerRect, element.width / 2 - 7.5, element.height - 20);

        var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
          xScaleFactor: 1.5,
          yScaleFactor: 1.5,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: (element.width / 2 - 7.5) / element.width,
            my: (element.height - 20) / element.height
          }
        });

        drawMarker('sub-process', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      },
      'ParallelMarker': function(parentGfx, element, position) {
        var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + position.parallel) / element.width),
            my: (element.height - 20) / element.height
          }
        });

        drawMarker('parallel', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      },
      'SequentialMarker': function(parentGfx, element, position) {
        var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + position.seq) / element.width),
            my: (element.height - 19) / element.height
          }
        });

        drawMarker('sequential', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      },
      'CompensationMarker': function(parentGfx, element, position) {
        var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + position.compensation) / element.width),
            my: (element.height - 13) / element.height
          }
        });

        drawMarker('compensation', parentGfx, markerMath, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      },
      'LoopMarker': function(parentGfx, element, position) {
        var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + position.loop) / element.width),
            my: (element.height - 7) / element.height
          }
        });

        drawMarker('loop', parentGfx, markerPath, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor),
          strokeLinecap: 'round',
          strokeMiterlimit: 0.5
        });
      },
      'AdhocMarker': function(parentGfx, element, position) {
        var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + position.adhoc) / element.width),
            my: (element.height - 15) / element.height
          }
        });

        drawMarker('adhoc', parentGfx, markerPath, {
          strokeWidth: 1,
          fill: getStrokeColor$1(element, defaultStrokeColor),
          stroke: getStrokeColor$1(element, defaultStrokeColor)
        });
      }
    };

    function attachTaskMarkers(parentGfx, element, taskMarkers) {
      var obj = getSemantic(element);

      var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1;
      var position;

      if (subprocess) {
        position = {
          seq: -21,
          parallel: -22,
          compensation: -42,
          loop: -18,
          adhoc: 10
        };
      } else {
        position = {
          seq: -3,
          parallel: -6,
          compensation: -27,
          loop: 0,
          adhoc: 10
        };
      }

      forEach$1(taskMarkers, function(marker) {
        renderer(marker)(parentGfx, element, position);
      });

      if (obj.isForCompensation) {
        renderer('CompensationMarker')(parentGfx, element, position);
      }

      if (obj.$type === 'bpmn:AdHocSubProcess') {
        renderer('AdhocMarker')(parentGfx, element, position);
      }

      var loopCharacteristics = obj.loopCharacteristics,
          isSequential = loopCharacteristics && loopCharacteristics.isSequential;

      if (loopCharacteristics) {

        if (isSequential === undefined) {
          renderer('LoopMarker')(parentGfx, element, position);
        }

        if (isSequential === false) {
          renderer('ParallelMarker')(parentGfx, element, position);
        }

        if (isSequential === true) {
          renderer('SequentialMarker')(parentGfx, element, position);
        }
      }
    }

    function renderDataItemCollection(parentGfx, element) {

      var yPosition = (element.height - 18) / element.height;

      var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
        xScaleFactor: 1,
        yScaleFactor: 1,
        containerWidth: element.width,
        containerHeight: element.height,
        position: {
          mx: 0.33,
          my: yPosition
        }
      });

      /* collection path */ drawPath(parentGfx, pathData, {
        strokeWidth: 2
      });
    }


    // extension API, use at your own risk
    this._drawPath = drawPath;

    this._renderer = renderer;
  }


  e(BpmnRenderer, BaseRenderer);

  BpmnRenderer.$inject = [
    'config.bpmnRenderer',
    'eventBus',
    'styles',
    'pathMap',
    'canvas',
    'textRenderer'
  ];


  BpmnRenderer.prototype.canRender = function(element) {
    return is$1(element, 'bpmn:BaseElement');
  };

  BpmnRenderer.prototype.drawShape = function(parentGfx, element) {
    var type = element.type;
    var h = this._renderer(type);

    /* jshint -W040 */
    return h(parentGfx, element);
  };

  BpmnRenderer.prototype.drawConnection = function(parentGfx, element) {
    var type = element.type;
    var h = this._renderer(type);

    /* jshint -W040 */
    return h(parentGfx, element);
  };

  BpmnRenderer.prototype.getShapePath = function(element) {

    if (is$1(element, 'bpmn:Event')) {
      return getCirclePath(element);
    }

    if (is$1(element, 'bpmn:Activity')) {
      return getRoundRectPath(element, TASK_BORDER_RADIUS);
    }

    if (is$1(element, 'bpmn:Gateway')) {
      return getDiamondPath(element);
    }

    return getRectPath(element);
  };

  var DEFAULT_BOX_PADDING = 0;

  var DEFAULT_LABEL_SIZE$1 = {
    width: 150,
    height: 50
  };


  function parseAlign(align) {

    var parts = align.split('-');

    return {
      horizontal: parts[0] || 'center',
      vertical: parts[1] || 'top'
    };
  }

  function parsePadding(padding) {

    if (isObject(padding)) {
      return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding);
    } else {
      return {
        top: padding,
        left: padding,
        right: padding,
        bottom: padding
      };
    }
  }

  function getTextBBox(text, fakeText) {

    fakeText.textContent = text;

    var textBBox;

    try {
      var bbox,
          emptyLine = text === '';

      // add dummy text, when line is empty to
      // determine correct height
      fakeText.textContent = emptyLine ? 'dummy' : text;

      textBBox = fakeText.getBBox();

      // take text rendering related horizontal
      // padding into account
      bbox = {
        width: textBBox.width + textBBox.x * 2,
        height: textBBox.height
      };

      if (emptyLine) {

        // correct width
        bbox.width = 0;
      }

      return bbox;
    } catch (e) {
      return { width: 0, height: 0 };
    }
  }


  /**
   * Layout the next line and return the layouted element.
   *
   * Alters the lines passed.
   *
   * @param  {Array<string>} lines
   * @return {Object} the line descriptor, an object { width, height, text }
   */
  function layoutNext(lines, maxWidth, fakeText) {

    var originalLine = lines.shift(),
        fitLine = originalLine;

    var textBBox;

    for (;;) {
      textBBox = getTextBBox(fitLine, fakeText);

      textBBox.width = fitLine ? textBBox.width : 0;

      // try to fit
      if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) {
        return fit(lines, fitLine, originalLine, textBBox);
      }

      fitLine = shortenLine(fitLine, textBBox.width, maxWidth);
    }
  }

  function fit(lines, fitLine, originalLine, textBBox) {
    if (fitLine.length < originalLine.length) {
      var remainder = originalLine.slice(fitLine.length).trim();

      lines.unshift(remainder);
    }

    return {
      width: textBBox.width,
      height: textBBox.height,
      text: fitLine
    };
  }

  var SOFT_BREAK = '\u00AD';


  /**
   * Shortens a line based on spacing and hyphens.
   * Returns the shortened result on success.
   *
   * @param  {string} line
   * @param  {number} maxLength the maximum characters of the string
   * @return {string} the shortened string
   */
  function semanticShorten(line, maxLength) {

    var parts = line.split(/(\s|-|\u00AD)/g),
        part,
        shortenedParts = [],
        length = 0;

    // try to shorten via break chars
    if (parts.length > 1) {

      while ((part = parts.shift())) {
        if (part.length + length < maxLength) {
          shortenedParts.push(part);
          length += part.length;
        } else {

          // remove previous part, too if hyphen does not fit anymore
          if (part === '-' || part === SOFT_BREAK) {
            shortenedParts.pop();
          }

          break;
        }
      }
    }

    var last = shortenedParts[shortenedParts.length - 1];

    // translate trailing soft break to actual hyphen
    if (last && last === SOFT_BREAK) {
      shortenedParts[shortenedParts.length - 1] = '-';
    }

    return shortenedParts.join('');
  }


  function shortenLine(line, width, maxWidth) {
    var length = Math.max(line.length * (maxWidth / width), 1);

    // try to shorten semantically (i.e. based on spaces and hyphens)
    var shortenedLine = semanticShorten(line, length);

    if (!shortenedLine) {

      // force shorten by cutting the long word
      shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1));
    }

    return shortenedLine;
  }


  function getHelperSvg() {
    var helperSvg = document.getElementById('helper-svg');

    if (!helperSvg) {
      helperSvg = create$1('svg');

      attr(helperSvg, {
        id: 'helper-svg'
      });

      assign$1(helperSvg, {
        visibility: 'hidden',
        position: 'fixed',
        width: 0,
        height: 0
      });

      document.body.appendChild(helperSvg);
    }

    return helperSvg;
  }


  /**
   * Creates a new label utility
   *
   * @param {Object} config
   * @param {Dimensions} config.size
   * @param {number} config.padding
   * @param {Object} config.style
   * @param {string} config.align
   */
  function Text(config) {

    this._config = assign({}, {
      size: DEFAULT_LABEL_SIZE$1,
      padding: DEFAULT_BOX_PADDING,
      style: {},
      align: 'center-top'
    }, config || {});
  }

  /**
   * Returns the layouted text as an SVG element.
   *
   * @param {string} text
   * @param {Object} options
   *
   * @return {SVGElement}
   */
  Text.prototype.createText = function(text, options) {
    return this.layoutText(text, options).element;
  };

  /**
   * Returns a labels layouted dimensions.
   *
   * @param {string} text to layout
   * @param {Object} options
   *
   * @return {Dimensions}
   */
  Text.prototype.getDimensions = function(text, options) {
    return this.layoutText(text, options).dimensions;
  };

  /**
   * Creates and returns a label and its bounding box.
   *
   * @method Text#createText
   *
   * @param {string} text the text to render on the label
   * @param {Object} options
   * @param {string} options.align how to align in the bounding box.
   *                               Any of { 'center-middle', 'center-top' },
   *                               defaults to 'center-top'.
   * @param {string} options.style style to be applied to the text
   * @param {boolean} options.fitBox indicates if box will be recalculated to
   *                                 fit text
   *
   * @return {Object} { element, dimensions }
   */
  Text.prototype.layoutText = function(text, options) {
    var box = assign({}, this._config.size, options.box),
        style = assign({}, this._config.style, options.style),
        align = parseAlign(options.align || this._config.align),
        padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding),
        fitBox = options.fitBox || false;

    var lineHeight = getLineHeight(style);

    // we split text by lines and normalize
    // {soft break} + {line break} => { line break }
    var lines = text.split(/\u00AD?\r?\n/),
        layouted = [];

    var maxWidth = box.width - padding.left - padding.right;

    // ensure correct rendering by attaching helper text node to invisible SVG
    var helperText = create$1('text');
    attr(helperText, { x: 0, y: 0 });
    attr(helperText, style);

    var helperSvg = getHelperSvg();

    append(helperSvg, helperText);

    while (lines.length) {
      layouted.push(layoutNext(lines, maxWidth, helperText));
    }

    if (align.vertical === 'middle') {
      padding.top = padding.bottom = 0;
    }

    var totalHeight = reduce(layouted, function(sum, line, idx) {
      return sum + (lineHeight || line.height);
    }, 0) + padding.top + padding.bottom;

    var maxLineWidth = reduce(layouted, function(sum, line, idx) {
      return line.width > sum ? line.width : sum;
    }, 0);

    // the y position of the next line
    var y = padding.top;

    if (align.vertical === 'middle') {
      y += (box.height - totalHeight) / 2;
    }

    // magic number initial offset
    y -= (lineHeight || layouted[0].height) / 4;


    var textElement = create$1('text');

    attr(textElement, style);

    // layout each line taking into account that parent
    // shape might resize to fit text size
    forEach$1(layouted, function(line) {

      var x;

      y += (lineHeight || line.height);

      switch (align.horizontal) {
      case 'left':
        x = padding.left;
        break;

      case 'right':
        x = ((fitBox ? maxLineWidth : maxWidth)
          - padding.right - line.width);
        break;

      default:

        // aka center
        x = Math.max((((fitBox ? maxLineWidth : maxWidth)
          - line.width) / 2 + padding.left), 0);
      }

      var tspan = create$1('tspan');
      attr(tspan, { x: x, y: y });

      tspan.textContent = line.text;

      append(textElement, tspan);
    });

    remove$1(helperText);

    var dimensions = {
      width: maxLineWidth,
      height: totalHeight
    };

    return {
      dimensions: dimensions,
      element: textElement
    };
  };


  function getLineHeight(style) {
    if ('fontSize' in style && 'lineHeight' in style) {
      return style.lineHeight * parseInt(style.fontSize, 10);
    }
  }

  var DEFAULT_FONT_SIZE = 12;
  var LINE_HEIGHT_RATIO = 1.2;

  var MIN_TEXT_ANNOTATION_HEIGHT = 30;


  function TextRenderer(config) {

    var defaultStyle = assign({
      fontFamily: 'Arial, sans-serif',
      fontSize: DEFAULT_FONT_SIZE,
      fontWeight: 'normal',
      lineHeight: LINE_HEIGHT_RATIO
    }, config && config.defaultStyle || {});

    var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;

    var externalStyle = assign({}, defaultStyle, {
      fontSize: fontSize
    }, config && config.externalStyle || {});

    var textUtil = new Text({
      style: defaultStyle
    });

    /**
     * Get the new bounds of an externally rendered,
     * layouted label.
     *
     * @param  {Bounds} bounds
     * @param  {string} text
     *
     * @return {Bounds}
     */
    this.getExternalLabelBounds = function(bounds, text) {

      var layoutedDimensions = textUtil.getDimensions(text, {
        box: {
          width: 90,
          height: 30,
          x: bounds.width / 2 + bounds.x,
          y: bounds.height / 2 + bounds.y
        },
        style: externalStyle
      });

      // resize label shape to fit label text
      return {
        x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
        y: Math.round(bounds.y),
        width: Math.ceil(layoutedDimensions.width),
        height: Math.ceil(layoutedDimensions.height)
      };

    };

    /**
     * Get the new bounds of text annotation.
     *
     * @param  {Bounds} bounds
     * @param  {string} text
     *
     * @return {Bounds}
     */
    this.getTextAnnotationBounds = function(bounds, text) {

      var layoutedDimensions = textUtil.getDimensions(text, {
        box: bounds,
        style: defaultStyle,
        align: 'left-top',
        padding: 5
      });

      return {
        x: bounds.x,
        y: bounds.y,
        width: bounds.width,
        height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
      };
    };

    /**
     * Create a layouted text element.
     *
     * @param {string} text
     * @param {Object} [options]
     *
     * @return {SVGElement} rendered text
     */
    this.createText = function(text, options) {
      return textUtil.createText(text, options || {});
    };

    /**
     * Get default text style.
     */
    this.getDefaultStyle = function() {
      return defaultStyle;
    };

    /**
     * Get the external text style.
     */
    this.getExternalStyle = function() {
      return externalStyle;
    };

  }

  TextRenderer.$inject = [
    'config.textRenderer'
  ];

  /**
   * Map containing SVG paths needed by BpmnRenderer.
   */

  function PathMap() {

    /**
     * Contains a map of path elements
     *
     * <h1>Path definition</h1>
     * A parameterized path is defined like this:
     * <pre>
     * 'GATEWAY_PARALLEL': {
     *   d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
            '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
     *   height: 17.5,
     *   width:  17.5,
     *   heightElements: [2.5, 7.5],
     *   widthElements: [2.5, 7.5]
     * }
     * </pre>
     * <p>It's important to specify a correct <b>height and width</b> for the path as the scaling
     * is based on the ratio between the specified height and width in this object and the
     * height and width that is set as scale target (Note x,y coordinates will be scaled with
     * individual ratios).</p>
     * <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled.
     * The scaling is based on the computed ratios.
     * Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using
     * the computed ratio coefficient.
     * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
     *   <ul>
     *    <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
     *    <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
     *   </ul>
     *   The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
     * </p>
     */
    this.pathMap = {
      'EVENT_MESSAGE': {
        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}',
        height: 36,
        width:  36,
        heightElements: [ 6, 14 ],
        widthElements: [ 10.5, 21 ]
      },
      'EVENT_SIGNAL': {
        d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
        height: 36,
        width: 36,
        heightElements: [ 18 ],
        widthElements: [ 10, 20 ]
      },
      'EVENT_ESCALATION': {
        d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
        height: 36,
        width: 36,
        heightElements: [ 20, 7 ],
        widthElements: [ 8 ]
      },
      'EVENT_CONDITIONAL': {
        d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
           'M {e.x2},{e.y3} l {e.x0},0 ' +
           'M {e.x2},{e.y4} l {e.x0},0 ' +
           'M {e.x2},{e.y5} l {e.x0},0 ' +
           'M {e.x2},{e.y6} l {e.x0},0 ' +
           'M {e.x2},{e.y7} l {e.x0},0 ' +
           'M {e.x2},{e.y8} l {e.x0},0 ',
        height: 36,
        width:  36,
        heightElements: [ 8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5 ],
        widthElements:  [ 10.5, 14.5, 12.5 ]
      },
      'EVENT_LINK': {
        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',
        height: 36,
        width: 36,
        heightElements: [ 4.4375, 6.75, 7.8125 ],
        widthElements: [ 9.84375, 13.5 ]
      },
      'EVENT_ERROR': {
        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',
        height: 36,
        width: 36,
        heightElements: [ 0.023, 8.737, 8.151, 16.564, 10.591, 8.714 ],
        widthElements: [ 0.085, 6.672, 6.97, 4.273, 5.337, 6.636 ]
      },
      'EVENT_CANCEL_45': {
        d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
          '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
        height: 36,
        width: 36,
        heightElements: [ 4.75, 8.5 ],
        widthElements: [ 4.75, 8.5 ]
      },
      'EVENT_COMPENSATION': {
        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',
        height: 36,
        width: 36,
        heightElements: [ 6.5, 13, 0.4, 6.1 ],
        widthElements: [ 9, 9.3, 8.7 ]
      },
      'EVENT_TIMER_WH': {
        d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
        height: 36,
        width:  36,
        heightElements: [ 10, 2 ],
        widthElements: [ 3, 7 ]
      },
      'EVENT_TIMER_LINE': {
        d:  'M {mx},{my} ' +
            'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
        height: 36,
        width:  36,
        heightElements: [ 10, 3 ],
        widthElements: [ 0, 0 ]
      },
      'EVENT_MULTIPLE': {
        d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
        height: 36,
        width:  36,
        heightElements: [ 6.28099, 12.56199 ],
        widthElements: [ 3.1405, 9.42149, 12.56198 ]
      },
      'EVENT_PARALLEL_MULTIPLE': {
        d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
          '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
        height: 36,
        width:  36,
        heightElements: [ 2.56228, 7.68683 ],
        widthElements: [ 2.56228, 7.68683 ]
      },
      'GATEWAY_EXCLUSIVE': {
        d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
                      '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
                      '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
        height: 17.5,
        width:  17.5,
        heightElements: [ 8.5, 6.5312, -6.5312, -8.5 ],
        widthElements:  [ 6.5, -6.5, 3, -3, 5, -5 ]
      },
      'GATEWAY_PARALLEL': {
        d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
          '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
        height: 30,
        width:  30,
        heightElements: [ 5, 12.5 ],
        widthElements: [ 5, 12.5 ]
      },
      'GATEWAY_EVENT_BASED': {
        d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
        height: 11,
        width:  11,
        heightElements: [ -6, 6, 12, -12 ],
        widthElements: [ 9, -3, -12 ]
      },
      'GATEWAY_COMPLEX': {
        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} ' +
          '{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} ' +
          '{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} ' +
          '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
        height: 17.125,
        width:  17.125,
        heightElements: [ 4.875, 3.4375, 2.125, 3 ],
        widthElements: [ 3.4375, 2.125, 4.875, 3 ]
      },
      'DATA_OBJECT_PATH': {
        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',
        height: 61,
        width:  51,
        heightElements: [ 10, 50, 60 ],
        widthElements: [ 10, 40, 50, 60 ]
      },
      'DATA_OBJECT_COLLECTION_PATH': {
        d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
        height: 10,
        width: 10,
        heightElements: [],
        widthElements: []
      },
      'DATA_ARROW': {
        d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
        height: 61,
        width:  51,
        heightElements: [],
        widthElements: []
      },
      'DATA_STORE': {
        d:'m  {mx},{my} ' +
          'l  0,{e.y2} ' +
          'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0 ' +
          'l  0,-{e.y2} ' +
          'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
          'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0 ' +
          'm  -{e.x2},{e.y0}' +
          'c  {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
          'm  -{e.x2},{e.y0}' +
          'c  {e.x0},{e.y1} {e.x1},{e.y1}  {e.x2},0',
        height: 61,
        width:  61,
        heightElements: [ 7, 10, 45 ],
        widthElements:  [ 2, 58, 60 ]
      },
      'TEXT_ANNOTATION': {
        d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
        height: 30,
        width: 10,
        heightElements: [ 30 ],
        widthElements: [ 10 ]
      },
      'MARKER_SUB_PROCESS': {
        d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
        height: 10,
        width: 10,
        heightElements: [],
        widthElements: []
      },
      'MARKER_PARALLEL': {
        d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
        height: 10,
        width: 10,
        heightElements: [],
        widthElements: []
      },
      'MARKER_SEQUENTIAL': {
        d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
        height: 10,
        width: 10,
        heightElements: [],
        widthElements: []
      },
      'MARKER_COMPENSATION': {
        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',
        height: 10,
        width: 21,
        heightElements: [],
        widthElements: []
      },
      'MARKER_LOOP': {
        d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
          '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
          '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
          'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
        height: 13.9,
        width: 13.7,
        heightElements: [],
        widthElements: []
      },
      'MARKER_ADHOC': {
        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 ' +
          '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
          '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 ' +
          '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
          '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
        height: 4,
        width: 15,
        heightElements: [],
        widthElements: []
      },
      'TASK_TYPE_SEND': {
        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}',
        height: 14,
        width:  21,
        heightElements: [ 6, 14 ],
        widthElements: [ 10.5, 21 ]
      },
      'TASK_TYPE_SCRIPT': {
        d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
          'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
          'm -7,-12 l 5,0 ' +
          'm -4.5,3 l 4.5,0 ' +
          'm -3,3 l 5,0' +
          'm -4,3 l 5,0',
        height: 15,
        width:  12.6,
        heightElements: [ 6, 14 ],
        widthElements: [ 10.5, 21 ]
      },
      'TASK_TYPE_USER_1': {
        d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
          '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
          '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
          'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
          'm -8,6 l 0,5.5 m 11,0 l 0,-5'
      },
      'TASK_TYPE_USER_2': {
        d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
          '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
      },
      'TASK_TYPE_USER_3': {
        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 ' +
          '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
          '-4.20799998,3.36699999 -4.20699998,4.34799999 z'
      },
      'TASK_TYPE_MANUAL': {
        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 ' +
          '-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 ' +
          '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 ' +
          '-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 ' +
          '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 ' +
          '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
          '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
          '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
          '-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 ' +
          '-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 ' +
          '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 ' +
          '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
      },
      'TASK_TYPE_INSTANTIATING_SEND': {
        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'
      },
      'TASK_TYPE_SERVICE': {
        d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
          '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
          '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
          'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
          '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
          '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
          '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 ' +
          '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
          'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
          'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
          '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
          'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
          'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
          '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
          '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
      },
      'TASK_TYPE_SERVICE_FILL': {
        d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
          '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
          '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
      },
      'TASK_TYPE_BUSINESS_RULE_HEADER': {
        d: 'm {mx},{my} 0,4 20,0 0,-4 z'
      },
      'TASK_TYPE_BUSINESS_RULE_MAIN': {
        d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
          'm 0,8 l 20,0 ' +
          'm -13,-4 l 0,8'
      },
      'MESSAGE_FLOW_MARKER': {
        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'
      }
    };

    this.getRawPath = function getRawPath(pathId) {
      return this.pathMap[pathId].d;
    };

    /**
     * Scales the path to the given height and width.
     * <h1>Use case</h1>
     * <p>Use case is to scale the content of elements (event, gateways) based
     * on the element bounding box's size.
     * </p>
     * <h1>Why not transform</h1>
     * <p>Scaling a path with transform() will also scale the stroke and IE does not support
     * the option 'non-scaling-stroke' to prevent this.
     * Also there are use cases where only some parts of a path should be
     * scaled.</p>
     *
     * @param {string} pathId The ID of the path.
     * @param {Object} param <p>
     *   Example param object scales the path to 60% size of the container (data.width, data.height).
     *   <pre>
     *   {
     *     xScaleFactor: 0.6,
     *     yScaleFactor:0.6,
     *     containerWidth: data.width,
     *     containerHeight: data.height,
     *     position: {
     *       mx: 0.46,
     *       my: 0.2,
     *     }
     *   }
     *   </pre>
     *   <ul>
     *    <li>targetpathwidth = xScaleFactor * containerWidth</li>
     *    <li>targetpathheight = yScaleFactor * containerHeight</li>
     *    <li>Position is used to set the starting coordinate of the path. M is computed:
      *    <ul>
      *      <li>position.x * containerWidth</li>
      *      <li>position.y * containerHeight</li>
      *    </ul>
      *    Center of the container <pre> position: {
     *       mx: 0.5,
     *       my: 0.5,
     *     }</pre>
     *     Upper left corner of the container
     *     <pre> position: {
     *       mx: 0.0,
     *       my: 0.0,
     *     }</pre>
     *    </li>
     *   </ul>
     * </p>
     *
     */
    this.getScaledPath = function getScaledPath(pathId, param) {
      var rawPath = this.pathMap[pathId];

      // positioning
      // compute the start point of the path
      var mx, my;

      if (param.abspos) {
        mx = param.abspos.x;
        my = param.abspos.y;
      } else {
        mx = param.containerWidth * param.position.mx;
        my = param.containerHeight * param.position.my;
      }

      var coordinates = {}; // map for the scaled coordinates
      if (param.position) {

        // path
        var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
        var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;


        // Apply height ratio
        for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
          coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
        }

        // Apply width ratio
        for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
          coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
        }
      }

      // Apply value to raw path
      var path = format(
        rawPath.d, {
          mx: mx,
          my: my,
          e: coordinates
        }
      );
      return path;
    };
  }

  // helpers //////////////////////

  // copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
  var tokenRegex = /\{([^{}]+)\}/g,
      objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties

  function replacer(all, key, obj) {
    var res = obj;
    key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
      name = name || quotedName;
      if (res) {
        if (name in res) {
          res = res[name];
        }
        typeof res == 'function' && isFunc && (res = res());
      }
    });
    res = (res == null || res == obj ? all : res) + '';

    return res;
  }

  function format(str, obj) {
    return String(str).replace(tokenRegex, function(all, key) {
      return replacer(all, key, obj);
    });
  }

  var DrawModule = {
    __init__: [ 'bpmnRenderer' ],
    bpmnRenderer: [ 'type', BpmnRenderer ],
    textRenderer: [ 'type', TextRenderer ],
    pathMap: [ 'type', PathMap ]
  };

  /**
   * A simple translation stub to be used for multi-language support
   * in diagrams. Can be easily replaced with a more sophisticated
   * solution.
   *
   * @example
   *
   * // use it inside any diagram component by injecting `translate`.
   *
   * function MyService(translate) {
   *   alert(translate('HELLO {you}', { you: 'You!' }));
   * }
   *
   * @param {string} template to interpolate
   * @param {Object} [replacements] a map with substitutes
   *
   * @return {string} the translated string
   */
  function translate$1(template, replacements) {

    replacements = replacements || {};

    return template.replace(/{([^}]+)}/g, function(_, key) {
      return replacements[key] || '{' + key + '}';
    });
  }

  var translate = {
    translate: [ 'value', translate$1 ]
  };

  var DEFAULT_LABEL_SIZE = {
    width: 90,
    height: 20
  };

  var FLOW_LABEL_INDENT = 15;


  /**
   * Returns true if the given semantic has an external label
   *
   * @param {BpmnElement} semantic
   * @return {boolean} true if has label
   */
  function isLabelExternal(semantic) {
    return is$1(semantic, 'bpmn:Event') ||
           is$1(semantic, 'bpmn:Gateway') ||
           is$1(semantic, 'bpmn:DataStoreReference') ||
           is$1(semantic, 'bpmn:DataObjectReference') ||
           is$1(semantic, 'bpmn:DataInput') ||
           is$1(semantic, 'bpmn:DataOutput') ||
           is$1(semantic, 'bpmn:SequenceFlow') ||
           is$1(semantic, 'bpmn:MessageFlow') ||
           is$1(semantic, 'bpmn:Group');
  }

  /**
   * Returns true if the given element has an external label
   *
   * @param {djs.model.shape} element
   * @return {boolean} true if has label
   */
  function hasExternalLabel(element) {
    return isLabel$6(element.label);
  }

  /**
   * Get the position for sequence flow labels
   *
   * @param  {Array<Point>} waypoints
   * @return {Point} the label position
   */
  function getFlowLabelPosition(waypoints) {

    // get the waypoints mid
    var mid = waypoints.length / 2 - 1;

    var first = waypoints[Math.floor(mid)];
    var second = waypoints[Math.ceil(mid + 0.01)];

    // get position
    var position = getWaypointsMid(waypoints);

    // calculate angle
    var angle = Math.atan((second.y - first.y) / (second.x - first.x));

    var x = position.x,
        y = position.y;

    if (Math.abs(angle) < Math.PI / 2) {
      y -= FLOW_LABEL_INDENT;
    } else {
      x += FLOW_LABEL_INDENT;
    }

    return { x: x, y: y };
  }


  /**
   * Get the middle of a number of waypoints
   *
   * @param  {Array<Point>} waypoints
   * @return {Point} the mid point
   */
  function getWaypointsMid(waypoints) {

    var mid = waypoints.length / 2 - 1;

    var first = waypoints[Math.floor(mid)];
    var second = waypoints[Math.ceil(mid + 0.01)];

    return {
      x: first.x + (second.x - first.x) / 2,
      y: first.y + (second.y - first.y) / 2
    };
  }


  function getExternalLabelMid(element) {

    if (element.waypoints) {
      return getFlowLabelPosition(element.waypoints);
    } else if (is$1(element, 'bpmn:Group')) {
      return {
        x: element.x + element.width / 2,
        y: element.y + DEFAULT_LABEL_SIZE.height / 2
      };
    } else {
      return {
        x: element.x + element.width / 2,
        y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2
      };
    }
  }


  /**
   * Returns the bounds of an elements label, parsed from the elements DI or
   * generated from its bounds.
   *
   * @param {BpmndDi} di
   * @param {djs.model.Base} element
   */
  function getExternalLabelBounds(di, element) {

    var mid,
        size,
        bounds,
        label = di.label;

    if (label && label.bounds) {
      bounds = label.bounds;

      size = {
        width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width),
        height: bounds.height
      };

      mid = {
        x: bounds.x + bounds.width / 2,
        y: bounds.y + bounds.height / 2
      };
    } else {

      mid = getExternalLabelMid(element);

      size = DEFAULT_LABEL_SIZE;
    }

    return assign({
      x: mid.x - size.width / 2,
      y: mid.y - size.height / 2
    }, size);
  }

  function isLabel$6(element) {
    return element && !!element.labelTarget;
  }

  /**
   * @param {ModdleElement} semantic
   * @param {ModdleElement} di
   * @param {Object} [attrs=null]
   *
   * @return {Object}
   */
  function elementData(semantic, di, attrs) {
    return assign({
      id: semantic.id,
      type: semantic.$type,
      businessObject: semantic,
      di: di
    }, attrs);
  }

  function getWaypoints(di, source, target) {

    var waypoints = di.waypoint;

    if (!waypoints || waypoints.length < 2) {
      return [ getMid(source), getMid(target) ];
    }

    return waypoints.map(function(p) {
      return { x: p.x, y: p.y };
    });
  }

  function notYetDrawn(translate, semantic, refSemantic, property) {
    return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
      element: elementToString(refSemantic),
      referenced: elementToString(semantic),
      property: property
    }));
  }


  /**
   * An importer that adds bpmn elements to the canvas
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {ElementFactory} elementFactory
   * @param {ElementRegistry} elementRegistry
   * @param {Function} translate
   * @param {TextRenderer} textRenderer
   */
  function BpmnImporter(
      eventBus, canvas, elementFactory,
      elementRegistry, translate, textRenderer) {

    this._eventBus = eventBus;
    this._canvas = canvas;
    this._elementFactory = elementFactory;
    this._elementRegistry = elementRegistry;
    this._translate = translate;
    this._textRenderer = textRenderer;
  }

  BpmnImporter.$inject = [
    'eventBus',
    'canvas',
    'elementFactory',
    'elementRegistry',
    'translate',
    'textRenderer'
  ];


  /**
   * Add bpmn element (semantic) to the canvas onto the
   * specified parent shape.
   */
  BpmnImporter.prototype.add = function(semantic, di, parentElement) {
    var element,
        translate = this._translate,
        hidden;

    var parentIndex;

    // ROOT ELEMENT
    // handle the special case that we deal with a
    // invisible root element (process, subprocess or collaboration)
    if (is$1(di, 'bpmndi:BPMNPlane')) {

      var attrs = is$1(semantic, 'bpmn:SubProcess')
        ? { id: semantic.id + '_plane' }
        : {};

      // add a virtual element (not being drawn)
      element = this._elementFactory.createRoot(elementData(semantic, di, attrs));

      this._canvas.addRootElement(element);
    }

    // SHAPE
    else if (is$1(di, 'bpmndi:BPMNShape')) {

      var collapsed = !isExpanded(semantic, di),
          isFrame = isFrameElement(semantic);

      hidden = parentElement && (parentElement.hidden || parentElement.collapsed);

      var bounds = di.bounds;

      element = this._elementFactory.createShape(elementData(semantic, di, {
        collapsed: collapsed,
        hidden: hidden,
        x: Math.round(bounds.x),
        y: Math.round(bounds.y),
        width: Math.round(bounds.width),
        height: Math.round(bounds.height),
        isFrame: isFrame
      }));

      if (is$1(semantic, 'bpmn:BoundaryEvent')) {
        this._attachBoundary(semantic, element);
      }

      // insert lanes behind other flow nodes (cf. #727)
      if (is$1(semantic, 'bpmn:Lane')) {
        parentIndex = 0;
      }

      if (is$1(semantic, 'bpmn:DataStoreReference')) {

        // check whether data store is inside our outside of its semantic parent
        if (!isPointInsideBBox$1(parentElement, getMid(bounds))) {
          parentElement = this._canvas.findRoot(parentElement);
        }
      }

      this._canvas.addShape(element, parentElement, parentIndex);
    }

    // CONNECTION
    else if (is$1(di, 'bpmndi:BPMNEdge')) {

      var source = this._getSource(semantic),
          target = this._getTarget(semantic);

      hidden = parentElement && (parentElement.hidden || parentElement.collapsed);

      element = this._elementFactory.createConnection(elementData(semantic, di, {
        hidden: hidden,
        source: source,
        target: target,
        waypoints: getWaypoints(di, source, target)
      }));

      if (is$1(semantic, 'bpmn:DataAssociation')) {

        // render always on top; this ensures DataAssociations
        // are rendered correctly across different "hacks" people
        // love to model such as cross participant / sub process
        // associations
        parentElement = this._canvas.findRoot(parentElement);
      }

      this._canvas.addConnection(element, parentElement, parentIndex);
    } else {
      throw new Error(translate('unknown di {di} for element {semantic}', {
        di: elementToString(di),
        semantic: elementToString(semantic)
      }));
    }

    // (optional) LABEL
    if (isLabelExternal(semantic) && getLabel(element)) {
      this.addLabel(semantic, di, element);
    }


    this._eventBus.fire('bpmnElement.added', { element: element });

    return element;
  };


  /**
   * Attach the boundary element to the given host
   *
   * @param {ModdleElement} boundarySemantic
   * @param {djs.model.Base} boundaryElement
   */
  BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
    var translate = this._translate;
    var hostSemantic = boundarySemantic.attachedToRef;

    if (!hostSemantic) {
      throw new Error(translate('missing {semantic}#attachedToRef', {
        semantic: elementToString(boundarySemantic)
      }));
    }

    var host = this._elementRegistry.get(hostSemantic.id),
        attachers = host && host.attachers;

    if (!host) {
      throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
    }

    // wire element.host <> host.attachers
    boundaryElement.host = host;

    if (!attachers) {
      host.attachers = attachers = [];
    }

    if (attachers.indexOf(boundaryElement) === -1) {
      attachers.push(boundaryElement);
    }
  };


  /**
   * add label for an element
   */
  BpmnImporter.prototype.addLabel = function(semantic, di, element) {
    var bounds,
        text,
        label;

    bounds = getExternalLabelBounds(di, element);

    text = getLabel(element);

    if (text) {

      // get corrected bounds from actual layouted text
      bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
    }

    label = this._elementFactory.createLabel(elementData(semantic, di, {
      id: semantic.id + '_label',
      labelTarget: element,
      type: 'label',
      hidden: element.hidden || !getLabel(element),
      x: Math.round(bounds.x),
      y: Math.round(bounds.y),
      width: Math.round(bounds.width),
      height: Math.round(bounds.height)
    }));

    return this._canvas.addShape(label, element.parent);
  };

  /**
   * Return the drawn connection end based on the given side.
   *
   * @throws {Error} if the end is not yet drawn
   */
  BpmnImporter.prototype._getEnd = function(semantic, side) {

    var element,
        refSemantic,
        type = semantic.$type,
        translate = this._translate;

    refSemantic = semantic[side + 'Ref'];

    // handle mysterious isMany DataAssociation#sourceRef
    if (side === 'source' && type === 'bpmn:DataInputAssociation') {
      refSemantic = refSemantic && refSemantic[0];
    }

    // fix source / target for DataInputAssociation / DataOutputAssociation
    if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
        side === 'target' && type === 'bpmn:DataInputAssociation') {

      refSemantic = semantic.$parent;
    }

    element = refSemantic && this._getElement(refSemantic);

    if (element) {
      return element;
    }

    if (refSemantic) {
      throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
    } else {
      throw new Error(translate('{semantic}#{side} Ref not specified', {
        semantic: elementToString(semantic),
        side: side
      }));
    }
  };

  BpmnImporter.prototype._getSource = function(semantic) {
    return this._getEnd(semantic, 'source');
  };

  BpmnImporter.prototype._getTarget = function(semantic) {
    return this._getEnd(semantic, 'target');
  };


  BpmnImporter.prototype._getElement = function(semantic) {
    return this._elementRegistry.get(semantic.id);
  };


  // helpers ////////////////////

  function isPointInsideBBox$1(bbox, point) {
    var x = point.x,
        y = point.y;

    return x >= bbox.x &&
      x <= bbox.x + bbox.width &&
      y >= bbox.y &&
      y <= bbox.y + bbox.height;
  }

  function isFrameElement(semantic) {
    return is$1(semantic, 'bpmn:Group');
  }

  var ImportModule = {
    __depends__: [
      translate
    ],
    bpmnImporter: [ 'type', BpmnImporter ]
  };

  var CoreModule = {
    __depends__: [
      DrawModule,
      ImportModule
    ]
  };

  function __stopPropagation(event) {
    if (!event || typeof event.stopPropagation !== 'function') {
      return;
    }

    event.stopPropagation();
  }


  function getOriginal$1(event) {
    return event.originalEvent || event.srcEvent;
  }


  function stopPropagation$1(event, immediate) {
    __stopPropagation(event);
    __stopPropagation(getOriginal$1(event));
  }


  function toPoint(event) {

    if (event.pointers && event.pointers.length) {
      event = event.pointers[0];
    }

    if (event.touches && event.touches.length) {
      event = event.touches[0];
    }

    return event ? {
      x: event.clientX,
      y: event.clientY
    } : null;
  }

  function isMac() {
    return (/mac/i).test(navigator.platform);
  }

  function isButton(event, button) {
    return (getOriginal$1(event) || event).button === button;
  }

  function isPrimaryButton(event) {

    // button === 0 -> left áka primary mouse button
    return isButton(event, 0);
  }

  function isAuxiliaryButton(event) {

    // button === 1 -> auxiliary áka wheel button
    return isButton(event, 1);
  }

  function hasPrimaryModifier(event) {
    var originalEvent = getOriginal$1(event) || event;

    if (!isPrimaryButton(event)) {
      return false;
    }

    // Use cmd as primary modifier key for mac OS
    if (isMac()) {
      return originalEvent.metaKey;
    } else {
      return originalEvent.ctrlKey;
    }
  }


  function hasSecondaryModifier(event) {
    var originalEvent = getOriginal$1(event) || event;

    return isPrimaryButton(event) && originalEvent.shiftKey;
  }

  function allowAll(event) { return true; }

  function allowPrimaryAndAuxiliary(event) {
    return isPrimaryButton(event) || isAuxiliaryButton(event);
  }

  var LOW_PRIORITY$q = 500;


  /**
   * A plugin that provides interaction events for diagram elements.
   *
   * It emits the following events:
   *
   *   * element.click
   *   * element.contextmenu
   *   * element.dblclick
   *   * element.hover
   *   * element.mousedown
   *   * element.mousemove
   *   * element.mouseup
   *   * element.out
   *
   * Each event is a tuple { element, gfx, originalEvent }.
   *
   * Canceling the event via Event#preventDefault()
   * prevents the original DOM operation.
   *
   * @param {EventBus} eventBus
   */
  function InteractionEvents(eventBus, elementRegistry, styles) {

    var self = this;

    /**
     * Fire an interaction event.
     *
     * @param {string} type local event name, e.g. element.click.
     * @param {DOMEvent} event native event
     * @param {djs.model.Base} [element] the diagram element to emit the event on;
     *                                   defaults to the event target
     */
    function fire(type, event, element) {

      if (isIgnored(type, event)) {
        return;
      }

      var target, gfx, returnValue;

      if (!element) {
        target = event.delegateTarget || event.target;

        if (target) {
          gfx = target;
          element = elementRegistry.get(gfx);
        }
      } else {
        gfx = elementRegistry.getGraphics(element);
      }

      if (!gfx || !element) {
        return;
      }

      returnValue = eventBus.fire(type, {
        element: element,
        gfx: gfx,
        originalEvent: event
      });

      if (returnValue === false) {
        event.stopPropagation();
        event.preventDefault();
      }
    }

    // TODO(nikku): document this
    var handlers = {};

    function mouseHandler(localEventName) {
      return handlers[localEventName];
    }

    function isIgnored(localEventName, event) {

      var filter = ignoredFilters[localEventName] || isPrimaryButton;

      // only react on left mouse button interactions
      // except for interaction events that are enabled
      // for secundary mouse button
      return !filter(event);
    }

    var bindings = {
      click: 'element.click',
      contextmenu: 'element.contextmenu',
      dblclick: 'element.dblclick',
      mousedown: 'element.mousedown',
      mousemove: 'element.mousemove',
      mouseover: 'element.hover',
      mouseout: 'element.out',
      mouseup: 'element.mouseup',
    };

    var ignoredFilters = {
      'element.contextmenu': allowAll,
      'element.mousedown': allowPrimaryAndAuxiliary,
      'element.mouseup': allowPrimaryAndAuxiliary,
      'element.click': allowPrimaryAndAuxiliary,
      'element.dblclick': allowPrimaryAndAuxiliary
    };


    // manual event trigger //////////

    /**
     * Trigger an interaction event (based on a native dom event)
     * on the target shape or connection.
     *
     * @param {string} eventName the name of the triggered DOM event
     * @param {MouseEvent} event
     * @param {djs.model.Base} targetElement
     */
    function triggerMouseEvent(eventName, event, targetElement) {

      // i.e. element.mousedown...
      var localEventName = bindings[eventName];

      if (!localEventName) {
        throw new Error('unmapped DOM event name <' + eventName + '>');
      }

      return fire(localEventName, event, targetElement);
    }


    var ELEMENT_SELECTOR = 'svg, .djs-element';

    // event handling ///////

    function registerEvent(node, event, localEvent, ignoredFilter) {

      var handler = handlers[localEvent] = function(event) {
        fire(localEvent, event);
      };

      if (ignoredFilter) {
        ignoredFilters[localEvent] = ignoredFilter;
      }

      handler.$delegate = delegate.bind(node, ELEMENT_SELECTOR, event, handler);
    }

    function unregisterEvent(node, event, localEvent) {

      var handler = mouseHandler(localEvent);

      if (!handler) {
        return;
      }

      delegate.unbind(node, event, handler.$delegate);
    }

    function registerEvents(svg) {
      forEach$1(bindings, function(val, key) {
        registerEvent(svg, key, val);
      });
    }

    function unregisterEvents(svg) {
      forEach$1(bindings, function(val, key) {
        unregisterEvent(svg, key, val);
      });
    }

    eventBus.on('canvas.destroy', function(event) {
      unregisterEvents(event.svg);
    });

    eventBus.on('canvas.init', function(event) {
      registerEvents(event.svg);
    });


    // hit box updating ////////////////

    eventBus.on([ 'shape.added', 'connection.added' ], function(event) {
      var element = event.element,
          gfx = event.gfx;

      eventBus.fire('interactionEvents.createHit', { element: element, gfx: gfx });
    });

    // Update djs-hit on change.
    // A low priortity is necessary, because djs-hit of labels has to be updated
    // after the label bounds have been updated in the renderer.
    eventBus.on([
      'shape.changed',
      'connection.changed'
    ], LOW_PRIORITY$q, function(event) {

      var element = event.element,
          gfx = event.gfx;

      eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx });
    });

    eventBus.on('interactionEvents.createHit', LOW_PRIORITY$q, function(event) {
      var element = event.element,
          gfx = event.gfx;

      self.createDefaultHit(element, gfx);
    });

    eventBus.on('interactionEvents.updateHit', function(event) {
      var element = event.element,
          gfx = event.gfx;

      self.updateDefaultHit(element, gfx);
    });


    // hit styles ////////////

    var STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-stroke');

    var CLICK_STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-click-stroke');

    var ALL_HIT_STYLE = createHitStyle('djs-hit djs-hit-all');

    var NO_MOVE_HIT_STYLE = createHitStyle('djs-hit djs-hit-no-move');

    var HIT_TYPES = {
      'all': ALL_HIT_STYLE,
      'click-stroke': CLICK_STROKE_HIT_STYLE,
      'stroke': STROKE_HIT_STYLE,
      'no-move': NO_MOVE_HIT_STYLE
    };

    function createHitStyle(classNames, attrs) {

      attrs = assign({
        stroke: 'white',
        strokeWidth: 15
      }, attrs || {});

      return styles.cls(classNames, [ 'no-fill', 'no-border' ], attrs);
    }


    // style helpers ///////////////

    function applyStyle(hit, type) {

      var attrs = HIT_TYPES[type];

      if (!attrs) {
        throw new Error('invalid hit type <' + type + '>');
      }

      attr(hit, attrs);

      return hit;
    }

    function appendHit(gfx, hit) {
      append(gfx, hit);
    }


    // API

    /**
     * Remove hints on the given graphics.
     *
     * @param {SVGElement} gfx
     */
    this.removeHits = function(gfx) {
      var hits = all('.djs-hit', gfx);

      forEach$1(hits, remove$1);
    };

    /**
     * Create default hit for the given element.
     *
     * @param {djs.model.Base} element
     * @param {SVGElement} gfx
     *
     * @return {SVGElement} created hit
     */
    this.createDefaultHit = function(element, gfx) {
      var waypoints = element.waypoints,
          isFrame = element.isFrame,
          boxType;

      if (waypoints) {
        return this.createWaypointsHit(gfx, waypoints);
      } else {

        boxType = isFrame ? 'stroke' : 'all';

        return this.createBoxHit(gfx, boxType, {
          width: element.width,
          height: element.height
        });
      }
    };

    /**
     * Create hits for the given waypoints.
     *
     * @param {SVGElement} gfx
     * @param {Array<Point>} waypoints
     *
     * @return {SVGElement}
     */
    this.createWaypointsHit = function(gfx, waypoints) {

      var hit = createLine(waypoints);

      applyStyle(hit, 'stroke');

      appendHit(gfx, hit);

      return hit;
    };

    /**
     * Create hits for a box.
     *
     * @param {SVGElement} gfx
     * @param {string} hitType
     * @param {Object} attrs
     *
     * @return {SVGElement}
     */
    this.createBoxHit = function(gfx, type, attrs) {

      attrs = assign({
        x: 0,
        y: 0
      }, attrs);

      var hit = create$1('rect');

      applyStyle(hit, type);

      attr(hit, attrs);

      appendHit(gfx, hit);

      return hit;
    };

    /**
     * Update default hit of the element.
     *
     * @param  {djs.model.Base} element
     * @param  {SVGElement} gfx
     *
     * @return {SVGElement} updated hit
     */
    this.updateDefaultHit = function(element, gfx) {

      var hit = query('.djs-hit', gfx);

      if (!hit) {
        return;
      }

      if (element.waypoints) {
        updateLine(hit, element.waypoints);
      } else {
        attr(hit, {
          width: element.width,
          height: element.height
        });
      }

      return hit;
    };

    this.fire = fire;

    this.triggerMouseEvent = triggerMouseEvent;

    this.mouseHandler = mouseHandler;

    this.registerEvent = registerEvent;
    this.unregisterEvent = unregisterEvent;
  }


  InteractionEvents.$inject = [
    'eventBus',
    'elementRegistry',
    'styles'
  ];


  /**
   * An event indicating that the mouse hovered over an element
   *
   * @event element.hover
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has left an element
   *
   * @event element.out
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has clicked an element
   *
   * @event element.click
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has double clicked an element
   *
   * @event element.dblclick
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has gone down on an element.
   *
   * @event element.mousedown
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has gone up on an element.
   *
   * @event element.mouseup
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the context menu action is triggered
   * via mouse or touch controls.
   *
   * @event element.contextmenu
   *
   * @type {Object}
   * @property {djs.model.Base} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  var InteractionEventsModule$1 = {
    __init__: [ 'interactionEvents' ],
    interactionEvents: [ 'type', InteractionEvents ]
  };

  var LOW_PRIORITY$p = 500;


  /**
   * @class
   *
   * A plugin that adds an outline to shapes and connections that may be activated and styled
   * via CSS classes.
   *
   * @param {EventBus} eventBus
   * @param {Styles} styles
   * @param {ElementRegistry} elementRegistry
   */
  function Outline(eventBus, styles, elementRegistry) {

    this.offset = 6;

    var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]);

    var self = this;

    function createOutline(gfx, bounds) {
      var outline = create$1('rect');

      attr(outline, assign({
        x: 10,
        y: 10,
        rx: 3,
        width: 100,
        height: 100
      }, OUTLINE_STYLE));

      append(gfx, outline);

      return outline;
    }

    // A low priortity is necessary, because outlines of labels have to be updated
    // after the label bounds have been updated in the renderer.
    eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$p, function(event) {
      var element = event.element,
          gfx = event.gfx;

      var outline = query('.djs-outline', gfx);

      if (!outline) {
        outline = createOutline(gfx);
      }

      self.updateShapeOutline(outline, element);
    });

    eventBus.on([ 'connection.added', 'connection.changed' ], function(event) {
      var element = event.element,
          gfx = event.gfx;

      var outline = query('.djs-outline', gfx);

      if (!outline) {
        outline = createOutline(gfx);
      }

      self.updateConnectionOutline(outline, element);
    });
  }


  /**
   * Updates the outline of a shape respecting the dimension of the
   * element and an outline offset.
   *
   * @param  {SVGElement} outline
   * @param  {djs.model.Base} element
   */
  Outline.prototype.updateShapeOutline = function(outline, element) {

    attr(outline, {
      x: -this.offset,
      y: -this.offset,
      width: element.width + this.offset * 2,
      height: element.height + this.offset * 2
    });

  };


  /**
   * Updates the outline of a connection respecting the bounding box of
   * the connection and an outline offset.
   *
   * @param  {SVGElement} outline
   * @param  {djs.model.Base} element
   */
  Outline.prototype.updateConnectionOutline = function(outline, connection) {

    var bbox = getBBox(connection);

    attr(outline, {
      x: bbox.x - this.offset,
      y: bbox.y - this.offset,
      width: bbox.width + this.offset * 2,
      height: bbox.height + this.offset * 2
    });

  };


  Outline.$inject = [ 'eventBus', 'styles', 'elementRegistry' ];

  var OutlineModule = {
    __init__: [ 'outline' ],
    outline: [ 'type', Outline ]
  };

  /**
   * A service that offers the current selection in a diagram.
   * Offers the api to control the selection, too.
   *
   * @class
   *
   * @param {EventBus} eventBus the event bus
   */
  function Selection(eventBus, canvas) {

    this._eventBus = eventBus;
    this._canvas = canvas;

    this._selectedElements = [];

    var self = this;

    eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
      var element = e.element;
      self.deselect(element);
    });

    eventBus.on([ 'diagram.clear', 'root.set' ], function(e) {
      self.select(null);
    });
  }

  Selection.$inject = [ 'eventBus', 'canvas' ];


  Selection.prototype.deselect = function(element) {
    var selectedElements = this._selectedElements;

    var idx = selectedElements.indexOf(element);

    if (idx !== -1) {
      var oldSelection = selectedElements.slice();

      selectedElements.splice(idx, 1);

      this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
    }
  };


  Selection.prototype.get = function() {
    return this._selectedElements;
  };

  Selection.prototype.isSelected = function(element) {
    return this._selectedElements.indexOf(element) !== -1;
  };


  /**
   * This method selects one or more elements on the diagram.
   *
   * By passing an additional add parameter you can decide whether or not the element(s)
   * should be added to the already existing selection or not.
   *
   * @method Selection#select
   *
   * @param  {Object|Object[]} elements element or array of elements to be selected
   * @param  {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false
   */
  Selection.prototype.select = function(elements, add) {
    var selectedElements = this._selectedElements,
        oldSelection = selectedElements.slice();

    if (!isArray$3(elements)) {
      elements = elements ? [ elements ] : [];
    }

    var canvas = this._canvas;

    var rootElement = canvas.getRootElement();

    elements = elements.filter(function(element) {
      var elementRoot = canvas.findRoot(element);

      return rootElement === elementRoot;
    });

    // selection may be cleared by passing an empty array or null
    // to the method
    if (add) {
      forEach$1(elements, function(element) {
        if (selectedElements.indexOf(element) !== -1) {

          // already selected
          return;
        } else {
          selectedElements.push(element);
        }
      });
    } else {
      this._selectedElements = selectedElements = elements.slice();
    }

    this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
  };

  var MARKER_HOVER = 'hover',
      MARKER_SELECTED = 'selected';

  var SELECTION_OUTLINE_PADDING = 6;


  /**
   * A plugin that adds a visible selection UI to shapes and connections
   * by appending the <code>hover</code> and <code>selected</code> classes to them.
   *
   * @class
   *
   * Makes elements selectable, too.
   *
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   */
  function SelectionVisuals(canvas, eventBus, selection) {
    this._canvas = canvas;

    var self = this;

    this._multiSelectionBox = null;

    function addMarker(e, cls) {
      canvas.addMarker(e, cls);
    }

    function removeMarker(e, cls) {
      canvas.removeMarker(e, cls);
    }

    eventBus.on('element.hover', function(event) {
      addMarker(event.element, MARKER_HOVER);
    });

    eventBus.on('element.out', function(event) {
      removeMarker(event.element, MARKER_HOVER);
    });

    eventBus.on('selection.changed', function(event) {

      function deselect(s) {
        removeMarker(s, MARKER_SELECTED);
      }

      function select(s) {
        addMarker(s, MARKER_SELECTED);
      }

      var oldSelection = event.oldSelection,
          newSelection = event.newSelection;

      forEach$1(oldSelection, function(e) {
        if (newSelection.indexOf(e) === -1) {
          deselect(e);
        }
      });

      forEach$1(newSelection, function(e) {
        if (oldSelection.indexOf(e) === -1) {
          select(e);
        }
      });

      self._updateSelectionOutline(newSelection);
    });


    eventBus.on('element.changed', function(event) {
      if (selection.isSelected(event.element)) {
        self._updateSelectionOutline(selection.get());
      }
    });
  }

  SelectionVisuals.$inject = [
    'canvas',
    'eventBus',
    'selection'
  ];

  SelectionVisuals.prototype._updateSelectionOutline = function(selection) {
    var layer = this._canvas.getLayer('selectionOutline');

    clear(layer);

    var enabled = selection.length > 1;

    var container = this._canvas.getContainer();

    classes(container)[enabled ? 'add' : 'remove']('djs-multi-select');

    if (!enabled) {
      return;
    }

    var bBox = addSelectionOutlinePadding(getBBox(selection));

    var rect = create$1('rect');

    attr(rect, assign({
      rx: 3
    }, bBox));

    classes(rect).add('djs-selection-outline');

    append(layer, rect);
  };

  // helpers //////////

  function addSelectionOutlinePadding(bBox) {
    return {
      x: bBox.x - SELECTION_OUTLINE_PADDING,
      y: bBox.y - SELECTION_OUTLINE_PADDING,
      width: bBox.width + SELECTION_OUTLINE_PADDING * 2,
      height: bBox.height + SELECTION_OUTLINE_PADDING * 2
    };
  }

  function SelectionBehavior(eventBus, selection, canvas, elementRegistry) {

    // Select elements on create
    eventBus.on('create.end', 500, function(event) {
      var context = event.context,
          canExecute = context.canExecute,
          elements = context.elements,
          hints = context.hints || {},
          autoSelect = hints.autoSelect;

      if (canExecute) {
        if (autoSelect === false) {

          // Select no elements
          return;
        }

        if (isArray$3(autoSelect)) {
          selection.select(autoSelect);
        } else {

          // Select all elements by default
          selection.select(elements.filter(isShown));
        }
      }
    });

    // Select connection targets on connect
    eventBus.on('connect.end', 500, function(event) {
      var context = event.context,
          connection = context.connection;

      if (connection) {
        selection.select(connection);
      }
    });

    // Select shapes on move
    eventBus.on('shape.move.end', 500, function(event) {
      var previousSelection = event.previousSelection || [];

      var shape = elementRegistry.get(event.context.shape.id);

      // Always select main shape on move
      var isSelected = find(previousSelection, function(selectedShape) {
        return shape.id === selectedShape.id;
      });

      if (!isSelected) {
        selection.select(shape);
      }
    });

    // Select elements on click
    eventBus.on('element.click', function(event) {

      if (!isPrimaryButton(event)) {
        return;
      }

      var element = event.element;

      if (element === canvas.getRootElement()) {
        element = null;
      }

      var isSelected = selection.isSelected(element),
          isMultiSelect = selection.get().length > 1;

      // Add to selection if CTRL or SHIFT pressed
      var add = hasPrimaryModifier(event) || hasSecondaryModifier(event);

      if (isSelected && isMultiSelect) {
        if (add) {

          // Deselect element
          return selection.deselect(element);
        } else {

          // Select element only
          return selection.select(element);
        }
      } else if (!isSelected) {

        // Select element
        selection.select(element, add);
      } else {

        // Deselect element
        selection.deselect(element);
      }
    });
  }

  SelectionBehavior.$inject = [
    'eventBus',
    'selection',
    'canvas',
    'elementRegistry'
  ];


  function isShown(element) {
    return !element.hidden;
  }

  var SelectionModule = {
    __init__: [ 'selectionVisuals', 'selectionBehavior' ],
    __depends__: [
      InteractionEventsModule$1,
      OutlineModule
    ],
    selection: [ 'type', Selection ],
    selectionVisuals: [ 'type', SelectionVisuals ],
    selectionBehavior: [ 'type', SelectionBehavior ]
  };

  /**
   * Util that provides unique IDs.
   *
   * @class djs.util.IdGenerator
   * @constructor
   * @memberOf djs.util
   *
   * The ids can be customized via a given prefix and contain a random value to avoid collisions.
   *
   * @param {string} prefix a prefix to prepend to generated ids (for better readability)
   */
  function IdGenerator(prefix) {

    this._counter = 0;
    this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-';
  }

  /**
   * Returns a next unique ID.
   *
   * @method djs.util.IdGenerator#next
   *
   * @returns {string} the id
   */
  IdGenerator.prototype.next = function() {
    return this._prefix + (++this._counter);
  };

  // document wide unique overlay ids
  var ids$1 = new IdGenerator('ov');

  var LOW_PRIORITY$o = 500;


  /**
   * A service that allows users to attach overlays to diagram elements.
   *
   * The overlay service will take care of overlay positioning during updates.
   *
   * @example
   *
   * // add a pink badge on the top left of the shape
   * overlays.add(someShape, {
   *   position: {
   *     top: -5,
   *     left: -5
   *   },
   *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
   * });
   *
   * // or add via shape id
   *
   * overlays.add('some-element-id', {
   *   position: {
   *     top: -5,
   *     left: -5
   *   }
   *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
   * });
   *
   * // or add with optional type
   *
   * overlays.add(someShape, 'badge', {
   *   position: {
   *     top: -5,
   *     left: -5
   *   }
   *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
   * });
   *
   *
   * // remove an overlay
   *
   * var id = overlays.add(...);
   * overlays.remove(id);
   *
   *
   * You may configure overlay defaults during tool by providing a `config` module
   * with `overlays.defaults` as an entry:
   *
   * {
   *   overlays: {
   *     defaults: {
   *       show: {
   *         minZoom: 0.7,
   *         maxZoom: 5.0
   *       },
   *       scale: {
   *         min: 1
   *       }
   *     }
   * }
   *
   * @param {Object} config
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {ElementRegistry} elementRegistry
   */
  function Overlays(config, eventBus, canvas, elementRegistry) {

    this._eventBus = eventBus;
    this._canvas = canvas;
    this._elementRegistry = elementRegistry;

    this._ids = ids$1;

    this._overlayDefaults = assign({

      // no show constraints
      show: null,

      // always scale
      scale: true
    }, config && config.defaults);

    /**
     * Mapping overlayId -> overlay
     */
    this._overlays = {};

    /**
     * Mapping elementId -> overlay container
     */
    this._overlayContainers = [];

    // root html element for all overlays
    this._overlayRoot = createRoot$1(canvas.getContainer());

    this._init();
  }


  Overlays.$inject = [
    'config.overlays',
    'eventBus',
    'canvas',
    'elementRegistry'
  ];


  /**
   * Returns the overlay with the specified id or a list of overlays
   * for an element with a given type.
   *
   * @example
   *
   * // return the single overlay with the given id
   * overlays.get('some-id');
   *
   * // return all overlays for the shape
   * overlays.get({ element: someShape });
   *
   * // return all overlays on shape with type 'badge'
   * overlays.get({ element: someShape, type: 'badge' });
   *
   * // shape can also be specified as id
   * overlays.get({ element: 'element-id', type: 'badge' });
   *
   *
   * @param {Object} search
   * @param {string} [search.id]
   * @param {string|djs.model.Base} [search.element]
   * @param {string} [search.type]
   *
   * @return {Object|Array<Object>} the overlay(s)
   */
  Overlays.prototype.get = function(search) {

    if (isString(search)) {
      search = { id: search };
    }

    if (isString(search.element)) {
      search.element = this._elementRegistry.get(search.element);
    }

    if (search.element) {
      var container = this._getOverlayContainer(search.element, true);

      // return a list of overlays when searching by element (+type)
      if (container) {
        return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice();
      } else {
        return [];
      }
    } else
    if (search.type) {
      return filter(this._overlays, matchPattern({ type: search.type }));
    } else {

      // return single element when searching by id
      return search.id ? this._overlays[search.id] : null;
    }
  };

  /**
   * Adds a HTML overlay to an element.
   *
   * @param {string|djs.model.Base}   element   attach overlay to this shape
   * @param {string}                  [type]    optional type to assign to the overlay
   * @param {Object}                  overlay   the overlay configuration
   *
   * @param {string|DOMElement}       overlay.html                 html element to use as an overlay
   * @param {Object}                  [overlay.show]               show configuration
   * @param {number}                  [overlay.show.minZoom]       minimal zoom level to show the overlay
   * @param {number}                  [overlay.show.maxZoom]       maximum zoom level to show the overlay
   * @param {Object}                  overlay.position             where to attach the overlay
   * @param {number}                  [overlay.position.left]      relative to element bbox left attachment
   * @param {number}                  [overlay.position.top]       relative to element bbox top attachment
   * @param {number}                  [overlay.position.bottom]    relative to element bbox bottom attachment
   * @param {number}                  [overlay.position.right]     relative to element bbox right attachment
   * @param {boolean|Object}          [overlay.scale=true]         false to preserve the same size regardless of
   *                                                               diagram zoom
   * @param {number}                  [overlay.scale.min]
   * @param {number}                  [overlay.scale.max]
   *
   * @return {string}                 id that may be used to reference the overlay for update or removal
   */
  Overlays.prototype.add = function(element, type, overlay) {

    if (isObject(type)) {
      overlay = type;
      type = null;
    }

    if (!element.id) {
      element = this._elementRegistry.get(element);
    }

    if (!overlay.position) {
      throw new Error('must specifiy overlay position');
    }

    if (!overlay.html) {
      throw new Error('must specifiy overlay html');
    }

    if (!element) {
      throw new Error('invalid element specified');
    }

    var id = this._ids.next();

    overlay = assign({}, this._overlayDefaults, overlay, {
      id: id,
      type: type,
      element: element,
      html: overlay.html
    });

    this._addOverlay(overlay);

    return id;
  };


  /**
   * Remove an overlay with the given id or all overlays matching the given filter.
   *
   * @see Overlays#get for filter options.
   *
   * @param {string|object} [filter]
   */
  Overlays.prototype.remove = function(filter) {

    var overlays = this.get(filter) || [];

    if (!isArray$3(overlays)) {
      overlays = [ overlays ];
    }

    var self = this;

    forEach$1(overlays, function(overlay) {

      var container = self._getOverlayContainer(overlay.element, true);

      if (overlay) {
        remove$2(overlay.html);
        remove$2(overlay.htmlContainer);

        delete overlay.htmlContainer;
        delete overlay.element;

        delete self._overlays[overlay.id];
      }

      if (container) {
        var idx = container.overlays.indexOf(overlay);
        if (idx !== -1) {
          container.overlays.splice(idx, 1);
        }
      }
    });

  };


  Overlays.prototype.show = function() {
    setVisible$1(this._overlayRoot);
  };


  Overlays.prototype.hide = function() {
    setVisible$1(this._overlayRoot, false);
  };

  Overlays.prototype.clear = function() {
    this._overlays = {};

    this._overlayContainers = [];

    clear$1(this._overlayRoot);
  };

  Overlays.prototype._updateOverlayContainer = function(container) {
    var element = container.element,
        html = container.html;

    // update container left,top according to the elements x,y coordinates
    // this ensures we can attach child elements relative to this container

    var x = element.x,
        y = element.y;

    if (element.waypoints) {
      var bbox = getBBox(element);
      x = bbox.x;
      y = bbox.y;
    }

    setPosition$1(html, x, y);

    attr$1(container.html, 'data-container-id', element.id);
  };


  Overlays.prototype._updateOverlay = function(overlay) {

    var position = overlay.position,
        htmlContainer = overlay.htmlContainer,
        element = overlay.element;

    // update overlay html relative to shape because
    // it is already positioned on the element

    // update relative
    var left = position.left,
        top = position.top;

    if (position.right !== undefined) {

      var width;

      if (element.waypoints) {
        width = getBBox(element).width;
      } else {
        width = element.width;
      }

      left = position.right * -1 + width;
    }

    if (position.bottom !== undefined) {

      var height;

      if (element.waypoints) {
        height = getBBox(element).height;
      } else {
        height = element.height;
      }

      top = position.bottom * -1 + height;
    }

    setPosition$1(htmlContainer, left || 0, top || 0);
    this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
  };


  Overlays.prototype._createOverlayContainer = function(element) {
    var html = domify('<div class="djs-overlays" />');
    assign$1(html, { position: 'absolute' });

    this._overlayRoot.appendChild(html);

    var container = {
      html: html,
      element: element,
      overlays: []
    };

    this._updateOverlayContainer(container);

    this._overlayContainers.push(container);

    return container;
  };


  Overlays.prototype._updateRoot = function(viewbox) {
    var scale = viewbox.scale || 1;

    var matrix = 'matrix(' +
    [
      scale,
      0,
      0,
      scale,
      -1 * viewbox.x * scale,
      -1 * viewbox.y * scale
    ].join(',') +
    ')';

    setTransform$1(this._overlayRoot, matrix);
  };


  Overlays.prototype._getOverlayContainer = function(element, raw) {
    var container = find(this._overlayContainers, function(c) {
      return c.element === element;
    });


    if (!container && !raw) {
      return this._createOverlayContainer(element);
    }

    return container;
  };


  Overlays.prototype._addOverlay = function(overlay) {

    var id = overlay.id,
        element = overlay.element,
        html = overlay.html,
        htmlContainer,
        overlayContainer;

    // unwrap jquery (for those who need it)
    if (html.get && html.constructor.prototype.jquery) {
      html = html.get(0);
    }

    // create proper html elements from
    // overlay HTML strings
    if (isString(html)) {
      html = domify(html);
    }

    overlayContainer = this._getOverlayContainer(element);

    htmlContainer = domify('<div class="djs-overlay" data-overlay-id="' + id + '">');
    assign$1(htmlContainer, { position: 'absolute' });

    htmlContainer.appendChild(html);

    if (overlay.type) {
      classes$1(htmlContainer).add('djs-overlay-' + overlay.type);
    }

    var elementRoot = this._canvas.findRoot(element);
    var activeRoot = this._canvas.getRootElement();

    setVisible$1(htmlContainer, elementRoot === activeRoot);

    overlay.htmlContainer = htmlContainer;

    overlayContainer.overlays.push(overlay);
    overlayContainer.html.appendChild(htmlContainer);

    this._overlays[id] = overlay;

    this._updateOverlay(overlay);
    this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
  };


  Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) {
    var show = overlay.show,
        rootElement = this._canvas.findRoot(overlay.element),
        minZoom = show && show.minZoom,
        maxZoom = show && show.maxZoom,
        htmlContainer = overlay.htmlContainer,
        activeRootElement = this._canvas.getRootElement(),
        visible = true;

    if (rootElement !== activeRootElement) {
      visible = false;
    } else if (show) {
      if (
        (isDefined(minZoom) && minZoom > viewbox.scale) ||
        (isDefined(maxZoom) && maxZoom < viewbox.scale)
      ) {
        visible = false;
      }
    }

    setVisible$1(htmlContainer, visible);

    this._updateOverlayScale(overlay, viewbox);
  };


  Overlays.prototype._updateOverlayScale = function(overlay, viewbox) {
    var shouldScale = overlay.scale,
        minScale,
        maxScale,
        htmlContainer = overlay.htmlContainer;

    var scale, transform = '';

    if (shouldScale !== true) {

      if (shouldScale === false) {
        minScale = 1;
        maxScale = 1;
      } else {
        minScale = shouldScale.min;
        maxScale = shouldScale.max;
      }

      if (isDefined(minScale) && viewbox.scale < minScale) {
        scale = (1 / viewbox.scale || 1) * minScale;
      }

      if (isDefined(maxScale) && viewbox.scale > maxScale) {
        scale = (1 / viewbox.scale || 1) * maxScale;
      }
    }

    if (isDefined(scale)) {
      transform = 'scale(' + scale + ',' + scale + ')';
    }

    setTransform$1(htmlContainer, transform);
  };


  Overlays.prototype._updateOverlaysVisibilty = function(viewbox) {

    var self = this;

    forEach$1(this._overlays, function(overlay) {
      self._updateOverlayVisibilty(overlay, viewbox);
    });
  };


  Overlays.prototype._init = function() {

    var eventBus = this._eventBus;

    var self = this;


    // scroll/zoom integration

    function updateViewbox(viewbox) {
      self._updateRoot(viewbox);
      self._updateOverlaysVisibilty(viewbox);

      self.show();
    }

    eventBus.on('canvas.viewbox.changing', function(event) {
      self.hide();
    });

    eventBus.on('canvas.viewbox.changed', function(event) {
      updateViewbox(event.viewbox);
    });


    // remove integration

    eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
      var element = e.element;
      var overlays = self.get({ element: element });

      forEach$1(overlays, function(o) {
        self.remove(o.id);
      });

      var container = self._getOverlayContainer(element);

      if (container) {
        remove$2(container.html);
        var i = self._overlayContainers.indexOf(container);
        if (i !== -1) {
          self._overlayContainers.splice(i, 1);
        }
      }
    });


    // move integration

    eventBus.on('element.changed', LOW_PRIORITY$o, function(e) {
      var element = e.element;

      var container = self._getOverlayContainer(element, true);

      if (container) {
        forEach$1(container.overlays, function(overlay) {
          self._updateOverlay(overlay);
        });

        self._updateOverlayContainer(container);
      }
    });


    // marker integration, simply add them on the overlays as classes, too.

    eventBus.on('element.marker.update', function(e) {
      var container = self._getOverlayContainer(e.element, true);
      if (container) {
        classes$1(container.html)[e.add ? 'add' : 'remove'](e.marker);
      }
    });


    eventBus.on('root.set', function() {
      self._updateOverlaysVisibilty(self._canvas.viewbox());
    });

    // clear overlays with diagram

    eventBus.on('diagram.clear', this.clear, this);
  };



  // helpers /////////////////////////////

  function createRoot$1(parentNode) {
    var root = domify(
      '<div class="djs-overlay-container" />'
    );

    assign$1(root, {
      position: 'absolute',
      width: 0,
      height: 0
    });

    parentNode.insertBefore(root, parentNode.firstChild);

    return root;
  }

  function setPosition$1(el, x, y) {
    assign$1(el, { left: x + 'px', top: y + 'px' });
  }

  /**
   * Set element visible
   *
   * @param {DOMElement} el
   * @param {boolean} [visible=true]
   */
  function setVisible$1(el, visible) {
    el.style.display = visible === false ? 'none' : '';
  }

  function setTransform$1(el, transform) {

    el.style['transform-origin'] = 'top left';

    [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
      el.style[prefix + 'transform'] = transform;
    });
  }

  var OverlaysModule = {
    __init__: [ 'overlays' ],
    overlays: [ 'type', Overlays ]
  };

  /**
   * Adds change support to the diagram, including
   *
   * <ul>
   *   <li>redrawing shapes and connections on change</li>
   * </ul>
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {ElementRegistry} elementRegistry
   * @param {GraphicsFactory} graphicsFactory
   */
  function ChangeSupport(
      eventBus, canvas, elementRegistry,
      graphicsFactory) {


    // redraw shapes / connections on change

    eventBus.on('element.changed', function(event) {

      var element = event.element;

      // element might have been deleted and replaced by new element with same ID
      // thus check for parent of element except for root element
      if (element.parent || element === canvas.getRootElement()) {
        event.gfx = elementRegistry.getGraphics(element);
      }

      // shape + gfx may have been deleted
      if (!event.gfx) {
        return;
      }

      eventBus.fire(getType(element) + '.changed', event);
    });

    eventBus.on('elements.changed', function(event) {

      var elements = event.elements;

      elements.forEach(function(e) {
        eventBus.fire('element.changed', { element: e });
      });

      graphicsFactory.updateContainments(elements);
    });

    eventBus.on('shape.changed', function(event) {
      graphicsFactory.update('shape', event.element, event.gfx);
    });

    eventBus.on('connection.changed', function(event) {
      graphicsFactory.update('connection', event.element, event.gfx);
    });
  }

  ChangeSupport.$inject = [
    'eventBus',
    'canvas',
    'elementRegistry',
    'graphicsFactory'
  ];

  var ChangeSupportModule = {
    __init__: [ 'changeSupport' ],
    changeSupport: [ 'type', ChangeSupport ]
  };

  var DEFAULT_PRIORITY$4 = 1000;

  /**
   * A utility that can be used to plug-in into the command execution for
   * extension and/or validation.
   *
   * @param {EventBus} eventBus
   *
   * @example
   *
   * import inherits from 'inherits-browser';
   *
   * import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
   *
   * function CommandLogger(eventBus) {
   *   CommandInterceptor.call(this, eventBus);
   *
   *   this.preExecute(function(event) {
   *     console.log('command pre-execute', event);
   *   });
   * }
   *
   * inherits(CommandLogger, CommandInterceptor);
   *
   */
  function CommandInterceptor(eventBus) {
    this._eventBus = eventBus;
  }

  CommandInterceptor.$inject = [ 'eventBus' ];

  function unwrapEvent(fn, that) {
    return function(event) {
      return fn.call(that || null, event.context, event.command, event);
    };
  }

  /**
   * Register an interceptor for a command execution
   *
   * @param {string|Array<string>} [events] list of commands to register on
   * @param {string} [hook] command hook, i.e. preExecute, executed to listen on
   * @param {number} [priority] the priority on which to hook into the execution
   * @param {Function} handlerFn interceptor to be invoked with (event)
   * @param {boolean} unwrap if true, unwrap the event and pass (context, command, event) to the
   *                          listener instead
   * @param {Object} [that] Pass context (`this`) to the handler function
   */
  CommandInterceptor.prototype.on = function(events, hook, priority, handlerFn, unwrap, that) {

    if (isFunction(hook) || isNumber(hook)) {
      that = unwrap;
      unwrap = handlerFn;
      handlerFn = priority;
      priority = hook;
      hook = null;
    }

    if (isFunction(priority)) {
      that = unwrap;
      unwrap = handlerFn;
      handlerFn = priority;
      priority = DEFAULT_PRIORITY$4;
    }

    if (isObject(unwrap)) {
      that = unwrap;
      unwrap = false;
    }

    if (!isFunction(handlerFn)) {
      throw new Error('handlerFn must be a function');
    }

    if (!isArray$3(events)) {
      events = [ events ];
    }

    var eventBus = this._eventBus;

    forEach$1(events, function(event) {

      // concat commandStack(.event)?(.hook)?
      var fullEvent = [ 'commandStack', event, hook ].filter(function(e) { return e; }).join('.');

      eventBus.on(fullEvent, priority, unwrap ? unwrapEvent(handlerFn, that) : handlerFn, that);
    });
  };


  var hooks = [
    'canExecute',
    'preExecute',
    'preExecuted',
    'execute',
    'executed',
    'postExecute',
    'postExecuted',
    'revert',
    'reverted'
  ];

  /*
   * Install hook shortcuts
   *
   * This will generate the CommandInterceptor#(preExecute|...|reverted) methods
   * which will in term forward to CommandInterceptor#on.
   */
  forEach$1(hooks, function(hook) {

    /**
     * {canExecute|preExecute|preExecuted|execute|executed|postExecute|postExecuted|revert|reverted}
     *
     * A named hook for plugging into the command execution
     *
     * @param {string|Array<string>} [events] list of commands to register on
     * @param {number} [priority] the priority on which to hook into the execution
     * @param {Function} handlerFn interceptor to be invoked with (event)
     * @param {boolean} [unwrap=false] if true, unwrap the event and pass (context, command, event) to the
     *                          listener instead
     * @param {Object} [that] Pass context (`this`) to the handler function
     */
    CommandInterceptor.prototype[hook] = function(events, priority, handlerFn, unwrap, that) {

      if (isFunction(events) || isNumber(events)) {
        that = unwrap;
        unwrap = handlerFn;
        handlerFn = priority;
        priority = events;
        events = null;
      }

      this.on(events, hook, priority, handlerFn, unwrap, that);
    };
  });

  /**
   * A modeling behavior that ensures we set the correct root element
   * as we undo and redo commands.
   *
   * @param {Canvas} canvas
   * @param {didi.Injector} injector
   */
  function RootElementsBehavior(canvas, injector) {

    injector.invoke(CommandInterceptor, this);

    this.executed(function(event) {
      var context = event.context;

      if (context.rootElement) {
        canvas.setRootElement(context.rootElement);
      } else {
        context.rootElement = canvas.getRootElement();
      }
    });

    this.revert(function(event) {
      var context = event.context;

      if (context.rootElement) {
        canvas.setRootElement(context.rootElement);
      }
    });
  }

  e(RootElementsBehavior, CommandInterceptor);

  RootElementsBehavior.$inject = [ 'canvas', 'injector' ];

  var RootElementsModule = {
    __init__: [ 'rootElementsBehavior' ],
    rootElementsBehavior: [ 'type', RootElementsBehavior ]
  };

  var css_escape = {exports: {}};

  /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */

  (function (module, exports) {
  (function(root, factory) {
  		// https://github.com/umdjs/umd/blob/master/returnExports.js
  		{
  			// For Node.js.
  			module.exports = factory(root);
  		}
  	}(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : commonjsGlobal, function(root) {

  		if (root.CSS && root.CSS.escape) {
  			return root.CSS.escape;
  		}

  		// https://drafts.csswg.org/cssom/#serialize-an-identifier
  		var cssEscape = function(value) {
  			if (arguments.length == 0) {
  				throw new TypeError('`CSS.escape` requires an argument.');
  			}
  			var string = String(value);
  			var length = string.length;
  			var index = -1;
  			var codeUnit;
  			var result = '';
  			var firstCodeUnit = string.charCodeAt(0);
  			while (++index < length) {
  				codeUnit = string.charCodeAt(index);
  				// Note: there’s no need to special-case astral symbols, surrogate
  				// pairs, or lone surrogates.

  				// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
  				// (U+FFFD).
  				if (codeUnit == 0x0000) {
  					result += '\uFFFD';
  					continue;
  				}

  				if (
  					// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
  					// U+007F, […]
  					(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
  					// If the character is the first character and is in the range [0-9]
  					// (U+0030 to U+0039), […]
  					(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
  					// If the character is the second character and is in the range [0-9]
  					// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
  					(
  						index == 1 &&
  						codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
  						firstCodeUnit == 0x002D
  					)
  				) {
  					// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
  					result += '\\' + codeUnit.toString(16) + ' ';
  					continue;
  				}

  				if (
  					// If the character is the first character and is a `-` (U+002D), and
  					// there is no second character, […]
  					index == 0 &&
  					length == 1 &&
  					codeUnit == 0x002D
  				) {
  					result += '\\' + string.charAt(index);
  					continue;
  				}

  				// If the character is not handled by one of the above rules and is
  				// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
  				// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
  				// U+005A), or [a-z] (U+0061 to U+007A), […]
  				if (
  					codeUnit >= 0x0080 ||
  					codeUnit == 0x002D ||
  					codeUnit == 0x005F ||
  					codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
  					codeUnit >= 0x0041 && codeUnit <= 0x005A ||
  					codeUnit >= 0x0061 && codeUnit <= 0x007A
  				) {
  					// the character itself
  					result += string.charAt(index);
  					continue;
  				}

  				// Otherwise, the escaped character.
  				// https://drafts.csswg.org/cssom/#escape-a-character
  				result += '\\' + string.charAt(index);

  			}
  			return result;
  		};

  		if (!root.CSS) {
  			root.CSS = {};
  		}

  		root.CSS.escape = cssEscape;
  		return cssEscape;

  	}));
  } (css_escape));

  var cssEscape = css_escape.exports;

  var HTML_ESCAPE_MAP = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    '\'': '&#39;'
  };

  function escapeHTML(str) {
    str = '' + str;

    return str && str.replace(/[&<>"']/g, function(match) {
      return HTML_ESCAPE_MAP[match];
    });
  }

  var planeSuffix = '_plane';

  /**
   * Get primary shape ID for a plane.
   *
   * @param  {djs.model.Base|ModdleElement} element
   *
   * @returns {String}
   */
  function getShapeIdFromPlane(element) {
    var id = element.id;

    return removePlaneSuffix(id);
  }

  /**
   * Get plane ID for a primary shape.
   *
   * @param  {djs.model.Base|ModdleElement} element
   *
   * @returns {String}
   */
  function getPlaneIdFromShape(element) {
    var id = element.id;

    if (is$1(element, 'bpmn:SubProcess')) {
      return addPlaneSuffix(id);
    }

    return id;
  }

  /**
   * Get plane ID for primary shape ID.
   *
   * @param {String} id
   *
   * @returns {String}
   */
  function toPlaneId(id) {
    return addPlaneSuffix(id);
  }

  /**
   * Check wether element is plane.
   *
   * @param  {djs.model.Base|ModdleElement} element
   *
   * @returns {Boolean}
   */
  function isPlane(element) {
    var di = getDi(element);

    return is$1(di, 'bpmndi:BPMNPlane');
  }

  function addPlaneSuffix(id) {
    return id + planeSuffix;
  }

  function removePlaneSuffix(id) {
    return id.replace(new RegExp(planeSuffix + '$'), '');
  }

  var OPEN_CLASS = 'bjs-breadcrumbs-shown';


  /**
   * Adds overlays that allow switching planes on collapsed subprocesses.
   *
   * @param {eventBus} eventBus
   * @param {elementRegistry} elementRegistry
   * @param {overlays} overlays
   * @param {canvas} canvas
   */
  function DrilldownBreadcrumbs(eventBus, elementRegistry, overlays, canvas) {
    var breadcrumbs = domify('<ul class="bjs-breadcrumbs"></ul>');
    var container = canvas.getContainer();
    var containerClasses = classes$1(container);
    container.appendChild(breadcrumbs);

    var boParents = [];

    // update breadcrumbs if name or ID of the primary shape changes
    eventBus.on('element.changed', function(e) {
      var shape = e.element,
          bo = getBusinessObject(shape);

      var isPresent = find(boParents, function(el) {
        return el === bo;
      });

      if (!isPresent) {
        return;
      }

      updateBreadcrumbs();
    });

    /**
     * Updates the displayed breadcrumbs. If no element is provided, only the
     * labels are updated.
     *
     * @param {djs.model.Base} [element]
     */
    function updateBreadcrumbs(element) {
      if (element) {
        boParents = getBoParentChain(element);
      }

      var path = boParents.map(function(parent) {
        var title = escapeHTML(parent.name || parent.id);
        var link = domify('<li><span class="bjs-crumb"><a title="' + title + '">' + title + '</a></span></li>');

        var parentPlane = canvas.findRoot(getPlaneIdFromShape(parent)) || canvas.findRoot(parent.id);

        // when the root is a collaboration, the process does not have a corresponding
        // element in the elementRegisty. Instead, we search for the corresponding participant
        if (!parentPlane && is$1(parent, 'bpmn:Process')) {
          var participant = elementRegistry.find(function(element) {
            var bo = getBusinessObject(element);
            return bo && bo.processRef && bo.processRef === parent;
          });

          parentPlane = canvas.findRoot(participant.id);
        }

        link.addEventListener('click', function() {
          canvas.setRootElement(parentPlane);
        });

        return link;
      });

      breadcrumbs.innerHTML = '';

      // show breadcrumbs and expose state to .djs-container
      var visible = path.length > 1;
      containerClasses.toggle(OPEN_CLASS, visible);

      path.forEach(function(el) {
        breadcrumbs.appendChild(el);
      });
    }

    eventBus.on('root.set', function(event) {
      updateBreadcrumbs(event.element);
    });

  }

  DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ];


  // helpers //////////

  /**
   * Returns the parents for the element using the business object chain,
   * starting with the root element.
   *
   * @param {djs.model.Shape} child
   *
   * @returns {Array<djs.model.Shape>} parents
   */
  function getBoParentChain(child) {
    var bo = getBusinessObject(child);

    var parents = [];

    for (var element = bo; element; element = element.$parent) {
      if (is$1(element, 'bpmn:SubProcess') || is$1(element, 'bpmn:Process')) {
        parents.push(element);
      }
    }

    return parents.reverse();
  }

  /**
   * Move collapsed subprocesses into view when drilling down.
   *
   * Zoom and scroll are saved in a session.
   *
   * @param {eventBus} eventBus
   * @param {canvas} canvas
   */
  function DrilldownCentering(eventBus, canvas) {

    var currentRoot = null;
    var positionMap = new Map();

    eventBus.on('root.set', function(event) {
      var newRoot = event.element;
      var currentViewbox = canvas.viewbox();
      var storedViewbox = positionMap.get(newRoot);

      positionMap.set(currentRoot, {
        x: currentViewbox.x,
        y: currentViewbox.y,
        zoom: currentViewbox.scale
      });

      currentRoot = newRoot;

      // current root was replaced with a collaboration, we don't update the viewbox
      if (is$1(newRoot, 'bpmn:Collaboration') && !storedViewbox) {
        return;
      }

      storedViewbox = storedViewbox || { x: 0, y: 0, zoom: 1 };

      var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale,
          dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale;

      if (dx !== 0 || dy !== 0) {
        canvas.scroll({
          dx: dx,
          dy: dy
        });
      }

      if (storedViewbox.zoom !== currentViewbox.scale) {
        canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 });
      }
    });

    eventBus.on('diagram.clear', function() {
      positionMap.clear();
      currentRoot = null;
    });

  }

  DrilldownCentering.$inject = [ 'eventBus', 'canvas' ];


  /**
   * ES5 Map implementation. Works.
   */
  function Map() {

    this._entries = [];

    this.set = function(key, value) {

      var found = false;

      for (var k in this._entries) {
        if (this._entries[k][0] === key) {
          this._entries[k][1] = value;

          found = true;

          break;
        }
      }

      if (!found) {
        this._entries.push([ key, value ]);
      }
    };

    this.get = function(key) {

      for (var k in this._entries) {
        if (this._entries[k][0] === key) {
          return this._entries[k][1];
        }
      }

      return null;
    };

    this.clear = function() {
      this._entries.length = 0;
    };

    this.remove = function(key) {

      var idx = -1;

      for (var k in this._entries) {
        if (this._entries[k][0] === key) {
          idx = k;

          break;
        }
      }

      if (idx !== -1) {
        this._entries.splice(idx, 1);
      }
    };
  }

  var DEFAULT_POSITION$1 = {
    x: 180,
    y: 160
  };

  /**
   * Hook into `import.render.start` and create new planes for diagrams with
   * collapsed subprocesses and all dis on the same plane.
   *
   * @param {eventBus} eventBus
   * @param {moddle} moddle
   */
  function SubprocessCompatibility(eventBus, moddle) {
    this._eventBus = eventBus;
    this._moddle = moddle;

    var self = this;

    eventBus.on('import.render.start', 1500, function(e, context) {
      self.handleImport(context.definitions);
    });
  }

  SubprocessCompatibility.prototype.handleImport = function(definitions) {
    if (!definitions.diagrams) {
      return;
    }

    var self = this;
    this._definitions = definitions;
    this._processToDiagramMap = {};

    definitions.diagrams.forEach(function(diagram) {
      if (!diagram.plane || !diagram.plane.bpmnElement) {
        return;
      }

      self._processToDiagramMap[diagram.plane.bpmnElement.id] = diagram;
    });

    var newDiagrams = [];
    definitions.diagrams.forEach(function(diagram) {
      var createdDiagrams = self.createNewDiagrams(diagram.plane);
      Array.prototype.push.apply(newDiagrams, createdDiagrams);
    });

    newDiagrams.forEach(function(diagram) {
      self.movePlaneElementsToOrigin(diagram.plane);
    });
  };


  /**
   * Moves all DI elements from collapsed subprocesses to a new plane.
   *
   * @param {Object} plane
   * @return {Array} new diagrams created for the collapsed subprocesses
   */
  SubprocessCompatibility.prototype.createNewDiagrams = function(plane) {
    var self = this;

    var collapsedElements = [];
    var elementsToMove = [];

    plane.get('planeElement').forEach(function(diElement) {
      var bo = diElement.bpmnElement;

      if (!bo) {
        return;
      }

      var parent = bo.$parent;

      if (is$1(bo, 'bpmn:SubProcess') && !diElement.isExpanded) {
        collapsedElements.push(bo);
      }

      if (shouldMoveToPlane(bo, plane)) {

        // don't change the array while we iterate over it
        elementsToMove.push({ diElement: diElement, parent: parent });
      }
    });

    var newDiagrams = [];

    // create new planes for all collapsed subprocesses, even when they are empty
    collapsedElements.forEach(function(element) {
      if (!self._processToDiagramMap[element.id]) {
        var diagram = self.createDiagram(element);
        self._processToDiagramMap[element.id] = diagram;
        newDiagrams.push(diagram);
      }
    });

    elementsToMove.forEach(function(element) {
      var diElement = element.diElement;
      var parent = element.parent;

      // parent is expanded, get nearest collapsed parent
      while (parent && collapsedElements.indexOf(parent) === -1) {
        parent = parent.$parent;
      }

      // false positive, all parents are expanded
      if (!parent) {
        return;
      }

      var diagram = self._processToDiagramMap[parent.id];
      self.moveToDiPlane(diElement, diagram.plane);
    });

    return newDiagrams;
  };

  SubprocessCompatibility.prototype.movePlaneElementsToOrigin = function(plane) {
    var elements = plane.get('planeElement');

    // get bounding box of all elements
    var planeBounds = getPlaneBounds(plane);

    var offset = {
      x: planeBounds.x - DEFAULT_POSITION$1.x,
      y: planeBounds.y - DEFAULT_POSITION$1.y
    };

    elements.forEach(function(diElement) {
      if (diElement.waypoint) {
        diElement.waypoint.forEach(function(waypoint) {
          waypoint.x = waypoint.x - offset.x;
          waypoint.y = waypoint.y - offset.y;
        });
      } else if (diElement.bounds) {
        diElement.bounds.x = diElement.bounds.x - offset.x;
        diElement.bounds.y = diElement.bounds.y - offset.y;
      }
    });
  };


  SubprocessCompatibility.prototype.moveToDiPlane = function(diElement, newPlane) {
    var containingDiagram = findRootDiagram(diElement);

    // remove DI from old Plane and add it to the new one
    var parentPlaneElement = containingDiagram.plane.get('planeElement');
    parentPlaneElement.splice(parentPlaneElement.indexOf(diElement), 1);
    newPlane.get('planeElement').push(diElement);
  };


  SubprocessCompatibility.prototype.createDiagram = function(bo) {
    var plane = this._moddle.create('bpmndi:BPMNPlane', { bpmnElement: bo });
    var diagram = this._moddle.create('bpmndi:BPMNDiagram', {
      plane: plane
    });
    plane.$parent = diagram;
    plane.bpmnElement = bo;
    diagram.$parent = this._definitions;
    this._definitions.diagrams.push(diagram);
    return diagram;
  };

  SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ];


  // helpers //////////////////////////

  function findRootDiagram(element) {
    if (is$1(element, 'bpmndi:BPMNDiagram')) {
      return element;
    } else {
      return findRootDiagram(element.$parent);
    }
  }

  function getPlaneBounds(plane) {
    var planeTrbl = {
      top: Infinity,
      right: -Infinity,
      bottom: -Infinity,
      left: Infinity
    };

    plane.planeElement.forEach(function(element) {
      if (!element.bounds) {
        return;
      }

      var trbl = asTRBL(element.bounds);

      planeTrbl.top = Math.min(trbl.top, planeTrbl.top);
      planeTrbl.left = Math.min(trbl.left, planeTrbl.left);
    });

    return asBounds(planeTrbl);
  }

  function shouldMoveToPlane(bo, plane) {
    var parent = bo.$parent;

    // don't move elements that are already on the plane
    if (!is$1(parent, 'bpmn:SubProcess') || parent === plane.bpmnElement) {
      return false;
    }

    // dataAssociations are children of the subprocess but rendered on process level
    // cf. https://github.com/bpmn-io/bpmn-js/issues/1619
    if (isAny(bo, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) {
      return false;
    }

    return true;
  }

  var LOW_PRIORITY$n = 250;
  var ARROW_DOWN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.81801948,3.50735931 L10.4996894,9.1896894 L10.5,4 L12,4 L12,12 L4,12 L4,10.5 L9.6896894,10.4996894 L3.75735931,4.56801948 C3.46446609,4.27512627 3.46446609,3.80025253 3.75735931,3.50735931 C4.05025253,3.21446609 4.52512627,3.21446609 4.81801948,3.50735931 Z"/></svg>';

  var EMPTY_MARKER = 'bjs-drilldown-empty';

  function DrilldownOverlayBehavior(
      canvas, eventBus, elementRegistry, overlays
  ) {
    CommandInterceptor.call(this, eventBus);

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._elementRegistry = elementRegistry;
    this._overlays = overlays;

    var self = this;

    this.executed('shape.toggleCollapse', LOW_PRIORITY$n, function(context) {
      var shape = context.shape;

      // Add overlay to the collapsed shape
      if (self.canDrillDown(shape)) {
        self.addOverlay(shape);
      } else {
        self.removeOverlay(shape);
      }
    }, true);


    this.reverted('shape.toggleCollapse', LOW_PRIORITY$n, function(context) {
      var shape = context.shape;

      // Add overlay to the collapsed shape
      if (self.canDrillDown(shape)) {
        self.addOverlay(shape);
      } else {
        self.removeOverlay(shape);
      }
    }, true);


    this.executed([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$n,
      function(context) {
        var oldParent = context.oldParent,
            newParent = context.newParent || context.parent,
            shape = context.shape;

        // Add overlay to the collapsed shape
        if (self.canDrillDown(shape)) {
          self.addOverlay(shape);
        }

        self.updateDrilldownOverlay(oldParent);
        self.updateDrilldownOverlay(newParent);
        self.updateDrilldownOverlay(shape);
      }, true);


    this.reverted([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$n,
      function(context) {
        var oldParent = context.oldParent,
            newParent = context.newParent || context.parent,
            shape = context.shape;

        // Add overlay to the collapsed shape
        if (self.canDrillDown(shape)) {
          self.addOverlay(shape);
        }

        self.updateDrilldownOverlay(oldParent);
        self.updateDrilldownOverlay(newParent);
        self.updateDrilldownOverlay(shape);
      }, true);


    eventBus.on('import.render.complete', function() {
      elementRegistry.filter(function(e) {
        return self.canDrillDown(e);
      }).map(function(el) {
        self.addOverlay(el);
      });
    });

  }

  e(DrilldownOverlayBehavior, CommandInterceptor);

  DrilldownOverlayBehavior.prototype.updateDrilldownOverlay = function(shape) {
    var canvas = this._canvas;

    if (!shape) {
      return;
    }

    var root = canvas.findRoot(shape);
    if (root) {
      this.updateOverlayVisibility(root);
    }
  };


  DrilldownOverlayBehavior.prototype.canDrillDown = function(element) {
    var canvas = this._canvas;
    return is$1(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element));
  };

  /**
   * Updates visibility of the drilldown overlay. If the plane has no elements,
   * the drilldown will be only shown when the element is selected.
   *
   * @param {djs.model.Shape|djs.model.Root} element collapsed shape or root element
   */
  DrilldownOverlayBehavior.prototype.updateOverlayVisibility = function(element) {
    var overlays = this._overlays;

    var bo = element.businessObject;

    var overlay = overlays.get({ element: bo.id, type: 'drilldown' })[0];

    if (!overlay) {
      return;
    }

    var hasContent = bo && bo.flowElements && bo.flowElements.length;
    classes$1(overlay.html).toggle(EMPTY_MARKER, !hasContent);
  };

  /**
   * Attaches a drilldown button to the given element. We assume that the plane has
   * the same id as the element.
   *
   * @param {djs.model.Shape} element collapsed shape
   */
  DrilldownOverlayBehavior.prototype.addOverlay = function(element) {
    var canvas = this._canvas;
    var overlays = this._overlays;

    var existingOverlays = overlays.get({ element: element, type: 'drilldown' });
    if (existingOverlays.length) {
      this.removeOverlay(element);
    }

    var button = domify('<button class="bjs-drilldown">' + ARROW_DOWN_SVG + '</button>');

    button.addEventListener('click', function() {
      canvas.setRootElement(canvas.findRoot(getPlaneIdFromShape(element)));
    });

    overlays.add(element, 'drilldown', {
      position: {
        bottom: -7,
        right: -8
      },
      html: button
    });

    this.updateOverlayVisibility(element);
  };

  DrilldownOverlayBehavior.prototype.removeOverlay = function(element) {
    var overlays = this._overlays;

    overlays.remove({
      element: element,
      type: 'drilldown'
    });
  };

  DrilldownOverlayBehavior.$inject = [
    'canvas',
    'eventBus',
    'elementRegistry',
    'overlays'
  ];

  var DrilldownModdule = {
    __depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ],
    __init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ],
    drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ],
    drilldownCentering: [ 'type', DrilldownCentering ],
    drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ],
    subprocessCompatibility: [ 'type', SubprocessCompatibility ]
  };

  /**
   * A viewer for BPMN 2.0 diagrams.
   *
   * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
   * additional features.
   *
   *
   * ## Extending the Viewer
   *
   * In order to extend the viewer pass extension modules to bootstrap via the
   * `additionalModules` option. An extension module is an object that exposes
   * named services.
   *
   * The following example depicts the integration of a simple
   * logging component that integrates with interaction events:
   *
   *
   * ```javascript
   *
   * // logging component
   * function InteractionLogger(eventBus) {
   *   eventBus.on('element.hover', function(event) {
   *     console.log()
   *   })
   * }
   *
   * InteractionLogger.$inject = [ 'eventBus' ]; // minification save
   *
   * // extension module
   * var extensionModule = {
   *   __init__: [ 'interactionLogger' ],
   *   interactionLogger: [ 'type', InteractionLogger ]
   * };
   *
   * // extend the viewer
   * var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
   * bpmnViewer.importXML(...);
   * ```
   *
   * @param {Object} [options] configuration options to pass to the viewer
   * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
   * @param {string|number} [options.width] the width of the viewer
   * @param {string|number} [options.height] the height of the viewer
   * @param {Object} [options.moddleExtensions] extension packages to provide
   * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
   * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
   */
  function Viewer(options) {
    BaseViewer.call(this, options);
  }

  e(Viewer, BaseViewer);

  // modules the viewer is composed of
  Viewer.prototype._modules = [
    CoreModule,
    translate,
    SelectionModule,
    OverlaysModule,
    DrilldownModdule
  ];

  // default moddle extensions the viewer is composed of
  Viewer.prototype._moddleExtensions = {};

  var KEYCODE_C = 67;
  var KEYCODE_V = 86;
  var KEYCODE_Y = 89;
  var KEYCODE_Z = 90;

  var KEYS_COPY = [ 'c', 'C', KEYCODE_C ];
  var KEYS_PASTE = [ 'v', 'V', KEYCODE_V ];
  var KEYS_REDO = [ 'y', 'Y', KEYCODE_Y ];
  var KEYS_UNDO = [ 'z', 'Z', KEYCODE_Z ];

  /**
   * Returns true if event was triggered with any modifier
   * @param {KeyboardEvent} event
   */
  function hasModifier(event) {
    return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey);
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isCmd(event) {

    // ensure we don't react to AltGr
    // (mapped to CTRL + ALT)
    if (event.altKey) {
      return false;
    }

    return event.ctrlKey || event.metaKey;
  }

  /**
   * Checks if key pressed is one of provided keys.
   *
   * @param {string|Array<string>} keys
   * @param {KeyboardEvent} event
   */
  function isKey(keys, event) {
    keys = isArray$3(keys) ? keys : [ keys ];

    return keys.indexOf(event.key) !== -1 || keys.indexOf(event.keyCode) !== -1;
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isShift(event) {
    return event.shiftKey;
  }

  function isCopy(event) {
    return isCmd(event) && isKey(KEYS_COPY, event);
  }

  function isPaste(event) {
    return isCmd(event) && isKey(KEYS_PASTE, event);
  }

  function isUndo(event) {
    return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
  }

  function isRedo(event) {
    return isCmd(event) && (
      isKey(KEYS_REDO, event) || (
        isKey(KEYS_UNDO, event) && isShift(event)
      )
    );
  }

  var KEYDOWN_EVENT = 'keyboard.keydown',
      KEYUP_EVENT = 'keyboard.keyup';

  var HANDLE_MODIFIER_ATTRIBUTE = 'input-handle-modified-keys';

  var DEFAULT_PRIORITY$3 = 1000;

  /**
   * A keyboard abstraction that may be activated and
   * deactivated by users at will, consuming key events
   * and triggering diagram actions.
   *
   * For keys pressed down, keyboard fires `keyboard.keydown` event.
   * The event context contains one field which is `KeyboardEvent` event.
   *
   * The implementation fires the following key events that allow
   * other components to hook into key handling:
   *
   *  - keyboard.bind
   *  - keyboard.unbind
   *  - keyboard.init
   *  - keyboard.destroy
   *
   * All events contain one field which is node.
   *
   * A default binding for the keyboard may be specified via the
   * `keyboard.bindTo` configuration option.
   *
   * @param {Config} config
   * @param {EventBus} eventBus
   */
  function Keyboard(config, eventBus) {
    var self = this;

    this._config = config || {};
    this._eventBus = eventBus;

    this._keydownHandler = this._keydownHandler.bind(this);
    this._keyupHandler = this._keyupHandler.bind(this);

    // properly clean dom registrations
    eventBus.on('diagram.destroy', function() {
      self._fire('destroy');

      self.unbind();
    });

    eventBus.on('diagram.init', function() {
      self._fire('init');
    });

    eventBus.on('attach', function() {
      if (config && config.bindTo) {
        self.bind(config.bindTo);
      }
    });

    eventBus.on('detach', function() {
      self.unbind();
    });
  }

  Keyboard.$inject = [
    'config.keyboard',
    'eventBus'
  ];

  Keyboard.prototype._keydownHandler = function(event) {
    this._keyHandler(event, KEYDOWN_EVENT);
  };

  Keyboard.prototype._keyupHandler = function(event) {
    this._keyHandler(event, KEYUP_EVENT);
  };

  Keyboard.prototype._keyHandler = function(event, type) {
    var eventBusResult;

    if (this._isEventIgnored(event)) {
      return;
    }

    var context = {
      keyEvent: event
    };

    eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context);

    if (eventBusResult) {
      event.preventDefault();
    }
  };

  Keyboard.prototype._isEventIgnored = function(event) {
    return isInput(event.target) && this._isModifiedKeyIgnored(event);
  };

  Keyboard.prototype._isModifiedKeyIgnored = function(event) {
    if (!isCmd(event)) {
      return true;
    }

    var allowedModifiers = this._getAllowedModifiers(event.target);
    return allowedModifiers.indexOf(event.key) === -1;
  };

  Keyboard.prototype._getAllowedModifiers = function(element) {
    var modifierContainer = closest(element, '[' + HANDLE_MODIFIER_ATTRIBUTE + ']', true);

    if (!modifierContainer || (this._node && !this._node.contains(modifierContainer))) {
      return [];
    }

    return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(',');
  };

  Keyboard.prototype.bind = function(node) {

    // make sure that the keyboard is only bound once to the DOM
    this.unbind();

    this._node = node;

    // bind key events
    componentEvent.bind(node, 'keydown', this._keydownHandler, true);
    componentEvent.bind(node, 'keyup', this._keyupHandler, true);

    this._fire('bind');
  };

  Keyboard.prototype.getBinding = function() {
    return this._node;
  };

  Keyboard.prototype.unbind = function() {
    var node = this._node;

    if (node) {
      this._fire('unbind');

      // unbind key events
      componentEvent.unbind(node, 'keydown', this._keydownHandler, true);
      componentEvent.unbind(node, 'keyup', this._keyupHandler, true);
    }

    this._node = null;
  };

  Keyboard.prototype._fire = function(event) {
    this._eventBus.fire('keyboard.' + event, { node: this._node });
  };

  /**
   * Add a listener function that is notified with `KeyboardEvent` whenever
   * the keyboard is bound and the user presses a key. If no priority is
   * provided, the default value of 1000 is used.
   *
   * @param {number} [priority]
   * @param {Function} listener
   * @param {string} type
   */
  Keyboard.prototype.addListener = function(priority, listener, type) {
    if (isFunction(priority)) {
      type = listener;
      listener = priority;
      priority = DEFAULT_PRIORITY$3;
    }

    this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
  };

  Keyboard.prototype.removeListener = function(listener, type) {
    this._eventBus.off(type || KEYDOWN_EVENT, listener);
  };

  Keyboard.prototype.hasModifier = hasModifier;
  Keyboard.prototype.isCmd = isCmd;
  Keyboard.prototype.isShift = isShift;
  Keyboard.prototype.isKey = isKey;



  // helpers ///////

  function isInput(target) {
    return target && (matchesSelector(target, 'input, textarea') || target.contentEditable === 'true');
  }

  var LOW_PRIORITY$m = 500;


  /**
   * Adds default keyboard bindings.
   *
   * This does not pull in any features will bind only actions that
   * have previously been registered against the editorActions component.
   *
   * @param {EventBus} eventBus
   * @param {Keyboard} keyboard
   */
  function KeyboardBindings(eventBus, keyboard) {

    var self = this;

    eventBus.on('editorActions.init', LOW_PRIORITY$m, function(event) {

      var editorActions = event.editorActions;

      self.registerBindings(keyboard, editorActions);
    });
  }

  KeyboardBindings.$inject = [
    'eventBus',
    'keyboard'
  ];


  /**
   * Register available keyboard bindings.
   *
   * @param {Keyboard} keyboard
   * @param {EditorActions} editorActions
   */
  KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {

    /**
     * Add keyboard binding if respective editor action
     * is registered.
     *
     * @param {string} action name
     * @param {Function} fn that implements the key binding
     */
    function addListener(action, fn) {

      if (editorActions.isRegistered(action)) {
        keyboard.addListener(fn);
      }
    }


    // undo
    // (CTRL|CMD) + Z
    addListener('undo', function(context) {

      var event = context.keyEvent;

      if (isUndo(event)) {
        editorActions.trigger('undo');

        return true;
      }
    });

    // redo
    // CTRL + Y
    // CMD + SHIFT + Z
    addListener('redo', function(context) {

      var event = context.keyEvent;

      if (isRedo(event)) {
        editorActions.trigger('redo');

        return true;
      }
    });

    // copy
    // CTRL/CMD + C
    addListener('copy', function(context) {

      var event = context.keyEvent;

      if (isCopy(event)) {
        editorActions.trigger('copy');

        return true;
      }
    });

    // paste
    // CTRL/CMD + V
    addListener('paste', function(context) {

      var event = context.keyEvent;

      if (isPaste(event)) {
        editorActions.trigger('paste');

        return true;
      }
    });

    // zoom in one step
    // CTRL/CMD + +
    addListener('stepZoom', function(context) {

      var event = context.keyEvent;

      // quirk: it has to be triggered by `=` as well to work on international keyboard layout
      // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
      if (isKey([ '+', 'Add', '=' ], event) && isCmd(event)) {
        editorActions.trigger('stepZoom', { value: 1 });

        return true;
      }
    });

    // zoom out one step
    // CTRL + -
    addListener('stepZoom', function(context) {

      var event = context.keyEvent;

      if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) {
        editorActions.trigger('stepZoom', { value: -1 });

        return true;
      }
    });

    // zoom to the default level
    // CTRL + 0
    addListener('zoom', function(context) {

      var event = context.keyEvent;

      if (isKey('0', event) && isCmd(event)) {
        editorActions.trigger('zoom', { value: 1 });

        return true;
      }
    });

    // delete selected element
    // DEL
    addListener('removeSelection', function(context) {

      var event = context.keyEvent;

      if (isKey([ 'Backspace', 'Delete', 'Del' ], event)) {
        editorActions.trigger('removeSelection');

        return true;
      }
    });
  };

  var KeyboardModule$1 = {
    __init__: [ 'keyboard', 'keyboardBindings' ],
    keyboard: [ 'type', Keyboard ],
    keyboardBindings: [ 'type', KeyboardBindings ]
  };

  var DEFAULT_CONFIG$1 = {
    moveSpeed: 50,
    moveSpeedAccelerated: 200
  };


  /**
   * A feature that allows users to move the canvas using the keyboard.
   *
   * @param {Object} config
   * @param {number} [config.moveSpeed=50]
   * @param {number} [config.moveSpeedAccelerated=200]
   * @param {Keyboard} keyboard
   * @param {Canvas} canvas
   */
  function KeyboardMove(
      config,
      keyboard,
      canvas
  ) {

    var self = this;

    this._config = assign({}, DEFAULT_CONFIG$1, config || {});

    keyboard.addListener(arrowsListener);


    function arrowsListener(context) {

      var event = context.keyEvent,
          config = self._config;

      if (!keyboard.isCmd(event)) {
        return;
      }

      if (keyboard.isKey([
        'ArrowLeft', 'Left',
        'ArrowUp', 'Up',
        'ArrowDown', 'Down',
        'ArrowRight', 'Right'
      ], event)) {

        var speed = (
          keyboard.isShift(event) ?
            config.moveSpeedAccelerated :
            config.moveSpeed
        );

        var direction;

        switch (event.key) {
        case 'ArrowLeft':
        case 'Left':
          direction = 'left';
          break;
        case 'ArrowUp':
        case 'Up':
          direction = 'up';
          break;
        case 'ArrowRight':
        case 'Right':
          direction = 'right';
          break;
        case 'ArrowDown':
        case 'Down':
          direction = 'down';
          break;
        }

        self.moveCanvas({
          speed: speed,
          direction: direction
        });

        return true;
      }
    }

    this.moveCanvas = function(opts) {

      var dx = 0,
          dy = 0,
          speed = opts.speed;

      var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1);

      switch (opts.direction) {
      case 'left': // Left
        dx = actualSpeed;
        break;
      case 'up': // Up
        dy = actualSpeed;
        break;
      case 'right': // Right
        dx = -actualSpeed;
        break;
      case 'down': // Down
        dy = -actualSpeed;
        break;
      }

      canvas.scroll({
        dx: dx,
        dy: dy
      });
    };

  }


  KeyboardMove.$inject = [
    'config.keyboardMove',
    'keyboard',
    'canvas'
  ];

  var KeyboardMoveModule = {
    __depends__: [
      KeyboardModule$1
    ],
    __init__: [ 'keyboardMove' ],
    keyboardMove: [ 'type', KeyboardMove ]
  };

  var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/;


  function set(mode) {
    var classes = classes$1(document.body);

    classes.removeMatching(CURSOR_CLS_PATTERN);

    if (mode) {
      classes.add('djs-cursor-' + mode);
    }
  }

  function unset() {
    set(null);
  }

  var TRAP_PRIORITY = 5000;

  /**
   * Installs a click trap that prevents a ghost click following a dragging operation.
   *
   * @return {Function} a function to immediately remove the installed trap.
   */
  function install(eventBus, eventName) {

    eventName = eventName || 'element.click';

    function trap() {
      return false;
    }

    eventBus.once(eventName, TRAP_PRIORITY, trap);

    return function() {
      eventBus.off(eventName, trap);
    };
  }

  function center(bounds) {
    return {
      x: bounds.x + (bounds.width / 2),
      y: bounds.y + (bounds.height / 2)
    };
  }


  function delta(a, b) {
    return {
      x: a.x - b.x,
      y: a.y - b.y
    };
  }

  var THRESHOLD$1 = 15;


  /**
   * Move the canvas via mouse.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function MoveCanvas(eventBus, canvas) {

    var context;


    // listen for move on element mouse down;
    // allow others to hook into the event before us though
    // (dragging / element moving will do this)
    eventBus.on('element.mousedown', 500, function(e) {
      return handleStart(e.originalEvent);
    });


    function handleMove(event) {

      var start = context.start,
          button = context.button,
          position = toPoint(event),
          delta$1 = delta(position, start);

      if (!context.dragging && length(delta$1) > THRESHOLD$1) {
        context.dragging = true;

        if (button === 0) {
          install(eventBus);
        }

        set('grab');
      }

      if (context.dragging) {

        var lastPosition = context.last || context.start;

        delta$1 = delta(position, lastPosition);

        canvas.scroll({
          dx: delta$1.x,
          dy: delta$1.y
        });

        context.last = position;
      }

      // prevent select
      event.preventDefault();
    }


    function handleEnd(event) {
      componentEvent.unbind(document, 'mousemove', handleMove);
      componentEvent.unbind(document, 'mouseup', handleEnd);

      context = null;

      unset();
    }

    function handleStart(event) {

      // event is already handled by '.djs-draggable'
      if (closest(event.target, '.djs-draggable')) {
        return;
      }

      var button = event.button;

      // reject right mouse button or modifier key
      if (button >= 2 || event.ctrlKey || event.shiftKey || event.altKey) {
        return;
      }

      context = {
        button: button,
        start: toPoint(event)
      };

      componentEvent.bind(document, 'mousemove', handleMove);
      componentEvent.bind(document, 'mouseup', handleEnd);

      // we've handled the event
      return true;
    }

    this.isActive = function() {
      return !!context;
    };

  }


  MoveCanvas.$inject = [
    'eventBus',
    'canvas'
  ];



  // helpers ///////

  function length(point) {
    return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
  }

  var MoveCanvasModule = {
    __init__: [ 'moveCanvas' ],
    moveCanvas: [ 'type', MoveCanvas ]
  };

  /**
   * Get the logarithm of x with base 10
   * @param  {Integer} value
   */
  function log10(x) {
    return Math.log(x) / Math.log(10);
  }

  /**
   * Get step size for given range and number of steps.
   *
   * @param {Object} range
   * @param {number} range.min
   * @param {number} range.max
   */
  function getStepSize(range, steps) {

    var minLinearRange = log10(range.min),
        maxLinearRange = log10(range.max);

    var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);

    return absoluteLinearRange / steps;
  }

  function cap(range, scale) {
    return Math.max(range.min, Math.min(range.max, scale));
  }

  var sign = Math.sign || function(n) {
    return n >= 0 ? 1 : -1;
  };

  var RANGE = { min: 0.2, max: 4 },
      NUM_STEPS = 10;

  var DELTA_THRESHOLD = 0.1;

  var DEFAULT_SCALE = 0.75;

  /**
   * An implementation of zooming and scrolling within the
   * {@link Canvas} via the mouse wheel.
   *
   * Mouse wheel zooming / scrolling may be disabled using
   * the {@link toggle(enabled)} method.
   *
   * @param {Object} [config]
   * @param {boolean} [config.enabled=true] default enabled state
   * @param {number} [config.scale=.75] scroll sensivity
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function ZoomScroll(config, eventBus, canvas) {

    config = config || {};

    this._enabled = false;

    this._canvas = canvas;
    this._container = canvas._container;

    this._handleWheel = bind(this._handleWheel, this);

    this._totalDelta = 0;
    this._scale = config.scale || DEFAULT_SCALE;

    var self = this;

    eventBus.on('canvas.init', function(e) {
      self._init(config.enabled !== false);
    });
  }

  ZoomScroll.$inject = [
    'config.zoomScroll',
    'eventBus',
    'canvas'
  ];

  ZoomScroll.prototype.scroll = function scroll(delta) {
    this._canvas.scroll(delta);
  };


  ZoomScroll.prototype.reset = function reset() {
    this._canvas.zoom('fit-viewport');
  };

  /**
   * Zoom depending on delta.
   *
   * @param {number} delta
   * @param {Object} position
   */
  ZoomScroll.prototype.zoom = function zoom(delta, position) {

    // zoom with half the step size of stepZoom
    var stepSize = getStepSize(RANGE, NUM_STEPS * 2);

    // add until threshold reached
    this._totalDelta += delta;

    if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) {
      this._zoom(delta, position, stepSize);

      // reset
      this._totalDelta = 0;
    }
  };


  ZoomScroll.prototype._handleWheel = function handleWheel(event) {

    // event is already handled by '.djs-scrollable'
    if (closest(event.target, '.djs-scrollable', true)) {
      return;
    }

    var element = this._container;

    event.preventDefault();

    // pinch to zoom is mapped to wheel + ctrlKey = true
    // in modern browsers (!)

    var isZoom = event.ctrlKey;

    var isHorizontalScroll = event.shiftKey;

    var factor = -1 * this._scale,
        delta;

    if (isZoom) {
      factor *= event.deltaMode === 0 ? 0.020 : 0.32;
    } else {
      factor *= event.deltaMode === 0 ? 1.0 : 16.0;
    }

    if (isZoom) {
      var elementRect = element.getBoundingClientRect();

      var offset = {
        x: event.clientX - elementRect.left,
        y: event.clientY - elementRect.top
      };

      delta = (
        Math.sqrt(
          Math.pow(event.deltaY, 2) +
          Math.pow(event.deltaX, 2)
        ) * sign(event.deltaY) * factor
      );

      // zoom in relative to diagram {x,y} coordinates
      this.zoom(delta, offset);
    } else {

      if (isHorizontalScroll) {
        delta = {
          dx: factor * event.deltaY,
          dy: 0
        };
      } else {
        delta = {
          dx: factor * event.deltaX,
          dy: factor * event.deltaY
        };
      }

      this.scroll(delta);
    }
  };

  /**
   * Zoom with fixed step size.
   *
   * @param {number} delta - Zoom delta (1 for zooming in, -1 for out).
   * @param {Object} position
   */
  ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) {

    var stepSize = getStepSize(RANGE, NUM_STEPS);

    this._zoom(delta, position, stepSize);
  };


  /**
   * Zoom in/out given a step size.
   *
   * @param {number} delta
   * @param {Object} position
   * @param {number} stepSize
   */
  ZoomScroll.prototype._zoom = function(delta, position, stepSize) {
    var canvas = this._canvas;

    var direction = delta > 0 ? 1 : -1;

    var currentLinearZoomLevel = log10(canvas.zoom());

    // snap to a proximate zoom step
    var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize;

    // increase or decrease one zoom step in the given direction
    newLinearZoomLevel += stepSize * direction;

    // calculate the absolute logarithmic zoom level based on the linear zoom level
    // (e.g. 2 for an absolute x2 zoom)
    var newLogZoomLevel = Math.pow(10, newLinearZoomLevel);

    canvas.zoom(cap(RANGE, newLogZoomLevel), position);
  };


  /**
   * Toggle the zoom scroll ability via mouse wheel.
   *
   * @param  {boolean} [newEnabled] new enabled state
   */
  ZoomScroll.prototype.toggle = function toggle(newEnabled) {

    var element = this._container;
    var handleWheel = this._handleWheel;

    var oldEnabled = this._enabled;

    if (typeof newEnabled === 'undefined') {
      newEnabled = !oldEnabled;
    }

    // only react on actual changes
    if (oldEnabled !== newEnabled) {

      // add or remove wheel listener based on
      // changed enabled state
      componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false);
    }

    this._enabled = newEnabled;

    return newEnabled;
  };


  ZoomScroll.prototype._init = function(newEnabled) {
    this.toggle(newEnabled);
  };

  var ZoomScrollModule = {
    __init__: [ 'zoomScroll' ],
    zoomScroll: [ 'type', ZoomScroll ]
  };

  /**
   * A viewer that includes mouse navigation facilities
   *
   * @param {Object} options
   */
  function NavigatedViewer(options) {
    Viewer.call(this, options);
  }

  e(NavigatedViewer, Viewer);


  NavigatedViewer.prototype._navigationModules = [
    KeyboardMoveModule,
    MoveCanvasModule,
    ZoomScrollModule
  ];

  NavigatedViewer.prototype._modules = [].concat(
    Viewer.prototype._modules,
    NavigatedViewer.prototype._navigationModules
  );

  var hammer = {exports: {}};

  /*! Hammer.JS - v2.0.7 - 2016-04-22
   * http://hammerjs.github.io/
   *
   * Copyright (c) 2016 Jorik Tangelder;
   * Licensed under the MIT license */

  (function (module) {
  	(function(window, document, exportName, undefined$1) {

  	var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
  	var TEST_ELEMENT = document.createElement('div');

  	var TYPE_FUNCTION = 'function';

  	var round = Math.round;
  	var abs = Math.abs;
  	var now = Date.now;

  	/**
  	 * set a timeout with a given scope
  	 * @param {Function} fn
  	 * @param {Number} timeout
  	 * @param {Object} context
  	 * @returns {number}
  	 */
  	function setTimeoutContext(fn, timeout, context) {
  	    return setTimeout(bindFn(fn, context), timeout);
  	}

  	/**
  	 * if the argument is an array, we want to execute the fn on each entry
  	 * if it aint an array we don't want to do a thing.
  	 * this is used by all the methods that accept a single and array argument.
  	 * @param {*|Array} arg
  	 * @param {String} fn
  	 * @param {Object} [context]
  	 * @returns {Boolean}
  	 */
  	function invokeArrayArg(arg, fn, context) {
  	    if (Array.isArray(arg)) {
  	        each(arg, context[fn], context);
  	        return true;
  	    }
  	    return false;
  	}

  	/**
  	 * walk objects and arrays
  	 * @param {Object} obj
  	 * @param {Function} iterator
  	 * @param {Object} context
  	 */
  	function each(obj, iterator, context) {
  	    var i;

  	    if (!obj) {
  	        return;
  	    }

  	    if (obj.forEach) {
  	        obj.forEach(iterator, context);
  	    } else if (obj.length !== undefined$1) {
  	        i = 0;
  	        while (i < obj.length) {
  	            iterator.call(context, obj[i], i, obj);
  	            i++;
  	        }
  	    } else {
  	        for (i in obj) {
  	            obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
  	        }
  	    }
  	}

  	/**
  	 * wrap a method with a deprecation warning and stack trace
  	 * @param {Function} method
  	 * @param {String} name
  	 * @param {String} message
  	 * @returns {Function} A new function wrapping the supplied method.
  	 */
  	function deprecate(method, name, message) {
  	    var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
  	    return function() {
  	        var e = new Error('get-stack-trace');
  	        var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
  	            .replace(/^\s+at\s+/gm, '')
  	            .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';

  	        var log = window.console && (window.console.warn || window.console.log);
  	        if (log) {
  	            log.call(window.console, deprecationMessage, stack);
  	        }
  	        return method.apply(this, arguments);
  	    };
  	}

  	/**
  	 * extend object.
  	 * means that properties in dest will be overwritten by the ones in src.
  	 * @param {Object} target
  	 * @param {...Object} objects_to_assign
  	 * @returns {Object} target
  	 */
  	var assign;
  	if (typeof Object.assign !== 'function') {
  	    assign = function assign(target) {
  	        if (target === undefined$1 || target === null) {
  	            throw new TypeError('Cannot convert undefined or null to object');
  	        }

  	        var output = Object(target);
  	        for (var index = 1; index < arguments.length; index++) {
  	            var source = arguments[index];
  	            if (source !== undefined$1 && source !== null) {
  	                for (var nextKey in source) {
  	                    if (source.hasOwnProperty(nextKey)) {
  	                        output[nextKey] = source[nextKey];
  	                    }
  	                }
  	            }
  	        }
  	        return output;
  	    };
  	} else {
  	    assign = Object.assign;
  	}

  	/**
  	 * extend object.
  	 * means that properties in dest will be overwritten by the ones in src.
  	 * @param {Object} dest
  	 * @param {Object} src
  	 * @param {Boolean} [merge=false]
  	 * @returns {Object} dest
  	 */
  	var extend = deprecate(function extend(dest, src, merge) {
  	    var keys = Object.keys(src);
  	    var i = 0;
  	    while (i < keys.length) {
  	        if (!merge || (merge && dest[keys[i]] === undefined$1)) {
  	            dest[keys[i]] = src[keys[i]];
  	        }
  	        i++;
  	    }
  	    return dest;
  	}, 'extend', 'Use `assign`.');

  	/**
  	 * merge the values from src in the dest.
  	 * means that properties that exist in dest will not be overwritten by src
  	 * @param {Object} dest
  	 * @param {Object} src
  	 * @returns {Object} dest
  	 */
  	var merge = deprecate(function merge(dest, src) {
  	    return extend(dest, src, true);
  	}, 'merge', 'Use `assign`.');

  	/**
  	 * simple class inheritance
  	 * @param {Function} child
  	 * @param {Function} base
  	 * @param {Object} [properties]
  	 */
  	function inherit(child, base, properties) {
  	    var baseP = base.prototype,
  	        childP;

  	    childP = child.prototype = Object.create(baseP);
  	    childP.constructor = child;
  	    childP._super = baseP;

  	    if (properties) {
  	        assign(childP, properties);
  	    }
  	}

  	/**
  	 * simple function bind
  	 * @param {Function} fn
  	 * @param {Object} context
  	 * @returns {Function}
  	 */
  	function bindFn(fn, context) {
  	    return function boundFn() {
  	        return fn.apply(context, arguments);
  	    };
  	}

  	/**
  	 * let a boolean value also be a function that must return a boolean
  	 * this first item in args will be used as the context
  	 * @param {Boolean|Function} val
  	 * @param {Array} [args]
  	 * @returns {Boolean}
  	 */
  	function boolOrFn(val, args) {
  	    if (typeof val == TYPE_FUNCTION) {
  	        return val.apply(args ? args[0] || undefined$1 : undefined$1, args);
  	    }
  	    return val;
  	}

  	/**
  	 * use the val2 when val1 is undefined
  	 * @param {*} val1
  	 * @param {*} val2
  	 * @returns {*}
  	 */
  	function ifUndefined(val1, val2) {
  	    return (val1 === undefined$1) ? val2 : val1;
  	}

  	/**
  	 * addEventListener with multiple events at once
  	 * @param {EventTarget} target
  	 * @param {String} types
  	 * @param {Function} handler
  	 */
  	function addEventListeners(target, types, handler) {
  	    each(splitStr(types), function(type) {
  	        target.addEventListener(type, handler, false);
  	    });
  	}

  	/**
  	 * removeEventListener with multiple events at once
  	 * @param {EventTarget} target
  	 * @param {String} types
  	 * @param {Function} handler
  	 */
  	function removeEventListeners(target, types, handler) {
  	    each(splitStr(types), function(type) {
  	        target.removeEventListener(type, handler, false);
  	    });
  	}

  	/**
  	 * find if a node is in the given parent
  	 * @method hasParent
  	 * @param {HTMLElement} node
  	 * @param {HTMLElement} parent
  	 * @return {Boolean} found
  	 */
  	function hasParent(node, parent) {
  	    while (node) {
  	        if (node == parent) {
  	            return true;
  	        }
  	        node = node.parentNode;
  	    }
  	    return false;
  	}

  	/**
  	 * small indexOf wrapper
  	 * @param {String} str
  	 * @param {String} find
  	 * @returns {Boolean} found
  	 */
  	function inStr(str, find) {
  	    return str.indexOf(find) > -1;
  	}

  	/**
  	 * split string on whitespace
  	 * @param {String} str
  	 * @returns {Array} words
  	 */
  	function splitStr(str) {
  	    return str.trim().split(/\s+/g);
  	}

  	/**
  	 * find if a array contains the object using indexOf or a simple polyFill
  	 * @param {Array} src
  	 * @param {String} find
  	 * @param {String} [findByKey]
  	 * @return {Boolean|Number} false when not found, or the index
  	 */
  	function inArray(src, find, findByKey) {
  	    if (src.indexOf && !findByKey) {
  	        return src.indexOf(find);
  	    } else {
  	        var i = 0;
  	        while (i < src.length) {
  	            if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
  	                return i;
  	            }
  	            i++;
  	        }
  	        return -1;
  	    }
  	}

  	/**
  	 * convert array-like objects to real arrays
  	 * @param {Object} obj
  	 * @returns {Array}
  	 */
  	function toArray(obj) {
  	    return Array.prototype.slice.call(obj, 0);
  	}

  	/**
  	 * unique array with objects based on a key (like 'id') or just by the array's value
  	 * @param {Array} src [{id:1},{id:2},{id:1}]
  	 * @param {String} [key]
  	 * @param {Boolean} [sort=False]
  	 * @returns {Array} [{id:1},{id:2}]
  	 */
  	function uniqueArray(src, key, sort) {
  	    var results = [];
  	    var values = [];
  	    var i = 0;

  	    while (i < src.length) {
  	        var val = key ? src[i][key] : src[i];
  	        if (inArray(values, val) < 0) {
  	            results.push(src[i]);
  	        }
  	        values[i] = val;
  	        i++;
  	    }

  	    if (sort) {
  	        if (!key) {
  	            results = results.sort();
  	        } else {
  	            results = results.sort(function sortUniqueArray(a, b) {
  	                return a[key] > b[key];
  	            });
  	        }
  	    }

  	    return results;
  	}

  	/**
  	 * get the prefixed property
  	 * @param {Object} obj
  	 * @param {String} property
  	 * @returns {String|Undefined} prefixed
  	 */
  	function prefixed(obj, property) {
  	    var prefix, prop;
  	    var camelProp = property[0].toUpperCase() + property.slice(1);

  	    var i = 0;
  	    while (i < VENDOR_PREFIXES.length) {
  	        prefix = VENDOR_PREFIXES[i];
  	        prop = (prefix) ? prefix + camelProp : property;

  	        if (prop in obj) {
  	            return prop;
  	        }
  	        i++;
  	    }
  	    return undefined$1;
  	}

  	/**
  	 * get a unique id
  	 * @returns {number} uniqueId
  	 */
  	var _uniqueId = 1;
  	function uniqueId() {
  	    return _uniqueId++;
  	}

  	/**
  	 * get the window object of an element
  	 * @param {HTMLElement} element
  	 * @returns {DocumentView|Window}
  	 */
  	function getWindowForElement(element) {
  	    var doc = element.ownerDocument || element;
  	    return (doc.defaultView || doc.parentWindow || window);
  	}

  	var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;

  	var SUPPORT_TOUCH = ('ontouchstart' in window);
  	var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined$1;
  	var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);

  	var INPUT_TYPE_TOUCH = 'touch';
  	var INPUT_TYPE_PEN = 'pen';
  	var INPUT_TYPE_MOUSE = 'mouse';
  	var INPUT_TYPE_KINECT = 'kinect';

  	var COMPUTE_INTERVAL = 25;

  	var INPUT_START = 1;
  	var INPUT_MOVE = 2;
  	var INPUT_END = 4;
  	var INPUT_CANCEL = 8;

  	var DIRECTION_NONE = 1;
  	var DIRECTION_LEFT = 2;
  	var DIRECTION_RIGHT = 4;
  	var DIRECTION_UP = 8;
  	var DIRECTION_DOWN = 16;

  	var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
  	var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
  	var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;

  	var PROPS_XY = ['x', 'y'];
  	var PROPS_CLIENT_XY = ['clientX', 'clientY'];

  	/**
  	 * create new input type manager
  	 * @param {Manager} manager
  	 * @param {Function} callback
  	 * @returns {Input}
  	 * @constructor
  	 */
  	function Input(manager, callback) {
  	    var self = this;
  	    this.manager = manager;
  	    this.callback = callback;
  	    this.element = manager.element;
  	    this.target = manager.options.inputTarget;

  	    // smaller wrapper around the handler, for the scope and the enabled state of the manager,
  	    // so when disabled the input events are completely bypassed.
  	    this.domHandler = function(ev) {
  	        if (boolOrFn(manager.options.enable, [manager])) {
  	            self.handler(ev);
  	        }
  	    };

  	    this.init();

  	}

  	Input.prototype = {
  	    /**
  	     * should handle the inputEvent data and trigger the callback
  	     * @virtual
  	     */
  	    handler: function() { },

  	    /**
  	     * bind the events
  	     */
  	    init: function() {
  	        this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
  	        this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
  	        this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  	    },

  	    /**
  	     * unbind the events
  	     */
  	    destroy: function() {
  	        this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
  	        this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
  	        this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  	    }
  	};

  	/**
  	 * create new input type manager
  	 * called by the Manager constructor
  	 * @param {Hammer} manager
  	 * @returns {Input}
  	 */
  	function createInputInstance(manager) {
  	    var Type;
  	    var inputClass = manager.options.inputClass;

  	    if (inputClass) {
  	        Type = inputClass;
  	    } else if (SUPPORT_POINTER_EVENTS) {
  	        Type = PointerEventInput;
  	    } else if (SUPPORT_ONLY_TOUCH) {
  	        Type = TouchInput;
  	    } else if (!SUPPORT_TOUCH) {
  	        Type = MouseInput;
  	    } else {
  	        Type = TouchMouseInput;
  	    }
  	    return new (Type)(manager, inputHandler);
  	}

  	/**
  	 * handle input events
  	 * @param {Manager} manager
  	 * @param {String} eventType
  	 * @param {Object} input
  	 */
  	function inputHandler(manager, eventType, input) {
  	    var pointersLen = input.pointers.length;
  	    var changedPointersLen = input.changedPointers.length;
  	    var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
  	    var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));

  	    input.isFirst = !!isFirst;
  	    input.isFinal = !!isFinal;

  	    if (isFirst) {
  	        manager.session = {};
  	    }

  	    // source event is the normalized value of the domEvents
  	    // like 'touchstart, mouseup, pointerdown'
  	    input.eventType = eventType;

  	    // compute scale, rotation etc
  	    computeInputData(manager, input);

  	    // emit secret event
  	    manager.emit('hammer.input', input);

  	    manager.recognize(input);
  	    manager.session.prevInput = input;
  	}

  	/**
  	 * extend the data with some usable properties like scale, rotate, velocity etc
  	 * @param {Object} manager
  	 * @param {Object} input
  	 */
  	function computeInputData(manager, input) {
  	    var session = manager.session;
  	    var pointers = input.pointers;
  	    var pointersLength = pointers.length;

  	    // store the first input to calculate the distance and direction
  	    if (!session.firstInput) {
  	        session.firstInput = simpleCloneInputData(input);
  	    }

  	    // to compute scale and rotation we need to store the multiple touches
  	    if (pointersLength > 1 && !session.firstMultiple) {
  	        session.firstMultiple = simpleCloneInputData(input);
  	    } else if (pointersLength === 1) {
  	        session.firstMultiple = false;
  	    }

  	    var firstInput = session.firstInput;
  	    var firstMultiple = session.firstMultiple;
  	    var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;

  	    var center = input.center = getCenter(pointers);
  	    input.timeStamp = now();
  	    input.deltaTime = input.timeStamp - firstInput.timeStamp;

  	    input.angle = getAngle(offsetCenter, center);
  	    input.distance = getDistance(offsetCenter, center);

  	    computeDeltaXY(session, input);
  	    input.offsetDirection = getDirection(input.deltaX, input.deltaY);

  	    var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
  	    input.overallVelocityX = overallVelocity.x;
  	    input.overallVelocityY = overallVelocity.y;
  	    input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;

  	    input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
  	    input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;

  	    input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
  	        session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);

  	    computeIntervalInputData(session, input);

  	    // find the correct target
  	    var target = manager.element;
  	    if (hasParent(input.srcEvent.target, target)) {
  	        target = input.srcEvent.target;
  	    }
  	    input.target = target;
  	}

  	function computeDeltaXY(session, input) {
  	    var center = input.center;
  	    var offset = session.offsetDelta || {};
  	    var prevDelta = session.prevDelta || {};
  	    var prevInput = session.prevInput || {};

  	    if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
  	        prevDelta = session.prevDelta = {
  	            x: prevInput.deltaX || 0,
  	            y: prevInput.deltaY || 0
  	        };

  	        offset = session.offsetDelta = {
  	            x: center.x,
  	            y: center.y
  	        };
  	    }

  	    input.deltaX = prevDelta.x + (center.x - offset.x);
  	    input.deltaY = prevDelta.y + (center.y - offset.y);
  	}

  	/**
  	 * velocity is calculated every x ms
  	 * @param {Object} session
  	 * @param {Object} input
  	 */
  	function computeIntervalInputData(session, input) {
  	    var last = session.lastInterval || input,
  	        deltaTime = input.timeStamp - last.timeStamp,
  	        velocity, velocityX, velocityY, direction;

  	    if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined$1)) {
  	        var deltaX = input.deltaX - last.deltaX;
  	        var deltaY = input.deltaY - last.deltaY;

  	        var v = getVelocity(deltaTime, deltaX, deltaY);
  	        velocityX = v.x;
  	        velocityY = v.y;
  	        velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
  	        direction = getDirection(deltaX, deltaY);

  	        session.lastInterval = input;
  	    } else {
  	        // use latest velocity info if it doesn't overtake a minimum period
  	        velocity = last.velocity;
  	        velocityX = last.velocityX;
  	        velocityY = last.velocityY;
  	        direction = last.direction;
  	    }

  	    input.velocity = velocity;
  	    input.velocityX = velocityX;
  	    input.velocityY = velocityY;
  	    input.direction = direction;
  	}

  	/**
  	 * create a simple clone from the input used for storage of firstInput and firstMultiple
  	 * @param {Object} input
  	 * @returns {Object} clonedInputData
  	 */
  	function simpleCloneInputData(input) {
  	    // make a simple copy of the pointers because we will get a reference if we don't
  	    // we only need clientXY for the calculations
  	    var pointers = [];
  	    var i = 0;
  	    while (i < input.pointers.length) {
  	        pointers[i] = {
  	            clientX: round(input.pointers[i].clientX),
  	            clientY: round(input.pointers[i].clientY)
  	        };
  	        i++;
  	    }

  	    return {
  	        timeStamp: now(),
  	        pointers: pointers,
  	        center: getCenter(pointers),
  	        deltaX: input.deltaX,
  	        deltaY: input.deltaY
  	    };
  	}

  	/**
  	 * get the center of all the pointers
  	 * @param {Array} pointers
  	 * @return {Object} center contains `x` and `y` properties
  	 */
  	function getCenter(pointers) {
  	    var pointersLength = pointers.length;

  	    // no need to loop when only one touch
  	    if (pointersLength === 1) {
  	        return {
  	            x: round(pointers[0].clientX),
  	            y: round(pointers[0].clientY)
  	        };
  	    }

  	    var x = 0, y = 0, i = 0;
  	    while (i < pointersLength) {
  	        x += pointers[i].clientX;
  	        y += pointers[i].clientY;
  	        i++;
  	    }

  	    return {
  	        x: round(x / pointersLength),
  	        y: round(y / pointersLength)
  	    };
  	}

  	/**
  	 * calculate the velocity between two points. unit is in px per ms.
  	 * @param {Number} deltaTime
  	 * @param {Number} x
  	 * @param {Number} y
  	 * @return {Object} velocity `x` and `y`
  	 */
  	function getVelocity(deltaTime, x, y) {
  	    return {
  	        x: x / deltaTime || 0,
  	        y: y / deltaTime || 0
  	    };
  	}

  	/**
  	 * get the direction between two points
  	 * @param {Number} x
  	 * @param {Number} y
  	 * @return {Number} direction
  	 */
  	function getDirection(x, y) {
  	    if (x === y) {
  	        return DIRECTION_NONE;
  	    }

  	    if (abs(x) >= abs(y)) {
  	        return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  	    }
  	    return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
  	}

  	/**
  	 * calculate the absolute distance between two points
  	 * @param {Object} p1 {x, y}
  	 * @param {Object} p2 {x, y}
  	 * @param {Array} [props] containing x and y keys
  	 * @return {Number} distance
  	 */
  	function getDistance(p1, p2, props) {
  	    if (!props) {
  	        props = PROPS_XY;
  	    }
  	    var x = p2[props[0]] - p1[props[0]],
  	        y = p2[props[1]] - p1[props[1]];

  	    return Math.sqrt((x * x) + (y * y));
  	}

  	/**
  	 * calculate the angle between two coordinates
  	 * @param {Object} p1
  	 * @param {Object} p2
  	 * @param {Array} [props] containing x and y keys
  	 * @return {Number} angle
  	 */
  	function getAngle(p1, p2, props) {
  	    if (!props) {
  	        props = PROPS_XY;
  	    }
  	    var x = p2[props[0]] - p1[props[0]],
  	        y = p2[props[1]] - p1[props[1]];
  	    return Math.atan2(y, x) * 180 / Math.PI;
  	}

  	/**
  	 * calculate the rotation degrees between two pointersets
  	 * @param {Array} start array of pointers
  	 * @param {Array} end array of pointers
  	 * @return {Number} rotation
  	 */
  	function getRotation(start, end) {
  	    return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
  	}

  	/**
  	 * calculate the scale factor between two pointersets
  	 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  	 * @param {Array} start array of pointers
  	 * @param {Array} end array of pointers
  	 * @return {Number} scale
  	 */
  	function getScale(start, end) {
  	    return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
  	}

  	var MOUSE_INPUT_MAP = {
  	    mousedown: INPUT_START,
  	    mousemove: INPUT_MOVE,
  	    mouseup: INPUT_END
  	};

  	var MOUSE_ELEMENT_EVENTS = 'mousedown';
  	var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';

  	/**
  	 * Mouse events input
  	 * @constructor
  	 * @extends Input
  	 */
  	function MouseInput() {
  	    this.evEl = MOUSE_ELEMENT_EVENTS;
  	    this.evWin = MOUSE_WINDOW_EVENTS;

  	    this.pressed = false; // mousedown state

  	    Input.apply(this, arguments);
  	}

  	inherit(MouseInput, Input, {
  	    /**
  	     * handle mouse events
  	     * @param {Object} ev
  	     */
  	    handler: function MEhandler(ev) {
  	        var eventType = MOUSE_INPUT_MAP[ev.type];

  	        // on start we want to have the left mouse button down
  	        if (eventType & INPUT_START && ev.button === 0) {
  	            this.pressed = true;
  	        }

  	        if (eventType & INPUT_MOVE && ev.which !== 1) {
  	            eventType = INPUT_END;
  	        }

  	        // mouse must be down
  	        if (!this.pressed) {
  	            return;
  	        }

  	        if (eventType & INPUT_END) {
  	            this.pressed = false;
  	        }

  	        this.callback(this.manager, eventType, {
  	            pointers: [ev],
  	            changedPointers: [ev],
  	            pointerType: INPUT_TYPE_MOUSE,
  	            srcEvent: ev
  	        });
  	    }
  	});

  	var POINTER_INPUT_MAP = {
  	    pointerdown: INPUT_START,
  	    pointermove: INPUT_MOVE,
  	    pointerup: INPUT_END,
  	    pointercancel: INPUT_CANCEL,
  	    pointerout: INPUT_CANCEL
  	};

  	// in IE10 the pointer types is defined as an enum
  	var IE10_POINTER_TYPE_ENUM = {
  	    2: INPUT_TYPE_TOUCH,
  	    3: INPUT_TYPE_PEN,
  	    4: INPUT_TYPE_MOUSE,
  	    5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
  	};

  	var POINTER_ELEMENT_EVENTS = 'pointerdown';
  	var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';

  	// IE10 has prefixed support, and case-sensitive
  	if (window.MSPointerEvent && !window.PointerEvent) {
  	    POINTER_ELEMENT_EVENTS = 'MSPointerDown';
  	    POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
  	}

  	/**
  	 * Pointer events input
  	 * @constructor
  	 * @extends Input
  	 */
  	function PointerEventInput() {
  	    this.evEl = POINTER_ELEMENT_EVENTS;
  	    this.evWin = POINTER_WINDOW_EVENTS;

  	    Input.apply(this, arguments);

  	    this.store = (this.manager.session.pointerEvents = []);
  	}

  	inherit(PointerEventInput, Input, {
  	    /**
  	     * handle mouse events
  	     * @param {Object} ev
  	     */
  	    handler: function PEhandler(ev) {
  	        var store = this.store;
  	        var removePointer = false;

  	        var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
  	        var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
  	        var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;

  	        var isTouch = (pointerType == INPUT_TYPE_TOUCH);

  	        // get index of the event in the store
  	        var storeIndex = inArray(store, ev.pointerId, 'pointerId');

  	        // start and mouse must be down
  	        if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
  	            if (storeIndex < 0) {
  	                store.push(ev);
  	                storeIndex = store.length - 1;
  	            }
  	        } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
  	            removePointer = true;
  	        }

  	        // it not found, so the pointer hasn't been down (so it's probably a hover)
  	        if (storeIndex < 0) {
  	            return;
  	        }

  	        // update the event in the store
  	        store[storeIndex] = ev;

  	        this.callback(this.manager, eventType, {
  	            pointers: store,
  	            changedPointers: [ev],
  	            pointerType: pointerType,
  	            srcEvent: ev
  	        });

  	        if (removePointer) {
  	            // remove from the store
  	            store.splice(storeIndex, 1);
  	        }
  	    }
  	});

  	var SINGLE_TOUCH_INPUT_MAP = {
  	    touchstart: INPUT_START,
  	    touchmove: INPUT_MOVE,
  	    touchend: INPUT_END,
  	    touchcancel: INPUT_CANCEL
  	};

  	var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
  	var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';

  	/**
  	 * Touch events input
  	 * @constructor
  	 * @extends Input
  	 */
  	function SingleTouchInput() {
  	    this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
  	    this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
  	    this.started = false;

  	    Input.apply(this, arguments);
  	}

  	inherit(SingleTouchInput, Input, {
  	    handler: function TEhandler(ev) {
  	        var type = SINGLE_TOUCH_INPUT_MAP[ev.type];

  	        // should we handle the touch events?
  	        if (type === INPUT_START) {
  	            this.started = true;
  	        }

  	        if (!this.started) {
  	            return;
  	        }

  	        var touches = normalizeSingleTouches.call(this, ev, type);

  	        // when done, reset the started state
  	        if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
  	            this.started = false;
  	        }

  	        this.callback(this.manager, type, {
  	            pointers: touches[0],
  	            changedPointers: touches[1],
  	            pointerType: INPUT_TYPE_TOUCH,
  	            srcEvent: ev
  	        });
  	    }
  	});

  	/**
  	 * @this {TouchInput}
  	 * @param {Object} ev
  	 * @param {Number} type flag
  	 * @returns {undefined|Array} [all, changed]
  	 */
  	function normalizeSingleTouches(ev, type) {
  	    var all = toArray(ev.touches);
  	    var changed = toArray(ev.changedTouches);

  	    if (type & (INPUT_END | INPUT_CANCEL)) {
  	        all = uniqueArray(all.concat(changed), 'identifier', true);
  	    }

  	    return [all, changed];
  	}

  	var TOUCH_INPUT_MAP = {
  	    touchstart: INPUT_START,
  	    touchmove: INPUT_MOVE,
  	    touchend: INPUT_END,
  	    touchcancel: INPUT_CANCEL
  	};

  	var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';

  	/**
  	 * Multi-user touch events input
  	 * @constructor
  	 * @extends Input
  	 */
  	function TouchInput() {
  	    this.evTarget = TOUCH_TARGET_EVENTS;
  	    this.targetIds = {};

  	    Input.apply(this, arguments);
  	}

  	inherit(TouchInput, Input, {
  	    handler: function MTEhandler(ev) {
  	        var type = TOUCH_INPUT_MAP[ev.type];
  	        var touches = getTouches.call(this, ev, type);
  	        if (!touches) {
  	            return;
  	        }

  	        this.callback(this.manager, type, {
  	            pointers: touches[0],
  	            changedPointers: touches[1],
  	            pointerType: INPUT_TYPE_TOUCH,
  	            srcEvent: ev
  	        });
  	    }
  	});

  	/**
  	 * @this {TouchInput}
  	 * @param {Object} ev
  	 * @param {Number} type flag
  	 * @returns {undefined|Array} [all, changed]
  	 */
  	function getTouches(ev, type) {
  	    var allTouches = toArray(ev.touches);
  	    var targetIds = this.targetIds;

  	    // when there is only one touch, the process can be simplified
  	    if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
  	        targetIds[allTouches[0].identifier] = true;
  	        return [allTouches, allTouches];
  	    }

  	    var i,
  	        targetTouches,
  	        changedTouches = toArray(ev.changedTouches),
  	        changedTargetTouches = [],
  	        target = this.target;

  	    // get target touches from touches
  	    targetTouches = allTouches.filter(function(touch) {
  	        return hasParent(touch.target, target);
  	    });

  	    // collect touches
  	    if (type === INPUT_START) {
  	        i = 0;
  	        while (i < targetTouches.length) {
  	            targetIds[targetTouches[i].identifier] = true;
  	            i++;
  	        }
  	    }

  	    // filter changed touches to only contain touches that exist in the collected target ids
  	    i = 0;
  	    while (i < changedTouches.length) {
  	        if (targetIds[changedTouches[i].identifier]) {
  	            changedTargetTouches.push(changedTouches[i]);
  	        }

  	        // cleanup removed touches
  	        if (type & (INPUT_END | INPUT_CANCEL)) {
  	            delete targetIds[changedTouches[i].identifier];
  	        }
  	        i++;
  	    }

  	    if (!changedTargetTouches.length) {
  	        return;
  	    }

  	    return [
  	        // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
  	        uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
  	        changedTargetTouches
  	    ];
  	}

  	/**
  	 * Combined touch and mouse input
  	 *
  	 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
  	 * This because touch devices also emit mouse events while doing a touch.
  	 *
  	 * @constructor
  	 * @extends Input
  	 */

  	var DEDUP_TIMEOUT = 2500;
  	var DEDUP_DISTANCE = 25;

  	function TouchMouseInput() {
  	    Input.apply(this, arguments);

  	    var handler = bindFn(this.handler, this);
  	    this.touch = new TouchInput(this.manager, handler);
  	    this.mouse = new MouseInput(this.manager, handler);

  	    this.primaryTouch = null;
  	    this.lastTouches = [];
  	}

  	inherit(TouchMouseInput, Input, {
  	    /**
  	     * handle mouse and touch events
  	     * @param {Hammer} manager
  	     * @param {String} inputEvent
  	     * @param {Object} inputData
  	     */
  	    handler: function TMEhandler(manager, inputEvent, inputData) {
  	        var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
  	            isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);

  	        if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
  	            return;
  	        }

  	        // when we're in a touch event, record touches to  de-dupe synthetic mouse event
  	        if (isTouch) {
  	            recordTouches.call(this, inputEvent, inputData);
  	        } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
  	            return;
  	        }

  	        this.callback(manager, inputEvent, inputData);
  	    },

  	    /**
  	     * remove the event listeners
  	     */
  	    destroy: function destroy() {
  	        this.touch.destroy();
  	        this.mouse.destroy();
  	    }
  	});

  	function recordTouches(eventType, eventData) {
  	    if (eventType & INPUT_START) {
  	        this.primaryTouch = eventData.changedPointers[0].identifier;
  	        setLastTouch.call(this, eventData);
  	    } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
  	        setLastTouch.call(this, eventData);
  	    }
  	}

  	function setLastTouch(eventData) {
  	    var touch = eventData.changedPointers[0];

  	    if (touch.identifier === this.primaryTouch) {
  	        var lastTouch = {x: touch.clientX, y: touch.clientY};
  	        this.lastTouches.push(lastTouch);
  	        var lts = this.lastTouches;
  	        var removeLastTouch = function() {
  	            var i = lts.indexOf(lastTouch);
  	            if (i > -1) {
  	                lts.splice(i, 1);
  	            }
  	        };
  	        setTimeout(removeLastTouch, DEDUP_TIMEOUT);
  	    }
  	}

  	function isSyntheticEvent(eventData) {
  	    var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
  	    for (var i = 0; i < this.lastTouches.length; i++) {
  	        var t = this.lastTouches[i];
  	        var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
  	        if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
  	            return true;
  	        }
  	    }
  	    return false;
  	}

  	var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
  	var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined$1;

  	// magical touchAction value
  	var TOUCH_ACTION_COMPUTE = 'compute';
  	var TOUCH_ACTION_AUTO = 'auto';
  	var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
  	var TOUCH_ACTION_NONE = 'none';
  	var TOUCH_ACTION_PAN_X = 'pan-x';
  	var TOUCH_ACTION_PAN_Y = 'pan-y';
  	var TOUCH_ACTION_MAP = getTouchActionProps();

  	/**
  	 * Touch Action
  	 * sets the touchAction property or uses the js alternative
  	 * @param {Manager} manager
  	 * @param {String} value
  	 * @constructor
  	 */
  	function TouchAction(manager, value) {
  	    this.manager = manager;
  	    this.set(value);
  	}

  	TouchAction.prototype = {
  	    /**
  	     * set the touchAction value on the element or enable the polyfill
  	     * @param {String} value
  	     */
  	    set: function(value) {
  	        // find out the touch-action by the event handlers
  	        if (value == TOUCH_ACTION_COMPUTE) {
  	            value = this.compute();
  	        }

  	        if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
  	            this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
  	        }
  	        this.actions = value.toLowerCase().trim();
  	    },

  	    /**
  	     * just re-set the touchAction value
  	     */
  	    update: function() {
  	        this.set(this.manager.options.touchAction);
  	    },

  	    /**
  	     * compute the value for the touchAction property based on the recognizer's settings
  	     * @returns {String} value
  	     */
  	    compute: function() {
  	        var actions = [];
  	        each(this.manager.recognizers, function(recognizer) {
  	            if (boolOrFn(recognizer.options.enable, [recognizer])) {
  	                actions = actions.concat(recognizer.getTouchAction());
  	            }
  	        });
  	        return cleanTouchActions(actions.join(' '));
  	    },

  	    /**
  	     * this method is called on each input cycle and provides the preventing of the browser behavior
  	     * @param {Object} input
  	     */
  	    preventDefaults: function(input) {
  	        var srcEvent = input.srcEvent;
  	        var direction = input.offsetDirection;

  	        // if the touch action did prevented once this session
  	        if (this.manager.session.prevented) {
  	            srcEvent.preventDefault();
  	            return;
  	        }

  	        var actions = this.actions;
  	        var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
  	        var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
  	        var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];

  	        if (hasNone) {
  	            //do not prevent defaults if this is a tap gesture

  	            var isTapPointer = input.pointers.length === 1;
  	            var isTapMovement = input.distance < 2;
  	            var isTapTouchTime = input.deltaTime < 250;

  	            if (isTapPointer && isTapMovement && isTapTouchTime) {
  	                return;
  	            }
  	        }

  	        if (hasPanX && hasPanY) {
  	            // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
  	            return;
  	        }

  	        if (hasNone ||
  	            (hasPanY && direction & DIRECTION_HORIZONTAL) ||
  	            (hasPanX && direction & DIRECTION_VERTICAL)) {
  	            return this.preventSrc(srcEvent);
  	        }
  	    },

  	    /**
  	     * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
  	     * @param {Object} srcEvent
  	     */
  	    preventSrc: function(srcEvent) {
  	        this.manager.session.prevented = true;
  	        srcEvent.preventDefault();
  	    }
  	};

  	/**
  	 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
  	 * @param {String} actions
  	 * @returns {*}
  	 */
  	function cleanTouchActions(actions) {
  	    // none
  	    if (inStr(actions, TOUCH_ACTION_NONE)) {
  	        return TOUCH_ACTION_NONE;
  	    }

  	    var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
  	    var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);

  	    // if both pan-x and pan-y are set (different recognizers
  	    // for different directions, e.g. horizontal pan but vertical swipe?)
  	    // we need none (as otherwise with pan-x pan-y combined none of these
  	    // recognizers will work, since the browser would handle all panning
  	    if (hasPanX && hasPanY) {
  	        return TOUCH_ACTION_NONE;
  	    }

  	    // pan-x OR pan-y
  	    if (hasPanX || hasPanY) {
  	        return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
  	    }

  	    // manipulation
  	    if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
  	        return TOUCH_ACTION_MANIPULATION;
  	    }

  	    return TOUCH_ACTION_AUTO;
  	}

  	function getTouchActionProps() {
  	    if (!NATIVE_TOUCH_ACTION) {
  	        return false;
  	    }
  	    var touchMap = {};
  	    var cssSupports = window.CSS && window.CSS.supports;
  	    ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {

  	        // If css.supports is not supported but there is native touch-action assume it supports
  	        // all values. This is the case for IE 10 and 11.
  	        touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
  	    });
  	    return touchMap;
  	}

  	/**
  	 * Recognizer flow explained; *
  	 * All recognizers have the initial state of POSSIBLE when a input session starts.
  	 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
  	 * Example session for mouse-input: mousedown -> mousemove -> mouseup
  	 *
  	 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
  	 * which determines with state it should be.
  	 *
  	 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
  	 * POSSIBLE to give it another change on the next cycle.
  	 *
  	 *               Possible
  	 *                  |
  	 *            +-----+---------------+
  	 *            |                     |
  	 *      +-----+-----+               |
  	 *      |           |               |
  	 *   Failed      Cancelled          |
  	 *                          +-------+------+
  	 *                          |              |
  	 *                      Recognized       Began
  	 *                                         |
  	 *                                      Changed
  	 *                                         |
  	 *                                  Ended/Recognized
  	 */
  	var STATE_POSSIBLE = 1;
  	var STATE_BEGAN = 2;
  	var STATE_CHANGED = 4;
  	var STATE_ENDED = 8;
  	var STATE_RECOGNIZED = STATE_ENDED;
  	var STATE_CANCELLED = 16;
  	var STATE_FAILED = 32;

  	/**
  	 * Recognizer
  	 * Every recognizer needs to extend from this class.
  	 * @constructor
  	 * @param {Object} options
  	 */
  	function Recognizer(options) {
  	    this.options = assign({}, this.defaults, options || {});

  	    this.id = uniqueId();

  	    this.manager = null;

  	    // default is enable true
  	    this.options.enable = ifUndefined(this.options.enable, true);

  	    this.state = STATE_POSSIBLE;

  	    this.simultaneous = {};
  	    this.requireFail = [];
  	}

  	Recognizer.prototype = {
  	    /**
  	     * @virtual
  	     * @type {Object}
  	     */
  	    defaults: {},

  	    /**
  	     * set options
  	     * @param {Object} options
  	     * @return {Recognizer}
  	     */
  	    set: function(options) {
  	        assign(this.options, options);

  	        // also update the touchAction, in case something changed about the directions/enabled state
  	        this.manager && this.manager.touchAction.update();
  	        return this;
  	    },

  	    /**
  	     * recognize simultaneous with an other recognizer.
  	     * @param {Recognizer} otherRecognizer
  	     * @returns {Recognizer} this
  	     */
  	    recognizeWith: function(otherRecognizer) {
  	        if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
  	            return this;
  	        }

  	        var simultaneous = this.simultaneous;
  	        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  	        if (!simultaneous[otherRecognizer.id]) {
  	            simultaneous[otherRecognizer.id] = otherRecognizer;
  	            otherRecognizer.recognizeWith(this);
  	        }
  	        return this;
  	    },

  	    /**
  	     * drop the simultaneous link. it doesnt remove the link on the other recognizer.
  	     * @param {Recognizer} otherRecognizer
  	     * @returns {Recognizer} this
  	     */
  	    dropRecognizeWith: function(otherRecognizer) {
  	        if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
  	            return this;
  	        }

  	        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  	        delete this.simultaneous[otherRecognizer.id];
  	        return this;
  	    },

  	    /**
  	     * recognizer can only run when an other is failing
  	     * @param {Recognizer} otherRecognizer
  	     * @returns {Recognizer} this
  	     */
  	    requireFailure: function(otherRecognizer) {
  	        if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
  	            return this;
  	        }

  	        var requireFail = this.requireFail;
  	        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  	        if (inArray(requireFail, otherRecognizer) === -1) {
  	            requireFail.push(otherRecognizer);
  	            otherRecognizer.requireFailure(this);
  	        }
  	        return this;
  	    },

  	    /**
  	     * drop the requireFailure link. it does not remove the link on the other recognizer.
  	     * @param {Recognizer} otherRecognizer
  	     * @returns {Recognizer} this
  	     */
  	    dropRequireFailure: function(otherRecognizer) {
  	        if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
  	            return this;
  	        }

  	        otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  	        var index = inArray(this.requireFail, otherRecognizer);
  	        if (index > -1) {
  	            this.requireFail.splice(index, 1);
  	        }
  	        return this;
  	    },

  	    /**
  	     * has require failures boolean
  	     * @returns {boolean}
  	     */
  	    hasRequireFailures: function() {
  	        return this.requireFail.length > 0;
  	    },

  	    /**
  	     * if the recognizer can recognize simultaneous with an other recognizer
  	     * @param {Recognizer} otherRecognizer
  	     * @returns {Boolean}
  	     */
  	    canRecognizeWith: function(otherRecognizer) {
  	        return !!this.simultaneous[otherRecognizer.id];
  	    },

  	    /**
  	     * You should use `tryEmit` instead of `emit` directly to check
  	     * that all the needed recognizers has failed before emitting.
  	     * @param {Object} input
  	     */
  	    emit: function(input) {
  	        var self = this;
  	        var state = this.state;

  	        function emit(event) {
  	            self.manager.emit(event, input);
  	        }

  	        // 'panstart' and 'panmove'
  	        if (state < STATE_ENDED) {
  	            emit(self.options.event + stateStr(state));
  	        }

  	        emit(self.options.event); // simple 'eventName' events

  	        if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
  	            emit(input.additionalEvent);
  	        }

  	        // panend and pancancel
  	        if (state >= STATE_ENDED) {
  	            emit(self.options.event + stateStr(state));
  	        }
  	    },

  	    /**
  	     * Check that all the require failure recognizers has failed,
  	     * if true, it emits a gesture event,
  	     * otherwise, setup the state to FAILED.
  	     * @param {Object} input
  	     */
  	    tryEmit: function(input) {
  	        if (this.canEmit()) {
  	            return this.emit(input);
  	        }
  	        // it's failing anyway
  	        this.state = STATE_FAILED;
  	    },

  	    /**
  	     * can we emit?
  	     * @returns {boolean}
  	     */
  	    canEmit: function() {
  	        var i = 0;
  	        while (i < this.requireFail.length) {
  	            if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
  	                return false;
  	            }
  	            i++;
  	        }
  	        return true;
  	    },

  	    /**
  	     * update the recognizer
  	     * @param {Object} inputData
  	     */
  	    recognize: function(inputData) {
  	        // make a new copy of the inputData
  	        // so we can change the inputData without messing up the other recognizers
  	        var inputDataClone = assign({}, inputData);

  	        // is is enabled and allow recognizing?
  	        if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
  	            this.reset();
  	            this.state = STATE_FAILED;
  	            return;
  	        }

  	        // reset when we've reached the end
  	        if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
  	            this.state = STATE_POSSIBLE;
  	        }

  	        this.state = this.process(inputDataClone);

  	        // the recognizer has recognized a gesture
  	        // so trigger an event
  	        if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
  	            this.tryEmit(inputDataClone);
  	        }
  	    },

  	    /**
  	     * return the state of the recognizer
  	     * the actual recognizing happens in this method
  	     * @virtual
  	     * @param {Object} inputData
  	     * @returns {Const} STATE
  	     */
  	    process: function(inputData) { }, // jshint ignore:line

  	    /**
  	     * return the preferred touch-action
  	     * @virtual
  	     * @returns {Array}
  	     */
  	    getTouchAction: function() { },

  	    /**
  	     * called when the gesture isn't allowed to recognize
  	     * like when another is being recognized or it is disabled
  	     * @virtual
  	     */
  	    reset: function() { }
  	};

  	/**
  	 * get a usable string, used as event postfix
  	 * @param {Const} state
  	 * @returns {String} state
  	 */
  	function stateStr(state) {
  	    if (state & STATE_CANCELLED) {
  	        return 'cancel';
  	    } else if (state & STATE_ENDED) {
  	        return 'end';
  	    } else if (state & STATE_CHANGED) {
  	        return 'move';
  	    } else if (state & STATE_BEGAN) {
  	        return 'start';
  	    }
  	    return '';
  	}

  	/**
  	 * direction cons to string
  	 * @param {Const} direction
  	 * @returns {String}
  	 */
  	function directionStr(direction) {
  	    if (direction == DIRECTION_DOWN) {
  	        return 'down';
  	    } else if (direction == DIRECTION_UP) {
  	        return 'up';
  	    } else if (direction == DIRECTION_LEFT) {
  	        return 'left';
  	    } else if (direction == DIRECTION_RIGHT) {
  	        return 'right';
  	    }
  	    return '';
  	}

  	/**
  	 * get a recognizer by name if it is bound to a manager
  	 * @param {Recognizer|String} otherRecognizer
  	 * @param {Recognizer} recognizer
  	 * @returns {Recognizer}
  	 */
  	function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
  	    var manager = recognizer.manager;
  	    if (manager) {
  	        return manager.get(otherRecognizer);
  	    }
  	    return otherRecognizer;
  	}

  	/**
  	 * This recognizer is just used as a base for the simple attribute recognizers.
  	 * @constructor
  	 * @extends Recognizer
  	 */
  	function AttrRecognizer() {
  	    Recognizer.apply(this, arguments);
  	}

  	inherit(AttrRecognizer, Recognizer, {
  	    /**
  	     * @namespace
  	     * @memberof AttrRecognizer
  	     */
  	    defaults: {
  	        /**
  	         * @type {Number}
  	         * @default 1
  	         */
  	        pointers: 1
  	    },

  	    /**
  	     * Used to check if it the recognizer receives valid input, like input.distance > 10.
  	     * @memberof AttrRecognizer
  	     * @param {Object} input
  	     * @returns {Boolean} recognized
  	     */
  	    attrTest: function(input) {
  	        var optionPointers = this.options.pointers;
  	        return optionPointers === 0 || input.pointers.length === optionPointers;
  	    },

  	    /**
  	     * Process the input and return the state for the recognizer
  	     * @memberof AttrRecognizer
  	     * @param {Object} input
  	     * @returns {*} State
  	     */
  	    process: function(input) {
  	        var state = this.state;
  	        var eventType = input.eventType;

  	        var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
  	        var isValid = this.attrTest(input);

  	        // on cancel input and we've recognized before, return STATE_CANCELLED
  	        if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
  	            return state | STATE_CANCELLED;
  	        } else if (isRecognized || isValid) {
  	            if (eventType & INPUT_END) {
  	                return state | STATE_ENDED;
  	            } else if (!(state & STATE_BEGAN)) {
  	                return STATE_BEGAN;
  	            }
  	            return state | STATE_CHANGED;
  	        }
  	        return STATE_FAILED;
  	    }
  	});

  	/**
  	 * Pan
  	 * Recognized when the pointer is down and moved in the allowed direction.
  	 * @constructor
  	 * @extends AttrRecognizer
  	 */
  	function PanRecognizer() {
  	    AttrRecognizer.apply(this, arguments);

  	    this.pX = null;
  	    this.pY = null;
  	}

  	inherit(PanRecognizer, AttrRecognizer, {
  	    /**
  	     * @namespace
  	     * @memberof PanRecognizer
  	     */
  	    defaults: {
  	        event: 'pan',
  	        threshold: 10,
  	        pointers: 1,
  	        direction: DIRECTION_ALL
  	    },

  	    getTouchAction: function() {
  	        var direction = this.options.direction;
  	        var actions = [];
  	        if (direction & DIRECTION_HORIZONTAL) {
  	            actions.push(TOUCH_ACTION_PAN_Y);
  	        }
  	        if (direction & DIRECTION_VERTICAL) {
  	            actions.push(TOUCH_ACTION_PAN_X);
  	        }
  	        return actions;
  	    },

  	    directionTest: function(input) {
  	        var options = this.options;
  	        var hasMoved = true;
  	        var distance = input.distance;
  	        var direction = input.direction;
  	        var x = input.deltaX;
  	        var y = input.deltaY;

  	        // lock to axis?
  	        if (!(direction & options.direction)) {
  	            if (options.direction & DIRECTION_HORIZONTAL) {
  	                direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  	                hasMoved = x != this.pX;
  	                distance = Math.abs(input.deltaX);
  	            } else {
  	                direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  	                hasMoved = y != this.pY;
  	                distance = Math.abs(input.deltaY);
  	            }
  	        }
  	        input.direction = direction;
  	        return hasMoved && distance > options.threshold && direction & options.direction;
  	    },

  	    attrTest: function(input) {
  	        return AttrRecognizer.prototype.attrTest.call(this, input) &&
  	            (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
  	    },

  	    emit: function(input) {

  	        this.pX = input.deltaX;
  	        this.pY = input.deltaY;

  	        var direction = directionStr(input.direction);

  	        if (direction) {
  	            input.additionalEvent = this.options.event + direction;
  	        }
  	        this._super.emit.call(this, input);
  	    }
  	});

  	/**
  	 * Pinch
  	 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
  	 * @constructor
  	 * @extends AttrRecognizer
  	 */
  	function PinchRecognizer() {
  	    AttrRecognizer.apply(this, arguments);
  	}

  	inherit(PinchRecognizer, AttrRecognizer, {
  	    /**
  	     * @namespace
  	     * @memberof PinchRecognizer
  	     */
  	    defaults: {
  	        event: 'pinch',
  	        threshold: 0,
  	        pointers: 2
  	    },

  	    getTouchAction: function() {
  	        return [TOUCH_ACTION_NONE];
  	    },

  	    attrTest: function(input) {
  	        return this._super.attrTest.call(this, input) &&
  	            (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
  	    },

  	    emit: function(input) {
  	        if (input.scale !== 1) {
  	            var inOut = input.scale < 1 ? 'in' : 'out';
  	            input.additionalEvent = this.options.event + inOut;
  	        }
  	        this._super.emit.call(this, input);
  	    }
  	});

  	/**
  	 * Press
  	 * Recognized when the pointer is down for x ms without any movement.
  	 * @constructor
  	 * @extends Recognizer
  	 */
  	function PressRecognizer() {
  	    Recognizer.apply(this, arguments);

  	    this._timer = null;
  	    this._input = null;
  	}

  	inherit(PressRecognizer, Recognizer, {
  	    /**
  	     * @namespace
  	     * @memberof PressRecognizer
  	     */
  	    defaults: {
  	        event: 'press',
  	        pointers: 1,
  	        time: 251, // minimal time of the pointer to be pressed
  	        threshold: 9 // a minimal movement is ok, but keep it low
  	    },

  	    getTouchAction: function() {
  	        return [TOUCH_ACTION_AUTO];
  	    },

  	    process: function(input) {
  	        var options = this.options;
  	        var validPointers = input.pointers.length === options.pointers;
  	        var validMovement = input.distance < options.threshold;
  	        var validTime = input.deltaTime > options.time;

  	        this._input = input;

  	        // we only allow little movement
  	        // and we've reached an end event, so a tap is possible
  	        if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
  	            this.reset();
  	        } else if (input.eventType & INPUT_START) {
  	            this.reset();
  	            this._timer = setTimeoutContext(function() {
  	                this.state = STATE_RECOGNIZED;
  	                this.tryEmit();
  	            }, options.time, this);
  	        } else if (input.eventType & INPUT_END) {
  	            return STATE_RECOGNIZED;
  	        }
  	        return STATE_FAILED;
  	    },

  	    reset: function() {
  	        clearTimeout(this._timer);
  	    },

  	    emit: function(input) {
  	        if (this.state !== STATE_RECOGNIZED) {
  	            return;
  	        }

  	        if (input && (input.eventType & INPUT_END)) {
  	            this.manager.emit(this.options.event + 'up', input);
  	        } else {
  	            this._input.timeStamp = now();
  	            this.manager.emit(this.options.event, this._input);
  	        }
  	    }
  	});

  	/**
  	 * Rotate
  	 * Recognized when two or more pointer are moving in a circular motion.
  	 * @constructor
  	 * @extends AttrRecognizer
  	 */
  	function RotateRecognizer() {
  	    AttrRecognizer.apply(this, arguments);
  	}

  	inherit(RotateRecognizer, AttrRecognizer, {
  	    /**
  	     * @namespace
  	     * @memberof RotateRecognizer
  	     */
  	    defaults: {
  	        event: 'rotate',
  	        threshold: 0,
  	        pointers: 2
  	    },

  	    getTouchAction: function() {
  	        return [TOUCH_ACTION_NONE];
  	    },

  	    attrTest: function(input) {
  	        return this._super.attrTest.call(this, input) &&
  	            (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
  	    }
  	});

  	/**
  	 * Swipe
  	 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
  	 * @constructor
  	 * @extends AttrRecognizer
  	 */
  	function SwipeRecognizer() {
  	    AttrRecognizer.apply(this, arguments);
  	}

  	inherit(SwipeRecognizer, AttrRecognizer, {
  	    /**
  	     * @namespace
  	     * @memberof SwipeRecognizer
  	     */
  	    defaults: {
  	        event: 'swipe',
  	        threshold: 10,
  	        velocity: 0.3,
  	        direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
  	        pointers: 1
  	    },

  	    getTouchAction: function() {
  	        return PanRecognizer.prototype.getTouchAction.call(this);
  	    },

  	    attrTest: function(input) {
  	        var direction = this.options.direction;
  	        var velocity;

  	        if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
  	            velocity = input.overallVelocity;
  	        } else if (direction & DIRECTION_HORIZONTAL) {
  	            velocity = input.overallVelocityX;
  	        } else if (direction & DIRECTION_VERTICAL) {
  	            velocity = input.overallVelocityY;
  	        }

  	        return this._super.attrTest.call(this, input) &&
  	            direction & input.offsetDirection &&
  	            input.distance > this.options.threshold &&
  	            input.maxPointers == this.options.pointers &&
  	            abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
  	    },

  	    emit: function(input) {
  	        var direction = directionStr(input.offsetDirection);
  	        if (direction) {
  	            this.manager.emit(this.options.event + direction, input);
  	        }

  	        this.manager.emit(this.options.event, input);
  	    }
  	});

  	/**
  	 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
  	 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
  	 * a single tap.
  	 *
  	 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
  	 * multi-taps being recognized.
  	 * @constructor
  	 * @extends Recognizer
  	 */
  	function TapRecognizer() {
  	    Recognizer.apply(this, arguments);

  	    // previous time and center,
  	    // used for tap counting
  	    this.pTime = false;
  	    this.pCenter = false;

  	    this._timer = null;
  	    this._input = null;
  	    this.count = 0;
  	}

  	inherit(TapRecognizer, Recognizer, {
  	    /**
  	     * @namespace
  	     * @memberof PinchRecognizer
  	     */
  	    defaults: {
  	        event: 'tap',
  	        pointers: 1,
  	        taps: 1,
  	        interval: 300, // max time between the multi-tap taps
  	        time: 250, // max time of the pointer to be down (like finger on the screen)
  	        threshold: 9, // a minimal movement is ok, but keep it low
  	        posThreshold: 10 // a multi-tap can be a bit off the initial position
  	    },

  	    getTouchAction: function() {
  	        return [TOUCH_ACTION_MANIPULATION];
  	    },

  	    process: function(input) {
  	        var options = this.options;

  	        var validPointers = input.pointers.length === options.pointers;
  	        var validMovement = input.distance < options.threshold;
  	        var validTouchTime = input.deltaTime < options.time;

  	        this.reset();

  	        if ((input.eventType & INPUT_START) && (this.count === 0)) {
  	            return this.failTimeout();
  	        }

  	        // we only allow little movement
  	        // and we've reached an end event, so a tap is possible
  	        if (validMovement && validTouchTime && validPointers) {
  	            if (input.eventType != INPUT_END) {
  	                return this.failTimeout();
  	            }

  	            var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
  	            var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;

  	            this.pTime = input.timeStamp;
  	            this.pCenter = input.center;

  	            if (!validMultiTap || !validInterval) {
  	                this.count = 1;
  	            } else {
  	                this.count += 1;
  	            }

  	            this._input = input;

  	            // if tap count matches we have recognized it,
  	            // else it has began recognizing...
  	            var tapCount = this.count % options.taps;
  	            if (tapCount === 0) {
  	                // no failing requirements, immediately trigger the tap event
  	                // or wait as long as the multitap interval to trigger
  	                if (!this.hasRequireFailures()) {
  	                    return STATE_RECOGNIZED;
  	                } else {
  	                    this._timer = setTimeoutContext(function() {
  	                        this.state = STATE_RECOGNIZED;
  	                        this.tryEmit();
  	                    }, options.interval, this);
  	                    return STATE_BEGAN;
  	                }
  	            }
  	        }
  	        return STATE_FAILED;
  	    },

  	    failTimeout: function() {
  	        this._timer = setTimeoutContext(function() {
  	            this.state = STATE_FAILED;
  	        }, this.options.interval, this);
  	        return STATE_FAILED;
  	    },

  	    reset: function() {
  	        clearTimeout(this._timer);
  	    },

  	    emit: function() {
  	        if (this.state == STATE_RECOGNIZED) {
  	            this._input.tapCount = this.count;
  	            this.manager.emit(this.options.event, this._input);
  	        }
  	    }
  	});

  	/**
  	 * Simple way to create a manager with a default set of recognizers.
  	 * @param {HTMLElement} element
  	 * @param {Object} [options]
  	 * @constructor
  	 */
  	function Hammer(element, options) {
  	    options = options || {};
  	    options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
  	    return new Manager(element, options);
  	}

  	/**
  	 * @const {string}
  	 */
  	Hammer.VERSION = '2.0.7';

  	/**
  	 * default settings
  	 * @namespace
  	 */
  	Hammer.defaults = {
  	    /**
  	     * set if DOM events are being triggered.
  	     * But this is slower and unused by simple implementations, so disabled by default.
  	     * @type {Boolean}
  	     * @default false
  	     */
  	    domEvents: false,

  	    /**
  	     * The value for the touchAction property/fallback.
  	     * When set to `compute` it will magically set the correct value based on the added recognizers.
  	     * @type {String}
  	     * @default compute
  	     */
  	    touchAction: TOUCH_ACTION_COMPUTE,

  	    /**
  	     * @type {Boolean}
  	     * @default true
  	     */
  	    enable: true,

  	    /**
  	     * EXPERIMENTAL FEATURE -- can be removed/changed
  	     * Change the parent input target element.
  	     * If Null, then it is being set the to main element.
  	     * @type {Null|EventTarget}
  	     * @default null
  	     */
  	    inputTarget: null,

  	    /**
  	     * force an input class
  	     * @type {Null|Function}
  	     * @default null
  	     */
  	    inputClass: null,

  	    /**
  	     * Default recognizer setup when calling `Hammer()`
  	     * When creating a new Manager these will be skipped.
  	     * @type {Array}
  	     */
  	    preset: [
  	        // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
  	        [RotateRecognizer, {enable: false}],
  	        [PinchRecognizer, {enable: false}, ['rotate']],
  	        [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
  	        [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
  	        [TapRecognizer],
  	        [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
  	        [PressRecognizer]
  	    ],

  	    /**
  	     * Some CSS properties can be used to improve the working of Hammer.
  	     * Add them to this method and they will be set when creating a new Manager.
  	     * @namespace
  	     */
  	    cssProps: {
  	        /**
  	         * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
  	         * @type {String}
  	         * @default 'none'
  	         */
  	        userSelect: 'none',

  	        /**
  	         * Disable the Windows Phone grippers when pressing an element.
  	         * @type {String}
  	         * @default 'none'
  	         */
  	        touchSelect: 'none',

  	        /**
  	         * Disables the default callout shown when you touch and hold a touch target.
  	         * On iOS, when you touch and hold a touch target such as a link, Safari displays
  	         * a callout containing information about the link. This property allows you to disable that callout.
  	         * @type {String}
  	         * @default 'none'
  	         */
  	        touchCallout: 'none',

  	        /**
  	         * Specifies whether zooming is enabled. Used by IE10>
  	         * @type {String}
  	         * @default 'none'
  	         */
  	        contentZooming: 'none',

  	        /**
  	         * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
  	         * @type {String}
  	         * @default 'none'
  	         */
  	        userDrag: 'none',

  	        /**
  	         * Overrides the highlight color shown when the user taps a link or a JavaScript
  	         * clickable element in iOS. This property obeys the alpha value, if specified.
  	         * @type {String}
  	         * @default 'rgba(0,0,0,0)'
  	         */
  	        tapHighlightColor: 'rgba(0,0,0,0)'
  	    }
  	};

  	var STOP = 1;
  	var FORCED_STOP = 2;

  	/**
  	 * Manager
  	 * @param {HTMLElement} element
  	 * @param {Object} [options]
  	 * @constructor
  	 */
  	function Manager(element, options) {
  	    this.options = assign({}, Hammer.defaults, options || {});

  	    this.options.inputTarget = this.options.inputTarget || element;

  	    this.handlers = {};
  	    this.session = {};
  	    this.recognizers = [];
  	    this.oldCssProps = {};

  	    this.element = element;
  	    this.input = createInputInstance(this);
  	    this.touchAction = new TouchAction(this, this.options.touchAction);

  	    toggleCssProps(this, true);

  	    each(this.options.recognizers, function(item) {
  	        var recognizer = this.add(new (item[0])(item[1]));
  	        item[2] && recognizer.recognizeWith(item[2]);
  	        item[3] && recognizer.requireFailure(item[3]);
  	    }, this);
  	}

  	Manager.prototype = {
  	    /**
  	     * set options
  	     * @param {Object} options
  	     * @returns {Manager}
  	     */
  	    set: function(options) {
  	        assign(this.options, options);

  	        // Options that need a little more setup
  	        if (options.touchAction) {
  	            this.touchAction.update();
  	        }
  	        if (options.inputTarget) {
  	            // Clean up existing event listeners and reinitialize
  	            this.input.destroy();
  	            this.input.target = options.inputTarget;
  	            this.input.init();
  	        }
  	        return this;
  	    },

  	    /**
  	     * stop recognizing for this session.
  	     * This session will be discarded, when a new [input]start event is fired.
  	     * When forced, the recognizer cycle is stopped immediately.
  	     * @param {Boolean} [force]
  	     */
  	    stop: function(force) {
  	        this.session.stopped = force ? FORCED_STOP : STOP;
  	    },

  	    /**
  	     * run the recognizers!
  	     * called by the inputHandler function on every movement of the pointers (touches)
  	     * it walks through all the recognizers and tries to detect the gesture that is being made
  	     * @param {Object} inputData
  	     */
  	    recognize: function(inputData) {
  	        var session = this.session;
  	        if (session.stopped) {
  	            return;
  	        }

  	        // run the touch-action polyfill
  	        this.touchAction.preventDefaults(inputData);

  	        var recognizer;
  	        var recognizers = this.recognizers;

  	        // this holds the recognizer that is being recognized.
  	        // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
  	        // if no recognizer is detecting a thing, it is set to `null`
  	        var curRecognizer = session.curRecognizer;

  	        // reset when the last recognizer is recognized
  	        // or when we're in a new session
  	        if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
  	            curRecognizer = session.curRecognizer = null;
  	        }

  	        var i = 0;
  	        while (i < recognizers.length) {
  	            recognizer = recognizers[i];

  	            // find out if we are allowed try to recognize the input for this one.
  	            // 1.   allow if the session is NOT forced stopped (see the .stop() method)
  	            // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
  	            //      that is being recognized.
  	            // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
  	            //      this can be setup with the `recognizeWith()` method on the recognizer.
  	            if (session.stopped !== FORCED_STOP && ( // 1
  	                    !curRecognizer || recognizer == curRecognizer || // 2
  	                    recognizer.canRecognizeWith(curRecognizer))) { // 3
  	                recognizer.recognize(inputData);
  	            } else {
  	                recognizer.reset();
  	            }

  	            // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
  	            // current active recognizer. but only if we don't already have an active recognizer
  	            if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
  	                curRecognizer = session.curRecognizer = recognizer;
  	            }
  	            i++;
  	        }
  	    },

  	    /**
  	     * get a recognizer by its event name.
  	     * @param {Recognizer|String} recognizer
  	     * @returns {Recognizer|Null}
  	     */
  	    get: function(recognizer) {
  	        if (recognizer instanceof Recognizer) {
  	            return recognizer;
  	        }

  	        var recognizers = this.recognizers;
  	        for (var i = 0; i < recognizers.length; i++) {
  	            if (recognizers[i].options.event == recognizer) {
  	                return recognizers[i];
  	            }
  	        }
  	        return null;
  	    },

  	    /**
  	     * add a recognizer to the manager
  	     * existing recognizers with the same event name will be removed
  	     * @param {Recognizer} recognizer
  	     * @returns {Recognizer|Manager}
  	     */
  	    add: function(recognizer) {
  	        if (invokeArrayArg(recognizer, 'add', this)) {
  	            return this;
  	        }

  	        // remove existing
  	        var existing = this.get(recognizer.options.event);
  	        if (existing) {
  	            this.remove(existing);
  	        }

  	        this.recognizers.push(recognizer);
  	        recognizer.manager = this;

  	        this.touchAction.update();
  	        return recognizer;
  	    },

  	    /**
  	     * remove a recognizer by name or instance
  	     * @param {Recognizer|String} recognizer
  	     * @returns {Manager}
  	     */
  	    remove: function(recognizer) {
  	        if (invokeArrayArg(recognizer, 'remove', this)) {
  	            return this;
  	        }

  	        recognizer = this.get(recognizer);

  	        // let's make sure this recognizer exists
  	        if (recognizer) {
  	            var recognizers = this.recognizers;
  	            var index = inArray(recognizers, recognizer);

  	            if (index !== -1) {
  	                recognizers.splice(index, 1);
  	                this.touchAction.update();
  	            }
  	        }

  	        return this;
  	    },

  	    /**
  	     * bind event
  	     * @param {String} events
  	     * @param {Function} handler
  	     * @returns {EventEmitter} this
  	     */
  	    on: function(events, handler) {
  	        if (events === undefined$1) {
  	            return;
  	        }
  	        if (handler === undefined$1) {
  	            return;
  	        }

  	        var handlers = this.handlers;
  	        each(splitStr(events), function(event) {
  	            handlers[event] = handlers[event] || [];
  	            handlers[event].push(handler);
  	        });
  	        return this;
  	    },

  	    /**
  	     * unbind event, leave emit blank to remove all handlers
  	     * @param {String} events
  	     * @param {Function} [handler]
  	     * @returns {EventEmitter} this
  	     */
  	    off: function(events, handler) {
  	        if (events === undefined$1) {
  	            return;
  	        }

  	        var handlers = this.handlers;
  	        each(splitStr(events), function(event) {
  	            if (!handler) {
  	                delete handlers[event];
  	            } else {
  	                handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
  	            }
  	        });
  	        return this;
  	    },

  	    /**
  	     * emit event to the listeners
  	     * @param {String} event
  	     * @param {Object} data
  	     */
  	    emit: function(event, data) {
  	        // we also want to trigger dom events
  	        if (this.options.domEvents) {
  	            triggerDomEvent(event, data);
  	        }

  	        // no handlers, so skip it all
  	        var handlers = this.handlers[event] && this.handlers[event].slice();
  	        if (!handlers || !handlers.length) {
  	            return;
  	        }

  	        data.type = event;
  	        data.preventDefault = function() {
  	            data.srcEvent.preventDefault();
  	        };

  	        var i = 0;
  	        while (i < handlers.length) {
  	            handlers[i](data);
  	            i++;
  	        }
  	    },

  	    /**
  	     * destroy the manager and unbinds all events
  	     * it doesn't unbind dom events, that is the user own responsibility
  	     */
  	    destroy: function() {
  	        this.element && toggleCssProps(this, false);

  	        this.handlers = {};
  	        this.session = {};
  	        this.input.destroy();
  	        this.element = null;
  	    }
  	};

  	/**
  	 * add/remove the css properties as defined in manager.options.cssProps
  	 * @param {Manager} manager
  	 * @param {Boolean} add
  	 */
  	function toggleCssProps(manager, add) {
  	    var element = manager.element;
  	    if (!element.style) {
  	        return;
  	    }
  	    var prop;
  	    each(manager.options.cssProps, function(value, name) {
  	        prop = prefixed(element.style, name);
  	        if (add) {
  	            manager.oldCssProps[prop] = element.style[prop];
  	            element.style[prop] = value;
  	        } else {
  	            element.style[prop] = manager.oldCssProps[prop] || '';
  	        }
  	    });
  	    if (!add) {
  	        manager.oldCssProps = {};
  	    }
  	}

  	/**
  	 * trigger dom event
  	 * @param {String} event
  	 * @param {Object} data
  	 */
  	function triggerDomEvent(event, data) {
  	    var gestureEvent = document.createEvent('Event');
  	    gestureEvent.initEvent(event, true, true);
  	    gestureEvent.gesture = data;
  	    data.target.dispatchEvent(gestureEvent);
  	}

  	assign(Hammer, {
  	    INPUT_START: INPUT_START,
  	    INPUT_MOVE: INPUT_MOVE,
  	    INPUT_END: INPUT_END,
  	    INPUT_CANCEL: INPUT_CANCEL,

  	    STATE_POSSIBLE: STATE_POSSIBLE,
  	    STATE_BEGAN: STATE_BEGAN,
  	    STATE_CHANGED: STATE_CHANGED,
  	    STATE_ENDED: STATE_ENDED,
  	    STATE_RECOGNIZED: STATE_RECOGNIZED,
  	    STATE_CANCELLED: STATE_CANCELLED,
  	    STATE_FAILED: STATE_FAILED,

  	    DIRECTION_NONE: DIRECTION_NONE,
  	    DIRECTION_LEFT: DIRECTION_LEFT,
  	    DIRECTION_RIGHT: DIRECTION_RIGHT,
  	    DIRECTION_UP: DIRECTION_UP,
  	    DIRECTION_DOWN: DIRECTION_DOWN,
  	    DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
  	    DIRECTION_VERTICAL: DIRECTION_VERTICAL,
  	    DIRECTION_ALL: DIRECTION_ALL,

  	    Manager: Manager,
  	    Input: Input,
  	    TouchAction: TouchAction,

  	    TouchInput: TouchInput,
  	    MouseInput: MouseInput,
  	    PointerEventInput: PointerEventInput,
  	    TouchMouseInput: TouchMouseInput,
  	    SingleTouchInput: SingleTouchInput,

  	    Recognizer: Recognizer,
  	    AttrRecognizer: AttrRecognizer,
  	    Tap: TapRecognizer,
  	    Pan: PanRecognizer,
  	    Swipe: SwipeRecognizer,
  	    Pinch: PinchRecognizer,
  	    Rotate: RotateRecognizer,
  	    Press: PressRecognizer,

  	    on: addEventListeners,
  	    off: removeEventListeners,
  	    each: each,
  	    merge: merge,
  	    extend: extend,
  	    assign: assign,
  	    inherit: inherit,
  	    bindFn: bindFn,
  	    prefixed: prefixed
  	});

  	// this prevents errors when Hammer is loaded in the presence of an AMD
  	//  style loader but by script tag, not by the loader.
  	var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
  	freeGlobal.Hammer = Hammer;

  	if (typeof undefined$1 === 'function' && undefined$1.amd) {
  	    undefined$1(function() {
  	        return Hammer;
  	    });
  	} else if (module.exports) {
  	    module.exports = Hammer;
  	} else {
  	    window[exportName] = Hammer;
  	}

  	})(window, document, 'Hammer');
  } (hammer));

  var Hammer = hammer.exports;

  var MIN_ZOOM = 0.2,
      MAX_ZOOM = 4;

  var mouseEvents = [
    'mousedown',
    'mouseup',
    'mouseover',
    'mouseout',
    'click',
    'dblclick'
  ];

  function get(service, injector) {
    return injector.get(service, false);
  }

  function stopEvent(event) {

    event.preventDefault();

    if (typeof event.stopPropagation === 'function') {
      event.stopPropagation();
    } else if (event.srcEvent && typeof event.srcEvent.stopPropagation === 'function') {

      // iPhone & iPad
      event.srcEvent.stopPropagation();
    }

    if (typeof event.stopImmediatePropagation === 'function') {
      event.stopImmediatePropagation();
    }
  }


  function createTouchRecognizer(node) {

    function stopMouse(event) {

      forEach$1(mouseEvents, function(e) {
        componentEvent.bind(node, e, stopEvent, true);
      });
    }

    function allowMouse(event) {
      setTimeout(function() {
        forEach$1(mouseEvents, function(e) {
          componentEvent.unbind(node, e, stopEvent, true);
        });
      }, 500);
    }

    componentEvent.bind(node, 'touchstart', stopMouse, true);
    componentEvent.bind(node, 'touchend', allowMouse, true);
    componentEvent.bind(node, 'touchcancel', allowMouse, true);

    // A touch event recognizer that handles
    // touch events only (we know, we can already handle
    // mouse events out of the box)

    var recognizer = new Hammer.Manager(node, {
      inputClass: Hammer.TouchInput,
      recognizers: [],
      domEvents: true
    });


    var tap = new Hammer.Tap();
    var pan = new Hammer.Pan({ threshold: 10 });
    var press = new Hammer.Press();
    var pinch = new Hammer.Pinch();

    var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });

    pinch.requireFailure(pan);
    pinch.requireFailure(press);

    recognizer.add([ pan, press, pinch, doubleTap, tap ]);

    recognizer.reset = function(force) {
      var recognizers = this.recognizers,
          session = this.session;

      if (session.stopped) {
        return;
      }

      recognizer.stop(force);

      setTimeout(function() {
        var i, r;
        for (i = 0; (r = recognizers[i]); i++) {
          r.reset();
          r.state = 8; // FAILED STATE
        }

        session.curRecognizer = null;
      }, 0);
    };

    recognizer.on('hammer.input', function(event) {
      if (event.srcEvent.defaultPrevented) {
        recognizer.reset(true);
      }
    });

    return recognizer;
  }

  /**
   * A plugin that provides touch events for elements.
   *
   * @param {EventBus} eventBus
   * @param {InteractionEvents} interactionEvents
   */
  function TouchInteractionEvents(
      injector, canvas, eventBus,
      elementRegistry, interactionEvents) {

    // optional integrations
    var dragging = get('dragging', injector),
        move = get('move', injector),
        contextPad = get('contextPad', injector),
        palette = get('palette', injector);

    // the touch recognizer
    var recognizer;

    function handler(type, buttonType) {

      return function(event) {

        var gfx = getGfx(event.target),
            element = gfx && elementRegistry.get(gfx);

        // translate into an actual mouse click event
        if (buttonType) {
          event.srcEvent.button = buttonType;
        }

        return interactionEvents.fire(type, event, element);
      };
    }


    function getGfx(target) {
      var node = closest(target, 'svg, .djs-element', true);
      return node;
    }

    function initEvents(svg) {

      // touch recognizer
      recognizer = createTouchRecognizer(svg);

      function startGrabCanvas(event) {

        var lx = 0, ly = 0;

        function update(e) {

          var dx = e.deltaX - lx,
              dy = e.deltaY - ly;

          canvas.scroll({ dx: dx, dy: dy });

          lx = e.deltaX;
          ly = e.deltaY;
        }

        function end(e) {
          recognizer.off('panmove', update);
          recognizer.off('panend', end);
          recognizer.off('pancancel', end);
        }

        recognizer.on('panmove', update);
        recognizer.on('panend', end);
        recognizer.on('pancancel', end);
      }

      function startGrab(event) {

        var gfx = getGfx(event.target),
            element = gfx && elementRegistry.get(gfx);

        // recognizer
        if (move && canvas.getRootElement() !== element) {
          return move.start(event, element, true);
        } else {
          startGrabCanvas();
        }
      }

      function startZoom(e) {

        var zoom = canvas.zoom(),
            mid = e.center;

        function update(e) {

          var ratio = 1 - (1 - e.scale) / 1.50,
              newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom));

          canvas.zoom(newZoom, mid);

          stopEvent(e);
        }

        function end(e) {
          recognizer.off('pinchmove', update);
          recognizer.off('pinchend', end);
          recognizer.off('pinchcancel', end);

          recognizer.reset(true);
        }

        recognizer.on('pinchmove', update);
        recognizer.on('pinchend', end);
        recognizer.on('pinchcancel', end);
      }

      recognizer.on('tap', handler('element.click'));
      recognizer.on('doubletap', handler('element.dblclick', 1));

      recognizer.on('panstart', startGrab);
      recognizer.on('press', startGrab);

      recognizer.on('pinchstart', startZoom);
    }

    if (dragging) {

      // simulate hover during dragging
      eventBus.on('drag.move', function(event) {

        var originalEvent = event.originalEvent;

        if (!originalEvent || originalEvent instanceof MouseEvent) {
          return;
        }

        var position = toPoint(originalEvent);

        // this gets really expensive ...
        var node = document.elementFromPoint(position.x, position.y),
            gfx = getGfx(node),
            element = gfx && elementRegistry.get(gfx);

        if (element !== event.hover) {
          if (event.hover) {
            dragging.out(event);
          }

          if (element) {
            dragging.hover({ element: element, gfx: gfx });

            event.hover = element;
            event.hoverGfx = gfx;
          }
        }
      });
    }

    if (contextPad) {

      eventBus.on('contextPad.create', function(event) {
        var node = event.pad.html;

        // touch recognizer
        var padRecognizer = createTouchRecognizer(node);

        padRecognizer.on('panstart', function(event) {
          contextPad.trigger('dragstart', event, true);
        });

        padRecognizer.on('press', function(event) {
          contextPad.trigger('dragstart', event, true);
        });

        padRecognizer.on('tap', function(event) {
          contextPad.trigger('click', event);
        });
      });
    }

    if (palette) {
      eventBus.on('palette.create', function(event) {
        var node = event.container;

        // touch recognizer
        var padRecognizer = createTouchRecognizer(node);

        padRecognizer.on('panstart', function(event) {
          palette.trigger('dragstart', event, true);
        });

        padRecognizer.on('press', function(event) {
          palette.trigger('dragstart', event, true);
        });

        padRecognizer.on('tap', function(event) {
          palette.trigger('click', event);
        });
      });
    }

    eventBus.on('canvas.init', function(event) {
      initEvents(event.svg);
    });
  }


  TouchInteractionEvents.$inject = [
    'injector',
    'canvas',
    'eventBus',
    'elementRegistry',
    'interactionEvents',
    'touchFix'
  ];

  function TouchFix(canvas, eventBus) {

    var self = this;

    eventBus.on('canvas.init', function(e) {
      self.addBBoxMarker(e.svg);
    });
  }

  TouchFix.$inject = [ 'canvas', 'eventBus' ];


  /**
   * Safari mobile (iOS 7) does not fire touchstart event in <SVG> element
   * if there is no shape between 0,0 and viewport elements origin.
   *
   * So touchstart event is only fired when the <g class="viewport"> element was hit.
   * Putting an element over and below the 'viewport' fixes that behavior.
   */
  TouchFix.prototype.addBBoxMarker = function(svg) {

    var markerStyle = {
      fill: 'none',
      class: 'outer-bound-marker'
    };

    var rect1 = create$1('rect');
    attr(rect1, {
      x: -10000,
      y: 10000,
      width: 10,
      height: 10
    });
    attr(rect1, markerStyle);

    append(svg, rect1);

    var rect2 = create$1('rect');
    attr(rect2, {
      x: 10000,
      y: 10000,
      width: 10,
      height: 10
    });
    attr(rect2, markerStyle);

    append(svg, rect2);
  };

  var TouchModule$1 = {
    __depends__: [ InteractionEventsModule$1 ],
    __init__: [ 'touchInteractionEvents' ],
    touchInteractionEvents: [ 'type', TouchInteractionEvents ],
    touchFix: [ 'type', TouchFix ]
  };

  var TouchModule = {
    __depends__: [
      TouchModule$1
    ]
  };

  function last(arr) {
    return arr && arr[arr.length - 1];
  }

  function sortTopOrMiddle(element) {
    return element.y;
  }

  function sortLeftOrCenter(element) {
    return element.x;
  }

  /**
   * Sorting functions for different types of alignment
   *
   * @type {Object}
   *
   * @return {Function}
   */
  var ALIGNMENT_SORTING = {
    left: sortLeftOrCenter,
    center: sortLeftOrCenter,
    right: function(element) {
      return element.x + element.width;
    },
    top: sortTopOrMiddle,
    middle: sortTopOrMiddle,
    bottom: function(element) {
      return element.y + element.height;
    }
  };


  function AlignElements$1(modeling, rules) {
    this._modeling = modeling;
    this._rules = rules;
  }

  AlignElements$1.$inject = [ 'modeling', 'rules' ];


  /**
   * Get the relevant "axis" and "dimension" related to the current type of alignment
   *
   * @param  {string} type left|right|center|top|bottom|middle
   *
   * @return {Object} { axis, dimension }
   */
  AlignElements$1.prototype._getOrientationDetails = function(type) {
    var vertical = [ 'top', 'bottom', 'middle' ],
        axis = 'x',
        dimension = 'width';

    if (vertical.indexOf(type) !== -1) {
      axis = 'y';
      dimension = 'height';
    }

    return {
      axis: axis,
      dimension: dimension
    };
  };

  AlignElements$1.prototype._isType = function(type, types) {
    return types.indexOf(type) !== -1;
  };

  /**
   * Get a point on the relevant axis where elements should align to
   *
   * @param  {string} type left|right|center|top|bottom|middle
   * @param  {Array} sortedElements
   *
   * @return {Object}
   */
  AlignElements$1.prototype._alignmentPosition = function(type, sortedElements) {
    var orientation = this._getOrientationDetails(type),
        axis = orientation.axis,
        dimension = orientation.dimension,
        alignment = {},
        centers = {},
        hasSharedCenters = false,
        centeredElements,
        firstElement,
        lastElement;

    function getMiddleOrTop(first, last) {
      return Math.round((first[axis] + last[axis] + last[dimension]) / 2);
    }

    if (this._isType(type, [ 'left', 'top' ])) {
      alignment[type] = sortedElements[0][axis];

    } else if (this._isType(type, [ 'right', 'bottom' ])) {
      lastElement = last(sortedElements);

      alignment[type] = lastElement[axis] + lastElement[dimension];

    } else if (this._isType(type, [ 'center', 'middle' ])) {

      // check if there is a center shared by more than one shape
      // if not, just take the middle of the range
      forEach$1(sortedElements, function(element) {
        var center = element[axis] + Math.round(element[dimension] / 2);

        if (centers[center]) {
          centers[center].elements.push(element);
        } else {
          centers[center] = {
            elements: [ element ],
            center: center
          };
        }
      });

      centeredElements = sortBy(centers, function(center) {
        if (center.elements.length > 1) {
          hasSharedCenters = true;
        }

        return center.elements.length;
      });

      if (hasSharedCenters) {
        alignment[type] = last(centeredElements).center;

        return alignment;
      }

      firstElement = sortedElements[0];

      sortedElements = sortBy(sortedElements, function(element) {
        return element[axis] + element[dimension];
      });

      lastElement = last(sortedElements);

      alignment[type] = getMiddleOrTop(firstElement, lastElement);
    }

    return alignment;
  };

  /**
   * Executes the alignment of a selection of elements
   *
   * @param  {Array} elements
   * @param  {string} type left|right|center|top|bottom|middle
   */
  AlignElements$1.prototype.trigger = function(elements, type) {
    var modeling = this._modeling,
        allowed;

    // filter out elements which cannot be aligned
    var filteredElements = filter(elements, function(element) {
      return !(element.waypoints || element.host || element.labelTarget);
    });

    // filter out elements via rules
    allowed = this._rules.allowed('elements.align', { elements: filteredElements });
    if (isArray$3(allowed)) {
      filteredElements = allowed;
    }

    if (filteredElements.length < 2 || !allowed) {
      return;
    }

    var sortFn = ALIGNMENT_SORTING[type];

    var sortedElements = sortBy(filteredElements, sortFn);

    var alignment = this._alignmentPosition(type, sortedElements);

    modeling.alignElements(sortedElements, alignment);
  };

  var AlignElementsModule$1 = {
    __init__: [ 'alignElements' ],
    alignElements: [ 'type', AlignElements$1 ]
  };

  var entrySelector = '.entry';

  var DEFAULT_PRIORITY$2 = 1000;
  var CONTEXT_PAD_PADDING = 12;


  /**
   * @typedef {djs.model.Base|djs.model.Base[]} ContextPadTarget
   */

  /**
   * A context pad that displays element specific, contextual actions next
   * to a diagram element.
   *
   * @param {Canvas} canvas
   * @param {Object} config
   * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
   * @param {number} [config.scale.min]
   * @param {number} [config.scale.max]
   * @param {EventBus} eventBus
   * @param {Overlays} overlays
   */
  function ContextPad(canvas, config, eventBus, overlays) {

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._overlays = overlays;

    var scale = isDefined(config && config.scale) ? config.scale : {
      min: 1,
      max: 1.5
    };

    this._overlaysConfig = {
      scale: scale
    };

    this._current = null;

    this._init();
  }

  ContextPad.$inject = [
    'canvas',
    'config.contextPad',
    'eventBus',
    'overlays'
  ];


  /**
   * Registers events needed for interaction with other components.
   */
  ContextPad.prototype._init = function() {
    var self = this;

    this._eventBus.on('selection.changed', function(event) {

      var selection = event.newSelection;

      var target = selection.length
        ? selection.length === 1
          ? selection[0]
          : selection
        : null;

      if (target) {
        self.open(target, true);
      } else {
        self.close();
      }
    });

    this._eventBus.on('elements.changed', function(event) {
      var elements = event.elements,
          current = self._current;

      if (!current) {
        return;
      }

      var currentTarget = current.target;

      var currentChanged = some(
        isArray$3(currentTarget) ? currentTarget : [ currentTarget ],
        function(element) {
          return includes$8(elements, element);
        }
      );

      // re-open if elements in current selection changed
      if (currentChanged) {
        self.open(currentTarget, true);
      }
    });
  };


  /**
   * Register context pad provider.
   *
   * @param  {number} [priority=1000]
   * @param  {ContextPadProvider} provider
   *
   * @example
   * const contextPadProvider = {
   *   getContextPadEntries: function(element) {
   *     return function(entries) {
   *       return {
   *         ...entries,
   *         'entry-1': {
   *           label: 'My Entry',
   *           action: function() { alert("I have been clicked!"); }
   *         }
   *       };
   *     }
   *   },
   *
   *   getMultiElementContextPadEntries: function(elements) {
   *     // ...
   *   }
   * };
   *
   * contextPad.registerProvider(800, contextPadProvider);
   */
  ContextPad.prototype.registerProvider = function(priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY$2;
    }

    this._eventBus.on('contextPad.getProviders', priority, function(event) {
      event.providers.push(provider);
    });
  };


  /**
   * Get context pad entries for given elements.
   *
   * @param {ContextPadTarget} target
   *
   * @return {ContextPadEntryDescriptor[]} list of entries
   */
  ContextPad.prototype.getEntries = function(target) {
    var providers = this._getProviders();

    var provideFn = isArray$3(target)
      ? 'getMultiElementContextPadEntries'
      : 'getContextPadEntries';

    var entries = {};

    // loop through all providers and their entries.
    // group entries by id so that overriding an entry is possible
    forEach$1(providers, function(provider) {

      if (!isFunction(provider[provideFn])) {
        return;
      }

      var entriesOrUpdater = provider[provideFn](target);

      if (isFunction(entriesOrUpdater)) {
        entries = entriesOrUpdater(entries);
      } else {
        forEach$1(entriesOrUpdater, function(entry, id) {
          entries[id] = entry;
        });
      }
    });

    return entries;
  };


  /**
   * Trigger context pad action.
   *
   * @param  {string} action
   * @param  {Event} event
   * @param  {boolean} [autoActivate=false]
   */
  ContextPad.prototype.trigger = function(action, event, autoActivate) {

    var target = this._current.target,
        entries = this._current.entries,
        entry,
        handler,
        originalEvent,
        button = event.delegateTarget || event.target;

    if (!button) {
      return event.preventDefault();
    }

    entry = entries[attr$1(button, 'data-action')];
    handler = entry.action;

    originalEvent = event.originalEvent || event;

    // simple action (via callback function)
    if (isFunction(handler)) {
      if (action === 'click') {
        return handler(originalEvent, target, autoActivate);
      }
    } else {
      if (handler[action]) {
        return handler[action](originalEvent, target, autoActivate);
      }
    }

    // silence other actions
    event.preventDefault();
  };


  /**
   * Open the context pad for given elements.
   *
   * @param {ContextPadTarget} target
   * @param {boolean} [force=false] - Force re-opening context pad.
   */
  ContextPad.prototype.open = function(target, force) {
    if (!force && this.isOpen(target)) {
      return;
    }

    this.close();

    this._updateAndOpen(target);
  };

  ContextPad.prototype._getProviders = function() {

    var event = this._eventBus.createEvent({
      type: 'contextPad.getProviders',
      providers: []
    });

    this._eventBus.fire(event);

    return event.providers;
  };


  /**
   * @param {ContextPadTarget} target
   */
  ContextPad.prototype._updateAndOpen = function(target) {
    var entries = this.getEntries(target),
        pad = this.getPad(target),
        html = pad.html,
        image;

    forEach$1(entries, function(entry, id) {
      var grouping = entry.group || 'default',
          control = domify(entry.html || '<div class="entry" draggable="true"></div>'),
          container;

      attr$1(control, 'data-action', id);

      container = query('[data-group=' + cssEscape(grouping) + ']', html);
      if (!container) {
        container = domify('<div class="group"></div>');
        attr$1(container, 'data-group', grouping);

        html.appendChild(container);
      }

      container.appendChild(control);

      if (entry.className) {
        addClasses$1(control, entry.className);
      }

      if (entry.title) {
        attr$1(control, 'title', entry.title);
      }

      if (entry.imageUrl) {
        image = domify('<img>');
        attr$1(image, 'src', entry.imageUrl);
        image.style.width = '100%';
        image.style.height = '100%';

        control.appendChild(image);
      }
    });

    classes$1(html).add('open');

    this._current = {
      target: target,
      entries: entries,
      pad: pad
    };

    this._eventBus.fire('contextPad.open', { current: this._current });
  };

  /**
   * @param {ContextPadTarget} target
   *
   * @return {Overlay}
   */
  ContextPad.prototype.getPad = function(target) {
    if (this.isOpen()) {
      return this._current.pad;
    }

    var self = this;

    var overlays = this._overlays;

    var html = domify('<div class="djs-context-pad"></div>');

    var position = this._getPosition(target);

    var overlaysConfig = assign({
      html: html
    }, this._overlaysConfig, position);

    delegate.bind(html, entrySelector, 'click', function(event) {
      self.trigger('click', event);
    });

    delegate.bind(html, entrySelector, 'dragstart', function(event) {
      self.trigger('dragstart', event);
    });

    // stop propagation of mouse events
    componentEvent.bind(html, 'mousedown', function(event) {
      event.stopPropagation();
    });

    var activeRootElement = this._canvas.getRootElement();

    this._overlayId = overlays.add(activeRootElement, 'context-pad', overlaysConfig);

    var pad = overlays.get(this._overlayId);

    this._eventBus.fire('contextPad.create', {
      target: target,
      pad: pad
    });

    return pad;
  };


  /**
   * Close the context pad
   */
  ContextPad.prototype.close = function() {
    if (!this.isOpen()) {
      return;
    }

    this._overlays.remove(this._overlayId);

    this._overlayId = null;

    this._eventBus.fire('contextPad.close', { current: this._current });

    this._current = null;
  };

  /**
   * Check if pad is open.
   *
   * If target is provided, check if it is opened
   * for the given target (single or multiple elements).
   *
   * @param {ContextPadTarget} [target]
   * @return {boolean}
   */
  ContextPad.prototype.isOpen = function(target) {
    var current = this._current;

    if (!current) {
      return false;
    }

    // basic no-args is open check
    if (!target) {
      return true;
    }

    var currentTarget = current.target;

    // strict handling of single vs. multi-selection
    if (isArray$3(target) !== isArray$3(currentTarget)) {
      return false;
    }

    if (isArray$3(target)) {
      return (
        target.length === currentTarget.length &&
        every(target, function(element) {
          return includes$8(currentTarget, element);
        })
      );
    } else {
      return currentTarget === target;
    }
  };


  /**
   * Get contex pad position.
   *
   * @param {ContextPadTarget} target
   * @return {Bounds}
   */
  ContextPad.prototype._getPosition = function(target) {

    var elements = isArray$3(target) ? target : [ target ];
    var bBox = getBBox(elements);

    return {
      position: {
        left: bBox.x + bBox.width + CONTEXT_PAD_PADDING,
        top: bBox.y - CONTEXT_PAD_PADDING / 2
      }
    };
  };


  // helpers //////////

  function addClasses$1(element, classNames) {
    var classes = classes$1(element);

    classNames = isArray$3(classNames) ? classNames : classNames.split(/\s+/g);

    classNames.forEach(function(cls) {
      classes.add(cls);
    });
  }

  /**
   * @param {any[]} array
   * @param {any} item
   *
   * @return {boolean}
   */
  function includes$8(array, item) {
    return array.indexOf(item) !== -1;
  }

  var ContextPadModule$1 = {
    __depends__: [
      InteractionEventsModule$1,
      OverlaysModule
    ],
    contextPad: [ 'type', ContextPad ]
  };

  var DATA_REF = 'data-id';

  var CLOSE_EVENTS = [
    'contextPad.close',
    'canvas.viewbox.changing',
    'commandStack.changed'
  ];

  var DEFAULT_PRIORITY$1 = 1000;


  /**
   * A popup menu that can be used to display a list of actions anywhere in the canvas.
   *
   * @param {Object} config
   * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
   * @param {number} [config.scale.min]
   * @param {number} [config.scale.max]
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   *
   * @class
   * @constructor
   */
  function PopupMenu(config, eventBus, canvas) {

    var scale = isDefined(config && config.scale) ? config.scale : {
      min: 1,
      max: 1.5
    };

    this._config = {
      scale: scale
    };

    this._eventBus = eventBus;
    this._canvas = canvas;
    this._providers = {};
    this._current = {};
  }

  PopupMenu.$inject = [
    'config.popupMenu',
    'eventBus',
    'canvas'
  ];

  /**
   * Registers a popup menu provider
   *
   * @param  {string} id
   * @param {number} [priority=1000]
   * @param  {Object} provider
   *
   * @example
   * const popupMenuProvider = {
   *   getPopupMenuEntries: function(element) {
   *     return {
   *       'entry-1': {
   *         label: 'My Entry',
   *         action: function() { alert("I have been clicked!"); }
   *       }
   *     }
   *   }
   * };
   *
   * popupMenu.registerProvider('myMenuID', popupMenuProvider);
   */
  PopupMenu.prototype.registerProvider = function(id, priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY$1;
    }

    this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
      event.providers.push(provider);
    });
  };

  /**
   * Determine if the popup menu has entries.
   *
   * @return {boolean} true if empty
   */
  PopupMenu.prototype.isEmpty = function(element, providerId) {
    if (!element) {
      throw new Error('element parameter is missing');
    }

    if (!providerId) {
      throw new Error('providerId parameter is missing');
    }

    var providers = this._getProviders(providerId);

    if (!providers) {
      return true;
    }

    var entries = this._getEntries(element, providers),
        headerEntries = this._getHeaderEntries(element, providers);

    var hasEntries = size(entries) > 0,
        hasHeaderEntries = headerEntries && size(headerEntries) > 0;

    return !hasEntries && !hasHeaderEntries;
  };


  /**
   * Create entries and open popup menu at given position
   *
   * @param  {Object} element
   * @param  {string} id provider id
   * @param  {Object} position
   *
   * @return {Object} popup menu instance
   */
  PopupMenu.prototype.open = function(element, id, position) {

    var providers = this._getProviders(id);

    if (!element) {
      throw new Error('Element is missing');
    }

    if (!providers || !providers.length) {
      throw new Error('No registered providers for: ' + id);
    }

    if (!position) {
      throw new Error('the position argument is missing');
    }

    if (this.isOpen()) {
      this.close();
    }

    this._emit('open');

    var current = this._current = {
      className: id,
      element: element,
      position: position
    };

    var entries = this._getEntries(element, providers),
        headerEntries = this._getHeaderEntries(element, providers);

    current.entries = assign({}, entries, headerEntries);

    current.container = this._createContainer(id);

    if (size(headerEntries)) {
      current.container.appendChild(
        this._createEntries(headerEntries, 'djs-popup-header')
      );
    }

    if (size(entries)) {
      current.container.appendChild(
        this._createEntries(entries, 'djs-popup-body')
      );
    }

    var canvas = this._canvas,
        parent = canvas.getContainer();

    this._attachContainer(current.container, parent, position.cursor);
    this._bindAutoClose();
  };


  /**
   * Removes the popup menu and unbinds the event handlers.
   */
  PopupMenu.prototype.close = function() {

    if (!this.isOpen()) {
      return;
    }

    this._emit('close');

    this._unbindAutoClose();
    remove$2(this._current.container);
    this._current.container = null;
  };


  /**
   * Determine if an open popup menu exist.
   *
   * @return {boolean} true if open
   */
  PopupMenu.prototype.isOpen = function() {
    return !!this._current.container;
  };


  /**
   * Trigger an action associated with an entry.
   *
   * @param {Object} event
   *
   * @return the result of the action callback, if any
   */
  PopupMenu.prototype.trigger = function(event) {

    // silence other actions
    event.preventDefault();

    var element = event.delegateTarget || event.target,
        entryId = attr$1(element, DATA_REF);

    var entry = this._getEntry(entryId);

    if (entry.action) {
      return entry.action.call(null, event, entry);
    }
  };

  PopupMenu.prototype._getProviders = function(id) {

    var event = this._eventBus.createEvent({
      type: 'popupMenu.getProviders.' + id,
      providers: []
    });

    this._eventBus.fire(event);

    return event.providers;
  };

  PopupMenu.prototype._getEntries = function(element, providers) {

    var entries = {};

    forEach$1(providers, function(provider) {

      // handle legacy method
      if (!provider.getPopupMenuEntries) {
        forEach$1(provider.getEntries(element), function(entry) {
          var id = entry.id;

          if (!id) {
            throw new Error('every entry must have the id property set');
          }

          entries[id] = omit(entry, [ 'id' ]);
        });

        return;
      }

      var entriesOrUpdater = provider.getPopupMenuEntries(element);

      if (isFunction(entriesOrUpdater)) {
        entries = entriesOrUpdater(entries);
      } else {
        forEach$1(entriesOrUpdater, function(entry, id) {
          entries[id] = entry;
        });
      }
    });

    return entries;
  };

  PopupMenu.prototype._getHeaderEntries = function(element, providers) {

    var entries = {};

    forEach$1(providers, function(provider) {

      // handle legacy method
      if (!provider.getPopupMenuHeaderEntries) {
        if (!provider.getHeaderEntries) {
          return;
        }

        forEach$1(provider.getHeaderEntries(element), function(entry) {
          var id = entry.id;

          if (!id) {
            throw new Error('every entry must have the id property set');
          }

          entries[id] = omit(entry, [ 'id' ]);
        });

        return;
      }

      var entriesOrUpdater = provider.getPopupMenuHeaderEntries(element);

      if (isFunction(entriesOrUpdater)) {
        entries = entriesOrUpdater(entries);
      } else {
        forEach$1(entriesOrUpdater, function(entry, id) {
          entries[id] = entry;
        });
      }
    });

    return entries;


  };

  /**
   * Gets an entry instance (either entry or headerEntry) by id.
   *
   * @param  {string} entryId
   *
   * @return {Object} entry instance
   */
  PopupMenu.prototype._getEntry = function(entryId) {

    var entry = this._current.entries[entryId];

    if (!entry) {
      throw new Error('entry not found');
    }

    return entry;
  };

  PopupMenu.prototype._emit = function(eventName) {
    this._eventBus.fire('popupMenu.' + eventName);
  };

  /**
   * Creates the popup menu container.
   *
   * @return {Object} a DOM container
   */
  PopupMenu.prototype._createContainer = function(id) {
    var container = domify('<div class="djs-popup">'),
        position = this._current.position,
        className = this._current.className;

    assign$1(container, {
      position: 'absolute',
      left: position.x + 'px',
      top: position.y + 'px',
      visibility: 'hidden'
    });

    classes$1(container).add(className);

    attr$1(container, 'data-popup', id);

    return container;
  };


  /**
   * Attaches the container to the DOM.
   *
   * @param {Object} container
   * @param {Object} parent
   */
  PopupMenu.prototype._attachContainer = function(container, parent, cursor) {
    var self = this;

    // Event handler
    delegate.bind(container, '.entry' ,'click', function(event) {
      self.trigger(event);
    });

    this._updateScale(container);

    // Attach to DOM
    parent.appendChild(container);

    if (cursor) {
      this._assureIsInbounds(container, cursor);
    }

    // display after position adjustment to avoid flickering
    assign$1(container, { visibility: 'visible' });
  };


  /**
   * Updates popup style.transform with respect to the config and zoom level.
   *
   * @method _updateScale
   *
   * @param {Object} container
   */
  PopupMenu.prototype._updateScale = function(container) {
    var zoom = this._canvas.zoom();

    var scaleConfig = this._config.scale,
        minScale,
        maxScale,
        scale = zoom;

    if (scaleConfig !== true) {

      if (scaleConfig === false) {
        minScale = 1;
        maxScale = 1;
      } else {
        minScale = scaleConfig.min;
        maxScale = scaleConfig.max;
      }

      if (isDefined(minScale) && zoom < minScale) {
        scale = minScale;
      }

      if (isDefined(maxScale) && zoom > maxScale) {
        scale = maxScale;
      }

    }

    setTransform(container, 'scale(' + scale + ')');
  };


  /**
   * Make sure that the menu is always fully shown
   *
   * @method function
   *
   * @param  {Object} container
   * @param  {Position} cursor {x, y}
   */
  PopupMenu.prototype._assureIsInbounds = function(container, cursor) {
    var canvas = this._canvas,
        clientRect = canvas._container.getBoundingClientRect();

    var containerX = container.offsetLeft,
        containerY = container.offsetTop,
        containerWidth = container.scrollWidth,
        containerHeight = container.scrollHeight,
        overAxis = {},
        left, top;

    var cursorPosition = {
      x: cursor.x - clientRect.left,
      y: cursor.y - clientRect.top
    };

    if (containerX + containerWidth > clientRect.width) {
      overAxis.x = true;
    }

    if (containerY + containerHeight > clientRect.height) {
      overAxis.y = true;
    }

    if (overAxis.x && overAxis.y) {
      left = cursorPosition.x - containerWidth + 'px';
      top = cursorPosition.y - containerHeight + 'px';
    } else if (overAxis.x) {
      left = cursorPosition.x - containerWidth + 'px';
      top = cursorPosition.y + 'px';
    } else if (overAxis.y && cursorPosition.y < containerHeight) {
      left = cursorPosition.x + 'px';
      top = 10 + 'px';
    } else if (overAxis.y) {
      left = cursorPosition.x + 'px';
      top = cursorPosition.y - containerHeight + 'px';
    }

    assign$1(container, { left: left, top: top }, { 'zIndex': 1000 });
  };


  /**
   * Creates a list of entries and returns them as a DOM container.
   *
   * @param {Array<Object>} entries an array of entry objects
   * @param {string} className the class name of the entry container
   *
   * @return {Object} a DOM container
   */
  PopupMenu.prototype._createEntries = function(entries, className) {

    var entriesContainer = domify('<div>'),
        self = this;

    classes$1(entriesContainer).add(className);

    forEach$1(entries, function(entry, id) {
      var entryContainer = self._createEntry(entry, id),
          grouping = entry.group || 'default',
          groupContainer = query('[data-group=' + cssEscape(grouping) + ']', entriesContainer);

      if (!groupContainer) {
        groupContainer = domify('<div class="group"></div>');
        attr$1(groupContainer, 'data-group', grouping);

        entriesContainer.appendChild(groupContainer);
      }

      groupContainer.appendChild(entryContainer);
    });

    return entriesContainer;
  };


  /**
   * Creates a single entry and returns it as a DOM container.
   *
   * @param  {Object} entry
   *
   * @return {Object} a DOM container
   */
  PopupMenu.prototype._createEntry = function(entry, id) {

    var entryContainer = domify('<div>'),
        entryClasses = classes$1(entryContainer);

    entryClasses.add('entry');

    if (entry.className) {
      entry.className.split(' ').forEach(function(className) {
        entryClasses.add(className);
      });
    }

    attr$1(entryContainer, DATA_REF, id);

    if (entry.label) {
      var label = domify('<span>');
      label.textContent = entry.label;
      entryContainer.appendChild(label);
    }

    if (entry.imageUrl) {
      var image = domify('<img>');
      attr$1(image, 'src', entry.imageUrl);

      entryContainer.appendChild(image);
    }

    if (entry.active === true) {
      entryClasses.add('active');
    }

    if (entry.disabled === true) {
      entryClasses.add('disabled');
    }

    if (entry.title) {
      entryContainer.title = entry.title;
    }

    return entryContainer;
  };


  /**
   * Set up listener to close popup automatically on certain events.
   */
  PopupMenu.prototype._bindAutoClose = function() {
    this._eventBus.once(CLOSE_EVENTS, this.close, this);
  };


  /**
   * Remove the auto-closing listener.
   */
  PopupMenu.prototype._unbindAutoClose = function() {
    this._eventBus.off(CLOSE_EVENTS, this.close, this);
  };



  // helpers /////////////////////////////

  function setTransform(element, transform) {
    element.style['transform-origin'] = 'top left';

    [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
      element.style[prefix + 'transform'] = transform;
    });
  }

  var PopupMenuModule$1 = {
    __init__: [ 'popupMenu' ],
    popupMenu: [ 'type', PopupMenu ]
  };

  /**
   * To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
   * and then replace respective icons with the optimized data URIs in `./dist`.
   */
  var icons$1 = {
    align:  'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202000%202000%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M200%20150v1700%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22700%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%221150%22%20width%3D%22700%22%20height%3D%22700%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    bottom: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%201650h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22350%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22850%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    center: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M900%20150v1500%22%2F%3E%3Crect%20x%3D%22250%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    left:   'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M100%20150v1500%22%2F%3E%3Crect%20x%3D%22100%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22100%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    right:  'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M1650%20150v1500%22%2F%3E%3Crect%20x%3D%22350%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22850%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    top:    'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%20150h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22150%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22150%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    middle: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%20900h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22250%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22500%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E'
  };

  var LOW_PRIORITY$l = 900;

  /**
   * A provider for align elements context pad button
   */
  function AlignElementsContextPadProvider(contextPad, popupMenu, translate, canvas) {

    contextPad.registerProvider(LOW_PRIORITY$l, this);

    this._contextPad = contextPad;
    this._popupMenu = popupMenu;
    this._translate = translate;
    this._canvas = canvas;
  }

  AlignElementsContextPadProvider.$inject = [
    'contextPad',
    'popupMenu',
    'translate',
    'canvas'
  ];

  AlignElementsContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
    var actions = {};

    if (this._isAllowed(elements)) {
      assign(actions, this._getEntries(elements));
    }

    return actions;
  };

  AlignElementsContextPadProvider.prototype._isAllowed = function(elements) {
    return !this._popupMenu.isEmpty(elements, 'align-elements');
  };

  AlignElementsContextPadProvider.prototype._getEntries = function(elements) {
    var self = this;

    return {
      'align-elements': {
        group: 'align-elements',
        title: self._translate('Align elements'),
        imageUrl: icons$1['align'],
        action: {
          click: function(event, elements) {
            var position = self._getMenuPosition(elements);

            assign(position, {
              cursor: {
                x: event.x,
                y: event.y
              }
            });

            self._popupMenu.open(elements, 'align-elements', position);
          }
        }
      }
    };
  };

  AlignElementsContextPadProvider.prototype._getMenuPosition = function(elements) {
    var Y_OFFSET = 5;

    var diagramContainer = this._canvas.getContainer(),
        pad = this._contextPad.getPad(elements).html;

    var diagramRect = diagramContainer.getBoundingClientRect(),
        padRect = pad.getBoundingClientRect();

    var top = padRect.top - diagramRect.top;
    var left = padRect.left - diagramRect.left;

    var pos = {
      x: left,
      y: top + padRect.height + Y_OFFSET
    };

    return pos;
  };

  var ALIGNMENT_OPTIONS = [
    'left',
    'center',
    'right',
    'top',
    'middle',
    'bottom'
  ];

  /**
   * A provider for align elements popup menu.
   */
  function AlignElementsMenuProvider(popupMenu, alignElements, translate, rules) {

    this._alignElements = alignElements;
    this._translate = translate;
    this._popupMenu = popupMenu;
    this._rules = rules;

    popupMenu.registerProvider('align-elements', this);
  }

  AlignElementsMenuProvider.$inject = [
    'popupMenu',
    'alignElements',
    'translate',
    'rules'
  ];

  AlignElementsMenuProvider.prototype.getPopupMenuEntries = function(elements) {
    var entries = {};

    if (this._isAllowed(elements)) {
      assign(entries, this._getEntries(elements));
    }

    return entries;
  };

  AlignElementsMenuProvider.prototype._isAllowed = function(elements) {
    return this._rules.allowed('elements.align', { elements: elements });
  };

  AlignElementsMenuProvider.prototype._getEntries = function(elements) {
    var alignElements = this._alignElements,
        translate = this._translate,
        popupMenu = this._popupMenu;

    var entries = {};

    forEach$1(ALIGNMENT_OPTIONS, function(alignment) {
      entries[ 'align-elements-' + alignment ] = {
        group: 'align',
        title: translate('Align elements ' + alignment),
        className: 'bjs-align-elements-menu-entry',
        imageUrl: icons$1[alignment],
        action: function(event, entry) {
          alignElements.trigger(elements, alignment);
          popupMenu.close();
        }
      };
    });

    return entries;
  };

  /**
   * A basic provider that may be extended to implement modeling rules.
   *
   * Extensions should implement the init method to actually add their custom
   * modeling checks. Checks may be added via the #addRule(action, fn) method.
   *
   * @param {EventBus} eventBus
   */
  function RuleProvider(eventBus) {
    CommandInterceptor.call(this, eventBus);

    this.init();
  }

  RuleProvider.$inject = [ 'eventBus' ];

  e(RuleProvider, CommandInterceptor);


  /**
   * Adds a modeling rule for the given action, implemented through
   * a callback function.
   *
   * The function will receive the modeling specific action context
   * to perform its check. It must return `false` to disallow the
   * action from happening or `true` to allow the action.
   *
   * A rule provider may pass over the evaluation to lower priority
   * rules by returning return nothing (or <code>undefined</code>).
   *
   * @example
   *
   * ResizableRules.prototype.init = function() {
   *
   *   \/**
   *    * Return `true`, `false` or nothing to denote
   *    * _allowed_, _not allowed_ and _continue evaluating_.
   *    *\/
   *   this.addRule('shape.resize', function(context) {
   *
   *     var shape = context.shape;
   *
   *     if (!context.newBounds) {
   *       // check general resizability
   *       if (!shape.resizable) {
   *         return false;
   *       }
   *
   *       // not returning anything (read: undefined)
   *       // will continue the evaluation of other rules
   *       // (with lower priority)
   *       return;
   *     } else {
   *       // element must have minimum size of 10*10 points
   *       return context.newBounds.width > 10 && context.newBounds.height > 10;
   *     }
   *   });
   * };
   *
   * @param {string|Array<string>} actions the identifier for the modeling action to check
   * @param {number} [priority] the priority at which this rule is being applied
   * @param {Function} fn the callback function that performs the actual check
   */
  RuleProvider.prototype.addRule = function(actions, priority, fn) {

    var self = this;

    if (typeof actions === 'string') {
      actions = [ actions ];
    }

    actions.forEach(function(action) {

      self.canExecute(action, priority, function(context, action, event) {
        return fn(context);
      }, true);
    });
  };

  /**
   * Implement this method to add new rules during provider initialization.
   */
  RuleProvider.prototype.init = function() {};

  /**
   * Rule provider for alignment of BPMN elements.
   */
  function BpmnAlignElements(eventBus) {
    RuleProvider.call(this, eventBus);
  }

  BpmnAlignElements.$inject = [ 'eventBus' ];

  e(BpmnAlignElements, RuleProvider);

  BpmnAlignElements.prototype.init = function() {
    this.addRule('elements.align', function(context) {
      var elements = context.elements;

      // filter out elements which cannot be aligned
      var filteredElements = filter(elements, function(element) {
        return !(element.waypoints || element.host || element.labelTarget);
      });

      // filter out elements which are children of any of the selected elements
      filteredElements = getParents$1(filteredElements);

      if (filteredElements.length < 2) {
        return false;
      }

      return filteredElements;
    });
  };

  var AlignElementsModule = {
    __depends__: [
      AlignElementsModule$1,
      ContextPadModule$1,
      PopupMenuModule$1
    ],
    __init__: [
      'alignElementsContextPadProvider',
      'alignElementsMenuProvider',
      'bpmnAlignElements'
    ],
    alignElementsContextPadProvider: [ 'type', AlignElementsContextPadProvider ],
    alignElementsMenuProvider: [ 'type', AlignElementsMenuProvider ],
    bpmnAlignElements: [ 'type', BpmnAlignElements ]
  };

  // padding to detect element placement
  var PLACEMENT_DETECTION_PAD = 10;

  var DEFAULT_DISTANCE = 50;

  var DEFAULT_MAX_DISTANCE = 250;


  /**
   * Get free position starting from given position.
   *
   * @param {djs.model.Shape} source
   * @param {djs.model.Shape} element
   * @param {Point} position
   * @param {Function} getNextPosition
   *
   * @return {Point}
   */
  function findFreePosition(source, element, position, getNextPosition) {
    var connectedAtPosition;

    while ((connectedAtPosition = getConnectedAtPosition(source, position, element))) {
      position = getNextPosition(element, position, connectedAtPosition);
    }

    return position;
  }

  /**
   * Returns function that returns next position.
   *
   * @param {Object} nextPositionDirection
   * @param {Object} [nextPositionDirection.x]
   * @param {Object} [nextPositionDirection.y]
   *
   * @returns {Function}
   */
  function generateGetNextPosition(nextPositionDirection) {
    return function(element, previousPosition, connectedAtPosition) {
      var nextPosition = {
        x: previousPosition.x,
        y: previousPosition.y
      };

      [ 'x', 'y' ].forEach(function(axis) {

        var nextPositionDirectionForAxis = nextPositionDirection[ axis ];

        if (!nextPositionDirectionForAxis) {
          return;
        }

        var dimension = axis === 'x' ? 'width' : 'height';

        var margin = nextPositionDirectionForAxis.margin,
            minDistance = nextPositionDirectionForAxis.minDistance;

        if (margin < 0) {
          nextPosition[ axis ] = Math.min(
            connectedAtPosition[ axis ] + margin - element[ dimension ] / 2,
            previousPosition[ axis ] - minDistance + margin
          );
        } else {
          nextPosition[ axis ] = Math.max(
            connectedAtPosition[ axis ] + connectedAtPosition[ dimension ] + margin + element[ dimension ] / 2,
            previousPosition[ axis ] + minDistance + margin
          );
        }
      });

      return nextPosition;
    };
  }

  /**
   * Return target at given position, if defined.
   *
   * This takes connected elements from host and attachers
   * into account, too.
   */
  function getConnectedAtPosition(source, position, element) {

    var bounds = {
      x: position.x - (element.width / 2),
      y: position.y - (element.height / 2),
      width: element.width,
      height: element.height
    };

    var closure = getAutoPlaceClosure(source);

    return find(closure, function(target) {

      if (target === element) {
        return false;
      }

      var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);

      return orientation === 'intersect';
    });
  }

  /**
  * Compute optimal distance between source and target based on existing connections to and from source.
  * Assumes left-to-right and top-to-down modeling.
  *
  * @param {djs.model.Shape} source
  * @param {Object} [hints]
  * @param {number} [hints.defaultDistance]
  * @param {string} [hints.direction]
  * @param {Function} [hints.filter]
  * @param {Function} [hints.getWeight]
  * @param {number} [hints.maxDistance]
  * @param {string} [hints.reference]
  *
  * @return {number}
  */
  function getConnectedDistance(source, hints) {
    if (!hints) {
      hints = {};
    }

    // targets > sources by default
    function getDefaultWeight(connection) {
      return connection.source === source ? 1 : -1;
    }

    var defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE,
        direction = hints.direction || 'e',
        filter = hints.filter,
        getWeight = hints.getWeight || getDefaultWeight,
        maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE,
        reference = hints.reference || 'start';

    if (!filter) {
      filter = noneFilter;
    }

    function getDistance(a, b) {
      if (direction === 'n') {
        if (reference === 'start') {
          return asTRBL(a).top - asTRBL(b).bottom;
        } else if (reference === 'center') {
          return asTRBL(a).top - getMid(b).y;
        } else {
          return asTRBL(a).top - asTRBL(b).top;
        }
      } else if (direction === 'w') {
        if (reference === 'start') {
          return asTRBL(a).left - asTRBL(b).right;
        } else if (reference === 'center') {
          return asTRBL(a).left - getMid(b).x;
        } else {
          return asTRBL(a).left - asTRBL(b).left;
        }
      } else if (direction === 's') {
        if (reference === 'start') {
          return asTRBL(b).top - asTRBL(a).bottom;
        } else if (reference === 'center') {
          return getMid(b).y - asTRBL(a).bottom;
        } else {
          return asTRBL(b).bottom - asTRBL(a).bottom;
        }
      } else {
        if (reference === 'start') {
          return asTRBL(b).left - asTRBL(a).right;
        } else if (reference === 'center') {
          return getMid(b).x - asTRBL(a).right;
        } else {
          return asTRBL(b).right - asTRBL(a).right;
        }
      }
    }

    var sourcesDistances = source.incoming
      .filter(filter)
      .map(function(connection) {
        var weight = getWeight(connection);

        var distance = weight < 0
          ? getDistance(connection.source, source)
          : getDistance(source, connection.source);

        return {
          id: connection.source.id,
          distance: distance,
          weight: weight
        };
      });

    var targetsDistances = source.outgoing
      .filter(filter)
      .map(function(connection) {
        var weight = getWeight(connection);

        var distance = weight > 0
          ? getDistance(source, connection.target)
          : getDistance(connection.target, source);

        return {
          id: connection.target.id,
          distance: distance,
          weight: weight
        };
      });

    var distances = sourcesDistances.concat(targetsDistances).reduce(function(accumulator, currentValue) {
      accumulator[ currentValue.id + '__weight_' + currentValue.weight ] = currentValue;

      return accumulator;
    }, {});

    var distancesGrouped = reduce(distances, function(accumulator, currentValue) {
      var distance = currentValue.distance,
          weight = currentValue.weight;

      if (distance < 0 || distance > maxDistance) {
        return accumulator;
      }

      if (!accumulator[ String(distance) ]) {
        accumulator[ String(distance) ] = 0;
      }

      accumulator[ String(distance) ] += 1 * weight;

      if (!accumulator.distance || accumulator[ accumulator.distance ] < accumulator[ String(distance) ]) {
        accumulator.distance = distance;
      }

      return accumulator;
    }, {});

    return distancesGrouped.distance || defaultDistance;
  }

  /**
   * Returns all connected elements around the given source.
   *
   * This includes:
   *
   *   - connected elements
   *   - host connected elements
   *   - attachers connected elements
   *
   * @param  {djs.model.Shape} source
   *
   * @return {Array<djs.model.Shape>}
   */
  function getAutoPlaceClosure(source) {

    var allConnected = getConnected(source);

    if (source.host) {
      allConnected = allConnected.concat(getConnected(source.host));
    }

    if (source.attachers) {
      allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
        return shapes.concat(getConnected(attacher));
      }, []));
    }

    return allConnected;
  }

  function getConnected(element) {
    return getTargets(element).concat(getSources(element));
  }

  function getSources(shape) {
    return shape.incoming.map(function(connection) {
      return connection.source;
    });
  }

  function getTargets(shape) {
    return shape.outgoing.map(function(connection) {
      return connection.target;
    });
  }

  function noneFilter() {
    return true;
  }

  var LOW_PRIORITY$k = 100;


  /**
   * A service that places elements connected to existing ones
   * to an appropriate position in an _automated_ fashion.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function AutoPlace$1(eventBus, modeling, canvas) {

    eventBus.on('autoPlace', LOW_PRIORITY$k, function(context) {
      var shape = context.shape,
          source = context.source;

      return getNewShapePosition$1(source, shape);
    });

    eventBus.on('autoPlace.end', function(event) {
      canvas.scrollToElement(event.shape);
    });

    /**
     * Append shape to source at appropriate position.
     *
     * @param {djs.model.Shape} source
     * @param {djs.model.Shape} shape
     *
     * @return {djs.model.Shape} appended shape
     */
    this.append = function(source, shape, hints) {

      eventBus.fire('autoPlace.start', {
        source: source,
        shape: shape
      });

      // allow others to provide the position
      var position = eventBus.fire('autoPlace', {
        source: source,
        shape: shape
      });

      var newShape = modeling.appendShape(source, shape, position, source.parent, hints);

      eventBus.fire('autoPlace.end', {
        source: source,
        shape: newShape
      });

      return newShape;
    };

  }

  AutoPlace$1.$inject = [
    'eventBus',
    'modeling',
    'canvas'
  ];

  // helpers //////////

  /**
   * Find the new position for the target element to
   * connect to source.
   *
   * @param  {djs.model.Shape} source
   * @param  {djs.model.Shape} element
   * @param  {Object} [hints]
   * @param  {Object} [hints.defaultDistance]
   *
   * @returns {Point}
   */
  function getNewShapePosition$1(source, element, hints) {
    if (!hints) {
      hints = {};
    }

    var distance = hints.defaultDistance || DEFAULT_DISTANCE;

    var sourceMid = getMid(source),
        sourceTrbl = asTRBL(source);

    // simply put element right next to source
    return {
      x: sourceTrbl.right + distance + element.width / 2,
      y: sourceMid.y
    };
  }

  /**
   * Select element after auto placement.
   *
   * @param {EventBus} eventBus
   * @param {Selection} selection
   */
  function AutoPlaceSelectionBehavior(eventBus, selection) {

    eventBus.on('autoPlace.end', 500, function(e) {
      selection.select(e.shape);
    });

  }

  AutoPlaceSelectionBehavior.$inject = [
    'eventBus',
    'selection'
  ];

  var AutoPlaceModule$1 = {
    __init__: [ 'autoPlaceSelectionBehavior' ],
    autoPlace: [ 'type', AutoPlace$1 ],
    autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
  };

  /**
   * Return the parent of the element with any of the given types.
   *
   * @param {djs.model.Base} element
   * @param {string|Array<string>} anyType
   *
   * @return {djs.model.Base}
   */
  function getParent(element, anyType) {

    if (typeof anyType === 'string') {
      anyType = [ anyType ];
    }

    while ((element = element.parent)) {
      if (isAny(element, anyType)) {
        return element;
      }
    }

    return null;
  }

  /**
   * Find the new position for the target element to
   * connect to source.
   *
   * @param  {djs.model.Shape} source
   * @param  {djs.model.Shape} element
   *
   * @return {Point}
   */
  function getNewShapePosition(source, element) {

    if (is$1(element, 'bpmn:TextAnnotation')) {
      return getTextAnnotationPosition(source, element);
    }

    if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
      return getDataElementPosition(source, element);
    }

    if (is$1(element, 'bpmn:FlowNode')) {
      return getFlowNodePosition(source, element);
    }
  }

  /**
   * Always try to place element right of source;
   * compute actual distance from previous nodes in flow.
   */
  function getFlowNodePosition(source, element) {

    var sourceTrbl = asTRBL(source);
    var sourceMid = getMid(source);

    var horizontalDistance = getConnectedDistance(source, {
      filter: function(connection) {
        return is$1(connection, 'bpmn:SequenceFlow');
      }
    });

    var margin = 30,
        minDistance = 80,
        orientation = 'left';

    if (is$1(source, 'bpmn:BoundaryEvent')) {
      orientation = getOrientation(source, source.host, -25);

      if (orientation.indexOf('top') !== -1) {
        margin *= -1;
      }
    }

    var position = {
      x: sourceTrbl.right + horizontalDistance + element.width / 2,
      y: sourceMid.y + getVerticalDistance(orientation, minDistance)
    };

    var nextPositionDirection = {
      y: {
        margin: margin,
        minDistance: minDistance
      }
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }


  function getVerticalDistance(orientation, minDistance) {
    if (orientation.indexOf('top') != -1) {
      return -1 * minDistance;
    } else if (orientation.indexOf('bottom') != -1) {
      return minDistance;
    } else {
      return 0;
    }
  }


  /**
   * Always try to place text annotations top right of source.
   */
  function getTextAnnotationPosition(source, element) {

    var sourceTrbl = asTRBL(source);

    var position = {
      x: sourceTrbl.right + element.width / 2,
      y: sourceTrbl.top - 50 - element.height / 2
    };

    if (isConnection$e(source)) {
      position = getMid(source);
      position.x += 100;
      position.y -= 50;
    }

    var nextPositionDirection = {
      y: {
        margin: -30,
        minDistance: 20
      }
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }


  /**
   * Always put element bottom right of source.
   */
  function getDataElementPosition(source, element) {

    var sourceTrbl = asTRBL(source);

    var position = {
      x: sourceTrbl.right - 10 + element.width / 2,
      y: sourceTrbl.bottom + 40 + element.width / 2
    };

    var nextPositionDirection = {
      x: {
        margin: 30,
        minDistance: 30
      }
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }

  function isConnection$e(element) {
    return !!element.waypoints;
  }

  /**
   * BPMN auto-place behavior.
   *
   * @param {EventBus} eventBus
   */
  function AutoPlace(eventBus) {
    eventBus.on('autoPlace', function(context) {
      var shape = context.shape,
          source = context.source;

      return getNewShapePosition(source, shape);
    });
  }

  AutoPlace.$inject = [ 'eventBus' ];

  var AutoPlaceModule = {
    __depends__: [ AutoPlaceModule$1 ],
    __init__: [ 'bpmnAutoPlace' ],
    bpmnAutoPlace: [ 'type', AutoPlace ]
  };

  /**
   * An auto resize component that takes care of expanding a parent element
   * if child elements are created or moved close the parents edge.
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  function AutoResize(eventBus, elementRegistry, modeling, rules) {

    CommandInterceptor.call(this, eventBus);

    this._elementRegistry = elementRegistry;
    this._modeling = modeling;
    this._rules = rules;

    var self = this;

    this.postExecuted([ 'shape.create' ], function(event) {
      var context = event.context,
          hints = context.hints || {},
          shape = context.shape,
          parent = context.parent || context.newParent;

      if (hints.autoResize === false) {
        return;
      }

      self._expand([ shape ], parent);
    });

    this.postExecuted([ 'elements.move' ], function(event) {
      var context = event.context,
          elements = flatten(values(context.closure.topLevel)),
          hints = context.hints;

      var autoResize = hints ? hints.autoResize : true;

      if (autoResize === false) {
        return;
      }

      var expandings = groupBy(elements, function(element) {
        return element.parent.id;
      });

      forEach$1(expandings, function(elements, parentId) {

        // optionally filter elements to be considered when resizing
        if (isArray$3(autoResize)) {
          elements = elements.filter(function(element) {
            return find(autoResize, matchPattern({ id: element.id }));
          });
        }

        self._expand(elements, parentId);
      });
    });

    this.postExecuted([ 'shape.toggleCollapse' ], function(event) {
      var context = event.context,
          hints = context.hints,
          shape = context.shape;

      if (hints && hints.autoResize === false) {
        return;
      }

      if (shape.collapsed) {
        return;
      }

      self._expand(shape.children || [], shape);
    });

    this.postExecuted([ 'shape.resize' ], function(event) {
      var context = event.context,
          hints = context.hints,
          shape = context.shape,
          parent = shape.parent;

      if (hints && hints.autoResize === false) {
        return;
      }

      if (parent) {
        self._expand([ shape ], parent);
      }
    });

  }

  AutoResize.$inject = [
    'eventBus',
    'elementRegistry',
    'modeling',
    'rules'
  ];

  e(AutoResize, CommandInterceptor);


  /**
   * Calculate the new bounds of the target shape, given
   * a number of elements have been moved or added into the parent.
   *
   * This method considers the current size, the added elements as well as
   * the provided padding for the new bounds.
   *
   * @param {Array<djs.model.Shape>} elements
   * @param {djs.model.Shape} target
   */
  AutoResize.prototype._getOptimalBounds = function(elements, target) {

    var offset = this.getOffset(target),
        padding = this.getPadding(target);

    var elementsTrbl = asTRBL(getBBox(elements)),
        targetTrbl = asTRBL(target);

    var newTrbl = {};

    if (elementsTrbl.top - targetTrbl.top < padding.top) {
      newTrbl.top = elementsTrbl.top - offset.top;
    }

    if (elementsTrbl.left - targetTrbl.left < padding.left) {
      newTrbl.left = elementsTrbl.left - offset.left;
    }

    if (targetTrbl.right - elementsTrbl.right < padding.right) {
      newTrbl.right = elementsTrbl.right + offset.right;
    }

    if (targetTrbl.bottom - elementsTrbl.bottom < padding.bottom) {
      newTrbl.bottom = elementsTrbl.bottom + offset.bottom;
    }

    return asBounds(assign({}, targetTrbl, newTrbl));
  };


  /**
   * Expand the target shape respecting rules, offset and padding
   *
   * @param {Array<djs.model.Shape>} elements
   * @param {djs.model.Shape|string} target|targetId
   */
  AutoResize.prototype._expand = function(elements, target) {

    if (typeof target === 'string') {
      target = this._elementRegistry.get(target);
    }

    var allowed = this._rules.allowed('element.autoResize', {
      elements: elements,
      target: target
    });

    if (!allowed) {
      return;
    }

    // calculate the new bounds
    var newBounds = this._getOptimalBounds(elements, target);

    if (!boundsChanged$1(newBounds, target)) {
      return;
    }

    var resizeDirections = getResizeDirections(pick(target, [ 'x', 'y', 'width', 'height' ]), newBounds);

    // resize the parent shape
    this.resize(target, newBounds, {
      autoResize: resizeDirections
    });

    var parent = target.parent;

    // recursively expand parent elements
    if (parent) {
      this._expand([ target ], parent);
    }
  };


  /**
   * Get the amount to expand the given shape in each direction.
   *
   * @param {djs.model.Shape} shape
   *
   * @return {TRBL}
   */
  AutoResize.prototype.getOffset = function(shape) {
    return { top: 60, bottom: 60, left: 100, right: 100 };
  };


  /**
   * Get the activation threshold for each side for which
   * resize triggers.
   *
   * @param {djs.model.Shape} shape
   *
   * @return {TRBL}
   */
  AutoResize.prototype.getPadding = function(shape) {
    return { top: 2, bottom: 2, left: 15, right: 15 };
  };


  /**
   * Perform the actual resize operation.
   *
   * @param {djs.model.Shape} shape
   * @param {Bounds} newBounds
   * @param {Object} [hints]
   * @param {string} [hints.autoResize]
   */
  AutoResize.prototype.resize = function(shape, newBounds, hints) {
    this._modeling.resizeShape(shape, newBounds, null, hints);
  };


  function boundsChanged$1(newBounds, oldBounds) {
    return (
      newBounds.x !== oldBounds.x ||
      newBounds.y !== oldBounds.y ||
      newBounds.width !== oldBounds.width ||
      newBounds.height !== oldBounds.height
    );
  }

  /**
   * Get directions of resize as {n|w|s|e} e.g. "nw".
   *
   * @param {Bounds} oldBounds
   * @param {Bounds} newBounds
   *
   * @returns {string} Resize directions as {n|w|s|e}.
   */
  function getResizeDirections(oldBounds, newBounds) {
    var directions = '';

    oldBounds = asTRBL(oldBounds);
    newBounds = asTRBL(newBounds);

    if (oldBounds.top > newBounds.top) {
      directions = directions.concat('n');
    }

    if (oldBounds.right < newBounds.right) {
      directions = directions.concat('w');
    }

    if (oldBounds.bottom < newBounds.bottom) {
      directions = directions.concat('s');
    }

    if (oldBounds.left > newBounds.left) {
      directions = directions.concat('e');
    }

    return directions;
  }

  /**
   * Sub class of the AutoResize module which implements a BPMN
   * specific resize function.
   */
  function BpmnAutoResize(injector) {

    injector.invoke(AutoResize, this);
  }

  BpmnAutoResize.$inject = [
    'injector'
  ];

  e(BpmnAutoResize, AutoResize);


  /**
   * Resize shapes and lanes.
   *
   * @param {djs.model.Shape} target
   * @param {Bounds} newBounds
   * @param {Object} hints
   */
  BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {

    if (is$1(target, 'bpmn:Participant')) {
      this._modeling.resizeLane(target, newBounds, null, hints);
    } else {
      this._modeling.resizeShape(target, newBounds, null, hints);
    }
  };

  /**
   * This is a base rule provider for the element.autoResize rule.
   */
  function AutoResizeProvider(eventBus) {

    RuleProvider.call(this, eventBus);

    var self = this;

    this.addRule('element.autoResize', function(context) {
      return self.canResize(context.elements, context.target);
    });
  }

  AutoResizeProvider.$inject = [ 'eventBus' ];

  e(AutoResizeProvider, RuleProvider);

  /**
   * Needs to be implemented by sub classes to allow actual auto resize
   *
   * @param  {Array<djs.model.Shape>} elements
   * @param  {djs.model.Shape} target
   *
   * @return {boolean}
   */
  AutoResizeProvider.prototype.canResize = function(elements, target) {
    return false;
  };

  /**
   * This module is a provider for automatically resizing parent BPMN elements
   */
  function BpmnAutoResizeProvider(eventBus, modeling) {
    AutoResizeProvider.call(this, eventBus);

    this._modeling = modeling;
  }

  e(BpmnAutoResizeProvider, AutoResizeProvider);

  BpmnAutoResizeProvider.$inject = [
    'eventBus',
    'modeling'
  ];


  /**
   * Check if the given target can be expanded
   *
   * @param  {djs.model.Shape} target
   *
   * @return {boolean}
   */
  BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {

    // do not resize plane elements:
    // root elements, collapsed sub-processes
    if (is$1(target.di, 'bpmndi:BPMNPlane')) {
      return false;
    }

    if (!is$1(target, 'bpmn:Participant') && !is$1(target, 'bpmn:Lane') && !(is$1(target, 'bpmn:SubProcess'))) {
      return false;
    }

    var canResize = true;

    forEach$1(elements, function(element) {

      if (is$1(element, 'bpmn:Lane') || element.labelTarget) {
        canResize = false;
        return;
      }
    });

    return canResize;
  };

  var AutoResizeModule = {
    __init__: [
      'bpmnAutoResize',
      'bpmnAutoResizeProvider'
    ],
    bpmnAutoResize: [ 'type', BpmnAutoResize ],
    bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
  };

  var HIGH_PRIORITY$j = 1500;


  /**
   * Browsers may swallow certain events (hover, out ...) if users are to
   * fast with the mouse.
   *
   * @see http://stackoverflow.com/questions/7448468/why-cant-i-reliably-capture-a-mouseout-event
   *
   * The fix implemented in this component ensure that we
   *
   * 1) have a hover state after a successful drag.move event
   * 2) have an out event when dragging leaves an element
   *
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Injector} injector
   */
  function HoverFix(elementRegistry, eventBus, injector) {

    var self = this;

    var dragging = injector.get('dragging', false);

    /**
     * Make sure we are god damn hovering!
     *
     * @param {Event} dragging event
     */
    function ensureHover(event) {

      if (event.hover) {
        return;
      }

      var originalEvent = event.originalEvent;

      var gfx = self._findTargetGfx(originalEvent);

      var element = gfx && elementRegistry.get(gfx);

      if (gfx && element) {

        // 1) cancel current mousemove
        event.stopPropagation();

        // 2) emit fake hover for new target
        dragging.hover({ element: element, gfx: gfx });

        // 3) re-trigger move event
        dragging.move(originalEvent);
      }
    }


    if (dragging) {

      /**
       * We wait for a specific sequence of events before
       * emitting a fake drag.hover event.
       *
       * Event Sequence:
       *
       * drag.start
       * drag.move >> ensure we are hovering
       */
      eventBus.on('drag.start', function(event) {

        eventBus.once('drag.move', HIGH_PRIORITY$j, function(event) {

          ensureHover(event);

        });

      });
    }


    /**
     * We make sure that element.out is always fired, even if the
     * browser swallows an element.out event.
     *
     * Event sequence:
     *
     * element.hover
     * (element.out >> sometimes swallowed)
     * element.hover >> ensure we fired element.out
     */
    (function() {
      var hoverGfx;
      var hover;

      eventBus.on('element.hover', function(event) {

        // (1) remember current hover element
        hoverGfx = event.gfx;
        hover = event.element;
      });

      eventBus.on('element.hover', HIGH_PRIORITY$j, function(event) {

        // (3) am I on an element still?
        if (hover) {

          // (4) that is a problem, gotta "simulate the out"
          eventBus.fire('element.out', {
            element: hover,
            gfx: hoverGfx
          });
        }

      });

      eventBus.on('element.out', function() {

        // (2) unset hover state if we correctly outed us *GG*
        hoverGfx = null;
        hover = null;
      });

    })();

    this._findTargetGfx = function(event) {
      var position,
          target;

      if (!(event instanceof MouseEvent)) {
        return;
      }

      position = toPoint(event);

      // damn expensive operation, ouch!
      target = document.elementFromPoint(position.x, position.y);

      return getGfx(target);
    };

  }

  HoverFix.$inject = [
    'elementRegistry',
    'eventBus',
    'injector'
  ];


  // helpers /////////////////////

  function getGfx(target) {
    return closest(target, 'svg, .djs-element', true);
  }

  var HoverFixModule = {
    __init__: [
      'hoverFix'
    ],
    hoverFix: [ 'type', HoverFix ],
  };

  var round$a = Math.round;

  var DRAG_ACTIVE_CLS = 'djs-drag-active';


  function preventDefault$1(event) {
    event.preventDefault();
  }

  function isTouchEvent(event) {

    // check for TouchEvent being available first
    // (i.e. not available on desktop Firefox)
    return typeof TouchEvent !== 'undefined' && event instanceof TouchEvent;
  }

  function getLength(point) {
    return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
  }

  /**
   * A helper that fires canvas localized drag events and realizes
   * the general "drag-and-drop" look and feel.
   *
   * Calling {@link Dragging#activate} activates dragging on a canvas.
   *
   * It provides the following:
   *
   *   * emits life cycle events, namespaced with a prefix assigned
   *     during dragging activation
   *   * sets and restores the cursor
   *   * sets and restores the selection if elements still exist
   *   * ensures there can be only one drag operation active at a time
   *
   * Dragging may be canceled manually by calling {@link Dragging#cancel}
   * or by pressing ESC.
   *
   *
   * ## Life-cycle events
   *
   * Dragging can be in three different states, off, initialized
   * and active.
   *
   * (1) off: no dragging operation is in progress
   * (2) initialized: a new drag operation got initialized but not yet
   *                  started (i.e. because of no initial move)
   * (3) started: dragging is in progress
   *
   * Eventually dragging will be off again after a drag operation has
   * been ended or canceled via user click or ESC key press.
   *
   * To indicate transitions between these states dragging emits generic
   * life-cycle events with the `drag.` prefix _and_ events namespaced
   * to a prefix choosen by a user during drag initialization.
   *
   * The following events are emitted (appropriately prefixed) via
   * the {@link EventBus}.
   *
   * * `init`
   * * `start`
   * * `move`
   * * `end`
   * * `ended` (dragging already in off state)
   * * `cancel` (only if previously started)
   * * `canceled` (dragging already in off state, only if previously started)
   * * `cleanup`
   *
   *
   * @example
   *
   * function MyDragComponent(eventBus, dragging) {
   *
   *   eventBus.on('mydrag.start', function(event) {
   *     console.log('yes, we start dragging');
   *   });
   *
   *   eventBus.on('mydrag.move', function(event) {
   *     console.log('canvas local coordinates', event.x, event.y, event.dx, event.dy);
   *
   *     // local drag data is passed with the event
   *     event.context.foo; // "BAR"
   *
   *     // the original mouse event, too
   *     event.originalEvent; // MouseEvent(...)
   *   });
   *
   *   eventBus.on('element.click', function(event) {
   *     dragging.init(event, 'mydrag', {
   *       cursor: 'grabbing',
   *       data: {
   *         context: {
   *           foo: "BAR"
   *         }
   *       }
   *     });
   *   });
   * }
   */
  function Dragging(eventBus, canvas, selection, elementRegistry) {

    var defaultOptions = {
      threshold: 5,
      trapClick: true
    };

    // the currently active drag operation
    // dragging is active as soon as this context exists.
    //
    // it is visually _active_ only when a context.active flag is set to true.
    var context;

    /* convert a global event into local coordinates */
    function toLocalPoint(globalPosition) {

      var viewbox = canvas.viewbox();

      var clientRect = canvas._container.getBoundingClientRect();

      return {
        x: viewbox.x + (globalPosition.x - clientRect.left) / viewbox.scale,
        y: viewbox.y + (globalPosition.y - clientRect.top) / viewbox.scale
      };
    }

    // helpers

    function fire(type, dragContext) {
      dragContext = dragContext || context;

      var event = eventBus.createEvent(
        assign(
          {},
          dragContext.payload,
          dragContext.data,
          { isTouch: dragContext.isTouch }
        )
      );

      // default integration
      if (eventBus.fire('drag.' + type, event) === false) {
        return false;
      }

      return eventBus.fire(dragContext.prefix + '.' + type, event);
    }

    function restoreSelection(previousSelection) {
      var existingSelection = previousSelection.filter(function(element) {
        return elementRegistry.get(element.id);
      });

      existingSelection.length && selection.select(existingSelection);
    }

    // event listeners

    function move(event, activate) {
      var payload = context.payload,
          displacement = context.displacement;

      var globalStart = context.globalStart,
          globalCurrent = toPoint(event),
          globalDelta = delta(globalCurrent, globalStart);

      var localStart = context.localStart,
          localCurrent = toLocalPoint(globalCurrent),
          localDelta = delta(localCurrent, localStart);


      // activate context explicitly or once threshold is reached
      if (!context.active && (activate || getLength(globalDelta) > context.threshold)) {

        // fire start event with original
        // starting coordinates

        assign(payload, {
          x: round$a(localStart.x + displacement.x),
          y: round$a(localStart.y + displacement.y),
          dx: 0,
          dy: 0
        }, { originalEvent: event });

        if (false === fire('start')) {
          return cancel();
        }

        context.active = true;

        // unset selection and remember old selection
        // the previous (old) selection will always passed
        // with the event via the event.previousSelection property
        if (!context.keepSelection) {
          payload.previousSelection = selection.get();
          selection.select(null);
        }

        // allow custom cursor
        if (context.cursor) {
          set(context.cursor);
        }

        // indicate dragging via marker on root element
        canvas.addMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);
      }

      stopPropagation$1(event);

      if (context.active) {

        // update payload with actual coordinates
        assign(payload, {
          x: round$a(localCurrent.x + displacement.x),
          y: round$a(localCurrent.y + displacement.y),
          dx: round$a(localDelta.x),
          dy: round$a(localDelta.y)
        }, { originalEvent: event });

        // emit move event
        fire('move');
      }
    }

    function end(event) {
      var previousContext,
          returnValue = true;

      if (context.active) {

        if (event) {
          context.payload.originalEvent = event;

          // suppress original event (click, ...)
          // because we just ended a drag operation
          stopPropagation$1(event);
        }

        // implementations may stop restoring the
        // original state (selections, ...) by preventing the
        // end events default action
        returnValue = fire('end');
      }

      if (returnValue === false) {
        fire('rejected');
      }

      previousContext = cleanup(returnValue !== true);

      // last event to be fired when all drag operations are done
      // at this point in time no drag operation is in progress anymore
      fire('ended', previousContext);
    }


    // cancel active drag operation if the user presses
    // the ESC key on the keyboard

    function checkCancel(event) {

      if (event.which === 27) {
        preventDefault$1(event);

        cancel();
      }
    }


    // prevent ghost click that might occur after a finished
    // drag and drop session

    function trapClickAndEnd(event) {

      var untrap;

      // trap the click in case we are part of an active
      // drag operation. This will effectively prevent
      // the ghost click that cannot be canceled otherwise.
      if (context.active) {

        untrap = install(eventBus);

        // remove trap after minimal delay
        setTimeout(untrap, 400);

        // prevent default action (click)
        preventDefault$1(event);
      }

      end(event);
    }

    function trapTouch(event) {
      move(event);
    }

    // update the drag events hover (djs.model.Base) and hoverGfx (Snap<SVGElement>)
    // properties during hover and out and fire {prefix}.hover and {prefix}.out properties
    // respectively

    function hover(event) {
      var payload = context.payload;

      payload.hoverGfx = event.gfx;
      payload.hover = event.element;

      fire('hover');
    }

    function out(event) {
      fire('out');

      var payload = context.payload;

      payload.hoverGfx = null;
      payload.hover = null;
    }


    // life-cycle methods

    function cancel(restore) {
      var previousContext;

      if (!context) {
        return;
      }

      var wasActive = context.active;

      if (wasActive) {
        fire('cancel');
      }

      previousContext = cleanup(restore);

      if (wasActive) {

        // last event to be fired when all drag operations are done
        // at this point in time no drag operation is in progress anymore
        fire('canceled', previousContext);
      }
    }

    function cleanup(restore) {
      var previousContext,
          endDrag;

      fire('cleanup');

      // reset cursor
      unset();

      if (context.trapClick) {
        endDrag = trapClickAndEnd;
      } else {
        endDrag = end;
      }

      // reset dom listeners
      componentEvent.unbind(document, 'mousemove', move);

      componentEvent.unbind(document, 'dragstart', preventDefault$1);
      componentEvent.unbind(document, 'selectstart', preventDefault$1);

      componentEvent.unbind(document, 'mousedown', endDrag, true);
      componentEvent.unbind(document, 'mouseup', endDrag, true);

      componentEvent.unbind(document, 'keyup', checkCancel);

      componentEvent.unbind(document, 'touchstart', trapTouch, true);
      componentEvent.unbind(document, 'touchcancel', cancel, true);
      componentEvent.unbind(document, 'touchmove', move, true);
      componentEvent.unbind(document, 'touchend', end, true);

      eventBus.off('element.hover', hover);
      eventBus.off('element.out', out);

      // remove drag marker on root element
      canvas.removeMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);

      // restore selection, unless it has changed
      var previousSelection = context.payload.previousSelection;

      if (restore !== false && previousSelection && !selection.get().length) {
        restoreSelection(previousSelection);
      }

      previousContext = context;

      context = null;

      return previousContext;
    }

    /**
     * Initialize a drag operation.
     *
     * If `localPosition` is given, drag events will be emitted
     * relative to it.
     *
     * @param {MouseEvent|TouchEvent} [event]
     * @param {Point} [localPosition] actual diagram local position this drag operation should start at
     * @param {string} prefix
     * @param {Object} [options]
     */
    function init(event, relativeTo, prefix, options) {

      // only one drag operation may be active, at a time
      if (context) {
        cancel(false);
      }

      if (typeof relativeTo === 'string') {
        options = prefix;
        prefix = relativeTo;
        relativeTo = null;
      }

      options = assign({}, defaultOptions, options || {});

      var data = options.data || {},
          originalEvent,
          globalStart,
          localStart,
          endDrag,
          isTouch;

      if (options.trapClick) {
        endDrag = trapClickAndEnd;
      } else {
        endDrag = end;
      }

      if (event) {
        originalEvent = getOriginal$1(event) || event;
        globalStart = toPoint(event);

        stopPropagation$1(event);

        // prevent default browser dragging behavior
        if (originalEvent.type === 'dragstart') {
          preventDefault$1(originalEvent);
        }
      } else {
        originalEvent = null;
        globalStart = { x: 0, y: 0 };
      }

      localStart = toLocalPoint(globalStart);

      if (!relativeTo) {
        relativeTo = localStart;
      }

      isTouch = isTouchEvent(originalEvent);

      context = assign({
        prefix: prefix,
        data: data,
        payload: {},
        globalStart: globalStart,
        displacement: delta(relativeTo, localStart),
        localStart: localStart,
        isTouch: isTouch
      }, options);

      // skip dom registration if trigger
      // is set to manual (during testing)
      if (!options.manual) {

        // add dom listeners

        if (isTouch) {
          componentEvent.bind(document, 'touchstart', trapTouch, true);
          componentEvent.bind(document, 'touchcancel', cancel, true);
          componentEvent.bind(document, 'touchmove', move, true);
          componentEvent.bind(document, 'touchend', end, true);
        } else {

          // assume we use the mouse to interact per default
          componentEvent.bind(document, 'mousemove', move);

          // prevent default browser drag and text selection behavior
          componentEvent.bind(document, 'dragstart', preventDefault$1);
          componentEvent.bind(document, 'selectstart', preventDefault$1);

          componentEvent.bind(document, 'mousedown', endDrag, true);
          componentEvent.bind(document, 'mouseup', endDrag, true);
        }

        componentEvent.bind(document, 'keyup', checkCancel);

        eventBus.on('element.hover', hover);
        eventBus.on('element.out', out);
      }

      fire('init');

      if (options.autoActivate) {
        move(event, true);
      }
    }

    // cancel on diagram destruction
    eventBus.on('diagram.destroy', cancel);


    // API

    this.init = init;
    this.move = move;
    this.hover = hover;
    this.out = out;
    this.end = end;

    this.cancel = cancel;

    // for introspection

    this.context = function() {
      return context;
    };

    this.setOptions = function(options) {
      assign(defaultOptions, options);
    };
  }

  Dragging.$inject = [
    'eventBus',
    'canvas',
    'selection',
    'elementRegistry'
  ];

  var DraggingModule = {
    __depends__: [
      HoverFixModule,
      SelectionModule,
    ],
    dragging: [ 'type', Dragging ],
  };

  /**
   * Initiates canvas scrolling if current cursor point is close to a border.
   * Cancelled when current point moves back inside the scrolling borders
   * or cancelled manually.
   *
   * Default options :
   *   scrollThresholdIn: [ 20, 20, 20, 20 ],
   *   scrollThresholdOut: [ 0, 0, 0, 0 ],
   *   scrollRepeatTimeout: 15,
   *   scrollStep: 10
   *
   * Threshold order:
   *   [ left, top, right, bottom ]
   */
  function AutoScroll(config, eventBus, canvas) {

    this._canvas = canvas;

    this._opts = assign({
      scrollThresholdIn: [ 20, 20, 20, 20 ],
      scrollThresholdOut: [ 0, 0, 0, 0 ],
      scrollRepeatTimeout: 15,
      scrollStep: 10
    }, config);

    var self = this;

    eventBus.on('drag.move', function(e) {
      var point = self._toBorderPoint(e);

      self.startScroll(point);
    });

    eventBus.on([ 'drag.cleanup' ], function() {
      self.stopScroll();
    });
  }

  AutoScroll.$inject = [
    'config.autoScroll',
    'eventBus',
    'canvas'
  ];


  /**
   * Starts scrolling loop.
   * Point is given in global scale in canvas container box plane.
   *
   * @param  {Object} point { x: X, y: Y }
   */
  AutoScroll.prototype.startScroll = function(point) {

    var canvas = this._canvas;
    var opts = this._opts;
    var self = this;

    var clientRect = canvas.getContainer().getBoundingClientRect();

    var diff = [
      point.x,
      point.y,
      clientRect.width - point.x,
      clientRect.height - point.y
    ];

    this.stopScroll();

    var dx = 0,
        dy = 0;

    for (var i = 0; i < 4; i++) {
      if (between(diff[i], opts.scrollThresholdOut[i], opts.scrollThresholdIn[i])) {
        if (i === 0) {
          dx = opts.scrollStep;
        } else if (i == 1) {
          dy = opts.scrollStep;
        } else if (i == 2) {
          dx = -opts.scrollStep;
        } else if (i == 3) {
          dy = -opts.scrollStep;
        }
      }
    }

    if (dx !== 0 || dy !== 0) {
      canvas.scroll({ dx: dx, dy: dy });

      this._scrolling = setTimeout(function() {
        self.startScroll(point);
      }, opts.scrollRepeatTimeout);
    }
  };

  function between(val, start, end) {
    if (start < val && val < end) {
      return true;
    }

    return false;
  }


  /**
   * Stops scrolling loop.
   */
  AutoScroll.prototype.stopScroll = function() {
    clearTimeout(this._scrolling);
  };


  /**
   * Overrides defaults options.
   *
   * @param  {Object} options
   */
  AutoScroll.prototype.setOptions = function(options) {
    this._opts = assign({}, this._opts, options);
  };


  /**
   * Converts event to a point in canvas container plane in global scale.
   *
   * @param  {Event} event
   * @return {Point}
   */
  AutoScroll.prototype._toBorderPoint = function(event) {
    var clientRect = this._canvas._container.getBoundingClientRect();

    var globalPosition = toPoint(event.originalEvent);

    return {
      x: globalPosition.x - clientRect.left,
      y: globalPosition.y - clientRect.top
    };
  };

  var AutoScrollModule = {
    __depends__: [
      DraggingModule,
    ],
    __init__: [ 'autoScroll' ],
    autoScroll: [ 'type', AutoScroll ]
  };

  /**
   * A service that provides rules for certain diagram actions.
   *
   * The default implementation will hook into the {@link CommandStack}
   * to perform the actual rule evaluation. Make sure to provide the
   * `commandStack` service with this module if you plan to use it.
   *
   * Together with this implementation you may use the {@link RuleProvider}
   * to implement your own rule checkers.
   *
   * This module is ment to be easily replaced, thus the tiny foot print.
   *
   * @param {Injector} injector
   */
  function Rules(injector) {
    this._commandStack = injector.get('commandStack', false);
  }

  Rules.$inject = [ 'injector' ];


  /**
   * Returns whether or not a given modeling action can be executed
   * in the specified context.
   *
   * This implementation will respond with allow unless anyone
   * objects.
   *
   * @param {string} action the action to be checked
   * @param {Object} [context] the context to check the action in
   *
   * @return {boolean} returns true, false or null depending on whether the
   *                   operation is allowed, not allowed or should be ignored.
   */
  Rules.prototype.allowed = function(action, context) {
    var allowed = true;

    var commandStack = this._commandStack;

    if (commandStack) {
      allowed = commandStack.canExecute(action, context);
    }

    // map undefined to true, i.e. no rules
    return allowed === undefined ? true : allowed;
  };

  var RulesModule$1 = {
    __init__: [ 'rules' ],
    rules: [ 'type', Rules ]
  };

  var round$9 = Math.round,
      max$6 = Math.max;


  function circlePath(center, r) {
    var x = center.x,
        y = center.y;

    return [
      [ 'M', x, y ],
      [ 'm', 0, -r ],
      [ 'a', r, r, 0, 1, 1, 0, 2 * r ],
      [ 'a', r, r, 0, 1, 1, 0, -2 * r ],
      [ 'z' ]
    ];
  }

  function linePath(points) {
    var segments = [];

    points.forEach(function(p, idx) {
      segments.push([ idx === 0 ? 'M' : 'L', p.x, p.y ]);
    });

    return segments;
  }


  var INTERSECTION_THRESHOLD$1 = 10;

  function getBendpointIntersection(waypoints, reference) {

    var i, w;

    for (i = 0; (w = waypoints[i]); i++) {

      if (pointDistance(w, reference) <= INTERSECTION_THRESHOLD$1) {
        return {
          point: waypoints[i],
          bendpoint: true,
          index: i
        };
      }
    }

    return null;
  }

  function getPathIntersection(waypoints, reference) {

    var intersections = intersect(circlePath(reference, INTERSECTION_THRESHOLD$1), linePath(waypoints));

    var a = intersections[0],
        b = intersections[intersections.length - 1],
        idx;

    if (!a) {

      // no intersection
      return null;
    }

    if (a !== b) {

      if (a.segment2 !== b.segment2) {

        // we use the bendpoint in between both segments
        // as the intersection point

        idx = max$6(a.segment2, b.segment2) - 1;

        return {
          point: waypoints[idx],
          bendpoint: true,
          index: idx
        };
      }

      return {
        point: {
          x: (round$9(a.x + b.x) / 2),
          y: (round$9(a.y + b.y) / 2)
        },
        index: a.segment2
      };
    }

    return {
      point: {
        x: round$9(a.x),
        y: round$9(a.y)
      },
      index: a.segment2
    };
  }

  /**
   * Returns the closest point on the connection towards a given reference point.
   *
   * @param  {Array<Point>} waypoints
   * @param  {Point} reference
   *
   * @return {Object} intersection data (segment, point)
   */
  function getApproxIntersection(waypoints, reference) {
    return getBendpointIntersection(waypoints, reference) || getPathIntersection(waypoints, reference);
  }

  /**
   * Returns the length of a vector
   *
   * @param {Vector}
   * @return {Float}
   */
  function vectorLength(v) {
    return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
  }


  /**
   * Calculates the angle between a line a the yAxis
   *
   * @param {Array}
   * @return {Float}
   */
  function getAngle(line) {

    // return value is between 0, 180 and -180, -0
    // @janstuemmel: maybe replace return a/b with b/a
    return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
  }


  /**
   * Rotates a vector by a given angle
   *
   * @param {Vector}
   * @param {Float} Angle in radians
   * @return {Vector}
   */
  function rotateVector(vector, angle) {
    return (!angle) ? vector : {
      x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
      y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
    };
  }


  /**
   * Solves a 2D equation system
   * a + r*b = c, where a,b,c are 2D vectors
   *
   * @param {Vector}
   * @param {Vector}
   * @param {Vector}
   * @return {Float}
   */
  function solveLambaSystem(a, b, c) {

    // the 2d system
    var system = [
      { n: a[0] - c[0], lambda: b[0] },
      { n: a[1] - c[1], lambda: b[1] }
    ];

    // solve
    var n = system[0].n * b[0] + system[1].n * b[1],
        l = system[0].lambda * b[0] + system[1].lambda * b[1];

    return -n / l;
  }


  /**
   * Position of perpendicular foot
   *
   * @param {Point}
   * @param [ {Point}, {Point} ] line defined through two points
   * @return {Point} the perpendicular foot position
   */
  function perpendicularFoot(point, line) {

    var a = line[0], b = line[1];

    // relative position of b from a
    var bd = { x: b.x - a.x, y: b.y - a.y };

    // solve equation system to the parametrized vectors param real value
    var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);

    return { x: a.x + r * bd.x, y: a.y + r * bd.y };
  }


  /**
   * Calculates the distance between a point and a line
   *
   * @param {Point}
   * @param [ {Point}, {Point} ] line defined through two points
   * @return {Float} distance
   */
  function getDistancePointLine(point, line) {

    var pfPoint = perpendicularFoot(point, line);

    // distance vector
    var connectionVector = {
      x: pfPoint.x - point.x,
      y: pfPoint.y - point.y
    };

    return vectorLength(connectionVector);
  }


  /**
   * Calculates the distance between two points
   *
   * @param {Point}
   * @param {Point}
   * @return {Float} distance
   */
  function getDistancePointPoint(point1, point2) {

    return vectorLength({
      x: point1.x - point2.x,
      y: point1.y - point2.y
    });
  }

  var BENDPOINT_CLS = 'djs-bendpoint';
  var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger';

  function toCanvasCoordinates(canvas, event) {

    var position = toPoint(event),
        clientRect = canvas._container.getBoundingClientRect(),
        offset;

    // canvas relative position

    offset = {
      x: clientRect.left,
      y: clientRect.top
    };

    // update actual event payload with canvas relative measures

    var viewbox = canvas.viewbox();

    return {
      x: viewbox.x + (position.x - offset.x) / viewbox.scale,
      y: viewbox.y + (position.y - offset.y) / viewbox.scale
    };
  }

  function getConnectionIntersection(canvas, waypoints, event) {
    var localPosition = toCanvasCoordinates(canvas, event),
        intersection = getApproxIntersection(waypoints, localPosition);

    return intersection;
  }

  function addBendpoint(parentGfx, cls) {
    var groupGfx = create$1('g');
    classes(groupGfx).add(BENDPOINT_CLS);

    append(parentGfx, groupGfx);

    var visual = create$1('circle');
    attr(visual, {
      cx: 0,
      cy: 0,
      r: 4
    });
    classes(visual).add('djs-visual');

    append(groupGfx, visual);

    var hit = create$1('circle');
    attr(hit, {
      cx: 0,
      cy: 0,
      r: 10
    });
    classes(hit).add('djs-hit');

    append(groupGfx, hit);

    if (cls) {
      classes(groupGfx).add(cls);
    }

    return groupGfx;
  }

  function createParallelDragger(parentGfx, segmentStart, segmentEnd, alignment) {
    var draggerGfx = create$1('g');

    append(parentGfx, draggerGfx);

    var width = 18,
        height = 6,
        padding = 11,
        hitWidth = calculateHitWidth(segmentStart, segmentEnd, alignment),
        hitHeight = height + padding;

    var visual = create$1('rect');
    attr(visual, {
      x: -width / 2,
      y: -height / 2,
      width: width,
      height: height
    });
    classes(visual).add('djs-visual');

    append(draggerGfx, visual);

    var hit = create$1('rect');
    attr(hit, {
      x: -hitWidth / 2,
      y: -hitHeight / 2,
      width: hitWidth,
      height: hitHeight
    });
    classes(hit).add('djs-hit');

    append(draggerGfx, hit);

    rotate(draggerGfx, alignment === 'v' ? 90 : 0);

    return draggerGfx;
  }


  function addSegmentDragger(parentGfx, segmentStart, segmentEnd) {

    var groupGfx = create$1('g'),
        mid = getMidPoint(segmentStart, segmentEnd),
        alignment = pointsAligned(segmentStart, segmentEnd);

    append(parentGfx, groupGfx);

    createParallelDragger(groupGfx, segmentStart, segmentEnd, alignment);

    classes(groupGfx).add(SEGMENT_DRAGGER_CLS);
    classes(groupGfx).add(alignment === 'h' ? 'horizontal' : 'vertical');

    translate$2(groupGfx, mid.x, mid.y);

    return groupGfx;
  }

  /**
   * Calculates region for segment move which is 2/3 of the full segment length
   * @param {number} segmentLength
   *
   * @return {number}
   */
  function calculateSegmentMoveRegion(segmentLength) {
    return Math.abs(Math.round(segmentLength * 2 / 3));
  }

  /**
   * Returns the point with the closest distance that is on the connection path.
   *
   * @param {Point} position
   * @param {djs.Base.Connection} connection
   * @returns {Point}
   */
  function getClosestPointOnConnection(position, connection) {
    var segment = getClosestSegment(position, connection);

    return perpendicularFoot(position, segment);
  }


  // helper //////////

  function calculateHitWidth(segmentStart, segmentEnd, alignment) {
    var segmentLengthXAxis = segmentEnd.x - segmentStart.x,
        segmentLengthYAxis = segmentEnd.y - segmentStart.y;

    return alignment === 'h' ?
      calculateSegmentMoveRegion(segmentLengthXAxis) :
      calculateSegmentMoveRegion(segmentLengthYAxis);
  }

  function getClosestSegment(position, connection) {
    var waypoints = connection.waypoints;

    var minDistance = Infinity,
        segmentIndex;

    for (var i = 0; i < waypoints.length - 1; i++) {
      var start = waypoints[i],
          end = waypoints[i + 1],
          distance = getDistancePointLine(position, [ start, end ]);

      if (distance < minDistance) {
        minDistance = distance;
        segmentIndex = i;
      }
    }

    return [ waypoints[segmentIndex], waypoints[segmentIndex + 1] ];
  }

  /**
   * A service that adds editable bendpoints to connections.
   */
  function Bendpoints(
      eventBus, canvas, interactionEvents,
      bendpointMove, connectionSegmentMove) {

    /**
     * Returns true if intersection point is inside middle region of segment, adjusted by
     * optional threshold
     */
    function isIntersectionMiddle(intersection, waypoints, treshold) {
      var idx = intersection.index,
          p = intersection.point,
          p0, p1, mid, aligned, xDelta, yDelta;

      if (idx <= 0 || intersection.bendpoint) {
        return false;
      }

      p0 = waypoints[idx - 1];
      p1 = waypoints[idx];
      mid = getMidPoint(p0, p1),
      aligned = pointsAligned(p0, p1);
      xDelta = Math.abs(p.x - mid.x);
      yDelta = Math.abs(p.y - mid.y);

      return aligned && xDelta <= treshold && yDelta <= treshold;
    }

    /**
     * Calculates the threshold from a connection's middle which fits the two-third-region
     */
    function calculateIntersectionThreshold(connection, intersection) {
      var waypoints = connection.waypoints,
          relevantSegment, alignment, segmentLength, threshold;

      if (intersection.index <= 0 || intersection.bendpoint) {
        return null;
      }

      // segment relative to connection intersection
      relevantSegment = {
        start: waypoints[intersection.index - 1],
        end: waypoints[intersection.index]
      };

      alignment = pointsAligned(relevantSegment.start, relevantSegment.end);

      if (!alignment) {
        return null;
      }

      if (alignment === 'h') {
        segmentLength = relevantSegment.end.x - relevantSegment.start.x;
      } else {
        segmentLength = relevantSegment.end.y - relevantSegment.start.y;
      }

      // calculate threshold relative to 2/3 of segment length
      threshold = calculateSegmentMoveRegion(segmentLength) / 2;

      return threshold;
    }

    function activateBendpointMove(event, connection) {
      var waypoints = connection.waypoints,
          intersection = getConnectionIntersection(canvas, waypoints, event),
          threshold;

      if (!intersection) {
        return;
      }

      threshold = calculateIntersectionThreshold(connection, intersection);

      if (isIntersectionMiddle(intersection, waypoints, threshold)) {
        connectionSegmentMove.start(event, connection, intersection.index);
      } else {
        bendpointMove.start(event, connection, intersection.index, !intersection.bendpoint);
      }

      // we've handled the event
      return true;
    }

    function bindInteractionEvents(node, eventName, element) {

      componentEvent.bind(node, eventName, function(event) {
        interactionEvents.triggerMouseEvent(eventName, event, element);
        event.stopPropagation();
      });
    }

    function getBendpointsContainer(element, create) {

      var layer = canvas.getLayer('overlays'),
          gfx = query('.djs-bendpoints[data-element-id="' + cssEscape(element.id) + '"]', layer);

      if (!gfx && create) {
        gfx = create$1('g');
        attr(gfx, { 'data-element-id': element.id });
        classes(gfx).add('djs-bendpoints');

        append(layer, gfx);

        bindInteractionEvents(gfx, 'mousedown', element);
        bindInteractionEvents(gfx, 'click', element);
        bindInteractionEvents(gfx, 'dblclick', element);
      }

      return gfx;
    }

    function getSegmentDragger(idx, parentGfx) {
      return query(
        '.djs-segment-dragger[data-segment-idx="' + idx + '"]',
        parentGfx
      );
    }

    function createBendpoints(gfx, connection) {
      connection.waypoints.forEach(function(p, idx) {
        var bendpoint = addBendpoint(gfx);

        append(gfx, bendpoint);

        translate$2(bendpoint, p.x, p.y);
      });

      // add floating bendpoint
      addBendpoint(gfx, 'floating');
    }

    function createSegmentDraggers(gfx, connection) {

      var waypoints = connection.waypoints;

      var segmentStart,
          segmentEnd,
          segmentDraggerGfx;

      for (var i = 1; i < waypoints.length; i++) {

        segmentStart = waypoints[i - 1];
        segmentEnd = waypoints[i];

        if (pointsAligned(segmentStart, segmentEnd)) {
          segmentDraggerGfx = addSegmentDragger(gfx, segmentStart, segmentEnd);

          attr(segmentDraggerGfx, { 'data-segment-idx': i });

          bindInteractionEvents(segmentDraggerGfx, 'mousemove', connection);
        }
      }
    }

    function clearBendpoints(gfx) {
      forEach$1(all('.' + BENDPOINT_CLS, gfx), function(node) {
        remove$1(node);
      });
    }

    function clearSegmentDraggers(gfx) {
      forEach$1(all('.' + SEGMENT_DRAGGER_CLS, gfx), function(node) {
        remove$1(node);
      });
    }

    function addHandles(connection) {

      var gfx = getBendpointsContainer(connection);

      if (!gfx) {
        gfx = getBendpointsContainer(connection, true);

        createBendpoints(gfx, connection);
        createSegmentDraggers(gfx, connection);
      }

      return gfx;
    }

    function updateHandles(connection) {

      var gfx = getBendpointsContainer(connection);

      if (gfx) {
        clearSegmentDraggers(gfx);
        clearBendpoints(gfx);
        createSegmentDraggers(gfx, connection);
        createBendpoints(gfx, connection);
      }
    }

    function updateFloatingBendpointPosition(parentGfx, intersection) {
      var floating = query('.floating', parentGfx),
          point = intersection.point;

      if (!floating) {
        return;
      }

      translate$2(floating, point.x, point.y);

    }

    function updateSegmentDraggerPosition(parentGfx, intersection, waypoints) {

      var draggerGfx = getSegmentDragger(intersection.index, parentGfx),
          segmentStart = waypoints[intersection.index - 1],
          segmentEnd = waypoints[intersection.index],
          point = intersection.point,
          mid = getMidPoint(segmentStart, segmentEnd),
          alignment = pointsAligned(segmentStart, segmentEnd),
          draggerVisual, relativePosition;

      if (!draggerGfx) {
        return;
      }

      draggerVisual = getDraggerVisual(draggerGfx);

      relativePosition = {
        x: point.x - mid.x,
        y: point.y - mid.y
      };

      if (alignment === 'v') {

        // rotate position
        relativePosition = {
          x: relativePosition.y,
          y: relativePosition.x
        };
      }

      translate$2(draggerVisual, relativePosition.x, relativePosition.y);
    }

    eventBus.on('connection.changed', function(event) {
      updateHandles(event.element);
    });

    eventBus.on('connection.remove', function(event) {
      var gfx = getBendpointsContainer(event.element);

      if (gfx) {
        remove$1(gfx);
      }
    });

    eventBus.on('element.marker.update', function(event) {

      var element = event.element,
          bendpointsGfx;

      if (!element.waypoints) {
        return;
      }

      bendpointsGfx = addHandles(element);

      if (event.add) {
        classes(bendpointsGfx).add(event.marker);
      } else {
        classes(bendpointsGfx).remove(event.marker);
      }
    });

    eventBus.on('element.mousemove', function(event) {

      var element = event.element,
          waypoints = element.waypoints,
          bendpointsGfx,
          intersection;

      if (waypoints) {
        bendpointsGfx = getBendpointsContainer(element, true);

        intersection = getConnectionIntersection(canvas, waypoints, event.originalEvent);

        if (!intersection) {
          return;
        }

        updateFloatingBendpointPosition(bendpointsGfx, intersection);

        if (!intersection.bendpoint) {
          updateSegmentDraggerPosition(bendpointsGfx, intersection, waypoints);
        }

      }
    });

    eventBus.on('element.mousedown', function(event) {

      if (!isPrimaryButton(event)) {
        return;
      }

      var originalEvent = event.originalEvent,
          element = event.element;

      if (!element.waypoints) {
        return;
      }

      return activateBendpointMove(originalEvent, element);
    });

    eventBus.on('selection.changed', function(event) {
      var newSelection = event.newSelection,
          primary = newSelection[0];

      if (primary && primary.waypoints) {
        addHandles(primary);
      }
    });

    eventBus.on('element.hover', function(event) {
      var element = event.element;

      if (element.waypoints) {
        addHandles(element);
        interactionEvents.registerEvent(event.gfx, 'mousemove', 'element.mousemove');
      }
    });

    eventBus.on('element.out', function(event) {
      interactionEvents.unregisterEvent(event.gfx, 'mousemove', 'element.mousemove');
    });

    // update bendpoint container data attribute on element ID change
    eventBus.on('element.updateId', function(context) {
      var element = context.element,
          newId = context.newId;

      if (element.waypoints) {
        var bendpointContainer = getBendpointsContainer(element);

        if (bendpointContainer) {
          attr(bendpointContainer, { 'data-element-id': newId });
        }
      }
    });

    // API

    this.addHandles = addHandles;
    this.updateHandles = updateHandles;
    this.getBendpointsContainer = getBendpointsContainer;
    this.getSegmentDragger = getSegmentDragger;
  }

  Bendpoints.$inject = [
    'eventBus',
    'canvas',
    'interactionEvents',
    'bendpointMove',
    'connectionSegmentMove'
  ];



  // helper /////////////

  function getDraggerVisual(draggerGfx) {
    return query('.djs-visual', draggerGfx);
  }

  var round$8 = Math.round;

  var RECONNECT_START$1 = 'reconnectStart',
      RECONNECT_END$1 = 'reconnectEnd',
      UPDATE_WAYPOINTS$1 = 'updateWaypoints';


  /**
   * Move bendpoints through drag and drop to add/remove bendpoints or reconnect connection.
   */
  function BendpointMove(injector, eventBus, canvas, dragging, rules, modeling) {
    this._injector = injector;

    this.start = function(event, connection, bendpointIndex, insert) {
      var gfx = canvas.getGraphics(connection),
          source = connection.source,
          target = connection.target,
          waypoints = connection.waypoints,
          type;

      if (!insert && bendpointIndex === 0) {
        type = RECONNECT_START$1;
      } else
      if (!insert && bendpointIndex === waypoints.length - 1) {
        type = RECONNECT_END$1;
      } else {
        type = UPDATE_WAYPOINTS$1;
      }

      var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect';

      var allowed = rules.allowed(command, {
        connection: connection,
        source: source,
        target: target
      });

      if (allowed === false) {
        allowed = rules.allowed(command, {
          connection: connection,
          source: target,
          target: source
        });
      }

      if (allowed === false) {
        return;
      }

      dragging.init(event, 'bendpoint.move', {
        data: {
          connection: connection,
          connectionGfx: gfx,
          context: {
            allowed: allowed,
            bendpointIndex: bendpointIndex,
            connection: connection,
            source: source,
            target: target,
            insert: insert,
            type: type
          }
        }
      });
    };

    eventBus.on('bendpoint.move.hover', function(event) {
      var context = event.context,
          connection = context.connection,
          source = connection.source,
          target = connection.target,
          hover = event.hover,
          type = context.type;

      // cache hover state
      context.hover = hover;

      var allowed;

      if (!hover) {
        return;
      }

      var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect';

      allowed = context.allowed = rules.allowed(command, {
        connection: connection,
        source: type === RECONNECT_START$1 ? hover : source,
        target: type === RECONNECT_END$1 ? hover : target
      });

      if (allowed) {
        context.source = type === RECONNECT_START$1 ? hover : source;
        context.target = type === RECONNECT_END$1 ? hover : target;

        return;
      }

      if (allowed === false) {
        allowed = context.allowed = rules.allowed(command, {
          connection: connection,
          source: type === RECONNECT_END$1 ? hover : target,
          target: type === RECONNECT_START$1 ? hover : source
        });
      }

      if (allowed) {
        context.source = type === RECONNECT_END$1 ? hover : target;
        context.target = type === RECONNECT_START$1 ? hover : source;
      }
    });

    eventBus.on([ 'bendpoint.move.out', 'bendpoint.move.cleanup' ], function(event) {
      var context = event.context,
          type = context.type;

      context.hover = null;
      context.source = null;
      context.target = null;

      if (type !== UPDATE_WAYPOINTS$1) {
        context.allowed = false;
      }
    });

    eventBus.on('bendpoint.move.end', function(event) {
      var context = event.context,
          allowed = context.allowed,
          bendpointIndex = context.bendpointIndex,
          connection = context.connection,
          insert = context.insert,
          newWaypoints = connection.waypoints.slice(),
          source = context.source,
          target = context.target,
          type = context.type,
          hints = context.hints || {};

      // ensure integer values (important if zoom level was > 1 during move)
      var docking = {
        x: round$8(event.x),
        y: round$8(event.y)
      };

      if (!allowed) {
        return false;
      }

      if (type === UPDATE_WAYPOINTS$1) {
        if (insert) {

          // insert new bendpoint
          newWaypoints.splice(bendpointIndex, 0, docking);
        } else {

          // swap previous waypoint with moved one
          newWaypoints[bendpointIndex] = docking;
        }

        // pass hints about actual moved bendpoint
        // useful for connection/label layout
        hints.bendpointMove = {
          insert: insert,
          bendpointIndex: bendpointIndex
        };

        newWaypoints = this.cropWaypoints(connection, newWaypoints);

        modeling.updateWaypoints(connection, filterRedundantWaypoints(newWaypoints), hints);
      } else {
        if (type === RECONNECT_START$1) {
          hints.docking = 'source';

          if (isReverse$2(context)) {
            hints.docking = 'target';

            hints.newWaypoints = newWaypoints.reverse();
          }
        } else if (type === RECONNECT_END$1) {
          hints.docking = 'target';

          if (isReverse$2(context)) {
            hints.docking = 'source';

            hints.newWaypoints = newWaypoints.reverse();
          }
        }

        modeling.reconnect(connection, source, target, docking, hints);
      }
    }, this);
  }

  BendpointMove.$inject = [
    'injector',
    'eventBus',
    'canvas',
    'dragging',
    'rules',
    'modeling'
  ];

  BendpointMove.prototype.cropWaypoints = function(connection, newWaypoints) {
    var connectionDocking = this._injector.get('connectionDocking', false);

    if (!connectionDocking) {
      return newWaypoints;
    }

    var waypoints = connection.waypoints;

    connection.waypoints = newWaypoints;

    connection.waypoints = connectionDocking.getCroppedWaypoints(connection);

    newWaypoints = connection.waypoints;

    connection.waypoints = waypoints;

    return newWaypoints;
  };


  // helpers //////////

  function isReverse$2(context) {
    var hover = context.hover,
        source = context.source,
        target = context.target,
        type = context.type;

    if (type === RECONNECT_START$1) {
      return hover && target && hover === target && source !== target;
    }

    if (type === RECONNECT_END$1) {
      return hover && source && hover === source && source !== target;
    }
  }

  var RECONNECT_START = 'reconnectStart',
      RECONNECT_END = 'reconnectEnd',
      UPDATE_WAYPOINTS = 'updateWaypoints';

  var MARKER_OK$4 = 'connect-ok',
      MARKER_NOT_OK$4 = 'connect-not-ok',
      MARKER_CONNECT_HOVER$1 = 'connect-hover',
      MARKER_CONNECT_UPDATING$1 = 'djs-updating',
      MARKER_ELEMENT_HIDDEN = 'djs-element-hidden';

  var HIGH_PRIORITY$i = 1100;

  /**
   * Preview connection while moving bendpoints.
   */
  function BendpointMovePreview(bendpointMove, injector, eventBus, canvas) {
    this._injector = injector;

    var connectionPreview = injector.get('connectionPreview', false);

    eventBus.on('bendpoint.move.start', function(event) {
      var context = event.context,
          bendpointIndex = context.bendpointIndex,
          connection = context.connection,
          insert = context.insert,
          waypoints = connection.waypoints,
          newWaypoints = waypoints.slice();

      context.waypoints = waypoints;

      if (insert) {

        // insert placeholder for new bendpoint
        newWaypoints.splice(bendpointIndex, 0, { x: event.x, y: event.y });
      }

      connection.waypoints = newWaypoints;

      // add dragger gfx
      var draggerGfx = context.draggerGfx = addBendpoint(canvas.getLayer('overlays'));

      classes(draggerGfx).add('djs-dragging');

      canvas.addMarker(connection, MARKER_ELEMENT_HIDDEN);
      canvas.addMarker(connection, MARKER_CONNECT_UPDATING$1);
    });

    eventBus.on('bendpoint.move.hover', function(event) {
      var context = event.context,
          allowed = context.allowed,
          hover = context.hover,
          type = context.type;

      if (hover) {
        canvas.addMarker(hover, MARKER_CONNECT_HOVER$1);

        if (type === UPDATE_WAYPOINTS) {
          return;
        }

        if (allowed) {
          canvas.removeMarker(hover, MARKER_NOT_OK$4);
          canvas.addMarker(hover, MARKER_OK$4);
        } else if (allowed === false) {
          canvas.removeMarker(hover, MARKER_OK$4);
          canvas.addMarker(hover, MARKER_NOT_OK$4);
        }
      }
    });

    eventBus.on([
      'bendpoint.move.out',
      'bendpoint.move.cleanup'
    ], HIGH_PRIORITY$i, function(event) {
      var context = event.context,
          hover = context.hover,
          target = context.target;

      if (hover) {
        canvas.removeMarker(hover, MARKER_CONNECT_HOVER$1);
        canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4);
      }
    });

    eventBus.on('bendpoint.move.move', function(event) {
      var context = event.context,
          allowed = context.allowed,
          bendpointIndex = context.bendpointIndex,
          draggerGfx = context.draggerGfx,
          hover = context.hover,
          type = context.type,
          connection = context.connection,
          source = connection.source,
          target = connection.target,
          newWaypoints = connection.waypoints.slice(),
          bendpoint = { x: event.x, y: event.y },
          hints = context.hints || {},
          drawPreviewHints = {};

      if (connectionPreview) {
        if (hints.connectionStart) {
          drawPreviewHints.connectionStart = hints.connectionStart;
        }

        if (hints.connectionEnd) {
          drawPreviewHints.connectionEnd = hints.connectionEnd;
        }


        if (type === RECONNECT_START) {
          if (isReverse$2(context)) {
            drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;

            drawPreviewHints.source = target;
            drawPreviewHints.target = hover || source;

            newWaypoints = newWaypoints.reverse();
          } else {
            drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;

            drawPreviewHints.source = hover || source;
            drawPreviewHints.target = target;
          }
        } else if (type === RECONNECT_END) {
          if (isReverse$2(context)) {
            drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;

            drawPreviewHints.source = hover || target;
            drawPreviewHints.target = source;

            newWaypoints = newWaypoints.reverse();
          } else {
            drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;

            drawPreviewHints.source = source;
            drawPreviewHints.target = hover || target;
          }

        } else {
          drawPreviewHints.noCropping = true;
          drawPreviewHints.noLayout = true;
          newWaypoints[ bendpointIndex ] = bendpoint;
        }

        if (type === UPDATE_WAYPOINTS) {
          newWaypoints = bendpointMove.cropWaypoints(connection, newWaypoints);
        }

        drawPreviewHints.waypoints = newWaypoints;

        connectionPreview.drawPreview(context, allowed, drawPreviewHints);
      }

      translate$2(draggerGfx, event.x, event.y);
    }, this);

    eventBus.on([
      'bendpoint.move.end',
      'bendpoint.move.cancel'
    ], HIGH_PRIORITY$i, function(event) {
      var context = event.context,
          connection = context.connection,
          draggerGfx = context.draggerGfx,
          hover = context.hover,
          target = context.target,
          waypoints = context.waypoints;

      connection.waypoints = waypoints;

      // remove dragger gfx
      remove$1(draggerGfx);

      canvas.removeMarker(connection, MARKER_CONNECT_UPDATING$1);
      canvas.removeMarker(connection, MARKER_ELEMENT_HIDDEN);

      if (hover) {
        canvas.removeMarker(hover, MARKER_OK$4);
        canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4);
      }

      if (connectionPreview) {
        connectionPreview.cleanUp(context);
      }
    });
  }

  BendpointMovePreview.$inject = [
    'bendpointMove',
    'injector',
    'eventBus',
    'canvas'
  ];

  var MARKER_CONNECT_HOVER = 'connect-hover',
      MARKER_CONNECT_UPDATING = 'djs-updating';


  function axisAdd(point, axis, delta) {
    return axisSet(point, axis, point[axis] + delta);
  }

  function axisSet(point, axis, value) {
    return {
      x: (axis === 'x' ? value : point.x),
      y: (axis === 'y' ? value : point.y)
    };
  }

  function axisFenced(position, segmentStart, segmentEnd, axis) {

    var maxValue = Math.max(segmentStart[axis], segmentEnd[axis]),
        minValue = Math.min(segmentStart[axis], segmentEnd[axis]);

    var padding = 20;

    var fencedValue = Math.min(Math.max(minValue + padding, position[axis]), maxValue - padding);

    return axisSet(segmentStart, axis, fencedValue);
  }

  function flipAxis(axis) {
    return axis === 'x' ? 'y' : 'x';
  }

  /**
   * Get the docking point on the given element.
   *
   * Compute a reasonable docking, if non exists.
   *
   * @param  {Point} point
   * @param  {djs.model.Shape} referenceElement
   * @param  {string} moveAxis (x|y)
   *
   * @return {Point}
   */
  function getDocking$2(point, referenceElement, moveAxis) {

    var referenceMid,
        inverseAxis;

    if (point.original) {
      return point.original;
    } else {
      referenceMid = getMid(referenceElement);
      inverseAxis = flipAxis(moveAxis);

      return axisSet(point, inverseAxis, referenceMid[inverseAxis]);
    }
  }

  /**
   * A component that implements moving of bendpoints
   */
  function ConnectionSegmentMove(
      injector, eventBus, canvas,
      dragging, graphicsFactory, modeling) {

    // optional connection docking integration
    var connectionDocking = injector.get('connectionDocking', false);


    // API

    this.start = function(event, connection, idx) {

      var context,
          gfx = canvas.getGraphics(connection),
          segmentStartIndex = idx - 1,
          segmentEndIndex = idx,
          waypoints = connection.waypoints,
          segmentStart = waypoints[segmentStartIndex],
          segmentEnd = waypoints[segmentEndIndex],
          intersection = getConnectionIntersection(canvas, waypoints, event),
          direction, axis, dragPosition;

      direction = pointsAligned(segmentStart, segmentEnd);

      // do not move diagonal connection
      if (!direction) {
        return;
      }

      // the axis where we are going to move things
      axis = direction === 'v' ? 'x' : 'y';

      if (segmentStartIndex === 0) {
        segmentStart = getDocking$2(segmentStart, connection.source, axis);
      }

      if (segmentEndIndex === waypoints.length - 1) {
        segmentEnd = getDocking$2(segmentEnd, connection.target, axis);
      }

      if (intersection) {
        dragPosition = intersection.point;
      } else {

        // set to segment center as default
        dragPosition = {
          x: (segmentStart.x + segmentEnd.x) / 2,
          y: (segmentStart.y + segmentEnd.y) / 2
        };
      }

      context = {
        connection: connection,
        segmentStartIndex: segmentStartIndex,
        segmentEndIndex: segmentEndIndex,
        segmentStart: segmentStart,
        segmentEnd: segmentEnd,
        axis: axis,
        dragPosition: dragPosition
      };

      dragging.init(event, dragPosition, 'connectionSegment.move', {
        cursor: axis === 'x' ? 'resize-ew' : 'resize-ns',
        data: {
          connection: connection,
          connectionGfx: gfx,
          context: context
        }
      });
    };

    /**
     * Crop connection if connection cropping is provided.
     *
     * @param {Connection} connection
     * @param {Array<Point>} newWaypoints
     *
     * @return {Array<Point>} cropped connection waypoints
     */
    function cropConnection(connection, newWaypoints) {

      // crop connection, if docking service is provided only
      if (!connectionDocking) {
        return newWaypoints;
      }

      var oldWaypoints = connection.waypoints,
          croppedWaypoints;

      // temporary set new waypoints
      connection.waypoints = newWaypoints;

      croppedWaypoints = connectionDocking.getCroppedWaypoints(connection);

      // restore old waypoints
      connection.waypoints = oldWaypoints;

      return croppedWaypoints;
    }

    // DRAGGING IMPLEMENTATION

    function redrawConnection(data) {
      graphicsFactory.update('connection', data.connection, data.connectionGfx);
    }

    function updateDragger(context, segmentOffset, event) {

      var newWaypoints = context.newWaypoints,
          segmentStartIndex = context.segmentStartIndex + segmentOffset,
          segmentStart = newWaypoints[segmentStartIndex],
          segmentEndIndex = context.segmentEndIndex + segmentOffset,
          segmentEnd = newWaypoints[segmentEndIndex],
          axis = flipAxis(context.axis);

      // make sure the dragger does not move
      // outside the connection
      var draggerPosition = axisFenced(event, segmentStart, segmentEnd, axis);

      // update dragger
      translate$2(context.draggerGfx, draggerPosition.x, draggerPosition.y);
    }

    /**
     * Filter waypoints for redundant ones (i.e. on the same axis).
     * Returns the filtered waypoints and the offset related to the segment move.
     *
     * @param {Array<Point>} waypoints
     * @param {Integer} segmentStartIndex of moved segment start
     *
     * @return {Object} { filteredWaypoints, segmentOffset }
     */
    function filterRedundantWaypoints(waypoints, segmentStartIndex) {

      var segmentOffset = 0;

      var filteredWaypoints = waypoints.filter(function(r, idx) {
        if (pointsOnLine(waypoints[idx - 1], waypoints[idx + 1], r)) {

          // remove point and increment offset
          segmentOffset = idx <= segmentStartIndex ? segmentOffset - 1 : segmentOffset;
          return false;
        }

        // dont remove point
        return true;
      });

      return {
        waypoints: filteredWaypoints,
        segmentOffset: segmentOffset
      };
    }

    eventBus.on('connectionSegment.move.start', function(event) {

      var context = event.context,
          connection = event.connection,
          layer = canvas.getLayer('overlays');

      context.originalWaypoints = connection.waypoints.slice();

      // add dragger gfx
      context.draggerGfx = addSegmentDragger(layer, context.segmentStart, context.segmentEnd);
      classes(context.draggerGfx).add('djs-dragging');

      canvas.addMarker(connection, MARKER_CONNECT_UPDATING);
    });

    eventBus.on('connectionSegment.move.move', function(event) {

      var context = event.context,
          connection = context.connection,
          segmentStartIndex = context.segmentStartIndex,
          segmentEndIndex = context.segmentEndIndex,
          segmentStart = context.segmentStart,
          segmentEnd = context.segmentEnd,
          axis = context.axis;

      var newWaypoints = context.originalWaypoints.slice(),
          newSegmentStart = axisAdd(segmentStart, axis, event['d' + axis]),
          newSegmentEnd = axisAdd(segmentEnd, axis, event['d' + axis]);

      // original waypoint count and added / removed
      // from start waypoint delta. We use the later
      // to retrieve the updated segmentStartIndex / segmentEndIndex
      var waypointCount = newWaypoints.length,
          segmentOffset = 0;

      // move segment start / end by axis delta
      newWaypoints[segmentStartIndex] = newSegmentStart;
      newWaypoints[segmentEndIndex] = newSegmentEnd;

      var sourceToSegmentOrientation,
          targetToSegmentOrientation;

      // handle first segment
      if (segmentStartIndex < 2) {
        sourceToSegmentOrientation = getOrientation(connection.source, newSegmentStart);

        // first bendpoint, remove first segment if intersecting
        if (segmentStartIndex === 1) {

          if (sourceToSegmentOrientation === 'intersect') {
            newWaypoints.shift();
            newWaypoints[0] = newSegmentStart;
            segmentOffset--;
          }
        }

        // docking point, add segment if not intersecting anymore
        else {
          if (sourceToSegmentOrientation !== 'intersect') {
            newWaypoints.unshift(segmentStart);
            segmentOffset++;
          }
        }
      }

      // handle last segment
      if (segmentEndIndex > waypointCount - 3) {
        targetToSegmentOrientation = getOrientation(connection.target, newSegmentEnd);

        // last bendpoint, remove last segment if intersecting
        if (segmentEndIndex === waypointCount - 2) {

          if (targetToSegmentOrientation === 'intersect') {
            newWaypoints.pop();
            newWaypoints[newWaypoints.length - 1] = newSegmentEnd;
          }
        }

        // last bendpoint, remove last segment if intersecting
        else {
          if (targetToSegmentOrientation !== 'intersect') {
            newWaypoints.push(segmentEnd);
          }
        }
      }

      // update connection waypoints
      context.newWaypoints = connection.waypoints = cropConnection(connection, newWaypoints);

      // update dragger position
      updateDragger(context, segmentOffset, event);

      // save segmentOffset in context
      context.newSegmentStartIndex = segmentStartIndex + segmentOffset;

      // redraw connection
      redrawConnection(event);
    });

    eventBus.on('connectionSegment.move.hover', function(event) {

      event.context.hover = event.hover;
      canvas.addMarker(event.hover, MARKER_CONNECT_HOVER);
    });

    eventBus.on([
      'connectionSegment.move.out',
      'connectionSegment.move.cleanup'
    ], function(event) {

      // remove connect marker
      // if it was added
      var hover = event.context.hover;

      if (hover) {
        canvas.removeMarker(hover, MARKER_CONNECT_HOVER);
      }
    });

    eventBus.on('connectionSegment.move.cleanup', function(event) {

      var context = event.context,
          connection = context.connection;

      // remove dragger gfx
      if (context.draggerGfx) {
        remove$1(context.draggerGfx);
      }

      canvas.removeMarker(connection, MARKER_CONNECT_UPDATING);
    });

    eventBus.on([
      'connectionSegment.move.cancel',
      'connectionSegment.move.end'
    ], function(event) {
      var context = event.context,
          connection = context.connection;

      connection.waypoints = context.originalWaypoints;

      redrawConnection(event);
    });

    eventBus.on('connectionSegment.move.end', function(event) {

      var context = event.context,
          connection = context.connection,
          newWaypoints = context.newWaypoints,
          newSegmentStartIndex = context.newSegmentStartIndex;

      // ensure we have actual pixel values bendpoint
      // coordinates (important when zoom level was > 1 during move)
      newWaypoints = newWaypoints.map(function(p) {
        return {
          original: p.original,
          x: Math.round(p.x),
          y: Math.round(p.y)
        };
      });

      // apply filter redunant waypoints
      var filtered = filterRedundantWaypoints(newWaypoints, newSegmentStartIndex);

      // get filtered waypoints
      var filteredWaypoints = filtered.waypoints,
          croppedWaypoints = cropConnection(connection, filteredWaypoints),
          segmentOffset = filtered.segmentOffset;

      var hints = {
        segmentMove: {
          segmentStartIndex: context.segmentStartIndex,
          newSegmentStartIndex: newSegmentStartIndex + segmentOffset
        }
      };

      modeling.updateWaypoints(connection, croppedWaypoints, hints);
    });
  }

  ConnectionSegmentMove.$inject = [
    'injector',
    'eventBus',
    'canvas',
    'dragging',
    'graphicsFactory',
    'modeling'
  ];

  var abs$6 = Math.abs,
      round$7 = Math.round;


  /**
   * Snap value to a collection of reference values.
   *
   * @param  {number} value
   * @param  {Array<number>} values
   * @param  {number} [tolerance=10]
   *
   * @return {number} the value we snapped to or null, if none snapped
   */
  function snapTo(value, values, tolerance) {
    tolerance = tolerance === undefined ? 10 : tolerance;

    var idx, snapValue;

    for (idx = 0; idx < values.length; idx++) {
      snapValue = values[idx];

      if (abs$6(snapValue - value) <= tolerance) {
        return snapValue;
      }
    }
  }


  function topLeft(bounds) {
    return {
      x: bounds.x,
      y: bounds.y
    };
  }

  function bottomRight(bounds) {
    return {
      x: bounds.x + bounds.width,
      y: bounds.y + bounds.height
    };
  }

  function mid$2(bounds, defaultValue) {

    if (!bounds || isNaN(bounds.x) || isNaN(bounds.y)) {
      return defaultValue;
    }

    return {
      x: round$7(bounds.x + bounds.width / 2),
      y: round$7(bounds.y + bounds.height / 2)
    };
  }


  /**
   * Retrieve the snap state of the given event.
   *
   * @param  {Event} event
   * @param  {string} axis
   *
   * @return {boolean} the snapped state
   *
   */
  function isSnapped(event, axis) {
    var snapped = event.snapped;

    if (!snapped) {
      return false;
    }

    if (typeof axis === 'string') {
      return snapped[axis];
    }

    return snapped.x && snapped.y;
  }


  /**
   * Set the given event as snapped.
   *
   * This method may change the x and/or y position of the shape
   * from the given event!
   *
   * @param {Event} event
   * @param {string} axis
   * @param {number|boolean} value
   *
   * @return {number} old value
   */
  function setSnapped(event, axis, value) {
    if (typeof axis !== 'string') {
      throw new Error('axis must be in [x, y]');
    }

    if (typeof value !== 'number' && value !== false) {
      throw new Error('value must be Number or false');
    }

    var delta,
        previousValue = event[axis];

    var snapped = event.snapped = (event.snapped || {});


    if (value === false) {
      snapped[axis] = false;
    } else {
      snapped[axis] = true;

      delta = value - previousValue;

      event[axis] += delta;
      event['d' + axis] += delta;
    }

    return previousValue;
  }

  /**
   * Get children of a shape.
   *
   * @param {djs.model.Shape} parent
   *
   * @returns {Array<djs.model.Shape|djs.model.Connection>}
   */
  function getChildren(parent) {
    return parent.children || [];
  }

  var abs$5 = Math.abs,
      round$6 = Math.round;

  var TOLERANCE = 10;


  function BendpointSnapping(eventBus) {

    function snapTo(values, value) {

      if (isArray$3(values)) {
        var i = values.length;

        while (i--) if (abs$5(values[i] - value) <= TOLERANCE) {
          return values[i];
        }
      } else {
        values = +values;
        var rem = value % values;

        if (rem < TOLERANCE) {
          return value - rem;
        }

        if (rem > values - TOLERANCE) {
          return value - rem + values;
        }
      }

      return value;
    }

    function getSnapPoint(element, event) {

      if (element.waypoints) {
        return getClosestPointOnConnection(event, element);
      }

      if (element.width) {
        return {
          x: round$6(element.width / 2 + element.x),
          y: round$6(element.height / 2 + element.y)
        };
      }
    }

    // connection segment snapping //////////////////////

    function getConnectionSegmentSnaps(event) {

      var context = event.context,
          snapPoints = context.snapPoints,
          connection = context.connection,
          waypoints = connection.waypoints,
          segmentStart = context.segmentStart,
          segmentStartIndex = context.segmentStartIndex,
          segmentEnd = context.segmentEnd,
          segmentEndIndex = context.segmentEndIndex,
          axis = context.axis;

      if (snapPoints) {
        return snapPoints;
      }

      var referenceWaypoints = [
        waypoints[segmentStartIndex - 1],
        segmentStart,
        segmentEnd,
        waypoints[segmentEndIndex + 1]
      ];

      if (segmentStartIndex < 2) {
        referenceWaypoints.unshift(getSnapPoint(connection.source, event));
      }

      if (segmentEndIndex > waypoints.length - 3) {
        referenceWaypoints.unshift(getSnapPoint(connection.target, event));
      }

      context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };

      forEach$1(referenceWaypoints, function(p) {

        // we snap on existing bendpoints only,
        // not placeholders that are inserted during add
        if (p) {
          p = p.original || p;

          if (axis === 'y') {
            snapPoints.horizontal.push(p.y);
          }

          if (axis === 'x') {
            snapPoints.vertical.push(p.x);
          }
        }
      });

      return snapPoints;
    }

    eventBus.on('connectionSegment.move.move', 1500, function(event) {
      var snapPoints = getConnectionSegmentSnaps(event),
          x = event.x,
          y = event.y,
          sx, sy;

      if (!snapPoints) {
        return;
      }

      // snap
      sx = snapTo(snapPoints.vertical, x);
      sy = snapTo(snapPoints.horizontal, y);


      // correction x/y
      var cx = (x - sx),
          cy = (y - sy);

      // update delta
      assign(event, {
        dx: event.dx - cx,
        dy: event.dy - cy,
        x: sx,
        y: sy
      });

      // only set snapped if actually snapped
      if (cx || snapPoints.vertical.indexOf(x) !== -1) {
        setSnapped(event, 'x', sx);
      }

      if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
        setSnapped(event, 'y', sy);
      }
    });


    // bendpoint snapping //////////////////////

    function getBendpointSnaps(context) {

      var snapPoints = context.snapPoints,
          waypoints = context.connection.waypoints,
          bendpointIndex = context.bendpointIndex;

      if (snapPoints) {
        return snapPoints;
      }

      var referenceWaypoints = [ waypoints[bendpointIndex - 1], waypoints[bendpointIndex + 1] ];

      context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };

      forEach$1(referenceWaypoints, function(p) {

        // we snap on existing bendpoints only,
        // not placeholders that are inserted during add
        if (p) {
          p = p.original || p;

          snapPoints.horizontal.push(p.y);
          snapPoints.vertical.push(p.x);
        }
      });

      return snapPoints;
    }

    // Snap Endpoint of new connection
    eventBus.on([
      'connect.hover',
      'connect.move',
      'connect.end'
    ], 1500, function(event) {
      var context = event.context,
          hover = context.hover,
          hoverMid = hover && getSnapPoint(hover, event);

      // only snap on connections, elements can have multiple connect endpoints
      if (!isConnection$d(hover) || !hoverMid || !hoverMid.x || !hoverMid.y) {
        return;
      }

      setSnapped(event, 'x', hoverMid.x);
      setSnapped(event, 'y', hoverMid.y);
    });

    eventBus.on([ 'bendpoint.move.move', 'bendpoint.move.end' ], 1500, function(event) {

      var context = event.context,
          snapPoints = getBendpointSnaps(context),
          hover = context.hover,
          hoverMid = hover && getSnapPoint(hover, event),
          x = event.x,
          y = event.y,
          sx, sy;

      if (!snapPoints) {
        return;
      }

      // snap to hover mid
      sx = snapTo(hoverMid ? snapPoints.vertical.concat([ hoverMid.x ]) : snapPoints.vertical, x);
      sy = snapTo(hoverMid ? snapPoints.horizontal.concat([ hoverMid.y ]) : snapPoints.horizontal, y);

      // correction x/y
      var cx = (x - sx),
          cy = (y - sy);

      // update delta
      assign(event, {
        dx: event.dx - cx,
        dy: event.dy - cy,
        x: event.x - cx,
        y: event.y - cy
      });

      // only set snapped if actually snapped
      if (cx || snapPoints.vertical.indexOf(x) !== -1) {
        setSnapped(event, 'x', sx);
      }

      if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
        setSnapped(event, 'y', sy);
      }
    });
  }


  BendpointSnapping.$inject = [ 'eventBus' ];


  // helpers //////////////////////

  function isConnection$d(element) {
    return element && !!element.waypoints;
  }

  var BendpointsModule = {
    __depends__: [
      DraggingModule,
      RulesModule$1
    ],
    __init__: [ 'bendpoints', 'bendpointSnapping', 'bendpointMovePreview' ],
    bendpoints: [ 'type', Bendpoints ],
    bendpointMove: [ 'type', BendpointMove ],
    bendpointMovePreview: [ 'type', BendpointMovePreview ],
    connectionSegmentMove: [ 'type', ConnectionSegmentMove ],
    bendpointSnapping: [ 'type', BendpointSnapping ]
  };

  function Connect(eventBus, dragging, modeling, rules) {

    // rules

    function canConnect(source, target) {
      return rules.allowed('connection.create', {
        source: source,
        target: target
      });
    }

    function canConnectReverse(source, target) {
      return canConnect(target, source);
    }


    // event handlers

    eventBus.on('connect.hover', function(event) {
      var context = event.context,
          start = context.start,
          hover = event.hover,
          canExecute;

      // cache hover state
      context.hover = hover;

      canExecute = context.canExecute = canConnect(start, hover);

      // ignore hover
      if (isNil(canExecute)) {
        return;
      }

      if (canExecute !== false) {
        context.source = start;
        context.target = hover;

        return;
      }

      canExecute = context.canExecute = canConnectReverse(start, hover);

      // ignore hover
      if (isNil(canExecute)) {
        return;
      }

      if (canExecute !== false) {
        context.source = hover;
        context.target = start;
      }
    });

    eventBus.on([ 'connect.out', 'connect.cleanup' ], function(event) {
      var context = event.context;

      context.hover = null;
      context.source = null;
      context.target = null;

      context.canExecute = false;
    });

    eventBus.on('connect.end', function(event) {
      var context = event.context,
          canExecute = context.canExecute,
          connectionStart = context.connectionStart,
          connectionEnd = {
            x: event.x,
            y: event.y
          },
          source = context.source,
          target = context.target;

      if (!canExecute) {
        return false;
      }

      var attrs = null,
          hints = {
            connectionStart: isReverse$1(context) ? connectionEnd : connectionStart,
            connectionEnd: isReverse$1(context) ? connectionStart : connectionEnd
          };

      if (isObject(canExecute)) {
        attrs = canExecute;
      }

      context.connection = modeling.connect(source, target, attrs, hints);
    });


    // API

    /**
     * Start connect operation.
     *
     * @param {DOMEvent} event
     * @param {djs.model.Base} start
     * @param {Point} [connectionStart]
     * @param {boolean} [autoActivate=false]
     */
    this.start = function(event, start, connectionStart, autoActivate) {
      if (!isObject(connectionStart)) {
        autoActivate = connectionStart;
        connectionStart = getMid(start);
      }

      dragging.init(event, 'connect', {
        autoActivate: autoActivate,
        data: {
          shape: start,
          context: {
            start: start,
            connectionStart: connectionStart
          }
        }
      });
    };
  }

  Connect.$inject = [
    'eventBus',
    'dragging',
    'modeling',
    'rules'
  ];


  // helpers //////////

  function isReverse$1(context) {
    var hover = context.hover,
        source = context.source,
        target = context.target;

    return hover && source && hover === source && source !== target;
  }

  var HIGH_PRIORITY$h = 1100,
      LOW_PRIORITY$j = 900;

  var MARKER_OK$3 = 'connect-ok',
      MARKER_NOT_OK$3 = 'connect-not-ok';

  /**
   * Shows connection preview during connect.
   *
   * @param {didi.Injector} injector
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function ConnectPreview(injector, eventBus, canvas) {
    var connectionPreview = injector.get('connectionPreview', false);

    connectionPreview && eventBus.on('connect.move', function(event) {
      var context = event.context,
          canConnect = context.canExecute,
          hover = context.hover,
          source = context.source,
          start = context.start,
          startPosition = context.startPosition,
          target = context.target,
          connectionStart = context.connectionStart || startPosition,
          connectionEnd = context.connectionEnd || {
            x: event.x,
            y: event.y
          },
          previewStart = connectionStart,
          previewEnd = connectionEnd;

      if (isReverse$1(context)) {
        previewStart = connectionEnd;
        previewEnd = connectionStart;
      }

      connectionPreview.drawPreview(context, canConnect, {
        source: source || start,
        target: target || hover,
        connectionStart: previewStart,
        connectionEnd: previewEnd
      });
    });

    eventBus.on('connect.hover', LOW_PRIORITY$j, function(event) {
      var context = event.context,
          hover = event.hover,
          canExecute = context.canExecute;

      // ignore hover
      if (canExecute === null) {
        return;
      }

      canvas.addMarker(hover, canExecute ? MARKER_OK$3 : MARKER_NOT_OK$3);
    });

    eventBus.on([
      'connect.out',
      'connect.cleanup'
    ], HIGH_PRIORITY$h, function(event) {
      var hover = event.hover;

      if (hover) {
        canvas.removeMarker(hover, MARKER_OK$3);
        canvas.removeMarker(hover, MARKER_NOT_OK$3);
      }
    });

    connectionPreview && eventBus.on('connect.cleanup', function(event) {
      connectionPreview.cleanUp(event.context);
    });
  }

  ConnectPreview.$inject = [
    'injector',
    'eventBus',
    'canvas'
  ];

  var ConnectModule = {
    __depends__: [
      SelectionModule,
      RulesModule$1,
      DraggingModule
    ],
    __init__: [
      'connectPreview'
    ],
    connect: [ 'type', Connect ],
    connectPreview: [ 'type', ConnectPreview ]
  };

  var MARKER_CONNECTION_PREVIEW = 'djs-connection-preview';

  /**
   * Draws connection preview. Optionally, this can use layouter and connection docking to draw
   * better looking previews.
   *
   * @param {didi.Injector} injector
   * @param {Canvas} canvas
   * @param {GraphicsFactory} graphicsFactory
   * @param {ElementFactory} elementFactory
   */
  function ConnectionPreview(
      injector,
      canvas,
      graphicsFactory,
      elementFactory
  ) {
    this._canvas = canvas;
    this._graphicsFactory = graphicsFactory;
    this._elementFactory = elementFactory;

    // optional components
    this._connectionDocking = injector.get('connectionDocking', false);
    this._layouter = injector.get('layouter', false);
  }

  ConnectionPreview.$inject = [
    'injector',
    'canvas',
    'graphicsFactory',
    'elementFactory'
  ];

  /**
   * Draw connection preview.
   *
   * Provide at least one of <source, connectionStart> and <target, connectionEnd> to create a preview.
   * In the clean up stage, call `connectionPreview#cleanUp` with the context to remove preview.
   *
   * @param {Object} context
   * @param {Object|boolean} canConnect
   * @param {Object} hints
   * @param {djs.model.shape} [hints.source] source element
   * @param {djs.model.shape} [hints.target] target element
   * @param {Point} [hints.connectionStart] connection preview start
   * @param {Point} [hints.connectionEnd] connection preview end
   * @param {Array<Point>} [hints.waypoints] provided waypoints for preview
   * @param {boolean} [hints.noLayout] true if preview should not be laid out
   * @param {boolean} [hints.noCropping] true if preview should not be cropped
   * @param {boolean} [hints.noNoop] true if simple connection should not be drawn
   */
  ConnectionPreview.prototype.drawPreview = function(context, canConnect, hints) {

    hints = hints || {};

    var connectionPreviewGfx = context.connectionPreviewGfx,
        getConnection = context.getConnection,
        source = hints.source,
        target = hints.target,
        waypoints = hints.waypoints,
        connectionStart = hints.connectionStart,
        connectionEnd = hints.connectionEnd,
        noLayout = hints.noLayout,
        noCropping = hints.noCropping,
        noNoop = hints.noNoop,
        connection;

    var self = this;

    if (!connectionPreviewGfx) {
      connectionPreviewGfx = context.connectionPreviewGfx = this.createConnectionPreviewGfx();
    }

    clear(connectionPreviewGfx);

    if (!getConnection) {
      getConnection = context.getConnection = cacheReturnValues(function(canConnect, source, target) {
        return self.getConnection(canConnect, source, target);
      });
    }

    if (canConnect) {
      connection = getConnection(canConnect, source, target);
    }

    if (!connection) {
      !noNoop && this.drawNoopPreview(connectionPreviewGfx, hints);
      return;
    }

    connection.waypoints = waypoints || [];

    // optional layout
    if (this._layouter && !noLayout) {
      connection.waypoints = this._layouter.layoutConnection(connection, {
        source: source,
        target: target,
        connectionStart: connectionStart,
        connectionEnd: connectionEnd,
        waypoints: hints.waypoints || connection.waypoints
      });
    }

    // fallback if no waypoints were provided nor created with layouter
    if (!connection.waypoints || !connection.waypoints.length) {
      connection.waypoints = [
        source ? getMid(source) : connectionStart,
        target ? getMid(target) : connectionEnd
      ];
    }

    // optional cropping
    if (this._connectionDocking && (source || target) && !noCropping) {
      connection.waypoints = this._connectionDocking.getCroppedWaypoints(connection, source, target);
    }

    this._graphicsFactory.drawConnection(connectionPreviewGfx, connection);
  };

  /**
   * Draw simple connection between source and target or provided points.
   *
   * @param {SVGElement} connectionPreviewGfx container for the connection
   * @param {Object} hints
   * @param {djs.model.shape} [hints.source] source element
   * @param {djs.model.shape} [hints.target] target element
   * @param {Point} [hints.connectionStart] required if source is not provided
   * @param {Point} [hints.connectionEnd] required if target is not provided
   */
  ConnectionPreview.prototype.drawNoopPreview = function(connectionPreviewGfx, hints) {
    var source = hints.source,
        target = hints.target,
        start = hints.connectionStart || getMid(source),
        end = hints.connectionEnd || getMid(target);

    var waypoints = this.cropWaypoints(start, end, source, target);

    var connection = this.createNoopConnection(waypoints[0], waypoints[1]);

    append(connectionPreviewGfx, connection);
  };

  /**
   * Return cropped waypoints.
   *
   * @param {Point} start
   * @param {Point} end
   * @param {djs.model.shape} source
   * @param {djs.model.shape} target
   *
   * @returns {Array}
   */
  ConnectionPreview.prototype.cropWaypoints = function(start, end, source, target) {
    var graphicsFactory = this._graphicsFactory,
        sourcePath = source && graphicsFactory.getShapePath(source),
        targetPath = target && graphicsFactory.getShapePath(target),
        connectionPath = graphicsFactory.getConnectionPath({ waypoints: [ start, end ] });

    start = (source && getElementLineIntersection(sourcePath, connectionPath, true)) || start;
    end = (target && getElementLineIntersection(targetPath, connectionPath, false)) || end;

    return [ start, end ];
  };

  /**
   * Remove connection preview container if it exists.
   *
   * @param {Object} [context]
   * @param {SVGElement} [context.connectionPreviewGfx] preview container
   */
  ConnectionPreview.prototype.cleanUp = function(context) {
    if (context && context.connectionPreviewGfx) {
      remove$1(context.connectionPreviewGfx);
    }
  };

  /**
   * Get connection that connects source and target.
   *
   * @param {Object|boolean} canConnect
   *
   * @returns {djs.model.connection}
   */
  ConnectionPreview.prototype.getConnection = function(canConnect) {
    var attrs = ensureConnectionAttrs(canConnect);

    return this._elementFactory.createConnection(attrs);
  };


  /**
   * Add and return preview graphics.
   *
   * @returns {SVGElement}
   */
  ConnectionPreview.prototype.createConnectionPreviewGfx = function() {
    var gfx = create$1('g');

    attr(gfx, {
      pointerEvents: 'none'
    });

    classes(gfx).add(MARKER_CONNECTION_PREVIEW);

    append(this._canvas.getActiveLayer(), gfx);

    return gfx;
  };

  /**
   * Create and return simple connection.
   *
   * @param {Point} start
   * @param {Point} end
   *
   * @returns {SVGElement}
   */
  ConnectionPreview.prototype.createNoopConnection = function(start, end) {
    var connection = create$1('polyline');

    attr(connection, {
      'stroke': '#333',
      'strokeDasharray': [ 1 ],
      'strokeWidth': 2,
      'pointer-events': 'none'
    });

    attr(connection, { 'points': [ start.x, start.y, end.x, end.y ] });

    return connection;
  };

  // helpers //////////

  /**
   * Returns function that returns cached return values referenced by stringified first argument.
   *
   * @param {Function} fn
   *
   * @return {Function}
   */
  function cacheReturnValues(fn) {
    var returnValues = {};

    /**
     * Return cached return value referenced by stringified first argument.
     *
     * @returns {*}
     */
    return function(firstArgument) {
      var key = JSON.stringify(firstArgument);

      var returnValue = returnValues[key];

      if (!returnValue) {
        returnValue = returnValues[key] = fn.apply(null, arguments);
      }

      return returnValue;
    };
  }

  /**
   * Ensure connection attributes is object.
   *
   * @param {Object|boolean} canConnect
   *
   * @returns {Object}
   */
  function ensureConnectionAttrs(canConnect) {
    if (isObject(canConnect)) {
      return canConnect;
    } else {
      return {};
    }
  }

  var ConnectionPreviewModule = {
    __init__: [ 'connectionPreview' ],
    connectionPreview: [ 'type', ConnectionPreview ]
  };

  var min$3 = Math.min,
      max$5 = Math.max;

  function preventDefault(e) {
    e.preventDefault();
  }

  function stopPropagation(e) {
    e.stopPropagation();
  }

  function isTextNode(node) {
    return node.nodeType === Node.TEXT_NODE;
  }

  function toArray(nodeList) {
    return [].slice.call(nodeList);
  }

  /**
   * Initializes a container for a content editable div.
   *
   * Structure:
   *
   * container
   *   parent
   *     content
   *     resize-handle
   *
   * @param {object} options
   * @param {DOMElement} options.container The DOM element to append the contentContainer to
   * @param {Function} options.keyHandler Handler for key events
   * @param {Function} options.resizeHandler Handler for resize events
   */
  function TextBox(options) {
    this.container = options.container;

    this.parent = domify(
      '<div class="djs-direct-editing-parent">' +
        '<div class="djs-direct-editing-content" contenteditable="true"></div>' +
      '</div>'
    );

    this.content = query('[contenteditable]', this.parent);

    this.keyHandler = options.keyHandler || function() {};
    this.resizeHandler = options.resizeHandler || function() {};

    this.autoResize = bind(this.autoResize, this);
    this.handlePaste = bind(this.handlePaste, this);
  }


  /**
   * Create a text box with the given position, size, style and text content
   *
   * @param {Object} bounds
   * @param {Number} bounds.x absolute x position
   * @param {Number} bounds.y absolute y position
   * @param {Number} [bounds.width] fixed width value
   * @param {Number} [bounds.height] fixed height value
   * @param {Number} [bounds.maxWidth] maximum width value
   * @param {Number} [bounds.maxHeight] maximum height value
   * @param {Number} [bounds.minWidth] minimum width value
   * @param {Number} [bounds.minHeight] minimum height value
   * @param {Object} [style]
   * @param {String} value text content
   *
   * @return {DOMElement} The created content DOM element
   */
  TextBox.prototype.create = function(bounds, style, value, options) {
    var self = this;

    var parent = this.parent,
        content = this.content,
        container = this.container;

    options = this.options = options || {};

    style = this.style = style || {};

    var parentStyle = pick(style, [
      'width',
      'height',
      'maxWidth',
      'maxHeight',
      'minWidth',
      'minHeight',
      'left',
      'top',
      'backgroundColor',
      'position',
      'overflow',
      'border',
      'wordWrap',
      'textAlign',
      'outline',
      'transform'
    ]);

    assign(parent.style, {
      width: bounds.width + 'px',
      height: bounds.height + 'px',
      maxWidth: bounds.maxWidth + 'px',
      maxHeight: bounds.maxHeight + 'px',
      minWidth: bounds.minWidth + 'px',
      minHeight: bounds.minHeight + 'px',
      left: bounds.x + 'px',
      top: bounds.y + 'px',
      backgroundColor: '#ffffff',
      position: 'absolute',
      overflow: 'visible',
      border: '1px solid #ccc',
      boxSizing: 'border-box',
      wordWrap: 'normal',
      textAlign: 'center',
      outline: 'none'
    }, parentStyle);

    var contentStyle = pick(style, [
      'fontFamily',
      'fontSize',
      'fontWeight',
      'lineHeight',
      'padding',
      'paddingTop',
      'paddingRight',
      'paddingBottom',
      'paddingLeft'
    ]);

    assign(content.style, {
      boxSizing: 'border-box',
      width: '100%',
      outline: 'none',
      wordWrap: 'break-word'
    }, contentStyle);

    if (options.centerVertically) {
      assign(content.style, {
        position: 'absolute',
        top: '50%',
        transform: 'translate(0, -50%)'
      }, contentStyle);
    }

    content.innerText = value;

    componentEvent.bind(content, 'keydown', this.keyHandler);
    componentEvent.bind(content, 'mousedown', stopPropagation);
    componentEvent.bind(content, 'paste', self.handlePaste);

    if (options.autoResize) {
      componentEvent.bind(content, 'input', this.autoResize);
    }

    if (options.resizable) {
      this.resizable(style);
    }

    container.appendChild(parent);

    // set selection to end of text
    this.setSelection(content.lastChild, content.lastChild && content.lastChild.length);

    return parent;
  };

  /**
   * Intercept paste events to remove formatting from pasted text.
   */
  TextBox.prototype.handlePaste = function(e) {
    var options = this.options,
        style = this.style;

    e.preventDefault();

    var text;

    if (e.clipboardData) {

      // Chrome, Firefox, Safari
      text = e.clipboardData.getData('text/plain');
    } else {

      // Internet Explorer
      text = window.clipboardData.getData('Text');
    }

    this.insertText(text);

    if (options.autoResize) {
      var hasResized = this.autoResize(style);

      if (hasResized) {
        this.resizeHandler(hasResized);
      }
    }
  };

  TextBox.prototype.insertText = function(text) {
    text = normalizeEndOfLineSequences(text);

    // insertText command not supported by Internet Explorer
    var success = document.execCommand('insertText', false, text);

    if (success) {
      return;
    }

    this._insertTextIE(text);
  };

  TextBox.prototype._insertTextIE = function(text) {

    // Internet Explorer
    var range = this.getSelection(),
        startContainer = range.startContainer,
        endContainer = range.endContainer,
        startOffset = range.startOffset,
        endOffset = range.endOffset,
        commonAncestorContainer = range.commonAncestorContainer;

    var childNodesArray = toArray(commonAncestorContainer.childNodes);

    var container,
        offset;

    if (isTextNode(commonAncestorContainer)) {
      var containerTextContent = startContainer.textContent;

      startContainer.textContent =
        containerTextContent.substring(0, startOffset)
        + text
        + containerTextContent.substring(endOffset);

      container = startContainer;
      offset = startOffset + text.length;

    } else if (startContainer === this.content && endContainer === this.content) {
      var textNode = document.createTextNode(text);

      this.content.insertBefore(textNode, childNodesArray[startOffset]);

      container = textNode;
      offset = textNode.textContent.length;
    } else {
      var startContainerChildIndex = childNodesArray.indexOf(startContainer),
          endContainerChildIndex = childNodesArray.indexOf(endContainer);

      childNodesArray.forEach(function(childNode, index) {

        if (index === startContainerChildIndex) {
          childNode.textContent =
            startContainer.textContent.substring(0, startOffset) +
            text +
            endContainer.textContent.substring(endOffset);
        } else if (index > startContainerChildIndex && index <= endContainerChildIndex) {
          remove$2(childNode);
        }
      });

      container = startContainer;
      offset = startOffset + text.length;
    }

    if (container && offset !== undefined) {

      // is necessary in Internet Explorer
      setTimeout(function() {
        self.setSelection(container, offset);
      });
    }
  };

  /**
   * Automatically resize element vertically to fit its content.
   */
  TextBox.prototype.autoResize = function() {
    var parent = this.parent,
        content = this.content;

    var fontSize = parseInt(this.style.fontSize) || 12;

    if (content.scrollHeight > parent.offsetHeight ||
        content.scrollHeight < parent.offsetHeight - fontSize) {
      var bounds = parent.getBoundingClientRect();

      var height = content.scrollHeight;
      parent.style.height = height + 'px';

      this.resizeHandler({
        width: bounds.width,
        height: bounds.height,
        dx: 0,
        dy: height - bounds.height
      });
    }
  };

  /**
   * Make an element resizable by adding a resize handle.
   */
  TextBox.prototype.resizable = function() {
    var self = this;

    var parent = this.parent,
        resizeHandle = this.resizeHandle;

    var minWidth = parseInt(this.style.minWidth) || 0,
        minHeight = parseInt(this.style.minHeight) || 0,
        maxWidth = parseInt(this.style.maxWidth) || Infinity,
        maxHeight = parseInt(this.style.maxHeight) || Infinity;

    if (!resizeHandle) {
      resizeHandle = this.resizeHandle = domify(
        '<div class="djs-direct-editing-resize-handle"></div>'
      );

      var startX, startY, startWidth, startHeight;

      var onMouseDown = function(e) {
        preventDefault(e);
        stopPropagation(e);

        startX = e.clientX;
        startY = e.clientY;

        var bounds = parent.getBoundingClientRect();

        startWidth = bounds.width;
        startHeight = bounds.height;

        componentEvent.bind(document, 'mousemove', onMouseMove);
        componentEvent.bind(document, 'mouseup', onMouseUp);
      };

      var onMouseMove = function(e) {
        preventDefault(e);
        stopPropagation(e);

        var newWidth = min$3(max$5(startWidth + e.clientX - startX, minWidth), maxWidth);
        var newHeight = min$3(max$5(startHeight + e.clientY - startY, minHeight), maxHeight);

        parent.style.width = newWidth + 'px';
        parent.style.height = newHeight + 'px';

        self.resizeHandler({
          width: startWidth,
          height: startHeight,
          dx: e.clientX - startX,
          dy: e.clientY - startY
        });
      };

      var onMouseUp = function(e) {
        preventDefault(e);
        stopPropagation(e);

        componentEvent.unbind(document,'mousemove', onMouseMove, false);
        componentEvent.unbind(document, 'mouseup', onMouseUp, false);
      };

      componentEvent.bind(resizeHandle, 'mousedown', onMouseDown);
    }

    assign(resizeHandle.style, {
      position: 'absolute',
      bottom: '0px',
      right: '0px',
      cursor: 'nwse-resize',
      width: '0',
      height: '0',
      borderTop: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent',
      borderRight: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
      borderBottom: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
      borderLeft: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent'
    });

    parent.appendChild(resizeHandle);
  };


  /**
   * Clear content and style of the textbox, unbind listeners and
   * reset CSS style.
   */
  TextBox.prototype.destroy = function() {
    var parent = this.parent,
        content = this.content,
        resizeHandle = this.resizeHandle;

    // clear content
    content.innerText = '';

    // clear styles
    parent.removeAttribute('style');
    content.removeAttribute('style');

    componentEvent.unbind(content, 'keydown', this.keyHandler);
    componentEvent.unbind(content, 'mousedown', stopPropagation);
    componentEvent.unbind(content, 'input', this.autoResize);
    componentEvent.unbind(content, 'paste', this.handlePaste);

    if (resizeHandle) {
      resizeHandle.removeAttribute('style');

      remove$2(resizeHandle);
    }

    remove$2(parent);
  };


  TextBox.prototype.getValue = function() {
    return this.content.innerText.trim();
  };


  TextBox.prototype.getSelection = function() {
    var selection = window.getSelection(),
        range = selection.getRangeAt(0);

    return range;
  };


  TextBox.prototype.setSelection = function(container, offset) {
    var range = document.createRange();

    if (container === null) {
      range.selectNodeContents(this.content);
    } else {
      range.setStart(container, offset);
      range.setEnd(container, offset);
    }

    var selection = window.getSelection();

    selection.removeAllRanges();
    selection.addRange(range);
  };

  // helpers //////////

  function normalizeEndOfLineSequences(string) {
    return string.replace(/\r\n|\r|\n/g, '\n');
  }

  /**
   * A direct editing component that allows users
   * to edit an elements text directly in the diagram
   *
   * @param {EventBus} eventBus the event bus
   */
  function DirectEditing(eventBus, canvas) {

    this._eventBus = eventBus;

    this._providers = [];
    this._textbox = new TextBox({
      container: canvas.getContainer(),
      keyHandler: bind(this._handleKey, this),
      resizeHandler: bind(this._handleResize, this)
    });
  }

  DirectEditing.$inject = [ 'eventBus', 'canvas' ];


  /**
   * Register a direct editing provider

   * @param {Object} provider the provider, must expose an #activate(element) method that returns
   *                          an activation context ({ bounds: {x, y, width, height }, text }) if
   *                          direct editing is available for the given element.
   *                          Additionally the provider must expose a #update(element, value) method
   *                          to receive direct editing updates.
   */
  DirectEditing.prototype.registerProvider = function(provider) {
    this._providers.push(provider);
  };


  /**
   * Returns true if direct editing is currently active
   *
   * @param {djs.model.Base} [element]
   *
   * @return {boolean}
   */
  DirectEditing.prototype.isActive = function(element) {
    return !!(this._active && (!element || this._active.element === element));
  };


  /**
   * Cancel direct editing, if it is currently active
   */
  DirectEditing.prototype.cancel = function() {
    if (!this._active) {
      return;
    }

    this._fire('cancel');
    this.close();
  };


  DirectEditing.prototype._fire = function(event, context) {
    this._eventBus.fire('directEditing.' + event, context || { active: this._active });
  };

  DirectEditing.prototype.close = function() {
    this._textbox.destroy();

    this._fire('deactivate');

    this._active = null;

    this.resizable = undefined;
  };


  DirectEditing.prototype.complete = function() {

    var active = this._active;

    if (!active) {
      return;
    }

    var containerBounds,
        previousBounds = active.context.bounds,
        newBounds = this.$textbox.getBoundingClientRect(),
        newText = this.getValue(),
        previousText = active.context.text;

    if (
      newText !== previousText ||
      newBounds.height !== previousBounds.height ||
      newBounds.width !== previousBounds.width
    ) {
      containerBounds = this._textbox.container.getBoundingClientRect();

      active.provider.update(active.element, newText, active.context.text, {
        x: newBounds.left - containerBounds.left,
        y: newBounds.top - containerBounds.top,
        width: newBounds.width,
        height: newBounds.height
      });
    }

    this._fire('complete');

    this.close();
  };


  DirectEditing.prototype.getValue = function() {
    return this._textbox.getValue();
  };


  DirectEditing.prototype._handleKey = function(e) {

    // stop bubble
    e.stopPropagation();

    var key = e.keyCode || e.charCode;

    // ESC
    if (key === 27) {
      e.preventDefault();
      return this.cancel();
    }

    // Enter
    if (key === 13 && !e.shiftKey) {
      e.preventDefault();
      return this.complete();
    }
  };


  DirectEditing.prototype._handleResize = function(event) {
    this._fire('resize', event);
  };


  /**
   * Activate direct editing on the given element
   *
   * @param {Object} ElementDescriptor the descriptor for a shape or connection
   * @return {Boolean} true if the activation was possible
   */
  DirectEditing.prototype.activate = function(element) {
    if (this.isActive()) {
      this.cancel();
    }

    // the direct editing context
    var context;

    var provider = find(this._providers, function(p) {
      return (context = p.activate(element)) ? p : null;
    });

    // check if activation took place
    if (context) {
      this.$textbox = this._textbox.create(
        context.bounds,
        context.style,
        context.text,
        context.options
      );

      this._active = {
        element: element,
        context: context,
        provider: provider
      };

      if (context.options && context.options.resizable) {
        this.resizable = true;
      }

      this._fire('activate');
    }

    return !!context;
  };

  var DirectEditingModule = {
    __depends__: [
      InteractionEventsModule$1
    ],
    __init__: [ 'directEditing' ],
    directEditing: [ 'type', DirectEditing ]
  };

  var MARKER_TYPES = [
    'marker-start',
    'marker-mid',
    'marker-end'
  ];

  var NODES_CAN_HAVE_MARKER = [
    'circle',
    'ellipse',
    'line',
    'path',
    'polygon',
    'polyline',
    'rect'
  ];


  /**
   * Adds support for previews of moving/resizing elements.
   */
  function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
    this._elementRegistry = elementRegistry;
    this._canvas = canvas;
    this._styles = styles;

    this._clonedMarkers = {};

    var self = this;

    eventBus.on('drag.cleanup', function() {
      forEach$1(self._clonedMarkers, function(clonedMarker) {
        remove$1(clonedMarker);
      });

      self._clonedMarkers = {};
    });
  }

  PreviewSupport.$inject = [
    'elementRegistry',
    'eventBus',
    'canvas',
    'styles'
  ];


  /**
   * Returns graphics of an element.
   *
   * @param {djs.model.Base} element
   *
   * @return {SVGElement}
   */
  PreviewSupport.prototype.getGfx = function(element) {
    return this._elementRegistry.getGraphics(element);
  };

  /**
   * Adds a move preview of a given shape to a given svg group.
   *
   * @param {djs.model.Base} element
   * @param {SVGElement} group
   * @param {SVGElement} [gfx]
   *
   * @return {SVGElement} dragger
   */
  PreviewSupport.prototype.addDragger = function(element, group, gfx) {
    gfx = gfx || this.getGfx(element);

    var dragger = clone$1(gfx);
    var bbox = gfx.getBoundingClientRect();

    this._cloneMarkers(getVisual(dragger));

    attr(dragger, this._styles.cls('djs-dragger', [], {
      x: bbox.top,
      y: bbox.left
    }));

    append(group, dragger);

    return dragger;
  };

  /**
   * Adds a resize preview of a given shape to a given svg group.
   *
   * @param {djs.model.Base} element
   * @param {SVGElement} group
   *
   * @return {SVGElement} frame
   */
  PreviewSupport.prototype.addFrame = function(shape, group) {

    var frame = create$1('rect', {
      class: 'djs-resize-overlay',
      width:  shape.width,
      height: shape.height,
      x: shape.x,
      y: shape.y
    });

    append(group, frame);

    return frame;
  };

  /**
   * Clone all markers referenced by a node and its child nodes.
   *
   * @param {SVGElement} gfx
   */
  PreviewSupport.prototype._cloneMarkers = function(gfx) {
    var self = this;

    if (gfx.childNodes) {

      // TODO: use forEach once we drop PhantomJS
      for (var i = 0; i < gfx.childNodes.length; i++) {

        // recursively clone markers of child nodes
        self._cloneMarkers(gfx.childNodes[ i ]);
      }
    }

    if (!canHaveMarker(gfx)) {
      return;
    }

    MARKER_TYPES.forEach(function(markerType) {
      if (attr(gfx, markerType)) {
        var marker = getMarker(gfx, markerType, self._canvas.getContainer());

        self._cloneMarker(gfx, marker, markerType);
      }
    });
  };

  /**
   * Clone marker referenced by an element.
   *
   * @param {SVGElement} gfx
   * @param {SVGElement} marker
   * @param {string} markerType
   */
  PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) {
    var markerId = marker.id;

    var clonedMarker = this._clonedMarkers[ markerId ];

    if (!clonedMarker) {
      clonedMarker = clone$1(marker);

      var clonedMarkerId = markerId + '-clone';

      clonedMarker.id = clonedMarkerId;

      classes(clonedMarker)
        .add('djs-dragger')
        .add('djs-dragger-marker');

      this._clonedMarkers[ markerId ] = clonedMarker;

      var defs = query('defs', this._canvas._svg);

      if (!defs) {
        defs = create$1('defs');

        append(this._canvas._svg, defs);
      }

      append(defs, clonedMarker);
    }

    var reference = idToReference(this._clonedMarkers[ markerId ].id);

    attr(gfx, markerType, reference);
  };

  // helpers //////////

  /**
   * Get marker of given type referenced by node.
   *
   * @param {Node} node
   * @param {string} markerType
   * @param {Node} [parentNode]
   *
   * @param {Node}
   */
  function getMarker(node, markerType, parentNode) {
    var id = referenceToId(attr(node, markerType));

    return query('marker#' + id, parentNode || document);
  }

  /**
   * Get ID of fragment within current document from its functional IRI reference.
   * References may use single or double quotes.
   *
   * @param {string} reference
   *
   * @returns {string}
   */
  function referenceToId(reference) {
    return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
  }

  /**
   * Get functional IRI reference for given ID of fragment within current document.
   *
   * @param {string} id
   *
   * @returns {string}
   */
  function idToReference(id) {
    return 'url(#' + id + ')';
  }

  /**
   * Check wether node type can have marker attributes.
   *
   * @param {Node} node
   *
   * @returns {boolean}
   */
  function canHaveMarker(node) {
    return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
  }

  var PreviewSupportModule = {
    __init__: [ 'previewSupport' ],
    previewSupport: [ 'type', PreviewSupport ]
  };

  var MARKER_OK$2 = 'drop-ok',
      MARKER_NOT_OK$2 = 'drop-not-ok',
      MARKER_ATTACH$2 = 'attach-ok',
      MARKER_NEW_PARENT$1 = 'new-parent';

  var PREFIX = 'create';

  var HIGH_PRIORITY$g = 2000;


  /**
   * Create new elements through drag and drop.
   *
   * @param {Canvas} canvas
   * @param {Dragging} dragging
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  function Create(
      canvas,
      dragging,
      eventBus,
      modeling,
      rules
  ) {

    // rules //////////

    /**
     * Check wether elements can be created.
     *
     * @param {Array<djs.model.Base>} elements
     * @param {djs.model.Base} target
     * @param {Point} position
     * @param {djs.model.Base} [source]
     *
     * @returns {boolean|null|Object}
     */
    function canCreate(elements, target, position, source, hints) {
      if (!target) {
        return false;
      }

      // ignore child elements and external labels
      elements = filter(elements, function(element) {
        var labelTarget = element.labelTarget;

        return !element.parent && !(isLabel$5(element) && elements.indexOf(labelTarget) !== -1);
      });

      var shape = find(elements, function(element) {
        return !isConnection$c(element);
      });

      var attach = false,
          connect = false,
          create = false;

      // (1) attaching single shapes
      if (isSingleShape(elements)) {
        attach = rules.allowed('shape.attach', {
          position: position,
          shape: shape,
          target: target
        });
      }

      if (!attach) {

        // (2) creating elements
        if (isSingleShape(elements)) {
          create = rules.allowed('shape.create', {
            position: position,
            shape: shape,
            source: source,
            target: target
          });
        } else {
          create = rules.allowed('elements.create', {
            elements: elements,
            position: position,
            target: target
          });
        }

      }

      var connectionTarget = hints.connectionTarget;

      // (3) appending single shapes
      if (create || attach) {
        if (shape && source) {
          connect = rules.allowed('connection.create', {
            source: connectionTarget === source ? shape : source,
            target: connectionTarget === source ? source : shape,
            hints: {
              targetParent: target,
              targetAttach: attach
            }
          });
        }

        return {
          attach: attach,
          connect: connect
        };
      }

      // ignore wether or not elements can be created
      if (create === null || attach === null) {
        return null;
      }

      return false;
    }

    function setMarker(element, marker) {
      [ MARKER_ATTACH$2, MARKER_OK$2, MARKER_NOT_OK$2, MARKER_NEW_PARENT$1 ].forEach(function(m) {

        if (m === marker) {
          canvas.addMarker(element, m);
        } else {
          canvas.removeMarker(element, m);
        }
      });
    }

    // event handling //////////

    eventBus.on([ 'create.move', 'create.hover' ], function(event) {
      var context = event.context,
          elements = context.elements,
          hover = event.hover,
          source = context.source,
          hints = context.hints || {};

      if (!hover) {
        context.canExecute = false;
        context.target = null;

        return;
      }

      ensureConstraints$2(event);

      var position = {
        x: event.x,
        y: event.y
      };

      var canExecute = context.canExecute = hover && canCreate(elements, hover, position, source, hints);

      if (hover && canExecute !== null) {
        context.target = hover;

        if (canExecute && canExecute.attach) {
          setMarker(hover, MARKER_ATTACH$2);
        } else {
          setMarker(hover, canExecute ? MARKER_NEW_PARENT$1 : MARKER_NOT_OK$2);
        }
      }
    });

    eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) {
      var hover = event.hover;

      if (hover) {
        setMarker(hover, null);
      }
    });

    eventBus.on('create.end', function(event) {
      var context = event.context,
          source = context.source,
          shape = context.shape,
          elements = context.elements,
          target = context.target,
          canExecute = context.canExecute,
          attach = canExecute && canExecute.attach,
          connect = canExecute && canExecute.connect,
          hints = context.hints || {};

      if (canExecute === false || !target) {
        return false;
      }

      ensureConstraints$2(event);

      var position = {
        x: event.x,
        y: event.y
      };

      if (connect) {
        shape = modeling.appendShape(source, shape, position, target, {
          attach: attach,
          connection: connect === true ? {} : connect,
          connectionTarget: hints.connectionTarget
        });
      } else {
        elements = modeling.createElements(elements, position, target, assign({}, hints, {
          attach: attach
        }));

        // update shape
        shape = find(elements, function(element) {
          return !isConnection$c(element);
        });
      }

      // update elements and shape
      assign(context, {
        elements: elements,
        shape: shape
      });

      assign(event, {
        elements: elements,
        shape: shape
      });
    });

    function cancel() {
      var context = dragging.context();

      if (context && context.prefix === PREFIX) {
        dragging.cancel();
      }
    }

    // cancel on <elements.changed> that is not result of <drag.end>
    eventBus.on('create.init', function() {
      eventBus.on('elements.changed', cancel);

      eventBus.once([ 'create.cancel', 'create.end' ], HIGH_PRIORITY$g, function() {
        eventBus.off('elements.changed', cancel);
      });
    });

    // API //////////

    this.start = function(event, elements, context) {
      if (!isArray$3(elements)) {
        elements = [ elements ];
      }

      var shape = find(elements, function(element) {
        return !isConnection$c(element);
      });

      if (!shape) {

        // at least one shape is required
        return;
      }

      context = assign({
        elements: elements,
        hints: {},
        shape: shape
      }, context || {});

      // make sure each element has x and y
      forEach$1(elements, function(element) {
        if (!isNumber(element.x)) {
          element.x = 0;
        }

        if (!isNumber(element.y)) {
          element.y = 0;
        }
      });

      var visibleElements = filter(elements, function(element) {
        return !element.hidden;
      });

      var bbox = getBBox(visibleElements);

      // center elements around cursor
      forEach$1(elements, function(element) {
        if (isConnection$c(element)) {
          element.waypoints = map(element.waypoints, function(waypoint) {
            return {
              x: waypoint.x - bbox.x - bbox.width / 2,
              y: waypoint.y - bbox.y - bbox.height / 2
            };
          });
        }

        assign(element, {
          x: element.x - bbox.x - bbox.width / 2,
          y: element.y - bbox.y - bbox.height / 2
        });
      });

      dragging.init(event, PREFIX, {
        cursor: 'grabbing',
        autoActivate: true,
        data: {
          shape: shape,
          elements: elements,
          context: context
        }
      });
    };
  }

  Create.$inject = [
    'canvas',
    'dragging',
    'eventBus',
    'modeling',
    'rules'
  ];

  // helpers //////////

  function ensureConstraints$2(event) {
    var context = event.context,
        createConstraints = context.createConstraints;

    if (!createConstraints) {
      return;
    }

    if (createConstraints.left) {
      event.x = Math.max(event.x, createConstraints.left);
    }

    if (createConstraints.right) {
      event.x = Math.min(event.x, createConstraints.right);
    }

    if (createConstraints.top) {
      event.y = Math.max(event.y, createConstraints.top);
    }

    if (createConstraints.bottom) {
      event.y = Math.min(event.y, createConstraints.bottom);
    }
  }

  function isConnection$c(element) {
    return !!element.waypoints;
  }

  function isSingleShape(elements) {
    return elements && elements.length === 1 && !isConnection$c(elements[0]);
  }

  function isLabel$5(element) {
    return !!element.labelTarget;
  }

  var LOW_PRIORITY$i = 750;


  function CreatePreview(
      canvas,
      eventBus,
      graphicsFactory,
      previewSupport,
      styles
  ) {
    function createDragGroup(elements) {
      var dragGroup = create$1('g');

      attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));

      var childrenGfx = create$1('g');

      elements.forEach(function(element) {

        // create graphics
        var gfx;

        if (element.hidden) {
          return;
        }

        if (element.waypoints) {
          gfx = graphicsFactory._createContainer('connection', childrenGfx);

          graphicsFactory.drawConnection(getVisual(gfx), element);
        } else {
          gfx = graphicsFactory._createContainer('shape', childrenGfx);

          graphicsFactory.drawShape(getVisual(gfx), element);

          translate$2(gfx, element.x, element.y);
        }

        // add preview
        previewSupport.addDragger(element, dragGroup, gfx);
      });

      return dragGroup;
    }

    eventBus.on('create.move', LOW_PRIORITY$i, function(event) {

      var hover = event.hover,
          context = event.context,
          elements = context.elements,
          dragGroup = context.dragGroup;

      // lazily create previews
      if (!dragGroup) {
        dragGroup = context.dragGroup = createDragGroup(elements);
      }

      var activeLayer;

      if (hover) {
        if (!dragGroup.parentNode) {
          activeLayer = canvas.getActiveLayer();

          append(activeLayer, dragGroup);
        }

        translate$2(dragGroup, event.x, event.y);
      } else {
        remove$1(dragGroup);
      }
    });

    eventBus.on('create.cleanup', function(event) {
      var context = event.context,
          dragGroup = context.dragGroup;

      if (dragGroup) {
        remove$1(dragGroup);
      }
    });
  }

  CreatePreview.$inject = [
    'canvas',
    'eventBus',
    'graphicsFactory',
    'previewSupport',
    'styles'
  ];

  var CreateModule = {
    __depends__: [
      DraggingModule,
      PreviewSupportModule,
      RulesModule$1,
      SelectionModule
    ],
    __init__: [
      'create',
      'createPreview'
    ],
    create: [ 'type', Create ],
    createPreview: [ 'type', CreatePreview ]
  };

  /**
   * A clip board stub
   */
  function Clipboard() {}


  Clipboard.prototype.get = function() {
    return this._data;
  };

  Clipboard.prototype.set = function(data) {
    this._data = data;
  };

  Clipboard.prototype.clear = function() {
    var data = this._data;

    delete this._data;

    return data;
  };

  Clipboard.prototype.isEmpty = function() {
    return !this._data;
  };

  var ClipboardModule = {
    clipboard: [ 'type', Clipboard ]
  };

  function Mouse(eventBus) {
    var self = this;

    this._lastMoveEvent = null;

    function setLastMoveEvent(mousemoveEvent) {
      self._lastMoveEvent = mousemoveEvent;
    }

    eventBus.on('canvas.init', function(context) {
      var svg = self._svg = context.svg;

      svg.addEventListener('mousemove', setLastMoveEvent);
    });

    eventBus.on('canvas.destroy', function() {
      self._lastMouseEvent = null;

      self._svg.removeEventListener('mousemove', setLastMoveEvent);
    });
  }

  Mouse.$inject = [ 'eventBus' ];

  Mouse.prototype.getLastMoveEvent = function() {
    return this._lastMoveEvent || createMoveEvent(0, 0);
  };

  // helpers //////////

  function createMoveEvent(x, y) {
    var event = document.createEvent('MouseEvent');

    var screenX = x,
        screenY = y,
        clientX = x,
        clientY = y;

    if (event.initMouseEvent) {
      event.initMouseEvent(
        'mousemove',
        true,
        true,
        window,
        0,
        screenX,
        screenY,
        clientX,
        clientY,
        false,
        false,
        false,
        false,
        0,
        null
      );
    }

    return event;
  }

  var MouseModule = {
    __init__: [ 'mouse' ],
    mouse: [ 'type', Mouse ]
  };

  /**
   * @typedef {Function} <copyPaste.canCopyElements> listener
   *
   * @param {Object} context
   * @param {Array<djs.model.Base>} context.elements
   *
   * @returns {Array<djs.model.Base>|boolean} - Return elements to be copied or false to disallow
   * copying.
   */

  /**
   * @typedef {Function} <copyPaste.copyElement> listener
   *
   * @param {Object} context
   * @param {Object} context.descriptor
   * @param {djs.model.Base} context.element
   * @param {Array<djs.model.Base>} context.elements
   */

  /**
   * @typedef {Function} <copyPaste.createTree> listener
   *
   * @param {Object} context
   * @param {djs.model.Base} context.element
   * @param {Array<djs.model.Base>} context.children - Add children to be added to tree.
   */

  /**
   * @typedef {Function} <copyPaste.elementsCopied> listener
   *
   * @param {Object} context
   * @param {Object} context.elements
   * @param {Object} context.tree
   */

  /**
   * @typedef {Function} <copyPaste.pasteElement> listener
   *
   * @param {Object} context
   * @param {Object} context.cache - Already created elements.
   * @param {Object} context.descriptor
   */

  /**
   * @typedef {Function} <copyPaste.pasteElements> listener
   *
   * @param {Object} context
   * @param {Object} context.hints - Add hints before pasting.
   */

  /**
   * Copy and paste elements.
   *
   * @param {Canvas} canvas
   * @param {Create} create
   * @param {Clipboard} clipboard
   * @param {ElementFactory} elementFactory
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {Mouse} mouse
   * @param {Rules} rules
   */
  function CopyPaste(
      canvas,
      create,
      clipboard,
      elementFactory,
      eventBus,
      modeling,
      mouse,
      rules
  ) {

    this._canvas = canvas;
    this._create = create;
    this._clipboard = clipboard;
    this._elementFactory = elementFactory;
    this._eventBus = eventBus;
    this._modeling = modeling;
    this._mouse = mouse;
    this._rules = rules;

    eventBus.on('copyPaste.copyElement', function(context) {
      var descriptor = context.descriptor,
          element = context.element,
          elements = context.elements;

      // default priority (priority = 1)
      descriptor.priority = 1;

      descriptor.id = element.id;

      var parentCopied = find(elements, function(e) {
        return e === element.parent;
      });

      // do NOT reference parent if parent wasn't copied
      if (parentCopied) {
        descriptor.parent = element.parent.id;
      }

      // attachers (priority = 2)
      if (isAttacher$1(element)) {
        descriptor.priority = 2;

        descriptor.host = element.host.id;
      }

      // connections (priority = 3)
      if (isConnection$b(element)) {
        descriptor.priority = 3;

        descriptor.source = element.source.id;
        descriptor.target = element.target.id;

        descriptor.waypoints = copyWaypoints$1(element);
      }

      // labels (priority = 4)
      if (isLabel$4(element)) {
        descriptor.priority = 4;

        descriptor.labelTarget = element.labelTarget.id;
      }

      forEach$1([ 'x', 'y', 'width', 'height' ], function(property) {
        if (isNumber(element[ property ])) {
          descriptor[ property ] = element[ property ];
        }
      });

      descriptor.hidden = element.hidden;
      descriptor.collapsed = element.collapsed;

    });

    eventBus.on('copyPaste.pasteElements', function(context) {
      var hints = context.hints;

      assign(hints, {
        createElementsBehavior: false
      });
    });
  }

  CopyPaste.$inject = [
    'canvas',
    'create',
    'clipboard',
    'elementFactory',
    'eventBus',
    'modeling',
    'mouse',
    'rules'
  ];


  /**
   * Copy elements.
   *
   * @param {Array<djs.model.Base>} elements
   *
   * @returns {Object}
   */
  CopyPaste.prototype.copy = function(elements) {
    var allowed,
        tree;

    if (!isArray$3(elements)) {
      elements = elements ? [ elements ] : [];
    }

    allowed = this._eventBus.fire('copyPaste.canCopyElements', {
      elements: elements
    });

    if (allowed === false) {
      tree = {};
    } else {
      tree = this.createTree(isArray$3(allowed) ? allowed : elements);
    }

    // we set an empty tree, selection of elements
    // to copy was empty.
    this._clipboard.set(tree);

    this._eventBus.fire('copyPaste.elementsCopied', {
      elements: elements,
      tree: tree
    });

    return tree;
  };

  /**
   * Paste elements.
   *
   * @param {Object} [context]
   * @param {djs.model.base} [context.element] - Parent.
   * @param {Point} [context.point] - Position.
   * @param {Object} [context.hints] - Hints.
   */
  CopyPaste.prototype.paste = function(context) {
    var tree = this._clipboard.get();

    if (this._clipboard.isEmpty()) {
      return;
    }

    var hints = context && context.hints || {};

    this._eventBus.fire('copyPaste.pasteElements', {
      hints: hints
    });

    var elements = this._createElements(tree);

    // paste directly
    if (context && context.element && context.point) {
      return this._paste(elements, context.element, context.point, hints);
    }

    this._create.start(this._mouse.getLastMoveEvent(), elements, {
      hints: hints || {}
    });
  };

  /**
   * Paste elements directly.
   *
   * @param {Array<djs.model.Base>} elements
   * @param {djs.model.base} target
   * @param {Point} position
   * @param {Object} [hints]
   */
  CopyPaste.prototype._paste = function(elements, target, position, hints) {

    // make sure each element has x and y
    forEach$1(elements, function(element) {
      if (!isNumber(element.x)) {
        element.x = 0;
      }

      if (!isNumber(element.y)) {
        element.y = 0;
      }
    });

    var bbox = getBBox(elements);

    // center elements around cursor
    forEach$1(elements, function(element) {
      if (isConnection$b(element)) {
        element.waypoints = map(element.waypoints, function(waypoint) {
          return {
            x: waypoint.x - bbox.x - bbox.width / 2,
            y: waypoint.y - bbox.y - bbox.height / 2
          };
        });
      }

      assign(element, {
        x: element.x - bbox.x - bbox.width / 2,
        y: element.y - bbox.y - bbox.height / 2
      });
    });

    return this._modeling.createElements(elements, position, target, assign({}, hints));
  };

  /**
   * Create elements from tree.
   */
  CopyPaste.prototype._createElements = function(tree) {
    var self = this;

    var eventBus = this._eventBus;

    var cache = {};

    var elements = [];

    forEach$1(tree, function(branch, depth) {

      // sort by priority
      branch = sortBy(branch, 'priority');

      forEach$1(branch, function(descriptor) {

        // remove priority
        var attrs = assign({}, omit(descriptor, [ 'priority' ]));

        if (cache[ descriptor.parent ]) {
          attrs.parent = cache[ descriptor.parent ];
        } else {
          delete attrs.parent;
        }

        eventBus.fire('copyPaste.pasteElement', {
          cache: cache,
          descriptor: attrs
        });

        var element;

        if (isConnection$b(attrs)) {
          attrs.source = cache[ descriptor.source ];
          attrs.target = cache[ descriptor.target ];

          element = cache[ descriptor.id ] = self.createConnection(attrs);

          elements.push(element);

          return;
        }

        if (isLabel$4(attrs)) {
          attrs.labelTarget = cache[ attrs.labelTarget ];

          element = cache[ descriptor.id ] = self.createLabel(attrs);

          elements.push(element);

          return;
        }

        if (attrs.host) {
          attrs.host = cache[ attrs.host ];
        }

        element = cache[ descriptor.id ] = self.createShape(attrs);

        elements.push(element);
      });

    });

    return elements;
  };

  CopyPaste.prototype.createConnection = function(attrs) {
    var connection = this._elementFactory.createConnection(omit(attrs, [ 'id' ]));

    return connection;
  };

  CopyPaste.prototype.createLabel = function(attrs) {
    var label = this._elementFactory.createLabel(omit(attrs, [ 'id' ]));

    return label;
  };

  CopyPaste.prototype.createShape = function(attrs) {
    var shape = this._elementFactory.createShape(omit(attrs, [ 'id' ]));

    return shape;
  };

  /**
   * Check wether element has relations to other elements e.g. attachers, labels and connections.
   *
   * @param  {Object} element
   * @param  {Array<djs.model.Base>} elements
   *
   * @returns {boolean}
   */
  CopyPaste.prototype.hasRelations = function(element, elements) {
    var labelTarget,
        source,
        target;

    if (isConnection$b(element)) {
      source = find(elements, matchPattern({ id: element.source.id }));
      target = find(elements, matchPattern({ id: element.target.id }));

      if (!source || !target) {
        return false;
      }
    }

    if (isLabel$4(element)) {
      labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));

      if (!labelTarget) {
        return false;
      }
    }

    return true;
  };

  /**
   * Create a tree-like structure from elements.
   *
   * @example
   * tree: {
    *  0: [
    *    { id: 'Shape_1', priority: 1, ... },
    *    { id: 'Shape_2', priority: 1, ... },
    *    { id: 'Connection_1', source: 'Shape_1', target: 'Shape_2', priority: 3, ... },
    *    ...
    *  ],
    *  1: [
    *    { id: 'Shape_3', parent: 'Shape1', priority: 1, ... },
    *    ...
    *  ]
    * };
    *
    * @param  {Array<djs.model.base>} elements
    *
    * @return {Object}
    */
  CopyPaste.prototype.createTree = function(elements) {
    var rules = this._rules,
        self = this;

    var tree = {},
        elementsData = [];

    var parents = getParents$1(elements);

    function canCopy(element, elements) {
      return rules.allowed('element.copy', {
        element: element,
        elements: elements
      });
    }

    function addElementData(element, depth) {

      // (1) check wether element has already been added
      var foundElementData = find(elementsData, function(elementsData) {
        return element === elementsData.element;
      });

      // (2) add element if not already added
      if (!foundElementData) {
        elementsData.push({
          element: element,
          depth: depth
        });

        return;
      }

      // (3) update depth
      if (foundElementData.depth < depth) {
        elementsData = removeElementData(foundElementData, elementsData);

        elementsData.push({
          element: foundElementData.element,
          depth: depth
        });
      }
    }

    function removeElementData(elementData, elementsData) {
      var index = elementsData.indexOf(elementData);

      if (index !== -1) {
        elementsData.splice(index, 1);
      }

      return elementsData;
    }

    // (1) add elements
    eachElement(parents, function(element, _index, depth) {

      // do NOT add external labels directly
      if (isLabel$4(element)) {
        return;
      }

      // always copy external labels
      forEach$1(element.labels, function(label) {
        addElementData(label, depth);
      });

      function addRelatedElements(elements) {
        elements && elements.length && forEach$1(elements, function(element) {

          // add external labels
          forEach$1(element.labels, function(label) {
            addElementData(label, depth);
          });

          addElementData(element, depth);
        });
      }

      forEach$1([ element.attachers, element.incoming, element.outgoing ], addRelatedElements);

      addElementData(element, depth);

      var children = [];

      if (element.children) {
        children = element.children.slice();
      }

      // allow others to add children to tree
      self._eventBus.fire('copyPaste.createTree', {
        element: element,
        children: children
      });

      return children;
    });

    elements = map(elementsData, function(elementData) {
      return elementData.element;
    });

    // (2) copy elements
    elementsData = map(elementsData, function(elementData) {
      elementData.descriptor = {};

      self._eventBus.fire('copyPaste.copyElement', {
        descriptor: elementData.descriptor,
        element: elementData.element,
        elements: elements
      });

      return elementData;
    });

    // (3) sort elements by priority
    elementsData = sortBy(elementsData, function(elementData) {
      return elementData.descriptor.priority;
    });

    elements = map(elementsData, function(elementData) {
      return elementData.element;
    });

    // (4) create tree
    forEach$1(elementsData, function(elementData) {
      var depth = elementData.depth;

      if (!self.hasRelations(elementData.element, elements)) {
        removeElement(elementData.element, elements);

        return;
      }

      if (!canCopy(elementData.element, elements)) {
        removeElement(elementData.element, elements);

        return;
      }

      if (!tree[depth]) {
        tree[depth] = [];
      }

      tree[depth].push(elementData.descriptor);
    });

    return tree;
  };

  // helpers //////////

  function isAttacher$1(element) {
    return !!element.host;
  }

  function isConnection$b(element) {
    return !!element.waypoints;
  }

  function isLabel$4(element) {
    return !!element.labelTarget;
  }

  function copyWaypoints$1(element) {
    return map(element.waypoints, function(waypoint) {

      waypoint = copyWaypoint$1(waypoint);

      if (waypoint.original) {
        waypoint.original = copyWaypoint$1(waypoint.original);
      }

      return waypoint;
    });
  }

  function copyWaypoint$1(waypoint) {
    return assign({}, waypoint);
  }

  function removeElement(element, elements) {
    var index = elements.indexOf(element);

    if (index === -1) {
      return elements;
    }

    return elements.splice(index, 1);
  }

  var CopyPasteModule$1 = {
    __depends__: [
      ClipboardModule,
      CreateModule,
      MouseModule,
      RulesModule$1
    ],
    __init__: [ 'copyPaste' ],
    copyPaste: [ 'type', CopyPaste ]
  };

  function copyProperties$1(source, target, properties) {
    if (!isArray$3(properties)) {
      properties = [ properties ];
    }

    forEach$1(properties, function(property) {
      if (!isUndefined$2(source[property])) {
        target[property] = source[property];
      }
    });
  }

  var LOW_PRIORITY$h = 750;


  function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {

    function copy(bo, clone) {
      var targetBo = bpmnFactory.create(bo.$type);

      return moddleCopy.copyElement(bo, targetBo, null, clone);
    }

    eventBus.on('copyPaste.copyElement', LOW_PRIORITY$h, function(context) {
      var descriptor = context.descriptor,
          element = context.element,
          businessObject = getBusinessObject(element);

      // do not copy business object + di for labels;
      // will be pulled from the referenced label target
      if (isLabel$3(element)) {
        return descriptor;
      }

      var businessObjectCopy = descriptor.businessObject = copy(businessObject, true);
      var diCopy = descriptor.di = copy(getDi(element), true);
      diCopy.bpmnElement = businessObjectCopy;

      copyProperties$1(businessObjectCopy, descriptor, 'name');
      copyProperties$1(diCopy, descriptor, 'isExpanded');

      // default sequence flow
      if (businessObject.default) {
        descriptor.default = businessObject.default.id;
      }
    });

    var referencesKey = '-bpmn-js-refs';

    function getReferences(cache) {
      return (cache[referencesKey] = cache[referencesKey] || {});
    }

    function setReferences(cache, references) {
      cache[referencesKey] = references;
    }

    function resolveReferences(descriptor, cache, references) {
      var businessObject = getBusinessObject(descriptor);

      // default sequence flows
      if (descriptor.default) {

        // relationship cannot be resolved immediately
        references[ descriptor.default ] = {
          element: businessObject,
          property: 'default'
        };
      }

      // boundary events
      if (descriptor.host) {

        // relationship can be resolved immediately
        getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]);
      }

      return omit(references, reduce(references, function(array, reference, key) {
        var element = reference.element,
            property = reference.property;

        if (key === descriptor.id) {
          element[ property ] = businessObject;

          array.push(descriptor.id);
        }

        return array;
      }, []));
    }

    eventBus.on('copyPaste.pasteElement', function(context) {
      var cache = context.cache,
          descriptor = context.descriptor,
          businessObject = descriptor.businessObject,
          di = descriptor.di;

      // wire existing di + businessObject for external label
      if (isLabel$3(descriptor)) {
        descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
        descriptor.di = getDi(cache[ descriptor.labelTarget ]);

        return;
      }

      businessObject = descriptor.businessObject = copy(businessObject);

      di = descriptor.di = copy(di);
      di.bpmnElement = businessObject;

      copyProperties$1(descriptor, businessObject, [
        'isExpanded',
        'name'
      ]);

      descriptor.type = businessObject.$type;
    });

    // copy + paste processRef with participant

    eventBus.on('copyPaste.copyElement', LOW_PRIORITY$h, function(context) {
      var descriptor = context.descriptor,
          element = context.element;

      if (!is$1(element, 'bpmn:Participant')) {
        return;
      }

      var participantBo = getBusinessObject(element);

      if (participantBo.processRef) {
        descriptor.processRef = copy(participantBo.processRef, true);
      }
    });

    eventBus.on('copyPaste.pasteElement', function(context) {
      var descriptor = context.descriptor,
          processRef = descriptor.processRef;

      if (processRef) {
        descriptor.processRef = copy(processRef);
      }
    });

    // resolve references

    eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$h, function(context) {
      var cache = context.cache,
          descriptor = context.descriptor;

      // resolve references e.g. default sequence flow
      setReferences(
        cache,
        resolveReferences(descriptor, cache, getReferences(cache))
      );
    });

  }


  BpmnCopyPaste.$inject = [
    'bpmnFactory',
    'eventBus',
    'moddleCopy'
  ];

  // helpers //////////

  function isLabel$3(element) {
    return !!element.labelTarget;
  }

  var DISALLOWED_PROPERTIES = [
    'artifacts',
    'dataInputAssociations',
    'dataOutputAssociations',
    'default',
    'flowElements',
    'lanes',
    'incoming',
    'outgoing',
    'categoryValue'
  ];

  /**
   * @typedef {Function} <moddleCopy.canCopyProperties> listener
   *
   * @param {Object} context
   * @param {Array<string>} context.propertyNames
   * @param {ModdleElement} context.sourceElement
   * @param {ModdleElement} context.targetElement
   *
   * @returns {Array<string>|boolean} - Return properties to be copied or false to disallow
   * copying.
   */

  /**
   * @typedef {Function} <moddleCopy.canCopyProperty> listener
   *
   * @param {Object} context
   * @param {ModdleElement} context.parent
   * @param {*} context.property
   * @param {string} context.propertyName
   *
   * @returns {*|boolean} - Return copied property or false to disallow
   * copying.
   */

  /**
   * @typedef {Function} <moddleCopy.canSetCopiedProperty> listener
   *
   * @param {Object} context
   * @param {ModdleElement} context.parent
   * @param {*} context.property
   * @param {string} context.propertyName
   *
   * @returns {boolean} - Return false to disallow
   * setting copied property.
   */

  /**
   * Utility for copying model properties from source element to target element.
   *
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   * @param {BpmnModdle} moddle
   */
  function ModdleCopy(eventBus, bpmnFactory, moddle) {
    this._bpmnFactory = bpmnFactory;
    this._eventBus = eventBus;
    this._moddle = moddle;

    // copy extension elements last
    eventBus.on('moddleCopy.canCopyProperties', function(context) {
      var propertyNames = context.propertyNames;

      if (!propertyNames || !propertyNames.length) {
        return;
      }

      return sortBy(propertyNames, function(propertyName) {
        return propertyName === 'extensionElements';
      });
    });

    // default check whether property can be copied
    eventBus.on('moddleCopy.canCopyProperty', function(context) {
      var parent = context.parent,
          parentDescriptor = isObject(parent) && parent.$descriptor,
          propertyName = context.propertyName;

      if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) {

        // disallow copying property
        return false;
      }

      if (propertyName &&
        parentDescriptor &&
        !find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {

        // disallow copying property
        return false;
      }
    });

    // do NOT allow to copy empty extension elements
    eventBus.on('moddleCopy.canSetCopiedProperty', function(context) {
      var property = context.property;

      if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {

        // disallow setting copied property
        return false;
      }
    });
  }

  ModdleCopy.$inject = [
    'eventBus',
    'bpmnFactory',
    'moddle'
  ];

  /**
   * Copy model properties of source element to target element.
   *
   * @param {ModdleElement} sourceElement
   * @param {ModdleElement} targetElement
   * @param {Array<string>} [propertyNames]
   * @param {boolean} clone
   *
   * @param {ModdleElement}
   */
  ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone) {
    var self = this;

    if (propertyNames && !isArray$3(propertyNames)) {
      propertyNames = [ propertyNames ];
    }

    propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);

    var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
      propertyNames: propertyNames,
      sourceElement: sourceElement,
      targetElement: targetElement,
      clone: clone
    });

    if (canCopyProperties === false) {
      return targetElement;
    }

    if (isArray$3(canCopyProperties)) {
      propertyNames = canCopyProperties;
    }

    // copy properties
    forEach$1(propertyNames, function(propertyName) {
      var sourceProperty;

      if (has$1(sourceElement, propertyName)) {
        sourceProperty = sourceElement.get(propertyName);
      }

      var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName, clone);

      if (!isDefined(copiedProperty)) {
        return;
      }

      var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', {
        parent: targetElement,
        property: copiedProperty,
        propertyName: propertyName
      });

      if (canSetProperty === false) {
        return;
      }

      // TODO(nikku): unclaim old IDs if ID property is copied over
      // this._moddle.getPropertyDescriptor(parent, propertyName)
      targetElement.set(propertyName, copiedProperty);
    });

    return targetElement;
  };

  /**
   * Copy model property.
   *
   * @param {*} property
   * @param {ModdleElement} parent
   * @param {string} propertyName
   * @param {boolean} clone
   *
   * @returns {*}
   */
  ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone) {
    var self = this;

    // allow others to copy property
    var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
      parent: parent,
      property: property,
      propertyName: propertyName,
      clone: clone
    });

    // return if copying is NOT allowed
    if (copiedProperty === false) {
      return;
    }

    if (copiedProperty) {
      if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
        copiedProperty.$parent = parent;
      }

      return copiedProperty;
    }

    var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);

    // do NOT copy references
    if (propertyDescriptor.isReference) {
      return;
    }

    // copy id
    if (propertyDescriptor.isId) {
      return property && this._copyId(property, parent, clone);
    }

    // copy arrays
    if (isArray$3(property)) {
      return reduce(property, function(childProperties, childProperty) {

        // recursion
        copiedProperty = self.copyProperty(childProperty, parent, propertyName, clone);

        // copying might NOT be allowed
        if (copiedProperty) {
          return childProperties.concat(copiedProperty);
        }

        return childProperties;
      }, []);
    }

    // copy model elements
    if (isObject(property) && property.$type) {
      if (this._moddle.getElementDescriptor(property).isGeneric) {
        return;
      }

      copiedProperty = self._bpmnFactory.create(property.$type);

      copiedProperty.$parent = parent;

      // recursion
      copiedProperty = self.copyElement(property, copiedProperty, null, clone);

      return copiedProperty;
    }

    // copy primitive properties
    return property;
  };

  ModdleCopy.prototype._copyId = function(id, element, clone) {

    if (clone) {
      return id;
    }

    // disallow if already taken
    if (this._moddle.ids.assigned(id)) {
      return;
    } else {

      this._moddle.ids.claim(id, element);
      return id;
    }
  };

  // helpers //////////

  function getPropertyNames(descriptor, keepDefaultProperties) {
    return reduce(descriptor.properties, function(properties, property) {

      if (keepDefaultProperties && property.default) {
        return properties;
      }

      return properties.concat(property.name);
    }, []);
  }

  function is(element, type) {
    return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type);
  }

  var CopyPasteModule = {
    __depends__: [
      CopyPasteModule$1
    ],
    __init__: [ 'bpmnCopyPaste', 'moddleCopy' ],
    bpmnCopyPaste: [ 'type', BpmnCopyPaste ],
    moddleCopy: [ 'type', ModdleCopy ]
  };

  var round$5 = Math.round;

  /**
   * Service that allow replacing of elements.
   */
  function Replace(modeling) {

    this._modeling = modeling;
  }

  Replace.$inject = [ 'modeling' ];

  /**
   * @param {Element} oldElement - Element to be replaced
   * @param {Object}  newElementData - Containing information about the new element,
   *                                   for example the new bounds and type.
   * @param {Object}  options - Custom options that will be attached to the context. It can be used to inject data
   *                            that is needed in the command chain. For example it could be used in
   *                            eventbus.on('commandStack.shape.replace.postExecute') to change shape attributes after
   *                            shape creation.
   */
  Replace.prototype.replaceElement = function(oldElement, newElementData, options) {

    if (oldElement.waypoints) {

      // TODO(nikku): we do not replace connections, yet
      return null;
    }

    var modeling = this._modeling;

    var width = newElementData.width || oldElement.width,
        height = newElementData.height || oldElement.height,
        x = newElementData.x || oldElement.x,
        y = newElementData.y || oldElement.y,
        centerX = round$5(x + width / 2),
        centerY = round$5(y + height / 2);

    // modeling API requires center coordinates,
    // account for that when handling shape bounds

    return modeling.replaceShape(
      oldElement,
      assign(
        {},
        newElementData,
        {
          x: centerX,
          y: centerY,
          width: width,
          height: height
        }
      ),
      options
    );
  };

  var ReplaceModule$1 = {
    __init__: [ 'replace' ],
    replace: [ 'type', Replace ]
  };

  function copyProperties(source, target, properties) {
    if (!isArray$3(properties)) {
      properties = [ properties ];
    }

    forEach$1(properties, function(property) {
      if (!isUndefined$2(source[property])) {
        target[property] = source[property];
      }
    });
  }


  var CUSTOM_PROPERTIES = [
    'cancelActivity',
    'instantiate',
    'eventGatewayType',
    'triggeredByEvent',
    'isInterrupting'
  ];

  /**
   * Check if element should be collapsed or expanded.
   */
  function shouldToggleCollapsed(element, targetElement) {

    var oldCollapsed = (
      element && has$1(element, 'collapsed') ? element.collapsed : !isExpanded(element)
    );

    var targetCollapsed;

    if (targetElement && (has$1(targetElement, 'collapsed') || has$1(targetElement, 'isExpanded'))) {

      // property is explicitly set so use it
      targetCollapsed = (
        has$1(targetElement, 'collapsed') ? targetElement.collapsed : !targetElement.isExpanded
      );
    } else {

      // keep old state
      targetCollapsed = oldCollapsed;
    }

    if (oldCollapsed !== targetCollapsed) {
      return true;
    }

    return false;
  }


  /**
   * This module takes care of replacing BPMN elements
   */
  function BpmnReplace(
      bpmnFactory,
      elementFactory,
      moddleCopy,
      modeling,
      replace,
      rules,
      selection
  ) {

    /**
     * Prepares a new business object for the replacement element
     * and triggers the replace operation.
     *
     * @param  {djs.model.Base} element
     * @param  {Object} target
     * @param  {Object} [hints]
     *
     * @return {djs.model.Base} the newly created element
     */
    function replaceElement(element, target, hints) {

      hints = hints || {};

      var type = target.type,
          oldBusinessObject = element.businessObject;

      if (isSubProcess(oldBusinessObject) && type === 'bpmn:SubProcess') {
        if (shouldToggleCollapsed(element, target)) {

          // expanding or collapsing process
          modeling.toggleCollapse(element);

          return element;
        }
      }

      var newBusinessObject = bpmnFactory.create(type);

      var newElement = {
        type: type,
        businessObject: newBusinessObject,
      };

      newElement.di = {};

      // colors will be set to DI
      copyProperties(element.di, newElement.di, [
        'fill',
        'stroke',
        'background-color',
        'border-color',
        'color'
      ]);

      var elementProps = getPropertyNames(oldBusinessObject.$descriptor),
          newElementProps = getPropertyNames(newBusinessObject.$descriptor, true),
          copyProps = intersection(elementProps, newElementProps);

      // initialize special properties defined in target definition
      assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));

      var properties = filter(copyProps, function(propertyName) {

        // copying event definitions, unless we replace
        if (propertyName === 'eventDefinitions') {
          return hasEventDefinition$1(element, target.eventDefinitionType);
        }

        // retain loop characteristics if the target element
        // is not an event sub process
        if (propertyName === 'loopCharacteristics') {
          return !isEventSubProcess(newBusinessObject);
        }

        // so the applied properties from 'target' don't get lost
        if (has$1(newBusinessObject, propertyName)) {
          return false;
        }

        if (propertyName === 'processRef' && target.isExpanded === false) {
          return false;
        }

        if (propertyName === 'triggeredByEvent') {
          return false;
        }

        return true;
      });

      newBusinessObject = moddleCopy.copyElement(
        oldBusinessObject,
        newBusinessObject,
        properties
      );

      // initialize custom BPMN extensions
      if (target.eventDefinitionType) {

        // only initialize with new eventDefinition
        // if we did not set an event definition yet,
        // i.e. because we copied it
        if (!hasEventDefinition$1(newBusinessObject, target.eventDefinitionType)) {
          newElement.eventDefinitionType = target.eventDefinitionType;
          newElement.eventDefinitionAttrs = target.eventDefinitionAttrs;
        }
      }

      if (is$1(oldBusinessObject, 'bpmn:Activity')) {

        if (isSubProcess(oldBusinessObject)) {

          // no toggeling, so keep old state
          newElement.isExpanded = isExpanded(element);
        }

        // else if property is explicitly set, use it
        else if (target && has$1(target, 'isExpanded')) {
          newElement.isExpanded = target.isExpanded;

          // assign default size of new expanded element
          var defaultSize = elementFactory.getDefaultSize(newBusinessObject, {
            isExpanded: newElement.isExpanded
          });

          newElement.width = defaultSize.width;
          newElement.height = defaultSize.height;

          // keep element centered
          newElement.x = element.x - (newElement.width - element.width) / 2;
          newElement.y = element.y - (newElement.height - element.height) / 2;
        }

        // TODO: need also to respect min/max Size
        // copy size, from an expanded subprocess to an expanded alternative subprocess
        // except bpmn:Task, because Task is always expanded
        if ((isExpanded(element) && !is$1(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
          newElement.width = element.width;
          newElement.height = element.height;
        }
      }

      // remove children if not expanding sub process
      if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
        hints.moveChildren = false;
      }

      // transform collapsed/expanded pools
      if (is$1(oldBusinessObject, 'bpmn:Participant')) {

        // create expanded pool
        if (target.isExpanded === true) {
          newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
        } else {

          // remove children when transforming to collapsed pool
          hints.moveChildren = false;
        }

        // apply same width and default height
        newElement.width = element.width;
        newElement.height = elementFactory.getDefaultSize(newElement).height;
      }

      if (!rules.allowed('shape.resize', { shape: newBusinessObject })) {
        newElement.height = elementFactory.getDefaultSize(newElement).height;
        newElement.width = elementFactory.getDefaultSize(newElement).width;
      }

      newBusinessObject.name = oldBusinessObject.name;

      // retain default flow's reference between inclusive <-> exclusive gateways and activities
      if (
        isAny(oldBusinessObject, [
          'bpmn:ExclusiveGateway',
          'bpmn:InclusiveGateway',
          'bpmn:Activity'
        ]) &&
        isAny(newBusinessObject, [
          'bpmn:ExclusiveGateway',
          'bpmn:InclusiveGateway',
          'bpmn:Activity'
        ])
      ) {
        newBusinessObject.default = oldBusinessObject.default;
      }

      if (
        target.host &&
        !is$1(oldBusinessObject, 'bpmn:BoundaryEvent') &&
        is$1(newBusinessObject, 'bpmn:BoundaryEvent')
      ) {
        newElement.host = target.host;
      }

      // The DataStoreReference element is 14px wider than the DataObjectReference element
      // This ensures that they stay centered on the x axis when replaced
      if (
        newElement.type === 'bpmn:DataStoreReference' ||
        newElement.type === 'bpmn:DataObjectReference'
      ) {
        newElement.x = element.x + (element.width - newElement.width) / 2;
      }


      newElement = replace.replaceElement(element, newElement, hints);

      if (hints.select !== false) {
        selection.select(newElement);
      }

      return newElement;
    }

    this.replaceElement = replaceElement;
  }

  BpmnReplace.$inject = [
    'bpmnFactory',
    'elementFactory',
    'moddleCopy',
    'modeling',
    'replace',
    'rules',
    'selection'
  ];


  function isSubProcess(bo) {
    return is$1(bo, 'bpmn:SubProcess');
  }

  function hasEventDefinition$1(element, type) {

    var bo = getBusinessObject(element);

    return type && bo.get('eventDefinitions').some(function(definition) {
      return is$1(definition, type);
    });
  }

  /**
   * Compute intersection between two arrays.
   */
  function intersection(a1, a2) {
    return a1.filter(function(el) {
      return a2.indexOf(el) !== -1;
    });
  }

  var ReplaceModule = {
    __depends__: [
      CopyPasteModule,
      ReplaceModule$1,
      SelectionModule
    ],
    bpmnReplace: [ 'type', BpmnReplace ]
  };

  /**
   * Returns true, if an element is from a different type
   * than a target definition. Takes into account the type,
   * event definition type and triggeredByEvent property.
   *
   * @param {djs.model.Base} element
   *
   * @return {boolean}
   */
  function isDifferentType(element) {

    return function(entry) {
      var target = entry.target;

      var businessObject = getBusinessObject(element),
          eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0];

      var isTypeEqual = businessObject.$type === target.type;

      var isEventDefinitionEqual = (
        (eventDefinition && eventDefinition.$type) === target.eventDefinitionType
      );

      var isTriggeredByEventEqual = (
        businessObject.triggeredByEvent === target.triggeredByEvent
      );

      var isExpandedEqual = (
        target.isExpanded === undefined ||
        target.isExpanded === isExpanded(element)
      );

      return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
    };
  }

  var START_EVENT = [
    {
      label: 'Start Event',
      actionName: 'replace-with-none-start',
      className: 'bpmn-icon-start-event-none',
      target: {
        type: 'bpmn:StartEvent'
      }
    },
    {
      label: 'Intermediate Throw Event',
      actionName: 'replace-with-none-intermediate-throwing',
      className: 'bpmn-icon-intermediate-event-none',
      target: {
        type: 'bpmn:IntermediateThrowEvent'
      }
    },
    {
      label: 'End Event',
      actionName: 'replace-with-none-end',
      className: 'bpmn-icon-end-event-none',
      target: {
        type: 'bpmn:EndEvent'
      }
    },
    {
      label: 'Message Start Event',
      actionName: 'replace-with-message-start',
      className: 'bpmn-icon-start-event-message',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Timer Start Event',
      actionName: 'replace-with-timer-start',
      className: 'bpmn-icon-start-event-timer',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition'
      }
    },
    {
      label: 'Conditional Start Event',
      actionName: 'replace-with-conditional-start',
      className: 'bpmn-icon-start-event-condition',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition'
      }
    },
    {
      label: 'Signal Start Event',
      actionName: 'replace-with-signal-start',
      className: 'bpmn-icon-start-event-signal',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    }
  ];

  var START_EVENT_SUB_PROCESS = [
    {
      label: 'Start Event',
      actionName: 'replace-with-none-start',
      className: 'bpmn-icon-start-event-none',
      target: {
        type: 'bpmn:StartEvent'
      }
    },
    {
      label: 'Intermediate Throw Event',
      actionName: 'replace-with-none-intermediate-throwing',
      className: 'bpmn-icon-intermediate-event-none',
      target: {
        type: 'bpmn:IntermediateThrowEvent'
      }
    },
    {
      label: 'End Event',
      actionName: 'replace-with-none-end',
      className: 'bpmn-icon-end-event-none',
      target: {
        type: 'bpmn:EndEvent'
      }
    }
  ];

  var INTERMEDIATE_EVENT = [
    {
      label: 'Start Event',
      actionName: 'replace-with-none-start',
      className: 'bpmn-icon-start-event-none',
      target: {
        type: 'bpmn:StartEvent'
      }
    },
    {
      label: 'Intermediate Throw Event',
      actionName: 'replace-with-none-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-none',
      target: {
        type: 'bpmn:IntermediateThrowEvent'
      }
    },
    {
      label: 'End Event',
      actionName: 'replace-with-none-end',
      className: 'bpmn-icon-end-event-none',
      target: {
        type: 'bpmn:EndEvent'
      }
    },
    {
      label: 'Message Intermediate Catch Event',
      actionName: 'replace-with-message-intermediate-catch',
      className: 'bpmn-icon-intermediate-event-catch-message',
      target: {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Message Intermediate Throw Event',
      actionName: 'replace-with-message-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-throw-message',
      target: {
        type: 'bpmn:IntermediateThrowEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Timer Intermediate Catch Event',
      actionName: 'replace-with-timer-intermediate-catch',
      className: 'bpmn-icon-intermediate-event-catch-timer',
      target: {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition'
      }
    },
    {
      label: 'Escalation Intermediate Throw Event',
      actionName: 'replace-with-escalation-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-throw-escalation',
      target: {
        type: 'bpmn:IntermediateThrowEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition'
      }
    },
    {
      label: 'Conditional Intermediate Catch Event',
      actionName: 'replace-with-conditional-intermediate-catch',
      className: 'bpmn-icon-intermediate-event-catch-condition',
      target: {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition'
      }
    },
    {
      label: 'Link Intermediate Catch Event',
      actionName: 'replace-with-link-intermediate-catch',
      className: 'bpmn-icon-intermediate-event-catch-link',
      target: {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: 'bpmn:LinkEventDefinition',
        eventDefinitionAttrs: {
          name: ''
        }
      }
    },
    {
      label: 'Link Intermediate Throw Event',
      actionName: 'replace-with-link-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-throw-link',
      target: {
        type: 'bpmn:IntermediateThrowEvent',
        eventDefinitionType: 'bpmn:LinkEventDefinition',
        eventDefinitionAttrs: {
          name: ''
        }
      }
    },
    {
      label: 'Compensation Intermediate Throw Event',
      actionName: 'replace-with-compensation-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-throw-compensation',
      target: {
        type: 'bpmn:IntermediateThrowEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition'
      }
    },
    {
      label: 'Signal Intermediate Catch Event',
      actionName: 'replace-with-signal-intermediate-catch',
      className: 'bpmn-icon-intermediate-event-catch-signal',
      target: {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    },
    {
      label: 'Signal Intermediate Throw Event',
      actionName: 'replace-with-signal-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-throw-signal',
      target: {
        type: 'bpmn:IntermediateThrowEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    }
  ];

  var END_EVENT = [
    {
      label: 'Start Event',
      actionName: 'replace-with-none-start',
      className: 'bpmn-icon-start-event-none',
      target: {
        type: 'bpmn:StartEvent'
      }
    },
    {
      label: 'Intermediate Throw Event',
      actionName: 'replace-with-none-intermediate-throw',
      className: 'bpmn-icon-intermediate-event-none',
      target: {
        type: 'bpmn:IntermediateThrowEvent'
      }
    },
    {
      label: 'End Event',
      actionName: 'replace-with-none-end',
      className: 'bpmn-icon-end-event-none',
      target: {
        type: 'bpmn:EndEvent'
      }
    },
    {
      label: 'Message End Event',
      actionName: 'replace-with-message-end',
      className: 'bpmn-icon-end-event-message',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Escalation End Event',
      actionName: 'replace-with-escalation-end',
      className: 'bpmn-icon-end-event-escalation',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition'
      }
    },
    {
      label: 'Error End Event',
      actionName: 'replace-with-error-end',
      className: 'bpmn-icon-end-event-error',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:ErrorEventDefinition'
      }
    },
    {
      label: 'Cancel End Event',
      actionName: 'replace-with-cancel-end',
      className: 'bpmn-icon-end-event-cancel',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:CancelEventDefinition'
      }
    },
    {
      label: 'Compensation End Event',
      actionName: 'replace-with-compensation-end',
      className: 'bpmn-icon-end-event-compensation',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition'
      }
    },
    {
      label: 'Signal End Event',
      actionName: 'replace-with-signal-end',
      className: 'bpmn-icon-end-event-signal',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    },
    {
      label: 'Terminate End Event',
      actionName: 'replace-with-terminate-end',
      className: 'bpmn-icon-end-event-terminate',
      target: {
        type: 'bpmn:EndEvent',
        eventDefinitionType: 'bpmn:TerminateEventDefinition'
      }
    }
  ];

  var GATEWAY = [
    {
      label: 'Exclusive Gateway',
      actionName: 'replace-with-exclusive-gateway',
      className: 'bpmn-icon-gateway-xor',
      target: {
        type: 'bpmn:ExclusiveGateway'
      }
    },
    {
      label: 'Parallel Gateway',
      actionName: 'replace-with-parallel-gateway',
      className: 'bpmn-icon-gateway-parallel',
      target: {
        type: 'bpmn:ParallelGateway'
      }
    },
    {
      label: 'Inclusive Gateway',
      actionName: 'replace-with-inclusive-gateway',
      className: 'bpmn-icon-gateway-or',
      target: {
        type: 'bpmn:InclusiveGateway'
      }
    },
    {
      label: 'Complex Gateway',
      actionName: 'replace-with-complex-gateway',
      className: 'bpmn-icon-gateway-complex',
      target: {
        type: 'bpmn:ComplexGateway'
      }
    },
    {
      label: 'Event based Gateway',
      actionName: 'replace-with-event-based-gateway',
      className: 'bpmn-icon-gateway-eventbased',
      target: {
        type: 'bpmn:EventBasedGateway',
        instantiate: false,
        eventGatewayType: 'Exclusive'
      }
    }

    // Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194
    // {
    //   label: 'Event based instantiating Gateway',
    //   actionName: 'replace-with-exclusive-event-based-gateway',
    //   className: 'bpmn-icon-exclusive-event-based',
    //   target: {
    //     type: 'bpmn:EventBasedGateway'
    //   },
    //   options: {
    //     businessObject: { instantiate: true, eventGatewayType: 'Exclusive' }
    //   }
    // },
    // {
    //   label: 'Parallel Event based instantiating Gateway',
    //   actionName: 'replace-with-parallel-event-based-instantiate-gateway',
    //   className: 'bpmn-icon-parallel-event-based-instantiate-gateway',
    //   target: {
    //     type: 'bpmn:EventBasedGateway'
    //   },
    //   options: {
    //     businessObject: { instantiate: true, eventGatewayType: 'Parallel' }
    //   }
    // }
  ];

  var SUBPROCESS_EXPANDED = [
    {
      label: 'Transaction',
      actionName: 'replace-with-transaction',
      className: 'bpmn-icon-transaction',
      target: {
        type: 'bpmn:Transaction',
        isExpanded: true
      }
    },
    {
      label: 'Event Sub Process',
      actionName: 'replace-with-event-subprocess',
      className: 'bpmn-icon-event-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        triggeredByEvent: true,
        isExpanded: true
      }
    },
    {
      label: 'Sub Process (collapsed)',
      actionName: 'replace-with-collapsed-subprocess',
      className: 'bpmn-icon-subprocess-collapsed',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: false
      }
    }
  ];

  var TRANSACTION = [
    {
      label: 'Sub Process',
      actionName: 'replace-with-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: true
      }
    },
    {
      label: 'Event Sub Process',
      actionName: 'replace-with-event-subprocess',
      className: 'bpmn-icon-event-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        triggeredByEvent: true,
        isExpanded: true
      }
    }
  ];

  var EVENT_SUB_PROCESS = [
    {
      label: 'Sub Process',
      actionName: 'replace-with-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: true
      }
    },
    {
      label: 'Transaction',
      actionName: 'replace-with-transaction',
      className: 'bpmn-icon-transaction',
      target: {
        type: 'bpmn:Transaction',
        isExpanded: true
      }
    }
  ];

  var TASK = [
    {
      label: 'Task',
      actionName: 'replace-with-task',
      className: 'bpmn-icon-task',
      target: {
        type: 'bpmn:Task'
      }
    },
    {
      label: 'Send Task',
      actionName: 'replace-with-send-task',
      className: 'bpmn-icon-send',
      target: {
        type: 'bpmn:SendTask'
      }
    },
    {
      label: 'Receive Task',
      actionName: 'replace-with-receive-task',
      className: 'bpmn-icon-receive',
      target: {
        type: 'bpmn:ReceiveTask'
      }
    },
    {
      label: 'User Task',
      actionName: 'replace-with-user-task',
      className: 'bpmn-icon-user',
      target: {
        type: 'bpmn:UserTask'
      }
    },
    {
      label: 'Manual Task',
      actionName: 'replace-with-manual-task',
      className: 'bpmn-icon-manual',
      target: {
        type: 'bpmn:ManualTask'
      }
    },
    {
      label: 'Business Rule Task',
      actionName: 'replace-with-rule-task',
      className: 'bpmn-icon-business-rule',
      target: {
        type: 'bpmn:BusinessRuleTask'
      }
    },
    {
      label: 'Service Task',
      actionName: 'replace-with-service-task',
      className: 'bpmn-icon-service',
      target: {
        type: 'bpmn:ServiceTask'
      }
    },
    {
      label: 'Script Task',
      actionName: 'replace-with-script-task',
      className: 'bpmn-icon-script',
      target: {
        type: 'bpmn:ScriptTask'
      }
    },
    {
      label: 'Call Activity',
      actionName: 'replace-with-call-activity',
      className: 'bpmn-icon-call-activity',
      target: {
        type: 'bpmn:CallActivity'
      }
    },
    {
      label: 'Sub Process (collapsed)',
      actionName: 'replace-with-collapsed-subprocess',
      className: 'bpmn-icon-subprocess-collapsed',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: false
      }
    },
    {
      label: 'Sub Process (expanded)',
      actionName: 'replace-with-expanded-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: true
      }
    }
  ];

  var DATA_OBJECT_REFERENCE = [
    {
      label: 'Data Store Reference',
      actionName: 'replace-with-data-store-reference',
      className: 'bpmn-icon-data-store',
      target: {
        type: 'bpmn:DataStoreReference'
      }
    }
  ];

  var DATA_STORE_REFERENCE = [
    {
      label: 'Data Object Reference',
      actionName: 'replace-with-data-object-reference',
      className: 'bpmn-icon-data-object',
      target: {
        type: 'bpmn:DataObjectReference'
      }
    }
  ];

  var BOUNDARY_EVENT = [
    {
      label: 'Message Boundary Event',
      actionName: 'replace-with-message-boundary',
      className: 'bpmn-icon-intermediate-event-catch-message',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Timer Boundary Event',
      actionName: 'replace-with-timer-boundary',
      className: 'bpmn-icon-intermediate-event-catch-timer',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition'
      }
    },
    {
      label: 'Escalation Boundary Event',
      actionName: 'replace-with-escalation-boundary',
      className: 'bpmn-icon-intermediate-event-catch-escalation',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition'
      }
    },
    {
      label: 'Conditional Boundary Event',
      actionName: 'replace-with-conditional-boundary',
      className: 'bpmn-icon-intermediate-event-catch-condition',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition'
      }
    },
    {
      label: 'Error Boundary Event',
      actionName: 'replace-with-error-boundary',
      className: 'bpmn-icon-intermediate-event-catch-error',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:ErrorEventDefinition'
      }
    },
    {
      label: 'Cancel Boundary Event',
      actionName: 'replace-with-cancel-boundary',
      className: 'bpmn-icon-intermediate-event-catch-cancel',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:CancelEventDefinition'
      }
    },
    {
      label: 'Signal Boundary Event',
      actionName: 'replace-with-signal-boundary',
      className: 'bpmn-icon-intermediate-event-catch-signal',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    },
    {
      label: 'Compensation Boundary Event',
      actionName: 'replace-with-compensation-boundary',
      className: 'bpmn-icon-intermediate-event-catch-compensation',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition'
      }
    },
    {
      label: 'Message Boundary Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-message-boundary',
      className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition',
        cancelActivity: false
      }
    },
    {
      label: 'Timer Boundary Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-timer-boundary',
      className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition',
        cancelActivity: false
      }
    },
    {
      label: 'Escalation Boundary Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-escalation-boundary',
      className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition',
        cancelActivity: false
      }
    },
    {
      label: 'Conditional Boundary Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-conditional-boundary',
      className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition',
        cancelActivity: false
      }
    },
    {
      label: 'Signal Boundary Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-signal-boundary',
      className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition',
        cancelActivity: false
      }
    }
  ];

  var EVENT_SUB_PROCESS_START_EVENT = [
    {
      label: 'Message Start Event',
      actionName: 'replace-with-message-start',
      className: 'bpmn-icon-start-event-message',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition'
      }
    },
    {
      label: 'Timer Start Event',
      actionName: 'replace-with-timer-start',
      className: 'bpmn-icon-start-event-timer',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition'
      }
    },
    {
      label: 'Conditional Start Event',
      actionName: 'replace-with-conditional-start',
      className: 'bpmn-icon-start-event-condition',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition'
      }
    },
    {
      label: 'Signal Start Event',
      actionName: 'replace-with-signal-start',
      className: 'bpmn-icon-start-event-signal',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition'
      }
    },
    {
      label: 'Error Start Event',
      actionName: 'replace-with-error-start',
      className: 'bpmn-icon-start-event-error',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ErrorEventDefinition'
      }
    },
    {
      label: 'Escalation Start Event',
      actionName: 'replace-with-escalation-start',
      className: 'bpmn-icon-start-event-escalation',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition'
      }
    },
    {
      label: 'Compensation Start Event',
      actionName: 'replace-with-compensation-start',
      className: 'bpmn-icon-start-event-compensation',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition'
      }
    },
    {
      label: 'Message Start Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-message-start',
      className: 'bpmn-icon-start-event-non-interrupting-message',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:MessageEventDefinition',
        isInterrupting: false
      }
    },
    {
      label: 'Timer Start Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-timer-start',
      className: 'bpmn-icon-start-event-non-interrupting-timer',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition',
        isInterrupting: false
      }
    },
    {
      label: 'Conditional Start Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-conditional-start',
      className: 'bpmn-icon-start-event-non-interrupting-condition',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition',
        isInterrupting: false
      }
    },
    {
      label: 'Signal Start Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-signal-start',
      className: 'bpmn-icon-start-event-non-interrupting-signal',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition',
        isInterrupting: false
      }
    },
    {
      label: 'Escalation Start Event (non-interrupting)',
      actionName: 'replace-with-non-interrupting-escalation-start',
      className: 'bpmn-icon-start-event-non-interrupting-escalation',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition',
        isInterrupting: false
      }
    }
  ];

  var SEQUENCE_FLOW = [
    {
      label: 'Sequence Flow',
      actionName: 'replace-with-sequence-flow',
      className: 'bpmn-icon-connection'
    },
    {
      label: 'Default Flow',
      actionName: 'replace-with-default-flow',
      className: 'bpmn-icon-default-flow'
    },
    {
      label: 'Conditional Flow',
      actionName: 'replace-with-conditional-flow',
      className: 'bpmn-icon-conditional-flow'
    }
  ];

  var PARTICIPANT = [
    {
      label: 'Expanded Pool',
      actionName: 'replace-with-expanded-pool',
      className: 'bpmn-icon-participant',
      target: {
        type: 'bpmn:Participant',
        isExpanded: true
      }
    },
    {
      label: function(element) {
        var label = 'Empty Pool';

        if (element.children && element.children.length) {
          label += ' (removes content)';
        }

        return label;
      },
      actionName: 'replace-with-collapsed-pool',

      // TODO(@janstuemmel): maybe design new icon
      className: 'bpmn-icon-lane',
      target: {
        type: 'bpmn:Participant',
        isExpanded: false
      }
    }
  ];

  /**
   * This module is an element agnostic replace menu provider for the popup menu.
   */
  function ReplaceMenuProvider(
      bpmnFactory, popupMenu, modeling, moddle,
      bpmnReplace, rules, translate) {

    this._bpmnFactory = bpmnFactory;
    this._popupMenu = popupMenu;
    this._modeling = modeling;
    this._moddle = moddle;
    this._bpmnReplace = bpmnReplace;
    this._rules = rules;
    this._translate = translate;

    this.register();
  }

  ReplaceMenuProvider.$inject = [
    'bpmnFactory',
    'popupMenu',
    'modeling',
    'moddle',
    'bpmnReplace',
    'rules',
    'translate'
  ];


  /**
   * Register replace menu provider in the popup menu
   */
  ReplaceMenuProvider.prototype.register = function() {
    this._popupMenu.registerProvider('bpmn-replace', this);
  };


  /**
   * Get all entries from replaceOptions for the given element and apply filters
   * on them. Get for example only elements, which are different from the current one.
   *
   * @param {djs.model.Base} element
   *
   * @return {Array<Object>} a list of menu entry items
   */
  ReplaceMenuProvider.prototype.getEntries = function(element) {

    var businessObject = element.businessObject;

    var rules = this._rules;

    var entries;

    if (!rules.allowed('shape.replace', { element: element })) {
      return [];
    }

    var differentType = isDifferentType(element);

    if (is$1(businessObject, 'bpmn:DataObjectReference')) {
      return this._createEntries(element, DATA_OBJECT_REFERENCE);
    }

    if (is$1(businessObject, 'bpmn:DataStoreReference') && !is$1(element.parent, 'bpmn:Collaboration')) {
      return this._createEntries(element, DATA_STORE_REFERENCE);
    }

    // start events outside sub processes
    if (is$1(businessObject, 'bpmn:StartEvent') && !is$1(businessObject.$parent, 'bpmn:SubProcess')) {

      entries = filter(START_EVENT, differentType);

      return this._createEntries(element, entries);
    }

    // expanded/collapsed pools
    if (is$1(businessObject, 'bpmn:Participant')) {

      entries = filter(PARTICIPANT, function(entry) {
        return isExpanded(element) !== entry.target.isExpanded;
      });

      return this._createEntries(element, entries);
    }

    // start events inside event sub processes
    if (is$1(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
      entries = filter(EVENT_SUB_PROCESS_START_EVENT, function(entry) {

        var target = entry.target;

        var isInterrupting = target.isInterrupting !== false;

        var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;

        // filters elements which types and event definition are equal but have have different interrupting types
        return differentType(entry) || !differentType(entry) && !isInterruptingEqual;

      });

      return this._createEntries(element, entries);
    }

    // start events inside sub processes
    if (is$1(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
        && is$1(businessObject.$parent, 'bpmn:SubProcess')) {
      entries = filter(START_EVENT_SUB_PROCESS, differentType);

      return this._createEntries(element, entries);
    }

    // end events
    if (is$1(businessObject, 'bpmn:EndEvent')) {

      entries = filter(END_EVENT, function(entry) {
        var target = entry.target;

        // hide cancel end events outside transactions
        if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is$1(businessObject.$parent, 'bpmn:Transaction')) {
          return false;
        }

        return differentType(entry);
      });

      return this._createEntries(element, entries);
    }

    // boundary events
    if (is$1(businessObject, 'bpmn:BoundaryEvent')) {

      entries = filter(BOUNDARY_EVENT, function(entry) {

        var target = entry.target;

        if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
           !is$1(businessObject.attachedToRef, 'bpmn:Transaction')) {
          return false;
        }
        var cancelActivity = target.cancelActivity !== false;

        var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;

        return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
      });

      return this._createEntries(element, entries);
    }

    // intermediate events
    if (is$1(businessObject, 'bpmn:IntermediateCatchEvent') ||
        is$1(businessObject, 'bpmn:IntermediateThrowEvent')) {

      entries = filter(INTERMEDIATE_EVENT, differentType);

      return this._createEntries(element, entries);
    }

    // gateways
    if (is$1(businessObject, 'bpmn:Gateway')) {

      entries = filter(GATEWAY, differentType);

      return this._createEntries(element, entries);
    }

    // transactions
    if (is$1(businessObject, 'bpmn:Transaction')) {

      entries = filter(TRANSACTION, differentType);

      return this._createEntries(element, entries);
    }

    // expanded event sub processes
    if (isEventSubProcess(businessObject) && isExpanded(element)) {

      entries = filter(EVENT_SUB_PROCESS, differentType);

      return this._createEntries(element, entries);
    }

    // expanded sub processes
    if (is$1(businessObject, 'bpmn:SubProcess') && isExpanded(element)) {

      entries = filter(SUBPROCESS_EXPANDED, differentType);

      return this._createEntries(element, entries);
    }

    // collapsed ad hoc sub processes
    if (is$1(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(element)) {

      entries = filter(TASK, function(entry) {

        var target = entry.target;

        var isTargetSubProcess = target.type === 'bpmn:SubProcess';

        var isTargetExpanded = target.isExpanded === true;

        return isDifferentType(element) && (!isTargetSubProcess || isTargetExpanded);
      });

      return this._createEntries(element, entries);
    }

    // sequence flows
    if (is$1(businessObject, 'bpmn:SequenceFlow')) {
      return this._createSequenceFlowEntries(element, SEQUENCE_FLOW);
    }

    // flow nodes
    if (is$1(businessObject, 'bpmn:FlowNode')) {
      entries = filter(TASK, differentType);

      // collapsed SubProcess can not be replaced with itself
      if (is$1(businessObject, 'bpmn:SubProcess') && !isExpanded(element)) {
        entries = filter(entries, function(entry) {
          return entry.label !== 'Sub Process (collapsed)';
        });
      }

      return this._createEntries(element, entries);
    }

    return [];
  };


  /**
   * Get a list of header items for the given element. This includes buttons
   * for multi instance markers and for the ad hoc marker.
   *
   * @param {djs.model.Base} element
   *
   * @return {Array<Object>} a list of menu entry items
   */
  ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {

    var headerEntries = [];

    if (is$1(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
      headerEntries = headerEntries.concat(this._getLoopEntries(element));
    }

    if (is$1(element, 'bpmn:DataObjectReference')) {
      headerEntries = headerEntries.concat(this._getDataObjectIsCollection(element));
    }

    if (is$1(element, 'bpmn:Participant')) {
      headerEntries = headerEntries.concat(this._getParticipantMultiplicity(element));
    }

    if (is$1(element, 'bpmn:SubProcess') &&
        !is$1(element, 'bpmn:Transaction') &&
        !isEventSubProcess(element)) {
      headerEntries.push(this._getAdHocEntry(element));
    }

    return headerEntries;
  };


  /**
   * Creates an array of menu entry objects for a given element and filters the replaceOptions
   * according to a filter function.
   *
   * @param  {djs.model.Base} element
   * @param  {Object} replaceOptions
   *
   * @return {Array<Object>} a list of menu items
   */
  ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
    var menuEntries = [];

    var self = this;

    forEach$1(replaceOptions, function(definition) {
      var entry = self._createMenuEntry(definition, element);

      menuEntries.push(entry);
    });

    return menuEntries;
  };

  /**
   * Creates an array of menu entry objects for a given sequence flow.
   *
   * @param  {djs.model.Base} element
   * @param  {Object} replaceOptions

   * @return {Array<Object>} a list of menu items
   */
  ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {

    var businessObject = getBusinessObject(element);

    var menuEntries = [];

    var modeling = this._modeling,
        moddle = this._moddle;

    var self = this;

    forEach$1(replaceOptions, function(entry) {

      switch (entry.actionName) {
      case 'replace-with-default-flow':
        if (businessObject.sourceRef.default !== businessObject &&
              (is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
               is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
               is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
               is$1(businessObject.sourceRef, 'bpmn:Activity'))) {

          menuEntries.push(self._createMenuEntry(entry, element, function() {
            modeling.updateProperties(element.source, { default: businessObject });
          }));
        }
        break;
      case 'replace-with-conditional-flow':
        if (!businessObject.conditionExpression && is$1(businessObject.sourceRef, 'bpmn:Activity')) {

          menuEntries.push(self._createMenuEntry(entry, element, function() {
            var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });

            modeling.updateProperties(element, { conditionExpression: conditionExpression });
          }));
        }
        break;
      default:

        // default flows
        if (is$1(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
          return menuEntries.push(self._createMenuEntry(entry, element, function() {
            modeling.updateProperties(element, { conditionExpression: undefined });
          }));
        }

        // conditional flows
        if ((is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
             is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
             is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
             is$1(businessObject.sourceRef, 'bpmn:Activity')) &&
             businessObject.sourceRef.default === businessObject) {

          return menuEntries.push(self._createMenuEntry(entry, element, function() {
            modeling.updateProperties(element.source, { default: undefined });
          }));
        }
      }
    });

    return menuEntries;
  };


  /**
   * Creates and returns a single menu entry item.
   *
   * @param  {Object} definition a single replace options definition object
   * @param  {djs.model.Base} element
   * @param  {Function} [action] an action callback function which gets called when
   *                             the menu entry is being triggered.
   *
   * @return {Object} menu entry item
   */
  ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
    var translate = this._translate;
    var replaceElement = this._bpmnReplace.replaceElement;

    var replaceAction = function() {
      return replaceElement(element, definition.target);
    };

    var label = definition.label;
    if (label && typeof label === 'function') {
      label = label(element);
    }

    action = action || replaceAction;

    var menuEntry = {
      label: translate(label),
      className: definition.className,
      id: definition.actionName,
      action: action
    };

    return menuEntry;
  };

  /**
   * Get a list of menu items containing buttons for multi instance markers
   *
   * @param  {djs.model.Base} element
   *
   * @return {Array<Object>} a list of menu items
   */
  ReplaceMenuProvider.prototype._getLoopEntries = function(element) {

    var self = this;
    var translate = this._translate;

    function toggleLoopEntry(event, entry) {
      var newLoopCharacteristics = getBusinessObject(element).loopCharacteristics;

      if (entry.active) {
        newLoopCharacteristics = undefined;
      } else {
        if (isUndefined$2(entry.options.isSequential) || !newLoopCharacteristics
        || !is$1(newLoopCharacteristics, entry.options.loopCharacteristics)) {
          newLoopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
        }

        newLoopCharacteristics.isSequential = entry.options.isSequential;
      }
      self._modeling.updateProperties(element, { loopCharacteristics: newLoopCharacteristics });
    }

    var businessObject = getBusinessObject(element),
        loopCharacteristics = businessObject.loopCharacteristics;

    var isSequential,
        isLoop,
        isParallel;

    if (loopCharacteristics) {
      isSequential = loopCharacteristics.isSequential;
      isLoop = loopCharacteristics.isSequential === undefined;
      isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
    }


    var loopEntries = [
      {
        id: 'toggle-parallel-mi',
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Parallel Multi Instance'),
        active: isParallel,
        action: toggleLoopEntry,
        options: {
          loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
          isSequential: false
        }
      },
      {
        id: 'toggle-sequential-mi',
        className: 'bpmn-icon-sequential-mi-marker',
        title: translate('Sequential Multi Instance'),
        active: isSequential,
        action: toggleLoopEntry,
        options: {
          loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
          isSequential: true
        }
      },
      {
        id: 'toggle-loop',
        className: 'bpmn-icon-loop-marker',
        title: translate('Loop'),
        active: isLoop,
        action: toggleLoopEntry,
        options: {
          loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
        }
      }
    ];
    return loopEntries;
  };

  /**
   * Get a list of menu items containing a button for the collection marker
   *
   * @param  {djs.model.Base} element
   *
   * @return {Array<Object>} a list of menu items
   */
  ReplaceMenuProvider.prototype._getDataObjectIsCollection = function(element) {

    var self = this;
    var translate = this._translate;

    function toggleIsCollection(event, entry) {
      self._modeling.updateModdleProperties(
        element,
        dataObject,
        { isCollection: !entry.active });
    }

    var dataObject = element.businessObject.dataObjectRef,
        isCollection = dataObject.isCollection;

    var dataObjectEntries = [
      {
        id: 'toggle-is-collection',
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Collection'),
        active: isCollection,
        action: toggleIsCollection,
      }
    ];
    return dataObjectEntries;
  };

  /**
   * Get a list of menu items containing a button for the participant multiplicity marker
   *
   * @param  {djs.model.Base} element
   *
   * @return {Array<Object>} a list of menu items
   */
  ReplaceMenuProvider.prototype._getParticipantMultiplicity = function(element) {

    var self = this;
    var bpmnFactory = this._bpmnFactory;
    var translate = this._translate;

    function toggleParticipantMultiplicity(event, entry) {
      var isActive = entry.active;
      var participantMultiplicity;

      if (!isActive) {
        participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity');
      }

      self._modeling.updateProperties(
        element,
        { participantMultiplicity: participantMultiplicity });
    }

    var participantMultiplicity = element.businessObject.participantMultiplicity;

    var participantEntries = [
      {
        id: 'toggle-participant-multiplicity',
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Participant Multiplicity'),
        active: !!participantMultiplicity,
        action: toggleParticipantMultiplicity,
      }
    ];
    return participantEntries;
  };


  /**
   * Get the menu items containing a button for the ad hoc marker
   *
   * @param  {djs.model.Base} element
   *
   * @return {Object} a menu item
   */
  ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
    var translate = this._translate;
    var businessObject = getBusinessObject(element);

    var isAdHoc = is$1(businessObject, 'bpmn:AdHocSubProcess');

    var replaceElement = this._bpmnReplace.replaceElement;

    var adHocEntry = {
      id: 'toggle-adhoc',
      className: 'bpmn-icon-ad-hoc-marker',
      title: translate('Ad-hoc'),
      active: isAdHoc,
      action: function(event, entry) {
        if (isAdHoc) {
          return replaceElement(element, { type: 'bpmn:SubProcess' }, {
            autoResize: false,
            layoutConnection: false
          });
        } else {
          return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, {
            autoResize: false,
            layoutConnection: false
          });
        }
      }
    };

    return adHocEntry;
  };

  var PopupMenuModule = {
    __depends__: [
      PopupMenuModule$1,
      ReplaceModule
    ],
    __init__: [ 'replaceMenuProvider' ],
    replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
  };

  var max$4 = Math.max,
      min$2 = Math.min;

  var DEFAULT_CHILD_BOX_PADDING = 20;


  /**
   * Substract a TRBL from another
   *
   * @param  {TRBL} trblA
   * @param  {TRBL} trblB
   *
   * @return {TRBL}
   */
  function substractTRBL(trblA, trblB) {
    return {
      top: trblA.top - trblB.top,
      right: trblA.right - trblB.right,
      bottom: trblA.bottom - trblB.bottom,
      left: trblA.left - trblB.left
    };
  }

  /**
   * Resize the given bounds by the specified delta from a given anchor point.
   *
   * @param {Bounds} bounds the bounding box that should be resized
   * @param {string} direction in which the element is resized (nw, ne, se, sw)
   * @param {Point} delta of the resize operation
   *
   * @return {Bounds} resized bounding box
   */
  function resizeBounds$1(bounds, direction, delta) {
    var dx = delta.x,
        dy = delta.y;

    var newBounds = {
      x: bounds.x,
      y: bounds.y,
      width: bounds.width,
      height: bounds.height
    };

    if (direction.indexOf('n') !== -1) {
      newBounds.y = bounds.y + dy;
      newBounds.height = bounds.height - dy;
    } else if (direction.indexOf('s') !== -1) {
      newBounds.height = bounds.height + dy;
    }

    if (direction.indexOf('e') !== -1) {
      newBounds.width = bounds.width + dx;
    } else if (direction.indexOf('w') !== -1) {
      newBounds.x = bounds.x + dx;
      newBounds.width = bounds.width - dx;
    }

    return newBounds;
  }


  /**
   * Resize the given bounds by applying the passed
   * { top, right, bottom, left } delta.
   *
   * @param {Bounds} bounds
   * @param {TRBL} trblResize
   *
   * @return {Bounds}
   */
  function resizeTRBL(bounds, resize) {
    return {
      x: bounds.x + (resize.left || 0),
      y: bounds.y + (resize.top || 0),
      width: bounds.width - (resize.left || 0) + (resize.right || 0),
      height: bounds.height - (resize.top || 0) + (resize.bottom || 0)
    };
  }


  function applyConstraints(attr, trbl, resizeConstraints) {

    var value = trbl[attr],
        minValue = resizeConstraints.min && resizeConstraints.min[attr],
        maxValue = resizeConstraints.max && resizeConstraints.max[attr];

    if (isNumber(minValue)) {
      value = (/top|left/.test(attr) ? min$2 : max$4)(value, minValue);
    }

    if (isNumber(maxValue)) {
      value = (/top|left/.test(attr) ? max$4 : min$2)(value, maxValue);
    }

    return value;
  }

  function ensureConstraints$1(currentBounds, resizeConstraints) {

    if (!resizeConstraints) {
      return currentBounds;
    }

    var currentTrbl = asTRBL(currentBounds);

    return asBounds({
      top: applyConstraints('top', currentTrbl, resizeConstraints),
      right: applyConstraints('right', currentTrbl, resizeConstraints),
      bottom: applyConstraints('bottom', currentTrbl, resizeConstraints),
      left: applyConstraints('left', currentTrbl, resizeConstraints)
    });
  }


  function getMinResizeBounds(direction, currentBounds, minDimensions, childrenBounds) {

    var currentBox = asTRBL(currentBounds);

    var minBox = {
      top: /n/.test(direction) ? currentBox.bottom - minDimensions.height : currentBox.top,
      left: /w/.test(direction) ? currentBox.right - minDimensions.width : currentBox.left,
      bottom: /s/.test(direction) ? currentBox.top + minDimensions.height : currentBox.bottom,
      right: /e/.test(direction) ? currentBox.left + minDimensions.width : currentBox.right
    };

    var childrenBox = childrenBounds ? asTRBL(childrenBounds) : minBox;

    var combinedBox = {
      top: min$2(minBox.top, childrenBox.top),
      left: min$2(minBox.left, childrenBox.left),
      bottom: max$4(minBox.bottom, childrenBox.bottom),
      right: max$4(minBox.right, childrenBox.right)
    };

    return asBounds(combinedBox);
  }

  function asPadding(mayBePadding, defaultValue) {
    if (typeof mayBePadding !== 'undefined') {
      return mayBePadding;
    } else {
      return DEFAULT_CHILD_BOX_PADDING;
    }
  }

  function addPadding$1(bbox, padding) {
    var left, right, top, bottom;

    if (typeof padding === 'object') {
      left = asPadding(padding.left);
      right = asPadding(padding.right);
      top = asPadding(padding.top);
      bottom = asPadding(padding.bottom);
    } else {
      left = right = top = bottom = asPadding(padding);
    }

    return {
      x: bbox.x - left,
      y: bbox.y - top,
      width: bbox.width + left + right,
      height: bbox.height + top + bottom
    };
  }


  /**
   * Is the given element part of the resize
   * targets min boundary box?
   *
   * This is the default implementation which excludes
   * connections and labels.
   *
   * @param {djs.model.Base} element
   */
  function isBBoxChild(element) {

    // exclude connections
    if (element.waypoints) {
      return false;
    }

    // exclude labels
    if (element.type === 'label') {
      return false;
    }

    return true;
  }

  /**
   * Return children bounding computed from a shapes children
   * or a list of prefiltered children.
   *
   * @param  {djs.model.Shape|Array<djs.model.Shape>} shapeOrChildren
   * @param  {number|Object} padding
   *
   * @return {Bounds}
   */
  function computeChildrenBBox(shapeOrChildren, padding) {

    var elements;

    // compute based on shape
    if (shapeOrChildren.length === undefined) {

      // grab all the children that are part of the
      // parents children box
      elements = filter(shapeOrChildren.children, isBBoxChild);

    } else {
      elements = shapeOrChildren;
    }

    if (elements.length) {
      return addPadding$1(getBBox(elements), padding);
    }
  }

  var abs$4 = Math.abs;


  function getTRBLResize(oldBounds, newBounds) {
    return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds));
  }


  var LANE_PARENTS = [
    'bpmn:Participant',
    'bpmn:Process',
    'bpmn:SubProcess'
  ];

  var LANE_INDENTATION = 30;


  /**
   * Collect all lane shapes in the given paren
   *
   * @param  {djs.model.Shape} shape
   * @param  {Array<djs.model.Base>} [collectedShapes]
   *
   * @return {Array<djs.model.Base>}
   */
  function collectLanes(shape, collectedShapes) {

    collectedShapes = collectedShapes || [];

    shape.children.filter(function(s) {
      if (is$1(s, 'bpmn:Lane')) {
        collectLanes(s, collectedShapes);

        collectedShapes.push(s);
      }
    });

    return collectedShapes;
  }


  /**
   * Return the lane children of the given element.
   *
   * @param {djs.model.Shape} shape
   *
   * @return {Array<djs.model.Shape>}
   */
  function getChildLanes(shape) {
    return shape.children.filter(function(c) {
      return is$1(c, 'bpmn:Lane');
    });
  }


  /**
   * Return the root element containing the given lane shape
   *
   * @param {djs.model.Shape} shape
   *
   * @return {djs.model.Shape}
   */
  function getLanesRoot(shape) {
    return getParent(shape, LANE_PARENTS) || shape;
  }


  /**
   * Compute the required resize operations for lanes
   * adjacent to the given shape, assuming it will be
   * resized to the given new bounds.
   *
   * @param {djs.model.Shape} shape
   * @param {Bounds} newBounds
   *
   * @return {Array<Object>}
   */
  function computeLanesResize(shape, newBounds) {

    var rootElement = getLanesRoot(shape);

    var initialShapes = is$1(rootElement, 'bpmn:Process') ? [] : [ rootElement ];

    var allLanes = collectLanes(rootElement, initialShapes),
        shapeTrbl = asTRBL(shape),
        shapeNewTrbl = asTRBL(newBounds),
        trblResize = getTRBLResize(shape, newBounds),
        resizeNeeded = [];

    allLanes.forEach(function(other) {

      if (other === shape) {
        return;
      }

      var topResize = 0,
          rightResize = trblResize.right,
          bottomResize = 0,
          leftResize = trblResize.left;

      var otherTrbl = asTRBL(other);

      if (trblResize.top) {
        if (abs$4(otherTrbl.bottom - shapeTrbl.top) < 10) {
          bottomResize = shapeNewTrbl.top - otherTrbl.bottom;
        }

        if (abs$4(otherTrbl.top - shapeTrbl.top) < 5) {
          topResize = shapeNewTrbl.top - otherTrbl.top;
        }
      }

      if (trblResize.bottom) {
        if (abs$4(otherTrbl.top - shapeTrbl.bottom) < 10) {
          topResize = shapeNewTrbl.bottom - otherTrbl.top;
        }

        if (abs$4(otherTrbl.bottom - shapeTrbl.bottom) < 5) {
          bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom;
        }
      }

      if (topResize || rightResize || bottomResize || leftResize) {

        resizeNeeded.push({
          shape: other,
          newBounds: resizeTRBL(other, {
            top: topResize,
            right: rightResize,
            bottom: bottomResize,
            left: leftResize
          })
        });
      }

    });

    return resizeNeeded;
  }

  /**
   * A provider for BPMN 2.0 elements context pad
   */
  function ContextPadProvider(
      config, injector, eventBus,
      contextPad, modeling, elementFactory,
      connect, create, popupMenu,
      canvas, rules, translate) {

    config = config || {};

    contextPad.registerProvider(this);

    this._contextPad = contextPad;

    this._modeling = modeling;

    this._elementFactory = elementFactory;
    this._connect = connect;
    this._create = create;
    this._popupMenu = popupMenu;
    this._canvas = canvas;
    this._rules = rules;
    this._translate = translate;

    if (config.autoPlace !== false) {
      this._autoPlace = injector.get('autoPlace', false);
    }

    eventBus.on('create.end', 250, function(event) {
      var context = event.context,
          shape = context.shape;

      if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
        return;
      }

      var entries = contextPad.getEntries(shape);

      if (entries.replace) {
        entries.replace.action.click(event, shape);
      }
    });
  }

  ContextPadProvider.$inject = [
    'config.contextPad',
    'injector',
    'eventBus',
    'contextPad',
    'modeling',
    'elementFactory',
    'connect',
    'create',
    'popupMenu',
    'canvas',
    'rules',
    'translate'
  ];

  ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
    var modeling = this._modeling;

    var actions = {};

    if (this._isDeleteAllowed(elements)) {
      assign(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: this._translate('Remove'),
          action: {
            click: function(event, elements) {
              modeling.removeElements(elements.slice());
            }
          }
        }
      });
    }

    return actions;
  };

  /**
   * @param {djs.model.Base[]} elements
   * @return {boolean}
   */
  ContextPadProvider.prototype._isDeleteAllowed = function(elements) {

    var baseAllowed = this._rules.allowed('elements.delete', {
      elements: elements
    });

    if (isArray$3(baseAllowed)) {
      return every(baseAllowed, function(element) {
        return includes$7(baseAllowed, element);
      });
    }

    return baseAllowed;
  };

  ContextPadProvider.prototype.getContextPadEntries = function(element) {
    var contextPad = this._contextPad,
        modeling = this._modeling,

        elementFactory = this._elementFactory,
        connect = this._connect,
        create = this._create,
        popupMenu = this._popupMenu,
        canvas = this._canvas,
        rules = this._rules,
        autoPlace = this._autoPlace,
        translate = this._translate;

    var actions = {};

    if (element.type === 'label') {
      return actions;
    }

    var businessObject = element.businessObject;

    function startConnect(event, element) {
      connect.start(event, element);
    }

    function removeElement(e, element) {
      modeling.removeElements([ element ]);
    }

    function getReplaceMenuPosition(element) {

      var Y_OFFSET = 5;

      var diagramContainer = canvas.getContainer(),
          pad = contextPad.getPad(element).html;

      var diagramRect = diagramContainer.getBoundingClientRect(),
          padRect = pad.getBoundingClientRect();

      var top = padRect.top - diagramRect.top;
      var left = padRect.left - diagramRect.left;

      var pos = {
        x: left,
        y: top + padRect.height + Y_OFFSET
      };

      return pos;
    }


    /**
     * Create an append action
     *
     * @param {string} type
     * @param {string} className
     * @param {string} [title]
     * @param {Object} [options]
     *
     * @return {Object} descriptor
     */
    function appendAction(type, className, title, options) {

      if (typeof title !== 'string') {
        options = title;
        title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
      }

      function appendStart(event, element) {

        var shape = elementFactory.createShape(assign({ type: type }, options));
        create.start(event, shape, {
          source: element
        });
      }


      var append = autoPlace ? function(event, element) {
        var shape = elementFactory.createShape(assign({ type: type }, options));

        autoPlace.append(element, shape);
      } : appendStart;


      return {
        group: 'model',
        className: className,
        title: title,
        action: {
          dragstart: appendStart,
          click: append
        }
      };
    }

    function splitLaneHandler(count) {

      return function(event, element) {

        // actual split
        modeling.splitLane(element, count);

        // refresh context pad after split to
        // get rid of split icons
        contextPad.open(element, true);
      };
    }


    if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {

      var childLanes = getChildLanes(element);

      assign(actions, {
        'lane-insert-above': {
          group: 'lane-insert-above',
          className: 'bpmn-icon-lane-insert-above',
          title: translate('Add Lane above'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'top');
            }
          }
        }
      });

      if (childLanes.length < 2) {

        if (element.height >= 120) {
          assign(actions, {
            'lane-divide-two': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-two',
              title: translate('Divide into two Lanes'),
              action: {
                click: splitLaneHandler(2)
              }
            }
          });
        }

        if (element.height >= 180) {
          assign(actions, {
            'lane-divide-three': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-three',
              title: translate('Divide into three Lanes'),
              action: {
                click: splitLaneHandler(3)
              }
            }
          });
        }
      }

      assign(actions, {
        'lane-insert-below': {
          group: 'lane-insert-below',
          className: 'bpmn-icon-lane-insert-below',
          title: translate('Add Lane below'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'bottom');
            }
          }
        }
      });

    }

    if (is$1(businessObject, 'bpmn:FlowNode')) {

      if (is$1(businessObject, 'bpmn:EventBasedGateway')) {

        assign(actions, {
          'append.receive-task': appendAction(
            'bpmn:ReceiveTask',
            'bpmn-icon-receive-task',
            translate('Append ReceiveTask')
          ),
          'append.message-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-message',
            translate('Append MessageIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:MessageEventDefinition' }
          ),
          'append.timer-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-timer',
            translate('Append TimerIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:TimerEventDefinition' }
          ),
          'append.condition-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-condition',
            translate('Append ConditionIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
          ),
          'append.signal-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-signal',
            translate('Append SignalIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:SignalEventDefinition' }
          )
        });
      } else

      if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {

        assign(actions, {
          'append.compensation-activity':
              appendAction(
                'bpmn:Task',
                'bpmn-icon-task',
                translate('Append compensation activity'),
                {
                  isForCompensation: true
                }
              )
        });
      } else

      if (!is$1(businessObject, 'bpmn:EndEvent') &&
          !businessObject.isForCompensation &&
          !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
          !isEventSubProcess(businessObject)) {

        assign(actions, {
          'append.end-event': appendAction(
            'bpmn:EndEvent',
            'bpmn-icon-end-event-none',
            translate('Append EndEvent')
          ),
          'append.gateway': appendAction(
            'bpmn:ExclusiveGateway',
            'bpmn-icon-gateway-none',
            translate('Append Gateway')
          ),
          'append.append-task': appendAction(
            'bpmn:Task',
            'bpmn-icon-task',
            translate('Append Task')
          ),
          'append.intermediate-event': appendAction(
            'bpmn:IntermediateThrowEvent',
            'bpmn-icon-intermediate-event-none',
            translate('Append Intermediate/Boundary Event')
          )
        });
      }
    }

    if (!popupMenu.isEmpty(element, 'bpmn-replace')) {

      // Replace menu entry
      assign(actions, {
        'replace': {
          group: 'edit',
          className: 'bpmn-icon-screw-wrench',
          title: translate('Change type'),
          action: {
            click: function(event, element) {

              var position = assign(getReplaceMenuPosition(element), {
                cursor: { x: event.x, y: event.y }
              });

              popupMenu.open(element, 'bpmn-replace', position);
            }
          }
        }
      });
    }

    if (is$1(businessObject, 'bpmn:SequenceFlow')) {
      assign(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation'
        )
      });
    }

    if (
      isAny(businessObject, [
        'bpmn:FlowNode',
        'bpmn:InteractionNode',
        'bpmn:DataObjectReference',
        'bpmn:DataStoreReference',
      ])
    ) {
      assign(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation'
        ),

        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate(
            'Connect using ' +
              (businessObject.isForCompensation
                ? ''
                : 'Sequence/MessageFlow or ') +
              'Association'
          ),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }

    if (is$1(businessObject, 'bpmn:TextAnnotation')) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using Association'),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }

    if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using DataInputAssociation'),
          action: {
            click: startConnect,
            dragstart: startConnect
          }
        }
      });
    }

    if (is$1(businessObject, 'bpmn:Group')) {
      assign(actions, {
        'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
      });
    }

    // delete element entry, only show if allowed by rules
    var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });

    if (isArray$3(deleteAllowed)) {

      // was the element returned as a deletion candidate?
      deleteAllowed = deleteAllowed[0] === element;
    }

    if (deleteAllowed) {
      assign(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: translate('Remove'),
          action: {
            click: removeElement
          }
        }
      });
    }

    return actions;
  };


  // helpers /////////

  function isEventType(eventBo, type, definition) {

    var isType = eventBo.$instanceOf(type);
    var isDefinition = false;

    var definitions = eventBo.eventDefinitions || [];
    forEach$1(definitions, function(def) {
      if (def.$type === definition) {
        isDefinition = true;
      }
    });

    return isType && isDefinition;
  }

  function includes$7(array, item) {
    return array.indexOf(item) !== -1;
  }

  var ContextPadModule = {
    __depends__: [
      DirectEditingModule,
      ContextPadModule$1,
      SelectionModule,
      ConnectModule,
      CreateModule,
      PopupMenuModule
    ],
    __init__: [ 'contextPadProvider' ],
    contextPadProvider: [ 'type', ContextPadProvider ]
  };

  var AXIS_DIMENSIONS = {
    horizontal: [ 'x', 'width' ],
    vertical: [ 'y', 'height' ]
  };

  var THRESHOLD = 5;


  /**
   * Groups and filters elements and then trigger even distribution.
   */
  function DistributeElements$1(modeling, rules) {
    this._modeling = modeling;

    this._filters = [];

    this.registerFilter(function(elements) {
      var allowed = rules.allowed('elements.distribute', { elements: elements });

      if (isArray$3(allowed)) {
        return allowed;
      }

      return allowed ? elements : [];
    });
  }

  DistributeElements$1.$inject = [ 'modeling', 'rules' ];


  /**
   * Registers filter functions that allow external parties to filter
   * out certain elements.
   *
   * @param  {Function} filterFn
   */
  DistributeElements$1.prototype.registerFilter = function(filterFn) {
    if (typeof filterFn !== 'function') {
      throw new Error('the filter has to be a function');
    }

    this._filters.push(filterFn);
  };

  /**
   * Distributes the elements with a given orientation
   *
   * @param  {Array} elements
   * @param  {string} orientation
   */
  DistributeElements$1.prototype.trigger = function(elements, orientation) {
    var modeling = this._modeling;

    var groups,
        distributableElements;

    if (elements.length < 3) {
      return;
    }

    this._setOrientation(orientation);

    distributableElements = this._filterElements(elements);

    groups = this._createGroups(distributableElements);

    // nothing to distribute
    if (groups.length <= 2) {
      return;
    }

    modeling.distributeElements(groups, this._axis, this._dimension);

    return groups;
  };

  /**
   * Filters the elements with provided filters by external parties
   *
   * @param  {Array[Elements]} elements
   *
   * @return {Array[Elements]}
   */
  DistributeElements$1.prototype._filterElements = function(elements) {
    var filters = this._filters,
        axis = this._axis,
        dimension = this._dimension,
        distributableElements = [].concat(elements);

    if (!filters.length) {
      return elements;
    }

    forEach$1(filters, function(filterFn) {
      distributableElements = filterFn(distributableElements, axis, dimension);
    });

    return distributableElements;
  };


  /**
   * Create range (min, max) groups. Also tries to group elements
   * together that share the same range.
   *
   * @example
   * 	var distributableElements = [
   * 		{
   * 			range: {
   * 				min: 100,
   * 				max: 200
   * 			},
   * 			elements: [ { id: 'shape1', .. }]
   * 		}
   * 	]
   *
   * @param  {Array} elements
   *
   * @return {Array[Objects]}
   */
  DistributeElements$1.prototype._createGroups = function(elements) {
    var rangeGroups = [],
        self = this,
        axis = this._axis,
        dimension = this._dimension;

    if (!axis) {
      throw new Error('must have a defined "axis" and "dimension"');
    }

    // sort by 'left->right' or 'top->bottom'
    var sortedElements = sortBy(elements, axis);

    forEach$1(sortedElements, function(element, idx) {
      var elementRange = self._findRange(element, axis, dimension),
          range;

      var previous = rangeGroups[rangeGroups.length - 1];

      if (previous && self._hasIntersection(previous.range, elementRange)) {
        rangeGroups[rangeGroups.length - 1].elements.push(element);
      } else {
        range = { range: elementRange, elements: [ element ] };

        rangeGroups.push(range);
      }
    });

    return rangeGroups;
  };


  /**
   * Maps a direction to the according axis and dimension
   *
   * @param  {string} direction 'horizontal' or 'vertical'
   */
  DistributeElements$1.prototype._setOrientation = function(direction) {
    var orientation = AXIS_DIMENSIONS[direction];

    this._axis = orientation[0];
    this._dimension = orientation[1];
  };


  /**
   * Checks if the two ranges intercept each other
   *
   * @param  {Object} rangeA {min, max}
   * @param  {Object} rangeB {min, max}
   *
   * @return {boolean}
   */
  DistributeElements$1.prototype._hasIntersection = function(rangeA, rangeB) {
    return Math.max(rangeA.min, rangeA.max) >= Math.min(rangeB.min, rangeB.max) &&
           Math.min(rangeA.min, rangeA.max) <= Math.max(rangeB.min, rangeB.max);
  };


  /**
   * Returns the min and max values for an element
   *
   * @param  {Bounds} element
   * @param  {string} axis
   * @param  {string} dimension
   *
   * @return {{ min: number, max: number }}
   */
  DistributeElements$1.prototype._findRange = function(element) {
    var axis = element[this._axis],
        dimension = element[this._dimension];

    return {
      min: axis + THRESHOLD,
      max: axis + dimension - THRESHOLD
    };
  };

  var DistributeElementsModule$1 = {
    __init__: [ 'distributeElements' ],
    distributeElements: [ 'type', DistributeElements$1 ]
  };

  /**
   * Registers element exclude filters for elements that
   * currently do not support distribution.
   */
  function BpmnDistributeElements(distributeElements, eventBus, rules) {
    RuleProvider.call(this, eventBus);
  }

  BpmnDistributeElements.$inject = [ 'distributeElements', 'eventBus', 'rules' ];

  e(BpmnDistributeElements, RuleProvider);

  BpmnDistributeElements.prototype.init = function() {
    this.addRule('elements.distribute', function(context) {
      var elements = context.elements;

      elements = filter(elements, function(element) {
        var cannotDistribute = isAny(element, [
          'bpmn:Association',
          'bpmn:BoundaryEvent',
          'bpmn:DataInputAssociation',
          'bpmn:DataOutputAssociation',
          'bpmn:Lane',
          'bpmn:MessageFlow',
          'bpmn:SequenceFlow',
          'bpmn:TextAnnotation'
        ]);

        return !(element.labelTarget || cannotDistribute);
      });

      // filter out elements which are children of any of the selected elements
      elements = getParents$1(elements);

      if (elements.length < 3) {
        return false;
      }

      return elements;
    });
  };

  /**
   * To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
   * and then replace respective icons with the optimized data URIs in `./dist`.
   */
  var icons = {
    horizontal: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linejoin%3Around%22%20d%3D%22M450%20400V150h900v250%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22450%22%20width%3D%22600%22%20height%3D%221200%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22450%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
    vertical: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linejoin%3Around%22%20d%3D%22M400%201350H150V450h250%22%2F%3E%3Crect%20x%3D%22450%22%20y%3D%22150%22%20width%3D%221200%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22450%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
  };

  var LOW_PRIORITY$g = 900;

  /**
   * A provider for distribute elements popup menu.
   */
  function DistributeElementsMenuProvider(
      popupMenu, distributeElements, translate, rules) {
    this._distributeElements = distributeElements;
    this._translate = translate;
    this._popupMenu = popupMenu;
    this._rules = rules;

    popupMenu.registerProvider('align-elements', LOW_PRIORITY$g, this);
  }

  DistributeElementsMenuProvider.$inject = [
    'popupMenu',
    'distributeElements',
    'translate',
    'rules'
  ];

  DistributeElementsMenuProvider.prototype.getPopupMenuEntries = function(elements) {
    var entries = {};

    if (this._isAllowed(elements)) {
      assign(entries, this._getEntries(elements));
    }

    return entries;
  };

  DistributeElementsMenuProvider.prototype._isAllowed = function(elements) {
    return this._rules.allowed('elements.distribute', { elements: elements });
  };

  DistributeElementsMenuProvider.prototype._getEntries = function(elements) {
    var distributeElements = this._distributeElements,
        translate = this._translate,
        popupMenu = this._popupMenu;

    var entries = {
      'distribute-elements-horizontal': {
        group: 'distribute',
        title: translate('Distribute elements horizontally'),
        className: 'bjs-align-elements-menu-entry',
        imageUrl: icons['horizontal'],
        action: function(event, entry) {
          distributeElements.trigger(elements, 'horizontal');
          popupMenu.close();
        }
      },
      'distribute-elements-vertical': {
        group: 'distribute',
        title: translate('Distribute elements vertically'),
        imageUrl: icons['vertical'],
        action: function(event, entry) {
          distributeElements.trigger(elements, 'vertical');
          popupMenu.close();
        }
      },
    };

    return entries;
  };

  var DistributeElementsModule = {
    __depends__: [
      PopupMenuModule$1,
      DistributeElementsModule$1
    ],
    __init__: [
      'bpmnDistributeElements',
      'distributeElementsMenuProvider'
    ],
    bpmnDistributeElements: [ 'type', BpmnDistributeElements ],
    distributeElementsMenuProvider: [ 'type', DistributeElementsMenuProvider ]
  };

  var NOT_REGISTERED_ERROR = 'is not a registered action',
      IS_REGISTERED_ERROR = 'is already registered';


  /**
   * An interface that provides access to modeling actions by decoupling
   * the one who requests the action to be triggered and the trigger itself.
   *
   * It's possible to add new actions by registering them with ´registerAction´
   * and likewise unregister existing ones with ´unregisterAction´.
   *
   *
   * ## Life-Cycle and configuration
   *
   * The editor actions will wait for diagram initialization before
   * registering default actions _and_ firing an `editorActions.init` event.
   *
   * Interested parties may listen to the `editorActions.init` event with
   * low priority to check, which actions got registered. Other components
   * may use the event to register their own actions via `registerAction`.
   *
   * @param {EventBus} eventBus
   * @param {Injector} injector
   */
  function EditorActions(eventBus, injector) {

    // initialize actions
    this._actions = {};

    var self = this;

    eventBus.on('diagram.init', function() {

      // all diagram modules got loaded; check which ones
      // are available and register the respective default actions
      self._registerDefaultActions(injector);

      // ask interested parties to register available editor
      // actions on diagram initialization
      eventBus.fire('editorActions.init', {
        editorActions: self
      });
    });

  }

  EditorActions.$inject = [
    'eventBus',
    'injector'
  ];

  /**
   * Register default actions.
   *
   * @param {Injector} injector
   */
  EditorActions.prototype._registerDefaultActions = function(injector) {

    // (1) retrieve optional components to integrate with

    var commandStack = injector.get('commandStack', false);
    var modeling = injector.get('modeling', false);
    var selection = injector.get('selection', false);
    var zoomScroll = injector.get('zoomScroll', false);
    var copyPaste = injector.get('copyPaste', false);
    var canvas = injector.get('canvas', false);
    var rules = injector.get('rules', false);
    var keyboardMove = injector.get('keyboardMove', false);
    var keyboardMoveSelection = injector.get('keyboardMoveSelection', false);

    // (2) check components and register actions

    if (commandStack) {
      this.register('undo', function() {
        commandStack.undo();
      });

      this.register('redo', function() {
        commandStack.redo();
      });
    }

    if (copyPaste && selection) {
      this.register('copy', function() {
        var selectedElements = selection.get();

        if (selectedElements.length) {
          return copyPaste.copy(selectedElements);
        }
      });
    }

    if (copyPaste) {
      this.register('paste', function() {
        copyPaste.paste();
      });
    }

    if (zoomScroll) {
      this.register('stepZoom', function(opts) {
        zoomScroll.stepZoom(opts.value);
      });
    }

    if (canvas) {
      this.register('zoom', function(opts) {
        canvas.zoom(opts.value);
      });
    }

    if (modeling && selection && rules) {
      this.register('removeSelection', function() {

        var selectedElements = selection.get();

        if (!selectedElements.length) {
          return;
        }

        var allowed = rules.allowed('elements.delete', { elements: selectedElements }),
            removableElements;

        if (allowed === false) {
          return;
        }
        else if (isArray$3(allowed)) {
          removableElements = allowed;
        }
        else {
          removableElements = selectedElements;
        }

        if (removableElements.length) {
          modeling.removeElements(removableElements.slice());
        }
      });
    }

    if (keyboardMove) {
      this.register('moveCanvas', function(opts) {
        keyboardMove.moveCanvas(opts);
      });
    }

    if (keyboardMoveSelection) {
      this.register('moveSelection', function(opts) {
        keyboardMoveSelection.moveSelection(opts.direction, opts.accelerated);
      });
    }

  };


  /**
   * Triggers a registered action
   *
   * @param  {string} action
   * @param  {Object} opts
   *
   * @return {Unknown} Returns what the registered listener returns
   */
  EditorActions.prototype.trigger = function(action, opts) {
    if (!this._actions[action]) {
      throw error(action, NOT_REGISTERED_ERROR);
    }

    return this._actions[action](opts);
  };


  /**
   * Registers a collections of actions.
   * The key of the object will be the name of the action.
   *
   * @example
   * ´´´
   * var actions = {
   *   spaceTool: function() {
   *     spaceTool.activateSelection();
   *   },
   *   lassoTool: function() {
   *     lassoTool.activateSelection();
   *   }
   * ];
   *
   * editorActions.register(actions);
   *
   * editorActions.isRegistered('spaceTool'); // true
   * ´´´
   *
   * @param  {Object} actions
   */
  EditorActions.prototype.register = function(actions, listener) {
    var self = this;

    if (typeof actions === 'string') {
      return this._registerAction(actions, listener);
    }

    forEach$1(actions, function(listener, action) {
      self._registerAction(action, listener);
    });
  };

  /**
   * Registers a listener to an action key
   *
   * @param  {string} action
   * @param  {Function} listener
   */
  EditorActions.prototype._registerAction = function(action, listener) {
    if (this.isRegistered(action)) {
      throw error(action, IS_REGISTERED_ERROR);
    }

    this._actions[action] = listener;
  };

  /**
   * Unregister an existing action
   *
   * @param {string} action
   */
  EditorActions.prototype.unregister = function(action) {
    if (!this.isRegistered(action)) {
      throw error(action, NOT_REGISTERED_ERROR);
    }

    this._actions[action] = undefined;
  };

  /**
   * Returns the number of actions that are currently registered
   *
   * @return {number}
   */
  EditorActions.prototype.getActions = function() {
    return Object.keys(this._actions);
  };

  /**
   * Checks wether the given action is registered
   *
   * @param {string} action
   *
   * @return {boolean}
   */
  EditorActions.prototype.isRegistered = function(action) {
    return !!this._actions[action];
  };


  function error(action, message) {
    return new Error(action + ' ' + message);
  }

  var EditorActionsModule$1 = {
    __init__: [ 'editorActions' ],
    editorActions: [ 'type', EditorActions ]
  };

  /**
   * Registers and executes BPMN specific editor actions.
   *
   * @param {Injector} injector
   */
  function BpmnEditorActions(injector) {
    injector.invoke(EditorActions, this);
  }

  e(BpmnEditorActions, EditorActions);

  BpmnEditorActions.$inject = [
    'injector'
  ];

  /**
   * Register default actions.
   *
   * @param {Injector} injector
   */
  BpmnEditorActions.prototype._registerDefaultActions = function(injector) {

    // (0) invoke super method

    EditorActions.prototype._registerDefaultActions.call(this, injector);

    // (1) retrieve optional components to integrate with

    var canvas = injector.get('canvas', false);
    var elementRegistry = injector.get('elementRegistry', false);
    var selection = injector.get('selection', false);
    var spaceTool = injector.get('spaceTool', false);
    var lassoTool = injector.get('lassoTool', false);
    var handTool = injector.get('handTool', false);
    var globalConnect = injector.get('globalConnect', false);
    var distributeElements = injector.get('distributeElements', false);
    var alignElements = injector.get('alignElements', false);
    var directEditing = injector.get('directEditing', false);
    var searchPad = injector.get('searchPad', false);
    var modeling = injector.get('modeling', false);

    // (2) check components and register actions

    if (canvas && elementRegistry && selection) {
      this._registerAction('selectElements', function() {

        // select all elements except for the invisible
        // root element
        var rootElement = canvas.getRootElement();

        var elements = elementRegistry.filter(function(element) {
          return element !== rootElement;
        });

        selection.select(elements);

        return elements;
      });
    }

    if (spaceTool) {
      this._registerAction('spaceTool', function() {
        spaceTool.toggle();
      });
    }

    if (lassoTool) {
      this._registerAction('lassoTool', function() {
        lassoTool.toggle();
      });
    }

    if (handTool) {
      this._registerAction('handTool', function() {
        handTool.toggle();
      });
    }

    if (globalConnect) {
      this._registerAction('globalConnectTool', function() {
        globalConnect.toggle();
      });
    }

    if (selection && distributeElements) {
      this._registerAction('distributeElements', function(opts) {
        var currentSelection = selection.get(),
            type = opts.type;

        if (currentSelection.length) {
          distributeElements.trigger(currentSelection, type);
        }
      });
    }

    if (selection && alignElements) {
      this._registerAction('alignElements', function(opts) {
        var currentSelection = selection.get(),
            aligneableElements = [],
            type = opts.type;

        if (currentSelection.length) {
          aligneableElements = filter(currentSelection, function(element) {
            return !is$1(element, 'bpmn:Lane');
          });

          alignElements.trigger(aligneableElements, type);
        }
      });
    }

    if (selection && modeling) {
      this._registerAction('setColor', function(opts) {
        var currentSelection = selection.get();

        if (currentSelection.length) {
          modeling.setColor(currentSelection, opts);
        }
      });
    }

    if (selection && directEditing) {
      this._registerAction('directEditing', function() {
        var currentSelection = selection.get();

        if (currentSelection.length) {
          directEditing.activate(currentSelection[0]);
        }
      });
    }

    if (searchPad) {
      this._registerAction('find', function() {
        searchPad.toggle();
      });
    }

    if (canvas && modeling) {
      this._registerAction('moveToOrigin', function() {
        var rootElement = canvas.getRootElement(),
            boundingBox,
            elements;

        if (is$1(rootElement, 'bpmn:Collaboration')) {
          elements = elementRegistry.filter(function(element) {
            return is$1(element.parent, 'bpmn:Collaboration');
          });
        } else {
          elements = elementRegistry.filter(function(element) {
            return element !== rootElement && !is$1(element.parent, 'bpmn:SubProcess');
          });
        }

        boundingBox = getBBox(elements);

        modeling.moveElements(
          elements,
          { x: -boundingBox.x, y: -boundingBox.y },
          rootElement
        );
      });
    }

  };

  var EditorActionsModule = {
    __depends__: [
      EditorActionsModule$1
    ],
    editorActions: [ 'type', BpmnEditorActions ]
  };

  function BpmnGridSnapping(eventBus) {
    eventBus.on([
      'create.init',
      'shape.move.init'
    ], function(event) {
      var context = event.context,
          shape = event.shape;

      if (isAny(shape, [
        'bpmn:Participant',
        'bpmn:SubProcess',
        'bpmn:TextAnnotation'
      ])) {
        if (!context.gridSnappingContext) {
          context.gridSnappingContext = {};
        }

        context.gridSnappingContext.snapLocation = 'top-left';
      }
    });
  }

  BpmnGridSnapping.$inject = [ 'eventBus' ];

  var SPACING = 10;

  function quantize(value, quantum, fn) {
    if (!fn) {
      fn = 'round';
    }

    return Math[ fn ](value / quantum) * quantum;
  }

  var LOWER_PRIORITY$1 = 1200;
  var LOW_PRIORITY$f = 800;

  /**
   * Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints
   * and connection segments.
   */
  function GridSnapping(elementRegistry, eventBus, config) {

    var active = !config || config.active !== false;

    this._eventBus = eventBus;

    var self = this;

    eventBus.on('diagram.init', LOW_PRIORITY$f, function() {
      self.setActive(active);
    });

    eventBus.on([
      'create.move',
      'create.end',
      'bendpoint.move.move',
      'bendpoint.move.end',
      'connect.move',
      'connect.end',
      'connectionSegment.move.move',
      'connectionSegment.move.end',
      'resize.move',
      'resize.end',
      'shape.move.move',
      'shape.move.end'
    ], LOWER_PRIORITY$1, function(event) {
      var originalEvent = event.originalEvent;

      if (!self.active || (originalEvent && isCmd(originalEvent))) {
        return;
      }

      var context = event.context,
          gridSnappingContext = context.gridSnappingContext;

      if (!gridSnappingContext) {
        gridSnappingContext = context.gridSnappingContext = {};
      }

      [ 'x', 'y' ].forEach(function(axis) {
        var options = {};

        // allow snapping with offset
        var snapOffset = getSnapOffset(event, axis, elementRegistry);

        if (snapOffset) {
          options.offset = snapOffset;
        }

        // allow snapping with min and max
        var snapConstraints = getSnapConstraints(event, axis);

        if (snapConstraints) {
          assign(options, snapConstraints);
        }

        if (!isSnapped(event, axis)) {
          self.snapEvent(event, axis, options);
        }
      });
    });
  }

  /**
   * Snap an events x or y with optional min, max and offset.
   *
   * @param {Object} event
   * @param {string} axis
   * @param {number} [options.min]
   * @param {number} [options.max]
   * @param {number} [options.offset]
   */
  GridSnapping.prototype.snapEvent = function(event, axis, options) {
    var snappedValue = this.snapValue(event[ axis ], options);

    setSnapped(event, axis, snappedValue);
  };

  /**
   * Expose grid spacing for third parties (i.e. extensions).
   *
   * @return {number} spacing of grid dots
   */
  GridSnapping.prototype.getGridSpacing = function() {
    return SPACING;
  };

  /**
   * Snap value with optional min, max and offset.
   *
   * @param {number} value
   * @param {Object} options
   * @param {number} [options.min]
   * @param {number} [options.max]
   * @param {number} [options.offset]
   */
  GridSnapping.prototype.snapValue = function(value, options) {
    var offset = 0;

    if (options && options.offset) {
      offset = options.offset;
    }

    value += offset;

    value = quantize(value, SPACING);

    var min, max;

    if (options && options.min) {
      min = options.min;

      if (isNumber(min)) {
        min = quantize(min + offset, SPACING, 'ceil');

        value = Math.max(value, min);
      }
    }

    if (options && options.max) {
      max = options.max;

      if (isNumber(max)) {
        max = quantize(max + offset, SPACING, 'floor');

        value = Math.min(value, max);
      }
    }

    value -= offset;

    return value;
  };

  GridSnapping.prototype.isActive = function() {
    return this.active;
  };

  GridSnapping.prototype.setActive = function(active) {
    this.active = active;

    this._eventBus.fire('gridSnapping.toggle', { active: active });
  };

  GridSnapping.prototype.toggleActive = function() {
    this.setActive(!this.active);
  };

  GridSnapping.$inject = [
    'elementRegistry',
    'eventBus',
    'config.gridSnapping'
  ];

  // helpers //////////

  /**
   * Get minimum and maximum snap constraints.
   * Constraints are cached.
   *
   * @param {Object} event
   * @param {Object} event.context
   * @param {string} axis
   *
   * @returns {boolean|Object}
   */
  function getSnapConstraints(event, axis) {
    var context = event.context,
        createConstraints = context.createConstraints,
        resizeConstraints = context.resizeConstraints || {},
        gridSnappingContext = context.gridSnappingContext,
        snapConstraints = gridSnappingContext.snapConstraints;

    // cache snap constraints
    if (snapConstraints && snapConstraints[ axis ]) {
      return snapConstraints[ axis ];
    }

    if (!snapConstraints) {
      snapConstraints = gridSnappingContext.snapConstraints = {};
    }

    if (!snapConstraints[ axis ]) {
      snapConstraints[ axis ] = {};
    }

    var direction = context.direction;

    // create
    if (createConstraints) {
      if (isHorizontal$3(axis)) {
        snapConstraints.x.min = createConstraints.left;
        snapConstraints.x.max = createConstraints.right;
      } else {
        snapConstraints.y.min = createConstraints.top;
        snapConstraints.y.max = createConstraints.bottom;
      }
    }

    // resize
    var minResizeConstraints = resizeConstraints.min,
        maxResizeConstraints = resizeConstraints.max;

    if (minResizeConstraints) {
      if (isHorizontal$3(axis)) {

        if (isWest(direction)) {
          snapConstraints.x.max = minResizeConstraints.left;
        } else {
          snapConstraints.x.min = minResizeConstraints.right;
        }

      } else {

        if (isNorth(direction)) {
          snapConstraints.y.max = minResizeConstraints.top;
        } else {
          snapConstraints.y.min = minResizeConstraints.bottom;
        }

      }
    }

    if (maxResizeConstraints) {
      if (isHorizontal$3(axis)) {

        if (isWest(direction)) {
          snapConstraints.x.min = maxResizeConstraints.left;
        } else {
          snapConstraints.x.max = maxResizeConstraints.right;
        }

      } else {

        if (isNorth(direction)) {
          snapConstraints.y.min = maxResizeConstraints.top;
        } else {
          snapConstraints.y.max = maxResizeConstraints.bottom;
        }

      }
    }

    return snapConstraints[ axis ];
  }

  /**
   * Get snap offset.
   * Offset is cached.
   *
   * @param {Object} event
   * @param {string} axis
   * @param {ElementRegistry} elementRegistry
   *
   * @returns {number}
   */
  function getSnapOffset(event, axis, elementRegistry) {
    var context = event.context,
        shape = event.shape,
        gridSnappingContext = context.gridSnappingContext,
        snapLocation = gridSnappingContext.snapLocation,
        snapOffset = gridSnappingContext.snapOffset;

    // cache snap offset
    if (snapOffset && isNumber(snapOffset[ axis ])) {
      return snapOffset[ axis ];
    }

    if (!snapOffset) {
      snapOffset = gridSnappingContext.snapOffset = {};
    }

    if (!isNumber(snapOffset[ axis ])) {
      snapOffset[ axis ] = 0;
    }

    if (!shape) {
      return snapOffset[ axis ];
    }

    if (!elementRegistry.get(shape.id)) {

      if (isHorizontal$3(axis)) {
        snapOffset[ axis ] += shape[ axis ] + shape.width / 2;
      } else {
        snapOffset[ axis ] += shape[ axis ] + shape.height / 2;
      }
    }

    if (!snapLocation) {
      return snapOffset[ axis ];
    }

    if (axis === 'x') {
      if (/left/.test(snapLocation)) {
        snapOffset[ axis ] -= shape.width / 2;
      } else if (/right/.test(snapLocation)) {
        snapOffset[ axis ] += shape.width / 2;
      }
    } else {
      if (/top/.test(snapLocation)) {
        snapOffset[ axis ] -= shape.height / 2;
      } else if (/bottom/.test(snapLocation)) {
        snapOffset[ axis ] += shape.height / 2;
      }
    }

    return snapOffset[ axis ];
  }

  function isHorizontal$3(axis) {
    return axis === 'x';
  }

  function isNorth(direction) {
    return direction.indexOf('n') !== -1;
  }

  function isWest(direction) {
    return direction.indexOf('w') !== -1;
  }

  /**
   * Integrates resizing with grid snapping.
   */
  function ResizeBehavior$1(eventBus, gridSnapping) {
    CommandInterceptor.call(this, eventBus);

    this._gridSnapping = gridSnapping;

    var self = this;

    this.preExecute('shape.resize', function(event) {
      var context = event.context,
          hints = context.hints || {},
          autoResize = hints.autoResize;

      if (!autoResize) {
        return;
      }

      var shape = context.shape,
          newBounds = context.newBounds;

      if (isString(autoResize)) {
        context.newBounds = self.snapComplex(newBounds, autoResize);
      } else {
        context.newBounds = self.snapSimple(shape, newBounds);
      }
    });
  }

  ResizeBehavior$1.$inject = [
    'eventBus',
    'gridSnapping',
    'modeling'
  ];

  e(ResizeBehavior$1, CommandInterceptor);

  /**
   * Snap width and height in relation to center.
   *
   * @param {djs.model.shape} shape
   * @param {Bounds} newBounds
   *
   * @returns {Bounds} Snapped bounds.
   */
  ResizeBehavior$1.prototype.snapSimple = function(shape, newBounds) {
    var gridSnapping = this._gridSnapping;

    newBounds.width = gridSnapping.snapValue(newBounds.width, {
      min: newBounds.width
    });

    newBounds.height = gridSnapping.snapValue(newBounds.height, {
      min: newBounds.height
    });

    newBounds.x = shape.x + (shape.width / 2) - (newBounds.width / 2);
    newBounds.y = shape.y + (shape.height / 2) - (newBounds.height / 2);

    return newBounds;
  };

  /**
   * Snap x, y, width and height according to given directions.
   *
   * @param {Bounds} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @returns {Bounds} Snapped bounds.
   */
  ResizeBehavior$1.prototype.snapComplex = function(newBounds, directions) {
    if (/w|e/.test(directions)) {
      newBounds = this.snapHorizontally(newBounds, directions);
    }

    if (/n|s/.test(directions)) {
      newBounds = this.snapVertically(newBounds, directions);
    }

    return newBounds;
  };

  /**
   * Snap in one or both directions horizontally.
   *
   * @param {Bounds} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @returns {Bounds} Snapped bounds.
   */
  ResizeBehavior$1.prototype.snapHorizontally = function(newBounds, directions) {
    var gridSnapping = this._gridSnapping,
        west = /w/.test(directions),
        east = /e/.test(directions);

    var snappedNewBounds = {};

    snappedNewBounds.width = gridSnapping.snapValue(newBounds.width, {
      min: newBounds.width
    });

    if (east) {

      // handle <we>
      if (west) {
        snappedNewBounds.x = gridSnapping.snapValue(newBounds.x, {
          max: newBounds.x
        });

        snappedNewBounds.width += gridSnapping.snapValue(newBounds.x - snappedNewBounds.x, {
          min: newBounds.x - snappedNewBounds.x
        });
      }

      // handle <e>
      else {
        newBounds.x = newBounds.x + newBounds.width - snappedNewBounds.width;
      }
    }

    // assign snapped x and width
    assign(newBounds, snappedNewBounds);

    return newBounds;
  };

  /**
   * Snap in one or both directions vertically.
   *
   * @param {Bounds} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @returns {Bounds} Snapped bounds.
   */
  ResizeBehavior$1.prototype.snapVertically = function(newBounds, directions) {
    var gridSnapping = this._gridSnapping,
        north = /n/.test(directions),
        south = /s/.test(directions);

    var snappedNewBounds = {};

    snappedNewBounds.height = gridSnapping.snapValue(newBounds.height, {
      min: newBounds.height
    });

    if (north) {

      // handle <ns>
      if (south) {
        snappedNewBounds.y = gridSnapping.snapValue(newBounds.y, {
          max: newBounds.y
        });

        snappedNewBounds.height += gridSnapping.snapValue(newBounds.y - snappedNewBounds.y, {
          min: newBounds.y - snappedNewBounds.y
        });
      }

      // handle <n>
      else {
        newBounds.y = newBounds.y + newBounds.height - snappedNewBounds.height;
      }
    }

    // assign snapped y and height
    assign(newBounds, snappedNewBounds);

    return newBounds;
  };

  var HIGH_PRIORITY$f = 2000;

  /**
   * Integrates space tool with grid snapping.
   */
  function SpaceToolBehavior$1(eventBus, gridSnapping) {
    eventBus.on([
      'spaceTool.move',
      'spaceTool.end'
    ], HIGH_PRIORITY$f, function(event) {
      var context = event.context;

      if (!context.initialized) {
        return;
      }

      var axis = context.axis;

      var snapped;

      if (axis === 'x') {

        // snap delta x to multiple of 10
        snapped = gridSnapping.snapValue(event.dx);

        event.x = event.x + snapped - event.dx;
        event.dx = snapped;
      } else {

        // snap delta y to multiple of 10
        snapped = gridSnapping.snapValue(event.dy);

        event.y = event.y + snapped - event.dy;
        event.dy = snapped;
      }
    });
  }

  SpaceToolBehavior$1.$inject = [
    'eventBus',
    'gridSnapping'
  ];

  var GridSnappingBehaviorModule$1 = {
    __init__: [
      'gridSnappingResizeBehavior',
      'gridSnappingSpaceToolBehavior'
    ],
    gridSnappingResizeBehavior: [ 'type', ResizeBehavior$1 ],
    gridSnappingSpaceToolBehavior: [ 'type', SpaceToolBehavior$1 ]
  };

  var GridSnappingModule$1 = {
    __depends__: [ GridSnappingBehaviorModule$1 ],
    __init__: [ 'gridSnapping' ],
    gridSnapping: [ 'type', GridSnapping ]
  };

  var HIGH_PRIORITY$e = 2000;


  function GridSnappingAutoPlaceBehavior(eventBus, gridSnapping) {
    eventBus.on('autoPlace', HIGH_PRIORITY$e, function(context) {
      var source = context.source,
          sourceMid = getMid(source),
          shape = context.shape;

      var position = getNewShapePosition(source, shape);

      [ 'x', 'y' ].forEach(function(axis) {
        var options = {};

        // do not snap if x/y equal
        if (position[ axis ] === sourceMid[ axis ]) {
          return;
        }

        if (position[ axis ] > sourceMid[ axis ]) {
          options.min = position[ axis ];
        } else {
          options.max = position[ axis ];
        }

        if (is$1(shape, 'bpmn:TextAnnotation')) {

          if (isHorizontal$2(axis)) {
            options.offset = -shape.width / 2;
          } else {
            options.offset = -shape.height / 2;
          }

        }

        position[ axis ] = gridSnapping.snapValue(position[ axis ], options);

      });

      // must be returned to be considered by auto place
      return position;
    });
  }

  GridSnappingAutoPlaceBehavior.$inject = [
    'eventBus',
    'gridSnapping'
  ];

  // helpers //////////

  function isHorizontal$2(axis) {
    return axis === 'x';
  }

  var HIGHER_PRIORITY$4 = 1750;


  function GridSnappingParticipantBehavior(canvas, eventBus, gridSnapping) {
    eventBus.on([
      'create.start',
      'shape.move.start'
    ], HIGHER_PRIORITY$4, function(event) {
      var context = event.context,
          shape = context.shape,
          rootElement = canvas.getRootElement();

      if (!is$1(shape, 'bpmn:Participant') ||
        !is$1(rootElement, 'bpmn:Process') ||
        !rootElement.children.length) {
        return;
      }

      var createConstraints = context.createConstraints;

      if (!createConstraints) {
        return;
      }

      shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
      shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
    });
  }

  GridSnappingParticipantBehavior.$inject = [
    'canvas',
    'eventBus',
    'gridSnapping'
  ];

  var HIGH_PRIORITY$d = 3000;


  /**
   * Snaps connections with Manhattan layout.
   */
  function GridSnappingLayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
    CommandInterceptor.call(this, eventBus);

    this._gridSnapping = gridSnapping;

    var self = this;

    this.postExecuted([
      'connection.create',
      'connection.layout'
    ], HIGH_PRIORITY$d, function(event) {
      var context = event.context,
          connection = context.connection,
          hints = context.hints || {},
          waypoints = connection.waypoints;

      if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) {
        return;
      }

      if (!hasMiddleSegments(waypoints)) {
        return;
      }

      modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
    });
  }

  GridSnappingLayoutConnectionBehavior.$inject = [
    'eventBus',
    'gridSnapping',
    'modeling'
  ];

  e(GridSnappingLayoutConnectionBehavior, CommandInterceptor);

  /**
   * Snap middle segments of a given connection.
   *
   * @param {Array<Point>} waypoints
   *
   * @returns {Array<Point>}
   */
  GridSnappingLayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
    var gridSnapping = this._gridSnapping,
        snapped;

    waypoints = waypoints.slice();

    for (var i = 1; i < waypoints.length - 2; i++) {

      snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);

      waypoints[i] = snapped[0];
      waypoints[i + 1] = snapped[1];
    }

    return waypoints;
  };


  // helpers //////////

  /**
   * Check whether a connection has a middle segments.
   *
   * @param {Array} waypoints
   *
   * @returns {boolean}
   */
  function hasMiddleSegments(waypoints) {
    return waypoints.length > 3;
  }

  /**
   * Check whether an alignment is horizontal.
   *
   * @param {string} aligned
   *
   * @returns {boolean}
   */
  function horizontallyAligned(aligned) {
    return aligned === 'h';
  }

  /**
   * Check whether an alignment is vertical.
   *
   * @param {string} aligned
   *
   * @returns {boolean}
   */
  function verticallyAligned(aligned) {
    return aligned === 'v';
  }

  /**
   * Get middle segments from a given connection.
   *
   * @param {Array} waypoints
   *
   * @returns {Array}
   */
  function snapSegment(gridSnapping, segmentStart, segmentEnd) {

    var aligned = pointsAligned(segmentStart, segmentEnd);

    var snapped = {};

    if (horizontallyAligned(aligned)) {

      // snap horizontally
      snapped.y = gridSnapping.snapValue(segmentStart.y);
    }

    if (verticallyAligned(aligned)) {

      // snap vertically
      snapped.x = gridSnapping.snapValue(segmentStart.x);
    }

    if ('x' in snapped || 'y' in snapped) {
      segmentStart = assign({}, segmentStart, snapped);
      segmentEnd = assign({}, segmentEnd, snapped);
    }

    return [ segmentStart, segmentEnd ];
  }

  var GridSnappingBehaviorModule = {
    __init__: [
      'gridSnappingAutoPlaceBehavior',
      'gridSnappingParticipantBehavior',
      'gridSnappingLayoutConnectionBehavior',
    ],
    gridSnappingAutoPlaceBehavior: [ 'type', GridSnappingAutoPlaceBehavior ],
    gridSnappingParticipantBehavior: [ 'type', GridSnappingParticipantBehavior ],
    gridSnappingLayoutConnectionBehavior: [ 'type', GridSnappingLayoutConnectionBehavior ]
  };

  var GridSnappingModule = {
    __depends__: [
      GridSnappingModule$1,
      GridSnappingBehaviorModule
    ],
    __init__: [ 'bpmnGridSnapping' ],
    bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
  };

  var LABEL_WIDTH = 30,
      LABEL_HEIGHT = 30;


  /**
   * BPMN-specific hit zones and interaction fixes.
   *
   * @param {EventBus} eventBus
   * @param {InteractionEvents} interactionEvents
   */
  function BpmnInteractionEvents(eventBus, interactionEvents) {

    this._interactionEvents = interactionEvents;

    var self = this;

    eventBus.on([
      'interactionEvents.createHit',
      'interactionEvents.updateHit'
    ], function(context) {
      var element = context.element,
          gfx = context.gfx;

      if (is$1(element, 'bpmn:Lane')) {
        return self.createParticipantHit(element, gfx);
      } else

      if (is$1(element, 'bpmn:Participant')) {
        if (isExpanded(element)) {
          return self.createParticipantHit(element, gfx);
        } else {
          return self.createDefaultHit(element, gfx);
        }
      } else

      if (is$1(element, 'bpmn:SubProcess')) {
        if (isExpanded(element)) {
          return self.createSubProcessHit(element, gfx);
        } else {
          return self.createDefaultHit(element, gfx);
        }
      }
    });

  }

  BpmnInteractionEvents.$inject = [
    'eventBus',
    'interactionEvents'
  ];


  BpmnInteractionEvents.prototype.createDefaultHit = function(element, gfx) {
    this._interactionEvents.removeHits(gfx);

    this._interactionEvents.createDefaultHit(element, gfx);

    // indicate that we created a hit
    return true;
  };

  BpmnInteractionEvents.prototype.createParticipantHit = function(element, gfx) {

    // remove existing hits
    this._interactionEvents.removeHits(gfx);

    // add body hit
    this._interactionEvents.createBoxHit(gfx, 'no-move', {
      width: element.width,
      height: element.height
    });

    // add outline hit
    this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
      width: element.width,
      height: element.height
    });

    // add label hit
    this._interactionEvents.createBoxHit(gfx, 'all', {
      width: LABEL_WIDTH,
      height: element.height
    });

    // indicate that we created a hit
    return true;
  };

  BpmnInteractionEvents.prototype.createSubProcessHit = function(element, gfx) {

    // remove existing hits
    this._interactionEvents.removeHits(gfx);

    // add body hit
    this._interactionEvents.createBoxHit(gfx, 'no-move', {
      width: element.width,
      height: element.height
    });

    // add outline hit
    this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
      width: element.width,
      height: element.height
    });

    // add label hit
    this._interactionEvents.createBoxHit(gfx, 'all', {
      width: element.width,
      height: LABEL_HEIGHT
    });

    // indicate that we created a hit
    return true;
  };

  var InteractionEventsModule = {
    __init__: [ 'bpmnInteractionEvents' ],
    bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ]
  };

  /**
   * BPMN 2.0 specific keyboard bindings.
   *
   * @param {Injector} injector
   */
  function BpmnKeyboardBindings(injector) {
    injector.invoke(KeyboardBindings, this);
  }

  e(BpmnKeyboardBindings, KeyboardBindings);

  BpmnKeyboardBindings.$inject = [
    'injector'
  ];


  /**
   * Register available keyboard bindings.
   *
   * @param {Keyboard} keyboard
   * @param {EditorActions} editorActions
   */
  BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {

    // inherit default bindings
    KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);

    /**
     * Add keyboard binding if respective editor action
     * is registered.
     *
     * @param {string} action name
     * @param {Function} fn that implements the key binding
     */
    function addListener(action, fn) {

      if (editorActions.isRegistered(action)) {
        keyboard.addListener(fn);
      }
    }

    // select all elements
    // CTRL + A
    addListener('selectElements', function(context) {

      var event = context.keyEvent;

      if (keyboard.isKey([ 'a', 'A' ], event) && keyboard.isCmd(event)) {
        editorActions.trigger('selectElements');

        return true;
      }
    });

    // search labels
    // CTRL + F
    addListener('find', function(context) {

      var event = context.keyEvent;

      if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) {
        editorActions.trigger('find');

        return true;
      }
    });

    // activate space tool
    // S
    addListener('spaceTool', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 's', 'S' ], event)) {
        editorActions.trigger('spaceTool');

        return true;
      }
    });

    // activate lasso tool
    // L
    addListener('lassoTool', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 'l', 'L' ], event)) {
        editorActions.trigger('lassoTool');

        return true;
      }
    });

    // activate hand tool
    // H
    addListener('handTool', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 'h', 'H' ], event)) {
        editorActions.trigger('handTool');

        return true;
      }
    });

    // activate global connect tool
    // C
    addListener('globalConnectTool', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 'c', 'C' ], event)) {
        editorActions.trigger('globalConnectTool');

        return true;
      }
    });

    // activate direct editing
    // E
    addListener('directEditing', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 'e', 'E' ], event)) {
        editorActions.trigger('directEditing');

        return true;
      }
    });

  };

  var KeyboardModule = {
    __depends__: [
      KeyboardModule$1
    ],
    __init__: [ 'keyboardBindings' ],
    keyboardBindings: [ 'type', BpmnKeyboardBindings ]
  };

  var DEFAULT_CONFIG = {
    moveSpeed: 1,
    moveSpeedAccelerated: 10
  };

  var HIGHER_PRIORITY$3 = 1500;

  var LEFT = 'left';
  var UP = 'up';
  var RIGHT = 'right';
  var DOWN = 'down';

  var KEY_TO_DIRECTION = {
    ArrowLeft: LEFT,
    Left: LEFT,
    ArrowUp: UP,
    Up: UP,
    ArrowRight: RIGHT,
    Right: RIGHT,
    ArrowDown: DOWN,
    Down: DOWN
  };

  var DIRECTIONS_DELTA = {
    left: function(speed) {
      return {
        x: -speed,
        y: 0
      };
    },
    up: function(speed) {
      return {
        x: 0,
        y: -speed
      };
    },
    right: function(speed) {
      return {
        x: speed,
        y: 0
      };
    },
    down: function(speed) {
      return {
        x: 0,
        y: speed
      };
    }
  };


  /**
   * Enables to move selection with keyboard arrows.
   * Use with Shift for modified speed (default=1, with Shift=10).
   * Pressed Cmd/Ctrl turns the feature off.
   *
   * @param {Object} config
   * @param {number} [config.moveSpeed=1]
   * @param {number} [config.moveSpeedAccelerated=10]
   * @param {Keyboard} keyboard
   * @param {Modeling} modeling
   * @param {Selection} selection
   */
  function KeyboardMoveSelection(
      config,
      keyboard,
      modeling,
      rules,
      selection
  ) {

    var self = this;

    this._config = assign({}, DEFAULT_CONFIG, config || {});

    keyboard.addListener(HIGHER_PRIORITY$3, function(event) {

      var keyEvent = event.keyEvent;

      var direction = KEY_TO_DIRECTION[keyEvent.key];

      if (!direction) {
        return;
      }

      if (keyboard.isCmd(keyEvent)) {
        return;
      }

      var accelerated = keyboard.isShift(keyEvent);

      self.moveSelection(direction, accelerated);

      return true;
    });


    /**
     * Move selected elements in the given direction,
     * optionally specifying accelerated movement.
     *
     * @param {string} direction
     * @param {boolean} [accelerated=false]
     */
    this.moveSelection = function(direction, accelerated) {

      var selectedElements = selection.get();

      if (!selectedElements.length) {
        return;
      }

      var speed = this._config[
        accelerated ?
          'moveSpeedAccelerated' :
          'moveSpeed'
      ];

      var delta = DIRECTIONS_DELTA[direction](speed);

      var canMove = rules.allowed('elements.move', {
        shapes: selectedElements
      });

      if (canMove) {
        modeling.moveElements(selectedElements, delta);
      }
    };

  }

  KeyboardMoveSelection.$inject = [
    'config.keyboardMoveSelection',
    'keyboard',
    'modeling',
    'rules',
    'selection'
  ];

  var KeyboardMoveSelectionModule = {
    __depends__: [
      KeyboardModule$1,
      SelectionModule
    ],
    __init__: [
      'keyboardMoveSelection'
    ],
    keyboardMoveSelection: [ 'type', KeyboardMoveSelection ]
  };

  var DEFAULT_MIN_WIDTH = 10;


  /**
   * A component that provides resizing of shapes on the canvas.
   *
   * The following components are part of shape resize:
   *
   *  * adding resize handles,
   *  * creating a visual during resize
   *  * checking resize rules
   *  * committing a change once finished
   *
   *
   * ## Customizing
   *
   * It's possible to customize the resizing behaviour by intercepting 'resize.start'
   * and providing the following parameters through the 'context':
   *
   *   * minDimensions ({ width, height }): minimum shape dimensions
   *
   *   * childrenBoxPadding ({ left, top, bottom, right } || number):
   *     gap between the minimum bounding box and the container
   *
   * f.ex:
   *
   * ```javascript
   * eventBus.on('resize.start', 1500, function(event) {
   *   var context = event.context,
   *
   *  context.minDimensions = { width: 140, height: 120 };
   *
   *  // Passing general padding
   *  context.childrenBoxPadding = 30;
   *
   *  // Passing padding to a specific side
   *  context.childrenBoxPadding.left = 20;
   * });
   * ```
   */
  function Resize(eventBus, rules, modeling, dragging) {

    this._dragging = dragging;
    this._rules = rules;

    var self = this;


    /**
     * Handle resize move by specified delta.
     *
     * @param {Object} context
     * @param {Point} delta
     */
    function handleMove(context, delta) {

      var shape = context.shape,
          direction = context.direction,
          resizeConstraints = context.resizeConstraints,
          newBounds;

      context.delta = delta;

      newBounds = resizeBounds$1(shape, direction, delta);

      // ensure constraints during resize
      context.newBounds = ensureConstraints$1(newBounds, resizeConstraints);

      // update + cache executable state
      context.canExecute = self.canResize(context);
    }

    /**
     * Handle resize start.
     *
     * @param  {Object} context
     */
    function handleStart(context) {

      var resizeConstraints = context.resizeConstraints,

          // evaluate minBounds for backwards compatibility
          minBounds = context.minBounds;

      if (resizeConstraints !== undefined) {
        return;
      }

      if (minBounds === undefined) {
        minBounds = self.computeMinResizeBox(context);
      }

      context.resizeConstraints = {
        min: asTRBL(minBounds)
      };
    }

    /**
     * Handle resize end.
     *
     * @param  {Object} context
     */
    function handleEnd(context) {
      var shape = context.shape,
          canExecute = context.canExecute,
          newBounds = context.newBounds;

      if (canExecute) {

        // ensure we have actual pixel values for new bounds
        // (important when zoom level was > 1 during move)
        newBounds = roundBounds(newBounds);

        if (!boundsChanged(shape, newBounds)) {

          // no resize necessary
          return;
        }

        // perform the actual resize
        modeling.resizeShape(shape, newBounds);
      }
    }


    eventBus.on('resize.start', function(event) {
      handleStart(event.context);
    });

    eventBus.on('resize.move', function(event) {
      var delta = {
        x: event.dx,
        y: event.dy
      };

      handleMove(event.context, delta);
    });

    eventBus.on('resize.end', function(event) {
      handleEnd(event.context);
    });

  }


  Resize.prototype.canResize = function(context) {
    var rules = this._rules;

    var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]);

    return rules.allowed('shape.resize', ctx);
  };

  /**
   * Activate a resize operation.
   *
   * You may specify additional contextual information and must specify a
   * resize direction during activation of the resize event.
   *
   * @param {MouseEvent} event
   * @param {djs.model.Shape} shape
   * @param {Object|string} contextOrDirection
   */
  Resize.prototype.activate = function(event, shape, contextOrDirection) {
    var dragging = this._dragging,
        context,
        direction;

    if (typeof contextOrDirection === 'string') {
      contextOrDirection = {
        direction: contextOrDirection
      };
    }

    context = assign({ shape: shape }, contextOrDirection);

    direction = context.direction;

    if (!direction) {
      throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)');
    }

    dragging.init(event, getReferencePoint$1(shape, direction), 'resize', {
      autoActivate: true,
      cursor: getCursor(direction),
      data: {
        shape: shape,
        context: context
      }
    });
  };

  Resize.prototype.computeMinResizeBox = function(context) {
    var shape = context.shape,
        direction = context.direction,
        minDimensions,
        childrenBounds;

    minDimensions = context.minDimensions || {
      width: DEFAULT_MIN_WIDTH,
      height: DEFAULT_MIN_WIDTH
    };

    // get children bounds
    childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding);

    // get correct minimum bounds from given resize direction
    // basically ensures that the minBounds is max(childrenBounds, minDimensions)
    return getMinResizeBounds(direction, shape, minDimensions, childrenBounds);
  };


  Resize.$inject = [
    'eventBus',
    'rules',
    'modeling',
    'dragging'
  ];

  // helpers //////////

  function boundsChanged(shape, newBounds) {
    return shape.x !== newBounds.x ||
      shape.y !== newBounds.y ||
      shape.width !== newBounds.width ||
      shape.height !== newBounds.height;
  }

  function getReferencePoint$1(shape, direction) {
    var mid = getMid(shape),
        trbl = asTRBL(shape);

    var referencePoint = {
      x: mid.x,
      y: mid.y
    };

    if (direction.indexOf('n') !== -1) {
      referencePoint.y = trbl.top;
    } else if (direction.indexOf('s') !== -1) {
      referencePoint.y = trbl.bottom;
    }

    if (direction.indexOf('e') !== -1) {
      referencePoint.x = trbl.right;
    } else if (direction.indexOf('w') !== -1) {
      referencePoint.x = trbl.left;
    }

    return referencePoint;
  }

  function getCursor(direction) {
    var prefix = 'resize-';

    if (direction === 'n' || direction === 's') {
      return prefix + 'ns';
    } else if (direction === 'e' || direction === 'w') {
      return prefix + 'ew';
    } else if (direction === 'nw' || direction === 'se') {
      return prefix + 'nwse';
    } else {
      return prefix + 'nesw';
    }
  }

  var MARKER_RESIZING$1 = 'djs-resizing',
      MARKER_RESIZE_NOT_OK = 'resize-not-ok';

  var LOW_PRIORITY$e = 500;


  /**
   * Provides previews for resizing shapes when resizing.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {PreviewSupport} previewSupport
   */
  function ResizePreview(eventBus, canvas, previewSupport) {

    /**
     * Update resizer frame.
     *
     * @param {Object} context
     */
    function updateFrame(context) {

      var shape = context.shape,
          bounds = context.newBounds,
          frame = context.frame;

      if (!frame) {
        frame = context.frame = previewSupport.addFrame(shape, canvas.getActiveLayer());

        canvas.addMarker(shape, MARKER_RESIZING$1);
      }

      if (bounds.width > 5) {
        attr(frame, { x: bounds.x, width: bounds.width });
      }

      if (bounds.height > 5) {
        attr(frame, { y: bounds.y, height: bounds.height });
      }

      if (context.canExecute) {
        classes(frame).remove(MARKER_RESIZE_NOT_OK);
      } else {
        classes(frame).add(MARKER_RESIZE_NOT_OK);
      }
    }

    /**
     * Remove resizer frame.
     *
     * @param {Object} context
     */
    function removeFrame(context) {
      var shape = context.shape,
          frame = context.frame;

      if (frame) {
        remove$1(context.frame);
      }

      canvas.removeMarker(shape, MARKER_RESIZING$1);
    }

    // add and update previews
    eventBus.on('resize.move', LOW_PRIORITY$e, function(event) {
      updateFrame(event.context);
    });

    // remove previews
    eventBus.on('resize.cleanup', function(event) {
      removeFrame(event.context);
    });

  }

  ResizePreview.$inject = [
    'eventBus',
    'canvas',
    'previewSupport'
  ];

  var HANDLE_OFFSET = -6,
      HANDLE_SIZE = 8,
      HANDLE_HIT_SIZE = 20;

  var CLS_RESIZER = 'djs-resizer';

  var directions = [ 'n', 'w', 's', 'e', 'nw', 'ne', 'se', 'sw' ];


  /**
   * This component is responsible for adding resize handles.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Selection} selection
   * @param {Resize} resize
   */
  function ResizeHandles(eventBus, canvas, selection, resize) {

    this._resize = resize;
    this._canvas = canvas;

    var self = this;

    eventBus.on('selection.changed', function(e) {
      var newSelection = e.newSelection;

      // remove old selection markers
      self.removeResizers();

      // add new selection markers ONLY if single selection
      if (newSelection.length === 1) {
        forEach$1(newSelection, bind(self.addResizer, self));
      }
    });

    eventBus.on('shape.changed', function(e) {
      var shape = e.element;

      if (selection.isSelected(shape)) {
        self.removeResizers();

        self.addResizer(shape);
      }
    });
  }


  ResizeHandles.prototype.makeDraggable = function(element, gfx, direction) {
    var resize = this._resize;

    function startResize(event) {

      // only trigger on left mouse button
      if (isPrimaryButton(event)) {
        resize.activate(event, element, direction);
      }
    }

    componentEvent.bind(gfx, 'mousedown', startResize);
    componentEvent.bind(gfx, 'touchstart', startResize);
  };


  ResizeHandles.prototype._createResizer = function(element, x, y, direction) {
    var resizersParent = this._getResizersParent();

    var offset = getHandleOffset(direction);

    var group = create$1('g');

    classes(group).add(CLS_RESIZER);
    classes(group).add(CLS_RESIZER + '-' + element.id);
    classes(group).add(CLS_RESIZER + '-' + direction);

    append(resizersParent, group);

    var visual = create$1('rect');

    attr(visual, {
      x: -HANDLE_SIZE / 2 + offset.x,
      y: -HANDLE_SIZE / 2 + offset.y,
      width: HANDLE_SIZE,
      height: HANDLE_SIZE
    });

    classes(visual).add(CLS_RESIZER + '-visual');

    append(group, visual);

    var hit = create$1('rect');

    attr(hit, {
      x: -HANDLE_HIT_SIZE / 2 + offset.x,
      y: -HANDLE_HIT_SIZE / 2 + offset.y,
      width: HANDLE_HIT_SIZE,
      height: HANDLE_HIT_SIZE
    });

    classes(hit).add(CLS_RESIZER + '-hit');

    append(group, hit);

    transform(group, x, y);

    return group;
  };

  ResizeHandles.prototype.createResizer = function(element, direction) {
    var point = getReferencePoint$1(element, direction);

    var resizer = this._createResizer(element, point.x, point.y, direction);

    this.makeDraggable(element, resizer, direction);
  };

  // resize handles implementation ///////////////////////////////

  /**
   * Add resizers for a given element.
   *
   * @param {djs.model.Element} element
   */
  ResizeHandles.prototype.addResizer = function(element) {
    var self = this;

    if (isConnection$a(element) || !this._resize.canResize({ shape: element })) {
      return;
    }

    forEach$1(directions, function(direction) {
      self.createResizer(element, direction);
    });
  };

  /**
   * Remove all resizers
   */
  ResizeHandles.prototype.removeResizers = function() {
    var resizersParent = this._getResizersParent();

    clear(resizersParent);
  };

  ResizeHandles.prototype._getResizersParent = function() {
    return this._canvas.getLayer('resizers');
  };

  ResizeHandles.$inject = [
    'eventBus',
    'canvas',
    'selection',
    'resize'
  ];

  // helpers //////////

  function getHandleOffset(direction) {
    var offset = {
      x: 0,
      y: 0
    };

    if (direction.indexOf('e') !== -1) {
      offset.x = -HANDLE_OFFSET;
    } else if (direction.indexOf('w') !== -1) {
      offset.x = HANDLE_OFFSET;
    }

    if (direction.indexOf('s') !== -1) {
      offset.y = -HANDLE_OFFSET;
    } else if (direction.indexOf('n') !== -1) {
      offset.y = HANDLE_OFFSET;
    }

    return offset;
  }

  function isConnection$a(element) {
    return !!element.waypoints;
  }

  var ResizeModule = {
    __depends__: [
      RulesModule$1,
      DraggingModule,
      PreviewSupportModule
    ],
    __init__: [
      'resize',
      'resizePreview',
      'resizeHandles'
    ],
    resize: [ 'type', Resize ],
    resizePreview: [ 'type', ResizePreview ],
    resizeHandles: [ 'type', ResizeHandles ]
  };

  var HIGH_PRIORITY$c = 2000;


  function LabelEditingProvider(
      eventBus, bpmnFactory, canvas, directEditing,
      modeling, resizeHandles, textRenderer) {

    this._bpmnFactory = bpmnFactory;
    this._canvas = canvas;
    this._modeling = modeling;
    this._textRenderer = textRenderer;

    directEditing.registerProvider(this);

    // listen to dblclick on non-root elements
    eventBus.on('element.dblclick', function(event) {
      activateDirectEdit(event.element, true);
    });

    // complete on followup canvas operation
    eventBus.on([
      'autoPlace.start',
      'canvas.viewbox.changing',
      'drag.init',
      'element.mousedown',
      'popupMenu.open',
      'root.set',
      'selection.changed'
    ], function(event) {

      if (directEditing.isActive()) {
        directEditing.complete();
      }
    });

    eventBus.on([
      'shape.remove',
      'connection.remove'
    ], HIGH_PRIORITY$c, function(event) {

      if (directEditing.isActive(event.element)) {
        directEditing.cancel();
      }
    });

    // cancel on command stack changes
    eventBus.on([ 'commandStack.changed' ], function(e) {
      if (directEditing.isActive()) {
        directEditing.cancel();
      }
    });


    eventBus.on('directEditing.activate', function(event) {
      resizeHandles.removeResizers();
    });

    eventBus.on('create.end', 500, function(event) {

      var context = event.context,
          element = context.shape,
          canExecute = event.context.canExecute,
          isTouch = event.isTouch;

      // TODO(nikku): we need to find a way to support the
      // direct editing on mobile devices; right now this will
      // break for desworkflowediting on mobile devices
      // as it breaks the user interaction workflow

      // TODO(nre): we should temporarily focus the edited element
      // here and release the focused viewport after the direct edit
      // operation is finished
      if (isTouch) {
        return;
      }

      if (!canExecute) {
        return;
      }

      if (context.hints && context.hints.createElementsBehavior === false) {
        return;
      }

      activateDirectEdit(element);
    });

    eventBus.on('autoPlace.end', 500, function(event) {
      activateDirectEdit(event.shape);
    });


    function activateDirectEdit(element, force) {
      if (force ||
          isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation' ]) ||
          isCollapsedSubProcess(element)) {

        directEditing.activate(element);
      }
    }

  }

  LabelEditingProvider.$inject = [
    'eventBus',
    'bpmnFactory',
    'canvas',
    'directEditing',
    'modeling',
    'resizeHandles',
    'textRenderer'
  ];


  /**
   * Activate direct editing for activities and text annotations.
   *
   * @param  {djs.model.Base} element
   *
   * @return {Object} an object with properties bounds (position and size), text and options
   */
  LabelEditingProvider.prototype.activate = function(element) {

    // text
    var text = getLabel(element);

    if (text === undefined) {
      return;
    }

    var context = {
      text: text
    };

    // bounds
    var bounds = this.getEditingBBox(element);

    assign(context, bounds);

    var options = {};

    // tasks
    if (
      isAny(element, [
        'bpmn:Task',
        'bpmn:Participant',
        'bpmn:Lane',
        'bpmn:CallActivity'
      ]) ||
      isCollapsedSubProcess(element)
    ) {
      assign(options, {
        centerVertically: true
      });
    }

    // external labels
    if (isLabelExternal(element)) {
      assign(options, {
        autoResize: true
      });
    }

    // text annotations
    if (is$1(element, 'bpmn:TextAnnotation')) {
      assign(options, {
        resizable: true,
        autoResize: true
      });
    }

    assign(context, {
      options: options
    });

    return context;
  };


  /**
   * Get the editing bounding box based on the element's size and position
   *
   * @param  {djs.model.Base} element
   *
   * @return {Object} an object containing information about position
   *                  and size (fixed or minimum and/or maximum)
   */
  LabelEditingProvider.prototype.getEditingBBox = function(element) {
    var canvas = this._canvas;

    var target = element.label || element;

    var bbox = canvas.getAbsoluteBBox(target);

    var mid = {
      x: bbox.x + bbox.width / 2,
      y: bbox.y + bbox.height / 2
    };

    // default position
    var bounds = { x: bbox.x, y: bbox.y };

    var zoom = canvas.zoom();

    var defaultStyle = this._textRenderer.getDefaultStyle(),
        externalStyle = this._textRenderer.getExternalStyle();

    // take zoom into account
    var externalFontSize = externalStyle.fontSize * zoom,
        externalLineHeight = externalStyle.lineHeight,
        defaultFontSize = defaultStyle.fontSize * zoom,
        defaultLineHeight = defaultStyle.lineHeight;

    var style = {
      fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
      fontWeight: this._textRenderer.getDefaultStyle().fontWeight
    };

    // adjust for expanded pools AND lanes
    if (is$1(element, 'bpmn:Lane') || isExpandedPool(element)) {

      assign(bounds, {
        width: bbox.height,
        height: 30 * zoom,
        x: bbox.x - bbox.height / 2 + (15 * zoom),
        y: mid.y - (30 * zoom) / 2
      });

      assign(style, {
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight,
        paddingTop: (7 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (5 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px',
        transform: 'rotate(-90deg)'
      });
    }


    // internal labels for tasks and collapsed call activities,
    // sub processes and participants
    if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) ||
        isCollapsedPool(element) ||
        isCollapsedSubProcess(element)) {

      assign(bounds, {
        width: bbox.width,
        height: bbox.height
      });

      assign(style, {
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight,
        paddingTop: (7 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (5 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px'
      });
    }


    // internal labels for expanded sub processes
    if (isExpandedSubProcess$1(element)) {
      assign(bounds, {
        width: bbox.width,
        x: bbox.x
      });

      assign(style, {
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight,
        paddingTop: (7 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (5 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px'
      });
    }

    var width = 90 * zoom,
        paddingTop = 7 * zoom,
        paddingBottom = 4 * zoom;

    // external labels for events, data elements, gateways, groups and connections
    if (target.labelTarget) {
      assign(bounds, {
        width: width,
        height: bbox.height + paddingTop + paddingBottom,
        x: mid.x - width / 2,
        y: bbox.y - paddingTop
      });

      assign(style, {
        fontSize: externalFontSize + 'px',
        lineHeight: externalLineHeight,
        paddingTop: paddingTop + 'px',
        paddingBottom: paddingBottom + 'px'
      });
    }

    // external label not yet created
    if (isLabelExternal(target)
        && !hasExternalLabel(target)
        && !isLabel$6(target)) {

      var externalLabelMid = getExternalLabelMid(element);

      var absoluteBBox = canvas.getAbsoluteBBox({
        x: externalLabelMid.x,
        y: externalLabelMid.y,
        width: 0,
        height: 0
      });

      var height = externalFontSize + paddingTop + paddingBottom;

      assign(bounds, {
        width: width,
        height: height,
        x: absoluteBBox.x - width / 2,
        y: absoluteBBox.y - height / 2
      });

      assign(style, {
        fontSize: externalFontSize + 'px',
        lineHeight: externalLineHeight,
        paddingTop: paddingTop + 'px',
        paddingBottom: paddingBottom + 'px'
      });
    }

    // text annotations
    if (is$1(element, 'bpmn:TextAnnotation')) {
      assign(bounds, {
        width: bbox.width,
        height: bbox.height,
        minWidth: 30 * zoom,
        minHeight: 10 * zoom
      });

      assign(style, {
        textAlign: 'left',
        paddingTop: (5 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (7 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px',
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight
      });
    }

    return { bounds: bounds, style: style };
  };


  LabelEditingProvider.prototype.update = function(
      element, newLabel,
      activeContextText, bounds) {

    var newBounds,
        bbox;

    if (is$1(element, 'bpmn:TextAnnotation')) {

      bbox = this._canvas.getAbsoluteBBox(element);

      newBounds = {
        x: element.x,
        y: element.y,
        width: element.width / bbox.width * bounds.width,
        height: element.height / bbox.height * bounds.height
      };
    }

    if (isEmptyText$1(newLabel)) {
      newLabel = null;
    }

    this._modeling.updateLabel(element, newLabel, newBounds);
  };



  // helpers //////////////////////

  function isCollapsedSubProcess(element) {
    return is$1(element, 'bpmn:SubProcess') && !isExpanded(element);
  }

  function isExpandedSubProcess$1(element) {
    return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
  }

  function isCollapsedPool(element) {
    return is$1(element, 'bpmn:Participant') && !isExpanded(element);
  }

  function isExpandedPool(element) {
    return is$1(element, 'bpmn:Participant') && isExpanded(element);
  }

  function isEmptyText$1(label) {
    return !label || !label.trim();
  }

  var MARKER_HIDDEN = 'djs-element-hidden',
      MARKER_LABEL_HIDDEN = 'djs-label-hidden';


  function LabelEditingPreview(
      eventBus, canvas, elementRegistry,
      pathMap) {

    var self = this;

    var defaultLayer = canvas.getDefaultLayer();

    var element, absoluteElementBBox, gfx;

    eventBus.on('directEditing.activate', function(context) {
      var activeProvider = context.active;

      element = activeProvider.element.label || activeProvider.element;

      // text annotation
      if (is$1(element, 'bpmn:TextAnnotation')) {
        absoluteElementBBox = canvas.getAbsoluteBBox(element);

        gfx = create$1('g');

        var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.0,
            my: 0.0
          }
        });

        var path = self.path = create$1('path');

        attr(path, {
          d: textPathData,
          strokeWidth: 2,
          stroke: getStrokeColor(element)
        });

        append(gfx, path);

        append(defaultLayer, gfx);

        translate$2(gfx, element.x, element.y);
      }

      if (is$1(element, 'bpmn:TextAnnotation') ||
          element.labelTarget) {
        canvas.addMarker(element, MARKER_HIDDEN);
      } else if (is$1(element, 'bpmn:Task') ||
                 is$1(element, 'bpmn:CallActivity') ||
                 is$1(element, 'bpmn:SubProcess') ||
                 is$1(element, 'bpmn:Participant')) {
        canvas.addMarker(element, MARKER_LABEL_HIDDEN);
      }
    });

    eventBus.on('directEditing.resize', function(context) {

      // text annotation
      if (is$1(element, 'bpmn:TextAnnotation')) {
        var height = context.height,
            dy = context.dy;

        var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);

        var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: newElementHeight,
          position: {
            mx: 0.0,
            my: 0.0
          }
        });

        attr(self.path, {
          d: textPathData
        });
      }
    });

    eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
      var activeProvider = context.active;

      if (activeProvider) {
        canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
        canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
      }

      element = undefined;
      absoluteElementBBox = undefined;

      if (gfx) {
        remove$1(gfx);

        gfx = undefined;
      }
    });
  }

  LabelEditingPreview.$inject = [
    'eventBus',
    'canvas',
    'elementRegistry',
    'pathMap'
  ];


  // helpers ///////////////////

  function getStrokeColor(element, defaultColor) {
    var di = getDi(element);

    return di.get('stroke') || defaultColor || 'black';
  }

  var LabelEditingModule = {
    __depends__: [
      ChangeSupportModule,
      ResizeModule,
      DirectEditingModule
    ],
    __init__: [
      'labelEditingProvider',
      'labelEditingPreview'
    ],
    labelEditingProvider: [ 'type', LabelEditingProvider ],
    labelEditingPreview: [ 'type', LabelEditingPreview ]
  };

  var ALIGNMENTS = [
    'top',
    'bottom',
    'left',
    'right'
  ];

  var ELEMENT_LABEL_DISTANCE = 10;

  /**
   * A component that makes sure that external labels are added
   * together with respective elements and properly updated (DI wise)
   * during move.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function AdaptiveLabelPositioningBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    this.postExecuted([
      'connection.create',
      'connection.layout',
      'connection.updateWaypoints'
    ], function(event) {
      var context = event.context,
          connection = context.connection,
          source = connection.source,
          target = connection.target,
          hints = context.hints || {};

      if (hints.createElementsBehavior !== false) {
        checkLabelAdjustment(source);
        checkLabelAdjustment(target);
      }
    });


    this.postExecuted([
      'label.create'
    ], function(event) {
      var context = event.context,
          shape = context.shape,
          hints = context.hints || {};

      if (hints.createElementsBehavior !== false) {
        checkLabelAdjustment(shape.labelTarget);
      }
    });


    this.postExecuted([
      'elements.create'
    ], function(event) {
      var context = event.context,
          elements = context.elements,
          hints = context.hints || {};

      if (hints.createElementsBehavior !== false) {
        elements.forEach(function(element) {
          checkLabelAdjustment(element);
        });
      }
    });

    function checkLabelAdjustment(element) {

      // skip non-existing labels
      if (!hasExternalLabel(element)) {
        return;
      }

      var optimalPosition = getOptimalPosition(element);

      // no optimal position found
      if (!optimalPosition) {
        return;
      }

      adjustLabelPosition(element, optimalPosition);
    }

    function adjustLabelPosition(element, orientation) {

      var elementMid = getMid(element),
          label = element.label,
          labelMid = getMid(label);

      // ignore labels that are being created
      if (!label.parent) {
        return;
      }

      var elementTrbl = asTRBL(element);

      var newLabelMid;

      switch (orientation) {
      case 'top':
        newLabelMid = {
          x: elementMid.x,
          y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
        };

        break;

      case 'left':

        newLabelMid = {
          x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
          y: elementMid.y
        };

        break;

      case 'bottom':

        newLabelMid = {
          x: elementMid.x,
          y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
        };

        break;

      case 'right':

        newLabelMid = {
          x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
          y: elementMid.y
        };

        break;
      }

      var delta$1 = delta(newLabelMid, labelMid);

      modeling.moveShape(label, delta$1);
    }

  }

  e(AdaptiveLabelPositioningBehavior, CommandInterceptor);

  AdaptiveLabelPositioningBehavior.$inject = [
    'eventBus',
    'modeling'
  ];


  // helpers //////////////////////

  /**
   * Return alignments which are taken by a boundary's host element
   *
   * @param {Shape} element
   *
   * @return {Array<string>}
   */
  function getTakenHostAlignments(element) {

    var hostElement = element.host,
        elementMid = getMid(element),
        hostOrientation = getOrientation(elementMid, hostElement);

    var freeAlignments;

    // check whether there is a multi-orientation, e.g. 'top-left'
    if (hostOrientation.indexOf('-') >= 0) {
      freeAlignments = hostOrientation.split('-');
    } else {
      freeAlignments = [ hostOrientation ];
    }

    var takenAlignments = ALIGNMENTS.filter(function(alignment) {

      return freeAlignments.indexOf(alignment) === -1;
    });

    return takenAlignments;

  }

  /**
   * Return alignments which are taken by related connections
   *
   * @param {Shape} element
   *
   * @return {Array<string>}
   */
  function getTakenConnectionAlignments(element) {

    var elementMid = getMid(element);

    var takenAlignments = [].concat(
      element.incoming.map(function(c) {
        return c.waypoints[c.waypoints.length - 2 ];
      }),
      element.outgoing.map(function(c) {
        return c.waypoints[1];
      })
    ).map(function(point) {
      return getApproximateOrientation(elementMid, point);
    });

    return takenAlignments;
  }

  /**
   * Return the optimal label position around an element
   * or _undefined_, if none was found.
   *
   * @param  {Shape} element
   *
   * @return {string} positioning identifier
   */
  function getOptimalPosition(element) {

    var labelMid = getMid(element.label);

    var elementMid = getMid(element);

    var labelOrientation = getApproximateOrientation(elementMid, labelMid);

    if (!isAligned(labelOrientation)) {
      return;
    }

    var takenAlignments = getTakenConnectionAlignments(element);

    if (element.host) {
      var takenHostAlignments = getTakenHostAlignments(element);

      takenAlignments = takenAlignments.concat(takenHostAlignments);
    }

    var freeAlignments = ALIGNMENTS.filter(function(alignment) {

      return takenAlignments.indexOf(alignment) === -1;
    });

    // NOTHING TO DO; label already aligned a.O.K.
    if (freeAlignments.indexOf(labelOrientation) !== -1) {
      return;
    }

    return freeAlignments[0];
  }

  function getApproximateOrientation(p0, p1) {
    return getOrientation(p1, p0, 5);
  }

  function isAligned(orientation) {
    return ALIGNMENTS.indexOf(orientation) !== -1;
  }

  function AppendBehavior(eventBus, elementFactory, bpmnRules) {

    CommandInterceptor.call(this, eventBus);

    // assign correct shape position unless already set

    this.preExecute('shape.append', function(context) {

      var source = context.source,
          shape = context.shape;

      if (!context.position) {

        if (is$1(shape, 'bpmn:TextAnnotation')) {
          context.position = {
            x: source.x + source.width / 2 + 75,
            y: source.y - (50) - shape.height / 2
          };
        } else {
          context.position = {
            x: source.x + source.width + 80 + shape.width / 2,
            y: source.y + source.height / 2
          };
        }
      }
    }, true);
  }

  e(AppendBehavior, CommandInterceptor);

  AppendBehavior.$inject = [
    'eventBus',
    'elementFactory',
    'bpmnRules'
  ];

  function AssociationBehavior(injector, modeling) {
    injector.invoke(CommandInterceptor, this);

    this.postExecute('shape.move', function(context) {
      var newParent = context.newParent,
          shape = context.shape;

      var associations = filter(shape.incoming.concat(shape.outgoing), function(connection) {
        return is$1(connection, 'bpmn:Association');
      });

      forEach$1(associations, function(association) {
        modeling.moveConnection(association, { x: 0, y: 0 }, newParent);
      });
    }, true);
  }

  e(AssociationBehavior, CommandInterceptor);

  AssociationBehavior.$inject = [
    'injector',
    'modeling'
  ];

  var LOW_PRIORITY$d = 500;


  /**
   * Replace intermediate event with boundary event when creating or moving results in attached event.
   */
  function AttachEventBehavior(bpmnReplace, injector) {
    injector.invoke(CommandInterceptor, this);

    this._bpmnReplace = bpmnReplace;

    var self = this;

    this.postExecuted('elements.create', LOW_PRIORITY$d, function(context) {
      var elements = context.elements;

      elements = elements.filter(function(shape) {
        var host = shape.host;

        return shouldReplace$1(shape, host);
      });

      if (elements.length !== 1) {
        return;
      }

      elements.map(function(element) {
        return elements.indexOf(element);
      }).forEach(function(index) {
        var host = elements[ index ];

        context.elements[ index ] = self.replaceShape(elements[ index ], host);
      });
    }, true);


    this.preExecute('elements.move', LOW_PRIORITY$d, function(context) {
      var shapes = context.shapes,
          host = context.newHost;

      if (shapes.length !== 1) {
        return;
      }

      var shape = shapes[0];

      if (shouldReplace$1(shape, host)) {
        context.shapes = [ self.replaceShape(shape, host) ];
      }
    }, true);
  }

  AttachEventBehavior.$inject = [
    'bpmnReplace',
    'injector'
  ];

  e(AttachEventBehavior, CommandInterceptor);

  AttachEventBehavior.prototype.replaceShape = function(shape, host) {
    var eventDefinition = getEventDefinition$1(shape);

    var boundaryEvent = {
      type: 'bpmn:BoundaryEvent',
      host: host
    };

    if (eventDefinition) {
      boundaryEvent.eventDefinitionType = eventDefinition.$type;
    }

    return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
  };


  // helpers //////////

  function getEventDefinition$1(element) {
    var businessObject = getBusinessObject(element),
        eventDefinitions = businessObject.eventDefinitions;

    return eventDefinitions && eventDefinitions[0];
  }

  function shouldReplace$1(shape, host) {
    return !isLabel$6(shape) &&
      isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host;
  }

  /**
   * BPMN specific boundary event behavior
   */
  function BoundaryEventBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    function getBoundaryEvents(element) {
      return filter(element.attachers, function(attacher) {
        return is$1(attacher, 'bpmn:BoundaryEvent');
      });
    }

    // remove after connecting to event-based gateway
    this.postExecute('connection.create', function(event) {
      var source = event.context.source,
          target = event.context.target,
          boundaryEvents = getBoundaryEvents(target);

      if (
        is$1(source, 'bpmn:EventBasedGateway') &&
        is$1(target, 'bpmn:ReceiveTask') &&
        boundaryEvents.length > 0
      ) {
        modeling.removeElements(boundaryEvents);
      }

    });

    // remove after replacing connected gateway with event-based gateway
    this.postExecute('connection.reconnect', function(event) {
      var oldSource = event.context.oldSource,
          newSource = event.context.newSource;

      if (is$1(oldSource, 'bpmn:Gateway') &&
          is$1(newSource, 'bpmn:EventBasedGateway')) {
        forEach$1(newSource.outgoing, function(connection) {
          var target = connection.target,
              attachedboundaryEvents = getBoundaryEvents(target);

          if (is$1(target, 'bpmn:ReceiveTask') &&
              attachedboundaryEvents.length > 0) {
            modeling.removeElements(attachedboundaryEvents);
          }
        });
      }
    });

  }

  BoundaryEventBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  e(BoundaryEventBehavior, CommandInterceptor);

  function CreateBehavior(injector) {
    injector.invoke(CommandInterceptor, this);

    this.preExecute('shape.create', 1500, function(event) {
      var context = event.context,
          parent = context.parent,
          shape = context.shape;

      if (is$1(parent, 'bpmn:Lane') && !is$1(shape, 'bpmn:Lane')) {
        context.parent = getParent(parent, 'bpmn:Participant');
      }
    });

  }


  CreateBehavior.$inject = [ 'injector' ];

  e(CreateBehavior, CommandInterceptor);

  /**
   * BPMN specific create data object behavior
   */
  function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) {

    CommandInterceptor.call(this, eventBus);

    this.preExecute('shape.create', function(event) {

      var context = event.context,
          shape = context.shape;

      if (is$1(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {

        // create a DataObject every time a DataObjectReference is created
        var dataObject = bpmnFactory.create('bpmn:DataObject');

        // set the reference to the DataObject
        shape.businessObject.dataObjectRef = dataObject;
      }
    });

  }

  CreateDataObjectBehavior.$inject = [
    'eventBus',
    'bpmnFactory',
    'moddle'
  ];

  e(CreateDataObjectBehavior, CommandInterceptor);

  var HORIZONTAL_PARTICIPANT_PADDING = 20,
      VERTICAL_PARTICIPANT_PADDING = 20;

  var PARTICIPANT_BORDER_WIDTH = 30;

  var HIGH_PRIORITY$b = 2000;


  /**
   * BPMN-specific behavior for creating participants.
   */
  function CreateParticipantBehavior(canvas, eventBus, modeling) {
    CommandInterceptor.call(this, eventBus);

    // fit participant
    eventBus.on([
      'create.start',
      'shape.move.start'
    ], HIGH_PRIORITY$b, function(event) {
      var context = event.context,
          shape = context.shape,
          rootElement = canvas.getRootElement();

      if (!is$1(shape, 'bpmn:Participant') ||
        !is$1(rootElement, 'bpmn:Process') ||
        !rootElement.children.length) {
        return;
      }

      // ignore connections, groups and labels
      var children = rootElement.children.filter(function(element) {
        return !is$1(element, 'bpmn:Group') &&
          !isLabel$6(element) &&
          !isConnection$9(element);
      });

      // ensure for available children to calculate bounds
      if (!children.length) {
        return;
      }

      var childrenBBox = getBBox(children);

      var participantBounds = getParticipantBounds(shape, childrenBBox);

      // assign width and height
      assign(shape, participantBounds);

      // assign create constraints
      context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
    });

    // force hovering process when creating first participant
    eventBus.on('create.start', HIGH_PRIORITY$b, function(event) {
      var context = event.context,
          shape = context.shape,
          rootElement = canvas.getRootElement(),
          rootElementGfx = canvas.getGraphics(rootElement);

      function ensureHoveringProcess(event) {
        event.element = rootElement;
        event.gfx = rootElementGfx;
      }

      if (is$1(shape, 'bpmn:Participant') && is$1(rootElement, 'bpmn:Process')) {
        eventBus.on('element.hover', HIGH_PRIORITY$b, ensureHoveringProcess);

        eventBus.once('create.cleanup', function() {
          eventBus.off('element.hover', ensureHoveringProcess);
        });
      }
    });

    // turn process into collaboration when creating first participant
    function getOrCreateCollaboration() {
      var rootElement = canvas.getRootElement();

      if (is$1(rootElement, 'bpmn:Collaboration')) {
        return rootElement;
      }

      return modeling.makeCollaboration();
    }

    // when creating mutliple elements through `elements.create` parent must be set to collaboration
    // and passed to `shape.create` as hint
    this.preExecute('elements.create', HIGH_PRIORITY$b, function(context) {
      var elements = context.elements,
          parent = context.parent,
          participant = findParticipant(elements),
          hints;

      if (participant && is$1(parent, 'bpmn:Process')) {
        context.parent = getOrCreateCollaboration();

        hints = context.hints = context.hints || {};

        hints.participant = participant;
        hints.process = parent;
        hints.processRef = getBusinessObject(participant).get('processRef');
      }
    }, true);

    // when creating single shape through `shape.create` parent must be set to collaboration
    // unless it was already set through `elements.create`
    this.preExecute('shape.create', function(context) {
      var parent = context.parent,
          shape = context.shape;

      if (is$1(shape, 'bpmn:Participant') && is$1(parent, 'bpmn:Process')) {
        context.parent = getOrCreateCollaboration();

        context.process = parent;
        context.processRef = getBusinessObject(shape).get('processRef');
      }
    }, true);

    // #execute necessary because #preExecute not called on CommandStack#redo
    this.execute('shape.create', function(context) {
      var hints = context.hints || {},
          process = context.process || hints.process,
          shape = context.shape,
          participant = hints.participant;

      // both shape.create and elements.create must be handled
      if (process && (!participant || shape === participant)) {

        // monkey-patch process ref
        getBusinessObject(shape).set('processRef', getBusinessObject(process));
      }
    }, true);

    this.revert('shape.create', function(context) {
      var hints = context.hints || {},
          process = context.process || hints.process,
          processRef = context.processRef || hints.processRef,
          shape = context.shape,
          participant = hints.participant;

      // both shape.create and elements.create must be handled
      if (process && (!participant || shape === participant)) {

        // monkey-patch process ref
        getBusinessObject(shape).set('processRef', processRef);
      }
    }, true);

    this.postExecute('shape.create', function(context) {
      var hints = context.hints || {},
          process = context.process || context.hints.process,
          shape = context.shape,
          participant = hints.participant;

      if (process) {
        var children = process.children.slice();

        // both shape.create and elements.create must be handled
        if (!participant) {
          modeling.moveElements(children, { x: 0, y: 0 }, shape);
        } else if (shape === participant) {
          modeling.moveElements(children, { x: 0, y: 0 }, participant);
        }
      }
    }, true);
  }

  CreateParticipantBehavior.$inject = [
    'canvas',
    'eventBus',
    'modeling'
  ];

  e(CreateParticipantBehavior, CommandInterceptor);

  // helpers //////////

  function getParticipantBounds(shape, childrenBBox) {
    childrenBBox = {
      width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
      height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
    };

    var width = Math.max(shape.width, childrenBBox.width),
        height = Math.max(shape.height, childrenBBox.height);

    return {
      x: -width / 2,
      y: -height / 2,
      width: width,
      height: height
    };
  }

  function getParticipantCreateConstraints(shape, childrenBBox) {
    childrenBBox = asTRBL(childrenBBox);

    return {
      bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
      left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
      top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
      right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
    };
  }

  function isConnection$9(element) {
    return !!element.waypoints;
  }

  function findParticipant(elements) {
    return find(elements, function(element) {
      return is$1(element, 'bpmn:Participant');
    });
  }

  var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';


  /**
   * This behavior makes sure we always set a fake
   * DataInputAssociation#targetRef as demanded by the BPMN 2.0
   * XSD schema.
   *
   * The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
   * which is created on the fly and cleaned up afterwards if not needed
   * anymore.
   *
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   */
  function DataInputAssociationBehavior(eventBus, bpmnFactory) {

    CommandInterceptor.call(this, eventBus);


    this.executed([
      'connection.create',
      'connection.delete',
      'connection.move',
      'connection.reconnect'
    ], ifDataInputAssociation(fixTargetRef));

    this.reverted([
      'connection.create',
      'connection.delete',
      'connection.move',
      'connection.reconnect'
    ], ifDataInputAssociation(fixTargetRef));


    function usesTargetRef(element, targetRef, removedConnection) {

      var inputAssociations = element.get('dataInputAssociations');

      return find(inputAssociations, function(association) {
        return association !== removedConnection &&
               association.targetRef === targetRef;
      });
    }

    function getTargetRef(element, create) {

      var properties = element.get('properties');

      var targetRefProp = find(properties, function(p) {
        return p.name === TARGET_REF_PLACEHOLDER_NAME;
      });

      if (!targetRefProp && create) {
        targetRefProp = bpmnFactory.create('bpmn:Property', {
          name: TARGET_REF_PLACEHOLDER_NAME
        });

        add(properties, targetRefProp);
      }

      return targetRefProp;
    }

    function cleanupTargetRef(element, connection) {

      var targetRefProp = getTargetRef(element);

      if (!targetRefProp) {
        return;
      }

      if (!usesTargetRef(element, targetRefProp, connection)) {
        remove(element.get('properties'), targetRefProp);
      }
    }

    /**
     * Make sure targetRef is set to a valid property or
     * `null` if the connection is detached.
     *
     * @param {Event} event
     */
    function fixTargetRef(event) {

      var context = event.context,
          connection = context.connection,
          connectionBo = connection.businessObject,
          target = connection.target,
          targetBo = target && target.businessObject,
          newTarget = context.newTarget,
          newTargetBo = newTarget && newTarget.businessObject,
          oldTarget = context.oldTarget || context.target,
          oldTargetBo = oldTarget && oldTarget.businessObject;

      var dataAssociation = connection.businessObject,
          targetRefProp;

      if (oldTargetBo && oldTargetBo !== targetBo) {
        cleanupTargetRef(oldTargetBo, connectionBo);
      }

      if (newTargetBo && newTargetBo !== targetBo) {
        cleanupTargetRef(newTargetBo, connectionBo);
      }

      if (targetBo) {
        targetRefProp = getTargetRef(targetBo, true);
        dataAssociation.targetRef = targetRefProp;
      } else {
        dataAssociation.targetRef = null;
      }
    }
  }

  DataInputAssociationBehavior.$inject = [
    'eventBus',
    'bpmnFactory'
  ];

  e(DataInputAssociationBehavior, CommandInterceptor);


  /**
   * Only call the given function when the event
   * touches a bpmn:DataInputAssociation.
   *
   * @param {Function} fn
   * @return {Function}
   */
  function ifDataInputAssociation(fn) {

    return function(event) {
      var context = event.context,
          connection = context.connection;

      if (is$1(connection, 'bpmn:DataInputAssociation')) {
        return fn(event);
      }
    };
  }

  function UpdateSemanticParentHandler(bpmnUpdater) {
    this._bpmnUpdater = bpmnUpdater;
  }

  UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ];


  UpdateSemanticParentHandler.prototype.execute = function(context) {
    var dataStoreBo = context.dataStoreBo,
        dataStoreDi = context.dataStoreDi,
        newSemanticParent = context.newSemanticParent,
        newDiParent = context.newDiParent;

    context.oldSemanticParent = dataStoreBo.$parent;
    context.oldDiParent = dataStoreDi.$parent;

    // update semantic parent
    this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);

    // update DI parent
    this._bpmnUpdater.updateDiParent(dataStoreDi, newDiParent);
  };

  UpdateSemanticParentHandler.prototype.revert = function(context) {
    var dataStoreBo = context.dataStoreBo,
        dataStoreDi = context.dataStoreDi,
        oldSemanticParent = context.oldSemanticParent,
        oldDiParent = context.oldDiParent;

    // update semantic parent
    this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);

    // update DI parent
    this._bpmnUpdater.updateDiParent(dataStoreDi, oldDiParent);
  };

  /**
   * BPMN specific data store behavior
   */
  function DataStoreBehavior(
      canvas, commandStack, elementRegistry,
      eventBus) {

    CommandInterceptor.call(this, eventBus);

    commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);

    function getFirstParticipantWithProcessRef() {
      return elementRegistry.filter(function(element) {
        return is$1(element, 'bpmn:Participant') && getBusinessObject(element).processRef;
      })[0];
    }

    function getDataStores(element) {
      return element.children.filter(function(child) {
        return is$1(child, 'bpmn:DataStoreReference') && !child.labelTarget;
      });
    }

    function updateDataStoreParent(dataStore, newDataStoreParent) {
      var dataStoreBo = dataStore.businessObject || dataStore;

      newDataStoreParent = newDataStoreParent || getFirstParticipantWithProcessRef();

      if (newDataStoreParent) {
        var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;

        commandStack.execute('dataStore.updateContainment', {
          dataStoreBo: dataStoreBo,
          dataStoreDi: getDi(dataStore),
          newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
          newDiParent: getDi(newDataStoreParent)
        });
      }
    }


    // disable auto-resize for data stores
    this.preExecute('shape.create', function(event) {

      var context = event.context,
          shape = context.shape;

      if (is$1(shape, 'bpmn:DataStoreReference') &&
          shape.type !== 'label') {

        if (!context.hints) {
          context.hints = {};
        }

        // prevent auto resizing
        context.hints.autoResize = false;
      }
    });


    // disable auto-resize for data stores
    this.preExecute('elements.move', function(event) {
      var context = event.context,
          shapes = context.shapes;

      var dataStoreReferences = shapes.filter(function(shape) {
        return is$1(shape, 'bpmn:DataStoreReference');
      });

      if (dataStoreReferences.length) {
        if (!context.hints) {
          context.hints = {};
        }

        // prevent auto resizing for data store references
        context.hints.autoResize = shapes.filter(function(shape) {
          return !is$1(shape, 'bpmn:DataStoreReference');
        });
      }
    });


    // update parent on data store created
    this.postExecute('shape.create', function(event) {
      var context = event.context,
          shape = context.shape,
          parent = shape.parent;


      if (is$1(shape, 'bpmn:DataStoreReference') &&
          shape.type !== 'label' &&
          is$1(parent, 'bpmn:Collaboration')) {

        updateDataStoreParent(shape);
      }
    });


    // update parent on data store moved
    this.postExecute('shape.move', function(event) {
      var context = event.context,
          shape = context.shape,
          oldParent = context.oldParent,
          parent = shape.parent;

      if (is$1(oldParent, 'bpmn:Collaboration')) {

        // do nothing if not necessary
        return;
      }

      if (is$1(shape, 'bpmn:DataStoreReference') &&
          shape.type !== 'label' &&
          is$1(parent, 'bpmn:Collaboration')) {

        var participant = is$1(oldParent, 'bpmn:Participant') ?
          oldParent :
          getAncestor(oldParent, 'bpmn:Participant');

        updateDataStoreParent(shape, participant);
      }
    });


    // update data store parents on participant or subprocess deleted
    this.postExecute('shape.delete', function(event) {
      var context = event.context,
          shape = context.shape,
          rootElement = canvas.getRootElement();

      if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
          && is$1(rootElement, 'bpmn:Collaboration')) {
        getDataStores(rootElement)
          .filter(function(dataStore) {
            return isDescendant(dataStore, shape);
          })
          .forEach(function(dataStore) {
            updateDataStoreParent(dataStore);
          });
      }
    });

    // update data store parents on collaboration -> process
    this.postExecute('canvas.updateRoot', function(event) {
      var context = event.context,
          oldRoot = context.oldRoot,
          newRoot = context.newRoot;

      var dataStores = getDataStores(oldRoot);

      dataStores.forEach(function(dataStore) {

        if (is$1(newRoot, 'bpmn:Process')) {
          updateDataStoreParent(dataStore, newRoot);
        }

      });
    });
  }

  DataStoreBehavior.$inject = [
    'canvas',
    'commandStack',
    'elementRegistry',
    'eventBus',
  ];

  e(DataStoreBehavior, CommandInterceptor);


  // helpers //////////

  function isDescendant(descendant, ancestor) {
    var descendantBo = descendant.businessObject || descendant,
        ancestorBo = ancestor.businessObject || ancestor;

    while (descendantBo.$parent) {
      if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
        return true;
      }

      descendantBo = descendantBo.$parent;
    }

    return false;
  }

  function getAncestor(element, type) {

    while (element.parent) {
      if (is$1(element.parent, type)) {
        return element.parent;
      }

      element = element.parent;
    }
  }

  var LOW_PRIORITY$c = 500;


  /**
   * BPMN specific delete lane behavior
   */
  function DeleteLaneBehavior(eventBus, modeling, spaceTool) {

    CommandInterceptor.call(this, eventBus);


    function compensateLaneDelete(shape, oldParent) {

      var siblings = getChildLanes(oldParent);

      var topAffected = [];
      var bottomAffected = [];

      eachElement(siblings, function(element) {

        if (element.y > shape.y) {
          bottomAffected.push(element);
        } else {
          topAffected.push(element);
        }

        return element.children;
      });

      if (!siblings.length) {
        return;
      }

      var offset;

      if (bottomAffected.length && topAffected.length) {
        offset = shape.height / 2;
      } else {
        offset = shape.height;
      }

      var topAdjustments,
          bottomAdjustments;

      if (topAffected.length) {
        topAdjustments = spaceTool.calculateAdjustments(
          topAffected, 'y', offset, shape.y - 10);

        spaceTool.makeSpace(
          topAdjustments.movingShapes,
          topAdjustments.resizingShapes,
          { x: 0, y: offset }, 's');
      }

      if (bottomAffected.length) {
        bottomAdjustments = spaceTool.calculateAdjustments(
          bottomAffected, 'y', -offset, shape.y + shape.height + 10);

        spaceTool.makeSpace(
          bottomAdjustments.movingShapes,
          bottomAdjustments.resizingShapes,
          { x: 0, y: -offset }, 'n');
      }
    }


    /**
     * Adjust sizes of other lanes after lane deletion
     */
    this.postExecuted('shape.delete', LOW_PRIORITY$c, function(event) {

      var context = event.context,
          hints = context.hints,
          shape = context.shape,
          oldParent = context.oldParent;

      // only compensate lane deletes
      if (!is$1(shape, 'bpmn:Lane')) {
        return;
      }

      // compensate root deletes only
      if (hints && hints.nested) {
        return;
      }

      compensateLaneDelete(shape, oldParent);
    });
  }

  DeleteLaneBehavior.$inject = [
    'eventBus',
    'modeling',
    'spaceTool'
  ];

  e(DeleteLaneBehavior, CommandInterceptor);

  var LOW_PRIORITY$b = 500;


  /**
   * Replace boundary event with intermediate event when creating or moving results in detached event.
   */
  function DetachEventBehavior(bpmnReplace, injector) {
    injector.invoke(CommandInterceptor, this);

    this._bpmnReplace = bpmnReplace;

    var self = this;

    this.postExecuted('elements.create', LOW_PRIORITY$b, function(context) {
      var elements = context.elements;

      elements.filter(function(shape) {
        var host = shape.host;

        return shouldReplace(shape, host);
      }).map(function(shape) {
        return elements.indexOf(shape);
      }).forEach(function(index) {
        context.elements[ index ] = self.replaceShape(elements[ index ]);
      });
    }, true);

    this.preExecute('elements.move', LOW_PRIORITY$b, function(context) {
      var shapes = context.shapes,
          newHost = context.newHost;

      shapes.forEach(function(shape, index) {
        var host = shape.host;

        if (shouldReplace(shape, includes$6(shapes, host) ? host : newHost)) {
          shapes[ index ] = self.replaceShape(shape);
        }
      });
    }, true);
  }

  DetachEventBehavior.$inject = [
    'bpmnReplace',
    'injector'
  ];

  e(DetachEventBehavior, CommandInterceptor);

  DetachEventBehavior.prototype.replaceShape = function(shape) {
    var eventDefinition = getEventDefinition(shape),
        intermediateEvent;

    if (eventDefinition) {
      intermediateEvent = {
        type: 'bpmn:IntermediateCatchEvent',
        eventDefinitionType: eventDefinition.$type
      };
    } else {
      intermediateEvent = {
        type: 'bpmn:IntermediateThrowEvent'
      };
    }

    return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
  };


  // helpers //////////

  function getEventDefinition(element) {
    var businessObject = getBusinessObject(element),
        eventDefinitions = businessObject.eventDefinitions;

    return eventDefinitions && eventDefinitions[0];
  }

  function shouldReplace(shape, host) {
    return !isLabel$6(shape) && is$1(shape, 'bpmn:BoundaryEvent') && !host;
  }

  function includes$6(array, item) {
    return array.indexOf(item) !== -1;
  }

  function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Reconnect start / end of a connection after
     * dropping an element on a flow.
     */

    function insertShape(shape, targetFlow, positionOrBounds) {
      var waypoints = targetFlow.waypoints,
          waypointsBefore,
          waypointsAfter,
          dockingPoint,
          source,
          target,
          incomingConnection,
          outgoingConnection,
          oldOutgoing = shape.outgoing.slice(),
          oldIncoming = shape.incoming.slice();

      var mid;

      if (isNumber(positionOrBounds.width)) {
        mid = getMid(positionOrBounds);
      } else {
        mid = positionOrBounds;
      }

      var intersection = getApproxIntersection(waypoints, mid);

      if (intersection) {
        waypointsBefore = waypoints.slice(0, intersection.index);
        waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));

        // due to inaccuracy intersection might have been found
        if (!waypointsBefore.length || !waypointsAfter.length) {
          return;
        }

        dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid;

        // if last waypointBefore is inside shape's bounds, ignore docking point
        if (waypointsBefore.length === 1 || !isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length - 1])) {
          waypointsBefore.push(copy(dockingPoint));
        }

        // if first waypointAfter is inside shape's bounds, ignore docking point
        if (waypointsAfter.length === 1 || !isPointInsideBBox(shape, waypointsAfter[0])) {
          waypointsAfter.unshift(copy(dockingPoint));
        }
      }

      source = targetFlow.source;
      target = targetFlow.target;

      if (bpmnRules.canConnect(source, shape, targetFlow)) {

        // reconnect source -> inserted shape
        modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid);

        incomingConnection = targetFlow;
      }

      if (bpmnRules.canConnect(shape, target, targetFlow)) {

        if (!incomingConnection) {

          // reconnect inserted shape -> end
          modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid);

          outgoingConnection = targetFlow;
        } else {
          outgoingConnection = modeling.connect(
            shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
          );
        }
      }

      var duplicateConnections = [].concat(

        incomingConnection && filter(oldIncoming, function(connection) {
          return connection.source === incomingConnection.source;
        }) || [],

        outgoingConnection && filter(oldOutgoing, function(connection) {
          return connection.target === outgoingConnection.target;
        }) || []
      );

      if (duplicateConnections.length) {
        modeling.removeElements(duplicateConnections);
      }
    }

    this.preExecute('elements.move', function(context) {

      var newParent = context.newParent,
          shapes = context.shapes,
          delta = context.delta,
          shape = shapes[0];

      if (!shape || !newParent) {
        return;
      }

      // if the new parent is a connection,
      // change it to the new parent's parent
      if (newParent && newParent.waypoints) {
        context.newParent = newParent = newParent.parent;
      }

      var shapeMid = getMid(shape);
      var newShapeMid = {
        x: shapeMid.x + delta.x,
        y: shapeMid.y + delta.y
      };

      // find a connection which intersects with the
      // element's mid point
      var connection = find(newParent.children, function(element) {
        var canInsert = bpmnRules.canInsert(shapes, element);

        return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
      });

      if (connection) {
        context.targetFlow = connection;
        context.position = newShapeMid;
      }

    }, true);

    this.postExecuted('elements.move', function(context) {

      var shapes = context.shapes,
          targetFlow = context.targetFlow,
          position = context.position;

      if (targetFlow) {
        insertShape(shapes[0], targetFlow, position);
      }

    }, true);

    this.preExecute('shape.create', function(context) {

      var parent = context.parent,
          shape = context.shape;

      if (bpmnRules.canInsert(shape, parent)) {
        context.targetFlow = parent;
        context.parent = parent.parent;
      }
    }, true);

    this.postExecuted('shape.create', function(context) {

      var shape = context.shape,
          targetFlow = context.targetFlow,
          positionOrBounds = context.position;

      if (targetFlow) {
        insertShape(shape, targetFlow, positionOrBounds);
      }
    }, true);
  }

  e(DropOnFlowBehavior, CommandInterceptor);

  DropOnFlowBehavior.$inject = [
    'eventBus',
    'bpmnRules',
    'modeling'
  ];


  // helpers /////////////////////

  function isPointInsideBBox(bbox, point) {
    var x = point.x,
        y = point.y;

    return x >= bbox.x &&
      x <= bbox.x + bbox.width &&
      y >= bbox.y &&
      y <= bbox.y + bbox.height;
  }

  function copy(obj) {
    return assign({}, obj);
  }

  function EventBasedGatewayBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Remove existing sequence flows of event-based target before connecting
     * from event-based gateway.
     */
    this.preExecuted('connection.create', function(event) {

      var context = event.context,
          source = context.source,
          target = context.target,
          existingIncomingConnections = target.incoming.slice();

      if (context.hints && context.hints.createElementsBehavior === false) {
        return;
      }

      if (
        is$1(source, 'bpmn:EventBasedGateway') &&
        target.incoming.length
      ) {

        existingIncomingConnections.filter(isSequenceFlow)
          .forEach(function(sequenceFlow) {
            modeling.removeConnection(sequenceFlow);
          });
      }
    });

    /**
     *  After replacing shape with event-based gateway, remove incoming sequence
     *  flows of event-based targets which do not belong to event-based gateway
     *  source.
     */
    this.preExecuted('shape.replace', function(event) {

      var newShape = event.context.newShape,
          newShapeTargets,
          newShapeTargetsIncomingSequenceFlows;

      if (!is$1(newShape, 'bpmn:EventBasedGateway')) {
        return;
      }

      newShapeTargets = newShape.outgoing.filter(isSequenceFlow)
        .map(function(sequenceFlow) {
          return sequenceFlow.target;
        });

      newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) {
        var incomingSequenceFlows = target.incoming.filter(isSequenceFlow);

        return sequenceFlows.concat(incomingSequenceFlows);
      }, []);

      newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) {
        if (sequenceFlow.source !== newShape) {
          modeling.removeConnection(sequenceFlow);
        }
      });
    });
  }

  EventBasedGatewayBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  e(EventBasedGatewayBehavior, CommandInterceptor);



  // helpers //////////////////////

  function isSequenceFlow(connection) {
    return is$1(connection, 'bpmn:SequenceFlow');
  }

  var HIGH_PRIORITY$a = 1500;
  var HIGHEST_PRIORITY = 2000;


  /**
   * Correct hover targets in certain situations to improve diagram interaction.
   *
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function FixHoverBehavior(elementRegistry, eventBus, canvas) {

    eventBus.on([
      'create.hover',
      'create.move',
      'create.out',
      'create.end',
      'shape.move.hover',
      'shape.move.move',
      'shape.move.out',
      'shape.move.end'
    ], HIGH_PRIORITY$a, function(event) {
      var context = event.context,
          shape = context.shape || event.shape,
          hover = event.hover;

      // ensure elements are not dropped onto a bpmn:Lane but onto
      // the underlying bpmn:Participant
      if (is$1(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
        event.hover = getLanesRoot(hover);
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }

      var rootElement = canvas.getRootElement();

      // ensure bpmn:Group and label elements are dropped
      // always onto the root
      if (hover !== rootElement && (shape.labelTarget || is$1(shape, 'bpmn:Group'))) {
        event.hover = rootElement;
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }
    });

    eventBus.on([
      'connect.hover',
      'connect.out',
      'connect.end',
      'connect.cleanup',
      'global-connect.hover',
      'global-connect.out',
      'global-connect.end',
      'global-connect.cleanup'
    ], HIGH_PRIORITY$a, function(event) {
      var hover = event.hover;

      // ensure connections start/end on bpmn:Participant,
      // not the underlying bpmn:Lane
      if (is$1(hover, 'bpmn:Lane')) {
        event.hover = getLanesRoot(hover) || hover;
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }
    });


    eventBus.on([
      'bendpoint.move.hover'
    ], HIGH_PRIORITY$a, function(event) {
      var context = event.context,
          hover = event.hover,
          type = context.type;

      // ensure reconnect start/end on bpmn:Participant,
      // not the underlying bpmn:Lane
      if (is$1(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
        event.hover = getLanesRoot(hover) || hover;
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }
    });


    eventBus.on([
      'connect.start'
    ], HIGH_PRIORITY$a, function(event) {
      var context = event.context,
          start = context.start;

      // ensure connect start on bpmn:Participant,
      // not the underlying bpmn:Lane
      if (is$1(start, 'bpmn:Lane')) {
        context.start = getLanesRoot(start) || start;
      }
    });


    // allow movement of participants from lanes
    eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
      var shape = event.shape;

      if (is$1(shape, 'bpmn:Lane')) {
        event.shape = getLanesRoot(shape) || shape;
      }
    });

  }

  FixHoverBehavior.$inject = [
    'elementRegistry',
    'eventBus',
    'canvas'
  ];

  /**
   * Creates a new bpmn:CategoryValue inside a new bpmn:Category
   *
   * @param {BpmnFactory} bpmnFactory
   *
   * @return {ModdleElement} categoryValue.
   */
  function createCategory(bpmnFactory) {
    return bpmnFactory.create('bpmn:Category');
  }

  /**
   * Creates a new bpmn:CategoryValue inside a new bpmn:Category
   *
   * @param {BpmnFactory} bpmnFactory
   *
   * @return {ModdleElement} categoryValue.
   */
  function createCategoryValue(bpmnFactory) {
    return bpmnFactory.create('bpmn:CategoryValue');
  }

  /**
   * Adds category value to definitions
   *
   * @param {ModdleElement} categoryValue
   * @param {ModdleElement} category
   * @param {ModdleElement} definitions
   *
   * @return {ModdleElement} categoryValue
   */
  function linkCategoryValue(categoryValue, category, definitions) {
    add(category.get('categoryValue'), categoryValue);
    categoryValue.$parent = category;

    add(definitions.get('rootElements'), category);
    category.$parent = definitions;

    return categoryValue;
  }

  /**
   * Unlink category value from parent
   *
   * @param {ModdleElement} categoryValue
   *
   * @return {ModdleElement} categoryValue
   */
  function unlinkCategoryValue(categoryValue) {
    var category = categoryValue.$parent;

    if (category) {
      remove(category.get('categoryValue'), categoryValue);
      categoryValue.$parent = null;
    }

    return categoryValue;
  }

  /**
   * Unlink category from parent
   *
   * @param {ModdleElement} category
   * @param {ModdleElement} definitions
   *
   * @return {ModdleElement} categoryValue
   */
  function unlinkCategory(category) {
    var definitions = category.$parent;

    if (definitions) {
      remove(definitions.get('rootElements'), category);
      category.$parent = null;
    }

    return category;
  }

  var LOWER_PRIORITY = 770;


  /**
   * BPMN specific Group behavior
   */
  function GroupBehavior(
      bpmnFactory,
      bpmnjs,
      elementRegistry,
      eventBus,
      injector,
      moddleCopy
  ) {
    injector.invoke(CommandInterceptor, this);

    /**
     * Returns all group element in the current registry
     *
     * @return {Array<djs.model.shape>} a list of group shapes
     */
    function getGroupElements() {
      return elementRegistry.filter(function(e) {
        return is$1(e, 'bpmn:Group');
      });
    }

    /**
     * Returns true if given category is referenced in one of the given elements
     *
     * @param { djs.model.Element[] } elements
     * @param { ModdleElement } category
     *
     * @return { boolean }
     */
    function isReferencedCategory(elements, category) {
      return elements.some(function(element) {
        var businessObject = getBusinessObject(element);

        var _category = businessObject.categoryValueRef && businessObject.categoryValueRef.$parent;

        return _category === category;
      });
    }

    /**
     * Returns true if given categoryValue is referenced in one of the given elements
     *
     * @param { djs.model.Element[] } elements
     * @param { ModdleElement } categoryValue
     *
     * @return { boolean }
     */
    function isReferencedCategoryValue(elements, categoryValue) {
      return elements.some(function(element) {
        var businessObject = getBusinessObject(element);

        return businessObject.categoryValueRef === categoryValue;
      });
    }

    /**
     * Remove category value unless it is still referenced
     *
     * @param {ModdleElement} categoryValue
     * @param {ModdleElement} category
     * @param {ModdleElement} businessObject
     */
    function removeCategoryValue(categoryValue, category, businessObject) {

      var groups = getGroupElements().filter(function(element) {
        return element.businessObject !== businessObject;
      });

      if (category && !isReferencedCategory(groups, category)) {
        unlinkCategory(category);
      }

      if (categoryValue && !isReferencedCategoryValue(groups, categoryValue)) {
        unlinkCategoryValue(categoryValue);
      }
    }

    /**
     * Add category value
     *
     * @param {ModdleElement} categoryValue
     * @param {ModdleElement} category
     */
    function addCategoryValue(categoryValue, category) {
      return linkCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
    }

    function setCategoryValue(element, context) {
      var businessObject = getBusinessObject(element),
          categoryValue = businessObject.categoryValueRef;

      if (!categoryValue) {
        categoryValue =
        businessObject.categoryValueRef =
        context.categoryValue = (
          context.categoryValue || createCategoryValue(bpmnFactory)
        );
      }

      var category = categoryValue.$parent;

      if (!category) {
        category =
        categoryValue.$parent =
        context.category = (
          context.category || createCategory(bpmnFactory)
        );
      }

      addCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
    }

    function unsetCategoryValue(element, context) {
      var category = context.category,
          categoryValue = context.categoryValue,
          businessObject = getBusinessObject(element);

      if (categoryValue) {
        businessObject.categoryValueRef = null;

        removeCategoryValue(categoryValue, category, businessObject);
      } else {
        removeCategoryValue(null, businessObject.categoryValueRef.$parent, businessObject);
      }
    }


    // ensure category + value exist before label editing

    this.execute('label.create', function(event) {
      var context = event.context,
          labelTarget = context.labelTarget;

      if (!is$1(labelTarget, 'bpmn:Group')) {
        return;
      }

      setCategoryValue(labelTarget, context);
    });

    this.revert('label.create', function(event) {
      var context = event.context,
          labelTarget = context.labelTarget;

      if (!is$1(labelTarget, 'bpmn:Group')) {
        return;
      }

      unsetCategoryValue(labelTarget, context);
    });


    // remove referenced category + value when group was deleted

    this.execute('shape.delete', function(event) {

      var context = event.context,
          shape = context.shape,
          businessObject = getBusinessObject(shape);

      if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
        return;
      }

      var categoryValue = context.categoryValue = businessObject.categoryValueRef,
          category;

      if (categoryValue) {
        category = context.category = categoryValue.$parent;

        removeCategoryValue(categoryValue, category, businessObject);

        businessObject.categoryValueRef = null;
      }
    });

    this.reverted('shape.delete', function(event) {

      var context = event.context,
          shape = context.shape;

      if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
        return;
      }

      var category = context.category,
          categoryValue = context.categoryValue,
          businessObject = getBusinessObject(shape);

      if (categoryValue) {
        businessObject.categoryValueRef = categoryValue;

        addCategoryValue(categoryValue, category);
      }
    });


    // create new category + value when group was created

    this.execute('shape.create', function(event) {
      var context = event.context,
          shape = context.shape;

      if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
        return;
      }

      if (getBusinessObject(shape).categoryValueRef) {
        setCategoryValue(shape, context);
      }
    });

    this.reverted('shape.create', function(event) {

      var context = event.context,
          shape = context.shape;

      if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
        return;
      }

      if (getBusinessObject(shape).categoryValueRef) {
        unsetCategoryValue(shape, context);
      }
    });


    // copy + paste categoryValueRef with group

    function copy(bo, clone) {
      var targetBo = bpmnFactory.create(bo.$type);

      return moddleCopy.copyElement(bo, targetBo, null, clone);
    }

    eventBus.on('copyPaste.copyElement', LOWER_PRIORITY, function(context) {
      var descriptor = context.descriptor,
          element = context.element;

      if (!is$1(element, 'bpmn:Group') || element.labelTarget) {
        return;
      }

      var groupBo = getBusinessObject(element);

      if (groupBo.categoryValueRef) {

        var categoryValue = groupBo.categoryValueRef;

        descriptor.categoryValue = copy(categoryValue, true);

        if (categoryValue.$parent) {
          descriptor.category = copy(categoryValue.$parent, true);
        }
      }
    });

    eventBus.on('copyPaste.pasteElement', LOWER_PRIORITY, function(context) {
      var descriptor = context.descriptor,
          businessObject = descriptor.businessObject,
          categoryValue = descriptor.categoryValue,
          category = descriptor.category;

      if (categoryValue) {
        categoryValue = businessObject.categoryValueRef = copy(categoryValue);
      }

      if (category) {
        categoryValue.$parent = copy(category);
      }

      delete descriptor.category;
      delete descriptor.categoryValue;
    });

  }

  GroupBehavior.$inject = [
    'bpmnFactory',
    'bpmnjs',
    'elementRegistry',
    'eventBus',
    'injector',
    'moddleCopy'
  ];

  e(GroupBehavior, CommandInterceptor);

  /**
   * Returns the intersection between two line segments a and b.
   *
   * @param {Point} l1s
   * @param {Point} l1e
   * @param {Point} l2s
   * @param {Point} l2e
   *
   * @return {Point}
   */
  function lineIntersect(l1s, l1e, l2s, l2e) {

    // if the lines intersect, the result contains the x and y of the
    // intersection (treating the lines as infinite) and booleans for
    // whether line segment 1 or line segment 2 contain the point
    var denominator, a, b, c, numerator;

    denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));

    if (denominator == 0) {
      return null;
    }

    a = l1s.y - l2s.y;
    b = l1s.x - l2s.x;
    numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);

    c = numerator / denominator;

    // if we cast these lines infinitely in
    // both directions, they intersect here
    return {
      x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
      y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
    };
  }

  /**
   * Fix broken dockings after DI imports.
   *
   * @param {EventBus} eventBus
   */
  function ImportDockingFix(eventBus) {

    function adjustDocking(startPoint, nextPoint, elementMid) {

      var elementTop = {
        x: elementMid.x,
        y: elementMid.y - 50
      };

      var elementLeft = {
        x: elementMid.x - 50,
        y: elementMid.y
      };

      var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
          horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);

      // original is horizontal or vertical center cross intersection
      var centerIntersect;

      if (verticalIntersect && horizontalIntersect) {
        if (getDistance$1(verticalIntersect, elementMid) > getDistance$1(horizontalIntersect, elementMid)) {
          centerIntersect = horizontalIntersect;
        } else {
          centerIntersect = verticalIntersect;
        }
      } else {
        centerIntersect = verticalIntersect || horizontalIntersect;
      }

      startPoint.original = centerIntersect;
    }

    function fixDockings(connection) {
      var waypoints = connection.waypoints;

      adjustDocking(
        waypoints[0],
        waypoints[1],
        getMid(connection.source)
      );

      adjustDocking(
        waypoints[waypoints.length - 1],
        waypoints[waypoints.length - 2],
        getMid(connection.target)
      );
    }

    eventBus.on('bpmnElement.added', function(e) {

      var element = e.element;

      if (element.waypoints) {
        fixDockings(element);
      }
    });
  }

  ImportDockingFix.$inject = [
    'eventBus'
  ];


  // helpers //////////////////////

  function getDistance$1(p1, p2) {
    return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
  }

  /**
   * A component that makes sure that each created or updated
   * Pool and Lane is assigned an isHorizontal property set to true.
   *
   * @param {EventBus} eventBus
   */
  function IsHorizontalFix(eventBus) {

    CommandInterceptor.call(this, eventBus);

    var elementTypesToUpdate = [
      'bpmn:Participant',
      'bpmn:Lane'
    ];

    this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
      var shape = event.context.shape,
          bo = getBusinessObject(shape),
          di = getDi(shape);

      if (isAny(bo, elementTypesToUpdate) && !di.get('isHorizontal')) {

        // set attribute directly to avoid modeling#updateProperty side effects
        di.set('isHorizontal', true);
      }
    });

  }

  IsHorizontalFix.$inject = [ 'eventBus' ];

  e(IsHorizontalFix, CommandInterceptor);

  var sqrt = Math.sqrt,
      min$1 = Math.min,
      max$3 = Math.max,
      abs$3 = Math.abs;

  /**
   * Calculate the square (power to two) of a number.
   *
   * @param {number} n
   *
   * @return {number}
   */
  function sq(n) {
    return Math.pow(n, 2);
  }

  /**
   * Get distance between two points.
   *
   * @param {Point} p1
   * @param {Point} p2
   *
   * @return {number}
   */
  function getDistance(p1, p2) {
    return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
  }

  /**
   * Return the attachment of the given point on the specified line.
   *
   * The attachment is either a bendpoint (attached to the given point)
   * or segment (attached to a location on a line segment) attachment:
   *
   * ```javascript
   * var pointAttachment = {
   *   type: 'bendpoint',
   *   bendpointIndex: 3,
   *   position: { x: 10, y: 10 } // the attach point on the line
   * };
   *
   * var segmentAttachment = {
   *   type: 'segment',
   *   segmentIndex: 2,
   *   relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
   *   position: { x: 10, y: 10 } // the attach point on the line
   * };
   * ```
   *
   * @param {Point} point
   * @param {Array<Point>} line
   *
   * @return {Object} attachment
   */
  function getAttachment(point, line) {

    var idx = 0,
        segmentStart,
        segmentEnd,
        segmentStartDistance,
        segmentEndDistance,
        attachmentPosition,
        minDistance,
        intersections,
        attachment,
        attachmentDistance,
        closestAttachmentDistance,
        closestAttachment;

    for (idx = 0; idx < line.length - 1; idx++) {

      segmentStart = line[idx];
      segmentEnd = line[idx + 1];

      if (pointsEqual(segmentStart, segmentEnd)) {
        intersections = [ segmentStart ];
      } else {
        segmentStartDistance = getDistance(point, segmentStart);
        segmentEndDistance = getDistance(point, segmentEnd);

        minDistance = min$1(segmentStartDistance, segmentEndDistance);

        intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
      }

      if (intersections.length < 1) {
        throw new Error('expected between [1, 2] circle -> line intersections');
      }

      // one intersection -> bendpoint attachment
      if (intersections.length === 1) {
        attachment = {
          type: 'bendpoint',
          position: intersections[0],
          segmentIndex: idx,
          bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
        };
      }

      // two intersections -> segment attachment
      if (intersections.length === 2) {

        attachmentPosition = mid$1(intersections[0], intersections[1]);

        attachment = {
          type: 'segment',
          position: attachmentPosition,
          segmentIndex: idx,
          relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
        };
      }

      attachmentDistance = getDistance(attachment.position, point);

      if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
        closestAttachment = attachment;
        closestAttachmentDistance = attachmentDistance;
      }
    }

    return closestAttachment;
  }

  /**
   * Gets the intersection between a circle and a line segment.
   *
   * @param {Point} s1 segment start
   * @param {Point} s2 segment end
   * @param {Point} cc circle center
   * @param {number} cr circle radius
   *
   * @return {Array<Point>} intersections
   */
  function getCircleSegmentIntersections(s1, s2, cc, cr) {

    var baX = s2.x - s1.x;
    var baY = s2.y - s1.y;
    var caX = cc.x - s1.x;
    var caY = cc.y - s1.y;

    var a = baX * baX + baY * baY;
    var bBy2 = baX * caX + baY * caY;
    var c = caX * caX + caY * caY - cr * cr;

    var pBy2 = bBy2 / a;
    var q = c / a;

    var disc = pBy2 * pBy2 - q;

    // check against negative value to work around
    // negative, very close to zero results (-4e-15)
    // being produced in some environments
    if (disc < 0 && disc > -0.000001) {
      disc = 0;
    }

    if (disc < 0) {
      return [];
    }

    // if disc == 0 ... dealt with later
    var tmpSqrt = sqrt(disc);
    var abScalingFactor1 = -pBy2 + tmpSqrt;
    var abScalingFactor2 = -pBy2 - tmpSqrt;

    var i1 = {
      x: s1.x - baX * abScalingFactor1,
      y: s1.y - baY * abScalingFactor1
    };

    if (disc === 0) { // abScalingFactor1 == abScalingFactor2
      return [ i1 ];
    }

    var i2 = {
      x: s1.x - baX * abScalingFactor2,
      y: s1.y - baY * abScalingFactor2
    };

    // return only points on line segment
    return [ i1, i2 ].filter(function(p) {
      return isPointInSegment(p, s1, s2);
    });
  }


  function isPointInSegment(p, segmentStart, segmentEnd) {
    return (
      fenced(p.x, segmentStart.x, segmentEnd.x) &&
      fenced(p.y, segmentStart.y, segmentEnd.y)
    );
  }

  function fenced(n, rangeStart, rangeEnd) {

    // use matching threshold to work around
    // precision errors in intersection computation

    return (
      n >= min$1(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
      n <= max$3(rangeStart, rangeEnd) + EQUAL_THRESHOLD
    );
  }

  /**
   * Calculate mid of two points.
   *
   * @param {Point} p1
   * @param {Point} p2
   *
   * @return {Point}
   */
  function mid$1(p1, p2) {

    return {
      x: (p1.x + p2.x) / 2,
      y: (p1.y + p2.y) / 2
    };
  }

  var EQUAL_THRESHOLD = 0.1;

  function pointsEqual(p1, p2) {

    return (
      abs$3(p1.x - p2.x) <= EQUAL_THRESHOLD &&
      abs$3(p1.y - p2.y) <= EQUAL_THRESHOLD
    );
  }

  function findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {

    var index = attachment.segmentIndex;

    var offset = newWaypoints.length - oldWaypoints.length;

    // segmentMove happened
    if (hints.segmentMove) {

      var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
          newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;

      // if point was on moved segment return new segment index
      if (index === oldSegmentStartIndex) {
        return newSegmentStartIndex;
      }

      // point is after new segment index
      if (index >= newSegmentStartIndex) {
        return (index + offset < newSegmentStartIndex) ? newSegmentStartIndex : index + offset;
      }

      // if point is before new segment index
      return index;
    }

    // bendpointMove happened
    if (hints.bendpointMove) {

      var insert = hints.bendpointMove.insert,
          bendpointIndex = hints.bendpointMove.bendpointIndex,
          newIndex;

      // waypoints length didnt change
      if (offset === 0) {
        return index;
      }

      // point behind new/removed bendpoint
      if (index >= bendpointIndex) {
        newIndex = insert ? index + 1 : index - 1;
      }

      // point before new/removed bendpoint
      if (index < bendpointIndex) {

        newIndex = index;

        // decide point should take right or left segment
        if (insert && attachment.type !== 'bendpoint' && bendpointIndex - 1 === index) {

          var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);

          if (rel < attachment.relativeLocation) {
            newIndex++;
          }
        }
      }

      return newIndex;
    }

    // start/end changed
    if (offset === 0) {
      return index;
    }

    if (hints.connectionStart && index === 0) {
      return 0;
    }

    if (hints.connectionEnd && index === oldWaypoints.length - 2) {
      return newWaypoints.length - 2;
    }

    // if nothing fits, take the middle segment
    return Math.floor((newWaypoints.length - 2) / 2);
  }


  /**
   * Calculate the required adjustment (move delta) for the given point
   * after the connection waypoints got updated.
   *
   * @param {Point} position
   * @param {Array<Point>} newWaypoints
   * @param {Array<Point>} oldWaypoints
   * @param {Object} hints
   *
   * @return {Object} result
   * @return {Point} result.point
   * @return {Point} result.delta
   */
  function getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints) {

    var dx = 0,
        dy = 0;

    var oldPosition = {
      point: position,
      delta: { x: 0, y: 0 }
    };

    // get closest attachment
    var attachment = getAttachment(position, oldWaypoints),
        oldLabelLineIndex = attachment.segmentIndex,
        newLabelLineIndex = findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);


    // should never happen
    // TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
    if (newLabelLineIndex < 0 ||
        newLabelLineIndex > newWaypoints.length - 2 ||
        newLabelLineIndex === null) {
      return oldPosition;
    }

    var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
        newLabelLine = getLine(newWaypoints, newLabelLineIndex),
        oldFoot = attachment.position;

    var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
        angleDelta = getAngleDelta(oldLabelLine, newLabelLine);

    // special rule if label on bendpoint
    if (attachment.type === 'bendpoint') {

      var offset = newWaypoints.length - oldWaypoints.length,
          oldBendpointIndex = attachment.bendpointIndex,
          oldBendpoint = oldWaypoints[oldBendpointIndex];

      // bendpoint position hasn't changed, return same position
      if (newWaypoints.indexOf(oldBendpoint) !== -1) {
        return oldPosition;
      }

      // new bendpoint and old bendpoint have same index, then just return the offset
      if (offset === 0) {
        var newBendpoint = newWaypoints[oldBendpointIndex];

        dx = newBendpoint.x - attachment.position.x,
        dy = newBendpoint.y - attachment.position.y;

        return {
          delta: {
            x: dx,
            y: dy
          },
          point: {
            x: position.x + dx,
            y: position.y + dy
          }
        };
      }

      // if bendpoints get removed
      if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
        relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
      }
    }

    var newFoot = {
      x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
      y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
    };

    // the rotated vector to label
    var newLabelVector = rotateVector({
      x: position.x - oldFoot.x,
      y: position.y - oldFoot.y
    }, angleDelta);

    // the new relative position
    dx = newFoot.x + newLabelVector.x - position.x;
    dy = newFoot.y + newLabelVector.y - position.y;

    return {
      point: roundPoint(newFoot),
      delta: roundPoint({
        x: dx,
        y: dy
      })
    };
  }


  // HELPERS //////////////////////

  function relativePositionMidWaypoint(waypoints, idx) {

    var distanceSegment1 = getDistancePointPoint(waypoints[idx - 1], waypoints[idx]),
        distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx + 1]);

    var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);

    return relativePosition;
  }

  function getAngleDelta(l1, l2) {
    var a1 = getAngle(l1),
        a2 = getAngle(l2);
    return a2 - a1;
  }

  function getLine(waypoints, idx) {
    return [ waypoints[idx], waypoints[idx + 1] ];
  }

  function getRelativeFootPosition(line, foot) {

    var length = getDistancePointPoint(line[0], line[1]),
        lengthToFoot = getDistancePointPoint(line[0], foot);

    return length === 0 ? 0 : lengthToFoot / length;
  }

  /**
   * Calculate the required adjustment (move delta) for the given label
   * after the connection waypoints got updated.
   *
   * @param {djs.model.Label} label
   * @param {Array<Point>} newWaypoints
   * @param {Array<Point>} oldWaypoints
   * @param {Object} hints
   *
   * @return {Point} delta
   */
  function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
    var labelPosition = getLabelMid(label);

    return getAnchorPointAdjustment(labelPosition, newWaypoints, oldWaypoints, hints).delta;
  }


  // HELPERS //////////////////////

  function getLabelMid(label) {
    return {
      x: label.x + label.width / 2,
      y: label.y + label.height / 2
    };
  }

  /**
   * Calculates the absolute point relative to the new element's position
   *
   * @param {point} point [absolute]
   * @param {bounds} oldBounds
   * @param {bounds} newBounds
   *
   * @return {point} point [absolute]
   */
  function getNewAttachPoint(point, oldBounds, newBounds) {
    var oldCenter = center(oldBounds),
        newCenter = center(newBounds),
        oldDelta = delta(point, oldCenter);

    var newDelta = {
      x: oldDelta.x * (newBounds.width / oldBounds.width),
      y: oldDelta.y * (newBounds.height / oldBounds.height)
    };

    return roundPoint({
      x: newCenter.x + newDelta.x,
      y: newCenter.y + newDelta.y
    });
  }


  /**
   * Calculates the shape's delta relative to a new position
   * of a certain element's bounds
   *
   * @param {djs.model.Shape} point [absolute]
   * @param {bounds} oldBounds
   * @param {bounds} newBounds
   *
   * @return {delta} delta
   */
  function getNewAttachShapeDelta(shape, oldBounds, newBounds) {
    var shapeCenter = center(shape),
        oldCenter = center(oldBounds),
        newCenter = center(newBounds),
        shapeDelta = delta(shape, shapeCenter),
        oldCenterDelta = delta(shapeCenter, oldCenter),
        stickyPositionDelta = getStickyPositionDelta(shapeCenter, oldBounds, newBounds);

    if (stickyPositionDelta) {
      return stickyPositionDelta;
    }

    var newCenterDelta = {
      x: oldCenterDelta.x * (newBounds.width / oldBounds.width),
      y: oldCenterDelta.y * (newBounds.height / oldBounds.height)
    };

    var newShapeCenter = {
      x: newCenter.x + newCenterDelta.x,
      y: newCenter.y + newCenterDelta.y
    };

    return roundPoint({
      x: newShapeCenter.x + shapeDelta.x - shape.x,
      y: newShapeCenter.y + shapeDelta.y - shape.y
    });
  }

  function getStickyPositionDelta(oldShapeCenter, oldBounds, newBounds) {
    var oldTRBL = asTRBL(oldBounds),
        newTRBL = asTRBL(newBounds);

    if (isMoved(oldTRBL, newTRBL)) {
      return null;
    }

    var oldOrientation = getOrientation(oldBounds, oldShapeCenter),
        stickyPositionDelta,
        newShapeCenter,
        newOrientation;

    if (oldOrientation === 'top') {
      stickyPositionDelta = {
        x: 0,
        y: newTRBL.bottom - oldTRBL.bottom
      };
    } else if (oldOrientation === 'bottom') {
      stickyPositionDelta = {
        x: 0,
        y: newTRBL.top - oldTRBL.top
      };
    } else if (oldOrientation === 'right') {
      stickyPositionDelta = {
        x: newTRBL.left - oldTRBL.left,
        y: 0
      };
    } else if (oldOrientation === 'left') {
      stickyPositionDelta = {
        x: newTRBL.right - oldTRBL.right,
        y: 0
      };
    } else {

      // fallback to proportional movement for corner-placed attachments
      return null;
    }

    newShapeCenter = {
      x: oldShapeCenter.x + stickyPositionDelta.x,
      y: oldShapeCenter.y + stickyPositionDelta.y
    };

    newOrientation = getOrientation(newBounds, newShapeCenter);

    if (newOrientation !== oldOrientation) {

      // fallback to proportional movement if orientation would otherwise change
      return null;
    }

    return stickyPositionDelta;
  }

  function isMoved(oldTRBL, newTRBL) {
    return isHorizontallyMoved(oldTRBL, newTRBL) || isVerticallyMoved(oldTRBL, newTRBL);
  }

  function isHorizontallyMoved(oldTRBL, newTRBL) {
    return oldTRBL.right !== newTRBL.right && oldTRBL.left !== newTRBL.left;
  }

  function isVerticallyMoved(oldTRBL, newTRBL) {
    return oldTRBL.top !== newTRBL.top && oldTRBL.bottom !== newTRBL.bottom;
  }

  var DEFAULT_LABEL_DIMENSIONS = {
    width: 90,
    height: 20
  };

  var NAME_PROPERTY = 'name';
  var TEXT_PROPERTY = 'text';

  /**
   * A component that makes sure that external labels are added
   * together with respective elements and properly updated (DI wise)
   * during move.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {BpmnFactory} bpmnFactory
   * @param {TextRenderer} textRenderer
   */
  function LabelBehavior(
      eventBus, modeling, bpmnFactory,
      textRenderer) {

    CommandInterceptor.call(this, eventBus);

    // update label if name property was updated
    this.postExecute('element.updateProperties', function(e) {
      var context = e.context,
          element = context.element,
          properties = context.properties;

      if (NAME_PROPERTY in properties) {
        modeling.updateLabel(element, properties[NAME_PROPERTY]);
      }

      if (TEXT_PROPERTY in properties
          && is$1(element, 'bpmn:TextAnnotation')) {

        var newBounds = textRenderer.getTextAnnotationBounds(
          {
            x: element.x,
            y: element.y,
            width: element.width,
            height: element.height
          },
          properties[TEXT_PROPERTY] || ''
        );

        modeling.updateLabel(element, properties.text, newBounds);
      }
    });

    // create label shape after shape/connection was created
    this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
      var context = e.context,
          hints = context.hints || {};

      if (hints.createElementsBehavior === false) {
        return;
      }

      var element = context.shape || context.connection,
          businessObject = element.businessObject;

      if (isLabel$6(element) || !isLabelExternal(element)) {
        return;
      }

      // only create label if attribute available
      if (!getLabel(element)) {
        return;
      }

      var labelCenter = getExternalLabelMid(element);

      // we don't care about x and y
      var labelDimensions = textRenderer.getExternalLabelBounds(
        DEFAULT_LABEL_DIMENSIONS,
        getLabel(element)
      );

      modeling.createLabel(element, labelCenter, {
        id: businessObject.id + '_label',
        businessObject: businessObject,
        width: labelDimensions.width,
        height: labelDimensions.height
      });
    });

    // update label after label shape was deleted
    this.postExecute('shape.delete', function(event) {
      var context = event.context,
          labelTarget = context.labelTarget,
          hints = context.hints || {};

      // check if label
      if (labelTarget && hints.unsetLabel !== false) {
        modeling.updateLabel(labelTarget, null, null, { removeShape: false });
      }
    });

    // update di information on label creation
    this.postExecute([ 'label.create' ], function(event) {

      var context = event.context,
          element = context.shape,
          labelTarget = context.labelTarget,
          di;

      // we want to trigger on real labels only
      if (!labelTarget) {
        return;
      }

      // we want to trigger on BPMN elements only
      if (!is$1(labelTarget, 'bpmn:BaseElement')) {
        return;
      }

      di = getDi(labelTarget);

      if (!di.label) {
        di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
          bounds: bpmnFactory.create('dc:Bounds')
        });

        element.di = di;
      }

      assign(di.label.bounds, {
        x: element.x,
        y: element.y,
        width: element.width,
        height: element.height
      });
    });

    function getVisibleLabelAdjustment(event) {

      var context = event.context,
          connection = context.connection,
          label = connection.label,
          hints = assign({}, context.hints),
          newWaypoints = context.newWaypoints || connection.waypoints,
          oldWaypoints = context.oldWaypoints;


      if (typeof hints.startChanged === 'undefined') {
        hints.startChanged = !!hints.connectionStart;
      }

      if (typeof hints.endChanged === 'undefined') {
        hints.endChanged = !!hints.connectionEnd;
      }

      return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
    }

    this.postExecute([
      'connection.layout',
      'connection.updateWaypoints'
    ], function(event) {
      var context = event.context,
          hints = context.hints || {};

      if (hints.labelBehavior === false) {
        return;
      }

      var connection = context.connection,
          label = connection.label,
          labelAdjustment;

      // handle missing label as well as the case
      // that the label parent does not exist (yet),
      // because it is being pasted / created via multi element create
      //
      // Cf. https://github.com/bpmn-io/bpmn-js/pull/1227
      if (!label || !label.parent) {
        return;
      }

      labelAdjustment = getVisibleLabelAdjustment(event);

      modeling.moveShape(label, labelAdjustment);
    });


    // keep label position on shape replace
    this.postExecute([ 'shape.replace' ], function(event) {
      var context = event.context,
          newShape = context.newShape,
          oldShape = context.oldShape;

      var businessObject = getBusinessObject(newShape);

      if (businessObject
        && isLabelExternal(businessObject)
        && oldShape.label
        && newShape.label) {
        newShape.label.x = oldShape.label.x;
        newShape.label.y = oldShape.label.y;
      }
    });


    // move external label after resizing
    this.postExecute('shape.resize', function(event) {

      var context = event.context,
          shape = context.shape,
          newBounds = context.newBounds,
          oldBounds = context.oldBounds;

      if (hasExternalLabel(shape)) {

        var label = shape.label,
            labelMid = getMid(label),
            edges = asEdges(oldBounds);

        // get nearest border point to label as reference point
        var referencePoint = getReferencePoint(labelMid, edges);

        var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);

        modeling.moveShape(label, delta);

      }

    });

  }

  e(LabelBehavior, CommandInterceptor);

  LabelBehavior.$inject = [
    'eventBus',
    'modeling',
    'bpmnFactory',
    'textRenderer'
  ];

  // helpers //////////////////////

  /**
   * Calculates a reference point delta relative to a new position
   * of a certain element's bounds
   *
   * @param {Point} point
   * @param {Bounds} oldBounds
   * @param {Bounds} newBounds
   *
   * @return {Delta} delta
   */
  function getReferencePointDelta(referencePoint, oldBounds, newBounds) {

    var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);

    return roundPoint(delta(newReferencePoint, referencePoint));
  }

  /**
   * Generates the nearest point (reference point) for a given point
   * onto given set of lines
   *
   * @param {Array<Point, Point>} lines
   * @param {Point} point
   *
   * @param {Point}
   */
  function getReferencePoint(point, lines) {

    if (!lines.length) {
      return;
    }

    var nearestLine = getNearestLine(point, lines);

    return perpendicularFoot(point, nearestLine);
  }

  /**
   * Convert the given bounds to a lines array containing all edges
   *
   * @param {Bounds|Point} bounds
   *
   * @return Array<Point>
   */
  function asEdges(bounds) {
    return [
      [ // top
        {
          x: bounds.x,
          y: bounds.y
        },
        {
          x: bounds.x + (bounds.width || 0),
          y: bounds.y
        }
      ],
      [ // right
        {
          x: bounds.x + (bounds.width || 0),
          y: bounds.y
        },
        {
          x: bounds.x + (bounds.width || 0),
          y: bounds.y + (bounds.height || 0)
        }
      ],
      [ // bottom
        {
          x: bounds.x,
          y: bounds.y + (bounds.height || 0)
        },
        {
          x: bounds.x + (bounds.width || 0),
          y: bounds.y + (bounds.height || 0)
        }
      ],
      [ // left
        {
          x: bounds.x,
          y: bounds.y
        },
        {
          x: bounds.x,
          y: bounds.y + (bounds.height || 0)
        }
      ]
    ];
  }

  /**
   * Returns the nearest line for a given point by distance
   * @param {Point} point
   * @param Array<Point> lines
   *
   * @return Array<Point>
   */
  function getNearestLine(point, lines) {

    var distances = lines.map(function(l) {
      return {
        line: l,
        distance: getDistancePointLine(point, l)
      };
    });

    var sorted = sortBy(distances, 'distance');

    return sorted[0].line;
  }

  /**
   * Calculate the new point after the connection waypoints got updated.
   *
   * @param {djs.model.Label} label
   * @param {Array<Point>} newWaypoints
   * @param {Array<Point>} oldWaypoints
   * @param {Object} hints
   *
   * @return {Point} point
   */
  function getConnectionAdjustment(position, newWaypoints, oldWaypoints, hints) {
    return getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints).point;
  }

  /**
   * A component that makes sure that Associations connected to Connections
   * are updated together with the Connection.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function LayoutConnectionBehavior(
      eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    function getnewAnchorPoint(event, point) {

      var context = event.context,
          connection = context.connection,
          hints = assign({}, context.hints),
          newWaypoints = context.newWaypoints || connection.waypoints,
          oldWaypoints = context.oldWaypoints;


      if (typeof hints.startChanged === 'undefined') {
        hints.startChanged = !!hints.connectionStart;
      }

      if (typeof hints.endChanged === 'undefined') {
        hints.endChanged = !!hints.connectionEnd;
      }

      return getConnectionAdjustment(point, newWaypoints, oldWaypoints, hints);
    }

    this.postExecute([
      'connection.layout',
      'connection.updateWaypoints'
    ], function(event) {
      var context = event.context;

      var connection = context.connection,
          outgoing = connection.outgoing,
          incoming = connection.incoming;

      incoming.forEach(function(connection) {
        var endPoint = connection.waypoints[connection.waypoints.length - 1];
        var newEndpoint = getnewAnchorPoint(event, endPoint);

        var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);

        modeling.updateWaypoints(connection, newWaypoints);
      });

      outgoing.forEach(function(connection) {
        var startpoint = connection.waypoints[0];
        var newStartpoint = getnewAnchorPoint(event, startpoint);

        var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));

        modeling.updateWaypoints(connection, newWaypoints);
      });

    });


    this.postExecute([
      'connection.move'
    ], function(event) {
      var context = event.context;

      var connection = context.connection,
          outgoing = connection.outgoing,
          incoming = connection.incoming,
          delta = context.delta;

      incoming.forEach(function(connection) {
        var endPoint = connection.waypoints[connection.waypoints.length - 1];
        var newEndpoint = {
          x: endPoint.x + delta.x,
          y: endPoint.y + delta.y
        };

        var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);

        modeling.updateWaypoints(connection, newWaypoints);
      });

      outgoing.forEach(function(connection) {
        var startpoint = connection.waypoints[0];
        var newStartpoint = {
          x: startpoint.x + delta.x,
          y: startpoint.y + delta.y
        };

        var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));

        modeling.updateWaypoints(connection, newWaypoints);
      });

    });

  }

  e(LayoutConnectionBehavior, CommandInterceptor);

  LayoutConnectionBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  function getResizedSourceAnchor(connection, shape, oldBounds) {

    var waypoints = safeGetWaypoints(connection),
        waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
        oldAnchor = waypoints[0];

    // new anchor is the last waypoint enclosed be resized source
    if (waypointsInsideNewBounds.length) {
      return waypointsInsideNewBounds[ waypointsInsideNewBounds.length - 1 ];
    }

    return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
  }


  function getResizedTargetAnchor(connection, shape, oldBounds) {

    var waypoints = safeGetWaypoints(connection),
        waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
        oldAnchor = waypoints[waypoints.length - 1];

    // new anchor is the first waypoint enclosed be resized target
    if (waypointsInsideNewBounds.length) {
      return waypointsInsideNewBounds[ 0 ];
    }

    return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
  }


  function getMovedSourceAnchor(connection, source, moveDelta) {

    var waypoints = safeGetWaypoints(connection),
        oldBounds = subtract(source, moveDelta),
        oldAnchor = waypoints[ 0 ];

    return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, source);
  }


  function getMovedTargetAnchor(connection, target, moveDelta) {

    var waypoints = safeGetWaypoints(connection),
        oldBounds = subtract(target, moveDelta),
        oldAnchor = waypoints[ waypoints.length - 1 ];

    return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, target);
  }


  // helpers //////////////////////

  function subtract(bounds, delta) {
    return {
      x: bounds.x - delta.x,
      y: bounds.y - delta.y,
      width: bounds.width,
      height: bounds.height
    };
  }


  /**
   * Return waypoints of given connection; throw if non exists (should not happen!!).
   *
   * @param {Connection} connection
   *
   * @return {Array<Point>}
   */
  function safeGetWaypoints(connection) {

    var waypoints = connection.waypoints;

    if (!waypoints.length) {
      throw new Error('connection#' + connection.id + ': no waypoints');
    }

    return waypoints;
  }

  function getWaypointsInsideBounds(waypoints, bounds) {
    var originalWaypoints = map(waypoints, getOriginal);

    return filter(originalWaypoints, function(waypoint) {
      return isInsideBounds(waypoint, bounds);
    });
  }

  /**
   * Checks if point is inside bounds, incl. edges.
   *
   * @param {Point} point
   * @param {Bounds} bounds
   */
  function isInsideBounds(point, bounds) {
    return getOrientation(bounds, point, 1) === 'intersect';
  }

  function getOriginal(point) {
    return point.original || point;
  }

  /**
   * BPMN-specific message flow behavior.
   */
  function MessageFlowBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    this.postExecute('shape.replace', function(context) {
      var oldShape = context.oldShape,
          newShape = context.newShape;

      if (!isParticipantCollapse(oldShape, newShape)) {
        return;
      }

      var messageFlows = getMessageFlows(oldShape);

      messageFlows.incoming.forEach(function(incoming) {
        var anchor = getResizedTargetAnchor(incoming, newShape, oldShape);

        modeling.reconnectEnd(incoming, newShape, anchor);
      });

      messageFlows.outgoing.forEach(function(outgoing) {
        var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape);

        modeling.reconnectStart(outgoing, newShape, anchor);
      });
    }, true);

  }

  MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ];

  e(MessageFlowBehavior, CommandInterceptor);

  // helpers //////////

  function isParticipantCollapse(oldShape, newShape) {
    return is$1(oldShape, 'bpmn:Participant')
      && isExpanded(oldShape)
      && is$1(newShape, 'bpmn:Participant')
      && !isExpanded(newShape);
  }

  function getMessageFlows(parent) {
    var elements = selfAndAllChildren([ parent ], false);

    var incoming = [],
        outgoing = [];

    elements.forEach(function(element) {
      if (element === parent) {
        return;
      }

      element.incoming.forEach(function(connection) {
        if (is$1(connection, 'bpmn:MessageFlow')) {
          incoming.push(connection);
        }
      });

      element.outgoing.forEach(function(connection) {
        if (is$1(connection, 'bpmn:MessageFlow')) {
          outgoing.push(connection);
        }
      });
    }, []);

    return {
      incoming: incoming,
      outgoing: outgoing
    };
  }

  var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants';

  function ModelingFeedback(eventBus, tooltips, translate) {

    function showError(position, message, timeout) {
      tooltips.add({
        position: {
          x: position.x + 5,
          y: position.y + 5
        },
        type: 'error',
        timeout: timeout || 2000,
        html: '<div>' + message + '</div>'
      });
    }

    eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
      var context = event.context,
          shape = context.shape,
          target = context.target;

      if (is$1(target, 'bpmn:Collaboration') && is$1(shape, 'bpmn:FlowNode')) {
        showError(event, translate(COLLAB_ERR_MSG));
      }
    });

  }

  ModelingFeedback.$inject = [
    'eventBus',
    'tooltips',
    'translate'
  ];

  /**
   * BPMN specific behavior ensuring that bpmndi:Label's dc:Bounds are removed
   * when shape is resized.
   */
  function RemoveEmbeddedLabelBoundsBehavior(eventBus, modeling) {
    CommandInterceptor.call(this, eventBus);

    this.preExecute('shape.resize', function(context) {
      var shape = context.shape;

      var di = getDi(shape),
          label = di && di.get('label'),
          bounds = label && label.get('bounds');

      if (bounds) {
        modeling.updateModdleProperties(shape, label, {
          bounds: undefined
        });
      }
    }, true);
  }

  e(RemoveEmbeddedLabelBoundsBehavior, CommandInterceptor);

  RemoveEmbeddedLabelBoundsBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  function RemoveElementBehavior(eventBus, bpmnRules, modeling) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Combine sequence flows when deleting an element
     * if there is one incoming and one outgoing
     * sequence flow
     */
    this.preExecute('shape.delete', function(e) {

      var shape = e.context.shape;

      // only handle [a] -> [shape] -> [b] patterns
      if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
        return;
      }

      var inConnection = shape.incoming[0],
          outConnection = shape.outgoing[0];

      // only handle sequence flows
      if (!is$1(inConnection, 'bpmn:SequenceFlow') || !is$1(outConnection, 'bpmn:SequenceFlow')) {
        return;
      }

      if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {

        // compute new, combined waypoints
        var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);

        modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
      }
    });

  }

  e(RemoveElementBehavior, CommandInterceptor);

  RemoveElementBehavior.$inject = [
    'eventBus',
    'bpmnRules',
    'modeling'
  ];


  // helpers //////////////////////

  function getDocking$1(point) {
    return point.original || point;
  }


  function getNewWaypoints(inWaypoints, outWaypoints) {

    var intersection = lineIntersect(
      getDocking$1(inWaypoints[inWaypoints.length - 2]),
      getDocking$1(inWaypoints[inWaypoints.length - 1]),
      getDocking$1(outWaypoints[1]),
      getDocking$1(outWaypoints[0]));

    if (intersection) {
      return [].concat(
        inWaypoints.slice(0, inWaypoints.length - 1),
        [ intersection ],
        outWaypoints.slice(1));
    } else {
      return [
        getDocking$1(inWaypoints[0]),
        getDocking$1(outWaypoints[outWaypoints.length - 1])
      ];
    }
  }

  /**
   * BPMN specific remove behavior
   */
  function RemoveParticipantBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);


    /**
     * morph collaboration diagram into process diagram
     * after the last participant has been removed
     */

    this.preExecute('shape.delete', function(context) {

      var shape = context.shape,
          parent = shape.parent;

      // activate the behavior if the shape to be removed
      // is a participant
      if (is$1(shape, 'bpmn:Participant')) {
        context.collaborationRoot = parent;
      }
    }, true);

    this.postExecute('shape.delete', function(context) {

      var collaborationRoot = context.collaborationRoot;

      if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {

        // replace empty collaboration with process diagram
        modeling.makeProcess();
      }
    }, true);

  }

  RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];

  e(RemoveParticipantBehavior, CommandInterceptor);

  function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {

    CommandInterceptor.call(this, eventBus);

    var dragging = injector.get('dragging', false);

    function fixConnection(connection) {

      var source = connection.source,
          target = connection.target,
          parent = connection.parent;

      // do not do anything if connection
      // is already deleted (may happen due to other
      // behaviors plugged-in before)
      if (!parent) {
        return;
      }

      var replacementType,
          remove;

      /**
       * Check if incoming or outgoing connections
       * can stay or could be substituted with an
       * appropriate replacement.
       *
       * This holds true for SequenceFlow <> MessageFlow.
       */

      if (is$1(connection, 'bpmn:SequenceFlow')) {
        if (!bpmnRules.canConnectSequenceFlow(source, target)) {
          remove = true;
        }

        if (bpmnRules.canConnectMessageFlow(source, target)) {
          replacementType = 'bpmn:MessageFlow';
        }
      }

      // transform message flows into sequence flows, if possible

      if (is$1(connection, 'bpmn:MessageFlow')) {

        if (!bpmnRules.canConnectMessageFlow(source, target)) {
          remove = true;
        }

        if (bpmnRules.canConnectSequenceFlow(source, target)) {
          replacementType = 'bpmn:SequenceFlow';
        }
      }

      if (is$1(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
        remove = true;
      }


      // remove invalid connection,
      // unless it has been removed already
      if (remove) {
        modeling.removeConnection(connection);
      }

      // replace SequenceFlow <> MessageFlow

      if (replacementType) {
        modeling.connect(source, target, {
          type: replacementType,
          waypoints: connection.waypoints.slice()
        });
      }
    }

    function replaceReconnectedConnection(event) {

      var context = event.context,
          connection = context.connection,
          source = context.newSource || connection.source,
          target = context.newTarget || connection.target,
          allowed,
          replacement;

      allowed = bpmnRules.canConnect(source, target);

      if (!allowed || allowed.type === connection.type) {
        return;
      }

      replacement = modeling.connect(source, target, {
        type: allowed.type,
        waypoints: connection.waypoints.slice()
      });

      // remove old connection
      modeling.removeConnection(connection);

      // replace connection in context to reconnect end/start
      context.connection = replacement;

      if (dragging) {
        cleanDraggingSelection(connection, replacement);
      }
    }

    // monkey-patch selection saved in dragging in order to re-select it when operation is finished
    function cleanDraggingSelection(oldConnection, newConnection) {
      var context = dragging.context(),
          previousSelection = context && context.payload.previousSelection,
          index;

      // do nothing if not dragging or no selection was present
      if (!previousSelection || !previousSelection.length) {
        return;
      }

      index = previousSelection.indexOf(oldConnection);

      if (index === -1) {
        return;
      }

      previousSelection.splice(index, 1, newConnection);
    }

    // lifecycle hooks

    this.postExecuted('elements.move', function(context) {

      var closure = context.closure,
          allConnections = closure.allConnections;

      forEach$1(allConnections, fixConnection);
    }, true);

    this.preExecute('connection.reconnect', replaceReconnectedConnection);

    this.postExecuted('element.updateProperties', function(event) {
      var context = event.context,
          properties = context.properties,
          element = context.element,
          businessObject = element.businessObject,
          connection;

      // remove condition on change to default
      if (properties.default) {
        connection = find(
          element.outgoing,
          matchPattern({ id: element.businessObject.default.id })
        );

        if (connection) {
          modeling.updateProperties(connection, { conditionExpression: undefined });
        }
      }

      // remove default from source on change to conditional
      if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
        modeling.updateProperties(element.source, { default: undefined });
      }
    });
  }

  e(ReplaceConnectionBehavior, CommandInterceptor);

  ReplaceConnectionBehavior.$inject = [
    'eventBus',
    'modeling',
    'bpmnRules',
    'injector'
  ];

  /**
   * BPMN-specific replace behavior.
   */
  function ReplaceElementBehaviour(
      bpmnReplace,
      bpmnRules,
      elementRegistry,
      injector,
      modeling,
      selection
  ) {
    injector.invoke(CommandInterceptor, this);

    this._bpmnReplace = bpmnReplace;
    this._elementRegistry = elementRegistry;
    this._selection = selection;

    // replace elements on create, e.g. during copy-paste
    this.postExecuted([ 'elements.create' ], 500, function(event) {
      var context = event.context,
          target = context.parent,
          elements = context.elements;

      var elementReplacements = reduce(elements, function(replacements, element) {
        var canReplace = bpmnRules.canReplace([ element ], element.host || element.parent || target);

        return canReplace ? replacements.concat(canReplace.replacements) : replacements;
      }, []);

      if (elementReplacements.length) {
        this.replaceElements(elements, elementReplacements);
      }
    }, this);

    // replace elements on move
    this.postExecuted([ 'elements.move' ], 500, function(event) {
      var context = event.context,
          target = context.newParent,
          newHost = context.newHost,
          elements = [];

      forEach$1(context.closure.topLevel, function(topLevelElements) {
        if (isEventSubProcess(topLevelElements)) {
          elements = elements.concat(topLevelElements.children);
        } else {
          elements = elements.concat(topLevelElements);
        }
      });

      // set target to host if attaching
      if (elements.length === 1 && newHost) {
        target = newHost;
      }

      var canReplace = bpmnRules.canReplace(elements, target);

      if (canReplace) {
        this.replaceElements(elements, canReplace.replacements, newHost);
      }
    }, this);

    // update attachments on host replace
    this.postExecute([ 'shape.replace' ], 1500, function(e) {
      var context = e.context,
          oldShape = context.oldShape,
          newShape = context.newShape,
          attachers = oldShape.attachers,
          canReplace;

      if (attachers && attachers.length) {
        canReplace = bpmnRules.canReplace(attachers, newShape);

        this.replaceElements(attachers, canReplace.replacements);
      }

    }, this);

    // keep ID on shape replace
    this.postExecuted([ 'shape.replace' ], 1500, function(e) {
      var context = e.context,
          oldShape = context.oldShape,
          newShape = context.newShape;

      modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
      modeling.updateProperties(newShape, { id: oldShape.id });
    });
  }

  e(ReplaceElementBehaviour, CommandInterceptor);

  ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) {
    var elementRegistry = this._elementRegistry,
        bpmnReplace = this._bpmnReplace,
        selection = this._selection;

    forEach$1(newElements, function(replacement) {
      var newElement = {
        type: replacement.newElementType
      };

      var oldElement = elementRegistry.get(replacement.oldElementId);

      var idx = elements.indexOf(oldElement);

      elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
    });

    if (newElements) {
      selection.select(elements);
    }
  };

  ReplaceElementBehaviour.$inject = [
    'bpmnReplace',
    'bpmnRules',
    'elementRegistry',
    'injector',
    'modeling',
    'selection'
  ];

  var HIGH_PRIORITY$9 = 1500;

  var GROUP_MIN_DIMENSIONS = { width: 140, height: 120 };

  var LANE_MIN_DIMENSIONS = { width: 300, height: 60 };

  var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 };

  var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 };

  var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };

  /**
   * Set minimum bounds/resize constraints on resize.
   *
   * @param {EventBus} eventBus
   */
  function ResizeBehavior(eventBus) {
    eventBus.on('resize.start', HIGH_PRIORITY$9, function(event) {
      var context = event.context,
          shape = context.shape,
          direction = context.direction,
          balanced = context.balanced;

      if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
        context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
      }

      if (is$1(shape, 'bpmn:Participant')) {
        context.minDimensions = PARTICIPANT_MIN_DIMENSIONS;
      }

      if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
        context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
      }

      if (is$1(shape, 'bpmn:TextAnnotation')) {
        context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
      }
    });
  }

  ResizeBehavior.$inject = [ 'eventBus' ];


  var abs$2 = Math.abs,
      min = Math.min,
      max$2 = Math.max;


  function addToTrbl(trbl, attr, value, choice) {
    var current = trbl[attr];

    // make sure to set the value if it does not exist
    // or apply the correct value by comparing against
    // choice(value, currentValue)
    trbl[attr] = current === undefined ? value : choice(value, current);
  }

  function addMin(trbl, attr, value) {
    return addToTrbl(trbl, attr, value, min);
  }

  function addMax(trbl, attr, value) {
    return addToTrbl(trbl, attr, value, max$2);
  }

  var LANE_RIGHT_PADDING = 20,
      LANE_LEFT_PADDING = 50,
      LANE_TOP_PADDING = 20,
      LANE_BOTTOM_PADDING = 20;

  function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
    var lanesRoot = getLanesRoot(laneShape);

    var isFirst = true,
        isLast = true;

    // max top/bottom size for lanes
    var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);

    var laneTrbl = asTRBL(laneShape);

    var maxTrbl = {},
        minTrbl = {};

    if (/e/.test(resizeDirection)) {
      minTrbl.right = laneTrbl.left + LANE_MIN_DIMENSIONS.width;
    } else
    if (/w/.test(resizeDirection)) {
      minTrbl.left = laneTrbl.right - LANE_MIN_DIMENSIONS.width;
    }

    allLanes.forEach(function(other) {

      var otherTrbl = asTRBL(other);

      if (/n/.test(resizeDirection)) {

        if (otherTrbl.top < (laneTrbl.top - 10)) {
          isFirst = false;
        }

        // max top size (based on next element)
        if (balanced && abs$2(laneTrbl.top - otherTrbl.bottom) < 10) {
          addMax(maxTrbl, 'top', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
        }

        // min top size (based on self or nested element)
        if (abs$2(laneTrbl.top - otherTrbl.top) < 5) {
          addMin(minTrbl, 'top', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
        }
      }

      if (/s/.test(resizeDirection)) {

        if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
          isLast = false;
        }

        // max bottom size (based on previous element)
        if (balanced && abs$2(laneTrbl.bottom - otherTrbl.top) < 10) {
          addMin(maxTrbl, 'bottom', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
        }

        // min bottom size (based on self or nested element)
        if (abs$2(laneTrbl.bottom - otherTrbl.bottom) < 5) {
          addMax(minTrbl, 'bottom', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
        }
      }
    });

    // max top/bottom/left/right size based on flow nodes
    var flowElements = lanesRoot.children.filter(function(s) {
      return !s.hidden && !s.waypoints && (is$1(s, 'bpmn:FlowElement') || is$1(s, 'bpmn:Artifact'));
    });

    flowElements.forEach(function(flowElement) {

      var flowElementTrbl = asTRBL(flowElement);

      if (isFirst && /n/.test(resizeDirection)) {
        addMin(minTrbl, 'top', flowElementTrbl.top - LANE_TOP_PADDING);
      }

      if (/e/.test(resizeDirection)) {
        addMax(minTrbl, 'right', flowElementTrbl.right + LANE_RIGHT_PADDING);
      }

      if (isLast && /s/.test(resizeDirection)) {
        addMax(minTrbl, 'bottom', flowElementTrbl.bottom + LANE_BOTTOM_PADDING);
      }

      if (/w/.test(resizeDirection)) {
        addMin(minTrbl, 'left', flowElementTrbl.left - LANE_LEFT_PADDING);
      }
    });

    return {
      min: minTrbl,
      max: maxTrbl
    };
  }

  var SLIGHTLY_HIGHER_PRIORITY = 1001;


  /**
   * Invoke {@link Modeling#resizeLane} instead of
   * {@link Modeling#resizeShape} when resizing a Lane
   * or Participant shape.
   */
  function ResizeLaneBehavior(eventBus, modeling) {

    eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
      var context = event.context,
          shape = context.shape;

      if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {

        // should we resize the opposite lane(s) in
        // order to compensate for the resize operation?
        context.balanced = !hasPrimaryModifier(event);
      }
    });

    /**
     * Intercept resize end and call resize lane function instead.
     */
    eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
      var context = event.context,
          shape = context.shape,
          canExecute = context.canExecute,
          newBounds = context.newBounds;

      if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {

        if (canExecute) {

          // ensure we have actual pixel values for new bounds
          // (important when zoom level was > 1 during move)
          newBounds = roundBounds(newBounds);

          // perform the actual resize
          modeling.resizeLane(shape, newBounds, context.balanced);
        }

        // stop propagation
        return false;
      }
    });
  }

  ResizeLaneBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  var LOW_PRIORITY$a = 500;


  /**
   * Add referenced root elements (error, escalation, message, signal) if they don't exist.
   * Copy referenced root elements on copy & paste.
   */
  function RootElementReferenceBehavior(
      bpmnjs, eventBus, injector, moddleCopy, bpmnFactory
  ) {
    injector.invoke(CommandInterceptor, this);

    function canHaveRootElementReference(element) {
      return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) ||
        hasAnyEventDefinition(element, [
          'bpmn:ErrorEventDefinition',
          'bpmn:EscalationEventDefinition',
          'bpmn:MessageEventDefinition',
          'bpmn:SignalEventDefinition'
        ]);
    }

    function hasRootElement(rootElement) {
      var definitions = bpmnjs.getDefinitions(),
          rootElements = definitions.get('rootElements');

      return !!find(rootElements, matchPattern({ id: rootElement.id }));
    }

    function getRootElementReferencePropertyName(eventDefinition) {
      if (is$1(eventDefinition, 'bpmn:ErrorEventDefinition')) {
        return 'errorRef';
      } else if (is$1(eventDefinition, 'bpmn:EscalationEventDefinition')) {
        return 'escalationRef';
      } else if (is$1(eventDefinition, 'bpmn:MessageEventDefinition')) {
        return 'messageRef';
      } else if (is$1(eventDefinition, 'bpmn:SignalEventDefinition')) {
        return 'signalRef';
      }
    }

    function getRootElement(businessObject) {
      if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
        return businessObject.get('messageRef');
      }

      var eventDefinitions = businessObject.get('eventDefinitions'),
          eventDefinition = eventDefinitions[ 0 ];

      return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
    }

    function setRootElement(businessObject, rootElement) {
      if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
        return businessObject.set('messageRef', rootElement);
      }

      var eventDefinitions = businessObject.get('eventDefinitions'),
          eventDefinition = eventDefinitions[ 0 ];

      return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement);
    }

    // create shape
    this.executed('shape.create', function(context) {
      var shape = context.shape;

      if (!canHaveRootElementReference(shape)) {
        return;
      }

      var businessObject = getBusinessObject(shape),
          rootElement = getRootElement(businessObject),
          rootElements;

      if (rootElement && !hasRootElement(rootElement)) {
        rootElements = bpmnjs.getDefinitions().get('rootElements');

        // add root element
        add(rootElements, rootElement);

        context.addedRootElement = rootElement;
      }
    }, true);

    this.reverted('shape.create', function(context) {
      var addedRootElement = context.addedRootElement;

      if (!addedRootElement) {
        return;
      }

      var rootElements = bpmnjs.getDefinitions().get('rootElements');

      // remove root element
      remove(rootElements, addedRootElement);
    }, true);

    eventBus.on('copyPaste.copyElement', function(context) {
      var descriptor = context.descriptor,
          element = context.element;

      if (element.labelTarget || !canHaveRootElementReference(element)) {
        return;
      }

      var businessObject = getBusinessObject(element),
          rootElement = getRootElement(businessObject);

      if (rootElement) {

        // TODO(nikku): clone on copy
        descriptor.referencedRootElement = rootElement;
      }
    });

    eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$a, function(context) {
      var descriptor = context.descriptor,
          businessObject = descriptor.businessObject,
          referencedRootElement = descriptor.referencedRootElement;

      if (!referencedRootElement) {
        return;
      }

      if (!hasRootElement(referencedRootElement)) {
        referencedRootElement = moddleCopy.copyElement(
          referencedRootElement,
          bpmnFactory.create(referencedRootElement.$type)
        );
      }

      setRootElement(businessObject, referencedRootElement);

      delete descriptor.referencedRootElement;
    });
  }

  RootElementReferenceBehavior.$inject = [
    'bpmnjs',
    'eventBus',
    'injector',
    'moddleCopy',
    'bpmnFactory'
  ];

  e(RootElementReferenceBehavior, CommandInterceptor);

  // helpers //////////

  function hasAnyEventDefinition(element, types) {
    if (!isArray$3(types)) {
      types = [ types ];
    }

    return some(types, function(type) {
      return hasEventDefinition$2(element, type);
    });
  }

  var max$1 = Math.max;


  function SpaceToolBehavior(eventBus) {
    eventBus.on('spaceTool.getMinDimensions', function(context) {
      var shapes = context.shapes,
          axis = context.axis,
          start = context.start,
          minDimensions = {};

      forEach$1(shapes, function(shape) {
        var id = shape.id;

        if (is$1(shape, 'bpmn:Participant')) {

          if (isHorizontal$1(axis)) {
            minDimensions[ id ] = PARTICIPANT_MIN_DIMENSIONS;
          } else {
            minDimensions[ id ] = {
              width: PARTICIPANT_MIN_DIMENSIONS.width,
              height: getParticipantMinHeight(shape, start)
            };
          }

        }

        if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
          minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS;
        }

        if (is$1(shape, 'bpmn:TextAnnotation')) {
          minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS;
        }

        if (is$1(shape, 'bpmn:Group')) {
          minDimensions[ id ] = GROUP_MIN_DIMENSIONS;
        }
      });

      return minDimensions;
    });
  }

  SpaceToolBehavior.$inject = [ 'eventBus' ];


  // helpers //////////
  function isHorizontal$1(axis) {
    return axis === 'x';
  }

  /**
   * Get minimum height for participant taking lanes into account.
   *
   * @param {<djs.model.Shape>} participant
   * @param {number} start
   *
   * @returns {Object}
   */
  function getParticipantMinHeight(participant, start) {
    var lanesMinHeight;

    if (!hasChildLanes(participant)) {
      return PARTICIPANT_MIN_DIMENSIONS.height;
    }

    lanesMinHeight = getLanesMinHeight(participant, start);

    return max$1(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight);
  }

  function hasChildLanes(element) {
    return !!getChildLanes(element).length;
  }

  function getLanesMinHeight(participant, resizeStart) {
    var lanes = getChildLanes(participant),
        resizedLane;

    // find the nested lane which is currently resized
    resizedLane = findResizedLane(lanes, resizeStart);

    // resized lane cannot shrink below the minimum height
    // but remaining lanes' dimensions are kept intact
    return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height;
  }

  /**
   * Find nested lane which is currently resized.
   *
   * @param {Array<djs.model.Shape>} lanes
   * @param {number} resizeStart
   */
  function findResizedLane(lanes, resizeStart) {
    var i, lane, childLanes;

    for (i = 0; i < lanes.length; i++) {
      lane = lanes[i];

      // resizing current lane or a lane nested
      if (resizeStart >= lane.y && resizeStart <= lane.y + lane.height) {
        childLanes = getChildLanes(lane);

        // a nested lane is resized
        if (childLanes.length) {
          return findResizedLane(childLanes, resizeStart);
        }

        // current lane is the resized one
        return lane;
      }
    }
  }

  var LOW_PRIORITY$9 = 400;
  var HIGH_PRIORITY$8 = 600;

  var DEFAULT_POSITION = {
    x: 180,
    y: 160
  };


  /**
   * Creates bpmndi:BPMNPlane elements and canvas planes when collapsed subprocesses are created.
   *
   *
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {ElementFactory} elementFactory
   * @param {BpmnFactory} bpmnFactory
   * @param {Bpmnjs} bpmnjs
   * @param {ElementRegistry} elementRegistry
   */
  function SubProcessPlaneBehavior(
      canvas, eventBus, modeling,
      elementFactory, bpmnFactory, bpmnjs, elementRegistry) {

    CommandInterceptor.call(this, eventBus);

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._modeling = modeling;
    this._elementFactory = elementFactory;
    this._bpmnFactory = bpmnFactory;
    this._bpmnjs = bpmnjs;
    this._elementRegistry = elementRegistry;

    var self = this;

    function isCollapsedSubProcess(element) {
      return is$1(element, 'bpmn:SubProcess') && !isExpanded(element);
    }

    function createRoot(context) {
      var shape = context.shape,
          rootElement = context.newRootElement;

      var businessObject = getBusinessObject(shape);

      rootElement = self._addDiagram(rootElement || businessObject);

      context.newRootElement = canvas.addRootElement(rootElement);
    }

    function removeRoot(context) {
      var shape = context.shape;

      var businessObject = getBusinessObject(shape);
      self._removeDiagram(businessObject);

      var rootElement = context.newRootElement = elementRegistry.get(getPlaneIdFromShape(businessObject));

      canvas.removeRootElement(rootElement);
    }

    // add plane elements for newly created sub-processes
    // this ensures we can actually drill down into the element
    this.executed('shape.create', function(context) {
      var shape = context.shape;
      if (!isCollapsedSubProcess(shape)) {
        return;
      }

      createRoot(context);
    }, true);


    this.postExecuted('shape.create', function(context) {
      var shape = context.shape,
          rootElement = context.newRootElement;

      if (!rootElement || !shape.children) {
        return;
      }

      self._showRecursively(shape.children);

      self._moveChildrenToShape(shape, rootElement);
    }, true);


    this.reverted('shape.create', function(context) {
      var shape = context.shape;
      if (!isCollapsedSubProcess(shape)) {
        return;
      }

      removeRoot(context);
    }, true);


    this.preExecuted('shape.delete', function(context) {
      var shape = context.shape;
      if (!isCollapsedSubProcess(shape)) {
        return;
      }

      var attachedRoot = elementRegistry.get(getPlaneIdFromShape(shape));

      if (!attachedRoot) {
        return;
      }

      modeling.removeElements(attachedRoot.children.slice());
    }, true);


    this.executed('shape.delete', function(context) {
      var shape = context.shape;
      if (!isCollapsedSubProcess(shape)) {
        return;
      }
      removeRoot(context);
    }, true);


    this.reverted('shape.delete', function(context) {
      var shape = context.shape;
      if (!isCollapsedSubProcess(shape)) {
        return;
      }

      createRoot(context);
    }, true);


    this.preExecuted('shape.replace', function(context) {
      var oldShape = context.oldShape;
      var newShape = context.newShape;

      if (!isCollapsedSubProcess(oldShape) || !isCollapsedSubProcess(newShape)) {
        return;
      }

      // old plane could have content,
      // we remove it so it is not recursively deleted from 'shape.delete'
      context.oldRoot = canvas.removeRootElement(getPlaneIdFromShape(oldShape));
    }, true);


    this.postExecuted('shape.replace', function(context) {
      var newShape = context.newShape,
          source = context.oldRoot,
          target = canvas.findRoot(getPlaneIdFromShape(newShape));

      if (!source || !target) {
        return;
      }
      var elements = source.children;

      modeling.moveElements(elements, { x: 0, y: 0 }, target);
    }, true);


    // rename primary elements when the secondary element changes
    // this ensures rootElement.id = element.id + '_plane'
    this.executed('element.updateProperties', function(context) {
      var shape = context.element;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      var properties = context.properties;
      var oldProperties = context.oldProperties;

      var oldId = oldProperties.id,
          newId = properties.id;

      if (oldId === newId) {
        return;
      }

      if (isPlane(shape)) {
        elementRegistry.updateId(shape, toPlaneId(newId));
        elementRegistry.updateId(oldId, newId);

        return;
      }

      var planeElement = elementRegistry.get(toPlaneId(oldId));

      if (!planeElement) {
        return;
      }

      elementRegistry.updateId(toPlaneId(oldId), toPlaneId(newId));
    }, true);


    this.reverted('element.updateProperties', function(context) {
      var shape = context.element;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      var properties = context.properties;
      var oldProperties = context.oldProperties;

      var oldId = oldProperties.id,
          newId = properties.id;

      if (oldId === newId) {
        return;
      }

      if (isPlane(shape)) {
        elementRegistry.updateId(shape, toPlaneId(oldId));
        elementRegistry.updateId(newId, oldId);

        return;
      }

      var planeElement = elementRegistry.get(toPlaneId(newId));

      if (!planeElement) {
        return;
      }

      elementRegistry.updateId(planeElement, toPlaneId(oldId));
    }, true);

    // re-throw element.changed to re-render primary shape if associated plane has
    // changed (e.g. bpmn:name property has changed)
    eventBus.on('element.changed', function(context) {
      var element = context.element;

      if (!isPlane(element)) {
        return;
      }

      var plane = element;

      var primaryShape = elementRegistry.get(getShapeIdFromPlane(plane));

      // do not re-throw if no associated primary shape (e.g. bpmn:Process)
      if (!primaryShape || primaryShape === plane) {
        return;
      }

      eventBus.fire('element.changed', { element: primaryShape });
    });


    // create/remove plane for the subprocess
    this.executed('shape.toggleCollapse', LOW_PRIORITY$9, function(context) {
      var shape = context.shape;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      if (!isExpanded(shape)) {
        createRoot(context);
        self._showRecursively(shape.children);
      } else {
        removeRoot(context);
      }

    }, true);


    // create/remove plane for the subprocess
    this.reverted('shape.toggleCollapse', LOW_PRIORITY$9, function(context) {
      var shape = context.shape;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      if (!isExpanded(shape)) {
        createRoot(context);
        self._showRecursively(shape.children);
      } else {
        removeRoot(context);
      }

    }, true);

    // move elements between planes
    this.postExecuted('shape.toggleCollapse', HIGH_PRIORITY$8, function(context) {
      var shape = context.shape;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      var rootElement = context.newRootElement;

      if (!rootElement) {
        return;
      }

      if (!isExpanded(shape)) {

        // collapsed
        self._moveChildrenToShape(shape, rootElement);

      } else {
        self._moveChildrenToShape(rootElement, shape);
      }
    }, true);


    // copy-paste ///////////

    // add elements in plane to tree
    eventBus.on('copyPaste.createTree', function(context) {
      var element = context.element,
          children = context.children;

      if (!isCollapsedSubProcess(element)) {
        return;
      }

      var id = getPlaneIdFromShape(element);
      var parent = elementRegistry.get(id);

      if (parent) {

        // do not copy invisible root element
        children.push.apply(children, parent.children);
      }
    });

    // set plane children as direct children of collapsed shape
    eventBus.on('copyPaste.copyElement', function(context) {
      var descriptor = context.descriptor,
          element = context.element,
          elements = context.elements;

      var parent = element.parent;

      var isPlane = is$1(getDi(parent), 'bpmndi:BPMNPlane');
      if (!isPlane) {
        return;
      }

      var parentId = getShapeIdFromPlane(parent);

      var referencedShape = find(elements, function(element) {
        return element.id === parentId;
      });

      if (!referencedShape) {
        return;
      }

      descriptor.parent = referencedShape.id;
    });

    // hide children during pasting
    eventBus.on('copyPaste.pasteElement', function(context) {
      var descriptor = context.descriptor;

      if (!descriptor.parent) {
        return;
      }

      if (isCollapsedSubProcess(descriptor.parent) || descriptor.parent.hidden) {
        descriptor.hidden = true;
      }
    });

  }

  e(SubProcessPlaneBehavior, CommandInterceptor);

  /**
   * Moves the child elements from source to target.
   *
   * If the target is a plane, the children are moved to the top left corner.
   * Otherwise, the center of the target is used.
   *
   * @param {Object|djs.model.Base} source
   * @param {Object|djs.model.Base} target
   */
  SubProcessPlaneBehavior.prototype._moveChildrenToShape = function(source, target) {
    var modeling = this._modeling;

    var children = source.children;
    var offset;

    if (!children) {
      return;
    }

    // add external labels that weren't children of sub process
    children = children.concat(children.reduce(function(labels, child) {
      if (child.label && child.label.parent !== source) {
        return labels.concat(child.label);
      }

      return labels;
    }, []));

    // only change plane if there are no visible children, but don't move them
    var visibleChildren = children.filter(function(child) {
      return !child.hidden;
    });

    if (!visibleChildren.length) {
      modeling.moveElements(children, { x: 0, y: 0 }, target, { autoResize: false });
      return;
    }

    var childrenBounds = getBBox(visibleChildren);

    // target is a plane
    if (!target.x) {
      offset = {
        x: DEFAULT_POSITION.x - childrenBounds.x,
        y: DEFAULT_POSITION.y - childrenBounds.y
      };
    }

    // source is a plane
    else {

      // move relative to the center of the shape
      var targetMid = getMid(target);
      var childrenMid = getMid(childrenBounds);

      offset = {
        x: targetMid.x - childrenMid.x,
        y: targetMid.y - childrenMid.y
      };
    }

    modeling.moveElements(children, offset, target, { autoResize: false });
  };

  /**
   * Sets `hidden` property on all children of the given shape.
   *
   * @param {Array} elements
   * @param {Boolean} [hidden]
   * @returns {Array} all child elements
   */
  SubProcessPlaneBehavior.prototype._showRecursively = function(elements, hidden) {
    var self = this;

    var result = [];
    elements.forEach(function(element) {
      element.hidden = !!hidden;

      result = result.concat(element);

      if (element.children) {
        result = result.concat(
          self._showRecursively(element.children, element.collapsed || hidden)
        );
      }
    });

    return result;
  };

  /**
  * Adds a given rootElement to the bpmnDi diagrams.
  *
  * @param {Object} rootElement
  * @returns {Object} planeElement
  */
  SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
    var bpmnjs = this._bpmnjs;
    var diagrams = bpmnjs.getDefinitions().diagrams;

    if (!planeElement.businessObject) {
      planeElement = this._createNewDiagram(planeElement);
    }

    diagrams.push(planeElement.di.$parent);

    return planeElement;
  };


  /**
  * Creates a new plane element for the given sub process.
  *
  * @param {Object} bpmnElement
  *
  * @return {Object} new diagram element
  */
  SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
    var bpmnFactory = this._bpmnFactory;
    var elementFactory = this._elementFactory;

    var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', {
      bpmnElement: bpmnElement
    });
    var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', {
      plane: diPlane
    });
    diPlane.$parent = diDiagram;

    // add a virtual element (not being drawn),
    // a copy cat of our BpmnImporter code
    var planeElement = elementFactory.createRoot({
      id: getPlaneIdFromShape(bpmnElement),
      type: bpmnElement.$type,
      di: diPlane,
      businessObject: bpmnElement,
      collapsed: true
    });

    return planeElement;
  };

  /**
   * Removes the diagram for a given root element
   *
   * @param {Object} rootElement
   * @returns {Object} removed bpmndi:BPMNDiagram
   */
  SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) {
    var bpmnjs = this._bpmnjs;

    var diagrams = bpmnjs.getDefinitions().diagrams;

    var removedDiagram = find(diagrams, function(diagram) {
      return diagram.plane.bpmnElement.id === rootElement.id;
    });

    diagrams.splice(diagrams.indexOf(removedDiagram), 1);

    return removedDiagram;
  };


  SubProcessPlaneBehavior.$inject = [
    'canvas',
    'eventBus',
    'modeling',
    'elementFactory',
    'bpmnFactory',
    'bpmnjs',
    'elementRegistry'
  ];

  /**
   * Add start event replacing element with expanded sub process.
   *
   * @param {Injector} injector
   * @param {Modeling} modeling
   */
  function SubProcessStartEventBehavior(injector, modeling) {
    injector.invoke(CommandInterceptor, this);

    this.postExecuted('shape.replace', function(event) {
      var oldShape = event.context.oldShape,
          newShape = event.context.newShape;

      if (
        !is$1(newShape, 'bpmn:SubProcess') ||
        ! (is$1(oldShape, 'bpmn:Task') || is$1(oldShape, 'bpmn:CallActivity')) ||
        !isExpanded(newShape)
      ) {
        return;
      }

      var position = getStartEventPosition(newShape);

      modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
    });
  }

  SubProcessStartEventBehavior.$inject = [
    'injector',
    'modeling'
  ];

  e(SubProcessStartEventBehavior, CommandInterceptor);

  // helpers //////////

  function getStartEventPosition(shape) {
    return {
      x: shape.x + shape.width / 6,
      y: shape.y + shape.height / 2
    };
  }

  function ToggleCollapseConnectionBehaviour(
      eventBus, modeling
  ) {

    CommandInterceptor.call(this, eventBus);

    this.postExecuted('shape.toggleCollapse', 1500, function(context) {

      // var shape = context.shape;
      var shape = context.shape;

      // only change connections when collapsing
      if (isExpanded(shape)) {
        return;
      }

      var allChildren = selfAndAllChildren(shape);

      allChildren.forEach(function(child) {

        // Ensure that the connection array is not modified during iteration
        var incomingConnections = child.incoming.slice(),
            outgoingConnections = child.outgoing.slice();

        forEach$1(incomingConnections, function(c) {
          handleConnection(c, true);
        });

        forEach$1(outgoingConnections, function(c) {
          handleConnection(c, false);
        });
      });


      function handleConnection(c, incoming) {
        if (allChildren.indexOf(c.source) !== -1 && allChildren.indexOf(c.target) !== -1) {
          return;
        }

        if (incoming) {
          modeling.reconnectEnd(c, shape, getMid(shape));
        } else {
          modeling.reconnectStart(c, shape, getMid(shape));
        }

      }

    }, true);

  }

  e(ToggleCollapseConnectionBehaviour, CommandInterceptor);

  ToggleCollapseConnectionBehaviour.$inject = [
    'eventBus',
    'modeling',
  ];

  var LOW_PRIORITY$8 = 500;


  function ToggleElementCollapseBehaviour(
      eventBus, elementFactory, modeling,
      resize) {

    CommandInterceptor.call(this, eventBus);


    function hideEmptyLabels(children) {
      if (children.length) {
        children.forEach(function(child) {
          if (child.type === 'label' && !child.businessObject.name) {
            child.hidden = true;
          }
        });
      }
    }

    function expandedBounds(shape, defaultSize) {
      var children = shape.children,
          newBounds = defaultSize,
          visibleElements,
          visibleBBox;

      visibleElements = filterVisible(children).concat([ shape ]);

      visibleBBox = computeChildrenBBox(visibleElements);

      if (visibleBBox) {

        // center to visibleBBox with max(defaultSize, childrenBounds)
        newBounds.width = Math.max(visibleBBox.width, newBounds.width);
        newBounds.height = Math.max(visibleBBox.height, newBounds.height);

        newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
        newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
      } else {

        // center to collapsed shape with defaultSize
        newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
        newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
      }

      return newBounds;
    }

    function collapsedBounds(shape, defaultSize) {

      return {
        x: shape.x + (shape.width - defaultSize.width) / 2,
        y: shape.y + (shape.height - defaultSize.height) / 2,
        width: defaultSize.width,
        height: defaultSize.height
      };
    }

    this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {

      var context = e.context,
          shape = context.shape;

      if (!is$1(shape, 'bpmn:SubProcess')) {
        return;
      }

      if (!shape.collapsed) {

        // all children got made visible through djs, hide empty labels
        hideEmptyLabels(shape.children);

        // remove collapsed marker
        getDi(shape).isExpanded = true;
      } else {

        // place collapsed marker
        getDi(shape).isExpanded = false;
      }
    });

    this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {

      var context = e.context;
      var shape = context.shape;


      // revert removing/placing collapsed marker
      if (!shape.collapsed) {
        getDi(shape).isExpanded = true;

      } else {
        getDi(shape).isExpanded = false;
      }
    });

    this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {
      var shape = e.context.shape,
          defaultSize = elementFactory.getDefaultSize(shape),
          newBounds;

      if (shape.collapsed) {

        // resize to default size of collapsed shapes
        newBounds = collapsedBounds(shape, defaultSize);
      } else {

        // resize to bounds of max(visible children, defaultSize)
        newBounds = expandedBounds(shape, defaultSize);
      }

      modeling.resizeShape(shape, newBounds, null, {
        autoResize: shape.collapsed ? false : 'nwse'
      });
    });

  }


  e(ToggleElementCollapseBehaviour, CommandInterceptor);

  ToggleElementCollapseBehaviour.$inject = [
    'eventBus',
    'elementFactory',
    'modeling'
  ];


  // helpers //////////////////////

  function filterVisible(elements) {
    return elements.filter(function(e) {
      return !e.hidden;
    });
  }

  /**
   * Unclaims model IDs on element deletion.
   *
   * @param {Canvas} canvas
   * @param {Injector} injector
   * @param {Moddle} moddle
   * @param {Modeling} modeling
   */
  function UnclaimIdBehavior(canvas, injector, moddle, modeling) {
    injector.invoke(CommandInterceptor, this);

    this.preExecute('shape.delete', function(event) {
      var context = event.context,
          shape = context.shape,
          shapeBo = shape.businessObject;

      if (isLabel$6(shape)) {
        return;
      }

      if (is$1(shape, 'bpmn:Participant') && isExpanded(shape)) {
        moddle.ids.unclaim(shapeBo.processRef.id);
      }

      modeling.unclaimId(shapeBo.id, shapeBo);
    });


    this.preExecute('connection.delete', function(event) {
      var context = event.context,
          connection = context.connection,
          connectionBo = connection.businessObject;

      modeling.unclaimId(connectionBo.id, connectionBo);
    });

    this.preExecute('canvas.updateRoot', function() {
      var rootElement = canvas.getRootElement(),
          rootElementBo = rootElement.businessObject;

      if (is$1(rootElement, 'bpmn:Collaboration')) {
        moddle.ids.unclaim(rootElementBo.id);
      }
    });
  }

  e(UnclaimIdBehavior, CommandInterceptor);

  UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];

  /**
   * A behavior that unsets the Default property of
   * sequence flow source on element delete, if the
   * removed element is the Gateway or Task's default flow.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function DeleteSequenceFlowBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);


    this.preExecute('connection.delete', function(event) {
      var context = event.context,
          connection = context.connection,
          source = connection.source;

      if (isDefaultFlow(connection, source)) {
        modeling.updateProperties(source, {
          'default': null
        });
      }
    });
  }

  e(DeleteSequenceFlowBehavior, CommandInterceptor);

  DeleteSequenceFlowBehavior.$inject = [
    'eventBus',
    'modeling'
  ];


  // helpers //////////////////////

  function isDefaultFlow(connection, source) {

    if (!is$1(connection, 'bpmn:SequenceFlow')) {
      return false;
    }

    var sourceBo = getBusinessObject(source),
        sequenceFlow = getBusinessObject(connection);

    return sourceBo.get('default') === sequenceFlow;
  }

  var LOW_PRIORITY$7 = 500,
      HIGH_PRIORITY$7 = 5000;


  /**
   * BPMN specific delete lane behavior
   */
  function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Ok, this is it:
     *
     * We have to update the Lane#flowNodeRefs _and_
     * FlowNode#lanes with every FlowNode move/resize and
     * Lane move/resize.
     *
     * We want to group that stuff to recompute containments
     * as efficient as possible.
     *
     * Yea!
     */

    // the update context
    var context;


    function initContext() {
      context = context || new UpdateContext();
      context.enter();

      return context;
    }

    function getContext() {
      if (!context) {
        throw new Error(translate('out of bounds release'));
      }

      return context;
    }

    function releaseContext() {

      if (!context) {
        throw new Error(translate('out of bounds release'));
      }

      var triggerUpdate = context.leave();

      if (triggerUpdate) {
        modeling.updateLaneRefs(context.flowNodes, context.lanes);

        context = null;
      }

      return triggerUpdate;
    }


    var laneRefUpdateEvents = [
      'spaceTool',
      'lane.add',
      'lane.resize',
      'lane.split',
      'elements.create',
      'elements.delete',
      'elements.move',
      'shape.create',
      'shape.delete',
      'shape.move',
      'shape.resize'
    ];


    // listen to a lot of stuff to group lane updates

    this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY$7, function(event) {
      initContext();
    });

    this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY$7, function(event) {
      releaseContext();
    });


    // Mark flow nodes + lanes that need an update

    this.preExecute([
      'shape.create',
      'shape.move',
      'shape.delete',
      'shape.resize'
    ], function(event) {

      var context = event.context,
          shape = context.shape;

      var updateContext = getContext();

      // no need to update labels
      if (shape.labelTarget) {
        return;
      }

      if (is$1(shape, 'bpmn:Lane')) {
        updateContext.addLane(shape);
      }

      if (is$1(shape, 'bpmn:FlowNode')) {
        updateContext.addFlowNode(shape);
      }
    });
  }

  UpdateFlowNodeRefsBehavior.$inject = [
    'eventBus',
    'modeling' ,
    'translate'
  ];

  e(UpdateFlowNodeRefsBehavior, CommandInterceptor);


  function UpdateContext() {

    this.flowNodes = [];
    this.lanes = [];

    this.counter = 0;

    this.addLane = function(lane) {
      this.lanes.push(lane);
    };

    this.addFlowNode = function(flowNode) {
      this.flowNodes.push(flowNode);
    };

    this.enter = function() {
      this.counter++;
    };

    this.leave = function() {
      this.counter--;

      return !this.counter;
    };
  }

  var BehaviorModule = {
    __init__: [
      'adaptiveLabelPositioningBehavior',
      'appendBehavior',
      'associationBehavior',
      'attachEventBehavior',
      'boundaryEventBehavior',
      'createBehavior',
      'createDataObjectBehavior',
      'createParticipantBehavior',
      'dataInputAssociationBehavior',
      'dataStoreBehavior',
      'deleteLaneBehavior',
      'detachEventBehavior',
      'dropOnFlowBehavior',
      'eventBasedGatewayBehavior',
      'fixHoverBehavior',
      'groupBehavior',
      'importDockingFix',
      'isHorizontalFix',
      'labelBehavior',
      'layoutConnectionBehavior',
      'messageFlowBehavior',
      'modelingFeedback',
      'removeElementBehavior',
      'removeEmbeddedLabelBoundsBehavior',
      'removeParticipantBehavior',
      'replaceConnectionBehavior',
      'replaceElementBehaviour',
      'resizeBehavior',
      'resizeLaneBehavior',
      'rootElementReferenceBehavior',
      'spaceToolBehavior',
      'subProcessPlaneBehavior',
      'subProcessStartEventBehavior',
      'toggleCollapseConnectionBehaviour',
      'toggleElementCollapseBehaviour',
      'unclaimIdBehavior',
      'updateFlowNodeRefsBehavior',
      'unsetDefaultFlowBehavior'
    ],
    adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
    appendBehavior: [ 'type', AppendBehavior ],
    associationBehavior: [ 'type', AssociationBehavior ],
    attachEventBehavior: [ 'type', AttachEventBehavior ],
    boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
    createBehavior: [ 'type', CreateBehavior ],
    createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
    createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
    dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
    dataStoreBehavior: [ 'type', DataStoreBehavior ],
    deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
    detachEventBehavior: [ 'type', DetachEventBehavior ],
    dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
    eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
    fixHoverBehavior: [ 'type', FixHoverBehavior ],
    groupBehavior: [ 'type', GroupBehavior ],
    importDockingFix: [ 'type', ImportDockingFix ],
    isHorizontalFix: [ 'type', IsHorizontalFix ],
    labelBehavior: [ 'type', LabelBehavior ],
    layoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ],
    messageFlowBehavior: [ 'type', MessageFlowBehavior ],
    modelingFeedback: [ 'type', ModelingFeedback ],
    removeElementBehavior: [ 'type', RemoveElementBehavior ],
    removeEmbeddedLabelBoundsBehavior: [ 'type', RemoveEmbeddedLabelBoundsBehavior ],
    removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
    replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
    replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
    resizeBehavior: [ 'type', ResizeBehavior ],
    resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
    rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
    spaceToolBehavior: [ 'type', SpaceToolBehavior ],
    subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
    subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
    toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
    toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
    unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
    unsetDefaultFlowBehavior: [ 'type', DeleteSequenceFlowBehavior ],
    updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ]
  };

  function getBoundaryAttachment(position, targetBounds) {

    var orientation = getOrientation(position, targetBounds, -15);

    if (orientation !== 'intersect') {
      return orientation;
    } else {
      return null;
    }
  }

  /**
   * BPMN specific modeling rule
   */
  function BpmnRules(eventBus) {
    RuleProvider.call(this, eventBus);
  }

  e(BpmnRules, RuleProvider);

  BpmnRules.$inject = [ 'eventBus' ];

  BpmnRules.prototype.init = function() {

    this.addRule('connection.start', function(context) {
      var source = context.source;

      return canStartConnection(source);
    });

    this.addRule('connection.create', function(context) {
      var source = context.source,
          target = context.target,
          hints = context.hints || {},
          targetParent = hints.targetParent,
          targetAttach = hints.targetAttach;

      // don't allow incoming connections on
      // newly created boundary events
      // to boundary events
      if (targetAttach) {
        return false;
      }

      // temporarily set target parent for scoping
      // checks to work
      if (targetParent) {
        target.parent = targetParent;
      }

      try {
        return canConnect(source, target);
      } finally {

        // unset temporary target parent
        if (targetParent) {
          target.parent = null;
        }
      }
    });

    this.addRule('connection.reconnect', function(context) {

      var connection = context.connection,
          source = context.source,
          target = context.target;

      return canConnect(source, target, connection);
    });

    this.addRule('connection.updateWaypoints', function(context) {
      return {
        type: context.connection.type
      };
    });

    this.addRule('shape.resize', function(context) {

      var shape = context.shape,
          newBounds = context.newBounds;

      return canResize(shape, newBounds);
    });

    this.addRule('elements.create', function(context) {
      var elements = context.elements,
          position = context.position,
          target = context.target;

      if (isConnection$8(target) && !canInsert(elements, target)) {
        return false;
      }

      return every(elements, function(element) {
        if (isConnection$8(element)) {
          return canConnect(element.source, element.target, element);
        }

        if (element.host) {
          return canAttach(element, element.host, null, position);
        }

        return canCreate(element, target, null);
      });
    });

    this.addRule('elements.move', function(context) {

      var target = context.target,
          shapes = context.shapes,
          position = context.position;

      return canAttach(shapes, target, null, position) ||
             canReplace(shapes, target, position) ||
             canMove(shapes, target) ||
             canInsert(shapes, target);
    });

    this.addRule('shape.create', function(context) {
      return canCreate(
        context.shape,
        context.target,
        context.source,
        context.position
      );
    });

    this.addRule('shape.attach', function(context) {

      return canAttach(
        context.shape,
        context.target,
        null,
        context.position
      );
    });

    this.addRule('element.copy', function(context) {
      var element = context.element,
          elements = context.elements;

      return canCopy(elements, element);
    });
  };

  BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;

  BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;

  BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;

  BpmnRules.prototype.canConnectAssociation = canConnectAssociation;

  BpmnRules.prototype.canMove = canMove;

  BpmnRules.prototype.canAttach = canAttach;

  BpmnRules.prototype.canReplace = canReplace;

  BpmnRules.prototype.canDrop = canDrop;

  BpmnRules.prototype.canInsert = canInsert;

  BpmnRules.prototype.canCreate = canCreate;

  BpmnRules.prototype.canConnect = canConnect;

  BpmnRules.prototype.canResize = canResize;

  BpmnRules.prototype.canCopy = canCopy;

  /**
   * Utility functions for rule checking
   */

  /**
   * Checks if given element can be used for starting connection.
   *
   * @param  {Element} source
   * @return {boolean}
   */
  function canStartConnection(element) {
    if (nonExistingOrLabel(element)) {
      return null;
    }

    return isAny(element, [
      'bpmn:FlowNode',
      'bpmn:InteractionNode',
      'bpmn:DataObjectReference',
      'bpmn:DataStoreReference',
      'bpmn:Group',
      'bpmn:TextAnnotation'
    ]);
  }

  function nonExistingOrLabel(element) {
    return !element || isLabel$6(element);
  }

  function isSame$1(a, b) {
    return a === b;
  }

  function getOrganizationalParent(element) {

    do {
      if (is$1(element, 'bpmn:Process')) {
        return getBusinessObject(element);
      }

      if (is$1(element, 'bpmn:Participant')) {
        return (
          getBusinessObject(element).processRef ||
          getBusinessObject(element)
        );
      }
    } while ((element = element.parent));

  }

  function isTextAnnotation(element) {
    return is$1(element, 'bpmn:TextAnnotation');
  }

  function isGroup(element) {
    return is$1(element, 'bpmn:Group') && !element.labelTarget;
  }

  function isCompensationBoundary(element) {
    return is$1(element, 'bpmn:BoundaryEvent') &&
           hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
  }

  function isForCompensation(e) {
    return getBusinessObject(e).isForCompensation;
  }

  function isSameOrganization(a, b) {
    var parentA = getOrganizationalParent(a),
        parentB = getOrganizationalParent(b);

    return parentA === parentB;
  }

  function isMessageFlowSource(element) {
    return (
      is$1(element, 'bpmn:InteractionNode') &&
      !is$1(element, 'bpmn:BoundaryEvent') && (
        !is$1(element, 'bpmn:Event') || (
          is$1(element, 'bpmn:ThrowEvent') &&
          hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
        )
      )
    );
  }

  function isMessageFlowTarget(element) {
    return (
      is$1(element, 'bpmn:InteractionNode') &&
      !isForCompensation(element) && (
        !is$1(element, 'bpmn:Event') || (
          is$1(element, 'bpmn:CatchEvent') &&
          hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
        )
      ) && !(
        is$1(element, 'bpmn:BoundaryEvent') &&
        !hasEventDefinition(element, 'bpmn:MessageEventDefinition')
      )
    );
  }

  function getScopeParent(element) {

    var parent = element;

    while ((parent = parent.parent)) {

      if (is$1(parent, 'bpmn:FlowElementsContainer')) {
        return getBusinessObject(parent);
      }

      if (is$1(parent, 'bpmn:Participant')) {
        return getBusinessObject(parent).processRef;
      }
    }

    return null;
  }

  function isSameScope(a, b) {
    var scopeParentA = getScopeParent(a),
        scopeParentB = getScopeParent(b);

    return scopeParentA === scopeParentB;
  }

  function hasEventDefinition(element, eventDefinition) {
    var bo = getBusinessObject(element);

    return !!find(bo.eventDefinitions || [], function(definition) {
      return is$1(definition, eventDefinition);
    });
  }

  function hasEventDefinitionOrNone(element, eventDefinition) {
    var bo = getBusinessObject(element);

    return (bo.eventDefinitions || []).every(function(definition) {
      return is$1(definition, eventDefinition);
    });
  }

  function isSequenceFlowSource(element) {
    return (
      is$1(element, 'bpmn:FlowNode') &&
      !is$1(element, 'bpmn:EndEvent') &&
      !isEventSubProcess(element) &&
      !(is$1(element, 'bpmn:IntermediateThrowEvent') &&
        hasEventDefinition(element, 'bpmn:LinkEventDefinition')
      ) &&
      !isCompensationBoundary(element) &&
      !isForCompensation(element)
    );
  }

  function isSequenceFlowTarget(element) {
    return (
      is$1(element, 'bpmn:FlowNode') &&
      !is$1(element, 'bpmn:StartEvent') &&
      !is$1(element, 'bpmn:BoundaryEvent') &&
      !isEventSubProcess(element) &&
      !(is$1(element, 'bpmn:IntermediateCatchEvent') &&
        hasEventDefinition(element, 'bpmn:LinkEventDefinition')
      ) &&
      !isForCompensation(element)
    );
  }

  function isEventBasedTarget(element) {
    return (
      is$1(element, 'bpmn:ReceiveTask') || (
        is$1(element, 'bpmn:IntermediateCatchEvent') && (
          hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
          hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
          hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
          hasEventDefinition(element, 'bpmn:SignalEventDefinition')
        )
      )
    );
  }

  function isConnection$8(element) {
    return element.waypoints;
  }

  function getParents(element) {

    var parents = [];

    while (element) {
      element = element.parent;

      if (element) {
        parents.push(element);
      }
    }

    return parents;
  }

  function isParent(possibleParent, element) {
    var allParents = getParents(element);
    return allParents.indexOf(possibleParent) !== -1;
  }

  function canConnect(source, target, connection) {

    if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
      return null;
    }

    if (!is$1(connection, 'bpmn:DataAssociation')) {

      if (canConnectMessageFlow(source, target)) {
        return { type: 'bpmn:MessageFlow' };
      }

      if (canConnectSequenceFlow(source, target)) {
        return { type: 'bpmn:SequenceFlow' };
      }
    }

    var connectDataAssociation = canConnectDataAssociation(source, target);

    if (connectDataAssociation) {
      return connectDataAssociation;
    }

    if (isCompensationBoundary(source) && isForCompensation(target)) {
      return {
        type: 'bpmn:Association',
        associationDirection: 'One'
      };
    }

    if (canConnectAssociation(source, target)) {

      return {
        type: 'bpmn:Association'
      };
    }

    return false;
  }

  /**
   * Can an element be dropped into the target element
   *
   * @return {boolean}
   */
  function canDrop(element, target, position) {

    // can move labels and groups everywhere
    if (isLabel$6(element) || isGroup(element)) {
      return true;
    }


    // disallow to create elements on collapsed pools
    if (is$1(target, 'bpmn:Participant') && !isExpanded(target)) {
      return false;
    }

    // allow to create new participants on
    // existing collaboration and process diagrams
    if (is$1(element, 'bpmn:Participant')) {
      return is$1(target, 'bpmn:Process') || is$1(target, 'bpmn:Collaboration');
    }

    // allow moving DataInput / DataOutput within its original container only
    if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {

      if (element.parent) {
        return target === element.parent;
      }
    }

    // allow creating lanes on participants and other lanes only
    if (is$1(element, 'bpmn:Lane')) {
      return is$1(target, 'bpmn:Participant') || is$1(target, 'bpmn:Lane');
    }

    // disallow dropping boundary events which cannot replace with intermediate event
    if (is$1(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
      return false;
    }

    // drop flow elements onto flow element containers
    // and participants
    if (is$1(element, 'bpmn:FlowElement') && !is$1(element, 'bpmn:DataStoreReference')) {
      if (is$1(target, 'bpmn:FlowElementsContainer')) {
        return isExpanded(target);
      }

      return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
    }

    // disallow dropping data store reference if there is no process to append to
    if (is$1(element, 'bpmn:DataStoreReference') && is$1(target, 'bpmn:Collaboration')) {
      return some(getBusinessObject(target).get('participants'), function(participant) {
        return !!participant.get('processRef');
      });
    }

    // account for the fact that data associations are always
    // rendered and moved to top (Process or Collaboration level)
    //
    // artifacts may be placed wherever, too
    if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
      return isAny(target, [
        'bpmn:Collaboration',
        'bpmn:Lane',
        'bpmn:Participant',
        'bpmn:Process',
        'bpmn:SubProcess' ]);
    }

    if (is$1(element, 'bpmn:MessageFlow')) {
      return is$1(target, 'bpmn:Collaboration')
        || element.source.parent == target
        || element.target.parent == target;
    }

    return false;
  }

  function isDroppableBoundaryEvent(event) {
    return getBusinessObject(event).cancelActivity && (
      hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
    );
  }

  function isBoundaryEvent(element) {
    return !isLabel$6(element) && is$1(element, 'bpmn:BoundaryEvent');
  }

  function isLane(element) {
    return is$1(element, 'bpmn:Lane');
  }

  /**
   * We treat IntermediateThrowEvents as boundary events during create,
   * this must be reflected in the rules.
   */
  function isBoundaryCandidate(element) {
    if (isBoundaryEvent(element)) {
      return true;
    }

    if (is$1(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
      return true;
    }

    return (
      is$1(element, 'bpmn:IntermediateCatchEvent') &&
      hasCommonBoundaryIntermediateEventDefinition(element)
    );
  }

  function hasNoEventDefinition(element) {
    var bo = getBusinessObject(element);

    return bo && !(bo.eventDefinitions && bo.eventDefinitions.length);
  }

  function hasCommonBoundaryIntermediateEventDefinition(element) {
    return hasOneOfEventDefinitions(element, [
      'bpmn:MessageEventDefinition',
      'bpmn:TimerEventDefinition',
      'bpmn:SignalEventDefinition',
      'bpmn:ConditionalEventDefinition'
    ]);
  }

  function hasOneOfEventDefinitions(element, eventDefinitions) {
    return eventDefinitions.some(function(definition) {
      return hasEventDefinition(element, definition);
    });
  }

  function isReceiveTaskAfterEventBasedGateway(element) {
    return (
      is$1(element, 'bpmn:ReceiveTask') &&
      find(element.incoming, function(incoming) {
        return is$1(incoming.source, 'bpmn:EventBasedGateway');
      })
    );
  }


  function canAttach(elements, target, source, position) {

    if (!Array.isArray(elements)) {
      elements = [ elements ];
    }

    // only (re-)attach one element at a time
    if (elements.length !== 1) {
      return false;
    }

    var element = elements[0];

    // do not attach labels
    if (isLabel$6(element)) {
      return false;
    }

    // only handle boundary events
    if (!isBoundaryCandidate(element)) {
      return false;
    }

    // disallow drop on event sub processes
    if (isEventSubProcess(target)) {
      return false;
    }

    // only allow drop on non compensation activities
    if (!is$1(target, 'bpmn:Activity') || isForCompensation(target)) {
      return false;
    }

    // only attach to subprocess border
    if (position && !getBoundaryAttachment(position, target)) {
      return false;
    }

    // do not attach on receive tasks after event based gateways
    if (isReceiveTaskAfterEventBasedGateway(target)) {
      return false;
    }

    return 'attach';
  }


  /**
   * Defines how to replace elements for a given target.
   *
   * Returns an array containing all elements which will be replaced.
   *
   * @example
   *
   *  [{ id: 'IntermediateEvent_2',
   *     type: 'bpmn:StartEvent'
   *   },
   *   { id: 'IntermediateEvent_5',
   *     type: 'bpmn:EndEvent'
   *   }]
   *
   * @param  {Array} elements
   * @param  {Object} target
   *
   * @return {Object} an object containing all elements which have to be replaced
   */
  function canReplace(elements, target, position) {

    if (!target) {
      return false;
    }

    var canExecute = {
      replacements: []
    };

    forEach$1(elements, function(element) {

      if (!isEventSubProcess(target)) {

        if (is$1(element, 'bpmn:StartEvent') &&
            element.type !== 'label' &&
            canDrop(element, target)) {

          // replace a non-interrupting start event by a blank interrupting start event
          // when the target is not an event sub process
          if (!isInterrupting(element)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:StartEvent'
            });
          }

          // replace an error/escalation/compensate start event by a blank interrupting start event
          // when the target is not an event sub process
          if (hasErrorEventDefinition(element) ||
              hasEscalationEventDefinition(element) ||
              hasCompensateEventDefinition(element)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:StartEvent'
            });
          }

          // replace a typed start event by a blank interrupting start event
          // when the target is a sub process but not an event sub process
          if (hasOneOfEventDefinitions(element,
            [
              'bpmn:MessageEventDefinition',
              'bpmn:TimerEventDefinition',
              'bpmn:SignalEventDefinition',
              'bpmn:ConditionalEventDefinition'
            ]) &&
              is$1(target, 'bpmn:SubProcess')) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:StartEvent'
            });
          }
        }
      }

      if (!is$1(target, 'bpmn:Transaction')) {
        if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
            element.type !== 'label') {

          if (is$1(element, 'bpmn:EndEvent') && canDrop(element, target)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:EndEvent'
            });
          }

          if (is$1(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:BoundaryEvent'
            });
          }
        }
      }
    });

    return canExecute.replacements.length ? canExecute : false;
  }

  function canMove(elements, target) {

    // do not move selection containing lanes
    if (some(elements, isLane)) {
      return false;
    }

    // allow default move check to start move operation
    if (!target) {
      return true;
    }

    return elements.every(function(element) {
      return canDrop(element, target);
    });
  }

  function canCreate(shape, target, source, position) {

    if (!target) {
      return false;
    }

    if (isLabel$6(shape) || isGroup(shape)) {
      return true;
    }

    if (isSame$1(source, target)) {
      return false;
    }

    // ensure we do not drop the element
    // into source
    if (source && isParent(source, target)) {
      return false;
    }

    return canDrop(shape, target) || canInsert(shape, target);
  }

  function canResize(shape, newBounds) {
    if (is$1(shape, 'bpmn:SubProcess')) {
      return (
        isExpanded(shape) && (
          !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
        )
      );
    }

    if (is$1(shape, 'bpmn:Lane')) {
      return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
    }

    if (is$1(shape, 'bpmn:Participant')) {
      return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
    }

    if (isTextAnnotation(shape)) {
      return true;
    }

    if (isGroup(shape)) {
      return true;
    }

    return false;
  }

  /**
   * Check, whether one side of the relationship
   * is a text annotation.
   */
  function isOneTextAnnotation(source, target) {

    var sourceTextAnnotation = isTextAnnotation(source),
        targetTextAnnotation = isTextAnnotation(target);

    return (
      (sourceTextAnnotation || targetTextAnnotation) &&
      (sourceTextAnnotation !== targetTextAnnotation)
    );
  }


  function canConnectAssociation(source, target) {

    // compensation boundary events are exception
    if (isCompensationBoundary(source) && isForCompensation(target)) {
      return true;
    }

    // don't connect parent <-> child
    if (isParent(target, source) || isParent(source, target)) {
      return false;
    }

    // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
    if (isOneTextAnnotation(source, target)) {
      return true;
    }

    // can connect associations where we can connect
    // data associations, too (!)
    return !!canConnectDataAssociation(source, target);
  }

  function canConnectMessageFlow(source, target) {

    // during connect user might move mouse out of canvas
    // https://github.com/bpmn-io/bpmn-js/issues/1033
    if (getRootElement(source) && !getRootElement(target)) {
      return false;
    }

    return (
      isMessageFlowSource(source) &&
      isMessageFlowTarget(target) &&
      !isSameOrganization(source, target)
    );
  }

  function canConnectSequenceFlow(source, target) {

    if (
      isEventBasedTarget(target) &&
      target.incoming.length > 0 &&
      areOutgoingEventBasedGatewayConnections(target.incoming) &&
      !is$1(source, 'bpmn:EventBasedGateway')
    ) {
      return false;
    }

    return isSequenceFlowSource(source) &&
           isSequenceFlowTarget(target) &&
           isSameScope(source, target) &&
           !(is$1(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
  }


  function canConnectDataAssociation(source, target) {

    if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
        isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
      return { type: 'bpmn:DataInputAssociation' };
    }

    if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
        isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
      return { type: 'bpmn:DataOutputAssociation' };
    }

    return false;
  }

  function canInsert(shape, flow, position) {

    if (!flow) {
      return false;
    }

    if (Array.isArray(shape)) {
      if (shape.length !== 1) {
        return false;
      }

      shape = shape[0];
    }

    if (flow.source === shape ||
        flow.target === shape) {
      return false;
    }

    // return true if we can drop on the
    // underlying flow parent
    //
    // at this point we are not really able to talk
    // about connection rules (yet)

    return (
      isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
      !isLabel$6(flow) &&
      is$1(shape, 'bpmn:FlowNode') &&
      !is$1(shape, 'bpmn:BoundaryEvent') &&
      canDrop(shape, flow.parent));
  }

  function includes$5(elements, element) {
    return (elements && element) && elements.indexOf(element) !== -1;
  }

  function canCopy(elements, element) {
    if (isLabel$6(element)) {
      return true;
    }

    if (is$1(element, 'bpmn:Lane') && !includes$5(elements, element.parent)) {
      return false;
    }

    return true;
  }

  function isOutgoingEventBasedGatewayConnection(connection) {

    if (connection && connection.source) {
      return is$1(connection.source, 'bpmn:EventBasedGateway');
    }
  }

  function areOutgoingEventBasedGatewayConnections(connections) {
    connections = connections || [];

    return connections.some(isOutgoingEventBasedGatewayConnection);
  }

  function getRootElement(element) {
    return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
  }

  var RulesModule = {
    __depends__: [
      RulesModule$1
    ],
    __init__: [ 'bpmnRules' ],
    bpmnRules: [ 'type', BpmnRules ]
  };

  var HIGH_PRIORITY$6 = 2000;

  function BpmnDiOrdering(eventBus, canvas) {

    eventBus.on('saveXML.start', HIGH_PRIORITY$6, orderDi);

    function orderDi() {
      var rootElements = canvas.getRootElements();

      forEach$1(rootElements, function(root) {
        var rootDi = getDi(root),
            elements,
            diElements;

        elements = selfAndAllChildren([ root ], false);

        // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane
        elements = filter(elements, function(element) {
          return element !== root && !element.labelTarget;
        });

        diElements = map(elements, getDi);

        rootDi.set('planeElement', diElements);
      });
    }
  }

  BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ];

  var DiOrderingModule = {
    __init__: [
      'bpmnDiOrdering'
    ],
    bpmnDiOrdering: [ 'type', BpmnDiOrdering ]
  };

  /**
   * An abstract provider that allows modelers to implement a custom
   * ordering of diagram elements on the canvas.
   *
   * It makes sure that the order is always preserved during element
   * creation and move operations.
   *
   * In order to use this behavior, inherit from it and override
   * the method {@link OrderingProvider#getOrdering}.
   *
   * @example
   *
   * ```javascript
   * function CustomOrderingProvider(eventBus) {
   *   OrderingProvider.call(this, eventBus);
   *
   *   this.getOrdering = function(element, newParent) {
   *     // always insert elements at the front
   *     // when moving
   *     return {
   *       index: 0,
   *       parent: newParent
   *     };
   *   };
   * }
   * ```
   *
   * @param {EventBus} eventBus
   */
  function OrderingProvider(eventBus) {

    CommandInterceptor.call(this, eventBus);


    var self = this;

    this.preExecute([ 'shape.create', 'connection.create' ], function(event) {

      var context = event.context,
          element = context.shape || context.connection,
          parent = context.parent;

      var ordering = self.getOrdering(element, parent);

      if (ordering) {

        if (ordering.parent !== undefined) {
          context.parent = ordering.parent;
        }

        context.parentIndex = ordering.index;
      }
    });

    this.preExecute([ 'shape.move', 'connection.move' ], function(event) {

      var context = event.context,
          element = context.shape || context.connection,
          parent = context.newParent || element.parent;

      var ordering = self.getOrdering(element, parent);

      if (ordering) {

        if (ordering.parent !== undefined) {
          context.newParent = ordering.parent;
        }

        context.newParentIndex = ordering.index;
      }
    });
  }

  /**
   * Return a custom ordering of the element, both in terms
   * of parent element and index in the new parent.
   *
   * Implementors of this method must return an object with
   * `parent` _and_ `index` in it.
   *
   * @param {djs.model.Base} element
   * @param {djs.model.Shape} newParent
   *
   * @return {Object} ordering descriptor
   */
  OrderingProvider.prototype.getOrdering = function(element, newParent) {
    return null;
  };

  e(OrderingProvider, CommandInterceptor);

  /**
   * a simple ordering provider that makes sure:
   *
   * (0) labels and groups are rendered always on top
   * (1) elements are ordered by a {level} property
   */
  function BpmnOrderingProvider(eventBus, canvas, translate) {

    OrderingProvider.call(this, eventBus);

    var orders = [
      { type: 'bpmn:SubProcess', order: { level: 6 } },

      // handle SequenceFlow(s) like message flows and render them always on top
      {
        type: 'bpmn:SequenceFlow',
        order: {
          level: 9,
          containers: [
            'bpmn:Participant',
            'bpmn:FlowElementsContainer'
          ]
        }
      },

      // handle DataAssociation(s) like message flows and render them always on top
      {
        type: 'bpmn:DataAssociation',
        order: {
          level: 9,
          containers: [
            'bpmn:Collaboration',
            'bpmn:FlowElementsContainer'
          ]
        }
      },
      {
        type: 'bpmn:MessageFlow', order: {
          level: 9,
          containers: [ 'bpmn:Collaboration' ]
        }
      },
      {
        type: 'bpmn:Association',
        order: {
          level: 6,
          containers: [
            'bpmn:Participant',
            'bpmn:FlowElementsContainer',
            'bpmn:Collaboration'
          ]
        }
      },
      { type: 'bpmn:BoundaryEvent', order: { level: 8 } },
      {
        type: 'bpmn:Group',
        order: {
          level: 10,
          containers: [
            'bpmn:Collaboration',
            'bpmn:FlowElementsContainer'
          ]
        }
      },
      { type: 'bpmn:FlowElement', order: { level: 5 } },
      { type: 'bpmn:Participant', order: { level: -2 } },
      { type: 'bpmn:Lane', order: { level: -1 } }
    ];

    function computeOrder(element) {
      if (element.labelTarget) {
        return { level: 10 };
      }

      var entry = find(orders, function(o) {
        return isAny(element, [ o.type ]);
      });

      return entry && entry.order || { level: 1 };
    }

    function getOrder(element) {

      var order = element.order;

      if (!order) {
        element.order = order = computeOrder(element);
      }

      if (!order) {
        throw new Error('no order for <' + element.id + '>');
      }

      return order;
    }

    function findActualParent(element, newParent, containers) {

      var actualParent = newParent;

      while (actualParent) {

        if (isAny(actualParent, containers)) {
          break;
        }

        actualParent = actualParent.parent;
      }

      if (!actualParent) {
        throw new Error('no parent for <' + element.id + '> in <' + (newParent && newParent.id) + '>');
      }

      return actualParent;
    }

    this.getOrdering = function(element, newParent) {

      // render labels always on top
      if (element.labelTarget) {
        return {
          parent: canvas.findRoot(newParent) || canvas.getRootElement(),
          index: -1
        };
      }

      var elementOrder = getOrder(element);

      if (elementOrder.containers) {
        newParent = findActualParent(element, newParent, elementOrder.containers);
      }

      var currentIndex = newParent.children.indexOf(element);

      var insertIndex = findIndex(newParent.children, function(child) {

        // do not compare with labels, they are created
        // in the wrong order (right after elements) during import and
        // mess up the positioning.
        if (!element.labelTarget && child.labelTarget) {
          return false;
        }

        return elementOrder.level < getOrder(child).level;
      });


      // if the element is already in the child list at
      // a smaller index, we need to adjust the insert index.
      // this takes into account that the element is being removed
      // before being re-inserted
      if (insertIndex !== -1) {
        if (currentIndex !== -1 && currentIndex < insertIndex) {
          insertIndex -= 1;
        }
      }

      return {
        index: insertIndex,
        parent: newParent
      };
    };
  }

  BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ];

  e(BpmnOrderingProvider, OrderingProvider);

  var OrderingModule = {
    __depends__: [
      translate
    ],
    __init__: [ 'bpmnOrderingProvider' ],
    bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
  };

  /**
   * A service that offers un- and redoable execution of commands.
   *
   * The command stack is responsible for executing modeling actions
   * in a un- and redoable manner. To do this it delegates the actual
   * command execution to {@link CommandHandler}s.
   *
   * Command handlers provide {@link CommandHandler#execute(ctx)} and
   * {@link CommandHandler#revert(ctx)} methods to un- and redo a command
   * identified by a command context.
   *
   *
   * ## Life-Cycle events
   *
   * In the process the command stack fires a number of life-cycle events
   * that other components to participate in the command execution.
   *
   *    * preExecute
   *    * preExecuted
   *    * execute
   *    * executed
   *    * postExecute
   *    * postExecuted
   *    * revert
   *    * reverted
   *
   * A special event is used for validating, whether a command can be
   * performed prior to its execution.
   *
   *    * canExecute
   *
   * Each of the events is fired as `commandStack.{eventName}` and
   * `commandStack.{commandName}.{eventName}`, respectively. This gives
   * components fine grained control on where to hook into.
   *
   * The event object fired transports `command`, the name of the
   * command and `context`, the command context.
   *
   *
   * ## Creating Command Handlers
   *
   * Command handlers should provide the {@link CommandHandler#execute(ctx)}
   * and {@link CommandHandler#revert(ctx)} methods to implement
   * redoing and undoing of a command.
   *
   * A command handler _must_ ensure undo is performed properly in order
   * not to break the undo chain. It must also return the shapes that
   * got changed during the `execute` and `revert` operations.
   *
   * Command handlers may execute other modeling operations (and thus
   * commands) in their `preExecute` and `postExecute` phases. The command
   * stack will properly group all commands together into a logical unit
   * that may be re- and undone atomically.
   *
   * Command handlers must not execute other commands from within their
   * core implementation (`execute`, `revert`).
   *
   *
   * ## Change Tracking
   *
   * During the execution of the CommandStack it will keep track of all
   * elements that have been touched during the command's execution.
   *
   * At the end of the CommandStack execution it will notify interested
   * components via an 'elements.changed' event with all the dirty
   * elements.
   *
   * The event can be picked up by components that are interested in the fact
   * that elements have been changed. One use case for this is updating
   * their graphical representation after moving / resizing or deletion.
   *
   * @see CommandHandler
   *
   * @param {EventBus} eventBus
   * @param {Injector} injector
   */
  function CommandStack(eventBus, injector) {

    /**
     * A map of all registered command handlers.
     *
     * @type {Object}
     */
    this._handlerMap = {};

    /**
     * A stack containing all re/undoable actions on the diagram
     *
     * @type {Array<Object>}
     */
    this._stack = [];

    /**
     * The current index on the stack
     *
     * @type {number}
     */
    this._stackIdx = -1;

    /**
     * Current active commandStack execution
     *
     * @type {Object}
     * @property {Object[]} actions
     * @property {Object[]} dirty
     * @property { 'undo' | 'redo' | 'clear' | 'execute' | null } trigger the cause of the current excecution
     */
    this._currentExecution = {
      actions: [],
      dirty: [],
      trigger: null
    };


    this._injector = injector;
    this._eventBus = eventBus;

    this._uid = 1;

    eventBus.on([
      'diagram.destroy',
      'diagram.clear'
    ], function() {
      this.clear(false);
    }, this);
  }

  CommandStack.$inject = [ 'eventBus', 'injector' ];


  /**
   * Execute a command
   *
   * @param {string} command the command to execute
   * @param {Object} context the environment to execute the command in
   */
  CommandStack.prototype.execute = function(command, context) {
    if (!command) {
      throw new Error('command required');
    }

    this._currentExecution.trigger = 'execute';

    var action = { command: command, context: context };

    this._pushAction(action);
    this._internalExecute(action);
    this._popAction(action);
  };


  /**
   * Ask whether a given command can be executed.
   *
   * Implementors may hook into the mechanism on two ways:
   *
   *   * in event listeners:
   *
   *     Users may prevent the execution via an event listener.
   *     It must prevent the default action for `commandStack.(<command>.)canExecute` events.
   *
   *   * in command handlers:
   *
   *     If the method {@link CommandHandler#canExecute} is implemented in a handler
   *     it will be called to figure out whether the execution is allowed.
   *
   * @param  {string} command the command to execute
   * @param  {Object} context the environment to execute the command in
   *
   * @return {boolean} true if the command can be executed
   */
  CommandStack.prototype.canExecute = function(command, context) {

    var action = { command: command, context: context };

    var handler = this._getHandler(command);

    var result = this._fire(command, 'canExecute', action);

    // handler#canExecute will only be called if no listener
    // decided on a result already
    if (result === undefined) {
      if (!handler) {
        return false;
      }

      if (handler.canExecute) {
        result = handler.canExecute(context);
      }
    }

    return result;
  };


  /**
   * Clear the command stack, erasing all undo / redo history
   */
  CommandStack.prototype.clear = function(emit) {
    this._stack.length = 0;
    this._stackIdx = -1;

    if (emit !== false) {
      this._fire('changed', { trigger: 'clear' });
    }
  };


  /**
   * Undo last command(s)
   */
  CommandStack.prototype.undo = function() {
    var action = this._getUndoAction(),
        next;

    if (action) {
      this._currentExecution.trigger = 'undo';

      this._pushAction(action);

      while (action) {
        this._internalUndo(action);
        next = this._getUndoAction();

        if (!next || next.id !== action.id) {
          break;
        }

        action = next;
      }

      this._popAction();
    }
  };


  /**
   * Redo last command(s)
   */
  CommandStack.prototype.redo = function() {
    var action = this._getRedoAction(),
        next;

    if (action) {
      this._currentExecution.trigger = 'redo';

      this._pushAction(action);

      while (action) {
        this._internalExecute(action, true);
        next = this._getRedoAction();

        if (!next || next.id !== action.id) {
          break;
        }

        action = next;
      }

      this._popAction();
    }
  };


  /**
   * Register a handler instance with the command stack
   *
   * @param {string} command
   * @param {CommandHandler} handler
   */
  CommandStack.prototype.register = function(command, handler) {
    this._setHandler(command, handler);
  };


  /**
   * Register a handler type with the command stack
   * by instantiating it and injecting its dependencies.
   *
   * @param {string} command
   * @param {Function} a constructor for a {@link CommandHandler}
   */
  CommandStack.prototype.registerHandler = function(command, handlerCls) {

    if (!command || !handlerCls) {
      throw new Error('command and handlerCls must be defined');
    }

    var handler = this._injector.instantiate(handlerCls);
    this.register(command, handler);
  };

  CommandStack.prototype.canUndo = function() {
    return !!this._getUndoAction();
  };

  CommandStack.prototype.canRedo = function() {
    return !!this._getRedoAction();
  };

  // stack access  //////////////////////

  CommandStack.prototype._getRedoAction = function() {
    return this._stack[this._stackIdx + 1];
  };


  CommandStack.prototype._getUndoAction = function() {
    return this._stack[this._stackIdx];
  };


  // internal functionality //////////////////////

  CommandStack.prototype._internalUndo = function(action) {
    var self = this;

    var command = action.command,
        context = action.context;

    var handler = this._getHandler(command);

    // guard against illegal nested command stack invocations
    this._atomicDo(function() {
      self._fire(command, 'revert', action);

      if (handler.revert) {
        self._markDirty(handler.revert(context));
      }

      self._revertedAction(action);

      self._fire(command, 'reverted', action);
    });
  };


  CommandStack.prototype._fire = function(command, qualifier, event) {
    if (arguments.length < 3) {
      event = qualifier;
      qualifier = null;
    }

    var names = qualifier ? [ command + '.' + qualifier, qualifier ] : [ command ],
        i, name, result;

    event = this._eventBus.createEvent(event);

    for (i = 0; (name = names[i]); i++) {
      result = this._eventBus.fire('commandStack.' + name, event);

      if (event.cancelBubble) {
        break;
      }
    }

    return result;
  };

  CommandStack.prototype._createId = function() {
    return this._uid++;
  };

  CommandStack.prototype._atomicDo = function(fn) {

    var execution = this._currentExecution;

    execution.atomic = true;

    try {
      fn();
    } finally {
      execution.atomic = false;
    }
  };

  CommandStack.prototype._internalExecute = function(action, redo) {
    var self = this;

    var command = action.command,
        context = action.context;

    var handler = this._getHandler(command);

    if (!handler) {
      throw new Error('no command handler registered for <' + command + '>');
    }

    this._pushAction(action);

    if (!redo) {
      this._fire(command, 'preExecute', action);

      if (handler.preExecute) {
        handler.preExecute(context);
      }

      this._fire(command, 'preExecuted', action);
    }

    // guard against illegal nested command stack invocations
    this._atomicDo(function() {

      self._fire(command, 'execute', action);

      if (handler.execute) {

        // actual execute + mark return results as dirty
        self._markDirty(handler.execute(context));
      }

      // log to stack
      self._executedAction(action, redo);

      self._fire(command, 'executed', action);
    });

    if (!redo) {
      this._fire(command, 'postExecute', action);

      if (handler.postExecute) {
        handler.postExecute(context);
      }

      this._fire(command, 'postExecuted', action);
    }

    this._popAction(action);
  };


  CommandStack.prototype._pushAction = function(action) {

    var execution = this._currentExecution,
        actions = execution.actions;

    var baseAction = actions[0];

    if (execution.atomic) {
      throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')');
    }

    if (!action.id) {
      action.id = (baseAction && baseAction.id) || this._createId();
    }

    actions.push(action);
  };


  CommandStack.prototype._popAction = function() {
    var execution = this._currentExecution,
        trigger = execution.trigger,
        actions = execution.actions,
        dirty = execution.dirty;

    actions.pop();

    if (!actions.length) {
      this._eventBus.fire('elements.changed', { elements: uniqueBy('id', dirty.reverse()) });

      dirty.length = 0;

      this._fire('changed', { trigger: trigger });

      execution.trigger = null;
    }
  };


  CommandStack.prototype._markDirty = function(elements) {
    var execution = this._currentExecution;

    if (!elements) {
      return;
    }

    elements = isArray$3(elements) ? elements : [ elements ];

    execution.dirty = execution.dirty.concat(elements);
  };


  CommandStack.prototype._executedAction = function(action, redo) {
    var stackIdx = ++this._stackIdx;

    if (!redo) {
      this._stack.splice(stackIdx, this._stack.length, action);
    }
  };


  CommandStack.prototype._revertedAction = function(action) {
    this._stackIdx--;
  };


  CommandStack.prototype._getHandler = function(command) {
    return this._handlerMap[command];
  };

  CommandStack.prototype._setHandler = function(command, handler) {
    if (!command || !handler) {
      throw new Error('command and handler required');
    }

    if (this._handlerMap[command]) {
      throw new Error('overriding handler for command <' + command + '>');
    }

    this._handlerMap[command] = handler;
  };

  var CommandModule = {
    commandStack: [ 'type', CommandStack ]
  };

  // document wide unique tooltip ids
  var ids = new IdGenerator('tt');


  function createRoot(parentNode) {
    var root = domify(
      '<div class="djs-tooltip-container" />'
    );

    assign$1(root, {
      position: 'absolute',
      width: '0',
      height: '0'
    });

    parentNode.insertBefore(root, parentNode.firstChild);

    return root;
  }


  function setPosition(el, x, y) {
    assign$1(el, { left: x + 'px', top: y + 'px' });
  }

  function setVisible(el, visible) {
    el.style.display = visible === false ? 'none' : '';
  }


  var tooltipClass = 'djs-tooltip',
      tooltipSelector = '.' + tooltipClass;

  /**
   * A service that allows users to render tool tips on the diagram.
   *
   * The tooltip service will take care of updating the tooltip positioning
   * during navigation + zooming.
   *
   * @example
   *
   * ```javascript
   *
   * // add a pink badge on the top left of the shape
   * tooltips.add({
   *   position: {
   *     x: 50,
   *     y: 100
   *   },
   *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
   * });
   *
   * // or with optional life span
   * tooltips.add({
   *   position: {
   *     top: -5,
   *     left: -5
   *   },
   *   html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>',
   *   ttl: 2000
   * });
   *
   * // remove a tool tip
   * var id = tooltips.add(...);
   * tooltips.remove(id);
   * ```
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function Tooltips(eventBus, canvas) {

    this._eventBus = eventBus;
    this._canvas = canvas;

    this._ids = ids;

    this._tooltipDefaults = {
      show: {
        minZoom: 0.7,
        maxZoom: 5.0
      }
    };

    /**
     * Mapping tooltipId -> tooltip
     */
    this._tooltips = {};

    // root html element for all tooltips
    this._tooltipRoot = createRoot(canvas.getContainer());


    var self = this;

    delegate.bind(this._tooltipRoot, tooltipSelector, 'mousedown', function(event) {
      event.stopPropagation();
    });

    delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseover', function(event) {
      self.trigger('mouseover', event);
    });

    delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseout', function(event) {
      self.trigger('mouseout', event);
    });

    this._init();
  }


  Tooltips.$inject = [ 'eventBus', 'canvas' ];


  /**
   * Adds a HTML tooltip to the diagram
   *
   * @param {Object}               tooltip   the tooltip configuration
   *
   * @param {string|DOMElement}    tooltip.html                 html element to use as an tooltip
   * @param {Object}               [tooltip.show]               show configuration
   * @param {number}               [tooltip.show.minZoom]       minimal zoom level to show the tooltip
   * @param {number}               [tooltip.show.maxZoom]       maximum zoom level to show the tooltip
   * @param {Object}               tooltip.position             where to attach the tooltip
   * @param {number}               [tooltip.position.left]      relative to element bbox left attachment
   * @param {number}               [tooltip.position.top]       relative to element bbox top attachment
   * @param {number}               [tooltip.position.bottom]    relative to element bbox bottom attachment
   * @param {number}               [tooltip.position.right]     relative to element bbox right attachment
   * @param {number}               [tooltip.timeout=-1]
   *
   * @return {string}              id that may be used to reference the tooltip for update or removal
   */
  Tooltips.prototype.add = function(tooltip) {

    if (!tooltip.position) {
      throw new Error('must specifiy tooltip position');
    }

    if (!tooltip.html) {
      throw new Error('must specifiy tooltip html');
    }

    var id = this._ids.next();

    tooltip = assign({}, this._tooltipDefaults, tooltip, {
      id: id
    });

    this._addTooltip(tooltip);

    if (tooltip.timeout) {
      this.setTimeout(tooltip);
    }

    return id;
  };

  Tooltips.prototype.trigger = function(action, event) {

    var node = event.delegateTarget || event.target;

    var tooltip = this.get(attr$1(node, 'data-tooltip-id'));

    if (!tooltip) {
      return;
    }

    if (action === 'mouseover' && tooltip.timeout) {
      this.clearTimeout(tooltip);
    }

    if (action === 'mouseout' && tooltip.timeout) {

      // cut timeout after mouse out
      tooltip.timeout = 1000;

      this.setTimeout(tooltip);
    }
  };

  /**
   * Get a tooltip with the given id
   *
   * @param {string} id
   */
  Tooltips.prototype.get = function(id) {

    if (typeof id !== 'string') {
      id = id.id;
    }

    return this._tooltips[id];
  };

  Tooltips.prototype.clearTimeout = function(tooltip) {

    tooltip = this.get(tooltip);

    if (!tooltip) {
      return;
    }

    var removeTimer = tooltip.removeTimer;

    if (removeTimer) {
      clearTimeout(removeTimer);
      tooltip.removeTimer = null;
    }
  };

  Tooltips.prototype.setTimeout = function(tooltip) {

    tooltip = this.get(tooltip);

    if (!tooltip) {
      return;
    }

    this.clearTimeout(tooltip);

    var self = this;

    tooltip.removeTimer = setTimeout(function() {
      self.remove(tooltip);
    }, tooltip.timeout);
  };

  /**
   * Remove an tooltip with the given id
   *
   * @param {string} id
   */
  Tooltips.prototype.remove = function(id) {

    var tooltip = this.get(id);

    if (tooltip) {
      remove$2(tooltip.html);
      remove$2(tooltip.htmlContainer);

      delete tooltip.htmlContainer;

      delete this._tooltips[tooltip.id];
    }
  };


  Tooltips.prototype.show = function() {
    setVisible(this._tooltipRoot);
  };


  Tooltips.prototype.hide = function() {
    setVisible(this._tooltipRoot, false);
  };


  Tooltips.prototype._updateRoot = function(viewbox) {
    var a = viewbox.scale || 1;
    var d = viewbox.scale || 1;

    var matrix = 'matrix(' + a + ',0,0,' + d + ',' + (-1 * viewbox.x * a) + ',' + (-1 * viewbox.y * d) + ')';

    this._tooltipRoot.style.transform = matrix;
    this._tooltipRoot.style['-ms-transform'] = matrix;
  };


  Tooltips.prototype._addTooltip = function(tooltip) {

    var id = tooltip.id,
        html = tooltip.html,
        htmlContainer,
        tooltipRoot = this._tooltipRoot;

    // unwrap jquery (for those who need it)
    if (html.get && html.constructor.prototype.jquery) {
      html = html.get(0);
    }

    // create proper html elements from
    // tooltip HTML strings
    if (isString(html)) {
      html = domify(html);
    }

    htmlContainer = domify('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '">');
    assign$1(htmlContainer, { position: 'absolute' });

    htmlContainer.appendChild(html);

    if (tooltip.type) {
      classes$1(htmlContainer).add('djs-tooltip-' + tooltip.type);
    }

    if (tooltip.className) {
      classes$1(htmlContainer).add(tooltip.className);
    }

    tooltip.htmlContainer = htmlContainer;

    tooltipRoot.appendChild(htmlContainer);

    this._tooltips[id] = tooltip;

    this._updateTooltip(tooltip);
  };


  Tooltips.prototype._updateTooltip = function(tooltip) {

    var position = tooltip.position,
        htmlContainer = tooltip.htmlContainer;

    // update overlay html based on tooltip x, y

    setPosition(htmlContainer, position.x, position.y);
  };


  Tooltips.prototype._updateTooltipVisibilty = function(viewbox) {

    forEach$1(this._tooltips, function(tooltip) {
      var show = tooltip.show,
          htmlContainer = tooltip.htmlContainer,
          visible = true;

      if (show) {
        if (show.minZoom > viewbox.scale ||
            show.maxZoom < viewbox.scale) {
          visible = false;
        }

        setVisible(htmlContainer, visible);
      }
    });
  };

  Tooltips.prototype._init = function() {

    var self = this;

    // scroll/zoom integration

    function updateViewbox(viewbox) {
      self._updateRoot(viewbox);
      self._updateTooltipVisibilty(viewbox);

      self.show();
    }

    this._eventBus.on('canvas.viewbox.changing', function(event) {
      self.hide();
    });

    this._eventBus.on('canvas.viewbox.changed', function(event) {
      updateViewbox(event.viewbox);
    });
  };

  var TooltipsModule = {
    __init__: [ 'tooltips' ],
    tooltips: [ 'type', Tooltips ]
  };

  /**
   * Remove from the beginning of a collection until it is empty.
   *
   * This is a null-safe operation that ensures elements
   * are being removed from the given collection until the
   * collection is empty.
   *
   * The implementation deals with the fact that a remove operation
   * may touch, i.e. remove multiple elements in the collection
   * at a time.
   *
   * @param {Array<Object>} [collection]
   * @param {Function} removeFn
   *
   * @return {Array<Object>} the cleared collection
   */
  function saveClear(collection, removeFn) {

    if (typeof removeFn !== 'function') {
      throw new Error('removeFn iterator must be a function');
    }

    if (!collection) {
      return;
    }

    var e;

    while ((e = collection[0])) {
      removeFn(e);
    }

    return collection;
  }

  var LOW_PRIORITY$6 = 250,
      HIGH_PRIORITY$5 = 1400;


  /**
   * A handler that makes sure labels are properly moved with
   * their label targets.
   *
   * @param {didi.Injector} injector
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function LabelSupport(injector, eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    var movePreview = injector.get('movePreview', false);

    // remove labels from the collection that are being
    // moved with other elements anyway
    eventBus.on('shape.move.start', HIGH_PRIORITY$5, function(e) {

      var context = e.context,
          shapes = context.shapes,
          validatedShapes = context.validatedShapes;

      context.shapes = removeLabels(shapes);
      context.validatedShapes = removeLabels(validatedShapes);
    });

    // add labels to visual's group
    movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$6, function(e) {

      var context = e.context,
          shapes = context.shapes;

      var labels = [];

      forEach$1(shapes, function(element) {

        forEach$1(element.labels, function(label) {

          if (!label.hidden && context.shapes.indexOf(label) === -1) {
            labels.push(label);
          }

          if (element.labelTarget) {
            labels.push(element);
          }
        });
      });

      forEach$1(labels, function(label) {
        movePreview.makeDraggable(context, label, true);
      });

    });

    // add all labels to move closure
    this.preExecuted('elements.move', HIGH_PRIORITY$5, function(e) {
      var context = e.context,
          closure = context.closure,
          enclosedElements = closure.enclosedElements;

      var enclosedLabels = [];

      // find labels that are not part of
      // move closure yet and add them
      forEach$1(enclosedElements, function(element) {
        forEach$1(element.labels, function(label) {

          if (!enclosedElements[label.id]) {
            enclosedLabels.push(label);
          }
        });
      });

      closure.addAll(enclosedLabels);
    });


    this.preExecute([
      'connection.delete',
      'shape.delete'
    ], function(e) {

      var context = e.context,
          element = context.connection || context.shape;

      saveClear(element.labels, function(label) {
        modeling.removeShape(label, { nested: true });
      });
    });


    this.execute('shape.delete', function(e) {

      var context = e.context,
          shape = context.shape,
          labelTarget = shape.labelTarget;

      // unset labelTarget
      if (labelTarget) {
        context.labelTargetIndex = indexOf(labelTarget.labels, shape);
        context.labelTarget = labelTarget;

        shape.labelTarget = null;
      }
    });

    this.revert('shape.delete', function(e) {

      var context = e.context,
          shape = context.shape,
          labelTarget = context.labelTarget,
          labelTargetIndex = context.labelTargetIndex;

      // restore labelTarget
      if (labelTarget) {
        add(labelTarget.labels, shape, labelTargetIndex);

        shape.labelTarget = labelTarget;
      }
    });

  }

  e(LabelSupport, CommandInterceptor);

  LabelSupport.$inject = [
    'injector',
    'eventBus',
    'modeling'
  ];


  /**
   * Return a filtered list of elements that do not
   * contain attached elements with hosts being part
   * of the selection.
   *
   * @param  {Array<djs.model.Base>} elements
   *
   * @return {Array<djs.model.Base>} filtered
   */
  function removeLabels(elements) {

    return filter(elements, function(element) {

      // filter out labels that are move together
      // with their label targets
      return elements.indexOf(element.labelTarget) === -1;
    });
  }

  var LabelSupportModule = {
    __init__: [ 'labelSupport' ],
    labelSupport: [ 'type', LabelSupport ]
  };

  var LOW_PRIORITY$5 = 251,
      HIGH_PRIORITY$4 = 1401;

  var MARKER_ATTACH$1 = 'attach-ok';


  /**
   * Adds the notion of attached elements to the modeler.
   *
   * Optionally depends on `diagram-js/lib/features/move` to render
   * the attached elements during move preview.
   *
   * Optionally depends on `diagram-js/lib/features/label-support`
   * to render attached labels during move preview.
   *
   * @param {didi.Injector} injector
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Rules} rules
   * @param {Modeling} modeling
   */
  function AttachSupport(injector, eventBus, canvas, rules, modeling) {

    CommandInterceptor.call(this, eventBus);

    var movePreview = injector.get('movePreview', false);


    // remove all the attached elements from the shapes to be validated
    // add all the attached shapes to the overall list of moved shapes
    eventBus.on('shape.move.start', HIGH_PRIORITY$4, function(e) {

      var context = e.context,
          shapes = context.shapes,
          validatedShapes = context.validatedShapes;

      context.shapes = addAttached(shapes);

      context.validatedShapes = removeAttached(validatedShapes);
    });

    // add attachers to the visual's group
    movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$5, function(e) {

      var context = e.context,
          shapes = context.shapes,
          attachers = getAttachers(shapes);

      forEach$1(attachers, function(attacher) {
        movePreview.makeDraggable(context, attacher, true);

        forEach$1(attacher.labels, function(label) {
          movePreview.makeDraggable(context, label, true);
        });
      });
    });

    // add attach-ok marker to current host
    movePreview && eventBus.on('shape.move.start', function(event) {
      var context = event.context,
          shapes = context.shapes;

      if (shapes.length !== 1) {
        return;
      }

      var shape = shapes[0];

      var host = shape.host;

      if (host) {
        canvas.addMarker(host, MARKER_ATTACH$1);

        eventBus.once([
          'shape.move.out',
          'shape.move.cleanup'
        ], function() {
          canvas.removeMarker(host, MARKER_ATTACH$1);
        });
      }
    });

    // add all attachers to move closure
    this.preExecuted('elements.move', HIGH_PRIORITY$4, function(e) {
      var context = e.context,
          closure = context.closure,
          shapes = context.shapes,
          attachers = getAttachers(shapes);

      forEach$1(attachers, function(attacher) {
        closure.add(attacher, closure.topLevel[attacher.host.id]);
      });
    });

    // perform the attaching after shapes are done moving
    this.postExecuted('elements.move', function(e) {

      var context = e.context,
          shapes = context.shapes,
          newHost = context.newHost,
          attachers;

      // only single elements can be attached
      // multiply elements can be detached
      if (newHost && shapes.length !== 1) {
        return;
      }

      if (newHost) {
        attachers = shapes;
      } else {

        // find attachers moved without host
        attachers = filter(shapes, function(shape) {
          var host = shape.host;

          return isAttacher(shape) && !includes$4(shapes, host);
        });
      }

      forEach$1(attachers, function(attacher) {
        modeling.updateAttachment(attacher, newHost);
      });
    });

    // ensure invalid attachment connections are removed
    this.postExecuted('elements.move', function(e) {

      var shapes = e.context.shapes;

      forEach$1(shapes, function(shape) {

        forEach$1(shape.attachers, function(attacher) {

          // remove invalid outgoing connections
          forEach$1(attacher.outgoing.slice(), function(connection) {
            var allowed = rules.allowed('connection.reconnect', {
              connection: connection,
              source: connection.source,
              target: connection.target
            });

            if (!allowed) {
              modeling.removeConnection(connection);
            }
          });

          // remove invalid incoming connections
          forEach$1(attacher.incoming.slice(), function(connection) {
            var allowed = rules.allowed('connection.reconnect', {
              connection: connection,
              source: connection.source,
              target: connection.target
            });

            if (!allowed) {
              modeling.removeConnection(connection);
            }
          });
        });
      });
    });

    this.postExecute('shape.create', function(e) {
      var context = e.context,
          shape = context.shape,
          host = context.host;

      if (host) {
        modeling.updateAttachment(shape, host);
      }
    });

    // update attachments if the host is replaced
    this.postExecute('shape.replace', function(e) {

      var context = e.context,
          oldShape = context.oldShape,
          newShape = context.newShape;

      // move the attachers to the new host
      saveClear(oldShape.attachers, function(attacher) {
        var allowed = rules.allowed('elements.move', {
          target: newShape,
          shapes: [ attacher ]
        });

        if (allowed === 'attach') {
          modeling.updateAttachment(attacher, newShape);
        } else {
          modeling.removeShape(attacher);
        }
      });

      // move attachers if new host has different size
      if (newShape.attachers.length) {

        forEach$1(newShape.attachers, function(attacher) {
          var delta = getNewAttachShapeDelta(attacher, oldShape, newShape);
          modeling.moveShape(attacher, delta, attacher.parent);
        });
      }

    });

    // move shape on host resize
    this.postExecute('shape.resize', function(event) {
      var context = event.context,
          shape = context.shape,
          oldBounds = context.oldBounds,
          newBounds = context.newBounds,
          attachers = shape.attachers,
          hints = context.hints || {};

      if (hints.attachSupport === false) {
        return;
      }

      forEach$1(attachers, function(attacher) {
        var delta = getNewAttachShapeDelta(attacher, oldBounds, newBounds);

        modeling.moveShape(attacher, delta, attacher.parent);

        forEach$1(attacher.labels, function(label) {
          modeling.moveShape(label, delta, label.parent);
        });
      });
    });

    // remove attachments
    this.preExecute('shape.delete', function(event) {

      var shape = event.context.shape;

      saveClear(shape.attachers, function(attacher) {
        modeling.removeShape(attacher);
      });

      if (shape.host) {
        modeling.updateAttachment(shape, null);
      }
    });
  }

  e(AttachSupport, CommandInterceptor);

  AttachSupport.$inject = [
    'injector',
    'eventBus',
    'canvas',
    'rules',
    'modeling'
  ];


  /**
   * Return attachers of the given shapes
   *
   * @param {Array<djs.model.Base>} shapes
   * @return {Array<djs.model.Base>}
   */
  function getAttachers(shapes) {
    return flatten(map(shapes, function(s) {
      return s.attachers || [];
    }));
  }

  /**
   * Return a combined list of elements and
   * attachers.
   *
   * @param {Array<djs.model.Base>} elements
   * @return {Array<djs.model.Base>} filtered
   */
  function addAttached(elements) {
    var attachers = getAttachers(elements);

    return unionBy('id', elements, attachers);
  }

  /**
   * Return a filtered list of elements that do not
   * contain attached elements with hosts being part
   * of the selection.
   *
   * @param  {Array<djs.model.Base>} elements
   *
   * @return {Array<djs.model.Base>} filtered
   */
  function removeAttached(elements) {

    var ids = groupBy(elements, 'id');

    return filter(elements, function(element) {
      while (element) {

        // host in selection
        if (element.host && ids[element.host.id]) {
          return false;
        }

        element = element.parent;
      }

      return true;
    });
  }

  function isAttacher(shape) {
    return !!shape.host;
  }

  function includes$4(array, item) {
    return array.indexOf(item) !== -1;
  }

  var AttachSupportModule = {
    __depends__: [
      RulesModule$1
    ],
    __init__: [ 'attachSupport' ],
    attachSupport: [ 'type', AttachSupport ]
  };

  var LOW_PRIORITY$4 = 250;

  /**
   * The tool manager acts as middle-man between the available tool's and the Palette,
   * it takes care of making sure that the correct active state is set.
   *
   * @param  {Object}    eventBus
   * @param  {Object}    dragging
   */
  function ToolManager(eventBus, dragging) {
    this._eventBus = eventBus;
    this._dragging = dragging;

    this._tools = [];
    this._active = null;
  }

  ToolManager.$inject = [ 'eventBus', 'dragging' ];

  ToolManager.prototype.registerTool = function(name, events) {
    var tools = this._tools;

    if (!events) {
      throw new Error('A tool has to be registered with it\'s "events"');
    }

    tools.push(name);

    this.bindEvents(name, events);
  };

  ToolManager.prototype.isActive = function(tool) {
    return tool && this._active === tool;
  };

  ToolManager.prototype.length = function(tool) {
    return this._tools.length;
  };

  ToolManager.prototype.setActive = function(tool) {
    var eventBus = this._eventBus;

    if (this._active !== tool) {
      this._active = tool;

      eventBus.fire('tool-manager.update', { tool: tool });
    }
  };

  ToolManager.prototype.bindEvents = function(name, events) {
    var eventBus = this._eventBus,
        dragging = this._dragging;

    var eventsToRegister = [];

    eventBus.on(events.tool + '.init', function(event) {
      var context = event.context;

      // Active tools that want to reactivate themselves must do this explicitly
      if (!context.reactivate && this.isActive(name)) {
        this.setActive(null);

        dragging.cancel();
        return;
      }

      this.setActive(name);

    }, this);

    // Todo[ricardo]: add test cases
    forEach$1(events, function(event) {
      eventsToRegister.push(event + '.ended');
      eventsToRegister.push(event + '.canceled');
    });

    eventBus.on(eventsToRegister, LOW_PRIORITY$4, function(event) {

      // We defer the de-activation of the tool to the .activate phase,
      // so we're able to check if we want to toggle off the current
      // active tool or switch to a new one
      if (!this._active) {
        return;
      }

      if (isPaletteClick(event)) {
        return;
      }

      this.setActive(null);
    }, this);

  };


  // helpers ///////////////

  /**
   * Check if a given event is a palette click event.
   *
   * @param {EventBus.Event} event
   *
   * @return {boolean}
   */
  function isPaletteClick(event) {
    var target = event.originalEvent && event.originalEvent.target;

    return target && closest(target, '.group[data-group="tools"]');
  }

  var ToolManagerModule = {
    __depends__: [
      DraggingModule
    ],
    __init__: [ 'toolManager' ],
    toolManager: [ 'type', ToolManager ]
  };

  /**
   * Return direction given axis and delta.
   *
   * @param {string} axis
   * @param {number} delta
   *
   * @return {string}
   */
  function getDirection(axis, delta) {

    if (axis === 'x') {
      if (delta > 0) {
        return 'e';
      }

      if (delta < 0) {
        return 'w';
      }
    }

    if (axis === 'y') {
      if (delta > 0) {
        return 's';
      }

      if (delta < 0) {
        return 'n';
      }
    }

    return null;
  }

  /**
   * Returns connections whose waypoints are to be updated. Waypoints are to be updated if start
   * or end is to be moved or resized.
   *
   * @param {Array<djs.model.Shape} movingShapes
   * @param {Array<djs.model.Shape} resizingShapes
   *
   * @returns {Array<djs.model.Connection>}
   */
  function getWaypointsUpdatingConnections(movingShapes, resizingShapes) {
    var waypointsUpdatingConnections = [];

    forEach$1(movingShapes.concat(resizingShapes), function(shape) {
      var incoming = shape.incoming,
          outgoing = shape.outgoing;

      forEach$1(incoming.concat(outgoing), function(connection) {
        var source = connection.source,
            target = connection.target;

        if (includes$3(movingShapes, source) ||
          includes$3(movingShapes, target) ||
          includes$3(resizingShapes, source) ||
          includes$3(resizingShapes, target)) {

          if (!includes$3(waypointsUpdatingConnections, connection)) {
            waypointsUpdatingConnections.push(connection);
          }
        }
      });
    });

    return waypointsUpdatingConnections;
  }

  function includes$3(array, item) {
    return array.indexOf(item) !== -1;
  }

  /**
   * Resize bounds.
   *
   * @param {Object} bounds
   * @param {number} bounds.x
   * @param {number} bounds.y
   * @param {number} bounds.width
   * @param {number} bounds.height
   * @param {string} direction
   * @param {Object} delta
   * @param {number} delta.x
   * @param {number} delta.y
   *
   * @return {Object}
   */
  function resizeBounds(bounds, direction, delta) {
    var x = bounds.x,
        y = bounds.y,
        width = bounds.width,
        height = bounds.height,
        dx = delta.x,
        dy = delta.y;

    switch (direction) {
    case 'n':
      return {
        x: x,
        y: y + dy,
        width: width,
        height: height - dy
      };
    case 's':
      return {
        x: x,
        y: y,
        width: width,
        height: height + dy
      };
    case 'w':
      return {
        x: x + dx,
        y: y,
        width: width - dx,
        height: height
      };
    case 'e':
      return {
        x: x,
        y: y,
        width: width + dx,
        height: height
      };
    default:
      throw new Error('unknown direction: ' + direction);
    }
  }

  var abs$1 = Math.abs,
      round$4 = Math.round;

  var AXIS_TO_DIMENSION = {
    x: 'width',
    y: 'height'
  };

  var CURSOR_CROSSHAIR = 'crosshair';

  var DIRECTION_TO_TRBL = {
    n: 'top',
    w: 'left',
    s: 'bottom',
    e: 'right'
  };

  var HIGH_PRIORITY$3 = 1500;

  var DIRECTION_TO_OPPOSITE = {
    n: 's',
    w: 'e',
    s: 'n',
    e: 'w'
  };

  var PADDING = 20;


  /**
   * Add or remove space by moving and resizing elements.
   *
   * @param {Canvas} canvas
   * @param {Dragging} dragging
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {Rules} rules
   * @param {ToolManager} toolManager
   * @param {Mouse} mouse
   */
  function SpaceTool(
      canvas, dragging, eventBus,
      modeling, rules, toolManager,
      mouse) {

    this._canvas = canvas;
    this._dragging = dragging;
    this._eventBus = eventBus;
    this._modeling = modeling;
    this._rules = rules;
    this._toolManager = toolManager;
    this._mouse = mouse;

    var self = this;

    toolManager.registerTool('space', {
      tool: 'spaceTool.selection',
      dragging: 'spaceTool'
    });

    eventBus.on('spaceTool.selection.end', function(event) {
      eventBus.once('spaceTool.selection.ended', function() {
        self.activateMakeSpace(event.originalEvent);
      });
    });

    eventBus.on('spaceTool.move', HIGH_PRIORITY$3 , function(event) {
      var context = event.context,
          initialized = context.initialized;

      if (!initialized) {
        initialized = context.initialized = self.init(event, context);
      }

      if (initialized) {
        ensureConstraints(event);
      }
    });

    eventBus.on('spaceTool.end', function(event) {
      var context = event.context,
          axis = context.axis,
          direction = context.direction,
          movingShapes = context.movingShapes,
          resizingShapes = context.resizingShapes,
          start = context.start;

      if (!context.initialized) {
        return;
      }

      ensureConstraints(event);

      var delta = {
        x: 0,
        y: 0
      };

      delta[ axis ] = round$4(event[ 'd' + axis ]);

      self.makeSpace(movingShapes, resizingShapes, delta, direction, start);

      eventBus.once('spaceTool.ended', function(event) {

        // activate space tool selection after make space
        self.activateSelection(event.originalEvent, true, true);
      });
    });
  }

  SpaceTool.$inject = [
    'canvas',
    'dragging',
    'eventBus',
    'modeling',
    'rules',
    'toolManager',
    'mouse'
  ];

  /**
   * Activate space tool selection.
   *
   * @param {Object} event
   * @param {boolean} autoActivate
   */
  SpaceTool.prototype.activateSelection = function(event, autoActivate, reactivate) {
    this._dragging.init(event, 'spaceTool.selection', {
      autoActivate: autoActivate,
      cursor: CURSOR_CROSSHAIR,
      data: {
        context: {
          reactivate: reactivate
        }
      },
      trapClick: false
    });
  };

  /**
   * Activate space tool make space.
   *
   * @param  {MouseEvent} event
   */
  SpaceTool.prototype.activateMakeSpace = function(event) {
    this._dragging.init(event, 'spaceTool', {
      autoActivate: true,
      cursor: CURSOR_CROSSHAIR,
      data: {
        context: {}
      }
    });
  };

  /**
   * Make space.
   *
   * @param  {Array<djs.model.Shape>} movingShapes
   * @param  {Array<djs.model.Shape>} resizingShapes
   * @param  {Object} delta
   * @param  {number} delta.x
   * @param  {number} delta.y
   * @param  {string} direction
   * @param  {number} start
   */
  SpaceTool.prototype.makeSpace = function(movingShapes, resizingShapes, delta, direction, start) {
    return this._modeling.createSpace(movingShapes, resizingShapes, delta, direction, start);
  };

  /**
   * Initialize make space and return true if that was successful.
   *
   * @param {Object} event
   * @param {Object} context
   *
   * @return {boolean}
   */
  SpaceTool.prototype.init = function(event, context) {
    var axis = abs$1(event.dx) > abs$1(event.dy) ? 'x' : 'y',
        delta = event[ 'd' + axis ],
        start = event[ axis ] - delta;

    if (abs$1(delta) < 5) {
      return false;
    }

    // invert delta to remove space when moving left
    if (delta < 0) {
      delta *= -1;
    }

    // invert delta to add/remove space when removing/adding space if modifier key is pressed
    if (hasPrimaryModifier(event)) {
      delta *= -1;
    }

    var direction = getDirection(axis, delta);

    var root = this._canvas.getRootElement();

    var children = selfAndAllChildren(root, true);

    var elements = this.calculateAdjustments(children, axis, delta, start);

    var minDimensions = this._eventBus.fire('spaceTool.getMinDimensions', {
      axis: axis,
      direction: direction,
      shapes: elements.resizingShapes,
      start: start
    });

    var spaceToolConstraints = getSpaceToolConstraints(elements, axis, direction, start, minDimensions);

    assign(
      context,
      elements,
      {
        axis: axis,
        direction: direction,
        spaceToolConstraints: spaceToolConstraints,
        start: start
      }
    );

    set('resize-' + (axis === 'x' ? 'ew' : 'ns'));

    return true;
  };

  /**
   * Get elements to be moved and resized.
   *
   * @param  {Array<djs.model.Shape>} elements
   * @param  {string} axis
   * @param  {number} delta
   * @param  {number} start
   *
   * @return {Object}
   */
  SpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {
    var rules = this._rules;

    var movingShapes = [],
        resizingShapes = [];

    forEach$1(elements, function(element) {
      if (!element.parent || isConnection$7(element)) {
        return;
      }

      var shapeStart = element[ axis ],
          shapeEnd = shapeStart + element[ AXIS_TO_DIMENSION[ axis ] ];

      // shape to be moved
      if ((delta > 0 && shapeStart > start) || (delta < 0 && shapeEnd < start)) {
        return movingShapes.push(element);
      }

      // shape to be resized
      if (shapeStart < start &&
        shapeEnd > start &&
        rules.allowed('shape.resize', { shape: element })
      ) {

        return resizingShapes.push(element);
      }
    });

    return {
      movingShapes: movingShapes,
      resizingShapes: resizingShapes
    };
  };

  SpaceTool.prototype.toggle = function() {

    if (this.isActive()) {
      return this._dragging.cancel();
    }

    var mouseEvent = this._mouse.getLastMoveEvent();

    this.activateSelection(mouseEvent, !!mouseEvent);
  };

  SpaceTool.prototype.isActive = function() {
    var context = this._dragging.context();

    return context && /^spaceTool/.test(context.prefix);
  };

  // helpers //////////

  function addPadding(trbl) {
    return {
      top: trbl.top - PADDING,
      right: trbl.right + PADDING,
      bottom: trbl.bottom + PADDING,
      left: trbl.left - PADDING
    };
  }

  function ensureConstraints(event) {
    var context = event.context,
        spaceToolConstraints = context.spaceToolConstraints;

    if (!spaceToolConstraints) {
      return;
    }

    var x, y;

    if (isNumber(spaceToolConstraints.left)) {
      x = Math.max(event.x, spaceToolConstraints.left);

      event.dx = event.dx + x - event.x;
      event.x = x;
    }

    if (isNumber(spaceToolConstraints.right)) {
      x = Math.min(event.x, spaceToolConstraints.right);

      event.dx = event.dx + x - event.x;
      event.x = x;
    }

    if (isNumber(spaceToolConstraints.top)) {
      y = Math.max(event.y, spaceToolConstraints.top);

      event.dy = event.dy + y - event.y;
      event.y = y;
    }

    if (isNumber(spaceToolConstraints.bottom)) {
      y = Math.min(event.y, spaceToolConstraints.bottom);

      event.dy = event.dy + y - event.y;
      event.y = y;
    }
  }

  function getSpaceToolConstraints(elements, axis, direction, start, minDimensions) {
    var movingShapes = elements.movingShapes,
        resizingShapes = elements.resizingShapes;

    if (!resizingShapes.length) {
      return;
    }

    var spaceToolConstraints = {},
        min,
        max;

    forEach$1(resizingShapes, function(resizingShape) {
      var resizingShapeBBox = asTRBL(resizingShape);

      // find children that are not moving or resizing
      var nonMovingResizingChildren = filter(resizingShape.children, function(child) {
        return !isConnection$7(child) &&
          !isLabel$2(child) &&
          !includes$2(movingShapes, child) &&
          !includes$2(resizingShapes, child);
      });

      // find children that are moving
      var movingChildren = filter(resizingShape.children, function(child) {
        return !isConnection$7(child) && !isLabel$2(child) && includes$2(movingShapes, child);
      });

      var minOrMax,
          nonMovingResizingChildrenBBox,
          movingChildrenBBox;

      if (nonMovingResizingChildren.length) {
        nonMovingResizingChildrenBBox = addPadding(asTRBL(getBBox(nonMovingResizingChildren)));

        minOrMax = start -
          resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] +
          nonMovingResizingChildrenBBox[ DIRECTION_TO_TRBL[ direction ] ];

        if (direction === 'n') {
          spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 'w') {
          spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 's') {
          spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        } else if (direction === 'e') {
          spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        }
      }

      if (movingChildren.length) {
        movingChildrenBBox = addPadding(asTRBL(getBBox(movingChildren)));

        minOrMax = start -
          movingChildrenBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] +
          resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ];

        if (direction === 'n') {
          spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 'w') {
          spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 's') {
          spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        } else if (direction === 'e') {
          spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        }
      }

      var resizingShapeMinDimensions = minDimensions && minDimensions[ resizingShape.id ];

      if (resizingShapeMinDimensions) {
        if (direction === 'n') {
          minOrMax = start +
            resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
            resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];

          spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 'w') {
          minOrMax = start +
            resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
            resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];

          spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 's') {
          minOrMax = start -
            resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
            resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];

          spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        } else if (direction === 'e') {
          minOrMax = start -
            resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
            resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];

          spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        }
      }
    });

    return spaceToolConstraints;
  }

  function includes$2(array, item) {
    return array.indexOf(item) !== -1;
  }

  function isConnection$7(element) {
    return !!element.waypoints;
  }

  function isLabel$2(element) {
    return !!element.labelTarget;
  }

  var MARKER_DRAGGING$1 = 'djs-dragging',
      MARKER_RESIZING = 'djs-resizing';

  var LOW_PRIORITY$3 = 250;

  var max = Math.max;


  /**
   * Provides previews for selecting/moving/resizing shapes when creating/removing space.
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {Canvas} canvas
   * @param {Styles} styles
   */
  function SpaceToolPreview(
      eventBus, elementRegistry, canvas,
      styles, previewSupport) {

    function addPreviewGfx(collection, dragGroup) {
      forEach$1(collection, function(element) {
        previewSupport.addDragger(element, dragGroup);

        canvas.addMarker(element, MARKER_DRAGGING$1);
      });
    }

    // add crosshair
    eventBus.on('spaceTool.selection.start', function(event) {
      var space = canvas.getLayer('space'),
          context = event.context;

      var orientation = {
        x: 'M 0,-10000 L 0,10000',
        y: 'M -10000,0 L 10000,0'
      };

      var crosshairGroup = create$1('g');
      attr(crosshairGroup, styles.cls('djs-crosshair-group', [ 'no-events' ]));

      append(space, crosshairGroup);

      // horizontal path
      var pathX = create$1('path');
      attr(pathX, 'd', orientation.x);
      classes(pathX).add('djs-crosshair');

      append(crosshairGroup, pathX);

      // vertical path
      var pathY = create$1('path');
      attr(pathY, 'd', orientation.y);
      classes(pathY).add('djs-crosshair');

      append(crosshairGroup, pathY);

      context.crosshairGroup = crosshairGroup;
    });

    // update crosshair
    eventBus.on('spaceTool.selection.move', function(event) {
      var crosshairGroup = event.context.crosshairGroup;

      translate$2(crosshairGroup, event.x, event.y);
    });

    // remove crosshair
    eventBus.on('spaceTool.selection.cleanup', function(event) {
      var context = event.context,
          crosshairGroup = context.crosshairGroup;

      if (crosshairGroup) {
        remove$1(crosshairGroup);
      }
    });

    // add and update move/resize previews
    eventBus.on('spaceTool.move', LOW_PRIORITY$3, function(event) {

      var context = event.context,
          line = context.line,
          axis = context.axis,
          movingShapes = context.movingShapes,
          resizingShapes = context.resizingShapes;

      if (!context.initialized) {
        return;
      }

      if (!context.dragGroup) {
        var spaceLayer = canvas.getLayer('space');

        line = create$1('path');
        attr(line, 'd', 'M0,0 L0,0');
        classes(line).add('djs-crosshair');

        append(spaceLayer, line);

        context.line = line;

        var dragGroup = create$1('g');
        attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));

        append(canvas.getActiveLayer(), dragGroup);

        // shapes
        addPreviewGfx(movingShapes, dragGroup);

        // connections
        var movingConnections = context.movingConnections = elementRegistry.filter(function(element) {
          var sourceIsMoving = false;

          forEach$1(movingShapes, function(shape) {
            forEach$1(shape.outgoing, function(connection) {
              if (element === connection) {
                sourceIsMoving = true;
              }
            });
          });

          var targetIsMoving = false;

          forEach$1(movingShapes, function(shape) {
            forEach$1(shape.incoming, function(connection) {
              if (element === connection) {
                targetIsMoving = true;
              }
            });
          });

          var sourceIsResizing = false;

          forEach$1(resizingShapes, function(shape) {
            forEach$1(shape.outgoing, function(connection) {
              if (element === connection) {
                sourceIsResizing = true;
              }
            });
          });

          var targetIsResizing = false;

          forEach$1(resizingShapes, function(shape) {
            forEach$1(shape.incoming, function(connection) {
              if (element === connection) {
                targetIsResizing = true;
              }
            });
          });

          return isConnection$6(element)
            && (sourceIsMoving || sourceIsResizing)
            && (targetIsMoving || targetIsResizing);
        });


        addPreviewGfx(movingConnections, dragGroup);

        context.dragGroup = dragGroup;
      }

      if (!context.frameGroup) {
        var frameGroup = create$1('g');
        attr(frameGroup, styles.cls('djs-frame-group', [ 'no-events' ]));

        append(canvas.getActiveLayer(), frameGroup);

        var frames = [];

        forEach$1(resizingShapes, function(shape) {
          var frame = previewSupport.addFrame(shape, frameGroup);

          var initialBounds = frame.getBBox();

          frames.push({
            element: frame,
            initialBounds: initialBounds
          });

          canvas.addMarker(shape, MARKER_RESIZING);
        });

        context.frameGroup = frameGroup;
        context.frames = frames;
      }

      var orientation = {
        x: 'M' + event.x + ', -10000 L' + event.x + ', 10000',
        y: 'M -10000, ' + event.y + ' L 10000, ' + event.y
      };

      attr(line, { d: orientation[ axis ] });

      var opposite = { x: 'y', y: 'x' };
      var delta = { x: event.dx, y: event.dy };
      delta[ opposite[ context.axis ] ] = 0;

      // update move previews
      translate$2(context.dragGroup, delta.x, delta.y);

      // update resize previews
      forEach$1(context.frames, function(frame) {
        var element = frame.element,
            initialBounds = frame.initialBounds,
            width,
            height;

        if (context.direction === 'e') {
          attr(element, {
            width: max(initialBounds.width + delta.x, 5)
          });
        } else {
          width = max(initialBounds.width - delta.x, 5);

          attr(element, {
            width: width,
            x: initialBounds.x + initialBounds.width - width
          });
        }

        if (context.direction === 's') {
          attr(element, {
            height: max(initialBounds.height + delta.y, 5)
          });
        } else {
          height = max(initialBounds.height - delta.y, 5);

          attr(element, {
            height: height,
            y: initialBounds.y + initialBounds.height - height
          });
        }
      });

    });

    // remove move/resize previews
    eventBus.on('spaceTool.cleanup', function(event) {

      var context = event.context,
          movingShapes = context.movingShapes,
          movingConnections = context.movingConnections,
          resizingShapes = context.resizingShapes,
          line = context.line,
          dragGroup = context.dragGroup,
          frameGroup = context.frameGroup;

      // moving shapes
      forEach$1(movingShapes, function(shape) {
        canvas.removeMarker(shape, MARKER_DRAGGING$1);
      });

      // moving connections
      forEach$1(movingConnections, function(connection) {
        canvas.removeMarker(connection, MARKER_DRAGGING$1);
      });

      if (dragGroup) {
        remove$1(line);
        remove$1(dragGroup);
      }

      forEach$1(resizingShapes, function(shape) {
        canvas.removeMarker(shape, MARKER_RESIZING);
      });

      if (frameGroup) {
        remove$1(frameGroup);
      }
    });
  }

  SpaceToolPreview.$inject = [
    'eventBus',
    'elementRegistry',
    'canvas',
    'styles',
    'previewSupport'
  ];


  // helpers //////////////////////

  /**
   * Checks if an element is a connection.
   */
  function isConnection$6(element) {
    return element.waypoints;
  }

  var SpaceToolModule = {
    __init__: [ 'spaceToolPreview' ],
    __depends__: [
      DraggingModule,
      RulesModule$1,
      ToolManagerModule,
      PreviewSupportModule,
      MouseModule
    ],
    spaceTool: [ 'type', SpaceTool ],
    spaceToolPreview: [ 'type', SpaceToolPreview ]
  };

  function BpmnFactory(moddle) {
    this._model = moddle;
  }

  BpmnFactory.$inject = [ 'moddle' ];


  BpmnFactory.prototype._needsId = function(element) {
    return isAny(element, [
      'bpmn:RootElement',
      'bpmn:FlowElement',
      'bpmn:MessageFlow',
      'bpmn:DataAssociation',
      'bpmn:Artifact',
      'bpmn:Participant',
      'bpmn:Lane',
      'bpmn:LaneSet',
      'bpmn:Process',
      'bpmn:Collaboration',
      'bpmndi:BPMNShape',
      'bpmndi:BPMNEdge',
      'bpmndi:BPMNDiagram',
      'bpmndi:BPMNPlane',
      'bpmn:Property',
      'bpmn:CategoryValue'
    ]);
  };

  BpmnFactory.prototype._ensureId = function(element) {
    if (element.id) {
      this._model.ids.claim(element.id, element);
      return;
    }

    // generate semantic ids for elements
    // bpmn:SequenceFlow -> SequenceFlow_ID
    var prefix;

    if (is$1(element, 'bpmn:Activity')) {
      prefix = 'Activity';
    } else if (is$1(element, 'bpmn:Event')) {
      prefix = 'Event';
    } else if (is$1(element, 'bpmn:Gateway')) {
      prefix = 'Gateway';
    } else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) {
      prefix = 'Flow';
    } else {
      prefix = (element.$type || '').replace(/^[^:]*:/g, '');
    }

    prefix += '_';

    if (!element.id && this._needsId(element)) {
      element.id = this._model.ids.nextPrefixed(prefix, element);
    }
  };


  BpmnFactory.prototype.create = function(type, attrs) {
    var element = this._model.create(type, attrs || {});

    this._ensureId(element);

    return element;
  };


  BpmnFactory.prototype.createDiLabel = function() {
    return this.create('bpmndi:BPMNLabel', {
      bounds: this.createDiBounds()
    });
  };


  BpmnFactory.prototype.createDiShape = function(semantic, attrs) {
    return this.create('bpmndi:BPMNShape', assign({
      bpmnElement: semantic,
      bounds: this.createDiBounds()
    }, attrs));
  };


  BpmnFactory.prototype.createDiBounds = function(bounds) {
    return this.create('dc:Bounds', bounds);
  };


  BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
    var self = this;

    return map(waypoints, function(pos) {
      return self.createDiWaypoint(pos);
    });
  };

  BpmnFactory.prototype.createDiWaypoint = function(point) {
    return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
  };


  BpmnFactory.prototype.createDiEdge = function(semantic, attrs) {
    return this.create('bpmndi:BPMNEdge', assign({
      bpmnElement: semantic,
      waypoint: this.createDiWaypoints([])
    }, attrs));
  };

  BpmnFactory.prototype.createDiPlane = function(semantic, attrs) {
    return this.create('bpmndi:BPMNPlane', assign({
      bpmnElement: semantic
    }, attrs));
  };

  /**
   * A handler responsible for updating the underlying BPMN 2.0 XML + DI
   * once changes on the diagram happen
   */
  function BpmnUpdater(
      eventBus, bpmnFactory, connectionDocking,
      translate) {

    CommandInterceptor.call(this, eventBus);

    this._bpmnFactory = bpmnFactory;
    this._translate = translate;

    var self = this;



    // connection cropping //////////////////////

    // crop connection ends during create/update
    function cropConnection(e) {
      var context = e.context,
          hints = context.hints || {},
          connection;

      if (!context.cropped && hints.createElementsBehavior !== false) {
        connection = context.connection;
        connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
        context.cropped = true;
      }
    }

    this.executed([
      'connection.layout',
      'connection.create'
    ], cropConnection);

    this.reverted([ 'connection.layout' ], function(e) {
      delete e.context.cropped;
    });



    // BPMN + DI update //////////////////////


    // update parent
    function updateParent(e) {
      var context = e.context;

      self.updateParent(context.shape || context.connection, context.oldParent);
    }

    function reverseUpdateParent(e) {
      var context = e.context;

      var element = context.shape || context.connection,

          // oldParent is the (old) new parent, because we are undoing
          oldParent = context.parent || context.newParent;

      self.updateParent(element, oldParent);
    }

    this.executed([
      'shape.move',
      'shape.create',
      'shape.delete',
      'connection.create',
      'connection.move',
      'connection.delete'
    ], ifBpmn(updateParent));

    this.reverted([
      'shape.move',
      'shape.create',
      'shape.delete',
      'connection.create',
      'connection.move',
      'connection.delete'
    ], ifBpmn(reverseUpdateParent));

    /*
     * ## Updating Parent
     *
     * When morphing a Process into a Collaboration or vice-versa,
     * make sure that both the *semantic* and *di* parent of each element
     * is updated.
     *
     */
    function updateRoot(event) {
      var context = event.context,
          oldRoot = context.oldRoot,
          children = oldRoot.children;

      forEach$1(children, function(child) {
        if (is$1(child, 'bpmn:BaseElement')) {
          self.updateParent(child);
        }
      });
    }

    this.executed([ 'canvas.updateRoot' ], updateRoot);
    this.reverted([ 'canvas.updateRoot' ], updateRoot);


    // update bounds
    function updateBounds(e) {
      var shape = e.context.shape;

      if (!is$1(shape, 'bpmn:BaseElement')) {
        return;
      }

      self.updateBounds(shape);
    }

    this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {

      // exclude labels because they're handled separately during shape.changed
      if (event.context.shape.type === 'label') {
        return;
      }

      updateBounds(event);
    }));

    this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {

      // exclude labels because they're handled separately during shape.changed
      if (event.context.shape.type === 'label') {
        return;
      }

      updateBounds(event);
    }));

    // Handle labels separately. This is necessary, because the label bounds have to be updated
    // every time its shape changes, not only on move, create and resize.
    eventBus.on('shape.changed', function(event) {
      if (event.element.type === 'label') {
        updateBounds({ context: { shape: event.element } });
      }
    });

    // attach / detach connection
    function updateConnection(e) {
      self.updateConnection(e.context);
    }

    this.executed([
      'connection.create',
      'connection.move',
      'connection.delete',
      'connection.reconnect'
    ], ifBpmn(updateConnection));

    this.reverted([
      'connection.create',
      'connection.move',
      'connection.delete',
      'connection.reconnect'
    ], ifBpmn(updateConnection));


    // update waypoints
    function updateConnectionWaypoints(e) {
      self.updateConnectionWaypoints(e.context.connection);
    }

    this.executed([
      'connection.layout',
      'connection.move',
      'connection.updateWaypoints',
    ], ifBpmn(updateConnectionWaypoints));

    this.reverted([
      'connection.layout',
      'connection.move',
      'connection.updateWaypoints',
    ], ifBpmn(updateConnectionWaypoints));

    // update conditional/default flows
    this.executed('connection.reconnect', ifBpmn(function(event) {
      var context = event.context,
          connection = context.connection,
          oldSource = context.oldSource,
          newSource = context.newSource,
          connectionBo = getBusinessObject(connection),
          oldSourceBo = getBusinessObject(oldSource),
          newSourceBo = getBusinessObject(newSource);

      // remove condition from connection on reconnect to new source
      // if new source can NOT have condional sequence flow
      if (connectionBo.conditionExpression && !isAny(newSourceBo, [
        'bpmn:Activity',
        'bpmn:ExclusiveGateway',
        'bpmn:InclusiveGateway'
      ])) {
        context.oldConditionExpression = connectionBo.conditionExpression;

        delete connectionBo.conditionExpression;
      }

      // remove default from old source flow on reconnect to new source
      // if source changed
      if (oldSource !== newSource && oldSourceBo.default === connectionBo) {
        context.oldDefault = oldSourceBo.default;

        delete oldSourceBo.default;
      }
    }));

    this.reverted('connection.reconnect', ifBpmn(function(event) {
      var context = event.context,
          connection = context.connection,
          oldSource = context.oldSource,
          newSource = context.newSource,
          connectionBo = getBusinessObject(connection),
          oldSourceBo = getBusinessObject(oldSource),
          newSourceBo = getBusinessObject(newSource);

      // add condition to connection on revert reconnect to new source
      if (context.oldConditionExpression) {
        connectionBo.conditionExpression = context.oldConditionExpression;
      }

      // add default to old source on revert reconnect to new source
      if (context.oldDefault) {
        oldSourceBo.default = context.oldDefault;

        delete newSourceBo.default;
      }
    }));

    // update attachments
    function updateAttachment(e) {
      self.updateAttachment(e.context);
    }

    this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
    this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
  }

  e(BpmnUpdater, CommandInterceptor);

  BpmnUpdater.$inject = [
    'eventBus',
    'bpmnFactory',
    'connectionDocking',
    'translate'
  ];


  // implementation //////////////////////

  BpmnUpdater.prototype.updateAttachment = function(context) {

    var shape = context.shape,
        businessObject = shape.businessObject,
        host = shape.host;

    businessObject.attachedToRef = host && host.businessObject;
  };

  BpmnUpdater.prototype.updateParent = function(element, oldParent) {

    // do not update BPMN 2.0 label parent
    if (element instanceof Label) {
      return;
    }

    // data stores in collaborations are handled separately by DataStoreBehavior
    if (is$1(element, 'bpmn:DataStoreReference') &&
        element.parent &&
        is$1(element.parent, 'bpmn:Collaboration')) {
      return;
    }

    var parentShape = element.parent;

    var businessObject = element.businessObject,
        di = getDi(element),
        parentBusinessObject = parentShape && parentShape.businessObject,
        parentDi = getDi(parentShape);

    if (is$1(element, 'bpmn:FlowNode')) {
      this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
    }

    if (is$1(element, 'bpmn:DataOutputAssociation')) {
      if (element.source) {
        parentBusinessObject = element.source.businessObject;
      } else {
        parentBusinessObject = null;
      }
    }

    if (is$1(element, 'bpmn:DataInputAssociation')) {
      if (element.target) {
        parentBusinessObject = element.target.businessObject;
      } else {
        parentBusinessObject = null;
      }
    }

    this.updateSemanticParent(businessObject, parentBusinessObject);

    if (is$1(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
      this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
    }

    this.updateDiParent(di, parentDi);
  };


  BpmnUpdater.prototype.updateBounds = function(shape) {

    var di = getDi(shape),
        embeddedLabelBounds = getEmbeddedLabelBounds(shape);

    // update embedded label bounds if possible
    if (embeddedLabelBounds) {
      var embeddedLabelBoundsDelta = delta(embeddedLabelBounds, di.get('bounds'));

      assign(embeddedLabelBounds, {
        x: shape.x + embeddedLabelBoundsDelta.x,
        y: shape.y + embeddedLabelBoundsDelta.y
      });
    }

    var target = (shape instanceof Label) ? this._getLabel(di) : di;

    var bounds = target.bounds;

    if (!bounds) {
      bounds = this._bpmnFactory.createDiBounds();
      target.set('bounds', bounds);
    }

    assign(bounds, {
      x: shape.x,
      y: shape.y,
      width: shape.width,
      height: shape.height
    });
  };

  BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {

    if (oldContainment === newContainment) {
      return;
    }

    var oldRefs, newRefs;

    if (is$1 (oldContainment, 'bpmn:Lane')) {
      oldRefs = oldContainment.get('flowNodeRef');
      remove(oldRefs, businessObject);
    }

    if (is$1(newContainment, 'bpmn:Lane')) {
      newRefs = newContainment.get('flowNodeRef');
      add(newRefs, businessObject);
    }
  };


  // update existing sourceElement and targetElement di information
  BpmnUpdater.prototype.updateDiConnection = function(connection, newSource, newTarget) {
    var connectionDi = getDi(connection),
        newSourceDi = getDi(newSource),
        newTargetDi = getDi(newTarget);

    if (connectionDi.sourceElement && connectionDi.sourceElement.bpmnElement !== getBusinessObject(newSource)) {
      connectionDi.sourceElement = newSource && newSourceDi;
    }

    if (connectionDi.targetElement && connectionDi.targetElement.bpmnElement !== getBusinessObject(newTarget)) {
      connectionDi.targetElement = newTarget && newTargetDi;
    }

  };


  BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {

    if (parentDi && !is$1(parentDi, 'bpmndi:BPMNPlane')) {
      parentDi = parentDi.$parent;
    }

    if (di.$parent === parentDi) {
      return;
    }

    var planeElements = (parentDi || di.$parent).get('planeElement');

    if (parentDi) {
      planeElements.push(di);
      di.$parent = parentDi;
    } else {
      remove(planeElements, di);
      di.$parent = null;
    }
  };

  function getDefinitions(element) {
    while (element && !is$1(element, 'bpmn:Definitions')) {
      element = element.$parent;
    }

    return element;
  }

  BpmnUpdater.prototype.getLaneSet = function(container) {

    var laneSet, laneSets;

    // bpmn:Lane
    if (is$1(container, 'bpmn:Lane')) {
      laneSet = container.childLaneSet;

      if (!laneSet) {
        laneSet = this._bpmnFactory.create('bpmn:LaneSet');
        container.childLaneSet = laneSet;
        laneSet.$parent = container;
      }

      return laneSet;
    }

    // bpmn:Participant
    if (is$1(container, 'bpmn:Participant')) {
      container = container.processRef;
    }

    // bpmn:FlowElementsContainer
    laneSets = container.get('laneSets');
    laneSet = laneSets[0];

    if (!laneSet) {
      laneSet = this._bpmnFactory.create('bpmn:LaneSet');
      laneSet.$parent = container;
      laneSets.push(laneSet);
    }

    return laneSet;
  };

  BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {

    var containment,
        translate = this._translate;

    if (businessObject.$parent === newParent) {
      return;
    }

    if (is$1(businessObject, 'bpmn:DataInput') || is$1(businessObject, 'bpmn:DataOutput')) {

      if (is$1(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
        newParent = newParent.processRef;
      }

      // already in correct ioSpecification
      if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
        return;
      }
    }

    if (is$1(businessObject, 'bpmn:Lane')) {

      if (newParent) {
        newParent = this.getLaneSet(newParent);
      }

      containment = 'lanes';
    } else

    if (is$1(businessObject, 'bpmn:FlowElement')) {

      if (newParent) {

        if (is$1(newParent, 'bpmn:Participant')) {
          newParent = newParent.processRef;
        } else

        if (is$1(newParent, 'bpmn:Lane')) {
          do {

            // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
            newParent = newParent.$parent.$parent;
          } while (is$1(newParent, 'bpmn:Lane'));

        }
      }

      containment = 'flowElements';

    } else

    if (is$1(businessObject, 'bpmn:Artifact')) {

      while (newParent &&
             !is$1(newParent, 'bpmn:Process') &&
             !is$1(newParent, 'bpmn:SubProcess') &&
             !is$1(newParent, 'bpmn:Collaboration')) {

        if (is$1(newParent, 'bpmn:Participant')) {
          newParent = newParent.processRef;
          break;
        } else {
          newParent = newParent.$parent;
        }
      }

      containment = 'artifacts';
    } else

    if (is$1(businessObject, 'bpmn:MessageFlow')) {
      containment = 'messageFlows';

    } else

    if (is$1(businessObject, 'bpmn:Participant')) {
      containment = 'participants';

      // make sure the participants process is properly attached / detached
      // from the XML document

      var process = businessObject.processRef,
          definitions;

      if (process) {
        definitions = getDefinitions(businessObject.$parent || newParent);

        if (businessObject.$parent) {
          remove(definitions.get('rootElements'), process);
          process.$parent = null;
        }

        if (newParent) {
          add(definitions.get('rootElements'), process);
          process.$parent = definitions;
        }
      }
    } else

    if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
      containment = 'dataOutputAssociations';
    } else

    if (is$1(businessObject, 'bpmn:DataInputAssociation')) {
      containment = 'dataInputAssociations';
    }

    if (!containment) {
      throw new Error(translate(
        'no parent for {element} in {parent}',
        {
          element: businessObject.id,
          parent: newParent.id
        }
      ));
    }

    var children;

    if (businessObject.$parent) {

      // remove from old parent
      children = businessObject.$parent.get(containment);
      remove(children, businessObject);
    }

    if (!newParent) {
      businessObject.$parent = null;
    } else {

      // add to new parent
      children = newParent.get(containment);
      children.push(businessObject);
      businessObject.$parent = newParent;
    }

    if (visualParent) {
      var diChildren = visualParent.get(containment);

      remove(children, businessObject);

      if (newParent) {

        if (!diChildren) {
          diChildren = [];
          newParent.set(containment, diChildren);
        }

        diChildren.push(businessObject);
      }
    }
  };


  BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
    var di = getDi(connection);

    di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
  };


  BpmnUpdater.prototype.updateConnection = function(context) {
    var connection = context.connection,
        businessObject = getBusinessObject(connection),
        newSource = connection.source,
        newSourceBo = getBusinessObject(newSource),
        newTarget = connection.target,
        newTargetBo = getBusinessObject(connection.target),
        visualParent;

    if (!is$1(businessObject, 'bpmn:DataAssociation')) {

      var inverseSet = is$1(businessObject, 'bpmn:SequenceFlow');

      if (businessObject.sourceRef !== newSourceBo) {
        if (inverseSet) {
          remove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);

          if (newSourceBo && newSourceBo.get('outgoing')) {
            newSourceBo.get('outgoing').push(businessObject);
          }
        }

        businessObject.sourceRef = newSourceBo;
      }

      if (businessObject.targetRef !== newTargetBo) {
        if (inverseSet) {
          remove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);

          if (newTargetBo && newTargetBo.get('incoming')) {
            newTargetBo.get('incoming').push(businessObject);
          }
        }

        businessObject.targetRef = newTargetBo;
      }
    } else

    if (is$1(businessObject, 'bpmn:DataInputAssociation')) {

      // handle obnoxious isMsome sourceRef
      businessObject.get('sourceRef')[0] = newSourceBo;

      visualParent = context.parent || context.newParent || newTargetBo;

      this.updateSemanticParent(businessObject, newTargetBo, visualParent);
    } else

    if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
      visualParent = context.parent || context.newParent || newSourceBo;

      this.updateSemanticParent(businessObject, newSourceBo, visualParent);

      // targetRef = new target
      businessObject.targetRef = newTargetBo;
    }

    this.updateConnectionWaypoints(connection);

    this.updateDiConnection(connection, newSource, newTarget);
  };


  // helpers //////////////////////

  BpmnUpdater.prototype._getLabel = function(di) {
    if (!di.label) {
      di.label = this._bpmnFactory.createDiLabel();
    }

    return di.label;
  };


  /**
   * Make sure the event listener is only called
   * if the touched element is a BPMN element.
   *
   * @param  {Function} fn
   * @return {Function} guarded function
   */
  function ifBpmn(fn) {

    return function(event) {

      var context = event.context,
          element = context.shape || context.connection;

      if (is$1(element, 'bpmn:BaseElement')) {
        fn(event);
      }
    };
  }

  /**
   * Return dc:Bounds of bpmndi:BPMNLabel if exists.
   *
   * @param {djs.model.shape} shape
   *
   * @returns {Object|undefined}
   */
  function getEmbeddedLabelBounds(shape) {
    if (!is$1(shape, 'bpmn:Activity')) {
      return;
    }

    var di = getDi(shape);

    if (!di) {
      return;
    }

    var label = di.get('label');

    if (!label) {
      return;
    }

    return label.get('bounds');
  }

  /**
   * A bpmn-aware factory for diagram-js shapes
   */
  function ElementFactory(bpmnFactory, moddle, translate) {
    ElementFactory$1.call(this);

    this._bpmnFactory = bpmnFactory;
    this._moddle = moddle;
    this._translate = translate;
  }

  e(ElementFactory, ElementFactory$1);

  ElementFactory.$inject = [
    'bpmnFactory',
    'moddle',
    'translate'
  ];

  ElementFactory.prototype.baseCreate = ElementFactory$1.prototype.create;

  ElementFactory.prototype.create = function(elementType, attrs) {

    // no special magic for labels,
    // we assume their businessObjects have already been created
    // and wired via attrs
    if (elementType === 'label') {
      var di = attrs.di || this._bpmnFactory.createDiLabel();
      return this.baseCreate(elementType, assign({ type: 'label', di: di }, DEFAULT_LABEL_SIZE, attrs));
    }

    return this.createBpmnElement(elementType, attrs);
  };

  ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
    var size,
        translate = this._translate;

    attrs = assign({}, attrs || {});

    var businessObject = attrs.businessObject,
        di = attrs.di;

    if (!businessObject) {
      if (!attrs.type) {
        throw new Error(translate('no shape type specified'));
      }

      businessObject = this._bpmnFactory.create(attrs.type);

      ensureCompatDiRef(businessObject);
    }

    if (!isModdleDi(di)) {
      var diAttrs = assign(
        {},
        di || {},
        { id: businessObject.id + '_di' }
      );

      if (elementType === 'root') {
        di = this._bpmnFactory.createDiPlane(businessObject, diAttrs);
      } else
      if (elementType === 'connection') {
        di = this._bpmnFactory.createDiEdge(businessObject, diAttrs);
      } else {
        di = this._bpmnFactory.createDiShape(businessObject, diAttrs);
      }
    }

    if (is$1(businessObject, 'bpmn:Group')) {
      attrs = assign({
        isFrame: true
      }, attrs);
    }

    attrs = applyAttributes(businessObject, attrs, [
      'processRef',
      'isInterrupting',
      'associationDirection',
      'isForCompensation'
    ]);

    if (attrs.isExpanded) {
      attrs = applyAttribute(di, attrs, 'isExpanded');
    }

    if (is$1(businessObject, 'bpmn:SubProcess')) {
      attrs.collapsed = !isExpanded(businessObject, di);
    }

    if (is$1(businessObject, 'bpmn:ExclusiveGateway')) {
      di.isMarkerVisible = true;
    }

    var eventDefinitions,
        newEventDefinition;

    if (attrs.eventDefinitionType) {
      eventDefinitions = businessObject.get('eventDefinitions') || [];
      newEventDefinition = this._bpmnFactory.create(attrs.eventDefinitionType, attrs.eventDefinitionAttrs);

      if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
        newEventDefinition.condition = this._bpmnFactory.create('bpmn:FormalExpression');
      }

      eventDefinitions.push(newEventDefinition);

      newEventDefinition.$parent = businessObject;
      businessObject.eventDefinitions = eventDefinitions;

      delete attrs.eventDefinitionType;
    }

    size = this.getDefaultSize(businessObject, di);

    attrs = assign({
      id: businessObject.id
    }, size, attrs, {
      businessObject: businessObject,
      di: di
    });

    return this.baseCreate(elementType, attrs);
  };


  ElementFactory.prototype.getDefaultSize = function(element, di) {

    var bo = getBusinessObject(element);
    di = di || getDi(element);

    if (is$1(bo, 'bpmn:SubProcess')) {
      if (isExpanded(bo, di)) {
        return { width: 350, height: 200 };
      } else {
        return { width: 100, height: 80 };
      }
    }

    if (is$1(bo, 'bpmn:Task')) {
      return { width: 100, height: 80 };
    }

    if (is$1(bo, 'bpmn:Gateway')) {
      return { width: 50, height: 50 };
    }

    if (is$1(bo, 'bpmn:Event')) {
      return { width: 36, height: 36 };
    }

    if (is$1(bo, 'bpmn:Participant')) {
      if (isExpanded(bo, di)) {
        return { width: 600, height: 250 };
      } else {
        return { width: 400, height: 60 };
      }
    }

    if (is$1(bo, 'bpmn:Lane')) {
      return { width: 400, height: 100 };
    }

    if (is$1(bo, 'bpmn:DataObjectReference')) {
      return { width: 36, height: 50 };
    }

    if (is$1(bo, 'bpmn:DataStoreReference')) {
      return { width: 50, height: 50 };
    }

    if (is$1(bo, 'bpmn:TextAnnotation')) {
      return { width: 100, height: 30 };
    }

    if (is$1(bo, 'bpmn:Group')) {
      return { width: 300, height: 300 };
    }

    return { width: 100, height: 80 };
  };


  /**
   * Create participant.
   *
   * @param {boolean|Object} [attrs] attrs
   *
   * @returns {djs.model.Shape}
   */
  ElementFactory.prototype.createParticipantShape = function(attrs) {

    if (!isObject(attrs)) {
      attrs = { isExpanded: attrs };
    }

    attrs = assign({ type: 'bpmn:Participant' }, attrs || {});

    // participants are expanded by default
    if (attrs.isExpanded !== false) {
      attrs.processRef = this._bpmnFactory.create('bpmn:Process');
    }

    return this.createShape(attrs);
  };


  // helpers //////////////////////

  /**
   * Apply attributes from a map to the given element,
   * remove attribute from the map on application.
   *
   * @param {Base} element
   * @param {Object} attrs (in/out map of attributes)
   * @param {Array<string>} attributeNames name of attributes to apply
   *
   * @return {Object} changed attrs
   */
  function applyAttributes(element, attrs, attributeNames) {

    forEach$1(attributeNames, function(property) {
      attrs = applyAttribute(element, attrs, property);
    });

    return attrs;
  }

  /**
   * Apply named property to element and drain it from the attrs
   * collection.
   *
   * @param {Base} element
   * @param {Object} attrs (in/out map of attributes)
   * @param {string} attributeName to apply
   *
   * @return {Object} changed attrs
   */
  function applyAttribute(element, attrs, attributeName) {
    if (attrs[attributeName] === undefined) {
      return attrs;
    }

    element[attributeName] = attrs[attributeName];

    return omit(attrs, [ attributeName ]);
  }


  function isModdleDi(element) {
    return isAny(element, [
      'bpmndi:BPMNShape',
      'bpmndi:BPMNEdge',
      'bpmndi:BPMNDiagram',
      'bpmndi:BPMNPlane',
    ]);
  }

  /**
   * A handler that align elements in a certain way.
   *
   */
  function AlignElements(modeling, canvas) {
    this._modeling = modeling;
    this._canvas = canvas;
  }

  AlignElements.$inject = [ 'modeling', 'canvas' ];


  AlignElements.prototype.preExecute = function(context) {
    var modeling = this._modeling;

    var elements = context.elements,
        alignment = context.alignment;


    forEach$1(elements, function(element) {
      var delta = {
        x: 0,
        y: 0
      };

      if (alignment.left) {
        delta.x = alignment.left - element.x;

      } else if (alignment.right) {
        delta.x = (alignment.right - element.width) - element.x;

      } else if (alignment.center) {
        delta.x = (alignment.center - Math.round(element.width / 2)) - element.x;

      } else if (alignment.top) {
        delta.y = alignment.top - element.y;

      } else if (alignment.bottom) {
        delta.y = (alignment.bottom - element.height) - element.y;

      } else if (alignment.middle) {
        delta.y = (alignment.middle - Math.round(element.height / 2)) - element.y;
      }

      modeling.moveElements([ element ], delta, element.parent);
    });
  };

  AlignElements.prototype.postExecute = function(context) {

  };

  /**
   * A handler that implements reversible appending of shapes
   * to a source shape.
   *
   * @param {canvas} Canvas
   * @param {elementFactory} ElementFactory
   * @param {modeling} Modeling
   */
  function AppendShapeHandler(modeling) {
    this._modeling = modeling;
  }

  AppendShapeHandler.$inject = [ 'modeling' ];


  // api //////////////////////


  /**
   * Creates a new shape
   *
   * @param {Object} context
   * @param {ElementDescriptor} context.shape the new shape
   * @param {ElementDescriptor} context.source the source object
   * @param {ElementDescriptor} context.parent the parent object
   * @param {Point} context.position position of the new element
   */
  AppendShapeHandler.prototype.preExecute = function(context) {

    var source = context.source;

    if (!source) {
      throw new Error('source required');
    }

    var target = context.target || source.parent,
        shape = context.shape,
        hints = context.hints || {};

    shape = context.shape =
      this._modeling.createShape(
        shape,
        context.position,
        target, { attach: hints.attach });

    context.shape = shape;
  };

  AppendShapeHandler.prototype.postExecute = function(context) {
    var hints = context.hints || {};

    if (!existsConnection(context.source, context.shape)) {

      // create connection
      if (hints.connectionTarget === context.source) {
        this._modeling.connect(context.shape, context.source, context.connection);
      } else {
        this._modeling.connect(context.source, context.shape, context.connection);
      }
    }
  };


  function existsConnection(source, target) {
    return some(source.outgoing, function(c) {
      return c.target === target;
    });
  }

  function CreateConnectionHandler(canvas, layouter) {
    this._canvas = canvas;
    this._layouter = layouter;
  }

  CreateConnectionHandler.$inject = [ 'canvas', 'layouter' ];


  // api //////////////////////


  /**
   * Appends a shape to a target shape
   *
   * @param {Object} context
   * @param {djs.element.Base} context.source the source object
   * @param {djs.element.Base} context.target the parent object
   * @param {Point} context.position position of the new element
   */
  CreateConnectionHandler.prototype.execute = function(context) {

    var connection = context.connection,
        source = context.source,
        target = context.target,
        parent = context.parent,
        parentIndex = context.parentIndex,
        hints = context.hints;

    if (!source || !target) {
      throw new Error('source and target required');
    }

    if (!parent) {
      throw new Error('parent required');
    }

    connection.source = source;
    connection.target = target;

    if (!connection.waypoints) {
      connection.waypoints = this._layouter.layoutConnection(connection, hints);
    }

    // add connection
    this._canvas.addConnection(connection, parent, parentIndex);

    return connection;
  };

  CreateConnectionHandler.prototype.revert = function(context) {
    var connection = context.connection;

    this._canvas.removeConnection(connection);

    connection.source = null;
    connection.target = null;

    return connection;
  };

  var round$3 = Math.round;

  function CreateElementsHandler(modeling) {
    this._modeling = modeling;
  }

  CreateElementsHandler.$inject = [
    'modeling'
  ];

  CreateElementsHandler.prototype.preExecute = function(context) {
    var elements = context.elements,
        parent = context.parent,
        parentIndex = context.parentIndex,
        position = context.position,
        hints = context.hints;

    var modeling = this._modeling;

    // make sure each element has x and y
    forEach$1(elements, function(element) {
      if (!isNumber(element.x)) {
        element.x = 0;
      }

      if (!isNumber(element.y)) {
        element.y = 0;
      }
    });

    var visibleElements = filter(elements, function(element) {
      return !element.hidden;
    });

    var bbox = getBBox(visibleElements);

    // center elements around position
    forEach$1(elements, function(element) {
      if (isConnection$5(element)) {
        element.waypoints = map(element.waypoints, function(waypoint) {
          return {
            x: round$3(waypoint.x - bbox.x - bbox.width / 2 + position.x),
            y: round$3(waypoint.y - bbox.y - bbox.height / 2 + position.y)
          };
        });
      }

      assign(element, {
        x: round$3(element.x - bbox.x - bbox.width / 2 + position.x),
        y: round$3(element.y - bbox.y - bbox.height / 2 + position.y)
      });
    });

    var parents = getParents$1(elements);

    var cache = {};

    forEach$1(elements, function(element) {
      if (isConnection$5(element)) {
        cache[ element.id ] = isNumber(parentIndex) ?
          modeling.createConnection(
            cache[ element.source.id ],
            cache[ element.target.id ],
            parentIndex,
            element,
            element.parent || parent,
            hints
          ) :
          modeling.createConnection(
            cache[ element.source.id ],
            cache[ element.target.id ],
            element,
            element.parent || parent,
            hints
          );

        return;
      }

      var createShapeHints = assign({}, hints);

      if (parents.indexOf(element) === -1) {
        createShapeHints.autoResize = false;
      }

      cache[ element.id ] = isNumber(parentIndex) ?
        modeling.createShape(
          element,
          pick(element, [ 'x', 'y', 'width', 'height' ]),
          element.parent || parent,
          parentIndex,
          createShapeHints
        ) :
        modeling.createShape(
          element,
          pick(element, [ 'x', 'y', 'width', 'height' ]),
          element.parent || parent,
          createShapeHints
        );
    });

    context.elements = values(cache);
  };

  // helpers //////////

  function isConnection$5(element) {
    return !!element.waypoints;
  }

  var round$2 = Math.round;


  /**
   * A handler that implements reversible addition of shapes.
   *
   * @param {canvas} Canvas
   */
  function CreateShapeHandler(canvas) {
    this._canvas = canvas;
  }

  CreateShapeHandler.$inject = [ 'canvas' ];


  // api //////////////////////


  /**
   * Appends a shape to a target shape
   *
   * @param {Object} context
   * @param {djs.model.Base} context.parent the parent object
   * @param {Point} context.position position of the new element
   */
  CreateShapeHandler.prototype.execute = function(context) {

    var shape = context.shape,
        positionOrBounds = context.position,
        parent = context.parent,
        parentIndex = context.parentIndex;

    if (!parent) {
      throw new Error('parent required');
    }

    if (!positionOrBounds) {
      throw new Error('position required');
    }

    // (1) add at event center position _or_ at given bounds
    if (positionOrBounds.width !== undefined) {
      assign(shape, positionOrBounds);
    } else {
      assign(shape, {
        x: positionOrBounds.x - round$2(shape.width / 2),
        y: positionOrBounds.y - round$2(shape.height / 2)
      });
    }

    // (2) add to canvas
    this._canvas.addShape(shape, parent, parentIndex);

    return shape;
  };


  /**
   * Undo append by removing the shape
   */
  CreateShapeHandler.prototype.revert = function(context) {

    var shape = context.shape;

    // (3) remove form canvas
    this._canvas.removeShape(shape);

    return shape;
  };

  /**
   * A handler that attaches a label to a given target shape.
   *
   * @param {Canvas} canvas
   */
  function CreateLabelHandler(canvas) {
    CreateShapeHandler.call(this, canvas);
  }

  e(CreateLabelHandler, CreateShapeHandler);

  CreateLabelHandler.$inject = [ 'canvas' ];


  // api //////////////////////


  var originalExecute = CreateShapeHandler.prototype.execute;

  /**
   * Appends a label to a target shape.
   *
   * @method CreateLabelHandler#execute
   *
   * @param {Object} context
   * @param {ElementDescriptor} context.target the element the label is attached to
   * @param {ElementDescriptor} context.parent the parent object
   * @param {Point} context.position position of the new element
   */
  CreateLabelHandler.prototype.execute = function(context) {

    var label = context.shape;

    ensureValidDimensions(label);

    label.labelTarget = context.labelTarget;

    return originalExecute.call(this, context);
  };

  var originalRevert = CreateShapeHandler.prototype.revert;

  /**
   * Undo append by removing the shape
   */
  CreateLabelHandler.prototype.revert = function(context) {
    context.shape.labelTarget = null;

    return originalRevert.call(this, context);
  };


  // helpers //////////////////////

  function ensureValidDimensions(label) {

    // make sure a label has valid { width, height } dimensions
    [ 'width', 'height' ].forEach(function(prop) {
      if (typeof label[prop] === 'undefined') {
        label[prop] = 0;
      }
    });
  }

  /**
   * A handler that implements reversible deletion of Connections.
   */
  function DeleteConnectionHandler(canvas, modeling) {
    this._canvas = canvas;
    this._modeling = modeling;
  }

  DeleteConnectionHandler.$inject = [
    'canvas',
    'modeling'
  ];


  /**
   * - Remove connections
   */
  DeleteConnectionHandler.prototype.preExecute = function(context) {

    var modeling = this._modeling;

    var connection = context.connection;

    // remove connections
    saveClear(connection.incoming, function(connection) {

      // To make sure that the connection isn't removed twice
      // For example if a container is removed
      modeling.removeConnection(connection, { nested: true });
    });

    saveClear(connection.outgoing, function(connection) {
      modeling.removeConnection(connection, { nested: true });
    });

  };


  DeleteConnectionHandler.prototype.execute = function(context) {

    var connection = context.connection,
        parent = connection.parent;

    context.parent = parent;

    // remember containment
    context.parentIndex = indexOf(parent.children, connection);

    context.source = connection.source;
    context.target = connection.target;

    this._canvas.removeConnection(connection);

    connection.source = null;
    connection.target = null;

    return connection;
  };

  /**
   * Command revert implementation.
   */
  DeleteConnectionHandler.prototype.revert = function(context) {

    var connection = context.connection,
        parent = context.parent,
        parentIndex = context.parentIndex;

    connection.source = context.source;
    connection.target = context.target;

    // restore containment
    add(parent.children, connection, parentIndex);

    this._canvas.addConnection(connection, parent);

    return connection;
  };

  function DeleteElementsHandler(modeling, elementRegistry) {
    this._modeling = modeling;
    this._elementRegistry = elementRegistry;
  }

  DeleteElementsHandler.$inject = [
    'modeling',
    'elementRegistry'
  ];


  DeleteElementsHandler.prototype.postExecute = function(context) {

    var modeling = this._modeling,
        elementRegistry = this._elementRegistry,
        elements = context.elements;

    forEach$1(elements, function(element) {

      // element may have been removed with previous
      // remove operations already (e.g. in case of nesting)
      if (!elementRegistry.get(element.id)) {
        return;
      }

      if (element.waypoints) {
        modeling.removeConnection(element);
      } else {
        modeling.removeShape(element);
      }
    });
  };

  /**
   * A handler that implements reversible deletion of shapes.
   *
   */
  function DeleteShapeHandler(canvas, modeling) {
    this._canvas = canvas;
    this._modeling = modeling;
  }

  DeleteShapeHandler.$inject = [ 'canvas', 'modeling' ];


  /**
   * - Remove connections
   * - Remove all direct children
   */
  DeleteShapeHandler.prototype.preExecute = function(context) {

    var modeling = this._modeling;

    var shape = context.shape;

    // remove connections
    saveClear(shape.incoming, function(connection) {

      // To make sure that the connection isn't removed twice
      // For example if a container is removed
      modeling.removeConnection(connection, { nested: true });
    });

    saveClear(shape.outgoing, function(connection) {
      modeling.removeConnection(connection, { nested: true });
    });

    // remove child shapes and connections
    saveClear(shape.children, function(child) {
      if (isConnection$4(child)) {
        modeling.removeConnection(child, { nested: true });
      } else {
        modeling.removeShape(child, { nested: true });
      }
    });
  };

  /**
   * Remove shape and remember the parent
   */
  DeleteShapeHandler.prototype.execute = function(context) {
    var canvas = this._canvas;

    var shape = context.shape,
        oldParent = shape.parent;

    context.oldParent = oldParent;

    // remove containment
    context.oldParentIndex = indexOf(oldParent.children, shape);

    // remove shape
    canvas.removeShape(shape);

    return shape;
  };


  /**
   * Command revert implementation
   */
  DeleteShapeHandler.prototype.revert = function(context) {

    var canvas = this._canvas;

    var shape = context.shape,
        oldParent = context.oldParent,
        oldParentIndex = context.oldParentIndex;

    // restore containment
    add(oldParent.children, shape, oldParentIndex);

    canvas.addShape(shape, oldParent);

    return shape;
  };

  function isConnection$4(element) {
    return element.waypoints;
  }

  /**
   * A handler that distributes elements evenly.
   */
  function DistributeElements(modeling) {
    this._modeling = modeling;
  }

  DistributeElements.$inject = [ 'modeling' ];

  var OFF_AXIS = {
    x: 'y',
    y: 'x'
  };

  DistributeElements.prototype.preExecute = function(context) {
    var modeling = this._modeling;

    var groups = context.groups,
        axis = context.axis,
        dimension = context.dimension;

    function updateRange(group, element) {
      group.range.min = Math.min(element[axis], group.range.min);
      group.range.max = Math.max(element[axis] + element[dimension], group.range.max);
    }

    function center(element) {
      return element[axis] + element[dimension] / 2;
    }

    function lastIdx(arr) {
      return arr.length - 1;
    }

    function rangeDiff(range) {
      return range.max - range.min;
    }

    function centerElement(refCenter, element) {
      var delta = { y: 0 };

      delta[axis] = refCenter - center(element);

      if (delta[axis]) {

        delta[OFF_AXIS[axis]] = 0;

        modeling.moveElements([ element ], delta, element.parent);
      }
    }

    var firstGroup = groups[0],
        lastGroupIdx = lastIdx(groups),
        lastGroup = groups[ lastGroupIdx ];

    var margin,
        spaceInBetween,
        groupsSize = 0; // the size of each range

    forEach$1(groups, function(group, idx) {
      var sortedElements,
          refElem,
          refCenter;

      if (group.elements.length < 2) {
        if (idx && idx !== groups.length - 1) {
          updateRange(group, group.elements[0]);

          groupsSize += rangeDiff(group.range);
        }
        return;
      }

      sortedElements = sortBy(group.elements, axis);

      refElem = sortedElements[0];

      if (idx === lastGroupIdx) {
        refElem = sortedElements[lastIdx(sortedElements)];
      }

      refCenter = center(refElem);

      // wanna update the ranges after the shapes have been centered
      group.range = null;

      forEach$1(sortedElements, function(element) {

        centerElement(refCenter, element);

        if (group.range === null) {
          group.range = {
            min: element[axis],
            max: element[axis] + element[dimension]
          };

          return;
        }

        // update group's range after centering the range elements
        updateRange(group, element);
      });

      if (idx && idx !== groups.length - 1) {
        groupsSize += rangeDiff(group.range);
      }
    });

    spaceInBetween = Math.abs(lastGroup.range.min - firstGroup.range.max);

    margin = Math.round((spaceInBetween - groupsSize) / (groups.length - 1));

    if (margin < groups.length - 1) {
      return;
    }

    forEach$1(groups, function(group, groupIdx) {
      var delta = {},
          prevGroup;

      if (group === firstGroup || group === lastGroup) {
        return;
      }

      prevGroup = groups[groupIdx - 1];

      group.range.max = 0;

      forEach$1(group.elements, function(element, idx) {
        delta[OFF_AXIS[axis]] = 0;
        delta[axis] = (prevGroup.range.max - element[axis]) + margin;

        if (group.range.min !== element[axis]) {
          delta[axis] += element[axis] - group.range.min;
        }

        if (delta[axis]) {
          modeling.moveElements([ element ], delta, element.parent);
        }

        group.range.max = Math.max(element[axis] + element[dimension], idx ? group.range.max : 0);
      });
    });
  };

  DistributeElements.prototype.postExecute = function(context) {

  };

  /**
   * A handler that implements reversible moving of shapes.
   */
  function LayoutConnectionHandler(layouter, canvas) {
    this._layouter = layouter;
    this._canvas = canvas;
  }

  LayoutConnectionHandler.$inject = [ 'layouter', 'canvas' ];

  LayoutConnectionHandler.prototype.execute = function(context) {

    var connection = context.connection;

    var oldWaypoints = connection.waypoints;

    assign(context, {
      oldWaypoints: oldWaypoints
    });

    connection.waypoints = this._layouter.layoutConnection(connection, context.hints);

    return connection;
  };

  LayoutConnectionHandler.prototype.revert = function(context) {

    var connection = context.connection;

    connection.waypoints = context.oldWaypoints;

    return connection;
  };

  /**
   * A handler that implements reversible moving of connections.
   *
   * The handler differs from the layout connection handler in a sense
   * that it preserves the connection layout.
   */
  function MoveConnectionHandler() { }


  MoveConnectionHandler.prototype.execute = function(context) {

    var connection = context.connection,
        delta = context.delta;

    var newParent = context.newParent || connection.parent,
        newParentIndex = context.newParentIndex,
        oldParent = connection.parent;

    // save old parent in context
    context.oldParent = oldParent;
    context.oldParentIndex = remove(oldParent.children, connection);

    // add to new parent at position
    add(newParent.children, connection, newParentIndex);

    // update parent
    connection.parent = newParent;

    // update waypoint positions
    forEach$1(connection.waypoints, function(p) {
      p.x += delta.x;
      p.y += delta.y;

      if (p.original) {
        p.original.x += delta.x;
        p.original.y += delta.y;
      }
    });

    return connection;
  };

  MoveConnectionHandler.prototype.revert = function(context) {

    var connection = context.connection,
        newParent = connection.parent,
        oldParent = context.oldParent,
        oldParentIndex = context.oldParentIndex,
        delta = context.delta;

    // remove from newParent
    remove(newParent.children, connection);

    // restore previous location in old parent
    add(oldParent.children, connection, oldParentIndex);

    // restore parent
    connection.parent = oldParent;

    // revert to old waypoint positions
    forEach$1(connection.waypoints, function(p) {
      p.x -= delta.x;
      p.y -= delta.y;

      if (p.original) {
        p.original.x -= delta.x;
        p.original.y -= delta.y;
      }
    });

    return connection;
  };

  function MoveClosure() {

    this.allShapes = {};
    this.allConnections = {};

    this.enclosedElements = {};
    this.enclosedConnections = {};

    this.topLevel = {};
  }


  MoveClosure.prototype.add = function(element, isTopLevel) {
    return this.addAll([ element ], isTopLevel);
  };


  MoveClosure.prototype.addAll = function(elements, isTopLevel) {

    var newClosure = getClosure(elements, !!isTopLevel, this);

    assign(this, newClosure);

    return this;
  };

  /**
   * A helper that is able to carry out serialized move
   * operations on multiple elements.
   *
   * @param {Modeling} modeling
   */
  function MoveHelper(modeling) {
    this._modeling = modeling;
  }

  /**
   * Move the specified elements and all children by the given delta.
   *
   * This moves all enclosed connections, too and layouts all affected
   * external connections.
   *
   * @param  {Array<djs.model.Base>} elements
   * @param  {Point} delta
   * @param  {djs.model.Base} newParent applied to the first level of shapes
   *
   * @return {Array<djs.model.Base>} list of touched elements
   */
  MoveHelper.prototype.moveRecursive = function(elements, delta, newParent) {
    if (!elements) {
      return [];
    } else {
      return this.moveClosure(this.getClosure(elements), delta, newParent);
    }
  };

  /**
   * Move the given closure of elmements.
   *
   * @param {Object} closure
   * @param {Point} delta
   * @param {djs.model.Base} [newParent]
   * @param {djs.model.Base} [newHost]
   */
  MoveHelper.prototype.moveClosure = function(closure, delta, newParent, newHost, primaryShape) {
    var modeling = this._modeling;

    var allShapes = closure.allShapes,
        allConnections = closure.allConnections,
        enclosedConnections = closure.enclosedConnections,
        topLevel = closure.topLevel,
        keepParent = false;

    if (primaryShape && primaryShape.parent === newParent) {
      keepParent = true;
    }

    // move all shapes
    forEach$1(allShapes, function(shape) {

      // move the element according to the given delta
      modeling.moveShape(shape, delta, topLevel[shape.id] && !keepParent && newParent, {
        recurse: false,
        layout: false
      });
    });

    // move all child connections / layout external connections
    forEach$1(allConnections, function(c) {

      var sourceMoved = !!allShapes[c.source.id],
          targetMoved = !!allShapes[c.target.id];

      if (enclosedConnections[c.id] && sourceMoved && targetMoved) {
        modeling.moveConnection(c, delta, topLevel[c.id] && !keepParent && newParent);
      } else {
        modeling.layoutConnection(c, {
          connectionStart: sourceMoved && getMovedSourceAnchor(c, c.source, delta),
          connectionEnd: targetMoved && getMovedTargetAnchor(c, c.target, delta)
        });
      }
    });
  };

  /**
   * Returns the closure for the selected elements
   *
   * @param  {Array<djs.model.Base>} elements
   * @return {MoveClosure} closure
   */
  MoveHelper.prototype.getClosure = function(elements) {
    return new MoveClosure().addAll(elements, true);
  };

  /**
   * A handler that implements reversible moving of shapes.
   */
  function MoveElementsHandler(modeling) {
    this._helper = new MoveHelper(modeling);
  }

  MoveElementsHandler.$inject = [ 'modeling' ];

  MoveElementsHandler.prototype.preExecute = function(context) {
    context.closure = this._helper.getClosure(context.shapes);
  };

  MoveElementsHandler.prototype.postExecute = function(context) {

    var hints = context.hints,
        primaryShape;

    if (hints && hints.primaryShape) {
      primaryShape = hints.primaryShape;
      hints.oldParent = primaryShape.parent;
    }

    this._helper.moveClosure(
      context.closure,
      context.delta,
      context.newParent,
      context.newHost,
      primaryShape
    );
  };

  /**
   * A handler that implements reversible moving of shapes.
   */
  function MoveShapeHandler(modeling) {
    this._modeling = modeling;

    this._helper = new MoveHelper(modeling);
  }

  MoveShapeHandler.$inject = [ 'modeling' ];


  MoveShapeHandler.prototype.execute = function(context) {

    var shape = context.shape,
        delta = context.delta,
        newParent = context.newParent || shape.parent,
        newParentIndex = context.newParentIndex,
        oldParent = shape.parent;

    context.oldBounds = pick(shape, [ 'x', 'y', 'width', 'height' ]);

    // save old parent in context
    context.oldParent = oldParent;
    context.oldParentIndex = remove(oldParent.children, shape);

    // add to new parent at position
    add(newParent.children, shape, newParentIndex);

    // update shape parent + position
    assign(shape, {
      parent: newParent,
      x: shape.x + delta.x,
      y: shape.y + delta.y
    });

    return shape;
  };

  MoveShapeHandler.prototype.postExecute = function(context) {

    var shape = context.shape,
        delta = context.delta,
        hints = context.hints;

    var modeling = this._modeling;

    if (hints.layout !== false) {

      forEach$1(shape.incoming, function(c) {
        modeling.layoutConnection(c, {
          connectionEnd: getMovedTargetAnchor(c, shape, delta)
        });
      });

      forEach$1(shape.outgoing, function(c) {
        modeling.layoutConnection(c, {
          connectionStart: getMovedSourceAnchor(c, shape, delta)
        });
      });
    }

    if (hints.recurse !== false) {
      this.moveChildren(context);
    }
  };

  MoveShapeHandler.prototype.revert = function(context) {

    var shape = context.shape,
        oldParent = context.oldParent,
        oldParentIndex = context.oldParentIndex,
        delta = context.delta;

    // restore previous location in old parent
    add(oldParent.children, shape, oldParentIndex);

    // revert to old position and parent
    assign(shape, {
      parent: oldParent,
      x: shape.x - delta.x,
      y: shape.y - delta.y
    });

    return shape;
  };

  MoveShapeHandler.prototype.moveChildren = function(context) {

    var delta = context.delta,
        shape = context.shape;

    this._helper.moveRecursive(shape.children, delta, null);
  };

  MoveShapeHandler.prototype.getNewParent = function(context) {
    return context.newParent || context.shape.parent;
  };

  /**
   * Reconnect connection handler
   */
  function ReconnectConnectionHandler(modeling) {
    this._modeling = modeling;
  }

  ReconnectConnectionHandler.$inject = [ 'modeling' ];

  ReconnectConnectionHandler.prototype.execute = function(context) {
    var newSource = context.newSource,
        newTarget = context.newTarget,
        connection = context.connection,
        dockingOrPoints = context.dockingOrPoints;

    if (!newSource && !newTarget) {
      throw new Error('newSource or newTarget required');
    }

    if (isArray$3(dockingOrPoints)) {
      context.oldWaypoints = connection.waypoints;
      connection.waypoints = dockingOrPoints;
    }

    if (newSource) {
      context.oldSource = connection.source;
      connection.source = newSource;
    }

    if (newTarget) {
      context.oldTarget = connection.target;
      connection.target = newTarget;
    }

    return connection;
  };

  ReconnectConnectionHandler.prototype.postExecute = function(context) {
    var connection = context.connection,
        newSource = context.newSource,
        newTarget = context.newTarget,
        dockingOrPoints = context.dockingOrPoints,
        hints = context.hints || {};

    var layoutConnectionHints = {};

    if (hints.connectionStart) {
      layoutConnectionHints.connectionStart = hints.connectionStart;
    }

    if (hints.connectionEnd) {
      layoutConnectionHints.connectionEnd = hints.connectionEnd;
    }

    if (hints.layoutConnection === false) {
      return;
    }

    if (newSource && (!newTarget || hints.docking === 'source')) {
      layoutConnectionHints.connectionStart = layoutConnectionHints.connectionStart
        || getDocking(isArray$3(dockingOrPoints) ? dockingOrPoints[ 0 ] : dockingOrPoints);
    }

    if (newTarget && (!newSource || hints.docking === 'target')) {
      layoutConnectionHints.connectionEnd = layoutConnectionHints.connectionEnd
        || getDocking(isArray$3(dockingOrPoints) ? dockingOrPoints[ dockingOrPoints.length - 1 ] : dockingOrPoints);
    }

    if (hints.newWaypoints) {
      layoutConnectionHints.waypoints = hints.newWaypoints;
    }

    this._modeling.layoutConnection(connection, layoutConnectionHints);
  };

  ReconnectConnectionHandler.prototype.revert = function(context) {
    var oldSource = context.oldSource,
        oldTarget = context.oldTarget,
        oldWaypoints = context.oldWaypoints,
        connection = context.connection;

    if (oldSource) {
      connection.source = oldSource;
    }

    if (oldTarget) {
      connection.target = oldTarget;
    }

    if (oldWaypoints) {
      connection.waypoints = oldWaypoints;
    }

    return connection;
  };



  // helpers //////////

  function getDocking(point) {
    return point.original || point;
  }

  /**
   * Replace shape by adding new shape and removing old shape. Incoming and outgoing connections will
   * be kept if possible.
   *
   * @class
   * @constructor
   *
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  function ReplaceShapeHandler(modeling, rules) {
    this._modeling = modeling;
    this._rules = rules;
  }

  ReplaceShapeHandler.$inject = [ 'modeling', 'rules' ];


  /**
   * Add new shape.
   *
   * @param {Object} context
   * @param {djs.model.Shape} context.oldShape
   * @param {Object} context.newData
   * @param {string} context.newData.type
   * @param {number} context.newData.x
   * @param {number} context.newData.y
   * @param {Object} [hints]
   */
  ReplaceShapeHandler.prototype.preExecute = function(context) {
    var self = this,
        modeling = this._modeling,
        rules = this._rules;

    var oldShape = context.oldShape,
        newData = context.newData,
        hints = context.hints || {},
        newShape;

    function canReconnect(source, target, connection) {
      return rules.allowed('connection.reconnect', {
        connection: connection,
        source: source,
        target: target
      });
    }

    // (1) add new shape at given position
    var position = {
      x: newData.x,
      y: newData.y
    };

    var oldBounds = {
      x: oldShape.x,
      y: oldShape.y,
      width: oldShape.width,
      height: oldShape.height
    };

    newShape = context.newShape =
      context.newShape ||
      self.createShape(newData, position, oldShape.parent, hints);

    // (2) update host
    if (oldShape.host) {
      modeling.updateAttachment(newShape, oldShape.host);
    }

    // (3) adopt all children from old shape
    var children;

    if (hints.moveChildren !== false) {
      children = oldShape.children.slice();

      modeling.moveElements(children, { x: 0, y: 0 }, newShape, hints);
    }

    // (4) reconnect connections to new shape if possible
    var incoming = oldShape.incoming.slice(),
        outgoing = oldShape.outgoing.slice();

    forEach$1(incoming, function(connection) {
      var source = connection.source,
          allowed = canReconnect(source, newShape, connection);

      if (allowed) {
        self.reconnectEnd(
          connection, newShape,
          getResizedTargetAnchor(connection, newShape, oldBounds),
          hints
        );
      }
    });

    forEach$1(outgoing, function(connection) {
      var target = connection.target,
          allowed = canReconnect(newShape, target, connection);

      if (allowed) {
        self.reconnectStart(
          connection, newShape,
          getResizedSourceAnchor(connection, newShape, oldBounds),
          hints
        );
      }
    });
  };


  /**
   * Remove old shape.
   */
  ReplaceShapeHandler.prototype.postExecute = function(context) {
    var oldShape = context.oldShape;

    this._modeling.removeShape(oldShape);
  };


  ReplaceShapeHandler.prototype.execute = function(context) {};


  ReplaceShapeHandler.prototype.revert = function(context) {};


  ReplaceShapeHandler.prototype.createShape = function(shape, position, target, hints) {
    return this._modeling.createShape(shape, position, target, hints);
  };


  ReplaceShapeHandler.prototype.reconnectStart = function(connection, newSource, dockingPoint, hints) {
    this._modeling.reconnectStart(connection, newSource, dockingPoint, hints);
  };


  ReplaceShapeHandler.prototype.reconnectEnd = function(connection, newTarget, dockingPoint, hints) {
    this._modeling.reconnectEnd(connection, newTarget, dockingPoint, hints);
  };

  /**
   * A handler that implements reversible resizing of shapes.
   *
   * @param {Modeling} modeling
   */
  function ResizeShapeHandler(modeling) {
    this._modeling = modeling;
  }

  ResizeShapeHandler.$inject = [ 'modeling' ];

  /**
   * {
   *   shape: {....}
   *   newBounds: {
   *     width:  20,
   *     height: 40,
   *     x:       5,
   *     y:      10
   *   }
   *
   * }
   */
  ResizeShapeHandler.prototype.execute = function(context) {
    var shape = context.shape,
        newBounds = context.newBounds,
        minBounds = context.minBounds;

    if (newBounds.x === undefined || newBounds.y === undefined ||
        newBounds.width === undefined || newBounds.height === undefined) {
      throw new Error('newBounds must have {x, y, width, height} properties');
    }

    if (minBounds && (newBounds.width < minBounds.width
      || newBounds.height < minBounds.height)) {
      throw new Error('width and height cannot be less than minimum height and width');
    } else if (!minBounds
      && newBounds.width < 10 || newBounds.height < 10) {
      throw new Error('width and height cannot be less than 10px');
    }

    // save old bbox in context
    context.oldBounds = {
      width:  shape.width,
      height: shape.height,
      x:      shape.x,
      y:      shape.y
    };

    // update shape
    assign(shape, {
      width:  newBounds.width,
      height: newBounds.height,
      x:      newBounds.x,
      y:      newBounds.y
    });

    return shape;
  };

  ResizeShapeHandler.prototype.postExecute = function(context) {
    var modeling = this._modeling;

    var shape = context.shape,
        oldBounds = context.oldBounds,
        hints = context.hints || {};

    if (hints.layout === false) {
      return;
    }

    forEach$1(shape.incoming, function(c) {
      modeling.layoutConnection(c, {
        connectionEnd: getResizedTargetAnchor(c, shape, oldBounds)
      });
    });

    forEach$1(shape.outgoing, function(c) {
      modeling.layoutConnection(c, {
        connectionStart: getResizedSourceAnchor(c, shape, oldBounds)
      });
    });

  };

  ResizeShapeHandler.prototype.revert = function(context) {

    var shape = context.shape,
        oldBounds = context.oldBounds;

    // restore previous bbox
    assign(shape, {
      width:  oldBounds.width,
      height: oldBounds.height,
      x:      oldBounds.x,
      y:      oldBounds.y
    });

    return shape;
  };

  /**
   * Add or remove space by moving and resizing shapes and updating connection waypoints.
   */
  function SpaceToolHandler(modeling) {
    this._modeling = modeling;
  }

  SpaceToolHandler.$inject = [ 'modeling' ];

  SpaceToolHandler.prototype.preExecute = function(context) {
    var delta = context.delta,
        direction = context.direction,
        movingShapes = context.movingShapes,
        resizingShapes = context.resizingShapes,
        start = context.start,
        oldBounds = {};

    // (1) move shapes
    this.moveShapes(movingShapes, delta);

    // (2a) save old bounds of resized shapes
    forEach$1(resizingShapes, function(shape) {
      oldBounds[shape.id] = getBounds(shape);
    });

    // (2b) resize shapes
    this.resizeShapes(resizingShapes, delta, direction);

    // (3) update connection waypoints
    this.updateConnectionWaypoints(
      getWaypointsUpdatingConnections(movingShapes, resizingShapes),
      delta,
      direction,
      start,
      movingShapes,
      resizingShapes,
      oldBounds
    );
  };

  SpaceToolHandler.prototype.execute = function() {};
  SpaceToolHandler.prototype.revert = function() {};

  SpaceToolHandler.prototype.moveShapes = function(shapes, delta) {
    var self = this;

    forEach$1(shapes, function(element) {
      self._modeling.moveShape(element, delta, null, {
        autoResize: false,
        layout: false,
        recurse: false
      });
    });
  };

  SpaceToolHandler.prototype.resizeShapes = function(shapes, delta, direction) {
    var self = this;

    forEach$1(shapes, function(shape) {
      var newBounds = resizeBounds(shape, direction, delta);

      self._modeling.resizeShape(shape, newBounds, null, {
        attachSupport: false,
        autoResize: false,
        layout: false
      });
    });
  };

  /**
   * Update connections waypoints according to the rules:
   *   1. Both source and target are moved/resized => move waypoints by the delta
   *   2. Only one of source and target is moved/resized => re-layout connection with moved start/end
   */
  SpaceToolHandler.prototype.updateConnectionWaypoints = function(
      connections,
      delta,
      direction,
      start,
      movingShapes,
      resizingShapes,
      oldBounds
  ) {
    var self = this,
        affectedShapes = movingShapes.concat(resizingShapes);

    forEach$1(connections, function(connection) {
      var source = connection.source,
          target = connection.target,
          waypoints = copyWaypoints(connection),
          axis = getAxisFromDirection(direction),
          layoutHints = {
            labelBehavior: false
          };

      if (includes$1(affectedShapes, source) && includes$1(affectedShapes, target)) {

        // move waypoints
        waypoints = map(waypoints, function(waypoint) {
          if (shouldMoveWaypoint(waypoint, start, direction)) {

            // move waypoint
            waypoint[ axis ] = waypoint[ axis ] + delta[ axis ];
          }

          if (waypoint.original && shouldMoveWaypoint(waypoint.original, start, direction)) {

            // move waypoint original
            waypoint.original[ axis ] = waypoint.original[ axis ] + delta[ axis ];
          }

          return waypoint;
        });

        self._modeling.updateWaypoints(connection, waypoints, {
          labelBehavior: false
        });
      } else if (includes$1(affectedShapes, source) || includes$1(affectedShapes, target)) {

        // re-layout connection with moved start/end
        if (includes$1(movingShapes, source)) {
          layoutHints.connectionStart = getMovedSourceAnchor(connection, source, delta);
        } else if (includes$1(movingShapes, target)) {
          layoutHints.connectionEnd = getMovedTargetAnchor(connection, target, delta);
        } else if (includes$1(resizingShapes, source)) {
          layoutHints.connectionStart = getResizedSourceAnchor(
            connection, source, oldBounds[source.id]
          );
        } else if (includes$1(resizingShapes, target)) {
          layoutHints.connectionEnd = getResizedTargetAnchor(
            connection, target, oldBounds[target.id]
          );
        }

        self._modeling.layoutConnection(connection, layoutHints);
      }
    });
  };


  // helpers //////////

  function copyWaypoint(waypoint) {
    return assign({}, waypoint);
  }

  function copyWaypoints(connection) {
    return map(connection.waypoints, function(waypoint) {

      waypoint = copyWaypoint(waypoint);

      if (waypoint.original) {
        waypoint.original = copyWaypoint(waypoint.original);
      }

      return waypoint;
    });
  }

  function getAxisFromDirection(direction) {
    switch (direction) {
    case 'n':
      return 'y';
    case 'w':
      return 'x';
    case 's':
      return 'y';
    case 'e':
      return 'x';
    }
  }

  function shouldMoveWaypoint(waypoint, start, direction) {
    var relevantAxis = getAxisFromDirection(direction);

    if (/e|s/.test(direction)) {
      return waypoint[ relevantAxis ] > start;
    } else if (/n|w/.test(direction)) {
      return waypoint[ relevantAxis ] < start;
    }
  }

  function includes$1(array, item) {
    return array.indexOf(item) !== -1;
  }

  function getBounds(shape) {
    return {
      x: shape.x,
      y: shape.y,
      height: shape.height,
      width: shape.width
    };
  }

  /**
   * A handler that toggles the collapsed state of an element
   * and the visibility of all its children.
   *
   * @param {Modeling} modeling
   */
  function ToggleShapeCollapseHandler(modeling) {
    this._modeling = modeling;
  }

  ToggleShapeCollapseHandler.$inject = [ 'modeling' ];


  ToggleShapeCollapseHandler.prototype.execute = function(context) {

    var shape = context.shape,
        children = shape.children;

    // recursively remember previous visibility of children
    context.oldChildrenVisibility = getElementsVisibilityRecursive(children);

    // toggle state
    shape.collapsed = !shape.collapsed;

    // recursively hide/show children
    var result = setHiddenRecursive(children, shape.collapsed);

    return [ shape ].concat(result);
  };


  ToggleShapeCollapseHandler.prototype.revert = function(context) {

    var shape = context.shape,
        oldChildrenVisibility = context.oldChildrenVisibility;

    var children = shape.children;

    // recursively set old visability of children
    var result = restoreVisibilityRecursive(children, oldChildrenVisibility);

    // retoggle state
    shape.collapsed = !shape.collapsed;

    return [ shape ].concat(result);
  };


  // helpers //////////////////////

  /**
   * Return a map { elementId -> hiddenState}.
   *
   * @param {Array<djs.model.Shape>} elements
   *
   * @return {Object}
   */
  function getElementsVisibilityRecursive(elements) {

    var result = {};

    forEach$1(elements, function(element) {
      result[element.id] = element.hidden;

      if (element.children) {
        result = assign({}, result, getElementsVisibilityRecursive(element.children));
      }
    });

    return result;
  }


  function setHiddenRecursive(elements, newHidden) {
    var result = [];
    forEach$1(elements, function(element) {
      element.hidden = newHidden;

      result = result.concat(element);

      if (element.children) {
        result = result.concat(setHiddenRecursive(element.children, element.collapsed || newHidden));
      }
    });

    return result;
  }

  function restoreVisibilityRecursive(elements, lastState) {
    var result = [];
    forEach$1(elements, function(element) {
      element.hidden = lastState[element.id];

      result = result.concat(element);

      if (element.children) {
        result = result.concat(restoreVisibilityRecursive(element.children, lastState));
      }
    });

    return result;
  }

  /**
   * A handler that implements reversible attaching/detaching of shapes.
   */
  function UpdateAttachmentHandler(modeling) {
    this._modeling = modeling;
  }

  UpdateAttachmentHandler.$inject = [ 'modeling' ];


  UpdateAttachmentHandler.prototype.execute = function(context) {
    var shape = context.shape,
        newHost = context.newHost,
        oldHost = shape.host;

    // (0) detach from old host
    context.oldHost = oldHost;
    context.attacherIdx = removeAttacher(oldHost, shape);

    // (1) attach to new host
    addAttacher(newHost, shape);

    // (2) update host
    shape.host = newHost;

    return shape;
  };

  UpdateAttachmentHandler.prototype.revert = function(context) {
    var shape = context.shape,
        newHost = context.newHost,
        oldHost = context.oldHost,
        attacherIdx = context.attacherIdx;

    // (2) update host
    shape.host = oldHost;

    // (1) attach to new host
    removeAttacher(newHost, shape);

    // (0) detach from old host
    addAttacher(oldHost, shape, attacherIdx);

    return shape;
  };


  function removeAttacher(host, attacher) {

    // remove attacher from host
    return remove(host && host.attachers, attacher);
  }

  function addAttacher(host, attacher, idx) {

    if (!host) {
      return;
    }

    var attachers = host.attachers;

    if (!attachers) {
      host.attachers = attachers = [];
    }

    add(attachers, attacher, idx);
  }

  function UpdateWaypointsHandler() { }

  UpdateWaypointsHandler.prototype.execute = function(context) {

    var connection = context.connection,
        newWaypoints = context.newWaypoints;

    context.oldWaypoints = connection.waypoints;

    connection.waypoints = newWaypoints;

    return connection;
  };

  UpdateWaypointsHandler.prototype.revert = function(context) {

    var connection = context.connection,
        oldWaypoints = context.oldWaypoints;

    connection.waypoints = oldWaypoints;

    return connection;
  };

  /**
   * The basic modeling entry point.
   *
   * @param {EventBus} eventBus
   * @param {ElementFactory} elementFactory
   * @param {CommandStack} commandStack
   */
  function Modeling$1(eventBus, elementFactory, commandStack) {
    this._eventBus = eventBus;
    this._elementFactory = elementFactory;
    this._commandStack = commandStack;

    var self = this;

    eventBus.on('diagram.init', function() {

      // register modeling handlers
      self.registerHandlers(commandStack);
    });
  }

  Modeling$1.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ];


  Modeling$1.prototype.getHandlers = function() {
    return {
      'shape.append': AppendShapeHandler,
      'shape.create': CreateShapeHandler,
      'shape.delete': DeleteShapeHandler,
      'shape.move': MoveShapeHandler,
      'shape.resize': ResizeShapeHandler,
      'shape.replace': ReplaceShapeHandler,
      'shape.toggleCollapse': ToggleShapeCollapseHandler,

      'spaceTool': SpaceToolHandler,

      'label.create': CreateLabelHandler,

      'connection.create': CreateConnectionHandler,
      'connection.delete': DeleteConnectionHandler,
      'connection.move': MoveConnectionHandler,
      'connection.layout': LayoutConnectionHandler,

      'connection.updateWaypoints': UpdateWaypointsHandler,

      'connection.reconnect': ReconnectConnectionHandler,

      'elements.create': CreateElementsHandler,
      'elements.move': MoveElementsHandler,
      'elements.delete': DeleteElementsHandler,

      'elements.distribute': DistributeElements,
      'elements.align': AlignElements,

      'element.updateAttachment': UpdateAttachmentHandler
    };
  };

  /**
   * Register handlers with the command stack
   *
   * @param {CommandStack} commandStack
   */
  Modeling$1.prototype.registerHandlers = function(commandStack) {
    forEach$1(this.getHandlers(), function(handler, id) {
      commandStack.registerHandler(id, handler);
    });
  };


  // modeling helpers //////////////////////

  Modeling$1.prototype.moveShape = function(shape, delta, newParent, newParentIndex, hints) {

    if (typeof newParentIndex === 'object') {
      hints = newParentIndex;
      newParentIndex = null;
    }

    var context = {
      shape: shape,
      delta:  delta,
      newParent: newParent,
      newParentIndex: newParentIndex,
      hints: hints || {}
    };

    this._commandStack.execute('shape.move', context);
  };


  /**
   * Update the attachment of the given shape.
   *
   * @param {djs.mode.Base} shape
   * @param {djs.model.Base} [newHost]
   */
  Modeling$1.prototype.updateAttachment = function(shape, newHost) {
    var context = {
      shape: shape,
      newHost: newHost
    };

    this._commandStack.execute('element.updateAttachment', context);
  };


  /**
   * Move a number of shapes to a new target, either setting it as
   * the new parent or attaching it.
   *
   * @param {Array<djs.mode.Base>} shapes
   * @param {Point} delta
   * @param {djs.model.Base} [target]
   * @param {Object} [hints]
   * @param {boolean} [hints.attach=false]
   */
  Modeling$1.prototype.moveElements = function(shapes, delta, target, hints) {

    hints = hints || {};

    var attach = hints.attach;

    var newParent = target,
        newHost;

    if (attach === true) {
      newHost = target;
      newParent = target.parent;
    } else

    if (attach === false) {
      newHost = null;
    }

    var context = {
      shapes: shapes,
      delta: delta,
      newParent: newParent,
      newHost: newHost,
      hints: hints
    };

    this._commandStack.execute('elements.move', context);
  };


  Modeling$1.prototype.moveConnection = function(connection, delta, newParent, newParentIndex, hints) {

    if (typeof newParentIndex === 'object') {
      hints = newParentIndex;
      newParentIndex = undefined;
    }

    var context = {
      connection: connection,
      delta: delta,
      newParent: newParent,
      newParentIndex: newParentIndex,
      hints: hints || {}
    };

    this._commandStack.execute('connection.move', context);
  };


  Modeling$1.prototype.layoutConnection = function(connection, hints) {
    var context = {
      connection: connection,
      hints: hints || {}
    };

    this._commandStack.execute('connection.layout', context);
  };


  /**
   * Create connection.
   *
   * @param {djs.model.Base} source
   * @param {djs.model.Base} target
   * @param {number} [parentIndex]
   * @param {Object|djs.model.Connection} connection
   * @param {djs.model.Base} parent
   * @param {Object} hints
   *
   * @return {djs.model.Connection} the created connection.
   */
  Modeling$1.prototype.createConnection = function(source, target, parentIndex, connection, parent, hints) {

    if (typeof parentIndex === 'object') {
      hints = parent;
      parent = connection;
      connection = parentIndex;
      parentIndex = undefined;
    }

    connection = this._create('connection', connection);

    var context = {
      source: source,
      target: target,
      parent: parent,
      parentIndex: parentIndex,
      connection: connection,
      hints: hints
    };

    this._commandStack.execute('connection.create', context);

    return context.connection;
  };


  /**
   * Create a shape at the specified position.
   *
   * @param {djs.model.Shape|Object} shape
   * @param {Point} position
   * @param {djs.model.Shape|djs.model.Root} target
   * @param {number} [parentIndex] position in parents children list
   * @param {Object} [hints]
   * @param {boolean} [hints.attach] whether to attach to target or become a child
   *
   * @return {djs.model.Shape} the created shape
   */
  Modeling$1.prototype.createShape = function(shape, position, target, parentIndex, hints) {

    if (typeof parentIndex !== 'number') {
      hints = parentIndex;
      parentIndex = undefined;
    }

    hints = hints || {};

    var attach = hints.attach,
        parent,
        host;

    shape = this._create('shape', shape);

    if (attach) {
      parent = target.parent;
      host = target;
    } else {
      parent = target;
    }

    var context = {
      position: position,
      shape: shape,
      parent: parent,
      parentIndex: parentIndex,
      host: host,
      hints: hints
    };

    this._commandStack.execute('shape.create', context);

    return context.shape;
  };


  Modeling$1.prototype.createElements = function(elements, position, parent, parentIndex, hints) {
    if (!isArray$3(elements)) {
      elements = [ elements ];
    }

    if (typeof parentIndex !== 'number') {
      hints = parentIndex;
      parentIndex = undefined;
    }

    hints = hints || {};

    var context = {
      position: position,
      elements: elements,
      parent: parent,
      parentIndex: parentIndex,
      hints: hints
    };

    this._commandStack.execute('elements.create', context);

    return context.elements;
  };


  Modeling$1.prototype.createLabel = function(labelTarget, position, label, parent) {

    label = this._create('label', label);

    var context = {
      labelTarget: labelTarget,
      position: position,
      parent: parent || labelTarget.parent,
      shape: label
    };

    this._commandStack.execute('label.create', context);

    return context.shape;
  };


  /**
   * Append shape to given source, drawing a connection
   * between source and the newly created shape.
   *
   * @param {djs.model.Shape} source
   * @param {djs.model.Shape|Object} shape
   * @param {Point} position
   * @param {djs.model.Shape} target
   * @param {Object} [hints]
   * @param {boolean} [hints.attach]
   * @param {djs.model.Connection|Object} [hints.connection]
   * @param {djs.model.Base} [hints.connectionParent]
   *
   * @return {djs.model.Shape} the newly created shape
   */
  Modeling$1.prototype.appendShape = function(source, shape, position, target, hints) {

    hints = hints || {};

    shape = this._create('shape', shape);

    var context = {
      source: source,
      position: position,
      target: target,
      shape: shape,
      connection: hints.connection,
      connectionParent: hints.connectionParent,
      hints: hints
    };

    this._commandStack.execute('shape.append', context);

    return context.shape;
  };


  Modeling$1.prototype.removeElements = function(elements) {
    var context = {
      elements: elements
    };

    this._commandStack.execute('elements.delete', context);
  };


  Modeling$1.prototype.distributeElements = function(groups, axis, dimension) {
    var context = {
      groups: groups,
      axis: axis,
      dimension: dimension
    };

    this._commandStack.execute('elements.distribute', context);
  };


  Modeling$1.prototype.removeShape = function(shape, hints) {
    var context = {
      shape: shape,
      hints: hints || {}
    };

    this._commandStack.execute('shape.delete', context);
  };


  Modeling$1.prototype.removeConnection = function(connection, hints) {
    var context = {
      connection: connection,
      hints: hints || {}
    };

    this._commandStack.execute('connection.delete', context);
  };

  Modeling$1.prototype.replaceShape = function(oldShape, newShape, hints) {
    var context = {
      oldShape: oldShape,
      newData: newShape,
      hints: hints || {}
    };

    this._commandStack.execute('shape.replace', context);

    return context.newShape;
  };

  Modeling$1.prototype.alignElements = function(elements, alignment) {
    var context = {
      elements: elements,
      alignment: alignment
    };

    this._commandStack.execute('elements.align', context);
  };

  Modeling$1.prototype.resizeShape = function(shape, newBounds, minBounds, hints) {
    var context = {
      shape: shape,
      newBounds: newBounds,
      minBounds: minBounds,
      hints: hints
    };

    this._commandStack.execute('shape.resize', context);
  };

  Modeling$1.prototype.createSpace = function(movingShapes, resizingShapes, delta, direction, start) {
    var context = {
      delta: delta,
      direction: direction,
      movingShapes: movingShapes,
      resizingShapes: resizingShapes,
      start: start
    };

    this._commandStack.execute('spaceTool', context);
  };

  Modeling$1.prototype.updateWaypoints = function(connection, newWaypoints, hints) {
    var context = {
      connection: connection,
      newWaypoints: newWaypoints,
      hints: hints || {}
    };

    this._commandStack.execute('connection.updateWaypoints', context);
  };

  Modeling$1.prototype.reconnect = function(connection, source, target, dockingOrPoints, hints) {
    var context = {
      connection: connection,
      newSource: source,
      newTarget: target,
      dockingOrPoints: dockingOrPoints,
      hints: hints || {}
    };

    this._commandStack.execute('connection.reconnect', context);
  };

  Modeling$1.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) {
    if (!hints) {
      hints = {};
    }

    this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign(hints, {
      docking: 'source'
    }));
  };

  Modeling$1.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) {
    if (!hints) {
      hints = {};
    }

    this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign(hints, {
      docking: 'target'
    }));
  };

  Modeling$1.prototype.connect = function(source, target, attrs, hints) {
    return this.createConnection(source, target, attrs || {}, source.parent, hints);
  };

  Modeling$1.prototype._create = function(type, attrs) {
    if (attrs instanceof Base$1) {
      return attrs;
    } else {
      return this._elementFactory.create(type, attrs);
    }
  };

  Modeling$1.prototype.toggleCollapse = function(shape, hints) {
    var context = {
      shape: shape,
      hints: hints || {}
    };

    this._commandStack.execute('shape.toggleCollapse', context);
  };

  function UpdateModdlePropertiesHandler(elementRegistry) {
    this._elementRegistry = elementRegistry;
  }

  UpdateModdlePropertiesHandler.$inject = [ 'elementRegistry' ];

  UpdateModdlePropertiesHandler.prototype.execute = function(context) {

    var element = context.element,
        moddleElement = context.moddleElement,
        properties = context.properties;

    if (!moddleElement) {
      throw new Error('<moddleElement> required');
    }

    // TODO(nikku): we need to ensure that ID properties
    // are properly registered / unregistered via
    // this._moddle.ids.assigned(id)
    var changed = context.changed || this.getVisualReferences(moddleElement).concat(element);
    var oldProperties = context.oldProperties || getModdleProperties(moddleElement, keys(properties));

    setModdleProperties(moddleElement, properties);

    context.oldProperties = oldProperties;
    context.changed = changed;

    return changed;
  };

  UpdateModdlePropertiesHandler.prototype.revert = function(context) {
    var oldProperties = context.oldProperties,
        moddleElement = context.moddleElement,
        changed = context.changed;

    setModdleProperties(moddleElement, oldProperties);

    return changed;
  };

  /**
   * Return visual references of given moddle element within the diagram.
   *
   * @param {ModdleElement} moddleElement
   *
   * @return {Array<djs.model.Element>}
   */
  UpdateModdlePropertiesHandler.prototype.getVisualReferences = function(moddleElement) {

    var elementRegistry = this._elementRegistry;

    if (is$1(moddleElement, 'bpmn:DataObject')) {
      return getAllDataObjectReferences(moddleElement, elementRegistry);
    }

    return [];
  };


  // helpers /////////////////

  function getModdleProperties(moddleElement, propertyNames) {
    return reduce(propertyNames, function(result, key) {
      result[key] = moddleElement.get(key);
      return result;
    }, {});
  }

  function setModdleProperties(moddleElement, properties) {
    forEach$1(properties, function(value, key) {
      moddleElement.set(key, value);
    });
  }

  function getAllDataObjectReferences(dataObject, elementRegistry) {
    return elementRegistry.filter(function(element) {
      return (
        is$1(element, 'bpmn:DataObjectReference') &&
            getBusinessObject(element).dataObjectRef === dataObject
      );
    });
  }

  var DEFAULT_FLOW = 'default',
      ID = 'id',
      DI = 'di';

  var NULL_DIMENSIONS$1 = {
    width: 0,
    height: 0
  };

  /**
   * A handler that implements a BPMN 2.0 property update.
   *
   * This should be used to set simple properties on elements with
   * an underlying BPMN business object.
   *
   * Use respective diagram-js provided handlers if you would
   * like to perform automated modeling.
   */
  function UpdatePropertiesHandler(
      elementRegistry, moddle, translate,
      modeling, textRenderer) {

    this._elementRegistry = elementRegistry;
    this._moddle = moddle;
    this._translate = translate;
    this._modeling = modeling;
    this._textRenderer = textRenderer;
  }

  UpdatePropertiesHandler.$inject = [
    'elementRegistry',
    'moddle',
    'translate',
    'modeling',
    'textRenderer'
  ];


  // api //////////////////////

  /**
   * Updates a BPMN element with a list of new properties
   *
   * @param {Object} context
   * @param {djs.model.Base} context.element the element to update
   * @param {Object} context.properties a list of properties to set on the element's
   *                                    businessObject (the BPMN model element)
   *
   * @return {Array<djs.model.Base>} the updated element
   */
  UpdatePropertiesHandler.prototype.execute = function(context) {

    var element = context.element,
        changed = [ element ],
        translate = this._translate;

    if (!element) {
      throw new Error(translate('element required'));
    }

    var elementRegistry = this._elementRegistry,
        ids = this._moddle.ids;

    var businessObject = element.businessObject,
        properties = unwrapBusinessObjects(context.properties),
        oldProperties = context.oldProperties || getProperties(element, properties);

    if (isIdChange(properties, businessObject)) {
      ids.unclaim(businessObject[ID]);

      elementRegistry.updateId(element, properties[ID]);

      ids.claim(properties[ID], businessObject);
    }

    // correctly indicate visual changes on default flow updates
    if (DEFAULT_FLOW in properties) {

      if (properties[DEFAULT_FLOW]) {
        changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
      }

      if (businessObject[DEFAULT_FLOW]) {
        changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
      }
    }

    // update properties
    setProperties(element, properties);

    // store old values
    context.oldProperties = oldProperties;
    context.changed = changed;

    // indicate changed on objects affected by the update
    return changed;
  };


  UpdatePropertiesHandler.prototype.postExecute = function(context) {
    var element = context.element,
        label = element.label;

    var text = label && getBusinessObject(label).name;

    if (!text) {
      return;
    }

    // get layouted text bounds and resize external
    // external label accordingly
    var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);

    this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS$1);
  };

  /**
   * Reverts the update on a BPMN elements properties.
   *
   * @param  {Object} context
   *
   * @return {djs.model.Base} the updated element
   */
  UpdatePropertiesHandler.prototype.revert = function(context) {

    var element = context.element,
        properties = context.properties,
        oldProperties = context.oldProperties,
        businessObject = element.businessObject,
        elementRegistry = this._elementRegistry,
        ids = this._moddle.ids;

    // update properties
    setProperties(element, oldProperties);

    if (isIdChange(properties, businessObject)) {
      ids.unclaim(properties[ID]);

      elementRegistry.updateId(element, oldProperties[ID]);

      ids.claim(oldProperties[ID], businessObject);
    }

    return context.changed;
  };


  function isIdChange(properties, businessObject) {
    return ID in properties && properties[ID] !== businessObject[ID];
  }


  function getProperties(element, properties) {
    var propertyNames = keys(properties),
        businessObject = element.businessObject,
        di = getDi(element);

    return reduce(propertyNames, function(result, key) {

      // handle DI separately
      if (key !== DI) {
        result[key] = businessObject.get(key);

      } else {
        result[key] = getDiProperties(di, keys(properties.di));
      }

      return result;
    }, {});
  }


  function getDiProperties(di, propertyNames) {
    return reduce(propertyNames, function(result, key) {
      result[key] = di && di.get(key);

      return result;
    }, {});
  }


  function setProperties(element, properties) {
    var businessObject = element.businessObject,
        di = getDi(element);

    forEach$1(properties, function(value, key) {

      if (key !== DI) {
        businessObject.set(key, value);
      } else {

        // only update, if di exists
        if (di) {
          setDiProperties(di, value);
        }
      }
    });
  }


  function setDiProperties(di, properties) {
    forEach$1(properties, function(value, key) {
      di.set(key, value);
    });
  }


  var referencePropertyNames = [ 'default' ];

  /**
   * Make sure we unwrap the actual business object
   * behind diagram element that may have been
   * passed as arguments.
   *
   * @param  {Object} properties
   *
   * @return {Object} unwrappedProps
   */
  function unwrapBusinessObjects(properties) {

    var unwrappedProps = assign({}, properties);

    referencePropertyNames.forEach(function(name) {
      if (name in properties) {
        unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
      }
    });

    return unwrappedProps;
  }

  function UpdateCanvasRootHandler(canvas, modeling) {
    this._canvas = canvas;
    this._modeling = modeling;
  }

  UpdateCanvasRootHandler.$inject = [
    'canvas',
    'modeling'
  ];


  UpdateCanvasRootHandler.prototype.execute = function(context) {

    var canvas = this._canvas;

    var newRoot = context.newRoot,
        newRootBusinessObject = newRoot.businessObject,
        oldRoot = canvas.getRootElement(),
        oldRootBusinessObject = oldRoot.businessObject,
        bpmnDefinitions = oldRootBusinessObject.$parent,
        diPlane = getDi(oldRoot);

    // (1) replace process old <> new root
    canvas.setRootElement(newRoot);
    canvas.removeRootElement(oldRoot);

    // (2) update root elements
    add(bpmnDefinitions.rootElements, newRootBusinessObject);
    newRootBusinessObject.$parent = bpmnDefinitions;

    remove(bpmnDefinitions.rootElements, oldRootBusinessObject);
    oldRootBusinessObject.$parent = null;

    // (3) wire di
    oldRoot.di = null;

    diPlane.bpmnElement = newRootBusinessObject;
    newRoot.di = diPlane;

    context.oldRoot = oldRoot;

    // TODO(nikku): return changed elements?
    // return [ newRoot, oldRoot ];
  };


  UpdateCanvasRootHandler.prototype.revert = function(context) {

    var canvas = this._canvas;

    var newRoot = context.newRoot,
        newRootBusinessObject = newRoot.businessObject,
        oldRoot = context.oldRoot,
        oldRootBusinessObject = oldRoot.businessObject,
        bpmnDefinitions = newRootBusinessObject.$parent,
        diPlane = getDi(newRoot);

    // (1) replace process old <> new root
    canvas.setRootElement(oldRoot);
    canvas.removeRootElement(newRoot);

    // (2) update root elements
    remove(bpmnDefinitions.rootElements, newRootBusinessObject);
    newRootBusinessObject.$parent = null;

    add(bpmnDefinitions.rootElements, oldRootBusinessObject);
    oldRootBusinessObject.$parent = bpmnDefinitions;

    // (3) wire di
    newRoot.di = null;

    diPlane.bpmnElement = oldRootBusinessObject;
    oldRoot.di = diPlane;

    // TODO(nikku): return changed elements?
    // return [ newRoot, oldRoot ];
  };

  /**
   * A handler that allows us to add a new lane
   * above or below an existing one.
   *
   * @param {Modeling} modeling
   * @param {SpaceTool} spaceTool
   */
  function AddLaneHandler(modeling, spaceTool) {
    this._modeling = modeling;
    this._spaceTool = spaceTool;
  }

  AddLaneHandler.$inject = [
    'modeling',
    'spaceTool'
  ];


  AddLaneHandler.prototype.preExecute = function(context) {

    var spaceTool = this._spaceTool,
        modeling = this._modeling;

    var shape = context.shape,
        location = context.location;

    var lanesRoot = getLanesRoot(shape);

    var isRoot = lanesRoot === shape,
        laneParent = isRoot ? shape : shape.parent;

    var existingChildLanes = getChildLanes(laneParent);

    // (0) add a lane if we currently got none and are adding to root
    if (!existingChildLanes.length) {
      modeling.createShape({ type: 'bpmn:Lane' }, {
        x: shape.x + LANE_INDENTATION,
        y: shape.y,
        width: shape.width - LANE_INDENTATION,
        height: shape.height
      }, laneParent);
    }

    // (1) collect affected elements to create necessary space
    var allAffected = [];

    eachElement(lanesRoot, function(element) {
      allAffected.push(element);

      // handle element labels in the diagram root
      if (element.label) {
        allAffected.push(element.label);
      }

      if (element === shape) {
        return [];
      }

      return filter(element.children, function(c) {
        return c !== shape;
      });
    });

    var offset = location === 'top' ? -120 : 120,
        lanePosition = location === 'top' ? shape.y : shape.y + shape.height,
        spacePos = lanePosition + (location === 'top' ? 10 : -10),
        direction = location === 'top' ? 'n' : 's';

    var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);

    spaceTool.makeSpace(
      adjustments.movingShapes,
      adjustments.resizingShapes,
      { x: 0, y: offset },
      direction,
      spacePos
    );

    // (2) create new lane at open space
    context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, {
      x: shape.x + (isRoot ? LANE_INDENTATION : 0),
      y: lanePosition - (location === 'top' ? 120 : 0),
      width: shape.width - (isRoot ? LANE_INDENTATION : 0),
      height: 120
    }, laneParent);
  };

  /**
   * A handler that splits a lane into a number of sub-lanes,
   * creating new sub lanes, if necessary.
   *
   * @param {Modeling} modeling
   */
  function SplitLaneHandler(modeling, translate) {
    this._modeling = modeling;
    this._translate = translate;
  }

  SplitLaneHandler.$inject = [
    'modeling',
    'translate'
  ];


  SplitLaneHandler.prototype.preExecute = function(context) {

    var modeling = this._modeling,
        translate = this._translate;

    var shape = context.shape,
        newLanesCount = context.count;

    var childLanes = getChildLanes(shape),
        existingLanesCount = childLanes.length;

    if (existingLanesCount > newLanesCount) {
      throw new Error(translate('more than {count} child lanes', { count: newLanesCount }));
    }

    var newLanesHeight = Math.round(shape.height / newLanesCount);

    // Iterate from top to bottom in child lane order,
    // resizing existing lanes and creating new ones
    // so that they split the parent proportionally.
    //
    // Due to rounding related errors, the bottom lane
    // needs to take up all the remaining space.
    var laneY,
        laneHeight,
        laneBounds,
        newLaneAttrs,
        idx;

    for (idx = 0; idx < newLanesCount; idx++) {

      laneY = shape.y + idx * newLanesHeight;

      // if bottom lane
      if (idx === newLanesCount - 1) {
        laneHeight = shape.height - (newLanesHeight * idx);
      } else {
        laneHeight = newLanesHeight;
      }

      laneBounds = {
        x: shape.x + LANE_INDENTATION,
        y: laneY,
        width: shape.width - LANE_INDENTATION,
        height: laneHeight
      };

      if (idx < existingLanesCount) {

        // resize existing lane
        modeling.resizeShape(childLanes[idx], laneBounds);
      } else {

        // create a new lane at position
        newLaneAttrs = {
          type: 'bpmn:Lane'
        };

        modeling.createShape(newLaneAttrs, laneBounds, shape);
      }
    }
  };

  /**
   * A handler that resizes a lane.
   *
   * @param {Modeling} modeling
   */
  function ResizeLaneHandler(modeling, spaceTool) {
    this._modeling = modeling;
    this._spaceTool = spaceTool;
  }

  ResizeLaneHandler.$inject = [
    'modeling',
    'spaceTool'
  ];


  ResizeLaneHandler.prototype.preExecute = function(context) {

    var shape = context.shape,
        newBounds = context.newBounds,
        balanced = context.balanced;

    if (balanced !== false) {
      this.resizeBalanced(shape, newBounds);
    } else {
      this.resizeSpace(shape, newBounds);
    }
  };


  /**
   * Resize balanced, adjusting next / previous lane sizes.
   *
   * @param {djs.model.Shape} shape
   * @param {Bounds} newBounds
   */
  ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) {

    var modeling = this._modeling;

    var resizeNeeded = computeLanesResize(shape, newBounds);

    // resize the lane
    modeling.resizeShape(shape, newBounds);

    // resize other lanes as needed
    resizeNeeded.forEach(function(r) {
      modeling.resizeShape(r.shape, r.newBounds);
    });
  };


  /**
   * Resize, making actual space and moving below / above elements.
   *
   * @param {djs.model.Shape} shape
   * @param {Bounds} newBounds
   */
  ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) {
    var spaceTool = this._spaceTool;

    var shapeTrbl = asTRBL(shape),
        newTrbl = asTRBL(newBounds);

    var trblDiff = substractTRBL(newTrbl, shapeTrbl);

    var lanesRoot = getLanesRoot(shape);

    var allAffected = [],
        allLanes = [];

    eachElement(lanesRoot, function(element) {
      allAffected.push(element);

      if (is$1(element, 'bpmn:Lane') || is$1(element, 'bpmn:Participant')) {
        allLanes.push(element);
      }

      return element.children;
    });

    var change,
        spacePos,
        direction,
        offset,
        adjustments;

    if (trblDiff.bottom || trblDiff.top) {

      change = trblDiff.bottom || trblDiff.top;
      spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10);
      direction = trblDiff.bottom ? 's' : 'n';

      offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change;

      adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);

      spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction);
    }


    if (trblDiff.left || trblDiff.right) {

      change = trblDiff.right || trblDiff.left;
      spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100);
      direction = trblDiff.right ? 'e' : 'w';

      offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change;

      adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos);

      spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction);
    }
  };

  var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
      LANES_ATTR = 'lanes';


  /**
   * A handler that updates lane refs on changed elements
   */
  function UpdateFlowNodeRefsHandler(elementRegistry) {
    this._elementRegistry = elementRegistry;
  }

  UpdateFlowNodeRefsHandler.$inject = [
    'elementRegistry'
  ];


  UpdateFlowNodeRefsHandler.prototype.computeUpdates = function(flowNodeShapes, laneShapes) {

    var handledNodes = [];

    var updates = [];

    var participantCache = {};

    var allFlowNodeShapes = [];

    function isInLaneShape(element, laneShape) {

      var laneTrbl = asTRBL(laneShape);

      var elementMid = {
        x: element.x + element.width / 2,
        y: element.y + element.height / 2
      };

      return elementMid.x > laneTrbl.left &&
             elementMid.x < laneTrbl.right &&
             elementMid.y > laneTrbl.top &&
             elementMid.y < laneTrbl.bottom;
    }

    function addFlowNodeShape(flowNodeShape) {
      if (handledNodes.indexOf(flowNodeShape) === -1) {
        allFlowNodeShapes.push(flowNodeShape);
        handledNodes.push(flowNodeShape);
      }
    }

    function getAllLaneShapes(flowNodeShape) {

      var root = getLanesRoot(flowNodeShape);

      if (!participantCache[root.id]) {
        participantCache[root.id] = collectLanes(root);
      }

      return participantCache[root.id];
    }

    function getNewLanes(flowNodeShape) {
      if (!flowNodeShape.parent) {
        return [];
      }

      var allLaneShapes = getAllLaneShapes(flowNodeShape);

      return allLaneShapes.filter(function(l) {
        return isInLaneShape(flowNodeShape, l);
      }).map(function(shape) {
        return shape.businessObject;
      });
    }

    laneShapes.forEach(function(laneShape) {
      var root = getLanesRoot(laneShape);

      if (!root || handledNodes.indexOf(root) !== -1) {
        return;
      }

      var children = root.children.filter(function(c) {
        return is$1(c, 'bpmn:FlowNode');
      });

      children.forEach(addFlowNodeShape);

      handledNodes.push(root);
    });

    flowNodeShapes.forEach(addFlowNodeShape);


    allFlowNodeShapes.forEach(function(flowNodeShape) {

      var flowNode = flowNodeShape.businessObject;

      var lanes = flowNode.get(LANES_ATTR),
          remove = lanes.slice(),
          add = getNewLanes(flowNodeShape);

      updates.push({ flowNode: flowNode, remove: remove, add: add });
    });

    laneShapes.forEach(function(laneShape) {

      var lane = laneShape.businessObject;

      // lane got removed XX-)
      if (!laneShape.parent) {
        lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) {
          updates.push({ flowNode: flowNode, remove: [ lane ], add: [] });
        });
      }
    });

    return updates;
  };

  UpdateFlowNodeRefsHandler.prototype.execute = function(context) {

    var updates = context.updates;

    if (!updates) {
      updates = context.updates = this.computeUpdates(context.flowNodeShapes, context.laneShapes);
    }


    updates.forEach(function(update) {

      var flowNode = update.flowNode,
          lanes = flowNode.get(LANES_ATTR);

      // unwire old
      update.remove.forEach(function(oldLane) {
        remove(lanes, oldLane);
        remove(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
      });

      // wire new
      update.add.forEach(function(newLane) {
        add(lanes, newLane);
        add(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
      });
    });

    // TODO(nikku): return changed elements
    // return [ ... ];
  };


  UpdateFlowNodeRefsHandler.prototype.revert = function(context) {

    var updates = context.updates;

    updates.forEach(function(update) {

      var flowNode = update.flowNode,
          lanes = flowNode.get(LANES_ATTR);

      // unwire new
      update.add.forEach(function(newLane) {
        remove(lanes, newLane);
        remove(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
      });

      // wire old
      update.remove.forEach(function(oldLane) {
        add(lanes, oldLane);
        add(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
      });
    });

    // TODO(nikku): return changed elements
    // return [ ... ];
  };

  function IdClaimHandler(moddle) {
    this._moddle = moddle;
  }

  IdClaimHandler.$inject = [ 'moddle' ];


  IdClaimHandler.prototype.execute = function(context) {
    var ids = this._moddle.ids,
        id = context.id,
        element = context.element,
        claiming = context.claiming;

    if (claiming) {
      ids.claim(id, element);
    } else {
      ids.unclaim(id);
    }
  };

  /**
   * Command revert implementation.
   */
  IdClaimHandler.prototype.revert = function(context) {
    var ids = this._moddle.ids,
        id = context.id,
        element = context.element,
        claiming = context.claiming;

    if (claiming) {
      ids.unclaim(id);
    } else {
      ids.claim(id, element);
    }
  };

  var DEFAULT_COLORS = {
    fill: undefined,
    stroke: undefined
  };


  function SetColorHandler(commandStack) {
    this._commandStack = commandStack;

    this._normalizeColor = function(color) {

      // Remove color for falsy values.
      if (!color) {
        return undefined;
      }

      if (isString(color)) {
        var hexColor = colorToHex(color);

        if (hexColor) {
          return hexColor;
        }
      }

      throw new Error('invalid color value: ' + color);
    };
  }

  SetColorHandler.$inject = [
    'commandStack'
  ];


  SetColorHandler.prototype.postExecute = function(context) {
    var elements = context.elements,
        colors = context.colors || DEFAULT_COLORS;

    var self = this;

    var di = {};

    if ('fill' in colors) {
      assign(di, {
        'background-color': this._normalizeColor(colors.fill) });
    }

    if ('stroke' in colors) {
      assign(di, {
        'border-color': this._normalizeColor(colors.stroke) });
    }

    forEach$1(elements, function(element) {
      var assignedDi = isConnection$3(element) ? pick(di, [ 'border-color' ]) : di;

      // TODO @barmac: remove once we drop bpmn.io properties
      ensureLegacySupport(assignedDi);

      if (isLabel$6(element)) {

        // set label colors as bpmndi:BPMNLabel#color
        self._commandStack.execute('element.updateModdleProperties', {
          element: element,
          moddleElement: getDi(element).label,
          properties: {
            color: di['border-color']
          }
        });
      } else {

        // set colors bpmndi:BPMNEdge or bpmndi:BPMNShape
        self._commandStack.execute('element.updateProperties', {
          element: element,
          properties: {
            di: assignedDi
          }
        });
      }
    });

  };

  /**
   * Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names and for colors
   * with alpha less than 1.0. This depends on `<canvas>` serialization of the `context.fillStyle`.
   * Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
   *
   * @example
   * ```js
   * var color = 'fuchsia';
   * console.log(colorToHex(color));
   * // "#ff00ff"
   * color = 'rgba(1,2,3,0.4)';
   * console.log(colorToHex(color));
   * // null
   * ```
   *
   * @param {string} color
   * @returns {string|null}
   */
  function colorToHex(color) {
    var context = document.createElement('canvas').getContext('2d');

    // (0) Start with transparent to account for browser default values.
    context.fillStyle = 'transparent';

    // (1) Assign color so that it's serialized.
    context.fillStyle = color;

    // (2) Return null for non-hex serialization result.
    return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null;
  }

  function isConnection$3(element) {
    return !!element.waypoints;
  }

  /**
   * Add legacy properties if required.
   * @param {{ 'border-color': string?, 'background-color': string? }} di
   */
  function ensureLegacySupport(di) {
    if ('border-color' in di) {
      di.stroke = di['border-color'];
    }

    if ('background-color' in di) {
      di.fill = di['background-color'];
    }
  }

  var NULL_DIMENSIONS = {
    width: 0,
    height: 0
  };


  /**
   * A handler that updates the text of a BPMN element.
   */
  function UpdateLabelHandler(modeling, textRenderer, bpmnFactory) {

    /**
     * Creates an empty `diLabel` attribute for embedded labels.
     *
     * @param {djs.model.Base} element
     * @param {string} text
     */
    function ensureInternalLabelDi(element, text) {
      if (isLabelExternal(element)) {
        return;
      }

      var di = getDi(element);

      if (text && !di.label) {
        di.label = bpmnFactory.create('bpmndi:BPMNLabel');
      }

      if (!text && di.label) {
        delete di.label;
      }
    }


    /**
     * Set the label and return the changed elements.
     *
     * Element parameter can be label itself or connection (i.e. sequence flow).
     *
     * @param {djs.model.Base} element
     * @param {string} text
     */
    function setText(element, text) {

      // external label if present
      var label = element.label || element;

      var labelTarget = element.labelTarget || element;

      setLabel(label, text);

      ensureInternalLabelDi(element, text);

      return [ label, labelTarget ];
    }

    function preExecute(ctx) {
      var element = ctx.element,
          businessObject = element.businessObject,
          newLabel = ctx.newLabel;

      if (!isLabel$6(element)
          && isLabelExternal(element)
          && !hasExternalLabel(element)
          && !isEmptyText(newLabel)) {

        // create label
        var paddingTop = 7;

        var labelCenter = getExternalLabelMid(element);

        labelCenter = {
          x: labelCenter.x,
          y: labelCenter.y + paddingTop
        };

        modeling.createLabel(element, labelCenter, {
          id: businessObject.id + '_label',
          businessObject: businessObject,
          di: element.di
        });
      }
    }

    function execute(ctx) {
      ctx.oldLabel = getLabel(ctx.element);
      return setText(ctx.element, ctx.newLabel);
    }

    function revert(ctx) {
      return setText(ctx.element, ctx.oldLabel);
    }

    function postExecute(ctx) {
      var element = ctx.element,
          label = element.label || element,
          newLabel = ctx.newLabel,
          newBounds = ctx.newBounds,
          hints = ctx.hints || {};

      // ignore internal labels for elements except text annotations
      if (!isLabel$6(label) && !is$1(label, 'bpmn:TextAnnotation')) {
        return;
      }

      if (isLabel$6(label) && isEmptyText(newLabel)) {

        if (hints.removeShape !== false) {
          modeling.removeShape(label, { unsetLabel: false });
        }

        return;
      }

      var text = getLabel(label);

      // resize element based on label _or_ pre-defined bounds
      if (typeof newBounds === 'undefined') {
        newBounds = textRenderer.getExternalLabelBounds(label, text);
      }

      // setting newBounds to false or _null_ will
      // disable the postExecute resize operation
      if (newBounds) {
        modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
      }
    }

    // API

    this.preExecute = preExecute;
    this.execute = execute;
    this.revert = revert;
    this.postExecute = postExecute;
  }

  UpdateLabelHandler.$inject = [
    'modeling',
    'textRenderer',
    'bpmnFactory'
  ];


  // helpers ///////////////////////

  function isEmptyText(label) {
    return !label || !label.trim();
  }

  /**
   * BPMN 2.0 modeling features activator
   *
   * @param {EventBus} eventBus
   * @param {ElementFactory} elementFactory
   * @param {CommandStack} commandStack
   * @param {BpmnRules} bpmnRules
   */
  function Modeling(
      eventBus, elementFactory, commandStack,
      bpmnRules) {

    Modeling$1.call(this, eventBus, elementFactory, commandStack);

    this._bpmnRules = bpmnRules;
  }

  e(Modeling, Modeling$1);

  Modeling.$inject = [
    'eventBus',
    'elementFactory',
    'commandStack',
    'bpmnRules'
  ];


  Modeling.prototype.getHandlers = function() {
    var handlers = Modeling$1.prototype.getHandlers.call(this);

    handlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler;
    handlers['element.updateProperties'] = UpdatePropertiesHandler;
    handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
    handlers['lane.add'] = AddLaneHandler;
    handlers['lane.resize'] = ResizeLaneHandler;
    handlers['lane.split'] = SplitLaneHandler;
    handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
    handlers['id.updateClaim'] = IdClaimHandler;
    handlers['element.setColor'] = SetColorHandler;
    handlers['element.updateLabel'] = UpdateLabelHandler;

    return handlers;
  };


  Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
    this._commandStack.execute('element.updateLabel', {
      element: element,
      newLabel: newLabel,
      newBounds: newBounds,
      hints: hints || {}
    });
  };


  Modeling.prototype.connect = function(source, target, attrs, hints) {

    var bpmnRules = this._bpmnRules;

    if (!attrs) {
      attrs = bpmnRules.canConnect(source, target);
    }

    if (!attrs) {
      return;
    }

    return this.createConnection(source, target, attrs, source.parent, hints);
  };


  Modeling.prototype.updateModdleProperties = function(element, moddleElement, properties) {
    this._commandStack.execute('element.updateModdleProperties', {
      element: element,
      moddleElement: moddleElement,
      properties: properties
    });
  };

  Modeling.prototype.updateProperties = function(element, properties) {
    this._commandStack.execute('element.updateProperties', {
      element: element,
      properties: properties
    });
  };

  Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
    this._commandStack.execute('lane.resize', {
      shape: laneShape,
      newBounds: newBounds,
      balanced: balanced
    });
  };

  Modeling.prototype.addLane = function(targetLaneShape, location) {
    var context = {
      shape: targetLaneShape,
      location: location
    };

    this._commandStack.execute('lane.add', context);

    return context.newLane;
  };

  Modeling.prototype.splitLane = function(targetLane, count) {
    this._commandStack.execute('lane.split', {
      shape: targetLane,
      count: count
    });
  };

  /**
   * Transform the current diagram into a collaboration.
   *
   * @return {djs.model.Root} the new root element
   */
  Modeling.prototype.makeCollaboration = function() {

    var collaborationElement = this._create('root', {
      type: 'bpmn:Collaboration'
    });

    var context = {
      newRoot: collaborationElement
    };

    this._commandStack.execute('canvas.updateRoot', context);

    return collaborationElement;
  };

  Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {

    this._commandStack.execute('lane.updateRefs', {
      flowNodeShapes: flowNodeShapes,
      laneShapes: laneShapes
    });
  };

  /**
   * Transform the current diagram into a process.
   *
   * @return {djs.model.Root} the new root element
   */
  Modeling.prototype.makeProcess = function() {

    var processElement = this._create('root', {
      type: 'bpmn:Process'
    });

    var context = {
      newRoot: processElement
    };

    this._commandStack.execute('canvas.updateRoot', context);
  };


  Modeling.prototype.claimId = function(id, moddleElement) {
    this._commandStack.execute('id.updateClaim', {
      id: id,
      element: moddleElement,
      claiming: true
    });
  };


  Modeling.prototype.unclaimId = function(id, moddleElement) {
    this._commandStack.execute('id.updateClaim', {
      id: id,
      element: moddleElement
    });
  };

  Modeling.prototype.setColor = function(elements, colors) {
    if (!elements.length) {
      elements = [ elements ];
    }

    this._commandStack.execute('element.setColor', {
      elements: elements,
      colors: colors
    });
  };

  /**
   * A base connection layouter implementation
   * that layouts the connection by directly connecting
   * mid(source) + mid(target).
   */
  function BaseLayouter() {}


  /**
   * Return the new layouted waypoints for the given connection.
   *
   * The connection passed is still unchanged; you may figure out about
   * the new connection start / end via the layout hints provided.
   *
   * @param {djs.model.Connection} connection
   * @param {Object} [hints]
   * @param {Point} [hints.connectionStart]
   * @param {Point} [hints.connectionEnd]
   * @param {Point} [hints.source]
   * @param {Point} [hints.target]
   *
   * @return {Array<Point>} the layouted connection waypoints
   */
  BaseLayouter.prototype.layoutConnection = function(connection, hints) {

    hints = hints || {};

    return [
      hints.connectionStart || getMid(hints.source || connection.source),
      hints.connectionEnd || getMid(hints.target || connection.target)
    ];
  };

  var MIN_SEGMENT_LENGTH = 20,
      POINT_ORIENTATION_PADDING = 5;

  var round$1 = Math.round;

  var INTERSECTION_THRESHOLD = 20,
      ORIENTATION_THRESHOLD = {
        'h:h': 20,
        'v:v': 20,
        'h:v': -10,
        'v:h': -10
      };

  function needsTurn(orientation, startDirection) {
    return !{
      t: /top/,
      r: /right/,
      b: /bottom/,
      l: /left/,
      h: /./,
      v: /./
    }[startDirection].test(orientation);
  }

  function canLayoutStraight(direction, targetOrientation) {
    return {
      t: /top/,
      r: /right/,
      b: /bottom/,
      l: /left/,
      h: /left|right/,
      v: /top|bottom/
    }[direction].test(targetOrientation);
  }

  function getSegmentBendpoints(a, b, directions) {
    var orientation = getOrientation(b, a, POINT_ORIENTATION_PADDING);

    var startDirection = directions.split(':')[0];

    var xmid = round$1((b.x - a.x) / 2 + a.x),
        ymid = round$1((b.y - a.y) / 2 + a.y);

    var segmentEnd, segmentDirections;

    var layoutStraight = canLayoutStraight(startDirection, orientation),
        layoutHorizontal = /h|r|l/.test(startDirection),
        layoutTurn = false;

    var turnNextDirections = false;

    if (layoutStraight) {
      segmentEnd = layoutHorizontal ? { x: xmid, y: a.y } : { x: a.x, y: ymid };

      segmentDirections = layoutHorizontal ? 'h:h' : 'v:v';
    } else {
      layoutTurn = needsTurn(orientation, startDirection);

      segmentDirections = layoutHorizontal ? 'h:v' : 'v:h';

      if (layoutTurn) {

        if (layoutHorizontal) {
          turnNextDirections = ymid === a.y;

          segmentEnd = {
            x: a.x + MIN_SEGMENT_LENGTH * (/l/.test(startDirection) ? -1 : 1),
            y: turnNextDirections ? ymid + MIN_SEGMENT_LENGTH : ymid
          };
        } else {
          turnNextDirections = xmid === a.x;

          segmentEnd = {
            x: turnNextDirections ? xmid + MIN_SEGMENT_LENGTH : xmid,
            y: a.y + MIN_SEGMENT_LENGTH * (/t/.test(startDirection) ? -1 : 1)
          };
        }

      } else {
        segmentEnd = {
          x: xmid,
          y: ymid
        };
      }
    }

    return {
      waypoints: getBendpoints(a, segmentEnd, segmentDirections).concat(segmentEnd),
      directions:  segmentDirections,
      turnNextDirections: turnNextDirections
    };
  }

  function getStartSegment(a, b, directions) {
    return getSegmentBendpoints(a, b, directions);
  }

  function getEndSegment(a, b, directions) {
    var invertedSegment = getSegmentBendpoints(b, a, invertDirections(directions));

    return {
      waypoints: invertedSegment.waypoints.slice().reverse(),
      directions: invertDirections(invertedSegment.directions),
      turnNextDirections: invertedSegment.turnNextDirections
    };
  }

  function getMidSegment(startSegment, endSegment) {

    var startDirection = startSegment.directions.split(':')[1],
        endDirection = endSegment.directions.split(':')[0];

    if (startSegment.turnNextDirections) {
      startDirection = startDirection == 'h' ? 'v' : 'h';
    }

    if (endSegment.turnNextDirections) {
      endDirection = endDirection == 'h' ? 'v' : 'h';
    }

    var directions = startDirection + ':' + endDirection;

    var bendpoints = getBendpoints(
      startSegment.waypoints[startSegment.waypoints.length - 1],
      endSegment.waypoints[0],
      directions
    );

    return {
      waypoints: bendpoints,
      directions: directions
    };
  }

  function invertDirections(directions) {
    return directions.split(':').reverse().join(':');
  }

  /**
   * Handle simple layouts with maximum two bendpoints.
   */
  function getSimpleBendpoints(a, b, directions) {

    var xmid = round$1((b.x - a.x) / 2 + a.x),
        ymid = round$1((b.y - a.y) / 2 + a.y);

    // one point, right or left from a
    if (directions === 'h:v') {
      return [ { x: b.x, y: a.y } ];
    }

    // one point, above or below a
    if (directions === 'v:h') {
      return [ { x: a.x, y: b.y } ];
    }

    // vertical segment between a and b
    if (directions === 'h:h') {
      return [
        { x: xmid, y: a.y },
        { x: xmid, y: b.y }
      ];
    }

    // horizontal segment between a and b
    if (directions === 'v:v') {
      return [
        { x: a.x, y: ymid },
        { x: b.x, y: ymid }
      ];
    }

    throw new Error('invalid directions: can only handle varians of [hv]:[hv]');
  }


  /**
   * Returns the mid points for a manhattan connection between two points.
   *
   * @example h:h (horizontal:horizontal)
   *
   * [a]----[x]
   *         |
   *        [x]----[b]
   *
   * @example h:v (horizontal:vertical)
   *
   * [a]----[x]
   *         |
   *        [b]
   *
   * @example h:r (horizontal:right)
   *
   * [a]----[x]
   *         |
   *    [b]-[x]
   *
   * @param  {Point} a
   * @param  {Point} b
   * @param  {string} directions
   *
   * @return {Array<Point>}
   */
  function getBendpoints(a, b, directions) {
    directions = directions || 'h:h';

    if (!isValidDirections(directions)) {
      throw new Error(
        'unknown directions: <' + directions + '>: ' +
        'must be specified as <start>:<end> ' +
        'with start/end in { h,v,t,r,b,l }'
      );
    }

    // compute explicit directions, involving trbl dockings
    // using a three segmented layouting algorithm
    if (isExplicitDirections(directions)) {
      var startSegment = getStartSegment(a, b, directions),
          endSegment = getEndSegment(a, b, directions),
          midSegment = getMidSegment(startSegment, endSegment);

      return [].concat(
        startSegment.waypoints,
        midSegment.waypoints,
        endSegment.waypoints
      );
    }

    // handle simple [hv]:[hv] cases that can be easily computed
    return getSimpleBendpoints(a, b, directions);
  }

  /**
   * Create a connection between the two points according
   * to the manhattan layout (only horizontal and vertical) edges.
   *
   * @param {Point} a
   * @param {Point} b
   *
   * @param {string} [directions='h:h'] specifies manhattan directions for each point as {adirection}:{bdirection}.
                     A directionfor a point is either `h` (horizontal) or `v` (vertical)
   *
   * @return {Array<Point>}
   */
  function connectPoints(a, b, directions) {

    var points = getBendpoints(a, b, directions);

    points.unshift(a);
    points.push(b);

    return withoutRedundantPoints(points);
  }


  /**
   * Connect two rectangles using a manhattan layouted connection.
   *
   * @param {Bounds} source source rectangle
   * @param {Bounds} target target rectangle
   * @param {Point} [start] source docking
   * @param {Point} [end] target docking
   *
   * @param {Object} [hints]
   * @param {string} [hints.preserveDocking=source] preserve docking on selected side
   * @param {Array<string>} [hints.preferredLayouts]
   * @param {Point|boolean} [hints.connectionStart] whether the start changed
   * @param {Point|boolean} [hints.connectionEnd] whether the end changed
   *
   * @return {Array<Point>} connection points
   */
  function connectRectangles(source, target, start, end, hints) {

    var preferredLayouts = hints && hints.preferredLayouts || [];

    var preferredLayout = without(preferredLayouts, 'straight')[0] || 'h:h';

    var threshold = ORIENTATION_THRESHOLD[preferredLayout] || 0;

    var orientation = getOrientation(source, target, threshold);

    var directions = getDirections(orientation, preferredLayout);

    start = start || getMid(source);
    end = end || getMid(target);

    var directionSplit = directions.split(':');

    // compute actual docking points for start / end
    // this ensures we properly layout only parts of the
    // connection that lies in between the two rectangles
    var startDocking = getDockingPoint(start, source, directionSplit[0], invertOrientation(orientation)),
        endDocking = getDockingPoint(end, target, directionSplit[1], orientation);

    return connectPoints(startDocking, endDocking, directions);
  }


  /**
   * Repair the connection between two rectangles, of which one has been updated.
   *
   * @param {Bounds} source
   * @param {Bounds} target
   * @param {Point} [start]
   * @param {Point} [end]
   * @param {Array<Point>} [waypoints]
   * @param {Object} [hints]
   * @param {Array<string>} [hints.preferredLayouts] list of preferred layouts
   * @param {boolean} [hints.connectionStart]
   * @param {boolean} [hints.connectionEnd]
   *
   * @return {Array<Point>} repaired waypoints
   */
  function repairConnection(source, target, start, end, waypoints, hints) {

    if (isArray$3(start)) {
      waypoints = start;
      hints = end;

      start = getMid(source);
      end = getMid(target);
    }

    hints = assign({ preferredLayouts: [] }, hints);
    waypoints = waypoints || [];

    var preferredLayouts = hints.preferredLayouts,
        preferStraight = preferredLayouts.indexOf('straight') !== -1,
        repairedWaypoints;

    // just layout non-existing or simple connections
    // attempt to render straight lines, if required

    // attempt to layout a straight line
    repairedWaypoints = preferStraight && tryLayoutStraight(source, target, start, end, hints);

    if (repairedWaypoints) {
      return repairedWaypoints;
    }

    // try to layout from end
    repairedWaypoints = hints.connectionEnd && tryRepairConnectionEnd(target, source, end, waypoints);

    if (repairedWaypoints) {
      return repairedWaypoints;
    }

    // try to layout from start
    repairedWaypoints = hints.connectionStart && tryRepairConnectionStart(source, target, start, waypoints);

    if (repairedWaypoints) {
      return repairedWaypoints;
    }

    // or whether nothing seems to have changed
    if (!hints.connectionStart && !hints.connectionEnd && waypoints && waypoints.length) {
      return waypoints;
    }

    // simply reconnect if nothing else worked
    return connectRectangles(source, target, start, end, hints);
  }


  function inRange(a, start, end) {
    return a >= start && a <= end;
  }

  function isInRange(axis, a, b) {
    var size = {
      x: 'width',
      y: 'height'
    };

    return inRange(a[axis], b[axis], b[axis] + b[size[axis]]);
  }

  /**
   * Layout a straight connection
   *
   * @param {Bounds} source
   * @param {Bounds} target
   * @param {Point} start
   * @param {Point} end
   * @param {Object} [hints]
   *
   * @return {Array<Point>|null} waypoints if straight layout worked
   */
  function tryLayoutStraight(source, target, start, end, hints) {
    var axis = {},
        primaryAxis,
        orientation;

    orientation = getOrientation(source, target);

    // only layout a straight connection if shapes are
    // horizontally or vertically aligned
    if (!/^(top|bottom|left|right)$/.test(orientation)) {
      return null;
    }

    if (/top|bottom/.test(orientation)) {
      primaryAxis = 'x';
    }

    if (/left|right/.test(orientation)) {
      primaryAxis = 'y';
    }

    if (hints.preserveDocking === 'target') {

      if (!isInRange(primaryAxis, end, source)) {
        return null;
      }

      axis[primaryAxis] = end[primaryAxis];

      return [
        {
          x: axis.x !== undefined ? axis.x : start.x,
          y: axis.y !== undefined ? axis.y : start.y,
          original: {
            x: axis.x !== undefined ? axis.x : start.x,
            y: axis.y !== undefined ? axis.y : start.y
          }
        },
        {
          x: end.x,
          y: end.y
        }
      ];

    } else {

      if (!isInRange(primaryAxis, start, target)) {
        return null;
      }

      axis[primaryAxis] = start[primaryAxis];

      return [
        {
          x: start.x,
          y: start.y
        },
        {
          x: axis.x !== undefined ? axis.x : end.x,
          y: axis.y !== undefined ? axis.y : end.y,
          original: {
            x: axis.x !== undefined ? axis.x : end.x,
            y: axis.y !== undefined ? axis.y : end.y
          }
        }
      ];
    }

  }

  /**
   * Repair a connection from start.
   *
   * @param {Bounds} moved
   * @param {Bounds} other
   * @param {Point} newDocking
   * @param {Array<Point>} points originalPoints from moved to other
   *
   * @return {Array<Point>|null} the repaired points between the two rectangles
   */
  function tryRepairConnectionStart(moved, other, newDocking, points) {
    return _tryRepairConnectionSide(moved, other, newDocking, points);
  }

  /**
   * Repair a connection from end.
   *
   * @param {Bounds} moved
   * @param {Bounds} other
   * @param {Point} newDocking
   * @param {Array<Point>} points originalPoints from moved to other
   *
   * @return {Array<Point>|null} the repaired points between the two rectangles
   */
  function tryRepairConnectionEnd(moved, other, newDocking, points) {
    var waypoints = points.slice().reverse();

    waypoints = _tryRepairConnectionSide(moved, other, newDocking, waypoints);

    return waypoints ? waypoints.reverse() : null;
  }

  /**
   * Repair a connection from one side that moved.
   *
   * @param {Bounds} moved
   * @param {Bounds} other
   * @param {Point} newDocking
   * @param {Array<Point>} points originalPoints from moved to other
   *
   * @return {Array<Point>} the repaired points between the two rectangles
   */
  function _tryRepairConnectionSide(moved, other, newDocking, points) {

    function needsRelayout(points) {
      if (points.length < 3) {
        return true;
      }

      if (points.length > 4) {
        return false;
      }

      // relayout if two points overlap
      // this is most likely due to
      return !!find(points, function(p, idx) {
        var q = points[idx - 1];

        return q && pointDistance(p, q) < 3;
      });
    }

    function repairBendpoint(candidate, oldPeer, newPeer) {

      var alignment = pointsAligned(oldPeer, candidate);

      switch (alignment) {
      case 'v':

        // repair horizontal alignment
        return { x: newPeer.x, y: candidate.y };
      case 'h':

        // repair vertical alignment
        return { x: candidate.x, y: newPeer.y };
      }

      return { x: candidate.x, y: candidate. y };
    }

    function removeOverlapping(points, a, b) {
      var i;

      for (i = points.length - 2; i !== 0; i--) {

        // intersects (?) break, remove all bendpoints up to this one and relayout
        if (pointInRect(points[i], a, INTERSECTION_THRESHOLD) ||
            pointInRect(points[i], b, INTERSECTION_THRESHOLD)) {

          // return sliced old connection
          return points.slice(i);
        }
      }

      return points;
    }

    // (0) only repair what has layoutable bendpoints

    // (1) if only one bendpoint and on shape moved onto other shapes axis
    //     (horizontally / vertically), relayout

    if (needsRelayout(points)) {
      return null;
    }

    var oldDocking = points[0],
        newPoints = points.slice(),
        slicedPoints;

    // (2) repair only last line segment and only if it was layouted before

    newPoints[0] = newDocking;
    newPoints[1] = repairBendpoint(newPoints[1], oldDocking, newDocking);


    // (3) if shape intersects with any bendpoint after repair,
    //     remove all segments up to this bendpoint and repair from there
    slicedPoints = removeOverlapping(newPoints, moved, other);

    if (slicedPoints !== newPoints) {
      newPoints = _tryRepairConnectionSide(moved, other, newDocking, slicedPoints);
    }

    // (4) do NOT repair if repaired bendpoints are aligned
    if (newPoints && pointsAligned(newPoints)) {
      return null;
    }

    return newPoints;
  }


  /**
   * Returns the manhattan directions connecting two rectangles
   * with the given orientation.
   *
   * Will always return the default layout, if it is specific
   * regarding sides already (trbl).
   *
   * @example
   *
   * getDirections('top'); // -> 'v:v'
   * getDirections('intersect'); // -> 't:t'
   *
   * getDirections('top-right', 'v:h'); // -> 'v:h'
   * getDirections('top-right', 'h:h'); // -> 'h:h'
   *
   *
   * @param {string} orientation
   * @param {string} defaultLayout
   *
   * @return {string}
   */
  function getDirections(orientation, defaultLayout) {

    // don't override specific trbl directions
    if (isExplicitDirections(defaultLayout)) {
      return defaultLayout;
    }

    switch (orientation) {
    case 'intersect':
      return 't:t';

    case 'top':
    case 'bottom':
      return 'v:v';

    case 'left':
    case 'right':
      return 'h:h';

    // 'top-left'
    // 'top-right'
    // 'bottom-left'
    // 'bottom-right'
    default:
      return defaultLayout;
    }
  }

  function isValidDirections(directions) {
    return directions && /^h|v|t|r|b|l:h|v|t|r|b|l$/.test(directions);
  }

  function isExplicitDirections(directions) {
    return directions && /t|r|b|l/.test(directions);
  }

  function invertOrientation(orientation) {
    return {
      'top': 'bottom',
      'bottom': 'top',
      'left': 'right',
      'right': 'left',
      'top-left': 'bottom-right',
      'bottom-right': 'top-left',
      'top-right': 'bottom-left',
      'bottom-left': 'top-right',
    }[orientation];
  }

  function getDockingPoint(point, rectangle, dockingDirection, targetOrientation) {

    // ensure we end up with a specific docking direction
    // based on the targetOrientation, if <h|v> is being passed

    if (dockingDirection === 'h') {
      dockingDirection = /left/.test(targetOrientation) ? 'l' : 'r';
    }

    if (dockingDirection === 'v') {
      dockingDirection = /top/.test(targetOrientation) ? 't' : 'b';
    }

    if (dockingDirection === 't') {
      return { original: point, x: point.x, y: rectangle.y };
    }

    if (dockingDirection === 'r') {
      return { original: point, x: rectangle.x + rectangle.width, y: point.y };
    }

    if (dockingDirection === 'b') {
      return { original: point, x: point.x, y: rectangle.y + rectangle.height };
    }

    if (dockingDirection === 'l') {
      return { original: point, x: rectangle.x, y: point.y };
    }

    throw new Error('unexpected dockingDirection: <' + dockingDirection + '>');
  }


  /**
   * Return list of waypoints with redundant ones filtered out.
   *
   * @example
   *
   * Original points:
   *
   *   [x] ----- [x] ------ [x]
   *                         |
   *                        [x] ----- [x] - [x]
   *
   * Filtered:
   *
   *   [x] ---------------- [x]
   *                         |
   *                        [x] ----------- [x]
   *
   * @param  {Array<Point>} waypoints
   *
   * @return {Array<Point>}
   */
  function withoutRedundantPoints(waypoints) {
    return waypoints.reduce(function(points, p, idx) {

      var previous = points[points.length - 1],
          next = waypoints[idx + 1];

      if (!pointsOnLine(previous, next, p, 0)) {
        points.push(p);
      }

      return points;
    }, []);
  }

  var ATTACH_ORIENTATION_PADDING = -10,
      BOUNDARY_TO_HOST_THRESHOLD$1 = 40;

  var oppositeOrientationMapping = {
    'top': 'bottom',
    'top-right': 'bottom-left',
    'top-left': 'bottom-right',
    'right': 'left',
    'bottom': 'top',
    'bottom-right': 'top-left',
    'bottom-left': 'top-right',
    'left': 'right'
  };

  var orientationDirectionMapping = {
    top: 't',
    right: 'r',
    bottom: 'b',
    left: 'l'
  };


  function BpmnLayouter() {}

  e(BpmnLayouter, BaseLayouter);


  BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
    if (!hints) {
      hints = {};
    }

    var source = hints.source || connection.source,
        target = hints.target || connection.target,
        waypoints = hints.waypoints || connection.waypoints,
        connectionStart = hints.connectionStart,
        connectionEnd = hints.connectionEnd;

    var manhattanOptions,
        updatedWaypoints;

    if (!connectionStart) {
      connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source);
    }

    if (!connectionEnd) {
      connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
    }

    // TODO(nikku): support vertical modeling
    // and invert preferredLayouts accordingly

    if (is$1(connection, 'bpmn:Association') ||
        is$1(connection, 'bpmn:DataAssociation')) {

      if (waypoints && !isCompensationAssociation(source, target)) {
        return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]);
      }
    }

    if (is$1(connection, 'bpmn:MessageFlow')) {
      manhattanOptions = getMessageFlowManhattanOptions(source, target);
    } else if (is$1(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {

      // layout all connection between flow elements h:h, except for
      // (1) outgoing of boundary events -> layout based on attach orientation and target orientation
      // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
      // (3) loops
      if (source === target) {
        manhattanOptions = {
          preferredLayouts: getLoopPreferredLayout(source, connection)
        };
      } else if (is$1(source, 'bpmn:BoundaryEvent')) {
        manhattanOptions = {
          preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd)
        };
      } else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) {
        manhattanOptions = getSubProcessManhattanOptions(source);
      } else if (is$1(source, 'bpmn:Gateway')) {
        manhattanOptions = {
          preferredLayouts: [ 'v:h' ]
        };
      } else if (is$1(target, 'bpmn:Gateway')) {
        manhattanOptions = {
          preferredLayouts: [ 'h:v' ]
        };
      } else {
        manhattanOptions = {
          preferredLayouts: [ 'h:h' ]
        };
      }
    }

    if (manhattanOptions) {
      manhattanOptions = assign(manhattanOptions, hints);

      updatedWaypoints = withoutRedundantPoints(repairConnection(
        source,
        target,
        connectionStart,
        connectionEnd,
        waypoints,
        manhattanOptions
      ));
    }

    return updatedWaypoints || [ connectionStart, connectionEnd ];
  };


  // helpers //////////

  function getAttachOrientation(attachedElement) {
    var hostElement = attachedElement.host;

    return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING);
  }

  function getMessageFlowManhattanOptions(source, target) {
    return {
      preferredLayouts: [ 'straight', 'v:v' ],
      preserveDocking: getMessageFlowPreserveDocking(source, target)
    };
  }

  function getMessageFlowPreserveDocking(source, target) {

    // (1) docking element connected to participant has precedence
    if (is$1(target, 'bpmn:Participant')) {
      return 'source';
    }

    if (is$1(source, 'bpmn:Participant')) {
      return 'target';
    }

    // (2) docking element connected to expanded sub-process has precedence
    if (isExpandedSubProcess(target)) {
      return 'source';
    }

    if (isExpandedSubProcess(source)) {
      return 'target';
    }

    // (3) docking event has precedence
    if (is$1(target, 'bpmn:Event')) {
      return 'target';
    }

    if (is$1(source, 'bpmn:Event')) {
      return 'source';
    }

    return null;
  }

  function getSubProcessManhattanOptions(source) {
    return {
      preferredLayouts: [ 'straight', 'h:h' ],
      preserveDocking: getSubProcessPreserveDocking(source)
    };
  }

  function getSubProcessPreserveDocking(source) {
    return isExpandedSubProcess(source) ? 'target' : 'source';
  }

  function getConnectionDocking(point, shape) {
    return point ? (point.original || point) : getMid(shape);
  }

  function isCompensationAssociation(source, target) {
    return is$1(target, 'bpmn:Activity') &&
      is$1(source, 'bpmn:BoundaryEvent') &&
      target.businessObject.isForCompensation;
  }

  function isExpandedSubProcess(element) {
    return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
  }

  function isSame(a, b) {
    return a === b;
  }

  function isAnyOrientation(orientation, orientations) {
    return orientations.indexOf(orientation) !== -1;
  }

  function getHorizontalOrientation(orientation) {
    var matches = /right|left/.exec(orientation);

    return matches && matches[0];
  }

  function getVerticalOrientation(orientation) {
    var matches = /top|bottom/.exec(orientation);

    return matches && matches[0];
  }

  function isOppositeOrientation(a, b) {
    return oppositeOrientationMapping[a] === b;
  }

  function isOppositeHorizontalOrientation(a, b) {
    var horizontalOrientation = getHorizontalOrientation(a);

    var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];

    return b.indexOf(oppositeHorizontalOrientation) !== -1;
  }

  function isOppositeVerticalOrientation(a, b) {
    var verticalOrientation = getVerticalOrientation(a);

    var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];

    return b.indexOf(oppositeVerticalOrientation) !== -1;
  }

  function isHorizontalOrientation(orientation) {
    return orientation === 'right' || orientation === 'left';
  }

  function getLoopPreferredLayout(source, connection) {
    var waypoints = connection.waypoints;

    var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);

    if (orientation === 'top') {
      return [ 't:r' ];
    } else if (orientation === 'right') {
      return [ 'r:b' ];
    } else if (orientation === 'left') {
      return [ 'l:t' ];
    }

    return [ 'b:l' ];
  }

  function getBoundaryEventPreferredLayouts(source, target, end) {
    var sourceMid = getMid(source),
        targetMid = getMid(target),
        attachOrientation = getAttachOrientation(source),
        sourceLayout,
        targetLayout;

    var isLoop = isSame(source.host, target);

    var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);

    var targetOrientation = getOrientation(targetMid, sourceMid, {
      x: source.width / 2 + target.width / 2,
      y: source.height / 2 + target.height / 2
    });

    if (isLoop) {
      return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
    }

    // source layout
    sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);

    // target layout
    targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);

    return [ sourceLayout + ':' + targetLayout ];
  }

  function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
    var orientation = attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation),
        sourceLayout = orientationDirectionMapping[ orientation ],
        targetLayout;

    if (attachedToSide) {
      if (isHorizontalOrientation(attachOrientation)) {
        targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
      } else {
        targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
      }
    } else {
      targetLayout = 'v';
    }

    return [ sourceLayout + ':' + targetLayout ];
  }

  function shouldConnectToSameSide(axis, source, target, end) {
    var threshold = BOUNDARY_TO_HOST_THRESHOLD$1;

    return !(
      areCloseOnAxis(axis, end, target, threshold) ||
      areCloseOnAxis(axis, end, {
        x: target.x + target.width,
        y: target.y + target.height
      }, threshold) ||
      areCloseOnAxis(axis, end, getMid(source), threshold)
    );
  }

  function areCloseOnAxis(axis, a, b, threshold) {
    return Math.abs(a[ axis ] - b[ axis ]) < threshold;
  }

  function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {

    // attached to either top, right, bottom or left side
    if (attachedToSide) {
      return orientationDirectionMapping[ attachOrientation ];
    }

    // attached to either top-right, top-left, bottom-right or bottom-left corner

    // same vertical or opposite horizontal orientation
    if (isSame(
      getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
    ) || isOppositeOrientation(
      getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
    )) {
      return orientationDirectionMapping[ getVerticalOrientation(attachOrientation) ];
    }

    // fallback
    return orientationDirectionMapping[ getHorizontalOrientation(attachOrientation) ];
  }

  function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {

    // attached to either top, right, bottom or left side
    if (attachedToSide) {
      if (isHorizontalOrientation(attachOrientation)) {

        // orientation is right or left

        // opposite horizontal orientation or same orientation
        if (
          isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
          isSame(attachOrientation, targetOrientation)
        ) {
          return 'h';
        }

        // fallback
        return 'v';
      } else {

        // orientation is top or bottom

        // opposite vertical orientation or same orientation
        if (
          isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
          isSame(attachOrientation, targetOrientation)
        ) {
          return 'v';
        }

        // fallback
        return 'h';
      }
    }

    // attached to either top-right, top-left, bottom-right or bottom-left corner

    // orientation is right, left
    // or same vertical orientation but also right or left
    if (isHorizontalOrientation(targetOrientation) ||
      (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
        getHorizontalOrientation(targetOrientation))) {
      return 'h';
    } else {
      return 'v';
    }
  }

  function dockingToPoint(docking) {

    // use the dockings actual point and
    // retain the original docking
    return assign({ original: docking.point.original || docking.point }, docking.actual);
  }


  /**
   * A {@link ConnectionDocking} that crops connection waypoints based on
   * the path(s) of the connection source and target.
   *
   * @param {djs.core.ElementRegistry} elementRegistry
   */
  function CroppingConnectionDocking(elementRegistry, graphicsFactory) {
    this._elementRegistry = elementRegistry;
    this._graphicsFactory = graphicsFactory;
  }

  CroppingConnectionDocking.$inject = [ 'elementRegistry', 'graphicsFactory' ];


  /**
   * @inheritDoc ConnectionDocking#getCroppedWaypoints
   */
  CroppingConnectionDocking.prototype.getCroppedWaypoints = function(connection, source, target) {

    source = source || connection.source;
    target = target || connection.target;

    var sourceDocking = this.getDockingPoint(connection, source, true),
        targetDocking = this.getDockingPoint(connection, target);

    var croppedWaypoints = connection.waypoints.slice(sourceDocking.idx + 1, targetDocking.idx);

    croppedWaypoints.unshift(dockingToPoint(sourceDocking));
    croppedWaypoints.push(dockingToPoint(targetDocking));

    return croppedWaypoints;
  };

  /**
   * Return the connection docking point on the specified shape
   *
   * @inheritDoc ConnectionDocking#getDockingPoint
   */
  CroppingConnectionDocking.prototype.getDockingPoint = function(connection, shape, dockStart) {

    var waypoints = connection.waypoints,
        dockingIdx,
        dockingPoint,
        croppedPoint;

    dockingIdx = dockStart ? 0 : waypoints.length - 1;
    dockingPoint = waypoints[dockingIdx];

    croppedPoint = this._getIntersection(shape, connection, dockStart);

    return {
      point: dockingPoint,
      actual: croppedPoint || dockingPoint,
      idx: dockingIdx
    };
  };


  // helpers //////////////////////

  CroppingConnectionDocking.prototype._getIntersection = function(shape, connection, takeFirst) {

    var shapePath = this._getShapePath(shape),
        connectionPath = this._getConnectionPath(connection);

    return getElementLineIntersection(shapePath, connectionPath, takeFirst);
  };

  CroppingConnectionDocking.prototype._getConnectionPath = function(connection) {
    return this._graphicsFactory.getConnectionPath(connection);
  };

  CroppingConnectionDocking.prototype._getShapePath = function(shape) {
    return this._graphicsFactory.getShapePath(shape);
  };

  CroppingConnectionDocking.prototype._getGfx = function(element) {
    return this._elementRegistry.getGraphics(element);
  };

  var ModelingModule = {
    __init__: [
      'modeling',
      'bpmnUpdater'
    ],
    __depends__: [
      BehaviorModule,
      RulesModule,
      DiOrderingModule,
      OrderingModule,
      ReplaceModule,
      CommandModule,
      TooltipsModule,
      LabelSupportModule,
      AttachSupportModule,
      SelectionModule,
      ChangeSupportModule,
      SpaceToolModule
    ],
    bpmnFactory: [ 'type', BpmnFactory ],
    bpmnUpdater: [ 'type', BpmnUpdater ],
    elementFactory: [ 'type', ElementFactory ],
    modeling: [ 'type', Modeling ],
    layouter: [ 'type', BpmnLayouter ],
    connectionDocking: [ 'type', CroppingConnectionDocking ]
  };

  var LOW_PRIORITY$2 = 500,
      MEDIUM_PRIORITY = 1250,
      HIGH_PRIORITY$2 = 1500;

  var round = Math.round;

  function mid(element) {
    return {
      x: element.x + round(element.width / 2),
      y: element.y + round(element.height / 2)
    };
  }

  /**
   * A plugin that makes shapes draggable / droppable.
   *
   * @param {EventBus} eventBus
   * @param {Dragging} dragging
   * @param {Modeling} modeling
   * @param {Selection} selection
   * @param {Rules} rules
   */
  function MoveEvents(
      eventBus, dragging, modeling,
      selection, rules) {

    // rules

    function canMove(shapes, delta, position, target) {

      return rules.allowed('elements.move', {
        shapes: shapes,
        delta: delta,
        position: position,
        target: target
      });
    }


    // move events

    // assign a high priority to this handler to setup the environment
    // others may hook up later, e.g. at default priority and modify
    // the move environment.
    //
    // This sets up the context with
    //
    // * shape: the primary shape being moved
    // * shapes: a list of shapes to be moved
    // * validatedShapes: a list of shapes that are being checked
    //                    against the rules before and during move
    //
    eventBus.on('shape.move.start', HIGH_PRIORITY$2, function(event) {

      var context = event.context,
          shape = event.shape,
          shapes = selection.get().slice();

      // move only single shape if the dragged element
      // is not part of the current selection
      if (shapes.indexOf(shape) === -1) {
        shapes = [ shape ];
      }

      // ensure we remove nested elements in the collection
      // and add attachers for a proper dragger
      shapes = removeNested(shapes);

      // attach shapes to drag context
      assign(context, {
        shapes: shapes,
        validatedShapes: shapes,
        shape: shape
      });
    });


    // assign a high priority to this handler to setup the environment
    // others may hook up later, e.g. at default priority and modify
    // the move environment
    //
    eventBus.on('shape.move.start', MEDIUM_PRIORITY, function(event) {

      var context = event.context,
          validatedShapes = context.validatedShapes,
          canExecute;

      canExecute = context.canExecute = canMove(validatedShapes);

      // check if we can move the elements
      if (!canExecute) {
        return false;
      }
    });

    // assign a low priority to this handler
    // to let others modify the move event before we update
    // the context
    //
    eventBus.on('shape.move.move', LOW_PRIORITY$2, function(event) {

      var context = event.context,
          validatedShapes = context.validatedShapes,
          hover = event.hover,
          delta = { x: event.dx, y: event.dy },
          position = { x: event.x, y: event.y },
          canExecute;

      // check if we can move the elements
      canExecute = canMove(validatedShapes, delta, position, hover);

      context.delta = delta;
      context.canExecute = canExecute;

      // simply ignore move over
      if (canExecute === null) {
        context.target = null;

        return;
      }

      context.target = hover;
    });

    eventBus.on('shape.move.end', function(event) {

      var context = event.context;

      var delta = context.delta,
          canExecute = context.canExecute,
          isAttach = canExecute === 'attach',
          shapes = context.shapes;

      if (canExecute === false) {
        return false;
      }

      // ensure we have actual pixel values deltas
      // (important when zoom level was > 1 during move)
      delta.x = round(delta.x);
      delta.y = round(delta.y);

      if (delta.x === 0 && delta.y === 0) {

        // didn't move
        return;
      }

      modeling.moveElements(shapes, delta, context.target, {
        primaryShape: context.shape,
        attach: isAttach
      });
    });


    // move activation

    eventBus.on('element.mousedown', function(event) {

      if (!isPrimaryButton(event)) {
        return;
      }

      var originalEvent = getOriginal$1(event);

      if (!originalEvent) {
        throw new Error('must supply DOM mousedown event');
      }

      return start(originalEvent, event.element);
    });

    /**
     * Start move.
     *
     * @param {MouseEvent} event
     * @param {djs.model.Shape} shape
     * @param {boolean} [activate]
     * @param {Object} [context]
     */
    function start(event, element, activate, context) {
      if (isObject(activate)) {
        context = activate;
        activate = false;
      }

      // do not move connections or the root element
      if (element.waypoints || !element.parent) {
        return;
      }

      // ignore non-draggable hits
      if (classes(event.target).has('djs-hit-no-move')) {
        return;
      }

      var referencePoint = mid(element);

      dragging.init(event, referencePoint, 'shape.move', {
        cursor: 'grabbing',
        autoActivate: activate,
        data: {
          shape: element,
          context: context || {}
        }
      });

      // we've handled the event
      return true;
    }

    // API

    this.start = start;
  }

  MoveEvents.$inject = [
    'eventBus',
    'dragging',
    'modeling',
    'selection',
    'rules'
  ];


  /**
   * Return a filtered list of elements that do not contain
   * those nested into others.
   *
   * @param  {Array<djs.model.Base>} elements
   *
   * @return {Array<djs.model.Base>} filtered
   */
  function removeNested(elements) {

    var ids = groupBy(elements, 'id');

    return filter(elements, function(element) {
      while ((element = element.parent)) {

        // parent in selection
        if (ids[element.id]) {
          return false;
        }
      }

      return true;
    });
  }

  var LOW_PRIORITY$1 = 499;

  var MARKER_DRAGGING = 'djs-dragging',
      MARKER_OK$1 = 'drop-ok',
      MARKER_NOT_OK$1 = 'drop-not-ok',
      MARKER_NEW_PARENT = 'new-parent',
      MARKER_ATTACH = 'attach-ok';


  /**
   * Provides previews for moving shapes when moving.
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {Canvas} canvas
   * @param {Styles} styles
   */
  function MovePreview(
      eventBus, canvas, styles, previewSupport) {

    function getVisualDragShapes(shapes) {
      var elements = getAllDraggedElements(shapes);

      var filteredElements = removeEdges(elements);

      return filteredElements;
    }

    function getAllDraggedElements(shapes) {
      var allShapes = selfAndAllChildren(shapes, true);

      var allConnections = map(allShapes, function(shape) {
        return (shape.incoming || []).concat(shape.outgoing || []);
      });

      return flatten(allShapes.concat(allConnections));
    }

    /**
     * Sets drop marker on an element.
     */
    function setMarker(element, marker) {

      [ MARKER_ATTACH, MARKER_OK$1, MARKER_NOT_OK$1, MARKER_NEW_PARENT ].forEach(function(m) {

        if (m === marker) {
          canvas.addMarker(element, m);
        } else {
          canvas.removeMarker(element, m);
        }
      });
    }

    /**
     * Make an element draggable.
     *
     * @param {Object} context
     * @param {djs.model.Base} element
     * @param {boolean} addMarker
     */
    function makeDraggable(context, element, addMarker) {

      previewSupport.addDragger(element, context.dragGroup);

      if (addMarker) {
        canvas.addMarker(element, MARKER_DRAGGING);
      }

      if (context.allDraggedElements) {
        context.allDraggedElements.push(element);
      } else {
        context.allDraggedElements = [ element ];
      }
    }

    // assign a low priority to this handler
    // to let others modify the move context before
    // we draw things
    eventBus.on('shape.move.start', LOW_PRIORITY$1, function(event) {
      var context = event.context,
          dragShapes = context.shapes,
          allDraggedElements = context.allDraggedElements;

      var visuallyDraggedShapes = getVisualDragShapes(dragShapes);

      if (!context.dragGroup) {
        var dragGroup = create$1('g');

        attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));

        var activeLayer = canvas.getActiveLayer();

        append(activeLayer, dragGroup);

        context.dragGroup = dragGroup;
      }

      // add previews
      visuallyDraggedShapes.forEach(function(shape) {
        previewSupport.addDragger(shape, context.dragGroup);
      });

      // cache all dragged elements / gfx
      // so that we can quickly undo their state changes later
      if (!allDraggedElements) {
        allDraggedElements = getAllDraggedElements(dragShapes);
      } else {
        allDraggedElements = flatten([
          allDraggedElements,
          getAllDraggedElements(dragShapes)
        ]);
      }

      // add dragging marker
      forEach$1(allDraggedElements, function(e) {
        canvas.addMarker(e, MARKER_DRAGGING);
      });

      context.allDraggedElements = allDraggedElements;

      // determine, if any of the dragged elements have different parents
      context.differentParents = haveDifferentParents(dragShapes);
    });

    // update previews
    eventBus.on('shape.move.move', LOW_PRIORITY$1, function(event) {

      var context = event.context,
          dragGroup = context.dragGroup,
          target = context.target,
          parent = context.shape.parent,
          canExecute = context.canExecute;

      if (target) {
        if (canExecute === 'attach') {
          setMarker(target, MARKER_ATTACH);
        } else if (context.canExecute && target && target.id !== parent.id) {
          setMarker(target, MARKER_NEW_PARENT);
        } else {
          setMarker(target, context.canExecute ? MARKER_OK$1 : MARKER_NOT_OK$1);
        }
      }

      translate$2(dragGroup, event.dx, event.dy);
    });

    eventBus.on([ 'shape.move.out', 'shape.move.cleanup' ], function(event) {
      var context = event.context,
          target = context.target;

      if (target) {
        setMarker(target, null);
      }
    });

    // remove previews
    eventBus.on('shape.move.cleanup', function(event) {

      var context = event.context,
          allDraggedElements = context.allDraggedElements,
          dragGroup = context.dragGroup;


      // remove dragging marker
      forEach$1(allDraggedElements, function(e) {
        canvas.removeMarker(e, MARKER_DRAGGING);
      });

      if (dragGroup) {
        remove$1(dragGroup);
      }
    });


    // API //////////////////////

    /**
     * Make an element draggable.
     *
     * @param {Object} context
     * @param {djs.model.Base} element
     * @param {boolean} addMarker
     */
    this.makeDraggable = makeDraggable;
  }

  MovePreview.$inject = [
    'eventBus',
    'canvas',
    'styles',
    'previewSupport'
  ];


  // helpers //////////////////////

  /**
   * returns elements minus all connections
   * where source or target is not elements
   */
  function removeEdges(elements) {

    var filteredElements = filter(elements, function(element) {

      if (!isConnection$2(element)) {
        return true;
      } else {

        return (
          find(elements, matchPattern({ id: element.source.id })) &&
          find(elements, matchPattern({ id: element.target.id }))
        );
      }
    });

    return filteredElements;
  }

  function haveDifferentParents(elements) {
    return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) !== 1;
  }

  /**
   * Checks if an element is a connection.
   */
  function isConnection$2(element) {
    return element.waypoints;
  }

  var MoveModule = {
    __depends__: [
      InteractionEventsModule$1,
      SelectionModule,
      OutlineModule,
      RulesModule$1,
      DraggingModule,
      PreviewSupportModule
    ],
    __init__: [
      'move',
      'movePreview'
    ],
    move: [ 'type', MoveEvents ],
    movePreview: [ 'type', MovePreview ]
  };

  var TOGGLE_SELECTOR = '.djs-palette-toggle',
      ENTRY_SELECTOR = '.entry',
      ELEMENT_SELECTOR = TOGGLE_SELECTOR + ', ' + ENTRY_SELECTOR;

  var PALETTE_PREFIX = 'djs-palette-',
      PALETTE_SHOWN_CLS = 'shown',
      PALETTE_OPEN_CLS = 'open',
      PALETTE_TWO_COLUMN_CLS = 'two-column';

  var DEFAULT_PRIORITY = 1000;


  /**
   * A palette containing modeling elements.
   */
  function Palette(eventBus, canvas) {

    this._eventBus = eventBus;
    this._canvas = canvas;

    var self = this;

    eventBus.on('tool-manager.update', function(event) {
      var tool = event.tool;

      self.updateToolHighlight(tool);
    });

    eventBus.on('i18n.changed', function() {
      self._update();
    });

    eventBus.on('diagram.init', function() {

      self._diagramInitialized = true;

      self._rebuild();
    });
  }

  Palette.$inject = [ 'eventBus', 'canvas' ];


  /**
   * Register a provider with the palette
   *
   * @param  {number} [priority=1000]
   * @param  {PaletteProvider} provider
   *
   * @example
   * const paletteProvider = {
   *   getPaletteEntries: function() {
   *     return function(entries) {
   *       return {
   *         ...entries,
   *         'entry-1': {
   *           label: 'My Entry',
   *           action: function() { alert("I have been clicked!"); }
   *         }
   *       };
   *     }
   *   }
   * };
   *
   * palette.registerProvider(800, paletteProvider);
   */
  Palette.prototype.registerProvider = function(priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY;
    }

    this._eventBus.on('palette.getProviders', priority, function(event) {
      event.providers.push(provider);
    });

    this._rebuild();
  };


  /**
   * Returns the palette entries
   *
   * @return {Object<string, PaletteEntryDescriptor>} map of entries
   */
  Palette.prototype.getEntries = function() {
    var providers = this._getProviders();

    return providers.reduce(addPaletteEntries, {});
  };

  Palette.prototype._rebuild = function() {

    if (!this._diagramInitialized) {
      return;
    }

    var providers = this._getProviders();

    if (!providers.length) {
      return;
    }

    if (!this._container) {
      this._init();
    }

    this._update();
  };

  /**
   * Initialize
   */
  Palette.prototype._init = function() {

    var self = this;

    var eventBus = this._eventBus;

    var parentContainer = this._getParentContainer();

    var container = this._container = domify(Palette.HTML_MARKUP);

    parentContainer.appendChild(container);
    classes$1(parentContainer).add(PALETTE_PREFIX + PALETTE_SHOWN_CLS);

    delegate.bind(container, ELEMENT_SELECTOR, 'click', function(event) {

      var target = event.delegateTarget;

      if (matchesSelector(target, TOGGLE_SELECTOR)) {
        return self.toggle();
      }

      self.trigger('click', event);
    });

    // prevent drag propagation
    componentEvent.bind(container, 'mousedown', function(event) {
      event.stopPropagation();
    });

    // prevent drag propagation
    delegate.bind(container, ENTRY_SELECTOR, 'dragstart', function(event) {
      self.trigger('dragstart', event);
    });

    eventBus.on('canvas.resized', this._layoutChanged, this);

    eventBus.fire('palette.create', {
      container: container
    });
  };

  Palette.prototype._getProviders = function(id) {

    var event = this._eventBus.createEvent({
      type: 'palette.getProviders',
      providers: []
    });

    this._eventBus.fire(event);

    return event.providers;
  };

  /**
   * Update palette state.
   *
   * @param  {Object} [state] { open, twoColumn }
   */
  Palette.prototype._toggleState = function(state) {

    state = state || {};

    var parent = this._getParentContainer(),
        container = this._container;

    var eventBus = this._eventBus;

    var twoColumn;

    var cls = classes$1(container),
        parentCls = classes$1(parent);

    if ('twoColumn' in state) {
      twoColumn = state.twoColumn;
    } else {
      twoColumn = this._needsCollapse(parent.clientHeight, this._entries || {});
    }

    // always update two column
    cls.toggle(PALETTE_TWO_COLUMN_CLS, twoColumn);
    parentCls.toggle(PALETTE_PREFIX + PALETTE_TWO_COLUMN_CLS, twoColumn);

    if ('open' in state) {
      cls.toggle(PALETTE_OPEN_CLS, state.open);
      parentCls.toggle(PALETTE_PREFIX + PALETTE_OPEN_CLS, state.open);
    }

    eventBus.fire('palette.changed', {
      twoColumn: twoColumn,
      open: this.isOpen()
    });
  };

  Palette.prototype._update = function() {

    var entriesContainer = query('.djs-palette-entries', this._container),
        entries = this._entries = this.getEntries();

    clear$1(entriesContainer);

    forEach$1(entries, function(entry, id) {

      var grouping = entry.group || 'default';

      var container = query('[data-group=' + cssEscape(grouping) + ']', entriesContainer);
      if (!container) {
        container = domify('<div class="group"></div>');
        attr$1(container, 'data-group', grouping);

        entriesContainer.appendChild(container);
      }

      var html = entry.html || (
        entry.separator ?
          '<hr class="separator" />' :
          '<div class="entry" draggable="true"></div>');


      var control = domify(html);
      container.appendChild(control);

      if (!entry.separator) {
        attr$1(control, 'data-action', id);

        if (entry.title) {
          attr$1(control, 'title', entry.title);
        }

        if (entry.className) {
          addClasses(control, entry.className);
        }

        if (entry.imageUrl) {
          var image = domify('<img>');
          attr$1(image, 'src', entry.imageUrl);

          control.appendChild(image);
        }
      }
    });

    // open after update
    this.open();
  };


  /**
   * Trigger an action available on the palette
   *
   * @param  {string} action
   * @param  {Event} event
   */
  Palette.prototype.trigger = function(action, event, autoActivate) {
    var entries = this._entries,
        entry,
        handler,
        originalEvent,
        button = event.delegateTarget || event.target;

    if (!button) {
      return event.preventDefault();
    }

    entry = entries[attr$1(button, 'data-action')];

    // when user clicks on the palette and not on an action
    if (!entry) {
      return;
    }

    handler = entry.action;

    originalEvent = event.originalEvent || event;

    // simple action (via callback function)
    if (isFunction(handler)) {
      if (action === 'click') {
        handler(originalEvent, autoActivate);
      }
    } else {
      if (handler[action]) {
        handler[action](originalEvent, autoActivate);
      }
    }

    // silence other actions
    event.preventDefault();
  };

  Palette.prototype._layoutChanged = function() {
    this._toggleState({});
  };

  /**
   * Do we need to collapse to two columns?
   *
   * @param {number} availableHeight
   * @param {Object} entries
   *
   * @return {boolean}
   */
  Palette.prototype._needsCollapse = function(availableHeight, entries) {

    // top margin + bottom toggle + bottom margin
    // implementors must override this method if they
    // change the palette styles
    var margin = 20 + 10 + 20;

    var entriesHeight = Object.keys(entries).length * 46;

    return availableHeight < entriesHeight + margin;
  };

  /**
   * Close the palette
   */
  Palette.prototype.close = function() {

    this._toggleState({
      open: false,
      twoColumn: false
    });
  };


  /**
   * Open the palette
   */
  Palette.prototype.open = function() {
    this._toggleState({ open: true });
  };


  Palette.prototype.toggle = function(open) {
    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  };

  Palette.prototype.isActiveTool = function(tool) {
    return tool && this._activeTool === tool;
  };

  Palette.prototype.updateToolHighlight = function(name) {
    var entriesContainer,
        toolsContainer;

    if (!this._toolsContainer) {
      entriesContainer = query('.djs-palette-entries', this._container);

      this._toolsContainer = query('[data-group=tools]', entriesContainer);
    }

    toolsContainer = this._toolsContainer;

    forEach$1(toolsContainer.children, function(tool) {
      var actionName = tool.getAttribute('data-action');

      if (!actionName) {
        return;
      }

      var toolClasses = classes$1(tool);

      actionName = actionName.replace('-tool', '');

      if (toolClasses.contains('entry') && actionName === name) {
        toolClasses.add('highlighted-entry');
      } else {
        toolClasses.remove('highlighted-entry');
      }
    });
  };


  /**
   * Return true if the palette is opened.
   *
   * @example
   *
   * palette.open();
   *
   * if (palette.isOpen()) {
   *   // yes, we are open
   * }
   *
   * @return {boolean} true if palette is opened
   */
  Palette.prototype.isOpen = function() {
    return classes$1(this._container).has(PALETTE_OPEN_CLS);
  };

  /**
   * Get container the palette lives in.
   *
   * @return {Element}
   */
  Palette.prototype._getParentContainer = function() {
    return this._canvas.getContainer();
  };


  /* markup definition */

  Palette.HTML_MARKUP =
    '<div class="djs-palette">' +
      '<div class="djs-palette-entries"></div>' +
      '<div class="djs-palette-toggle"></div>' +
    '</div>';


  // helpers //////////////////////

  function addClasses(element, classNames) {

    var classes = classes$1(element);

    var actualClassNames = isArray$3(classNames) ? classNames : classNames.split(/\s+/g);
    actualClassNames.forEach(function(cls) {
      classes.add(cls);
    });
  }

  function addPaletteEntries(entries, provider) {

    var entriesOrUpdater = provider.getPaletteEntries();

    if (isFunction(entriesOrUpdater)) {
      return entriesOrUpdater(entries);
    }

    forEach$1(entriesOrUpdater, function(entry, id) {
      entries[id] = entry;
    });

    return entries;
  }

  var PaletteModule$1 = {
    __init__: [ 'palette' ],
    palette: [ 'type', Palette ]
  };

  var LASSO_TOOL_CURSOR = 'crosshair';


  function LassoTool(
      eventBus, canvas, dragging,
      elementRegistry, selection, toolManager,
      mouse) {

    this._selection = selection;
    this._dragging = dragging;
    this._mouse = mouse;

    var self = this;

    // lasso visuals implementation

    /**
    * A helper that realizes the selection box visual
    */
    var visuals = {

      create: function(context) {
        var container = canvas.getActiveLayer(),
            frame;

        frame = context.frame = create$1('rect');
        attr(frame, {
          class: 'djs-lasso-overlay',
          width:  1,
          height: 1,
          x: 0,
          y: 0
        });

        append(container, frame);
      },

      update: function(context) {
        var frame = context.frame,
            bbox = context.bbox;

        attr(frame, {
          x: bbox.x,
          y: bbox.y,
          width: bbox.width,
          height: bbox.height
        });
      },

      remove: function(context) {

        if (context.frame) {
          remove$1(context.frame);
        }
      }
    };

    toolManager.registerTool('lasso', {
      tool: 'lasso.selection',
      dragging: 'lasso'
    });

    eventBus.on('lasso.selection.end', function(event) {
      var target = event.originalEvent.target;

      // only reactive on diagram click
      // on some occasions, event.hover is not set and we have to check if the target is an svg
      if (!event.hover && !(target instanceof SVGElement)) {
        return;
      }

      eventBus.once('lasso.selection.ended', function() {
        self.activateLasso(event.originalEvent, true);
      });
    });

    // lasso interaction implementation

    eventBus.on('lasso.end', function(event) {

      var bbox = toBBox(event);

      var elements = elementRegistry.filter(function(element) {
        return element;
      });

      self.select(elements, bbox);
    });

    eventBus.on('lasso.start', function(event) {

      var context = event.context;

      context.bbox = toBBox(event);
      visuals.create(context);
    });

    eventBus.on('lasso.move', function(event) {

      var context = event.context;

      context.bbox = toBBox(event);
      visuals.update(context);
    });

    eventBus.on('lasso.cleanup', function(event) {

      var context = event.context;

      visuals.remove(context);
    });


    // event integration

    eventBus.on('element.mousedown', 1500, function(event) {

      if (!hasSecondaryModifier(event)) {
        return;
      }

      self.activateLasso(event.originalEvent);

      // we've handled the event
      return true;
    });
  }

  LassoTool.$inject = [
    'eventBus',
    'canvas',
    'dragging',
    'elementRegistry',
    'selection',
    'toolManager',
    'mouse'
  ];


  LassoTool.prototype.activateLasso = function(event, autoActivate) {

    this._dragging.init(event, 'lasso', {
      autoActivate: autoActivate,
      cursor: LASSO_TOOL_CURSOR,
      data: {
        context: {}
      }
    });
  };

  LassoTool.prototype.activateSelection = function(event, autoActivate) {

    this._dragging.init(event, 'lasso.selection', {
      trapClick: false,
      autoActivate: autoActivate,
      cursor: LASSO_TOOL_CURSOR,
      data: {
        context: {}
      }
    });
  };

  LassoTool.prototype.select = function(elements, bbox) {
    var selectedElements = getEnclosedElements(elements, bbox);

    this._selection.select(values(selectedElements));
  };

  LassoTool.prototype.toggle = function() {
    if (this.isActive()) {
      return this._dragging.cancel();
    }

    var mouseEvent = this._mouse.getLastMoveEvent();

    this.activateSelection(mouseEvent, !!mouseEvent);
  };

  LassoTool.prototype.isActive = function() {
    var context = this._dragging.context();

    return context && /^lasso/.test(context.prefix);
  };



  function toBBox(event) {

    var start = {

      x: event.x - event.dx,
      y: event.y - event.dy
    };

    var end = {
      x: event.x,
      y: event.y
    };

    var bbox;

    if ((start.x <= end.x && start.y < end.y) ||
        (start.x < end.x && start.y <= end.y)) {

      bbox = {
        x: start.x,
        y: start.y,
        width:  end.x - start.x,
        height: end.y - start.y
      };
    } else if ((start.x >= end.x && start.y < end.y) ||
               (start.x > end.x && start.y <= end.y)) {

      bbox = {
        x: end.x,
        y: start.y,
        width:  start.x - end.x,
        height: end.y - start.y
      };
    } else if ((start.x <= end.x && start.y > end.y) ||
               (start.x < end.x && start.y >= end.y)) {

      bbox = {
        x: start.x,
        y: end.y,
        width:  end.x - start.x,
        height: start.y - end.y
      };
    } else if ((start.x >= end.x && start.y > end.y) ||
               (start.x > end.x && start.y >= end.y)) {

      bbox = {
        x: end.x,
        y: end.y,
        width:  start.x - end.x,
        height: start.y - end.y
      };
    } else {

      bbox = {
        x: end.x,
        y: end.y,
        width:  0,
        height: 0
      };
    }
    return bbox;
  }

  var LassoToolModule = {
    __depends__: [
      ToolManagerModule,
      MouseModule
    ],
    __init__: [ 'lassoTool' ],
    lassoTool: [ 'type', LassoTool ]
  };

  var HIGH_PRIORITY$1 = 1500;
  var HAND_CURSOR = 'grab';


  function HandTool(
      eventBus, canvas, dragging,
      injector, toolManager, mouse) {

    this._dragging = dragging;
    this._mouse = mouse;

    var self = this,
        keyboard = injector.get('keyboard', false);

    toolManager.registerTool('hand', {
      tool: 'hand',
      dragging: 'hand.move'
    });

    eventBus.on('element.mousedown', HIGH_PRIORITY$1, function(event) {

      if (!hasPrimaryModifier(event)) {
        return;
      }

      self.activateMove(event.originalEvent, true);

      return false;
    });

    keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) {
      if (!isSpace(e.keyEvent) || self.isActive()) {
        return;
      }

      var mouseEvent = self._mouse.getLastMoveEvent();

      self.activateMove(mouseEvent, !!mouseEvent);
    }, 'keyboard.keydown');

    keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) {
      if (!isSpace(e.keyEvent) || !self.isActive()) {
        return;
      }

      self.toggle();
    }, 'keyboard.keyup');

    eventBus.on('hand.end', function(event) {
      var target = event.originalEvent.target;

      // only reactive on diagram click
      // on some occasions, event.hover is not set and we have to check if the target is an svg
      if (!event.hover && !(target instanceof SVGElement)) {
        return false;
      }

      eventBus.once('hand.ended', function() {
        self.activateMove(event.originalEvent, { reactivate: true });
      });

    });

    eventBus.on('hand.move.move', function(event) {
      var scale = canvas.viewbox().scale;

      canvas.scroll({
        dx: event.dx * scale,
        dy: event.dy * scale
      });
    });

    eventBus.on('hand.move.end', function(event) {
      var context = event.context,
          reactivate = context.reactivate;

      // Don't reactivate if the user is using the keyboard keybinding
      if (!hasPrimaryModifier(event) && reactivate) {

        eventBus.once('hand.move.ended', function(event) {
          self.activateHand(event.originalEvent, true, true);
        });

      }

      return false;
    });

  }

  HandTool.$inject = [
    'eventBus',
    'canvas',
    'dragging',
    'injector',
    'toolManager',
    'mouse'
  ];


  HandTool.prototype.activateMove = function(event, autoActivate, context) {
    if (typeof autoActivate === 'object') {
      context = autoActivate;
      autoActivate = false;
    }

    this._dragging.init(event, 'hand.move', {
      autoActivate: autoActivate,
      cursor: HAND_CURSOR,
      data: {
        context: context || {}
      }
    });
  };

  HandTool.prototype.activateHand = function(event, autoActivate, reactivate) {
    this._dragging.init(event, 'hand', {
      trapClick: false,
      autoActivate: autoActivate,
      cursor: HAND_CURSOR,
      data: {
        context: {
          reactivate: reactivate
        }
      }
    });
  };

  HandTool.prototype.toggle = function() {
    if (this.isActive()) {
      return this._dragging.cancel();
    }

    var mouseEvent = this._mouse.getLastMoveEvent();

    this.activateHand(mouseEvent, !!mouseEvent);
  };

  HandTool.prototype.isActive = function() {
    var context = this._dragging.context();

    if (context) {
      return /^(hand|hand\.move)$/.test(context.prefix);
    }

    return false;
  };

  // helpers //////////

  function isSpace(keyEvent) {
    return isKey(' ', keyEvent);
  }

  var HandToolModule = {
    __depends__: [
      ToolManagerModule,
      MouseModule
    ],
    __init__: [ 'handTool' ],
    handTool: [ 'type', HandTool ]
  };

  var MARKER_OK = 'connect-ok',
      MARKER_NOT_OK = 'connect-not-ok';

  /**
   * @class
   * @constructor
   *
   * @param {EventBus} eventBus
   * @param {Dragging} dragging
   * @param {Connect} connect
   * @param {Canvas} canvas
   * @param {ToolManager} toolManager
   * @param {Rules} rules
   * @param {Mouse} mouse
   */
  function GlobalConnect(
      eventBus, dragging, connect,
      canvas, toolManager, rules,
      mouse) {

    var self = this;

    this._dragging = dragging;
    this._rules = rules;
    this._mouse = mouse;

    toolManager.registerTool('global-connect', {
      tool: 'global-connect',
      dragging: 'global-connect.drag'
    });

    eventBus.on('global-connect.hover', function(event) {
      var context = event.context,
          startTarget = event.hover;

      var canStartConnect = context.canStartConnect = self.canStartConnect(startTarget);

      // simply ignore hover
      if (canStartConnect === null) {
        return;
      }

      context.startTarget = startTarget;

      canvas.addMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK);
    });


    eventBus.on([ 'global-connect.out', 'global-connect.cleanup' ], function(event) {
      var startTarget = event.context.startTarget,
          canStartConnect = event.context.canStartConnect;

      if (startTarget) {
        canvas.removeMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK);
      }
    });


    eventBus.on([ 'global-connect.ended' ], function(event) {
      var context = event.context,
          startTarget = context.startTarget,
          startPosition = {
            x: event.x,
            y: event.y
          };

      var canStartConnect = self.canStartConnect(startTarget);

      if (!canStartConnect) {
        return;
      }

      eventBus.once('element.out', function() {
        eventBus.once([ 'connect.ended', 'connect.canceled' ], function() {
          eventBus.fire('global-connect.drag.ended');
        });

        connect.start(null, startTarget, startPosition);
      });

      return false;
    });
  }

  GlobalConnect.$inject = [
    'eventBus',
    'dragging',
    'connect',
    'canvas',
    'toolManager',
    'rules',
    'mouse'
  ];

  /**
   * Initiates tool activity.
   */
  GlobalConnect.prototype.start = function(event, autoActivate) {
    this._dragging.init(event, 'global-connect', {
      autoActivate: autoActivate,
      trapClick: false,
      data: {
        context: {}
      }
    });
  };

  GlobalConnect.prototype.toggle = function() {

    if (this.isActive()) {
      return this._dragging.cancel();
    }

    var mouseEvent = this._mouse.getLastMoveEvent();

    return this.start(mouseEvent, !!mouseEvent);
  };

  GlobalConnect.prototype.isActive = function() {
    var context = this._dragging.context();

    return context && /^global-connect/.test(context.prefix);
  };

  /**
   * Check if source shape can initiate connection.
   *
   * @param  {Shape} startTarget
   * @return {boolean}
   */
  GlobalConnect.prototype.canStartConnect = function(startTarget) {
    return this._rules.allowed('connection.start', { source: startTarget });
  };

  var GlobalConnectModule = {
    __depends__: [
      ConnectModule,
      RulesModule$1,
      DraggingModule,
      ToolManagerModule,
      MouseModule
    ],
    globalConnect: [ 'type', GlobalConnect ]
  };

  /**
   * A palette provider for BPMN 2.0 elements.
   */
  function PaletteProvider(
      palette, create, elementFactory,
      spaceTool, lassoTool, handTool,
      globalConnect, translate) {

    this._palette = palette;
    this._create = create;
    this._elementFactory = elementFactory;
    this._spaceTool = spaceTool;
    this._lassoTool = lassoTool;
    this._handTool = handTool;
    this._globalConnect = globalConnect;
    this._translate = translate;

    palette.registerProvider(this);
  }

  PaletteProvider.$inject = [
    'palette',
    'create',
    'elementFactory',
    'spaceTool',
    'lassoTool',
    'handTool',
    'globalConnect',
    'translate'
  ];


  PaletteProvider.prototype.getPaletteEntries = function(element) {

    var actions = {},
        create = this._create,
        elementFactory = this._elementFactory,
        spaceTool = this._spaceTool,
        lassoTool = this._lassoTool,
        handTool = this._handTool,
        globalConnect = this._globalConnect,
        translate = this._translate;

    function createAction(type, group, className, title, options) {

      function createListener(event) {
        var shape = elementFactory.createShape(assign({ type: type }, options));

        if (options) {
          var di = getDi(shape);
          di.isExpanded = options.isExpanded;
        }

        create.start(event, shape);
      }

      var shortType = type.replace(/^bpmn:/, '');

      return {
        group: group,
        className: className,
        title: title || translate('Create {type}', { type: shortType }),
        action: {
          dragstart: createListener,
          click: createListener
        }
      };
    }

    function createSubprocess(event) {
      var subProcess = elementFactory.createShape({
        type: 'bpmn:SubProcess',
        x: 0,
        y: 0,
        isExpanded: true
      });

      var startEvent = elementFactory.createShape({
        type: 'bpmn:StartEvent',
        x: 40,
        y: 82,
        parent: subProcess
      });

      create.start(event, [ subProcess, startEvent ], {
        hints: {
          autoSelect: [ subProcess ]
        }
      });
    }

    function createParticipant(event) {
      create.start(event, elementFactory.createParticipantShape());
    }

    assign(actions, {
      'hand-tool': {
        group: 'tools',
        className: 'bpmn-icon-hand-tool',
        title: translate('Activate the hand tool'),
        action: {
          click: function(event) {
            handTool.activateHand(event);
          }
        }
      },
      'lasso-tool': {
        group: 'tools',
        className: 'bpmn-icon-lasso-tool',
        title: translate('Activate the lasso tool'),
        action: {
          click: function(event) {
            lassoTool.activateSelection(event);
          }
        }
      },
      'space-tool': {
        group: 'tools',
        className: 'bpmn-icon-space-tool',
        title: translate('Activate the create/remove space tool'),
        action: {
          click: function(event) {
            spaceTool.activateSelection(event);
          }
        }
      },
      'global-connect-tool': {
        group: 'tools',
        className: 'bpmn-icon-connection-multi',
        title: translate('Activate the global connect tool'),
        action: {
          click: function(event) {
            globalConnect.start(event);
          }
        }
      },
      'tool-separator': {
        group: 'tools',
        separator: true
      },
      'create.start-event': createAction(
        'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
        translate('Create StartEvent')
      ),
      'create.intermediate-event': createAction(
        'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
        translate('Create Intermediate/Boundary Event')
      ),
      'create.end-event': createAction(
        'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
        translate('Create EndEvent')
      ),
      'create.exclusive-gateway': createAction(
        'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none',
        translate('Create Gateway')
      ),
      'create.task': createAction(
        'bpmn:Task', 'activity', 'bpmn-icon-task',
        translate('Create Task')
      ),
      'create.data-object': createAction(
        'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
        translate('Create DataObjectReference')
      ),
      'create.data-store': createAction(
        'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
        translate('Create DataStoreReference')
      ),
      'create.subprocess-expanded': {
        group: 'activity',
        className: 'bpmn-icon-subprocess-expanded',
        title: translate('Create expanded SubProcess'),
        action: {
          dragstart: createSubprocess,
          click: createSubprocess
        }
      },
      'create.participant-expanded': {
        group: 'collaboration',
        className: 'bpmn-icon-participant',
        title: translate('Create Pool/Participant'),
        action: {
          dragstart: createParticipant,
          click: createParticipant
        }
      },
      'create.group': createAction(
        'bpmn:Group', 'artifact', 'bpmn-icon-group',
        translate('Create Group')
      ),
    });

    return actions;
  };

  var PaletteModule = {
    __depends__: [
      PaletteModule$1,
      CreateModule,
      SpaceToolModule,
      LassoToolModule,
      HandToolModule,
      GlobalConnectModule,
      translate
    ],
    __init__: [ 'paletteProvider' ],
    paletteProvider: [ 'type', PaletteProvider ]
  };

  var LOW_PRIORITY = 250;


  function BpmnReplacePreview(
      eventBus, elementRegistry, elementFactory,
      canvas, previewSupport) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Replace the visuals of all elements in the context which can be replaced
     *
     * @param  {Object} context
     */
    function replaceVisual(context) {

      var replacements = context.canExecute.replacements;

      forEach$1(replacements, function(replacement) {

        var id = replacement.oldElementId;

        var newElement = {
          type: replacement.newElementType
        };

        // if the visual of the element is already replaced
        if (context.visualReplacements[id]) {
          return;
        }

        var element = elementRegistry.get(id);

        assign(newElement, { x: element.x, y: element.y });

        // create a temporary shape
        var tempShape = elementFactory.createShape(newElement);

        canvas.addShape(tempShape, element.parent);

        // select the original SVG element related to the element and hide it
        var gfx = query('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup);

        if (gfx) {
          attr(gfx, { display: 'none' });
        }

        // clone the gfx of the temporary shape and add it to the drag group
        var dragger = previewSupport.addDragger(tempShape, context.dragGroup);

        context.visualReplacements[id] = dragger;

        canvas.removeShape(tempShape);
      });
    }

    /**
     * Restore the original visuals of the previously replaced elements
     *
     * @param  {Object} context
     */
    function restoreVisual(context) {

      var visualReplacements = context.visualReplacements;

      forEach$1(visualReplacements, function(dragger, id) {

        var originalGfx = query('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup);

        if (originalGfx) {
          attr(originalGfx, { display: 'inline' });
        }

        dragger.remove();

        if (visualReplacements[id]) {
          delete visualReplacements[id];
        }
      });
    }

    eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {

      var context = event.context,
          canExecute = context.canExecute;

      if (!context.visualReplacements) {
        context.visualReplacements = {};
      }

      if (canExecute && canExecute.replacements) {
        replaceVisual(context);
      } else {
        restoreVisual(context);
      }
    });
  }

  BpmnReplacePreview.$inject = [
    'eventBus',
    'elementRegistry',
    'elementFactory',
    'canvas',
    'previewSupport'
  ];

  e(BpmnReplacePreview, CommandInterceptor);

  var ReplacePreviewModule = {
    __depends__: [
      PreviewSupportModule
    ],
    __init__: [ 'bpmnReplacePreview' ],
    bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
  };

  var HIGHER_PRIORITY$2 = 1250;

  var BOUNDARY_TO_HOST_THRESHOLD = 40;

  var TARGET_BOUNDS_PADDING = 20,
      TASK_BOUNDS_PADDING = 10;

  var TARGET_CENTER_PADDING = 20;

  var AXES = [ 'x', 'y' ];

  var abs = Math.abs;

  /**
   * Snap during connect.
   *
   * @param {EventBus} eventBus
   */
  function BpmnConnectSnapping(eventBus) {
    eventBus.on([
      'connect.hover',
      'connect.move',
      'connect.end',
    ], HIGHER_PRIORITY$2, function(event) {
      var context = event.context,
          canExecute = context.canExecute,
          start = context.start,
          hover = context.hover,
          source = context.source,
          target = context.target;

      // do NOT snap on CMD
      if (event.originalEvent && isCmd(event.originalEvent)) {
        return;
      }

      if (!context.initialConnectionStart) {
        context.initialConnectionStart = context.connectionStart;
      }

      // snap hover
      if (canExecute && hover) {
        snapToShape(event, hover, getTargetBoundsPadding(hover));
      }

      if (hover && isAnyType(canExecute, [
        'bpmn:Association',
        'bpmn:DataInputAssociation',
        'bpmn:DataOutputAssociation',
        'bpmn:SequenceFlow'
      ])) {
        context.connectionStart = mid$2(start);

        // snap hover
        if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) {
          snapToPosition(event, mid$2(hover));
        }

        // snap hover
        if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) {
          snapToTargetMid(event, hover);
        }

        // snap source and target
        if (is$1(source, 'bpmn:BoundaryEvent') && target === source.host) {
          snapBoundaryEventLoop(event);
        }

      } else if (isType(canExecute, 'bpmn:MessageFlow')) {

        if (is$1(start, 'bpmn:Event')) {

          // snap start
          context.connectionStart = mid$2(start);
        }

        if (is$1(hover, 'bpmn:Event')) {

          // snap hover
          snapToPosition(event, mid$2(hover));
        }

      } else {

        // un-snap source
        context.connectionStart = context.initialConnectionStart;
      }
    });
  }

  BpmnConnectSnapping.$inject = [ 'eventBus' ];


  // helpers //////////

  // snap to target if event in target
  function snapToShape(event, target, padding) {
    AXES.forEach(function(axis) {
      var dimensionForAxis = getDimensionForAxis(axis, target);

      if (event[ axis ] < target[ axis ] + padding) {
        setSnapped(event, axis, target[ axis ] + padding);
      } else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) {
        setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding);
      }
    });
  }

  // snap to target mid if event in target mid
  function snapToTargetMid(event, target) {
    var targetMid = mid$2(target);

    AXES.forEach(function(axis) {
      if (isMid(event, target, axis)) {
        setSnapped(event, axis, targetMid[ axis ]);
      }
    });
  }

  // snap to prevent loop overlapping boundary event
  function snapBoundaryEventLoop(event) {
    var context = event.context,
        source = context.source,
        target = context.target;

    if (isReverse(context)) {
      return;
    }

    var sourceMid = mid$2(source),
        orientation = getOrientation(sourceMid, target, -10),
        axes = [];

    if (/top|bottom/.test(orientation)) {
      axes.push('x');
    }

    if (/left|right/.test(orientation)) {
      axes.push('y');
    }

    axes.forEach(function(axis) {
      var coordinate = event[ axis ], newCoordinate;

      if (abs(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD) {
        if (coordinate > sourceMid[ axis ]) {
          newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD;
        }
        else {
          newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD;
        }

        setSnapped(event, axis, newCoordinate);
      }
    });
  }

  function snapToPosition(event, position) {
    setSnapped(event, 'x', position.x);
    setSnapped(event, 'y', position.y);
  }

  function isType(attrs, type) {
    return attrs && attrs.type === type;
  }

  function isAnyType(attrs, types) {
    return some(types, function(type) {
      return isType(attrs, type);
    });
  }

  function getDimensionForAxis(axis, element) {
    return axis === 'x' ? element.width : element.height;
  }

  function getTargetBoundsPadding(target) {
    if (is$1(target, 'bpmn:Task')) {
      return TASK_BOUNDS_PADDING;
    } else {
      return TARGET_BOUNDS_PADDING;
    }
  }

  function isMid(event, target, axis) {
    return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING
      && event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING;
  }

  function isReverse(context) {
    var hover = context.hover,
        source = context.source;

    return hover && source && hover === source;
  }

  /**
   * A snap context, containing the (possibly incomplete)
   * mappings of drop targets (to identify the snapping)
   * to computed snap points.
   */
  function SnapContext() {

    /**
     * Map<String, SnapPoints> mapping drop targets to
     * a list of possible snappings.
     *
     * @type {Object}
     */
    this._targets = {};

    /**
     * Map<String, Point> initial positioning of element
     * regarding various snap directions.
     *
     * @type {Object}
     */
    this._snapOrigins = {};

    /**
     * List of snap locations
     *
     * @type {Array<string>}
     */
    this._snapLocations = [];

    /**
     * Map<String, Array<Point>> of default snapping locations
     *
     * @type {Object}
     */
    this._defaultSnaps = {};
  }


  SnapContext.prototype.getSnapOrigin = function(snapLocation) {
    return this._snapOrigins[snapLocation];
  };


  SnapContext.prototype.setSnapOrigin = function(snapLocation, initialValue) {
    this._snapOrigins[snapLocation] = initialValue;

    if (this._snapLocations.indexOf(snapLocation) === -1) {
      this._snapLocations.push(snapLocation);
    }
  };


  SnapContext.prototype.addDefaultSnap = function(type, point) {

    var snapValues = this._defaultSnaps[type];

    if (!snapValues) {
      snapValues = this._defaultSnaps[type] = [];
    }

    snapValues.push(point);
  };

  /**
   * Return a number of initialized snaps, i.e. snap locations such as
   * top-left, mid, bottom-right and so forth.
   *
   * @return {Array<string>} snapLocations
   */
  SnapContext.prototype.getSnapLocations = function() {
    return this._snapLocations;
  };

  /**
   * Set the snap locations for this context.
   *
   * The order of locations determines precedence.
   *
   * @param {Array<string>} snapLocations
   */
  SnapContext.prototype.setSnapLocations = function(snapLocations) {
    this._snapLocations = snapLocations;
  };

  /**
   * Get snap points for a given target
   *
   * @param {Element|string} target
   */
  SnapContext.prototype.pointsForTarget = function(target) {

    var targetId = target.id || target;

    var snapPoints = this._targets[targetId];

    if (!snapPoints) {
      snapPoints = this._targets[targetId] = new SnapPoints();
      snapPoints.initDefaults(this._defaultSnaps);
    }

    return snapPoints;
  };


  /**
   * Creates the snap points and initializes them with the
   * given default values.
   *
   * @param {Object<string, Array<Point>>} [defaultPoints]
   */
  function SnapPoints(defaultSnaps) {

    /**
     * Map<String, Map<(x|y), Array<number>>> mapping snap locations,
     * i.e. top-left, bottom-right, center to actual snap values.
     *
     * @type {Object}
     */
    this._snapValues = {};
  }

  SnapPoints.prototype.add = function(snapLocation, point) {

    var snapValues = this._snapValues[snapLocation];

    if (!snapValues) {
      snapValues = this._snapValues[snapLocation] = { x: [], y: [] };
    }

    if (snapValues.x.indexOf(point.x) === -1) {
      snapValues.x.push(point.x);
    }

    if (snapValues.y.indexOf(point.y) === -1) {
      snapValues.y.push(point.y);
    }
  };


  SnapPoints.prototype.snap = function(point, snapLocation, axis, tolerance) {
    var snappingValues = this._snapValues[snapLocation];

    return snappingValues && snapTo(point[axis], snappingValues[axis], tolerance);
  };

  /**
   * Initialize a number of default snapping points.
   *
   * @param  {Object} defaultSnaps
   */
  SnapPoints.prototype.initDefaults = function(defaultSnaps) {

    var self = this;

    forEach$1(defaultSnaps || {}, function(snapPoints, snapLocation) {
      forEach$1(snapPoints, function(point) {
        self.add(snapLocation, point);
      });
    });
  };

  var HIGHER_PRIORITY$1 = 1250;


  /**
   * Snap during create and move.
   *
   * @param {EventBus} elementRegistry
   * @param {EventBus} eventBus
   * @param {Snapping} snapping
   */
  function CreateMoveSnapping(elementRegistry, eventBus, snapping) {
    var self = this;

    this._elementRegistry = elementRegistry;

    eventBus.on([
      'create.start',
      'shape.move.start'
    ], function(event) {
      self.initSnap(event);
    });

    eventBus.on([
      'create.move',
      'create.end',
      'shape.move.move',
      'shape.move.end'
    ], HIGHER_PRIORITY$1, function(event) {
      var context = event.context,
          shape = context.shape,
          snapContext = context.snapContext,
          target = context.target;

      if (event.originalEvent && isCmd(event.originalEvent)) {
        return;
      }

      if (isSnapped(event) || !target) {
        return;
      }

      var snapPoints = snapContext.pointsForTarget(target);

      if (!snapPoints.initialized) {
        snapPoints = self.addSnapTargetPoints(snapPoints, shape, target);

        snapPoints.initialized = true;
      }

      snapping.snap(event, snapPoints);
    });

    eventBus.on([
      'create.cleanup',
      'shape.move.cleanup'
    ], function() {
      snapping.hide();
    });
  }

  CreateMoveSnapping.$inject = [
    'elementRegistry',
    'eventBus',
    'snapping'
  ];

  CreateMoveSnapping.prototype.initSnap = function(event) {
    var elementRegistry = this._elementRegistry;

    var context = event.context,
        shape = context.shape,
        snapContext = context.snapContext;

    if (!snapContext) {
      snapContext = context.snapContext = new SnapContext();
    }

    var shapeMid;

    if (elementRegistry.get(shape.id)) {

      // move
      shapeMid = mid$2(shape, event);
    } else {

      // create
      shapeMid = {
        x: event.x + mid$2(shape).x,
        y: event.y + mid$2(shape).y
      };
    }

    var shapeTopLeft = {
          x: shapeMid.x - shape.width / 2,
          y: shapeMid.y - shape.height / 2
        },
        shapeBottomRight = {
          x: shapeMid.x + shape.width / 2,
          y: shapeMid.y + shape.height / 2
        };

    snapContext.setSnapOrigin('mid', {
      x: shapeMid.x - event.x,
      y: shapeMid.y - event.y
    });

    // snap labels to mid only
    if (isLabel$1(shape)) {
      return snapContext;
    }

    snapContext.setSnapOrigin('top-left', {
      x: shapeTopLeft.x - event.x,
      y: shapeTopLeft.y - event.y
    });

    snapContext.setSnapOrigin('bottom-right', {
      x: shapeBottomRight.x - event.x,
      y: shapeBottomRight.y - event.y
    });

    return snapContext;
  };

  CreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
    var snapTargets = this.getSnapTargets(shape, target);

    forEach$1(snapTargets, function(snapTarget) {

      // handle labels
      if (isLabel$1(snapTarget)) {

        if (isLabel$1(shape)) {
          snapPoints.add('mid', mid$2(snapTarget));
        }

        return;
      }

      // handle connections
      if (isConnection$1(snapTarget)) {

        // ignore single segment connections
        if (snapTarget.waypoints.length < 3) {
          return;
        }

        // ignore first and last waypoint
        var waypoints = snapTarget.waypoints.slice(1, -1);

        forEach$1(waypoints, function(waypoint) {
          snapPoints.add('mid', waypoint);
        });

        return;
      }

      // handle shapes
      snapPoints.add('mid', mid$2(snapTarget));
    });

    if (!isNumber(shape.x) || !isNumber(shape.y)) {
      return snapPoints;
    }

    // snap to original position when moving
    if (this._elementRegistry.get(shape.id)) {
      snapPoints.add('mid', mid$2(shape));
    }

    return snapPoints;
  };

  CreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
    return getChildren(target).filter(function(child) {
      return !isHidden$1(child);
    });
  };

  // helpers //////////

  function isConnection$1(element) {
    return !!element.waypoints;
  }

  function isHidden$1(element) {
    return !!element.hidden;
  }

  function isLabel$1(element) {
    return !!element.labelTarget;
  }

  var HIGH_PRIORITY = 1500;


  /**
   * Snap during create and move.
   *
   * @param {EventBus} eventBus
   * @param {Injector} injector
   */
  function BpmnCreateMoveSnapping(eventBus, injector) {
    injector.invoke(CreateMoveSnapping, this);

    // creating first participant
    eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);

    // snap boundary events
    eventBus.on([
      'create.move',
      'create.end',
      'shape.move.move',
      'shape.move.end'
    ], HIGH_PRIORITY, function(event) {
      var context = event.context,
          canExecute = context.canExecute,
          target = context.target;

      var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);

      if (canAttach && !isSnapped(event)) {
        snapBoundaryEvent(event, target);
      }
    });
  }

  e(BpmnCreateMoveSnapping, CreateMoveSnapping);

  BpmnCreateMoveSnapping.$inject = [
    'eventBus',
    'injector'
  ];

  BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
    var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);

    var shape = event.shape;

    var isMove = !!this._elementRegistry.get(shape.id);

    // snap to docking points
    forEach$1(shape.outgoing, function(connection) {
      var docking = connection.waypoints[0];

      docking = docking.original || docking;

      snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
    });

    forEach$1(shape.incoming, function(connection) {
      var docking = connection.waypoints[connection.waypoints.length - 1];

      docking = docking.original || docking;

      snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
    });

    if (is$1(shape, 'bpmn:Participant')) {

      // snap to borders with higher priority
      snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
    }

    return snapContext;
  };

  BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
    CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);

    var snapTargets = this.getSnapTargets(shape, target);

    forEach$1(snapTargets, function(snapTarget) {

      // handle TRBL alignment
      //
      // * with container elements
      // * with text annotations
      if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
        snapPoints.add('top-left', topLeft(snapTarget));
        snapPoints.add('bottom-right', bottomRight(snapTarget));
      }
    });

    var elementRegistry = this._elementRegistry;

    // snap to docking points if not create mode
    forEach$1(shape.incoming, function(connection) {
      if (elementRegistry.get(shape.id)) {

        if (!includes(snapTargets, connection.source)) {
          snapPoints.add('mid', getMid(connection.source));
        }

        var docking = connection.waypoints[0];
        snapPoints.add(connection.id + '-docking', docking.original || docking);
      }
    });

    forEach$1(shape.outgoing, function(connection) {
      if (elementRegistry.get(shape.id)) {

        if (!includes(snapTargets, connection.target)) {
          snapPoints.add('mid', getMid(connection.target));
        }

        var docking = connection.waypoints[ connection.waypoints.length - 1 ];

        snapPoints.add(connection.id + '-docking', docking.original || docking);
      }
    });

    // add sequence flow parents as snap targets
    if (is$1(target, 'bpmn:SequenceFlow')) {
      snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
    }

    return snapPoints;
  };

  BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
    return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
      .filter(function(snapTarget) {

        // do not snap to lanes
        return !is$1(snapTarget, 'bpmn:Lane');
      });
  };

  // helpers //////////

  function snapBoundaryEvent(event, target) {
    var targetTRBL = asTRBL(target);

    var direction = getBoundaryAttachment(event, target);

    var context = event.context,
        shape = context.shape;

    var offset;

    if (shape.parent) {
      offset = { x: 0, y: 0 };
    } else {
      offset = getMid(shape);
    }

    if (/top/.test(direction)) {
      setSnapped(event, 'y', targetTRBL.top - offset.y);
    } else if (/bottom/.test(direction)) {
      setSnapped(event, 'y', targetTRBL.bottom - offset.y);
    }

    if (/left/.test(direction)) {
      setSnapped(event, 'x', targetTRBL.left - offset.x);
    } else if (/right/.test(direction)) {
      setSnapped(event, 'x', targetTRBL.right - offset.x);
    }
  }

  function areAll(elements, type) {
    return elements.every(function(el) {
      return is$1(el, type);
    });
  }

  function isContainer(element) {
    if (is$1(element, 'bpmn:SubProcess') && isExpanded(element)) {
      return true;
    }

    return is$1(element, 'bpmn:Participant');
  }


  function setSnappedIfConstrained(event) {
    var context = event.context,
        createConstraints = context.createConstraints;

    if (!createConstraints) {
      return;
    }

    var top = createConstraints.top,
        right = createConstraints.right,
        bottom = createConstraints.bottom,
        left = createConstraints.left;

    if ((left && left >= event.x) || (right && right <= event.x)) {
      setSnapped(event, 'x', event.x);
    }

    if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
      setSnapped(event, 'y', event.y);
    }
  }

  function includes(array, value) {
    return array.indexOf(value) !== -1;
  }

  function getDockingSnapOrigin(docking, isMove, event) {
    return isMove ? (
      {
        x: docking.x - event.x,
        y: docking.y - event.y
      }
    ) : {
      x: docking.x,
      y: docking.y
    };
  }

  var HIGHER_PRIORITY = 1250;


  /**
   * Snap during resize.
   *
   * @param {EventBus} eventBus
   * @param {Snapping} snapping
   */
  function ResizeSnapping(eventBus, snapping) {
    var self = this;

    eventBus.on([ 'resize.start' ], function(event) {
      self.initSnap(event);
    });

    eventBus.on([
      'resize.move',
      'resize.end',
    ], HIGHER_PRIORITY, function(event) {
      var context = event.context,
          shape = context.shape,
          parent = shape.parent,
          direction = context.direction,
          snapContext = context.snapContext;

      if (event.originalEvent && isCmd(event.originalEvent)) {
        return;
      }

      if (isSnapped(event)) {
        return;
      }

      var snapPoints = snapContext.pointsForTarget(parent);

      if (!snapPoints.initialized) {
        snapPoints = self.addSnapTargetPoints(snapPoints, shape, parent, direction);

        snapPoints.initialized = true;
      }

      if (isHorizontal(direction)) {
        setSnapped(event, 'x', event.x);
      }

      if (isVertical(direction)) {
        setSnapped(event, 'y', event.y);
      }

      snapping.snap(event, snapPoints);
    });

    eventBus.on([ 'resize.cleanup' ], function() {
      snapping.hide();
    });
  }

  ResizeSnapping.prototype.initSnap = function(event) {
    var context = event.context,
        shape = context.shape,
        direction = context.direction,
        snapContext = context.snapContext;

    if (!snapContext) {
      snapContext = context.snapContext = new SnapContext();
    }

    var snapOrigin = getSnapOrigin(shape, direction);

    snapContext.setSnapOrigin('corner', {
      x: snapOrigin.x - event.x,
      y: snapOrigin.y - event.y
    });

    return snapContext;
  };

  ResizeSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target, direction) {
    var snapTargets = this.getSnapTargets(shape, target);

    forEach$1(snapTargets, function(snapTarget) {
      snapPoints.add('corner', bottomRight(snapTarget));
      snapPoints.add('corner', topLeft(snapTarget));
    });

    snapPoints.add('corner', getSnapOrigin(shape, direction));

    return snapPoints;
  };

  ResizeSnapping.$inject = [
    'eventBus',
    'snapping'
  ];

  ResizeSnapping.prototype.getSnapTargets = function(shape, target) {
    return getChildren(target).filter(function(child) {
      return !isAttached(child, shape)
        && !isConnection(child)
        && !isHidden(child)
        && !isLabel(child);
    });
  };

  // helpers //////////

  function getSnapOrigin(shape, direction) {
    var mid = getMid(shape),
        trbl = asTRBL(shape);

    var snapOrigin = {
      x: mid.x,
      y: mid.y
    };

    if (direction.indexOf('n') !== -1) {
      snapOrigin.y = trbl.top;
    } else if (direction.indexOf('s') !== -1) {
      snapOrigin.y = trbl.bottom;
    }

    if (direction.indexOf('e') !== -1) {
      snapOrigin.x = trbl.right;
    } else if (direction.indexOf('w') !== -1) {
      snapOrigin.x = trbl.left;
    }

    return snapOrigin;
  }

  function isAttached(element, host) {
    return element.host === host;
  }

  function isConnection(element) {
    return !!element.waypoints;
  }

  function isHidden(element) {
    return !!element.hidden;
  }

  function isLabel(element) {
    return !!element.labelTarget;
  }

  function isHorizontal(direction) {
    return direction === 'n' || direction === 's';
  }

  function isVertical(direction) {
    return direction === 'e' || direction === 'w';
  }

  var SNAP_TOLERANCE = 7;

  var SNAP_LINE_HIDE_DELAY = 1000;


  /**
   * Generic snapping feature.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function Snapping(canvas) {
    this._canvas = canvas;

    // delay hide by 1000 seconds since last snap
    this._asyncHide = debounce(bind(this.hide, this), SNAP_LINE_HIDE_DELAY);
  }

  Snapping.$inject = [ 'canvas' ];

  /**
   * Snap an event to given snap points.
   *
   * @param {Event} event
   * @param {SnapPoints} snapPoints
   */
  Snapping.prototype.snap = function(event, snapPoints) {
    var context = event.context,
        snapContext = context.snapContext,
        snapLocations = snapContext.getSnapLocations();

    var snapping = {
      x: isSnapped(event, 'x'),
      y: isSnapped(event, 'y')
    };

    forEach$1(snapLocations, function(location) {
      var snapOrigin = snapContext.getSnapOrigin(location);

      var snapCurrent = {
        x: event.x + snapOrigin.x,
        y: event.y + snapOrigin.y
      };

      // snap both axis if not snapped already
      forEach$1([ 'x', 'y' ], function(axis) {
        var locationSnapping;

        if (!snapping[axis]) {
          locationSnapping = snapPoints.snap(snapCurrent, location, axis, SNAP_TOLERANCE);

          if (locationSnapping !== undefined) {
            snapping[axis] = {
              value: locationSnapping,
              originValue: locationSnapping - snapOrigin[axis]
            };
          }
        }
      });

      // no need to continue snapping
      if (snapping.x && snapping.y) {
        return false;
      }
    });

    // show snap lines
    this.showSnapLine('vertical', snapping.x && snapping.x.value);
    this.showSnapLine('horizontal', snapping.y && snapping.y.value);

    // snap event
    forEach$1([ 'x', 'y' ], function(axis) {
      var axisSnapping = snapping[axis];

      if (isObject(axisSnapping)) {
        setSnapped(event, axis, axisSnapping.originValue);
      }
    });
  };

  Snapping.prototype._createLine = function(orientation) {
    var root = this._canvas.getLayer('snap');

    var line = create$1('path');

    attr(line, { d: 'M0,0 L0,0' });

    classes(line).add('djs-snap-line');

    append(root, line);

    return {
      update: function(position) {

        if (!isNumber(position)) {
          attr(line, { display: 'none' });
        } else {
          if (orientation === 'horizontal') {
            attr(line, {
              d: 'M-100000,' + position + ' L+100000,' + position,
              display: ''
            });
          } else {
            attr(line, {
              d: 'M ' + position + ',-100000 L ' + position + ', +100000',
              display: ''
            });
          }
        }
      }
    };
  };

  Snapping.prototype._createSnapLines = function() {
    this._snapLines = {
      horizontal: this._createLine('horizontal'),
      vertical: this._createLine('vertical')
    };
  };

  Snapping.prototype.showSnapLine = function(orientation, position) {

    var line = this.getSnapLine(orientation);

    if (line) {
      line.update(position);
    }

    this._asyncHide();
  };

  Snapping.prototype.getSnapLine = function(orientation) {
    if (!this._snapLines) {
      this._createSnapLines();
    }

    return this._snapLines[orientation];
  };

  Snapping.prototype.hide = function() {
    forEach$1(this._snapLines, function(snapLine) {
      snapLine.update();
    });
  };

  var SnappingModule$1 = {
    __init__: [
      'createMoveSnapping',
      'resizeSnapping',
      'snapping'
    ],
    createMoveSnapping: [ 'type', CreateMoveSnapping ],
    resizeSnapping: [ 'type', ResizeSnapping ],
    snapping: [ 'type', Snapping ]
  };

  var SnappingModule = {
    __depends__: [ SnappingModule$1 ],
    __init__: [
      'connectSnapping',
      'createMoveSnapping'
    ],
    connectSnapping: [ 'type', BpmnConnectSnapping ],
    createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
  };

  /**
   * Provides searching infrastructure
   */
  function SearchPad(canvas, eventBus, overlays, selection) {
    this._open = false;
    this._results = [];
    this._eventMaps = [];

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._overlays = overlays;
    this._selection = selection;

    // setup elements
    this._container = domify(SearchPad.BOX_HTML);
    this._searchInput = query(SearchPad.INPUT_SELECTOR, this._container);
    this._resultsContainer = query(SearchPad.RESULTS_CONTAINER_SELECTOR, this._container);

    // attach search pad
    this._canvas.getContainer().appendChild(this._container);

    // cleanup on destroy
    eventBus.on([ 'canvas.destroy', 'diagram.destroy' ], this.close, this);
  }


  SearchPad.$inject = [
    'canvas',
    'eventBus',
    'overlays',
    'selection'
  ];


  /**
   * Binds and keeps track of all event listereners
   */
  SearchPad.prototype._bindEvents = function() {
    var self = this;

    function listen(el, selector, type, fn) {
      self._eventMaps.push({
        el: el,
        type: type,
        listener: delegate.bind(el, selector, type, fn)
      });
    }

    // close search on clicking anywhere outside
    listen(document, 'html', 'click', function(e) {
      self.close();
    });

    // stop event from propagating and closing search
    // focus on input
    listen(this._container, SearchPad.INPUT_SELECTOR, 'click', function(e) {
      e.stopPropagation();
      e.delegateTarget.focus();
    });

    // preselect result on hover
    listen(this._container, SearchPad.RESULT_SELECTOR, 'mouseover', function(e) {
      e.stopPropagation();
      self._scrollToNode(e.delegateTarget);
      self._preselect(e.delegateTarget);
    });

    // selects desired result on mouse click
    listen(this._container, SearchPad.RESULT_SELECTOR, 'click', function(e) {
      e.stopPropagation();
      self._select(e.delegateTarget);
    });

    // prevent cursor in input from going left and right when using up/down to
    // navigate results
    listen(this._container, SearchPad.INPUT_SELECTOR, 'keydown', function(e) {

      // up
      if (e.keyCode === 38) {
        e.preventDefault();
      }

      // down
      if (e.keyCode === 40) {
        e.preventDefault();
      }
    });

    // handle keyboard input
    listen(this._container, SearchPad.INPUT_SELECTOR, 'keyup', function(e) {

      // escape
      if (e.keyCode === 27) {
        return self.close();
      }

      // enter
      if (e.keyCode === 13) {
        var selected = self._getCurrentResult();

        return selected ? self._select(selected) : self.close();
      }

      // up
      if (e.keyCode === 38) {
        return self._scrollToDirection(true);
      }

      // down
      if (e.keyCode === 40) {
        return self._scrollToDirection();
      }

      // left && right
      // do not search while navigating text input
      if (e.keyCode === 37 || e.keyCode === 39) {
        return;
      }

      // anything else
      self._search(e.delegateTarget.value);
    });
  };


  /**
   * Unbinds all previously established listeners
   */
  SearchPad.prototype._unbindEvents = function() {
    this._eventMaps.forEach(function(m) {
      delegate.unbind(m.el, m.type, m.listener);
    });
  };


  /**
   * Performs a search for the given pattern.
   *
   * @param  {string} pattern
   */
  SearchPad.prototype._search = function(pattern) {
    var self = this;

    this._clearResults();

    // do not search on empty query
    if (!pattern || pattern === '') {
      return;
    }

    var searchResults = this._searchProvider.find(pattern);

    if (!searchResults.length) {
      return;
    }

    // append new results
    searchResults.forEach(function(result) {
      var id = result.element.id;
      var node = self._createResultNode(result, id);
      self._results[id] = {
        element: result.element,
        node: node
      };
    });

    // preselect first result
    var node = query(SearchPad.RESULT_SELECTOR, this._resultsContainer);
    this._scrollToNode(node);
    this._preselect(node);
  };


  /**
   * Navigate to the previous/next result. Defaults to next result.
   * @param  {boolean} previous
   */
  SearchPad.prototype._scrollToDirection = function(previous) {
    var selected = this._getCurrentResult();
    if (!selected) {
      return;
    }

    var node = previous ? selected.previousElementSibling : selected.nextElementSibling;
    if (node) {
      this._scrollToNode(node);
      this._preselect(node);
    }
  };


  /**
   * Scroll to the node if it is not visible.
   *
   * @param  {Element} node
   */
  SearchPad.prototype._scrollToNode = function(node) {
    if (!node || node === this._getCurrentResult()) {
      return;
    }

    var nodeOffset = node.offsetTop;
    var containerScroll = this._resultsContainer.scrollTop;

    var bottomScroll = nodeOffset - this._resultsContainer.clientHeight + node.clientHeight;

    if (nodeOffset < containerScroll) {
      this._resultsContainer.scrollTop = nodeOffset;
    } else if (containerScroll < bottomScroll) {
      this._resultsContainer.scrollTop = bottomScroll;
    }
  };


  /**
   * Clears all results data.
   */
  SearchPad.prototype._clearResults = function() {
    clear$1(this._resultsContainer);

    this._results = [];

    this._resetOverlay();

    this._eventBus.fire('searchPad.cleared');
  };


  /**
   * Get currently selected result.
   *
   * @return {Element}
   */
  SearchPad.prototype._getCurrentResult = function() {
    return query(SearchPad.RESULT_SELECTED_SELECTOR, this._resultsContainer);
  };


  /**
   * Create result DOM element within results container
   * that corresponds to a search result.
   *
   * 'result' : one of the elements returned by SearchProvider
   * 'id' : id attribute value to assign to the new DOM node
   * return : created DOM element
   *
   * @param  {SearchResult} result
   * @param  {string} id
   * @return {Element}
   */
  SearchPad.prototype._createResultNode = function(result, id) {
    var node = domify(SearchPad.RESULT_HTML);

    // create only if available
    if (result.primaryTokens.length > 0) {
      createInnerTextNode(node, result.primaryTokens, SearchPad.RESULT_PRIMARY_HTML);
    }

    // secondary tokens (represent element ID) are allways available
    createInnerTextNode(node, result.secondaryTokens, SearchPad.RESULT_SECONDARY_HTML);

    attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE, id);

    this._resultsContainer.appendChild(node);

    return node;
  };


  /**
   * Register search element provider.
   *
   * SearchProvider.find - provides search function over own elements
   *  (pattern) => [{ text: <String>, element: <Element>}, ...]
   *
   * @param  {SearchProvider} provider
   */
  SearchPad.prototype.registerProvider = function(provider) {
    this._searchProvider = provider;
  };


  /**
   * Open search pad.
   */
  SearchPad.prototype.open = function() {
    if (!this._searchProvider) {
      throw new Error('no search provider registered');
    }

    if (this.isOpen()) {
      return;
    }

    this._bindEvents();

    this._open = true;

    classes$1(this._container).add('open');

    this._searchInput.focus();

    this._eventBus.fire('searchPad.opened');
  };


  /**
   * Close search pad.
   */
  SearchPad.prototype.close = function() {
    if (!this.isOpen()) {
      return;
    }

    this._unbindEvents();

    this._open = false;

    classes$1(this._container).remove('open');

    this._clearResults();

    this._searchInput.value = '';
    this._searchInput.blur();

    this._resetOverlay();

    this._eventBus.fire('searchPad.closed');
  };


  /**
   * Toggles search pad on/off.
   */
  SearchPad.prototype.toggle = function() {
    this.isOpen() ? this.close() : this.open();
  };


  /**
   * Report state of search pad.
   */
  SearchPad.prototype.isOpen = function() {
    return this._open;
  };


  /**
   * Preselect result entry.
   *
   * @param  {Element} element
   */
  SearchPad.prototype._preselect = function(node) {
    var selectedNode = this._getCurrentResult();

    // already selected
    if (node === selectedNode) {
      return;
    }

    // removing preselection from current node
    if (selectedNode) {
      classes$1(selectedNode).remove(SearchPad.RESULT_SELECTED_CLASS);
    }

    var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE);
    var element = this._results[id].element;

    classes$1(node).add(SearchPad.RESULT_SELECTED_CLASS);

    this._resetOverlay(element);

    this._canvas.scrollToElement(element, { top: 400 });

    this._selection.select(element);

    this._eventBus.fire('searchPad.preselected', element);
  };


  /**
   * Select result node.
   *
   * @param  {Element} element
   */
  SearchPad.prototype._select = function(node) {
    var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE);
    var element = this._results[id].element;

    this.close();

    this._resetOverlay();

    this._canvas.scrollToElement(element, { top: 400 });

    this._selection.select(element);

    this._eventBus.fire('searchPad.selected', element);
  };


  /**
   * Reset overlay removes and, optionally, set
   * overlay to a new element.
   *
   * @param  {Element} element
   */
  SearchPad.prototype._resetOverlay = function(element) {
    if (this._overlayId) {
      this._overlays.remove(this._overlayId);
    }

    if (element) {
      var box = getBBox(element);
      var overlay = constructOverlay(box);
      this._overlayId = this._overlays.add(element, overlay);
    }
  };


  /**
   * Construct overlay object for the given bounding box.
   *
   * @param  {BoundingBox} box
   * @return {Object}
   */
  function constructOverlay(box) {

    var offset = 6;
    var w = box.width + offset * 2;
    var h = box.height + offset * 2;

    var styles = {
      width: w + 'px',
      height: h + 'px'
    };

    var html = domify('<div class="' + SearchPad.OVERLAY_CLASS + '"></div>');

    assign$1(html, styles);

    return {
      position: {
        bottom: h - offset,
        right: w - offset
      },
      show: true,
      html: html
    };
  }


  /**
   * Creates and appends child node from result tokens and HTML template.
   *
   * @param  {Element} node
   * @param  {Array<Object>} tokens
   * @param  {string} template
   */
  function createInnerTextNode(parentNode, tokens, template) {
    var text = createHtmlText(tokens);
    var childNode = domify(template);
    childNode.innerHTML = text;
    parentNode.appendChild(childNode);
  }

  /**
   * Create internal HTML markup from result tokens.
   * Caters for highlighting pattern matched tokens.
   *
   * @param  {Array<Object>} tokens
   * @return {string}
   */
  function createHtmlText(tokens) {
    var htmlText = '';

    tokens.forEach(function(t) {
      if (t.matched) {
        htmlText += '<strong class="' + SearchPad.RESULT_HIGHLIGHT_CLASS + '">' + escapeHTML(t.matched) + '</strong>';
      } else {
        htmlText += escapeHTML(t.normal);
      }
    });

    return htmlText !== '' ? htmlText : null;
  }


  /**
   * CONSTANTS
   */
  SearchPad.CONTAINER_SELECTOR = '.djs-search-container';
  SearchPad.INPUT_SELECTOR = '.djs-search-input input';
  SearchPad.RESULTS_CONTAINER_SELECTOR = '.djs-search-results';
  SearchPad.RESULT_SELECTOR = '.djs-search-result';
  SearchPad.RESULT_SELECTED_CLASS = 'djs-search-result-selected';
  SearchPad.RESULT_SELECTED_SELECTOR = '.' + SearchPad.RESULT_SELECTED_CLASS;
  SearchPad.RESULT_ID_ATTRIBUTE = 'data-result-id';
  SearchPad.RESULT_HIGHLIGHT_CLASS = 'djs-search-highlight';
  SearchPad.OVERLAY_CLASS = 'djs-search-overlay';

  SearchPad.BOX_HTML =
    '<div class="djs-search-container djs-draggable djs-scrollable">' +
      '<div class="djs-search-input">' +
        '<input type="text"/>' +
      '</div>' +
      '<div class="djs-search-results"></div>' +
    '</div>';

  SearchPad.RESULT_HTML =
    '<div class="djs-search-result"></div>';

  SearchPad.RESULT_PRIMARY_HTML =
    '<div class="djs-search-result-primary"></div>';

  SearchPad.RESULT_SECONDARY_HTML =
    '<p class="djs-search-result-secondary"></p>';

  var SearchPadModule = {
    __depends__: [
      OverlaysModule,
      SelectionModule
    ],
    searchPad: [ 'type', SearchPad ]
  };

  /**
   * Provides ability to search through BPMN elements
   */
  function BpmnSearchProvider(elementRegistry, searchPad, canvas) {

    this._elementRegistry = elementRegistry;
    this._canvas = canvas;

    searchPad.registerProvider(this);
  }

  BpmnSearchProvider.$inject = [
    'elementRegistry',
    'searchPad',
    'canvas'
  ];


  /**
   * Finds all elements that match given pattern
   *
   * <Result> :
   *  {
   *    primaryTokens: <Array<Token>>,
   *    secondaryTokens: <Array<Token>>,
   *    element: <Element>
   *  }
   *
   * <Token> :
   *  {
   *    normal|matched: <string>
   *  }
   *
   * @param  {string} pattern
   * @return {Array<Result>}
   */
  BpmnSearchProvider.prototype.find = function(pattern) {
    var rootElement = this._canvas.getRootElement();

    var elements = this._elementRegistry.filter(function(element) {
      if (element.labelTarget) {
        return false;
      }
      return true;
    });

    // do not include root element
    elements = filter(elements, function(element) {
      return element !== rootElement;
    });

    elements = map(elements, function(element) {
      return {
        primaryTokens: matchAndSplit(getLabel(element), pattern),
        secondaryTokens: matchAndSplit(element.id, pattern),
        element: element
      };
    });

    // exclude non-matched elements
    elements = filter(elements, function(element) {
      return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens);
    });

    elements = sortBy(elements, function(element) {
      return getLabel(element.element) + element.element.id;
    });

    return elements;
  };


  function hasMatched(tokens) {
    var matched = filter(tokens, function(t) {
      return !!t.matched;
    });

    return matched.length > 0;
  }


  function matchAndSplit(text, pattern) {
    var tokens = [],
        originalText = text;

    if (!text) {
      return tokens;
    }

    text = text.toLowerCase();
    pattern = pattern.toLowerCase();

    var i = text.indexOf(pattern);

    if (i > -1) {
      if (i !== 0) {
        tokens.push({
          normal: originalText.substr(0, i)
        });
      }

      tokens.push({
        matched: originalText.substr(i, pattern.length)
      });

      if (pattern.length + i < text.length) {
        tokens.push({
          normal: originalText.substr(pattern.length + i, text.length)
        });
      }
    } else {
      tokens.push({
        normal: originalText
      });
    }

    return tokens;
  }

  var SearchModule = {
    __depends__: [
      SearchPadModule
    ],
    __init__: [ 'bpmnSearch' ],
    bpmnSearch: [ 'type', BpmnSearchProvider ]
  };

  var initialDiagram =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
                      'xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" ' +
                      'xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" ' +
                      'xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" ' +
                      'targetNamespace="http://bpmn.io/schema/bpmn" ' +
                      'id="Definitions_1">' +
      '<bpmn:process id="Process_1" isExecutable="false">' +
        '<bpmn:startEvent id="StartEvent_1"/>' +
      '</bpmn:process>' +
      '<bpmndi:BPMNDiagram id="BPMNDiagram_1">' +
        '<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">' +
          '<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">' +
            '<dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/>' +
          '</bpmndi:BPMNShape>' +
        '</bpmndi:BPMNPlane>' +
      '</bpmndi:BPMNDiagram>' +
    '</bpmn:definitions>';


  /**
   * A modeler for BPMN 2.0 diagrams.
   *
   *
   * ## Extending the Modeler
   *
   * In order to extend the viewer pass extension modules to bootstrap via the
   * `additionalModules` option. An extension module is an object that exposes
   * named services.
   *
   * The following example depicts the integration of a simple
   * logging component that integrates with interaction events:
   *
   *
   * ```javascript
   *
   * // logging component
   * function InteractionLogger(eventBus) {
   *   eventBus.on('element.hover', function(event) {
   *     console.log()
   *   })
   * }
   *
   * InteractionLogger.$inject = [ 'eventBus' ]; // minification save
   *
   * // extension module
   * var extensionModule = {
   *   __init__: [ 'interactionLogger' ],
   *   interactionLogger: [ 'type', InteractionLogger ]
   * };
   *
   * // extend the viewer
   * var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
   * bpmnModeler.importXML(...);
   * ```
   *
   *
   * ## Customizing / Replacing Components
   *
   * You can replace individual diagram components by redefining them in override modules.
   * This works for all components, including those defined in the core.
   *
   * Pass in override modules via the `options.additionalModules` flag like this:
   *
   * ```javascript
   * function CustomContextPadProvider(contextPad) {
   *
   *   contextPad.registerProvider(this);
   *
   *   this.getContextPadEntries = function(element) {
   *     // no entries, effectively disable the context pad
   *     return {};
   *   };
   * }
   *
   * CustomContextPadProvider.$inject = [ 'contextPad' ];
   *
   * var overrideModule = {
   *   contextPadProvider: [ 'type', CustomContextPadProvider ]
   * };
   *
   * var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]});
   * ```
   *
   * @param {Object} [options] configuration options to pass to the viewer
   * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
   * @param {string|number} [options.width] the width of the viewer
   * @param {string|number} [options.height] the height of the viewer
   * @param {Object} [options.moddleExtensions] extension packages to provide
   * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
   * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
   */
  function Modeler(options) {
    BaseModeler.call(this, options);
  }

  e(Modeler, BaseModeler);


  Modeler.Viewer = Viewer;
  Modeler.NavigatedViewer = NavigatedViewer;

  /**
  * The createDiagram result.
  *
  * @typedef {Object} CreateDiagramResult
  *
  * @property {Array<string>} warnings
  */

  /**
  * The createDiagram error.
  *
  * @typedef {Error} CreateDiagramError
  *
  * @property {Array<string>} warnings
  */

  /**
   * Create a new diagram to start modeling.
   *
   * Returns {Promise<CreateDiagramResult, CreateDiagramError>}
   */
  Modeler.prototype.createDiagram = wrapForCompatibility(function createDiagram() {
    return this.importXML(initialDiagram);
  });


  Modeler.prototype._interactionModules = [

    // non-modeling components
    KeyboardMoveModule,
    MoveCanvasModule,
    TouchModule,
    ZoomScrollModule
  ];

  Modeler.prototype._modelingModules = [

    // modeling components
    AlignElementsModule,
    AutoPlaceModule,
    AutoScrollModule,
    AutoResizeModule,
    BendpointsModule,
    ConnectModule,
    ConnectionPreviewModule,
    ContextPadModule,
    CopyPasteModule,
    CreateModule,
    DistributeElementsModule,
    EditorActionsModule,
    GridSnappingModule,
    InteractionEventsModule,
    KeyboardModule,
    KeyboardMoveSelectionModule,
    LabelEditingModule,
    ModelingModule,
    MoveModule,
    PaletteModule,
    ReplacePreviewModule,
    ResizeModule,
    SnappingModule,
    SearchModule
  ];


  // modules the modeler is composed of
  //
  // - viewer modules
  // - interaction modules
  // - modeling modules

  Modeler.prototype._modules = [].concat(
    Viewer.prototype._modules,
    Modeler.prototype._interactionModules,
    Modeler.prototype._modelingModules
  );

  return Modeler;

}));