UNPKG

bpmn-js

Version:

A bpmn 2.0 toolkit and web modeler

66,370 lines 1.69 MB
/*!
 * bpmn-js - bpmn-modeler v18.6.1
 *
 * 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: 2025-04-25
 */
(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$2(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:false,writable:true,configurable:true}}));}

  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$1(seed) {
    if (!(this instanceof Ids$1)) {
      return new Ids$1(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$1.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$1.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$1.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$1.prototype.assigned = function (id) {
    return this._seed.get(id) || false;
  };

  /**
   * Unclaim an id.
   *
   * @param  {String} id the id to unclaim
   */
  Ids$1.prototype.unclaim = function (id) {
    delete this._seed.hats[id];
  };

  /**
   * Clear all claimed ids.
   */
  Ids$1.prototype.clear = function () {
    var hats = this._seed.hats,
      id;
    for (id in hats) {
      this.unclaim(id);
    }
  };

  /**
   * Flatten array, one level deep.
   *
   * @template T
   *
   * @param {T[][] | T[] | null} [arr]
   *
   * @return {T[]}
   */
  function flatten(arr) {
    return Array.prototype.concat.apply([], arr);
  }

  const nativeToString$1 = Object.prototype.toString;
  const 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]';
  }

  /**
   * @param {any} obj
   *
   * @return {boolean}
   */
  function isFunction(obj) {
    const 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);
  }

  /**
   * @template T
   * @typedef { (
   *   ((e: T) => boolean) |
   *   ((e: T, idx: number) => boolean) |
   *   ((e: T, key: string) => boolean) |
   *   string |
   *   number
   * ) } Matcher
   */

  /**
   * @template T
   * @template U
   *
   * @typedef { (
   *   ((e: T) => U) | string | number
   * ) } Extractor
   */


  /**
   * @template T
   * @typedef { (val: T, key: any) => boolean } MatchFn
   */

  /**
   * @template T
   * @typedef { T[] } ArrayCollection
   */

  /**
   * @template T
   * @typedef { { [key: string]: T } } StringKeyValueCollection
   */

  /**
   * @template T
   * @typedef { { [key: number]: T } } NumberKeyValueCollection
   */

  /**
   * @template T
   * @typedef { StringKeyValueCollection<T> | NumberKeyValueCollection<T> } KeyValueCollection
   */

  /**
   * @template T
   * @typedef { KeyValueCollection<T> | ArrayCollection<T> } Collection
   */

  /**
   * Find element in collection.
   *
   * @template T
   * @param {Collection<T>} collection
   * @param {Matcher<T>} matcher
   *
   * @return {Object}
   */
  function find(collection, matcher) {

    const matchFn = toMatcher(matcher);

    let match;

    forEach$1(collection, function(val, key) {
      if (matchFn(val, key)) {
        match = val;

        return false;
      }
    });

    return match;

  }


  /**
   * Find element index in collection.
   *
   * @template T
   * @param {Collection<T>} collection
   * @param {Matcher<T>} matcher
   *
   * @return {number}
   */
  function findIndex(collection, matcher) {

    const matchFn = toMatcher(matcher);

    let idx = isArray$3(collection) ? -1 : undefined;

    forEach$1(collection, function(val, key) {
      if (matchFn(val, key)) {
        idx = key;

        return false;
      }
    });

    return idx;
  }


  /**
   * Filter elements in collection.
   *
   * @template T
   * @param {Collection<T>} collection
   * @param {Matcher<T>} matcher
   *
   * @return {T[]} result
   */
  function filter(collection, matcher) {

    const matchFn = toMatcher(matcher);

    let result = [];

    forEach$1(collection, function(val, key) {
      if (matchFn(val, key)) {
        result.push(val);
      }
    });

    return result;
  }


  /**
   * Iterate over collection; returning something
   * (non-undefined) will stop iteration.
   *
   * @template T
   * @param {Collection<T>} collection
   * @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
   *
   * @return {T} return result that stopped the iteration
   */
  function forEach$1(collection, iterator) {

    let val,
        result;

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

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

    for (let 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.
   *
   * @template T
   * @param {ArrayCollection<T>} arr
   * @param {Matcher<T>} matcher
   *
   * @return {T[]}
   */
  function without(arr, matcher) {

    if (isUndefined$2(arr)) {
      return [];
    }

    ensureArray(arr);

    const matchFn = toMatcher(matcher);

    return arr.filter(function(el, idx) {
      return !matchFn(el, idx);
    });

  }


  /**
   * Reduce collection, returning a single result.
   *
   * @template T
   * @template V
   *
   * @param {Collection<T>} collection
   * @param {(result: V, entry: T, index: any) => V} iterator
   * @param {V} result
   *
   * @return {V} 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$1(collection, fn) {

    let 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$1(collection, (val) => val);
  }


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

    extractor = toExtractor(extractor);

    forEach$1(collection, function(val) {
      let discriminator = extractor(val) || '_';

      let group = grouped[discriminator];

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

      group.push(val);
    });

    return grouped;
  }


  function uniqueBy(extractor, ...collections) {

    extractor = toExtractor(extractor);

    let grouped = {};

    forEach$1(collections, (c) => groupBy(c, extractor, grouped));

    let result = map$1(grouped, function(val, key) {
      return val[0];
    });

    return result;
  }


  const unionBy = uniqueBy;



  /**
   * Sort collection by criteria.
   *
   * @template T
   *
   * @param {Collection<T>} collection
   * @param {Extractor<T, number | string>} extractor
   *
   * @return {Array}
   */
  function sortBy(collection, extractor) {

    extractor = toExtractor(extractor);

    let sorted = [];

    forEach$1(collection, function(value, key) {
      let disc = extractor(value, key);

      let entry = {
        d: disc,
        v: value
      };

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

        if (disc < d) {
          sorted.splice(idx, 0, entry);
          return;
        }
      }

      // not inserted, append (!)
      sorted.push(entry);
    });

    return map$1(sorted, (e) => e.v);
  }


  /**
   * Create an object pattern matcher.
   *
   * @example
   *
   * ```javascript
   * const matcher = matchPattern({ id: 1 });
   *
   * let element = find(elements, matcher);
   * ```
   *
   * @template T
   *
   * @param {T} pattern
   *
   * @return { (el: any) =>  boolean } matcherFn
   */
  function matchPattern(pattern) {

    return function(el) {

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

    };
  }


  /**
   * @param {string | ((e: any) => any) } extractor
   *
   * @return { (e: any) => any }
   */
  function toExtractor(extractor) {

    /**
     * @satisfies { (e: any) => any }
     */
    return isFunction(extractor) ? extractor : (e) => {

      // @ts-ignore: just works
      return e[extractor];
    };
  }


  /**
   * @template T
   * @param {Matcher<T>} matcher
   *
   * @return {MatchFn<T>}
   */
  function toMatcher(matcher) {
    return isFunction(matcher) ? matcher : (e) => {
      return e === matcher;
    };
  }


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

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

  /* global setTimeout clearTimeout */

  /**
   * @typedef { {
   *   (...args: any[]): any;
   *   flush: () => void;
   *   cancel: () => void;
   * } } DebouncedFunction
   */

  /**
   * 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 {DebouncedFunction} debounced function
   */
  function debounce(fn, timeout) {

    let timer;

    let lastArgs;
    let lastThis;

    let lastNow;

    function fire(force) {

      let now = Date.now();

      let 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();
    }

    /**
     * @type { DebouncedFunction }
     */
    function callback(...args) {
      lastNow = Date.now();

      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$2(fn, target) {
    return fn.bind(target);
  }

  /**
   * Convenience wrapper for `Object.assign`.
   *
   * @param {Object} target
   * @param {...Object} others
   *
   * @return {Object} the target
   */
  function assign$1(target, ...others) {
    return Object.assign(target, ...others);
  }

  /**
   * Sets a nested property of a given object to the specified value.
   *
   * This mutates the object and returns it.
   *
   * @template T
   *
   * @param {T} target The target of the set operation.
   * @param {(string|number)[]} path The path to the nested value.
   * @param {any} value The value to set.
   *
   * @return {T}
   */
  function set$1(target, path, value) {

    let currentTarget = target;

    forEach$1(path, function(key, idx) {

      if (typeof key !== 'number' && typeof key !== 'string') {
        throw new Error('illegal key type: ' + typeof key + '. Key should be of type number or string.');
      }

      if (key === 'constructor') {
        throw new Error('illegal key: constructor');
      }

      if (key === '__proto__') {
        throw new Error('illegal key: __proto__');
      }

      let nextKey = path[idx + 1];
      let nextTarget = currentTarget[key];

      if (isDefined(nextKey) && isNil(nextTarget)) {
        nextTarget = currentTarget[key] = isNaN(+nextKey) ? {} : [];
      }

      if (isUndefined$2(nextKey)) {
        if (isUndefined$2(value)) {
          delete currentTarget[key];
        } else {
          currentTarget[key] = value;
        }
      } else {
        currentTarget = nextTarget;
      }
    });

    return target;
  }

  /**
   * Pick properties from the given target.
   *
   * @template T
   * @template {any[]} V
   *
   * @param {T} target
   * @param {V} properties
   *
   * @return Pick<T, V>
   */
  function pick(target, properties) {

    let result = {};

    let 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.
   *
   * @template T
   * @template {any[]} V
   *
   * @param {T} target
   * @param {V} properties
   *
   * @return {Omit<T, V>} target
   */
  function omit(target, properties) {

    let result = {};

    let obj = Object(target);

    forEach$1(obj, function(prop, key) {

      if (properties.indexOf(key) === -1) {
        result[key] = prop;
      }
    });

    return result;
  }

  function _mergeNamespaces$1(n, m) {
    m.forEach(function (e) {
      e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
        if (k !== 'default' && !(k in n)) {
          var d = Object.getOwnPropertyDescriptor(e, k);
          Object.defineProperty(n, k, d.get ? d : {
            enumerable: true,
            get: function () { return e[k]; }
          });
        }
      });
    });
    return Object.freeze(n);
  }

  /**
   * Flatten array, one level deep.
   *
   * @template T
   *
   * @param {T[][] | T[] | null} [arr]
   *
   * @return {T[]}
   */

  const nativeToString = Object.prototype.toString;
  const 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.
   *
   * @template T
   * @param {Collection<T>} collection
   * @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
   *
   * @return {T} return result that stopped the iteration
   */
  function forEach(collection, iterator) {

    let val,
        result;

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

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

    for (let 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(element, ...styleSources) {
    const target = element.style;

    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;
  }

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

  /**
   * toString reference.
   */

  const 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) {
    this.list.add(name);
    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);
    }

    this.list.remove(name);
    return this;
  };

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

  ClassList$1.prototype.removeMatching = function(re) {
    const arr = this.array();
    for (let 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) {
    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;
  };

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

  ClassList$1.prototype.array = function() {
    return Array.from(this.list);
  };

  /**
   * 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.contains(name);
  };

  /**
   * Clear utility
   */

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

    while ((child = element.firstChild)) {
      element.removeChild(child);
    }

    return element;
  }

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

    return actualElement && typeof actualElement.closest === 'function' && actualElement.closest(selector) || null;
  }

  var componentEvent = {};

  var bind$1, unbind$1, prefix$6;

  function detect () {
    bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent';
    unbind$1 = 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 = componentEvent.bind = function(el, type, fn, capture){
    if (!bind$1) detect();
    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 = componentEvent.unbind = function(el, type, fn, capture){
    if (!unbind$1) detect();
    el[unbind$1](prefix$6 + type, fn, capture || false);
    return fn;
  };

  var event = /*#__PURE__*/_mergeNamespaces$1({
    __proto__: null,
    bind: bind_1,
    unbind: unbind_1,
    'default': componentEvent
  }, [componentEvent]);

  /**
   * 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(el, selector, type, fn, capture) {
    if (forceCaptureEvents.indexOf(type) !== -1) {
      capture = true;
    }

    return event.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(el, type, fn, capture) {
    if (forceCaptureEvents.indexOf(type) !== -1) {
      capture = true;
    }

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

  var delegate = {
    bind,
    unbind
  };

  /**
   * 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 = {
    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.td =
  map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];

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

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

  map.polyline =
  map.ellipse =
  map.polygon =
  map.circle =
  map.text =
  map.line =
  map.path =
  map.rect =
  map.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 = Object.prototype.hasOwnProperty.call(map, tag) ? map[tag] : map._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;
  }

  var domify$1 = domify;

  /**
   * @param { HTMLElement } element
   * @param { String } selector
   *
   * @return { boolean }
   */
  function matches(element, selector) {
    return element && typeof element.matches === 'function' && element.matches(selector) || false;
  }

  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;
  }

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

  /**
   * toString reference.
   */

  const toString = Object.prototype.toString;

  /**
    * 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) {
    this.list.add(name);
    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);
    }

    this.list.remove(name);
    return this;
  };

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

  ClassList.prototype.removeMatching = function(re) {
    const arr = this.array();
    for (let 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) {
    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;
  };

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

  ClassList.prototype.array = function() {
    return Array.from(this.list);
  };

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

  ClassList.prototype.has =
   ClassList.prototype.contains = function(name) {
     return this.list.contains(name);
   };

  /**
   * Clear utility
   */

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

    while ((child = element.firstChild)) {
      element.removeChild(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;

    name = name.trim();

    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) {
    {
      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;
  }

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

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

    return output.join('');
  }

  function innerSVG(element, svg) {

    {
      return get(element);
    }
  }

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

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

    return element;
  }

  /**
   * 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();
  }

  const 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 Array.isArray(obj);
  }

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

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

  /**
   * @template T
   *
   * @params {[...string[], T] | ...string[], T} args
   *
   * @return {T & InjectAnnotated}
   */
  function annotate(...args) {

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

    args = [ ...args ];

    const 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.

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

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

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

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

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

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

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

  /**
   * @typedef { import('./index.js').ModuleDeclaration } ModuleDeclaration
   * @typedef { import('./index.js').ModuleDefinition } ModuleDefinition
   * @typedef { import('./index.js').InjectorContext } InjectorContext
   *
   * @typedef { import('./index.js').TypedDeclaration<any, any> } TypedDeclaration
   */

  /**
   * Create a new injector with the given modules.
   *
   * @param {ModuleDefinition[]} modules
   * @param {InjectorContext} [_parent]
   */
  function Injector(modules, _parent) {

    const parent = _parent || /** @type InjectorContext */ ({
      get: function(name, strict) {
        currentlyResolving.push(name);

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

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

    const self = instances.injector = this;

    const error = function(msg) {
      const 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.includes('.')) {

        const parts = name.split('.');
        let pivot = get(/** @type { string } */ (parts.shift()));

        while (parts.length) {
          pivot = pivot[/** @type { string } */ (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 error(`Cannot invoke "${ fn }". Expected a function!`);
        }
      }

      /**
       * @type {string[]}
       */
      const inject = fn.$inject || parseAnnotations(fn);
      const dependencies = inject.map(dep => {
        if (hasOwnProp(locals, dep)) {
          return locals[dep];
        } else {
          return get(dep);
        }
      });

      return {
        fn: fn,
        dependencies
      };
    }

    /**
     * Instantiate the given type, injecting dependencies.
     *
     * @template T
     *
     * @param { Function | [...string[], Function ]} type
     *
     * @return T
     */
    function instantiate(type) {
      const {
        fn,
        dependencies
      } = fnDef(type);

      // instantiate var args constructor
      const Constructor = Function.prototype.bind.call(fn, null, ...dependencies);

      return new Constructor();
    }

    /**
     * Invoke the given function, injecting dependencies. Return the result.
     *
     * @template T
     *
     * @param { Function | [...string[], Function ]} func
     * @param { Object } [context]
     * @param { Object } [locals]
     *
     * @return {T} invocation result
     */
    function invoke(func, context, locals) {
      const {
        fn,
        dependencies
      } = fnDef(func, locals);

      return fn.apply(context, dependencies);
    }

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

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

        const privateInjectorsCache = [];
        const privateChildInjectors = [];
        const privateChildFactories = [];

        let provider;
        let cacheIdx;
        let privateChildInjector;
        let privateChildInjectorFactory;

        for (let 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(scope => {
              if (provider[1].$scope.indexOf(scope) !== -1) {
                fromParentModule[name] = [ provider[2], provider[1] ];
                matchedScopes[scope] = true;
              }
            });
          }
        }

        forceNewInstances.forEach(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);
    }

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

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

      const initializers = moduleDefinition.__init__ || [];

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

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

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

      const moduleExports = moduleDefinition.__exports__;

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

        const clonedModule = Object.keys(moduleDefinition).reduce((clonedModule, key) => {

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

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

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

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

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

        // ensure child injector initializes
        const 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;
        }

        const typeDeclaration = /** @type { TypedDeclaration } */ (
          moduleDefinition[key]
        );

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

        const type = typeDeclaration[0];
        const value = typeDeclaration[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) {

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

      let initialized = false;

      return function() {

        if (initialized) {
          return;
        }

        initialized = true;

        initializers.forEach(initializer => 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;

  /**
   * @typedef {import('../core/Types').ElementLike} Element
   * @typedef {import('../core/Types').ConnectionLike} Connection
   * @typedef {import('../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../core/EventBus').default} EventBus
   */

  /**
   * 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);
        }
      }
    });
  }

  /**
   * Checks whether an element can be rendered.
   *
   * @param {Element} element The element to be rendered.
   *
   * @return {boolean} Whether the element can be rendered.
   */
  BaseRenderer.prototype.canRender = function(element) {};

  /**
   * Draws a shape.
   *
   * @param {SVGElement} visuals The SVG element to draw the shape into.
   * @param {Shape} shape The shape to be drawn.
   *
   * @return {SVGElement} The SVG element of the shape drawn.
   */
  BaseRenderer.prototype.drawShape = function(visuals, shape) {};

  /**
   * Draws a connection.
   *
   * @param {SVGElement} visuals The SVG element to draw the connection into.
   * @param {Connection} connection The connection to be drawn.
   *
   * @return {SVGElement} The SVG element of the connection drawn.
   */
  BaseRenderer.prototype.drawConnection = function(visuals, connection) {};

  /**
   * Gets the SVG path of the graphical representation of a shape.
   *
   * @param {Shape} shape The shape.
   *
   * @return {string} The SVG path of the shape.
   */
  BaseRenderer.prototype.getShapePath = function(shape) {};

  /**
   * Gets the SVG path of the graphical representation of a connection.
   *
   * @param {Connection} connection The connection.
   *
   * @return {string} The SVG path of the connection.
   */
  BaseRenderer.prototype.getConnectionPath = function(connection) {};

  /**
   * @typedef {(string|number)[]} Component
   *
   * @typedef {import('../util/Types').Point} Point
   */

  /**
   * @param {Component[] | Component[][]} elements
   *
   * @return {string}
   */
  function componentsToPath(elements) {
    return elements.flat().join(',').replace(/,?([A-Za-z]),?/g, '$1');
  }

  /**
   * @param {Point} point
   *
   * @return {Component[]}
   */
  function move(point) {
    return [ 'M', point.x, point.y ];
  }

  /**
   * @param {Point} point
   *
   * @return {Component[]}
   */
  function lineTo(point) {
    return [ 'L', point.x, point.y ];
  }

  /**
   * @param {Point} p1
   * @param {Point} p2
   * @param {Point} p3
   *
   * @return {Component[]}
   */
  function curveTo(p1, p2, p3) {
    return [ 'C', p1.x, p1.y, p2.x, p2.y, p3.x, p3.y ];
  }

  /**
   * @param {Point[]} waypoints
   * @param {number} [cornerRadius]
   * @return {Component[][]}
   */
  function drawPath(waypoints, cornerRadius) {
    const pointCount = waypoints.length;

    const path = [ move(waypoints[0]) ];

    for (let i = 1; i < pointCount; i++) {

      const pointBefore = waypoints[i - 1];
      const point = waypoints[i];
      const pointAfter = waypoints[i + 1];

      if (!pointAfter || !cornerRadius) {
        path.push(lineTo(point));

        continue;
      }

      const effectiveRadius = Math.min(
        cornerRadius,
        vectorLength$1(point.x - pointBefore.x, point.y - pointBefore.y),
        vectorLength$1(pointAfter.x - point.x, pointAfter.y - point.y)
      );

      if (!effectiveRadius) {
        path.push(lineTo(point));

        continue;
      }

      const beforePoint = getPointAtLength(point, pointBefore, effectiveRadius);
      const beforePoint2 = getPointAtLength(point, pointBefore, effectiveRadius * .5);

      const afterPoint = getPointAtLength(point, pointAfter, effectiveRadius);
      const afterPoint2 = getPointAtLength(point, pointAfter, effectiveRadius * .5);

      path.push(lineTo(beforePoint));
      path.push(curveTo(beforePoint2, afterPoint2, afterPoint));
    }

    return path;
  }

  function getPointAtLength(start, end, length) {

    const deltaX = end.x - start.x;
    const deltaY = end.y - start.y;

    const totalLength = vectorLength$1(deltaX, deltaY);

    const percent = length / totalLength;

    return {
      x: start.x + deltaX * percent,
      y: start.y + deltaY * percent
    };
  }

  function vectorLength$1(x, y) {
    return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  }

  /**
   * @param {Point[]} points
   * @param {number|Object} [attrs]
   * @param {number} [radius]
   *
   * @return {SVGElement}
   */
  function createLine(points, attrs, radius) {

    if (isNumber(attrs)) {
      radius = attrs;
      attrs = null;
    }

    if (!attrs) {
      attrs = {};
    }

    const line = create$1('path', attrs);

    if (isNumber(radius)) {
      line.dataset.cornerRadius = String(radius);
    }

    return updateLine(line, points);
  }

  /**
   * @param {SVGElement} gfx
   * @param {Point[]} points
   *
   * @return {SVGElement}
   */
  function updateLine(gfx, points) {

    const cornerRadius = parseInt(gfx.dataset.cornerRadius, 10) || 0;

    attr(gfx, {
      d: componentsToPath(drawPath(points, cornerRadius))
    });

    return gfx;
  }

  /**
   * @typedef {import('../model/Types').Connection} Connection
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').Shape} Shape
   *
   * @typedef {import('../util/Types').Rect} Rect
   *
   * @typedef { {
   *   allShapes: Record<string, Shape>,
   *   allConnections: Record<string, Connection>,
   *   topLevel: Record<string, Element>,
   *   enclosedConnections: Record<string, Connection>,
   *   enclosedElements: Record<string, Element>
   * } } Closure
   */

  /**
   * Get parent elements.
   *
   * @param {Element[]} elements
   *
   * @return {Element[]}
   */
  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 {Object[]} elements
   * @param {Object} element
   * @param {boolean} [unique]
   */
  function add$1(elements, element, unique) {
    var canAdd = !unique || elements.indexOf(element) === -1;

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

    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 {Element|Element[]} elements
   * @param {(element: Element, index: number, depth: number) => Element[] | boolean | undefined} fn
   * @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 {Element|Element[]} 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 {Element[]} 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
      {

        // 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 {Element[]} elements to query
   * @param {boolean} [allowDuplicates] to allow duplicates in the result set
   *
   * @return {Element[]} the collected elements
   */
  function selfAndAllChildren(elements, allowDuplicates) {
    return selfAndChildren(elements, !allowDuplicates);
  }


  /**
   * Gets the the closure for all selected elements,
   * their enclosed children and connections.
   *
   * @param {Element[]} elements
   * @param {boolean} [isTopLevel=true]
   * @param {Closure} [closure]
   *
   * @return {Closure} 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 {Element|Element[]} elements
   * @param {boolean} [stopRecursion=false]
   *
   * @return {Rect}
   */
  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 {Element[]} elements List of Elements to search through
   * @param {Rect} bbox the enclosing bbox.
   *
   * @return {Element[]} 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;
  }

  /**
   * Get the element's type
   *
   * @param {Element} element
   *
   * @return {'connection' | 'shape' | 'root'}
   */
  function getType(element) {

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

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

    return 'root';
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isFrameElement$1(element) {
    return !!(element && element.isFrame);
  }

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

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

  /**
   * @typedef {import('../core/EventBus').default} EventBus
   * @typedef {import('./Styles').default} Styles
   */

  // 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$2(DefaultRenderer, BaseRenderer);


  /**
   * @private
   */
  DefaultRenderer.prototype.canRender = function() {
    return true;
  };

  /**
   * @private
   */
  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$1({}, this.FRAME_STYLE, attrs || {}));
    } else {
      attr(rect, assign$1({}, this.SHAPE_STYLE, attrs || {}));
    }

    append(visuals, rect);

    return rect;
  };

  /**
   * @private
   */
  DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection, attrs) {

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

    return line;
  };

  /**
   * @private
   */
  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);
  };

  /**
   * @private
   */
  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 {string[]} [traits]
     * @param {Object} [additionalAttrs]
     *
     * @return {Object} the style definition
     */
    this.cls = function(className, traits, additionalAttrs) {
      var attrs = this.style(traits, additionalAttrs);

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

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

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

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

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


    /**
     * Computes a style definition from a list of traits and an object of
     * additional attributes, with custom style definition object.
     *
     * @param {Object} custom
     * @param {string[]} [traits]
     * @param {Object} defaultStyles
     *
     * @return {Object} the style definition
     */
    this.computeStyle = function(custom, traits, defaultStyles) {
      if (!isArray$3(traits)) {
        defaultStyles = traits;
        traits = [];
      }

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

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  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);
  }

  /**
   * @typedef {import('../util/Types').Axis} Axis
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   */

  /**
   * Computes the distance between two points.
   *
   * @param {Point} a
   * @param {Point} b
   *
   * @return {number} The distance between the two points.
   */
  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] The accuracy with which to 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 {Point[]|Point} a
   * @param {Point} [b]
   *
   * @return {string|boolean} If and how the two points are aligned ('h', 'v' or `false`).
   */
  function pointsAligned(a, b) {
    var points = Array.from(arguments).flat();

    const axisMap = {
      'x': 'v',
      'y': 'h'
    };

    for (const [ axis, orientation ] of Object.entries(axisMap)) {
      if (pointsAlignedOnAxis(axis, points)) {
        return orientation;
      }
    }

    return false;
  }

  /**
   * @param {Axis} axis
   * @param {Point[]} points
   *
   * @return {boolean}
   */
  function pointsAlignedOnAxis(axis, points) {
    const referencePoint = points[0];

    return every(points, function(point) {
      return Math.abs(referencePoint[axis] - point[axis]) <= 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} The mid point between the two points.
   */
  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))
    };
  }

  /**
   * 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(...arguments);

      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, ...params.splice(0, 2) ]);
          name = 'l';
          b = b == 'm' ? 'l' : 'L';
        }

        while (params.length >= paramCounts[name]) {
          data.push([ b, ...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(...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 = [ -0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.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 [];
    }

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

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

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

    for (i = 0; i < n2 + 1; i++) {
      p = findDotsAtSegment(...bez2, 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) {

            {
              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, ...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, ...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);

            {

              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(...bounds[0]),
      y0: mmin(...bounds[1]),
      x1: mmax(...bounds[0]),
      y1: mmax(...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', ...arcToCurve(d.x, d.y, ...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, ...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', ...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', ...qubicToCurve(d.x, d.y, path[1], path[2], path[3], path[4]) ];
            break;
          case 'L':
            path = [ 'C', ...lineToCurve(d.x, d.y, path[1], path[2]) ];
            break;
          case 'H':
            path = [ 'C', ...lineToCurve(d.x, d.y, path[1], d.y) ];
            break;
          case 'V':
            path = [ 'C', ...lineToCurve(d.x, d.y, d.x, path[1]) ];
            break;
          case 'Z':
            path = [ 'C', ...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', ...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;
  }

  /**
   * Checks whether a value is an instance of Connection.
   *
   * @param {any} value
   *
   * @return {boolean}
   */
  function isConnection(value) {
    return isObject(value) && has$1(value, 'waypoints');
  }

  /**
   * Checks whether a value is an instance of Label.
   *
   * @param {any} value
   *
   * @return {boolean}
   */
  function isLabel(value) {
    return isObject(value) && has$1(value, 'labelTarget');
  }

  /**
   * @typedef {import('../core/Types').ElementLike} Element
   * @typedef {import('../core/Types').ConnectionLike} Connection
   *
   * @typedef {import('../util/Types').DirectionTRBL} DirectionTRBL
   * @typedef {import('../util/Types').Intersection} Intersection
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   * @typedef {import('../util/Types').RectTRBL} RectTRBL
   */

  /**
   * @param {Rect} bounds
   *
   * @returns {Rect}
   */
  function roundBounds(bounds) {
    return {
      x: Math.round(bounds.x),
      y: Math.round(bounds.y),
      width: Math.round(bounds.width),
      height: Math.round(bounds.height)
    };
  }

  /**
   * @param {Point} point
   *
   * @returns {Point}
   */
  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 {Point|Rect} bounds
   *
   * @return {RectTRBL}
   */
  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 {RectTRBL} trbl
   *
   * @return {Rect}
   */
  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 {Point|Rect} 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 {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 {Element} element
   *
   * @return {Point}
   */
  function getMid(element) {
    if (isConnection(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 {Rect} rect
   * @param {Rect} reference
   * @param {Point|number} padding
   *
   * @return {DirectionTRBL|Intersection} 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 {string} elementPath
   * @param {string} linePath
   * @param {boolean} cropStart Whether to crop 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 findPathIntersections(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));
  }

  /**
   * @typedef {import('./Types').ConnectionLike} ConnectionLike
   * @typedef {import('./Types').RootLike} RootLike
   * @typedef {import('./Types').ParentLike } ParentLike
   * @typedef {import('./Types').ShapeLike} ShapeLike
   *
   * @typedef { {
   *   container?: HTMLElement;
   *   deferUpdate?: boolean;
   *   width?: number;
   *   height?: number;
   *   autoFocus?: boolean;
   * } } CanvasConfig
   * @typedef { {
   *   group: SVGElement;
   *   index: number;
   *   visible: boolean;
   * } } CanvasLayer
   * @typedef { {
   *   [key: string]: CanvasLayer;
   * } } CanvasLayers
   * @typedef { {
   *   rootElement: ShapeLike;
   *   layer: CanvasLayer;
   * } } CanvasPlane
   * @typedef { {
   *   scale: number;
   *   inner: Rect;
   *   outer: Dimensions;
   * } & Rect } CanvasViewbox
   *
   * @typedef {import('./ElementRegistry').default} ElementRegistry
   * @typedef {import('./EventBus').default} EventBus
   * @typedef {import('./GraphicsFactory').default} GraphicsFactory
   *
   * @typedef {import('../util/Types').Dimensions} Dimensions
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   * @typedef {import('../util/Types').RectTRBL} RectTRBL
   * @typedef {import('../util/Types').ScrollDelta} ScrollDelta
   */

  function round$c(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 {CanvasConfig} options
   *
   * @return {HTMLElement} the container element
   */
  function createContainer(options) {

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

    const 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)
    const parent = document.createElement('div');
    parent.setAttribute('class', 'djs-container djs-parent');

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

    container.appendChild(parent);

    return parent;
  }

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

    const 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;
  }

  const BASE_LAYER = 'base';

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


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

  /**
   * The main drawing canvas.
   *
   * @class
   * @constructor
   *
   * @emits Canvas#canvas.init
   *
   * @param {CanvasConfig|null} 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;

    /**
     * @type {number}
     */
    this._rootsIdx = 0;

    /**
     * @type {CanvasLayers}
     */
    this._layers = {};

    /**
     * @type {CanvasPlane[]}
     */
    this._planes = [];

    /**
     * @type {RootLike|null}
     */
    this._rootElement = null;

    /**
     * @type {boolean}
     */
    this._focused = false;

    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>
   *
   * @param {CanvasConfig} config
   */
  Canvas.prototype._init = function(config) {
    const eventBus = this._eventBus;

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

    const svg = this._svg = create$1('svg');

    attr(svg, {
      width: '100%',
      height: '100%'
    });

    attr$1(svg, 'tabindex', 0);

    config.autoFocus && eventBus.on('element.hover', () => {
      this.restoreFocus();
    });

    eventBus.on('element.mousedown', 500, (event) => {
      this.focus();
    });

    svg.addEventListener('focusin', () => {
      this._setFocused(true);
    });

    svg.addEventListener('focusout', () => {
      this._setFocused(false);
    });

    append(container, svg);

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

    // debounce canvas.viewbox.changed events when deferUpdate is set
    // to help with potential performance issues
    if (config.deferUpdate) {
      this._viewboxChanged = debounce(bind$2(this._viewboxChanged, this), 300);
    }

    eventBus.on('diagram.init', () => {

      /**
       * 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
      });

    });

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

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

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

    const 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._setFocused = function(focused) {

    if (focused == this._focused) {
      return;
    }

    this._focused = focused;

    this._eventBus.fire('canvas.focus.changed', { focused });
  };

  Canvas.prototype._clear = function() {

    const allElements = this._elementRegistry.getAll();

    // remove all elements
    allElements.forEach(element => {
      const type = getType(element);

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

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

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

  /**
   * Sets focus on the canvas SVG element.
   */
  Canvas.prototype.focus = function() {
    this._svg.focus({ preventScroll: true });

    this._setFocused(true);
  };

  /**
  * Sets focus on the canvas SVG element if `document.body` is currently focused.
  */
  Canvas.prototype.restoreFocus = function() {
    if (document.activeElement === document.body) {
      this.focus();
    }
  };

  /**
  * Returns true if the canvas is focused.
  *
  * @return {boolean}
  */
  Canvas.prototype.isFocused = function() {
    return this._focused;
  };

  /**
   * Returns the default layer on which
   * all elements are drawn.
   *
   * @return {SVGElement}  The SVG element of the layer.
   */
  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 The name of the layer.
   * @param {number} [index] The index of the layer.
   *
   * @return {SVGElement} The SVG element of the layer.
   */
  Canvas.prototype.getLayer = function(name, index) {

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

    let 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
   *
   * @return {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 {CanvasLayer}
   */
  Canvas.prototype._createLayer = function(name, index) {

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

    const childIndex = this._getChildIndex(index);

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


  /**
   * Shows a given layer.
   *
   * @param {string} name The name of the layer.
   *
   * @return {SVGElement} The SVG element of the layer.
   */
  Canvas.prototype.showLayer = function(name) {

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

    const layer = this._layers[name];

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

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

    if (layer.visible) {
      return group;
    }

    const childIndex = this._getChildIndex(index);

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

    layer.visible = true;

    return group;
  };

  /**
   * Hides a given layer.
   *
   * @param {string} name The name of the layer.
   *
   * @return {SVGElement} The SVG element of the layer.
   */
  Canvas.prototype.hideLayer = function(name) {

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

    const layer = this._layers[name];

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

    const group = layer.group;

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

    remove$1(group);

    layer.visible = false;

    return group;
  };


  Canvas.prototype._removeLayer = function(name) {

    const layer = this._layers[name];

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

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

  /**
   * Returns the currently active layer. Can be null.
   *
   * @return {CanvasLayer|null} The active layer of `null`.
   */
  Canvas.prototype.getActiveLayer = function() {
    const plane = this._findPlaneForRoot(this.getRootElement());

    if (!plane) {
      return null;
    }

    return plane.layer;
  };


  /**
   * Returns the plane which contains the given element.
   *
   * @param {ShapeLike|ConnectionLike|string} element The element or its ID.
   *
   * @return {RootLike|undefined} The root of the element.
   */
  Canvas.prototype.findRoot = function(element) {
    if (typeof element === 'string') {
      element = this._elementRegistry.get(element);
    }

    if (!element) {
      return;
    }

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

    return plane.rootElement;
  };

  /**
   * Return a list of all root elements on the diagram.
   *
   * @return {(RootLike)[]} The list of root elements.
   */
  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 {HTMLElement} The HTML element of the container.
   */
  Canvas.prototype.getContainer = function() {
    return this._container;
  };


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

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

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

    element.markers = element.markers || new Set();

    // 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) {
          element.markers.add(marker);
          classes(gfx).add(marker);
        } else {
          element.markers.delete(marker);
          classes(gfx).remove(marker);
        }
      }
    });

    /**
     * An event indicating that a marker has been updated for an element
     *
     * @event element.marker.update
     * @type {Object}
     * @property {Element} element the shape
     * @property {SVGElement} 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
   *
   * ```javascript
   * canvas.addMarker('foo', 'some-marker');
   *
   * const fooGfx = canvas.getGraphics('foo');
   *
   * fooGfx; // <g class="... some-marker"> ... </g>
   * ```
   *
   * @param {ShapeLike|ConnectionLike|string} element The element or its ID.
   * @param {string} marker The 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 {ShapeLike|ConnectionLike|string} element The element or its ID.
   * @param {string} marker The marker.
   */
  Canvas.prototype.removeMarker = function(element, marker) {
    this._updateMarker(element, marker, false);
  };

  /**
   * Check whether an element has a given marker.
   *
   * @param {ShapeLike|ConnectionLike|string} element The element or its ID.
   * @param {string} marker The marker.
   */
  Canvas.prototype.hasMarker = function(element, marker) {
    if (!element.id) {
      element = this._elementRegistry.get(element);
    }

    if (!element.markers) {
      return false;
    }

    return element.markers.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 {ShapeLike|ConnectionLike|string} element The element or its ID.
   * @param {string} marker The 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.
   *
   * @return {RootLike} The current root element.
   */
  Canvas.prototype.getRootElement = function() {
    const 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 {RootLike} [rootElement] The root element to be added.
   *
   * @return {RootLike} The added root element or an implicit root element.
   */
  Canvas.prototype.addRootElement = function(rootElement) {
    const idx = this._rootsIdx++;

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

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

    this._ensureValid('root', rootElement);

    const 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 root element and returns it.
   *
   * @param {RootLike|string} rootElement element or element ID
   *
   * @return {RootLike|undefined} removed element
   */
  Canvas.prototype.removeRootElement = function(rootElement) {

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

    const 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;
  };


  /**
   * Sets a given element as the new root element for the canvas
   * and returns the new root element.
   *
   * @param {RootLike} rootElement The root element to be set.
   *
   * @return {RootLike} The set root element.
   */
  Canvas.prototype.setRootElement = function(rootElement) {

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

    let 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) {
    const 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) {
    const 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) {

    const 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 });
  };

  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');
    }

    const requiredAttrs = REQUIRED_MODEL_ATTRS[type];

    const 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 {ConnectionLike|ShapeLike} element
   * @param {ShapeLike} [parent]
   * @param {number} [parentIndex]
   *
   * @return {ConnectionLike|ShapeLike} The added element.
   */
  Canvas.prototype._addElement = function(type, element, parent, parentIndex) {

    parent = parent || this.getRootElement();

    const 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
    const 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 {ShapeLike} shape The shape to be added
   * @param {ParentLike} [parent] The shape's parent.
   * @param {number} [parentIndex] The index at which to add the shape to the parent's children.
   *
   * @return {ShapeLike} The added shape.
   */
  Canvas.prototype.addShape = function(shape, parent, parentIndex) {
    return this._addElement('shape', shape, parent, parentIndex);
  };

  /**
   * Adds a connection to the canvas.
   *
   * @param {ConnectionLike} connection The connection to be added.
   * @param {ParentLike} [parent] The connection's parent.
   * @param {number} [parentIndex] The index at which to add the connection to the parent's children.
   *
   * @return {ConnectionLike} 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) {

    const 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.
   *
   * @fires ShapeRemoveEvent
   * @fires ShapeRemovedEvent
   *
   * @param {ShapeLike|string} shape The shape or its ID.
   *
   * @return {ShapeLike} 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 ShapeRemoveEvent
     * @type {Object}
     * @property {ShapeLike} element The shape.
     * @property {SVGElement} gfx The graphical element.
     */

    /**
     * An event indicating that a shape has been removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event ShapeRemovedEvent
     * @type {Object}
     * @property {ShapeLike} element The shape.
     * @property {SVGElement} gfx The graphical element.
     */
    return this._removeElement(shape, 'shape');
  };


  /**
   * Removes a connection from the canvas.
   *
   * @fires ConnectionRemoveEvent
   * @fires ConnectionRemovedEvent
   *
   * @param {ConnectionLike|string} connection The connection or its ID.
   *
   * @return {ConnectionLike} 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 ConnectionRemoveEvent
     * @type {Object}
     * @property {ConnectionLike} element The connection.
     * @property {SVGElement} gfx The graphical element.
     */

    /**
     * An event indicating that a connection has been removed from the canvas.
     *
     * @memberOf Canvas
     *
     * @event ConnectionRemovedEvent
     * @type {Object}
     * @property {ConnectionLike} element The connection.
     * @property {SVGElement} gfx The graphical element.
     */
    return this._removeElement(connection, 'connection');
  };


  /**
   * Returns the graphical element of an element.
   *
   * @param {ShapeLike|ConnectionLike|string} element The element or its ID.
   * @param {boolean} [secondary=false] Whether to return the secondary graphical element.
   *
   * @return {SVGElement} The graphical element.
   */
  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
   *
   * ```javascript
   * 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
   *
   * const 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:
   *
   * const zoomedAndScrolledViewbox = canvas.viewbox();
   *
   * canvas.viewbox({
   *   x: 0,
   *   y: 0,
   *   width: zoomedAndScrolledViewbox.outer.width,
   *   height: zoomedAndScrolledViewbox.outer.height
   * });
   * ```
   *
   * @param {Rect} [box] The viewbox to be set.
   *
   * @return {CanvasViewbox} The set viewbox.
   */
  Canvas.prototype.viewbox = function(box) {

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

    const viewport = this._viewport,
          outerBox = this.getSize();
    let innerBox,
        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$c(matrix.a, 1000);

      x = round$c(-matrix.e || 0, 1000);
      y = round$c(-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);

        const 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 {ScrollDelta} [delta] The scroll to be set.
   *
   * @return {Point}
   */
  Canvas.prototype.scroll = function(delta) {

    const node = this._viewport;
    let matrix = node.getCTM();

    if (delta) {
      this._changeViewbox(function() {
        delta = assign$1({ 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 {ShapeLike|ConnectionLike|string} element The element to scroll to or its ID.
   * @param {RectTRBL|number} [padding=100] The padding to be applied. Can also specify top, bottom, left and right.
   */
  Canvas.prototype.scrollToElement = function(element, padding) {
    let defaultPadding = 100;

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

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

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

    // element is rootElement, do not change viewport
    if (rootElement === element) {
      return;
    }

    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
    };

    const elementBounds = getBBox(element),
          elementTrbl = asTRBL(elementBounds),
          viewboxBounds = this.viewbox(),
          zoom = this.zoom();
    let 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;

    const viewboxTrbl = asTRBL(viewboxBounds);

    const 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 {

      const 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 {number|'fit-viewport'} [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 {Point} [center] The reference point { x: ..., y: ...} to zoom to.
   *
   * @return {number} The set zoom level.
   */
  Canvas.prototype.zoom = function(newScale, center) {

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

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

    let 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$c(matrix.a, 1000);
  };

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

  Canvas.prototype._fitViewport = function(center) {

    const vbox = this.viewbox(),
          outer = vbox.outer,
          inner = vbox.inner;
    let 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) {

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

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

    let centerPoint,
        originalPoint,
        currentMatrix,
        scaleMatrix,
        newMatrix;

    currentMatrix = viewport.getCTM();

    const currentScale = currentMatrix.a;

    if (center) {
      centerPoint = assign$1(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} The size of the canvas.
   */
  Canvas.prototype.getSize = function() {
    return {
      width: this._container.clientWidth,
      height: this._container.clientHeight
    };
  };


  /**
   * Returns the absolute bounding box of an 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 {ShapeLike|ConnectionLike} element The element.
   *
   * @return {Rect} The element's absolute bounding box.
   */
  Canvas.prototype.getAbsoluteBBox = function(element) {
    const vbox = this.viewbox();
    let bbox;

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

      bbox = gfx.getBBox();
    }

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

    const x = bbox.x * vbox.scale - vbox.x * vbox.scale;
    const y = bbox.y * vbox.scale - vbox.y * vbox.scale;

    const width = bbox.width * vbox.scale;
    const height = bbox.height * vbox.scale;

    return {
      x: x,
      y: y,
      width: width,
      height: height
    };
  };

  /**
   * Fires an event so 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';

  /**
   * @typedef {import('./Types').ElementLike} ElementLike
   *
   * @typedef {import('./EventBus').default} EventBus
   *
   * @typedef { (element: ElementLike, gfx: SVGElement) => boolean|any } ElementRegistryFilterCallback
   * @typedef { (element: ElementLike, gfx: SVGElement) => any } ElementRegistryForEachCallback
   */

  /**
   * A registry that keeps track of all shapes in the diagram.
   *
   * @class
   * @constructor
   *
   * @param {EventBus} eventBus
   */
  function ElementRegistry(eventBus) {

    /**
     * @type { {
     *   [id: string]: {
     *     element: ElementLike;
     *     gfx?: SVGElement;
     *     secondaryGfx?: SVGElement;
     *   }
     * } }
     */
    this._elements = {};

    this._eventBus = eventBus;
  }

  ElementRegistry.$inject = [ 'eventBus' ];

  /**
   * Add an element and its graphical representation(s) to the registry.
   *
   * @param {ElementLike} element The element to be added.
   * @param {SVGElement} gfx The primary graphical representation.
   * @param {SVGElement} [secondaryGfx] The secondary graphical representation.
   */
  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 };
  };

  /**
   * Remove an element from the registry.
   *
   * @param {ElementLike|string} 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 an elements ID.
   *
   * @param {ElementLike|string} element The element or its ID.
   * @param {string} newId The new ID.
   */
  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 graphical representation of an element.
   *
   * @param {ElementLike|string} filter The element or its ID.
   * @param {SVGElement} gfx The new graphical representation.
   * @param {boolean} [secondary=false] Whether to update the secondary graphical representation.
   */
  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;
  };

  /**
   * Get the element with the given ID or graphical representation.
   *
   * @example
   *
   * ```javascript
   * elementRegistry.get('SomeElementId_1');
   *
   * elementRegistry.get(gfx);
   * ```
   *
   * @param {string|SVGElement} filter The elements ID or graphical representation.
   *
   * @return {ElementLike|undefined} The element.
   */
  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 {ElementRegistryFilterCallback} fn The filter function.
   *
   * @return {ElementLike[]} The matching elements.
   */
  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 matches the given filter function.
   *
   * @param {ElementRegistryFilterCallback} fn The filter function.
   *
   * @return {ElementLike|undefined} The matching element.
   */
  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;
      }
    }
  };

  /**
   * Get all elements.
   *
   * @return {ElementLike[]} All elements.
   */
  ElementRegistry.prototype.getAll = function() {
    return this.filter(function(e) { return e; });
  };

  /**
   * Execute a given function for each element.
   *
   * @param {ElementRegistryForEachCallback} fn The function to execute.
   */
  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.
   *
   * @example
   *
   * ```javascript
   * elementRegistry.getGraphics('SomeElementId_1');
   *
   * elementRegistry.getGraphics(rootElement); // <g ...>
   *
   * elementRegistry.getGraphics(rootElement, true); // <svg ...>
   * ```
   *
   * @param {ElementLike|string} filter The element or its ID.
   * @param {boolean} [secondary=false] Whether to return the secondary graphical representation.
   *
   * @return {SVGElement} The graphical representation.
   */
  ElementRegistry.prototype.getGraphics = function(filter, secondary) {
    var id = filter.id || filter;

    var container = this._elements[id];
    return container && (secondary ? container.secondaryGfx : container.gfx);
  };

  /**
   * Validate an ID and throw an error if invalid.
   *
   * @param {string} id
   *
   * @throws {Error} Error indicating that the ID is invalid 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');
    }
  };

  /**
   * Extends a collection with {@link Refs} aware methods
   *
   * @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;
  }

  /**
   * Checks if a given collection is extended
   *
   * @param {Array<Object>} collection
   *
   * @return {boolean}
   */
  function isExtended(collection) {
    return collection.__refs_collection === true;
  }

  function hasOwnProperty$1(e, property) {
    return Object.prototype.hasOwnProperty.call(e, property.name || property);
  }
  function defineCollectionProperty(ref, property, target) {
    var 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(a, b) {
    if (!(this instanceof Refs)) {
      return new Refs(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.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.prototype.ensureRefsCollection = function (target, property) {
    var collection = target[property.name];
    if (!isExtended(collection)) {
      defineCollectionProperty(this, property, target);
    }
    return collection;
  };
  Refs.prototype.ensureBound = function (target, property) {
    if (!hasOwnProperty$1(target, property)) {
      this.bind(target, property);
    }
  };
  Refs.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.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 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' });

  /**
   * @typedef {import('./Types').Element} Element
   * @typedef {import('./Types').Shape} Shape
   * @typedef {import('./Types').Root} Root
   * @typedef {import('./Types').Label} Label
   * @typedef {import('./Types').Connection} Connection
   */

  /**
   * The basic graphical representation
   *
   * @class
   * @constructor
   */
  function ElementImpl() {

    /**
     * The object that backs up the shape
     *
     * @name Element#businessObject
     * @type Object
     */
    Object.defineProperty(this, 'businessObject', {
      writable: true
    });


    /**
     * Single label support, will mapped to multi label array
     *
     * @name Element#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 Element#parent
     * @type Shape
     */
    parentRefs.bind(this, 'parent');

    /**
     * The list of labels
     *
     * @name Element#labels
     * @type Label
     */
    labelRefs.bind(this, 'labels');

    /**
     * The list of outgoing connections
     *
     * @name Element#outgoing
     * @type Array<Connection>
     */
    outgoingRefs.bind(this, 'outgoing');

    /**
     * The list of incoming connections
     *
     * @name Element#incoming
     * @type Array<Connection>
     */
    incomingRefs.bind(this, 'incoming');
  }


  /**
   * A graphical object
   *
   * @class
   * @constructor
   *
   * @extends ElementImpl
   */
  function ShapeImpl() {
    ElementImpl.call(this);

    /**
     * Indicates frame shapes
     *
     * @name ShapeImpl#isFrame
     * @type boolean
     */

    /**
     * The list of children
     *
     * @name ShapeImpl#children
     * @type Element[]
     */
    parentRefs.bind(this, 'children');

    /**
     * @name ShapeImpl#host
     * @type Shape
     */
    attacherRefs.bind(this, 'host');

    /**
     * @name ShapeImpl#attachers
     * @type Shape
     */
    attacherRefs.bind(this, 'attachers');
  }

  e$2(ShapeImpl, ElementImpl);


  /**
   * A root graphical object
   *
   * @class
   * @constructor
   *
   * @extends ElementImpl
   */
  function RootImpl() {
    ElementImpl.call(this);

    /**
     * The list of children
     *
     * @name RootImpl#children
     * @type Element[]
     */
    parentRefs.bind(this, 'children');
  }

  e$2(RootImpl, ShapeImpl);


  /**
   * A label for an element
   *
   * @class
   * @constructor
   *
   * @extends ShapeImpl
   */
  function LabelImpl() {
    ShapeImpl.call(this);

    /**
     * The labeled element
     *
     * @name LabelImpl#labelTarget
     * @type Element
     */
    labelRefs.bind(this, 'labelTarget');
  }

  e$2(LabelImpl, ShapeImpl);


  /**
   * A connection between two elements
   *
   * @class
   * @constructor
   *
   * @extends ElementImpl
   */
  function ConnectionImpl() {
    ElementImpl.call(this);

    /**
     * The element this connection originates from
     *
     * @name ConnectionImpl#source
     * @type Element
     */
    outgoingRefs.bind(this, 'source');

    /**
     * The element this connection points to
     *
     * @name ConnectionImpl#target
     * @type Element
     */
    incomingRefs.bind(this, 'target');
  }

  e$2(ConnectionImpl, ElementImpl);


  var types$6 = {
    connection: ConnectionImpl,
    shape: ShapeImpl,
    label: LabelImpl,
    root: RootImpl
  };

  /**
   * Creates a root element.
   *
   * @overlord
   *
   * @example
   *
   * ```javascript
   * import * as Model from 'diagram-js/lib/model';
   *
   * const root = Model.create('root', {
   *   x: 100,
   *   y: 100,
   *   width: 100,
   *   height: 100
   * });
   * ```
   *
   * @param {'root'} type
   * @param {any} [attrs]
   *
   * @return {Root}
   */

  /**
   * Creates a connection.
   *
   * @overlord
   *
   * @example
   *
   * ```javascript
   * import * as Model from 'diagram-js/lib/model';
   *
   * const connection = Model.create('connection', {
   *   waypoints: [
   *     { x: 100, y: 100 },
   *     { x: 200, y: 100 }
   *   ]
   * });
   * ```
   *
   * @param {'connection'} type
   * @param {any} [attrs]
   *
   * @return {Connection}
   */

  /**
   * Creates a shape.
   *
   * @overlord
   *
   * @example
   *
   * ```javascript
   * import * as Model from 'diagram-js/lib/model';
   *
   * const shape = Model.create('shape', {
   *   x: 100,
   *   y: 100,
   *   width: 100,
   *   height: 100
   * });
   * ```
   *
   * @param {'shape'} type
   * @param {any} [attrs]
   *
   * @return {Shape}
   */

  /**
   * Creates a label.
   *
   * @example
   *
   * ```javascript
   * import * as Model from 'diagram-js/lib/model';
   *
   * const label = Model.create('label', {
   *   x: 100,
   *   y: 100,
   *   width: 100,
   *   height: 100,
   *   labelTarget: shape
   * });
   * ```
   *
   * @param {'label'} type
   * @param {Object} [attrs]
   *
   * @return {Label}
   */
  function create(type, attrs) {
    var Type = types$6[type];
    if (!Type) {
      throw new Error('unknown type: <' + type + '>');
    }
    return assign$1(new Type(), attrs);
  }

  /**
   * Checks whether an object is a model instance.
   *
   * @param {any} obj
   *
   * @return {boolean}
   */
  function isModelElement(obj) {
    return obj instanceof ElementImpl;
  }

  /**
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').Connection} Connection
   * @typedef {import('../model/Types').Label} Label
   * @typedef {import('../model/Types').Root} Root
   * @typedef {import('../model/Types').Shape} Shape
   */

  /**
   * A factory for model elements.
   *
   * @template {Connection} [T=Connection]
   * @template {Label} [U=Label]
   * @template {Root} [V=Root]
   * @template {Shape} [W=Shape]
   */
  function ElementFactory$1() {
    this._uid = 12;
  }

  /**
   * Create a root element.
   *
   * @param {Partial<Root>} [attrs]
   *
   * @return {V} The created root element.
   */
  ElementFactory$1.prototype.createRoot = function(attrs) {
    return this.create('root', attrs);
  };

  /**
   * Create a label.
   *
   * @param {Partial<Label>} [attrs]
   *
   * @return {U} The created label.
   */
  ElementFactory$1.prototype.createLabel = function(attrs) {
    return this.create('label', attrs);
  };

  /**
   * Create a shape.
   *
   * @param {Partial<Shape>} [attrs]
   *
   * @return {W} The created shape.
   */
  ElementFactory$1.prototype.createShape = function(attrs) {
    return this.create('shape', attrs);
  };

  /**
   * Create a connection.
   *
   * @param {Partial<Connection>} [attrs]
   *
   * @return {T} The created connection.
   */
  ElementFactory$1.prototype.createConnection = function(attrs) {
    return this.create('connection', attrs);
  };

  /**
   * Create a root element.
   *
   * @overlord
   * @param {'root'} type
   * @param {Partial<Root>} [attrs]
   * @return {V}
   */
  /**
   * Create a shape.
   *
   * @overlord
   * @param {'shape'} type
   * @param {Partial<Shape>} [attrs]
   * @return {W}
   */
  /**
   * Create a connection.
   *
   * @overlord
   * @param {'connection'} type
   * @param {Partial<Connection>} [attrs]
   * @return {T}
   */
  /**
   * Create a label.
   *
   * @param {'label'} type
   * @param {Partial<Label>} [attrs]
   * @return {U}
   */
  ElementFactory$1.prototype.create = function(type, attrs) {

    attrs = assign$1({}, attrs || {});

    if (!attrs.id) {
      attrs.id = type + '_' + (this._uid++);
    }

    return create(type, attrs);
  };

  var FN_REF = '__fn';

  var DEFAULT_PRIORITY$6 = 1000;

  var slice = Array.prototype.slice;

  /**
   * @typedef { {
   *   stopPropagation(): void;
   *   preventDefault(): void;
   *   cancelBubble: boolean;
   *   defaultPrevented: boolean;
   *   returnValue: any;
   * } } Event
   */

  /**
   * @template E
   *
   * @typedef { (event: E & Event, ...any) => any } EventBusEventCallback
   */

  /**
   * @typedef { {
   *  priority: number;
   *  next: EventBusListener | null;
   *  callback: EventBusEventCallback<any>;
   * } } EventBusListener
   */

  /**
   * 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
   * ```
   *
   * @template [EventMap=null]
   */
  function EventBus() {

    /**
     * @type { Record<string, EventBusListener> }
     */
    this._listeners = {};

    // cleanup on destroy on lowest priority to allow
    // message passing until the bitter end
    this.on('diagram.destroy', 1, this._destroy, this);
  }

  /**
   * @overlord
   *
   * 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.
   *
   * @template T
   *
   * @param {string|string[]} events to subscribe to
   * @param {number} [priority=1000] listen priority
   * @param {EventBusEventCallback<T>} callback
   * @param {any} [that] callback context
   */
  /**
   * 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.
   *
   * @template {keyof EventMap} EventName
   *
   * @param {EventName} events to subscribe to
   * @param {number} [priority=1000] listen priority
   * @param {EventBusEventCallback<EventMap[EventName]>} callback
   * @param {any} [that] callback context
   */
  EventBus.prototype.on = function(events, priority, callback, that) {

    events = isArray$3(events) ? events : [ events ];

    if (isFunction(priority)) {
      that = callback;
      callback = priority;
      priority = DEFAULT_PRIORITY$6;
    }

    if (!isNumber(priority)) {
      throw new Error('priority must be a number');
    }

    var actualCallback = callback;

    if (that) {
      actualCallback = bind$2(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
      });
    });
  };

  /**
   * @overlord
   *
   * Register an event listener that is called only once.
   *
   * @template T
   *
   * @param {string|string[]} events to subscribe to
   * @param {number} [priority=1000] the listen priority
   * @param {EventBusEventCallback<T>} callback
   * @param {any} [that] callback context
   */
  /**
   * Register an event listener that is called only once.
   *
   * @template {keyof EventMap} EventName
   *
   * @param {EventName} events to subscribe to
   * @param {number} [priority=1000] listen priority
   * @param {EventBusEventCallback<EventMap[EventName]>} callback
   * @param {any} [that] callback context
   */
  EventBus.prototype.once = function(events, priority, callback, that) {
    var self = this;

    if (isFunction(priority)) {
      that = callback;
      callback = priority;
      priority = DEFAULT_PRIORITY$6;
    }

    if (!isNumber(priority)) {
      throw new Error('priority must be a number');
    }

    function wrappedCallback() {
      wrappedCallback.__isTomb = true;

      var result = callback.apply(that, arguments);

      self.off(events, 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(events, 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|string[]} events
   * @param {EventBusEventCallback<unknown>} [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 event recognized be the event bus.
   *
   * @param {Object} data Event data.
   *
   * @return {Event} An event that will be recognized by the event bus.
   */
  EventBus.prototype.createEvent = function(data) {
    var event = new InternalEvent();

    event.init(data);

    return event;
  };


  /**
   * Fires an event.
   *
   * @example
   *
   * ```javascript
   * // 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} [type] event type
   * @param {Object} [data] event or event data
   * @param {...any} [args] additional arguments the callback will be called with.
   *
   * @return {any} The return value. Will be set to `false` if the default was prevented.
   */
  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;
  };

  /**
   * Handle an error by firing an event.
   *
   * @param {Error} error The error to be handled.
   *
   * @return {boolean} Whether the error was handled.
   */
  EventBus.prototype.handleError = function(error) {
    return this.fire('error', { error: error }) === false;
  };


  EventBus.prototype._destroy = function() {
    this._listeners = {};
  };

  /**
   * @param {Event} event
   * @param {any[]} args
   * @param {EventBusListener} listener
   *
   * @return {any}
   */
  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;
  };

  /**
   * @param {Event} event
   * @param {any[]} args
   * @param {EventBusListener} listener
   *
   * @return {any}
   */
  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 {EventBusListener} newListener
   */
  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;
  };


  /**
   * @param {string} name
   *
   * @return {EventBusListener}
   */
  EventBus.prototype._getListeners = function(name) {
    return this._listeners[name];
  };

  /**
   * @param {string} name
   * @param {EventBusListener} listener
   */
  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$1(this, data || {});
  };


  /**
   * Invoke function. Be fast...
   *
   * @param {Function} fn
   * @param {any[]} 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 {SVGElement} gfx
   *
   * @return {SVGElement}
   */
  function getVisual(gfx) {
    return gfx.childNodes[0];
  }

  /**
   * Returns the children for a given diagram element.
   *
   * @param {SVGElement} gfx
   * @return {SVGElement}
   */
  function getChildren$1(gfx) {
    return gfx.parentNode.childNodes[1];
  }

  /**
   * @param {SVGElement} gfx
   * @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(1, 1);

    transform$1(gfx, [ translate, rotate, scale ]);
  }


  /**
   * @param {SVGElement} gfx
   * @param {number} x
   * @param {number} y
   */
  function translate$1(gfx, x, y) {
    var translate = createTransform();
    translate.setTranslate(x, y);

    transform$1(gfx, translate);
  }


  /**
   * @param {SVGElement} gfx
   * @param {number} angle
   */
  function rotate(gfx, angle) {
    var rotate = createTransform();
    rotate.setRotate(angle, 0, 0);

    transform$1(gfx, rotate);
  }

  /**
   * @typedef {import('./Types').ConnectionLike} ConnectionLike
   * @typedef {import('./Types').ElementLike} ElementLike
   * @typedef {import('./Types').ShapeLike} ShapeLike
   *
   * @typedef {import('./ElementRegistry').default} ElementRegistry
   * @typedef {import('./EventBus').default} EventBus
   */

  /**
   * 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' ];

  /**
   * @param { { parent?: any } } element
   * @return {SVGElement}
   */
  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;
  };

  /**
   * Create a graphical element.
   *
   * @param { 'shape' | 'connection' | 'label' | 'root' } type The type of the element.
   * @param {ElementLike} element The element.
   * @param {number} [parentIndex] The index at which to add the graphical element to its parent's children.
   *
   * @return {SVGElement} The graphical element.
   */
  GraphicsFactory.prototype.create = function(type, element, parentIndex) {
    var childrenGfx = this._getChildrenContainer(element.parent);
    return this._createContainer(type, childrenGfx, parentIndex, isFrameElement$1(element));
  };

  /**
   * Update the containments of the given elements.
   *
   * @param {ElementLike[]} elements The elements.
   */
  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);
      });
    });
  };

  /**
   * Draw a shape.
   *
   * @param {SVGElement} visual The graphical element.
   * @param {ShapeLike} element The shape.
   * @param {Object} attrs Optional attributes.
   *
   * @return {SVGElement}
   */
  GraphicsFactory.prototype.drawShape = function(visual, element, attrs = {}) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.shape', { gfx: visual, element, attrs });
  };

  /**
   * Get the path of a shape.
   *
   * @param {ShapeLike} element The shape.
   *
   * @return {string} The path of the shape.
   */
  GraphicsFactory.prototype.getShapePath = function(element) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.getShapePath', element);
  };

  /**
   * Draw a connection.
   *
   * @param {SVGElement} visual The graphical element.
   * @param {ConnectionLike} element The connection.
   * @param {Object} attrs Optional attributes.
   *
   * @return {SVGElement}
   */
  GraphicsFactory.prototype.drawConnection = function(visual, element, attrs = {}) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.connection', { gfx: visual, element, attrs });
  };

  /**
   * Get the path of a connection.
   *
   * @param {ConnectionLike} connection The connection.
   *
   * @return {string} The path of the connection.
   */
  GraphicsFactory.prototype.getConnectionPath = function(connection) {
    var eventBus = this._eventBus;

    return eventBus.fire('render.getConnectionPath', connection);
  };

  /**
   * Update an elements graphical representation.
   *
   * @param {'shape'|'connection'} type
   * @param {ElementLike} element
   * @param {SVGElement} gfx
   */
  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$1(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');
    }
  };

  /**
   * Remove a graphical element.
   *
   * @param {ElementLike} element The element.
   */
  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);
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  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').InjectionContext} InjectionContext
   * @typedef {import('didi').LocalsMap} LocalsMap
   * @typedef {import('didi').ModuleDeclaration} ModuleDeclaration
   *
   * @typedef { {
   *   modules?: ModuleDeclaration[];
   * } & Record<string, any> } DiagramOptions
   */

  /**
   * @template T
   * @typedef {import('didi').FactoryFunction<T>} FactoryFunction
   */

  /**
   * @template T
   * @typedef {import('didi').ArrayFunc<T>} ArrayFunc
   */

  /**
   * Bootstrap an injector from a list of modules, instantiating a number of default components
   *
   * @param {ModuleDeclaration[]} 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.
   *
   * @template ServiceMap
   * @param {DiagramOptions} [options]
   *
   * @return {Injector<ServiceMap>}
   */
  function createInjector(options) {

    options = options || {};

    /**
     * @type { ModuleDeclaration }
     */
    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
   * @constructor
   * @template [ServiceMap=null]
   *
   * @example Creating a plug-in that logs whenever a shape is added to the canvas.
   *
   * ```javascript
   * // plug-in implementation
   * 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 ]
   * };
   * ```
   *
   * Use the plug-in in a Diagram instance:
   *
   * ```javascript
   * 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 {DiagramOptions} [options]
   * @param {Injector<ServiceMap>} [injector] An (optional) injector to bootstrap the diagram with.
   */
  function Diagram(options, injector) {

    /**
     * @type {Injector<ServiceMap>}
     */
    this._injector = injector || createInjector(options);

    // init

    /**
     * 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
     *
     * ```javascript
     * eventBus.on('diagram.init', function() {
     *   eventBus.fire('my-custom-event', { foo: 'BAR' });
     * });
     * ```
     *
     * @type {Object}
     */
    this.get('eventBus').fire('diagram.init');
  }

  /**
   * @overlord
   *
   * Resolves a diagram service.
   *
   * @template T
   *
   * @param {string} name The name of the service to get.
   *
   * @return {T}
   */
  /**
   * @overlord
   *
   * Resolves a diagram service.
   *
   * @template T
   *
   * @param {string} name The name of the service to get.
   * @param {true} strict If false, resolve missing services to null.
   *
   * @return {T}
   */
  /**
   * @overlord
   *
   * Resolves a diagram service.
   *
   * @template T
   *
   * @param {string} name The name of the service to get.
   * @param {boolean} strict If false, resolve missing services to null.
   *
   * @return {T|null}
   */
  /**
   * Resolves a diagram service.
   *
   * @template {keyof ServiceMap} Name
   *
   * @param {Name} name The name of the service to get.
   *
   * @return {ServiceMap[Name]}
   */
  Diagram.prototype.get = function(name, strict) {
    return this._injector.get(name, strict);
  };

  /**
   * @overlord
   *
   * Invoke the given function, injecting dependencies. Return the result.
   *
   * @template T
   *
   * @param {FactoryFunction<T>} func
   * @param {InjectionContext} [context]
   * @param {LocalsMap} [locals]
   *
   * @return {T}
   */
  /**
   * Invoke the given function, injecting dependencies provided in
   * array notation. Return the result.
   *
   * @template T
   *
   * @param {ArrayFunc<T>} func function to be invoked
   * @param {InjectionContext} [context] context of the invocation
   * @param {LocalsMap} [locals] locals provided
   *
   * @return {T}
   */
  Diagram.prototype.invoke = function(func, context, locals) {
    return this._injector.invoke(func, context, locals);
  };

  /**
   * Destroys the diagram
   */
  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$2(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;
    }

    // prefix + local name
    else 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.assertNotTrait = function(typeDescriptor) {

    const _extends = typeDescriptor.extends || [];

    if (_extends.length) {
      throw new Error(
        `cannot create <${ typeDescriptor.name }> extending <${ typeDescriptor.extends }>`
      );
    }
  };

  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) {

    if (inherited) {
      this.assertNotTrait(t);
    }

    var typesByName = this.allTypesByName,
        types = this.allTypes;

    var typeName = t.name;

    if (typeName in typesByName) {
      return;
    }

    forEach$1(t.properties, bind$2(function(p) {

      // clone property to allow extensions
      p = assign$1({}, 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$2(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$1({}, pkg);

    var pkgMap = this.packageMap;

    ensureAvailable(pkgMap, pkg, 'prefix');
    ensureAvailable(pkgMap, pkg, 'uri');

    // register types
    forEach$1(pkg.types, bind$2(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$1({}, type, {
      superClass: (type.superClass || []).slice(),
      extends: (type.extends || []).slice(),
      properties: (type.properties || []).slice(),
      meta: assign$1((type.meta || {}))
    });

    var ns = parseName(type.name, pkg.prefix),
        name = ns.name,
        propertiesByName = {};

    // parse properties
    forEach$1(type.properties, bind$2(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$1(p, {
        ns: propertyNs,
        name: propertyName
      });

      propertiesByName[propertyName] = p;
    }, this));

    // update ns + name
    assign$1(type, {
      ns: ns,
      name: name,
      propertiesByName: propertiesByName
    });

    forEach$1(type.extends, bind$2(function(extendsName) {
      var extendsNameNs = parseName(extendsName, ns.prefix);

      var extended = this.typeMap[extendsNameNs.name];

      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 super type or trait
     *
     * @param {String} cls
     * @param {Boolean} [trait=false]
     */
    function traverse(cls, trait) {
      var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
      self.mapTypes(parentNs, iterator, trait);
    }

    /**
     * Traverse the selected trait.
     *
     * @param {String} cls
     */
    function traverseTrait(cls) {
      return traverse(cls, true);
    }

    /**
     * Traverse the selected super type
     *
     * @param {String} cls
     */
    function traverseSuper(cls) {
      return traverse(cls, false);
    }

    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) {

    if (!isString(name) || !name.length) {
      throw new TypeError('property name must be a non-empty string');
    }

    var property = this.getProperty(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[stripGlobal(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[stripGlobal(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.getProperty(target, name);

    if (!property) {
      return target.$attrs[stripGlobal(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$1({}, 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 });
  };

  /**
   * Return property with the given name on the element.
   *
   * @param {any} target
   * @param {string} name
   *
   * @return {object | null} property
   */
  Properties.prototype.getProperty = function(target, name) {

    var model = this.model;

    var property = model.getPropertyDescriptor(target, name);

    if (property) {
      return property;
    }

    if (name.includes(':')) {
      return null;
    }

    const strict = model.config.strict;

    if (typeof strict !== 'undefined') {
      const error = new TypeError(`unknown property <${ name }> on <${ target.$type }>`);

      if (strict) {
        throw error;
      } else {

        // eslint-disable-next-line no-undef
        typeof console !== 'undefined' && console.warn(error);
      }
    }

    return null;
  };

  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
    });
  }

  function stripGlobal(name) {
    return name.replace(/^:/, '');
  }

  // 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
   *
   * @param { { strict?: boolean } } [config] moddle configuration
   */
  function Moddle(packages, config = {}) {

    this.properties = new Properties(this);

    this.factory = new Factory(this, this.properties);
    this.registry = new Registry(packages, this.properties);

    this.typeCache = {};

    this.config = config;
  }


  /**
   * 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;
      },
      get: function(key) {
        return this[key];
      },
      set: function(key, value) {
        set$1(this, [ key ], value);
      }
    };

    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, 'get', { enumerable: false, writable: true });
    this.properties.define(element, 'set', { enumerable: false, writable: true });
    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} s
   *
   * @return {string} decoded string
   */
  function decodeEntities(s) {
    if (s.length > 3 && s.indexOf('&') !== -1) {
      return s.replace(ENTITY_PATTERN, replaceEntities);
    }

    return s;
  }

  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];
      }

      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

          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
            }

            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 SERIALIZE_PROPERTY = 'property';

  function getSerialization(element) {
    return element.xml && element.xml.serialize;
  }

  function getSerializationType(element) {
    const type = getSerialization(element);

    return type !== SERIALIZE_PROPERTY && (type || null);
  }

  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);
  }

  /**
   * Un-prefix a potentially prefixed type name.
   *
   * @param {NsName} nameNs
   * @param {Object} [pkg]
   *
   * @return {string}
   */
  function prefixedToName(nameNs, pkg) {

    var name = nameNs.name,
        localName = nameNs.localName;

    var typePrefix = pkg && pkg.xml && pkg.xml.typePrefix;

    if (typePrefix && localName.indexOf(typePrefix) === 0) {
      return nameNs.prefix + ':' + localName.slice(typePrefix.length);
    } else {
      return name;
    }
  }

  function normalizeTypeName(name, nsMap, model) {

    // normalize against actual NS
    const nameNs = parseName(name, nsMap.xmlns);

    const normalizedName = `${ nsMap[nameNs.prefix] || nameNs.prefix }:${ nameNs.localName }`;

    const normalizedNameNs = parseName(normalizedName);

    // determine actual type name, based on package-defined prefix
    var pkg = model.getPackage(normalizedNameNs.prefix);

    return prefixedToName(normalizedNameNs, 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$1(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') {
          name = ':' + name;
        } else {
          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];

    // search for properties by name first

    if (property && !property.isAttr) {

      const serializationType = getSerializationType(property);

      if (serializationType) {
        const elementTypeName = node.attributes[serializationType];

        // type is optional, if it does not exists the
        // default type is assumed
        if (elementTypeName) {

          // convert the prefix used to the mapped form, but also
          // take possible type prefixes from XML
          // into account, i.e.: xsi:type="t{ActualType}",
          const normalizedTypeName = normalizeTypeName(elementTypeName, node.ns, model);

          const elementType = model.getType(normalizedTypeName);

          return assign$1({}, property, {
            effectiveType: getModdleDescriptor(elementType).name
          });
        }
      }

      // search for properties by name first
      return property;
    }

    var pkg = model.getPackage(nameNs.prefix);

    if (pkg) {
      const elementTypeName = aliasToName(nameNs, pkg);
      const 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$1({}, 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$1(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$1(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$1({}, 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;
    }, Object.entries(DEFAULT_NS_MAP).reduce(function(map, [ prefix, url ]) {
      map[url] = prefix;

      return map;
    }, model.config && model.config.nsMap || {}));

    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) {

    this.prefixMap = {};
    this.uriMap = {};
    this.used = {};

    this.wellknown = [];
    this.custom = [];
    this.parent = parent;

    this.defaultPrefixMap = parent && parent.defaultPrefixMap || {};
  }

  Namespaces.prototype.mapDefaultPrefixes = function(defaultPrefixMap) {
    this.defaultPrefixMap = defaultPrefixMap;
  };

  Namespaces.prototype.defaultUriByPrefix = function(prefix) {
    return this.defaultPrefixMap[prefix];
  };

  Namespaces.prototype.byUri = function(uri) {
    return this.uriMap[uri] || (
      this.parent && this.parent.byUri(uri)
    );
  };

  Namespaces.prototype.add = function(ns, isWellknown) {

    this.uriMap[ns.uri] = ns;

    if (isWellknown) {
      this.wellknown.push(ns);
    } else {
      this.custom.push(ns);
    }

    this.mapPrefix(ns.prefix, ns.uri);
  };

  Namespaces.prototype.uriByPrefix = function(prefix) {
    return this.prefixMap[prefix || 'xmlns'] || (
      this.parent && this.parent.uriByPrefix(prefix)
    );
  };

  Namespaces.prototype.mapPrefix = function(prefix, uri) {
    this.prefixMap[prefix || 'xmlns'] = uri;
  };

  Namespaces.prototype.getNSKey = function(ns) {
    return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri;
  };

  Namespaces.prototype.logUsed = function(ns) {

    var uri = ns.uri;
    var nsKey = this.getNSKey(ns);

    this.used[nsKey] = this.byUri(uri);

    // Inform parent recursively about the usage of this NS
    if (this.parent) {
      this.parent.logUsed(ns);
    }
  };

  Namespaces.prototype.getUsed = function(ns) {

    var allNs = [].concat(this.wellknown, this.custom);

    return allNs.filter(ns => {
      var nsKey = this.getNSKey(ns);

      return this.used[nsKey];
    });
  };


  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$1({ localName: descriptor.ns.localName }, ns);
    } else {
      return assign$1({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns);
    }
  }

  function getPropertyNs(ns, descriptor) {
    return assign$1({ 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.parseGenericNsAttributes(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) {
      this.parseGenericContainments(element);
    } else {
      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$1({ localName: ns.localName }, effectiveNs);
    }
  };

  ElementSerializer.prototype.parseGenericNsAttributes = function(element) {

    return Object.entries(element).filter(
      ([ key, value ]) => !key.startsWith('$') && this.parseNsAttribute(element, key, value)
    ).map(
      ([ key, value ]) => ({ name: key, value: value })
    );
  };

  ElementSerializer.prototype.parseGenericContainments = function(element) {
    var body = element.$body;

    if (body) {
      this.body.push(new BodySerializer().build({ type: 'String' }, body));
    }

    var children = element.$children;

    if (children) {
      forEach$1(children, child => {
        this.body.push(new ElementSerializer(this).build(child));
      });
    }
  };

  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) {
    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) {

      try {
        self.addAttribute(self.nsAttributeName(attr.name), attr.value);
      } catch (e) {

        // eslint-disable-next-line no-undef
        typeof console !== 'undefined' && 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 serialization = getSerialization(p);

        forEach$1(value, function(v) {
          var serializer;

          if (serialization) {
            if (serialization === SERIALIZE_PROPERTY) {
              serializer = new ElementSerializer(self, p);
            } else {
              serializer = new TypeSerializer(self, p, serialization);
            }
          } 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 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 = namespaces.defaultUriByPrefix(prefix);

    uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);

    if (!uri) {
      throw new Error('no namespace uri given for prefix <' + prefix + '>');
    }

    ns = namespaces.byUri(uri);

    // register new default prefix <xmlns> in local scope
    if (!ns && !prefix) {
      ns = this.logNamespace({ uri }, wellknownUri === uri, true);
    }

    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, serialization) {
    ElementSerializer.call(this, parent, propertyDescriptor);

    this.serialization = serialization;
  }

  inherits(TypeSerializer, ElementSerializer);

  TypeSerializer.prototype.parseNsAttributes = function(element) {

    // extracted attributes with serialization attribute
    // <type=typeName> stripped; it may be later
    var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element).filter(
      attr => attr.name !== this.serialization
    );

    var descriptor = element.$descriptor;

    // only serialize <type=typeName> 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(this.serialization),
      (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$1({ 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);
      }

      var serializer = new ElementSerializer();

      var model = tree.$model;

      serializer.getNamespaces().mapDefaultPrefixes(getDefaultPrefixMappings(model));

      serializer.build(tree).serializeTo(formatingWriter);

      if (!writer) {
        return internalWriter.value;
      }
    }

    return {
      toXML: toXML
    };
  }


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

  /**
   * @param {Moddle} model
   *
   * @return { Record<string, string> } map from prefix to URI
   */
  function getDefaultPrefixMappings(model) {

    const nsMap = model.config && model.config.nsMap || {};

    const prefixMap = {};

    // { prefix -> uri }
    for (const prefix in DEFAULT_NS_MAP) {
      prefixMap[prefix] = DEFAULT_NS_MAP[prefix];
    }

    // { uri -> prefix }
    for (const uri in nsMap) {
      const prefix = nsMap[uri];

      prefixMap[prefix] = uri;
    }

    for (const pkg of model.getPackages()) {
      prefixMap[pkg.prefix] = pkg.uri;
    }

    return prefixMap;
  }

  /**
   * 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$1({ 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",
  				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
  };

  const packages = {
    bpmn: BpmnPackage,
    bpmndi: BpmnDiPackage,
    dc: DcPackage,
    di: DiPackage,
    bioc: BiocPackage,
    color: BpmnInColorPackage
  };

  function SimpleBpmnModdle(additionalPackages, options) {
    const pks = assign$1({}, packages, additionalPackages);

    return new BpmnModdle(pks, options);
  }

  function elementToString(e) {
    if (!e) {
      return '<null>';
    }

    return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
  }

  /**
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */


  // 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';

  /**
   * @private
   *
   * @param {ModdleElement} businessObject
   */
  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);
        }
      });
    }
  }

  /**
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */

  /**
   * Returns true if an element is of the given meta-model type.
   *
   * @param {ModdleElement} element
   * @param {string} type
   *
   * @return {boolean}
   */
  function is$1(element, type) {
    return element.$instanceOf(type);
  }


  /**
   * Find a suitable display candidate for definitions where the DI does not
   * correctly specify one.
   *
   * @param {ModdleElement} definitions
   *
   * @return {ModdleElement}
   */
  function findDisplayCandidate(definitions) {
    return find(definitions.rootElements, function(e) {
      return is$1(e, 'bpmn:Process') || is$1(e, 'bpmn:Collaboration');
    });
  }

  /**
   * @param {Record<'element' | 'root' | 'error', Function>} handler
   */
  function BpmnTreeWalker(handler) {

    // 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(
          `already rendered ${ 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 (error) {
        logError(error.message, { element, error });

        console.error(`failed to import ${ elementToString(element) }`, error);
      }
    }

    function logError(message, context) {
      handler.error(message, context);
    }

    // DI handling //////////////////////

    var registerDi = this.registerDi = function registerDi(di) {
      var bpmnElement = di.bpmnElement;

      if (bpmnElement) {
        if (diMap[bpmnElement.id]) {
          logError(
            `multiple DI elements defined for ${ elementToString(bpmnElement) }`,
            { element: bpmnElement }
          );
        } else {
          diMap[bpmnElement.id] = di;

          ensureCompatDiRef(bpmnElement);
        }
      } else {
        logError(
          `no bpmnElement referenced in ${ 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
     */
    this.handleDefinitions = function handleDefinitions(definitions, diagram) {

      // make sure we walk the correct bpmnElement

      var diagrams = definitions.diagrams;

      if (diagram && diagrams.indexOf(diagram) === -1) {
        throw new Error('diagram not part of <bpmn:Definitions />');
      }

      if (!diagram && diagrams && diagrams.length) {
        diagram = diagrams[0];
      }

      // no diagram -> nothing to import
      if (!diagram) {
        throw new Error('no diagram to display');
      }

      // load DI from selected diagram only
      diMap = {};
      handleDiagram(diagram);


      var plane = diagram.plane;

      if (!plane) {
        throw new Error(
          `no plane for ${ 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('no process or collaboration to display');
        } else {

          logError(
            `correcting missing bpmnElement on ${ elementToString(plane) } to ${ elementToString(rootElement) }`
          );

          // correct DI on the fly
          plane.bpmnElement = rootElement;
          registerDi(plane);
        }
      }


      var ctx = visitRoot(rootElement, plane);

      if (is$1(rootElement, 'bpmn:Process') || is$1(rootElement, 'bpmn:SubProcess')) {
        handleProcess(rootElement, ctx);
      } else if (is$1(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(
          `unsupported bpmnElement for ${ elementToString(plane) }: ${ elementToString(rootElement) }`
        );
      }

      // handle all deferred elements
      handleDeferred(deferred);
    };

    var handleDeferred = this.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$1(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$1(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));
    }

    var handleSubProcess = this.handleSubProcess = function handleSubProcess(subProcess, context) {
      handleFlowElementsContainer(subProcess, context);
      handleArtifacts(subProcess.artifacts, context);
    };

    function handleFlowNode(flowNode, context) {
      var childCtx = visitIfDi(flowNode, context);

      if (is$1(flowNode, 'bpmn:SubProcess')) {
        handleSubProcess(flowNode, childCtx || context);
      }

      if (is$1(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(flowElement) {
        if (is$1(flowElement, 'bpmn:SequenceFlow')) {
          deferred.push(function() {
            handleSequenceFlow(flowElement, context);
          });
        } else if (is$1(flowElement, 'bpmn:BoundaryEvent')) {
          deferred.unshift(function() {
            handleFlowNode(flowElement, context);
          });
        } else if (is$1(flowElement, 'bpmn:FlowNode')) {
          handleFlowNode(flowElement, context);
        } else if (is$1(flowElement, 'bpmn:DataObject')) ; else if (is$1(flowElement, 'bpmn:DataStoreReference')) {
          handleDataElement(flowElement, context);
        } else if (is$1(flowElement, 'bpmn:DataObjectReference')) {
          handleDataElement(flowElement, context);
        } else {
          logError(
            `unrecognized flowElement ${ elementToString(flowElement) } in context ${ elementToString(context && context.businessObject) }`,
            {
              element: flowElement,
              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));

      deferred.push(function() {
        handleMessageFlows(collaboration.messageFlows, context);
      });

      handleArtifacts(collaboration.artifacts, 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);
        }
      });
    }
  }

  /**
   * @typedef { import('../model/Types').Element } Element
   * @typedef { import('../model/Types').ModdleElement } ModdleElement
   */

  /**
   * Is an element of the given BPMN type?
   *
   * @param  {Element|ModdleElement} element
   * @param  {string} type
   *
   * @return {boolean}
   */
  function is(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 {Element|ModdleElement} element
   * @param {string[]} types
   *
   * @return {boolean}
   */
  function isAny(element, types) {
    return some(types, function(t) {
      return is(element, t);
    });
  }

  /**
   * Return the business object for a given element.
   *
   * @param {Element|ModdleElement} element
   *
   * @return {ModdleElement}
   */
  function getBusinessObject(element) {
    return (element && element.businessObject) || element;
  }

  /**
   * Return the di object for a given element.
   *
   * @param {Element} element
   *
   * @return {ModdleElement}
   */
  function getDi(element) {
    return element && element.di;
  }

  /**
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   *
   * @typedef { {
   *   warnings: string[];
   * } } ImportBPMNDiagramResult
   *
   * @typedef {ImportBPMNDiagramResult & Error} ImportBPMNDiagramError
   */

  /**
   * Import the definitions into a diagram.
   *
   * Errors and warnings are reported through the specified callback.
   *
   * @param {ModdleElement} diagram
   * @param {ModdleElement} definitions
   * @param {ModdleElement} [bpmnDiagram] The diagram to be rendered (if not
   * provided, the first one will be rendered).
   *
   * @return {Promise<ImportBPMNDiagramResult>}
   */
  function importBpmnDiagram(diagram, definitions, bpmnDiagram) {

    var importer,
        eventBus,
        canvas;

    var error,
        warnings = [];

    /**
     * Walk the diagram semantically, importing (=drawing)
     * all elements you encounter.
     *
     * @param {ModdleElement} definitions
     * @param {ModdleElement} 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);


      bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]);

      var diagramsToImport = getDiagramsToImport(definitions, bpmnDiagram);

      if (!diagramsToImport) {
        throw new Error('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');
        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 {ModdleElement} definitions
   * @param {ModdleElement} bpmnDiagram
   *
   * @return {ModdleElement[]}
   */
  function getDiagramsToImport(definitions, bpmnDiagram) {
    if (!bpmnDiagram || !bpmnDiagram.plane) {
      return;
    }

    var bpmnElement = bpmnDiagram.plane.bpmnElement,
        rootElement = bpmnElement;

    if (!is(bpmnElement, 'bpmn:Process') && !is(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(rootElement, 'bpmn:Collaboration')) {
      collaboration = rootElement;
    } else {
      collaboration = find(definitions.rootElements, function(element) {
        if (!is(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$1(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) {

      if (!diagram.plane) {
        return;
      }

      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(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 forms ' +
          'powered by <a href="https://bpmn.io" target="_blank" rel="noopener">bpmn.io</a>.' +
        '</span>' +
      '</div>' +
    '</div>';


  var lightbox;

  function createLightbox() {
    lightbox = domify$1(LIGHTBOX_MARKUP);

    assign(lightbox, LIGHTBOX_STYLES);
    assign(query('svg', lightbox), LOGO_STYLES);
    assign(query('.backdrop', lightbox), BACKDROP_STYLES);
    assign(query('.notice', lightbox), NOTICE_STYLES);
    assign(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.
   */

  /**
   * @template T
   *
   * @typedef { import('diagram-js/lib/core/EventBus').default<T> } EventBus
   */

  /**
   * @template T
   *
   * @typedef {import('diagram-js/lib/core/EventBus').EventBusEventCallback<T>} EventBusEventCallback
   */

  /**
   * @typedef {import('didi').ModuleDeclaration} ModuleDeclaration
   *
   * @typedef {import('./model/Types').Moddle} Moddle
   * @typedef {import('./model/Types').ModdleElement} ModdleElement
   * @typedef {import('./model/Types').ModdleExtension} ModdleExtension
   *
   * @typedef { {
   *   width?: number|string;
   *   height?: number|string;
   *   position?: string;
   *   container?: string|HTMLElement;
   *   moddleExtensions?: ModdleExtensions;
   *   additionalModules?: ModuleDeclaration[];
   * } & Record<string, any> } BaseViewerOptions
   *
   * @typedef {Record<string, ModdleElement>} ModdleElementsById
   *
   * @typedef { {
   *   [key: string]: ModdleExtension;
   * } } ModdleExtensions
   *
   * @typedef { {
   *   warnings: string[];
   * } } ImportXMLResult
   *
   * @typedef {ImportXMLResult & Error} ImportXMLError
   *
   * @typedef {ImportXMLResult} ImportDefinitionsResult
   *
   * @typedef {ImportXMLError} ImportDefinitionsError
   *
   * @typedef {ImportXMLResult} OpenResult
   *
   * @typedef {ImportXMLError} OpenError
   *
   * @typedef { {
   *   format?: boolean;
   *   preamble?: boolean;
   * } } SaveXMLOptions
   *
   * @typedef { {
   *   xml?: string;
   *   error?: Error;
   * } } SaveXMLResult
   *
   * @typedef { {
   *   svg: string;
   * } } SaveSVGResult
   *
   * @typedef { {
   *   xml: string;
   * } } ImportParseStartEvent
   *
   * @typedef { {
   *   error?: ImportXMLError;
   *   definitions?: ModdleElement;
   *   elementsById?: ModdleElementsById;
   *   references?: ModdleElement[];
   *   warnings: string[];
   * } } ImportParseCompleteEvent
   *
   * @typedef { {
   *   error?: ImportXMLError;
   *   warnings: string[];
   * } } ImportDoneEvent
   *
   * @typedef { {
   *   definitions: ModdleElement;
   * } } SaveXMLStartEvent
   *
   * @typedef {SaveXMLResult} SaveXMLDoneEvent
   *
   * @typedef { {
   *   error?: Error;
   *   svg: string;
   * } } SaveSVGDoneEvent
   */

  /**
   * @template Type
   *
   * @typedef { Type extends { eventBus: EventBus<infer X> } ? X : never } EventMap
   */

  /**
   * A base viewer for BPMN 2.0 diagrams.
   *
   * Have a look at {@link bpmn-js/lib/Viewer}, {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/Modeler} for
   * bundles that include actual features.
   *
   * @template [ServiceMap=null]
   *
   * @extends Diagram<ServiceMap>
   *
   * @param {BaseViewerOptions} [options] The options to configure the viewer.
   */
  function BaseViewer(options) {

    /**
     * @type {BaseViewerOptions}
     */
    options = assign$1({}, DEFAULT_OPTIONS, options);

    /**
     * @type {Moddle}
     */
    this._moddle = this._createModdle(options);

    /**
     * @type {HTMLElement}
     */
    this._container = this._createContainer(options);

    this._init(this._container, this._moddle, options);

    /* <project-logo> */

    addProjectLogo(this._container);

    /* </project-logo> */
  }

  e$2(BaseViewer, Diagram);

  /**
   * 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.
   *
   * @throws {ImportXMLError} An error thrown during the import of the XML.
   *
   * @fires BaseViewer#ImportParseStartEvent
   * @fires BaseViewer#ImportParseCompleteEvent
   * @fires Importer#ImportRenderStartEvent
   * @fires Importer#ImportRenderCompleteEvent
   * @fires BaseViewer#ImportDoneEvent
   *
   * @param {string} xml The BPMN 2.0 XML to be imported.
   * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or Id of the BPMN diagram to open.
   *
   * @return {Promise<ImportXMLResult>} A promise resolving with warnings that were produced during the import.
   */
  BaseViewer.prototype.importXML = async function importXML(xml, bpmnDiagram) {

    const self = this;

    function ParseCompleteEvent(data) {
      return self.get('eventBus').createEvent(data);
    }

    let aggregatedWarnings = [];
    try {

      // hook in pre-parse listeners +
      // allow xml manipulation

      /**
       * A `import.parse.start` event.
       *
       * @event BaseViewer#ImportParseStartEvent
       * @type {ImportParseStartEvent}
       */
      xml = this._emit('import.parse.start', { xml: xml }) || xml;

      let parseResult;
      try {
        parseResult = await this._moddle.fromXML(xml, 'bpmn:Definitions');
      } catch (error) {
        this._emit('import.parse.complete', {
          error
        });

        throw error;
      }

      let definitions = parseResult.rootElement;
      const references = parseResult.references;
      const parseWarnings = parseResult.warnings;
      const elementsById = parseResult.elementsById;

      aggregatedWarnings = aggregatedWarnings.concat(parseWarnings);

      // hook in post parse listeners +
      // allow definitions manipulation

      /**
       * A `import.parse.complete` event.
       *
       * @event BaseViewer#ImportParseCompleteEvent
       * @type {ImportParseCompleteEvent}
       */
      definitions = this._emit('import.parse.complete', ParseCompleteEvent({
        error: null,
        definitions: definitions,
        elementsById: elementsById,
        references: references,
        warnings: aggregatedWarnings
      })) || definitions;

      const importResult = await this.importDefinitions(definitions, bpmnDiagram);

      aggregatedWarnings = aggregatedWarnings.concat(importResult.warnings);

      /**
       * A `import.parse.complete` event.
       *
       * @event BaseViewer#ImportDoneEvent
       * @type {ImportDoneEvent}
       */
      this._emit('import.done', { error: null, warnings: aggregatedWarnings });

      return { warnings: aggregatedWarnings };
    } catch (err) {
      let error = err;
      aggregatedWarnings = aggregatedWarnings.concat(error.warnings || []);
      addWarningsToError(error, aggregatedWarnings);

      error = checkValidationError(error);

      this._emit('import.done', { error, warnings: error.warnings });

      throw error;
    }
  };


  /**
   * 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.
   *
   * @throws {ImportDefinitionsError} An error thrown during the import of the definitions.
   *
   * @param {ModdleElement} definitions The definitions.
   * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or ID of the BPMN diagram to open.
   *
   * @return {Promise<ImportDefinitionsResult>} A promise resolving with warnings that were produced during the import.
   */
  BaseViewer.prototype.importDefinitions = async function importDefinitions(definitions, bpmnDiagram) {
    this._setDefinitions(definitions);
    const result = await this.open(bpmnDiagram);

    return { warnings: result.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.
   *
   * @throws {OpenError} An error thrown during opening.
   *
   * @param {ModdleElement|string} bpmnDiagramOrId The diagram or Id of the BPMN diagram to open.
   *
   * @return {Promise<OpenResult>} A promise resolving with warnings that were produced during opening.
   */
  BaseViewer.prototype.open = async function open(bpmnDiagramOrId) {

    const definitions = this._definitions;
    let bpmnDiagram = bpmnDiagramOrId;

    if (!definitions) {
      const error = new Error('no XML imported');
      addWarningsToError(error, []);

      throw error;
    }

    if (typeof bpmnDiagramOrId === 'string') {
      bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);

      if (!bpmnDiagram) {
        const error = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
        addWarningsToError(error, []);

        throw error;
      }
    }

    // clear existing rendered diagram
    // catch synchronous exceptions during #clear()
    try {
      this.clear();
    } catch (error) {
      addWarningsToError(error, []);

      throw error;
    }

    // perform graphical import
    const { warnings } = await importBpmnDiagram(this, definitions, bpmnDiagram);

    return { warnings };
  };

  /**
   * 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.
   *
   * @throws {Error} An error thrown during export.
   *
   * @fires BaseViewer#SaveXMLStart
   * @fires BaseViewer#SaveXMLDone
   *
   * @param {SaveXMLOptions} [options] The options.
   *
   * @return {Promise<SaveXMLResult>} A promise resolving with the XML.
   */
  BaseViewer.prototype.saveXML = async function saveXML(options) {

    options = options || {};

    let definitions = this._definitions,
        error, xml;

    try {
      if (!definitions) {
        throw new Error('no definitions loaded');
      }

      // allow to fiddle around with definitions

      /**
       * A `saveXML.start` event.
       *
       * @event BaseViewer#SaveXMLStartEvent
       * @type {SaveXMLStartEvent}
       */
      definitions = this._emit('saveXML.start', {
        definitions
      }) || definitions;

      const result = await this._moddle.toXML(definitions, options);
      xml = result.xml;

      xml = this._emit('saveXML.serialized', {
        xml
      }) || xml;
    } catch (err) {
      error = err;
    }

    const result = error ? { error } : { xml };

    /**
     * A `saveXML.done` event.
     *
     * @event BaseViewer#SaveXMLDoneEvent
     * @type {SaveXMLDoneEvent}
     */
    this._emit('saveXML.done', result);

    if (error) {
      throw error;
    }

    return result;
  };


  /**
   * 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.
   *
   * @throws {Error} An error thrown during export.
   *
   * @fires BaseViewer#SaveSVGDone
   *
   * @return {Promise<SaveSVGResult>} A promise resolving with the SVG.
   */
  BaseViewer.prototype.saveSVG = async function saveSVG() {
    this._emit('saveSVG.start');

    let svg, err;

    try {
      const canvas = this.get('canvas');

      const contentNode = canvas.getActiveLayer(),
            defsNode = query(':scope > defs', canvas._svg);

      const contents = innerSVG(contentNode),
            defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';

      const 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;
    }

    /**
     * A `saveSVG.done` event.
     *
     * @event BaseViewer#SaveSVGDoneEvent
     * @type {SaveSVGDoneEvent}
     */
    this._emit('saveSVG.done', {
      error: err,
      svg: svg
    });

    if (err) {
      throw err;
    }

    return { svg };
  };

  BaseViewer.prototype._setDefinitions = function(definitions) {
    this._definitions = definitions;
  };

  /**
   * Return modules to instantiate with.
   *
   * @return {ModuleDeclaration[]} The modules.
   */
  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.
   */
  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);
  };

  /**
   * @overlord
   *
   * 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.
   *
   * @template T
   *
   * @param {string|string[]} events The event(s) to listen to.
   * @param {number} [priority] The priority with which to listen.
   * @param {EventBusEventCallback<T>} callback The callback.
   * @param {any} [that] Value of `this` the callback will be called with.
   */
  /**
   * 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.
   *
   * @template {keyof EventMap<ServiceMap>} EventName
   *
   * @param {EventName} events to subscribe to
   * @param {number} [priority=1000] listen priority
   * @param {EventBusEventCallback<(EventMap<ServiceMap>)[EventName]>} callback
   * @param {any} [that] callback context
   */
  BaseViewer.prototype.on = function(events, priority, callback, that) {
    return this.get('eventBus').on(events, priority, callback, that);
  };

  /**
   * Remove an event listener.
   *
   * @param {string|string[]} events The event(s).
   * @param {Function} [callback] The callback.
   */
  BaseViewer.prototype.off = function(events, callback) {
    this.get('eventBus').off(events, callback);
  };

  /**
   * Attach the viewer to an HTML element.
   *
   * @param {HTMLElement} parentNode The parent node to attach to.
   */
  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();
  };

  /**
   * Get the definitions model element.
   *
   * @return {ModdleElement} The definitions model element.
   */
  BaseViewer.prototype.getDefinitions = function() {
    return this._definitions;
  };

  /**
   * Detach the viewer.
   *
   * @fires BaseViewer#DetachEvent
   */
  BaseViewer.prototype.detach = function() {

    const container = this._container,
          parentNode = container.parentNode;

    if (!parentNode) {
      return;
    }

    /**
     * A `detach` event.
     *
     * @event BaseViewer#DetachEvent
     * @type {Object}
     */
    this._emit('detach', {});

    parentNode.removeChild(container);
  };

  BaseViewer.prototype._init = function(container, moddle, options) {

    const baseModules = options.modules || this.getModules(options),
          additionalModules = options.additionalModules || [],
          staticModules = [
            {
              bpmnjs: [ 'value', this ],
              moddle: [ 'value', moddle ]
            }
          ];

    const diagramModules = [].concat(staticModules, baseModules, additionalModules);

    const diagramOptions = assign$1(omit(options, [ 'additionalModules' ]), {
      canvas: assign$1({}, 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} The return value after calling all event listeners.
   */
  BaseViewer.prototype._emit = function(type, event) {
    return this.get('eventBus').fire(type, event);
  };

  /**
   * @param {BaseViewerOptions} options
   *
   * @return {HTMLElement}
   */
  BaseViewer.prototype._createContainer = function(options) {

    const container = domify$1('<div class="bjs-container"></div>');

    assign(container, {
      width: ensureUnit(options.width),
      height: ensureUnit(options.height),
      position: options.position
    });

    return container;
  };

  /**
   * @param {BaseViewerOptions} options
   *
   * @return {Moddle}
   */
  BaseViewer.prototype._createModdle = function(options) {
    const moddleOptions = assign$1({}, this._moddleExtensions, options.moddleExtensions);

    return new SimpleBpmnModdle(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)

    const pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
    const 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;
  }

  const 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) {
    const img = BPMNIO_IMG;

    const linkMarkup =
      '<a href="http://bpmn.io" ' +
      'target="_blank" ' +
      'class="bjs-powered-by" ' +
      'title="Powered by bpmn.io" ' +
      '>' +
      img +
      '</a>';

    const linkElement = domify$1(linkMarkup);

    assign(query('svg', linkElement), LOGO_STYLES);
    assign(linkElement, LINK_STYLES, {
      position: 'absolute',
      bottom: '15px',
      right: '15px',
      zIndex: '100'
    });

    container.appendChild(linkElement);

    event.bind(linkElement, 'click', function(event) {
      open();

      event.preventDefault();
    });
  }

  /* </project-logo> */

  /**
   * @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions
   * @typedef {import('./BaseViewer').ModdleElementsById} ModdleElementsById
   *
   * @typedef {import('./model/Types').ModdleElement} ModdleElement
   */

  /**
   * A base modeler for BPMN 2.0 diagrams.
   *
   * See {@link bpmn-js/lib/Modeler} for a fully-featured modeler.
   *
   * @template [ServiceMap=null]
   *
   * @extends BaseViewer<ServiceMap>
   *
   * @param {BaseViewerOptions} [options] The options to configure the modeler.
   */
  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$2(BaseModeler, BaseViewer);


  /**
   * Create a moddle instance, attaching IDs to it.
   *
   * @param {BaseViewerOptions} options
   *
   * @return {Moddle}
   */
  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$1([ 32, 36, 1 ]);

    return moddle;
  };

  /**
   * Collect IDs processed during parsing of the definitions object.
   *
   * @param {ModdleElement} definitions
   * @param {ModdleElementsById} elementsById
   */
  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 ]);
    }
  };

  /**
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */

  /**
   * @param {Element} element
   * @param {ModdleElement} [di]
   *
   * @return {boolean}
   */
  function isExpanded(element, di) {

    if (is(element, 'bpmn:CallActivity')) {
      return false;
    }

    if (is(element, 'bpmn:SubProcess')) {
      di = di || getDi(element);

      if (di && is(di, 'bpmndi:BPMNPlane')) {
        return true;
      }

      return di && !!di.isExpanded;
    }

    if (is(element, 'bpmn:Participant')) {
      return !!getBusinessObject(element).processRef;
    }

    return true;
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isHorizontal$3(element) {

    if (!is(element, 'bpmn:Participant') && !is(element, 'bpmn:Lane')) {
      return undefined;
    }

    var isHorizontal = getDi(element).isHorizontal;

    if (isHorizontal === undefined) {
      return true;
    }

    return isHorizontal;
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isInterrupting(element) {
    return element && getBusinessObject(element).isInterrupting !== false;
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isEventSubProcess(element) {
    return element && !!getBusinessObject(element).triggeredByEvent;
  }

  /**
   * @param {Element} element
   * @param {string} eventType
   *
   * @return {boolean}
   */
  function hasEventDefinition$2(element, eventType) {
    var eventDefinitions = getBusinessObject(element).eventDefinitions;

    return some(eventDefinitions, function(event) {
      return is(event, eventType);
    });
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasErrorEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:ErrorEventDefinition');
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasEscalationEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:EscalationEventDefinition');
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasCompensateEventDefinition(element) {
    return hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
  }

  /**
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   *
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */

  var DEFAULT_LABEL_SIZE$1 = {
    width: 90,
    height: 20
  };

  var FLOW_LABEL_INDENT = 15;


  /**
   * Return true if the given semantic has an external label.
   *
   * @param {Element} semantic
   *
   * @return {boolean}
   */
  function isLabelExternal(semantic) {
    return is(semantic, 'bpmn:Event') ||
           is(semantic, 'bpmn:Gateway') ||
           is(semantic, 'bpmn:DataStoreReference') ||
           is(semantic, 'bpmn:DataObjectReference') ||
           is(semantic, 'bpmn:DataInput') ||
           is(semantic, 'bpmn:DataOutput') ||
           is(semantic, 'bpmn:SequenceFlow') ||
           is(semantic, 'bpmn:MessageFlow') ||
           is(semantic, 'bpmn:Group');
  }

  /**
   * Return true if the given element has an external label.
   *
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasExternalLabel(element) {
    return isLabel(element.label);
  }

  /**
   * Get the position of a sequence flow label.
   *
   * @param  {Point[]} waypoints
   *
   * @return {Point}
   */
  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  {Point[]} waypoints
   *
   * @return {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
    };
  }

  /**
   * Get the middle of the external label of an element.
   *
   * @param {Element} element
   *
   * @return {Point}
   */
  function getExternalLabelMid(element) {

    if (element.waypoints) {
      return getFlowLabelPosition(element.waypoints);
    } else if (is(element, 'bpmn:Group')) {
      return {
        x: element.x + element.width / 2,
        y: element.y + DEFAULT_LABEL_SIZE$1.height / 2
      };
    } else {
      return {
        x: element.x + element.width / 2,
        y: element.y + element.height + DEFAULT_LABEL_SIZE$1.height / 2
      };
    }
  }


  /**
   * Return the bounds of an elements label, parsed from the elements DI or
   * generated from its bounds.
   *
   * @param {ModdleElement} di
   * @param {Element} element
   *
   * @return {Rect}
   */
  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$1.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$1;
    }

    return assign$1({
      x: mid.x - size.width / 2,
      y: mid.y - size.height / 2
    }, size);
  }

  /**
   * @param {ModdleElement} semantic
   *
   * @returns {string}
   */
  function getLabelAttr(semantic) {
    if (
      is(semantic, 'bpmn:FlowElement') ||
      is(semantic, 'bpmn:Participant') ||
      is(semantic, 'bpmn:Lane') ||
      is(semantic, 'bpmn:SequenceFlow') ||
      is(semantic, 'bpmn:MessageFlow') ||
      is(semantic, 'bpmn:DataInput') ||
      is(semantic, 'bpmn:DataOutput')
    ) {
      return 'name';
    }

    if (is(semantic, 'bpmn:TextAnnotation')) {
      return 'text';
    }

    if (is(semantic, 'bpmn:Group')) {
      return 'categoryValueRef';
    }
  }

  /**
   * @param {ModdleElement} semantic
   *
   * @returns {string}
   */
  function getCategoryValue(semantic) {
    var categoryValueRef = semantic['categoryValueRef'];

    if (!categoryValueRef) {
      return '';
    }


    return categoryValueRef.value || '';
  }

  /**
   * @param {Element} element
   *
   * @return {string}
   */
  function getLabel(element) {
    var semantic = element.businessObject,
        attr = getLabelAttr(semantic);

    if (attr) {

      if (attr === 'categoryValueRef') {

        return getCategoryValue(semantic);
      }

      return semantic[attr] || '';
    }
  }


  /**
   * @param {Element} element
   * @param {string} text
   *
   * @return {Element}
   */
  function setLabel(element, text) {
    var semantic = element.businessObject,
        attr = getLabelAttr(semantic);

    if (attr) {

      if (attr === 'categoryValueRef') {
        if (!semantic[attr]) {
          return element;
        }

        semantic[attr].value = text;
      } else {
        semantic[attr] = text;
      }

    }

    return element;
  }

  var black = 'hsl(225, 10%, 15%)';
  var white = 'white';

  // element utils //////////////////////

  /**
   * Checks if eventDefinition of the given element matches with semantic type.
   *
   * @param {ModdleElement} event
   * @param {string} eventDefinitionType
   *
   * @return {boolean}
   */
  function isTypedEvent(event, eventDefinitionType) {
    return some(event.eventDefinitions, function(definition) {
      return definition.$type === eventDefinitionType;
    });
  }

  /**
   * Check if element is a throw event.
   *
   * @param {ModdleElement} event
   *
   * @return {boolean}
   */
  function isThrowEvent(event) {
    return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
  }

  /**
   * Check if element is a throw event.
   *
   * @param {ModdleElement} element
   *
   * @return {boolean}
   */
  function isCollection(element) {
    var dataObject = element.dataObjectRef;

    return element.isCollection || (dataObject && dataObject.isCollection);
  }


  // color access //////////////////////

  /**
   * @param {Element} element
   * @param {string} [defaultColor]
   * @param {string} [overrideColor]
   *
   * @return {string}
   */
  function getFillColor(element, defaultColor, overrideColor) {
    var di = getDi(element);

    return overrideColor || di.get('color:background-color') || di.get('bioc:fill') || defaultColor || white;
  }

  /**
   * @param {Element} element
   * @param {string} [defaultColor]
   * @param {string} [overrideColor]
   *
   * @return {string}
   */
  function getStrokeColor$1(element, defaultColor, overrideColor) {
    var di = getDi(element);

    return overrideColor || di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black;
  }

  /**
   * @param {Element} element
   * @param {string} [defaultColor]
   * @param {string} [defaultStrokeColor]
   * @param {string} [overrideColor]
   *
   * @return {string}
   */
  function getLabelColor(element, defaultColor, defaultStrokeColor, overrideColor) {
    var di = getDi(element),
        label = di.get('label');

    return overrideColor || (label && label.get('color:color')) || defaultColor ||
      getStrokeColor$1(element, defaultStrokeColor);
  }

  // cropping path customizations //////////////////////

  /**
   * @param {ShapeLike} shape
   *
   * @return {string} path
   */
  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);
  }

  /**
   * @param {ShapeLike} shape
   * @param {number} [borderRadius]
   *
   * @return {string} path
   */
  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, -10, borderRadius ],
      [ 'l', borderRadius * 2 - width, 0 ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, -10, -10 ],
      [ 'l', 0, borderRadius * 2 - height ],
      [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -10 ],
      [ 'z' ]
    ];

    return componentsToPath(roundRectPath);
  }

  /**
   * @param {ShapeLike} shape
   *
   * @return {string} path
   */
  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);
  }

  /**
   * @param {ShapeLike} shape
   *
   * @return {string} path
   */
  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);
  }

  /**
   * Get width and height from element or overrides.
   *
   * @param {Dimensions|Rect|ShapeLike} bounds
   * @param {Object} overrides
   *
   * @returns {Dimensions}
   */
  function getBounds$1(bounds, overrides = {}) {
    return {
      width: getWidth(bounds, overrides),
      height: getHeight(bounds, overrides)
    };
  }

  /**
   * Get width from element or overrides.
   *
   * @param {Dimensions|Rect|ShapeLike} bounds
   * @param {Object} overrides
   *
   * @returns {number}
   */
  function getWidth(bounds, overrides = {}) {
    return has$1(overrides, 'width') ? overrides.width : bounds.width;
  }

  /**
   * Get height from element or overrides.
   *
   * @param {Dimensions|Rect|ShapeLike} bounds
   * @param {Object} overrides
   *
   * @returns {number}
   */
  function getHeight(bounds, overrides = {}) {
    return has$1(overrides, 'height') ? overrides.height : bounds.height;
  }

  var markerIds = new Ids$1();

  var ELEMENT_LABEL_DISTANCE$1 = 10,
      INNER_OUTER_DIST = 3,
      PARTICIPANT_STROKE_WIDTH = 1.5,
      TASK_BORDER_RADIUS = 10;

  var DEFAULT_OPACITY = 0.95,
      FULL_OPACITY = 1,
      LOW_OPACITY = 0.25;

  /**
   * @typedef { Partial<{
   *   defaultFillColor: string,
   *   defaultStrokeColor: string,
   *   defaultLabelColor: string
   * }> } BpmnRendererConfig
   *
   * @typedef { Partial<{
   *   fill: string,
   *   stroke: string,
   *   width: string,
   *   height: string
   * }> } Attrs
   */

  /**
   * @typedef { import('../model/Types').Element } Element
   * @typedef { import('../model/Types').Shape } Shape
   * @typedef { import('../model/Types').Connection } Connection
   */

  /**
   * A renderer for BPMN elements
   *
   * @param {BpmnRendererConfig} config
   * @param {import('diagram-js/lib/core/EventBus').default} eventBus
   * @param {import('diagram-js/lib/draw/Styles').default} styles
   * @param {import('./PathMap').default} pathMap
   * @param {import('diagram-js/lib/core/Canvas').default} canvas
   * @param {import('./TextRenderer').default} textRenderer
   * @param {number} [priority]
   */
  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;

    function shapeStyle(attrs) {
      return styles.computeStyle(attrs, {
        strokeLinecap: 'round',
        strokeLinejoin: 'round',
        stroke: black,
        strokeWidth: 2,
        fill: 'white'
      });
    }

    function lineStyle(attrs) {
      return styles.computeStyle(attrs, [ 'no-fill' ], {
        strokeLinecap: 'round',
        strokeLinejoin: 'round',
        stroke: black,
        strokeWidth: 2
      });
    }

    function addMarker(id, options) {
      var {
        ref = { x: 0, y: 0 },
        scale = 1,
        element,
        parentGfx = canvas._svg
      } = options;

      var marker = create$1('marker', {
        id: id,
        viewBox: '0 0 20 20',
        refX: ref.x,
        refY: ref.y,
        markerWidth: 20 * scale,
        markerHeight: 20 * scale,
        orient: 'auto'
      });

      append(marker, element);

      var defs = query(':scope > defs', parentGfx);

      if (!defs) {
        defs = create$1('defs');

        append(parentGfx, defs);
      }

      append(defs, marker);
    }

    function marker(parentGfx, type, fill, stroke) {


      var id = markerIds.nextPrefixed('marker-');

      createMarker(parentGfx, id, type, fill, stroke);

      return 'url(#' + id + ')';
    }

    function createMarker(parentGfx, id, type, fill, stroke) {

      if (type === 'sequenceflow-end') {
        var sequenceflowEnd = create$1('path', {
          d: 'M 1 5 L 11 10 L 1 15 Z',
          ...shapeStyle({
            fill: stroke,
            stroke: stroke,
            strokeWidth: 1
          })
        });

        addMarker(id, {
          element: sequenceflowEnd,
          ref: { x: 11, y: 10 },
          scale: 0.5,
          parentGfx
        });
      }

      if (type === 'messageflow-start') {
        var messageflowStart = create$1('circle', {
          cx: 6,
          cy: 6,
          r: 3.5,
          ...shapeStyle({
            fill,
            stroke: stroke,
            strokeWidth: 1,

            // fix for safari / chrome / firefox bug not correctly
            // resetting stroke dash array
            strokeDasharray: [ 10000, 1 ]
          })
        });

        addMarker(id, {
          element: messageflowStart,
          ref: { x: 6, y: 6 },
          parentGfx
        });
      }

      if (type === 'messageflow-end') {
        var messageflowEnd = create$1('path', {
          d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z',
          ...shapeStyle({
            fill,
            stroke: stroke,
            strokeWidth: 1,

            // fix for safari / chrome / firefox bug not correctly
            // resetting stroke dash array
            strokeDasharray: [ 10000, 1 ]
          })
        });

        addMarker(id, {
          element: messageflowEnd,
          ref: { x: 8.5, y: 5 },
          parentGfx
        });
      }

      if (type === 'association-start') {
        var associationStart = create$1('path', {
          d: 'M 11 5 L 1 10 L 11 15',
          ...lineStyle({
            fill: 'none',
            stroke,
            strokeWidth: 1.5,

            // fix for safari / chrome / firefox bug not correctly
            // resetting stroke dash array
            strokeDasharray: [ 10000, 1 ]
          })
        });

        addMarker(id, {
          element: associationStart,
          ref: { x: 1, y: 10 },
          scale: 0.5,
          parentGfx
        });
      }

      if (type === 'association-end') {
        var associationEnd = create$1('path', {
          d: 'M 1 5 L 11 10 L 1 15',
          ...lineStyle({
            fill: 'none',
            stroke,
            strokeWidth: 1.5,

            // fix for safari / chrome / firefox bug not correctly
            // resetting stroke dash array
            strokeDasharray: [ 10000, 1 ]
          })
        });

        addMarker(id, {
          element: associationEnd,
          ref: { x: 11, y: 10 },
          scale: 0.5,
          parentGfx
        });
      }

      if (type === 'conditional-flow-marker') {
        var conditionalFlowMarker = create$1('path', {
          d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z',
          ...shapeStyle({
            fill,
            stroke: stroke
          })
        });

        addMarker(id, {
          element: conditionalFlowMarker,
          ref: { x: -1, y: 10 },
          scale: 0.5,
          parentGfx
        });
      }

      if (type === 'conditional-default-flow-marker') {
        var defaultFlowMarker = create$1('path', {
          d: 'M 6 4 L 10 16',
          ...shapeStyle({
            stroke: stroke,
            fill: 'none'
          })
        });

        addMarker(id, {
          element: defaultFlowMarker,
          ref: { x: 0, y: 10 },
          scale: 0.5,
          parentGfx
        });
      }
    }

    function drawCircle(parentGfx, width, height, offset, attrs = {}) {

      if (isObject(offset)) {
        attrs = offset;
        offset = 0;
      }

      offset = offset || 0;

      attrs = shapeStyle(attrs);

      var cx = width / 2,
          cy = height / 2;

      var circle = create$1('circle', {
        cx: cx,
        cy: cy,
        r: Math.round((width + height) / 4 - offset),
        ...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 = shapeStyle(attrs);

      var rect = create$1('rect', {
        x: offset,
        y: offset,
        width: width - offset * 2,
        height: height - offset * 2,
        rx: r,
        ry: r,
        ...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 = shapeStyle(attrs);

      var polygon = create$1('polygon', {
        ...attrs,
        points: pointsString
      });

      append(parentGfx, polygon);

      return polygon;
    }

    /**
     * @param {SVGElement} parentGfx
     * @param {Point[]} waypoints
     * @param {any} attrs
     * @param {number} [radius]
     *
     * @return {SVGElement}
     */
    function drawLine(parentGfx, waypoints, attrs, radius) {
      attrs = lineStyle(attrs);

      var line = createLine(waypoints, attrs, radius);

      append(parentGfx, line);

      return line;
    }

    /**
     * @param {SVGElement} parentGfx
     * @param {Point[]} waypoints
     * @param {any} attrs
     *
     * @return {SVGElement}
     */
    function drawConnectionSegments(parentGfx, waypoints, attrs) {
      return drawLine(parentGfx, waypoints, attrs, 5);
    }

    function drawPath(parentGfx, d, attrs) {
      attrs = lineStyle(attrs);

      var path = create$1('path', {
        ...attrs,
        d
      });

      append(parentGfx, path);

      return path;
    }

    function drawMarker(type, parentGfx, path, attrs) {
      return drawPath(parentGfx, path, assign$1({ 'data-marker': type }, attrs));
    }

    function renderer(type) {
      return handlers[type];
    }

    function as(type) {
      return function(parentGfx, element, attrs) {
        return renderer(type)(parentGfx, element, attrs);
      };
    }

    var eventIconRenderers = {
      'bpmn:MessageEventDefinition': function(parentGfx, element, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: attrs.width || element.width,
          containerHeight: attrs.height || element.height,
          position: {
            mx: 0.235,
            my: 0.315
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
          : getFillColor(element, defaultFillColor, attrs.fill);

        var stroke = isThrowing
          ? getFillColor(element, defaultFillColor, attrs.fill)
          : getStrokeColor$1(element, defaultStrokeColor, attrs.stroke);

        var messagePath = drawPath(parentGfx, pathData, {
          fill,
          stroke,
          strokeWidth: 1
        });

        return messagePath;
      },
      'bpmn:TimerEventDefinition': function(parentGfx, element, attrs = {}) {
        var baseWidth = attrs.width || element.width;
        var baseHeight = attrs.height || element.height;

        // use a lighter stroke for event suprocess icons
        var strokeWidth = attrs.width ? 1 : 2;

        var circle = drawCircle(parentGfx, baseWidth, baseHeight, 0.2 * baseHeight, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: strokeWidth
        });

        var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
          xScaleFactor: 0.75,
          yScaleFactor: 0.75,
          containerWidth: baseWidth,
          containerHeight: baseHeight,
          position: {
            mx: 0.5,
            my: 0.5
          }
        });

        drawPath(parentGfx, pathData, {
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: strokeWidth
        });

        for (var i = 0; i < 12; i++) {
          var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
            xScaleFactor: 0.75,
            yScaleFactor: 0.75,
            containerWidth: baseWidth,
            containerHeight: baseHeight,
            position: {
              mx: 0.5,
              my: 0.5
            }
          });

          var width = baseWidth / 2,
              height = baseHeight / 2;

          drawPath(parentGfx, linePathData, {
            strokeWidth: 1,
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')'
          });
        }

        return circle;
      },
      'bpmn:EscalationEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:ConditionalEventDefinition': function(parentGfx, event, attrs = {}) {
        var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.5,
            my: 0.222
          }
        });

        return drawPath(parentGfx, pathData, {
          fill: getFillColor(event, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:LinkEventDefinition': function(parentGfx, event, attrs = {}, 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, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:ErrorEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_ERROR', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.2,
            my: 0.722
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:CancelEventDefinition': function(parentGfx, event, attrs = {}, 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, attrs.stroke) : 'none';

        var path = drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        rotate(path, 45);

        return path;
      },
      'bpmn:CompensateEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.22,
            my: 0.5
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:SignalEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
          xScaleFactor: 0.9,
          yScaleFactor: 0.9,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.5,
            my: 0.2
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          strokeWidth: 1,
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
        });
      },
      'bpmn:MultipleEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) {
        var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
          xScaleFactor: 1.1,
          yScaleFactor: 1.1,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.211,
            my: 0.36
          }
        });

        var fill = isThrowing
          ? getStrokeColor$1(event, defaultStrokeColor, attrs.stroke)
          : getFillColor(event, defaultFillColor, attrs.fill);

        return drawPath(parentGfx, pathData, {
          fill,
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event, attrs = {}) {
        var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
          xScaleFactor: 1.2,
          yScaleFactor: 1.2,
          containerWidth: attrs.width || event.width,
          containerHeight: attrs.height || event.height,
          position: {
            mx: 0.458,
            my: 0.194
          }
        });

        return drawPath(parentGfx, pathData, {
          fill: getFillColor(event, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(event, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });
      },
      'bpmn:TerminateEventDefinition': function(parentGfx, element, attrs = {}) {
        var circle = drawCircle(parentGfx, element.width, element.height, 8, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 4
        });

        return circle;
      }
    };

    function renderEventIcon(element, parentGfx, attrs = {}, proxyElement) {
      var semantic = getBusinessObject(element),
          isThrowing = isThrowEvent(semantic);

      var nodeElement = proxyElement || element;

      if (semantic.get('eventDefinitions') && semantic.get('eventDefinitions').length > 1) {
        if (semantic.get('parallelMultiple')) {
          return eventIconRenderers[ 'bpmn:ParallelMultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
        }
        else {
          return eventIconRenderers[ 'bpmn:MultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
        }
      }

      if (isTypedEvent(semantic, 'bpmn:MessageEventDefinition')) {
        return eventIconRenderers[ 'bpmn:MessageEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:TimerEventDefinition')) {
        return eventIconRenderers[ 'bpmn:TimerEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:ConditionalEventDefinition')) {
        return eventIconRenderers[ 'bpmn:ConditionalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:SignalEventDefinition')) {
        return eventIconRenderers[ 'bpmn:SignalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:EscalationEventDefinition')) {
        return eventIconRenderers[ 'bpmn:EscalationEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:LinkEventDefinition')) {
        return eventIconRenderers[ 'bpmn:LinkEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:ErrorEventDefinition')) {
        return eventIconRenderers[ 'bpmn:ErrorEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:CancelEventDefinition')) {
        return eventIconRenderers[ 'bpmn:CancelEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:CompensateEventDefinition')) {
        return eventIconRenderers[ 'bpmn:CompensateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      if (isTypedEvent(semantic, 'bpmn:TerminateEventDefinition')) {
        return eventIconRenderers[ 'bpmn:TerminateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing);
      }

      return null;
    }

    var taskMarkerRenderers = {
      'ParticipantMultiplicityMarker': function(parentGfx, element, attrs = {}) {
        var width = getWidth(element, attrs),
            height = getHeight(element, attrs);

        var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: width,
          containerHeight: height,
          position: {
            mx: ((width / 2 - 6) / width),
            my: (height - 15) / height
          }
        });

        drawMarker('participant-multiplicity', parentGfx, markerPath, {
          strokeWidth: 2,
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      },
      'SubProcessMarker': function(parentGfx, element, attrs = {}) {
        var markerRect = drawRect(parentGfx, 14, 14, 0, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });

        translate$1(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, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      },
      'ParallelMarker': function(parentGfx, element, attrs) {
        var width = getWidth(element, attrs),
            height = getHeight(element, attrs);

        var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: width,
          containerHeight: height,
          position: {
            mx: ((width / 2 + attrs.parallel) / width),
            my: (height - 20) / height
          }
        });

        drawMarker('parallel', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      },
      'SequentialMarker': function(parentGfx, element, attrs) {
        var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + attrs.seq) / element.width),
            my: (element.height - 19) / element.height
          }
        });

        drawMarker('sequential', parentGfx, markerPath, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      },
      'CompensationMarker': function(parentGfx, element, attrs) {
        var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: ((element.width / 2 + attrs.compensation) / element.width),
            my: (element.height - 13) / element.height
          }
        });

        drawMarker('compensation', parentGfx, markerMath, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      },
      'LoopMarker': function(parentGfx, element, attrs) {
        var width = getWidth(element, attrs),
            height = getHeight(element, attrs);

        var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: width,
          containerHeight: height,
          position: {
            mx: ((width / 2 + attrs.loop) / width),
            my: (height - 7) / height
          }
        });

        drawMarker('loop', parentGfx, markerPath, {
          strokeWidth: 1.5,
          fill: 'none',
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeMiterlimit: 0.5
        });
      },
      'AdhocMarker': function(parentGfx, element, attrs) {
        var width = getWidth(element, attrs),
            height = getHeight(element, attrs);

        var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: width,
          containerHeight: height,
          position: {
            mx: ((width / 2 + attrs.adhoc) / width),
            my: (height - 15) / height
          }
        });

        drawMarker('adhoc', parentGfx, markerPath, {
          strokeWidth: 1,
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });
      }
    };

    function renderTaskMarker(type, parentGfx, element, attrs) {
      taskMarkerRenderers[ type ](parentGfx, element, attrs);
    }

    function renderTaskMarkers(parentGfx, element, taskMarkers, attrs = {}) {
      attrs = {
        fill: attrs.fill,
        stroke: attrs.stroke,
        width: getWidth(element, attrs),
        height: getHeight(element, attrs)
      };

      var semantic = getBusinessObject(element);

      var subprocess = taskMarkers && taskMarkers.includes('SubProcessMarker');

      if (subprocess) {
        attrs = {
          ...attrs,
          seq: -21,
          parallel: -22,
          compensation: -42,
          loop: -18,
          adhoc: 10
        };
      } else {
        attrs = {
          ...attrs,
          seq: -5,
          parallel: -6,
          compensation: -27,
          loop: 0,
          adhoc: 10
        };
      }

      forEach$1(taskMarkers, function(marker) {
        renderTaskMarker(marker, parentGfx, element, attrs);
      });

      if (semantic.get('isForCompensation')) {
        renderTaskMarker('CompensationMarker', parentGfx, element, attrs);
      }

      if (is(semantic, 'bpmn:AdHocSubProcess')) {
        renderTaskMarker('AdhocMarker', parentGfx, element, attrs);
      }

      var loopCharacteristics = semantic.get('loopCharacteristics'),
          isSequential = loopCharacteristics && loopCharacteristics.get('isSequential');

      if (loopCharacteristics) {

        if (isSequential === undefined) {
          renderTaskMarker('LoopMarker', parentGfx, element, attrs);
        }

        if (isSequential === false) {
          renderTaskMarker('ParallelMarker', parentGfx, element, attrs);
        }

        if (isSequential === true) {
          renderTaskMarker('SequentialMarker', parentGfx, element, attrs);
        }
      }
    }

    function renderLabel(parentGfx, label, attrs = {}) {
      attrs = assign$1({
        size: {
          width: 100
        }
      }, attrs);

      var text = textRenderer.createText(label || '', attrs);

      classes(text).add('djs-label');

      append(parentGfx, text);

      return text;
    }

    function renderEmbeddedLabel(parentGfx, element, align, attrs = {}) {
      var semantic = getBusinessObject(element);

      var box = getBounds$1({
        x: element.x,
        y: element.y,
        width: element.width,
        height: element.height
      }, attrs);

      return renderLabel(parentGfx, semantic.name, {
        align,
        box,
        padding: 7,
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
        }
      });
    }

    function renderExternalLabel(parentGfx, element, attrs = {}) {
      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$1(
          {},
          textRenderer.getExternalStyle(),
          {
            fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
          }
        )
      });
    }

    function renderLaneLabel(parentGfx, text, element, attrs = {}) {
      var isHorizontalLane = isHorizontal$3(element);

      var textBox = renderLabel(parentGfx, text, {
        box: {
          height: 30,
          width: isHorizontalLane ? getHeight(element, attrs) : getWidth(element, attrs),
        },
        align: 'center-middle',
        style: {
          fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
        }
      });

      if (isHorizontalLane) {
        var top = -1 * getHeight(element, attrs);
        transform(textBox, 0, -top, 270);
      }
    }

    function renderActivity(parentGfx, element, attrs = {}) {
      var {
        width,
        height
      } = getBounds$1(element, attrs);

      return drawRect(parentGfx, width, height, TASK_BORDER_RADIUS, {
        ...attrs,
        fill: getFillColor(element, defaultFillColor, attrs.fill),
        fillOpacity: DEFAULT_OPACITY,
        stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
      });
    }

    function renderAssociation(parentGfx, element, attrs = {}) {
      var semantic = getBusinessObject(element);

      var fill = getFillColor(element, defaultFillColor, attrs.fill),
          stroke = getStrokeColor$1(element, defaultStrokeColor, attrs.stroke);

      if (semantic.get('associationDirection') === 'One' ||
          semantic.get('associationDirection') === 'Both') {
        attrs.markerEnd = marker(parentGfx, 'association-end', fill, stroke);
      }

      if (semantic.get('associationDirection') === 'Both') {
        attrs.markerStart = marker(parentGfx, 'association-start', fill, stroke);
      }

      attrs = pickAttrs(attrs, [
        'markerStart',
        'markerEnd'
      ]);

      return drawConnectionSegments(parentGfx, element.waypoints, {
        ...attrs,
        stroke,
        strokeDasharray: '0, 5'
      });
    }

    function renderDataObject(parentGfx, element, attrs = {}) {
      var fill = getFillColor(element, defaultFillColor, attrs.fill),
          stroke = getStrokeColor$1(element, defaultStrokeColor, attrs.stroke);

      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 dataObject = drawPath(parentGfx, pathData, {
        fill,
        fillOpacity: DEFAULT_OPACITY,
        stroke
      });

      var semantic = getBusinessObject(element);

      if (isCollection(semantic)) {
        var collectionPathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0.33,
            my: (element.height - 18) / element.height
          }
        });

        drawPath(parentGfx, collectionPathData, {
          strokeWidth: 2,
          fill,
          stroke
        });
      }

      return dataObject;
    }

    function renderEvent(parentGfx, element, attrs = {}) {
      return drawCircle(parentGfx, element.width, element.height, {
        fillOpacity: DEFAULT_OPACITY,
        ...attrs,
        fill: getFillColor(element, defaultFillColor, attrs.fill),
        stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
      });
    }

    function renderGateway(parentGfx, element, attrs = {}) {
      return drawDiamond(parentGfx, element.width, element.height, {
        fill: getFillColor(element, defaultFillColor, attrs.fill),
        fillOpacity: DEFAULT_OPACITY,
        stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
      });
    }

    function renderLane(parentGfx, element, attrs = {}) {
      var lane = drawRect(parentGfx, getWidth(element, attrs), getHeight(element, attrs), 0, {
        fill: getFillColor(element, defaultFillColor, attrs.fill),
        fillOpacity: attrs.fillOpacity || DEFAULT_OPACITY,
        stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
        strokeWidth: 1.5
      });

      var semantic = getBusinessObject(element);

      if (is(semantic, 'bpmn:Lane')) {
        var text = semantic.get('name');

        renderLaneLabel(parentGfx, text, element, attrs);
      }

      return lane;
    }

    function renderSubProcess(parentGfx, element, attrs = {}) {
      var activity = renderActivity(parentGfx, element, attrs);

      var expanded = isExpanded(element);

      if (isEventSubProcess(element)) {
        attr(activity, {
          strokeDasharray: '0, 5.5',
          strokeWidth: 2.5
        });

        if (!expanded) {
          var flowElements = getBusinessObject(element).flowElements || [];
          var startEvents = flowElements.filter(e => is(e, 'bpmn:StartEvent'));

          if (startEvents.length === 1) {
            renderEventSubProcessIcon(startEvents[0], parentGfx, attrs, element);
          }
        }
      }

      renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle', attrs);

      if (expanded) {
        renderTaskMarkers(parentGfx, element, undefined, attrs);
      } else {
        renderTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ], attrs);
      }

      return activity;
    }

    function renderEventSubProcessIcon(startEvent, parentGfx, attrs, proxyElement) {
      var iconSize = 22;

      // match the colors of the enclosing subprocess
      var proxyAttrs = {
        fill: getFillColor(proxyElement, defaultFillColor, attrs.fill),
        stroke: getStrokeColor$1(proxyElement, defaultStrokeColor, attrs.stroke),
        width: iconSize,
        height: iconSize
      };

      var interrupting = getBusinessObject(startEvent).isInterrupting;
      var strokeDasharray = interrupting ? 0 : 3;

      // better visibility for non-interrupting events
      var strokeWidth = interrupting ? 1 : 1.2;

      // make the icon look larger by drawing a smaller circle
      var circleSize = 20;
      var shift = (iconSize - circleSize) / 2;
      var transform = 'translate(' + shift + ',' + shift + ')';

      drawCircle(parentGfx, circleSize, circleSize, {
        fill: proxyAttrs.fill,
        stroke: proxyAttrs.stroke,
        strokeWidth,
        strokeDasharray,
        transform
      });

      renderEventIcon(startEvent, parentGfx, proxyAttrs, proxyElement);
    }

    function renderTask(parentGfx, element, attrs = {}) {
      var activity = renderActivity(parentGfx, element, attrs);

      renderEmbeddedLabel(parentGfx, element, 'center-middle', attrs);

      renderTaskMarkers(parentGfx, element, undefined, attrs);

      return activity;
    }

    var handlers = this.handlers = {
      'bpmn:AdHocSubProcess': function(parentGfx, element, attrs = {}) {
        if (isExpanded(element)) {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke',
            'width',
            'height'
          ]);
        } else {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke'
          ]);
        }

        return renderSubProcess(parentGfx, element, attrs);
      },
      'bpmn:Association': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderAssociation(parentGfx, element, attrs);
      },
      'bpmn:BoundaryEvent': function(parentGfx, element, attrs = {}) {
        var { renderIcon = true } = attrs;

        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var semantic = getBusinessObject(element),
            cancelActivity = semantic.get('cancelActivity');

        attrs = {
          strokeWidth: 1.5,
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          fillOpacity: FULL_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        };

        if (!cancelActivity) {
          attrs.strokeDasharray = '6';
        }

        var event = renderEvent(parentGfx, element, attrs);

        drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
          ...attrs,
          fill: 'none'
        });

        if (renderIcon) {
          renderEventIcon(element, parentGfx, attrs);
        }

        return event;
      },
      'bpmn:BusinessRuleTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
          abspos: {
            x: 8,
            y: 8
          }
        });

        var businessPath = drawPath(parentGfx, headerData);

        attr(businessPath, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
          abspos: {
            x: 8,
            y: 8
          }
        });

        var businessHeaderPath = drawPath(parentGfx, headerPathData);

        attr(businessHeaderPath, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return task;
      },
      'bpmn:CallActivity': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderSubProcess(parentGfx, element, {
          strokeWidth: 5,
          ...attrs
        });
      },
      'bpmn:ComplexGateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var gateway = renderGateway(parentGfx, element, attrs);

        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
          }
        });

        drawPath(parentGfx, pathData, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return gateway;
      },
      'bpmn:DataInput': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var arrowPathData = pathMap.getRawPath('DATA_ARROW');

        var dataObject = renderDataObject(parentGfx, element, attrs);

        drawPath(parentGfx, arrowPathData, {
          fill: 'none',
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return dataObject;
      },
      'bpmn:DataInputAssociation': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderAssociation(parentGfx, element, {
          ...attrs,
          markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor$1(element, defaultStrokeColor, attrs.stroke))
        });
      },
      'bpmn:DataObject': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderDataObject(parentGfx, element, attrs);
      },
      'bpmn:DataObjectReference': as('bpmn:DataObject'),
      'bpmn:DataOutput': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var arrowPathData = pathMap.getRawPath('DATA_ARROW');

        var dataObject = renderDataObject(parentGfx, element, attrs);

        drawPath(parentGfx, arrowPathData, {
          strokeWidth: 1,
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });

        return dataObject;
      },
      'bpmn:DataOutputAssociation': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderAssociation(parentGfx, element, {
          ...attrs,
          markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor$1(element, defaultStrokeColor, attrs.stroke))
        });
      },
      'bpmn:DataStoreReference': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var dataStorePath = pathMap.getScaledPath('DATA_STORE', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: element.width,
          containerHeight: element.height,
          position: {
            mx: 0,
            my: 0.133
          }
        });

        return drawPath(parentGfx, dataStorePath, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          fillOpacity: DEFAULT_OPACITY,
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 2
        });
      },
      'bpmn:EndEvent': function(parentGfx, element, attrs = {}) {
        var { renderIcon = true } = attrs;

        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var event = renderEvent(parentGfx, element, {
          ...attrs,
          strokeWidth: 4
        });

        if (renderIcon) {
          renderEventIcon(element, parentGfx, attrs);
        }

        return event;
      },
      'bpmn:EventBasedGateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var semantic = getBusinessObject(element);

        var diamond = renderGateway(parentGfx, element, attrs);

        drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
          fill: getFillColor(element, 'none', attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        var type = semantic.get('eventGatewayType'),
            instantiate = !!semantic.get('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
            }
          });

          drawPath(parentGfx, pathData, {
            fill: 'none',
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            strokeWidth: 2
          });
        }

        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
            }
          });

          drawPath(parentGfx, pathData, {
            fill: 'none',
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            strokeWidth: 1
          });
        } else if (type === 'Exclusive') {
          if (!instantiate) {
            drawCircle(parentGfx, element.width, element.height, element.height * 0.26, {
              fill: 'none',
              stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
              strokeWidth: 1
            });
          }

          drawEvent();
        }


        return diamond;
      },
      'bpmn:ExclusiveGateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var gateway = renderGateway(parentGfx, element, attrs);

        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
          }
        });

        var di = getDi(element);

        if (di.get('isMarkerVisible')) {
          drawPath(parentGfx, pathData, {
            fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            strokeWidth: 1
          });
        }

        return gateway;
      },
      'bpmn:Gateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderGateway(parentGfx, element, attrs);
      },
      'bpmn:Group': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke',
          'width',
          'height'
        ]);

        return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1.5,
          strokeDasharray: '10, 6, 0, 6',
          fill: 'none',
          pointerEvents: 'none',
          width: getWidth(element, attrs),
          height: getHeight(element, attrs)
        });
      },
      'bpmn:InclusiveGateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var gateway = renderGateway(parentGfx, element, attrs);

        drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 2.5
        });

        return gateway;
      },
      'bpmn:IntermediateEvent': function(parentGfx, element, attrs = {}) {
        var { renderIcon = true } = attrs;

        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var outer = renderEvent(parentGfx, element, {
          ...attrs,
          strokeWidth: 1.5
        });

        drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
          fill: 'none',
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1.5
        });

        if (renderIcon) {
          renderEventIcon(element, parentGfx, attrs);
        }

        return outer;
      },
      'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
      'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),
      'bpmn:Lane': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke',
          'width',
          'height'
        ]);

        return renderLane(parentGfx, element, {
          ...attrs,
          fillOpacity: LOW_OPACITY
        });
      },
      'bpmn:ManualTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
          abspos: {
            x: 17,
            y: 15
          }
        });

        drawPath(parentGfx, pathData, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 0.5
        });

        return task;
      },
      'bpmn:MessageFlow': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var semantic = getBusinessObject(element),
            di = getDi(element);

        var fill = getFillColor(element, defaultFillColor, attrs.fill),
            stroke = getStrokeColor$1(element, defaultStrokeColor, attrs.stroke);

        var path = drawConnectionSegments(parentGfx, element.waypoints, {
          markerEnd: marker(parentGfx, 'messageflow-end', fill, stroke),
          markerStart: marker(parentGfx, 'messageflow-start', fill, stroke),
          stroke,
          strokeDasharray: '10, 11',
          strokeWidth: 1.5
        });

        if (semantic.get('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.get('messageVisibleKind') === 'initiating') {
            messageAttrs.fill = fill;
            messageAttrs.stroke = stroke;
          } else {
            messageAttrs.fill = stroke;
            messageAttrs.stroke = fill;
          }

          var message = drawPath(parentGfx, markerPathData, messageAttrs);

          var messageRef = semantic.get('messageRef'),
              name = messageRef.get('name');

          var label = renderLabel(parentGfx, name, {
            align: 'center-top',
            fitBox: true,
            style: {
              fill: stroke
            }
          });

          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:ParallelGateway': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var diamond = renderGateway(parentGfx, element, attrs);

        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
          }
        });

        drawPath(parentGfx, pathData, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return diamond;
      },
      'bpmn:Participant': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke',
          'width',
          'height'
        ]);

        var participant = renderLane(parentGfx, element, attrs);

        var expandedParticipant = isExpanded(element);
        var horizontalParticipant = isHorizontal$3(element);

        var semantic = getBusinessObject(element),
            name = semantic.get('name');

        if (expandedParticipant) {
          var waypoints = horizontalParticipant ? [
            {
              x: 30,
              y: 0
            },
            {
              x: 30,
              y: getHeight(element, attrs)
            }
          ] : [
            {
              x: 0,
              y: 30
            },
            {
              x: getWidth(element, attrs),
              y: 30
            }
          ];

          drawLine(parentGfx, waypoints, {
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            strokeWidth: PARTICIPANT_STROKE_WIDTH
          });

          renderLaneLabel(parentGfx, name, element, attrs);
        } else {
          var bounds = getBounds$1(element, attrs);

          if (!horizontalParticipant) {
            bounds.height = getWidth(element, attrs);
            bounds.width = getHeight(element, attrs);
          }

          var textBox = renderLabel(parentGfx, name, {
            box: bounds,
            align: 'center-middle',
            style: {
              fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
            }
          });

          if (!horizontalParticipant) {
            var top = -1 * getHeight(element, attrs);
            transform(textBox, 0, -top, 270);
          }
        }

        if (semantic.get('participantMultiplicity')) {
          renderTaskMarker('ParticipantMultiplicityMarker', parentGfx, element, attrs);
        }

        return participant;
      },
      'bpmn:ReceiveTask' : function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var semantic = getBusinessObject(element);

        var task = renderTask(parentGfx, element, attrs);

        var pathData;

        if (semantic.get('instantiate')) {
          drawCircle(parentGfx, 28, 28, 20 * 0.22, {
            fill: getFillColor(element, defaultFillColor, attrs.fill),
            stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
            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
            }
          });
        }

        drawPath(parentGfx, pathData, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return task;
      },
      'bpmn:ScriptTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
          abspos: {
            x: 15,
            y: 20
          }
        });

        drawPath(parentGfx, pathData, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return task;
      },
      'bpmn:SendTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: 21,
          containerHeight: 14,
          position: {
            mx: 0.285,
            my: 0.357
          }
        });

        drawPath(parentGfx, pathData, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getFillColor(element, defaultFillColor, attrs.fill),
          strokeWidth: 1
        });

        return task;
      },
      'bpmn:SequenceFlow': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var fill = getFillColor(element, defaultFillColor, attrs.fill),
            stroke = getStrokeColor$1(element, defaultStrokeColor, attrs.stroke);

        var connection = drawConnectionSegments(parentGfx, element.waypoints, {
          markerEnd: marker(parentGfx, 'sequenceflow-end', fill, stroke),
          stroke
        });

        var semantic = getBusinessObject(element);

        var { source } = element;

        if (source) {
          var sourceSemantic = getBusinessObject(source);

          // conditional flow marker
          if (semantic.get('conditionExpression') && is(sourceSemantic, 'bpmn:Activity')) {
            attr(connection, {
              markerStart: marker(parentGfx, 'conditional-flow-marker', fill, stroke)
            });
          }

          // default marker
          if (sourceSemantic.get('default') && (is(sourceSemantic, 'bpmn:Gateway') || is(sourceSemantic, 'bpmn:Activity')) &&
              sourceSemantic.get('default') === semantic) {
            attr(connection, {
              markerStart: marker(parentGfx, 'conditional-default-flow-marker', fill, stroke)
            });
          }
        }

        return connection;
      },
      'bpmn:ServiceTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        drawCircle(parentGfx, 10, 10, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: 'none',
          transform: 'translate(6, 6)'
        });

        var pathDataService1 = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 12,
            y: 18
          }
        });

        drawPath(parentGfx, pathDataService1, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        drawCircle(parentGfx, 10, 10, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: 'none',
          transform: 'translate(11, 10)'
        });

        var pathDataService2 = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
          abspos: {
            x: 17,
            y: 22
          }
        });

        drawPath(parentGfx, pathDataService2, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1
        });

        return task;
      },
      'bpmn:StartEvent': function(parentGfx, element, attrs = {}) {
        var { renderIcon = true } = attrs;

        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var semantic = getBusinessObject(element);

        if (!semantic.get('isInterrupting')) {
          attrs = {
            ...attrs,
            strokeDasharray: '6'
          };
        }

        var event = renderEvent(parentGfx, element, attrs);

        if (renderIcon) {
          renderEventIcon(element, parentGfx, attrs);
        }

        return event;
      },
      'bpmn:SubProcess': function(parentGfx, element, attrs = {}) {
        if (isExpanded(element)) {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke',
            'width',
            'height'
          ]);
        } else {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke'
          ]);
        }

        return renderSubProcess(parentGfx, element, attrs);
      },
      'bpmn:Task': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        return renderTask(parentGfx, element, attrs);
      },
      'bpmn:TextAnnotation': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke',
          'width',
          'height'
        ]);

        var {
          width,
          height
        } = getBounds$1(element, attrs);

        var textElement = drawRect(parentGfx, width, height, 0, 0, {
          fill: 'none',
          stroke: 'none'
        });

        var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
          xScaleFactor: 1,
          yScaleFactor: 1,
          containerWidth: width,
          containerHeight: height,
          position: {
            mx: 0.0,
            my: 0.0
          }
        });

        drawPath(parentGfx, textPathData, {
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke)
        });

        var semantic = getBusinessObject(element),
            text = semantic.get('text') || '';

        renderLabel(parentGfx, text, {
          align: 'left-top',
          box: getBounds$1(element, attrs),
          padding: 7,
          style: {
            fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke)
          }
        });

        return textElement;
      },
      'bpmn:Transaction': function(parentGfx, element, attrs = {}) {
        if (isExpanded(element)) {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke',
            'width',
            'height'
          ]);
        } else {
          attrs = pickAttrs(attrs, [
            'fill',
            'stroke'
          ]);
        }

        var outer = renderSubProcess(parentGfx, element, {
          strokeWidth: 1.5,
          ...attrs
        });

        var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 1.5
        });

        var expanded = isExpanded(element);

        if (!expanded) {
          attrs = {};
        }

        drawRect(
          parentGfx,
          getWidth(element, attrs),
          getHeight(element, attrs),
          TASK_BORDER_RADIUS - INNER_OUTER_DIST,
          INNER_OUTER_DIST,
          innerAttrs
        );

        return outer;
      },
      'bpmn:UserTask': function(parentGfx, element, attrs = {}) {
        attrs = pickAttrs(attrs, [
          'fill',
          'stroke'
        ]);

        var task = renderTask(parentGfx, element, attrs);

        var x = 15;
        var y = 12;

        var pathDataUser1 = pathMap.getScaledPath('TASK_TYPE_USER_1', {
          abspos: {
            x: x,
            y: y
          }
        });

        drawPath(parentGfx, pathDataUser1, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 0.5
        });

        var pathDataUser2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
          abspos: {
            x: x,
            y: y
          }
        });

        drawPath(parentGfx, pathDataUser2, {
          fill: getFillColor(element, defaultFillColor, attrs.fill),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 0.5
        });

        var pathDataUser3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
          abspos: {
            x: x,
            y: y
          }
        });

        drawPath(parentGfx, pathDataUser3, {
          fill: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          stroke: getStrokeColor$1(element, defaultStrokeColor, attrs.stroke),
          strokeWidth: 0.5
        });

        return task;
      },
      'label': function(parentGfx, element, attrs = {}) {
        return renderExternalLabel(parentGfx, element, attrs);
      }
    };

    // extension API, use at your own risk
    this._drawPath = drawPath;

    this._renderer = renderer;
  }


  e$2(BpmnRenderer, BaseRenderer);

  BpmnRenderer.$inject = [
    'config.bpmnRenderer',
    'eventBus',
    'styles',
    'pathMap',
    'canvas',
    'textRenderer'
  ];


  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  BpmnRenderer.prototype.canRender = function(element) {
    return is(element, 'bpmn:BaseElement');
  };

  /**
   * Draw shape into parentGfx.
   *
   * @param {SVGElement} parentGfx
   * @param {Shape} shape
   * @param {Attrs} [attrs]
   *
   * @return {SVGElement} mainGfx
   */
  BpmnRenderer.prototype.drawShape = function(parentGfx, shape, attrs = {}) {
    var { type } = shape;

    var handler = this._renderer(type);

    return handler(parentGfx, shape, attrs);
  };

  /**
   * Draw connection into parentGfx.
   *
   * @param {SVGElement} parentGfx
   * @param {Connection} connection
   * @param {Attrs} [attrs]
   *
   * @return {SVGElement} mainGfx
   */
  BpmnRenderer.prototype.drawConnection = function(parentGfx, connection, attrs = {}) {
    var { type } = connection;

    var handler = this._renderer(type);

    return handler(parentGfx, connection, attrs);
  };

  /**
   * Get shape path.
   *
   * @param {Shape} shape
   *
   * @return {string} path
   */
  BpmnRenderer.prototype.getShapePath = function(shape) {
    if (is(shape, 'bpmn:Event')) {
      return getCirclePath(shape);
    }

    if (is(shape, 'bpmn:Activity')) {
      return getRoundRectPath(shape, TASK_BORDER_RADIUS);
    }

    if (is(shape, 'bpmn:Gateway')) {
      return getDiamondPath(shape);
    }

    return getRectPath(shape);
  };

  /**
   * Pick attributes if they exist.
   *
   * @param {Object} attrs
   * @param {string[]} keys
   *
   * @returns {Object}
   */
  function pickAttrs(attrs, keys = []) {
    return keys.reduce((pickedAttrs, key) => {
      if (attrs[ key ]) {
        pickedAttrs[ key ] = attrs[ key ];
      }

      return pickedAttrs;
    }, {});
  }

  /**
   * @typedef {import('../util/Types').Dimensions} Dimensions
   *
   * @typedef { {
   *   top: number;
   *   left: number;
   *   right: number;
   *   bottom: number;
   * } } Padding
   *
   * @typedef { number | Partial<Padding> } PaddingConfig
   *
   * @typedef { {
   *   horizontal: 'center' | 'left' | 'right';
   *   vertical: 'top' | 'middle';
   * } } Alignment
   *
   *  @typedef { 'center-middle' | 'center-top' } AlignmentConfig
   *
   * @typedef { Partial<{
   *   align: AlignmentConfig;
   *   style: Record<string, number | string>;
   *   padding: PaddingConfig;
   * }> } BaseTextConfig
   *
   * @typedef { BaseTextConfig & Partial<{
   *   size: Dimensions;
   * }> } TextConfig
   *
   * @typedef { BaseTextConfig & Partial<{
   *   box: Dimensions;
   *   fitBox: boolean;
   * }> } TextLayoutConfig
   *
   *  @typedef { Dimensions & {
   *  text: string;
   * } } LineDescriptor
   */

  var DEFAULT_BOX_PADDING = 0;

  var DEFAULT_LABEL_SIZE = {
    width: 150,
    height: 50
  };


  /**
   * @param {AlignmentConfig} align
   * @return {Alignment}
   */
  function parseAlign(align) {

    var parts = align.split('-');

    return {
      horizontal: parts[0] || 'center',
      vertical: parts[1] || 'top'
    };
  }

  /**
   * @param {PaddingConfig} padding
   *
   * @return {Padding}
   */
  function parsePadding(padding) {

    if (isObject(padding)) {
      return assign$1({ top: 0, left: 0, right: 0, bottom: 0 }, padding);
    } else {
      return {
        top: padding,
        left: padding,
        right: padding,
        bottom: padding
      };
    }
  }

  /**
   * @param {string} text
   * @param {SVGTextElement} fakeText
   *
   * @return {import('../util/Types').Dimensions}
   */
  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) {
      console.log(e);

      return { width: 0, height: 0 };
    }
  }


  /**
   * Layout the next line and return the layouted element.
   *
   * Alters the lines passed.
   *
   * @param {string[]} lines
   * @param {number} maxWidth
   * @param {SVGTextElement} fakeText
   *
   * @return {LineDescriptor} the line descriptor
   */
  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);
    }
  }

  /**
   * @param {string[]} lines
   * @param {string} fitLine
   * @param {string} originalLine
   * @param {Dimensions} textBBox
   *
   * @return {LineDescriptor}
   */
  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('');
  }


  /**
   * @param {string} line
   * @param {number} width
   * @param {number} maxWidth
   *
   * @return {string}
   */
  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;
  }


  /**
   * @return {SVGSVGElement}
   */
  function getHelperSvg() {
    var helperSvg = document.getElementById('helper-svg');

    if (!helperSvg) {
      helperSvg = create$1('svg');

      attr(helperSvg, {
        id: 'helper-svg'
      });

      assign(helperSvg, {
        visibility: 'hidden',
        position: 'fixed',
        width: 0,
        height: 0
      });

      document.body.appendChild(helperSvg);
    }

    return helperSvg;
  }


  /**
   * Creates a new label utility
   *
   * @param {TextConfig} [config]
   */
  function Text(config) {

    this._config = assign$1({}, {
      size: DEFAULT_LABEL_SIZE,
      padding: DEFAULT_BOX_PADDING,
      style: {},
      align: 'center-top'
    }, config || {});
  }

  /**
   * Returns the layouted text as an SVG element.
   *
   * @param {string} text
   * @param {TextLayoutConfig} 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 {TextLayoutConfig} options
   *
   * @return {Dimensions}
   */
  Text.prototype.getDimensions = function(text, options) {
    return this.layoutText(text, options).dimensions;
  };

  /**
   * Creates and returns a label and its bounding box.
   *
   * @param {string} text the text to render on the label
   * @param {TextLayoutConfig} options
   *
   * @return { {
   *   element: SVGElement,
   *   dimensions: Dimensions
   * } }
   */
  Text.prototype.layoutText = function(text, options) {
    var box = assign$1({}, this._config.size, options.box),
        style = assign$1({}, 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;

  /**
   * @typedef { {
   *   fontFamily: string;
   *   fontSize: number;
   *   fontWeight: string;
   *   lineHeight: number;
   * } } TextRendererStyle
   *
   * @typedef { {
   *   defaultStyle?: Partial<TextRendererStyle>;
   *   externalStyle?: Partial<TextRendererStyle>;
   * } } TextRendererConfig
   *
   * @typedef { import('diagram-js/lib/util/Text').TextLayoutConfig } TextLayoutConfig
   *
   * @typedef { import('diagram-js/lib/util/Types').Rect } Rect
   */


  /**
   * Renders text and computes text bounding boxes.
   *
   * @param {TextRendererConfig} [config]
   */
  function TextRenderer(config) {

    var defaultStyle = assign$1({
      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$1({}, defaultStyle, {
      fontSize: fontSize
    }, config && config.externalStyle || {});

    var textUtil = new Text({
      style: defaultStyle
    });

    /**
     * Get the new bounds of an externally rendered,
     * layouted label.
     *
     * @param {Rect} bounds
     * @param {string} text
     *
     * @return {Rect}
     */
    this.getExternalLabelBounds = function(bounds, text) {

      var layoutedDimensions = textUtil.getDimensions(text, {
        box: {
          width: 90,
          height: 30
        },
        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 {Rect} bounds
     * @param {string} text
     *
     * @return {Rect}
     */
    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 {TextLayoutConfig} [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'
      }
    };

    /**
     * Return raw path for the given ID.
     *
     * @param {string} pathId
     *
     * @return {string} raw path
     */
    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>
     *
     * @return {string} scaled path
     */
    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 ]
  };

  /**
   * @typedef { {
   *   [key: string]: string;
   * } } TranslateReplacements
   */

  /**
   * A simple translation stub to be used for multi-language support
   * in diagrams. Can be easily replaced with a more sophisticated
   * solution.
   *
   * @example
   *
   * ```javascript
   * // use it inside any diagram component by injecting `translate`.
   *
   * function MyService(translate) {
   *   alert(translate('HELLO {you}', { you: 'You!' }));
   * }
   * ```
   *
   * @param {string} template to interpolate
   * @param {TranslateReplacements} [replacements] a map with substitutes
   *
   * @return {string} the translated string
   */
  function translate(template, replacements) {

    replacements = replacements || {};

    return template.replace(/{([^}]+)}/g, function(_, key) {
      return replacements[key] || '{' + key + '}';
    });
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var TranslateModule = {
    translate: [ 'value', translate ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('../features/modeling/ElementFactory').default} ElementFactory
   * @typedef {import('../draw/TextRenderer').default} TextRenderer
   *
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').Label} Label
   * @typedef {import('../model/Types').Shape} Shape
   * @typedef {import('../model/Types').Connection} Connection
   * @typedef {import('../model/Types').Root} Root
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */

  /**
   * @param {ModdleElement} semantic
   * @param {ModdleElement} di
   * @param {Object} [attrs=null]
   *
   * @return {Object}
   */
  function elementData(semantic, di, attrs) {
    return assign$1({
      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(semantic, refSemantic, property) {
    return new Error(
      `element ${ elementToString(refSemantic) } referenced by ${ elementToString(semantic) }#${ property } not yet drawn`
    );
  }


  /**
   * An importer that adds bpmn elements to the canvas
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {ElementFactory} elementFactory
   * @param {ElementRegistry} elementRegistry
   * @param {TextRenderer} textRenderer
   */
  function BpmnImporter(
      eventBus, canvas, elementFactory,
      elementRegistry, textRenderer) {

    this._eventBus = eventBus;
    this._canvas = canvas;
    this._elementFactory = elementFactory;
    this._elementRegistry = elementRegistry;
    this._textRenderer = textRenderer;
  }

  BpmnImporter.$inject = [
    'eventBus',
    'canvas',
    'elementFactory',
    'elementRegistry',
    'textRenderer'
  ];


  /**
   * Add a BPMN element (semantic) to the canvas making it a child of the
   * given parent.
   *
   * @param {ModdleElement} semantic
   * @param {ModdleElement} di
   * @param {Shape} parentElement
   *
   * @return {Shape | Root | Connection}
   */
  BpmnImporter.prototype.add = function(semantic, di, parentElement) {
    var element,
        hidden;

    var parentIndex;

    // ROOT ELEMENT
    // handle the special case that we deal with a
    // invisible root element (process, subprocess or collaboration)
    if (is(di, 'bpmndi:BPMNPlane')) {

      var attrs = is(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(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(semantic, 'bpmn:BoundaryEvent')) {
        this._attachBoundary(semantic, element);
      }

      // insert lanes behind other flow nodes (cf. #727)
      if (is(semantic, 'bpmn:Lane')) {
        parentIndex = 0;
      }

      if (is(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(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(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(
        `unknown di ${ elementToString(di) } for element ${ elementToString(semantic) }`
      );
    }

    // (optional) LABEL
    if (isLabelExternal(semantic) && getLabel(element)) {
      this.addLabel(semantic, di, element);
    }

    this._eventBus.fire('bpmnElement.added', { element: element });

    return element;
  };


  /**
   * Attach a boundary element to the given host.
   *
   * @param {ModdleElement} boundarySemantic
   * @param {Shape} boundaryElement
   */
  BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
    var hostSemantic = boundarySemantic.attachedToRef;

    if (!hostSemantic) {
      throw new Error(
        `missing ${ elementToString(boundarySemantic) }#attachedToRef`
      );
    }

    var host = this._elementRegistry.get(hostSemantic.id),
        attachers = host && host.attachers;

    if (!host) {
      throw notYetDrawn(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 a label to a given element.
   *
   * @param {ModdleElement} semantic
   * @param {ModdleElement} di
   * @param {Element} element
   *
   * @return {Label}
   */
  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);
  };

  /**
   * Get the source or target of the given connection.
   *
   * @param {ModdleElement} semantic
   * @param {'source' | 'target'} side
   *
   * @return {Element}
   */
  BpmnImporter.prototype._getConnectedElement = function(semantic, side) {

    var element,
        refSemantic,
        type = semantic.$type;

    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(semantic, refSemantic, side + 'Ref');
    } else {
      throw new Error(
        `${ elementToString(semantic) }#${ side } Ref not specified`
      );
    }
  };

  BpmnImporter.prototype._getSource = function(semantic) {
    return this._getConnectedElement(semantic, 'source');
  };

  BpmnImporter.prototype._getTarget = function(semantic) {
    return this._getConnectedElement(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(semantic, 'bpmn:Group');
  }

  var ImportModule = {
    __depends__: [
      TranslateModule
    ],
    bpmnImporter: [ 'type', BpmnImporter ]
  };

  var CoreModule = {
    __depends__: [
      DrawModule,
      ImportModule
    ]
  };

  /**
   * Util that provides unique IDs.
   *
   * @class
   * @constructor
   *
   * 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.
   *
   * @return {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$q = 500;

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef { {
   *   minZoom?: number,
   *   maxZoom?: number
   * } } OverlaysConfigShow
   *
   * @typedef { {
   *   min?: number,
   *   max?: number
   * } } OverlaysConfigScale
   *
   * @typedef { {
  *   id: string,
  *   type: string | null,
  *   element: Element | string
  * } & OverlayAttrs } Overlay
  *
   * @typedef { {
   *   html: HTMLElement | string,
   *   position: {
   *     top?: number,
   *     right?: number,
   *     bottom?: number,
   *     left?: number
   *   }
   * } & OverlaysConfigDefault } OverlayAttrs
   *
   * @typedef { {
   *   html: HTMLElement,
   *   element: Element,
   *   overlays: Overlay[]
   * } } OverlayContainer
   *
   * @typedef {{
   *   defaults?: OverlaysConfigDefault
   * }} OverlaysConfig
   *
   * @typedef { {
   *  show?: OverlaysConfigShow,
   *  scale?: OverlaysConfigScale | boolean
   * } } OverlaysConfigDefault
   *
   * @typedef { {
   *   id?: string;
   *   element?: Element | string;
   *   type?: string;
   * } | string } OverlaysFilter
   */

  /**
   * A service that allows users to attach overlays to diagram elements.
   *
   * The overlay service will take care of overlay positioning during updates.
   *
   * @example
   *
   * ```javascript
   * // 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>'
   * });
   * ```
   *
   * ```javascript
   * // 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 {OverlaysConfig} 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;

    /**
     * @type {OverlaysConfigDefault}
     */
    this._overlayDefaults = assign$1({

      // no show constraints
      show: null,

      // always scale
      scale: true
    }, config && config.defaults);

    /**
     * @type {Record<string, Overlay>}
     */
    this._overlays = {};

    /**
     * @type {OverlayContainer[]}
     */
    this._overlayContainers = [];

    /**
     * @type {HTMLElement}
     */
    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
   *
   * ```javascript
   * // 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 {OverlaysFilter} search The filter to be used to find the overlay(s).
   *
   * @return {Overlay|Overlay[]} 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 an HTML overlay to an element.
   *
   * @param {Element|string} element The element to add the overlay to.
   * @param {string} [type] An optional type that can be used to filter.
   * @param {OverlayAttrs} overlay The overlay.
   *
   * @return {string} The overlay's ID that can be used to get or remove it.
   */
  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$1({}, 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 {OverlaysFilter} filter The filter to be used to find the overlay.
   */
  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);
        }
      }
    });

  };

  /**
   * Checks whether overlays are shown.
   *
   * @return {boolean} Whether overlays are shown.
   */
  Overlays.prototype.isShown = function() {
    return this._overlayRoot.style.display !== 'none';
  };

  /**
   * Show all overlays.
   */
  Overlays.prototype.show = function() {
    setVisible$1(this._overlayRoot);
  };

  /**
   * Hide all overlays.
   */
  Overlays.prototype.hide = function() {
    setVisible$1(this._overlayRoot, false);
  };

  /**
   * Remove all overlays and their container.
   */
  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$1('<div class="djs-overlays" />');
    assign(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(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$1(html);
    }

    overlayContainer = this._getOverlayContainer(element);

    htmlContainer = domify$1('<div class="djs-overlay" data-overlay-id="' + id + '">');
    assign(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(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$q, 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$1(
      '<div class="djs-overlay-container" />'
    );

    assign(root, {
      position: 'absolute',
      width: 0,
      height: 0
    });

    parentNode.insertBefore(root, parentNode.firstChild);

    return root;
  }

  function setPosition$1(el, x, y) {
    assign(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(el, transform) {

    el.style['transform-origin'] = 'top left';

    [ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
      el.style[prefix + 'transform'] = transform;
    });
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var OverlaysModule = {
    __init__: [ 'overlays' ],
    overlays: [ 'type', Overlays ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../core/GraphicsFactory').default} GraphicsFactory
   */

  /**
   * 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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ChangeSupportModule = {
    __init__: [ 'changeSupport' ],
    changeSupport: [ 'type', ChangeSupport ]
  };

  /**
   * @typedef {import('../core/Types').ElementLike} ElementLike
   * @typedef {import('../core/EventBus').default} EventBus
   * @typedef {import('./CommandStack').CommandContext} CommandContext
   *
   * @typedef {string|string[]} Events
   * @typedef { (context: CommandContext) => ElementLike[] | void } HandlerFunction
   * @typedef { (context: CommandContext) => void } ComposeHandlerFunction
   */

  var DEFAULT_PRIORITY$5 = 1000;

  /**
   * A utility that can be used to plug into the command execution for
   * extension and/or validation.
   *
   * @class
   * @constructor
   *
   * @example
   *
   * ```javascript
   * import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
   *
   * class CommandLogger extends CommandInterceptor {
   *   constructor(eventBus) {
   *     super(eventBus);
   *
   *   this.preExecute('shape.create', (event) => {
   *     console.log('commandStack.shape-create.preExecute', event);
   *   });
   * }
   * ```
   *
   * @param {EventBus} eventBus
   */
  function CommandInterceptor(eventBus) {

    /**
     * @type {EventBus}
     */
    this._eventBus = eventBus;
  }

  CommandInterceptor.$inject = [ 'eventBus' ];

  function unwrapEvent(fn, that) {
    return function(event) {
      return fn.call(that || null, event.context, event.command, event);
    };
  }


  /**
   * Intercept a command during one of the phases.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {string} [hook] phase to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  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$5;
    }

    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);
    });
  };

  /**
   * Add a <canExecute> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.canExecute = createHook('canExecute');

  /**
   * Add a <preExecute> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.preExecute = createHook('preExecute');

  /**
   * Add a <preExecuted> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.preExecuted = createHook('preExecuted');

  /**
   * Add a <execute> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.execute = createHook('execute');

  /**
   * Add a <executed> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.executed = createHook('executed');

  /**
   * Add a <postExecute> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.postExecute = createHook('postExecute');

  /**
   * Add a <postExecuted> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.postExecuted = createHook('postExecuted');

  /**
   * Add a <revert> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.revert = createHook('revert');

  /**
   * Add a <reverted> phase of command interceptor.
   *
   * @param {Events} [events] command(s) to intercept
   * @param {number} [priority]
   * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
   * @param {boolean} [unwrap] whether the event should be unwrapped
   * @param {any} [that]
   */
  CommandInterceptor.prototype.reverted = createHook('reverted');

  /*
   * Add prototype methods for each phase of command execution (e.g. execute,
   * revert).
   *
   * @param {string} hook
   *
   * @return { (
   *   events?: Events,
   *   priority?: number,
   *   handlerFn: ComposeHandlerFunction|HandlerFunction,
   *   unwrap?: boolean
   * ) => any }
   */
  function createHook(hook) {

    /**
     * @this {CommandInterceptor}
     *
     * @param {Events} [events]
     * @param {number} [priority]
     * @param {ComposeHandlerFunction|HandlerFunction} handlerFn
     * @param {boolean} [unwrap]
     * @param {any} [that]
     */
    const hookFn = 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);
    };

    return hookFn;
  }

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   */

  /**
   * A modeling behavior that ensures we set the correct root element
   * as we undo and redo commands.
   *
   * @param {Canvas} canvas
   * @param {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$2(RootElementsBehavior, CommandInterceptor);

  RootElementsBehavior.$inject = [ 'canvas', 'injector' ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var RootElementsModule = {
    __init__: [ 'rootElementsBehavior' ],
    rootElementsBehavior: [ 'type', RootElementsBehavior ]
  };

  /**
   * @param {string} str
   *
   * @return {string}
   */
  function escapeCSS(str) {
    return CSS.escape(str);
  }

  var HTML_ESCAPE_MAP = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    '\'': '&#39;'
  };

  /**
   * @param {string} str
   *
   * @return {string}
   */
  function escapeHTML(str) {
    str = '' + str;

    return str && str.replace(/[&<>"']/g, function(match) {
      return HTML_ESCAPE_MAP[match];
    });
  }

  /**
   * @typedef {import('../model/Types').Element} Element
   * @typedef {import('../model/Types').ModdleElement} ModdleElement
   */

  var planeSuffix = '_plane';

  /**
   * Get primary shape ID for a plane.
   *
   * @param  {Element|ModdleElement} element
   *
   * @return {string}
   */
  function getShapeIdFromPlane(element) {
    var id = element.id;

    return removePlaneSuffix(id);
  }

  /**
   * Get plane ID for a primary shape.
   *
   * @param  {Element|ModdleElement} element
   *
   * @return {string}
   */
  function getPlaneIdFromShape(element) {
    var id = element.id;

    if (is(element, 'bpmn:SubProcess')) {
      return addPlaneSuffix(id);
    }

    return id;
  }

  /**
   * Get plane ID for primary shape ID.
   *
   * @param {string} id
   *
   * @return {string}
   */
  function toPlaneId(id) {
    return addPlaneSuffix(id);
  }

  /**
   * Check wether element is plane.
   *
   * @param  {Element|ModdleElement} element
   *
   * @return {boolean}
   */
  function isPlane(element) {
    var di = getDi(element);

    return is(di, 'bpmndi:BPMNPlane');
  }

  function addPlaneSuffix(id) {
    return id + planeSuffix;
  }

  function removePlaneSuffix(id) {
    return id.replace(new RegExp(planeSuffix + '$'), '');
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   */

  var OPEN_CLASS = 'bjs-breadcrumbs-shown';


  /**
   * Adds overlays that allow switching planes on collapsed subprocesses.
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {Canvas} canvas
   */
  function DrilldownBreadcrumbs(eventBus, elementRegistry, canvas) {
    var breadcrumbs = domify$1('<ul class="bjs-breadcrumbs"></ul>');
    var container = canvas.getContainer();
    var containerClasses = classes$1(container);
    container.appendChild(breadcrumbs);

    var businessObjectParents = [];

    // update breadcrumbs if name or ID of the primary shape changes
    eventBus.on('element.changed', function(event) {
      var shape = event.element,
          businessObject = getBusinessObject(shape);

      var isPresent = find(businessObjectParents, function(element) {
        return element === businessObject;
      });

      if (!isPresent) {
        return;
      }

      updateBreadcrumbs();
    });

    /**
     * Updates the displayed breadcrumbs. If no element is provided, only the
     * labels are updated.
     *
     * @param {Element} [element]
     */
    function updateBreadcrumbs(element) {
      if (element) {
        businessObjectParents = getBusinessObjectParentChain(element);
      }

      var path = businessObjectParents.flatMap(function(parent) {
        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(parent, 'bpmn:Process')) {
          var participant = elementRegistry.find(function(element) {
            var businessObject = getBusinessObject(element);

            return businessObject && businessObject.get('processRef') === parent;
          });

          parentPlane = participant && canvas.findRoot(participant.id);
        }

        if (!parentPlane) {
          return [];
        }

        var title = escapeHTML(parent.name || parent.id);
        var link = domify$1('<li><span class="bjs-crumb"><a title="' + title + '">' + title + '</a></span></li>');

        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(element) {
        breadcrumbs.appendChild(element);
      });
    }

    eventBus.on('root.set', function(event) {
      updateBreadcrumbs(event.element);
    });

  }

  DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'canvas' ];


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

  /**
   * Returns the parents for the element using the business object chain,
   * starting with the root element.
   *
   * @param {Shape} child
   *
   * @return {Shape}
   */
  function getBusinessObjectParentChain(child) {
    var businessObject = getBusinessObject(child);

    var parents = [];

    for (var element = businessObject; element; element = element.$parent) {
      if (is(element, 'bpmn:SubProcess') || is(element, 'bpmn:Process')) {
        parents.push(element);
      }
    }

    return parents.reverse();
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * 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$1();

    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;

      // Keep viewbox when replacing root elements
      if (!is(newRoot, 'bpmn:SubProcess') && !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$1() {

    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);
      }
    };
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../model/Types').Moddle} Moddle
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/core/Canvas').CanvasPlane} CanvasPlane
   *
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   */

  var DEFAULT_POSITION$1 = {
    x: 180,
    y: 160
  };

  /**
   * Hook into `import.render.start` and create new planes for diagrams with
   * collapsed subprocesses and all DI elements 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);
    });
  }

  /**
   * @param {ModdleElement} 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
      .filter(diagram => diagram.plane)
      .flatMap(diagram => self._createNewDiagrams(diagram.plane));

    newDiagrams.forEach(function(diagram) {
      self._movePlaneElementsToOrigin(diagram.plane);
    });
  };


  /**
   * Moves all DI elements from collapsed subprocesses to a new plane.
   *
   * @param {CanvasPlane} plane
   *
   * @return {ModdleElement[]} 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 businessObject = diElement.bpmnElement;

      if (!businessObject) {
        return;
      }

      var parent = businessObject.$parent;

      if (is(businessObject, 'bpmn:SubProcess') && !diElement.isExpanded) {
        collapsedElements.push(businessObject);
      }

      if (shouldMoveToPlane(businessObject, 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;
  };

  /**
   * @param {CanvasPlane} plane
   */
  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;
      }
    });
  };

  /**
   * @param {ModdleElement} diElement
   * @param {CanvasPlane} newPlane
   */
  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);
  };

  /**
   * @param {ModdleElement} businessObject
   *
   * @return {ModdleElement}
   */
  SubprocessCompatibility.prototype._createDiagram = function(businessObject) {
    var plane = this._moddle.create('bpmndi:BPMNPlane', {
      bpmnElement: businessObject
    });

    var diagram = this._moddle.create('bpmndi:BPMNDiagram', {
      plane: plane
    });

    plane.$parent = diagram;

    plane.bpmnElement = businessObject;

    diagram.$parent = this._definitions;

    this._definitions.diagrams.push(diagram);

    return diagram;
  };

  SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ];


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

  function findRootDiagram(element) {
    if (is(element, 'bpmndi:BPMNDiagram')) {
      return element;
    } else {
      return findRootDiagram(element.$parent);
    }
  }

  /**
   * @param {CanvasPlane} plane
   *
   * @return {Rect}
   */
  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);
  }

  /**
   * @param {ModdleElement} businessObject
   * @param {CanvasPlane} plane
   *
   * @return {boolean}
   */
  function shouldMoveToPlane(businessObject, plane) {
    var parent = businessObject.$parent;

    // don't move elements that are already on the plane
    if (!is(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(businessObject, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) {
      return false;
    }

    return true;
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/overlays/Overlays').default} Overlays
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Parent} Parent
   * @typedef {import('../../model/Types').Shape} Shape
   */

  var LOW_PRIORITY$p = 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';

  /**
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {Overlays} overlays
   * @param {Translate} translate
   */
  function DrilldownOverlayBehavior(
      canvas, eventBus, elementRegistry, overlays, translate
  ) {
    CommandInterceptor.call(this, eventBus);

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._elementRegistry = elementRegistry;
    this._overlays = overlays;
    this._translate = translate;

    var self = this;

    this.executed('shape.toggleCollapse', LOW_PRIORITY$p, 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$p, 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$p,
      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$p,
      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$2(DrilldownOverlayBehavior, CommandInterceptor);

  /**
   * @param {Shape} shape
   */
  DrilldownOverlayBehavior.prototype._updateDrilldownOverlay = function(shape) {
    var canvas = this._canvas;

    if (!shape) {
      return;
    }

    var root = canvas.findRoot(shape);

    if (root) {
      this._updateOverlayVisibility(root);
    }
  };

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  DrilldownOverlayBehavior.prototype._canDrillDown = function(element) {
    var canvas = this._canvas;

    return is(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element));
  };

  /**
   * Update the visibility of the drilldown overlay. If the plane has no elements,
   * the drilldown will only be shown when the element is selected.
   *
   * @param {Parent} element The collapsed root or shape.
   */
  DrilldownOverlayBehavior.prototype._updateOverlayVisibility = function(element) {
    var overlays = this._overlays;

    var businessObject = getBusinessObject(element);

    var overlay = overlays.get({ element: businessObject.id, type: 'drilldown' })[0];

    if (!overlay) {
      return;
    }

    var hasFlowElements = businessObject
      && businessObject.get('flowElements')
      && businessObject.get('flowElements').length;

    classes$1(overlay.html).toggle(EMPTY_MARKER, !hasFlowElements);
  };

  /**
   * Add a drilldown button to the given element assuming the plane has the same
   * ID as the element.
   *
   * @param {Shape} element The collapsed shape.
   */
  DrilldownOverlayBehavior.prototype._addOverlay = function(element) {
    var canvas = this._canvas,
        overlays = this._overlays,
        bo = getBusinessObject(element);

    var existingOverlays = overlays.get({ element: element, type: 'drilldown' });

    if (existingOverlays.length) {
      this._removeOverlay(element);
    }

    var button = domify$1('<button type="button" class="bjs-drilldown">' + ARROW_DOWN_SVG + '</button>'),
        elementName = bo.get('name') || bo.get('id'),
        title = this._translate('Open {element}', { element: elementName });
    button.setAttribute('title', title);

    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',
    'translate'
  ];

  var DrilldownModdule = {
    __depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ],
    __init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ],
    drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ],
    drilldownCentering: [ 'type', DrilldownCentering ],
    drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ],
    subprocessCompatibility: [ 'type', SubprocessCompatibility ]
  };

  /**
   * @typedef {import('../util/Types').Point} Point
   */

  function __stopPropagation(event) {
    if (!event || typeof event.stopPropagation !== 'function') {
      return;
    }

    event.stopPropagation();
  }

  /**
   * @param {import('../core/EventBus').Event} event
   *
   * @return {Event}
   */
  function getOriginal$1(event) {
    return event.originalEvent || event.srcEvent;
  }

  /**
   * @param {Event|import('../core/EventBus').Event} event
   */
  function stopPropagation$1(event) {
    __stopPropagation(event);
    __stopPropagation(getOriginal$1(event));
  }

  /**
   * @param {Event} event
   *
   * @return {Point|null}
   */
  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);
  }

  /**
   * @param {MouseEvent} event
   * @param {string} button
   *
   * @return {boolean}
   */
  function isButton(event, button) {
    return (getOriginal$1(event) || event).button === button;
  }

  /**
   * @param {MouseEvent} event
   *
   * @return {boolean}
   */
  function isPrimaryButton(event) {

    // button === 0 -> left áka primary mouse button
    return isButton(event, 0);
  }

  /**
   * @param {MouseEvent} event
   *
   * @return {boolean}
   */
  function isAuxiliaryButton(event) {

    // button === 1 -> auxiliary áka wheel button
    return isButton(event, 1);
  }

  /**
   * @param {MouseEvent} event
   *
   * @return {boolean}
   */
  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;
    }
  }

  /**
   * @param {MouseEvent} event
   *
   * @return {boolean}
   */
  function hasSecondaryModifier(event) {
    var originalEvent = getOriginal$1(event) || event;

    return isPrimaryButton(event) && originalEvent.shiftKey;
  }

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../draw/Styles').default} Styles
   *
   * @typedef {import('../../util/Types').Point} Point
   */

  function allowAll(event) { return true; }

  function allowPrimaryAndAuxiliary(event) {
    return isPrimaryButton(event) || isAuxiliaryButton(event);
  }

  var LOW_PRIORITY$o = 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
   * @param {ElementRegistry} elementRegistry
   * @param {Styles} styles
   */
  function InteractionEvents(eventBus, elementRegistry, styles) {

    var self = this;

    /**
     * Fire an interaction event.
     *
     * @param {string} type local event name, e.g. element.click.
     * @param {MouseEvent|TouchEvent} event native event
     * @param {Element} [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|TouchEvent} event
     * @param {Element} 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$o, function(event) {

      var element = event.element,
          gfx = event.gfx;

      eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx });
    });

    eventBus.on('interactionEvents.createHit', LOW_PRIORITY$o, 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$1({
        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 {Element} 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 {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} type
     * @param {Object} attrs
     *
     * @return {SVGElement}
     */
    this.createBoxHit = function(gfx, type, attrs) {

      attrs = assign$1({
        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 {Element} 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 {Element} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has left an element
   *
   * @event element.out
   *
   * @type {Object}
   * @property {Element} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has clicked an element
   *
   * @event element.click
   *
   * @type {Object}
   * @property {Element} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * An event indicating that the mouse has double clicked an element
   *
   * @event element.dblclick
   *
   * @type {Object}
   * @property {Element} 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 {Element} 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 {Element} 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 {Element} element
   * @property {SVGElement} gfx
   * @property {Event} originalEvent
   */

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var InteractionEventsModule$1 = {
    __init__: [ 'interactionEvents' ],
    interactionEvents: [ 'type', InteractionEvents ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  /**
   * A service that offers the current selection in a diagram.
   * Offers the api to control the selection, too.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function Selection(eventBus, canvas) {

    this._eventBus = eventBus;
    this._canvas = canvas;

    /**
     * @type {Object[]}
     */
    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' ];

  /**
   * Deselect an element.
   *
   * @param {Object} element The element to deselect.
   */
  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 });
    }
  };

  /**
   * Get the selected elements.
   *
   * @return {Object[]} The selected elements.
   */
  Selection.prototype.get = function() {
    return this._selectedElements;
  };

  /**
   * Check whether an element is selected.
   *
   * @param {Object} element The element.
   *
   * @return {boolean} Whether the element is selected.
   */
  Selection.prototype.isSelected = function(element) {
    return this._selectedElements.indexOf(element) !== -1;
  };


  /**
   * Select one or many elements.
   *
   * @param {Object|Object[]} elements The element(s) to select.
   * @param {boolean} [add] Whether to add the element(s) to the selected elements.
   * 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 });
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  var MARKER_HOVER = 'hover',
      MARKER_SELECTED = 'selected';

  /**
   * 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) {
    this._canvas = canvas;

    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);
        }
      });
    });
  }

  SelectionVisuals.$inject = [
    'canvas',
    'eventBus'
  ];

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('./Selection').default} Selection
   */

  /**
   * @param {EventBus} eventBus
   * @param {Selection} selection
   * @param {Canvas} canvas
   * @param {ElementRegistry} elementRegistry
   */
  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 SHIFT pressed
      var add = 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;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var SelectionModule = {
    __init__: [ 'selectionVisuals', 'selectionBehavior' ],
    __depends__: [
      InteractionEventsModule$1,
    ],
    selection: [ 'type', Selection ],
    selectionVisuals: [ 'type', SelectionVisuals ],
    selectionBehavior: [ 'type', SelectionBehavior ]
  };

  /**
   * @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions
   */

  /**
   * A viewer for BPMN 2.0 diagrams.
   *
   * Have a look at {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/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(...);
   * ```
   *
   * @template [ServiceMap=null]
   *
   * @extends BaseViewer<ServiceMap>
   *
   * @param {BaseViewerOptions} [options] The options to configure the viewer.
   */
  function Viewer(options) {
    BaseViewer.call(this, options);
  }

  e$2(Viewer, BaseViewer);

  // modules the viewer is composed of
  Viewer.prototype._modules = [
    CoreModule,
    DrilldownModdule,
    OverlaysModule,
    SelectionModule,
    TranslateModule
  ];

  // default moddle extensions the viewer is composed of
  Viewer.prototype._moddleExtensions = {};

  var KEYS_COPY = [ 'c', 'C' ];
  var KEYS_PASTE = [ 'v', 'V' ];
  var KEYS_REDO = [ 'y', 'Y' ];
  var KEYS_UNDO = [ 'z', '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
   * @return {boolean}
   */
  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|string[]} keys
   * @param {KeyboardEvent} event
   * @return {boolean}
   */
  function isKey(keys, event) {
    keys = isArray$3(keys) ? keys : [ keys ];

    return keys.indexOf(event.key) !== -1 || keys.indexOf(event.code) !== -1;
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isShift(event) {
    return event.shiftKey;
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isCopy(event) {
    return isCmd(event) && isKey(KEYS_COPY, event);
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isPaste(event) {
    return isCmd(event) && isKey(KEYS_PASTE, event);
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isUndo(event) {
    return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
  }

  /**
   * @param {KeyboardEvent} event
   */
  function isRedo(event) {
    return isCmd(event) && (
      isKey(KEYS_REDO, event) || (
        isKey(KEYS_UNDO, event) && isShift(event)
      )
    );
  }

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {({ keyEvent: KeyboardEvent }) => any} Listener
   */

  var KEYDOWN_EVENT = 'keyboard.keydown',
      KEYUP_EVENT = 'keyboard.keyup';

  var DEFAULT_PRIORITY$4 = 1000;

  var compatMessage = 'Keyboard binding is now implicit; explicit binding to an element got removed. For more information, see https://github.com/bpmn-io/diagram-js/issues/661';


  /**
   * A keyboard abstraction that may be activated and
   * deactivated by users at will, consuming global 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.
   *
   * Specify the initial keyboard binding state via the
   * `keyboard.bind=true|false` configuration option.
   *
   * @param {Object} config
   * @param {boolean} [config.bind]
   * @param {EventBus} eventBus
   */
  function Keyboard(config, eventBus) {
    var self = this;

    this._config = 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();
    });

    if (config.bindTo) {
      console.error('unsupported configuration <keyboard.bindTo>', new Error(compatMessage));
    }

    var bind = config && config.bind !== false;

    eventBus.on('canvas.init', function(event) {
      self._target = event.svg;

      if (bind) {
        self.bind();
      }

      self._fire('init');
    });

  }

  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 false;
  };

  /**
   * Bind keyboard events to the given DOM node.
   *
   * @overlord
   * @deprecated No longer in use since version 15.0.0.
   *
   * @param {EventTarget} node
   */
  /**
   * Bind keyboard events to the canvas node.
   */
  Keyboard.prototype.bind = function(node) {

    // legacy <node> argument provided
    if (node) {
      console.error('unsupported argument <node>', new Error(compatMessage));
    }

    // make sure that the keyboard is only bound once to the DOM
    this.unbind();

    node = this._node = this._target;

    // bind key events
    event.bind(node, 'keydown', this._keydownHandler);
    event.bind(node, 'keyup', this._keyupHandler);

    this._fire('bind');
  };

  /**
   * @return {EventTarget}
   */
  Keyboard.prototype.getBinding = function() {
    return this._node;
  };

  Keyboard.prototype.unbind = function() {
    var node = this._node;

    if (node) {
      this._fire('unbind');

      // unbind key events
      event.unbind(node, 'keydown', this._keydownHandler);
      event.unbind(node, 'keyup', this._keyupHandler);
    }

    this._node = null;
  };

  /**
   * @param {string} event
   */
  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 {Listener} listener
   * @param {string} [type='keyboard.keydown']
   */
  Keyboard.prototype.addListener = function(priority, listener, type) {
    if (isFunction(priority)) {
      type = listener;
      listener = priority;
      priority = DEFAULT_PRIORITY$4;
    }

    this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
  };

  /**
   * Remove a listener function.
   *
   * @param {Listener} listener
   * @param {string} [type='keyboard.keydown']
   */
  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;

  var LOW_PRIORITY$n = 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$n, 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;
      }
    });
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var KeyboardModule$1 = {
    __init__: [ 'keyboard', 'keyboardBindings' ],
    keyboard: [ 'type', Keyboard ],
    keyboardBindings: [ 'type', KeyboardBindings ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../features/keyboard/Keyboard').default} Keyboard
   */

  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$1({}, 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;
      }
    }

    /**
     * @param {{
     *   direction: 'up' | 'down' | 'left' | 'right';
     *   speed: number;
     * }} options
     */
    this.moveCanvas = function(options) {

      var dx = 0,
          dy = 0,
          speed = options.speed;

      var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1);

      switch (options.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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var KeyboardMoveModule = {
    __depends__: [
      KeyboardModule$1
    ],
    __init__: [ 'keyboardMove' ],
    keyboardMove: [ 'type', KeyboardMove ]
  };

  var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/;

  /**
   * @param {string} mode
   */
  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);
  }

  /**
   * @typedef {import('../core/EventBus').default} EventBus
   */

  var TRAP_PRIORITY = 5000;

  /**
   * Installs a click trap that prevents a ghost click following a dragging operation.
   *
   * @param {EventBus} eventBus
   * @param {string} [eventName='element.click']
   *
   * @return {() => void} 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);
    };
  }

  /**
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   */

  /**
   * @param {Rect} bounds
   * @return {Point}
   */
  function center(bounds) {
    return {
      x: bounds.x + (bounds.width / 2),
      y: bounds.y + (bounds.height / 2)
    };
  }


  /**
   * @param {Point} a
   * @param {Point} b
   * @return {Point}
   */
  function delta(a, b) {
    return {
      x: a.x - b.x,
      y: a.y - b.y
    };
  }

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  var THRESHOLD$1 = 15;


  /**
   * Move the canvas via mouse.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function MoveCanvas(eventBus, canvas) {

    var context;

    function handleMousedown(event) {
      return handleStart(event.originalEvent);
    }

    // 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('canvas.focus.changed', function(event) {
      if (event.focused) {
        eventBus.on('element.mousedown', 500, handleMousedown);
      } else {
        eventBus.off('element.mousedown', handleMousedown);
      }
    });


    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$1) {
      event.unbind(document, 'mousemove', handleMove);
      event.unbind(document, 'mouseup', handleEnd);

      context = null;

      unset();
    }

    function handleStart(event$1) {

      // event is already handled by '.djs-draggable'
      if (closest(event$1.target, '.djs-draggable')) {
        return;
      }

      var button = event$1.button;

      // reject right mouse button or modifier key
      if (button >= 2 || event$1.ctrlKey || event$1.shiftKey || event$1.altKey) {
        return;
      }

      context = {
        button: button,
        start: toPoint(event$1)
      };

      event.bind(document, 'mousemove', handleMove);
      event.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));
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var MoveCanvasModule = {
    __init__: [ 'moveCanvas' ],
    moveCanvas: [ 'type', MoveCanvas ]
  };

  /**
   * Get the logarithm of x with base 10.
   *
   * @param {number} x
   */
  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
   * @param {number} steps
   */
  function getStepSize(range, steps) {

    var minLinearRange = log10(range.min),
        maxLinearRange = log10(range.max);

    var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);

    return absoluteLinearRange / steps;
  }

  /**
   * @param {Object} range
   * @param {number} range.min
   * @param {number} range.max
   * @param {number} scale
   */
  function cap(range, scale) {
    return Math.max(range.min, Math.min(range.max, scale));
  }

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').ScrollDelta} ScrollDelta
   */

  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$2(this._handleWheel, this);

    this._totalDelta = 0;
    this._scale = config.scale || DEFAULT_SCALE;

    var self = this;

    eventBus.on('canvas.focus.changed', function(event) {
      self._init(event.focused && config.enabled !== false);
    });
  }

  ZoomScroll.$inject = [
    'config.zoomScroll',
    'eventBus',
    'canvas'
  ];

  /**
   * @param {ScrollDelta} delta
   */
  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 {Point} 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) {

    if (!this._canvas.isFocused()) {
      return;
    }

    var element = this._container;

    event.preventDefault();

    // pinch to zoom is mapped to wheel + ctrlKey = true
    // in modern browsers (!)

    var isZoom = event.ctrlKey || (isMac() && event.metaKey);

    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 zooming out).
   * @param {Point} [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 {Point} [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
      event[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false);
    }

    this._enabled = newEnabled;

    return newEnabled;
  };


  ZoomScroll.prototype._init = function(newEnabled) {
    this.toggle(newEnabled);
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ZoomScrollModule = {
    __init__: [ 'zoomScroll' ],
    zoomScroll: [ 'type', ZoomScroll ]
  };

  /**
   * @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions
   */

  /**
   * A viewer with mouse and keyboard navigation features.
   *
   * @template [ServiceMap=null]
   *
   * @extends Viewer<ServiceMap>
   *
   * @param {BaseViewerOptions} [options]
   */
  function NavigatedViewer(options) {
    Viewer.call(this, options);
  }

  e$2(NavigatedViewer, Viewer);


  NavigatedViewer.prototype._navigationModules = [
    KeyboardMoveModule,
    MoveCanvasModule,
    ZoomScrollModule
  ];

  NavigatedViewer.prototype._modules = [].concat(
    Viewer.prototype._modules,
    NavigatedViewer.prototype._navigationModules
  );

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').Dimension} Dimension
   *
   * @typedef { 'top' | 'right' | 'bottom' | 'left' | 'center' | 'middle' } Alignment
   */

  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 alignments.
   *
   * @type {Record<string, 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;
    }
  };

  /**
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  function AlignElements$1(modeling, rules) {
    this._modeling = modeling;
    this._rules = rules;
  }

  AlignElements$1.$inject = [ 'modeling', 'rules' ];


  /**
   * Get relevant axis and dimension for given alignment.
   *
   * @param {Alignment} type
   *
   * @return { {
   *   axis: Axis;
   *   dimension: 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 point on relevant axis for given alignment.
   *
   * @param {Alignment} type
   * @param {Element[]} sortedElements
   *
   * @return {Partial<Record<Alignment, number>>}
   */
  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;
  };

  /**
   * Align elements on relevant axis for given alignment.
   *
   * @param {Element[]} elements
   * @param {Alignment} type
   */
  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);
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var AlignElementsModule$1 = {
    __init__: [ 'alignElements' ],
    alignElements: [ 'type', AlignElements$1 ]
  };

  const Ids = new IdGenerator();


  /**
   * @typedef { {
   *   promise: Promise<unknown>,
   *   executionId: number
   * } } ScheduledTask
   */

  /**
   * A utility that allows you to schedule async tasks.
   *
   * @class
   * @constructor
   *
   * @param { import('../core/EventBus').default } eventBus
   */
  function Scheduler(eventBus) {

    /**
     * @type { Record<string, ScheduledTask> }
     */
    this._scheduled = {};

    eventBus.on('diagram.destroy', () => {
      Object.keys(this._scheduled).forEach(id => {
        this.cancel(id);
      });
    });
  }

  Scheduler.$inject = [ 'eventBus' ];

  /**
   * Schedule execution of a task in the next tick.
   *
   * Call with an id to ensure only the latest call will be executed.
   *
   * @template T

   * @param {(...args: any[]) => T} taskFn function to be executed
   * @param {string} [id] identifying the task to ensure uniqueness
   *
   * @return Promise<T> result of the executed task
   */
  Scheduler.prototype.schedule = function(taskFn, id = Ids.next()) {

    this.cancel(id);

    const newScheduled = this._schedule(taskFn, id);

    this._scheduled[id] = newScheduled;

    return newScheduled.promise;
  };

  Scheduler.prototype._schedule = function(taskFn, id) {

    const deferred = defer();

    const executionId = setTimeout(() => {

      try {
        this._scheduled[id] = null;

        try {
          deferred.resolve(taskFn());
        } catch (error) {
          deferred.reject(error);
        }
      } catch (error) {
        console.error('Scheduler#_schedule execution failed', error);
      }
    });

    return {
      executionId,
      promise: deferred.promise
    };
  };

  /**
   * Cancel a previously scheduled task.
   *
   * @param {string} id
   */
  Scheduler.prototype.cancel = function(id) {

    const scheduled = this._scheduled[id];

    if (scheduled) {
      this._cancel(scheduled);

      this._scheduled[id] = null;
    }
  };

  Scheduler.prototype._cancel = function(scheduled) {
    clearTimeout(scheduled.executionId);
  };

  /**
   * @return { {
   *   promise: Promise,
   *   resolve: Function,
   *   reject: Function
   * } }
   */
  function defer() {

    const deferred = {};

    deferred.promise = new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    });

    return deferred;
  }

  var SchedulerModule = {
    scheduler: [ 'type', Scheduler ]
  };

  var MARKER_HIDDEN$1 = 'djs-element-hidden';

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../util/Types').Rect} Rect
   * @typedef {import('../../util/Types').RectTRBL} RectTRBL
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../scheduler/Scheduler').default} Scheduler
   *
   * @typedef {import('./ContextPadProvider').default} ContextPadProvider
   * @typedef {import('./ContextPadProvider').ContextPadEntries} ContextPadEntries
   *
   */

  /**
   * @template {Element} [ElementType=Element]
   *
   * @typedef {ElementType|ElementType[]} ContextPadTarget
   */

  var entrySelector = '.entry';

  var DEFAULT_PRIORITY$3 = 1000;
  var CONTEXT_PAD_MARGIN = 8;
  var HOVER_DELAY = 300;

  /**
   * A context pad that displays element specific, contextual actions next
   * to a diagram element.
   *
   * @param {Canvas} canvas
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Scheduler} scheduler
   */
  function ContextPad(canvas, elementRegistry, eventBus, scheduler) {

    this._canvas = canvas;
    this._elementRegistry = elementRegistry;
    this._eventBus = eventBus;
    this._scheduler = scheduler;

    this._current = null;

    this._init();
  }

  ContextPad.$inject = [
    'canvas',
    'elementRegistry',
    'eventBus',
    'scheduler'
  ];


  /**
   * 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 target = current.target;

      var targets = isArray$3(target) ? target : [ target ];

      var targetsChanged = targets.filter(function(element) {
        return elements.includes(element);
      });

      if (targetsChanged.length) {

        // (1) close
        self.close();

        var targetsNew = targets.filter(function(element) {
          return self._elementRegistry.get(element.id);
        });

        if (targetsNew.length) {

          // (2) re-open with new targets being all previous targets that still
          // exist
          self._updateAndOpen(targetsNew.length > 1 ? targetsNew : targetsNew[ 0 ]);
        }
      }
    });

    this._eventBus.on('canvas.viewbox.changed', function() {
      self._updatePosition();
    });

    this._eventBus.on('element.marker.update', function(event) {
      if (!self.isOpen()) {
        return;
      }

      var element = event.element;

      var current = self._current;

      var targets = isArray$3(current.target) ? current.target : [ current.target ];

      if (!targets.includes(element)) {
        return;
      }

      self._updateVisibility();
    });

    this._container = this._createContainer();
  };

  ContextPad.prototype._createContainer = function() {
    var container = domify$1('<div class="djs-context-pad-parent"></div>');

    this._canvas.getContainer().appendChild(container);

    return container;
  };

  /**
   * @overlord
   *
   * Register a context pad provider with the default priority. See
   * {@link ContextPadProvider} for examples.
   *
   * @param {ContextPadProvider} provider
   */

  /**
   * Register a context pad provider with the given priority. See
   * {@link ContextPadProvider} for examples.
   *
   * @param {number} priority
   * @param {ContextPadProvider} provider
   */
  ContextPad.prototype.registerProvider = function(priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY$3;
    }

    this._eventBus.on('contextPad.getProviders', priority, function(event) {
      event.providers.push(provider);
    });
  };


  /**
   * Get context pad entries for given elements.
   *
   * @param {ContextPadTarget} target
   *
   * @return {ContextPadEntries} 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 via DOM event.
   *
   * The entry to trigger is determined by the target element.
   *
   * @param {string} action
   * @param {Event} event
   * @param {boolean} [autoActivate=false]
   */
  ContextPad.prototype.trigger = function(action, event, autoActivate) {
    var self = this;

    var entry,
        originalEvent,
        button = event.delegateTarget || event.target;

    if (!button) {
      return event.preventDefault();
    }

    entry = attr$1(button, 'data-action');
    originalEvent = event.originalEvent || event;

    if (action === 'mouseover') {
      this._timeout = setTimeout(function() {
        self._mouseout = self.triggerEntry(entry, 'hover', originalEvent, autoActivate);
      }, HOVER_DELAY);

      return;
    } else if (action === 'mouseout') {
      clearTimeout(this._timeout);

      if (this._mouseout) {
        this._mouseout();

        this._mouseout = null;
      }

      return;
    }

    return this.triggerEntry(entry, action, originalEvent, autoActivate);
  };

  /**
   * Trigger action on context pad entry entry, e.g. click, mouseover or mouseout.
   *
   * @param {string} entryId
   * @param {string} action
   * @param {Event} event
   * @param {boolean} [autoActivate=false]
   */
  ContextPad.prototype.triggerEntry = function(entryId, action, event, autoActivate) {

    if (!this.isShown()) {
      return;
    }

    var target = this._current.target,
        entries = this._current.entries;

    var entry = entries[entryId];

    if (!entry) {
      return;
    }

    var handler = entry.action;

    if (this._eventBus.fire('contextPad.trigger', { entry, event }) === false) {
      return;
    }

    // simple action (via callback function)
    if (isFunction(handler)) {
      if (action === 'click') {
        return handler(event, target, autoActivate);
      }
    } else {
      if (handler[action]) {
        return handler[action](event, 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),
        html = this._createHtml(target),
        image;

    forEach$1(entries, function(entry, id) {
      var grouping = entry.group || 'default',
          control = domify$1(entry.html || '<div class="entry" draggable="true"></div>'),
          container;

      attr$1(control, 'data-action', id);

      container = query('[data-group=' + escapeCSS(grouping) + ']', html);
      if (!container) {
        container = domify$1('<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$1('<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 = {
      entries,
      html,
      target,
    };

    this._updatePosition();

    this._updateVisibility();

    this._eventBus.fire('contextPad.open', { current: this._current });
  };

  /**
   * @param {ContextPadTarget} target
   *
   * @return {HTMLElement}
   */
  ContextPad.prototype._createHtml = function(target) {
    var self = this;

    var html = domify$1('<div class="djs-context-pad"></div>');

    delegate.bind(html, entrySelector, 'click', function(event) {
      self.trigger('click', event);
    });

    delegate.bind(html, entrySelector, 'dragstart', function(event) {
      self.trigger('dragstart', event);
    });

    delegate.bind(html, entrySelector, 'mouseover', function(event) {
      self.trigger('mouseover', event);
    });

    delegate.bind(html, entrySelector, 'mouseout', function(event) {
      self.trigger('mouseout', event);
    });

    // stop propagation of mouse events
    event.bind(html, 'mousedown', function(event) {
      event.stopPropagation();
    });

    this._container.appendChild(html);

    this._eventBus.fire('contextPad.create', {
      target: target,
      pad: html
    });

    return html;
  };

  /**
   * @param {ContextPadTarget} target
   *
   * @return { { html: HTMLElement } }
   */
  ContextPad.prototype.getPad = function(target) {
    console.warn(new Error('ContextPad#getPad is deprecated and will be removed in future library versions, cf. https://github.com/bpmn-io/diagram-js/pull/888'));

    let html;

    if (this.isOpen() && targetsEqual(this._current.target, target)) {
      html = this._current.html;
    } else {
      html = this._createHtml(target);
    }

    return { html };
  };


  /**
   * Close the context pad
   */
  ContextPad.prototype.close = function() {
    if (!this.isOpen()) {
      return;
    }

    clearTimeout(this._timeout);

    this._container.innerHTML = '';

    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 currentTarget.includes(element);
        })
      );
    } else {
      return currentTarget === target;
    }
  };


  /**
   * Check if pad is open and not hidden.
   *
   * @return {boolean}
   */
  ContextPad.prototype.isShown = function() {
    return this.isOpen() && classes$1(this._current.html).has('open');
  };

  /**
   * Show context pad.
   */
  ContextPad.prototype.show = function() {
    if (!this.isOpen()) {
      return;
    }

    classes$1(this._current.html).add('open');

    this._updatePosition();

    this._eventBus.fire('contextPad.show', { current: this._current });
  };

  /**
   * Hide context pad.
   */
  ContextPad.prototype.hide = function() {
    if (!this.isOpen()) {
      return;
    }

    classes$1(this._current.html).remove('open');

    this._eventBus.fire('contextPad.hide', { current: this._current });
  };

  /**
   * Get context pad position.
   *
   * If target is connection context pad will be positioned at connection end.
   *
   * If multiple targets context pad will be placed at top right corner bounding
   * box.
   *
   * @param {ContextPadTarget} target
   *
   * @return {RectTRBL & { x: number, y: number }}
   */
  ContextPad.prototype._getPosition = function(target) {
    if (!isArray$3(target) && isConnection(target)) {
      var viewbox = this._canvas.viewbox();

      var lastWaypoint = getLastWaypoint(target);

      var x = lastWaypoint.x * viewbox.scale - viewbox.x * viewbox.scale,
          y = lastWaypoint.y * viewbox.scale - viewbox.y * viewbox.scale;

      return {
        left: x + CONTEXT_PAD_MARGIN * this._canvas.zoom(),
        top: y
      };
    }

    var container = this._canvas.getContainer();

    var containerBounds = container.getBoundingClientRect();

    var targetBounds = this._getTargetBounds(target);

    return {
      left: targetBounds.right - containerBounds.left + CONTEXT_PAD_MARGIN * this._canvas.zoom(),
      top: targetBounds.top - containerBounds.top
    };
  };

  /**
   * Update context pad position.
   */
  ContextPad.prototype._updatePosition = function() {

    const updateFn = () => {
      if (!this.isOpen()) {
        return;
      }

      var html = this._current.html;

      var position = this._getPosition(this._current.target);

      if ('x' in position && 'y' in position) {
        html.style.left = position.x + 'px';
        html.style.top = position.y + 'px';
      } else {
        [
          'top',
          'right',
          'bottom',
          'left'
        ].forEach(function(key) {
          if (key in position) {
            html.style[ key ] = position[ key ] + 'px';
          }
        });
      }
    };

    this._scheduler.schedule(updateFn, 'ContextPad#_updatePosition');
  };

  /**
   * Update context pad visibility. Hide if any of the target elements is hidden
   * using the `djs-element-hidden` or `djs-label-hidden` markers.
   */
  ContextPad.prototype._updateVisibility = function() {

    const updateFn = () => {
      if (!this.isOpen()) {
        return;
      }

      var self = this;

      var target = this._current.target;

      var targets = isArray$3(target) ? target : [ target ];

      var isHidden = targets.some(function(target) {
        return self._canvas.hasMarker(target, MARKER_HIDDEN$1);
      });

      if (isHidden) {
        self.hide();
      } else {
        self.show();
      }
    };

    this._scheduler.schedule(updateFn, 'ContextPad#_updateVisibility');
  };

  /**
   * Get bounding client rect of target element(s).
   *
   * @param {ContextPadTarget} target
   *
   * @returns {Rect & RectTRBL}
   */
  ContextPad.prototype._getTargetBounds = function(target) {
    var self = this;

    var elements = isArray$3(target) ? target : [ target ];

    var elementsGfx = elements.map(function(element) {
      return self._canvas.getGraphics(element);
    });

    return elementsGfx.reduce(function(bounds, elementGfx) {
      const elementBounds = elementGfx.getBoundingClientRect();

      bounds.top = Math.min(bounds.top, elementBounds.top);
      bounds.right = Math.max(bounds.right, elementBounds.right);
      bounds.bottom = Math.max(bounds.bottom, elementBounds.bottom);
      bounds.left = Math.min(bounds.left, elementBounds.left);

      bounds.x = bounds.left;
      bounds.y = bounds.top;

      bounds.width = bounds.right - bounds.left;
      bounds.height = bounds.bottom - bounds.top;

      return bounds;
    }, {
      top: Infinity,
      right: -Infinity,
      bottom: -Infinity,
      left: Infinity
    });
  };

  // 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);
    });
  }

  function getLastWaypoint(connection) {
    return connection.waypoints[connection.waypoints.length - 1];
  }

  /**
   * @param {ContextPadTarget} target
   * @param {ContextPadTarget} otherTarget
   *
   * @return {boolean}
   */
  function targetsEqual(target, otherTarget) {
    target = isArray$3(target) ? target : [ target ];
    otherTarget = isArray$3(otherTarget) ? otherTarget : [ otherTarget ];

    return target.length === otherTarget.length
      && every(target, function(element) {
        return otherTarget.includes(element);
      });
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ContextPadModule$1 = {
    __depends__: [
      InteractionEventsModule$1,
      SchedulerModule,
      OverlaysModule
    ],
    contextPad: [ 'type', ContextPad ]
  };

  var n$1,l$1,u$1,i$1,o$1,r$2,f$1,c$1={},s$1=[],a$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,h$1=Array.isArray;function v$1(n,l){for(var u in l)n[u]=l[u];return n}function p$1(n){var l=n.parentNode;l&&l.removeChild(n);}function y$1(l,u,t){var i,o,r,f={};for(r in u)"key"==r?i=u[r]:"ref"==r?o=u[r]:f[r]=u[r];if(arguments.length>2&&(f.children=arguments.length>3?n$1.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(r in l.defaultProps) void 0===f[r]&&(f[r]=l.defaultProps[r]);return d$1(l,f,i,o,null)}function d$1(n,t,i,o,r){var f={type:n,props:t,key:i,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:null==r?++u$1:r,__i:-1,__u:0};return null==r&&null!=l$1.vnode&&l$1.vnode(f),f}function g(n){return n.children}function b(n,l){this.props=n,this.context=l;}function m$2(n,l){if(null==l)return n.__?m$2(n.__,n.__i+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return "function"==typeof n.type?m$2(n):null}function w$1(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return w$1(n)}}function k$1(n){(!n.__d&&(n.__d=true)&&i$1.push(n)&&!x$1.__r++||o$1!==l$1.debounceRendering)&&((o$1=l$1.debounceRendering)||r$2)(x$1);}function x$1(){var n,u,t,o,r,e,c,s,a;for(i$1.sort(f$1);n=i$1.shift();)n.__d&&(u=i$1.length,o=void 0,e=(r=(t=n).__v).__e,s=[],a=[],(c=t.__P)&&((o=v$1({},r)).__v=r.__v+1,l$1.vnode&&l$1.vnode(o),L(c,o,r,t.__n,void 0!==c.ownerSVGElement,32&r.__u?[e]:null,s,null==e?m$2(r):e,!!(32&r.__u),a),o.__v=r.__v,o.__.__k[o.__i]=o,M(s,o,a),o.__e!=e&&w$1(o)),i$1.length>u&&i$1.sort(f$1));x$1.__r=0;}function C$1(n,l,u,t,i,o,r,f,e,a,h){var v,p,y,d,_,g=t&&t.__k||s$1,b=l.length;for(u.__d=e,P(u,l,g),e=u.__d,v=0;v<b;v++)null!=(y=u.__k[v])&&"boolean"!=typeof y&&"function"!=typeof y&&(p=-1===y.__i?c$1:g[y.__i]||c$1,y.__i=v,L(n,y,p,i,o,r,f,e,a,h),d=y.__e,y.ref&&p.ref!=y.ref&&(p.ref&&z$1(p.ref,null,y),h.push(y.ref,y.__c||d,y)),null==_&&null!=d&&(_=d),65536&y.__u||p.__k===y.__k?e=S(y,e,n):"function"==typeof y.type&&void 0!==y.__d?e=y.__d:d&&(e=d.nextSibling),y.__d=void 0,y.__u&=-196609);u.__d=e,u.__e=_;}function P(n,l,u){var t,i,o,r,f,e=l.length,c=u.length,s=c,a=0;for(n.__k=[],t=0;t<e;t++)r=t+a,null!=(i=n.__k[t]=null==(i=l[t])||"boolean"==typeof i||"function"==typeof i?null:"string"==typeof i||"number"==typeof i||"bigint"==typeof i||i.constructor==String?d$1(null,i,null,null,null):h$1(i)?d$1(g,{children:i},null,null,null):void 0===i.constructor&&i.__b>0?d$1(i.type,i.props,i.key,i.ref?i.ref:null,i.__v):i)?(i.__=n,i.__b=n.__b+1,f=H(i,u,r,s),i.__i=f,o=null,-1!==f&&(s--,(o=u[f])&&(o.__u|=131072)),null==o||null===o.__v?(-1==f&&a--,"function"!=typeof i.type&&(i.__u|=65536)):f!==r&&(f===r+1?a++:f>r?s>e-r?a+=f-r:a--:f<r?f==r-1&&(a=f-r):a=0,f!==t+a&&(i.__u|=65536))):(o=u[r])&&null==o.key&&o.__e&&0==(131072&o.__u)&&(o.__e==n.__d&&(n.__d=m$2(o)),N(o,o,false),u[r]=null,s--);if(s)for(t=0;t<c;t++)null!=(o=u[t])&&0==(131072&o.__u)&&(o.__e==n.__d&&(n.__d=m$2(o)),N(o,o));}function S(n,l,u){var t,i;if("function"==typeof n.type){for(t=n.__k,i=0;t&&i<t.length;i++)t[i]&&(t[i].__=n,l=S(t[i],l,u));return l}n.__e!=l&&(u.insertBefore(n.__e,l||null),l=n.__e);do{l=l&&l.nextSibling;}while(null!=l&&8===l.nodeType);return l}function H(n,l,u,t){var i=n.key,o=n.type,r=u-1,f=u+1,e=l[u];if(null===e||e&&i==e.key&&o===e.type&&0==(131072&e.__u))return u;if(t>(null!=e&&0==(131072&e.__u)?1:0))for(;r>=0||f<l.length;){if(r>=0){if((e=l[r])&&0==(131072&e.__u)&&i==e.key&&o===e.type)return r;r--;}if(f<l.length){if((e=l[f])&&0==(131072&e.__u)&&i==e.key&&o===e.type)return f;f++;}}return  -1}function I(n,l,u){"-"===l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||a$1.test(l)?u:u+"px";}function T(n,l,u,t,i){var o;n:if("style"===l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof t&&(n.style.cssText=t=""),t)for(l in t)u&&l in u||I(n.style,l,"");if(u)for(l in u)t&&u[l]===t[l]||I(n.style,l,u[l]);}else if("o"===l[0]&&"n"===l[1])o=l!==(l=l.replace(/(PointerCapture)$|Capture$/i,"$1")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+o]=u,u?t?u.u=t.u:(u.u=Date.now(),n.addEventListener(l,o?D$1:A$1,o)):n.removeEventListener(l,o?D$1:A$1,o);else {if(i)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==l&&"height"!==l&&"href"!==l&&"list"!==l&&"form"!==l&&"tabIndex"!==l&&"download"!==l&&"rowSpan"!==l&&"colSpan"!==l&&"role"!==l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||false===u&&"-"!==l[4]?n.removeAttribute(l):n.setAttribute(l,u));}}function A$1(n){if(this.l){var u=this.l[n.type+false];if(n.t){if(n.t<=u.u)return}else n.t=Date.now();return u(l$1.event?l$1.event(n):n)}}function D$1(n){if(this.l)return this.l[n.type+true](l$1.event?l$1.event(n):n)}function L(n,u,t,i,o,r,f,e,c,s){var a,p,y,d,_,m,w,k,x,P,S,$,H,I,T,A=u.type;if(void 0!==u.constructor)return null;128&t.__u&&(c=!!(32&t.__u),r=[e=u.__e=t.__e]),(a=l$1.__b)&&a(u);n:if("function"==typeof A)try{if(k=u.props,x=(a=A.contextType)&&i[a.__c],P=a?x?x.props.value:a.__:i,t.__c?w=(p=u.__c=t.__c).__=p.__E:("prototype"in A&&A.prototype.render?u.__c=p=new A(k,P):(u.__c=p=new b(k,P),p.constructor=A,p.render=O),x&&x.sub(p),p.props=k,p.state||(p.state={}),p.context=P,p.__n=i,y=p.__d=!0,p.__h=[],p._sb=[]),null==p.__s&&(p.__s=p.state),null!=A.getDerivedStateFromProps&&(p.__s==p.state&&(p.__s=v$1({},p.__s)),v$1(p.__s,A.getDerivedStateFromProps(k,p.__s))),d=p.props,_=p.state,p.__v=u,y)null==A.getDerivedStateFromProps&&null!=p.componentWillMount&&p.componentWillMount(),null!=p.componentDidMount&&p.__h.push(p.componentDidMount);else {if(null==A.getDerivedStateFromProps&&k!==d&&null!=p.componentWillReceiveProps&&p.componentWillReceiveProps(k,P),!p.__e&&(null!=p.shouldComponentUpdate&&!1===p.shouldComponentUpdate(k,p.__s,P)||u.__v===t.__v)){for(u.__v!==t.__v&&(p.props=k,p.state=p.__s,p.__d=!1),u.__e=t.__e,u.__k=t.__k,u.__k.forEach(function(n){n&&(n.__=u);}),S=0;S<p._sb.length;S++)p.__h.push(p._sb[S]);p._sb=[],p.__h.length&&f.push(p);break n}null!=p.componentWillUpdate&&p.componentWillUpdate(k,p.__s,P),null!=p.componentDidUpdate&&p.__h.push(function(){p.componentDidUpdate(d,_,m);});}if(p.context=P,p.props=k,p.__P=n,p.__e=!1,$=l$1.__r,H=0,"prototype"in A&&A.prototype.render){for(p.state=p.__s,p.__d=!1,$&&$(u),a=p.render(p.props,p.state,p.context),I=0;I<p._sb.length;I++)p.__h.push(p._sb[I]);p._sb=[];}else do{p.__d=!1,$&&$(u),a=p.render(p.props,p.state,p.context),p.state=p.__s;}while(p.__d&&++H<25);p.state=p.__s,null!=p.getChildContext&&(i=v$1(v$1({},i),p.getChildContext())),y||null==p.getSnapshotBeforeUpdate||(m=p.getSnapshotBeforeUpdate(d,_)),C$1(n,h$1(T=null!=a&&a.type===g&&null==a.key?a.props.children:a)?T:[T],u,t,i,o,r,f,e,c,s),p.base=u.__e,u.__u&=-161,p.__h.length&&f.push(p),w&&(p.__E=p.__=null);}catch(n){u.__v=null,c||null!=r?(u.__e=e,u.__u|=c?160:32,r[r.indexOf(e)]=null):(u.__e=t.__e,u.__k=t.__k),l$1.__e(n,u,t);}else null==r&&u.__v===t.__v?(u.__k=t.__k,u.__e=t.__e):u.__e=j$1(t.__e,u,t,i,o,r,f,c,s);(a=l$1.diffed)&&a(u);}function M(n,u,t){u.__d=void 0;for(var i=0;i<t.length;i++)z$1(t[i],t[++i],t[++i]);l$1.__c&&l$1.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u);});}catch(n){l$1.__e(n,u.__v);}});}function j$1(l,u,t,i,o,r,f,e,s){var a,v,y,d,_,g,b,w=t.props,k=u.props,x=u.type;if("svg"===x&&(o=true),null!=r)for(a=0;a<r.length;a++)if((_=r[a])&&"setAttribute"in _==!!x&&(x?_.localName===x:3===_.nodeType)){l=_,r[a]=null;break}if(null==l){if(null===x)return document.createTextNode(k);l=o?document.createElementNS("http://www.w3.org/2000/svg",x):document.createElement(x,k.is&&k),r=null,e=false;}if(null===x)w===k||e&&l.data===k||(l.data=k);else {if(r=r&&n$1.call(l.childNodes),w=t.props||c$1,!e&&null!=r)for(w={},a=0;a<l.attributes.length;a++)w[(_=l.attributes[a]).name]=_.value;for(a in w)_=w[a],"children"==a||("dangerouslySetInnerHTML"==a?y=_:"key"===a||a in k||T(l,a,null,_,o));for(a in k)_=k[a],"children"==a?d=_:"dangerouslySetInnerHTML"==a?v=_:"value"==a?g=_:"checked"==a?b=_:"key"===a||e&&"function"!=typeof _||w[a]===_||T(l,a,_,w[a],o);if(v)e||y&&(v.__html===y.__html||v.__html===l.innerHTML)||(l.innerHTML=v.__html),u.__k=[];else if(y&&(l.innerHTML=""),C$1(l,h$1(d)?d:[d],u,t,i,o&&"foreignObject"!==x,r,f,r?r[0]:t.__k&&m$2(t,0),e,s),null!=r)for(a=r.length;a--;)null!=r[a]&&p$1(r[a]);e||(a="value",void 0!==g&&(g!==l[a]||"progress"===x&&!g||"option"===x&&g!==w[a])&&T(l,a,g,w[a],false),a="checked",void 0!==b&&b!==l[a]&&T(l,a,b,w[a],false));}return l}function z$1(n,u,t){try{"function"==typeof n?n(u):n.current=u;}catch(n){l$1.__e(n,t);}}function N(n,u,t){var i,o;if(l$1.unmount&&l$1.unmount(n),(i=n.ref)&&(i.current&&i.current!==n.__e||z$1(i,null,u)),null!=(i=n.__c)){if(i.componentWillUnmount)try{i.componentWillUnmount();}catch(n){l$1.__e(n,u);}i.base=i.__P=null,n.__c=void 0;}if(i=n.__k)for(o=0;o<i.length;o++)i[o]&&N(i[o],u,t||"function"!=typeof n.type);t||null==n.__e||p$1(n.__e),n.__=n.__e=n.__d=void 0;}function O(n,l,u){return this.constructor(n,u)}function q$1(u,t,i){var o,r,f,e;l$1.__&&l$1.__(u,t),r=(o="function"=="undefined")?null:t.__k,f=[],e=[],L(t,u=(t).__k=y$1(g,null,[u]),r||c$1,c$1,void 0!==t.ownerSVGElement,r?null:t.firstChild?n$1.call(t.childNodes):null,f,r?r.__e:t.firstChild,o,e),M(f,u,e);}n$1=s$1.slice,l$1={__e:function(n,l,u,t){for(var i,o,r;l=l.__;)if((i=l.__c)&&!i.__)try{if((o=i.constructor)&&null!=o.getDerivedStateFromError&&(i.setState(o.getDerivedStateFromError(n)),r=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),r=i.__d),r)return i.__E=i}catch(l){n=l;}throw n}},u$1=0,b.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=v$1({},this.state),"function"==typeof n&&(n=n(v$1({},u),this.props)),n&&v$1(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),k$1(this));},b.prototype.forceUpdate=function(n){this.__v&&(this.__e=true,n&&this.__h.push(n),k$1(this));},b.prototype.render=g,i$1=[],r$2="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,f$1=function(n,l){return n.__v.__b-l.__v.__b},x$1.__r=0;

  var n=function(t,s,r,e){var u;s[0]=0;for(var h=1;h<s.length;h++){var p=s[h++],a=s[h]?(s[0]|=p?1:2,r[s[h++]]):s[++h];3===p?e[0]=a:4===p?e[1]=Object.assign(e[1]||{},a):5===p?(e[1]=e[1]||{})[s[++h]]=a:6===p?e[1][s[++h]]+=a+"":p?(u=t.apply(a,n(t,a,r,["",null])),e.push(u),a[0]?s[0]|=2:(s[h-2]=0,s[h]=u)):e.push(a);}return e},t$1=new Map;function e$1(s){var r=t$1.get(this);return r||(r=new Map,t$1.set(this,r)),(r=n(this,r.get(s)||(r.set(s,r=function(n){for(var t,s,r=1,e="",u="",h=[0],p=function(n){1===r&&(n||(e=e.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?h.push(0,n,e):3===r&&(n||e)?(h.push(3,n,e),r=2):2===r&&"..."===e&&n?h.push(4,n,0):2===r&&e&&!n?h.push(5,0,true,e):r>=5&&((e||!n&&5===r)&&(h.push(r,0,e,s),r=6),n&&(h.push(r,n,0,s),r=6)),e="";},a=0;a<n.length;a++){a&&(1===r&&p(),p(a));for(var l=0;l<n[a].length;l++)t=n[a][l],1===r?"<"===t?(p(),h=[h],r=3):e+=t:4===r?"--"===e&&">"===t?(r=1,e=""):e=t+e[0]:u?t===u?u="":e+=t:'"'===t||"'"===t?u=t:">"===t?(p(),r=1):r&&("="===t?(r=5,s=e,e=""):"/"===t&&(r<5||">"===n[a][l+1])?(p(),3===r&&(h=h[0]),r=h,(h=h[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),r=2):e+=t),3===r&&"!--"===e&&(r=4,h=h[0]);}return p(),h}(s)),r),arguments,[])).length>1?r:r[0]}

  var m$1=e$1.bind(y$1);

  var t,r$1,u,i,o=0,f=[],c=[],e=l$1,a=e.__b,v=e.__r,l=e.diffed,m=e.__c,s=e.unmount,d=e.__;function h(n,t){e.__h&&e.__h(r$1,n,o||t),o=0;var u=r$1.__H||(r$1.__H={__:[],__h:[]});return n>=u.__.length&&u.__.push({__V:c}),u.__[n]}function p(n){return o=1,y(D,n)}function y(n,u,i){var o=h(t++,2);if(o.t=n,!o.__c&&(o.__=[D(void 0,u),function(n){var t=o.__N?o.__N[0]:o.__[0],r=o.t(t,n);t!==r&&(o.__N=[r,o.__[1]],o.__c.setState({}));}],o.__c=r$1,!r$1.u)){var f=function(n,t,r){if(!o.__c.__H)return  true;var u=o.__c.__H.__.filter(function(n){return !!n.__c});if(u.every(function(n){return !n.__N}))return !c||c.call(this,n,t,r);var i=false;return u.forEach(function(n){if(n.__N){var t=n.__[0];n.__=n.__N,n.__N=void 0,t!==n.__[0]&&(i=true);}}),!(!i&&o.__c.props===n)&&(!c||c.call(this,n,t,r))};r$1.u=true;var c=r$1.shouldComponentUpdate,e=r$1.componentWillUpdate;r$1.componentWillUpdate=function(n,t,r){if(this.__e){var u=c;c=void 0,f(n,t,r),c=u;}e&&e.call(this,n,t,r);},r$1.shouldComponentUpdate=f;}return o.__N||o.__}function _(n,u){var i=h(t++,3);!e.__s&&C(i.__H,u)&&(i.__=n,i.i=u,r$1.__H.__h.push(i));}function A(n,u){var i=h(t++,4);!e.__s&&C(i.__H,u)&&(i.__=n,i.i=u,r$1.__h.push(i));}function F(n){return o=5,q(function(){return {current:n}},[])}function q(n,r){var u=h(t++,7);return C(u.__H,r)?(u.__V=n(),u.i=r,u.__h=n,u.__V):u.__}function x(n,t){return o=8,q(function(){return n},t)}function j(){for(var n;n=f.shift();)if(n.__P&&n.__H)try{n.__H.__h.forEach(z),n.__H.__h.forEach(B),n.__H.__h=[];}catch(t){n.__H.__h=[],e.__e(t,n.__v);}}e.__b=function(n){r$1=null,a&&a(n);},e.__=function(n,t){n&&t.__k&&t.__k.__m&&(n.__m=t.__k.__m),d&&d(n,t);},e.__r=function(n){v&&v(n),t=0;var i=(r$1=n.__c).__H;i&&(u===r$1?(i.__h=[],r$1.__h=[],i.__.forEach(function(n){n.__N&&(n.__=n.__N),n.__V=c,n.__N=n.i=void 0;})):(i.__h.forEach(z),i.__h.forEach(B),i.__h=[],t=0)),u=r$1;},e.diffed=function(n){l&&l(n);var t=n.__c;t&&t.__H&&(t.__H.__h.length&&(1!==f.push(t)&&i===e.requestAnimationFrame||((i=e.requestAnimationFrame)||w)(j)),t.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.__V!==c&&(n.__=n.__V),n.i=void 0,n.__V=c;})),u=r$1=null;},e.__c=function(n,t){t.some(function(n){try{n.__h.forEach(z),n.__h=n.__h.filter(function(n){return !n.__||B(n)});}catch(r){t.some(function(n){n.__h&&(n.__h=[]);}),t=[],e.__e(r,n.__v);}}),m&&m(n,t);},e.unmount=function(n){s&&s(n);var t,r=n.__c;r&&r.__H&&(r.__H.__.forEach(function(n){try{z(n);}catch(n){t=n;}}),r.__H=void 0,t&&e.__e(t,r.__v));};var k="function"==typeof requestAnimationFrame;function w(n){var t,r=function(){clearTimeout(u),k&&cancelAnimationFrame(t),setTimeout(n);},u=setTimeout(r,100);k&&(t=requestAnimationFrame(r));}function z(n){var t=r$1,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r$1=t;}function B(n){var t=r$1;n.__c=n.__(),r$1=t;}function C(n,t){return !n||n.length!==t.length||t.some(function(t,r){return t!==n[r]})}function D(n,t){return "function"==typeof t?t(n):t}

  function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}

  /**
   * @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntry} PopupMenuHeaderEntry
   */

  /**
   * Component that renders a popup menu header.
   *
   * @param {Object} props
   * @param {PopupMenuHeaderEntry[]} props.headerEntries
   * @param {PopupMenuHeaderEntry} props.selectedEntry
   * @param {(event: MouseEvent, entry: PopupMenuHeaderEntry) => void} props.onSelect
   * @param {(entry: PopupMenuHeaderEntry | null) => void} props.setSelectedEntry
   * @param {string} props.title
   */
  function PopupMenuHeader(props) {
    const {
      headerEntries,
      onSelect,
      selectedEntry,
      setSelectedEntry,
      title
    } = props;

    const groups = q(() => groupEntries$1(headerEntries), [ headerEntries ]);

    return m$1`
    <div class="djs-popup-header">
      <h3 class="djs-popup-title" title=${ title }>${ title }</h3>
      ${ groups.map((group) => m$1`
        <ul key=${ group.id } class="djs-popup-header-group" data-header-group=${ group.id }>

          ${ group.entries.map(entry => m$1`
            <li key=${ entry.id }>
              <${ entry.action ? 'button' : 'span' }
                class=${ getHeaderClasses(entry, entry === selectedEntry) }
                onClick=${ event => entry.action && onSelect(event, entry) }
                title=${ entry.title || entry.label }
                data-id=${ entry.id }
                onMouseEnter=${ () => entry.action && setSelectedEntry(entry) }
                onMouseLeave=${ () => entry.action && setSelectedEntry(null) }
                onFocus=${ () => entry.action && setSelectedEntry(entry) }
                onBlur=${ () => entry.action && setSelectedEntry(null) }
              >
                ${(entry.imageUrl && m$1`<img class="djs-popup-entry-icon" src=${ entry.imageUrl } alt="" />`) ||
                (entry.imageHtml && m$1`<div class="djs-popup-entry-icon" dangerouslySetInnerHTML=${ { __html: entry.imageHtml } } />`)}
                ${ entry.label ? m$1`
                  <span class="djs-popup-label">${ entry.label }</span>
                ` : null }
              </${ entry.action ? 'button' : 'span' }>
            </li>
          `) }
        </ul>
      `) }
    </div>
  `;
  }


  // helpers
  function groupEntries$1(entries) {
    return entries.reduce((groups, entry) => {
      const groupId = entry.group || 'default';

      const group = groups.find(group => group.id === groupId);

      if (group) {
        group.entries.push(entry);
      } else {
        groups.push({
          id: groupId,
          entries: [ entry ]
        });
      }

      return groups;
    }, []);
  }

  function getHeaderClasses(entry, selected) {
    return clsx(
      'entry',
      entry.className,
      entry.active ? 'active' : '',
      entry.disabled ? 'disabled' : '',
      selected ? 'selected' : ''
    );
  }

  /**
   * @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
   */

  /**
   * Component that renders a popup menu entry.
   *
   * @param {Object} props
   * @param {string} props.key
   * @param {PopupMenuEntry} props.entry
   * @param {boolean} props.selected
   * @param {(event: MouseEvent) => void} props.onMouseEnter
   * @param {(event: MouseEvent) => void} props.onMouseLeave
   * @param {(event: MouseEvent, entry?: PopupMenuEntry, action?: string) => void} props.onAction
   */
  function PopupMenuItem(props) {
    const {
      entry,
      selected,
      onMouseEnter,
      onMouseLeave,
      onAction
    } = props;

    return m$1`
    <li
      class=${ clsx('entry', { selected }) }
      data-id=${ entry.id }
      title=${ entry.title || entry.label }
      tabIndex="0"
      onClick=${ onAction }
      onFocus=${ onMouseEnter }
      onBlur=${ onMouseLeave }
      onMouseEnter=${ onMouseEnter }
      onMouseLeave=${ onMouseLeave }
      onDragStart=${ (event) => onAction(event, entry, 'dragstart') }
      draggable=${ true }
    >
      <div class="djs-popup-entry-content">
        <span
          class=${ clsx('djs-popup-entry-name', entry.className) }
        >
          ${(entry.imageUrl && m$1`<img class="djs-popup-entry-icon" src=${ entry.imageUrl } alt="" />`) ||
            (entry.imageHtml && m$1`<div class="djs-popup-entry-icon" dangerouslySetInnerHTML=${ { __html: entry.imageHtml } } />`)}

          ${ entry.label ? m$1`
            <span class="djs-popup-label">
              ${ entry.label }
            </span>
          ` : null }
        </span>
        ${ entry.description && m$1`
          <span
            class="djs-popup-entry-description"
            title=${ entry.description }
          >
            ${ entry.description }
          </span>
        ` }
      </div>
      ${ entry.documentationRef && m$1`
        <div class="djs-popup-entry-docs">
          <a
            href="${ entry.documentationRef }"
            onClick=${ (event) => event.stopPropagation() }
            title="Open element documentation"
            target="_blank"
            rel="noopener"
          >
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" clip-rule="evenodd" d="M10.6368 10.6375V5.91761H11.9995V10.6382C11.9995 10.9973 11.8623 11.3141 11.5878 11.5885C11.3134 11.863 10.9966 12.0002 10.6375 12.0002H1.36266C0.982345 12.0002 0.660159 11.8681 0.396102 11.6041C0.132044 11.34 1.52588e-05 11.0178 1.52588e-05 10.6375V1.36267C1.52588e-05 0.98236 0.132044 0.660173 0.396102 0.396116C0.660159 0.132058 0.982345 2.95639e-05 1.36266 2.95639e-05H5.91624V1.36267H1.36266V10.6375H10.6368ZM12 0H7.2794L7.27873 1.36197H9.68701L3.06507 7.98391L4.01541 8.93425L10.6373 2.31231V4.72059H12V0Z" fill="#818798"/>
            </svg>
          </a>
        </div>
      ` }
    </li>
  `;
  }

  /**
   * @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
   */

  /**
   * Component that renders a popup menu entry list.
   *
   * @param {Object} props
   * @param {PopupMenuEntry[]} props.entries
   * @param {PopupMenuEntry} props.selectedEntry
   * @param {(entry: PopupMenuEntry | null) => void} props.setSelectedEntry
   */
  function PopupMenuList(props) {
    const {
      selectedEntry,
      setSelectedEntry,
      entries,
      ...restProps
    } = props;

    const resultsRef = F();

    const groups = q(() => groupEntries(entries), [ entries ]);

    // scroll to selected result
    A(() => {
      const containerEl = resultsRef.current;

      if (!containerEl)
        return;

      const selectedEl = containerEl.querySelector('.selected');

      if (selectedEl) {
        scrollIntoView(selectedEl);
      }
    }, [ selectedEntry ]);

    return m$1`
    <div class="djs-popup-results" ref=${ resultsRef }>
      ${ groups.map(group => m$1`
        ${ group.name && m$1`
          <div key=${ group.id } class="entry-header" title=${ group.name }>
            ${ group.name }
          </div>
        ` }
        <ul class="djs-popup-group" data-group=${ group.id }>
          ${ group.entries.map(entry => m$1`
            <${PopupMenuItem}
              key=${ entry.id }
              entry=${ entry }
              selected=${ entry === selectedEntry }
              onMouseEnter=${ () => setSelectedEntry(entry) }
              onMouseLeave=${ () => setSelectedEntry(null) }
              ...${ restProps }
            />
          `) }
        </ul>
      `) }
    </div>
  `;
  }


  // helpers
  function groupEntries(entries) {
    const groups = [];

    const getGroup = group => groups.find(elem => group.id === elem.id);

    const containsGroup = group => !!getGroup(group);

    // legacy support for provider built for the old popUp menu
    const formatGroup = group =>
      typeof group === 'string' ? { id: group } : group;

    entries.forEach(entry => {

      // assume a default group when none is provided
      const group = entry.group ? formatGroup(entry.group) : { id: 'default' };

      if (!containsGroup(group)) {
        groups.push({ ...group, entries: [ entry ] });
      } else {
        getGroup(group).entries.push(entry);
      }
    });

    return groups;
  }

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

  function scrollIntoView(el) {
    if (typeof el.scrollIntoViewIfNeeded === 'function') {
      el.scrollIntoViewIfNeeded();
    } else {
      el.scrollIntoView({
        scrollMode: 'if-needed',
        block: 'nearest'
      });
    }
  }

  /**
   * @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
   * @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntry} PopupMenuHeaderEntry
   * @typedef {import('./PopupMenuProvider').PopupMenuEmptyPlaceholderProvider | import('./PopupMenuProvider').PopupMenuEmptyPlaceholder} PopupMenuEmptyPlaceholder
   *
   * @typedef {import('../search/search').default} search
   *
   * @typedef {import('../../util/Types').Point} Point
   */

  /**
   * A component that renders the popup menus.
   *
   * @param {Object} props
   * @param {() => void} props.onClose
   * @param {() => void} props.onSelect
   * @param {(element: HTMLElement) => Point} props.position
   * @param {string} props.className
   * @param {PopupMenuEntry[]} props.entries
   * @param {PopupMenuHeaderEntry[]} props.headerEntries
   * @param {number} props.scale
   * @param {string} [props.title]
   * @param {boolean} [props.search]
   * @param {PopupMenuEmptyPlaceholder} [props.emptyPlaceholder]
   * @param {number} [props.width]
   * @param {search} props.searchFn
   */
  function PopupMenuComponent(props) {
    const {
      onClose,
      onSelect,
      className,
      headerEntries,
      position,
      title,
      width,
      scale,
      search,
      emptyPlaceholder,
      searchFn,
      entries: originalEntries,
      onOpened,
      onClosed
    } = props;

    const searchable = q(() => {
      if (!isDefined(search)) {
        return false;
      }

      return originalEntries.length > 5;
    }, [ search, originalEntries ]);

    const [ searchValue, setSearchValue ] = p('');

    const filterEntries = x((originalEntries, searchValue) => {

      if (!searchable) {
        return originalEntries;
      }

      if (!searchValue.trim()) {
        return originalEntries.filter(({ rank = 0 }) => rank >= 0);
      }

      const searchableEntries = originalEntries.filter(({ searchable }) => searchable !== false);

      return searchFn(searchableEntries, searchValue, {
        keys: [
          'label',
          'search',
          'description'
        ]
      }).map(({ item }) => item);
    }, [ searchable ]);

    const [ entries, setEntries ] = p(filterEntries(originalEntries, searchValue));
    const [ selectedEntry, setSelectedEntry ] = p(entries[0]);

    const updateEntries = x((newEntries) => {

      // always select first
      setSelectedEntry(newEntries[0]);

      setEntries(newEntries);
    }, [ selectedEntry, setEntries, setSelectedEntry ]);

    // filter entries on search value change
    _(() => {
      updateEntries(filterEntries(originalEntries, searchValue));
    }, [ searchValue, originalEntries ]);

    // handle keyboard seleciton
    const keyboardSelect = x(direction => {
      const idx = entries.indexOf(selectedEntry);

      let nextIdx = idx + direction;

      if (nextIdx < 0) {
        nextIdx = entries.length - 1;
      }

      if (nextIdx >= entries.length) {
        nextIdx = 0;
      }

      setSelectedEntry(entries[nextIdx]);
    }, [ entries, selectedEntry, setSelectedEntry ]);

    const handleKeyDown = x(event => {
      if (event.key === 'Enter' && selectedEntry) {
        return onSelect(event, selectedEntry);
      }

      // ARROW_UP
      if (event.key === 'ArrowUp') {
        keyboardSelect(-1);

        return event.preventDefault();
      }

      // ARROW_DOWN
      if (event.key === 'ArrowDown') {
        keyboardSelect(1);

        return event.preventDefault();
      }
    }, [ onSelect, selectedEntry, keyboardSelect ]);

    const handleKey = x(event => {
      if (matches(event.target, 'input')) {
        setSearchValue(() => event.target.value);
      }
    }, [ setSearchValue ]);

    _(() => {
      onOpened();

      return () => {
        onClosed();
      };
    }, []);

    const displayHeader = q(() => title || headerEntries.length > 0, [ title, headerEntries ]);

    return m$1`
    <${PopupMenuWrapper}
      onClose=${ onClose }
      onKeyup=${ handleKey }
      onKeydown=${ handleKeyDown }
      className=${ className }
      position=${ position }
      width=${ width }
      scale=${ scale }
    >
      ${ displayHeader && m$1`
        <${PopupMenuHeader}
          headerEntries=${ headerEntries }
          onSelect=${ onSelect }
          selectedEntry=${ selectedEntry }
          setSelectedEntry=${ setSelectedEntry }
          title=${ title }
        />
      ` }
      ${ originalEntries.length > 0 && m$1`
        <div class="djs-popup-body">

          ${ searchable && m$1`
          <div class="djs-popup-search">
            <svg class="djs-popup-search-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" clip-rule="evenodd" d="M9.0325 8.5H9.625L13.3675 12.25L12.25 13.3675L8.5 9.625V9.0325L8.2975 8.8225C7.4425 9.5575 6.3325 10 5.125 10C2.4325 10 0.25 7.8175 0.25 5.125C0.25 2.4325 2.4325 0.25 5.125 0.25C7.8175 0.25 10 2.4325 10 5.125C10 6.3325 9.5575 7.4425 8.8225 8.2975L9.0325 8.5ZM1.75 5.125C1.75 6.9925 3.2575 8.5 5.125 8.5C6.9925 8.5 8.5 6.9925 8.5 5.125C8.5 3.2575 6.9925 1.75 5.125 1.75C3.2575 1.75 1.75 3.2575 1.75 5.125Z" fill="#22242A"/>
            </svg>
            <input type="text" spellcheck=${ false } aria-label="${ title }" />
          </div>
          ` }

          <${PopupMenuList}
            entries=${ entries }
            selectedEntry=${ selectedEntry }
            setSelectedEntry=${ setSelectedEntry }
            onAction=${ onSelect }
          />
        </div>
      ` }
    ${ emptyPlaceholder && entries.length === 0 && m$1`
      <div class="djs-popup-no-results">${ isFunction(emptyPlaceholder) ? emptyPlaceholder(searchValue) : emptyPlaceholder }</div>
    ` }
    </${PopupMenuWrapper}>
  `;
  }

  /**
   * A component that wraps the popup menu.
   *
   * @param {*} props
   */
  function PopupMenuWrapper(props) {
    const {
      onClose,
      onKeydown,
      onKeyup,
      className,
      children,
      position: positionGetter
    } = props;

    const popupRef = F();

    // initial position
    A(() => {
      if (typeof positionGetter !== 'function') {
        return;
      }

      const popupEl = popupRef.current;
      const position = positionGetter(popupEl);

      popupEl.style.left = `${position.x}px`;
      popupEl.style.top = `${position.y}px`;
    }, [ popupRef.current, positionGetter ]);

    // initial focus
    A(() => {
      const popupEl = popupRef.current;

      if (!popupEl) {
        return;
      }

      const inputEl = popupEl.querySelector('input');

      (inputEl || popupEl).focus();
    }, []);

    // global <Escape> / blur handlers
    _(() => {
      const handleKeyDown = event => {
        if (event.key === 'Escape') {
          event.preventDefault();

          return onClose();
        }
      };

      const handleClick = event => {
        const popup = closest(event.target, '.djs-popup', true);

        if (popup) {
          return;
        }

        return onClose();
      };

      document.documentElement.addEventListener('keydown', handleKeyDown);
      document.body.addEventListener('click', handleClick);

      return () => {
        document.documentElement.removeEventListener('keydown', handleKeyDown);
        document.body.removeEventListener('click', handleClick);
      };
    }, []);

    return m$1`
    <div
      class=${ clsx('djs-popup', className) }
      style=${ getPopupStyle(props) }
      onKeydown=${ onKeydown }
      onKeyup=${ onKeyup }
      ref=${ popupRef }
      tabIndex="-1"
    >
      ${ children }
    </div>
  `;
  }

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

  function getPopupStyle(props) {
    return {
      transform: `scale(${props.scale})`,
      width: `${props.width}px`,
      'transform-origin': 'top left'
    };
  }

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../search/search').default} search
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('./PopupMenuProvider').PopupMenuEntries} PopupMenuEntries
   * @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
   * @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntries} PopupMenuHeaderEntries
   * @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntry} PopupMenuHeaderEntry
   * @typedef {import('./PopupMenuProvider').default} PopupMenuProvider
   *
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef { {
   *   scale?: {
   *     min?: number;
   *     max?: number;
   *   } | boolean;
   * } } PopupMenuConfig
   *
   * @typedef {Element|Element[]} PopupMenuTarget;
   */

  var DATA_REF = 'data-id';

  var CLOSE_EVENTS = [
    'contextPad.close',
    'canvas.viewbox.changing',
    'commandStack.changed'
  ];

  var DEFAULT_PRIORITY$2 = 1000;

  /**
   * A popup menu to show a number of actions on the canvas.
   *
   * @param {PopupMenuConfig} config
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {search} search
   */
  function PopupMenu(config, eventBus, canvas, search) {
    this._eventBus = eventBus;
    this._canvas = canvas;
    this._search = search;

    this._current = null;

    var scale = isDefined(config && config.scale) ? config.scale : {
      min: 1,
      max: 1
    };

    this._config = {
      scale: scale
    };


    eventBus.on('diagram.destroy', () => {
      this.close();
    });

    eventBus.on('element.changed', event => {

      const element = this.isOpen() && this._current.target;

      if (event.element === element) {
        this.refresh();
      }
    });

  }

  PopupMenu.$inject = [
    'config.popupMenu',
    'eventBus',
    'canvas',
    'search'
  ];

  PopupMenu.prototype._render = function() {

    const {
      position: _position,
      providerId: className,
      entries,
      headerEntries,
      emptyPlaceholder,
      options
    } = this._current;

    const entriesArray = Object.entries(entries).map(
      ([ key, value ]) => ({ id: key, ...value })
    );

    const headerEntriesArray = Object.entries(headerEntries).map(
      ([ key, value ]) => ({ id: key, ...value })
    );

    const position = _position && (
      (container) => this._ensureVisible(container, _position)
    );

    const scale = this._updateScale(this._current.container);

    const onClose = result => this.close(result);
    const onSelect = (event, entry, action) => this.trigger(event, entry, action);

    q$1(
      m$1`
      <${PopupMenuComponent}
        onClose=${ onClose }
        onSelect=${ onSelect }
        position=${ position }
        className=${ className }
        entries=${ entriesArray }
        headerEntries=${ headerEntriesArray }
        emptyPlaceholder=${ emptyPlaceholder }
        scale=${ scale }
        onOpened=${ this._onOpened.bind(this) }
        onClosed=${ this._onClosed.bind(this) }
        searchFn=${ this._search }
        ...${{ ...options }}
      />
    `,
      this._current.container
    );
  };


  /**
   * Open the popup menu at the given position.
   *
   * @param {PopupMenuTarget} target
   * @param {string} providerId
   * @param {Point} position
   * @param {Object} [options]
   */
  PopupMenu.prototype.open = function(target, providerId, position, options) {
    if (!target) {
      throw new Error('target is missing');
    }

    if (!providerId) {
      throw new Error('providers for <' + providerId + '> not found');
    }

    if (!position) {
      throw new Error('position is missing');
    }

    if (this.isOpen()) {
      this.close();
    }

    const {
      entries,
      headerEntries,
      emptyPlaceholder
    } = this._getContext(target, providerId);

    this._current = {
      position,
      providerId,
      target,
      entries,
      headerEntries,
      emptyPlaceholder,
      container: this._createContainer({ provider: providerId }),
      options
    };

    this._emit('open');

    this._bindAutoClose();

    this._render();
  };

  /**
   * Refresh the popup menu entries without changing the target or position.
   */
  PopupMenu.prototype.refresh = function() {
    if (!this.isOpen()) {
      return;
    }

    const {
      target,
      providerId
    } = this._current;

    const {
      entries,
      headerEntries,
      emptyPlaceholder
    } = this._getContext(target, providerId);

    this._current = {
      ...this._current,
      entries,
      headerEntries,
      emptyPlaceholder
    };

    this._emit('refresh');

    this._render();
  };


  PopupMenu.prototype._getContext = function(target, provider) {

    const providers = this._getProviders(provider);

    if (!providers || !providers.length) {
      throw new Error('provider for <' + provider + '> not found');
    }

    const entries = this._getEntries(target, providers);

    const headerEntries = this._getHeaderEntries(target, providers);

    const emptyPlaceholder = this._getEmptyPlaceholder(providers);

    return {
      entries,
      headerEntries,
      emptyPlaceholder,
      empty: !(
        Object.keys(entries).length ||
            Object.keys(headerEntries).length
      )
    };
  };

  PopupMenu.prototype.close = function() {

    if (!this.isOpen()) {
      return;
    }

    this._emit('close');

    this.reset();

    this._canvas.restoreFocus();

    this._current = null;
  };

  PopupMenu.prototype.reset = function() {
    const container = this._current.container;

    q$1(null, container);

    remove$2(container);
  };

  PopupMenu.prototype._emit = function(event, payload) {
    this._eventBus.fire(`popupMenu.${ event }`, payload);
  };

  PopupMenu.prototype._onOpened = function() {
    this._emit('opened');
  };

  PopupMenu.prototype._onClosed = function() {
    this._emit('closed');
  };

  PopupMenu.prototype._createContainer = function(config) {

    var canvas = this._canvas,
        parent = canvas.getContainer();

    const container = domify$1(`<div class="djs-popup-parent djs-scrollable" data-popup=${config.provider}></div>`);

    parent.appendChild(container);

    return container;
  };

  /**
   * 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);
  };


  /**
   * Updates popup style.transform with respect to the config and zoom level.
   *
   * @return {number}
   */
  PopupMenu.prototype._updateScale = function() {
    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;
      }

    }

    return scale;
  };

  PopupMenu.prototype._ensureVisible = function(container, position) {
    var documentBounds = document.documentElement.getBoundingClientRect();
    var containerBounds = container.getBoundingClientRect();

    var overAxis = {},
        left = position.x,
        top = position.y;

    if (position.x + containerBounds.width > documentBounds.width) {
      overAxis.x = true;
    }

    if (position.y + containerBounds.height > documentBounds.height) {
      overAxis.y = true;
    }

    if (overAxis.x && overAxis.y) {
      left = position.x - containerBounds.width;
      top = position.y - containerBounds.height;
    } else if (overAxis.x) {
      left = position.x - containerBounds.width;
      top = position.y;
    } else if (overAxis.y && position.y < containerBounds.height) {
      left = position.x;
      top = 10;
    } else if (overAxis.y) {
      left = position.x;
      top = position.y - containerBounds.height;
    }

    // underAxis
    if (position.y < documentBounds.top) {
      top = position.y + containerBounds.height;
    }

    return {
      x: left,
      y: top
    };
  };

  /**
   * Check whether there are no popup menu providers or provided entries for the
   * given target.
   *
   * @param {PopupMenuTarget} target
   * @param {string} providerId
   *
   * @return {boolean}
   */
  PopupMenu.prototype.isEmpty = function(target, providerId) {
    if (!target) {
      throw new Error('target is missing');
    }

    if (!providerId) {
      throw new Error('provider ID is missing');
    }

    const providers = this._getProviders(providerId);

    if (!providers || !providers.length) {
      return true;
    }

    return this._getContext(target, providerId).empty;
  };

  /**
   * @overlord
   *
   * Register a popup menu provider with default priority. See
   * {@link PopupMenuProvider} for examples.
   *
   * @param {string} id
   * @param {PopupMenuProvider} provider
   */

  /**
   * Register a popup menu provider with the given priority. See
   * {@link PopupMenuProvider} for examples.
   *
   * @param {string} id
   * @param {number} priority
   * @param {PopupMenuProvider} provider
   */
  PopupMenu.prototype.registerProvider = function(id, priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY$2;
    }

    this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
      event.providers.push(provider);
    });
  };

  /**
   * @param {string} id
   *
   * @return {PopupMenuProvider[]}
   */
  PopupMenu.prototype._getProviders = function(id) {
    var event = this._eventBus.createEvent({
      type: 'popupMenu.getProviders.' + id,
      providers: []
    });

    this._eventBus.fire(event);

    return event.providers;
  };

  /**
   * @param {PopupMenuTarget} target
   * @param {PopupMenuProvider[]} providers
   *
   * @return {PopupMenuEntries}
   */
  PopupMenu.prototype._getEntries = function(target, providers) {
    var entries = {};

    forEach$1(providers, function(provider) {

      // handle legacy method
      if (!provider.getPopupMenuEntries) {
        forEach$1(provider.getEntries(target), function(entry) {
          var id = entry.id;

          if (!id) {
            throw new Error('entry ID is missing');
          }

          entries[id] = omit(entry, [ 'id' ]);
        });

        return;
      }

      var entriesOrUpdater = provider.getPopupMenuEntries(target);

      if (isFunction(entriesOrUpdater)) {
        entries = entriesOrUpdater(entries);
      } else {
        forEach$1(entriesOrUpdater, function(entry, id) {
          entries[id] = entry;
        });
      }
    });

    return entries;
  };

  /**
   * @param {PopupMenuTarget} target
   * @param {PopupMenuProvider[]} providers
   *
   * @return {PopupMenuHeaderEntries}
   */
  PopupMenu.prototype._getHeaderEntries = function(target, providers) {
    var entries = {};

    forEach$1(providers, function(provider) {

      // handle legacy method
      if (!provider.getPopupMenuHeaderEntries) {
        if (!provider.getHeaderEntries) {
          return;
        }

        forEach$1(provider.getHeaderEntries(target), function(entry) {
          var id = entry.id;

          if (!id) {
            throw new Error('entry ID is missing');
          }

          entries[id] = omit(entry, [ 'id' ]);
        });

        return;
      }

      var entriesOrUpdater = provider.getPopupMenuHeaderEntries(target);

      if (isFunction(entriesOrUpdater)) {
        entries = entriesOrUpdater(entries);
      } else {
        forEach$1(entriesOrUpdater, function(entry, id) {
          entries[id] = entry;
        });
      }
    });

    return entries;
  };


  PopupMenu.prototype._getEmptyPlaceholder = function(providers) {
    const provider = providers.find(
      provider => isFunction(provider.getEmptyPlaceholder)
    );

    return provider && provider.getEmptyPlaceholder();
  };


  /**
   * Check if the popup menu is open.
   *
   * @return {boolean}
   */
  PopupMenu.prototype.isOpen = function() {
    return !!this._current;
  };


  /**
   * Trigger an action associated with an entry.
   *
   * @param {Event} event
   * @param {PopupMenuEntry} entry
   * @param {string} [action='click']
   *
   * @return {any}
   */
  PopupMenu.prototype.trigger = function(event, entry, action = 'click') {

    // silence other actions
    event.preventDefault();

    if (!entry) {
      let element = closest(event.delegateTarget || event.target, '.entry', true);
      let entryId = attr$1(element, DATA_REF);

      entry = { id: entryId, ...this._getEntry(entryId) };
    }

    const handler = entry.action;

    if (this._emit('trigger', { entry, event }) === false) {
      return;
    }

    if (isFunction(handler)) {
      if (action === 'click') {
        return handler(event, entry);
      }
    } else {
      if (handler[action]) {
        return handler[action](event, entry);
      }
    }
  };

  /**
   * Get the entry (entry or header entry) with the given ID.
   *
   * @param {string} entryId
   *
   * @return {PopupMenuEntry|PopupMenuHeaderEntry}
   */
  PopupMenu.prototype._getEntry = function(entryId) {

    var entry = this._current.entries[ entryId ] || this._current.headerEntries[ entryId ];


    if (!entry) {
      throw new Error('entry not found');
    }

    return entry;
  };

  /**
   * @typedef { {
   *   index: number;
   *   match: boolean;
   *   value: string;
   * } } Token
   *
   * @typedef {Token[]} Tokens
   */

  /**
   * @template R
   *
   * @typedef { {
   *   item: R,
   *   tokens: Record<string, Tokens>
   * } } SearchResult
   */

  /**
   * @typedef {Record<string, string | string[]>} SearchItem
   */

  /**
   * Search items by query.
   *
   * @template {SearchItem} T
   *
   * @param {T[]} items elements to search in
   * @param {string} pattern pattern to search for
   * @param { {
   *   keys: string[];
   * } } options
   *
   * @returns {SearchResult<T>[]}
   */
  function search(items, pattern, options) {

    const {
      keys
    } = options;

    pattern = pattern.trim().toLowerCase();

    if (!pattern) {
      throw new Error('<pattern> must not be empty');
    }

    const words = pattern.trim().toLowerCase().split(/\s+/);

    return items.flatMap((item) => {
      const tokens = matchItem(item, words, keys);

      if (!tokens) {
        return [];
      }

      return {
        item,
        tokens
      };
    }).sort(createResultSorter(keys));
  }

  /**
   * Match an item and return tokens in case of a match.
   *
   * @param {SearchItem} item element to be matched
   * @param {string[]} words words from search pattern to find
   * @param {string[]} keys keys to search in the item
   *
   * @returns {Record<string, Tokens>}
   */
  function matchItem(item, words, keys) {

    const {
      matchedWords,
      tokens
    } = keys.reduce((result, key) => {
      const itemValue = item[ key ];

      const {
        tokens,
        matchedWords
      } = isArray$3(itemValue) ? (
        itemValue.reduce(
          (result, itemString) => {
            const { tokens, matchedWords } = matchString(itemString, words);

            return {
              tokens: [ ...result.tokens, tokens ],
              matchedWords: {
                ...result.matchedWords,
                ...matchedWords
              }
            };
          },
          {
            matchedWords: {},
            tokens: []
          }
        )
      ) : (
        matchString(itemValue, words)
      );

      return {
        tokens: {
          ...result.tokens,
          [ key ]: tokens,
        },
        matchedWords: {
          ...result.matchedWords,
          ...matchedWords
        }
      };
    }, {
      matchedWords: {},
      tokens: {}
    });

    // only return result if every word got matched
    if (Object.keys(matchedWords).length !== words.length) {
      return null;
    }

    return tokens;
  }

  /**
   * Creates a compare function that can be used in Array.sort() based on a custom scoring function
   *
   * @param {string[]} keys
   *
   * @returns { (resultA: SearchResult, resultB: SearchResult) => number}
   */
  function createResultSorter(keys) {

    /**
     * @param {SearchResult} resultA
     * @param {SearchResult} resultB
     */
    return (resultA, resultB) => {
      let comparison = 0;

      // used to assign some priority to earlier keys
      let modifier = 1;

      for (const key of keys) {

        const tokenComparison = compareTokens(
          resultA.tokens[key],
          resultB.tokens[key]
        );

        if (tokenComparison !== 0) {
          comparison += tokenComparison * modifier;
          modifier *= 0.9;
          continue;
        }

        const stringComparison = compareStrings(
          resultA.item[ key ],
          resultB.item[ key ]
        );

        if (stringComparison !== 0) {
          comparison += stringComparison * modifier;
          modifier *= 0.9;
          continue;
        }
      }

      return comparison;
    };

  }

  /**
   * Compares two token arrays.
   *
   * @param {Token[]} [tokensA]
   * @param {Token[]} [tokensB]
   *
   * @returns {number}
   */
  function compareTokens(tokensA, tokensB) {
    return scoreTokens(tokensB) - scoreTokens(tokensA);
  }

  /**
   * @param { Token[] } tokens
   * @returns { number }
   */
  function scoreTokens(tokens) {
    return tokens.reduce((sum, token) => sum + scoreToken(token), 0);
  }

  /**
   * Score a token based on its characteristics
   * and the length of the matched content.
   *
   * @param { Token } token
   *
   * @returns { number }
   */
  function scoreToken(token) {
    if (isArray$3(token)) {
      return Math.max(...token.map(scoreToken));
    }

    const modifier = Math.log(token.value.length);

    if (!token.match) {
      return -0.07 * modifier;
    }

    return (
      token.start
        ? (
          token.end
            ? 131.9
            : 7.87
        )
        : (
          token.wordStart
            ? 2.19
            : 1
        )
    ) * modifier;
  }

  /**
   * @param {string|string[]} [str='']
   *
   * @return {string}
   */
  function stringJoin(str = '') {
    return isArray$3(str) ? str.join(', ') : str;
  }

  /**
   * Compares two strings. also supports string arrays, which will be joined
   *
   * @param {string|string[]} [a]
   * @param {string|string[]} [b]
   *
   * @returns {number}
   */
  function compareStrings(a, b) {
    return stringJoin(a).localeCompare(stringJoin(b));
  }

  /**
   * Match a given string against a set of words,
   * and return the result.
   *
   * @param {string} string
   * @param {string[]} words
   *
   * @return { {
   *   tokens: Token[],
   *   matchedWords: Record<string, boolean>
   * } }
   */
  function matchString(string, words) {

    if (!string) {
      return {
        tokens: [],
        matchedWords: {}
      };
    }

    const tokens = [];
    const matchedWords = {};

    const wordsEscaped = words.map(escapeRegexp);

    const regexpString = [
      `(?<all>${wordsEscaped.join('\\s+')})`,
      ...wordsEscaped
    ].join('|');

    const regexp = new RegExp(regexpString, 'ig');

    let match;
    let lastIndex = 0;

    while ((match = regexp.exec(string))) {

      const [ value ] = match;

      const startIndex = match.index;
      const endIndex = match.index + value.length;

      const start = startIndex === 0;
      const end = endIndex === string.length;

      const all = !!match.groups.all;

      const wordStart = start || /\s/.test(string.charAt(startIndex - 1));
      const wordEnd = end || /\s/.test(string.charAt(endIndex));

      if (match.index > lastIndex) {

        // add previous token (NO match)
        tokens.push({
          value: string.slice(lastIndex, match.index),
          index: lastIndex
        });
      }

      // add current token (match)
      tokens.push({
        value,
        index: match.index,
        match: true,
        wordStart,
        wordEnd,
        start,
        end,
        all
      });

      const newMatchedWords = all ? words : [ value ];

      for (const word of newMatchedWords) {
        matchedWords[word.toLowerCase()] = true;
      }

      lastIndex = match.index + value.length;
    }

    // add after token (NO match)
    if (lastIndex < string.length) {
      tokens.push({
        value: string.slice(lastIndex),
        index: lastIndex
      });
    }

    return {
      tokens,
      matchedWords
    };
  }

  function escapeRegexp(string) {
    return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var SearchModule$1 = {
    search: [ 'value', search ]
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var PopupMenuModule$1 = {
    __depends__: [ SearchModule$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:  `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
              <line x1="200" y1="150" x2="200" y2="1850" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
              <rect x="500" y="150" width="1300" height="700" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
              <rect x="500" y="1150" width="700" height="700" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    bottom: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="150" y1="1650" x2="1650" y2="1650" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="150" y="350" width="600" height="1300" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="1050" y="850" width="600" height="800" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    center: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="900" y1="150" x2="900" y2="1650" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="250" y="150" width="1300" height="600" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="500" y="1050" width="800" height="600" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    left:   `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="100" y1="150" x2="100" y2="1650" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="100" y="150" width="1300" height="600" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="100" y="1050" width="800" height="600" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    right:  `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="1650" y1="150" x2="1650" y2="1650" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="350" y="150" width="1300" height="600" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="850" y="1050" width="800" height="600" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    top:    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="150" y1="150" x2="1650" y2="150" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="150" y="150" width="600" height="1300" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="1050" y="150" width="600" height="800" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`,
    middle: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
            <line x1="150" y1="900" x2="1650" y2="900" style="stroke:currentColor;stroke-width:100;stroke-linecap:round;"/>
            <rect x="150" y="250" width="600" height="1300" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
            <rect x="1050" y="500" width="600" height="800" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
          </svg>`
  };

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').ContextPadEntries} ContextPadEntries
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default<Element>} ContextPadProvider
   */

  var LOW_PRIORITY$m = 900;

  /**
   * A provider for the `Align elements` context pad entry.
   *
   * @implements {ContextPadProvider}
   *
   * @param {ContextPad} contextPad
   * @param {PopupMenu} popupMenu
   * @param {Translate} translate
   * @param {Canvas} canvas
   */
  function AlignElementsContextPadProvider(contextPad, popupMenu, translate, canvas) {

    contextPad.registerProvider(LOW_PRIORITY$m, this);

    this._contextPad = contextPad;
    this._popupMenu = popupMenu;
    this._translate = translate;
    this._canvas = canvas;
  }

  AlignElementsContextPadProvider.$inject = [
    'contextPad',
    'popupMenu',
    'translate',
    'canvas'
  ];

  /**
   * @param {Element[]} elements
   *
   * @return {ContextPadEntries}
   */
  AlignElementsContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
    var actions = {};

    if (this._isAllowed(elements)) {
      assign$1(actions, this._getEntries(elements));
    }

    return actions;
  };

  AlignElementsContextPadProvider.prototype._isAllowed = function(elements) {
    return !this._popupMenu.isEmpty(elements, 'align-elements');
  };

  AlignElementsContextPadProvider.prototype._getEntries = function() {
    var self = this;

    return {
      'align-elements': {
        group: 'align-elements',
        title: self._translate('Align elements'),
        html: `<div class="entry">${icons$1['align']}</div>`,
        action: {
          click: function(event, target) {
            var position = self._getMenuPosition(target);

            assign$1(position, {
              cursor: {
                x: event.x,
                y: event.y
              }
            });

            self._popupMenu.open(target, 'align-elements', position);
          }
        }
      }
    };
  };

  AlignElementsContextPadProvider.prototype._getMenuPosition = function(elements) {
    var Y_OFFSET = 5;

    var pad = this._contextPad.getPad(elements).html;

    var padRect = pad.getBoundingClientRect();

    var pos = {
      x: padRect.left,
      y: padRect.bottom + Y_OFFSET
    };

    return pos;
  };

  /**
   * @typedef {import('diagram-js/lib/features/align-elements/AlignElements').default} AlignElements
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
   * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   *
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuEntries} PopupMenuEntries
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
   */

  var ALIGNMENT_OPTIONS = [
    'left',
    'center',
    'right',
    'top',
    'middle',
    'bottom'
  ];

  /**
   * A provider for the `Align elements` popup menu.
   *
   * @implements {PopupMenuProvider}
   *
   * @param {PopupMenu} popupMenu
   * @param {AlignElements} alignElements
   * @param {Translate} translate
   * @param {Rules} rules
   */
  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'
  ];

  /**
   * @param {PopupMenuTarget} target
   *
   * @return {PopupMenuEntries}
   */
  AlignElementsMenuProvider.prototype.getPopupMenuEntries = function(target) {
    var entries = {};

    if (this._isAllowed(target)) {
      assign$1(entries, this._getEntries(target));
    }

    return entries;
  };

  AlignElementsMenuProvider.prototype._isAllowed = function(target) {
    return this._rules.allowed('elements.align', { elements: target });
  };

  /**
   * @param {PopupMenuTarget} target
   *
   * @return {PopupMenuEntries}
   */
  AlignElementsMenuProvider.prototype._getEntries = function(target) {
    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',
        imageHtml: icons$1[ alignment ],
        action: function() {
          alignElements.trigger(target, alignment);
          popupMenu.close();
        }
      };
    });

    return entries;
  };

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  /**
   * 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.
   *
   * @class
   *
   * @param {EventBus} eventBus
   */
  function RuleProvider(eventBus) {
    CommandInterceptor.call(this, eventBus);

    this.init();
  }

  RuleProvider.$inject = [ 'eventBus' ];

  e$2(RuleProvider, CommandInterceptor);


  /**
   * Adds a modeling rule for the given action, implemented through
   * a callback function.
   *
   * The callback receives a modeling specific action context
   * to perform its check. It must return `false` to disallow the
   * action from happening or `true` to allow the action. Usually returing
   * `null` denotes that a particular interaction shall be ignored.
   * By returning nothing or `undefined` you pass evaluation to lower
   * priority rules.
   *
   * @example
   *
   * ```javascript
   * 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|string[]} actions the identifier for the modeling action to check
   * @param {number} [priority] the priority at which this rule is being applied
   * @param {(any) => any} 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() {};

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * Rule provider for aligning BPMN elements.
   *
   * @param {EventBus} eventBus
   */
  function BpmnAlignElements(eventBus) {
    RuleProvider.call(this, eventBus);
  }

  BpmnAlignElements.$inject = [ 'eventBus' ];

  e$2(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 ]
  };

  /**
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../../util/Types').Point} Point
   */

  // 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 {Shape} source
   * @param {Shape} element
   * @param {Point} position
   * @param {(element: Element, position: Point, connectedAtPosition: Element) => Point} 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]
   *
   * @return {(element: Element, previousPosition: Point, connectedAtPosition: Element) => Point}
   */
  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 connected element at given position and within given bounds. Takes
   * connected elements from host and attachers into account, too.
   *
   * @param {Shape} source
   * @param {Point} position
   * @param {Shape} element
   *
   * @return {Shape|undefined}
   */
  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 {Shape} source
  * @param {Object} [hints]
  * @param {number} [hints.defaultDistance]
  * @param {string} [hints.direction]
  * @param {(connection: Connection) => boolean} [hints.filter]
  * @param {(connection: Connection) => number} [hints.getWeight]
  * @param {number} [hints.maxDistance]
  * @param {'start'|'center'|'end'} [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 elements connected to given source.
   *
   * This includes:
   *
   *   - elements connected to source
   *   - elements connected to host if source is an attacher
   *   - elements connected to attachers if source is a host
   *
   * @param {Shape} source
   *
   * @return {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;
  }

  /**
   * Get all connected elements.
   *
   * @param {Shape} element
   *
   * @returns {Shape[]}
   */
  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;
  }

  /**
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   */

  var LOW_PRIORITY$l = 100;


  /**
   * A service that places elements connected to existing ones
   * to an appropriate position in an _automated_ fashion.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {Canvas} canvas
   */
  function AutoPlace$1(eventBus, modeling, canvas) {

    eventBus.on('autoPlace', LOW_PRIORITY$l, 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 {Shape} source
     * @param {Shape} shape
     * @param {any} [hints={}]
     *
     * @return {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 {Shape} source
   * @param {Shape} element
   * @param {Object} [hints]
   * @param {Object} [hints.defaultDistance]
   *
   * @return {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
    };
  }

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../selection/Selection').default} Selection
   */

  /**
   * 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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var AutoPlaceModule$1 = {
    __init__: [ 'autoPlaceSelectionBehavior' ],
    autoPlace: [ 'type', AutoPlace$1 ],
    autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../../model/Types').Element} Element
   */

  /**
   * Return the parent of the element with any of the given types.
   *
   * @param {Element} element
   * @param {string|string[]} anyType
   *
   * @return {Element|null}
   */
  function getParent(element, anyType) {

    if (isString(anyType)) {
      anyType = [ anyType ];
    }

    while ((element = element.parent)) {
      if (isAny(element, anyType)) {
        return element;
      }
    }

    return null;
  }

  /**
   * Determines if the local modeling direction is vertical or horizontal.
   *
   * @param {Element} element
   * @param {ElementRegistry} [elementRegistry] - provide to consider parent diagram direction
   *
   * @return {boolean} false for vertical pools, lanes and their children. true otherwise
   */
  function isDirectionHorizontal(element, elementRegistry) {

    var parent = getParent(element, 'bpmn:Process');
    if (parent) {
      return true;
    }

    var types = [ 'bpmn:Participant', 'bpmn:Lane' ];

    parent = getParent(element, types);
    if (parent) {
      return isHorizontal$3(parent);
    } else if (isAny(element, types)) {
      return isHorizontal$3(element);
    }

    var process;
    for (process = getBusinessObject(element); process; process = process.$parent) {
      if (is(process, 'bpmn:Process')) {
        break;
      }
    }

    if (!elementRegistry) {
      return true;
    }

    // The direction may be specified in another diagram. We ignore that there
    // could be multiple diagrams with contradicting properties based on the
    // assumption that such BPMN files are unusual.
    var pool = elementRegistry.find(function(shape) {
      var businessObject = getBusinessObject(shape);
      return businessObject && businessObject.get('processRef') === process;
    });

    if (!pool) {
      return true;
    }

    return isHorizontal$3(pool);
  }

  /**
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   */

  /**
   * Get the position for given new target relative to the source it will be
   * connected to.
   *
   * @param  {Shape} source
   * @param  {Shape} element
   * @param  {ElementRegistry} elementRegistry
   *
   * @return {Point}
   */
  function getNewShapePosition(source, element, elementRegistry) {

    var placeHorizontally = isDirectionHorizontal(source, elementRegistry);

    if (is(element, 'bpmn:TextAnnotation')) {
      return getTextAnnotationPosition(source, element, placeHorizontally);
    }

    if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
      return getDataElementPosition(source, element, placeHorizontally);
    }

    if (is(element, 'bpmn:FlowNode')) {
      return getFlowNodePosition(source, element, placeHorizontally);
    }
  }

  /**
   * Get the position for given new flow node. Try placing the flow node right/bottom of
   * the source.
   *
   * @param {Shape} source
   * @param {Shape} element
   * @param {boolean} placeHorizontally Whether to place the new element horizontally
   *
   * @return {Point}
   */
  function getFlowNodePosition(source, element, placeHorizontally) {

    var sourceTrbl = asTRBL(source);
    var sourceMid = getMid(source);

    var placement = placeHorizontally ? {
      directionHint: 'e',
      minDistance: 80,
      baseOrientation: 'left',
      boundaryOrientation: 'top',
      start: 'top',
      end: 'bottom'
    } : {
      directionHint: 's',
      minDistance: 90,
      baseOrientation: 'top',
      boundaryOrientation: 'left',
      start: 'left',
      end: 'right'
    };

    var connectedDistance = getConnectedDistance(source, {
      filter: function(connection) {
        return is(connection, 'bpmn:SequenceFlow');
      },
      direction: placement.directionHint
    });

    var margin = 30,
        minDistance = placement.minDistance,
        orientation = placement.baseOrientation;

    if (is(source, 'bpmn:BoundaryEvent')) {
      orientation = getOrientation(source, source.host, -25);

      if (orientation.indexOf(placement.boundaryOrientation) !== -1) {
        margin *= -1;
      }
    }

    var position = placeHorizontally ? {
      x: sourceTrbl.right + connectedDistance + element.width / 2,
      y: sourceMid.y + getDistance$2(orientation, minDistance, placement)
    } : {
      x: sourceMid.x + getDistance$2(orientation, minDistance, placement),
      y: sourceTrbl.bottom + connectedDistance + element.height / 2
    };

    var nextPosition = {
      margin: margin,
      minDistance: minDistance
    };

    var nextPositionDirection = placeHorizontally ? {
      y: nextPosition
    } : {
      x: nextPosition
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }

  /**
   * @param {DirectionTRBL} orientation
   * @param {number} minDistance
   * @param {{ start: DirectionTRBL, end: DirectionTRBL }} placement
   *
   * @return {number}
   */
  function getDistance$2(orientation, minDistance, placement) {
    if (orientation.includes(placement.start)) {
      return -1 * minDistance;
    } else if (orientation.includes(placement.end)) {
      return minDistance;
    } else {
      return 0;
    }
  }


  /**
   * Get the position for given text annotation. Try placing the text annotation
   * top-right of the source (bottom-right in vertical layouts).
   *
   * @param {Shape} source
   * @param {Shape} element
   * @param {boolean} placeHorizontally Whether to place the new element horizontally
   *
   * @return {Point}
   */
  function getTextAnnotationPosition(source, element, placeHorizontally) {

    var sourceTrbl = asTRBL(source);

    var position = placeHorizontally ? {
      x: sourceTrbl.right + element.width / 2,
      y: sourceTrbl.top - 50 - element.height / 2
    } : {
      x: sourceTrbl.right + 50 + element.width / 2,
      y: sourceTrbl.bottom + element.height / 2
    };

    if (isConnection(source)) {
      position = getMid(source);
      if (placeHorizontally) {
        position.x += 100;
        position.y -= 50;
      } else {
        position.x += 100;
        position.y += 50;
      }
    }

    var nextPosition = {
      margin: placeHorizontally ? -30 : 30,
      minDistance: 20
    };
    var nextPositionDirection = placeHorizontally ? {
      y: nextPosition
    } : {
      x: nextPosition
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }


  /**
   * Get the position for given new data element. Try placing the data element
   * bottom-right of the source (bottom-left in vertical layouts).
   *
   * @param {Shape} source
   * @param {Shape} element
   * @param {boolean} placeHorizontally Whether to place the new element horizontally
   *
   * @return {Point}
   */
  function getDataElementPosition(source, element, placeHorizontally) {

    var sourceTrbl = asTRBL(source);

    var position = placeHorizontally ? {
      x: sourceTrbl.right - 10 + element.width / 2,
      y: sourceTrbl.bottom + 40 + element.width / 2
    } : {
      x: sourceTrbl.left - 40 - element.width / 2,
      y: sourceTrbl.bottom - 10 + element.height / 2
    };

    var nextPosition = {
      margin: 30,
      minDistance: 30
    };
    var nextPositionDirection = placeHorizontally ? {
      x: nextPosition
    } : {
      y: nextPosition
    };

    return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   */

  /**
   * BPMN auto-place behavior.
   *
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   */
  function AutoPlace(eventBus, elementRegistry) {
    eventBus.on('autoPlace', function(context) {
      var shape = context.shape,
          source = context.source;

      return getNewShapePosition(source, shape, elementRegistry);
    });
  }

  AutoPlace.$inject = [ 'eventBus', 'elementRegistry' ];

  var AutoPlaceModule = {
    __depends__: [ AutoPlaceModule$1 ],
    __init__: [ 'bpmnAutoPlace' ],
    bpmnAutoPlace: [ 'type', AutoPlace ]
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Rect} Rect
   * @typedef {import('../../util/Types').RectTRBL} RectTRBL
   *
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   */

  /**
   * 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$2(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 {Shape[]} elements
   * @param {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$1({}, targetTrbl, newTrbl));
  };


  /**
   * Expand the target shape respecting rules, offset and padding
   *
   * @param {Shape[]} elements
   * @param {Shape|string} target The target or its ID.
   */
  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 {Shape} shape
   *
   * @return {RectTRBL}
   */
  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 {Shape} shape
   *
   * @return {RectTRBL}
   */
  AutoResize.prototype.getPadding = function(shape) {
    return { top: 2, bottom: 2, left: 15, right: 15 };
  };


  /**
   * Perform the actual resize operation.
   *
   * @param {Shape} shape
   * @param {Rect} 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 {Rect} oldBounds
   * @param {Rect} newBounds
   *
   * @return {Direction} 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;
  }

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   */

  /**
   * BPMN-specific resize behavior.
   *
   * @param {Injector} injector
   */
  function BpmnAutoResize(injector) {

    injector.invoke(AutoResize, this);
  }

  BpmnAutoResize.$inject = [
    'injector'
  ];

  e$2(BpmnAutoResize, AutoResize);

  /**
   * Perform BPMN-specific resizing of participants.
   *
   * @param {Shape} target
   * @param {Rect} newBounds
   * @param {Object} [hints]
   * @param {string} [hints.autoResize]
   */
  BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {

    if (is(target, 'bpmn:Participant')) {
      this._modeling.resizeLane(target, newBounds, null, hints);
    } else {
      this._modeling.resizeShape(target, newBounds, null, hints);
    }
  };

  /**
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  /**
   * This is a base rule provider for the element.autoResize rule.
   *
   * @param {EventBus} eventBus
   */
  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$2(AutoResizeProvider, RuleProvider);

  /**
   * Needs to be implemented by sub classes to allow actual auto resize
   *
   * @param {Shape[]} elements
   * @param {Shape} target
   *
   * @return {boolean}
   */
  AutoResizeProvider.prototype.canResize = function(elements, target) {
    return false;
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   *
   * @typedef {import('../../model/Types').Shape} Shape
   */

  /**
   * BPMN-specific provider for automatic resizung.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function BpmnAutoResizeProvider(eventBus, modeling) {
    AutoResizeProvider.call(this, eventBus);

    this._modeling = modeling;
  }

  e$2(BpmnAutoResizeProvider, AutoResizeProvider);

  BpmnAutoResizeProvider.$inject = [
    'eventBus',
    'modeling'
  ];


  /**
   * BPMN-specific check whether given elements can be resized.
   *
   * @param {Shape[]} elements
   * @param {Shape} target
   *
   * @return {boolean}
   */
  BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {

    // do not resize plane elements:
    // root elements, collapsed sub-processes
    if (is(target.di, 'bpmndi:BPMNPlane')) {
      return false;
    }

    if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) {
      return false;
    }

    var canResize = true;

    forEach$1(elements, function(element) {

      if (is(element, 'bpmn:Lane') || isLabel(element)) {
        canResize = false;
        return;
      }
    });

    return canResize;
  };

  var AutoResizeModule = {
    __init__: [
      'bpmnAutoResize',
      'bpmnAutoResizeProvider'
    ],
    bpmnAutoResize: [ 'type', BpmnAutoResize ],
    bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  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);
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var HoverFixModule = {
    __init__: [
      'hoverFix'
    ],
    hoverFix: [ 'type', HoverFix ],
  };

  var round$b = Math.round;

  /**
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../selection/Selection').default} Selection
   */

  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
   *
   * ```javascript
   * 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"
   *         }
   *       }
   *     });
   *   });
   * }
   * ```
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Selection} selection
   * @param {ElementRegistry} elementRegistry
   */
  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$1(
          {},
          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$1(payload, {
          x: round$b(localStart.x + displacement.x),
          y: round$b(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$1(payload, {
          x: round$b(localCurrent.x + displacement.x),
          y: round$b(localCurrent.y + displacement.y),
          dx: round$b(localDelta.x),
          dy: round$b(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 (isKey('Escape', event)) {
        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 model element (`hover`) and graphical element (`hoverGfx`)
    // 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
      event.unbind(document, 'mousemove', move);

      event.unbind(document, 'dragstart', preventDefault$1);
      event.unbind(document, 'selectstart', preventDefault$1);

      event.unbind(document, 'mousedown', endDrag, true);
      event.unbind(document, 'mouseup', endDrag, true);

      event.unbind(document, 'keyup', checkCancel);

      event.unbind(document, 'touchstart', trapTouch, true);
      event.unbind(document, 'touchcancel', cancel, true);
      event.unbind(document, 'touchmove', move, true);
      event.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} [relativeTo] actual diagram local position this drag operation should start at
     * @param {string} prefix
     * @param {Object} [options]
     */
    function init(event$1, 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$1({}, defaultOptions, options || {});

      var data = options.data || {},
          originalEvent,
          globalStart,
          localStart,
          endDrag,
          isTouch;

      if (options.trapClick) {
        endDrag = trapClickAndEnd;
      } else {
        endDrag = end;
      }

      if (event$1) {
        originalEvent = getOriginal$1(event$1) || event$1;
        globalStart = toPoint(event$1);

        stopPropagation$1(event$1);

        // 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$1({
        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) {
          event.bind(document, 'touchstart', trapTouch, true);
          event.bind(document, 'touchcancel', cancel, true);
          event.bind(document, 'touchmove', move, true);
          event.bind(document, 'touchend', end, true);
        } else {

          // assume we use the mouse to interact per default
          event.bind(document, 'mousemove', move);

          // prevent default browser drag and text selection behavior
          event.bind(document, 'dragstart', preventDefault$1);
          event.bind(document, 'selectstart', preventDefault$1);

          event.bind(document, 'mousedown', endDrag, true);
          event.bind(document, 'mouseup', endDrag, true);
        }

        event.bind(document, 'keyup', checkCancel);

        eventBus.on('element.hover', hover);
        eventBus.on('element.out', out);
      }

      fire('init');

      if (options.autoActivate) {
        move(event$1, 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$1(defaultOptions, options);
    };
  }

  Dragging.$inject = [
    'eventBus',
    'canvas',
    'selection',
    'elementRegistry'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var DraggingModule = {
    __depends__: [
      HoverFixModule,
      SelectionModule,
    ],
    dragging: [ 'type', Dragging ],
  };

  /**
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../core/Canvas').default} Canvas
   */

  /**
   * 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 ]
   *
   * @param {Object} config
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function AutoScroll(config, eventBus, canvas) {

    this._canvas = canvas;

    this._opts = assign$1({
      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 {Point} point
   */
  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$1({}, 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
    };
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var AutoScrollModule = {
    __depends__: [
      DraggingModule,
    ],
    __init__: [ 'autoScroll' ],
    autoScroll: [ 'type', AutoScroll ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   */

  /**
   * 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 import('./RuleProvider').default}
   * 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 allowed or disallowed.
   * @param {Object} [context] The context for allowing or disallowing the action.
   *
   * @return {boolean|null} Wether the action is allowed. Returns `null` if the action
   * is to 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;
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var RulesModule$1 = {
    __init__: [ 'rules' ],
    rules: [ 'type', Rules ]
  };

  /**
   * @typedef {import('../util/Types').Point} Point
   *
   * @typedef { {
   *   bendpoint?: boolean;
   *   index: number;
   *   point: Point;
   * } } Intersection
   */

  var round$a = Math.round,
      max$6 = Math.max;


  function circlePath(center, r) {
    var x = center.x,
        y = center.y;

    return [
      [ 'M', x, y ],
      [ 'm', 0, -10 ],
      [ '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;

  /**
   * @param {Point[]} waypoints
   * @param {Point} reference
   *
   * @return {Intersection|null}
   */
  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;
  }

  /**
   * @param {Point[]} waypoints
   * @param {Point} reference
   *
   * @return {Intersection|null}
   */
  function getPathIntersection(waypoints, reference) {

    var intersections = findPathIntersections(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$a(a.x + b.x) / 2),
          y: (round$a(a.y + b.y) / 2)
        },
        index: a.segment2
      };
    }

    return {
      point: {
        x: round$a(a.x),
        y: round$a(a.y)
      },
      index: a.segment2
    };
  }

  /**
   * Returns the closest point on the connection towards a given reference point.
   *
   * @param {Point[]} waypoints
   * @param {Point} reference
   *
   * @return {Intersection|null}
   */
  function getApproxIntersection(waypoints, reference) {
    return getBendpointIntersection(waypoints, reference) || getPathIntersection(waypoints, reference);
  }

  /**
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').Vector} Vector
   */

  /**
   * Returns the length of a vector.
   *
   * @param {Vector} vector
   *
   * @return {number}
   */
  function vectorLength(vector) {
    return Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2));
  }


  /**
   * Calculates the angle between a line a the Y axis.
   *
   * @param {Point[]} line
   *
   * @return {number}
   */
  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} vector
   * @param {number} angle The 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} a
   * @param {Vector} b
   * @param {Vector} c
   *
   * @return {number}
   */
  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;
  }


  /**
   * Calculates the position of the perpendicular foot.
   *
   * @param {Point} point
   * @param {Point[]} line
   *
   * @return {Point}
   */
  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} point
   * @param {Point[]} line
   *
   * @return {number}
   */
  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} point1
   * @param {Point} point2
   *
   * @return {number}
   */
  function getDistancePointPoint(point1, point2) {

    return vectorLength({
      x: point1.x - point2.x,
      y: point1.y - point2.y
    });
  }

  /**
   * @typedef {import('../../core/Types').ConnectionLike} Connection
   *
   * @typedef {import('../../util/Types').Point} Point
   */

  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: -18 / 2,
      y: -6 / 2,
      width: width,
      height: height
    });
    classes(visual).add('djs-visual');

    append(draggerGfx, visual);

    var hit = create$1('rect');
    attr(hit, {
      x: -hitWidth / 2,
      y: -17 / 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$1(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 {Connection} connection
   * @return {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] ];
  }

  /**
   * @typedef {import('../bendpoints/BendpointMove').default} BendpointMove
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../bendpoints/ConnectionSegmentMove').default} ConnectionSegmentMove
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../interaction-events/InteractionEvents').default} InteractionEvents
   */

  /**
   * A service that adds editable bendpoints to connections.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {InteractionEvents} interactionEvents
   * @param {BendpointMove} bendpointMove
   * @param {ConnectionSegmentMove} connectionSegmentMove
   */
  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) {

      event.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="' + escapeCSS(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$1(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$1(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$1(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);
  }

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   */

  var round$9 = 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.
   *
   * @param {Injector} injector
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Dragging} dragging
   * @param {Rules} rules
   * @param {Modeling} modeling
   */
  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$9(event.x),
        y: round$9(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;
    }
  }

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../bendpoints/BendpointMove').default} BendpointMove
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  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_DRAGGER = 'djs-dragging';

  var HIGH_PRIORITY$i = 1100;

  /**
   * Preview connection while moving bendpoints.
   *
   * @param {BendpointMove} bendpointMove
   * @param {Injector} injector
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  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_DRAGGER);
      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$1(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_DRAGGER);

      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';

  /**
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../core/GraphicsFactory').default} GraphicsFactory
   * @typedef {import('../modeling/Modeling').default} Modeling
   */

  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 {Shape} referenceElement
   * @param {Axis} moveAxis
   *
   * @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.
   *
   * @param {Injector} injector
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Canvas} dragging
   * @param {GraphicsFactory} graphicsFactory
   * @param {Modeling} modeling
   */
  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 {Point[]} newWaypoints
     *
     * @return {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$1(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 {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'
  ];

  /**
   * @typedef {import('../../core/Types').ConnectionLike} Connection
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../core/EventBus').Event} Event
   *
   * @typedef {import('../../util/Types').Axis} Axis
   */

  var abs$6 = Math.abs,
      round$8 = 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$8(bounds.x + bounds.width / 2),
      y: round$8(bounds.y + bounds.height / 2)
    };
  }


  /**
   * Retrieve the snap state of the given event.
   *
   * @param {Event} event
   * @param {Axis} 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 {Axis} 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 {Shape} parent
   *
   * @return {Array<Shape|Connection>}
   */
  function getChildren(parent) {
    return parent.children || [];
  }

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   */
  var abs$5 = Math.abs,
      round$7 = Math.round;

  var TOLERANCE = 10;

  /**
   * @param {EventBus} eventBus
   */
  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$7(element.width / 2 + element.x),
          y: round$7(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$1(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(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$1(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' ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  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 ]
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   */

  /**
   * @param {EventBus} eventBus
   * @param {Dragging} dragging
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  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 {MouseEvent|TouchEvent} event
     * @param {Element} 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;
  }

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  var HIGH_PRIORITY$h = 1100,
      LOW_PRIORITY$k = 900;

  var MARKER_OK$3 = 'connect-ok',
      MARKER_NOT_OK$3 = 'connect-not-ok';

  /**
   * Shows connection preview during connect.
   *
   * @param {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$k, 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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ConnectModule = {
    __depends__: [
      SelectionModule,
      RulesModule$1,
      DraggingModule
    ],
    __init__: [
      'connectPreview'
    ],
    connect: [ 'type', Connect ],
    connectPreview: [ 'type', ConnectPreview ]
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementFactory').default} ElementFactory
   * @typedef {import('../../core/GraphicsFactory').default} GraphicsFactory
   */

  var MARKER_CONNECTION_PREVIEW = 'djs-dragger';

  /**
   * Draws connection preview. Optionally, this can use layouter and connection docking to draw
   * better looking previews.
   *
   * @param {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 {Element} [hints.source] source element
   * @param {Element} [hints.target] target element
   * @param {Point} [hints.connectionStart] connection preview start
   * @param {Point} [hints.connectionEnd] connection preview end
   * @param {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, {
      stroke: 'var(--element-dragger-color)'
    });
  };

  /**
   * Draw simple connection between source and target or provided points.
   *
   * @param {SVGElement} connectionPreviewGfx container for the connection
   * @param {Object} hints
   * @param {Element} [hints.source] source element
   * @param {Element} [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 {Element} source
   * @param {Element} target
   *
   * @return {Point[]}
   */
  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
   *
   * @return {Connection}
   */
  ConnectionPreview.prototype.getConnection = function(canConnect) {
    var attrs = ensureConnectionAttrs(canConnect);

    return this._elementFactory.createConnection(attrs);
  };


  /**
   * Add and return preview graphics.
   *
   * @return {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
   *
   * @return {SVGElement}
   */
  ConnectionPreview.prototype.createNoopConnection = function(start, end) {
    return createLine([ start, end ], {
      'stroke': '#333',
      'strokeDasharray': [ 1 ],
      'strokeWidth': 2,
      'pointer-events': 'none'
    });
  };

  // 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.
     *
     * @return {*}
     */
    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
   *
   * @return {Object}
   */
  function ensureConnectionAttrs(canConnect) {
    if (isObject(canConnect)) {
      return canConnect;
    } else {
      return {};
    }
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ConnectionPreviewModule = {
    __init__: [ 'connectionPreview' ],
    connectionPreview: [ 'type', ConnectionPreview ]
  };

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../draw/Styles').default} Styles
   */

  const cloneIds = new IdGenerator('ps');

  var MARKER_TYPES = [
    'marker-start',
    'marker-mid',
    'marker-end'
  ];

  var NODES_CAN_HAVE_MARKER = [
    'circle',
    'ellipse',
    'line',
    'path',
    'polygon',
    'polyline',
    'path',
    'rect'
  ];


  /**
   * Adds support for previews of moving/resizing elements.
   *
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Styles} styles
   */
  function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
    this._elementRegistry = elementRegistry;
    this._canvas = canvas;
    this._styles = styles;
  }

  PreviewSupport.$inject = [
    'elementRegistry',
    'eventBus',
    'canvas',
    'styles'
  ];

  // Markers are cleaned up with visuals, keep stub for compatibility
  // cf. https://github.com/camunda/camunda-modeler/issues/4307
  PreviewSupport.prototype.cleanUp = function() {
    console.warn('PreviewSupport#cleanUp is deprecated and will be removed in future versions. You do not need to manually clean up previews anymore. cf. https://github.com/bpmn-io/diagram-js/pull/906');
  };

  /**
   * Returns graphics of an element.
   *
   * @param {Element} 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 {Element} element The element to be moved.
   * @param {SVGElement} group The SVG group to add the preview to.
   * @param {SVGElement} [gfx] The optional graphical element of the element.
   * @param {string} [className="djs-dragger"] The optional class name to add to the preview.
   *
   * @return {SVGElement} The preview.
   */
  PreviewSupport.prototype.addDragger = function(element, group, gfx, className = 'djs-dragger') {
    gfx = gfx || this.getGfx(element);

    var dragger = clone$1(gfx);
    var bbox = gfx.getBoundingClientRect();

    this._cloneMarkers(getVisual(dragger), className);

    attr(dragger, this._styles.cls(className, [], {
      x: bbox.top,
      y: bbox.left
    }));

    append(group, dragger);

    attr(dragger, 'data-preview-support-element-id', element.id);

    return dragger;
  };

  /**
   * Adds a resize preview of a given shape to a given SVG group.
   *
   * @param {Shape} shape The element to be resized.
   * @param {SVGElement} group The SVG group to add the preview to.
   *
   * @return {SVGElement} The preview.
   */
  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);

    attr(frame, 'data-preview-support-element-id', shape.id);

    return frame;
  };

  /**
   * Clone all markers referenced by a node and its child nodes.
   *
   * @param {SVGElement} gfx
   * @param {string} [className="djs-dragger"]
   */
  PreviewSupport.prototype._cloneMarkers = function(gfx, className = 'djs-dragger', rootGfx = gfx) {
    var self = this;

    if (gfx.childNodes) {

      gfx.childNodes.forEach((childNode) => {
        self._cloneMarkers(childNode, className, rootGfx);
      });

    }

    if (!canHaveMarker(gfx)) {
      return;
    }

    MARKER_TYPES.forEach(function(markerType) {
      if (attr(gfx, markerType)) {
        var marker = getMarker(gfx, markerType, self._canvas.getContainer());

        // Only clone marker if it is already present on the DOM
        marker && self._cloneMarker(rootGfx, gfx, marker, markerType, className);
      }
    });
  };

  /**
   * Clone marker referenced by an element.
   *
   * @param {SVGElement} gfx
   * @param {SVGElement} marker
   * @param {string} markerType
   * @param {string} [className="djs-dragger"]
   */
  PreviewSupport.prototype._cloneMarker = function(parentGfx, gfx, marker, markerType, className = 'djs-dragger') {

    // Add a random suffix to the marker ID in case the same marker is previewed multiple times
    var clonedMarkerId = [ marker.id, className, cloneIds.next() ].join('-');

    // reuse marker if it was part of original gfx
    var copiedMarker = query('marker#' + marker.id, parentGfx);

    parentGfx = parentGfx || this._canvas._svg;

    var clonedMarker = copiedMarker || clone$1(marker);

    clonedMarker.id = clonedMarkerId;

    classes(clonedMarker).add(className);

    var defs = query(':scope > defs', parentGfx);

    if (!defs) {
      defs = create$1('defs');

      append(parentGfx, defs);
    }

    append(defs, clonedMarker);

    var reference = idToReference(clonedMarker.id);

    attr(gfx, markerType, reference);
  };

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

  /**
   * Get marker of given type referenced by node.
   *
   * @param {HTMLElement} node
   * @param {string} markerType
   * @param {HTMLElement} [parentNode]
   *
   * @param {HTMLElement}
   */
  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
   *
   * @return {string}
   */
  function referenceToId(reference) {
    return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
  }

  /**
   * Get functional IRI reference for given ID of fragment within current document.
   *
   * @param {string} id
   *
   * @return {string}
   */
  function idToReference(id) {
    return 'url(#' + id + ')';
  }

  /**
   * Check wether node type can have marker attributes.
   *
   * @param {HTMLElement} node
   *
   * @return {boolean}
   */
  function canHaveMarker(node) {
    return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var PreviewSupportModule = {
    __init__: [ 'previewSupport' ],
    previewSupport: [ 'type', PreviewSupport ]
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').Rect} Rect
   *
   * @typedef { { element: Element, delta: Point } } MovedOption
   * @typedef { { shape: Shape, bounds: Rect } } ResizedOption
   *
   * @typedef { {
   *   created?: Element[],
   *   removed?: Element[],
   *   moved?: MovedOption[],
   *   resized?: ResizedOption[]
   * } } CreateOptions
   */

  const LAYER_NAME = 'complex-preview';

  /**
   * Complex preview for shapes and connections.
   */
  class ComplexPreview {
    constructor(canvas, graphicsFactory, previewSupport) {
      this._canvas = canvas;
      this._graphicsFactory = graphicsFactory;
      this._previewSupport = previewSupport;

      this._markers = [];
    }

    /**
     * Create complex preview.
     *
     * @param {CreateOptions} options
     */
    create(options) {

      // there can only be one complex preview at a time
      this.cleanUp();

      const {
        created = [],
        moved = [],
        removed = [],
        resized = []
      } = options;

      const layer = this._canvas.getLayer(LAYER_NAME);

      // shapes and connections to be created
      created.filter(element => !isHidden$2(element)).forEach(element => {
        let gfx;

        if (isConnection(element)) {
          gfx = this._graphicsFactory._createContainer('connection', create$1('g'));

          this._graphicsFactory.drawConnection(getVisual(gfx), element);
        } else {
          gfx = this._graphicsFactory._createContainer('shape', create$1('g'));

          this._graphicsFactory.drawShape(getVisual(gfx), element);

          translate$1(gfx, element.x, element.y);
        }

        this._previewSupport.addDragger(element, layer, gfx);
      });

      // elements to be moved
      moved.forEach(({ element, delta }) => {
        this._previewSupport.addDragger(element, layer, undefined, 'djs-dragging');

        this._canvas.addMarker(element, 'djs-element-hidden');

        this._markers.push([ element, 'djs-element-hidden' ]);

        const dragger = this._previewSupport.addDragger(element, layer);

        if (isConnection(element)) {
          translate$1(dragger, delta.x, delta.y);
        } else {
          translate$1(dragger, element.x + delta.x, element.y + delta.y);
        }
      });

      // elements to be removed
      removed.forEach(element => {
        this._previewSupport.addDragger(element, layer, undefined, 'djs-dragging');

        this._canvas.addMarker(element, 'djs-element-hidden');

        this._markers.push([ element, 'djs-element-hidden' ]);
      });

      // elements to be resized
      resized.forEach(({ shape, bounds }) => {
        this._canvas.addMarker(shape, 'djs-hidden');

        this._markers.push([ shape, 'djs-hidden' ]);

        this._previewSupport.addDragger(shape, layer, undefined, 'djs-dragging');

        const gfx = this._graphicsFactory._createContainer('shape', create$1('g'));

        this._graphicsFactory.drawShape(getVisual(gfx), shape, {
          width: bounds.width,
          height: bounds.height
        });

        translate$1(gfx, bounds.x, bounds.y);

        this._previewSupport.addDragger(shape, layer, gfx);
      });
    }

    cleanUp() {
      clear(this._canvas.getLayer(LAYER_NAME));

      this._markers.forEach(([ element, marker ]) => this._canvas.removeMarker(element, marker));

      this._markers = [];
    }

    show() {
      this._canvas.showLayer(LAYER_NAME);
    }

    hide() {
      this._canvas.hideLayer(LAYER_NAME);
    }
  }

  ComplexPreview.$inject = [
    'canvas',
    'graphicsFactory',
    'previewSupport'
  ];

  function isHidden$2(element) {
    return element.hidden;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ComplexPreviewModule = {
    __depends__: [ PreviewSupportModule ],
    __init__: [ 'complexPreview' ],
    complexPreview: [ 'type', ComplexPreview ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   */

  var ALIGNMENTS = [
    'top',
    'bottom',
    'left',
    'right'
  ];

  var ELEMENT_LABEL_DISTANCE = 10;

  /**
   * A behavior that ensures that labels are positioned in a way that they do not
   * overlap with other elements or connections.
   *
   * @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;
      }

      if (isConnection(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$2(AdaptiveLabelPositioningBehavior, CommandInterceptor);

  AdaptiveLabelPositioningBehavior.$inject = [
    'eventBus',
    'modeling'
  ];


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

  /**
   * Return alignments which are taken by a boundary's host element
   *
   * @param {Shape} element
   *
   * @return {DirectionTRBL[]}
   */
  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 {Element} element
   *
   * @return {DirectionTRBL[]}
   */
  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  {Element} element
   *
   * @return {DirectionTRBL|undefined}
   */
  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;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  function AppendBehavior(eventBus) {

    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(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$2(AppendBehavior, CommandInterceptor);

  AppendBehavior.$inject = [
    'eventBus'
  ];

  /**
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * @param {Injector} injector
   * @param {Modeling} modeling
   */
  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(connection, 'bpmn:Association');
      });

      forEach$1(associations, function(association) {
        modeling.moveConnection(association, { x: 0, y: 0 }, newParent);
      });
    }, true);
  }

  e$2(AssociationBehavior, CommandInterceptor);

  AssociationBehavior.$inject = [
    'injector',
    'modeling'
  ];

  /**
   * @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
   * @typedef {import('didi').Injector} Injector
   */

  var LOW_PRIORITY$j = 500;


  /**
   * Replace intermediate event with boundary event when creating or moving results in attached event.
   *
   * @param {BpmnReplace} bpmnReplace
   * @param {Injector} injector
   */
  function AttachEventBehavior(bpmnReplace, injector) {
    injector.invoke(CommandInterceptor, this);

    this._bpmnReplace = bpmnReplace;

    var self = this;

    this.postExecuted('elements.create', LOW_PRIORITY$j, 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$j, 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$2(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(shape) &&
      isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * BPMN specific boundary event behavior.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function BoundaryEventBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    function getBoundaryEvents(element) {
      return filter(element.attachers, function(attacher) {
        return is(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(source, 'bpmn:EventBasedGateway') &&
        is(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(oldSource, 'bpmn:Gateway') &&
          is(newSource, 'bpmn:EventBasedGateway')) {
        forEach$1(newSource.outgoing, function(connection) {
          var target = connection.target,
              attachedboundaryEvents = getBoundaryEvents(target);

          if (is(target, 'bpmn:ReceiveTask') &&
              attachedboundaryEvents.length > 0) {
            modeling.removeElements(attachedboundaryEvents);
          }
        });
      }
    });

  }

  BoundaryEventBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  e$2(BoundaryEventBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../lib/features/modeling/Modeling').default} Modeling
   */

  /**
   * Behavior ensuring that only a single compensation activity is connected to a
   * compensation boundary event when connecting, reconnecting or replacing shapes.
   *
   * @param {import('diagram-js/lib/core/EventBus').default} eventBus
   * @param {import('../Modeling').default} modeling
   * @param {import('../../rules/BpmnRules').default} bpmnRules
   */
  function CompensateBoundaryEventBehavior(eventBus, modeling, bpmnRules) {

    CommandInterceptor.call(this, eventBus);

    this.preExecute('shape.replace', handleReplacement, true);
    this.postExecuted('shape.replace', handleReplacementPostExecuted, true);
    this.preExecute('connection.create', handleNewConnection, true);
    this.postExecuted('connection.delete', handleConnectionRemoval, true);
    this.postExecuted('connection.reconnect', handleReconnection, true);
    this.postExecuted('element.updateProperties', handlePropertiesUpdate, true);

    /**
     * Given a connection from boundary event is removed, remove the `isForCompensation` property.
     */
    function handleConnectionRemoval(context) {
      const source = context.source,
            target = context.target;

      if (isCompensationBoundaryEvent$1(source) && isForCompensation$2(target)) {
        removeIsForCompensationProperty(target);
      }
    }

    /**
     * Add `isForCompensation` property and make sure only a single compensation activity is connected.
     */
    function handleNewConnection(context) {
      const connection = context.connection,
            source = context.source,
            target = context.target;

      if (isCompensationBoundaryEvent$1(source) && isForCompensationAllowed(target)) {
        addIsForCompensationProperty(target);
        removeExistingAssociations(source, [ connection ]);
      }
    }

    function handleReconnection(context) {
      const newTarget = context.newTarget,
            oldSource = context.oldSource,
            oldTarget = context.oldTarget;

      // target changes
      if (oldTarget !== newTarget) {
        const source = oldSource;

        // oldTarget perspective
        if (isForCompensation$2(oldTarget)) {
          removeIsForCompensationProperty(oldTarget);
        }

        // newTarget perspective
        if (isCompensationBoundaryEvent$1(source) && isForCompensationAllowed(newTarget)) {
          addIsForCompensationProperty(newTarget);
        }
      }
    }

    function handlePropertiesUpdate(context) {
      const { element } = context;

      if (isForCompensation$2(element)) {
        removeDisallowedConnections(element);
        removeAttachments(element);
      } else if (isForCompensationAllowed(element)) {
        removeIncomingCompensationAssociations(element);
      }
    }

    /**
     * When replacing a boundary event, make sure the compensation activity is connected,
     * and remove the potential candidates for connection replacement to have a single compensation activity.
     */
    function handleReplacement(context) {
      const {
        newData,
        oldShape
      } = context;

      // from compensate boundary event
      if (isCompensationBoundaryEvent$1(context.oldShape) &&
        newData.eventDefinitionType !== 'bpmn:CompensateEventDefinition' ||
        newData.type !== 'bpmn:BoundaryEvent'
      ) {
        const targetConnection = oldShape.outgoing.find(
          ({ target }) => isForCompensation$2(target)
        );

        if (targetConnection && targetConnection.target) {
          context._connectionTarget = targetConnection.target;
        }
      }

      // to compensate boundary event
      else if (
        !isCompensationBoundaryEvent$1(context.oldShape) &&
        newData.eventDefinitionType === 'bpmn:CompensateEventDefinition' &&
        newData.type === 'bpmn:BoundaryEvent'
      ) {
        const targetConnection = oldShape.outgoing.find(
          ({ target }) => isForCompensationAllowed(target)
        );

        if (targetConnection && targetConnection.target) {
          context._connectionTarget = targetConnection.target;
        }

        removeOutgoingSequenceFlows(oldShape);
      }
    }

    function handleReplacementPostExecuted(context) {
      const { _connectionTarget: target, newShape } = context;

      if (target) {
        modeling.connect(newShape, target);
      }
    }

    function addIsForCompensationProperty(target) {
      modeling.updateProperties(target, { isForCompensation: true });
    }

    function removeIsForCompensationProperty(target) {
      modeling.updateProperties(target, { isForCompensation: undefined });
    }

    function removeDisallowedConnections(element) {

      for (const connection of element.incoming) {
        if (!bpmnRules.canConnect(connection.source, element)) {
          modeling.removeConnection(connection);
        }
      }

      for (const connection of element.outgoing) {
        if (!bpmnRules.canConnect(element, connection.target)) {
          modeling.removeConnection(connection);
        }
      }
    }

    function removeExistingAssociations(boundaryEvent, ignoredAssociations) {
      const associations = boundaryEvent.outgoing.filter(connection => is(connection, 'bpmn:Association'));
      const associationsToRemove = associations.filter(association => {
        return isForCompensation$2(association.target) && !ignoredAssociations.includes(association);
      });

      // remove existing associations
      associationsToRemove.forEach(association => modeling.removeConnection(association));
    }

    function removeAttachments(element) {
      const attachments = element.attachers.slice();

      if (!attachments.length) {
        return;
      }

      modeling.removeElements(attachments);
    }

    function removeIncomingCompensationAssociations(element) {
      const compensationAssociations = element.incoming.filter(
        connection => isCompensationBoundaryEvent$1(connection.source)
      );

      modeling.removeElements(compensationAssociations);
    }

    function removeOutgoingSequenceFlows(element) {
      const sequenceFlows = element.outgoing.filter(
        connection => is(connection, 'bpmn:SequenceFlow')
      );

      modeling.removeElements(sequenceFlows);
    }
  }

  e$2(CompensateBoundaryEventBehavior, CommandInterceptor);

  CompensateBoundaryEventBehavior.$inject = [
    'eventBus',
    'modeling',
    'bpmnRules'
  ];

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

  function isForCompensation$2(element) {
    const bo = getBusinessObject(element);
    return bo && bo.get('isForCompensation');
  }

  function isCompensationBoundaryEvent$1(element) {
    return element && is(element, 'bpmn:BoundaryEvent') &&
      hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
  }

  function isForCompensationAllowed(element) {
    return element && is(element, 'bpmn:Activity') && !isEventSubProcess(element);
  }

  /**
   * @typedef {import('didi').Injector} Injector
   */

  /**
   * @param {Injector} injector
   */
  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(parent, 'bpmn:Lane') && !is(shape, 'bpmn:Lane')) {
        context.parent = getParent(parent, 'bpmn:Participant');
      }
    });

  }


  CreateBehavior.$inject = [ 'injector' ];

  e$2(CreateBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   */

  /**
   * BPMN specific create data object behavior.
   *
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   */
  function CreateDataObjectBehavior(eventBus, bpmnFactory) {

    CommandInterceptor.call(this, eventBus);

    this.preExecute('shape.create', function(event) {

      var context = event.context,
          shape = context.shape;

      if (is(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'
  ];

  e$2(CreateDataObjectBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  var HORIZONTAL_PARTICIPANT_PADDING = 20,
      VERTICAL_PARTICIPANT_PADDING = 20;

  var PARTICIPANT_BORDER_WIDTH = 30;

  var HIGH_PRIORITY$g = 2000;


  /**
   * BPMN-specific behavior for creating participants.
   *
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function CreateParticipantBehavior(canvas, eventBus, modeling) {
    CommandInterceptor.call(this, eventBus);

    // fit participant
    eventBus.on([
      'create.start',
      'shape.move.start'
    ], HIGH_PRIORITY$g, function(event) {
      var context = event.context,
          shape = context.shape,
          rootElement = canvas.getRootElement();

      if (!is(shape, 'bpmn:Participant') ||
        !is(rootElement, 'bpmn:Process') ||
        !rootElement.children.length) {
        return;
      }

      // ignore connections, groups and labels
      var children = rootElement.children.filter(function(element) {
        return !is(element, 'bpmn:Group') &&
          !isLabel(element) &&
          !isConnection(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$1(shape, participantBounds);

      // assign create constraints
      context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
    });

    // force hovering process when creating first participant
    eventBus.on('create.start', HIGH_PRIORITY$g, 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(shape, 'bpmn:Participant') && is(rootElement, 'bpmn:Process')) {
        eventBus.on('element.hover', HIGH_PRIORITY$g, 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(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$g, function(context) {
      var elements = context.elements,
          parent = context.parent,
          participant = findParticipant(elements),
          hints;

      if (participant && is(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(shape, 'bpmn:Participant') && is(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$2(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 findParticipant(elements) {
    return find(elements, function(element) {
      return is(element, 'bpmn:Participant');
    });
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   */

  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$2(DataInputAssociationBehavior, CommandInterceptor);


  /**
   * Only call the given function when the event
   * changes a bpmn:DataInputAssociation.
   *
   * @param {Function} fn
   * @return {Function}
   */
  function ifDataInputAssociation(fn) {

    return function(event) {
      var context = event.context,
          connection = context.connection;

      if (is(connection, 'bpmn:DataInputAssociation')) {
        return fn(event);
      }
    };
  }

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('../BpmnUpdater').default} BpmnUpdater
   */

  /**
   * @implements {CommandHandler}
   *
   * @param {BpmnUpdater} bpmnUpdater
   */
  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);

    return [];
  };

  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);

    return [];
  };

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * BPMN specific data store behavior.
   *
   * @param {Canvas} canvas
   * @param {CommandStack} commandStack
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   */
  function DataStoreBehavior(
      canvas, commandStack, elementRegistry,
      eventBus) {

    CommandInterceptor.call(this, eventBus);

    commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);

    function getFirstParticipantWithProcessRef() {
      return elementRegistry.filter(function(element) {
        return is(element, 'bpmn:Participant') && getBusinessObject(element).processRef;
      })[0];
    }

    function getDataStores(element) {
      return element.children.filter(function(child) {
        return is(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(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(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(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(shape, 'bpmn:DataStoreReference') &&
          shape.type !== 'label' &&
          is(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(oldParent, 'bpmn:Collaboration')) {

        // do nothing if not necessary
        return;
      }

      if (is(shape, 'bpmn:DataStoreReference') &&
          shape.type !== 'label' &&
          is(parent, 'bpmn:Collaboration')) {

        var participant = is(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(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(newRoot, 'bpmn:Process')) {
          updateDataStoreParent(dataStore, newRoot);
        }

      });
    });
  }

  DataStoreBehavior.$inject = [
    'canvas',
    'commandStack',
    'elementRegistry',
    'eventBus',
  ];

  e$2(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(element.parent, type)) {
        return element.parent;
      }

      element = element.parent;
    }
  }

  var max$5 = Math.max,
      min$3 = Math.min;

  var DEFAULT_CHILD_BOX_PADDING = 20;

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').Rect} Rect
   * @typedef {import('../../util/Types').RectTRBL} RectTRBL
   */

  /**
   * Substract a TRBL from another
   *
   * @param {RectTRBL} trblA
   * @param {RectTRBL} trblB
   *
   * @return {RectTRBL}
   */
  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 {Rect} bounds the bounding box that should be resized
   * @param {Direction} direction in which the element is resized (nw, ne, se, sw)
   * @param {Point} delta of the resize operation
   *
   * @return {Rect} 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 {Rect} bounds
   * @param {RectTRBL} resize
   *
   * @return {Rect}
   */
  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$3 : max$5)(value, minValue);
    }

    if (isNumber(maxValue)) {
      value = (/top|left/.test(attr) ? max$5 : min$3)(value, maxValue);
    }

    return value;
  }

  function ensureConstraints$2(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$3(minBox.top, childrenBox.top),
      left: min$3(minBox.left, childrenBox.left),
      bottom: max$5(minBox.bottom, childrenBox.bottom),
      right: max$5(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 {Element} 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 {Shape|Shape[]} shapeOrChildren
   * @param {RectTRBL|number} padding
   *
   * @return {Rect}
   */
  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);
    }
  }

  /**
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   */

  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;


  /**
   * Return all lanes that are children of the given shape.
   *
   * @param  {Shape} shape
   * @param  {Shape[]} [collectedShapes]
   *
   * @return {Shape[]}
   */
  function collectLanes(shape, collectedShapes) {

    collectedShapes = collectedShapes || [];

    shape.children.filter(function(s) {
      if (is(s, 'bpmn:Lane')) {
        collectLanes(s, collectedShapes);

        collectedShapes.push(s);
      }
    });

    return collectedShapes;
  }


  /**
   * Return all lanes that are direct children of the given shape.
   *
   * @param {Shape} shape
   *
   * @return {Shape[]}
   */
  function getChildLanes(shape) {
    return shape.children.filter(function(c) {
      return is(c, 'bpmn:Lane');
    });
  }


  /**
   * Return the parent shape of the given lane.
   *
   * @param {Shape} shape
   *
   * @return {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 {Shape} shape
   * @param {Rect} newBounds
   *
   * @return { {
   *   shape: Shape;
   *   newBounds: Rect;
   * }[] }
   */
  function computeLanesResize(shape, newBounds) {

    var rootElement = getLanesRoot(shape);

    var initialShapes = is(rootElement, 'bpmn:Process') ? [] : [ rootElement ];

    var allLanes = collectLanes(rootElement, initialShapes),
        shapeTrbl = asTRBL(shape),
        shapeNewTrbl = asTRBL(newBounds),
        trblResize = getTRBLResize(shape, newBounds),
        resizeNeeded = [];

    var isHorizontalLane = isHorizontal$3(shape);

    allLanes.forEach(function(other) {

      if (other === shape) {
        return;
      }

      var topResize = isHorizontalLane ? 0 : trblResize.top,
          rightResize = isHorizontalLane ? trblResize.right : 0,
          bottomResize = isHorizontalLane ? 0 : trblResize.bottom,
          leftResize = isHorizontalLane ? trblResize.left : 0;

      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.left) {
        if (abs$4(otherTrbl.right - shapeTrbl.left) < 10) {
          rightResize = shapeNewTrbl.left - otherTrbl.right;
        }

        if (abs$4(otherTrbl.left - shapeTrbl.left) < 5) {
          leftResize = shapeNewTrbl.left - otherTrbl.left;
        }
      }

      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 (trblResize.right) {
        if (abs$4(otherTrbl.left - shapeTrbl.right) < 10) {
          leftResize = shapeNewTrbl.right - otherTrbl.left;
        }

        if (abs$4(otherTrbl.right - shapeTrbl.right) < 5) {
          rightResize = shapeNewTrbl.right - otherTrbl.right;
        }
      }

      if (topResize || rightResize || bottomResize || leftResize) {

        resizeNeeded.push({
          shape: other,
          newBounds: resizeTRBL(other, {
            top: topResize,
            right: rightResize,
            bottom: bottomResize,
            left: leftResize
          })
        });
      }

    });

    return resizeNeeded;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
   */

  var LOW_PRIORITY$i = 500;


  /**
   * BPMN specific delete lane behavior.
   *
   * @param {EventBus} eventBus
   * @param {SpaceTool} spaceTool
   */
  function DeleteLaneBehavior(eventBus, spaceTool) {

    CommandInterceptor.call(this, eventBus);


    function compensateLaneDelete(shape, oldParent) {
      var isHorizontalLane = isHorizontal$3(shape);

      var siblings = getChildLanes(oldParent);

      var topAffected = [];
      var bottomAffected = [];
      var leftAffected = [];
      var rightAffected = [];

      eachElement(siblings, function(element) {

        if (isHorizontalLane) {
          if (element.y > shape.y) {
            bottomAffected.push(element);
          } else {
            topAffected.push(element);
          }
        } else {
          if (element.x > shape.x) {
            rightAffected.push(element);
          } else {
            leftAffected.push(element);
          }
        }

        return element.children;
      });

      if (!siblings.length) {
        return;
      }

      var offset;

      if (isHorizontalLane) {
        if (bottomAffected.length && topAffected.length) {
          offset = shape.height / 2;
        } else {
          offset = shape.height;
        }
      } else {
        if (rightAffected.length && leftAffected.length) {
          offset = shape.width / 2;
        } else {
          offset = shape.width;
        }
      }

      var topAdjustments,
          bottomAdjustments,
          leftAdjustments,
          rightAdjustments;

      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');
      }

      if (leftAffected.length) {
        leftAdjustments = spaceTool.calculateAdjustments(
          leftAffected, 'x', offset, shape.x - 10);

        spaceTool.makeSpace(
          leftAdjustments.movingShapes,
          leftAdjustments.resizingShapes,
          { x: offset, y: 0 }, 'e');
      }

      if (rightAffected.length) {
        rightAdjustments = spaceTool.calculateAdjustments(
          rightAffected, 'x', -offset, shape.x + shape.width + 10);

        spaceTool.makeSpace(
          rightAdjustments.movingShapes,
          rightAdjustments.resizingShapes,
          { x: -offset, y: 0 }, 'w');
      }
    }


    /**
     * Adjust sizes of other lanes after lane deletion
     */
    this.postExecuted('shape.delete', LOW_PRIORITY$i, function(event) {

      var context = event.context,
          hints = context.hints,
          shape = context.shape,
          oldParent = context.oldParent;

      // only compensate lane deletes
      if (!is(shape, 'bpmn:Lane')) {
        return;
      }

      // compensate root deletes only
      if (hints && hints.nested) {
        return;
      }

      compensateLaneDelete(shape, oldParent);
    });
  }

  DeleteLaneBehavior.$inject = [
    'eventBus',
    'spaceTool'
  ];

  e$2(DeleteLaneBehavior, CommandInterceptor);

  /**
   * @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
   * @typedef {import('didi').Injector} Injector
   */

  var LOW_PRIORITY$h = 500;


  /**
   * Replace boundary event with intermediate event when creating or moving results in detached event.
   *
   * @param {BpmnReplace} bpmnReplace
   * @param {Injector} injector
   */
  function DetachEventBehavior(bpmnReplace, injector) {
    injector.invoke(CommandInterceptor, this);

    this._bpmnReplace = bpmnReplace;

    var self = this;

    this.postExecuted('elements.create', LOW_PRIORITY$h, 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$h, 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$2(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(shape) && is(shape, 'bpmn:BoundaryEvent') && !host;
  }

  function includes$6(array, item) {
    return array.indexOf(item) !== -1;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../rules/BpmnRules').default} BpmnRules
   * @typedef {import('../../modeling/Modeling').default} Modeling
   */

  /**
   * @param {EventBus} eventBus
   * @param {BpmnRules} bpmnRules
   * @param {Modeling} modeling
   */
  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$2(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$1({}, obj);
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function EventBasedGatewayBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Remove incoming sequence flows of event-based target when creating
     * sequence flow.
     *
     * 1. If source is event-based gateway remove all incoming sequence flows
     * 2. If source is not event-based gateway remove all incoming sequence flows
     * whose source is event-based gateway
     */
    this.preExecuted('connection.create', function(event) {
      var context = event.context,
          connection = context.connection,
          source = context.source,
          target = context.target,
          hints = context.hints;

      if (hints && hints.createElementsBehavior === false) {
        return;
      }

      if (!isSequenceFlow(connection)) {
        return;
      }

      var sequenceFlows = [];

      if (is(source, 'bpmn:EventBasedGateway')) {
        sequenceFlows = target.incoming
          .filter(flow =>
            flow !== connection &&
            isSequenceFlow(flow)
          );
      } else {
        sequenceFlows = target.incoming
          .filter(flow =>
            flow !== connection &&
            isSequenceFlow(flow) &&
            is(flow.source, 'bpmn:EventBasedGateway')
          );
      }

      sequenceFlows.forEach(function(sequenceFlow) {
        modeling.removeConnection(sequenceFlow);
      });
    });

    /**
     * Remove incoming sequence flows of event-based targets when replacing source
     * with event-based gateway.
     */
    this.preExecuted('shape.replace', function(event) {
      var context = event.context,
          newShape = context.newShape;

      if (!is(newShape, 'bpmn:EventBasedGateway')) {
        return;
      }

      var targets = newShape.outgoing.filter(isSequenceFlow)
        .reduce(function(targets, sequenceFlow) {
          if (!targets.includes(sequenceFlow.target)) {
            return targets.concat(sequenceFlow.target);
          }

          return targets;
        }, []);

      targets.forEach(function(target) {
        target.incoming.filter(isSequenceFlow).forEach(function(sequenceFlow) {
          const sequenceFlowsFromNewShape = target.incoming.filter(isSequenceFlow).filter(function(sequenceFlow) {
            return sequenceFlow.source === newShape;
          });

          if (sequenceFlow.source !== newShape || sequenceFlowsFromNewShape.length > 1) {
            modeling.removeConnection(sequenceFlow);
          }
        });
      });
    });
  }

  EventBasedGatewayBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  e$2(EventBasedGatewayBehavior, CommandInterceptor);

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

  function isSequenceFlow(connection) {
    return is(connection, 'bpmn:SequenceFlow');
  }

  /**
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   */

  var HIGH_PRIORITY$f = 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$f, 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(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 || isAny(shape, [ 'bpmn:Group', 'bpmn:TextAnnotation' ]))) {
        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$f, function(event) {
      var hover = event.hover;

      // ensure connections start/end on bpmn:Participant,
      // not the underlying bpmn:Lane
      if (is(hover, 'bpmn:Lane')) {
        event.hover = getLanesRoot(hover) || hover;
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }
    });


    eventBus.on([
      'bendpoint.move.hover'
    ], HIGH_PRIORITY$f, 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(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
        event.hover = getLanesRoot(hover) || hover;
        event.hoverGfx = elementRegistry.getGraphics(event.hover);
      }
    });


    eventBus.on([
      'connect.start'
    ], HIGH_PRIORITY$f, function(event) {
      var context = event.context,
          start = context.start;

      // ensure connect start on bpmn:Participant,
      // not the underlying bpmn:Lane
      if (is(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(shape, 'bpmn:Lane')) {
        event.shape = getLanesRoot(shape) || shape;
      }
    });

    // ensure lanes aren't resized without their parent participant when using
    // space tool
    eventBus.on('spaceTool.move', HIGHEST_PRIORITY, function(event) {
      var hover = event.hover;

      if (hover && is(hover, 'bpmn:Lane')) {
        event.hover = getLanesRoot(hover);
      }
    });

  }

  FixHoverBehavior.$inject = [
    'elementRegistry',
    'eventBus',
    'canvas'
  ];

  /**
   * @typedef {import('../../BpmnFactory').default} BpmnFactory
   *
   * @typedef {import('../../../model/Types').ModdleElement} ModdleElement
   */

  /**
   * Creates a new bpmn:CategoryValue inside a new bpmn:Category
   *
   * @param {BpmnFactory} bpmnFactory
   *
   * @return {ModdleElement}
   */
  function createCategory(bpmnFactory) {
    return bpmnFactory.create('bpmn:Category');
  }

  /**
   * Creates a new bpmn:CategoryValue inside a new bpmn:Category
   *
   * @param {BpmnFactory} bpmnFactory
   *
   * @return {ModdleElement}
   */
  function createCategoryValue(bpmnFactory) {
    return bpmnFactory.create('bpmn:CategoryValue');
  }

  /**
   * Adds category value to definitions
   *
   * @param {ModdleElement} categoryValue
   * @param {ModdleElement} category
   * @param {ModdleElement} definitions
   *
   * @return {ModdleElement}
   */
  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}
   */
  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
   *
   * @return {ModdleElement}
   */
  function unlinkCategory(category) {
    var definitions = category.$parent;

    if (definitions) {
      remove(definitions.get('rootElements'), category);
      category.$parent = null;
    }

    return category;
  }

  /**
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   * @typedef {import('../../../Modeler').default} Modeler
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../../copy-paste/ModdleCopy').default} ModdleCopy
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   */

  var LOWER_PRIORITY$1 = 770;


  /**
   * BPMN specific group behavior.
   *
   * @param {BpmnFactory} bpmnFactory
   * @param {Modeler} bpmnjs
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Injector} injector
   * @param {ModdleCopy} moddleCopy
   */
  function GroupBehavior(
      bpmnFactory,
      bpmnjs,
      elementRegistry,
      eventBus,
      injector,
      moddleCopy
  ) {
    injector.invoke(CommandInterceptor, this);

    /**
     * Returns all group element in the current registry.
     *
     * @return {Shape[]}
     */
    function getGroupElements() {
      return elementRegistry.filter(function(e) {
        return is(e, 'bpmn:Group');
      });
    }

    /**
     * Returns true if given category is referenced in one of the given elements.
     *
     * @param {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 {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
     *
     * @return {ModdleElement}
     */
    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(labelTarget, 'bpmn:Group')) {
        return;
      }

      setCategoryValue(labelTarget, context);
    });

    this.revert('label.create', function(event) {
      var context = event.context,
          labelTarget = context.labelTarget;

      if (!is(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(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(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(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(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$1, function(context) {
      var descriptor = context.descriptor,
          element = context.element;

      if (!is(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$1, 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$2(GroupBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   */

  /**
   * 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)))
    };
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * 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));
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * 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)) {
        var isHorizontal = di.get('isHorizontal');

        if (isHorizontal === undefined) {
          isHorizontal = true;
        }

        // set attribute directly to avoid modeling#updateProperty side effects
        di.set('isHorizontal', isHorizontal);
      }
    });

  }

  IsHorizontalFix.$inject = [ 'eventBus' ];

  e$2(IsHorizontalFix, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   *
   * @typedef { {
  *   type: 'bendpoint' | 'segment';
  *   position: Point;
  *   segmentIndex: number;
  *   bendpointIndex?: number;
  *   relativeLocation?: number;
  * } } Attachment
  */

  var sqrt = Math.sqrt,
      min$2 = Math.min,
      max$4 = 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 {Point[]} line
   *
   * @return {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$2(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;
  }

  /**
   * Get 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 {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 > -1e-6) {
      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$2(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
      n <= max$4(rangeStart, rangeEnd) + EQUAL_THRESHOLD
    );
  }

  /**
   * Calculate the mid between 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
    );
  }

  /**
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   *
   * @typedef {import('./LineAttachmentUtil').Attachment} Attachment
   *
   * @typedef { {
   *   point: Point;
   *   delta: Point;
   * } } AnchorPointAdjustment
   *
   * @typedef { {
   *   segmentMove?: {
  *     segmentStartIndex: number;
  *     newSegmentStartIndex: number;
  *   };
  *   bendpointMove?: {
  *     insert: boolean;
  *     bendpointIndex: number;
  *   };
  *   connectionStart: boolean;
  *   connectionEnd: boolean;
  * } } FindNewLineStartIndexHints
   */

  /**
   * @param {Point[]} oldWaypoints
   * @param {Point[]} newWaypoints
   * @param {Attachment} attachment
   * @param {FindNewLineStartIndexHints} hints
   *
   * @return {number}
   */
  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 {Point[]} newWaypoints
   * @param {Point[]} oldWaypoints
   * @param {FindNewLineStartIndexHints} hints
   *
   * @return {AnchorPointAdjustment} result
   */
  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 {Label} label
   * @param {Point[]} newWaypoints
   * @param {Point[]} oldWaypoints
   * @param {FindNewLineStartIndexHints} hints
   *
   * @return {Point}
   */
  function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
    var labelPosition = getMid(label);

    return getAnchorPointAdjustment(labelPosition, newWaypoints, oldWaypoints, hints).delta;
  }

  /**
   * @typedef {import('../model/Types').Shape} Shape
   *
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   */

  /**
   * Calculates the absolute point relative to the new element's position.
   *
   * @param {Point} point [absolute]
   * @param {Rect} oldBounds
   * @param {Rect} 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 {Shape} shape
   * @param {Rect} oldBounds
   * @param {Rect} newBounds
   *
   * @return {Point} 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 NAME_PROPERTY = 'name';
  var TEXT_PROPERTY = 'text';

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   * @typedef {import('../../../draw/TextRenderer').default} TextRenderer
   *
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   *
   * @typedef {Point[]} Line
   */

  /**
   * 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', onPropertyUpdate);
    this.postExecute('element.updateModdleProperties', e => {
      const elementBo = getBusinessObject(e.context.element);

      if (elementBo === e.context.moddleElement) {
        onPropertyUpdate(e);
      }
    });

    function onPropertyUpdate(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(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;

      if (isLabel(element) || !isLabelExternal(element)) {
        return;
      }

      // only create label if attribute available
      if (!getLabel(element)) {
        return;
      }

      modeling.updateLabel(element, getLabel(element));
    });

    // 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 });
      }
    });

    function getVisibleLabelAdjustment(event) {

      var context = event.context,
          connection = context.connection,
          label = connection.label,
          hints = assign$1({}, 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$1(labelMid, edges);

        var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);

        modeling.moveShape(label, delta);

      }

    });

  }

  e$2(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} referencePoint
   * @param {Rect} oldBounds
   * @param {Rect} newBounds
   *
   * @return {Point}
   */
  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 {Point} point
   * @param {Line[]} lines
   *
   * @return {Point}
   */
  function getReferencePoint$1(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 {Rect|Point} bounds
   *
   * @return {Line[]}
   */
  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 {Line[]} lines
   *
   * @return {Line}
   */
  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;
  }

  /**
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   *
   * @typedef {import('./LayoutUtil').FindNewLineStartIndexHints} FindNewLineStartIndexHints
   */

  /**
   * Calculate the new point after the connection waypoints got updated.
   *
   * @param {Point} position
   * @param {Point[]} newWaypoints
   * @param {Point[]} oldWaypoints
   * @param {FindNewLineStartIndexHints} hints
   *
   * @return {Point}
   */
  function getConnectionAdjustment(position, newWaypoints, oldWaypoints, hints) {
    return getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints).point;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * 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$1({}, 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$2(LayoutConnectionBehavior, CommandInterceptor);

  LayoutConnectionBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  /**
   * @typedef {import('../../../../core/Types').ConnectionLike} Connection
   * @typedef {import('../../../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../../../util/Types').Point} Point
   * @typedef {import('../../../../util/Types').Rect} Rect
   */

  /**
   * @param {Connection} connection
   * @param {Shape} shape
   * @param {Rect} oldBounds
   * @return {Point}
   */
  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 {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$1(waypoints, getOriginal);

    return filter(originalWaypoints, function(waypoint) {
      return isInsideBounds(waypoint, bounds);
    });
  }

  /**
   * Checks if point is inside bounds, incl. edges.
   *
   * @param {Point} point
   * @param {Rect} bounds
   */
  function isInsideBounds(point, bounds) {
    return getOrientation(bounds, point, 1) === 'intersect';
  }

  function getOriginal(point) {
    return point.original || point;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * BPMN-specific message flow behavior.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  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$2(MessageFlowBehavior, CommandInterceptor);

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

  function isParticipantCollapse(oldShape, newShape) {
    return is(oldShape, 'bpmn:Participant')
      && isExpanded(oldShape)
      && is(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(connection, 'bpmn:MessageFlow')) {
          incoming.push(connection);
        }
      });

      element.outgoing.forEach(function(connection) {
        if (is(connection, 'bpmn:MessageFlow')) {
          outgoing.push(connection);
        }
      });
    }, []);

    return {
      incoming: incoming,
      outgoing: outgoing
    };
  }

  const NON_INTERRUPTING_EVENT_TYPES = [
    'bpmn:MessageEventDefinition',
    'bpmn:TimerEventDefinition',
    'bpmn:EscalationEventDefinition',
    'bpmn:ConditionalEventDefinition',
    'bpmn:SignalEventDefinition'
  ];

  function canBeNonInterrupting(shape) {

    const businessObject = getBusinessObject(shape);

    if (
      !is(businessObject, 'bpmn:BoundaryEvent') &&
      !(is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent))
    ) {
      return false;
    }

    const eventDefinitions = businessObject.get('eventDefinitions');
    if (!eventDefinitions || !eventDefinitions.length) {
      return false;
    }

    return NON_INTERRUPTING_EVENT_TYPES.some(event => is(eventDefinitions[0], event));
  }

  function getInterruptingProperty(shape) {
    return is(shape, 'bpmn:BoundaryEvent') ? 'cancelActivity' : 'isInterrupting';
  }

  function NonInterruptingBehavior(injector, modeling) {
    injector.invoke(CommandInterceptor, this);

    this.postExecuted('shape.replace', function(event) {
      const oldShape = event.context.oldShape;
      const newShape = event.context.newShape;
      const hints = event.context.hints;

      if (!canBeNonInterrupting(newShape)) {
        return;
      }

      const property = getInterruptingProperty(newShape);
      const isExplicitChange = hints.targetElement && hints.targetElement[property] !== undefined;

      if (isExplicitChange) {
        return;
      }

      const isOldInterrupting = getBusinessObject(oldShape).get(property);
      const isNewInterruptingDefault = getBusinessObject(newShape).get(property);

      if (isOldInterrupting === isNewInterruptingDefault) {
        return;
      }

      modeling.updateProperties(newShape, {
        [property]: isOldInterrupting
      });
    });
  }

  NonInterruptingBehavior.$inject = [ 'injector', 'modeling' ];

  e$2(NonInterruptingBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * BPMN specific behavior ensuring that bpmndi:Label's dc:Bounds are removed
   * when shape is resized.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  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$2(RemoveEmbeddedLabelBoundsBehavior, CommandInterceptor);

  RemoveEmbeddedLabelBoundsBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../rules/BpmnRules').default} BpmnRules
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * @param {EventBus} eventBus
   * @param {BpmnRules} bpmnRules
   * @param {Modeling} 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(inConnection, 'bpmn:SequenceFlow') || !is(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$2(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])
      ];
    }
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * BPMN specific remove behavior.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  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(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
        var process = modeling.makeProcess();

        // move all root elements from collaboration to process
        var children = collaborationRoot.children.slice();

        modeling.moveElements(children, { x: 0, y: 0 }, process);
      }
    }, true);

  }

  RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];

  e$2(RemoveParticipantBehavior, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../../rules/BpmnRules').default} BpmnRules
   * @typedef {import('didi').Injector} Injector
   */

  /**
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   * @param {BpmnRules} bpmnRules
   * @param {Injector} injector
   */
  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(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(connection, 'bpmn:MessageFlow')) {

        if (!bpmnRules.canConnectMessageFlow(source, target)) {
          remove = true;
        }

        if (bpmnRules.canConnectSequenceFlow(source, target)) {
          replacementType = 'bpmn:SequenceFlow';
        }
      }

      // 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,
        associationDirection: allowed.associationDirection,
        waypoints: connection.waypoints.slice()
      });

      // remove old connection unless it's already removed
      if (connection.parent) {
        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$2(ReplaceConnectionBehavior, CommandInterceptor);

  ReplaceConnectionBehavior.$inject = [
    'eventBus',
    'modeling',
    'bpmnRules',
    'injector'
  ];

  /**
   * @typedef {import('../../replace/BpmnReplace').default} BpmnReplace
   * @typedef {import('../../rules/BpmnRules').default} BpmnRules
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('diagram-js/lib/features/selection/Selection').default} Selection
   */

  /**
   * BPMN-specific replace behavior.
   *
   * @param {BpmnReplace} bpmnReplace
   * @param {BpmnRules} bpmnRules
   * @param {ElementRegistry} elementRegistry
   * @param {Injector} injector
   * @param {Modeling} modeling
   * @param {Selection} selection
   */
  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$2(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$e = 1500;

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions
   * @typedef {import('diagram-js/lib/util/Types').Direction} Direction
   * @typedef {import('diagram-js/lib/util/Types').RectTRBL} RectTRBL
   */

  /**
   * @type {Dimensions}
   */
  var GROUP_MIN_DIMENSIONS = { width: 140, height: 120 };

  /**
   * @type {Dimensions}
   */
  var LANE_MIN_DIMENSIONS = { width: 300, height: 60 };

  /**
   * @type {Dimensions}
   */
  var VERTICAL_LANE_MIN_DIMENSIONS = { width: 60, height: 300 };

  /**
   * @type {Dimensions}
   */
  var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 };

  /**
   * @type {Dimensions}
   */
  var VERTICAL_PARTICIPANT_MIN_DIMENSIONS = { width: 150, height: 300 };

  /**
   * @type {Dimensions}
   */
  var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 };

  /**
   * @type {Dimensions}
   */
  var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };

  /**
   * Set minimum bounds/resize constraints on resize.
   *
   * @param {EventBus} eventBus
   */
  function ResizeBehavior$1(eventBus) {
    eventBus.on('resize.start', HIGH_PRIORITY$e, function(event) {
      var context = event.context,
          shape = context.shape,
          direction = context.direction,
          balanced = context.balanced;

      if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
        context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
      }

      if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
        context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
      }

      if (is(shape, 'bpmn:TextAnnotation')) {
        context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
      }
    });
  }

  ResizeBehavior$1.$inject = [ 'eventBus' ];


  var abs$2 = Math.abs,
      min$1 = Math.min,
      max$3 = 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$1);
  }

  function addMax(trbl, attr, value) {
    return addToTrbl(trbl, attr, value, max$3);
  }

  var LANE_PADDING = { top: 20, left: 50, right: 20, bottom: 20 },
      VERTICAL_LANE_PADDING = { top: 50, left: 20, right: 20, bottom: 20 };

  /**
   * @param {Shape} laneShape
   * @param {Direction} resizeDirection
   * @param {boolean} [balanced=false]
   *
   * @return { {
   *   min: RectTRBL;
   *   max: RectTRBL;
   * } }
   */
  function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
    var lanesRoot = getLanesRoot(laneShape);

    var isFirst = true,
        isLast = true;

    var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);

    var laneTrbl = asTRBL(laneShape);

    var maxTrbl = {},
        minTrbl = {};

    var isHorizontalLane = isHorizontal$3(laneShape);

    var minDimensions = isHorizontalLane ? LANE_MIN_DIMENSIONS : VERTICAL_LANE_MIN_DIMENSIONS;

    if (/n/.test(resizeDirection)) {
      minTrbl.top = laneTrbl.bottom - minDimensions.height;
    } else if (/e/.test(resizeDirection)) {
      minTrbl.right = laneTrbl.left + minDimensions.width;
    } else if (/s/.test(resizeDirection)) {
      minTrbl.bottom = laneTrbl.top + minDimensions.height;
    } else if (/w/.test(resizeDirection)) {
      minTrbl.left = laneTrbl.right - minDimensions.width;
    }

    // min/max size based on related lanes
    allLanes.forEach(function(other) {

      var otherTrbl = asTRBL(other);

      // lane flags
      if (isHorizontalLane) {
        if (otherTrbl.top < (laneTrbl.top - 10)) {
          isFirst = false;
        }
        if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
          isLast = false;
        }
      }
      else {
        if (otherTrbl.left < (laneTrbl.left - 10)) {
          isFirst = false;
        }
        if (otherTrbl.right > (laneTrbl.right + 10)) {
          isLast = false;
        }
      }

      if (/n/.test(resizeDirection)) {

        // max top size (based on next element)
        if (balanced && abs$2(laneTrbl.top - otherTrbl.bottom) < 10) {
          addMax(maxTrbl, 'top', otherTrbl.top + minDimensions.height);
        }

        // min top size (based on self or nested element)
        if (abs$2(laneTrbl.top - otherTrbl.top) < 5) {
          addMin(minTrbl, 'top', otherTrbl.bottom - minDimensions.height);
        }
      }

      if (/e/.test(resizeDirection)) {

        // max right size (based on previous element)
        if (balanced && abs$2(laneTrbl.right - otherTrbl.left) < 10) {
          addMin(maxTrbl, 'right', otherTrbl.right - minDimensions.width);
        }

        // min right size (based on self or nested element)
        if (abs$2(laneTrbl.right - otherTrbl.right) < 5) {
          addMax(minTrbl, 'right', otherTrbl.left + minDimensions.width);
        }
      }

      if (/s/.test(resizeDirection)) {

        // max bottom size (based on previous element)
        if (balanced && abs$2(laneTrbl.bottom - otherTrbl.top) < 10) {
          addMin(maxTrbl, 'bottom', otherTrbl.bottom - minDimensions.height);
        }

        // min bottom size (based on self or nested element)
        if (abs$2(laneTrbl.bottom - otherTrbl.bottom) < 5) {
          addMax(minTrbl, 'bottom', otherTrbl.top + minDimensions.height);
        }
      }

      if (/w/.test(resizeDirection)) {

        // max left size (based on next element)
        if (balanced && abs$2(laneTrbl.left - otherTrbl.right) < 10) {
          addMax(maxTrbl, 'left', otherTrbl.left + minDimensions.width);
        }

        // min left size (based on self or nested element)
        if (abs$2(laneTrbl.left - otherTrbl.left) < 5) {
          addMin(minTrbl, 'left', otherTrbl.right - minDimensions.width);
        }
      }
    });

    // max top/bottom/left/right size based on flow nodes
    var flowElements = lanesRoot.children.filter(function(s) {
      return !s.hidden && !s.waypoints && (is(s, 'bpmn:FlowElement') || is(s, 'bpmn:Artifact'));
    });

    var padding = isHorizontalLane ? LANE_PADDING : VERTICAL_LANE_PADDING;

    flowElements.forEach(function(flowElement) {

      var flowElementTrbl = asTRBL(flowElement);

      // vertical lane will resize from top with respect to flow element irrespective of first or last lane
      if (/n/.test(resizeDirection) && (!isHorizontalLane || isFirst)) {
        addMin(minTrbl, 'top', flowElementTrbl.top - padding.top);
      }

      // horizonal lane will resize from right with respect to flow element irrespective of first or last lane
      if (/e/.test(resizeDirection) && (isHorizontalLane || isLast)) {
        addMax(minTrbl, 'right', flowElementTrbl.right + padding.right);
      }

      // vertical lane will resize from bottom with respect to flow element irrespective of first or last lane
      if (/s/.test(resizeDirection) && (!isHorizontalLane || isLast)) {
        addMax(minTrbl, 'bottom', flowElementTrbl.bottom + padding.bottom);
      }

      // horizonal lane will resize from left with respect to flow element irrespective of first or last lane
      if (/w/.test(resizeDirection) && (isHorizontalLane || isFirst)) {
        addMin(minTrbl, 'left', flowElementTrbl.left - padding.left);
      }
    });
    return {
      min: minTrbl,
      max: maxTrbl
    };
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  var SLIGHTLY_HIGHER_PRIORITY = 1001;


  /**
   * Invoke {@link Modeling#resizeLane} instead of {@link Modeling#resizeShape}
   * when resizing a lane or participant shape.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function ResizeLaneBehavior(eventBus, modeling) {

    eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
      var context = event.context,
          shape = context.shape;

      if (is(shape, 'bpmn:Lane') || is(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(shape, 'bpmn:Lane') || is(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'
  ];

  /**
   * @typedef {import('../../../Modeler').default} Modeler
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../../copy-paste/ModdleCopy').default} ModdleCopy
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   */

  var LOW_PRIORITY$g = 500;


  /**
   * Add referenced root elements (error, escalation, message, signal) if they don't exist.
   * Copy referenced root elements on copy & paste.
   *
   * @param {Modeler} bpmnjs
   * @param {EventBus} eventBus
   * @param {Injector} injector
   * @param {ModdleCopy} moddleCopy
   * @param {BpmnFactory} bpmnFactory
   */
  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(eventDefinition, 'bpmn:ErrorEventDefinition')) {
        return 'errorRef';
      } else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
        return 'escalationRef';
      } else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
        return 'messageRef';
      } else if (is(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',
      'element.updateProperties',
      'element.updateModdleProperties'
    ], function(context) {
      var shape = context.shape || context.element;

      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',
      'element.updateProperties',
      'element.updateModdleProperties'
    ], 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$g, 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$2(RootElementReferenceBehavior, CommandInterceptor);

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

  function hasAnyEventDefinition(element, types) {
    if (!isArray$3(types)) {
      types = [ types ];
    }

    return some(types, function(type) {
      return hasEventDefinition$2(element, type);
    });
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('../../../model/Types').Shape} Shape
   */

  var max$2 = Math.max;

  /**
   * @param {EventBus} eventBus
   */
  function SpaceToolBehavior$1(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(shape, 'bpmn:Participant')) {
          minDimensions[ id ] = getParticipantMinDimensions(shape, axis, start);
        }

        if (is(shape, 'bpmn:Lane')) {
          minDimensions[ id ] = isHorizontal$3(shape) ? LANE_MIN_DIMENSIONS : VERTICAL_LANE_MIN_DIMENSIONS;
        }

        if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
          minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS;
        }

        if (is(shape, 'bpmn:TextAnnotation')) {
          minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS;
        }

        if (is(shape, 'bpmn:Group')) {
          minDimensions[ id ] = GROUP_MIN_DIMENSIONS;
        }
      });

      return minDimensions;
    });
  }

  SpaceToolBehavior$1.$inject = [ 'eventBus' ];


  // helpers //////////
  function isHorizontalAxis(axis) {
    return axis === 'x';
  }

  /**
   * Get minimum dimensions for participant taking lanes into account.
   *
   * @param {Shape} participant
   * @param {Axis} axis
   * @param {number} start
   *
   * @return {number}
   */
  function getParticipantMinDimensions(participant, axis, start) {
    var isHorizontalLane = isHorizontal$3(participant);

    if (!hasChildLanes(participant)) {
      return isHorizontalLane ? PARTICIPANT_MIN_DIMENSIONS : VERTICAL_PARTICIPANT_MIN_DIMENSIONS;
    }

    var isHorizontalResize = isHorizontalAxis(axis);
    var minDimensions = {};

    if (isHorizontalResize) {
      if (isHorizontalLane) {
        minDimensions = PARTICIPANT_MIN_DIMENSIONS;
      } else {
        minDimensions = {
          width: getParticipantMinWidth(participant, start, isHorizontalResize),
          height: VERTICAL_PARTICIPANT_MIN_DIMENSIONS.height
        };
      }

    } else {
      if (isHorizontalLane) {
        minDimensions = {
          width: PARTICIPANT_MIN_DIMENSIONS.width,
          height: getParticipantMinHeight(participant, start, isHorizontalResize)
        };
      } else {
        minDimensions = VERTICAL_PARTICIPANT_MIN_DIMENSIONS;
      }
    }

    return minDimensions;
  }

  /**
   * Get minimum height for participant taking lanes into account.
   *
   * @param {Shape} participant
   * @param {number} start
   * @param {boolean} isHorizontalResize
   *
   * @return {number}
   */
  function getParticipantMinHeight(participant, start, isHorizontalResize) {
    var lanesMinHeight;
    lanesMinHeight = getLanesMinHeight(participant, start, isHorizontalResize);
    return max$2(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight);
  }

  /**
   * Get minimum width for participant taking lanes into account.
   *
   * @param {Shape} participant
   * @param {number} start
   * @param {boolean} isHorizontalResize
   *
   * @return {number}
   */
  function getParticipantMinWidth(participant, start, isHorizontalResize) {
    var lanesMinWidth;
    lanesMinWidth = getLanesMinWidth(participant, start, isHorizontalResize);
    return max$2(VERTICAL_PARTICIPANT_MIN_DIMENSIONS.width, lanesMinWidth);
  }

  function hasChildLanes(element) {
    return !!getChildLanes(element).length;
  }

  function getLanesMinHeight(participant, resizeStart, isHorizontalResize) {
    var lanes = getChildLanes(participant),
        resizedLane;

    // find the nested lane which is currently resized
    resizedLane = findResizedLane(lanes, resizeStart, isHorizontalResize);

    // resized lane cannot shrink below the minimum height
    // but remaining lanes' dimensions are kept intact
    return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height;
  }

  function getLanesMinWidth(participant, resizeStart, isHorizontalResize) {
    var lanes = getChildLanes(participant),
        resizedLane;

    // find the nested lane which is currently resized
    resizedLane = findResizedLane(lanes, resizeStart, isHorizontalResize);

    // resized lane cannot shrink below the minimum width
    // but remaining lanes' dimensions are kept intact
    return participant.width - resizedLane.width + VERTICAL_LANE_MIN_DIMENSIONS.width;
  }

  /**
   * Find nested lane which is currently resized.
   *
   * @param {Shape[]} lanes
   * @param {number} resizeStart
   * @param {boolean} isHorizontalResize
   *
   * @return {Shape}
   */
  function findResizedLane(lanes, resizeStart, isHorizontalResize) {
    var i, lane, childLanes;

    for (i = 0; i < lanes.length; i++) {
      lane = lanes[i];

      // resizing current lane or a lane nested
      if (!isHorizontalResize && resizeStart >= lane.y && resizeStart <= lane.y + lane.height ||
          isHorizontalResize && resizeStart >= lane.x && resizeStart <= lane.x + lane.width) {

        childLanes = getChildLanes(lane);

        // a nested lane is resized
        if (childLanes.length) {
          return findResizedLane(childLanes, resizeStart, isHorizontalResize);
        }

        // current lane is the resized one
        return lane;
      }
    }
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../ElementFactory').default} ElementFactory
   * @typedef {import('../BpmnFactory').default} BpmnFactory
   * @typedef {import('../../../Modeler').default} Modeler
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Root} Root
   * @typedef {import('../../../model/Types').ModdleElement} ModdleElement
   */

  var LOW_PRIORITY$f = 400;
  var HIGH_PRIORITY$d = 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 {Modeler} 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(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(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(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$f, function(context) {
      var shape = context.shape;

      if (!is(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$f, function(context) {
      var shape = context.shape;

      if (!is(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$d, function(context) {
      var shape = context.shape;

      if (!is(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(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$2(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 {Root} source
   * @param {Root} 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 {Element[]} elements
   * @param {boolean} [hidden=false]
   *
   * @return {Element[]}
   */
  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 root element to the BPMNDI diagrams.
   *
   * @param {Root|ModdleElement} planeElement
   *
   * @return {Root}
   */
  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 {ModdleElement} bpmnElement
   *
   * @return {Root}
   */
  SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
    var bpmnFactory = this._bpmnFactory,
        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 {Root} rootElement
   *
   * @return {ModdleElement}
   */
  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'
  ];

  /**
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * 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(newShape, 'bpmn:SubProcess') ||
        is(newShape,'bpmn:AdHocSubProcess') ||
        ! (is(oldShape, 'bpmn:Task') || is(oldShape, 'bpmn:CallActivity')) ||
        !isExpanded(newShape)
      ) {
        return;
      }

      var position = getStartEventPosition(newShape);

      modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
    });
  }

  SubProcessStartEventBehavior.$inject = [
    'injector',
    'modeling'
  ];

  e$2(SubProcessStartEventBehavior, CommandInterceptor);

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

  function getStartEventPosition(shape) {
    return {
      x: shape.x + shape.width / 6,
      y: shape.y + shape.height / 2
    };
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  function TextAnnotationBehavior(eventBus) {

    CommandInterceptor.call(this, eventBus);

    // On Append, TextAnnotations will be created on the Root.
    // The default for connections will create the connection in the parent of
    // the source element, so we overwrite the parent here.
    this.preExecute('connection.create', function(context) {
      const { target } = context;

      if (!is(target, 'bpmn:TextAnnotation')) {
        return;
      }

      context.parent = target.parent;
    }, true);

    this.preExecute([ 'shape.create', 'shape.resize', 'elements.move' ], function(context) {
      const shapes = context.shapes || [ context.shape ];

      if (shapes.length === 1 && is(shapes[0], 'bpmn:TextAnnotation')) {
        context.hints = context.hints || {};

        context.hints.autoResize = false;
      }
    }, true);
  }

  e$2(TextAnnotationBehavior, CommandInterceptor);

  TextAnnotationBehavior.$inject = [
    'eventBus'
  ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   */

  /**
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  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$2(ToggleCollapseConnectionBehaviour, CommandInterceptor);

  ToggleCollapseConnectionBehaviour.$inject = [
    'eventBus',
    'modeling',
  ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../ElementFactory').default} ElementFactory
   * @typedef {import('../Modeling').default} Modeling
   */

  var LOW_PRIORITY$e = 500;

  /**
   * @param {EventBus} eventBus
   * @param {ElementFactory} elementFactory
   * @param {Modeling} modeling
   */
  function ToggleElementCollapseBehaviour(
      eventBus, elementFactory, modeling) {

    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$e, function(e) {

      var context = e.context,
          shape = context.shape;

      if (!is(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$e, 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$e, 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$2(ToggleElementCollapseBehaviour, CommandInterceptor);

  ToggleElementCollapseBehaviour.$inject = [
    'eventBus',
    'elementFactory',
    'modeling'
  ];


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

  function filterVisible(elements) {
    return elements.filter(function(e) {
      return !e.hidden;
    });
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('../Modeling').default} Modeling
   *
   * @typedef {import('../../../model/Types').Moddle} Moddle
   */

  /**
   * 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(shape)) {
        return;
      }

      if (is(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(rootElement, 'bpmn:Collaboration')) {
        moddle.ids.unclaim(rootElementBo.id);
      }
    });
  }

  e$2(UnclaimIdBehavior, CommandInterceptor);

  UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} 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$2(DeleteSequenceFlowBehavior, CommandInterceptor);

  DeleteSequenceFlowBehavior.$inject = [
    'eventBus',
    'modeling'
  ];


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

  function isDefaultFlow(connection, source) {

    if (!is(connection, 'bpmn:SequenceFlow')) {
      return false;
    }

    var sourceBo = getBusinessObject(source),
        sequenceFlow = getBusinessObject(connection);

    return sourceBo.get('default') === sequenceFlow;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../Modeling').default} Modeling
   */

  var LOW_PRIORITY$d = 500,
      HIGH_PRIORITY$c = 5000;


  /**
   * BPMN specific delete lane behavior.
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function UpdateFlowNodeRefsBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    /**
     * Update Lane#flowNodeRefs and FlowNode#lanes with every flow node
     * move/resize and lane move/resize. Groups elements to recompute containments
     * as efficient as possible.
     */

    // the update context
    var context;


    function initContext() {
      context = context || new UpdateContext();
      context.enter();

      return context;
    }

    function getContext() {
      if (!context) {
        throw new Error('out of bounds release');
      }

      return context;
    }

    function releaseContext() {

      if (!context) {
        throw new Error('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$c, function(event) {
      initContext();
    });

    this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY$d, 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(shape, 'bpmn:Lane')) {
        updateContext.addLane(shape);
      }

      if (is(shape, 'bpmn:FlowNode')) {
        updateContext.addFlowNode(shape);
      }
    });
  }

  UpdateFlowNodeRefsBehavior.$inject = [
    'eventBus',
    'modeling'
  ];

  e$2(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;
    };
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../rules/BpmnRules').default} BpmnRules
   * @typedef {import('../Modeling').default} Modeling
   */


  /**
   * A behavior that sets the property of Compensation Activity after paste operation
   *
   * @param {EventBus} eventBus
   * @param {Modeling} modeling
   */
  function SetCompensationActivityAfterPasteBehavior(eventBus, modeling) {

    CommandInterceptor.call(this, eventBus);

    this.postExecuted('elements.create', function(event) {
      const context = event.context,
            elements = context.elements;

      // check if compensation activity is connected to compensation boundary event
      for (const element of elements) {
        if (isForCompensation$1(element) && !isConnectedToCompensationBoundaryEvent(element)) {
          modeling.updateProperties(element, { isForCompensation: undefined });
        }
      }
    });
  }

  e$2(SetCompensationActivityAfterPasteBehavior, CommandInterceptor);

  SetCompensationActivityAfterPasteBehavior.$inject = [
    'eventBus',
    'modeling'
  ];


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

  function isForCompensation$1(element) {
    const bo = getBusinessObject(element);
    return bo && bo.isForCompensation;
  }

  function isCompensationBoundaryEvent(element) {
    return element && is(element, 'bpmn:BoundaryEvent') &&
        hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
  }

  function isConnectedToCompensationBoundaryEvent(element) {
    const compensationAssociations = element.incoming.filter(
      connection => isCompensationBoundaryEvent(connection.source)
    );
    if (compensationAssociations.length > 0) {
      return true;
    }
    return false;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var BehaviorModule = {
    __init__: [
      'adaptiveLabelPositioningBehavior',
      'appendBehavior',
      'associationBehavior',
      'attachEventBehavior',
      'boundaryEventBehavior',
      'compensateBoundaryEventBehaviour',
      'createBehavior',
      'createDataObjectBehavior',
      'createParticipantBehavior',
      'dataInputAssociationBehavior',
      'dataStoreBehavior',
      'deleteLaneBehavior',
      'detachEventBehavior',
      'dropOnFlowBehavior',
      'eventBasedGatewayBehavior',
      'fixHoverBehavior',
      'groupBehavior',
      'importDockingFix',
      'isHorizontalFix',
      'labelBehavior',
      'layoutConnectionBehavior',
      'messageFlowBehavior',
      'nonInterruptingBehavior',
      'removeElementBehavior',
      'removeEmbeddedLabelBoundsBehavior',
      'removeParticipantBehavior',
      'replaceConnectionBehavior',
      'replaceElementBehaviour',
      'resizeBehavior',
      'resizeLaneBehavior',
      'rootElementReferenceBehavior',
      'spaceToolBehavior',
      'subProcessPlaneBehavior',
      'subProcessStartEventBehavior',
      'textAnnotationBehavior',
      'toggleCollapseConnectionBehaviour',
      'toggleElementCollapseBehaviour',
      'unclaimIdBehavior',
      'updateFlowNodeRefsBehavior',
      'unsetDefaultFlowBehavior',
      'setCompensationActivityAfterPasteBehavior'
    ],
    adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
    appendBehavior: [ 'type', AppendBehavior ],
    associationBehavior: [ 'type', AssociationBehavior ],
    attachEventBehavior: [ 'type', AttachEventBehavior ],
    boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
    compensateBoundaryEventBehaviour: [ 'type', CompensateBoundaryEventBehavior ],
    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 ],
    nonInterruptingBehavior: [ 'type', NonInterruptingBehavior ],
    removeElementBehavior: [ 'type', RemoveElementBehavior ],
    removeEmbeddedLabelBoundsBehavior: [ 'type', RemoveEmbeddedLabelBoundsBehavior ],
    removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
    replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
    replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
    resizeBehavior: [ 'type', ResizeBehavior$1 ],
    resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
    rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
    spaceToolBehavior: [ 'type', SpaceToolBehavior$1 ],
    subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
    subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
    textAnnotationBehavior: [ 'type', TextAnnotationBehavior ],
    toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
    toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
    unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
    unsetDefaultFlowBehavior: [ 'type', DeleteSequenceFlowBehavior ],
    updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
    setCompensationActivityAfterPasteBehavior: [ 'type', SetCompensationActivityAfterPasteBehavior ]
  };

  /**
   * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   */

  /**
   * @param {Point} position
   * @param {Rect} targetBounds
   *
   * @return {DirectionTRBL|null}
   */
  function getBoundaryAttachment(position, targetBounds) {

    var orientation = getOrientation(position, targetBounds, -15);

    if (orientation !== 'intersect') {
      return orientation;
    } else {
      return null;
    }
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   *
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   *
   * @typedef { {
   *   associationDirection?: 'None' | 'One' | 'Both';
   *   type: string;
   * } | boolean | null } CanConnectResult
   *
   * @typedef { {
   *   id: string;
   *   type: string;
   * } | boolean } CanReplaceResult
   */

  /**
   * BPMN-specific modeling rules.
   *
   * @param {EventBus} eventBus
   */
  function BpmnRules(eventBus) {
    RuleProvider.call(this, eventBus);
  }

  e$2(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(target) && !canInsert(elements, target)) {
        return false;
      }

      return every(elements, function(element) {
        if (isConnection(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.canConnectCompensationAssociation = canConnectCompensationAssociation;

  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'
    ]);
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function nonExistingOrLabel(element) {
    return !element || isLabel(element);
  }

  function isSame$1(a, b) {
    return a === b;
  }

  /**
   * @param {Element} element
   *
   * @return {ModdleElement}
   */
  function getOrganizationalParent(element) {

    do {
      if (is(element, 'bpmn:Process')) {
        return getBusinessObject(element);
      }

      if (is(element, 'bpmn:Participant')) {
        return (
          getBusinessObject(element).processRef ||
          getBusinessObject(element)
        );
      }
    } while ((element = element.parent));

  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isTextAnnotation(element) {
    return is(element, 'bpmn:TextAnnotation');
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isGroup(element) {
    return is(element, 'bpmn:Group') && !element.labelTarget;
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isCompensationBoundary(element) {
    return is(element, 'bpmn:BoundaryEvent') &&
           hasEventDefinition$1(element, 'bpmn:CompensateEventDefinition');
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isForCompensation(element) {
    return getBusinessObject(element).isForCompensation;
  }

  /**
   * @param {Element} a
   * @param {Element} b
   *
   * @return {boolean}
   */
  function isSameOrganization(a, b) {
    var parentA = getOrganizationalParent(a),
        parentB = getOrganizationalParent(b);

    return parentA === parentB;
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isMessageFlowSource(element) {
    return (
      is(element, 'bpmn:InteractionNode') &&
      !is(element, 'bpmn:BoundaryEvent') && (
        !is(element, 'bpmn:Event') || (
          is(element, 'bpmn:ThrowEvent') &&
          hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
        )
      )
    );
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isMessageFlowTarget(element) {
    return (
      is(element, 'bpmn:InteractionNode') &&
      !isForCompensation(element) && (
        !is(element, 'bpmn:Event') || (
          is(element, 'bpmn:CatchEvent') &&
          hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
        )
      ) && !(
        is(element, 'bpmn:BoundaryEvent') &&
        !hasEventDefinition$1(element, 'bpmn:MessageEventDefinition')
      )
    );
  }

  /**
   * @param {Element} element
   *
   * @return {ModdleElement}
   */
  function getScopeParent(element) {

    var parent = element;

    while ((parent = parent.parent)) {

      if (is(parent, 'bpmn:FlowElementsContainer')) {
        return getBusinessObject(parent);
      }

      if (is(parent, 'bpmn:Participant')) {
        return getBusinessObject(parent).processRef;
      }
    }

    return null;
  }

  /**
   * @param {Element} a
   * @param {Element} b
   *
   * @return {boolean}
   */
  function isSameScope(a, b) {
    var scopeParentA = getScopeParent(a),
        scopeParentB = getScopeParent(b);

    return scopeParentA === scopeParentB;
  }

  /**
   * @param {Element} element
   * @param {string} eventDefinition
   *
   * @return {boolean}
   */
  function hasEventDefinition$1(element, eventDefinition) {
    var businessObject = getBusinessObject(element);

    return !!find(businessObject.eventDefinitions || [], function(definition) {
      return is(definition, eventDefinition);
    });
  }

  /**
   * @param {Element} element
   * @param {string} eventDefinition
   *
   * @return {boolean}
   */
  function hasEventDefinitionOrNone(element, eventDefinition) {
    var businessObject = getBusinessObject(element);

    return (businessObject.eventDefinitions || []).every(function(definition) {
      return is(definition, eventDefinition);
    });
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isSequenceFlowSource(element) {
    return (
      is(element, 'bpmn:FlowNode') &&
      !is(element, 'bpmn:EndEvent') &&
      !isEventSubProcess(element) &&
      !(is(element, 'bpmn:IntermediateThrowEvent') &&
        hasEventDefinition$1(element, 'bpmn:LinkEventDefinition')
      ) &&
      !isCompensationBoundary(element) &&
      !isForCompensation(element)
    );
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isSequenceFlowTarget(element) {
    return (
      is(element, 'bpmn:FlowNode') &&
      !is(element, 'bpmn:StartEvent') &&
      !is(element, 'bpmn:BoundaryEvent') &&
      !isEventSubProcess(element) &&
      !(is(element, 'bpmn:IntermediateCatchEvent') &&
        hasEventDefinition$1(element, 'bpmn:LinkEventDefinition')
      ) &&
      !isForCompensation(element)
    );
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isEventBasedTarget(element) {
    return (
      is(element, 'bpmn:ReceiveTask') || (
        is(element, 'bpmn:IntermediateCatchEvent') && (
          hasEventDefinition$1(element, 'bpmn:MessageEventDefinition') ||
          hasEventDefinition$1(element, 'bpmn:TimerEventDefinition') ||
          hasEventDefinition$1(element, 'bpmn:ConditionalEventDefinition') ||
          hasEventDefinition$1(element, 'bpmn:SignalEventDefinition')
        )
      )
    );
  }

  /**
   * @param {Element} element
   *
   * @return {Shape[]}
   */
  function getParents(element) {

    var parents = [];

    while (element) {
      element = element.parent;

      if (element) {
        parents.push(element);
      }
    }

    return parents;
  }

  /**
   * @param {Shape} possibleParent
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isParent(possibleParent, element) {
    var allParents = getParents(element);

    return allParents.indexOf(possibleParent) !== -1;
  }

  /**
   * @param {Element} source
   * @param {Element} target
   * @param {Connection} connection
   *
   * @return {CanConnectResult}
   */
  function canConnect(source, target, connection) {

    if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
      return null;
    }

    if (!is(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 (canConnectCompensationAssociation(source, target)) {
      return {
        type: 'bpmn:Association',
        associationDirection: 'One'
      };
    }

    if (canConnectAssociation(source, target)) {
      return {
        type: 'bpmn:Association',
        associationDirection: 'None'
      };
    }

    return false;
  }

  /**
   * Can an element be dropped into the target element.
   *
   * @param {Element} element
   * @param {Shape} target
   *
   * @return {boolean}
   */
  function canDrop(element, target) {

    // can move labels and groups everywhere
    if (isLabel(element) || isGroup(element)) {
      return true;
    }


    // disallow to create elements on collapsed pools
    if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
      return false;
    }

    // allow to create new participants on
    // existing collaboration and process diagrams
    if (is(element, 'bpmn:Participant')) {
      return is(target, 'bpmn:Process') || is(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(element, 'bpmn:Lane')) {
      return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
    }

    // disallow dropping boundary events which cannot replace with intermediate event
    if (is(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
      return false;
    }

    // drop flow elements onto flow element containers
    // and participants
    if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
      if (is(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(element, 'bpmn:DataStoreReference') && is(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(element, 'bpmn:MessageFlow')) {
      return is(target, 'bpmn:Collaboration')
        || element.source.parent == target
        || element.target.parent == target;
    }

    return false;
  }

  /**
   * @param {Shape} event
   *
   * @return {boolean}
   */
  function isDroppableBoundaryEvent(event) {
    return getBusinessObject(event).cancelActivity && (
      hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
    );
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isBoundaryEvent(element) {
    return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isLane(element) {
    return is(element, 'bpmn:Lane');
  }

  /**
   * `bpmn:IntermediateThrowEvents` are treated as boundary events during create.
   *
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isBoundaryCandidate(element) {
    if (isBoundaryEvent(element)) {
      return true;
    }

    if (is(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
      return true;
    }

    return (
      is(element, 'bpmn:IntermediateCatchEvent') &&
      hasCommonBoundaryIntermediateEventDefinition(element)
    );
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasNoEventDefinition(element) {
    var businessObject = getBusinessObject(element);

    return businessObject && !(businessObject.eventDefinitions && businessObject.eventDefinitions.length);
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function hasCommonBoundaryIntermediateEventDefinition(element) {
    return hasOneOfEventDefinitions(element, [
      'bpmn:MessageEventDefinition',
      'bpmn:TimerEventDefinition',
      'bpmn:SignalEventDefinition',
      'bpmn:ConditionalEventDefinition'
    ]);
  }

  /**
   * @param {Element} element
   * @param {string[]} eventDefinitions
   *
   * @return {boolean}
   */
  function hasOneOfEventDefinitions(element, eventDefinitions) {
    return eventDefinitions.some(function(definition) {
      return hasEventDefinition$1(element, definition);
    });
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isReceiveTaskAfterEventBasedGateway(element) {
    return (
      is(element, 'bpmn:ReceiveTask') &&
      find(element.incoming, function(incoming) {
        return is(incoming.source, 'bpmn:EventBasedGateway');
      })
    );
  }

  /**
   * TODO(philippfromme): remove `source` parameter
   *
   * @param {Element[]} elements
   * @param {Shape} target
   * @param {Element} source
   * @param {Point} [position]
   *
   * @return {boolean | 'attach'}
   */
  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(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(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';
  }

  /**
   * Check whether the given elements can be replaced. Return all elements which
   * can be replaced.
   *
   * @example
   *
   * ```javascript
   * [{
   *   id: 'IntermediateEvent_1',
   *   type: 'bpmn:StartEvent'
   * },
   * {
   *   id: 'Task_1',
   *   type: 'bpmn:ServiceTask'
   * }]
   * ```
   *
   * @param  {Element[]} elements
   * @param  {Shape} [target]
   * @param  {Point} [position]
   *
   * @return {CanReplaceResult}
   */
  function canReplace(elements, target, position) {

    if (!target) {
      return false;
    }

    var canExecute = {
      replacements: []
    };

    forEach$1(elements, function(element) {

      if (!isEventSubProcess(target)) {

        if (is(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(target, 'bpmn:SubProcess')) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:StartEvent'
            });
          }
        }
      }

      if (!is(target, 'bpmn:Transaction')) {
        if (hasEventDefinition$1(element, 'bpmn:CancelEventDefinition') &&
            element.type !== 'label') {

          if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:EndEvent'
            });
          }

          if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
            canExecute.replacements.push({
              oldElementId: element.id,
              newElementType: 'bpmn:BoundaryEvent'
            });
          }
        }
      }
    });

    return canExecute.replacements.length ? canExecute : false;
  }

  /**
   * @param {Element[]} elements
   * @param {Shape} target
   *
   * @return {boolean}
   */
  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);
    });
  }

  /**
   * @param {Shape} shape
   * @param {Shape} target
   * @param {Element} source
   * @param {Point} position
   *
   * @return {boolean}
   */
  function canCreate(shape, target, source, position) {

    if (!target) {
      return false;
    }

    if (isLabel(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);
  }

  /**
   * @param {Shape} shape
   * @param {Rect} newBounds
   *
   * @return {boolean}
   */
  function canResize(shape, newBounds) {
    if (is(shape, 'bpmn:SubProcess')) {
      return (
        isExpanded(shape) && (
          !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
        )
      );
    }

    if (is(shape, 'bpmn:Lane')) {
      return true;
    }

    if (is(shape, 'bpmn:Participant')) {
      return true;
    }

    if (isTextAnnotation(shape)) {
      return true;
    }

    if (isGroup(shape)) {
      return true;
    }

    return false;
  }

  /**
   * Check whether one of of the elements to be connected is a text annotation.
   *
   * @param {Element} source
   * @param {Element} target
   *
   * @return {boolean}
   */
  function isOneTextAnnotation(source, target) {

    var sourceTextAnnotation = isTextAnnotation(source),
        targetTextAnnotation = isTextAnnotation(target);

    return (
      (sourceTextAnnotation || targetTextAnnotation) &&
      (sourceTextAnnotation !== targetTextAnnotation)
    );
  }

  /**
   * @param {Element} source
   * @param {Element} target
   *
   * @return {CanConnectResult}
   */
  function canConnectAssociation(source, target) {

    // 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);
  }

  /**
   * @param {Element} source
   * @param {Element} target
   *
   * @return {boolean}
   */
  function canConnectCompensationAssociation(source, target) {
    return (
      isSameScope(source, target) &&
      isCompensationBoundary(source) &&
      is(target, 'bpmn:Activity') &&
      !isHostOfElement(target, source) &&
      !isEventSubProcess(target)
    );
  }

  /**
   * @param {Element} source
   * @param {Element} target
   *
   * @return {boolean}
   */
  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)
    );
  }

  /**
   * @param {Element} source
   * @param {Element} target
   *
   * @return {boolean}
   */
  function canConnectSequenceFlow(source, target) {
    return isSequenceFlowSource(source) &&
           isSequenceFlowTarget(target) &&
           isSameScope(source, target) &&
           !(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
  }

  /**
   * @param {Element} source
   * @param {Element} target
   *
   * @return {CanConnectResult}
   */
  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;
  }

  /**
   * @param {Shape} shape
   * @param {Connection} connection
   * @param {Point} position
   *
   * @return {boolean}
   */
  function canInsert(shape, connection, position) {
    if (!connection) {
      return false;
    }

    if (Array.isArray(shape)) {
      if (shape.length !== 1) {
        return false;
      }

      shape = shape[ 0 ];
    }

    if (connection.source === shape ||
        connection.target === shape) {
      return false;
    }

    // return true if shape can be inserted into connection parent
    return (
      isAny(connection, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
      !isLabel(connection) &&
      is(shape, 'bpmn:FlowNode') &&
      !is(shape, 'bpmn:BoundaryEvent') &&
      canDrop(shape, connection.parent));
  }

  /**
   * @param {Element[]} elements
   * @param {Element} element
   *
   * @return {boolean}
   */
  function includes$5(elements, element) {
    return (elements && element) && elements.indexOf(element) !== -1;
  }

  /**
   * @param {Element[]} elements
   * @param {Element} element
   *
   * @return {boolean}
   */
  function canCopy(elements, element) {
    if (isLabel(element)) {
      return true;
    }

    if (is(element, 'bpmn:Lane') && !includes$5(elements, element.parent)) {
      return false;
    }

    return true;
  }

  /**
   * @param {Element} element
   *
   * @return {Element|null}
   */
  function getRootElement(element) {
    return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
  }

  function isHostOfElement(potentialHost, element) {
    return potentialHost.attachers.includes(element);
  }

  var RulesModule = {
    __depends__: [
      RulesModule$1
    ],
    __init__: [ 'bpmnRules' ],
    bpmnRules: [ 'type', BpmnRules ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   */

  var HIGH_PRIORITY$b = 2000;

  /**
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function BpmnDiOrdering(eventBus, canvas) {

    eventBus.on('saveXML.start', HIGH_PRIORITY$b, 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$1(elements, getDi);

        rootDi.set('planeElement', diElements);
      });
    }
  }

  BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ];

  var DiOrderingModule = {
    __init__: [
      'bpmnDiOrdering'
    ],
    bpmnDiOrdering: [ 'type', BpmnDiOrdering ]
  };

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  /**
   * 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 {Element} element
   * @param {Shape} newParent
   *
   * @return {Object} ordering descriptor
   */
  OrderingProvider.prototype.getOrdering = function(element, newParent) {
    return null;
  };

  e$2(OrderingProvider, CommandInterceptor);

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * A BPMN-specific ordering provider.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  function BpmnOrderingProvider(eventBus, canvas) {

    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:TextAnnotation',
        order: {
          level: 9
        }
      },
      {
        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 and text annotations always on top
      if (element.labelTarget || is(element, 'bpmn:TextAnnotation')) {
        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' ];

  e$2(BpmnOrderingProvider, OrderingProvider);

  var OrderingModule = {
    __init__: [ 'bpmnOrderingProvider' ],
    bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
  };

  /**
   * 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;
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ClipboardModule = {
    clipboard: [ 'type', Clipboard ]
  };

  var MARKER_OK$2 = 'drop-ok',
      MARKER_NOT_OK$2 = 'drop-not-ok',
      MARKER_ATTACH$2 = 'attach-ok',
      MARKER_NEW_PARENT$1 = 'new-parent';

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   */

  var PREFIX = 'create';

  var HIGH_PRIORITY$a = 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 {Element[]} elements
     * @param {Shape} target
     * @param {Point} position
     * @param {Element} [source]
     *
     * @return {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(element) && elements.indexOf(labelTarget) !== -1);
      });

      var shape = find(elements, function(element) {
        return !isConnection(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$1(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$1(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$1({}, hints, {
          attach: attach
        }));

        // update shape
        shape = find(elements, function(element) {
          return !isConnection(element);
        });
      }

      // update elements and shape
      assign$1(context, {
        elements: elements,
        shape: shape
      });

      assign$1(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$a, function() {
        eventBus.off('elements.changed', cancel);
      });
    });

    // API //////////

    /**
     * @param event
     * @param elements
     * @param {any} [context={}]
     */
    this.start = function(event, elements, context) {
      if (!isArray$3(elements)) {
        elements = [ elements ];
      }

      var shape = find(elements, function(element) {
        return !isConnection(element);
      });

      if (!shape) {

        // at least one shape is required
        return;
      }

      context = assign$1({
        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(element)) {
          element.waypoints = map$1(element.waypoints, function(waypoint) {
            return {
              x: waypoint.x - bbox.x - bbox.width / 2,
              y: waypoint.y - bbox.y - bbox.height / 2
            };
          });
        }

        assign$1(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$1(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 isSingleShape(elements) {
    return elements && elements.length === 1 && !isConnection(elements[ 0 ]);
  }

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../core/GraphicsFactory').default} GraphicsFactory
   * @typedef {import('../preview-support/PreviewSupport').default} PreviewSupport
   * @typedef {import('../../draw/Styles').default} Styles
   */

  var LOW_PRIORITY$c = 750;

  /**
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {GraphicsFactory} graphicsFactory
   * @param {PreviewSupport} previewSupport
   * @param {Styles} styles
   */
  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$1(gfx, element.x, element.y);
        }

        // add preview
        previewSupport.addDragger(element, dragGroup, gfx);
      });

      return dragGroup;
    }

    eventBus.on('create.move', LOW_PRIORITY$c, 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$1(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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var CreateModule = {
    __depends__: [
      DraggingModule,
      PreviewSupportModule,
      RulesModule$1,
      SelectionModule
    ],
    __init__: [
      'create',
      'createPreview'
    ],
    create: [ 'type', Create ],
    createPreview: [ 'type', CreatePreview ]
  };

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  /**
   * @param {EventBus} eventBus
   */
  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;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var MouseModule = {
    __init__: [ 'mouse' ],
    mouse: [ 'type', Mouse ]
  };

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../clipboard/Clipboard').default} Clipboard
   * @typedef {import('../create/Create').default} Create
   * @typedef {import('../../core/ElementFactory').default} ElementFactory
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../mouse/Mouse').default} Mouse
   * @typedef {import('../rules/Rules').default} Rules
   */

  /**
   * @typedef { (event: { elements: Element[] }) => Element[]|boolean } CopyPasteCanCopyElementsListener
   */

  /**
   * @typedef { (event: { descriptor: any, element: Element, elements: Element[] }) => void } CopyPasteCopyElementListener
   */

  /**
   * @typedef { (event: { element: Element, children: Element[] }) => void } CopyPasteCreateTreeListener
   */

  /**
   * @typedef { (event: { elements: any, tree: any }) => void } CopyPasteElementsCopiedListener
   */

  /**
   * @typedef { (event: { cache: any, descriptor: any }) => void } CopyPastePasteElementListener
   */

  /**
   * @typedef { (event: { hints: any }) => void } CopyPastePasteElementsListener
   */

  /**
   * 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$2(element)) {
        descriptor.priority = 2;

        descriptor.host = element.host.id;
      }

      // connections (priority = 3)
      if (isConnection(element)) {
        descriptor.priority = 3;

        descriptor.source = element.source.id;
        descriptor.target = element.target.id;

        descriptor.waypoints = copyWaypoints$1(element);
      }

      // labels (priority = 4)
      if (isLabel(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$1(hints, {
        createElementsBehavior: false
      });
    });
  }

  CopyPaste.$inject = [
    'canvas',
    'create',
    'clipboard',
    'elementFactory',
    'eventBus',
    'modeling',
    'mouse',
    'rules'
  ];


  /**
   * Copy elements.
   *
   * @param {Element[]} elements
   *
   * @return {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 {Shape} [context.element] The optional parent.
   * @param {Point} [context.point] The optional position.
   * @param {Object} [context.hints] The optional 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 {Element[]} elements
   * @param {Shape} 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(element)) {
        element.waypoints = map$1(element.waypoints, function(waypoint) {
          return {
            x: waypoint.x - bbox.x - bbox.width / 2,
            y: waypoint.y - bbox.y - bbox.height / 2
          };
        });
      }

      assign$1(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$1({}, 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$1({}, 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(attrs)) {
          attrs.source = cache[ descriptor.source ];
          attrs.target = cache[ descriptor.target ];

          element = cache[ descriptor.id ] = self.createConnection(attrs);

          elements.push(element);

          return;
        }

        if (isLabel(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 {Element[]} elements
   *
   * @return {boolean}
   */
  CopyPaste.prototype.hasRelations = function(element, elements) {
    var labelTarget,
        source,
        target;

    if (isConnection(element)) {
      source = find(elements, matchPattern({ id: element.source.id }));
      target = find(elements, matchPattern({ id: element.target.id }));

      if (!source || !target) {
        return false;
      }
    }

    if (isLabel(element)) {
      labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));

      if (!labelTarget) {
        return false;
      }
    }

    return true;
  };

  /**
   * Create a tree-like structure from elements.
   *
   * @example
   *
   * ```javascript
   * 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 {Element[]} 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(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$1(elementsData, function(elementData) {
      return elementData.element;
    });

    // (2) copy elements
    elementsData = map$1(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$1(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$2(element) {
    return !!element.host;
  }

  function copyWaypoints$1(element) {
    return map$1(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$1({}, waypoint);
  }

  function removeElement(element, elements) {
    var index = elements.indexOf(element);

    if (index === -1) {
      return elements;
    }

    return elements.splice(index, 1);
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var CopyPasteModule$1 = {
    __depends__: [
      ClipboardModule,
      CreateModule,
      MouseModule,
      RulesModule$1
    ],
    __init__: [ 'copyPaste' ],
    copyPaste: [ 'type', CopyPaste ]
  };

  /**
   * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('./ModdleCopy').default} ModdleCopy
   */

  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$b = 750;

  /**
   * BPMN-specific copy & paste.
   *
   * @param {BpmnFactory} bpmnFactory
   * @param {EventBus} eventBus
   * @param {ModdleCopy} moddleCopy
   */
  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$b, 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(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.set(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(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$b, function(context) {
      var descriptor = context.descriptor,
          element = context.element;

      if (!is(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$b, 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'
  ];

  const DISALLOWED_PROPERTIES = [
    'artifacts',
    'dataInputAssociations',
    'dataOutputAssociations',
    'default',
    'flowElements',
    'lanes',
    'incoming',
    'outgoing',
    'categoryValue'
  ];

  const ALLOWED_REFERENCES = [
    'errorRef',
    'escalationRef',
    'messageRef',
    'signalRef'
  ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
   * @typedef {import('../../model/Types').Moddle} Moddle
   *
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   */

  /**
   * Utility for copying model properties from source element to target element.
   *
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   * @param {Moddle} moddle
   */
  function ModdleCopy(eventBus, bpmnFactory, moddle) {
    this._bpmnFactory = bpmnFactory;
    this._eventBus = eventBus;
    this._moddle = moddle;

    // copy extension elements last
    eventBus.on('moddleCopy.canCopyProperties', (context) => {
      const { propertyNames } = context;

      if (!propertyNames || !propertyNames.length) {
        return;
      }

      return sortBy(propertyNames, (propertyName) => {
        return propertyName === 'extensionElements';
      });
    });

    // default check whether property can be copied
    eventBus.on('moddleCopy.canCopyProperty', (context) => {
      const {
        parent,
        property,
        propertyName
      } = context;

      const parentDescriptor = isObject(parent) && parent.$descriptor;

      if (propertyName && ALLOWED_REFERENCES.includes(propertyName)) {

        // allow copying reference
        return property;
      }

      if (propertyName && DISALLOWED_PROPERTIES.includes(propertyName)) {

        // 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', (context) => {
      const { property } = context;

      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 {string[]} [propertyNames]
   * @param {boolean} [clone=false]
   *
   * @return {ModdleElement}
   */
  ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone = false) {
    if (propertyNames && !isArray$3(propertyNames)) {
      propertyNames = [ propertyNames ];
    }

    propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);

    const 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, (propertyName) => {
      let sourceProperty;

      if (has$1(sourceElement, propertyName)) {
        sourceProperty = sourceElement.get(propertyName);
      }

      const copiedProperty = this.copyProperty(sourceProperty, targetElement, propertyName, clone);

      if (!isDefined(copiedProperty)) {
        return;
      }

      const canSetProperty = this._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 {any} property
   * @param {ModdleElement} parent
   * @param {string} propertyName
   * @param {boolean} [clone=false]
   *
   * @return {any}
   */
  ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone = false) {

    // allow others to copy property
    let 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;
    }

    const 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, (childProperties, childProperty) => {

        // recursion
        const copiedProperty = this.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 = this._bpmnFactory.create(property.$type);

      copiedProperty.$parent = parent;

      // recursion
      copiedProperty = this.copyElement(property, copiedProperty, null, clone);

      return copiedProperty;
    }

    // copy primitive properties
    return property;
  };

  ModdleCopy.prototype._copyId = function(id, element, clone = false) {
    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, (properties, property) => {

      if (keepDefaultProperties && property.default) {
        return properties;
      }

      return properties.concat(property.name);
    }, []);
  }

  var CopyPasteModule = {
    __depends__: [
      CopyPasteModule$1
    ],
    __init__: [ 'bpmnCopyPaste', 'moddleCopy' ],
    bpmnCopyPaste: [ 'type', BpmnCopyPaste ],
    moddleCopy: [ 'type', ModdleCopy ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   *
   * @typedef {import('../../core/Types').ShapeLike} Shape
   */

  var round$6 = Math.round;

  /**
   * Service that allows replacing of elements.
   *
   * @param {Modeling} modeling
   * @param {EventBus} eventBus
   */
  function Replace(modeling, eventBus) {
    this._modeling = modeling;
    this._eventBus = eventBus;
  }

  Replace.$inject = [ 'modeling', 'eventBus' ];

  /**
   * Replace an element.
   *
   * @param {Shape} oldElement The element to be replaced.
   * @param {Object} attrs Containing information about the new element, for
   * example the new bounds and type.
   * @param {Object} hints Custom hints 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.
   *
   * @return {Shape}
   */
  Replace.prototype.replaceElement = function(oldElement, attrs, hints) {

    if (oldElement.waypoints) {

      // TODO(nikku): we do not replace connections, yet
      return null;
    }

    var modeling = this._modeling;
    var eventBus = this._eventBus;

    eventBus.fire('replace.start', {
      element: oldElement,
      attrs,
      hints
    });

    var width = attrs.width || oldElement.width,
        height = attrs.height || oldElement.height,
        x = attrs.x || oldElement.x,
        y = attrs.y || oldElement.y,
        centerX = round$6(x + width / 2),
        centerY = round$6(y + height / 2);

    // modeling API requires center coordinates,
    // account for that when handling shape bounds

    var newElement = modeling.replaceShape(
      oldElement,
      assign$1(
        {},
        attrs,
        {
          x: centerX,
          y: centerY,
          width: width,
          height: height
        }
      ),
      hints
    );

    eventBus.fire('replace.end', {
      element: oldElement,
      newElement,
      hints
    });

    return newElement;
  };

  function ReplaceSelectionBehavior(selection, eventBus) {

    eventBus.on('replace.end', 500, function(event) {
      const {
        newElement,
        hints = {}
      } = event;

      if (hints.select === false) {
        return;
      }

      selection.select(newElement);
    });

  }

  ReplaceSelectionBehavior.$inject = [ 'selection', 'eventBus' ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ReplaceModule$1 = {
    __init__: [ 'replace', 'replaceSelectionBehavior' ],
    replaceSelectionBehavior: [ 'type', ReplaceSelectionBehavior ],
    replace: [ 'type', Replace ]
  };

  /**
   * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
   * @typedef {import('../modeling/ElementFactory').default} ElementFactory
   * @typedef {import('../copy-paste/ModdleCopy').default} ModdleCopy
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('diagram-js/lib/features/replace/Replace').default} Replace
   * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   *
   * @typedef { {
   *   type: string;
   *   cancelActivity: boolean;
   *   instantiate: boolean;
   *   eventGatewayType: string;
   *   triggeredByEvent: boolean;
   *   isInterrupting: boolean;
   *   collapsed: boolean;
   *   isExpanded: boolean;
   *   eventDefinitionType: string;
   *   eventDefinitionAttrs: Object;
   *   host: Shape;
   * } } TargetElement
   *
   * @typedef { {
   *   moveChildren: boolean;
   * } & Record<string, any> } Hints
   */

  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;
  }


  /**
   * BPMN-specific replace.
   *
   * @param {BpmnFactory} bpmnFactory
   * @param {ElementFactory} elementFactory
   * @param {ModdleCopy} moddleCopy
   * @param {Modeling} modeling
   * @param {Replace} replace
   * @param {Rules} rules
   */
  function BpmnReplace(
      bpmnFactory,
      elementFactory,
      moddleCopy,
      modeling,
      replace,
      rules
  ) {

    /**
     * Prepares a new business object for the replacement element
     * and triggers the replace operation.
     *
     * @param  {Element} element
     * @param  {TargetElement} targetElement
     * @param  {Hints} [hints]
     *
     * @return {Element}
     */
    function replaceElement(element, targetElement, hints) {

      hints = hints || {};

      var type = targetElement.type,
          oldBusinessObject = element.businessObject;

      if (isSubProcess(oldBusinessObject) && (type === 'bpmn:SubProcess' || type === 'bpmn:AdHocSubProcess')) {
        if (shouldToggleCollapsed(element, targetElement)) {

          // expanding or collapsing process
          modeling.toggleCollapse(element);

          return element;
        }
      }

      var newBusinessObject = bpmnFactory.create(type);

      var newElement = {
        type: type,
        businessObject: newBusinessObject,
      };

      newElement.di = {};

      if (type === 'bpmn:ExclusiveGateway') {
        newElement.di.isMarkerVisible = true;
      }

      // 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$1(newBusinessObject, pick(targetElement, CUSTOM_PROPERTIES));

      var properties = filter(copyProps, function(propertyName) {

        // copying event definitions, unless we replace
        if (propertyName === 'eventDefinitions') {
          return hasEventDefinition(element, targetElement.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' && targetElement.isExpanded === false) {
          return false;
        }

        if (propertyName === 'triggeredByEvent') {
          return false;
        }

        if (propertyName === 'isForCompensation') {
          return !isEventSubProcess(newBusinessObject);
        }

        return true;
      });

      newBusinessObject = moddleCopy.copyElement(
        oldBusinessObject,
        newBusinessObject,
        properties
      );

      // initialize custom BPMN extensions
      if (targetElement.eventDefinitionType) {

        // only initialize with new eventDefinition
        // if we did not set an event definition yet,
        // i.e. because we copied it
        if (!hasEventDefinition(newBusinessObject, targetElement.eventDefinitionType)) {
          newElement.eventDefinitionType = targetElement.eventDefinitionType;
          newElement.eventDefinitionAttrs = targetElement.eventDefinitionAttrs;
        }
      }

      if (is(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 (targetElement && has$1(targetElement, 'isExpanded')) {
          newElement.isExpanded = targetElement.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(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(oldBusinessObject, 'bpmn:Participant')) {

        // create expanded pool
        if (targetElement.isExpanded === true) {
          newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
        } else {

          // remove children when transforming to collapsed pool
          hints.moveChildren = false;
        }

        // apply same directionality
        var isHorizontalPool = isHorizontal$3(element);
        if (!getDi(element).isHorizontal) {
          getDi(newElement).isHorizontal = isHorizontalPool;
        }

        // keep the existing size of the pool's direction to
        // prevent dangling message flows
        newElement.width = isHorizontalPool ? element.width : elementFactory.getDefaultSize(newElement).width;
        newElement.height = isHorizontalPool ? elementFactory.getDefaultSize(newElement).height : element.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 (
        targetElement.host &&
        !is(oldBusinessObject, 'bpmn:BoundaryEvent') &&
        is(newBusinessObject, 'bpmn:BoundaryEvent')
      ) {
        newElement.host = targetElement.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;
      }

      return replace.replaceElement(element, newElement, { ...hints, targetElement });
    }

    this.replaceElement = replaceElement;
  }

  BpmnReplace.$inject = [
    'bpmnFactory',
    'elementFactory',
    'moddleCopy',
    'modeling',
    'replace',
    'rules'
  ];

  /**
   * @param {ModdleElement} businessObject
   *
   * @return {boolean}
   */
  function isSubProcess(businessObject) {
    return is(businessObject, 'bpmn:SubProcess');
  }

  /**
   * @param {Element|ModdleElement} element
   * @param {string} type
   *
   * @return {boolean}
   */
  function hasEventDefinition(element, type) {
    var businessObject = getBusinessObject(element);

    return type && businessObject.get('eventDefinitions').some(function(definition) {
      return is(definition, type);
    });
  }

  /**
   * Compute intersection between two arrays.
   *
   * @param {Array} a
   * @param {Array} b
   *
   * @return {Array}
   */
  function intersection(a, b) {
    return a.filter(function(item) {
      return b.includes(item);
    });
  }

  var ReplaceModule = {
    __depends__: [
      CopyPasteModule,
      ReplaceModule$1,
      SelectionModule
    ],
    bpmnReplace: [ 'type', BpmnReplace ]
  };

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('../../core/EventBus').Event} Event
   */

  var LOW_PRIORITY$a = 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 {EventBus} eventBus
   */
  function ToolManager(eventBus) {
    this._eventBus = eventBus;

    this._tools = [];
    this._active = null;
  }

  ToolManager.$inject = [ 'eventBus' ];

  /**
   * Register a tool.
   *
   * @param {string} name
   * @param { {
   *   tool: string;
   * } } events
   */
  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;

    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);

        return;
      }

      this.setActive(name);

    }, this);

    // TODO: add test cases
    forEach$1(events, function(event) {
      eventsToRegister.push(event + '.ended');
      eventsToRegister.push(event + '.canceled');
    });

    eventBus.on(eventsToRegister, LOW_PRIORITY$a, 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 {Event} event
   *
   * @return {boolean}
   */
  function isPaletteClick(event) {
    var target = event.originalEvent && event.originalEvent.target;

    return target && closest(target, '.group[data-group="tools"]');
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ToolManagerModule = {
    __depends__: [
      DraggingModule
    ],
    __init__: [ 'toolManager' ],
    toolManager: [ 'type', ToolManager ]
  };

  /**
   * @typedef {import('../../core/Types').ConnectionLike} Connection
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').Rect} Rect
   */


  /**
   * Return direction given axis and delta.
   *
   * @param {Axis} axis
   * @param {number} delta
   *
   * @return {Direction}
   */
  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<Shape>} movingShapes
   * @param {Array<Shape>} resizingShapes
   *
   * @return {Array<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$4(movingShapes, source) ||
          includes$4(movingShapes, target) ||
          includes$4(resizingShapes, source) ||
          includes$4(resizingShapes, target)) {

          if (!includes$4(waypointsUpdatingConnections, connection)) {
            waypointsUpdatingConnections.push(connection);
          }
        }
      });
    });

    return waypointsUpdatingConnections;
  }

  function includes$4(array, item) {
    return array.indexOf(item) !== -1;
  }

  /**
   * Resize bounds.
   *
   * @param {Rect} bounds
   * @param {Direction} direction
   * @param {Point} delta
   *
   * @return {Rect}
   */
  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);
    }
  }

  /**
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../mouse/Mouse').default} Mouse
   * @typedef {import('../rules/Rules').default} Rules
   * @typedef {import('../tool-manager/ToolManager').default} ToolManager
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Point} Point
   */

  var abs$1 = Math.abs,
      round$5 = 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$9 = 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$9 , 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$5(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 {MouseEvent|TouchEvent} event
   * @param {boolean} autoActivate
   * @param {boolean} reactivate
   */
  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|TouchEvent} event
   */
  SpaceTool.prototype.activateMakeSpace = function(event) {
    this._dragging.init(event, 'spaceTool', {
      autoActivate: true,
      cursor: CURSOR_CROSSHAIR,
      data: {
        context: {}
      }
    });
  };

  /**
   * Make space.
   *
   * @param {Array<Shape>} movingShapes
   * @param {Array<Shape>} resizingShapes
   * @param {Point} delta
   * @param {Direction} 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 {MouseEvent|TouchEvent} 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();

    if (!hasSecondaryModifier(event) && event.hover) {
      root = event.hover;
    }

    var children = [
      ...selfAndAllChildren(root, true),
      ...(root.attachers || [])
    ];

    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$1(
      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<Shape>} elements
   * @param {Axis} axis
   * @param {Point} delta
   * @param {number} start
   *
   * @return {Object}
   */
  SpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {
    var rules = this._rules;

    var movingShapes = [],
        resizingShapes = [];

    var attachers = [],
        connections = [];

    function moveShape(shape) {
      if (!movingShapes.includes(shape)) {
        movingShapes.push(shape);
      }

      var label = shape.label;

      // move external label if its label target is moving
      if (label && !movingShapes.includes(label)) {
        movingShapes.push(label);
      }
    }

    function resizeShape(shape) {
      if (!resizingShapes.includes(shape)) {
        resizingShapes.push(shape);
      }
    }

    forEach$1(elements, function(element) {
      if (!element.parent || isLabel(element)) {
        return;
      }

      // handle connections separately
      if (isConnection(element)) {
        connections.push(element);

        return;
      }

      var shapeStart = element[ axis ],
          shapeEnd = shapeStart + element[ AXIS_TO_DIMENSION[ axis ] ];

      // handle attachers separately
      if (isAttacher$1(element)
        && ((delta > 0 && getMid(element)[ axis ] > start)
          || (delta < 0 && getMid(element)[ axis ] < start))) {
        attachers.push(element);

        return;
      }

      // move shape if its start is after space tool
      if ((delta > 0 && shapeStart > start)
        || (delta < 0 && shapeEnd < start)) {
        moveShape(element);

        return;
      }

      // resize shape if it's resizable and its start is before and its end is after space tool
      if (shapeStart < start
        && shapeEnd > start
        && rules.allowed('shape.resize', { shape: element })
      ) {
        resizeShape(element);

        return;
      }
    });

    // move attacher if its host is moving
    forEach$1(movingShapes, function(shape) {
      var attachers = shape.attachers;

      if (attachers) {
        forEach$1(attachers, function(attacher) {
          moveShape(attacher);
        });
      }
    });

    var allShapes = movingShapes.concat(resizingShapes);

    // move attacher if its mid is after space tool and its host is moving or resizing
    forEach$1(attachers, function(attacher) {
      var host = attacher.host;

      if (includes$3(allShapes, host)) {
        moveShape(attacher);
      }
    });

    allShapes = movingShapes.concat(resizingShapes);

    // move external label if its label target's (connection) source and target are moving
    forEach$1(connections, function(connection) {
      var source = connection.source,
          target = connection.target,
          label = connection.label;

      if (includes$3(allShapes, source)
        && includes$3(allShapes, target)
        && label) {
        moveShape(label);
      }
    });

    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();

    if (context) {
      return /^spaceTool/.test(context.prefix);
    }

    return false;
  };

  // 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 attachers = resizingShape.attachers,
          children = resizingShape.children;

      var resizingShapeBBox = asTRBL(resizingShape);

      // find children that are not moving or resizing
      var nonMovingResizingChildren = filter(children, function(child) {
        return !isConnection(child) &&
          !isLabel(child) &&
          !includes$3(movingShapes, child) &&
          !includes$3(resizingShapes, child);
      });

      // find children that are moving
      var movingChildren = filter(children, function(child) {
        return !isConnection(child) && !isLabel(child) && includes$3(movingShapes, child);
      });

      var minOrMax,
          nonMovingResizingChildrenBBox,
          movingChildrenBBox,
          movingAttachers = [],
          nonMovingAttachers = [],
          movingAttachersBBox,
          movingAttachersConstraint,
          nonMovingAttachersBBox,
          nonMovingAttachersConstraint;

      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;
        }
      }

      if (attachers && attachers.length) {
        attachers.forEach(function(attacher) {
          if (includes$3(movingShapes, attacher)) {
            movingAttachers.push(attacher);
          } else {
            nonMovingAttachers.push(attacher);
          }
        });

        if (movingAttachers.length) {
          movingAttachersBBox = asTRBL(getBBox(movingAttachers.map(getMid)));

          movingAttachersConstraint = resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ]
                - (movingAttachersBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] - start);
        }

        if (nonMovingAttachers.length) {
          nonMovingAttachersBBox = asTRBL(getBBox(nonMovingAttachers.map(getMid)));

          nonMovingAttachersConstraint = nonMovingAttachersBBox[ DIRECTION_TO_TRBL[ direction ] ]
                - (resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] - start);
        }

        if (direction === 'n') {
          minOrMax = Math.min(movingAttachersConstraint || Infinity, nonMovingAttachersConstraint || Infinity);

          spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 'w') {
          minOrMax = Math.min(movingAttachersConstraint || Infinity, nonMovingAttachersConstraint || Infinity);

          spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
        } else if (direction === 's') {
          minOrMax = Math.max(movingAttachersConstraint || -Infinity, nonMovingAttachersConstraint || -Infinity);

          spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
        } else if (direction === 'e') {
          minOrMax = Math.max(movingAttachersConstraint || -Infinity, nonMovingAttachersConstraint || -Infinity);

          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$3(array, item) {
    return array.indexOf(item) !== -1;
  }

  function isAttacher$1(element) {
    return !!element.host;
  }

  var MARKER_DRAGGING$1 = 'djs-dragging',
      MARKER_RESIZING$1 = 'djs-resizing';

  var LOW_PRIORITY$9 = 250;

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../draw/Styles').default} Styles
   */

  var max$1 = 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$1(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$9, 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(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$1);
        });

        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$1(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$1(initialBounds.width + delta.x, 5)
          });
        } else {
          width = max$1(initialBounds.width - delta.x, 5);

          attr(element, {
            width: width,
            x: initialBounds.x + initialBounds.width - width
          });
        }

        if (context.direction === 's') {
          attr(element, {
            height: max$1(initialBounds.height + delta.y, 5)
          });
        } else {
          height = max$1(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$1);
      });

      if (frameGroup) {
        remove$1(frameGroup);
      }
    });
  }

  SpaceToolPreview.$inject = [
    'eventBus',
    'elementRegistry',
    'canvas',
    'styles',
    'previewSupport'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var SpaceToolModule$1 = {
    __init__: [ 'spaceToolPreview' ],
    __depends__: [
      DraggingModule,
      RulesModule$1,
      ToolManagerModule,
      PreviewSupportModule,
      MouseModule
    ],
    spaceTool: [ 'type', SpaceTool ],
    spaceToolPreview: [ 'type', SpaceToolPreview ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   *
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Axis} Axis
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   */

  /**
   * @param {Injector} injector
   * @param {Canvas} canvas
   */
  function BpmnSpaceTool(injector, canvas) {
    injector.invoke(SpaceTool, this);

    this._canvas = canvas;
  }

  BpmnSpaceTool.$inject = [
    'injector',
    'canvas'
  ];

  e$2(BpmnSpaceTool, SpaceTool);

  /**
   * @param {Shape[]} elements
   * @param {Axis} axis
   * @param {Point} delta
   * @param {number} start
   *
   * @return {Object}
   */
  BpmnSpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {

    var canvasRoot = this._canvas.getRootElement(),
        spaceRoot = elements[0] === canvasRoot ? null : elements[0],
        enclosedArtifacts = [];

    // ensure
    if (spaceRoot) {
      enclosedArtifacts = values(
        getEnclosedElements(
          canvasRoot.children.filter(
            (child) => is(child, 'bpmn:Artifact')
          ),
          getBBox(spaceRoot)
        )
      );
    }

    const elementsToMove = [ ...elements, ...enclosedArtifacts ];

    var adjustments = SpaceTool.prototype.calculateAdjustments.call(this, elementsToMove, axis, delta, start);

    // do not resize:
    //
    // * text annotations (horizontally/vertically)
    // * empty horizontal pools (vertically)
    // * empty vertical pools (horizontally)
    adjustments.resizingShapes = adjustments.resizingShapes.filter(function(shape) {

      if (is(shape, 'bpmn:TextAnnotation')) {
        return false;
      }

      if (isCollapsedPool$1(shape)) {
        if (axis === 'y' && isHorizontal$3(shape) || axis === 'x' && !isHorizontal$3(shape)) {
          return false;
        }
      }

      return true;
    });

    return adjustments;
  };


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

  function isCollapsedPool$1(shape) {
    return is(shape, 'bpmn:Participant') && !getBusinessObject(shape).processRef;
  }

  var SpaceToolModule = {
    __depends__: [ SpaceToolModule$1 ],
    spaceTool: [ 'type', BpmnSpaceTool ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../core/Types').ElementLike} ElementLike
   *
   * @typedef {import('../core/EventBus').default} EventBus
   * @typedef {import('./CommandHandler').default} CommandHandler
   *
   * @typedef { any } CommandContext
   * @typedef { {
   *   new (...args: any[]) : CommandHandler
   * } } CommandHandlerConstructor
   * @typedef { {
   *   [key: string]: CommandHandler;
   * } } CommandHandlerMap
   * @typedef { {
   *   command: string;
   *   context: any;
   *   id?: any;
   * } } CommandStackAction
   * @typedef { {
   *   actions: CommandStackAction[];
   *   dirty: ElementLike[];
   *   trigger: 'execute' | 'undo' | 'redo' | 'clear' | null;
   *   atomic?: boolean;
   * } } CurrentExecution
   */

  /**
   * 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(d)` and `postExecute(d)` 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 {CommandHandlerMap}
     */
    this._handlerMap = {};

    /**
     * A stack containing all re/undoable actions on the diagram
     *
     * @type {CommandStackAction[]}
     */
    this._stack = [];

    /**
     * The current index on the stack
     *
     * @type {number}
     */
    this._stackIdx = -1;

    /**
     * Current active commandStack execution
     *
     * @type {CurrentExecution}
     */
    this._currentExecution = {
      actions: [],
      dirty: [],
      trigger: null
    };

    /**
     * @type {Injector}
     */
    this._injector = injector;

    /**
     * @type EventBus
     */
    this._eventBus = eventBus;

    /**
     * @type { number }
     */
    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 {CommandContext} context The context with which to execute the command.
   */
  CommandStack.prototype.execute = function(command, context) {
    if (!command) {
      throw new Error('command required');
    }

    this._currentExecution.trigger = 'execute';

    const action = { command: command, context: context };

    this._pushAction(action);
    this._internalExecute(action);
    this._popAction();
  };


  /**
   * Check whether a 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 {CommandContext} context The context with which to execute the command.
   *
   * @return {boolean} Whether the command can be executed with the given context.
   */
  CommandStack.prototype.canExecute = function(command, context) {

    const action = { command: command, context: context };

    const handler = this._getHandler(command);

    let 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.
   *
   * @param {boolean} [emit=true] Whether to fire an event. Defaults to `true`.
   */
  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() {
    let 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() {
    let 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 Command to be executed.
   * @param {CommandHandler} handler Handler to execute the command.
   */
  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 Command to be executed.
   * @param {CommandHandlerConstructor} handlerCls Constructor to instantiate a {@link CommandHandler}.
   */
  CommandStack.prototype.registerHandler = function(command, handlerCls) {

    if (!command || !handlerCls) {
      throw new Error('command and handlerCls must be defined');
    }

    const handler = this._injector.instantiate(handlerCls);
    this.register(command, handler);
  };

  /**
   * @return {boolean}
   */
  CommandStack.prototype.canUndo = function() {
    return !!this._getUndoAction();
  };

  /**
   * @return {boolean}
   */
  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) {
    const command = action.command,
          context = action.context;

    const handler = this._getHandler(command);

    // guard against illegal nested command stack invocations
    this._atomicDo(() => {
      this._fire(command, 'revert', action);

      if (handler.revert) {
        this._markDirty(handler.revert(context));
      }

      this._revertedAction(action);

      this._fire(command, 'reverted', action);
    });
  };


  CommandStack.prototype._fire = function(command, qualifier, event) {
    if (arguments.length < 3) {
      event = qualifier;
      qualifier = null;
    }

    const names = qualifier ? [ command + '.' + qualifier, qualifier ] : [ command ];
    let result;

    event = this._eventBus.createEvent(event);

    for (const name of names) {
      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) {

    const execution = this._currentExecution;

    execution.atomic = true;

    try {
      fn();
    } finally {
      execution.atomic = false;
    }
  };

  CommandStack.prototype._internalExecute = function(action, redo) {
    const command = action.command,
          context = action.context;

    const 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(() => {

      this._fire(command, 'execute', action);

      if (handler.execute) {

        // actual execute + mark return results as dirty
        this._markDirty(handler.execute(context));
      }

      // log to stack
      this._executedAction(action, redo);

      this._fire(command, 'executed', action);
    });

    if (!redo) {
      this._fire(command, 'postExecute', action);

      if (handler.postExecute) {
        handler.postExecute(context);
      }

      this._fire(command, 'postExecuted', action);
    }

    this._popAction();
  };


  CommandStack.prototype._pushAction = function(action) {

    const execution = this._currentExecution,
          actions = execution.actions;

    const 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() {
    const 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) {
    const execution = this._currentExecution;

    if (!elements) {
      return;
    }

    elements = isArray$3(elements) ? elements : [ elements ];

    execution.dirty = execution.dirty.concat(elements);
  };


  CommandStack.prototype._executedAction = function(action, redo) {
    const 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;
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var CommandModule = {
    commandStack: [ 'type', CommandStack ]
  };

  /**
   * 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 {Object[]} [collection]
   * @param {(element: Object) => void} removeFn
   *
   * @return {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$8 = 250,
      HIGH_PRIORITY$8 = 1400;

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   */

  /**
   * A handler that makes sure labels are properly moved with
   * their label targets.
   *
   * @param {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$8, 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$8, 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$8, 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$2(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 {Element[]} elements
   *
   * @return {Element[]} 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;
    });
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var LabelSupportModule = {
    __init__: [ 'labelSupport' ],
    labelSupport: [ 'type', LabelSupport ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../rules/Rules').default} Rules
   * @typedef {import('../modeling/Modeling').default} Modeling
   */

  var LOW_PRIORITY$7 = 251,
      HIGH_PRIORITY$7 = 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 {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$7, 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$7, 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$7, 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$2(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$2(AttachSupport, CommandInterceptor);

  AttachSupport.$inject = [
    'injector',
    'eventBus',
    'canvas',
    'rules',
    'modeling'
  ];


  /**
   * Return attachers of the given shapes
   *
   * @param {Element[]} shapes
   * @return {Element[]}
   */
  function getAttachers(shapes) {
    return flatten(map$1(shapes, function(s) {
      return s.attachers || [];
    }));
  }

  /**
   * Return a combined list of elements and
   * attachers.
   *
   * @param {Element[]} elements
   * @return {Element[]} 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 {Element[]} elements
   *
   * @return {Element[]} 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$2(array, item) {
    return array.indexOf(item) !== -1;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var AttachSupportModule = {
    __depends__: [
      RulesModule$1
    ],
    __init__: [ 'attachSupport' ],
    attachSupport: [ 'type', AttachSupport ]
  };

  /**
   * @typedef {import('../../model/Types').Moddle} Moddle
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   *
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   */

  /**
   * A factory for BPMN elements.
   *
   * @param {Moddle} moddle
   */
  function BpmnFactory(moddle) {
    this._model = moddle;
  }

  BpmnFactory.$inject = [ 'moddle' ];

  /**
   * @param {ModdleElement} element
   *
   * @return {boolean}
   */
  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'
    ]);
  };

  /**
   * @param {ModdleElement} element
   */
  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(element, 'bpmn:Activity')) {
      prefix = 'Activity';
    } else if (is(element, 'bpmn:Event')) {
      prefix = 'Event';
    } else if (is(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);
    }
  };

  /**
   * Create BPMN element.
   *
   * @param {string} type
   * @param {Object} [attrs]
   *
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.create = function(type, attrs) {
    var element = this._model.create(type, attrs || {});

    this._ensureId(element);

    return element;
  };

  /**
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiLabel = function() {
    return this.create('bpmndi:BPMNLabel', {
      bounds: this.createDiBounds()
    });
  };

  /**
   * @param {ModdleElement} semantic
   * @param {Object} [attrs]
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiShape = function(semantic, attrs) {
    return this.create('bpmndi:BPMNShape', assign$1({
      bpmnElement: semantic,
      bounds: this.createDiBounds()
    }, attrs));
  };

  /**
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiBounds = function(bounds) {
    return this.create('dc:Bounds', bounds);
  };

  /**
   * @param {Point[]} waypoints
   *
   * @return {ModdleElement[]}
   */
  BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
    var self = this;

    return map$1(waypoints, function(pos) {
      return self.createDiWaypoint(pos);
    });
  };

  /**
   * @param {Point} point
   *
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiWaypoint = function(point) {
    return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
  };

  /**
   * @param {ModdleElement} semantic
   * @param {Object} [attrs]
   *
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiEdge = function(semantic, attrs) {
    return this.create('bpmndi:BPMNEdge', assign$1({
      bpmnElement: semantic,
      waypoint: this.createDiWaypoints([])
    }, attrs));
  };

  /**
   * @param {ModdleElement} semantic
   * @param {Object} [attrs]
   *
   * @return {ModdleElement}
   */
  BpmnFactory.prototype.createDiPlane = function(semantic, attrs) {
    return this.create('bpmndi:BPMNPlane', assign$1({
      bpmnElement: semantic
    }, attrs));
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('./BpmnFactory').default} BpmnFactory
   * @typedef {import('diagram-js/lib/layout/CroppingConnectionDocking').default} CroppingConnectionDocking
   *
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').Parent} Parent
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   */

  /**
   * A handler responsible for updating the underlying BPMN 2.0 XML & DI
   * once changes on the diagram happen.
   *
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   * @param {CroppingConnectionDocking} connectionDocking
   */
  function BpmnUpdater(
      eventBus,
      bpmnFactory,
      connectionDocking
  ) {

    CommandInterceptor.call(this, eventBus);

    this._bpmnFactory = bpmnFactory;

    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(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(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));


    // update BPMNLabel
    this.executed('element.updateLabel', ifBpmn(updateBPMNLabel));
    this.reverted('element.updateLabel', ifBpmn(updateBPMNLabel));

    function updateBPMNLabel(event) {
      const { element } = event.context,
            label = getLabel(element);
      const di = getDi(element),
            diLabel = di && di.get('label');

      if (isLabelExternal(element) || isPlane(element)) {
        return;
      }

      if (label && !diLabel) {
        di.set('label', bpmnFactory.create('bpmndi:BPMNLabel'));
      } else if (!label && diLabel) {
        di.set('label', undefined);
      }
    }
  }

  e$2(BpmnUpdater, CommandInterceptor);

  BpmnUpdater.$inject = [
    'eventBus',
    'bpmnFactory',
    'connectionDocking'
  ];


  // implementation //////////////////////

  /**
   * @param { {
   *   shape: Shape;
   *   host: Shape;
   * } } context
   */
  BpmnUpdater.prototype.updateAttachment = function(context) {

    var shape = context.shape,
        businessObject = shape.businessObject,
        host = shape.host;

    businessObject.attachedToRef = host && host.businessObject;
  };

  /**
   * @param {Element} element
   * @param {Parent} oldParent
   */
  BpmnUpdater.prototype.updateParent = function(element, oldParent) {

    // do not update BPMN 2.0 label parent
    if (isLabel(element)) {
      return;
    }

    // data stores in collaborations are handled separately by DataStoreBehavior
    if (is(element, 'bpmn:DataStoreReference') &&
        element.parent &&
        is(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(element, 'bpmn:FlowNode')) {
      this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
    }

    if (is(element, 'bpmn:DataOutputAssociation')) {
      if (element.source) {
        parentBusinessObject = element.source.businessObject;
      } else {
        parentBusinessObject = null;
      }
    }

    if (is(element, 'bpmn:DataInputAssociation')) {
      if (element.target) {
        parentBusinessObject = element.target.businessObject;
      } else {
        parentBusinessObject = null;
      }
    }

    this.updateSemanticParent(businessObject, parentBusinessObject);

    if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
      this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
    }

    this.updateDiParent(di, parentDi);
  };

  /**
   * @param {Shape} shape
   */
  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$1(embeddedLabelBounds, {
        x: shape.x + embeddedLabelBoundsDelta.x,
        y: shape.y + embeddedLabelBoundsDelta.y
      });
    }

    var target = isLabel(shape) ? this._getLabel(di) : di;

    var bounds = target.bounds;

    if (!bounds) {
      bounds = this._bpmnFactory.createDiBounds();
      target.set('bounds', bounds);
    }

    assign$1(bounds, {
      x: shape.x,
      y: shape.y,
      width: shape.width,
      height: shape.height
    });
  };

  /**
   * @param {ModdleElement} businessObject
   * @param {ModdleElement} newContainment
   * @param {ModdleElement} oldContainment
   */
  BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {

    if (oldContainment === newContainment) {
      return;
    }

    var oldRefs, newRefs;

    if (is (oldContainment, 'bpmn:Lane')) {
      oldRefs = oldContainment.get('flowNodeRef');
      remove(oldRefs, businessObject);
    }

    if (is(newContainment, 'bpmn:Lane')) {
      newRefs = newContainment.get('flowNodeRef');
      add(newRefs, businessObject);
    }
  };

  /**
   * @param {Connection} connection
   * @param {Element} newSource
   * @param {Element} newTarget
   */
  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;
    }

  };

  /**
   * @param {ModdleElement} di
   * @param {ModdleElement} parentDi
   */
  BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {

    if (parentDi && !is(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;
    }
  };

  /**
   * @param {ModdleElement} element
   *
   * @return {ModdleElement}
   */
  function getDefinitions(element) {
    while (element && !is(element, 'bpmn:Definitions')) {
      element = element.$parent;
    }

    return element;
  }

  /**
   * @param {ModdleElement} container
   *
   * @return {ModdleElement}
   */
  BpmnUpdater.prototype.getLaneSet = function(container) {

    var laneSet, laneSets;

    // bpmn:Lane
    if (is(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(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;
  };

  /**
   * @param {ModdleElement} businessObject
   * @param {ModdleElement} newParent
   * @param {ModdleElement} visualParent
   */
  BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {

    var containment;

    if (businessObject.$parent === newParent) {
      return;
    }

    if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) {

      if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
        newParent = newParent.processRef;
      }

      // already in correct ioSpecification
      if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
        return;
      }
    }

    if (is(businessObject, 'bpmn:Lane')) {

      if (newParent) {
        newParent = this.getLaneSet(newParent);
      }

      containment = 'lanes';
    } else if (is(businessObject, 'bpmn:FlowElement')) {

      if (newParent) {

        if (is(newParent, 'bpmn:Participant')) {
          newParent = newParent.processRef;
        } else if (is(newParent, 'bpmn:Lane')) {
          do {

            // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
            newParent = newParent.$parent.$parent;
          } while (is(newParent, 'bpmn:Lane'));

        }
      }

      containment = 'flowElements';

    } else if (is(businessObject, 'bpmn:Artifact')) {

      while (newParent &&
             !is(newParent, 'bpmn:Process') &&
             !is(newParent, 'bpmn:SubProcess') &&
             !is(newParent, 'bpmn:Collaboration')) {

        if (is(newParent, 'bpmn:Participant')) {
          newParent = newParent.processRef;
          break;
        } else {
          newParent = newParent.$parent;
        }
      }

      containment = 'artifacts';
    } else if (is(businessObject, 'bpmn:MessageFlow')) {
      containment = 'messageFlows';
    } else if (is(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(businessObject, 'bpmn:DataOutputAssociation')) {
      containment = 'dataOutputAssociations';
    } else if (is(businessObject, 'bpmn:DataInputAssociation')) {
      containment = 'dataInputAssociations';
    }

    if (!containment) {
      throw new Error(`no parent for <${ businessObject.id }> in <${ 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);
      }
    }
  };

  /**
   * @param {Connection} connection
   */
  BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
    var di = getDi(connection);

    di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
  };

  /**
   * @param { {
   *   connection: Connection;
   *   parent: Parent;
   *   newParent: Parent;
   * } } context
   */
  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(businessObject, 'bpmn:DataAssociation')) {

      var inverseSet = is(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(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(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;
  };


  /**
   * Call function if shape or connection is BPMN element.
   *
   * @param  {Function} fn
   *
   * @return {Function}
   */
  function ifBpmn(fn) {

    return function(event) {

      var context = event.context,
          element = context.shape || context.connection || context.element;

      if (is(element, 'bpmn:BaseElement')) {
        fn(event);
      }
    };
  }

  /**
   * Return dc:Bounds of bpmndi:BPMNLabel if exists.
   *
   * @param {Shape} shape
   *
   * @return {ModdleElement|undefined}
   */
  function getEmbeddedLabelBounds(shape) {
    if (!is(shape, 'bpmn:Activity')) {
      return;
    }

    var di = getDi(shape);

    if (!di) {
      return;
    }

    var label = di.get('label');

    if (!label) {
      return;
    }

    return label.get('bounds');
  }

  /**
   * @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions
   *
   * @typedef {import('./BpmnFactory').default} BpmnFactory
   *
   * @typedef {import('../../model/Types').BpmnAttributes} BpmnAttributes
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Label} Label
   * @typedef {import('../../model/Types').Root} Root
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').Moddle} Moddle
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   */

  /**
   * A BPMN-specific element factory.
   *
   * @template {Connection} [T=Connection]
   * @template {Label} [U=Label]
   * @template {Root} [V=Root]
   * @template {Shape} [W=Shape]
   *
   * @extends {BaseElementFactory<T, U, V, W>}
   *
   * @param {BpmnFactory} bpmnFactory
   * @param {Moddle} moddle
   */
  function ElementFactory(bpmnFactory, moddle) {
    ElementFactory$1.call(this);

    this._bpmnFactory = bpmnFactory;
    this._moddle = moddle;
  }

  e$2(ElementFactory, ElementFactory$1);

  ElementFactory.$inject = [
    'bpmnFactory',
    'moddle'
  ];

  ElementFactory.prototype._baseCreate = ElementFactory$1.prototype.create;

  /**
   * Create a root element.
   *
   * @overlord
   * @param {'root'} elementType
   * @param {Partial<Root> & Partial<BpmnAttributes>} [attrs]
   * @return {V}
   */

  /**
   * Create a shape.
   *
   * @overlord
   * @param {'shape'} elementType
   * @param {Partial<Shape> & Partial<BpmnAttributes>} [attrs]
   * @return {W}
   */

  /**
   * Create a connection.
   *
   * @overlord
   * @param {'connection'} elementType
   * @param {Partial<Connection> & Partial<BpmnAttributes>} [attrs]
   * @return {T}
   */

  /**
   * Create a label.
   *
   * @param {'label'} elementType
   * @param {Partial<Label> & Partial<BpmnAttributes>} [attrs]
   * @return {U}
   */
  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$1({ type: 'label', di: di }, DEFAULT_LABEL_SIZE$1, attrs));
    }

    return this.createElement(elementType, attrs);
  };

  /**
   * Create a BPMN root element.
   *
   * @overlord
   * @param {'root'} elementType
   * @param {Partial<Root> & Partial<BpmnAttributes>} [attrs]
   * @return {V}
   */

  /**
   * Create a BPMN shape.
   *
   * @overlord
   * @param {'shape'} elementType
   * @param {Partial<Shape> & Partial<BpmnAttributes>} [attrs]
   * @return {W}
   */

  /**
   * Create a BPMN connection.
   *
   * @param {'connection'} elementType
   * @param {Partial<Connection> & Partial<BpmnAttributes>} [attrs]
   * @return {T}
   */
  ElementFactory.prototype.createElement = function(elementType, attrs) {

    attrs = assign$1({}, attrs || {});

    var size;

    var businessObject = attrs.businessObject,
        di = attrs.di;

    if (!businessObject) {
      if (!attrs.type) {
        throw new Error('no shape type specified');
      }

      businessObject = this._bpmnFactory.create(attrs.type);

      ensureCompatDiRef(businessObject);
    }

    if (!isModdleDi(di)) {
      var diAttrs = assign$1(
        {},
        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(businessObject, 'bpmn:Group')) {
      attrs = assign$1({
        isFrame: true
      }, attrs);
    }

    attrs = applyAttributes(businessObject, attrs, [
      'processRef',
      'isInterrupting',
      'associationDirection',
      'isForCompensation'
    ]);

    if (attrs.isExpanded) {
      attrs = applyAttribute(di, attrs, 'isExpanded');
    }

    if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
      attrs = applyAttribute(di, attrs, 'isHorizontal');
    }

    if (is(businessObject, 'bpmn:SubProcess')) {
      attrs.collapsed = !isExpanded(businessObject, di);
    }

    if (is(businessObject, 'bpmn:ExclusiveGateway')) {
      if (has$1(di, 'isMarkerVisible')) {
        if (di.isMarkerVisible === undefined) {
          di.isMarkerVisible = false;
        }
      } else {
        di.isMarkerVisible = true;
      }
    }

    if (isDefined(attrs.triggeredByEvent)) {
      businessObject.triggeredByEvent = attrs.triggeredByEvent;
      delete attrs.triggeredByEvent;
    }

    if (isDefined(attrs.cancelActivity)) {
      businessObject.cancelActivity = attrs.cancelActivity;
      delete attrs.cancelActivity;
    }

    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$1({
      id: businessObject.id
    }, size, attrs, {
      businessObject: businessObject,
      di: di
    });

    return this._baseCreate(elementType, attrs);
  };

  /**
   * Get the default size of a diagram element.
   *
   * @param {Element} element The element.
   * @param {ModdleElement} di The DI.
   *
   * @return {Dimensions} Default width and height of the element.
   */
  ElementFactory.prototype.getDefaultSize = function(element, di) {

    var bo = getBusinessObject(element);
    di = di || getDi(element);

    if (is(bo, 'bpmn:SubProcess')) {
      if (isExpanded(bo, di)) {
        return { width: 350, height: 200 };
      } else {
        return { width: 100, height: 80 };
      }
    }

    if (is(bo, 'bpmn:Task')) {
      return { width: 100, height: 80 };
    }

    if (is(bo, 'bpmn:Gateway')) {
      return { width: 50, height: 50 };
    }

    if (is(bo, 'bpmn:Event')) {
      return { width: 36, height: 36 };
    }

    if (is(bo, 'bpmn:Participant')) {
      var isHorizontalPool = di.isHorizontal === undefined || di.isHorizontal === true;
      if (isExpanded(bo, di)) {
        if (isHorizontalPool) {
          return { width: 600, height: 250 };
        }
        return { width: 250, height: 600 };
      } else {
        if (isHorizontalPool) {
          return { width: 400, height: 60 };
        }
        return { width: 60, height: 400 };
      }
    }

    if (is(bo, 'bpmn:Lane')) {
      return { width: 400, height: 100 };
    }

    if (is(bo, 'bpmn:DataObjectReference')) {
      return { width: 36, height: 50 };
    }

    if (is(bo, 'bpmn:DataStoreReference')) {
      return { width: 50, height: 50 };
    }

    if (is(bo, 'bpmn:TextAnnotation')) {
      return { width: 100, height: 30 };
    }

    if (is(bo, 'bpmn:Group')) {
      return { width: 300, height: 300 };
    }

    return { width: 100, height: 80 };
  };


  /**
   * Create participant.
   *
   * @param {boolean|Partial<Shape> & Partial<BpmnAttributes>} [attrs]
   * Attributes or whether the participant is expanded.
   *
   * @return {W} The created participant.
   */
  ElementFactory.prototype.createParticipantShape = function(attrs) {

    if (!isObject(attrs)) {
      attrs = { isExpanded: attrs };
    }

    attrs = assign$1({ 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 {Element} element
   * @param {Object} attrs (in/out map of attributes)
   * @param {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 {Element} 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 ]);
  }

  /**
   * @param {Element} element
   *
   * @return {boolean}
   */
  function isModdleDi(element) {
    return isAny(element, [
      'bpmndi:BPMNShape',
      'bpmndi:BPMNEdge',
      'bpmndi:BPMNDiagram',
      'bpmndi:BPMNPlane',
    ]);
  }

  /**
   * @typedef {import('../../../core/Canvas').default} Canvas
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that align elements in a certain way.
   *
   * @param {Modeling} modeling
   * @param {Canvas} canvas
   */
  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 (isDefined(alignment.left)) {
        delta.x = alignment.left - element.x;

      } else if (isDefined(alignment.right)) {
        delta.x = (alignment.right - element.width) - element.x;

      } else if (isDefined(alignment.center)) {
        delta.x = (alignment.center - Math.round(element.width / 2)) - element.x;

      } else if (isDefined(alignment.top)) {
        delta.y = alignment.top - element.y;

      } else if (isDefined(alignment.bottom)) {
        delta.y = (alignment.bottom - element.height) - element.y;

      } else if (isDefined(alignment.middle)) {
        delta.y = (alignment.middle - Math.round(element.height / 2)) - element.y;
      }

      modeling.moveElements([ element ], delta, element.parent);
    });
  };

  AlignElements.prototype.postExecute = function(context) {

  };

  /**
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Parent} Parent
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('../../../util/Types').Point} Point
   *
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that implements reversible appending of shapes
   * to a source shape.
   *
   * @param {Modeling} modeling
   */
  function AppendShapeHandler(modeling) {
    this._modeling = modeling;
  }

  AppendShapeHandler.$inject = [ 'modeling' ];


  // api //////////////////////


  /**
   * Creates a new shape.
   *
   * @param {Object} context
   * @param {Partial<Shape>} context.shape The new shape.
   * @param {Element} context.source The element to which to append the new shape to.
   * @param {Parent} context.parent The parent.
   * @param {Point} context.position The position at which to create the new shape.
   */
  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;
    });
  }

  /**
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('../../../util/Types').Point} Point
   *
   * @typedef {import('../Modeling').ModelingHints} ModelingHints
   *
   * @typedef {import('../../../core/Canvas').default} Canvas
   * @typedef {import('../../../layout/BaseLayouter').default} Layouter
   */

  /**
   * @param {Canvas} canvas
   * @param {Layouter} layouter
   */
  function CreateConnectionHandler(canvas, layouter) {
    this._canvas = canvas;
    this._layouter = layouter;
  }

  CreateConnectionHandler.$inject = [ 'canvas', 'layouter' ];


  // api //////////////////////


  /**
   * Creates a new connection between two elements.
   *
   * @param {Object} context
   * @param {Element} context.source The source.
   * @param {Element} context.target The target.
   * @param {Shape} context.parent The parent.
   * @param {number} [context.parentIndex] The optional index at which to add the
   * connection to the parent's children.
   * @param {ModelingHints} [context.hints] The optional hints.
   */
  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;
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  var round$4 = Math.round;

  /**
   * @param {Modeling} modeling
   */
  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(element)) {
        element.waypoints = map$1(element.waypoints, function(waypoint) {
          return {
            x: round$4(waypoint.x - bbox.x - bbox.width / 2 + position.x),
            y: round$4(waypoint.y - bbox.y - bbox.height / 2 + position.y)
          };
        });
      }

      assign$1(element, {
        x: round$4(element.x - bbox.x - bbox.width / 2 + position.x),
        y: round$4(element.y - bbox.y - bbox.height / 2 + position.y)
      });
    });

    var parents = getParents$1(elements);

    var cache = {};

    forEach$1(elements, function(element) {
      if (isConnection(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$1({}, hints);

      if (parents.indexOf(element) === -1) {
        createShapeHints.autoResize = false;
      }

      if (isLabel(element)) {
        createShapeHints = omit(createShapeHints, [ 'attach' ]);
      }

      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);
  };

  /**
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../util/Types').Point} Point
   *
   * @typedef {import('../../../core/Canvas').default} Canvas
   */

  var round$3 = 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 {Element} context.parent The parent.
   * @param {Point} context.position The position at which to create the new shape.
   * @param {number} [context.parentIndex] The optional index at which to add the
   * shape to the parent's children.
   */
  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$1(shape, positionOrBounds);
    } else {
      assign$1(shape, {
        x: positionOrBounds.x - round$3(shape.width / 2),
        y: positionOrBounds.y - round$3(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;
  };

  /**
   * @typedef {import('../../../core/Canvas').default} Canvas
   *
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('../../../model/Types').Parent} Parent
   * @typedef {import('../../../model/Types').Shape} Shape
   * @typedef {import('../../../util/Types').Point} Point
   */

  /**
   * A handler that attaches a label to a given target shape.
   *
   * @param {Canvas} canvas
   */
  function CreateLabelHandler(canvas) {
    CreateShapeHandler.call(this, canvas);
  }

  e$2(CreateLabelHandler, CreateShapeHandler);

  CreateLabelHandler.$inject = [ 'canvas' ];


  // api //////////////////////


  var originalExecute = CreateShapeHandler.prototype.execute;

  /**
   * Append label to element.
   *
   * @param { {
   *   parent: Parent;
   *   position: Point;
   *   shape: Shape;
   *   target: Element;
   * } } context
   */
  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;

  /**
   * Revert appending by removing label.
   */
  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;
      }
    });
  }

  /**
   * @typedef {import('../../../core/Canvas').default} Canvas
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * 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;
  };

  /**
   * @typedef {import('../../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * @param {Modeling} modeling
   * @param {ElementRegistry} elementRegistry
   */
  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);
      }
    });
  };

  /**
   * @typedef {import('../../../core/Canvas').default} Canvas
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that implements reversible deletion of shapes.
   *
   * @param {Canvas} canvas
   * @param {Modeling} modeling
   */
  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(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;
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that distributes elements evenly.
   *
   * @param {Modeling} modeling
   */
  function DistributeElements$1(modeling) {
    this._modeling = modeling;
  }

  DistributeElements$1.$inject = [ 'modeling' ];

  var OFF_AXIS = {
    x: 'y',
    y: 'x'
  };

  DistributeElements$1.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$1.prototype.postExecute = function(context) {

  };

  /**
   * @typedef {import('../../../core/Canvas').default} Canvas
   * @typedef {import('../../../layout/BaseLayouter').default} Layouter
   */

  /**
   * A handler that implements reversible moving of shapes.
   *
   * @param {Layouter} layouter
   * @param {Canvas} canvas
   */
  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$1(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;
  };

  /**
   * @typedef {import('../../../../model/Types').Connection} Connection
   * @typedef {import('../../../../model/Types').Element} Element
   * @typedef {import('../../../../model/Types').Shape} Shape
   */

  function MoveClosure() {

    /**
     * @type {Record<string, Shape>}
     */
    this.allShapes = {};

    /**
     * @type {Record<string, Connection>}
     */
    this.allConnections = {};

    /**
     * @type {Record<string, Element>}
     */
    this.enclosedElements = {};

    /**
     * @type {Record<string, Connection>}
     */
    this.enclosedConnections = {};

    /**
     * @type {Record<string, Element>}
     */
    this.topLevel = {};
  }

  /**
   * @param {Element} element
   * @param {boolean} [isTopLevel]
   *
   * @return {MoveClosure}
   */
  MoveClosure.prototype.add = function(element, isTopLevel) {
    return this.addAll([ element ], isTopLevel);
  };

  /**
   * @param {Element[]} elements
   * @param {boolean} [isTopLevel]
   *
   * @return {MoveClosure}
   */
  MoveClosure.prototype.addAll = function(elements, isTopLevel) {

    var newClosure = getClosure(elements, !!isTopLevel, this);

    assign$1(this, newClosure);

    return this;
  };

  /**
   * @typedef {import('../../../../core/Types').ElementLike} Element
   * @typedef {import('../../../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../../../util/Types').Point} Point
   *
   * @typedef {import('../../Modeling').default} Modeling
   */

  /**
   * 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.
   *
   * @template {Element} T
   *
   * @param {T[]} elements
   * @param {Point} delta
   * @param {Shape} newParent The new parent of all elements that are not nested.
   *
   * @return {T[]}
   */
  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 {Shape} [newParent]
   * @param {Shape} [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 {Element[]} elements
   *
   * @return {MoveClosure}
   */
  MoveHelper.prototype.getClosure = function(elements) {
    return new MoveClosure().addAll(elements, true);
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that implements reversible moving of shapes.
   *
   * @param {Modeling} modeling
   */
  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
    );
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that implements reversible moving of shapes.
   *
   * @param {Modeling} modeling
   */
  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$1(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$1(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;
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * Reconnect connection handler.
   *
   * @param {Modeling} modeling
   */
  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;
  }

  /**
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../../rules/Rules').default} Rules
   */

  /**
   * 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 {Shape} context.oldShape
   * @param {Object} context.newData
   * @param {string} context.newData.type
   * @param {number} context.newData.x
   * @param {number} context.newData.y
   * @param {Object} [context.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);
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * 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$1(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$1(shape, {
      width:  oldBounds.width,
      height: oldBounds.height,
      x:      oldBounds.x,
      y:      oldBounds.y
    });

    return shape;
  };

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * Add or remove space by moving and resizing shapes and updating connection waypoints.
   *
   * @param {Modeling} modeling
   */
  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 = {};

      if (includes$1(affectedShapes, source) && includes$1(affectedShapes, target)) {

        // move waypoints
        waypoints = map$1(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$1({}, waypoint);
  }

  function copyWaypoints(connection) {
    return map$1(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
    };
  }

  /**
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * 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 {Shape[]} elements
   *
   * @return {Object}
   */
  function getElementsVisibilityRecursive(elements) {

    var result = {};

    forEach$1(elements, function(element) {
      result[element.id] = element.hidden;

      if (element.children) {
        result = assign$1({}, 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;
  }

  /**
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that implements reversible attaching/detaching of shapes.
   *
   * @param {Modeling} modeling
   */
  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;
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Parent} Parent
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').Label} Label
   *
   * @typedef {import('../../command/CommandStack').default} CommandStack
   * @typedef {import('../../core/ElementFactory').default} ElementFactory
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('../../command/CommandStack').CommandHandlerConstructor} CommandHandlerConstructor
   *
   * @typedef {import('../../util/Types').Dimensions} Dimensions
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Point} Point
   * @typedef {import('../../util/Types').Rect} Rect
   *
   * @typedef { 'x' | 'y' } ModelingDistributeAxis
   *
   * @typedef { 'width' | 'height' } ModelingDistributeDimension
   *
   * @typedef { {
   *   bottom?: number;
   *   center?: number;
   *   left?: number;
   *   middle?: number;
   *   right?: number;
   *   top?: number;
   * } } ModelingAlignAlignment
   *
   * @typedef { {
   *   [key: string]: any;
   * } } ModelingHints
   *
   * @typedef { {
   *   attach?: boolean;
   * } & ModelingHints } ModelingMoveElementsHints
   *
   * @typedef { {
   *   attach?: boolean;
   * } & ModelingHints } ModelingCreateShapeHints
   */

  /**
   * @template {Element} U
   *
   * @typedef { {
   *   elements: U[],
   *   range: {
   *     min: number;
   *     max: number;
   *   } }
   * } ModelingDistributeGroup
   */

  /**
   * The basic modeling entry point.
   *
   * @template {Connection} [T=Connection]
   * @template {Element} [U=Element]
   * @template {Label} [V=Label]
   * @template {Parent} [W=Parent]
   * @template {Shape} [X=Shape]
   *
   * @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' ];

  /**
   * Get a map of all command handlers.
   *
   * @return {Map<string, CommandHandlerConstructor>}
   */
  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$1,
      '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);
    });
  };


  /**
   * Move a shape by the given delta and optionally to a new parent.
   *
   * @param {X} shape
   * @param {Point} delta
   * @param {W} [newParent]
   * @param {number} [newParentIndex]
   * @param {ModelingHints} [hints]
   */
  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 a shape.
   *
   * @param {X} shape
   * @param {X} [newHost=undefined]
   */
  Modeling$1.prototype.updateAttachment = function(shape, newHost) {
    var context = {
      shape: shape,
      newHost: newHost
    };

    this._commandStack.execute('element.updateAttachment', context);
  };


  /**
   * Move elements by a given delta and optionally to a new parent.
   *
   * @param {U[]} shapes
   * @param {Point} delta
   * @param {W} [target]
   * @param {ModelingMoveElementsHints} [hints]
   */
  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);
  };

  /**
   * Move a shape by the given delta and optionally to a new parent.
   *
   * @param {T} connection
   * @param {Point} delta
   * @param {W} [newParent]
   * @param {number} [newParentIndex]
   * @param {ModelingHints} [hints]
   */
  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);
  };

  /**
   * Layout a connection.
   *
   * @param {T} connection
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.layoutConnection = function(connection, hints) {
    var context = {
      connection: connection,
      hints: hints || {}
    };

    this._commandStack.execute('connection.layout', context);
  };

  /**
   * Create a connection.
   *
   * @overlord
   *
   * @param {U} source
   * @param {U} target
   * @param {Partial<T>} connection
   * @param {W} parent
   * @param {ModelingHints} [hints]
   *
   * @return {T}
   */

  /**
   * Create a connection.
   *
   * @param {U} source
   * @param {U} target
   * @param {number} parentIndex
   * @param {Partial<T>} connection
   * @param {W} parent
   * @param {ModelingHints} [hints]
   *
   * @return {T}
   */
  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.
   *
   * @overlord
   *
   * @param {Partial<X>} shape
   * @param {Point} position
   * @param {W} target
   * @param {ModelingCreateShapeHints} [hints]
   *
   * @return {X}
   */

  /**
   * Create a shape.
   *
   * @param {Partial<X>} shape
   * @param {Point} position
   * @param {W} target
   * @param {number} parentIndex
   * @param {ModelingCreateShapeHints} [hints]
   *
   * @return {X}
   */
  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;
  };

  /**
   * Create elements.
   *
   * @param {Partial<U>[]} elements
   * @param {Point} position
   * @param {W} parent
   * @param {number} [parentIndex]
   * @param {ModelingHints} [hints]
   *
   * @return {U[]}
   */
  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;
  };

  /**
   * Create a label.
   *
   * @param {U} labelTarget
   * @param {Point} position
   * @param {Partial<V>} label
   * @param {W} [parent]
   *
   * @return {V}
   */
  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;
  };


  /**
   * Create and connect a shape to a source.
   *
   * @param {U} source
   * @param {Partial<X>} shape
   * @param {Point} position
   * @param {W} target
   * @param {ModelingHints} [hints]
   *
   * @return {X}
   */
  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;
  };

  /**
   * Remove elements.
   *
   * @param {U[]} elements
   */
  Modeling$1.prototype.removeElements = function(elements) {
    var context = {
      elements: elements
    };

    this._commandStack.execute('elements.delete', context);
  };

  /**
   * Distribute elements along a given axis.
   *
   * @param {ModelingDistributeGroup<U>[]} groups
   * @param {ModelingDistributeAxis} axis
   * @param {ModelingDistributeDimension} dimension
   */
  Modeling$1.prototype.distributeElements = function(groups, axis, dimension) {
    var context = {
      groups: groups,
      axis: axis,
      dimension: dimension
    };

    this._commandStack.execute('elements.distribute', context);
  };

  /**
   * Remove a shape.
   *
   * @param {X} shape
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.removeShape = function(shape, hints) {
    var context = {
      shape: shape,
      hints: hints || {}
    };

    this._commandStack.execute('shape.delete', context);
  };

  /**
   * Remove a connection.
   *
   * @param {T} connection
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.removeConnection = function(connection, hints) {
    var context = {
      connection: connection,
      hints: hints || {}
    };

    this._commandStack.execute('connection.delete', context);
  };

  /**
   * Replace a shape.
   *
   * @param {X} oldShape
   * @param {Partial<X>} newShape
   * @param {ModelingHints} [hints]
   *
   * @return {X}
   */
  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;
  };

  /**
   * Align elements.
   *
   * @param {U[]} elements
   * @param {ModelingAlignAlignment} alignment
   */
  Modeling$1.prototype.alignElements = function(elements, alignment) {
    var context = {
      elements: elements,
      alignment: alignment
    };

    this._commandStack.execute('elements.align', context);
  };

  /**
   * Resize a shape.
   *
   * @param {X} shape
   * @param {Rect} newBounds
   * @param {Dimensions} [minBounds]
   * @param {ModelingHints} [hints]
   */
  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);
  };

  /**
   * Create space along an horizontally or vertically.
   *
   * @param {X[]} movingShapes
   * @param {X[]} resizingShapes
   * @param {Point} delta
   * @param {Direction} direction
   * @param {number} start
   */
  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);
  };

  /**
   * Update a connetions waypoints.
   *
   * @param {T} connection
   * @param {Point[]} newWaypoints
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.updateWaypoints = function(connection, newWaypoints, hints) {
    var context = {
      connection: connection,
      newWaypoints: newWaypoints,
      hints: hints || {}
    };

    this._commandStack.execute('connection.updateWaypoints', context);
  };

  /**
   * Reconnect a connections source and/or target.
   *
   * @param {T} connection
   * @param {U} source
   * @param {U} target
   * @param {Point|Point[]} dockingOrPoints
   * @param {ModelingHints} [hints]
   */
  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);
  };

  /**
   * Reconnect a connections source.
   *
   * @param {T} connection
   * @param {U} newSource
   * @param {Point|Point[]} dockingOrPoints
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) {
    if (!hints) {
      hints = {};
    }

    this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign$1(hints, {
      docking: 'source'
    }));
  };

  /**
   * Reconnect a connections target.
   *
   * @param {T} connection
   * @param {U} newTarget
   * @param {Point|Point[]} dockingOrPoints
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) {
    if (!hints) {
      hints = {};
    }

    this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign$1(hints, {
      docking: 'target'
    }));
  };

  /**
   * Connect two elements.
   *
   * @param {U} source
   * @param {U} target
   * @param {Partial<T>} [attrs]
   * @param {ModelingHints} [hints]
   *
   * @return {T}
   */
  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 (isModelElement(attrs)) {
      return attrs;
    } else {
      return this._elementFactory.create(type, attrs);
    }
  };

  /**
   * Collapse or expand a shape.
   *
   * @param {X} shape
   * @param {ModelingHints} [hints]
   */
  Modeling$1.prototype.toggleCollapse = function(shape, hints) {
    var context = {
      shape: shape,
      hints: hints || {}
    };

    this._commandStack.execute('shape.toggleCollapse', context);
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   *
   * @typedef {import('../../../model/Types').Shape} Shape
   * @typedef {import('../../../model/Types').ModdleElement} ModdleElement
   */

  /**
   * @implements {CommandHandler}
   *
   * @param {ElementRegistry} elementRegistry
   */
  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 {Shape[]}
   */
  UpdateModdlePropertiesHandler.prototype._getVisualReferences = function(moddleElement) {

    var elementRegistry = this._elementRegistry;

    if (is(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(element, 'bpmn:DataObjectReference') &&
            getBusinessObject(element).dataObjectRef === dataObject
      );
    });
  }

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   * @typedef {import('diagram-js/lib/command/CommandStack').CommandContext} CommandContext
   *
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../../model/Types').Moddle} Moddle
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../../../draw/TextRenderer').default} TextRenderer
   *
   * @typedef {import('../../../model/Types').Element} Element
   */

  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.
   *
   * @implements {CommandHandler}
   *
   * @param {ElementRegistry} elementRegistry
   * @param {Moddle} moddle
   * @param {Modeling} modeling
   * @param {TextRenderer} textRenderer
   */
  function UpdatePropertiesHandler(
      elementRegistry, moddle,
      modeling, textRenderer) {

    this._elementRegistry = elementRegistry;
    this._moddle = moddle;
    this._modeling = modeling;
    this._textRenderer = textRenderer;
  }

  UpdatePropertiesHandler.$inject = [
    'elementRegistry',
    'moddle',
    'modeling',
    'textRenderer'
  ];


  // api //////////////////////

  /**
   * Update a BPMN element's properties.
   *
   * @param { {
   *   element: Element;
   *   properties: Record<string, any>;
   * } & CommandContext } context
   *
   * @return {Element[]}
   */
  UpdatePropertiesHandler.prototype.execute = function(context) {

    var element = context.element,
        changed = [ element ];

    if (!element) {
      throw new Error('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);
  };

  /**
   * Revert updating a BPMN element's properties.
   *
   * @param { {
   *   element: Element;
   *   properties: Record<string, any>;
   *   oldProperties: Record<string, any>;
   * } & CommandContext } context
   *
   * @return {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  {Record<string, any>} properties
   *
   * @return {Record<string, any>} unwrappedProps
   */
  function unwrapBusinessObjects(properties) {

    var unwrappedProps = assign$1({}, properties);

    referencePropertyNames.forEach(function(name) {
      if (name in properties) {
        unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
      }
    });

    return unwrappedProps;
  }

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * @implements {CommandHandler}
   *
   * @param {Canvas} canvas
   * @param {Modeling} modeling
   */
  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 ];
    return [];
  };


  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 ];

    return [];
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
   */

  /**
   * A handler that allows us to add a new lane
   * above or below an existing one.
   *
   * @implements {CommandHandler}
   *
   * @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);

    var isHorizontalLane = isHorizontal$3(shape);

    // never mix up horizontal/vertical lanes
    if (isHorizontalLane) {
      if (location === 'left') {
        location = 'top';
      } else if (location === 'right') {
        location = 'bottom';
      }
    } else {
      if (location === 'top') {
        location = 'left';
      } else if (location === 'bottom') {
        location = 'right';
      }
    }

    // (0) add a lane if we currently got none and are adding to root
    if (!existingChildLanes.length) {
      var siblingPosition = isHorizontalLane ? {
        x: shape.x + LANE_INDENTATION,
        y: shape.y,
        width: shape.width - LANE_INDENTATION,
        height: shape.height
      } : {
        x: shape.x,
        y: shape.y + LANE_INDENTATION,
        width: shape.width,
        height: shape.height - LANE_INDENTATION
      };

      modeling.createShape(
        {
          type: 'bpmn:Lane',
          isHorizontal: isHorizontalLane
        },
        siblingPosition,
        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,
        lanePosition,
        spacePos,
        direction,
        axis;

    if (location === 'top') {
      offset = -120;
      lanePosition = shape.y;
      spacePos = lanePosition + 10;
      direction = 'n';
      axis = 'y';
    } else if (location === 'left') {
      offset = -120;
      lanePosition = shape.x;
      spacePos = lanePosition + 10;
      direction = 'w';
      axis = 'x';
    } else if (location === 'bottom') {
      offset = 120;
      lanePosition = shape.y + shape.height;
      spacePos = lanePosition - 10;
      direction = 's';
      axis = 'y';
    } else if (location === 'right') {
      offset = 120;
      lanePosition = shape.x + shape.width;
      spacePos = lanePosition - 10;
      direction = 'e';
      axis = 'x';
    }

    var adjustments = spaceTool.calculateAdjustments(allAffected, axis, offset, spacePos);

    var delta = isHorizontalLane ? { x: 0, y: offset } : { x: offset, y: 0 };

    spaceTool.makeSpace(
      adjustments.movingShapes,
      adjustments.resizingShapes,
      delta,
      direction,
      spacePos
    );

    // (2) create new lane at open space
    var newLanePosition = isHorizontalLane ? {
      x: shape.x + (isRoot ? LANE_INDENTATION : 0),
      y: lanePosition - (location === 'top' ? 120 : 0),
      width: shape.width - (isRoot ? LANE_INDENTATION : 0),
      height: 120
    } : {
      x: lanePosition - (location === 'left' ? 120 : 0),
      y: shape.y + (isRoot ? LANE_INDENTATION : 0),
      width: 120,
      height: shape.height - (isRoot ? LANE_INDENTATION : 0)
    };

    context.newLane = modeling.createShape(
      {
        type: 'bpmn:Lane',
        isHorizontal: isHorizontalLane
      },
      newLanePosition,
      laneParent
    );
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('../Modeling').default} Modeling
   */

  /**
   * A handler that splits a lane into a number of sub-lanes,
   * creating new sub lanes, if necessary.
   *
   * @implements {CommandHandler}
   *
   * @param {Modeling} modeling
   */
  function SplitLaneHandler(modeling) {
    this._modeling = modeling;
  }

  SplitLaneHandler.$inject = [
    'modeling'
  ];


  SplitLaneHandler.prototype.preExecute = function(context) {

    var modeling = this._modeling;

    var shape = context.shape,
        newLanesCount = context.count;

    var childLanes = getChildLanes(shape),
        existingLanesCount = childLanes.length;

    if (existingLanesCount > newLanesCount) {
      throw new Error(`more than <${ newLanesCount }> child lanes`);
    }

    var isHorizontalLane = isHorizontal$3(shape);

    var laneBaseSize = isHorizontalLane ? shape.height : shape.width;
    var newLanesSize = Math.round(laneBaseSize / newLanesCount);

    // Iterate from first to last in child lane order,
    // resizing existing lanes and creating new ones
    // so that they split the parent proportionally.
    //
    // Due to rounding related errors, the last lane
    // needs to take up all the remaining space.
    var laneSize,
        laneBounds,
        newLaneAttrs,
        idx;

    for (idx = 0; idx < newLanesCount; idx++) {

      // if last lane
      if (idx === newLanesCount - 1) {
        laneSize = laneBaseSize - (newLanesSize * idx);
      } else {
        laneSize = newLanesSize;
      }

      laneBounds = isHorizontalLane ? {
        x: shape.x + LANE_INDENTATION,
        y: shape.y + idx * newLanesSize,
        width: shape.width - LANE_INDENTATION,
        height: laneSize
      } : {
        x: shape.x + idx * newLanesSize,
        y: shape.y + LANE_INDENTATION,
        width: laneSize,
        height: shape.height - LANE_INDENTATION
      };

      if (idx < existingLanesCount) {

        // resize existing lane
        modeling.resizeShape(childLanes[idx], laneBounds);
      } else {

        // create a new lane at position
        newLaneAttrs = {
          type: 'bpmn:Lane',
          isHorizontal: isHorizontalLane
        };

        modeling.createShape(newLaneAttrs, laneBounds, shape);
      }
    }
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('../Modeling').default} Modeling
   * @typedef {import('../../space-tool/BpmnSpaceTool').default} SpaceTool
   *
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   */

  /**
   * A handler that resizes a lane.
   *
   * @implements {CommandHandler}
   *
   * @param {Modeling} modeling
   * @param {SpaceTool} spaceTool
   */
  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 {Shape} shape
   * @param {Rect} 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 {Shape} shape
   * @param {Rect} 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(element, 'bpmn:Lane') || is(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);
    }
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   *
   * @typedef {import('../../../model/Types').Shape} Shape
   */

  var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
      LANES_ATTR = 'lanes';


  /**
   * A handler that updates lane refs on changed elements.
   *
   * @implements {CommandHandler}
   *
   * @param {ElementRegistry} elementRegistry
   */
  function UpdateFlowNodeRefsHandler(elementRegistry) {
    this._elementRegistry = elementRegistry;
  }

  UpdateFlowNodeRefsHandler.$inject = [
    'elementRegistry'
  ];

  /**
   * @param {Shape} flowNodeShapes
   * @param {Shape} laneShapes
   *
   * @return { {
   *   flowNode: Shape;
   *   add: Shape[];
   *   remove: Shape[];
   * }[] }
   */
  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(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 [ ... ];

    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 [ ... ];

    return [];
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('../../../model/Types').Moddle} Moddle
   */

  /**
   * @implements {CommandHandler}
   *
   * @param {Moddle} moddle
   */
  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);
    }

    return [];
  };

  /**
   * 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);
    }

    return [];
  };

  /**
   * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler
   *
   * @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
   *
   * @typedef {import('../../../model/Types').ModdleElement} ModdleElement
   */

  var DEFAULT_COLORS = {
    fill: undefined,
    stroke: undefined
  };

  /**
   * @implements {CommandHandler}
   *
   * @param {CommandStack} commandStack
   */
  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$1(di, {
        'background-color': this._normalizeColor(colors.fill) });
    }

    if ('stroke' in colors) {
      assign$1(di, {
        'border-color': this._normalizeColor(colors.stroke) });
    }

    forEach$1(elements, function(element) {
      var assignedDi = isConnection(element) ? pick(di, [ 'border-color' ]) : di,
          elementDi = getDi(element);

      // TODO @barmac: remove once we drop bpmn.io properties
      ensureLegacySupport(assignedDi);

      if (isLabel(element)) {

        // set label colors as bpmndi:BPMNLabel#color
        self._commandStack.execute('element.updateModdleProperties', {
          element: element,
          moddleElement: elementDi.label,
          properties: {
            color: di['border-color']
          }
        });
      } else {

        // ignore non-compliant di
        if (!isAny(elementDi, [ 'bpmndi:BPMNEdge', 'bpmndi:BPMNShape' ])) {
          return;
        }

        // 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
   *
   * ```javascript
   * colorToHex('fuchsia'); // "#ff00ff"
   *
   * colorToHex('rgba(1, 2, 3, 0.4)'); // null
   * ```
   *
   * @param {string} color
   *
   * @return {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;
  }

  /**
   * Add legacy properties if required.
   *
   * @param {ModdleElement} 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
  };

  /**
   * @typedef {import('../../modeling/Modeling').default} Modeling
   * @typedef {import('../../../draw/TextRenderer').default} TextRenderer
   * @typedef {import('../../modeling/BpmnFactory').default} BpmnFactory
   *
   * @typedef {import('../../../model/Types').Element} Element
   */

  /**
   * A handler that updates the text of a BPMN element.
   *
   * @param {Modeling} modeling
   * @param {TextRenderer} textRenderer
   * @param {BpmnFactory} bpmnFactory
   */
  function UpdateLabelHandler(modeling, textRenderer, bpmnFactory) {

    /**
     * Set the label and return the changed elements.
     *
     * Element parameter can be label itself or connection (i.e. sequence flow).
     *
     * @param {Element} 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);

      return [ label, labelTarget ];
    }

    function preExecute(ctx) {
      var element = ctx.element,
          businessObject = element.businessObject,
          newLabel = ctx.newLabel;

      if (!isLabel(element)
          && isLabelExternal(element)
          && !hasExternalLabel(element)
          && !isEmptyText$1(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(label) && !is(label, 'bpmn:TextAnnotation')) {
        return;
      }

      if (isLabel(label) && isEmptyText$1(newLabel)) {

        if (hints.removeShape !== false) {
          modeling.removeShape(label, { unsetLabel: false });
        }

        return;
      }

      var text = getLabel(element);

      // 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$1(label) {
    return !label || !label.trim();
  }

  /**
   * @typedef {import('../rules/BpmnRules').default} BpmnRules
   * @typedef {import('diagram-js/lib/command/CommandStack').default} CommandStack
   * @typedef {import('./ElementFactory').default} ElementFactory
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('diagram-js/lib/features/modeling/Modeling').ModelingHints} ModelingHints
   *
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Label} Label
   * @typedef {import('../../model/Types').Parent} Parent
   * @typedef {import('../../model/Types').Root} Root
   * @typedef {import('../../model/Types').Shape} Shape
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   *
   * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
   *
   * @typedef {import('../../util/Types').Colors} Colors
   *
   * @typedef { {
   *   removeShape?: boolean;
   * } } UpdateLabelHints
   */

  /**
   * The BPMN 2.0 modeling entry point.
   *
   * @template {Connection} [T=Connection]
   * @template {Element} [U=Element]
   * @template {Label} [V=Label]
   * @template {Parent} [W=Parent]
   * @template {Shape} [X=Shape]
   *
   * @extends {BaseModeling<T, U, V, W, X>}
   *
   * @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$2(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;
  };

  /**
   * Update an element's label.
   *
   * @param {Element} element The element.
   * @param {string} newLabel The new label.
   * @param {Rect} [newBounds] The optional bounds of the label.
   * @param {UpdateLabelHints} [hints] The optional hints.
   */
  Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
    this._commandStack.execute('element.updateLabel', {
      element: element,
      newLabel: newLabel,
      newBounds: newBounds,
      hints: hints || {}
    });
  };

  /**
   * @param {Element} source
   * @param {Element} target
   * @param {Partial<Connection>} [attrs]
   * @param {ModelingHints} [hints]
   *
   * @return {T}
   */
  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);
  };

  /**
   * Update a model element's properties.
   *
   * @param {Element} element The element.
   * @param {ModdleElement} moddleElement The model element.
   * @param {Object} properties The updated properties.
   */
  Modeling.prototype.updateModdleProperties = function(element, moddleElement, properties) {
    this._commandStack.execute('element.updateModdleProperties', {
      element: element,
      moddleElement: moddleElement,
      properties: properties
    });
  };

  /**
   * Update an element's properties.
   *
   * @param {Element} element The element.
   * @param {Object} properties The updated properties.
   */
  Modeling.prototype.updateProperties = function(element, properties) {
    this._commandStack.execute('element.updateProperties', {
      element: element,
      properties: properties
    });
  };

  /**
   * Resize a lane.
   *
   * @param {Shape} laneShape The lane.
   * @param {Rect} newBounds The new bounds of the lane.
   * @param {boolean} [balanced] Wether to resize neighboring lanes.
   */
  Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
    this._commandStack.execute('lane.resize', {
      shape: laneShape,
      newBounds: newBounds,
      balanced: balanced
    });
  };

  /**
   * Add a lane.
   *
   * @param {Shape} targetLaneShape The shape to add the lane to.
   * @param {string} location The location.
   *
   * @return {Shape} The added lane.
   */
  Modeling.prototype.addLane = function(targetLaneShape, location) {
    var context = {
      shape: targetLaneShape,
      location: location
    };

    this._commandStack.execute('lane.add', context);

    return context.newLane;
  };

  /**
   * Split a lane.
   *
   * @param {Shape} targetLane The lane to split.
   * @param {number} count The number of lanes to split the lane into. Must not
   * exceed the number of existing lanes.
   */
  Modeling.prototype.splitLane = function(targetLane, count) {
    this._commandStack.execute('lane.split', {
      shape: targetLane,
      count: count
    });
  };

  /**
   * Turn a process into a collaboration.
   *
   * @return {Root} The root of the collaboration.
   */
  Modeling.prototype.makeCollaboration = function() {

    var collaborationElement = this._create('root', {
      type: 'bpmn:Collaboration'
    });

    var context = {
      newRoot: collaborationElement
    };

    this._commandStack.execute('canvas.updateRoot', context);

    return collaborationElement;
  };

  /**
   * Transform a collaboration into a process.
   *
   * @return {Root} The root of the process.
   */
  Modeling.prototype.makeProcess = function() {

    var processElement = this._create('root', {
      type: 'bpmn:Process'
    });

    var context = {
      newRoot: processElement
    };

    this._commandStack.execute('canvas.updateRoot', context);

    return processElement;
  };

  /**
   * Update the referenced lanes of each flow node.
   *
   * @param {Shape[]} flowNodeShapes The flow nodes to update.
   * @param {Shape[]} laneShapes The lanes.
   */
  Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {

    this._commandStack.execute('lane.updateRefs', {
      flowNodeShapes: flowNodeShapes,
      laneShapes: laneShapes
    });
  };

  /**
   * Claim an ID.
   *
   * @param {string} id The ID to claim.
   * @param {ModdleElement} moddleElement The model element the ID is claimed for.
   */
  Modeling.prototype.claimId = function(id, moddleElement) {
    this._commandStack.execute('id.updateClaim', {
      id: id,
      element: moddleElement,
      claiming: true
    });
  };

  /**
   * Unclaim an ID.
   *
   * @param {string} id The ID to unclaim.
   * @param {ModdleElement} moddleElement The model element the ID is claimed for.
   */
  Modeling.prototype.unclaimId = function(id, moddleElement) {
    this._commandStack.execute('id.updateClaim', {
      id: id,
      element: moddleElement
    });
  };

  /**
   * Set the color(s) of one or many elements.
   *
   * @param {Element[]} elements The elements to set the color(s) for.
   * @param {Colors} colors The color(s) to set.
   */
  Modeling.prototype.setColor = function(elements, colors) {
    if (!elements.length) {
      elements = [ elements ];
    }

    this._commandStack.execute('element.setColor', {
      elements: elements,
      colors: colors
    });
  };

  /**
   * @typedef {import('../core/Types').ElementLike} Element
   * @typedef {import('../core/Types').ConnectionLike} Connection
   *
   * @typedef {import('../util').Point} Point
   *
   * @typedef { {
   *   connectionStart?: Point;
   *   connectionEnd?: Point;
   *   source?: Element;
   *   target?: Element;
   * } } LayoutConnectionHints
   */



  /**
   * 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 {Connection} connection
   * @param {LayoutConnectionHints} [hints]
   *
   * @return {Point[]} The waypoints of the laid out connection.
   */
  BaseLayouter.prototype.layoutConnection = function(connection, hints) {

    hints = hints || {};

    return [
      hints.connectionStart || getMid(hints.source || connection.source),
      hints.connectionEnd || getMid(hints.target || connection.target)
    ];
  };

  /**
   * @typedef {import('../util/Types').Point} Point
   * @typedef {import('../util/Types').Rect} Rect
   */

  var MIN_SEGMENT_LENGTH = 20,
      POINT_ORIENTATION_PADDING = 5;

  var round$2 = 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$2((b.x - a.x) / 2 + a.x),
        ymid = round$2((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$2((b.x - a.x) / 2 + a.x),
        ymid = round$2((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 {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 {direction}:{direction}. A direction for a point is either
   * `h` (horizontal) or `v` (vertical).
   *
   * @return {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 {Rect} source source rectangle
   * @param {Rect} 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 {string[]} [hints.preferredLayouts]
   * @param {Point|boolean} [hints.connectionStart] whether the start changed
   * @param {Point|boolean} [hints.connectionEnd] whether the end changed
   *
   * @return {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 {Rect} source
   * @param {Rect} target
   * @param {Point} [start]
   * @param {Point} [end]
   * @param {Point[]} [waypoints]
   * @param {Object} [hints]
   * @param {string[]} [hints.preferredLayouts] The list of preferred layouts.
   * @param {boolean} [hints.connectionStart]
   * @param {boolean} [hints.connectionEnd]
   *
   * @return {Point[]} The waypoints of the repaired connection.
   */
  function repairConnection(source, target, start, end, waypoints, hints) {

    if (isArray$3(start)) {
      waypoints = start;
      hints = end;

      start = getMid(source);
      end = getMid(target);
    }

    hints = assign$1({ 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]]);
  }

  /**
   * Lay out a straight connection.
   *
   * @param {Rect} source
   * @param {Rect} target
   * @param {Point} start
   * @param {Point} end
   * @param {Object} [hints]
   * @param {string} [hints.preserveDocking]
   *
   * @return {Point[]|null} The waypoints or null if layout isn't possible.
   */
  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 {Rect} moved
   * @param {Rect} other
   * @param {Point} newDocking
   * @param {Point[]} points originalPoints from moved to other
   *
   * @return {Point[]|null} The waypoints of the repaired connection.
   */
  function tryRepairConnectionStart(moved, other, newDocking, points) {
    return _tryRepairConnectionSide(moved, other, newDocking, points);
  }

  /**
   * Repair a connection from end.
   *
   * @param {Rect} moved
   * @param {Rect} other
   * @param {Point} newDocking
   * @param {Point[]} points originalPoints from moved to other
   *
   * @return {Point[]|null} The waypoints of the repaired connection.
   */
  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 {Rect} moved
   * @param {Rect} other
   * @param {Point} newDocking
   * @param {Point[]} points originalPoints from moved to other
   *
   * @return {Point[]} The waypoints of the repaired connection.
   */
  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
   *
   * ```javascript
   * 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 {Point[]} waypoints
   *
   * @return {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;
    }, []);
  }

  /**
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   *
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   *
   * @typedef {import('../../model/Types').Connection} Connection
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('diagram-js/lib/layout/BaseLayouter').LayoutConnectionHints} LayoutConnectionHints
   *
   * @typedef { {
   *   source?: Element;
   *   target?: Element;
   *   waypoints?: Point[];
   *   connectionStart?: Point;
   *   connectionEnd?: Point;
   * } & LayoutConnectionHints } BpmnLayoutConnectionHints
   */

  var ATTACH_ORIENTATION_PADDING = -10,
      BOUNDARY_TO_HOST_THRESHOLD$1 = 40;

  // 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 connect sides clockwise
  var PREFERRED_LAYOUTS_HORIZONTAL = {
    default: [ 'h:h' ],
    fromGateway: [ 'v:h' ],
    toGateway: [ 'h:v' ],
    loop: {
      fromTop: [ 't:r' ],
      fromRight: [ 'r:b' ],
      fromLeft: [ 'l:t' ],
      fromBottom: [ 'b:l' ]
    },
    boundaryLoop: {
      alternateHorizontalSide: 'b',
      alternateVerticalSide: 'l',
      default: 'v'
    },
    messageFlow: [ 'straight', 'v:v' ],
    subProcess: [ 'straight', 'h:h' ],
    isHorizontal: true
  };

  // for vertical layouts, switch h and v and loop counter-clockwise
  var PREFERRED_LAYOUTS_VERTICAL = {
    default: [ 'v:v' ],
    fromGateway: [ 'h:v' ],
    toGateway: [ 'v:h' ],
    loop: {
      fromTop: [ 't:l' ],
      fromRight: [ 'r:t' ],
      fromLeft: [ 'l:b' ],
      fromBottom: [ 'b:r' ]
    },
    boundaryLoop: {
      alternateHorizontalSide: 't',
      alternateVerticalSide: 'r',
      default: 'h'
    },
    messageFlow: [ 'straight', 'h:h' ],
    subProcess: [ 'straight', 'v:v' ],
    isHorizontal: false
  };

  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(elementRegistry) {
    this._elementRegistry = elementRegistry;
  }

  e$2(BpmnLayouter, BaseLayouter);

  /**
   * Returns waypoints of laid out connection.
   *
   * @param {Connection} connection
   * @param {BpmnLayoutConnectionHints} [hints]
   *
   * @return {Point[]}
   */
  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,
        elementRegistry = this._elementRegistry;

    var manhattanOptions,
        updatedWaypoints;

    if (!connectionStart) {
      connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source);
    }

    if (!connectionEnd) {
      connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
    }

    if (is(connection, 'bpmn:Association') ||
        is(connection, 'bpmn:DataAssociation')) {

      if (waypoints && !isCompensationAssociation(source, target)) {
        return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]);
      }
    }

    var layout = isDirectionHorizontal(source, elementRegistry) ? PREFERRED_LAYOUTS_HORIZONTAL : PREFERRED_LAYOUTS_VERTICAL;

    if (is(connection, 'bpmn:MessageFlow')) {
      manhattanOptions = getMessageFlowManhattanOptions(source, target, layout);
    } else if (is(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {

      if (source === target) {
        manhattanOptions = {
          preferredLayouts: getLoopPreferredLayout(source, connection, layout)
        };
      } else if (is(source, 'bpmn:BoundaryEvent')) {
        manhattanOptions = {
          preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd, layout)
        };
      } else if (isExpandedSubProcess$1(source) || isExpandedSubProcess$1(target)) {
        manhattanOptions = {
          preferredLayouts: layout.subProcess,
          preserveDocking: getSubProcessPreserveDocking(source)
        };
      } else if (is(source, 'bpmn:Gateway')) {
        manhattanOptions = {
          preferredLayouts: layout.fromGateway
        };
      } else if (is(target, 'bpmn:Gateway')) {
        manhattanOptions = {
          preferredLayouts: layout.toGateway
        };
      } else {
        manhattanOptions = {
          preferredLayouts: layout.default
        };
      }
    }

    if (manhattanOptions) {
      manhattanOptions = assign$1(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, layout) {
    return {
      preferredLayouts: layout.messageFlow,
      preserveDocking: getMessageFlowPreserveDocking(source, target)
    };
  }

  function getMessageFlowPreserveDocking(source, target) {

    // (1) docking element connected to participant has precedence
    if (is(target, 'bpmn:Participant')) {
      return 'source';
    }

    if (is(source, 'bpmn:Participant')) {
      return 'target';
    }

    // (2) docking element connected to expanded sub-process has precedence
    if (isExpandedSubProcess$1(target)) {
      return 'source';
    }

    if (isExpandedSubProcess$1(source)) {
      return 'target';
    }

    // (3) docking event has precedence
    if (is(target, 'bpmn:Event')) {
      return 'target';
    }

    if (is(source, 'bpmn:Event')) {
      return 'source';
    }

    return null;
  }

  function getSubProcessPreserveDocking(source) {
    return isExpandedSubProcess$1(source) ? 'target' : 'source';
  }

  function getConnectionDocking(point, shape) {
    return point ? (point.original || point) : getMid(shape);
  }

  function isCompensationAssociation(source, target) {
    return is(target, 'bpmn:Activity') &&
      is(source, 'bpmn:BoundaryEvent') &&
      target.businessObject.isForCompensation;
  }

  function isExpandedSubProcess$1(element) {
    return is(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, layout) {
    var waypoints = connection.waypoints;

    var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);

    if (orientation === 'top') {
      return layout.loop.fromTop;
    } else if (orientation === 'right') {
      return layout.loop.fromRight;
    } else if (orientation === 'left') {
      return layout.loop.fromLeft;
    }

    return layout.loop.fromBottom;
  }

  function getBoundaryEventPreferredLayouts(source, target, end, layout) {
    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, layout);
    }

    // source layout
    sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);

    // target layout
    targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);

    return [ sourceLayout + ':' + targetLayout ];
  }

  function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout) {
    var orientation = attachedToSide ? attachOrientation : layout.isHorizontal ? getVerticalOrientation(attachOrientation) : getHorizontalOrientation(attachOrientation),
        sourceLayout = orientationDirectionMapping[ orientation ],
        targetLayout;

    if (attachedToSide) {
      if (isHorizontalOrientation(attachOrientation)) {
        targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : layout.boundaryLoop.alternateHorizontalSide;
      } else {
        targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : layout.boundaryLoop.alternateVerticalSide;
      }
    } else {
      targetLayout = layout.boundaryLoop.default;
    }

    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, isHorizontal) {

    // 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

    var verticalAttachOrientation = getVerticalOrientation(attachOrientation),
        horizontalAttachOrientation = getHorizontalOrientation(attachOrientation),
        verticalTargetOrientation = getVerticalOrientation(targetOrientation),
        horizontalTargetOrientation = getHorizontalOrientation(targetOrientation);

    if (isHorizontal) {

      // same vertical or opposite horizontal orientation
      if (
        isSame(verticalAttachOrientation, verticalTargetOrientation) ||
        isOppositeOrientation(horizontalAttachOrientation, horizontalTargetOrientation)
      ) {
        return orientationDirectionMapping[ verticalAttachOrientation ];
      }
    } else {

      // same horizontal or opposite vertical orientation
      if (
        isSame(horizontalAttachOrientation, horizontalTargetOrientation) ||
        isOppositeOrientation(verticalAttachOrientation, verticalTargetOrientation)
      ) {
        return orientationDirectionMapping[ horizontalAttachOrientation ];
      }
    }

    // fallback
    return orientationDirectionMapping[ isHorizontal ? horizontalAttachOrientation : verticalAttachOrientation ];
  }

  function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) {

    // 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
    // and orientation is same on the counter-axis

    if (isHorizontal) {
      if (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation))) {
        return 'h';
      } else {
        return 'v';
      }
    } else {
      if (isSame(getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation))) {
        return 'v';
      } else {
        return 'h';
      }
    }
  }

  BpmnLayouter.$inject = [ 'elementRegistry' ];

  /**
   * @typedef {import('../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../core/GraphicsFactory').default} GraphicsFactory
   */

  function dockingToPoint(docking) {

    // use the dockings actual point and
    // retain the original docking
    return assign$1({ 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 {ElementRegistry} elementRegistry
   * @param {GraphicsFactory} graphicsFactory
   */
  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,
      LabelSupportModule,
      AttachSupportModule,
      SelectionModule,
      ChangeSupportModule,
      SpaceToolModule
    ],
    bpmnFactory: [ 'type', BpmnFactory ],
    bpmnUpdater: [ 'type', BpmnUpdater ],
    elementFactory: [ 'type', ElementFactory ],
    modeling: [ 'type', Modeling ],
    layouter: [ 'type', BpmnLayouter ],
    connectionDocking: [ 'type', CroppingConnectionDocking ]
  };

  const round$1 = Math.round;

  /**
   * @typedef {import('diagram-js/lib/features/complex-preview/ComplexPreview').default} ComplexPreview
   * @typedef {import('diagram-js/lib/layout/ConnectionDocking').default} ConnectionDocking
   * @typedef {import('../modeling/ElementFactory').default} ElementFactory
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../modeling/BpmnLayouter').default} BpmnLayouter
   * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
   *
   * @typedef {import('../../model/Types').Shape} Shape
   */

  /**
   * A preview for appending.
   *
   * @param {ComplexPreview} complexPreview
   * @param {ConnectionDocking} connectionDocking
   * @param {ElementFactory} elementFactory
   * @param {EventBus} eventBus
   * @param {BpmnLayouter} layouter
   * @param {Rules} rules
   */
  function AppendPreview(complexPreview, connectionDocking, elementFactory, eventBus, layouter, rules) {
    this._complexPreview = complexPreview;
    this._connectionDocking = connectionDocking;
    this._elementFactory = elementFactory;
    this._eventBus = eventBus;
    this._layouter = layouter;
    this._rules = rules;
  }

  /**
   * Create a preview of appending a shape of the given type to the given source.
   *
   * @param {Shape} source
   * @param {string} type
   * @param {Partial<Shape>} options
   */
  AppendPreview.prototype.create = function(source, type, options) {
    const complexPreview = this._complexPreview,
          connectionDocking = this._connectionDocking,
          elementFactory = this._elementFactory,
          eventBus = this._eventBus,
          layouter = this._layouter,
          rules = this._rules;

    const shape = elementFactory.createShape(assign$1({ type }, options));

    const position = eventBus.fire('autoPlace', {
      source,
      shape
    });

    if (!position) {
      return;
    }

    assign$1(shape, {
      x: position.x - round$1(shape.width / 2),
      y: position.y - round$1(shape.height / 2)
    });

    const connectionCreateAllowed = rules.allowed('connection.create', {
      source,
      target: shape,
      hints: {
        targetParent: source.parent
      }
    });

    let connection = null;

    if (connectionCreateAllowed) {
      connection = elementFactory.createConnection(connectionCreateAllowed);

      connection.waypoints = layouter.layoutConnection(connection, {
        source,
        target: shape
      });

      connection.waypoints = connectionDocking.getCroppedWaypoints(connection, source, shape);
    }

    complexPreview.create({
      created: [
        shape,
        connection
      ].filter((element) => !isNil(element))
    });
  };

  AppendPreview.prototype.cleanUp = function() {
    this._complexPreview.cleanUp();
  };

  AppendPreview.$inject = [
    'complexPreview',
    'connectionDocking',
    'elementFactory',
    'eventBus',
    'layouter',
    'rules'
  ];

  var AppendPreviewModule = {
    __depends__: [
      AutoPlaceModule,
      ComplexPreviewModule,
      ModelingModule
    ],
    __init__: [ 'appendPreview' ],
    appendPreview: [ 'type', AppendPreview ]
  };

  var min = Math.min,
      max = 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$1(
      '<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$2(this.autoResize, this);
    this.handlePaste = bind$2(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$1(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$1(content.style, {
      boxSizing: 'border-box',
      width: '100%',
      outline: 'none',
      wordWrap: 'break-word'
    }, contentStyle);

    if (options.centerVertically) {
      assign$1(content.style, {
        position: 'absolute',
        top: '50%',
        transform: 'translate(0, -50%)'
      }, contentStyle);
    }

    content.innerText = value;

    event.bind(content, 'keydown', this.keyHandler);
    event.bind(content, 'mousedown', stopPropagation);
    event.bind(content, 'paste', self.handlePaste);

    if (options.autoResize) {
      event.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$1(
        '<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;

        event.bind(document, 'mousemove', onMouseMove);
        event.bind(document, 'mouseup', onMouseUp);
      };

      var onMouseMove = function(e) {
        preventDefault(e);
        stopPropagation(e);

        var newWidth = min(max(startWidth + e.clientX - startX, minWidth), maxWidth);
        var newHeight = min(max(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);

        event.unbind(document,'mousemove', onMouseMove, false);
        event.unbind(document, 'mouseup', onMouseUp, false);
      };

      event.bind(resizeHandle, 'mousedown', onMouseDown);
    }

    assign$1(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');

    event.unbind(content, 'keydown', this.keyHandler);
    event.unbind(content, 'mousedown', stopPropagation);
    event.unbind(content, 'input', this.autoResize);
    event.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
   * @param {Canvas} canvas the canvas
   */
  function DirectEditing(eventBus, canvas) {

    this._eventBus = eventBus;
    this._canvas = canvas;

    this._providers = [];
    this._textbox = new TextBox({
      container: canvas.getContainer(),
      keyHandler: bind$2(this._handleKey, this),
      resizeHandler: bind$2(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;

    // restoreFocus API is available from diagram-js@15.0.0
    this._canvas.restoreFocus && this._canvas.restoreFocus();
  };


  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 ]
  };

  /**
   * @typedef {import('../../../model/Types').Element} Element
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
   *
   * @typedef {(entry: PopupMenuTarget) => boolean} DifferentTypeValidator
   */

  /**
   * 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 {Element} element
   *
   * @return {DifferentTypeValidator}
   */
  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 = (

        // coherse to <false>
        !!target.triggeredByEvent === !!businessObject.triggeredByEvent
      );

      var isExpandedEqual = (
        target.isExpanded === undefined ||
        target.isExpanded === isExpanded(element)
      );

      return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
    };
  }

  /**
   * @typedef { () => string } LabelGetter
   *
   * @typedef { {
   *   label: string | LabelGetter;
   *   actionName: string;
   *   className: string;
   *   target?: {
   *     type: string;
   *     isExpanded?: boolean;
   *     isInterrupting?: boolean;
   *     triggeredByEvent?: boolean;
   *     cancelActivity?: boolean;
   *     eventDefinitionType?: string;
   *     eventDefinitionAttrs?: Record<string, any>
   *   };
   * } } ReplaceOption
   */

  /**
   * @type {ReplaceOption[]}
   */
  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'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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' }
    //   }
    // }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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: 'Ad-hoc sub-process',
      actionName: 'replace-with-ad-hoc-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:AdHocSubProcess',
        isExpanded: true
      }
    },
    {
      label: 'Sub-process (collapsed)',
      actionName: 'replace-with-collapsed-subprocess',
      className: 'bpmn-icon-subprocess-collapsed',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: false
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var AD_HOC_SUBPROCESS_EXPANDED = [
    {
      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
      }
    },
    {
      label: 'Event sub-process',
      actionName: 'replace-with-event-subprocess',
      className: 'bpmn-icon-event-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        triggeredByEvent: true,
        isExpanded: true
      }
    },
    {
      label: 'Ad-hoc sub-process (collapsed)',
      actionName: 'replace-with-collapsed-ad-hoc-subprocess',
      className: 'bpmn-icon-subprocess-collapsed',
      target: {
        type: 'bpmn:AdHocSubProcess',
        isExpanded: false
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var TRANSACTION = [
    {
      label: 'Transaction',
      actionName: 'replace-with-transaction',
      className: 'bpmn-icon-transaction',
      target: {
        type: 'bpmn:Transaction',
        isExpanded: true
      }
    },
    {
      label: 'Sub-process',
      actionName: 'replace-with-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:SubProcess',
        isExpanded: true
      }
    },
    {
      label: 'Ad-hoc sub-process',
      actionName: 'replace-with-ad-hoc-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:AdHocSubProcess',
        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
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var EVENT_SUB_PROCESS = TRANSACTION;

  /**
   * @type {ReplaceOption[]}
   */
  var TASK = [
    {
      label: 'Task',
      actionName: 'replace-with-task',
      className: 'bpmn-icon-task',
      target: {
        type: 'bpmn:Task'
      }
    },
    {
      label: 'User task',
      actionName: 'replace-with-user-task',
      className: 'bpmn-icon-user',
      target: {
        type: 'bpmn:UserTask'
      }
    },
    {
      label: 'Service task',
      actionName: 'replace-with-service-task',
      className: 'bpmn-icon-service',
      target: {
        type: 'bpmn:ServiceTask'
      }
    },
    {
      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: '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: '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
      }
    },
    {
      label: 'Ad-hoc sub-process (collapsed)',
      actionName: 'replace-with-collapsed-ad-hoc-subprocess',
      className: 'bpmn-icon-subprocess-collapsed',
      target: {
        type: 'bpmn:AdHocSubProcess',
        isExpanded: false
      }
    },
    {
      label: 'Ad-hoc sub-process (expanded)',
      actionName: 'replace-with-ad-hoc-subprocess',
      className: 'bpmn-icon-subprocess-expanded',
      target: {
        type: 'bpmn:AdHocSubProcess',
        isExpanded: true
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var DATA_OBJECT_REFERENCE = [
    {
      label: 'Data store reference',
      actionName: 'replace-with-data-store-reference',
      className: 'bpmn-icon-data-store',
      target: {
        type: 'bpmn:DataStoreReference'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var DATA_STORE_REFERENCE = [
    {
      label: 'Data object reference',
      actionName: 'replace-with-data-object-reference',
      className: 'bpmn-icon-data-object',
      target: {
        type: 'bpmn:DataObjectReference'
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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',
        cancelActivity: true
      }
    },
    {
      label: 'Timer boundary event',
      actionName: 'replace-with-timer-boundary',
      className: 'bpmn-icon-intermediate-event-catch-timer',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Escalation boundary event',
      actionName: 'replace-with-escalation-boundary',
      className: 'bpmn-icon-intermediate-event-catch-escalation',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Conditional boundary event',
      actionName: 'replace-with-conditional-boundary',
      className: 'bpmn-icon-intermediate-event-catch-condition',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Error boundary event',
      actionName: 'replace-with-error-boundary',
      className: 'bpmn-icon-intermediate-event-catch-error',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:ErrorEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Cancel boundary event',
      actionName: 'replace-with-cancel-boundary',
      className: 'bpmn-icon-intermediate-event-catch-cancel',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:CancelEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Signal boundary event',
      actionName: 'replace-with-signal-boundary',
      className: 'bpmn-icon-intermediate-event-catch-signal',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition',
        cancelActivity: true
      }
    },
    {
      label: 'Compensation boundary event',
      actionName: 'replace-with-compensation-boundary',
      className: 'bpmn-icon-intermediate-event-catch-compensation',
      target: {
        type: 'bpmn:BoundaryEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition',
        cancelActivity: true
      }
    },
    {
      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
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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',
        isInterrupting: true
      }
    },
    {
      label: 'Timer start event',
      actionName: 'replace-with-timer-start',
      className: 'bpmn-icon-start-event-timer',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:TimerEventDefinition',
        isInterrupting: true
      }
    },
    {
      label: 'Conditional start event',
      actionName: 'replace-with-conditional-start',
      className: 'bpmn-icon-start-event-condition',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ConditionalEventDefinition',
        isInterrupting: true
      }
    },
    {
      label: 'Signal start event',
      actionName: 'replace-with-signal-start',
      className: 'bpmn-icon-start-event-signal',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:SignalEventDefinition',
        isInterrupting: true
      }
    },
    {
      label: 'Error start event',
      actionName: 'replace-with-error-start',
      className: 'bpmn-icon-start-event-error',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:ErrorEventDefinition',
        isInterrupting: true
      }
    },
    {
      label: 'Escalation start event',
      actionName: 'replace-with-escalation-start',
      className: 'bpmn-icon-start-event-escalation',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:EscalationEventDefinition',
        isInterrupting: true
      }
    },
    {
      label: 'Compensation start event',
      actionName: 'replace-with-compensation-start',
      className: 'bpmn-icon-start-event-compensation',
      target: {
        type: 'bpmn:StartEvent',
        eventDefinitionType: 'bpmn:CompensateEventDefinition',
        isInterrupting: true
      }
    },
    {
      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
      }
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  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'
    }
  ];

  /**
   * @type {ReplaceOption[]}
   */
  var PARTICIPANT = [
    {
      label: 'Expanded pool/participant',
      actionName: 'replace-with-expanded-pool',
      className: 'bpmn-icon-participant',
      target: {
        type: 'bpmn:Participant',
        isExpanded: true
      }
    },
    {
      label: function(element) {
        var label = 'Empty pool/participant';

        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
      }
    }
  ];

  /**
   * @type {{ [key: string]: ReplaceOption[]}}
   */
  var TYPED_EVENT = {
    'bpmn:MessageEventDefinition': [
      {
        label: 'Message start event',
        actionName: 'replace-with-message-start',
        className: 'bpmn-icon-start-event-message',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:MessageEventDefinition'
        }
      },
      {
        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: 'Message end event',
        actionName: 'replace-with-message-end',
        className: 'bpmn-icon-end-event-message',
        target: {
          type: 'bpmn:EndEvent',
          eventDefinitionType: 'bpmn:MessageEventDefinition'
        }
      }
    ],
    'bpmn:TimerEventDefinition': [
      {
        label: 'Timer start event',
        actionName: 'replace-with-timer-start',
        className: 'bpmn-icon-start-event-timer',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:TimerEventDefinition'
        }
      },
      {
        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'
        }
      }
    ],
    'bpmn:ConditionalEventDefinition': [
      {
        label: 'Conditional start event',
        actionName: 'replace-with-conditional-start',
        className: 'bpmn-icon-start-event-condition',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:ConditionalEventDefinition'
        }
      },
      {
        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'
        }
      }
    ],
    'bpmn:SignalEventDefinition': [
      {
        label: 'Signal start event',
        actionName: 'replace-with-signal-start',
        className: 'bpmn-icon-start-event-signal',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:SignalEventDefinition'
        }
      },
      {
        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'
        }
      },
      {
        label: 'Signal end event',
        actionName: 'replace-with-signal-end',
        className: 'bpmn-icon-end-event-signal',
        target: {
          type: 'bpmn:EndEvent',
          eventDefinitionType: 'bpmn:SignalEventDefinition'
        }
      }
    ],
    'bpmn:ErrorEventDefinition': [
      {
        label: 'Error start event',
        actionName: 'replace-with-error-start',
        className: 'bpmn-icon-start-event-error',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:ErrorEventDefinition'
        }
      },
      {
        label: 'Error end event',
        actionName: 'replace-with-error-end',
        className: 'bpmn-icon-end-event-error',
        target: {
          type: 'bpmn:EndEvent',
          eventDefinitionType: 'bpmn:ErrorEventDefinition'
        }
      }
    ],
    'bpmn:EscalationEventDefinition': [
      {
        label: 'Escalation start event',
        actionName: 'replace-with-escalation-start',
        className: 'bpmn-icon-start-event-escalation',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:EscalationEventDefinition'
        }
      },
      {
        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: 'Escalation end event',
        actionName: 'replace-with-escalation-end',
        className: 'bpmn-icon-end-event-escalation',
        target: {
          type: 'bpmn:EndEvent',
          eventDefinitionType: 'bpmn:EscalationEventDefinition'
        }
      }
    ],
    'bpmn:CompensateEventDefinition': [
      {
        label: 'Compensation start event',
        actionName: 'replace-with-compensation-start',
        className: 'bpmn-icon-start-event-compensation',
        target: {
          type: 'bpmn:StartEvent',
          eventDefinitionType: 'bpmn:CompensateEventDefinition'
        }
      },
      {
        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: 'Compensation end event',
        actionName: 'replace-with-compensation-end',
        className: 'bpmn-icon-end-event-compensation',
        target: {
          type: 'bpmn:EndEvent',
          eventDefinitionType: 'bpmn:CompensateEventDefinition'
        }
      }
    ]
  };

  var Icons = {
    'start-event-non-interrupting': `
  <svg viewBox="0 0 2048 2048" xmlns="http://www.w3.org/2000/svg">
    <g transform="translate(0 995.64)">
      <path d="m1899 28.357c21.545 567.43-598.38 1023.5-1133.6 835.92-548.09-147.21-801.57-873.95-463.59-1330 302.62-480.3 1071.7-507.54 1407.6-49.847 122.14 153.12 190.07 348.07 189.59 543.91z" fill="none" stroke="currentColor" stroke-dasharray="418.310422, 361.2328165" stroke-linecap="round" stroke-width="100"/>
    </g>
  </svg>`,
    'intermediate-event-non-interrupting': `
  <svg viewBox="0 0 2048 2048" xmlns="http://www.w3.org/2000/svg">
     <g transform="translate(0 995.64)" fill="none" stroke="currentColor" stroke-linecap="round">
        <circle cx="1024" cy="28.357" r="875" stroke-dasharray="418.310422, 361.2328165" stroke-width="100"/>
        <circle cx="1024" cy="28.357" r="685" stroke-dasharray="348.31044857,261.23283643" stroke-dashoffset="500" stroke-width="100"/>
     </g>
  </svg>`
  };

  /**
   * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../replace/BpmnReplace').default} BpmnReplace
   * @typedef {import('diagram-js/lib/features/Rules').default} Rules
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   * @typedef {import('../copy-paste/ModdleCopy').default} ModdleCopy
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Moddle} Moddle
   *
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntryAction} PopupMenuEntryAction
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuHeaderEntries} PopupMenuHeaderEntries
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
   *
   * @typedef {import('./ReplaceOptions').ReplaceOption} ReplaceOption
   */

  /**
   * A BPMN-specific popup menu provider.
   *
   * @implements {PopupMenuProvider}
   *
   * @param {BpmnFactory} bpmnFactory
   * @param {PopupMenu} popupMenu
   * @param {Modeling} modeling
   * @param {Moddle} moddle
   * @param {BpmnReplace} bpmnReplace
   * @param {Rules} rules
   * @param {Translate} translate
   * @param {ModdleCopy} moddleCopy
   */
  function ReplaceMenuProvider(
      bpmnFactory, popupMenu, modeling, moddle,
      bpmnReplace, rules, translate, moddleCopy) {

    this._bpmnFactory = bpmnFactory;
    this._popupMenu = popupMenu;
    this._modeling = modeling;
    this._moddle = moddle;
    this._bpmnReplace = bpmnReplace;
    this._rules = rules;
    this._translate = translate;
    this._moddleCopy = moddleCopy;

    this._register();
  }

  ReplaceMenuProvider.$inject = [
    'bpmnFactory',
    'popupMenu',
    'modeling',
    'moddle',
    'bpmnReplace',
    'rules',
    'translate',
    'moddleCopy'
  ];

  ReplaceMenuProvider.prototype._register = function() {
    this._popupMenu.registerProvider('bpmn-replace', this);
  };

  /**
   * @param {PopupMenuTarget} target
   *
   * @return {PopupMenuEntries}
   */
  ReplaceMenuProvider.prototype.getPopupMenuEntries = function(target) {

    var businessObject = target.businessObject;

    var rules = this._rules;

    var sameTypeEventOptions = [],
        eventDefinitionType;

    var filteredReplaceOptions = [];

    if (isArray$3(target) || !rules.allowed('shape.replace', { element: target })) {
      return {};
    }

    var differentType = isDifferentType(target);

    if (is(businessObject, 'bpmn:DataObjectReference')) {
      return this._createEntries(target, DATA_OBJECT_REFERENCE);
    }

    if (is(businessObject, 'bpmn:DataStoreReference') && !is(target.parent, 'bpmn:Collaboration')) {
      return this._createEntries(target, DATA_STORE_REFERENCE);
    }

    // typed start, intermediate, and end events
    if (is(businessObject, 'bpmn:Event') && !is(businessObject, 'bpmn:BoundaryEvent')) {
      eventDefinitionType = businessObject.get('eventDefinitions')[0]?.$type;

      sameTypeEventOptions = TYPED_EVENT[eventDefinitionType] || [];

      if (!isEventSubProcess(businessObject.$parent) && is(businessObject.$parent, 'bpmn:SubProcess')) {
        sameTypeEventOptions = filter(sameTypeEventOptions, function(option) {
          return option.target.type !== 'bpmn:StartEvent';
        });
      }
    }

    // start events outside sub processes
    if (is(businessObject, 'bpmn:StartEvent') && !is(businessObject.$parent, 'bpmn:SubProcess')) {

      filteredReplaceOptions = filter(
        START_EVENT.concat(sameTypeEventOptions),
        differentType
      );

      return this._createEntries(target, filteredReplaceOptions);
    }

    // expanded/collapsed pools
    if (is(businessObject, 'bpmn:Participant')) {

      filteredReplaceOptions = filter(PARTICIPANT, function(replaceOption) {
        return isExpanded(target) !== replaceOption.target.isExpanded;
      });

      return this._createEntries(target, filteredReplaceOptions);
    }

    // start events inside event sub processes
    if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
      filteredReplaceOptions = filter(
        EVENT_SUB_PROCESS_START_EVENT.concat(sameTypeEventOptions), function(replaceOption) {

          var target = replaceOption.target;

          var isInterrupting = target.isInterrupting !== false;

          var isInterruptingEqual = businessObject.isInterrupting === isInterrupting;

          // filters elements which types and event definition are equal but have have different interrupting types
          return differentType(replaceOption) || !differentType(replaceOption) && !isInterruptingEqual;

        }
      );

      return this._createEntries(target, filteredReplaceOptions);
    }

    // start events inside sub processes
    if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
        && is(businessObject.$parent, 'bpmn:SubProcess')) {
      filteredReplaceOptions = filter(
        START_EVENT_SUB_PROCESS.concat(sameTypeEventOptions),
        differentType
      );

      return this._createEntries(target, filteredReplaceOptions);
    }

    // end events
    if (is(businessObject, 'bpmn:EndEvent')) {

      filteredReplaceOptions = filter(END_EVENT.concat(sameTypeEventOptions), function(replaceOption) {
        var target = replaceOption.target;

        // hide cancel end events outside transactions
        if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) {
          return false;
        }

        return differentType(replaceOption);
      });

      return this._createEntries(target, filteredReplaceOptions);
    }

    // boundary events
    if (is(businessObject, 'bpmn:BoundaryEvent')) {

      filteredReplaceOptions = filter(BOUNDARY_EVENT, function(replaceOption) {

        var target = replaceOption.target;

        if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
           !is(businessObject.attachedToRef, 'bpmn:Transaction')) {
          return false;
        }
        var cancelActivity = target.cancelActivity !== false;

        var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;

        return differentType(replaceOption) || !differentType(replaceOption) && !isCancelActivityEqual;
      });

      return this._createEntries(target, filteredReplaceOptions);
    }

    // intermediate events
    if (is(businessObject, 'bpmn:IntermediateCatchEvent') ||
        is(businessObject, 'bpmn:IntermediateThrowEvent')) {

      filteredReplaceOptions = filter(
        INTERMEDIATE_EVENT.concat(sameTypeEventOptions),
        differentType
      );

      return this._createEntries(target, filteredReplaceOptions);
    }

    // gateways
    if (is(businessObject, 'bpmn:Gateway')) {

      filteredReplaceOptions = filter(GATEWAY, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    // transactions
    if (is(businessObject, 'bpmn:Transaction')) {

      filteredReplaceOptions = filter(TRANSACTION, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    // expanded event sub processes
    if (isEventSubProcess(businessObject) && isExpanded(target)) {

      filteredReplaceOptions = filter(EVENT_SUB_PROCESS, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    // expanded ad hoc sub processes
    if (is(businessObject, 'bpmn:AdHocSubProcess') && isExpanded(target)) {

      filteredReplaceOptions = filter(AD_HOC_SUBPROCESS_EXPANDED, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    // expanded sub processes
    if (is(businessObject, 'bpmn:SubProcess') && isExpanded(target)) {

      filteredReplaceOptions = filter(SUBPROCESS_EXPANDED, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    // collapsed sub process or collapsed ad hoc sub process
    if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(target)) {

      filteredReplaceOptions = filter(TASK, function(replaceOption) {

        var isTargetSameType = replaceOption.target.type === target.type;
        var isTargetExpanded = replaceOption.target.isExpanded === true;

        // Collapsed subprocess cannot be replaced with itself or expanded subprocess of different type.
        return isTargetSameType === isTargetExpanded;
      });

      return this._createEntries(target, filteredReplaceOptions);
    }

    // sequence flows
    if (is(businessObject, 'bpmn:SequenceFlow')) {
      return this._createSequenceFlowEntries(target, SEQUENCE_FLOW);
    }

    // flow nodes
    if (is(businessObject, 'bpmn:FlowNode')) {
      filteredReplaceOptions = filter(TASK, differentType);

      return this._createEntries(target, filteredReplaceOptions);
    }

    return {};
  };

  /**
   * @param {PopupMenuTarget} target
   *
   * @return {PopupMenuHeaderEntries}
   */
  ReplaceMenuProvider.prototype.getPopupMenuHeaderEntries = function(target) {

    var headerEntries = {};

    if (is(target, 'bpmn:Activity') && !isEventSubProcess(target)) {
      headerEntries = {
        ...headerEntries,
        ...this._getLoopCharacteristicsHeaderEntries(target)
      };
    }

    if (is(target, 'bpmn:DataObjectReference')) {
      headerEntries = {
        ...headerEntries,
        ...this._getCollectionHeaderEntries(target)
      };
    }

    if (is(target, 'bpmn:Participant')) {
      headerEntries = {
        ...headerEntries,
        ...this._getParticipantMultiplicityHeaderEntries(target)
      };
    }

    if (canBeNonInterrupting(target)) {
      headerEntries = {
        ...headerEntries,
        ...this._getNonInterruptingHeaderEntries(target)
      };
    }

    return headerEntries;
  };


  /**
   * Create popup menu entries for the given target.
   *
   * @param  {PopupMenuTarget} target
   * @param  {ReplaceOption[]} replaceOptions
   *
   * @return {PopupMenuEntries}
   */
  ReplaceMenuProvider.prototype._createEntries = function(target, replaceOptions) {
    var entries = {};

    var self = this;

    forEach$1(replaceOptions, function(replaceOption) {
      entries[ replaceOption.actionName ] = self._createEntry(replaceOption, target);
    });

    return entries;
  };

  /**
   * Creates popup menu entries for the given sequence flow.
   *
   * @param  {PopupMenuTarget} target
   * @param  {ReplaceOption[]} replaceOptions
   *
   * @return {PopupMenuEntries}
   */
  ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(target, replaceOptions) {

    var businessObject = getBusinessObject(target);

    var entries = {};

    var modeling = this._modeling,
        moddle = this._moddle;

    var self = this;

    forEach$1(replaceOptions, function(replaceOption) {

      switch (replaceOption.actionName) {
      case 'replace-with-default-flow':
        if (businessObject.sourceRef.default !== businessObject &&
              (is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
               is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
               is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
               is(businessObject.sourceRef, 'bpmn:Activity'))) {

          entries = {
            ...entries,
            [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
              modeling.updateProperties(target.source, { default: businessObject });
            })
          };
        }
        break;
      case 'replace-with-conditional-flow':
        if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) {

          entries = {
            ...entries,
            [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
              var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });

              modeling.updateProperties(target, { conditionExpression: conditionExpression });
            })
          };
        }
        break;
      default:

        // conditional flow -> sequence flow
        if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
          entries = {
            ...entries,
            [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
              modeling.updateProperties(target, { conditionExpression: undefined });
            })
          };
        }

        // default flow -> sequence flow
        if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
             is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
             is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
             is(businessObject.sourceRef, 'bpmn:Activity')) &&
             businessObject.sourceRef.default === businessObject) {
          entries = {
            ...entries,
            [ replaceOption.actionName ]: self._createEntry(replaceOption, target, function() {
              modeling.updateProperties(target.source, { default: undefined });
            })
          };
        }
      }
    });

    return entries;
  };

  /**
   * Create a popup menu entry for the given replace option.
   *
   * @param  {ReplaceOption} replaceOption
   * @param  {PopupMenuTarget} target
   * @param  {PopupMenuEntryAction} [action]
   *
   * @return {PopupMenuEntry}
   */
  ReplaceMenuProvider.prototype._createEntry = function(replaceOption, target, action) {
    var translate = this._translate;
    var replaceElement = this._bpmnReplace.replaceElement;

    var replaceAction = function() {
      return replaceElement(target, replaceOption.target);
    };

    var label = replaceOption.label;
    if (label && typeof label === 'function') {
      label = label(target);
    }

    action = action || replaceAction;

    return {
      label: translate(label),
      className: replaceOption.className,
      action: action
    };
  };

  /**
   * Get popup menu header entries for the loop characteristics of the given BPMN element.
   *
   * @param  {PopupMenuTarget} target
   *
   * @return {PopupMenuHeaderEntries}
   */
  ReplaceMenuProvider.prototype._getLoopCharacteristicsHeaderEntries = function(target) {

    var self = this;
    var translate = this._translate;

    function toggleLoopCharacteristics(event, entry) {
      if (entry.active) {
        self._modeling.updateProperties(target, { loopCharacteristics: undefined });

        return;
      }

      var loopCharacteristics = target.businessObject.get('loopCharacteristics');

      if (loopCharacteristics && is(loopCharacteristics, entry.options.loopCharacteristics)) {
        self._modeling.updateModdleProperties(target, loopCharacteristics, { isSequential: entry.options.isSequential });
      } else {
        loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics, {
          isSequential: entry.options.isSequential
        });

        self._modeling.updateProperties(target, { loopCharacteristics: loopCharacteristics });
      }
    }

    var businessObject = getBusinessObject(target),
        loopCharacteristics = businessObject.loopCharacteristics;

    var isSequential,
        isLoop,
        isParallel;

    if (loopCharacteristics) {
      isSequential = loopCharacteristics.isSequential;
      isLoop = loopCharacteristics.isSequential === undefined;
      isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
    }


    return {
      'toggle-parallel-mi' : {
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Parallel multi-instance'),
        active: isParallel,
        action: toggleLoopCharacteristics,
        options: {
          loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
          isSequential: false
        }
      },
      'toggle-sequential-mi': {
        className: 'bpmn-icon-sequential-mi-marker',
        title: translate('Sequential multi-instance'),
        active: isSequential,
        action: toggleLoopCharacteristics,
        options: {
          loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
          isSequential: true
        }
      },
      'toggle-loop': {
        className: 'bpmn-icon-loop-marker',
        title: translate('Loop'),
        active: isLoop,
        action: toggleLoopCharacteristics,
        options: {
          loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
        }
      }
    };
  };

  /**
   * Get popup menu header entries for the collection property of the given BPMN element.
   *
   * @param  {PopupMenuTarget} target
   *
   * @return {PopupMenuHeaderEntries}
   */
  ReplaceMenuProvider.prototype._getCollectionHeaderEntries = function(target) {

    var self = this;
    var translate = this._translate;

    var dataObject = target.businessObject.dataObjectRef;

    if (!dataObject) {
      return {};
    }

    function toggleIsCollection(event, entry) {
      self._modeling.updateModdleProperties(
        target,
        dataObject,
        { isCollection: !entry.active });
    }

    var isCollection = dataObject.isCollection;

    return {
      'toggle-is-collection': {
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Collection'),
        active: isCollection,
        action: toggleIsCollection,
      }
    };
  };

  /**
   * Get popup menu header entries for the participant multiplicity property of the given BPMN element.
   *
   * @param  {PopupMenuTarget} target
   *
   * @return {PopupMenuHeaderEntries}
   */
  ReplaceMenuProvider.prototype._getParticipantMultiplicityHeaderEntries = function(target) {

    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(
        target,
        { participantMultiplicity: participantMultiplicity });
    }

    var participantMultiplicity = target.businessObject.participantMultiplicity;

    return {
      'toggle-participant-multiplicity': {
        className: 'bpmn-icon-parallel-mi-marker',
        title: translate('Participant multiplicity'),
        active: !!participantMultiplicity,
        action: toggleParticipantMultiplicity,
      }
    };
  };

  ReplaceMenuProvider.prototype._getNonInterruptingHeaderEntries = function(element) {
    const translate = this._translate;
    const businessObject = getBusinessObject(element);
    const self = this;

    const interruptingProperty = getInterruptingProperty(element);

    const icon = is(element, 'bpmn:BoundaryEvent') ? Icons['intermediate-event-non-interrupting'] : Icons['start-event-non-interrupting'];

    const isNonInterrupting = !businessObject[interruptingProperty];

    return {
      'toggle-non-interrupting': {
        imageHtml: icon,
        title: translate('Toggle non-interrupting'),
        active: isNonInterrupting,
        action: function() {
          self._modeling.updateProperties(element, {
            [interruptingProperty]: !!isNonInterrupting
          });
        }
      }
    };
  };

  var PopupMenuModule = {
    __depends__: [
      PopupMenuModule$1,
      ReplaceModule,
      AutoPlaceModule
    ],
    __init__: [
      'replaceMenuProvider'
    ],
    replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../modeling/ElementFactory').default} ElementFactory
   * @typedef {import('../append-preview/AppendPreview').default} AppendPreview
   * @typedef {import('diagram-js/lib/features/connect/Connect').default} Connect
   * @typedef {import('diagram-js/lib/features/create/Create').default} Create
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
   * @typedef {import('diagram-js/lib/features/canvas/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').ModdleElement} ModdleElement
   *
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default<Element>} BaseContextPadProvider
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntries} ContextPadEntries
   * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntry} ContextPadEntry
   *
   * @typedef { { autoPlace?: boolean; } } ContextPadConfig
   */

  /**
   * BPMN-specific context pad provider.
   *
   * @implements {BaseContextPadProvider}
   *
   * @param {ContextPadConfig} config
   * @param {Injector} injector
   * @param {EventBus} eventBus
   * @param {ContextPad} contextPad
   * @param {Modeling} modeling
   * @param {ElementFactory} elementFactory
   * @param {Connect} connect
   * @param {Create} create
   * @param {PopupMenu} popupMenu
   * @param {Canvas} canvas
   * @param {Rules} rules
   * @param {Translate} translate
   * @param {AppendPreview} appendPreview
   */
  function ContextPadProvider(
      config, injector, eventBus,
      contextPad, modeling, elementFactory,
      connect, create, popupMenu,
      canvas, rules, translate, appendPreview) {

    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;
    this._eventBus = eventBus;
    this._appendPreview = appendPreview;

    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);
      }
    });

    eventBus.on('contextPad.close', function() {
      appendPreview.cleanUp();
    });
  }

  ContextPadProvider.$inject = [
    'config.contextPad',
    'injector',
    'eventBus',
    'contextPad',
    'modeling',
    'elementFactory',
    'connect',
    'create',
    'popupMenu',
    'canvas',
    'rules',
    'translate',
    'appendPreview'
  ];

  /**
   * @param {Element[]} elements
   *
   * @return {ContextPadEntries}
   */
  ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
    var modeling = this._modeling;

    var actions = {};

    if (this._isDeleteAllowed(elements)) {
      assign$1(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: this._translate('Delete'),
          action: {
            click: function(event, elements) {
              modeling.removeElements(elements.slice());
            }
          }
        }
      });
    }

    return actions;
  };

  /**
   * @param {Element[]} elements
   *
   * @return {boolean}
   */
  ContextPadProvider.prototype._isDeleteAllowed = function(elements) {

    var baseAllowed = this._rules.allowed('elements.delete', {
      elements: elements
    });

    if (isArray$3(baseAllowed)) {
      return every(elements, el => baseAllowed.includes(el));
    }

    return baseAllowed;
  };

  /**
   * @param {Element} element
   *
   * @return {ContextPadEntries}
   */
  ContextPadProvider.prototype.getContextPadEntries = function(element) {
    var contextPad = this._contextPad,
        modeling = this._modeling,
        elementFactory = this._elementFactory,
        connect = this._connect,
        create = this._create,
        popupMenu = this._popupMenu,
        autoPlace = this._autoPlace,
        translate = this._translate,
        appendPreview = this._appendPreview;

    var actions = {};

    if (element.type === 'label') {
      if (this._isDeleteAllowed([ element ])) {
        assign$1(actions, deleteAction());
      }

      return actions;
    }

    var businessObject = element.businessObject;

    function startConnect(event, element) {
      connect.start(event, element);
    }

    function removeElement(e, element) {
      modeling.removeElements([ element ]);
    }

    function deleteAction() {
      return {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: translate('Delete'),
          action: {
            click: removeElement
          }
        }
      };
    }

    function getReplaceMenuPosition(element) {

      var Y_OFFSET = 5;

      var pad = contextPad.getPad(element).html;

      var padRect = pad.getBoundingClientRect();

      var pos = {
        x: padRect.left,
        y: padRect.bottom + Y_OFFSET
      };

      return pos;
    }

    /**
     * Create an append action.
     *
     * @param {string} type
     * @param {string} className
     * @param {string} title
     * @param {Object} [options]
     *
     * @return {ContextPadEntry}
     */
    function appendAction(type, className, title, options) {

      function appendStart(event, element) {

        var shape = elementFactory.createShape(assign$1({ type: type }, options));

        create.start(event, shape, {
          source: element
        });
      }

      var append = autoPlace ? function(_, element) {
        var shape = elementFactory.createShape(assign$1({ type: type }, options));

        autoPlace.append(element, shape);
      } : appendStart;

      var previewAppend = autoPlace ? function(_, element) {

        // mouseover
        appendPreview.create(element, type, options);

        return () => {

          // mouseout
          appendPreview.cleanUp();
        };
      } : null;

      return {
        group: 'model',
        className: className,
        title: title,
        action: {
          dragstart: appendStart,
          click: append,
          hover: previewAppend
        }
      };
    }

    function splitLaneHandler(count) {

      return function(_, 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$1(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 (isHorizontal$3(element) ? element.height >= 120 : element.width >= 120) {
          assign$1(actions, {
            'lane-divide-two': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-two',
              title: translate('Divide into two lanes'),
              action: {
                click: splitLaneHandler(2)
              }
            }
          });
        }

        if (isHorizontal$3(element) ? element.height >= 180 : element.width >= 180) {
          assign$1(actions, {
            'lane-divide-three': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-three',
              title: translate('Divide into three lanes'),
              action: {
                click: splitLaneHandler(3)
              }
            }
          });
        }
      }

      assign$1(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(businessObject, 'bpmn:FlowNode')) {

      if (is(businessObject, 'bpmn:EventBasedGateway')) {

        assign$1(actions, {
          'append.receive-task': appendAction(
            'bpmn:ReceiveTask',
            'bpmn-icon-receive-task',
            translate('Append receive task')
          ),
          'append.message-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-message',
            translate('Append message intermediate catch event'),
            { eventDefinitionType: 'bpmn:MessageEventDefinition' }
          ),
          'append.timer-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-timer',
            translate('Append timer intermediate catch event'),
            { eventDefinitionType: 'bpmn:TimerEventDefinition' }
          ),
          'append.condition-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-condition',
            translate('Append conditional intermediate catch event'),
            { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
          ),
          'append.signal-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-signal',
            translate('Append signal intermediate catch event'),
            { eventDefinitionType: 'bpmn:SignalEventDefinition' }
          )
        });
      } else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {

        assign$1(actions, {
          'append.compensation-activity':
              appendAction(
                'bpmn:Task',
                'bpmn-icon-task',
                translate('Append compensation activity'),
                {
                  isForCompensation: true
                }
              )
        });
      } else if (!is(businessObject, 'bpmn:EndEvent') &&
          !businessObject.isForCompensation &&
          !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
          !isEventSubProcess(businessObject)) {

        assign$1(actions, {
          'append.end-event': appendAction(
            'bpmn:EndEvent',
            'bpmn-icon-end-event-none',
            translate('Append end event')
          ),
          '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$1(actions, {
        'replace': {
          group: 'edit',
          className: 'bpmn-icon-screw-wrench',
          title: translate('Change element'),
          action: {
            click: function(event, element) {

              var position = assign$1(getReplaceMenuPosition(element), {
                cursor: { x: event.x, y: event.y }
              });

              popupMenu.open(element, 'bpmn-replace', position, {
                title: translate('Change element'),
                width: 300,
                search: true
              });
            }
          }
        }
      });
    }

    if (is(businessObject, 'bpmn:SequenceFlow')) {
      assign$1(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation',
          translate('Add text annotation')
        )
      });
    }

    if (is(businessObject, 'bpmn:MessageFlow')) {
      assign$1(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation',
          translate('Add text annotation')
        )
      });
    }

    if (
      isAny(businessObject, [
        'bpmn:FlowNode',
        'bpmn:InteractionNode',
        'bpmn:DataObjectReference',
        'bpmn:DataStoreReference',
      ])
    ) {
      assign$1(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation',
          translate('Add text annotation')
        ),
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect to other element'),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }

    if (is(businessObject, 'bpmn:TextAnnotation')) {
      assign$1(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$1(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using data input association'),
          action: {
            click: startConnect,
            dragstart: startConnect
          }
        }
      });
    }

    if (is(businessObject, 'bpmn:Group')) {
      assign$1(actions, {
        'append.text-annotation': appendAction(
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation',
          translate('Add text annotation')
        )
      });
    }

    if (this._isDeleteAllowed([ element ])) {
      assign$1(actions, deleteAction());
    }

    return actions;
  };


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

  /**
   * @param {ModdleElement} businessObject
   * @param {string} type
   * @param {string} eventDefinitionType
   *
   * @return {boolean}
   */
  function isEventType(businessObject, type, eventDefinitionType) {

    var isType = businessObject.$instanceOf(type);
    var isDefinition = false;

    var definitions = businessObject.eventDefinitions || [];
    forEach$1(definitions, function(def) {
      if (def.$type === eventDefinitionType) {
        isDefinition = true;
      }
    });

    return isType && isDefinition;
  }

  var ContextPadModule = {
    __depends__: [
      AppendPreviewModule,
      DirectEditingModule,
      ContextPadModule$1,
      SelectionModule,
      ConnectModule,
      CreateModule,
      PopupMenuModule
    ],
    __init__: [ 'contextPadProvider' ],
    contextPadProvider: [ 'type', ContextPadProvider ]
  };

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').Dimension} Dimension
   * @typedef {import('../../util/Types').Rect} Rect
   *
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   *
   * @typedef { {
   *   min: number;
   *   max: number;
   * } } Range
   *
   * @typedef { {
   *   elements: Element[];
   *   range: Range;
   * } } Group
   */

  var AXIS_DIMENSIONS = {
    horizontal: [ 'x', 'width' ],
    vertical: [ 'y', 'height' ]
  };

  var THRESHOLD = 5;


  /**
   * Groups and filters elements and then trigger even distribution.
   *
   * @param {Modeling} modeling
   * @param {Rules} rules
   */
  function DistributeElements(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.$inject = [ 'modeling', 'rules' ];


  /**
   * Registers filter functions that allow external parties to filter
   * out certain elements.
   *
   * @param {(distributableElements: Element[], axis: Axis, dimension: Dimension) => Element[]} filterFn
   */
  DistributeElements.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 {Element[]} elements
   * @param {string} orientation
   */
  DistributeElements.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 {Element[]} elements
   *
   * @return {Element[]}
   */
  DistributeElements.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
   *
   * ```javascript
   *   const groups = [
   *     {
   *       range: { min: 100, max: 200 },
   *       elements: [ { id: 'shape1', ... } ]
   *     }
   *   ]
   * ```
   *
   * @param {Element[]} elements
   *
   * @return {Group[]}
   */
  DistributeElements.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 {'horizontal' | 'vertical'} direction 'horizontal' or 'vertical'
   */
  DistributeElements.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 {Range} rangeA
   * @param {Range} rangeB
   *
   * @return {boolean}
   */
  DistributeElements.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 {Element} element
   *
   * @return {Range}
   */
  DistributeElements.prototype._findRange = function(element) {
    var axis = element[this._axis],
        dimension = element[this._dimension];

    return {
      min: axis + THRESHOLD,
      max: axis + dimension - THRESHOLD
    };
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var DistributeElementsModule$1 = {
    __init__: [ 'distributeElements' ],
    distributeElements: [ 'type', DistributeElements ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * Registers element exclude filters for elements that currently do not support
   * distribution.
   *
   * @param {EventBus} eventBus
   */
  function BpmnDistributeElements(eventBus) {
    RuleProvider.call(this, eventBus);
  }

  BpmnDistributeElements.$inject = [ 'eventBus' ];

  e$2(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: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
                <polyline points="450 400 450 150 1350 150 1350 400" style="fill:none;stroke:currentColor;stroke-width:100;stroke-linejoin:round;"/>
                <rect x="150" y="450" width="600" height="1200" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
                <rect x="1050" y="450" width="600" height="800" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
              </svg>`,
    vertical: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800">
              <polyline points="400 1350 150 1350 150 450 400 450" style="fill:none;stroke:currentColor;stroke-width:100;stroke-linejoin:round;"/>
              <rect x="450" y="150" width="1200" height="600" rx="1" style="fill:none;stroke:currentColor;stroke-width:100;"></rect>
              <rect x="450" y="1050" width="800" height="600" rx="1" style="fill:currentColor;stroke:currentColor;stroke-width:100;opacity:.5;"></rect>
            </svg>`
  };

  /**
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
   * @typedef {import('./BpmnDistributeElements').default} DistributeElements
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
   *
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider
   * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget
   */

  var LOW_PRIORITY$6 = 900;

  /**
   * A provider for the distribute elements popup menu.
   *
   * @implements {PopupMenuProvider}
   *
   * @param {PopupMenu} popupMenu
   * @param {DistributeElements} distributeElements
   * @param {Translate} translate
   * @param {Rules} rules
   */
  function DistributeElementsMenuProvider(
      popupMenu, distributeElements, translate, rules) {
    this._distributeElements = distributeElements;
    this._translate = translate;
    this._popupMenu = popupMenu;
    this._rules = rules;

    popupMenu.registerProvider('align-elements', LOW_PRIORITY$6, this);
  }

  DistributeElementsMenuProvider.$inject = [
    'popupMenu',
    'distributeElements',
    'translate',
    'rules'
  ];

  /**
   * @param {PopupMenuTarget} target
   *
   * @return {PopupMenuEntries}
   */
  DistributeElementsMenuProvider.prototype.getPopupMenuEntries = function(target) {
    var entries = {};

    if (this._isAllowed(target)) {
      assign$1(entries, this._getEntries(target));
    }

    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',
        imageHtml: icons['horizontal'],
        action: function(event, entry) {
          distributeElements.trigger(elements, 'horizontal');
          popupMenu.close();
        }
      },
      'distribute-elements-vertical': {
        group: 'distribute',
        title: translate('Distribute elements vertically'),
        imageHtml: 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 ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  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
   *
   * ```javascript
   * 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 identifiers of all currently registered editor actions
   *
   * @return {string[]}
   */
  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);
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var EditorActionsModule$1 = {
    __init__: [ 'editorActions' ],
    editorActions: [ 'type', EditorActions ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   */

  /**
   * Registers and executes BPMN specific editor actions.
   *
   * @param {Injector} injector
   */
  function BpmnEditorActions(injector) {
    injector.invoke(EditorActions, this);
  }

  e$2(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);
    var contextPad = injector.get('contextPad', 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(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(rootElement, 'bpmn:Collaboration')) {
          elements = elementRegistry.filter(function(element) {
            return is(element.parent, 'bpmn:Collaboration');
          });
        } else {
          elements = elementRegistry.filter(function(element) {
            return element !== rootElement && !is(element.parent, 'bpmn:SubProcess');
          });
        }

        boundingBox = getBBox(elements);

        modeling.moveElements(
          elements,
          { x: -boundingBox.x, y: -boundingBox.y },
          rootElement
        );
      });
    }

    if (selection && contextPad) {
      this._registerAction('replaceElement', function(event) {
        contextPad.triggerEntry('replace', 'click', event);
      });
    }

  };

  var EditorActionsModule = {
    __depends__: [
      EditorActionsModule$1
    ],
    editorActions: [ 'type', BpmnEditorActions ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   */

  /**
   * @param {EventBus} eventBus
   */
  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;
  }

  /**
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   */

  var LOWER_PRIORITY = 1200;
  var LOW_PRIORITY$5 = 800;

  /**
   * Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints
   * and connection segments.
   *
   * @param {ElementRegistry} elementRegistry
   * @param {EventBus} eventBus
   * @param {Object} config
   */
  function GridSnapping(elementRegistry, eventBus, config) {

    var active = !config || config.active !== false;

    this._eventBus = eventBus;

    var self = this;

    eventBus.on('diagram.init', LOW_PRIORITY$5, 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, 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$1(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
   *
   * @return {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$2(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$2(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$2(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
   *
   * @return {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$2(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$2(axis) {
    return axis === 'x';
  }

  function isNorth(direction) {
    return direction.indexOf('n') !== -1;
  }

  function isWest(direction) {
    return direction.indexOf('w') !== -1;
  }

  /**
   * @typedef {import('../../../model/Types').Shape} Shape
   *
   * @typedef {import('../../../util/Types').Rect} Rect
   *
   * @typedef {import('../../../core/EventBus').default} EventBus
   * @typedef {import('../../grid-snapping/GridSnapping').default} GridSnapping
   */

  /**
   * Integrates resizing with grid snapping.
   *
   * @param {EventBus} eventBus
   * @param {GridSnapping} gridSnapping
   */
  function ResizeBehavior(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.$inject = [
    'eventBus',
    'gridSnapping',
    'modeling'
  ];

  e$2(ResizeBehavior, CommandInterceptor);

  /**
   * Snap width and height in relation to center.
   *
   * @param {Shape} shape
   * @param {Rect} newBounds
   *
   * @return {Rect} Snapped bounds.
   */
  ResizeBehavior.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 {Rect} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @return {Rect} Snapped bounds.
   */
  ResizeBehavior.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 {Rect} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @return {Rect} Snapped bounds.
   */
  ResizeBehavior.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$1(newBounds, snappedNewBounds);

    return newBounds;
  };

  /**
   * Snap in one or both directions vertically.
   *
   * @param {Rect} newBounds
   * @param {string} directions - Directions as {n|w|s|e}.
   *
   * @return {Rect} Snapped bounds.
   */
  ResizeBehavior.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$1(newBounds, snappedNewBounds);

    return newBounds;
  };

  /**
   * @typedef {import('../../../core/EventBus').default} EventBus
   * @typedef {import('../../grid-snapping/GridSnapping').default} GridSnapping
   */

  var HIGH_PRIORITY$6 = 2000;

  /**
   * Integrates space tool with grid snapping.
   *
   * @param {EventBus} eventBus
   * @param {GridSnapping} gridSnapping
   */
  function SpaceToolBehavior(eventBus, gridSnapping) {
    eventBus.on([
      'spaceTool.move',
      'spaceTool.end'
    ], HIGH_PRIORITY$6, 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.$inject = [
    'eventBus',
    'gridSnapping'
  ];

  var GridSnappingBehaviorModule$1 = {
    __init__: [
      'gridSnappingResizeBehavior',
      'gridSnappingSpaceToolBehavior'
    ],
    gridSnappingResizeBehavior: [ 'type', ResizeBehavior ],
    gridSnappingSpaceToolBehavior: [ 'type', SpaceToolBehavior ]
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var GridSnappingModule$1 = {
    __depends__: [ GridSnappingBehaviorModule$1 ],
    __init__: [ 'gridSnapping' ],
    gridSnapping: [ 'type', GridSnapping ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
   *
   * @typedef {import('diagram-js/lib/util/Types').Axis} Axis
   */

  var HIGH_PRIORITY$5 = 2000;

  /**
   * @param {EventBus} eventBus
   * @param {GridSnapping} gridSnapping
   * @param {ElementRegistry} elementRegistry
   */
  function GridSnappingAutoPlaceBehavior(eventBus, gridSnapping, elementRegistry) {
    eventBus.on('autoPlace', HIGH_PRIORITY$5, function(context) {
      var source = context.source,
          sourceMid = getMid(source),
          shape = context.shape;

      var position = getNewShapePosition(source, shape, elementRegistry);

      [ '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(shape, 'bpmn:TextAnnotation')) {

          if (isHorizontal$1(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',
    'elementRegistry'
  ];

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

  /**
   * @param {Axis} axis
   *
   * @return {boolean}
   */
  function isHorizontal$1(axis) {
    return axis === 'x';
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
   */

  var HIGHER_PRIORITY$4 = 1750;

  /**
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {GridSnapping} gridSnapping
   */
  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(shape, 'bpmn:Participant') ||
        !is(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'
  ];

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping
   * @typedef {import('../../modeling/Modeling').default} Modeling
   *
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   */

  var HIGH_PRIORITY$4 = 3000;


  /**
   * Snaps connections with Manhattan layout.
   *
   * @param {EventBus} eventBus
   * @param {GridSnapping} gridSnapping
   * @param {Modeling} modeling
   */
  function GridSnappingLayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
    CommandInterceptor.call(this, eventBus);

    this._gridSnapping = gridSnapping;

    var self = this;

    this.postExecuted([
      'connection.create',
      'connection.layout'
    ], HIGH_PRIORITY$4, 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$2(GridSnappingLayoutConnectionBehavior, CommandInterceptor);

  /**
   * Snap middle segments of a given connection.
   *
   * @param {Point[]} waypoints
   *
   * @return {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 {Point[]} waypoints
   *
   * @return {boolean}
   */
  function hasMiddleSegments(waypoints) {
    return waypoints.length > 3;
  }

  /**
   * Check whether an alignment is horizontal.
   *
   * @param {string} aligned
   *
   * @return {boolean}
   */
  function horizontallyAligned(aligned) {
    return aligned === 'h';
  }

  /**
   * Check whether an alignment is vertical.
   *
   * @param {string} aligned
   *
   * @return {boolean}
   */
  function verticallyAligned(aligned) {
    return aligned === 'v';
  }

  /**
   * Get middle segments from a given connection.
   *
   * @param {Point[]} waypoints
   *
   * @return {Point[]}
   */
  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$1({}, segmentStart, snapped);
      segmentEnd = assign$1({}, 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 ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/interaction-events/InteractionEvents').default} InteractionEvents
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   */

  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(element, 'bpmn:Lane')) {
        return self._createParticipantHit(element, gfx);
      } else if (is(element, 'bpmn:Participant')) {
        if (isExpanded(element)) {
          return self._createParticipantHit(element, gfx);
        } else {
          return self._createDefaultHit(element, gfx);
        }
      } else if (is(element, 'bpmn:SubProcess')) {
        if (isExpanded(element)) {
          return self._createSubProcessHit(element, gfx);
        } else {
          return self._createDefaultHit(element, gfx);
        }
      }
    });

  }

  BpmnInteractionEvents.$inject = [
    'eventBus',
    'interactionEvents'
  ];

  /**
   * @param {Element} element
   * @param {SVGElement} gfx
   *
   * @return {boolean}
   */
  BpmnInteractionEvents.prototype._createDefaultHit = function(element, gfx) {
    this._interactionEvents.removeHits(gfx);

    this._interactionEvents.createDefaultHit(element, gfx);

    // indicate that we created a hit
    return true;
  };

  /**
   * @param {Shape} element
   * @param {SVGElement} gfx
   *
   * @return {boolean}
   */
  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
    var box = isHorizontal$3(element) ? {
      width: LABEL_WIDTH,
      height: element.height
    } : {
      width: element.width,
      height: LABEL_HEIGHT
    };

    this._interactionEvents.createBoxHit(gfx, 'all', box);

    // indicate that we created a hit
    return true;
  };

  /**
   * @param {Shape} element
   * @param {SVGElement} gfx
   *
   * @return {boolean}
   */
  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 ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   * @typedef {import('diagram-js/lib/features/editor-actions/EditorActions').default} EditorActions
   * @typedef {import('diagram-js/lib/features/keyboard/Keyboard').default} Keyboard
   */

  /**
   * BPMN 2.0 specific keyboard bindings.
   *
   * @param {Injector} injector
   */
  function BpmnKeyboardBindings(injector) {
    injector.invoke(KeyboardBindings, this);
  }

  e$2(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;
      }
    });

    // activate replace element
    // R
    addListener('replaceElement', function(context) {

      var event = context.keyEvent;

      if (keyboard.hasModifier(event)) {
        return;
      }

      if (keyboard.isKey([ 'r', 'R' ], event)) {
        editorActions.trigger('replaceElement', event);

        return true;
      }
    });

  };

  var KeyboardModule = {
    __depends__: [
      KeyboardModule$1
    ],
    __init__: [ 'keyboardBindings' ],
    keyboardBindings: [ 'type', BpmnKeyboardBindings ]
  };

  /**
   * @typedef {import('../keyboard/Keyboard').default} Keyboard
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   * @typedef {import('../selection/Selection').default} Selection
   */

  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 {Rules} rules
   * @param {Selection} selection
   */
  function KeyboardMoveSelection(
      config,
      keyboard,
      modeling,
      rules,
      selection
  ) {

    var self = this;

    this._config = assign$1({}, 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'
  ];

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var KeyboardMoveSelectionModule = {
    __depends__: [
      KeyboardModule$1,
      SelectionModule
    ],
    __init__: [
      'keyboardMoveSelection'
    ],
    keyboardMoveSelection: [ 'type', KeyboardMoveSelection ]
  };

  /**
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../../util/Types').Direction} Direction
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   */

  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;
   * });
   * ```
   *
   * @param {EventBus} eventBus
   * @param {Rules} rules
   * @param {Modeling} modeling
   * @param {Dragging} dragging
   */
  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$2(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|TouchEvent} event
   * @param {Shape} shape
   * @param {Object|Direction} contextOrDirection
   */
  Resize.prototype.activate = function(event, shape, contextOrDirection) {
    var dragging = this._dragging,
        context,
        direction;

    if (typeof contextOrDirection === 'string') {
      contextOrDirection = {
        direction: contextOrDirection
      };
    }

    context = assign$1({ 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(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(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 = 'djs-resizing',
      MARKER_RESIZE_NOT_OK = 'resize-not-ok';

  var LOW_PRIORITY$4 = 500;

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../preview-support/PreviewSupport').default} PreviewSupport
   */

  /**
   * 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);
      }

      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);
    }

    // add and update previews
    eventBus.on('resize.move', LOW_PRIORITY$4, function(event) {
      updateFrame(event.context);
    });

    // remove previews
    eventBus.on('resize.cleanup', function(event) {
      removeFrame(event.context);
    });

  }

  ResizePreview.$inject = [
    'eventBus',
    'canvas',
    'previewSupport'
  ];

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../resize/Resize').default} Resize
   * @typedef {import('../selection/Selection').default} Selection
   */

  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$2(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);
      }
    }

    event.bind(gfx, 'mousedown', startResize);
    event.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: -8 / 2 + offset.x,
      y: -8 / 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: -20 / 2 + offset.x,
      y: -20 / 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(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 {Element} element
   */
  ResizeHandles.prototype.addResizer = function(element) {
    var self = this;

    if (isConnection(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 = 6;
    } else if (direction.indexOf('w') !== -1) {
      offset.x = HANDLE_OFFSET;
    }

    if (direction.indexOf('s') !== -1) {
      offset.y = 6;
    } else if (direction.indexOf('n') !== -1) {
      offset.y = HANDLE_OFFSET;
    }

    return offset;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var ResizeModule = {
    __depends__: [
      RulesModule$1,
      DraggingModule,
      PreviewSupportModule
    ],
    __init__: [
      'resize',
      'resizePreview',
      'resizeHandles'
    ],
    resize: [ 'type', Resize ],
    resizePreview: [ 'type', ResizePreview ],
    resizeHandles: [ 'type', ResizeHandles ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js-direct-editing/lib/DirectEditing').default} DirectEditing
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('diagram-js/lib/features/resize/ResizeHandles').default} ResizeHandles
   * @typedef {import('../../draw/TextRenderer').default} TextRenderer
   *
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef { {
   *   bounds: {
   *     x: number;
   *     y: number;
   *     width: number;
   *     height: number;
   *     minWidth?: number;
   *     minHeight?: number;
   *   };
   *   style: Object;
   * } } DirectEditingContext
   */

  var HIGH_PRIORITY$3 = 2000;

  /**
   * @param {EventBus} eventBus
   * @param {BpmnFactory} bpmnFactory
   * @param {Canvas} canvas
   * @param {DirectEditing} directEditing
   * @param {Modeling} modeling
   * @param {ResizeHandles} resizeHandles
   * @param {TextRenderer} textRenderer
   */
  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() {
      if (directEditing.isActive()) {
        directEditing.complete();
      }
    });

    eventBus.on([
      'shape.remove',
      'connection.remove'
    ], HIGH_PRIORITY$3, 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(nikku): 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', 'bpmn:Participant' ]) ||
          isCollapsedSubProcess(element)) {

        directEditing.activate(element);
      }
    }

  }

  LabelEditingProvider.$inject = [
    'eventBus',
    'bpmnFactory',
    'canvas',
    'directEditing',
    'modeling',
    'resizeHandles',
    'textRenderer'
  ];


  /**
   * Activate direct editing for activities and text annotations.
   *
   * @param {Element} element
   *
   * @return { {
   *   text: string;
   *   options?: {
   *     autoResize?: boolean;
   *     centerVertically?: boolean;
   *     resizable?: boolean;
   *   }
   * } & DirectEditingContext }
   */
  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$1(context, bounds);

    var options = {};
    var style = context.style || {};

    // Remove background and border
    assign$1(style, {
      backgroundColor: null,
      border: null
    });

    // tasks
    if (
      isAny(element, [
        'bpmn:Task',
        'bpmn:Participant',
        'bpmn:Lane',
        'bpmn:CallActivity'
      ]) ||
      isCollapsedSubProcess(element)
    ) {
      assign$1(options, {
        centerVertically: true
      });
    }

    // external labels
    if (isLabelExternal(element)) {
      assign$1(options, {
        autoResize: true
      });

      // keep background and border for external labels
      assign$1(style, {
        backgroundColor: '#ffffff',
        border: '1px solid #ccc'
      });
    }

    // text annotations
    if (is(element, 'bpmn:TextAnnotation')) {
      assign$1(options, {
        resizable: true,
        autoResize: true
      });

      // keep background and border for text annotations
      assign$1(style, {
        backgroundColor: '#ffffff',
        border: '1px solid #ccc'
      });
    }

    assign$1(context, {
      options: options,
      style: style
    });

    return context;
  };


  /**
   * Get the editing bounding box based on the element's size and position.
   *
   * @param {Element} element
   *
   * @return {DirectEditingContext}
   */
  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(element, 'bpmn:Lane') || isExpandedPool(element)) {
      var isHorizontalLane = isHorizontal$3(element);

      var laneBounds = isHorizontalLane ? {
        width: bbox.height,
        height: 30 * zoom,
        x: bbox.x - bbox.height / 2 + (15 * zoom),
        y: mid.y - (30 * zoom) / 2
      } : {
        width: bbox.width,
        height: 30 * zoom
      };

      assign$1(bounds, laneBounds);

      assign$1(style, {
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight,
        paddingTop: (7 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (5 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px',
        transform: isHorizontalLane ? 'rotate(-90deg)' : null
      });
    }


    // internal labels for collapsed participants
    if (isCollapsedPool(element)) {
      var isHorizontalPool = isHorizontal$3(element);

      var poolBounds = isHorizontalPool ? {
        width: bbox.width,
        height: bbox.height
      } : {
        width: bbox.height,
        height: bbox.width,
        x: mid.x - bbox.height / 2,
        y: mid.y - bbox.width / 2
      };

      assign$1(bounds, poolBounds);

      assign$1(style, {
        fontSize: defaultFontSize + 'px',
        lineHeight: defaultLineHeight,
        paddingTop: (7 * zoom) + 'px',
        paddingBottom: (7 * zoom) + 'px',
        paddingLeft: (5 * zoom) + 'px',
        paddingRight: (5 * zoom) + 'px',
        transform: isHorizontalPool ? null : 'rotate(-90deg)'
      });
    }


    // internal labels for tasks and collapsed call activities
    // and sub processes
    if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) ||
        isCollapsedSubProcess(element)) {

      assign$1(bounds, {
        width: bbox.width,
        height: bbox.height
      });

      assign$1(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(element)) {
      assign$1(bounds, {
        width: bbox.width,
        x: bbox.x
      });

      assign$1(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$1(bounds, {
        width: width,
        height: bbox.height + paddingTop + paddingBottom,
        x: mid.x - width / 2,
        y: bbox.y - paddingTop
      });

      assign$1(style, {
        fontSize: externalFontSize + 'px',
        lineHeight: externalLineHeight,
        paddingTop: paddingTop + 'px',
        paddingBottom: paddingBottom + 'px'
      });
    }

    // external label not yet created
    if (isLabelExternal(target)
        && !hasExternalLabel(target)
        && !isLabel(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$1(bounds, {
        width: width,
        height: height,
        x: absoluteBBox.x - width / 2,
        y: absoluteBBox.y - height / 2
      });

      assign$1(style, {
        fontSize: externalFontSize + 'px',
        lineHeight: externalLineHeight,
        paddingTop: paddingTop + 'px',
        paddingBottom: paddingBottom + 'px'
      });
    }

    // text annotations
    if (is(element, 'bpmn:TextAnnotation')) {
      assign$1(bounds, {
        width: bbox.width,
        height: bbox.height,
        minWidth: 30 * zoom,
        minHeight: 10 * zoom
      });

      assign$1(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(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(newLabel)) {
      newLabel = null;
    }

    this._modeling.updateLabel(element, newLabel, newBounds);
  };



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

  function isCollapsedSubProcess(element) {
    return is(element, 'bpmn:SubProcess') && !isExpanded(element);
  }

  function isExpandedSubProcess(element) {
    return is(element, 'bpmn:SubProcess') && isExpanded(element);
  }

  function isCollapsedPool(element) {
    return is(element, 'bpmn:Participant') && !isExpanded(element);
  }

  function isExpandedPool(element) {
    return is(element, 'bpmn:Participant') && isExpanded(element);
  }

  function isEmptyText(label) {
    return !label || !label.trim();
  }

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('../../draw/PathMap').default} PathMap
   */

  var MARKER_HIDDEN = 'djs-element-hidden',
      MARKER_LABEL_HIDDEN = 'djs-label-hidden';

  /**
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {PathMap} pathMap
   */
  function LabelEditingPreview(eventBus, canvas, 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(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$1(gfx, element.x, element.y);
      }

      if (is(element, 'bpmn:TextAnnotation') ||
          element.labelTarget) {
        canvas.addMarker(element, MARKER_HIDDEN);
      } else if (is(element, 'bpmn:Task') ||
                 is(element, 'bpmn:CallActivity') ||
                 is(element, 'bpmn:SubProcess') ||
                 is(element, 'bpmn:Participant') ||
                 is(element, 'bpmn:Lane')) {
        canvas.addMarker(element, MARKER_LABEL_HIDDEN);
      }
    });

    eventBus.on('directEditing.resize', function(context) {

      // text annotation
      if (is(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',
    '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 ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('../../util/Types').RectTRBL} RectTRBL
   *
   * @typedef { {
   *   html: string | HTMLElement;
   *   position: RectTRBL;
   *   show?: {
   *     minZoom?: number;
   *     maxZoom?: number;
   *   };
   *   timeout?: number;
   * } } Tooltip
   */

  // document wide unique tooltip ids
  var ids = new IdGenerator('tt');


  function createRoot(parentNode) {
    var root = domify$1(
      '<div class="djs-tooltip-container" />'
    );

    assign(root, {
      position: 'absolute',
      width: '0',
      height: '0'
    });

    parentNode.insertBefore(root, parentNode.firstChild);

    return root;
  }


  function setPosition(el, x, y) {
    assign(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
      }
    };

    /**
     * @type {Record<string, 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 an HTML tooltip to the diagram.
   *
   * @param {Tooltip} tooltip
   *
   * @return {string} ID of the tooltip.
   */
  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$1({}, this._tooltipDefaults, tooltip, {
      id: id
    });

    this._addTooltip(tooltip);

    if (tooltip.timeout) {
      this.setTimeout(tooltip);
    }

    return id;
  };

  /**
   * @param {string} action
   * @param {Event} event
   */
  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 tooltip with given ID.
   *
   * @param {Tooltip|string} id
   *
   * @return {Tooltip|undefined}
   */
  Tooltips.prototype.get = function(id) {

    if (typeof id !== 'string') {
      id = id.id;
    }

    return this._tooltips[id];
  };

  /**
   * @param {Tooltip} tooltip
   */
  Tooltips.prototype.clearTimeout = function(tooltip) {

    tooltip = this.get(tooltip);

    if (!tooltip) {
      return;
    }

    var removeTimer = tooltip.removeTimer;

    if (removeTimer) {
      clearTimeout(removeTimer);
      tooltip.removeTimer = null;
    }
  };

  /**
   * @param {Tooltip} tooltip
   */
  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 tooltip with given ID.
   *
   * @param {string | Tooltip} 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$1(html);
    }

    htmlContainer = domify$1('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '">');
    assign(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);
    });
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var TooltipsModule = {
    __init__: [ 'tooltips' ],
    tooltips: [ 'type', Tooltips ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/features/tooltips/Tooltips').default} Tooltips
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   */

  var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants';
  var DATA_OBJECT_ERR_MSG = 'Data object must be placed within a pool/participant.';

  /**
   * @param {EventBus} eventBus
   * @param {Tooltips} tooltips
   * @param {Translate} translate
   */
  function ModelingFeedback(eventBus, tooltips, translate) {

    function showError(position, message, timeout) {
      tooltips.add({
        position: {
          x: position.x + 5,
          y: position.y + 5
        },
        type: 'error',
        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(target, 'bpmn:Collaboration')) {
        if (is(shape, 'bpmn:FlowNode')) {
          showError(event, translate(COLLAB_ERR_MSG));
        } else if (is(shape, 'bpmn:DataObjectReference')) {
          showError(event, translate(DATA_OBJECT_ERR_MSG));
        }
      }
    });

  }

  ModelingFeedback.$inject = [
    'eventBus',
    'tooltips',
    'translate'
  ];

  var ModelingFeedbackModule = {
    __depends__: [
      TooltipsModule
    ],
    __init__: [
      'modelingFeedback'
    ],
    modelingFeedback: [ 'type', ModelingFeedback ]
  };

  var LOW_PRIORITY$3 = 500;

  var DEFAULT_PRIORITY$1 = 1000;

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('./OutlineProvider').default} OutlineProvider
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../../draw/Styles').default} Styles
   */

  /**
   * @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
   */
  function Outline(eventBus, styles) {

    this._eventBus = eventBus;

    this.offset = 5;

    var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]);

    var self = this;

    /**
     * @param {SVGElement} gfx
     *
     * @return {SVGElement} outline
     */
    function createOutline(gfx) {
      var outline = create$1('rect');

      attr(outline, assign$1({
        x: 0,
        y: 0,
        rx: 4,
        width: 100,
        height: 100
      }, OUTLINE_STYLE));

      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$3, function(event) {
      var element = event.element,
          gfx = event.gfx;

      var outline = query('.djs-outline', gfx);

      if (!outline) {
        outline = self.getOutline(element) || createOutline();
        append(gfx, outline);
      }

      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();
        append(gfx, outline);
      }

      self.updateConnectionOutline(outline, element);
    });
  }


  /**
   * Updates the outline of a shape respecting the dimension of the
   * element and an outline offset.
   *
   * @param {SVGElement} outline
   * @param {Element} element
   */
  Outline.prototype.updateShapeOutline = function(outline, element) {

    var updated = false;
    var providers = this._getProviders();

    if (providers.length) {
      forEach$1(providers, function(provider) {
        updated = updated || provider.updateOutline(element, outline);
      });
    }

    if (!updated) {
      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.
   * Register an outline provider with the given priority.
   *
   * @param {SVGElement} outline
   * @param {Element} connection
   */
  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
    });
  };

  /**
   * Register an outline provider with the given priority.
   *
   * @param {number} priority
   * @param {OutlineProvider} provider
   */
  Outline.prototype.registerProvider = function(priority, provider) {
    if (!provider) {
      provider = priority;
      priority = DEFAULT_PRIORITY$1;
    }

    this._eventBus.on('outline.getProviders', priority, function(event) {
      event.providers.push(provider);
    });
  };

  /**
   * Returns the registered outline providers.
   *
   * @returns {OutlineProvider[]}
   */
  Outline.prototype._getProviders = function() {
    var event = this._eventBus.createEvent({
      type: 'outline.getProviders',
      providers: []
    });

    this._eventBus.fire(event);

    return event.providers;
  };

  /**
   * Returns the outline for an element.
   *
   * @param {Element} element
   */
  Outline.prototype.getOutline = function(element) {
    var outline;
    var providers = this._getProviders();

    forEach$1(providers, function(provider) {

      if (!isFunction(provider.getOutline)) {
        return;
      }

      outline = outline || provider.getOutline(element);
    });

    return outline;
  };

  Outline.$inject = [ 'eventBus', 'styles', 'elementRegistry' ];

  var SELECTION_OUTLINE_PADDING = 6;

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../selection/Selection').default} Selection
   * @typedef {import('../../core/Canvas').default} Canvas
   */

  /**
   * @class
   *
   * A plugin that adds an outline to shapes and connections that may be activated and styled
   * via CSS classes.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Selection} selection
   */
  function MultiSelectionOutline(eventBus, canvas, selection) {
    this._canvas = canvas;

    var self = this;

    eventBus.on('element.changed', function(event) {
      if (selection.isSelected(event.element)) {
        self._updateMultiSelectionOutline(selection.get());
      }
    });

    eventBus.on('selection.changed', function(event) {
      var newSelection = event.newSelection;

      self._updateMultiSelectionOutline(newSelection);
    });
  }



  MultiSelectionOutline.prototype._updateMultiSelectionOutline = 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$1({
      rx: 3
    }, bBox));

    classes(rect).add('djs-selection-outline');

    append(layer, rect);
  };


  MultiSelectionOutline.$inject = [ 'eventBus', 'canvas', 'selection' ];

  // 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
    };
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var Ouline = {
    __depends__: [
      SelectionModule
    ],
    __init__: [ 'outline', 'multiSelectionOutline' ],
    outline: [ 'type', Outline ],
    multiSelectionOutline: [ 'type', MultiSelectionOutline ]
  };

  /**
   * @typedef {import('../../core/Types').ElementLike} Element
   * @typedef {import('../../core/Types').ShapeLike} Shape
   *
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../modeling/Modeling').default} Modeling
   * @typedef {import('../rules/Rules').default} Rules
   * @typedef {import('../selection/Selection').default} Selection
   */

  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$1(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|TouchEvent} event
     * @param {Shape} element
     * @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 {Element[]} elements
   *
   * @return {Element[]} 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;
    });
  }

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../preview-support/PreviewSupport').default} PreviewSupport
   * @typedef {import('../../draw/Styles').default} Styles
   */

  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 {Canvas} canvas
   * @param {Styles} styles
   * @param {PreviewSupport} previewSupport
   */
  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 = allShapes.flatMap(shape =>
        (shape.incoming || []).concat(shape.outgoing || [])
      );

      var allElements = allShapes.concat(allConnections);
      var uniqueElements = [ ...new Set(allElements) ];

      return uniqueElements;
    }

    /**
     * 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 {Element} 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 && parent && target.id !== parent.id) {
          setMarker(target, MARKER_NEW_PARENT);
        } else {
          setMarker(target, context.canExecute ? MARKER_OK$1 : MARKER_NOT_OK$1);
        }
      }

      translate$1(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 {Element} 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(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;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var MoveModule = {
    __depends__: [
      InteractionEventsModule$1,
      SelectionModule,
      Ouline,
      RulesModule$1,
      DraggingModule,
      PreviewSupportModule
    ],
    __init__: [
      'move',
      'movePreview'
    ],
    move: [ 'type', MoveEvents ],
    movePreview: [ 'type', MovePreview ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   *
   * @typedef {import('./PaletteProvider').PaletteEntries} PaletteEntries
   * @typedef {import('./PaletteProvider').default} PaletteProvider
   */

  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.
   *
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   */
  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' ];

  /**
   * @overlord
   *
   * Register a palette provider with default priority. See
   * {@link PaletteProvider} for examples.
   *
   * @param {PaletteProvider} provider
   */

  /**
   * Register a palette provider with the given priority. See
   * {@link PaletteProvider} for examples.
   *
   * @param {number} priority
   * @param {PaletteProvider} provider
   */
  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 {PaletteEntries}
   */
  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.
   */
  Palette.prototype._init = function() {

    var self = this;

    var eventBus = this._eventBus;

    var parentContainer = this._getParentContainer();

    var container = this._container = domify$1(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 (matches(target, TOGGLE_SELECTOR)) {
        return self.toggle();
      }

      self.trigger('click', event);
    });

    // prevent drag propagation
    event.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 { {
   *   open?: boolean;
   *   twoColumn?: boolean;
   * } } [state]
   */
  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=' + escapeCSS(grouping) + ']', entriesContainer);
      if (!container) {
        container = domify$1('<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$1(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$1('<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
   * @param {boolean} [autoActivate=false]
   */
  Palette.prototype.trigger = function(action, event, autoActivate) {
    var entry,
        originalEvent,
        button = event.delegateTarget || event.target;

    if (!button) {
      return event.preventDefault();
    }

    entry = attr$1(button, 'data-action');
    originalEvent = event.originalEvent || event;

    return this.triggerEntry(entry, action, originalEvent, autoActivate);
  };

  /**
   * @param {string} entryId
   * @param {string} action
   * @param {Event} event
   * @param {boolean} [autoActivate=false]
   */
  Palette.prototype.triggerEntry = function(entryId, action, event, autoActivate) {
    var entries = this._entries,
        entry,
        handler;

    entry = entries[entryId];

    // when user clicks on the palette and not on an action
    if (!entry) {
      return;
    }

    handler = entry.action;

    if (this._eventBus.fire('palette.trigger', { entry, event }) === false) {
      return;
    }

    // simple action (via callback function)
    if (isFunction(handler)) {
      if (action === 'click') {
        return handler(event, autoActivate);
      }
    } else {
      if (handler[action]) {
        return handler[action](event, autoActivate);
      }
    }

    // silence other actions
    event.preventDefault();
  };

  Palette.prototype._layoutChanged = function() {
    this._toggleState({});
  };

  /**
   * Do we need to collapse to two columns?
   *
   * @param {number} availableHeight
   * @param {PaletteEntries} 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 });
  };

  /**
   * Toggle the palette.
   */
  Palette.prototype.toggle = function() {
    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  };

  /**
   * @param {string} tool
   *
   * @return {boolean}
   */
  Palette.prototype.isActiveTool = function(tool) {
    return tool && this._activeTool === tool;
  };

  /**
   * @param {string} name
   */
  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
   *
   * ```javascript
   * palette.open();
   *
   * if (palette.isOpen()) {
   *   // yes, we are open
   * }
   * ```
   *
   * @return {boolean}
   */
  Palette.prototype.isOpen = function() {
    return classes$1(this._container).has(PALETTE_OPEN_CLS);
  };

  /**
   * Get parent element of palette.
   *
   * @return {HTMLElement}
   */
  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;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var PaletteModule$1 = {
    __init__: [ 'palette' ],
    palette: [ 'type', Palette ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../mouse/Mouse').default} Mouse
   * @typedef {import('../selection/Selection').default} Selection
   * @typedef {import('../tool-manager/ToolManager').default} ToolManager
   *
   * @typedef {import('../../util/Types').Rect} Rect
   */

  var LASSO_TOOL_CURSOR = 'crosshair';

  /**
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Dragging} dragging
   * @param {ElementRegistry} elementRegistry
   * @param {Selection} selection
   * @param {ToolManager} toolManager
   * @param {Mouse} mouse
   */
  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', 0, function(event) {

      var context = event.context;

      var bbox = toBBox(event);

      var elements = elementRegistry.filter(function(element) {
        return element;
      });

      var add = hasSecondaryModifier(event);

      self.select(elements, bbox, add ? context.selection : []);
    });

    eventBus.on('lasso.start', function(event) {

      var context = event.context;

      context.bbox = toBBox(event);
      visuals.create(context);

      context.selection = selection.get();
    });

    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'
  ];

  /**
   * Activate lasso.
   *
   * @param {MouseEvent} event
   * @param {boolean} [autoActivate=false]
   */
  LassoTool.prototype.activateLasso = function(event, autoActivate) {

    this._dragging.init(event, 'lasso', {
      autoActivate: autoActivate,
      cursor: LASSO_TOOL_CURSOR,
      data: {
        context: {}
      }
    });
  };

  /**
   * Activate selection.
   *
   * @param {MouseEvent} event
   * @param {boolean} [autoActivate=false]
   */
  LassoTool.prototype.activateSelection = function(event, autoActivate) {

    this._dragging.init(event, 'lasso.selection', {
      trapClick: false,
      autoActivate: autoActivate,
      cursor: LASSO_TOOL_CURSOR,
      data: {
        context: {}
      },
      keepSelection: true
    });
  };

  /**
   * Select elements within the given bounds.
   *
   * @param {Element[]} elements
   * @param {Rect} bbox
   * @param {Element[]} [previousSelection]
   */
  LassoTool.prototype.select = function(elements, bbox, previousSelection = []) {
    var selectedElements = getEnclosedElements(elements, bbox);

    this._selection.select([
      ...previousSelection,
      ...values(selectedElements)
    ]);
  };

  /**
   * Toggle the lasso tool.
   */
  LassoTool.prototype.toggle = function() {
    if (this.isActive()) {
      return this._dragging.cancel();
    }

    var mouseEvent = this._mouse.getLastMoveEvent();

    this.activateSelection(mouseEvent, !!mouseEvent);
  };

  /**
   * Check if the lasso tool is active.
   *
   * @returns {boolean}
   */
  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;
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var LassoToolModule = {
    __depends__: [
      ToolManagerModule,
      MouseModule
    ],
    __init__: [ 'lassoTool' ],
    lassoTool: [ 'type', LassoTool ]
  };

  /**
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../mouse/Mouse').default} Mouse
   * @typedef {import('../tool-manager/ToolManager').default} ToolManager
   */

  var HIGH_PRIORITY$1 = 1500;
  var HAND_CURSOR = 'grab';

  /**
   * @param {EventBus} eventBus
   * @param {Canvas} canvas
   * @param {Dragging} dragging
   * @param {Injector} injector
   * @param {ToolManager} toolManager
   * @param {Mouse} mouse
   */
  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'
  ];

  /**
   *
   * @param event
   * @param {boolean} [autoActivate]
   * @param {object} [context]
   */
  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 || {}
      }
    });
  };

  /**
   *
   * @param event
   * @param {boolean} [autoActivate]
   * @param {boolean} [reactivate]
   */
  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('Space', keyEvent);
  }

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var HandToolModule = {
    __depends__: [
      ToolManagerModule,
      MouseModule
    ],
    __init__: [ 'handTool' ],
    handTool: [ 'type', HandTool ]
  };

  var MARKER_OK = 'connect-ok',
      MARKER_NOT_OK = 'connect-not-ok';

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../connect/Connect').default} Connect
   * @typedef {import('../dragging/Dragging').default} Dragging
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../mouse/Mouse').default} Mouse
   * @typedef {import('../rules/Rules').default} Rules
   * @typedef {import('../tool-manager/ToolManager').default} ToolManager
   *
   * @typedef {import('../../model/Types').Element} Element
   */

  /**
   * @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 element can initiate connection.
   *
   * @param {Element} startTarget
   * @return {boolean}
   */
  GlobalConnect.prototype.canStartConnect = function(startTarget) {
    return this._rules.allowed('connection.start', { source: startTarget });
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var GlobalConnectModule = {
    __depends__: [
      ConnectModule,
      RulesModule$1,
      DraggingModule,
      ToolManagerModule,
      MouseModule
    ],
    globalConnect: [ 'type', GlobalConnect ]
  };

  /**
   * @typedef {import('diagram-js/lib/features/palette/Palette').default} Palette
   * @typedef {import('diagram-js/lib/features/create/Create').default} Create
   * @typedef {import('diagram-js/lib/core/ElementFactory').default} ElementFactory
   * @typedef {import('../space-tool/BpmnSpaceTool').default} SpaceTool
   * @typedef {import('diagram-js/lib/features/lasso-tool/LassoTool').default} LassoTool
   * @typedef {import('diagram-js/lib/features/hand-tool/HandTool').default} HandTool
   * @typedef {import('diagram-js/lib/features/global-connect/GlobalConnect').default} GlobalConnect
   * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
   *
   * @typedef {import('diagram-js/lib/features/palette/Palette').PaletteEntries} PaletteEntries
   */

  /**
   * A palette provider for BPMN 2.0 elements.
   *
   * @param {Palette} palette
   * @param {Create} create
   * @param {ElementFactory} elementFactory
   * @param {SpaceTool} spaceTool
   * @param {LassoTool} lassoTool
   * @param {HandTool} handTool
   * @param {GlobalConnect} globalConnect
   * @param {Translate} translate
   */
  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'
  ];

  /**
   * @return {PaletteEntries}
   */
  PaletteProvider.prototype.getPaletteEntries = function() {

    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$1({ type: type }, options));
        create.start(event, shape);
      }

      return {
        group: group,
        className: className,
        title: title,
        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$1(actions, {
      'hand-tool': {
        group: 'tools',
        className: 'bpmn-icon-hand-tool',
        title: translate('Activate hand tool'),
        action: {
          click: function(event) {
            handTool.activateHand(event);
          }
        }
      },
      'lasso-tool': {
        group: 'tools',
        className: 'bpmn-icon-lasso-tool',
        title: translate('Activate lasso tool'),
        action: {
          click: function(event) {
            lassoTool.activateSelection(event);
          }
        }
      },
      'space-tool': {
        group: 'tools',
        className: 'bpmn-icon-space-tool',
        title: translate('Activate create/remove space tool'),
        action: {
          click: function(event) {
            spaceTool.activateSelection(event);
          }
        }
      },
      'global-connect-tool': {
        group: 'tools',
        className: 'bpmn-icon-connection-multi',
        title: translate('Activate 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 start event')
      ),
      '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 end event')
      ),
      '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 data object reference')
      ),
      'create.data-store': createAction(
        'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
        translate('Create data store reference')
      ),
      'create.subprocess-expanded': {
        group: 'activity',
        className: 'bpmn-icon-subprocess-expanded',
        title: translate('Create expanded sub-process'),
        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,
      TranslateModule
    ],
    __init__: [ 'paletteProvider' ],
    paletteProvider: [ 'type', PaletteProvider ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/core/ElementFactory').default} ElementFactory
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/features/preview-support/PreviewSupport').default} PreviewSupport
   */

  var LOW_PRIORITY = 250;

  /**
   * @param {EventBus} eventBus
   * @param {ElementRegistry} elementRegistry
   * @param {ElementFactory} elementFactory
   * @param {Canvas} canvas
   * @param {PreviewSupport} previewSupport
   */
  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$1(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="' + escapeCSS(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="' + escapeCSS(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$2(BpmnReplacePreview, CommandInterceptor);

  var ReplacePreviewModule = {
    __depends__: [
      PreviewSupportModule
    ],
    __init__: [ 'bpmnReplacePreview' ],
    bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   *
   * @typedef {import('diagram-js/lib/core/EventBus').Event} Event
   *
   * @typedef {import('../../model/Types').Shape} Shape
   *
   * @typedef {import('diagram-js/lib/util/Types').Axis} Axis
   * @typedef {import('diagram-js/lib/util/Types').Point} Point
   */

  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(source, 'bpmn:BoundaryEvent') && target === source.host) {
          snapBoundaryEventLoop(event);
        }

      } else if (isType(canExecute, 'bpmn:MessageFlow')) {

        if (is(start, 'bpmn:Event')) {

          // snap start
          context.connectionStart = mid$2(start);
        }

        if (is(hover, 'bpmn:Event')) {

          // snap hover
          snapToPosition(event, mid$2(hover));
        }

      } else {

        // un-snap source
        context.connectionStart = context.initialConnectionStart;
      }
    });
  }

  BpmnConnectSnapping.$inject = [ 'eventBus' ];


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

  /**
   * Snap to the given target if the event is inside the bounds of the target.
   *
   * @param {Event} event
   * @param {Shape} target
   * @param {number} padding
   */
  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 the target mid if the event is in the target mid.
   *
   * @param {Event} event
   * @param {Shape} target
   */
  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 a loop overlapping a boundary event.
   *
   * @param {Event} 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);
      }
    });
  }

  /**
   * @param {Event} event
   * @param {Point} position
   */
  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);
    });
  }

  /**
   * @param {Axis} axis
   * @param {Shape} element
   *
   * @return {number}
   */
  function getDimensionForAxis(axis, element) {
    return axis === 'x' ? element.width : element.height;
  }

  /**
   * @param {Shape} target
   *
   * @return {number}
   */
  function getTargetBoundsPadding(target) {
    if (is(target, 'bpmn:Task')) {
      return TASK_BOUNDS_PADDING;
    } else {
      return TARGET_BOUNDS_PADDING;
    }
  }

  /**
   * @param {Event} event
   * @param {Shape} target
   * @param {Axis} axis
   *
   * @return {boolean}
   */
  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;
  }

  /**
   * @typedef {import('../../model/Types').Element} Element
   *
   * @typedef {import('../../util/Types').Axis} Axis
   * @typedef {import('../../util/Types').DirectionTRBL} DirectionTRBL
   * @typedef {import('../../util/Types').Point} Point
   *
   * @typedef {DirectionTRBL & 'mid'} SnapLocation
   */

  /**
   * A snap context, containing the (possibly incomplete)
   * mappings of drop targets (to identify the snapping)
   * to computed snap points.
   */
  function SnapContext() {

    /**
     * @type {Record<string, SnapPoints>}
     */
    this._targets = {};

    /**
     * @type {Record<SnapLocation, Point>}
     */
    this._snapOrigins = {};

    /**
     * @type {SnapLocation[]}
     */
    this._snapLocations = [];

    /**
     * @type {Record<SnapLocation, Point[]>}
     */
    this._defaultSnaps = {};
  }

  /**
   * Get the snap origin for a given location.
   *
   * @param {SnapLocation} snapLocation
   *
   * @return {Point}
   */
  SnapContext.prototype.getSnapOrigin = function(snapLocation) {
    return this._snapOrigins[snapLocation];
  };

  /**
   * Set the snap origin for a given location.
   *
   * @param {SnapLocation} snapLocation
   * @param {Point} snapOrigin
   */
  SnapContext.prototype.setSnapOrigin = function(snapLocation, snapOrigin) {
    this._snapOrigins[snapLocation] = snapOrigin;

    if (this._snapLocations.indexOf(snapLocation) === -1) {
      this._snapLocations.push(snapLocation);
    }
  };

  /**
   * Add a default snap point.
   *
   * @param {SnapLocation} snapLocation
   * @param {Point} point
   */
  SnapContext.prototype.addDefaultSnap = function(snapLocation, point) {

    var snapValues = this._defaultSnaps[snapLocation];

    if (!snapValues) {
      snapValues = this._defaultSnaps[snapLocation] = [];
    }

    snapValues.push(point);
  };

  /**
   * Get the snap locations for this context.
   *
   * @return {SnapLocation[]}
   */
  SnapContext.prototype.getSnapLocations = function() {
    return this._snapLocations;
  };

  /**
   * Set the snap locations for this context.
   *
   * The order of locations determines precedence.
   *
   * @param {SnapLocation[]} snapLocations
   */
  SnapContext.prototype.setSnapLocations = function(snapLocations) {
    this._snapLocations = snapLocations;
  };

  /**
   * Get snap points for the given target.
   *
   * @param {Element|string} target
   *
   * @return {SnapPoints}
   */
  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;
  };


  /**
   * Add points to snap to.
   */
  function SnapPoints() {

    /**
     * Example:
     *
     * ```javascript
     * {
     *   'top-right': {
     *     x: [ 100, 200, 300 ]
     *     y: [ 100, 200, 300 ]
     *   }
     * }
     * ```
     *
     * @type {Record<string, Record<Axis, number[]>>}
     */
    this._snapValues = {};
  }

  /**
   * Add a snap point.
   *
   * @param {SnapLocation} snapLocation
   * @param {Point} point
   */
  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);
    }
  };

  /**
   * Snap a point's x or y value.
   *
   * @param {Point} point
   * @param {SnapLocation} snapLocation
   * @param {Axis} axis
   * @param {number} tolerance
   *
   * @return {number}
   */
  SnapPoints.prototype.snap = function(point, snapLocation, axis, tolerance) {
    var snappingValues = this._snapValues[snapLocation];

    return snappingValues && snapTo(point[axis], snappingValues[axis], tolerance);
  };

  /**
   * Initialize default snap points.
   *
   * @param {Record<SnapLocation, Point[]>} defaultSnaps
   */
  SnapPoints.prototype.initDefaults = function(defaultSnaps) {

    var self = this;

    forEach$1(defaultSnaps || {}, function(snapPoints, snapLocation) {
      forEach$1(snapPoints, function(point) {
        self.add(snapLocation, point);
      });
    });
  };

  /**
   * @typedef {import('../../core/ElementRegistry').default} ElementRegistry
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('./Snapping').default} Snapping
   */

  var HIGHER_PRIORITY$1 = 1250;


  /**
   * Snap during create and move.
   *
   * @param {ElementRegistry} 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(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(snapTarget)) {

        if (isLabel(shape)) {
          snapPoints.add('mid', mid$2(snapTarget));
        }

        return;
      }

      // handle connections
      if (isConnection(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 isHidden$1(element) {
    return !!element.hidden;
  }

  /**
   * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
   * @typedef {import('didi').Injector} Injector
   *
   * @typedef {import('diagram-js/lib/features/snapping/SnapContext').default} SnapContext
   * @typedef {import('diagram-js/lib/features/snapping/SnapContext').SnapPoints} SnapPoints
   *
   * @typedef {import('diagram-js/lib/core/EventBus').Event} Event
   *
   * @typedef {import('../../model/Types').Element} Element
   * @typedef {import('../../model/Types').Shape} Shape
   */

  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$2(BpmnCreateMoveSnapping, CreateMoveSnapping);

  BpmnCreateMoveSnapping.$inject = [
    'eventBus',
    'injector'
  ];

  /**
   * @param {Event} event
   *
   * @return {SnapContext}
   */
  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(shape, 'bpmn:Participant')) {

      // snap to borders with higher priority
      snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
    }

    return snapContext;
  };

  /**
   * @param {SnapPoints} snapPoints
   * @param {Shape} shape
   * @param {Shape} target
   *
   * @return {SnapPoints}
   */
  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(target, 'bpmn:SequenceFlow')) {
      snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
    }

    return snapPoints;
  };

  /**
   * @param {Shape} shape
   * @param {Shape} target
   *
   * @return {Shape[]}
   */
  BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
    return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
      .filter(function(snapTarget) {

        // do not snap to lanes
        return !is(snapTarget, 'bpmn:Lane');
      });
  };

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

  /**
   * @param {Shape} event
   * @param {Shape} target
   */
  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);
    }
  }

  /**
   * @param {Element[]} elements
   * @param {string} type
   *
   * @return {boolean}
   */
  function areAll(elements, type) {
    return elements.every(function(el) {
      return is(el, type);
    });
  }

  /**
   * @param {Element} element
   */
  function isContainer(element) {
    if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
      return true;
    }

    return is(element, 'bpmn:Participant');
  }

  /**
   * @param {Event} event
   */
  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
    };
  }

  /**
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('./Snapping').default} Snapping
   */

  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 isHidden(element) {
    return !!element.hidden;
  }

  function isHorizontal(direction) {
    return direction === 'n' || direction === 's';
  }

  function isVertical(direction) {
    return direction === 'e' || direction === 'w';
  }

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   *
   * @typedef {import('../../core/EventBus').Event} Event
   *
   * @typedef {import('./SnapContext').SnapPoints} SnapPoints
   */

  var SNAP_TOLERANCE = 7;

  var SNAP_LINE_HIDE_DELAY = 1000;


  /**
   * Generic snapping feature.
   *
   * @param {Canvas} canvas
   */
  function Snapping(canvas) {
    this._canvas = canvas;

    // delay hide by 1000 seconds since last snap
    this._asyncHide = debounce(bind$2(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();
    });
  };

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  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 ]
  };

  /**
   * @typedef {import('../../core/Canvas').default} Canvas
   * @typedef {import('../../core/EventBus').default} EventBus
   * @typedef {import('../selection/Selection').default} Selection
   * @typedef {import('../../i18n/translate/translate.js').default} Translate
   *
   * @typedef {import('../../util/Types').Dimensions} Dimensions
   *
   * @typedef {import('./SearchPadProvider').default} SearchPadProvider
   * @typedef {import('./SearchPadProvider').SearchResult} SearchResult
   * @typedef {import('./SearchPadProvider').Token} Token
   */

  var SCROLL_TO_ELEMENT_PADDING = 300;

  /**
   * Provides searching infrastructure.
   *
   * @param {Canvas} canvas
   * @param {EventBus} eventBus
   * @param {Selection} selection
   * @param {Translate} translate
   */
  function SearchPad(canvas, eventBus, selection, translate) {
    this._open = false;
    this._results = {};
    this._eventMaps = [];

    this._cachedRootElement = null;
    this._cachedSelection = null;
    this._cachedViewbox = null;

    this._canvas = canvas;
    this._eventBus = eventBus;
    this._selection = selection;
    this._translate = translate;

    // setup elements
    this._container = this._getBoxHtml();
    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 whenever appropriate
    eventBus.on([
      'canvas.destroy',
      'diagram.destroy',
      'drag.init',
      'elements.changed'
    ], this.close, this);
  }


  SearchPad.$inject = [
    'canvas',
    'eventBus',
    'selection',
    'translate'
  ];


  /**
   * 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(false);
    });

    // 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) {

      if (isKey('ArrowUp', e)) {
        e.preventDefault();
      }

      if (isKey('ArrowDown', e)) {
        e.preventDefault();
      }
    });

    // handle keyboard input
    listen(this._container, SearchPad.INPUT_SELECTOR, 'keyup', function(e) {

      if (isKey('Escape', e)) {
        return self.close();
      }

      if (isKey('Enter', e)) {
        var selected = self._getCurrentResult();

        return selected ? self._select(selected) : self.close(false);
      }

      if (isKey('ArrowUp', e)) {
        return self._scrollToDirection(true);
      }

      if (isKey('ArrowDown', e)) {
        return self._scrollToDirection();
      }

      // do not search while navigating text input
      if (isKey([ 'ArrowLeft', 'ArrowRight' ], e)) {
        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.trim()) {
      return;
    }

    var searchResults = this._searchProvider.find(pattern);

    searchResults = searchResults.filter(function(searchResult) {
      return !self._canvas.getRootElements().includes(searchResult.element);
    });

    if (!searchResults.length) {
      this._selection.select(null);

      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 {HTMLElement} 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._eventBus.fire('searchPad.cleared');
  };


  /**
   * Get currently selected result.
   *
   * @return {HTMLElement}
   */
  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 Pad
   * 'id' : id attribute value to assign to the new DOM node
   * return : created DOM element
   *
   * @param {SearchResult} result
   * @param {string} id
   *
   * @return {HTMLElement}
   */
  SearchPad.prototype._createResultNode = function(result, id) {
    var node = domify$1(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.
   *
   * @param {SearchPadProvider} 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._cachedRootElement = this._canvas.getRootElement();
    this._cachedSelection = this._selection.get();
    this._cachedViewbox = this._canvas.viewbox();

    this._selection.select(null);

    this._bindEvents();

    this._open = true;

    classes$1(this._canvas.getContainer()).add('djs-search-open');
    classes$1(this._container).add('open');

    this._searchInput.focus();

    this._eventBus.fire('searchPad.opened');
  };


  /**
   * Close search pad.
   */
  SearchPad.prototype.close = function(restoreCached = true) {
    if (!this.isOpen()) {
      return;
    }

    if (restoreCached) {
      if (this._cachedRootElement) {
        this._canvas.setRootElement(this._cachedRootElement);
      }

      if (this._cachedSelection) {
        this._selection.select(this._cachedSelection);
      }

      if (this._cachedViewbox) {
        this._canvas.viewbox(this._cachedViewbox);
      }

      this._eventBus.fire('searchPad.restored');
    }

    this._cachedRootElement = null;
    this._cachedSelection = null;
    this._cachedViewbox = null;

    this._unbindEvents();

    this._open = false;

    classes$1(this._canvas.getContainer()).remove('djs-search-open');
    classes$1(this._container).remove('open');

    this._clearResults();

    this._searchInput.value = '';
    this._searchInput.blur();

    this._eventBus.fire('searchPad.closed');

    this._canvas.restoreFocus();
  };


  /**
   * 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 {HTMLElement} 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._canvas.scrollToElement(element, {
      top: SCROLL_TO_ELEMENT_PADDING
    });

    this._selection.select(element);

    this._eventBus.fire('searchPad.preselected', element);
  };


  /**
   * Select result node.
   *
   * @param {HTMLElement} element
   */
  SearchPad.prototype._select = function(node) {
    var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE);
    var element = this._results[id].element;

    this._cachedSelection = null;
    this._cachedViewbox = null;

    this.close(false);

    this._canvas.scrollToElement(element, {
      top: SCROLL_TO_ELEMENT_PADDING
    });

    this._selection.select(element);

    this._eventBus.fire('searchPad.selected', element);
  };


  SearchPad.prototype._getBoxHtml = function() {
    const box = domify$1(SearchPad.BOX_HTML);
    const input = query(SearchPad.INPUT_SELECTOR, box);

    if (input) {
      input.setAttribute('aria-label', this._translate('Search in diagram'));
    }

    return box;
  };


  /**
   * Creates and appends child node from result tokens and HTML template.
   *
   * @param {HTMLElement} node
   * @param {Token[]} tokens
   * @param {string} template
   */
  function createInnerTextNode(parentNode, tokens, template) {
    var text = createHtmlText(tokens);
    var childNode = domify$1(template);
    childNode.innerHTML = text;
    parentNode.appendChild(childNode);
  }

  /**
   * Create internal HTML markup from result tokens.
   * Caters for highlighting pattern matched tokens.
   *
   * @param {Token[]} tokens
   *
   * @return {string|null}
   */
  function createHtmlText(tokens) {
    var htmlText = '';

    tokens.forEach(function(t) {
      var text = escapeHTML(t.value || t.matched || t.normal);
      var match = t.match || t.matched;

      if (match) {
        htmlText += '<b class="' + SearchPad.RESULT_HIGHLIGHT_CLASS + '">' + text + '</b>';
      } else {
        htmlText += text;
      }
    });

    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.BOX_HTML =
`<div class="djs-search-container djs-scrollable">
  <div class="djs-search-input">
    <svg class="djs-search-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path fill-rule="evenodd" clip-rule="evenodd" d="M9.0325 8.5H9.625L13.3675 12.25L12.25 13.3675L8.5 9.625V9.0325L8.2975 8.8225C7.4425 9.5575 6.3325 10 5.125 10C2.4325 10 0.25 7.8175 0.25 5.125C0.25 2.4325 2.4325 0.25 5.125 0.25C7.8175 0.25 10 2.4325 10 5.125C10 6.3325 9.5575 7.4425 8.8225 8.2975L9.0325 8.5ZM1.75 5.125C1.75 6.9925 3.2575 8.5 5.125 8.5C6.9925 8.5 8.5 6.9925 8.5 5.125C8.5 3.2575 6.9925 1.75 5.125 1.75C3.2575 1.75 1.75 3.2575 1.75 5.125Z" fill="#22242A"/>
    </svg>
    <input type="text" spellcheck="false" />
  </div>
  <div class="djs-search-results" />
</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>';

  /**
   * @type { import('didi').ModuleDeclaration }
   */
  var SearchPadModule = {
    __depends__: [
      TranslateModule,
      OverlaysModule,
      SelectionModule
    ],
    searchPad: [ 'type', SearchPad ]
  };

  /**
   * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
   * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
   * @typedef {import('diagram-js/lib/features/search-pad/SearchPad').default} SearchPad
   *
   * @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').default} SearchPadProvider
   * @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').SearchResult} SearchPadResult
   * @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').Token} SearchPadToken
   * @typedef {import('diagram-js/lib/features/search/search').default} Search
   * @typedef {import('diagram-js/lib/features/search/search').SearchResult} SearchResult
   * @typedef {import('diagram-js/lib/features/search/search').Token} SearchToken
   */

  /**
   * Provides ability to search for BPMN elements.
   *
   * @implements {SearchPadProvider}
   *
   * @param {ElementRegistry} elementRegistry
   * @param {SearchPad} searchPad
   * @param {Canvas} canvas
   * @param {Search} search
   */
  function BpmnSearchProvider(elementRegistry, searchPad, canvas, search) {
    this._elementRegistry = elementRegistry;
    this._canvas = canvas;
    this._search = search;

    searchPad.registerProvider(this);
  }

  BpmnSearchProvider.$inject = [
    'elementRegistry',
    'searchPad',
    'canvas',
    'search'
  ];

  /**
   * @param {string} pattern
   *
   * @return {SearchPadResult[]}
   */
  BpmnSearchProvider.prototype.find = function(pattern) {
    var rootElements = this._canvas.getRootElements();

    var elements = this._elementRegistry.filter(function(element) {
      return !isLabel(element) && !rootElements.includes(element);
    });

    return this._search(
      elements.map(element => {
        return {
          element,
          label: getLabel(element),
          id: element.id
        };
      }),
      pattern, {
        keys: [
          'label',
          'id'
        ]
      }
    ).map(toSearchPadResult);
  };

  /**
   * @param {SearchResult} token
   *
   * @return {SearchPadResult}
   */
  function toSearchPadResult(result) {

    const {
      item: {
        element
      },
      tokens
    } = result;

    return {
      element,
      primaryTokens: tokens.label,
      secondaryTokens: tokens.id
    };
  }

  var SearchModule = {
    __depends__: [
      SearchPadModule,
      SearchModule$1
    ],
    __init__: [ 'bpmnSearch' ],
    bpmnSearch: [ 'type', BpmnSearchProvider ]
  };

  const DATA_OBJECT_REFERENCE_OUTLINE_PATH = 'M44.7648 11.3263L36.9892 2.64074C36.0451 1.58628 34.5651 0.988708 33.1904 0.988708H5.98667C3.22688 0.988708 0.989624 3.34892 0.989624 6.26039V55.0235C0.989624 57.9349 3.22688 60.2952 5.98667 60.2952H40.966C43.7257 60.2952 45.963 57.9349 45.963 55.0235V14.9459C45.963 13.5998 45.6407 12.3048 44.7648 11.3263Z';
  const DATA_STORE_REFERENCE_OUTLINE_PATH = 'M1.03845 48.1347C1.03845 49.3511 1.07295 50.758 1.38342 52.064C1.69949 53.3938 2.32428 54.7154 3.56383 55.6428C6.02533 57.4841 10.1161 58.7685 14.8212 59.6067C19.5772 60.4538 25.1388 60.8738 30.6831 60.8738C36.2276 60.8738 41.7891 60.4538 46.545 59.6067C51.2504 58.7687 55.3412 57.4842 57.8028 55.6429C59.0424 54.7156 59.6673 53.3938 59.9834 52.064C60.2938 50.7579 60.3285 49.351 60.3285 48.1344V13.8415C60.3285 12.6249 60.2938 11.218 59.9834 9.91171C59.6673 8.58194 59.0423 7.2602 57.8027 6.33294C55.341 4.49168 51.2503 3.20723 46.545 2.36914C41.7891 1.522 36.2276 1.10204 30.6831 1.10205C25.1388 1.10206 19.5772 1.52206 14.8213 2.36923C10.1162 3.20734 6.02543 4.49183 3.5639 6.33314C2.32433 7.26038 1.69951 8.58206 1.38343 9.91181C1.07295 11.2179 1.03845 12.6247 1.03845 13.8411V48.1347Z';

  /**
   * @typedef { import('diagram-js/lib/util/Types').Dimensions} Dimensions
   */

  /**
   * @type {Dimensions}
   */
  const DATA_OBJECT_REFERENCE_STANDARD_SIZE = { width: 36, height: 50 };

  /**
   * @type {Dimensions}
   */
  const DATA_STORE_REFERENCE_STANDARD_SIZE = { width: 50, height: 50 };

  /**
   * Create a path element with given attributes.
   * @param {string} path
   * @param {Object} attrs
   * @param {Object} OUTLINE_STYLE
   * @return {SVGElement}
   */
  function createPath(path, attrs, OUTLINE_STYLE) {
    return create$1('path', {
      d: path,
      strokeWidth: 2,
      transform: `translate(${attrs.x}, ${attrs.y})`,
      ...OUTLINE_STYLE
    });
  }

  /**
   * @typedef { import('diagram-js/lib/features/outline/OutlineProvider').default } BaseOutlineProvider
   *
   * @typedef { import('diagram-js/lib/features/outline/OutlineProvider').Outline } Outline
   *
   * @typedef { import('diagram-js/lib/draw/Styles').default } Styles
   *
   * @typedef { import('diagram-js/lib/model/Types').Element } Element
   */

  const DEFAULT_OFFSET = 5;

  /**
   * BPMN-specific outline provider.
   *
   * @implements {BaseOutlineProvider}
   *
   * @param {Outline} outline
   * @param {Styles} styles
   */
  function OutlineProvider(outline, styles) {

    this._styles = styles;
    outline.registerProvider(this);
  }

  OutlineProvider.$inject = [
    'outline',
    'styles'
  ];

  /**
   * Returns outline for a given element.
   *
   * @param {Element} element
   *
   * @return {Outline}
   */
  OutlineProvider.prototype.getOutline = function(element) {

    const OUTLINE_STYLE = this._styles.cls('djs-outline', [ 'no-fill' ]);

    var outline;

    if (isLabel(element)) {
      return;
    }

    if (is(element, 'bpmn:Gateway')) {
      outline = create$1('rect');

      assign$1(outline.style, {
        'transform-box': 'fill-box',
        'transform': 'rotate(45deg)',
        'transform-origin': 'center'
      });

      attr(outline, assign$1({
        x: 2,
        y: 2,
        rx: 4,
        width: element.width - 4,
        height: element.height - 4,
      }, OUTLINE_STYLE));

    } else if (isAny(element, [ 'bpmn:Task', 'bpmn:SubProcess', 'bpmn:Group', 'bpmn:CallActivity' ])) {
      outline = create$1('rect');

      attr(outline, assign$1({
        x: -5,
        y: -5,
        rx: 14,
        width: element.width + DEFAULT_OFFSET * 2,
        height: element.height + DEFAULT_OFFSET * 2
      }, OUTLINE_STYLE));

    } else if (is(element, 'bpmn:EndEvent')) {

      outline = create$1('circle');

      // Extra 1px offset needed due to increased stroke-width of end event
      // which makes it bigger than other events.

      attr(outline, assign$1({
        cx: element.width / 2,
        cy: element.height / 2,
        r: element.width / 2 + DEFAULT_OFFSET + 1
      }, OUTLINE_STYLE));

    } else if (is(element, 'bpmn:Event')) {
      outline = create$1('circle');

      attr(outline, assign$1({
        cx: element.width / 2,
        cy: element.height / 2,
        r: element.width / 2 + DEFAULT_OFFSET
      }, OUTLINE_STYLE));

    } else if (is(element, 'bpmn:DataObjectReference') && isStandardSize(element, 'bpmn:DataObjectReference')) {

      outline = createPath(
        DATA_OBJECT_REFERENCE_OUTLINE_PATH,
        { x: -6, y: -6 },
        OUTLINE_STYLE
      );

    } else if (is(element, 'bpmn:DataStoreReference') && isStandardSize(element, 'bpmn:DataStoreReference')) {

      outline = createPath(
        DATA_STORE_REFERENCE_OUTLINE_PATH,
        { x: -6, y: -6 },
        OUTLINE_STYLE
      );
    }

    return outline;
  };

  /**
   * Updates the outline for a given element.
   * Returns true if the update for the given element was handled by this provider.
   *
   * @param {Element} element
   * @param {Outline} outline
   * @returns {boolean}
   */
  OutlineProvider.prototype.updateOutline = function(element, outline) {

    if (isLabel(element)) {
      return;
    }

    if (isAny(element, [ 'bpmn:SubProcess', 'bpmn:Group' ])) {

      attr(outline, {
        width: element.width + DEFAULT_OFFSET * 2,
        height: element.height + DEFAULT_OFFSET * 2
      });

      return true;

    } else if (isAny(element, [
      'bpmn:Event',
      'bpmn:Gateway',
      'bpmn:DataStoreReference',
      'bpmn:DataObjectReference'
    ])) {
      return true;
    }

    return false;
  };


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

  function isStandardSize(element, type) {
    var standardSize;

    if (type === 'bpmn:DataObjectReference') {
      standardSize = DATA_OBJECT_REFERENCE_STANDARD_SIZE;
    } else if (type === 'bpmn:DataStoreReference') {
      standardSize = DATA_STORE_REFERENCE_STANDARD_SIZE;
    }

    return element.width === standardSize.width
            && element.height === standardSize.height;
  }

  var OutlineModule = {
    __depends__: [
      Ouline
    ],
    __init__: [ 'outlineProvider' ],
    outlineProvider: [ 'type', OutlineProvider ]
  };

  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>';


  /**
   * @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions
   * @typedef {import('./BaseViewer').ImportXMLResult} ImportXMLResult
   */

  /**
   * 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 ]});
   * ```
   *
   * @template [ServiceMap=null]
   *
   * @extends BaseModeler<ServiceMap>
   *
   * @param {BaseViewerOptions} [options] The options to configure the modeler.
   */
  function Modeler(options) {
    BaseModeler.call(this, options);
  }

  e$2(Modeler, BaseModeler);


  Modeler.Viewer = Viewer;
  Modeler.NavigatedViewer = NavigatedViewer;

  /**
   * Create a new diagram to start modeling.
   *
   * @throws {ImportXMLError} An error thrown during the import of the XML.
   *
   * @return {Promise<ImportXMLResult>} A promise resolving with warnings that were produced during the import.
   */
  Modeler.prototype.createDiagram = function createDiagram() {
    return this.importXML(initialDiagram);
  };


  Modeler.prototype._interactionModules = [

    // non-modeling components
    KeyboardMoveModule,
    MoveCanvasModule,
    ZoomScrollModule
  ];

  Modeler.prototype._modelingModules = [

    // modeling components
    AlignElementsModule,
    AutoPlaceModule,
    AutoScrollModule,
    AutoResizeModule,
    BendpointsModule,
    ConnectModule,
    ConnectionPreviewModule,
    ContextPadModule,
    CopyPasteModule,
    CreateModule,
    DistributeElementsModule,
    EditorActionsModule,
    GridSnappingModule,
    InteractionEventsModule,
    KeyboardModule,
    KeyboardMoveSelectionModule,
    LabelEditingModule,
    ModelingModule,
    ModelingFeedbackModule,
    MoveModule,
    PaletteModule,
    ReplacePreviewModule,
    ResizeModule,
    SnappingModule,
    SearchModule,
    OutlineModule
  ];


  // 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;

}));