UNPKG

vue3-free-layout

Version:

[vue3+ts]拖动布局,自由拖动,标尺,放大缩小,控件之间的标尺

14,915 lines 485 kB
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
    typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["well-bricks"] = {}, global.Vue));
})(this, (function (exports, vue) { 'use strict';

    /**
     * Make a map and return a function for checking if a key
     * is in that map.
     * IMPORTANT: all calls of this function must be prefixed with
     * \/\*#\_\_PURE\_\_\*\/
     * So that rollup can tree-shake them if necessary.
     */

    (process.env.NODE_ENV !== 'production')
        ? Object.freeze({})
        : {};
    (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
    const NOOP = () => { };
    const extend = Object.assign;
    const isArray$1 = Array.isArray;
    const isFunction = (val) => typeof val === 'function';

    let activeEffectScope;
    function recordEffectScope(effect, scope = activeEffectScope) {
        if (scope && scope.active) {
            scope.effects.push(effect);
        }
    }

    const createDep = (effects) => {
        const dep = new Set(effects);
        dep.w = 0;
        dep.n = 0;
        return dep;
    };
    const wasTracked = (dep) => (dep.w & trackOpBit) > 0;
    const newTracked = (dep) => (dep.n & trackOpBit) > 0;
    const initDepMarkers = ({ deps }) => {
        if (deps.length) {
            for (let i = 0; i < deps.length; i++) {
                deps[i].w |= trackOpBit; // set was tracked
            }
        }
    };
    const finalizeDepMarkers = (effect) => {
        const { deps } = effect;
        if (deps.length) {
            let ptr = 0;
            for (let i = 0; i < deps.length; i++) {
                const dep = deps[i];
                if (wasTracked(dep) && !newTracked(dep)) {
                    dep.delete(effect);
                }
                else {
                    deps[ptr++] = dep;
                }
                // clear bits
                dep.w &= ~trackOpBit;
                dep.n &= ~trackOpBit;
            }
            deps.length = ptr;
        }
    };
    // The number of effects currently being tracked recursively.
    let effectTrackDepth = 0;
    let trackOpBit = 1;
    /**
     * The bitwise track markers support at most 30 levels of recursion.
     * This value is chosen to enable modern JS engines to use a SMI on all platforms.
     * When recursion depth is greater, fall back to using a full cleanup.
     */
    const maxMarkerBits = 30;
    let activeEffect;
    Symbol((process.env.NODE_ENV !== 'production') ? 'iterate' : '');
    Symbol((process.env.NODE_ENV !== 'production') ? 'Map key iterate' : '');
    class ReactiveEffect {
        constructor(fn, scheduler = null, scope) {
            this.fn = fn;
            this.scheduler = scheduler;
            this.active = true;
            this.deps = [];
            this.parent = undefined;
            recordEffectScope(this, scope);
        }
        run() {
            if (!this.active) {
                return this.fn();
            }
            let parent = activeEffect;
            let lastShouldTrack = shouldTrack;
            while (parent) {
                if (parent === this) {
                    return;
                }
                parent = parent.parent;
            }
            try {
                this.parent = activeEffect;
                activeEffect = this;
                shouldTrack = true;
                trackOpBit = 1 << ++effectTrackDepth;
                if (effectTrackDepth <= maxMarkerBits) {
                    initDepMarkers(this);
                }
                else {
                    cleanupEffect(this);
                }
                return this.fn();
            }
            finally {
                if (effectTrackDepth <= maxMarkerBits) {
                    finalizeDepMarkers(this);
                }
                trackOpBit = 1 << --effectTrackDepth;
                activeEffect = this.parent;
                shouldTrack = lastShouldTrack;
                this.parent = undefined;
                if (this.deferStop) {
                    this.stop();
                }
            }
        }
        stop() {
            // stopped while running itself - defer the cleanup
            if (activeEffect === this) {
                this.deferStop = true;
            }
            else if (this.active) {
                cleanupEffect(this);
                if (this.onStop) {
                    this.onStop();
                }
                this.active = false;
            }
        }
    }
    function cleanupEffect(effect) {
        const { deps } = effect;
        if (deps.length) {
            for (let i = 0; i < deps.length; i++) {
                deps[i].delete(effect);
            }
            deps.length = 0;
        }
    }
    let shouldTrack = true;
    function trackEffects(dep, debuggerEventExtraInfo) {
        let shouldTrack = false;
        if (effectTrackDepth <= maxMarkerBits) {
            if (!newTracked(dep)) {
                dep.n |= trackOpBit; // set newly tracked
                shouldTrack = !wasTracked(dep);
            }
        }
        else {
            // Full cleanup mode.
            shouldTrack = !dep.has(activeEffect);
        }
        if (shouldTrack) {
            dep.add(activeEffect);
            activeEffect.deps.push(dep);
            if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) {
                activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo));
            }
        }
    }
    function triggerEffects(dep, debuggerEventExtraInfo) {
        // spread into array for stabilization
        const effects = isArray$1(dep) ? dep : [...dep];
        for (const effect of effects) {
            if (effect.computed) {
                triggerEffect(effect, debuggerEventExtraInfo);
            }
        }
        for (const effect of effects) {
            if (!effect.computed) {
                triggerEffect(effect, debuggerEventExtraInfo);
            }
        }
    }
    function triggerEffect(effect, debuggerEventExtraInfo) {
        if (effect !== activeEffect || effect.allowRecurse) {
            if ((process.env.NODE_ENV !== 'production') && effect.onTrigger) {
                effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
            }
            if (effect.scheduler) {
                effect.scheduler();
            }
            else {
                effect.run();
            }
        }
    }
    function toRaw(observed) {
        const raw = observed && observed["__v_raw" /* ReactiveFlags.RAW */];
        return raw ? toRaw(raw) : observed;
    }

    function trackRefValue(ref) {
        if (shouldTrack && activeEffect) {
            ref = toRaw(ref);
            if ((process.env.NODE_ENV !== 'production')) {
                trackEffects(ref.dep || (ref.dep = createDep()), {
                    target: ref,
                    type: "get" /* TrackOpTypes.GET */,
                    key: 'value'
                });
            }
            else {
                trackEffects(ref.dep || (ref.dep = createDep()));
            }
        }
    }
    function triggerRefValue(ref, newVal) {
        ref = toRaw(ref);
        const dep = ref.dep;
        if (dep) {
            if ((process.env.NODE_ENV !== 'production')) {
                triggerEffects(dep, {
                    target: ref,
                    type: "set" /* TriggerOpTypes.SET */,
                    key: 'value',
                    newValue: newVal
                });
            }
            else {
                triggerEffects(dep);
            }
        }
    }

    var _a$1;
    class ComputedRefImpl {
        constructor(getter, _setter, isReadonly, isSSR) {
            this._setter = _setter;
            this.dep = undefined;
            this.__v_isRef = true;
            this[_a$1] = false;
            this._dirty = true;
            this.effect = new ReactiveEffect(getter, () => {
                if (!this._dirty) {
                    this._dirty = true;
                    triggerRefValue(this);
                }
            });
            this.effect.computed = this;
            this.effect.active = this._cacheable = !isSSR;
            this["__v_isReadonly" /* ReactiveFlags.IS_READONLY */] = isReadonly;
        }
        get value() {
            // the computed ref may get wrapped by other proxies e.g. readonly() #3376
            const self = toRaw(this);
            trackRefValue(self);
            if (self._dirty || !self._cacheable) {
                self._dirty = false;
                self._value = self.effect.run();
            }
            return self._value;
        }
        set value(newValue) {
            this._setter(newValue);
        }
    }
    _a$1 = "__v_isReadonly" /* ReactiveFlags.IS_READONLY */;
    function computed(getterOrOptions, debugOptions, isSSR = false) {
        let getter;
        let setter;
        const onlyGetter = isFunction(getterOrOptions);
        if (onlyGetter) {
            getter = getterOrOptions;
            setter = (process.env.NODE_ENV !== 'production')
                ? () => {
                    console.warn('Write operation failed: computed value is readonly');
                }
                : NOOP;
        }
        else {
            getter = getterOrOptions.get;
            setter = getterOrOptions.set;
        }
        const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
        if ((process.env.NODE_ENV !== 'production') && debugOptions && !isSSR) {
            cRef.effect.onTrack = debugOptions.onTrack;
            cRef.effect.onTrigger = debugOptions.onTrigger;
        }
        return cRef;
    }

    class CheckType {
      static getType(v) {
        return Object.prototype.toString.call(v);
      }
      static isNumber(v) {
        return CheckType.getType(v) === "[object Number]";
      }
      static isString(v) {
        return CheckType.getType(v) === "[object String]";
      }
      static isBoolean(v) {
        return CheckType.getType(v) === "[object Boolean]";
      }
      static isObject(v) {
        return CheckType.getType(v) === "[object Object]";
      }
      static isArray(v) {
        return CheckType.getType(v) === "[object Array]";
      }
      static isEmptyObj(v) {
        return CheckType.isObject(v) && !Object.keys(v).length;
      }
      static isEmptyArr(v) {
        return CheckType.isArray(v) && !v.length;
      }
      static isFunction(v) {
        return CheckType.getType(v) === "[object Function]";
      }
      // 不为 undefined && null
      static isExist(v) {
        return v !== void 0 && v !== null;
      }
      static isDate(v) {
        return CheckType.getType(v) === "[object Date]";
      }
    }

    /**
     * Utility module to work with key-value stores.
     *
     * @module map
     */

    /**
     * Creates a new Map instance.
     *
     * @function
     * @return {Map<any, any>}
     *
     * @function
     */
    const create$5 = () => new Map();

    /**
     * Copy a Map object into a fresh Map object.
     *
     * @function
     * @template X,Y
     * @param {Map<X,Y>} m
     * @return {Map<X,Y>}
     */
    const copy = m => {
      const r = create$5();
      m.forEach((v, k) => { r.set(k, v); });
      return r
    };

    /**
     * Get map property. Create T if property is undefined and set T on map.
     *
     * ```js
     * const listeners = map.setIfUndefined(events, 'eventName', set.create)
     * listeners.add(listener)
     * ```
     *
     * @function
     * @template V,K
     * @template {Map<K,V>} MAP
     * @param {MAP} map
     * @param {K} key
     * @param {function():V} createT
     * @return {V}
     */
    const setIfUndefined = (map, key, createT) => {
      let set = map.get(key);
      if (set === undefined) {
        map.set(key, set = createT());
      }
      return set
    };

    /**
     * Creates an Array and populates it with the content of all key-value pairs using the `f(value, key)` function.
     *
     * @function
     * @template K
     * @template V
     * @template R
     * @param {Map<K,V>} m
     * @param {function(V,K):R} f
     * @return {Array<R>}
     */
    const map$1 = (m, f) => {
      const res = [];
      for (const [key, value] of m) {
        res.push(f(value, key));
      }
      return res
    };

    /**
     * Tests whether any key-value pairs pass the test implemented by `f(value, key)`.
     *
     * @todo should rename to some - similarly to Array.some
     *
     * @function
     * @template K
     * @template V
     * @param {Map<K,V>} m
     * @param {function(V,K):boolean} f
     * @return {boolean}
     */
    const any = (m, f) => {
      for (const [key, value] of m) {
        if (f(value, key)) {
          return true
        }
      }
      return false
    };

    /**
     * Utility module to work with sets.
     *
     * @module set
     */

    const create$4 = () => new Set();

    /**
     * Utility module to work with Arrays.
     *
     * @module array
     */

    /**
     * Return the last element of an array. The element must exist
     *
     * @template L
     * @param {ArrayLike<L>} arr
     * @return {L}
     */
    const last = arr => arr[arr.length - 1];

    /**
     * Append elements from src to dest
     *
     * @template M
     * @param {Array<M>} dest
     * @param {Array<M>} src
     */
    const appendTo = (dest, src) => {
      for (let i = 0; i < src.length; i++) {
        dest.push(src[i]);
      }
    };

    /**
     * Transforms something array-like to an actual Array.
     *
     * @function
     * @template T
     * @param {ArrayLike<T>|Iterable<T>} arraylike
     * @return {T}
     */
    const from = Array.from;

    const isArray = Array.isArray;

    /**
     * Observable class prototype.
     *
     * @module observable
     */

    /**
     * Handles named events.
     *
     * @template N
     */
    class Observable {
      constructor () {
        /**
         * Some desc.
         * @type {Map<N, any>}
         */
        this._observers = create$5();
      }

      /**
       * @param {N} name
       * @param {function} f
       */
      on (name, f) {
        setIfUndefined(this._observers, name, create$4).add(f);
      }

      /**
       * @param {N} name
       * @param {function} f
       */
      once (name, f) {
        /**
         * @param  {...any} args
         */
        const _f = (...args) => {
          this.off(name, _f);
          f(...args);
        };
        this.on(name, _f);
      }

      /**
       * @param {N} name
       * @param {function} f
       */
      off (name, f) {
        const observers = this._observers.get(name);
        if (observers !== undefined) {
          observers.delete(f);
          if (observers.size === 0) {
            this._observers.delete(name);
          }
        }
      }

      /**
       * Emit a named event. All registered event listeners that listen to the
       * specified name will receive the event.
       *
       * @todo This should catch exceptions
       *
       * @param {N} name The event name.
       * @param {Array<any>} args The arguments that are applied to the event listener.
       */
      emit (name, args) {
        // copy all listeners to an array first to make sure that no event is emitted to listeners that are subscribed while the event handler is called.
        return from((this._observers.get(name) || create$5()).values()).forEach(f => f(...args))
      }

      destroy () {
        this._observers = create$5();
      }
    }

    /**
     * Common Math expressions.
     *
     * @module math
     */

    const floor = Math.floor;
    const abs = Math.abs;

    /**
     * @function
     * @param {number} a
     * @param {number} b
     * @return {number} The smaller element of a and b
     */
    const min = (a, b) => a < b ? a : b;

    /**
     * @function
     * @param {number} a
     * @param {number} b
     * @return {number} The bigger element of a and b
     */
    const max = (a, b) => a > b ? a : b;

    const pow = Math.pow;

    /**
     * @param {number} n
     * @return {boolean} Wether n is negative. This function also differentiates between -0 and +0
     */
    const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0;

    /**
     * Utility module to work with strings.
     *
     * @module string
     */

    const fromCharCode = String.fromCharCode;

    /**
     * @param {string} s
     * @return {string}
     */
    const toLowerCase = s => s.toLowerCase();

    const trimLeftRegex = /^\s*/g;

    /**
     * @param {string} s
     * @return {string}
     */
    const trimLeft = s => s.replace(trimLeftRegex, '');

    const fromCamelCaseRegex = /([A-Z])/g;

    /**
     * @param {string} s
     * @param {string} separator
     * @return {string}
     */
    const fromCamelCase = (s, separator) => trimLeft(s.replace(fromCamelCaseRegex, match => `${separator}${toLowerCase(match)}`));

    /**
     * @param {string} str
     * @return {Uint8Array}
     */
    const _encodeUtf8Polyfill = str => {
      const encodedString = unescape(encodeURIComponent(str));
      const len = encodedString.length;
      const buf = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
      }
      return buf
    };

    /* c8 ignore next */
    const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);

    /**
     * @param {string} str
     * @return {Uint8Array}
     */
    const _encodeUtf8Native = str => utf8TextEncoder.encode(str);

    /**
     * @param {string} str
     * @return {Uint8Array}
     */
    /* c8 ignore next */
    const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;

    /* c8 ignore next */
    let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });

    /* c8 ignore start */
    if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
      // Safari doesn't handle BOM correctly.
      // This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
      // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
      // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
      // Another issue is that from then on no BOM chars are recognized anymore
      /* c8 ignore next */
      utf8TextDecoder = null;
    }

    /**
     * Often used conditions.
     *
     * @module conditions
     */

    /**
     * @template T
     * @param {T|null|undefined} v
     * @return {T|null}
     */
    /* c8 ignore next */
    const undefinedToNull = v => v === undefined ? null : v;

    /* eslint-env browser */

    /**
     * Isomorphic variable storage.
     *
     * Uses LocalStorage in the browser and falls back to in-memory storage.
     *
     * @module storage
     */

    /* c8 ignore start */
    class VarStoragePolyfill {
      constructor () {
        this.map = new Map();
      }

      /**
       * @param {string} key
       * @param {any} newValue
       */
      setItem (key, newValue) {
        this.map.set(key, newValue);
      }

      /**
       * @param {string} key
       */
      getItem (key) {
        return this.map.get(key)
      }
    }
    /* c8 ignore stop */

    /**
     * @type {any}
     */
    let _localStorage = new VarStoragePolyfill();
    let usePolyfill = true;

    /* c8 ignore start */
    try {
      // if the same-origin rule is violated, accessing localStorage might thrown an error
      if (typeof localStorage !== 'undefined') {
        _localStorage = localStorage;
        usePolyfill = false;
      }
    } catch (e) { }
    /* c8 ignore stop */

    /**
     * This is basically localStorage in browser, or a polyfill in nodejs
     */
    /* c8 ignore next */
    const varStorage = _localStorage;

    /**
     * A polyfill for `addEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
     *
     * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
     * @function
     */
    /* c8 ignore next */
    const onChange = eventHandler => usePolyfill || addEventListener('storage', /** @type {any} */ (eventHandler));

    /**
     * A polyfill for `removeEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
     *
     * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
     * @function
     */
    /* c8 ignore next */
    const offChange = eventHandler => usePolyfill || removeEventListener('storage', /** @type {any} */ (eventHandler));

    /**
     * Utility functions for working with EcmaScript objects.
     *
     * @module object
     */

    /**
     * Object.assign
     */
    const assign = Object.assign;

    /**
     * @param {Object<string,any>} obj
     */
    const keys = Object.keys;

    /**
     * @todo implement mapToArray & map
     *
     * @template R
     * @param {Object<string,any>} obj
     * @param {function(any,string):R} f
     * @return {Array<R>}
     */
    const map = (obj, f) => {
      const results = [];
      for (const key in obj) {
        results.push(f(obj[key], key));
      }
      return results
    };

    /**
     * @param {Object<string,any>} obj
     * @return {number}
     */
    const length$1 = obj => keys(obj).length;

    /**
     * @param {Object|undefined} obj
     */
    const isEmpty = obj => {
      for (const _k in obj) {
        return false
      }
      return true
    };

    /**
     * @param {Object<string,any>} obj
     * @param {function(any,string):boolean} f
     * @return {boolean}
     */
    const every = (obj, f) => {
      for (const key in obj) {
        if (!f(obj[key], key)) {
          return false
        }
      }
      return true
    };

    /**
     * Calls `Object.prototype.hasOwnProperty`.
     *
     * @param {any} obj
     * @param {string|symbol} key
     * @return {boolean}
     */
    const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);

    /**
     * @param {Object<string,any>} a
     * @param {Object<string,any>} b
     * @return {boolean}
     */
    const equalFlat = (a, b) => a === b || (length$1(a) === length$1(b) && every(a, (val, key) => (val !== undefined || hasProperty(b, key)) && b[key] === val));

    /**
     * Common functions and function call helpers.
     *
     * @module function
     */

    /**
     * Calls all functions in `fs` with args. Only throws after all functions were called.
     *
     * @param {Array<function>} fs
     * @param {Array<any>} args
     */
    const callAll = (fs, args, i = 0) => {
      try {
        for (; i < fs.length; i++) {
          fs[i](...args);
        }
      } finally {
        if (i < fs.length) {
          callAll(fs, args, i + 1);
        }
      }
    };

    /**
     * @template A
     *
     * @param {A} a
     * @return {A}
     */
    const id = a => a;

    /**
     * @template T
     *
     * @param {T} a
     * @param {T} b
     * @return {boolean}
     */
    const equalityStrict = (a, b) => a === b;

    /* c8 ignore start */

    /**
     * @param {any} a
     * @param {any} b
     * @return {boolean}
     */
    const equalityDeep = (a, b) => {
      if (a == null || b == null) {
        return equalityStrict(a, b)
      }
      if (a.constructor !== b.constructor) {
        return false
      }
      if (a === b) {
        return true
      }
      switch (a.constructor) {
        case ArrayBuffer:
          a = new Uint8Array(a);
          b = new Uint8Array(b);
        // eslint-disable-next-line no-fallthrough
        case Uint8Array: {
          if (a.byteLength !== b.byteLength) {
            return false
          }
          for (let i = 0; i < a.length; i++) {
            if (a[i] !== b[i]) {
              return false
            }
          }
          break
        }
        case Set: {
          if (a.size !== b.size) {
            return false
          }
          for (const value of a) {
            if (!b.has(value)) {
              return false
            }
          }
          break
        }
        case Map: {
          if (a.size !== b.size) {
            return false
          }
          for (const key of a.keys()) {
            if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) {
              return false
            }
          }
          break
        }
        case Object:
          if (length$1(a) !== length$1(b)) {
            return false
          }
          for (const key in a) {
            if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) {
              return false
            }
          }
          break
        case Array:
          if (a.length !== b.length) {
            return false
          }
          for (let i = 0; i < a.length; i++) {
            if (!equalityDeep(a[i], b[i])) {
              return false
            }
          }
          break
        default:
          return false
      }
      return true
    };

    /**
     * @template V
     * @template {V} OPTS
     *
     * @param {V} value
     * @param {Array<OPTS>} options
     */
    // @ts-ignore
    const isOneOf = (value, options) => options.includes(value);
    /* c8 ignore stop */

    /**
     * Isomorphic module to work access the environment (query params, env variables).
     *
     * @module map
     */

    /* c8 ignore next */
    // @ts-ignore
    const isNode = typeof process !== 'undefined' && process.release &&
      /node|io\.js/.test(process.release.name);
    /* c8 ignore next */
    const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && !isNode;
    /* c8 ignore next 3 */
    typeof navigator !== 'undefined'
      ? /Mac/.test(navigator.platform)
      : false;

    /**
     * @type {Map<string,string>}
     */
    let params;

    /* c8 ignore start */
    const computeParams = () => {
      if (params === undefined) {
        if (isNode) {
          params = create$5();
          const pargs = process.argv;
          let currParamName = null;
          for (let i = 0; i < pargs.length; i++) {
            const parg = pargs[i];
            if (parg[0] === '-') {
              if (currParamName !== null) {
                params.set(currParamName, '');
              }
              currParamName = parg;
            } else {
              if (currParamName !== null) {
                params.set(currParamName, parg);
                currParamName = null;
              }
            }
          }
          if (currParamName !== null) {
            params.set(currParamName, '');
          }
          // in ReactNative for example this would not be true (unless connected to the Remote Debugger)
        } else if (typeof location === 'object') {
          params = create$5(); // eslint-disable-next-line no-undef
          (location.search || '?').slice(1).split('&').forEach((kv) => {
            if (kv.length !== 0) {
              const [key, value] = kv.split('=');
              params.set(`--${fromCamelCase(key, '-')}`, value);
              params.set(`-${fromCamelCase(key, '-')}`, value);
            }
          });
        } else {
          params = create$5();
        }
      }
      return params
    };
    /* c8 ignore stop */

    /**
     * @param {string} name
     * @return {boolean}
     */
    /* c8 ignore next */
    const hasParam = (name) => computeParams().has(name);

    /**
     * @param {string} name
     * @return {string|null}
     */
    /* c8 ignore next 4 */
    const getVariable = (name) =>
      isNode
        ? undefinedToNull(process.env[name.toUpperCase()])
        : undefinedToNull(varStorage.getItem(name));

    /**
     * @param {string} name
     * @return {boolean}
     */
    /* c8 ignore next 2 */
    const hasConf = (name) =>
      hasParam('--' + name) || getVariable(name) !== null;

    /* c8 ignore next */
    hasConf('production');

    /* c8 ignore next 2 */
    const forceColor = isNode &&
      isOneOf(process.env.FORCE_COLOR, ['true', '1', '2']);

    /* c8 ignore start */
    const supportsColor = !hasParam('no-colors') &&
      (!isNode || process.stdout.isTTY || forceColor) && (
      !isNode || hasParam('color') || forceColor ||
        getVariable('COLORTERM') !== null ||
        (getVariable('TERM') || '').includes('color')
    );
    /* c8 ignore stop */

    /* eslint-env browser */

    /**
     * Binary data constants.
     *
     * @module binary
     */

    /**
     * n-th bit activated.
     *
     * @type {number}
     */
    const BIT1 = 1;
    const BIT2 = 2;
    const BIT3 = 4;
    const BIT4 = 8;
    const BIT6 = 32;
    const BIT7 = 64;
    const BIT8 = 128;
    const BITS5 = 31;
    const BITS6 = 63;
    const BITS7 = 127;
    /**
     * @type {number}
     */
    const BITS31 = 0x7FFFFFFF;

    /**
     * Utility helpers for working with numbers.
     *
     * @module number
     */

    const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;

    /**
     * @module number
     */

    /* c8 ignore next */
    const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && floor(num) === num);

    /**
     * Error helpers.
     *
     * @module error
     */

    /**
     * @param {string} s
     * @return {Error}
     */
    /* c8 ignore next */
    const create$3 = s => new Error(s);

    /**
     * @throws {Error}
     * @return {never}
     */
    /* c8 ignore next 3 */
    const methodUnimplemented = () => {
      throw create$3('Method unimplemented')
    };

    /**
     * @throws {Error}
     * @return {never}
     */
    /* c8 ignore next 3 */
    const unexpectedCase = () => {
      throw create$3('Unexpected case')
    };

    /**
     * Efficient schema-less binary decoding with support for variable length encoding.
     *
     * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
     *
     * Encodes numbers in little-endian order (least to most significant byte order)
     * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
     * which is also used in Protocol Buffers.
     *
     * ```js
     * // encoding step
     * const encoder = encoding.createEncoder()
     * encoding.writeVarUint(encoder, 256)
     * encoding.writeVarString(encoder, 'Hello world!')
     * const buf = encoding.toUint8Array(encoder)
     * ```
     *
     * ```js
     * // decoding step
     * const decoder = decoding.createDecoder(buf)
     * decoding.readVarUint(decoder) // => 256
     * decoding.readVarString(decoder) // => 'Hello world!'
     * decoding.hasContent(decoder) // => false - all data is read
     * ```
     *
     * @module decoding
     */

    const errorUnexpectedEndOfArray = create$3('Unexpected end of array');
    const errorIntegerOutOfRange = create$3('Integer out of Range');

    /**
     * A Decoder handles the decoding of an Uint8Array.
     */
    class Decoder {
      /**
       * @param {Uint8Array} uint8Array Binary data to decode
       */
      constructor (uint8Array) {
        /**
         * Decoding target.
         *
         * @type {Uint8Array}
         */
        this.arr = uint8Array;
        /**
         * Current decoding position.
         *
         * @type {number}
         */
        this.pos = 0;
      }
    }

    /**
     * @function
     * @param {Uint8Array} uint8Array
     * @return {Decoder}
     */
    const createDecoder = uint8Array => new Decoder(uint8Array);

    /**
     * @function
     * @param {Decoder} decoder
     * @return {boolean}
     */
    const hasContent = decoder => decoder.pos !== decoder.arr.length;

    /**
     * Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
     *
     * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
     *            Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
     *
     * @function
     * @param {Decoder} decoder The decoder instance
     * @param {number} len The length of bytes to read
     * @return {Uint8Array}
     */
    const readUint8Array = (decoder, len) => {
      const view = createUint8ArrayViewFromArrayBuffer(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
      decoder.pos += len;
      return view
    };

    /**
     * Read variable length Uint8Array.
     *
     * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
     *            Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
     *
     * @function
     * @param {Decoder} decoder
     * @return {Uint8Array}
     */
    const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));

    /**
     * Read one byte as unsigned integer.
     * @function
     * @param {Decoder} decoder The decoder instance
     * @return {number} Unsigned 8-bit integer
     */
    const readUint8 = decoder => decoder.arr[decoder.pos++];

    /**
     * Read unsigned integer (32bit) with variable length.
     * 1/8th of the storage is used as encoding overhead.
     *  * numbers < 2^7 is stored in one bytlength
     *  * numbers < 2^14 is stored in two bylength
     *
     * @function
     * @param {Decoder} decoder
     * @return {number} An unsigned integer.length
     */
    const readVarUint = decoder => {
      let num = 0;
      let mult = 1;
      const len = decoder.arr.length;
      while (decoder.pos < len) {
        const r = decoder.arr[decoder.pos++];
        // num = num | ((r & binary.BITS7) << len)
        num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
        mult *= 128; // next iteration, shift 7 "more" to the left
        if (r < BIT8) {
          return num
        }
        /* c8 ignore start */
        if (num > MAX_SAFE_INTEGER) {
          throw errorIntegerOutOfRange
        }
        /* c8 ignore stop */
      }
      throw errorUnexpectedEndOfArray
    };

    /**
     * Read signed integer (32bit) with variable length.
     * 1/8th of the storage is used as encoding overhead.
     *  * numbers < 2^7 is stored in one bytlength
     *  * numbers < 2^14 is stored in two bylength
     * @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
     *
     * @function
     * @param {Decoder} decoder
     * @return {number} An unsigned integer.length
     */
    const readVarInt = decoder => {
      let r = decoder.arr[decoder.pos++];
      let num = r & BITS6;
      let mult = 64;
      const sign = (r & BIT7) > 0 ? -1 : 1;
      if ((r & BIT8) === 0) {
        // don't continue reading
        return sign * num
      }
      const len = decoder.arr.length;
      while (decoder.pos < len) {
        r = decoder.arr[decoder.pos++];
        // num = num | ((r & binary.BITS7) << len)
        num = num + (r & BITS7) * mult;
        mult *= 128;
        if (r < BIT8) {
          return sign * num
        }
        /* c8 ignore start */
        if (num > MAX_SAFE_INTEGER) {
          throw errorIntegerOutOfRange
        }
        /* c8 ignore stop */
      }
      throw errorUnexpectedEndOfArray
    };

    /**
     * We don't test this function anymore as we use native decoding/encoding by default now.
     * Better not modify this anymore..
     *
     * Transforming utf8 to a string is pretty expensive. The code performs 10x better
     * when String.fromCodePoint is fed with all characters as arguments.
     * But most environments have a maximum number of arguments per functions.
     * For effiency reasons we apply a maximum of 10000 characters at once.
     *
     * @function
     * @param {Decoder} decoder
     * @return {String} The read String.
     */
    /* c8 ignore start */
    const _readVarStringPolyfill = decoder => {
      let remainingLen = readVarUint(decoder);
      if (remainingLen === 0) {
        return ''
      } else {
        let encodedString = String.fromCodePoint(readUint8(decoder)); // remember to decrease remainingLen
        if (--remainingLen < 100) { // do not create a Uint8Array for small strings
          while (remainingLen--) {
            encodedString += String.fromCodePoint(readUint8(decoder));
          }
        } else {
          while (remainingLen > 0) {
            const nextLen = remainingLen < 10000 ? remainingLen : 10000;
            // this is dangerous, we create a fresh array view from the existing buffer
            const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
            decoder.pos += nextLen;
            // Starting with ES5.1 we can supply a generic array-like object as arguments
            encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
            remainingLen -= nextLen;
          }
        }
        return decodeURIComponent(escape(encodedString))
      }
    };
    /* c8 ignore stop */

    /**
     * @function
     * @param {Decoder} decoder
     * @return {String} The read String
     */
    const _readVarStringNative = decoder =>
      /** @type any */ (utf8TextDecoder).decode(readVarUint8Array(decoder));

    /**
     * Read string of variable length
     * * varUint is used to store the length of the string
     *
     * @function
     * @param {Decoder} decoder
     * @return {String} The read String
     *
     */
    /* c8 ignore next */
    const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;

    /**
     * @param {Decoder} decoder
     * @param {number} len
     * @return {DataView}
     */
    const readFromDataView = (decoder, len) => {
      const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len);
      decoder.pos += len;
      return dv
    };

    /**
     * @param {Decoder} decoder
     */
    const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false);

    /**
     * @param {Decoder} decoder
     */
    const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false);

    /**
     * @param {Decoder} decoder
     */
    const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false);

    /**
     * @type {Array<function(Decoder):any>}
     */
    const readAnyLookupTable = [
      decoder => undefined, // CASE 127: undefined
      decoder => null, // CASE 126: null
      readVarInt, // CASE 125: integer
      readFloat32, // CASE 124: float32
      readFloat64, // CASE 123: float64
      readBigInt64, // CASE 122: bigint
      decoder => false, // CASE 121: boolean (false)
      decoder => true, // CASE 120: boolean (true)
      readVarString, // CASE 119: string
      decoder => { // CASE 118: object<string,any>
        const len = readVarUint(decoder);
        /**
         * @type {Object<string,any>}
         */
        const obj = {};
        for (let i = 0; i < len; i++) {
          const key = readVarString(decoder);
          obj[key] = readAny(decoder);
        }
        return obj
      },
      decoder => { // CASE 117: array<any>
        const len = readVarUint(decoder);
        const arr = [];
        for (let i = 0; i < len; i++) {
          arr.push(readAny(decoder));
        }
        return arr
      },
      readVarUint8Array // CASE 116: Uint8Array
    ];

    /**
     * @param {Decoder} decoder
     */
    const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder);

    /**
     * T must not be null.
     *
     * @template T
     */
    class RleDecoder extends Decoder {
      /**
       * @param {Uint8Array} uint8Array
       * @param {function(Decoder):T} reader
       */
      constructor (uint8Array, reader) {
        super(uint8Array);
        /**
         * The reader
         */
        this.reader = reader;
        /**
         * Current state
         * @type {T|null}
         */
        this.s = null;
        this.count = 0;
      }

      read () {
        if (this.count === 0) {
          this.s = this.reader(this);
          if (hasContent(this)) {
            this.count = readVarUint(this) + 1; // see encoder implementation for the reason why this is incremented
          } else {
            this.count = -1; // read the current value forever
          }
        }
        this.count--;
        return /** @type {T} */ (this.s)
      }
    }

    class UintOptRleDecoder extends Decoder {
      /**
       * @param {Uint8Array} uint8Array
       */
      constructor (uint8Array) {
        super(uint8Array);
        /**
         * @type {number}
         */
        this.s = 0;
        this.count = 0;
      }

      read () {
        if (this.count === 0) {
          this.s = readVarInt(this);
          // if the sign is negative, we read the count too, otherwise count is 1
          const isNegative = isNegativeZero(this.s);
          this.count = 1;
          if (isNegative) {
            this.s = -this.s;
            this.count = readVarUint(this) + 2;
          }
        }
        this.count--;
        return /** @type {number} */ (this.s)
      }
    }

    class IntDiffOptRleDecoder extends Decoder {
      /**
       * @param {Uint8Array} uint8Array
       */
      constructor (uint8Array) {
        super(uint8Array);
        /**
         * @type {number}
         */
        this.s = 0;
        this.count = 0;
        this.diff = 0;
      }

      /**
       * @return {number}
       */
      read () {
        if (this.count === 0) {
          const diff = readVarInt(this);
          // if the first bit is set, we read more data
          const hasCount = diff & 1;
          this.diff = floor(diff / 2); // shift >> 1
          this.count = 1;
          if (hasCount) {
            this.count = readVarUint(this) + 2;
          }
        }
        this.s += this.diff;
        this.count--;
        return this.s
      }
    }

    class StringDecoder {
      /**
       * @param {Uint8Array} uint8Array
       */
      constructor (uint8Array) {
        this.decoder = new UintOptRleDecoder(uint8Array);
        this.str = readVarString(this.decoder);
        /**
         * @type {number}
         */
        this.spos = 0;
      }

      /**
       * @return {string}
       */
      read () {
        const end = this.spos + this.decoder.read();
        const res = this.str.slice(this.spos, end);
        this.spos = end;
        return res
      }
    }

    /**
     * Utility functions to work with buffers (Uint8Array).
     *
     * @module buffer
     */

    /**
     * @param {number} len
     */
    const createUint8ArrayFromLen = len => new Uint8Array(len);

    /**
     * Create Uint8Array with initial content from buffer
     *
     * @param {ArrayBuffer} buffer
     * @param {number} byteOffset
     * @param {number} length
     */
    const createUint8ArrayViewFromArrayBuffer = (buffer, byteOffset, length) => new Uint8Array(buffer, byteOffset, length);

    /**
     * Create Uint8Array with initial content from buffer
     *
     * @param {ArrayBuffer} buffer
     */
    const createUint8ArrayFromArrayBuffer = buffer => new Uint8Array(buffer);

    /* c8 ignore start */
    /**
     * @param {Uint8Array} bytes
     * @return {string}
     */
    const toBase64Browser = bytes => {
      let s = '';
      for (let i = 0; i < bytes.byteLength; i++) {
        s += fromCharCode(bytes[i]);
      }
      // eslint-disable-next-line no-undef
      return btoa(s)
    };
    /* c8 ignore stop */

    /**
     * @param {Uint8Array} bytes
     * @return {string}
     */
    const toBase64Node = bytes => Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');

    /* c8 ignore start */
    /**
     * @param {string} s
     * @return {Uint8Array}
     */
    const fromBase64Browser = s => {
      // eslint-disable-next-line no-undef
      const a = atob(s);
      const bytes = createUint8ArrayFromLen(a.length);
      for (let i = 0; i < a.length; i++) {
        bytes[i] = a.charCodeAt(i);
      }
      return bytes
    };
    /* c8 ignore stop */

    /**
     * @param {string} s
     */
    const fromBase64Node = s => {
      const buf = Buffer.from(s, 'base64');
      return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
    };

    /* c8 ignore next */
    const toBase64 = isBrowser ? toBase64Browser : toBase64Node;

    /* c8 ignore next */
    const fromBase64 = isBrowser ? fromBase64Browser : fromBase64Node;

    /**
     * Copy the content of an Uint8Array view to a new ArrayBuffer.
     *
     * @param {Uint8Array} uint8Array
     * @return {Uint8Array}
     */
    const copyUint8Array = uint8Array => {
      const newBuf = createUint8ArrayFromLen(uint8Array.byteLength);
      newBuf.set(uint8Array);
      return newBuf
    };

    /**
     * Efficient schema-less binary encoding with support for variable length encoding.
     *
     * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
     *
     * Encodes numbers in little-endian order (least to most significant byte order)
     * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
     * which is also used in Protocol Buffers.
     *
     * ```js
     * // encoding step
     * const encoder = encoding.createEncoder()
     * encoding.writeVarUint(encoder, 256)
     * encoding.writeVarString(encoder, 'Hello world!')
     * const buf = encoding.toUint8Array(encoder)
     * ```
     *
     * ```js
     * // decoding step
     * const decoder = decoding.createDecoder(buf)
     * decoding.readVarUint(decoder) // => 256
     * decoding.readVarString(decoder) // => 'Hello world!'
     * decoding.hasContent(decoder) // => false - all data is read
     * ```
     *
     * @module encoding
     */

    /**
     * A BinaryEncoder handles the encoding to an Uint8Array.
     */
    class Encoder {
      constructor () {
        this.cpos = 0;
        this.cbuf = new Uint8Array(100);
        /**
         * @type {Array<Uint8Array>}
         */
        this.bufs = [];
      }
    }

    /**
     * @function
     * @return {Encoder}
     */
    const createEncoder = () => new Encoder();

    /**
     * The current length of the encoded data.
     *
     * @function
     * @param {Encoder} encoder
     * @return {number}
     */
    const length = encoder => {
      let len = encoder.cpos;
      for (let i = 0; i < encoder.bufs.length; i++) {
        len += encoder.bufs[i].length;
      }
      return len
    };

    /**
     * Transform to Uint8Array.
     *
     * @function
     * @param {Encoder} encoder
     * @return {Uint8Array} The created ArrayBuffer.
     */
    const toUint8Array = encoder => {
      const uint8arr = new Uint8Array(length(encoder));
      let curPos = 0;
      for (let i = 0; i < encoder.bufs.length; i++) {
        const d = encoder.bufs[i];
        uint8arr.set(d, curPos);
        curPos += d.length;
      }
      uint8arr.set(createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
      return uint8arr
    };

    /**
     * Verify that it is possible to write `len` bytes wtihout checking. If
     * necessary, a new Buffer with the required length is attached.
     *
     * @param {Encoder} encoder
     * @param {number} len
     */
    const verifyLen = (encoder, len) => {
      const bufferLen = encoder.cbuf.length;
      if (bufferLen - encoder.cpos < len) {
        encoder.bufs.push(createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos));
        encoder.cbuf = new Uint8Array(max(bufferLen, len) * 2);
        encoder.cpos = 0;
      }
    };

    /**
     * Write one byte to the encoder.
     *
     * @function
     * @param {Encoder} encoder
     * @param {number} num The byte that is to be encoded.
     */
    const write = (encoder, num) => {
      const bufferLen = encoder.cbuf.length;
      if (encoder.cpos === bufferLen) {
        encoder.bufs.push(encoder.cbuf);
        encoder.cbuf = new Uint8Array(bufferLen * 2);
        encoder.cpos = 0;
      }
      encoder.cbuf[encoder.cpos++] = num;
    };

    /**
     * Write one byte as an unsigned integer.
     *
     * @function
     * @param {Encoder} encoder
     * @param {number} num The number that is to be encoded.
     */
    const writeUint8 = write;

    /**
     * Write a variable length unsigned integer. Max encodable integer is 2^53.
     *
     * @function
     * @param {Encoder} encoder
     * @param {number} num The number that is to be encoded.
     */
    const writeVarUint = (encoder, num) => {
      while (num > BITS7) {
        write(encoder, BIT8 | (BITS7 & num));
        num = floor(num / 128); // shift >>> 7
      }
      write(encoder, BITS7 & num);
    };

    /**
     * Write a variable length integer.
     *
     * We use the 7th bit instead for signaling that this is a negative number.
     *
     * @function
     * @param {Encoder} encoder
     * @param {number} num The number that is to be encoded.
     */
    const writeVarInt = (encoder, num) => {
      const isNegative = isNegativeZero(num);
      if (isNegative) {
        num = -num;
      }
      //             |- whether to continue reading         |- whether is negative     |- number
      write(encoder, (num > BITS6 ? BIT8 : 0) | (isNegative ? BIT7 : 0) | (BITS6 & num));
      num = floor(num / 64); // shift >>> 6
      // We don't need to consider the case of num === 0 so we can use a different
      // pattern here than above.
      while (num > 0) {
        write(encoder, (num > BITS7 ? BIT8 : 0) | (BITS7 & num));
        num = floor(num / 128); // shift >>> 7
      }
    };

    /**
     * A cache to store strings temporarily
     */
    const _strBuffer = new Uint8Array(30000);
    const _maxStrBSize = _strBuffer.length / 3;

    /**
     * Write a variable length string.
     *
     * @function
     * @param {Encoder} encoder
     * @param {String} str The string that is to be encoded.
     */
    const _writeVarStringNative = (encoder, str) => {
      if (str.length < _maxStrBSize) {
        // We can encode the string into the existing buffer
        /* c8 ignore next */
        const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
        writeVarUint(encoder, written);
        for (let i = 0; i < written; i++) {
          write(encoder, _strBuffer[i]);
        }
      } else {
        writeVarUint8Array(encoder, encodeUtf8(str));
      }
    };

    /**
     * Write a variable length string.
     *
     * @function
     * @param {Encoder} encoder
     * @param {String} str The string that is to be encoded.
     */
    const _writeVarStringPolyfill = (encoder, str) => {
      const encodedString = unescape(encodeURIComponent(str));
      const len = encodedString.length;
      writeVarUint(encoder, len);
      for (let i = 0; i < len; i++) {
        write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
      }
    };

    /**
     * Write a variable length string.
     *
     * @function
     * @param {Encoder} encoder
     * @param {String} str The string that is to be encoded.
     */
    /* c8 ignore next */
    const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;

    /**
     * Append fixed-length Uint8Array to the encoder.
     *
     * @function
     * @param {Encoder} encoder
     * @param {Uint8Array} uint8Array
     */
    const writeUint8Array = (encoder, uint8Array) => {
      const bufferLen = encoder.cbuf.length;
      const cpos = encoder.cpos;
      const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
      const rightCopyLen = uint8Array.length - leftCopyLen;
      encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
      encoder.cpos += leftCopyLen;
      if (rightCopyLen > 0) {
        // Still something to write, write right half..
        // Append new buffer
        encoder.bufs.push(encoder.cbuf);
        // must have at least size of remaining buffer
        encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
        // copy array
        encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
        encoder.cpos = rightCopyLen;
      }
    };

    /**
     * Append an Uint8Array to Encoder.
     *
     * @function
     * @param {Encoder} encoder
     * @param {Uint8Array} uint8Array
     */
    const writeVarUint8Array = (encoder, uint8Array) => {
      writeVarUint(encoder, uint8Array.byteLength);
      writeUint8Array(encoder, uint8Array);
    };

    /**
     * Create an DataView of the next `len` bytes. Use it to write data after
     * calling this function.
     *
     * ```js
     * // write float32 using DataView
     * const dv = writeOnDataView(encoder, 4)
     * dv.setFloat32(0, 1.1)
     * // read float32 using DataView
     * const dv = readFromDataView(encoder, 4)
     * dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result)
     * ```
     *
     * @param {Encoder} encoder
     * @param {number} len
     * @return {DataView}
     */
    const writeOnDataView = (encoder, len) => {
      verifyLen(encoder, len);
      const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len);
      encoder.cpos += len;
      return dview
    };

    /**
     * @param {Encoder} encoder
     * @param {number} num
     */
    const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false);

    /**
     * @param {Encoder} encoder
     * @param {number} num
     */
    const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false);

    /**
     * @param {Encoder} encoder
     * @param {bigint} num
     */
    const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false);

    const floatTestBed = new DataView(new ArrayBuffer(4));
    /**
     * Check if a number can be encoded as a 32 bit float.
     *
     * @param {number} num
     * @return {boolean}
     */
    const isFloat32 = num => {
      floatTestBed.setFloat32(0, num);
      return floatTestBed.getFloat32(0) === num
    };

    /**
     * Encode data with efficient binary format.
     *
     * Differences to JSON:
     * • Transforms data to a binary format (not to a string)
     * • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON)
     * • Numbers are efficiently encoded either as a variable length integer, as a
     *   32 bit float, as a 64 bit float, or as a 64 bit bigint.
     *
     * Encoding table:
     *
     * | Data Type           | Prefix   | Encoding Method    | Comment |
     * | ------------------- | -------- | ------------------ | ------- |
     * | undefined           | 127      |                    | Functions, symbol, and everything that cannot be identified is encoded as undefined |
     * | null                | 126      |                    | |
     * | integer             | 125      | writeVarInt        | Only encodes 32 bit signed integers |
     * | float32             | 124      | writeFloat32       | |
     * | float64             | 123      | writeFloat64       | |
     * | bigint              | 122      | writeBigInt64      | |
     * | boolean (false)     | 121      |                    | True and false are different data types so we save the following byte |
     * | boolean (true)      | 120      |                    | - 0b01111000 so the last bit determines whether true or false |
     * | string              | 119      | writeVarString     | |
     * | object<string,any>  | 118      | custom             | Writes {length} then {length} key-value pairs |
     * | array<any>          | 117      | custom             | Writes {length} then {length} json values |
     * | Uint8Array          | 116      | writeVarUint8Array | We use Uint8Array for any kind of binary data |
     *
     * Reasons for the decreasing prefix:
     * We need the first bit for extendability (later we may want to encode the
     * prefix with writeVarUint). The remaining 7 bits are divided as follows:
     * [0-30]   the beginning of the data range is used for custom purposes
     *          (defined by the function that uses this library)
     * [31-127] the end of the data range is used for data encoding by
     *          lib0/encoding.js
     *
     * @param {Encoder} encoder
     * @param {undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array} data
     */
    const writeAny = (encoder, data) => {
      switch (typeof data) {
        case 'string':
          // TYPE 119: STRING
          write(encoder, 119);
          writeVarString(encoder, data);
          break
        case 'number':
          if (isInteger(data) && abs(data) <= BITS31) {
            // TYPE 125: INTEGER
            write(encoder, 125);
            writeVarInt(encoder, data);
          } else if (isFloat32(data)) {
            // TYPE 124: FLOAT32
            write(encoder, 124);
            writeFloat32(encoder, data);
          } else {
            // TYPE 123: FLOAT64
            write(encoder, 123);
            writeFloat64(encoder, data);
          }
          break
        case 'bigint':
          // TYPE 122: BigInt
          write(encoder, 122);
          writeBigInt64(encoder, data);
          break
        case 'object':
          if (data === null) {
            // TYPE 126: null
            write(encoder, 126);
          } else if (isArray(data)) {
            // TYPE 117: Array
            write(encoder, 117);
            writeVarUint(encoder, data.length);
            for (let i = 0; i < data.length; i++) {
              writeAny(encoder, data[i]);
            }
          } else if (data instanceof Uint8Array) {
            // TYPE 116: ArrayBuffer
            write(encoder, 116);
            writeVarUint8Array(encoder, data);
          } else {
            // TYPE 118: Object
            write(encoder, 118);
            const keys = Object.keys(data);
            writeVarUint(encoder, keys.length);
            for (let i = 0; i < keys.length; i++) {
              const key = keys[i];
              writeVarString(encoder, key);
              writeAny(encoder, data[key]);
            }
          }
          break
        case 'boolean':
          // TYPE 120/121: boolean (true/false)
          write(encoder, data ? 120 : 121);
          break
        default:
          // TYPE 127: undefined
          write(encoder, 127);
      }
    };

    /**
     * Now come a few stateful encoder that have their own classes.
     */

    /**
     * Basic Run Length Encoder - a basic compression implementation.
     *
     * Encodes [1,1,1,7] to [1,3,7,1] (3 times 1, 1 time 7). This encoder might do more harm than good if there are a lot of values that are not repeated.
     *
     * It was originally used for image compression. Cool .. article http://csbruce.com/cbm/transactor/pdfs/trans_v7_i06.pdf
     *
     * @note T must not be null!
     *
     * @template T
     */
    class RleEncoder extends Encoder {
      /**
       * @param {function(Encoder, T):void} writer
       */
      constructor (writer) {
        super();
        /**
         * The writer
         */
        this.w = writer;
        /**
         * Current state
         * @type {T|null}
         */
        this.s = null;
        this.count = 0;
      }

      /**
       * @param {T} v
       */
      write (v) {
        if (this.s === v) {
          this.count++;
        } else {
          if (this.count > 0) {
            // flush counter, unless this is the first value (count = 0)
            writeVarUint(this, this.count - 1); // since count is always > 0, we can decrement by one. non-standard encoding ftw
          }
          this.count = 1;
          // write first value
          this.w(this, v);
          this.s = v;
        }
      }
    }

    /**
     * @param {UintOptRleEncoder} encoder
     */
    const flushUintOptRleEncoder = encoder => {
      if (encoder.count > 0) {
        // flush counter, unless this is the first value (count = 0)
        // case 1: just a single value. set sign to positive
        // case 2: write several values. set sign to negative to indicate that there is a length coming
        writeVarInt(encoder.encoder, encoder.count === 1 ? encoder.s : -encoder.s);
        if (encoder.count > 1) {
          writeVarUint(encoder.encoder, encoder.count - 2); // since count is always > 1, we can decrement by one. non-standard encoding ftw
        }
      }
    };

    /**
     * Optimized Rle encoder that does not suffer from the mentioned problem of the basic Rle encoder.
     *
     * Internally uses VarInt encoder to write unsigned integers. If the input occurs multiple times, we write
     * write it as a negative number. The UintOptRleDecoder then understands that it needs to read a count.
     *
     * Encodes [1,2,3,3,3] as [1,2,-3,3] (once 1, once 2, three times 3)
     */
    class UintOptRleEncoder {
      constructor () {
        this.encoder = new Encoder();
        /**
         * @type {number}
         */
        this.s = 0;
        this.count = 0;
      }

      /**
       * @param {number} v
       */
      write (v) {
        if (this.s === v) {
          this.count++;
        } else {
          flushUintOptRleEncoder(this);
          this.count = 1;
          this.s = v;
        }
      }

      toUint8Array () {
        flushUintOptRleEncoder(this);
        return toUint8Array(this.encoder)
      }
    }

    /**
     * @param {IntDiffOptRleEncoder} encoder
     */
    const flushIntDiffOptRleEncoder = encoder => {
      if (encoder.count > 0) {
        //          31 bit making up the diff | wether to write the counter
        // const encodedDiff = encoder.diff << 1 | (encoder.count === 1 ? 0 : 1)
        const encodedDiff = encoder.diff * 2 + (encoder.count === 1 ? 0 : 1);
        // flush counter, unless this is the first value (count = 0)
        // case 1: just a single value. set first bit to positive
        // case 2: write several values. set first bit to negative to indicate that there is a length coming
        writeVarInt(encoder.encoder, encodedDiff);
        if (encoder.count > 1) {
          writeVarUint(encoder.encoder, encoder.count - 2); // since count is always > 1, we can decrement by one. non-standard encoding ftw
        }
      }
    };

    /**
     * A combination of the IntDiffEncoder and the UintOptRleEncoder.
     *
     * The count approach is similar to the UintDiffOptRleEncoder, but instead of using the negative bitflag, it encodes
     * in the LSB whether a count is to be read. Therefore this Encoder only supports 31 bit integers!
     *
     * Encodes [1, 2, 3, 2] as [3, 1, 6, -1] (more specifically [(1 << 1) | 1, (3 << 0) | 0, -1])
     *
     * Internally uses variable length encoding. Contrary to normal UintVar encoding, the first byte contains:
     * * 1 bit that denotes whether the next value is a count (LSB)
     * * 1 bit that denotes whether this value is negative (MSB - 1)
     * * 1 bit that denotes whether to continue reading the variable length integer (MSB)
     *
     * Therefore, only five bits remain to encode diff ranges.
     *
     * Use this Encoder only when appropriate. In most cases, this is probably a bad idea.
     */
    class IntDiffOptRleEncoder {
      constructor () {
        this.encoder = new Encoder();
        /**
         * @type {number}
         */
        this.s = 0;
        this.count = 0;
        this.diff = 0;
      }

      /**
       * @param {number} v
       */
      write (v) {
        if (this.diff === v - this.s) {
          this.s = v;
          this.count++;
        } else {
          flushIntDiffOptRleEncoder(this);
          this.count = 1;
          this.diff = v - this.s;
          this.s = v;
        }
      }

      toUint8Array () {
        flushIntDiffOptRleEncoder(this);
        return toUint8Array(this.encoder)
      }
    }

    /**
     * Optimized String Encoder.
     *
     * Encoding many small strings in a simple Encoder is not very efficient. The function call to decode a string takes some time and creates references that must be eventually deleted.
     * In practice, when decoding several million small strings, the GC will kick in more and more often to collect orphaned string objects (or maybe there is another reason?).
     *
     * This string encoder solves the above problem. All strings are concatenated and written as a single string using a single encoding call.
     *
     * The lengths are encoded using a UintOptRleEncoder.
     */
    class StringEncoder {
      constructor () {
        /**
         * @type {Array<string>}
         */
        this.sarr = [];
        this.s = '';
        this.lensE = new UintOptRleEncoder();
      }

      /**
       * @param {string} string
       */
      write (string) {
        this.s += string;
        if (this.s.length > 19) {
          this.sarr.push(this.s);
          this.s = '';
        }
        this.lensE.write(string.length);
      }

      toUint8Array () {
        const encoder = new Encoder();
        this.sarr.push(this.s);
        this.s = '';
        writeVarString(encoder, this.sarr.join(''));
        writeUint8Array(encoder, this.lensE.toUint8Array());
        return toUint8Array(encoder)
      }
    }

    /* eslint-env browser */
    const getRandomValues = crypto.getRandomValues.bind(crypto);

    const uint32 = () => getRandomValues(new Uint32Array(1))[0];

    // @ts-ignore
    const uuidv4Template = [1e7] + -1e3 + -4e3 + -8e3 + -1e11;
    const uuidv4 = () => uuidv4Template.replace(/[018]/g, /** @param {number} c */ c =>
      (c ^ uint32() & 15 >> c / 4).toString(16)
    );

    /**
     * Utility module to work with time.
     *
     * @module time
     */

    /**
     * Return current unix time.
     *
     * @return {number}
     */
    const getUnixTime = Date.now;

    /**
     * Utility helpers to work with promises.
     *
     * @module promise
     */

    /**
     * @template T
     * @callback PromiseResolve
     * @param {T|PromiseLike<T>} [result]
     */

    /**
     * @template T
     * @param {function(PromiseResolve<T>,function(Error):void):any} f
     * @return {Promise<T>}
     */
    const create$2 = f => /** @type {Promise<T>} */ (new Promise(f));

    /**
     * Working with value pairs.
     *
     * @module pair
     */

    /**
     * @template L,R
     */
    class Pair {
      /**
       * @param {L} left
       * @param {R} right
       */
      constructor (left, right) {
        this.left = left;
        this.right = right;
      }
    }

    /**
     * @template L,R
     * @param {L} left
     * @param {R} right
     * @return {Pair<L,R>}
     */
    const create$1 = (left, right) => new Pair(left, right);

    /* eslint-env browser */

    /* c8 ignore start */
    /**
     * @type {Document}
     */
    const doc = /** @type {Document} */ (typeof document !== 'undefined' ? document : {});

    /** @type {DOMParser} */ (typeof DOMParser !== 'undefined' ? new DOMParser() : null);

    /**
     * @param {Map<string,string>} m
     * @return {string}
     */
    const mapToStyleString = m => map$1(m, (value, key) => `${key}:${value};`).join('');

    doc.ELEMENT_NODE;
    doc.TEXT_NODE;
    doc.CDATA_SECTION_NODE;
    doc.COMMENT_NODE;
    doc.DOCUMENT_NODE;
    doc.DOCUMENT_TYPE_NODE;
    doc.DOCUMENT_FRAGMENT_NODE;
    /* c8 ignore stop */

    /**
     * Utility module to work with EcmaScript Symbols.
     *
     * @module symbol
     */

    /**
     * Return fresh symbol.
     *
     * @return {Symbol}
     */
    const create = Symbol;

    const BOLD = create();
    const UNBOLD = create();
    const BLUE = create();
    const GREY = create();
    const GREEN = create();
    const RED = create();
    const PURPLE = create();
    const ORANGE = create();
    const UNCOLOR = create();

    /* c8 ignore start */
    /**
     * @param {Array<string|Symbol|Object|number>} args
     * @return {Array<string|object|number>}
     */
    const computeNoColorLoggingArgs = args => {
      const logArgs = [];
      // try with formatting until we find something unsupported
      let i = 0;
      for (; i < args.length; i++) {
        const arg = args[i];
        if (arg.constructor === String || arg.constructor === Number) ; else if (arg.constructor === Object) {
          logArgs.push(JSON.stringify(arg));
        }
      }
      return logArgs
    };
    /* c8 ignore stop */

    /**
     * Isomorphic logging module with support for colors!
     *
     * @module logging
     */

    /**
     * @type {Object<Symbol,pair.Pair<string,string>>}
     */
    const _browserStyleMap = {
      [BOLD]: create$1('font-weight', 'bold'),
      [UNBOLD]: create$1('font-weight', 'normal'),
      [BLUE]: create$1('color', 'blue'),
      [GREEN]: create$1('color', 'green'),
      [GREY]: create$1('color', 'grey'),
      [RED]: create$1('color', 'red'),
      [PURPLE]: create$1('color', 'purple'),
      [ORANGE]: create$1('color', 'orange'), // not well supported in chrome when debugging node with inspector - TODO: deprecate
      [UNCOLOR]: create$1('color', 'black')
    };

    /**
     * @param {Array<string|Symbol|Object|number>} args
     * @return {Array<string|object|number>}
     */
    /* c8 ignore start */
    const computeBrowserLoggingArgs = (args) => {
      const strBuilder = [];
      const styles = [];
      const currentStyle = create$5();
      /**
       * @type {Array<string|Object|number>}
       */
      let logArgs = [];
      // try with formatting until we find something unsupported
      let i = 0;
      for (; i < args.length; i++) {
        const arg = args[i];
        // @ts-ignore
        const style = _browserStyleMap[arg];
        if (style !== undefined) {
          currentStyle.set(style.left, style.right);
        } else {
          if (arg.constructor === String || arg.constructor === Number) {
            const style = mapToStyleString(currentStyle);
            if (i > 0 || style.length > 0) {
              strBuilder.push('%c' + arg);
              styles.push(style);
            } else {
              strBuilder.push(arg);
            }
          } else {
            break
          }
        }
      }
      if (i > 0) {
        // create logArgs with what we have so far
        logArgs = styles;
        logArgs.unshift(strBuilder.join(''));
      }
      // append the rest
      for (; i < args.length; i++) {
        const arg = args[i];
        if (!(arg instanceof Symbol)) {
          logArgs.push(arg);
        }
      }
      return logArgs
    };
    /* c8 ignore stop */

    /* c8 ignore start */
    const computeLoggingArgs = supportsColor
      ? computeBrowserLoggingArgs
      : computeNoColorLoggingArgs;
    /* c8 ignore stop */

    /**
     * @param {Array<string|Symbol|Object|number>} args
     */
    const print = (...args) => {
      console.log(...computeLoggingArgs(args));
      /* c8 ignore next */
      vconsoles.forEach((vc) => vc.print(args));
    };

    const vconsoles = create$4();

    /**
     * Utility module to create and manipulate Iterators.
     *
     * @module iterator
     */

    /**
     * @template T
     * @param {function():IteratorResult<T>} next
     * @return {IterableIterator<T>}
     */
    const createIterator = next => ({
      /**
       * @return {IterableIterator<T>}
       */
      [Symbol.iterator] () {
        return this
      },
      // @ts-ignore
      next
    });

    /**
     * @template T
     * @param {Iterator<T>} iterator
     * @param {function(T):boolean} filter
     */
    const iteratorFilter = (iterator, filter) => createIterator(() => {
      let res;
      do {
        res = iterator.next();
      } while (!res.done && !filter(res.value))
      return res
    });

    /**
     * @template T,M
     * @param {Iterator<T>} iterator
     * @param {function(T):M} fmap
     */
    const iteratorMap = (iterator, fmap) => createIterator(() => {
      const { done, value } = iterator.next();
      return { done, value: done ? undefined : fmap(value) }
    });

    class DeleteItem {
      /**
       * @param {number} clock
       * @param {number} len
       */
      constructor (clock, len) {
        /**
         * @type {number}
         */
        this.clock = clock;
        /**
         * @type {number}
         */
        this.len = len;
      }
    }

    /**
     * We no longer maintain a DeleteStore. DeleteSet is a temporary object that is created when needed.
     * - When created in a transaction, it must only be accessed after sorting, and merging
     *   - This DeleteSet is send to other clients
     * - We do not create a DeleteSet when we send a sync message. The DeleteSet message is created directly from StructStore
     * - We read a DeleteSet as part of a sync/update message. In this case the DeleteSet is already sorted and merged.
     */
    class DeleteSet {
      constructor () {
        /**
         * @type {Map<number,Array<DeleteItem>>}
         */
        this.clients = new Map();
      }
    }

    /**
     * Iterate over all structs that the DeleteSet gc's.
     *
     * @param {Transaction} transaction
     * @param {DeleteSet} ds
     * @param {function(GC|Item):void} f
     *
     * @function
     */
    const iterateDeletedStructs = (transaction, ds, f) =>
      ds.clients.forEach((deletes, clientid) => {
        const structs = /** @type {Array<GC|Item>} */ (transaction.doc.store.clients.get(clientid));
        for (let i = 0; i < deletes.length; i++) {
          const del = deletes[i];
          iterateStructs(transaction, structs, del.clock, del.len, f);
        }
      });

    /**
     * @param {Array<DeleteItem>} dis
     * @param {number} clock
     * @return {number|null}
     *
     * @private
     * @function
     */
    const findIndexDS = (dis, clock) => {
      let left = 0;
      let right = dis.length - 1;
      while (left <= right) {
        const midindex = floor((left + right) / 2);
        const mid = dis[midindex];
        const midclock = mid.clock;
        if (midclock <= clock) {
          if (clock < midclock + mid.len) {
            return midindex
          }
          left = midindex + 1;
        } else {
          right = midindex - 1;
        }
      }
      return null
    };

    /**
     * @param {DeleteSet} ds
     * @param {ID} id
     * @return {boolean}
     *
     * @private
     * @function
     */
    const isDeleted = (ds, id) => {
      const dis = ds.clients.get(id.client);
      return dis !== undefined && findIndexDS(dis, id.clock) !== null
    };

    /**
     * @param {DeleteSet} ds
     *
     * @private
     * @function
     */
    const sortAndMergeDeleteSet = ds => {
      ds.clients.forEach(dels => {
        dels.sort((a, b) => a.clock - b.clock);
        // merge items without filtering or splicing the array
        // i is the current pointer
        // j refers to the current insert position for the pointed item
        // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
        let i, j;
        for (i = 1, j = 1; i < dels.length; i++) {
          const left = dels[j - 1];
          const right = dels[i];
          if (left.clock + left.len >= right.clock) {
            left.len = max(left.len, right.clock + right.len - left.clock);
          } else {
            if (j < i) {
              dels[j] = right;
            }
            j++;
          }
        }
        dels.length = j;
      });
    };

    /**
     * @param {Array<DeleteSet>} dss
     * @return {DeleteSet} A fresh DeleteSet
     */
    const mergeDeleteSets = dss => {
      const merged = new DeleteSet();
      for (let dssI = 0; dssI < dss.length; dssI++) {
        dss[dssI].clients.forEach((delsLeft, client) => {
          if (!merged.clients.has(client)) {
            // Write all missing keys from current ds and all following.
            // If merged already contains `client` current ds has already been added.
            /**
             * @type {Array<DeleteItem>}
             */
            const dels = delsLeft.slice();
            for (let i = dssI + 1; i < dss.length; i++) {
              appendTo(dels, dss[i].clients.get(client) || []);
            }
            merged.clients.set(client, dels);
          }
        });
      }
      sortAndMergeDeleteSet(merged);
      return merged
    };

    /**
     * @param {DeleteSet} ds
     * @param {number} client
     * @param {number} clock
     * @param {number} length
     *
     * @private
     * @function
     */
    const addToDeleteSet = (ds, client, clock, length) => {
      setIfUndefined(ds.clients, client, () => /** @type {Array<DeleteItem>} */ ([])).push(new DeleteItem(clock, length));
    };

    const createDeleteSet = () => new DeleteSet();

    /**
     * @param {StructStore} ss
     * @return {DeleteSet} Merged and sorted DeleteSet
     *
     * @private
     * @function
     */
    const createDeleteSetFromStructStore = ss => {
      const ds = createDeleteSet();
      ss.clients.forEach((structs, client) => {
        /**
         * @type {Array<DeleteItem>}
         */
        const dsitems = [];
        for (let i = 0; i < structs.length; i++) {
          const struct = structs[i];
          if (struct.deleted) {
            const clock = struct.id.clock;
            let len = struct.length;
            if (i + 1 < structs.length) {
              for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) {
                len += next.length;
              }
            }
            dsitems.push(new DeleteItem(clock, len));
          }
        }
        if (dsitems.length > 0) {
          ds.clients.set(client, dsitems);
        }
      });
      return ds
    };

    /**
     * @param {DSEncoderV1 | DSEncoderV2} encoder
     * @param {DeleteSet} ds
     *
     * @private
     * @function
     */
    const writeDeleteSet = (encoder, ds) => {
      writeVarUint(encoder.restEncoder, ds.clients.size);

      // Ensure that the delete set is written in a deterministic order
      from(ds.clients.entries())
        .sort((a, b) => b[0] - a[0])
        .forEach(([client, dsitems]) => {
          encoder.resetDsCurVal();
          writeVarUint(encoder.restEncoder, client);
          const len = dsitems.length;
          writeVarUint(encoder.restEncoder, len);
          for (let i = 0; i < len; i++) {
            const item = dsitems[i];
            encoder.writeDsClock(item.clock);
            encoder.writeDsLen(item.len);
          }
        });
    };

    /**
     * @param {DSDecoderV1 | DSDecoderV2} decoder
     * @return {DeleteSet}
     *
     * @private
     * @function
     */
    const readDeleteSet = decoder => {
      const ds = new DeleteSet();
      const numClients = readVarUint(decoder.restDecoder);
      for (let i = 0; i < numClients; i++) {
        decoder.resetDsCurVal();
        const client = readVarUint(decoder.restDecoder);
        const numberOfDeletes = readVarUint(decoder.restDecoder);
        if (numberOfDeletes > 0) {
          const dsField = setIfUndefined(ds.clients, client, () => /** @type {Array<DeleteItem>} */ ([]));
          for (let i = 0; i < numberOfDeletes; i++) {
            dsField.push(new DeleteItem(decoder.readDsClock(), decoder.readDsLen()));
          }
        }
      }
      return ds
    };

    /**
     * @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array()..
     */

    /**
     * @param {DSDecoderV1 | DSDecoderV2} decoder
     * @param {Transaction} transaction
     * @param {StructStore} store
     * @return {Uint8Array|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully.
     *
     * @private
     * @function
     */
    const readAndApplyDeleteSet = (decoder, transaction, store) => {
      const unappliedDS = new DeleteSet();
      const numClients = readVarUint(decoder.restDecoder);
      for (let i = 0; i < numClients; i++) {
        decoder.resetDsCurVal();
        const client = readVarUint(decoder.restDecoder);
        const numberOfDeletes = readVarUint(decoder.restDecoder);
        const structs = store.clients.get(client) || [];
        const state = getState(store, client);
        for (let i = 0; i < numberOfDeletes; i++) {
          const clock = decoder.readDsClock();
          const clockEnd = clock + decoder.readDsLen();
          if (clock < state) {
            if (state < clockEnd) {
              addToDeleteSet(unappliedDS, client, state, clockEnd - state);
            }
            let index = findIndexSS(structs, clock);
            /**
             * We can ignore the case of GC and Delete structs, because we are going to skip them
             * @type {Item}
             */
            // @ts-ignore
            let struct = structs[index];
            // split the first item if necessary
            if (!struct.deleted && struct.id.clock < clock) {
              structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock));
              index++; // increase we now want to use the next struct
            }
            while (index < structs.length) {
              // @ts-ignore
              struct = structs[index++];
              if (struct.id.clock < clockEnd) {
                if (!struct.deleted) {
                  if (clockEnd < struct.id.clock + struct.length) {
                    structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock));
                  }
                  struct.delete(transaction);
                }
              } else {
                break
              }
            }
          } else {
            addToDeleteSet(unappliedDS, client, clock, clockEnd - clock);
          }
        }
      }
      if (unappliedDS.clients.size > 0) {
        const ds = new UpdateEncoderV2();
        writeVarUint(ds.restEncoder, 0); // encode 0 structs
        writeDeleteSet(ds, unappliedDS);
        return ds.toUint8Array()
      }
      return null
    };

    /**
     * @module Y
     */

    const generateNewClientId = uint32;

    /**
     * @typedef {Object} DocOpts
     * @property {boolean} [DocOpts.gc=true] Disable garbage collection (default: gc=true)
     * @property {function(Item):boolean} [DocOpts.gcFilter] Will be called before an Item is garbage collected. Return false to keep the Item.
     * @property {string} [DocOpts.guid] Define a globally unique identifier for this document
     * @property {string | null} [DocOpts.collectionid] Associate this document with a collection. This only plays a role if your provider has a concept of collection.
     * @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well.
     * @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically.
     * @property {boolean} [DocOpts.shouldLoad] Whether the document should be synced by the provider now. This is toggled to true when you call ydoc.load()
     */

    /**
     * A Yjs instance handles the state of shared data.
     * @extends Observable<string>
     */
    class Doc extends Observable {
      /**
       * @param {DocOpts} opts configuration
       */
      constructor ({ guid = uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false, shouldLoad = true } = {}) {
        super();
        this.gc = gc;
        this.gcFilter = gcFilter;
        this.clientID = generateNewClientId();
        this.guid = guid;
        this.collectionid = collectionid;
        /**
         * @type {Map<string, AbstractType<YEvent<any>>>}
         */
        this.share = new Map();
        this.store = new StructStore();
        /**
         * @type {Transaction | null}
         */
        this._transaction = null;
        /**
         * @type {Array<Transaction>}
         */
        this._transactionCleanups = [];
        /**
         * @type {Set<Doc>}
         */
        this.subdocs = new Set();
        /**
         * If this document is a subdocument - a document integrated into another document - then _item is defined.
         * @type {Item?}
         */
        this._item = null;
        this.shouldLoad = shouldLoad;
        this.autoLoad = autoLoad;
        this.meta = meta;
        /**
         * This is set to true when the persistence provider loaded the document from the database or when the `sync` event fires.
         * Note that not all providers implement this feature. Provider authors are encouraged to fire the `load` event when the doc content is loaded from the database.
         *
         * @type {boolean}
         */
        this.isLoaded = false;
        /**
         * This is set to true when the connection provider has successfully synced with a backend.
         * Note that when using peer-to-peer providers this event may not provide very useful.
         * Also note that not all providers implement this feature. Provider authors are encouraged to fire
         * the `sync` event when the doc has been synced (with `true` as a parameter) or if connection is
         * lost (with false as a parameter).
         */
        this.isSynced = false;
        /**
         * Promise that resolves once the document has been loaded from a presistence provider.
         */
        this.whenLoaded = create$2(resolve => {
          this.on('load', () => {
            this.isLoaded = true;
            resolve(this);
          });
        });
        const provideSyncedPromise = () => create$2(resolve => {
          /**
           * @param {boolean} isSynced
           */
          const eventHandler = (isSynced) => {
            if (isSynced === undefined || isSynced === true) {
              this.off('sync', eventHandler);
              resolve();
            }
          };
          this.on('sync', eventHandler);
        });
        this.on('sync', isSynced => {
          if (isSynced === false && this.isSynced) {
            this.whenSynced = provideSyncedPromise();
          }
          this.isSynced = isSynced === undefined || isSynced === true;
          if (!this.isLoaded) {
            this.emit('load', []);
          }
        });
        /**
         * Promise that resolves once the document has been synced with a backend.
         * This promise is recreated when the connection is lost.
         * Note the documentation about the `isSynced` property.
         */
        this.whenSynced = provideSyncedPromise();
      }

      /**
       * Notify the parent document that you request to load data into this subdocument (if it is a subdocument).
       *
       * `load()` might be used in the future to request any provider to load the most current data.
       *
       * It is safe to call `load()` multiple times.
       */
      load () {
        const item = this._item;
        if (item !== null && !this.shouldLoad) {
          transact(/** @type {any} */ (item.parent).doc, transaction => {
            transaction.subdocsLoaded.add(this);
          }, null, true);
        }
        this.shouldLoad = true;
      }

      getSubdocs () {
        return this.subdocs
      }

      getSubdocGuids () {
        return new Set(from(this.subdocs).map(doc => doc.guid))
      }

      /**
       * Changes that happen inside of a transaction are bundled. This means that
       * the observer fires _after_ the transaction is finished and that all changes
       * that happened inside of the transaction are sent as one message to the
       * other peers.
       *
       * @template T
       * @param {function(Transaction):T} f The function that should be executed as a transaction
       * @param {any} [origin] Origin of who started the transaction. Will be stored on transaction.origin
       * @return T
       *
       * @public
       */
      transact (f, origin = null) {
        return transact(this, f, origin)
      }

      /**
       * Define a shared data type.
       *
       * Multiple calls of `y.get(name, TypeConstructor)` yield the same result
       * and do not overwrite each other. I.e.
       * `y.define(name, Y.Array) === y.define(name, Y.Array)`
       *
       * After this method is called, the type is also available on `y.share.get(name)`.
       *
       * *Best Practices:*
       * Define all types right after the Yjs instance is created and store them in a separate object.
       * Also use the typed methods `getText(name)`, `getArray(name)`, ..
       *
       * @example
       *   const y = new Y(..)
       *   const appState = {
       *     document: y.getText('document')
       *     comments: y.getArray('comments')
       *   }
       *
       * @param {string} name
       * @param {Function} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ...
       * @return {AbstractType<any>} The created type. Constructed with TypeConstructor
       *
       * @public
       */
      get (name, TypeConstructor = AbstractType) {
        const type = setIfUndefined(this.share, name, () => {
          // @ts-ignore
          const t = new TypeConstructor();
          t._integrate(this, null);
          return t
        });
        const Constr = type.constructor;
        if (TypeConstructor !== AbstractType && Constr !== TypeConstructor) {
          if (Constr === AbstractType) {
            // @ts-ignore
            const t = new TypeConstructor();
            t._map = type._map;
            type._map.forEach(/** @param {Item?} n */ n => {
              for (; n !== null; n = n.left) {
                // @ts-ignore
                n.parent = t;
              }
            });
            t._start = type._start;
            for (let n = t._start; n !== null; n = n.right) {
              n.parent = t;
            }
            t._length = type._length;
            this.share.set(name, t);
            t._integrate(this, null);
            return t
          } else {
            throw new Error(`Type with the name ${name} has already been defined with a different constructor`)
          }
        }
        return type
      }

      /**
       * @template T
       * @param {string} [name]
       * @return {YArray<T>}
       *
       * @public
       */
      getArray (name = '') {
        // @ts-ignore
        return this.get(name, YArray)
      }

      /**
       * @param {string} [name]
       * @return {YText}
       *
       * @public
       */
      getText (name = '') {
        // @ts-ignore
        return this.get(name, YText)
      }

      /**
       * @template T
       * @param {string} [name]
       * @return {YMap<T>}
       *
       * @public
       */
      getMap (name = '') {
        // @ts-ignore
        return this.get(name, YMap)
      }

      /**
       * @param {string} [name]
       * @return {YXmlFragment}
       *
       * @public
       */
      getXmlFragment (name = '') {
        // @ts-ignore
        return this.get(name, YXmlFragment)
      }

      /**
       * Converts the entire document into a js object, recursively traversing each yjs type
       * Doesn't log types that have not been defined (using ydoc.getType(..)).
       *
       * @deprecated Do not use this method and rather call toJSON directly on the shared types.
       *
       * @return {Object<string, any>}
       */
      toJSON () {
        /**
         * @type {Object<string, any>}
         */
        const doc = {};

        this.share.forEach((value, key) => {
          doc[key] = value.toJSON();
        });

        return doc
      }

      /**
       * Emit `destroy` event and unregister all event handlers.
       */
      destroy () {
        from(this.subdocs).forEach(subdoc => subdoc.destroy());
        const item = this._item;
        if (item !== null) {
          this._item = null;
          const content = /** @type {ContentDoc} */ (item.content);
          content.doc = new Doc({ guid: this.guid, ...content.opts, shouldLoad: false });
          content.doc._item = item;
          transact(/** @type {any} */ (item).parent.doc, transaction => {
            const doc = content.doc;
            if (!item.deleted) {
              transaction.subdocsAdded.add(doc);
            }
            transaction.subdocsRemoved.add(this);
          }, null, true);
        }
        this.emit('destroyed', [true]);
        this.emit('destroy', [this]);
        super.destroy();
      }

      /**
       * @param {string} eventName
       * @param {function(...any):any} f
       */
      on (eventName, f) {
        super.on(eventName, f);
      }

      /**
       * @param {string} eventName
       * @param {function} f
       */
      off (eventName, f) {
        super.off(eventName, f);
      }
    }

    class DSDecoderV1 {
      /**
       * @param {decoding.Decoder} decoder
       */
      constructor (decoder) {
        this.restDecoder = decoder;
      }

      resetDsCurVal () {
        // nop
      }

      /**
       * @return {number}
       */
      readDsClock () {
        return readVarUint(this.restDecoder)
      }

      /**
       * @return {number}
       */
      readDsLen () {
        return readVarUint(this.restDecoder)
      }
    }

    class UpdateDecoderV1 extends DSDecoderV1 {
      /**
       * @return {ID}
       */
      readLeftID () {
        return createID(readVarUint(this.restDecoder), readVarUint(this.restDecoder))
      }

      /**
       * @return {ID}
       */
      readRightID () {
        return createID(readVarUint(this.restDecoder), readVarUint(this.restDecoder))
      }

      /**
       * Read the next client id.
       * Use this in favor of readID whenever possible to reduce the number of objects created.
       */
      readClient () {
        return readVarUint(this.restDecoder)
      }

      /**
       * @return {number} info An unsigned 8-bit integer
       */
      readInfo () {
        return readUint8(this.restDecoder)
      }

      /**
       * @return {string}
       */
      readString () {
        return readVarString(this.restDecoder)
      }

      /**
       * @return {boolean} isKey
       */
      readParentInfo () {
        return readVarUint(this.restDecoder) === 1
      }

      /**
       * @return {number} info An unsigned 8-bit integer
       */
      readTypeRef () {
        return readVarUint(this.restDecoder)
      }

      /**
       * Write len of a struct - well suited for Opt RLE encoder.
       *
       * @return {number} len
       */
      readLen () {
        return readVarUint(this.restDecoder)
      }

      /**
       * @return {any}
       */
      readAny () {
        return readAny(this.restDecoder)
      }

      /**
       * @return {Uint8Array}
       */
      readBuf () {
        return copyUint8Array(readVarUint8Array(this.restDecoder))
      }

      /**
       * Legacy implementation uses JSON parse. We use any-decoding in v2.
       *
       * @return {any}
       */
      readJSON () {
        return JSON.parse(readVarString(this.restDecoder))
      }

      /**
       * @return {string}
       */
      readKey () {
        return readVarString(this.restDecoder)
      }
    }

    class DSDecoderV2 {
      /**
       * @param {decoding.Decoder} decoder
       */
      constructor (decoder) {
        /**
         * @private
         */
        this.dsCurrVal = 0;
        this.restDecoder = decoder;
      }

      resetDsCurVal () {
        this.dsCurrVal = 0;
      }

      /**
       * @return {number}
       */
      readDsClock () {
        this.dsCurrVal += readVarUint(this.restDecoder);
        return this.dsCurrVal
      }

      /**
       * @return {number}
       */
      readDsLen () {
        const diff = readVarUint(this.restDecoder) + 1;
        this.dsCurrVal += diff;
        return diff
      }
    }

    class UpdateDecoderV2 extends DSDecoderV2 {
      /**
       * @param {decoding.Decoder} decoder
       */
      constructor (decoder) {
        super(decoder);
        /**
         * List of cached keys. If the keys[id] does not exist, we read a new key
         * from stringEncoder and push it to keys.
         *
         * @type {Array<string>}
         */
        this.keys = [];
        readVarUint(decoder); // read feature flag - currently unused
        this.keyClockDecoder = new IntDiffOptRleDecoder(readVarUint8Array(decoder));
        this.clientDecoder = new UintOptRleDecoder(readVarUint8Array(decoder));
        this.leftClockDecoder = new IntDiffOptRleDecoder(readVarUint8Array(decoder));
        this.rightClockDecoder = new IntDiffOptRleDecoder(readVarUint8Array(decoder));
        this.infoDecoder = new RleDecoder(readVarUint8Array(decoder), readUint8);
        this.stringDecoder = new StringDecoder(readVarUint8Array(decoder));
        this.parentInfoDecoder = new RleDecoder(readVarUint8Array(decoder), readUint8);
        this.typeRefDecoder = new UintOptRleDecoder(readVarUint8Array(decoder));
        this.lenDecoder = new UintOptRleDecoder(readVarUint8Array(decoder));
      }

      /**
       * @return {ID}
       */
      readLeftID () {
        return new ID(this.clientDecoder.read(), this.leftClockDecoder.read())
      }

      /**
       * @return {ID}
       */
      readRightID () {
        return new ID(this.clientDecoder.read(), this.rightClockDecoder.read())
      }

      /**
       * Read the next client id.
       * Use this in favor of readID whenever possible to reduce the number of objects created.
       */
      readClient () {
        return this.clientDecoder.read()
      }

      /**
       * @return {number} info An unsigned 8-bit integer
       */
      readInfo () {
        return /** @type {number} */ (this.infoDecoder.read())
      }

      /**
       * @return {string}
       */
      readString () {
        return this.stringDecoder.read()
      }

      /**
       * @return {boolean}
       */
      readParentInfo () {
        return this.parentInfoDecoder.read() === 1
      }

      /**
       * @return {number} An unsigned 8-bit integer
       */
      readTypeRef () {
        return this.typeRefDecoder.read()
      }

      /**
       * Write len of a struct - well suited for Opt RLE encoder.
       *
       * @return {number}
       */
      readLen () {
        return this.lenDecoder.read()
      }

      /**
       * @return {any}
       */
      readAny () {
        return readAny(this.restDecoder)
      }

      /**
       * @return {Uint8Array}
       */
      readBuf () {
        return readVarUint8Array(this.restDecoder)
      }

      /**
       * This is mainly here for legacy purposes.
       *
       * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder.
       *
       * @return {any}
       */
      readJSON () {
        return readAny(this.restDecoder)
      }

      /**
       * @return {string}
       */
      readKey () {
        const keyClock = this.keyClockDecoder.read();
        if (keyClock < this.keys.length) {
          return this.keys[keyClock]
        } else {
          const key = this.stringDecoder.read();
          this.keys.push(key);
          return key
        }
      }
    }

    class DSEncoderV1 {
      constructor () {
        this.restEncoder = createEncoder();
      }

      toUint8Array () {
        return toUint8Array(this.restEncoder)
      }

      resetDsCurVal () {
        // nop
      }

      /**
       * @param {number} clock
       */
      writeDsClock (clock) {
        writeVarUint(this.restEncoder, clock);
      }

      /**
       * @param {number} len
       */
      writeDsLen (len) {
        writeVarUint(this.restEncoder, len);
      }
    }

    class UpdateEncoderV1 extends DSEncoderV1 {
      /**
       * @param {ID} id
       */
      writeLeftID (id) {
        writeVarUint(this.restEncoder, id.client);
        writeVarUint(this.restEncoder, id.clock);
      }

      /**
       * @param {ID} id
       */
      writeRightID (id) {
        writeVarUint(this.restEncoder, id.client);
        writeVarUint(this.restEncoder, id.clock);
      }

      /**
       * Use writeClient and writeClock instead of writeID if possible.
       * @param {number} client
       */
      writeClient (client) {
        writeVarUint(this.restEncoder, client);
      }

      /**
       * @param {number} info An unsigned 8-bit integer
       */
      writeInfo (info) {
        writeUint8(this.restEncoder, info);
      }

      /**
       * @param {string} s
       */
      writeString (s) {
        writeVarString(this.restEncoder, s);
      }

      /**
       * @param {boolean} isYKey
       */
      writeParentInfo (isYKey) {
        writeVarUint(this.restEncoder, isYKey ? 1 : 0);
      }

      /**
       * @param {number} info An unsigned 8-bit integer
       */
      writeTypeRef (info) {
        writeVarUint(this.restEncoder, info);
      }

      /**
       * Write len of a struct - well suited for Opt RLE encoder.
       *
       * @param {number} len
       */
      writeLen (len) {
        writeVarUint(this.restEncoder, len);
      }

      /**
       * @param {any} any
       */
      writeAny (any) {
        writeAny(this.restEncoder, any);
      }

      /**
       * @param {Uint8Array} buf
       */
      writeBuf (buf) {
        writeVarUint8Array(this.restEncoder, buf);
      }

      /**
       * @param {any} embed
       */
      writeJSON (embed) {
        writeVarString(this.restEncoder, JSON.stringify(embed));
      }

      /**
       * @param {string} key
       */
      writeKey (key) {
        writeVarString(this.restEncoder, key);
      }
    }

    class DSEncoderV2 {
      constructor () {
        this.restEncoder = createEncoder(); // encodes all the rest / non-optimized
        this.dsCurrVal = 0;
      }

      toUint8Array () {
        return toUint8Array(this.restEncoder)
      }

      resetDsCurVal () {
        this.dsCurrVal = 0;
      }

      /**
       * @param {number} clock
       */
      writeDsClock (clock) {
        const diff = clock - this.dsCurrVal;
        this.dsCurrVal = clock;
        writeVarUint(this.restEncoder, diff);
      }

      /**
       * @param {number} len
       */
      writeDsLen (len) {
        if (len === 0) {
          unexpectedCase();
        }
        writeVarUint(this.restEncoder, len - 1);
        this.dsCurrVal += len;
      }
    }

    class UpdateEncoderV2 extends DSEncoderV2 {
      constructor () {
        super();
        /**
         * @type {Map<string,number>}
         */
        this.keyMap = new Map();
        /**
         * Refers to the next uniqe key-identifier to me used.
         * See writeKey method for more information.
         *
         * @type {number}
         */
        this.keyClock = 0;
        this.keyClockEncoder = new IntDiffOptRleEncoder();
        this.clientEncoder = new UintOptRleEncoder();
        this.leftClockEncoder = new IntDiffOptRleEncoder();
        this.rightClockEncoder = new IntDiffOptRleEncoder();
        this.infoEncoder = new RleEncoder(writeUint8);
        this.stringEncoder = new StringEncoder();
        this.parentInfoEncoder = new RleEncoder(writeUint8);
        this.typeRefEncoder = new UintOptRleEncoder();
        this.lenEncoder = new UintOptRleEncoder();
      }

      toUint8Array () {
        const encoder = createEncoder();
        writeVarUint(encoder, 0); // this is a feature flag that we might use in the future
        writeVarUint8Array(encoder, this.keyClockEncoder.toUint8Array());
        writeVarUint8Array(encoder, this.clientEncoder.toUint8Array());
        writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array());
        writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array());
        writeVarUint8Array(encoder, toUint8Array(this.infoEncoder));
        writeVarUint8Array(encoder, this.stringEncoder.toUint8Array());
        writeVarUint8Array(encoder, toUint8Array(this.parentInfoEncoder));
        writeVarUint8Array(encoder, this.typeRefEncoder.toUint8Array());
        writeVarUint8Array(encoder, this.lenEncoder.toUint8Array());
        // @note The rest encoder is appended! (note the missing var)
        writeUint8Array(encoder, toUint8Array(this.restEncoder));
        return toUint8Array(encoder)
      }

      /**
       * @param {ID} id
       */
      writeLeftID (id) {
        this.clientEncoder.write(id.client);
        this.leftClockEncoder.write(id.clock);
      }

      /**
       * @param {ID} id
       */
      writeRightID (id) {
        this.clientEncoder.write(id.client);
        this.rightClockEncoder.write(id.clock);
      }

      /**
       * @param {number} client
       */
      writeClient (client) {
        this.clientEncoder.write(client);
      }

      /**
       * @param {number} info An unsigned 8-bit integer
       */
      writeInfo (info) {
        this.infoEncoder.write(info);
      }

      /**
       * @param {string} s
       */
      writeString (s) {
        this.stringEncoder.write(s);
      }

      /**
       * @param {boolean} isYKey
       */
      writeParentInfo (isYKey) {
        this.parentInfoEncoder.write(isYKey ? 1 : 0);
      }

      /**
       * @param {number} info An unsigned 8-bit integer
       */
      writeTypeRef (info) {
        this.typeRefEncoder.write(info);
      }

      /**
       * Write len of a struct - well suited for Opt RLE encoder.
       *
       * @param {number} len
       */
      writeLen (len) {
        this.lenEncoder.write(len);
      }

      /**
       * @param {any} any
       */
      writeAny (any) {
        writeAny(this.restEncoder, any);
      }

      /**
       * @param {Uint8Array} buf
       */
      writeBuf (buf) {
        writeVarUint8Array(this.restEncoder, buf);
      }

      /**
       * This is mainly here for legacy purposes.
       *
       * Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder.
       *
       * @param {any} embed
       */
      writeJSON (embed) {
        writeAny(this.restEncoder, embed);
      }

      /**
       * Property keys are often reused. For example, in y-prosemirror the key `bold` might
       * occur very often. For a 3d application, the key `position` might occur very often.
       *
       * We cache these keys in a Map and refer to them via a unique number.
       *
       * @param {string} key
       */
      writeKey (key) {
        const clock = this.keyMap.get(key);
        if (clock === undefined) {
          /**
           * @todo uncomment to introduce this feature finally
           *
           * Background. The ContentFormat object was always encoded using writeKey, but the decoder used to use readString.
           * Furthermore, I forgot to set the keyclock. So everything was working fine.
           *
           * However, this feature here is basically useless as it is not being used (it actually only consumes extra memory).
           *
           * I don't know yet how to reintroduce this feature..
           *
           * Older clients won't be able to read updates when we reintroduce this feature. So this should probably be done using a flag.
           *
           */
          // this.keyMap.set(key, this.keyClock)
          this.keyClockEncoder.write(this.keyClock++);
          this.stringEncoder.write(key);
        } else {
          this.keyClockEncoder.write(clock);
        }
      }
    }

    /**
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
     * @param {Array<GC|Item>} structs All structs by `client`
     * @param {number} client
     * @param {number} clock write structs starting with `ID(client,clock)`
     *
     * @function
     */
    const writeStructs = (encoder, structs, client, clock) => {
      // write first id
      clock = max(clock, structs[0].id.clock); // make sure the first id exists
      const startNewStructs = findIndexSS(structs, clock);
      // write # encoded structs
      writeVarUint(encoder.restEncoder, structs.length - startNewStructs);
      encoder.writeClient(client);
      writeVarUint(encoder.restEncoder, clock);
      const firstStruct = structs[startNewStructs];
      // write first struct with an offset
      firstStruct.write(encoder, clock - firstStruct.id.clock);
      for (let i = startNewStructs + 1; i < structs.length; i++) {
        structs[i].write(encoder, 0);
      }
    };

    /**
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
     * @param {StructStore} store
     * @param {Map<number,number>} _sm
     *
     * @private
     * @function
     */
    const writeClientsStructs = (encoder, store, _sm) => {
      // we filter all valid _sm entries into sm
      const sm = new Map();
      _sm.forEach((clock, client) => {
        // only write if new structs are available
        if (getState(store, client) > clock) {
          sm.set(client, clock);
        }
      });
      getStateVector(store).forEach((clock, client) => {
        if (!_sm.has(client)) {
          sm.set(client, 0);
        }
      });
      // write # states that were updated
      writeVarUint(encoder.restEncoder, sm.size);
      // Write items with higher client ids first
      // This heavily improves the conflict algorithm.
      from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
        // @ts-ignore
        writeStructs(encoder, store.clients.get(client), client, clock);
      });
    };

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from.
     * @param {Doc} doc
     * @return {Map<number, { i: number, refs: Array<Item | GC> }>}
     *
     * @private
     * @function
     */
    const readClientsStructRefs = (decoder, doc) => {
      /**
       * @type {Map<number, { i: number, refs: Array<Item | GC> }>}
       */
      const clientRefs = create$5();
      const numOfStateUpdates = readVarUint(decoder.restDecoder);
      for (let i = 0; i < numOfStateUpdates; i++) {
        const numberOfStructs = readVarUint(decoder.restDecoder);
        /**
         * @type {Array<GC|Item>}
         */
        const refs = new Array(numberOfStructs);
        const client = decoder.readClient();
        let clock = readVarUint(decoder.restDecoder);
        // const start = performance.now()
        clientRefs.set(client, { i: 0, refs });
        for (let i = 0; i < numberOfStructs; i++) {
          const info = decoder.readInfo();
          switch (BITS5 & info) {
            case 0: { // GC
              const len = decoder.readLen();
              refs[i] = new GC(createID(client, clock), len);
              clock += len;
              break
            }
            case 10: { // Skip Struct (nothing to apply)
              // @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing.
              const len = readVarUint(decoder.restDecoder);
              refs[i] = new Skip(createID(client, clock), len);
              clock += len;
              break
            }
            default: { // Item with content
              /**
               * The optimized implementation doesn't use any variables because inlining variables is faster.
               * Below a non-optimized version is shown that implements the basic algorithm with
               * a few comments
               */
              const cantCopyParentInfo = (info & (BIT7 | BIT8)) === 0;
              // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
              // and we read the next string as parentYKey.
              // It indicates how we store/retrieve parent from `y.share`
              // @type {string|null}
              const struct = new Item(
                createID(client, clock),
                null, // leftd
                (info & BIT8) === BIT8 ? decoder.readLeftID() : null, // origin
                null, // right
                (info & BIT7) === BIT7 ? decoder.readRightID() : null, // right origin
                cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent
                cantCopyParentInfo && (info & BIT6) === BIT6 ? decoder.readString() : null, // parentSub
                readItemContent(decoder, info) // item content
              );
              /* A non-optimized implementation of the above algorithm:

              // The item that was originally to the left of this item.
              const origin = (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null
              // The item that was originally to the right of this item.
              const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null
              const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
              const hasParentYKey = cantCopyParentInfo ? decoder.readParentInfo() : false
              // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
              // and we read the next string as parentYKey.
              // It indicates how we store/retrieve parent from `y.share`
              // @type {string|null}
              const parentYKey = cantCopyParentInfo && hasParentYKey ? decoder.readString() : null

              const struct = new Item(
                createID(client, clock),
                null, // leftd
                origin, // origin
                null, // right
                rightOrigin, // right origin
                cantCopyParentInfo && !hasParentYKey ? decoder.readLeftID() : (parentYKey !== null ? doc.get(parentYKey) : null), // parent
                cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
                readItemContent(decoder, info) // item content
              )
              */
              refs[i] = struct;
              clock += struct.length;
            }
          }
        }
        // console.log('time to read: ', performance.now() - start) // @todo remove
      }
      return clientRefs
    };

    /**
     * Resume computing structs generated by struct readers.
     *
     * While there is something to do, we integrate structs in this order
     * 1. top element on stack, if stack is not empty
     * 2. next element from current struct reader (if empty, use next struct reader)
     *
     * If struct causally depends on another struct (ref.missing), we put next reader of
     * `ref.id.client` on top of stack.
     *
     * At some point we find a struct that has no causal dependencies,
     * then we start emptying the stack.
     *
     * It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2)
     * depends on struct3 (from client1). Therefore the max stack size is eqaul to `structReaders.length`.
     *
     * This method is implemented in a way so that we can resume computation if this update
     * causally depends on another update.
     *
     * @param {Transaction} transaction
     * @param {StructStore} store
     * @param {Map<number, { i: number, refs: (GC | Item)[] }>} clientsStructRefs
     * @return { null | { update: Uint8Array, missing: Map<number,number> } }
     *
     * @private
     * @function
     */
    const integrateStructs = (transaction, store, clientsStructRefs) => {
      /**
       * @type {Array<Item | GC>}
       */
      const stack = [];
      // sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user.
      let clientsStructRefsIds = from(clientsStructRefs.keys()).sort((a, b) => a - b);
      if (clientsStructRefsIds.length === 0) {
        return null
      }
      const getNextStructTarget = () => {
        if (clientsStructRefsIds.length === 0) {
          return null
        }
        let nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]));
        while (nextStructsTarget.refs.length === nextStructsTarget.i) {
          clientsStructRefsIds.pop();
          if (clientsStructRefsIds.length > 0) {
            nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]));
          } else {
            return null
          }
        }
        return nextStructsTarget
      };
      let curStructsTarget = getNextStructTarget();
      if (curStructsTarget === null && stack.length === 0) {
        return null
      }

      /**
       * @type {StructStore}
       */
      const restStructs = new StructStore();
      const missingSV = new Map();
      /**
       * @param {number} client
       * @param {number} clock
       */
      const updateMissingSv = (client, clock) => {
        const mclock = missingSV.get(client);
        if (mclock == null || mclock > clock) {
          missingSV.set(client, clock);
        }
      };
      /**
       * @type {GC|Item}
       */
      let stackHead = /** @type {any} */ (curStructsTarget).refs[/** @type {any} */ (curStructsTarget).i++];
      // caching the state because it is used very often
      const state = new Map();

      const addStackToRestSS = () => {
        for (const item of stack) {
          const client = item.id.client;
          const unapplicableItems = clientsStructRefs.get(client);
          if (unapplicableItems) {
            // decrement because we weren't able to apply previous operation
            unapplicableItems.i--;
            restStructs.clients.set(client, unapplicableItems.refs.slice(unapplicableItems.i));
            clientsStructRefs.delete(client);
            unapplicableItems.i = 0;
            unapplicableItems.refs = [];
          } else {
            // item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue
            restStructs.clients.set(client, [item]);
          }
          // remove client from clientsStructRefsIds to prevent users from applying the same update again
          clientsStructRefsIds = clientsStructRefsIds.filter(c => c !== client);
        }
        stack.length = 0;
      };

      // iterate over all struct readers until we are done
      while (true) {
        if (stackHead.constructor !== Skip) {
          const localClock = setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client));
          const offset = localClock - stackHead.id.clock;
          if (offset < 0) {
            // update from the same client is missing
            stack.push(stackHead);
            updateMissingSv(stackHead.id.client, stackHead.id.clock - 1);
            // hid a dead wall, add all items from stack to restSS
            addStackToRestSS();
          } else {
            const missing = stackHead.getMissing(transaction, store);
            if (missing !== null) {
              stack.push(stackHead);
              // get the struct reader that has the missing struct
              /**
               * @type {{ refs: Array<GC|Item>, i: number }}
               */
              const structRefs = clientsStructRefs.get(/** @type {number} */ (missing)) || { refs: [], i: 0 };
              if (structRefs.refs.length === structRefs.i) {
                // This update message causally depends on another update message that doesn't exist yet
                updateMissingSv(/** @type {number} */ (missing), getState(store, missing));
                addStackToRestSS();
              } else {
                stackHead = structRefs.refs[structRefs.i++];
                continue
              }
            } else if (offset === 0 || offset < stackHead.length) {
              // all fine, apply the stackhead
              stackHead.integrate(transaction, offset);
              state.set(stackHead.id.client, stackHead.id.clock + stackHead.length);
            }
          }
        }
        // iterate to next stackHead
        if (stack.length > 0) {
          stackHead = /** @type {GC|Item} */ (stack.pop());
        } else if (curStructsTarget !== null && curStructsTarget.i < curStructsTarget.refs.length) {
          stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++]);
        } else {
          curStructsTarget = getNextStructTarget();
          if (curStructsTarget === null) {
            // we are done!
            break
          } else {
            stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++]);
          }
        }
      }
      if (restStructs.clients.size > 0) {
        const encoder = new UpdateEncoderV2();
        writeClientsStructs(encoder, restStructs, new Map());
        // write empty deleteset
        // writeDeleteSet(encoder, new DeleteSet())
        writeVarUint(encoder.restEncoder, 0); // => no need for an extra function call, just write 0 deletes
        return { missing: missingSV, update: encoder.toUint8Array() }
      }
      return null
    };

    /**
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
     * @param {Transaction} transaction
     *
     * @private
     * @function
     */
    const writeStructsFromTransaction = (encoder, transaction) => writeClientsStructs(encoder, transaction.doc.store, transaction.beforeState);

    /**
     * Read and apply a document update.
     *
     * This function has the same effect as `applyUpdate` but accepts an decoder.
     *
     * @param {decoding.Decoder} decoder
     * @param {Doc} ydoc
     * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
     * @param {UpdateDecoderV1 | UpdateDecoderV2} [structDecoder]
     *
     * @function
     */
    const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = new UpdateDecoderV2(decoder)) =>
      transact(ydoc, transaction => {
        // force that transaction.local is set to non-local
        transaction.local = false;
        let retry = false;
        const doc = transaction.doc;
        const store = doc.store;
        // let start = performance.now()
        const ss = readClientsStructRefs(structDecoder, doc);
        // console.log('time to read structs: ', performance.now() - start) // @todo remove
        // start = performance.now()
        // console.log('time to merge: ', performance.now() - start) // @todo remove
        // start = performance.now()
        const restStructs = integrateStructs(transaction, store, ss);
        const pending = store.pendingStructs;
        if (pending) {
          // check if we can apply something
          for (const [client, clock] of pending.missing) {
            if (clock < getState(store, client)) {
              retry = true;
              break
            }
          }
          if (restStructs) {
            // merge restStructs into store.pending
            for (const [client, clock] of restStructs.missing) {
              const mclock = pending.missing.get(client);
              if (mclock == null || mclock > clock) {
                pending.missing.set(client, clock);
              }
            }
            pending.update = mergeUpdatesV2([pending.update, restStructs.update]);
          }
        } else {
          store.pendingStructs = restStructs;
        }
        // console.log('time to integrate: ', performance.now() - start) // @todo remove
        // start = performance.now()
        const dsRest = readAndApplyDeleteSet(structDecoder, transaction, store);
        if (store.pendingDs) {
          // @todo we could make a lower-bound state-vector check as we do above
          const pendingDSUpdate = new UpdateDecoderV2(createDecoder(store.pendingDs));
          readVarUint(pendingDSUpdate.restDecoder); // read 0 structs, because we only encode deletes in pendingdsupdate
          const dsRest2 = readAndApplyDeleteSet(pendingDSUpdate, transaction, store);
          if (dsRest && dsRest2) {
            // case 1: ds1 != null && ds2 != null
            store.pendingDs = mergeUpdatesV2([dsRest, dsRest2]);
          } else {
            // case 2: ds1 != null
            // case 3: ds2 != null
            // case 4: ds1 == null && ds2 == null
            store.pendingDs = dsRest || dsRest2;
          }
        } else {
          // Either dsRest == null && pendingDs == null OR dsRest != null
          store.pendingDs = dsRest;
        }
        // console.log('time to cleanup: ', performance.now() - start) // @todo remove
        // start = performance.now()

        // console.log('time to resume delete readers: ', performance.now() - start) // @todo remove
        // start = performance.now()
        if (retry) {
          const update = /** @type {{update: Uint8Array}} */ (store.pendingStructs).update;
          store.pendingStructs = null;
          applyUpdateV2(transaction.doc, update);
        }
      }, transactionOrigin, false);

    /**
     * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
     *
     * This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
     *
     * @param {Doc} ydoc
     * @param {Uint8Array} update
     * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
     * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
     *
     * @function
     */
    const applyUpdateV2 = (ydoc, update, transactionOrigin, YDecoder = UpdateDecoderV2) => {
      const decoder = createDecoder(update);
      readUpdateV2(decoder, ydoc, transactionOrigin, new YDecoder(decoder));
    };

    /**
     * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
     *
     * This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
     *
     * @param {Doc} ydoc
     * @param {Uint8Array} update
     * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
     *
     * @function
     */
    const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateV2(ydoc, update, transactionOrigin, UpdateDecoderV1);

    /**
     * Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will
     * only write the operations that are missing.
     *
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
     * @param {Doc} doc
     * @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs
     *
     * @function
     */
    const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) => {
      writeClientsStructs(encoder, doc.store, targetStateVector);
      writeDeleteSet(encoder, createDeleteSetFromStructStore(doc.store));
    };

    /**
     * Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
     * only write the operations that are missing.
     *
     * Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
     *
     * @param {Doc} doc
     * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
     * @param {UpdateEncoderV1 | UpdateEncoderV2} [encoder]
     * @return {Uint8Array}
     *
     * @function
     */
    const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8Array([0]), encoder = new UpdateEncoderV2()) => {
      const targetStateVector = decodeStateVector(encodedTargetStateVector);
      writeStateAsUpdate(encoder, doc, targetStateVector);
      const updates = [encoder.toUint8Array()];
      // also add the pending updates (if there are any)
      if (doc.store.pendingDs) {
        updates.push(doc.store.pendingDs);
      }
      if (doc.store.pendingStructs) {
        updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector));
      }
      if (updates.length > 1) {
        if (encoder.constructor === UpdateEncoderV1) {
          return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update)))
        } else if (encoder.constructor === UpdateEncoderV2) {
          return mergeUpdatesV2(updates)
        }
      }
      return updates[0]
    };

    /**
     * Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
     * only write the operations that are missing.
     *
     * Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
     *
     * @param {Doc} doc
     * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
     * @return {Uint8Array}
     *
     * @function
     */
    const encodeStateAsUpdate = (doc, encodedTargetStateVector) => encodeStateAsUpdateV2(doc, encodedTargetStateVector, new UpdateEncoderV1());

    /**
     * Read state vector from Decoder and return as Map
     *
     * @param {DSDecoderV1 | DSDecoderV2} decoder
     * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
     *
     * @function
     */
    const readStateVector = decoder => {
      const ss = new Map();
      const ssLength = readVarUint(decoder.restDecoder);
      for (let i = 0; i < ssLength; i++) {
        const client = readVarUint(decoder.restDecoder);
        const clock = readVarUint(decoder.restDecoder);
        ss.set(client, clock);
      }
      return ss
    };

    /**
     * Read decodedState and return State as Map.
     *
     * @param {Uint8Array} decodedState
     * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
     *
     * @function
     */
    // export const decodeStateVectorV2 = decodedState => readStateVector(new DSDecoderV2(decoding.createDecoder(decodedState)))

    /**
     * Read decodedState and return State as Map.
     *
     * @param {Uint8Array} decodedState
     * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
     *
     * @function
     */
    const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(createDecoder(decodedState)));

    /**
     * @param {DSEncoderV1 | DSEncoderV2} encoder
     * @param {Map<number,number>} sv
     * @function
     */
    const writeStateVector = (encoder, sv) => {
      writeVarUint(encoder.restEncoder, sv.size);
      from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
        writeVarUint(encoder.restEncoder, client); // @todo use a special client decoder that is based on mapping
        writeVarUint(encoder.restEncoder, clock);
      });
      return encoder
    };

    /**
     * @param {DSEncoderV1 | DSEncoderV2} encoder
     * @param {Doc} doc
     *
     * @function
     */
    const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store));

    /**
     * Encode State as Uint8Array.
     *
     * @param {Doc|Map<number,number>} doc
     * @param {DSEncoderV1 | DSEncoderV2} [encoder]
     * @return {Uint8Array}
     *
     * @function
     */
    const encodeStateVectorV2 = (doc, encoder = new DSEncoderV2()) => {
      if (doc instanceof Map) {
        writeStateVector(encoder, doc);
      } else {
        writeDocumentStateVector(encoder, doc);
      }
      return encoder.toUint8Array()
    };

    /**
     * Encode State as Uint8Array.
     *
     * @param {Doc|Map<number,number>} doc
     * @return {Uint8Array}
     *
     * @function
     */
    const encodeStateVector = doc => encodeStateVectorV2(doc, new DSEncoderV1());

    /**
     * General event handler implementation.
     *
     * @template ARG0, ARG1
     *
     * @private
     */
    class EventHandler {
      constructor () {
        /**
         * @type {Array<function(ARG0, ARG1):void>}
         */
        this.l = [];
      }
    }

    /**
     * @template ARG0,ARG1
     * @returns {EventHandler<ARG0,ARG1>}
     *
     * @private
     * @function
     */
    const createEventHandler = () => new EventHandler();

    /**
     * Adds an event listener that is called when
     * {@link EventHandler#callEventListeners} is called.
     *
     * @template ARG0,ARG1
     * @param {EventHandler<ARG0,ARG1>} eventHandler
     * @param {function(ARG0,ARG1):void} f The event handler.
     *
     * @private
     * @function
     */
    const addEventHandlerListener = (eventHandler, f) =>
      eventHandler.l.push(f);

    /**
     * Removes an event listener.
     *
     * @template ARG0,ARG1
     * @param {EventHandler<ARG0,ARG1>} eventHandler
     * @param {function(ARG0,ARG1):void} f The event handler that was added with
     *                     {@link EventHandler#addEventListener}
     *
     * @private
     * @function
     */
    const removeEventHandlerListener = (eventHandler, f) => {
      const l = eventHandler.l;
      const len = l.length;
      eventHandler.l = l.filter(g => f !== g);
      if (len === eventHandler.l.length) {
        console.error('[yjs] Tried to remove event handler that doesn\'t exist.');
      }
    };

    /**
     * Call all event listeners that were added via
     * {@link EventHandler#addEventListener}.
     *
     * @template ARG0,ARG1
     * @param {EventHandler<ARG0,ARG1>} eventHandler
     * @param {ARG0} arg0
     * @param {ARG1} arg1
     *
     * @private
     * @function
     */
    const callEventHandlerListeners = (eventHandler, arg0, arg1) =>
      callAll(eventHandler.l, [arg0, arg1]);

    class ID {
      /**
       * @param {number} client client id
       * @param {number} clock unique per client id, continuous number
       */
      constructor (client, clock) {
        /**
         * Client id
         * @type {number}
         */
        this.client = client;
        /**
         * unique per client id, continuous number
         * @type {number}
         */
        this.clock = clock;
      }
    }

    /**
     * @param {ID | null} a
     * @param {ID | null} b
     * @return {boolean}
     *
     * @function
     */
    const compareIDs = (a, b) => a === b || (a !== null && b !== null && a.client === b.client && a.clock === b.clock);

    /**
     * @param {number} client
     * @param {number} clock
     *
     * @private
     * @function
     */
    const createID = (client, clock) => new ID(client, clock);

    /**
     * The top types are mapped from y.share.get(keyname) => type.
     * `type` does not store any information about the `keyname`.
     * This function finds the correct `keyname` for `type` and throws otherwise.
     *
     * @param {AbstractType<any>} type
     * @return {string}
     *
     * @private
     * @function
     */
    const findRootTypeKey = type => {
      // @ts-ignore _y must be defined, otherwise unexpected case
      for (const [key, value] of type.doc.share.entries()) {
        if (value === type) {
          return key
        }
      }
      throw unexpectedCase()
    };

    class Snapshot {
      /**
       * @param {DeleteSet} ds
       * @param {Map<number,number>} sv state map
       */
      constructor (ds, sv) {
        /**
         * @type {DeleteSet}
         */
        this.ds = ds;
        /**
         * State Map
         * @type {Map<number,number>}
         */
        this.sv = sv;
      }
    }

    /**
     * @param {DeleteSet} ds
     * @param {Map<number,number>} sm
     * @return {Snapshot}
     */
    const createSnapshot = (ds, sm) => new Snapshot(ds, sm);

    createSnapshot(createDeleteSet(), new Map());

    /**
     * @param {Item} item
     * @param {Snapshot|undefined} snapshot
     *
     * @protected
     * @function
     */
    const isVisible = (item, snapshot) => snapshot === undefined
      ? !item.deleted
      : snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id);

    /**
     * @param {Transaction} transaction
     * @param {Snapshot} snapshot
     */
    const splitSnapshotAffectedStructs = (transaction, snapshot) => {
      const meta = setIfUndefined(transaction.meta, splitSnapshotAffectedStructs, create$4);
      const store = transaction.doc.store;
      // check if we already split for this snapshot
      if (!meta.has(snapshot)) {
        snapshot.sv.forEach((clock, client) => {
          if (clock < getState(store, client)) {
            getItemCleanStart(transaction, createID(client, clock));
          }
        });
        iterateDeletedStructs(transaction, snapshot.ds, item => {});
        meta.add(snapshot);
      }
    };

    class StructStore {
      constructor () {
        /**
         * @type {Map<number,Array<GC|Item>>}
         */
        this.clients = new Map();
        /**
         * @type {null | { missing: Map<number, number>, update: Uint8Array }}
         */
        this.pendingStructs = null;
        /**
         * @type {null | Uint8Array}
         */
        this.pendingDs = null;
      }
    }

    /**
     * Return the states as a Map<client,clock>.
     * Note that clock refers to the next expected clock id.
     *
     * @param {StructStore} store
     * @return {Map<number,number>}
     *
     * @public
     * @function
     */
    const getStateVector = store => {
      const sm = new Map();
      store.clients.forEach((structs, client) => {
        const struct = structs[structs.length - 1];
        sm.set(client, struct.id.clock + struct.length);
      });
      return sm
    };

    /**
     * @param {StructStore} store
     * @param {number} client
     * @return {number}
     *
     * @public
     * @function
     */
    const getState = (store, client) => {
      const structs = store.clients.get(client);
      if (structs === undefined) {
        return 0
      }
      const lastStruct = structs[structs.length - 1];
      return lastStruct.id.clock + lastStruct.length
    };

    /**
     * @param {StructStore} store
     * @param {GC|Item} struct
     *
     * @private
     * @function
     */
    const addStruct = (store, struct) => {
      let structs = store.clients.get(struct.id.client);
      if (structs === undefined) {
        structs = [];
        store.clients.set(struct.id.client, structs);
      } else {
        const lastStruct = structs[structs.length - 1];
        if (lastStruct.id.clock + lastStruct.length !== struct.id.clock) {
          throw unexpectedCase()
        }
      }
      structs.push(struct);
    };

    /**
     * Perform a binary search on a sorted array
     * @param {Array<Item|GC>} structs
     * @param {number} clock
     * @return {number}
     *
     * @private
     * @function
     */
    const findIndexSS = (structs, clock) => {
      let left = 0;
      let right = structs.length - 1;
      let mid = structs[right];
      let midclock = mid.id.clock;
      if (midclock === clock) {
        return right
      }
      // @todo does it even make sense to pivot the search?
      // If a good split misses, it might actually increase the time to find the correct item.
      // Currently, the only advantage is that search with pivoting might find the item on the first try.
      let midindex = floor((clock / (midclock + mid.length - 1)) * right); // pivoting the search
      while (left <= right) {
        mid = structs[midindex];
        midclock = mid.id.clock;
        if (midclock <= clock) {
          if (clock < midclock + mid.length) {
            return midindex
          }
          left = midindex + 1;
        } else {
          right = midindex - 1;
        }
        midindex = floor((left + right) / 2);
      }
      // Always check state before looking for a struct in StructStore
      // Therefore the case of not finding a struct is unexpected
      throw unexpectedCase()
    };

    /**
     * Expects that id is actually in store. This function throws or is an infinite loop otherwise.
     *
     * @param {StructStore} store
     * @param {ID} id
     * @return {GC|Item}
     *
     * @private
     * @function
     */
    const find = (store, id) => {
      /**
       * @type {Array<GC|Item>}
       */
      // @ts-ignore
      const structs = store.clients.get(id.client);
      return structs[findIndexSS(structs, id.clock)]
    };

    /**
     * Expects that id is actually in store. This function throws or is an infinite loop otherwise.
     * @private
     * @function
     */
    const getItem = /** @type {function(StructStore,ID):Item} */ (find);

    /**
     * @param {Transaction} transaction
     * @param {Array<Item|GC>} structs
     * @param {number} clock
     */
    const findIndexCleanStart = (transaction, structs, clock) => {
      const index = findIndexSS(structs, clock);
      const struct = structs[index];
      if (struct.id.clock < clock && struct instanceof Item) {
        structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock));
        return index + 1
      }
      return index
    };

    /**
     * Expects that id is actually in store. This function throws or is an infinite loop otherwise.
     *
     * @param {Transaction} transaction
     * @param {ID} id
     * @return {Item}
     *
     * @private
     * @function
     */
    const getItemCleanStart = (transaction, id) => {
      const structs = /** @type {Array<Item>} */ (transaction.doc.store.clients.get(id.client));
      return structs[findIndexCleanStart(transaction, structs, id.clock)]
    };

    /**
     * Expects that id is actually in store. This function throws or is an infinite loop otherwise.
     *
     * @param {Transaction} transaction
     * @param {StructStore} store
     * @param {ID} id
     * @return {Item}
     *
     * @private
     * @function
     */
    const getItemCleanEnd = (transaction, store, id) => {
      /**
       * @type {Array<Item>}
       */
      // @ts-ignore
      const structs = store.clients.get(id.client);
      const index = findIndexSS(structs, id.clock);
      const struct = structs[index];
      if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) {
        structs.splice(index + 1, 0, splitItem(transaction, struct, id.clock - struct.id.clock + 1));
      }
      return struct
    };

    /**
     * Replace `item` with `newitem` in store
     * @param {StructStore} store
     * @param {GC|Item} struct
     * @param {GC|Item} newStruct
     *
     * @private
     * @function
     */
    const replaceStruct = (store, struct, newStruct) => {
      const structs = /** @type {Array<GC|Item>} */ (store.clients.get(struct.id.client));
      structs[findIndexSS(structs, struct.id.clock)] = newStruct;
    };

    /**
     * Iterate over a range of structs
     *
     * @param {Transaction} transaction
     * @param {Array<Item|GC>} structs
     * @param {number} clockStart Inclusive start
     * @param {number} len
     * @param {function(GC|Item):void} f
     *
     * @function
     */
    const iterateStructs = (transaction, structs, clockStart, len, f) => {
      if (len === 0) {
        return
      }
      const clockEnd = clockStart + len;
      let index = findIndexCleanStart(transaction, structs, clockStart);
      let struct;
      do {
        struct = structs[index++];
        if (clockEnd < struct.id.clock + struct.length) {
          findIndexCleanStart(transaction, structs, clockEnd);
        }
        f(struct);
      } while (index < structs.length && structs[index].id.clock < clockEnd)
    };

    /**
     * A transaction is created for every change on the Yjs model. It is possible
     * to bundle changes on the Yjs model in a single transaction to
     * minimize the number on messages sent and the number of observer calls.
     * If possible the user of this library should bundle as many changes as
     * possible. Here is an example to illustrate the advantages of bundling:
     *
     * @example
     * const map = y.define('map', YMap)
     * // Log content when change is triggered
     * map.observe(() => {
     *   console.log('change triggered')
     * })
     * // Each change on the map type triggers a log message:
     * map.set('a', 0) // => "change triggered"
     * map.set('b', 0) // => "change triggered"
     * // When put in a transaction, it will trigger the log after the transaction:
     * y.transact(() => {
     *   map.set('a', 1)
     *   map.set('b', 1)
     * }) // => "change triggered"
     *
     * @public
     */
    class Transaction {
      /**
       * @param {Doc} doc
       * @param {any} origin
       * @param {boolean} local
       */
      constructor (doc, origin, local) {
        /**
         * The Yjs instance.
         * @type {Doc}
         */
        this.doc = doc;
        /**
         * Describes the set of deleted items by ids
         * @type {DeleteSet}
         */
        this.deleteSet = new DeleteSet();
        /**
         * Holds the state before the transaction started.
         * @type {Map<Number,Number>}
         */
        this.beforeState = getStateVector(doc.store);
        /**
         * Holds the state after the transaction.
         * @type {Map<Number,Number>}
         */
        this.afterState = new Map();
        /**
         * All types that were directly modified (property added or child
         * inserted/deleted). New types are not included in this Set.
         * Maps from type to parentSubs (`item.parentSub = null` for YArray)
         * @type {Map<AbstractType<YEvent<any>>,Set<String|null>>}
         */
        this.changed = new Map();
        /**
         * Stores the events for the types that observe also child elements.
         * It is mainly used by `observeDeep`.
         * @type {Map<AbstractType<YEvent<any>>,Array<YEvent<any>>>}
         */
        this.changedParentTypes = new Map();
        /**
         * @type {Array<AbstractStruct>}
         */
        this._mergeStructs = [];
        /**
         * @type {any}
         */
        this.origin = origin;
        /**
         * Stores meta information on the transaction
         * @type {Map<any,any>}
         */
        this.meta = new Map();
        /**
         * Whether this change originates from this doc.
         * @type {boolean}
         */
        this.local = local;
        /**
         * @type {Set<Doc>}
         */
        this.subdocsAdded = new Set();
        /**
         * @type {Set<Doc>}
         */
        this.subdocsRemoved = new Set();
        /**
         * @type {Set<Doc>}
         */
        this.subdocsLoaded = new Set();
      }
    }

    /**
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
     * @param {Transaction} transaction
     * @return {boolean} Whether data was written.
     */
    const writeUpdateMessageFromTransaction = (encoder, transaction) => {
      if (transaction.deleteSet.clients.size === 0 && !any(transaction.afterState, (clock, client) => transaction.beforeState.get(client) !== clock)) {
        return false
      }
      sortAndMergeDeleteSet(transaction.deleteSet);
      writeStructsFromTransaction(encoder, transaction);
      writeDeleteSet(encoder, transaction.deleteSet);
      return true
    };

    /**
     * If `type.parent` was added in current transaction, `type` technically
     * did not change, it was just added and we should not fire events for `type`.
     *
     * @param {Transaction} transaction
     * @param {AbstractType<YEvent<any>>} type
     * @param {string|null} parentSub
     */
    const addChangedTypeToTransaction = (transaction, type, parentSub) => {
      const item = type._item;
      if (item === null || (item.id.clock < (transaction.beforeState.get(item.id.client) || 0) && !item.deleted)) {
        setIfUndefined(transaction.changed, type, create$4).add(parentSub);
      }
    };

    /**
     * @param {Array<AbstractStruct>} structs
     * @param {number} pos
     */
    const tryToMergeWithLeft = (structs, pos) => {
      const left = structs[pos - 1];
      const right = structs[pos];
      if (left.deleted === right.deleted && left.constructor === right.constructor) {
        if (left.mergeWith(right)) {
          structs.splice(pos, 1);
          if (right instanceof Item && right.parentSub !== null && /** @type {AbstractType<any>} */ (right.parent)._map.get(right.parentSub) === right) {
            /** @type {AbstractType<any>} */ (right.parent)._map.set(right.parentSub, /** @type {Item} */ (left));
          }
        }
      }
    };

    /**
     * @param {DeleteSet} ds
     * @param {StructStore} store
     * @param {function(Item):boolean} gcFilter
     */
    const tryGcDeleteSet = (ds, store, gcFilter) => {
      for (const [client, deleteItems] of ds.clients.entries()) {
        const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client));
        for (let di = deleteItems.length - 1; di >= 0; di--) {
          const deleteItem = deleteItems[di];
          const endDeleteItemClock = deleteItem.clock + deleteItem.len;
          for (
            let si = findIndexSS(structs, deleteItem.clock), struct = structs[si];
            si < structs.length && struct.id.clock < endDeleteItemClock;
            struct = structs[++si]
          ) {
            const struct = structs[si];
            if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
              break
            }
            if (struct instanceof Item && struct.deleted && !struct.keep && gcFilter(struct)) {
              struct.gc(store, false);
            }
          }
        }
      }
    };

    /**
     * @param {DeleteSet} ds
     * @param {StructStore} store
     */
    const tryMergeDeleteSet = (ds, store) => {
      // try to merge deleted / gc'd items
      // merge from right to left for better efficiecy and so we don't miss any merge targets
      ds.clients.forEach((deleteItems, client) => {
        const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client));
        for (let di = deleteItems.length - 1; di >= 0; di--) {
          const deleteItem = deleteItems[di];
          // start with merging the item next to the last deleted item
          const mostRightIndexToCheck = min(structs.length - 1, 1 + findIndexSS(structs, deleteItem.clock + deleteItem.len - 1));
          for (
            let si = mostRightIndexToCheck, struct = structs[si];
            si > 0 && struct.id.clock >= deleteItem.clock;
            struct = structs[--si]
          ) {
            tryToMergeWithLeft(structs, si);
          }
        }
      });
    };

    /**
     * @param {Array<Transaction>} transactionCleanups
     * @param {number} i
     */
    const cleanupTransactions = (transactionCleanups, i) => {
      if (i < transactionCleanups.length) {
        const transaction = transactionCleanups[i];
        const doc = transaction.doc;
        const store = doc.store;
        const ds = transaction.deleteSet;
        const mergeStructs = transaction._mergeStructs;
        try {
          sortAndMergeDeleteSet(ds);
          transaction.afterState = getStateVector(transaction.doc.store);
          doc.emit('beforeObserverCalls', [transaction, doc]);
          /**
           * An array of event callbacks.
           *
           * Each callback is called even if the other ones throw errors.
           *
           * @type {Array<function():void>}
           */
          const fs = [];
          // observe events on changed types
          transaction.changed.forEach((subs, itemtype) =>
            fs.push(() => {
              if (itemtype._item === null || !itemtype._item.deleted) {
                itemtype._callObserver(transaction, subs);
              }
            })
          );
          fs.push(() => {
            // deep observe events
            transaction.changedParentTypes.forEach((events, type) =>
              fs.push(() => {
                // We need to think about the possibility that the user transforms the
                // Y.Doc in the event.
                if (type._item === null || !type._item.deleted) {
                  events = events
                    .filter(event =>
                      event.target._item === null || !event.target._item.deleted
                    );
                  events
                    .forEach(event => {
                      event.currentTarget = type;
                    });
                  // sort events by path length so that top-level events are fired first.
                  events
                    .sort((event1, event2) => event1.path.length - event2.path.length);
                  // We don't need to check for events.length
                  // because we know it has at least one element
                  callEventHandlerListeners(type._dEH, events, transaction);
                }
              })
            );
            fs.push(() => doc.emit('afterTransaction', [transaction, doc]));
          });
          callAll(fs, []);
        } finally {
          // Replace deleted items with ItemDeleted / GC.
          // This is where content is actually remove from the Yjs Doc.
          if (doc.gc) {
            tryGcDeleteSet(ds, store, doc.gcFilter);
          }
          tryMergeDeleteSet(ds, store);

          // on all affected store.clients props, try to merge
          transaction.afterState.forEach((clock, client) => {
            const beforeClock = transaction.beforeState.get(client) || 0;
            if (beforeClock !== clock) {
              const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client));
              // we iterate from right to left so we can safely remove entries
              const firstChangePos = max(findIndexSS(structs, beforeClock), 1);
              for (let i = structs.length - 1; i >= firstChangePos; i--) {
                tryToMergeWithLeft(structs, i);
              }
            }
          });
          // try to merge mergeStructs
          // @todo: it makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left
          //        but at the moment DS does not handle duplicates
          for (let i = 0; i < mergeStructs.length; i++) {
            const { client, clock } = mergeStructs[i].id;
            const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client));
            const replacedStructPos = findIndexSS(structs, clock);
            if (replacedStructPos + 1 < structs.length) {
              tryToMergeWithLeft(structs, replacedStructPos + 1);
            }
            if (replacedStructPos > 0) {
              tryToMergeWithLeft(structs, replacedStructPos);
            }
          }
          if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) {
            print(ORANGE, BOLD, '[yjs] ', UNBOLD, RED, 'Changed the client-id because another client seems to be using it.');
            doc.clientID = generateNewClientId();
          }
          // @todo Merge all the transactions into one and provide send the data as a single update message
          doc.emit('afterTransactionCleanup', [transaction, doc]);
          if (doc._observers.has('update')) {
            const encoder = new UpdateEncoderV1();
            const hasContent = writeUpdateMessageFromTransaction(encoder, transaction);
            if (hasContent) {
              doc.emit('update', [encoder.toUint8Array(), transaction.origin, doc, transaction]);
            }
          }
          if (doc._observers.has('updateV2')) {
            const encoder = new UpdateEncoderV2();
            const hasContent = writeUpdateMessageFromTransaction(encoder, transaction);
            if (hasContent) {
              doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc, transaction]);
            }
          }
          const { subdocsAdded, subdocsLoaded, subdocsRemoved } = transaction;
          if (subdocsAdded.size > 0 || subdocsRemoved.size > 0 || subdocsLoaded.size > 0) {
            subdocsAdded.forEach(subdoc => {
              subdoc.clientID = doc.clientID;
              if (subdoc.collectionid == null) {
                subdoc.collectionid = doc.collectionid;
              }
              doc.subdocs.add(subdoc);
            });
            subdocsRemoved.forEach(subdoc => doc.subdocs.delete(subdoc));
            doc.emit('subdocs', [{ loaded: subdocsLoaded, added: subdocsAdded, removed: subdocsRemoved }, doc, transaction]);
            subdocsRemoved.forEach(subdoc => subdoc.destroy());
          }

          if (transactionCleanups.length <= i + 1) {
            doc._transactionCleanups = [];
            doc.emit('afterAllTransactions', [doc, transactionCleanups]);
          } else {
            cleanupTransactions(transactionCleanups, i + 1);
          }
        }
      }
    };

    /**
     * Implements the functionality of `y.transact(()=>{..})`
     *
     * @template T
     * @param {Doc} doc
     * @param {function(Transaction):T} f
     * @param {any} [origin=true]
     * @return {T}
     *
     * @function
     */
    const transact = (doc, f, origin = null, local = true) => {
      const transactionCleanups = doc._transactionCleanups;
      let initialCall = false;
      /**
       * @type {any}
       */
      let result = null;
      if (doc._transaction === null) {
        initialCall = true;
        doc._transaction = new Transaction(doc, origin, local);
        transactionCleanups.push(doc._transaction);
        if (transactionCleanups.length === 1) {
          doc.emit('beforeAllTransactions', [doc]);
        }
        doc.emit('beforeTransaction', [doc._transaction, doc]);
      }
      try {
        result = f(doc._transaction);
      } finally {
        if (initialCall) {
          const finishCleanup = doc._transaction === transactionCleanups[0];
          doc._transaction = null;
          if (finishCleanup) {
            // The first transaction ended, now process observer calls.
            // Observer call may create new transactions for which we need to call the observers and do cleanup.
            // We don't want to nest these calls, so we execute these calls one after
            // another.
            // Also we need to ensure that all cleanups are called, even if the
            // observes throw errors.
            // This file is full of hacky try {} finally {} blocks to ensure that an
            // event can throw errors and also that the cleanup is called.
            cleanupTransactions(transactionCleanups, 0);
          }
        }
      }
      return result
    };

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     */
    function * lazyStructReaderGenerator (decoder) {
      const numOfStateUpdates = readVarUint(decoder.restDecoder);
      for (let i = 0; i < numOfStateUpdates; i++) {
        const numberOfStructs = readVarUint(decoder.restDecoder);
        const client = decoder.readClient();
        let clock = readVarUint(decoder.restDecoder);
        for (let i = 0; i < numberOfStructs; i++) {
          const info = decoder.readInfo();
          // @todo use switch instead of ifs
          if (info === 10) {
            const len = readVarUint(decoder.restDecoder);
            yield new Skip(createID(client, clock), len);
            clock += len;
          } else if ((BITS5 & info) !== 0) {
            const cantCopyParentInfo = (info & (BIT7 | BIT8)) === 0;
            // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
            // and we read the next string as parentYKey.
            // It indicates how we store/retrieve parent from `y.share`
            // @type {string|null}
            const struct = new Item(
              createID(client, clock),
              null, // left
              (info & BIT8) === BIT8 ? decoder.readLeftID() : null, // origin
              null, // right
              (info & BIT7) === BIT7 ? decoder.readRightID() : null, // right origin
              // @ts-ignore Force writing a string here.
              cantCopyParentInfo ? (decoder.readParentInfo() ? decoder.readString() : decoder.readLeftID()) : null, // parent
              cantCopyParentInfo && (info & BIT6) === BIT6 ? decoder.readString() : null, // parentSub
              readItemContent(decoder, info) // item content
            );
            yield struct;
            clock += struct.length;
          } else {
            const len = decoder.readLen();
            yield new GC(createID(client, clock), len);
            clock += len;
          }
        }
      }
    }

    class LazyStructReader {
      /**
       * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
       * @param {boolean} filterSkips
       */
      constructor (decoder, filterSkips) {
        this.gen = lazyStructReaderGenerator(decoder);
        /**
         * @type {null | Item | Skip | GC}
         */
        this.curr = null;
        this.done = false;
        this.filterSkips = filterSkips;
        this.next();
      }

      /**
       * @return {Item | GC | Skip |null}
       */
      next () {
        // ignore "Skip" structs
        do {
          this.curr = this.gen.next().value || null;
        } while (this.filterSkips && this.curr !== null && this.curr.constructor === Skip)
        return this.curr
      }
    }

    class LazyStructWriter {
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       */
      constructor (encoder) {
        this.currClient = 0;
        this.startClock = 0;
        this.written = 0;
        this.encoder = encoder;
        /**
         * We want to write operations lazily, but also we need to know beforehand how many operations we want to write for each client.
         *
         * This kind of meta-information (#clients, #structs-per-client-written) is written to the restEncoder.
         *
         * We fragment the restEncoder and store a slice of it per-client until we know how many clients there are.
         * When we flush (toUint8Array) we write the restEncoder using the fragments and the meta-information.
         *
         * @type {Array<{ written: number, restEncoder: Uint8Array }>}
         */
        this.clientStructs = [];
      }
    }

    /**
     * @param {Array<Uint8Array>} updates
     * @return {Uint8Array}
     */
    const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, UpdateEncoderV1);

    /**
     * This method is intended to slice any kind of struct and retrieve the right part.
     * It does not handle side-effects, so it should only be used by the lazy-encoder.
     *
     * @param {Item | GC | Skip} left
     * @param {number} diff
     * @return {Item | GC}
     */
    const sliceStruct = (left, diff) => {
      if (left.constructor === GC) {
        const { client, clock } = left.id;
        return new GC(createID(client, clock + diff), left.length - diff)
      } else if (left.constructor === Skip) {
        const { client, clock } = left.id;
        return new Skip(createID(client, clock + diff), left.length - diff)
      } else {
        const leftItem = /** @type {Item} */ (left);
        const { client, clock } = leftItem.id;
        return new Item(
          createID(client, clock + diff),
          null,
          createID(client, clock + diff - 1),
          null,
          leftItem.rightOrigin,
          leftItem.parent,
          leftItem.parentSub,
          leftItem.content.splice(diff)
        )
      }
    };

    /**
     *
     * This function works similarly to `readUpdateV2`.
     *
     * @param {Array<Uint8Array>} updates
     * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
     * @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
     * @return {Uint8Array}
     */
    const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
      if (updates.length === 1) {
        return updates[0]
      }
      const updateDecoders = updates.map(update => new YDecoder(createDecoder(update)));
      let lazyStructDecoders = updateDecoders.map(decoder => new LazyStructReader(decoder, true));

      /**
       * @todo we don't need offset because we always slice before
       * @type {null | { struct: Item | GC | Skip, offset: number }}
       */
      let currWrite = null;

      const updateEncoder = new YEncoder();
      // write structs lazily
      const lazyStructEncoder = new LazyStructWriter(updateEncoder);

      // Note: We need to ensure that all lazyStructDecoders are fully consumed
      // Note: Should merge document updates whenever possible - even from different updates
      // Note: Should handle that some operations cannot be applied yet ()

      while (true) {
        // Write higher clients first ⇒ sort by clientID & clock and remove decoders without content
        lazyStructDecoders = lazyStructDecoders.filter(dec => dec.curr !== null);
        lazyStructDecoders.sort(
          /** @type {function(any,any):number} */ (dec1, dec2) => {
            if (dec1.curr.id.client === dec2.curr.id.client) {
              const clockDiff = dec1.curr.id.clock - dec2.curr.id.clock;
              if (clockDiff === 0) {
                // @todo remove references to skip since the structDecoders must filter Skips.
                return dec1.curr.constructor === dec2.curr.constructor
                  ? 0
                  : dec1.curr.constructor === Skip ? 1 : -1 // we are filtering skips anyway.
              } else {
                return clockDiff
              }
            } else {
              return dec2.curr.id.client - dec1.curr.id.client
            }
          }
        );
        if (lazyStructDecoders.length === 0) {
          break
        }
        const currDecoder = lazyStructDecoders[0];
        // write from currDecoder until the next operation is from another client or if filler-struct
        // then we need to reorder the decoders and find the next operation to write
        const firstClient = /** @type {Item | GC} */ (currDecoder.curr).id.client;

        if (currWrite !== null) {
          let curr = /** @type {Item | GC | null} */ (currDecoder.curr);
          let iterated = false;

          // iterate until we find something that we haven't written already
          // remember: first the high client-ids are written
          while (curr !== null && curr.id.clock + curr.length <= currWrite.struct.id.clock + currWrite.struct.length && curr.id.client >= currWrite.struct.id.client) {
            curr = currDecoder.next();
            iterated = true;
          }
          if (
            curr === null || // current decoder is empty
            curr.id.client !== firstClient || // check whether there is another decoder that has has updates from `firstClient`
            (iterated && curr.id.clock > currWrite.struct.id.clock + currWrite.struct.length) // the above while loop was used and we are potentially missing updates
          ) {
            continue
          }

          if (firstClient !== currWrite.struct.id.client) {
            writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset);
            currWrite = { struct: curr, offset: 0 };
            currDecoder.next();
          } else {
            if (currWrite.struct.id.clock + currWrite.struct.length < curr.id.clock) {
              // @todo write currStruct & set currStruct = Skip(clock = currStruct.id.clock + currStruct.length, length = curr.id.clock - self.clock)
              if (currWrite.struct.constructor === Skip) {
                // extend existing skip
                currWrite.struct.length = curr.id.clock + curr.length - currWrite.struct.id.clock;
              } else {
                writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset);
                const diff = curr.id.clock - currWrite.struct.id.clock - currWrite.struct.length;
                /**
                 * @type {Skip}
                 */
                const struct = new Skip(createID(firstClient, currWrite.struct.id.clock + currWrite.struct.length), diff);
                currWrite = { struct, offset: 0 };
              }
            } else { // if (currWrite.struct.id.clock + currWrite.struct.length >= curr.id.clock) {
              const diff = currWrite.struct.id.clock + currWrite.struct.length - curr.id.clock;
              if (diff > 0) {
                if (currWrite.struct.constructor === Skip) {
                  // prefer to slice Skip because the other struct might contain more information
                  currWrite.struct.length -= diff;
                } else {
                  curr = sliceStruct(curr, diff);
                }
              }
              if (!currWrite.struct.mergeWith(/** @type {any} */ (curr))) {
                writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset);
                currWrite = { struct: curr, offset: 0 };
                currDecoder.next();
              }
            }
          }
        } else {
          currWrite = { struct: /** @type {Item | GC} */ (currDecoder.curr), offset: 0 };
          currDecoder.next();
        }
        for (
          let next = currDecoder.curr;
          next !== null && next.id.client === firstClient && next.id.clock === currWrite.struct.id.clock + currWrite.struct.length && next.constructor !== Skip;
          next = currDecoder.next()
        ) {
          writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset);
          currWrite = { struct: next, offset: 0 };
        }
      }
      if (currWrite !== null) {
        writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset);
        currWrite = null;
      }
      finishLazyStructWriting(lazyStructEncoder);

      const dss = updateDecoders.map(decoder => readDeleteSet(decoder));
      const ds = mergeDeleteSets(dss);
      writeDeleteSet(updateEncoder, ds);
      return updateEncoder.toUint8Array()
    };

    /**
     * @param {Uint8Array} update
     * @param {Uint8Array} sv
     * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
     * @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
     */
    const diffUpdateV2 = (update, sv, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
      const state = decodeStateVector(sv);
      const encoder = new YEncoder();
      const lazyStructWriter = new LazyStructWriter(encoder);
      const decoder = new YDecoder(createDecoder(update));
      const reader = new LazyStructReader(decoder, false);
      while (reader.curr) {
        const curr = reader.curr;
        const currClient = curr.id.client;
        const svClock = state.get(currClient) || 0;
        if (reader.curr.constructor === Skip) {
          // the first written struct shouldn't be a skip
          reader.next();
          continue
        }
        if (curr.id.clock + curr.length > svClock) {
          writeStructToLazyStructWriter(lazyStructWriter, curr, max(svClock - curr.id.clock, 0));
          reader.next();
          while (reader.curr && reader.curr.id.client === currClient) {
            writeStructToLazyStructWriter(lazyStructWriter, reader.curr, 0);
            reader.next();
          }
        } else {
          // read until something new comes up
          while (reader.curr && reader.curr.id.client === currClient && reader.curr.id.clock + reader.curr.length <= svClock) {
            reader.next();
          }
        }
      }
      finishLazyStructWriting(lazyStructWriter);
      // write ds
      const ds = readDeleteSet(decoder);
      writeDeleteSet(encoder, ds);
      return encoder.toUint8Array()
    };

    /**
     * @param {LazyStructWriter} lazyWriter
     */
    const flushLazyStructWriter = lazyWriter => {
      if (lazyWriter.written > 0) {
        lazyWriter.clientStructs.push({ written: lazyWriter.written, restEncoder: toUint8Array(lazyWriter.encoder.restEncoder) });
        lazyWriter.encoder.restEncoder = createEncoder();
        lazyWriter.written = 0;
      }
    };

    /**
     * @param {LazyStructWriter} lazyWriter
     * @param {Item | GC} struct
     * @param {number} offset
     */
    const writeStructToLazyStructWriter = (lazyWriter, struct, offset) => {
      // flush curr if we start another client
      if (lazyWriter.written > 0 && lazyWriter.currClient !== struct.id.client) {
        flushLazyStructWriter(lazyWriter);
      }
      if (lazyWriter.written === 0) {
        lazyWriter.currClient = struct.id.client;
        // write next client
        lazyWriter.encoder.writeClient(struct.id.client);
        // write startClock
        writeVarUint(lazyWriter.encoder.restEncoder, struct.id.clock + offset);
      }
      struct.write(lazyWriter.encoder, offset);
      lazyWriter.written++;
    };
    /**
     * Call this function when we collected all parts and want to
     * put all the parts together. After calling this method,
     * you can continue using the UpdateEncoder.
     *
     * @param {LazyStructWriter} lazyWriter
     */
    const finishLazyStructWriting = (lazyWriter) => {
      flushLazyStructWriter(lazyWriter);

      // this is a fresh encoder because we called flushCurr
      const restEncoder = lazyWriter.encoder.restEncoder;

      /**
       * Now we put all the fragments together.
       * This works similarly to `writeClientsStructs`
       */

      // write # states that were updated - i.e. the clients
      writeVarUint(restEncoder, lazyWriter.clientStructs.length);

      for (let i = 0; i < lazyWriter.clientStructs.length; i++) {
        const partStructs = lazyWriter.clientStructs[i];
        /**
         * Works similarly to `writeStructs`
         */
        // write # encoded structs
        writeVarUint(restEncoder, partStructs.written);
        // write the rest of the fragment
        writeUint8Array(restEncoder, partStructs.restEncoder);
      }
    };

    /**
     * @param {Uint8Array} update
     * @param {function(Item|GC|Skip):Item|GC|Skip} blockTransformer
     * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} YDecoder
     * @param {typeof UpdateEncoderV2 | typeof UpdateEncoderV1 } YEncoder
     */
    const convertUpdateFormat = (update, blockTransformer, YDecoder, YEncoder) => {
      const updateDecoder = new YDecoder(createDecoder(update));
      const lazyDecoder = new LazyStructReader(updateDecoder, false);
      const updateEncoder = new YEncoder();
      const lazyWriter = new LazyStructWriter(updateEncoder);
      for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
        writeStructToLazyStructWriter(lazyWriter, blockTransformer(curr), 0);
      }
      finishLazyStructWriting(lazyWriter);
      const ds = readDeleteSet(updateDecoder);
      writeDeleteSet(updateEncoder, ds);
      return updateEncoder.toUint8Array()
    };

    /**
     * @param {Uint8Array} update
     */
    const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, id, UpdateDecoderV2, UpdateEncoderV1);

    /**
     * @template {AbstractType<any>} T
     * YEvent describes the changes on a YType.
     */
    class YEvent {
      /**
       * @param {T} target The changed type.
       * @param {Transaction} transaction
       */
      constructor (target, transaction) {
        /**
         * The type on which this event was created on.
         * @type {T}
         */
        this.target = target;
        /**
         * The current target on which the observe callback is called.
         * @type {AbstractType<any>}
         */
        this.currentTarget = target;
        /**
         * The transaction that triggered this event.
         * @type {Transaction}
         */
        this.transaction = transaction;
        /**
         * @type {Object|null}
         */
        this._changes = null;
        /**
         * @type {null | Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
         */
        this._keys = null;
        /**
         * @type {null | Array<{ insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
         */
        this._delta = null;
      }

      /**
       * Computes the path from `y` to the changed type.
       *
       * @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with.
       *
       * The following property holds:
       * @example
       *   let type = y
       *   event.path.forEach(dir => {
       *     type = type.get(dir)
       *   })
       *   type === event.target // => true
       */
      get path () {
        // @ts-ignore _item is defined because target is integrated
        return getPathTo(this.currentTarget, this.target)
      }

      /**
       * Check if a struct is deleted by this event.
       *
       * In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
       *
       * @param {AbstractStruct} struct
       * @return {boolean}
       */
      deletes (struct) {
        return isDeleted(this.transaction.deleteSet, struct.id)
      }

      /**
       * @type {Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
       */
      get keys () {
        if (this._keys === null) {
          const keys = new Map();
          const target = this.target;
          const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target));
          changed.forEach(key => {
            if (key !== null) {
              const item = /** @type {Item} */ (target._map.get(key));
              /**
               * @type {'delete' | 'add' | 'update'}
               */
              let action;
              let oldValue;
              if (this.adds(item)) {
                let prev = item.left;
                while (prev !== null && this.adds(prev)) {
                  prev = prev.left;
                }
                if (this.deletes(item)) {
                  if (prev !== null && this.deletes(prev)) {
                    action = 'delete';
                    oldValue = last(prev.content.getContent());
                  } else {
                    return
                  }
                } else {
                  if (prev !== null && this.deletes(prev)) {
                    action = 'update';
                    oldValue = last(prev.content.getContent());
                  } else {
                    action = 'add';
                    oldValue = undefined;
                  }
                }
              } else {
                if (this.deletes(item)) {
                  action = 'delete';
                  oldValue = last(/** @type {Item} */ item.content.getContent());
                } else {
                  return // nop
                }
              }
              keys.set(key, { action, oldValue });
            }
          });
          this._keys = keys;
        }
        return this._keys
      }

      /**
       * This is a computed property. Note that this can only be safely computed during the
       * event call. Computing this property after other changes happened might result in
       * unexpected behavior (incorrect computation of deltas). A safe way to collect changes
       * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object.
       *
       * @type {Array<{insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
       */
      get delta () {
        return this.changes.delta
      }

      /**
       * Check if a struct is added by this event.
       *
       * In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
       *
       * @param {AbstractStruct} struct
       * @return {boolean}
       */
      adds (struct) {
        return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
      }

      /**
       * This is a computed property. Note that this can only be safely computed during the
       * event call. Computing this property after other changes happened might result in
       * unexpected behavior (incorrect computation of deltas). A safe way to collect changes
       * is to store the `changes` or the `delta` object. Avoid storing the `transaction` object.
       *
       * @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
       */
      get changes () {
        let changes = this._changes;
        if (changes === null) {
          const target = this.target;
          const added = create$4();
          const deleted = create$4();
          /**
           * @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
           */
          const delta = [];
          changes = {
            added,
            deleted,
            delta,
            keys: this.keys
          };
          const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target));
          if (changed.has(null)) {
            /**
             * @type {any}
             */
            let lastOp = null;
            const packOp = () => {
              if (lastOp) {
                delta.push(lastOp);
              }
            };
            for (let item = target._start; item !== null; item = item.right) {
              if (item.deleted) {
                if (this.deletes(item) && !this.adds(item)) {
                  if (lastOp === null || lastOp.delete === undefined) {
                    packOp();
                    lastOp = { delete: 0 };
                  }
                  lastOp.delete += item.length;
                  deleted.add(item);
                } // else nop
              } else {
                if (this.adds(item)) {
                  if (lastOp === null || lastOp.insert === undefined) {
                    packOp();
                    lastOp = { insert: [] };
                  }
                  lastOp.insert = lastOp.insert.concat(item.content.getContent());
                  added.add(item);
                } else {
                  if (lastOp === null || lastOp.retain === undefined) {
                    packOp();
                    lastOp = { retain: 0 };
                  }
                  lastOp.retain += item.length;
                }
              }
            }
            if (lastOp !== null && lastOp.retain === undefined) {
              packOp();
            }
          }
          this._changes = changes;
        }
        return /** @type {any} */ (changes)
      }
    }

    /**
     * Compute the path from this type to the specified target.
     *
     * @example
     *   // `child` should be accessible via `type.get(path[0]).get(path[1])..`
     *   const path = type.getPathTo(child)
     *   // assuming `type instanceof YArray`
     *   console.log(path) // might look like => [2, 'key1']
     *   child === type.get(path[0]).get(path[1])
     *
     * @param {AbstractType<any>} parent
     * @param {AbstractType<any>} child target
     * @return {Array<string|number>} Path to the target
     *
     * @private
     * @function
     */
    const getPathTo = (parent, child) => {
      const path = [];
      while (child._item !== null && child !== parent) {
        if (child._item.parentSub !== null) {
          // parent is map-ish
          path.unshift(child._item.parentSub);
        } else {
          // parent is array-ish
          let i = 0;
          let c = /** @type {AbstractType<any>} */ (child._item.parent)._start;
          while (c !== child._item && c !== null) {
            if (!c.deleted) {
              i++;
            }
            c = c.right;
          }
          path.unshift(i);
        }
        child = /** @type {AbstractType<any>} */ (child._item.parent);
      }
      return path
    };

    const maxSearchMarker = 80;

    /**
     * A unique timestamp that identifies each marker.
     *
     * Time is relative,.. this is more like an ever-increasing clock.
     *
     * @type {number}
     */
    let globalSearchMarkerTimestamp = 0;

    class ArraySearchMarker {
      /**
       * @param {Item} p
       * @param {number} index
       */
      constructor (p, index) {
        p.marker = true;
        this.p = p;
        this.index = index;
        this.timestamp = globalSearchMarkerTimestamp++;
      }
    }

    /**
     * @param {ArraySearchMarker} marker
     */
    const refreshMarkerTimestamp = marker => { marker.timestamp = globalSearchMarkerTimestamp++; };

    /**
     * This is rather complex so this function is the only thing that should overwrite a marker
     *
     * @param {ArraySearchMarker} marker
     * @param {Item} p
     * @param {number} index
     */
    const overwriteMarker = (marker, p, index) => {
      marker.p.marker = false;
      marker.p = p;
      p.marker = true;
      marker.index = index;
      marker.timestamp = globalSearchMarkerTimestamp++;
    };

    /**
     * @param {Array<ArraySearchMarker>} searchMarker
     * @param {Item} p
     * @param {number} index
     */
    const markPosition = (searchMarker, p, index) => {
      if (searchMarker.length >= maxSearchMarker) {
        // override oldest marker (we don't want to create more objects)
        const marker = searchMarker.reduce((a, b) => a.timestamp < b.timestamp ? a : b);
        overwriteMarker(marker, p, index);
        return marker
      } else {
        // create new marker
        const pm = new ArraySearchMarker(p, index);
        searchMarker.push(pm);
        return pm
      }
    };

    /**
     * Search marker help us to find positions in the associative array faster.
     *
     * They speed up the process of finding a position without much bookkeeping.
     *
     * A maximum of `maxSearchMarker` objects are created.
     *
     * This function always returns a refreshed marker (updated timestamp)
     *
     * @param {AbstractType<any>} yarray
     * @param {number} index
     */
    const findMarker = (yarray, index) => {
      if (yarray._start === null || index === 0 || yarray._searchMarker === null) {
        return null
      }
      const marker = yarray._searchMarker.length === 0 ? null : yarray._searchMarker.reduce((a, b) => abs(index - a.index) < abs(index - b.index) ? a : b);
      let p = yarray._start;
      let pindex = 0;
      if (marker !== null) {
        p = marker.p;
        pindex = marker.index;
        refreshMarkerTimestamp(marker); // we used it, we might need to use it again
      }
      // iterate to right if possible
      while (p.right !== null && pindex < index) {
        if (!p.deleted && p.countable) {
          if (index < pindex + p.length) {
            break
          }
          pindex += p.length;
        }
        p = p.right;
      }
      // iterate to left if necessary (might be that pindex > index)
      while (p.left !== null && pindex > index) {
        p = p.left;
        if (!p.deleted && p.countable) {
          pindex -= p.length;
        }
      }
      // we want to make sure that p can't be merged with left, because that would screw up everything
      // in that cas just return what we have (it is most likely the best marker anyway)
      // iterate to left until p can't be merged with left
      while (p.left !== null && p.left.id.client === p.id.client && p.left.id.clock + p.left.length === p.id.clock) {
        p = p.left;
        if (!p.deleted && p.countable) {
          pindex -= p.length;
        }
      }

      // @todo remove!
      // assure position
      // {
      //   let start = yarray._start
      //   let pos = 0
      //   while (start !== p) {
      //     if (!start.deleted && start.countable) {
      //       pos += start.length
      //     }
      //     start = /** @type {Item} */ (start.right)
      //   }
      //   if (pos !== pindex) {
      //     debugger
      //     throw new Error('Gotcha position fail!')
      //   }
      // }
      // if (marker) {
      //   if (window.lengthes == null) {
      //     window.lengthes = []
      //     window.getLengthes = () => window.lengthes.sort((a, b) => a - b)
      //   }
      //   window.lengthes.push(marker.index - pindex)
      //   console.log('distance', marker.index - pindex, 'len', p && p.parent.length)
      // }
      if (marker !== null && abs(marker.index - pindex) < /** @type {YText|YArray<any>} */ (p.parent).length / maxSearchMarker) {
        // adjust existing marker
        overwriteMarker(marker, p, pindex);
        return marker
      } else {
        // create new marker
        return markPosition(yarray._searchMarker, p, pindex)
      }
    };

    /**
     * Update markers when a change happened.
     *
     * This should be called before doing a deletion!
     *
     * @param {Array<ArraySearchMarker>} searchMarker
     * @param {number} index
     * @param {number} len If insertion, len is positive. If deletion, len is negative.
     */
    const updateMarkerChanges = (searchMarker, index, len) => {
      for (let i = searchMarker.length - 1; i >= 0; i--) {
        const m = searchMarker[i];
        if (len > 0) {
          /**
           * @type {Item|null}
           */
          let p = m.p;
          p.marker = false;
          // Ideally we just want to do a simple position comparison, but this will only work if
          // search markers don't point to deleted items for formats.
          // Iterate marker to prev undeleted countable position so we know what to do when updating a position
          while (p && (p.deleted || !p.countable)) {
            p = p.left;
            if (p && !p.deleted && p.countable) {
              // adjust position. the loop should break now
              m.index -= p.length;
            }
          }
          if (p === null || p.marker === true) {
            // remove search marker if updated position is null or if position is already marked
            searchMarker.splice(i, 1);
            continue
          }
          m.p = p;
          p.marker = true;
        }
        if (index < m.index || (len > 0 && index === m.index)) { // a simple index <= m.index check would actually suffice
          m.index = max(index, m.index + len);
        }
      }
    };

    /**
     * Call event listeners with an event. This will also add an event to all
     * parents (for `.observeDeep` handlers).
     *
     * @template EventType
     * @param {AbstractType<EventType>} type
     * @param {Transaction} transaction
     * @param {EventType} event
     */
    const callTypeObservers = (type, transaction, event) => {
      const changedType = type;
      const changedParentTypes = transaction.changedParentTypes;
      while (true) {
        // @ts-ignore
        setIfUndefined(changedParentTypes, type, () => []).push(event);
        if (type._item === null) {
          break
        }
        type = /** @type {AbstractType<any>} */ (type._item.parent);
      }
      callEventHandlerListeners(changedType._eH, event, transaction);
    };

    /**
     * @template EventType
     * Abstract Yjs Type class
     */
    class AbstractType {
      constructor () {
        /**
         * @type {Item|null}
         */
        this._item = null;
        /**
         * @type {Map<string,Item>}
         */
        this._map = new Map();
        /**
         * @type {Item|null}
         */
        this._start = null;
        /**
         * @type {Doc|null}
         */
        this.doc = null;
        this._length = 0;
        /**
         * Event handlers
         * @type {EventHandler<EventType,Transaction>}
         */
        this._eH = createEventHandler();
        /**
         * Deep event handlers
         * @type {EventHandler<Array<YEvent<any>>,Transaction>}
         */
        this._dEH = createEventHandler();
        /**
         * @type {null | Array<ArraySearchMarker>}
         */
        this._searchMarker = null;
      }

      /**
       * @return {AbstractType<any>|null}
       */
      get parent () {
        return this._item ? /** @type {AbstractType<any>} */ (this._item.parent) : null
      }

      /**
       * Integrate this type into the Yjs instance.
       *
       * * Save this struct in the os
       * * This type is sent to other client
       * * Observer functions are fired
       *
       * @param {Doc} y The Yjs instance
       * @param {Item|null} item
       */
      _integrate (y, item) {
        this.doc = y;
        this._item = item;
      }

      /**
       * @return {AbstractType<EventType>}
       */
      _copy () {
        throw methodUnimplemented()
      }

      /**
       * @return {AbstractType<EventType>}
       */
      clone () {
        throw methodUnimplemented()
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} _encoder
       */
      _write (_encoder) { }

      /**
       * The first non-deleted item
       */
      get _first () {
        let n = this._start;
        while (n !== null && n.deleted) {
          n = n.right;
        }
        return n
      }

      /**
       * Creates YEvent and calls all type observers.
       * Must be implemented by each type.
       *
       * @param {Transaction} transaction
       * @param {Set<null|string>} _parentSubs Keys changed on this type. `null` if list was modified.
       */
      _callObserver (transaction, _parentSubs) {
        if (!transaction.local && this._searchMarker) {
          this._searchMarker.length = 0;
        }
      }

      /**
       * Observe all events that are created on this type.
       *
       * @param {function(EventType, Transaction):void} f Observer function
       */
      observe (f) {
        addEventHandlerListener(this._eH, f);
      }

      /**
       * Observe all events that are created by this type and its children.
       *
       * @param {function(Array<YEvent<any>>,Transaction):void} f Observer function
       */
      observeDeep (f) {
        addEventHandlerListener(this._dEH, f);
      }

      /**
       * Unregister an observer function.
       *
       * @param {function(EventType,Transaction):void} f Observer function
       */
      unobserve (f) {
        removeEventHandlerListener(this._eH, f);
      }

      /**
       * Unregister an observer function.
       *
       * @param {function(Array<YEvent<any>>,Transaction):void} f Observer function
       */
      unobserveDeep (f) {
        removeEventHandlerListener(this._dEH, f);
      }

      /**
       * @abstract
       * @return {any}
       */
      toJSON () {}
    }

    /**
     * @param {AbstractType<any>} type
     * @param {number} start
     * @param {number} end
     * @return {Array<any>}
     *
     * @private
     * @function
     */
    const typeListSlice = (type, start, end) => {
      if (start < 0) {
        start = type._length + start;
      }
      if (end < 0) {
        end = type._length + end;
      }
      let len = end - start;
      const cs = [];
      let n = type._start;
      while (n !== null && len > 0) {
        if (n.countable && !n.deleted) {
          const c = n.content.getContent();
          if (c.length <= start) {
            start -= c.length;
          } else {
            for (let i = start; i < c.length && len > 0; i++) {
              cs.push(c[i]);
              len--;
            }
            start = 0;
          }
        }
        n = n.right;
      }
      return cs
    };

    /**
     * @param {AbstractType<any>} type
     * @return {Array<any>}
     *
     * @private
     * @function
     */
    const typeListToArray = type => {
      const cs = [];
      let n = type._start;
      while (n !== null) {
        if (n.countable && !n.deleted) {
          const c = n.content.getContent();
          for (let i = 0; i < c.length; i++) {
            cs.push(c[i]);
          }
        }
        n = n.right;
      }
      return cs
    };

    /**
     * Executes a provided function on once on overy element of this YArray.
     *
     * @param {AbstractType<any>} type
     * @param {function(any,number,any):void} f A function to execute on every element of this YArray.
     *
     * @private
     * @function
     */
    const typeListForEach = (type, f) => {
      let index = 0;
      let n = type._start;
      while (n !== null) {
        if (n.countable && !n.deleted) {
          const c = n.content.getContent();
          for (let i = 0; i < c.length; i++) {
            f(c[i], index++, type);
          }
        }
        n = n.right;
      }
    };

    /**
     * @template C,R
     * @param {AbstractType<any>} type
     * @param {function(C,number,AbstractType<any>):R} f
     * @return {Array<R>}
     *
     * @private
     * @function
     */
    const typeListMap = (type, f) => {
      /**
       * @type {Array<any>}
       */
      const result = [];
      typeListForEach(type, (c, i) => {
        result.push(f(c, i, type));
      });
      return result
    };

    /**
     * @param {AbstractType<any>} type
     * @return {IterableIterator<any>}
     *
     * @private
     * @function
     */
    const typeListCreateIterator = type => {
      let n = type._start;
      /**
       * @type {Array<any>|null}
       */
      let currentContent = null;
      let currentContentIndex = 0;
      return {
        [Symbol.iterator] () {
          return this
        },
        next: () => {
          // find some content
          if (currentContent === null) {
            while (n !== null && n.deleted) {
              n = n.right;
            }
            // check if we reached the end, no need to check currentContent, because it does not exist
            if (n === null) {
              return {
                done: true,
                value: undefined
              }
            }
            // we found n, so we can set currentContent
            currentContent = n.content.getContent();
            currentContentIndex = 0;
            n = n.right; // we used the content of n, now iterate to next
          }
          const value = currentContent[currentContentIndex++];
          // check if we need to empty currentContent
          if (currentContent.length <= currentContentIndex) {
            currentContent = null;
          }
          return {
            done: false,
            value
          }
        }
      }
    };

    /**
     * @param {AbstractType<any>} type
     * @param {number} index
     * @return {any}
     *
     * @private
     * @function
     */
    const typeListGet = (type, index) => {
      const marker = findMarker(type, index);
      let n = type._start;
      if (marker !== null) {
        n = marker.p;
        index -= marker.index;
      }
      for (; n !== null; n = n.right) {
        if (!n.deleted && n.countable) {
          if (index < n.length) {
            return n.content.getContent()[index]
          }
          index -= n.length;
        }
      }
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {Item?} referenceItem
     * @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
     *
     * @private
     * @function
     */
    const typeListInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
      let left = referenceItem;
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      const store = doc.store;
      const right = referenceItem === null ? parent._start : referenceItem.right;
      /**
       * @type {Array<Object|Array<any>|number|null>}
       */
      let jsonContent = [];
      const packJsonContent = () => {
        if (jsonContent.length > 0) {
          left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentAny(jsonContent));
          left.integrate(transaction, 0);
          jsonContent = [];
        }
      };
      content.forEach(c => {
        if (c === null) {
          jsonContent.push(c);
        } else {
          switch (c.constructor) {
            case Number:
            case Object:
            case Boolean:
            case Array:
            case String:
              jsonContent.push(c);
              break
            default:
              packJsonContent();
              switch (c.constructor) {
                case Uint8Array:
                case ArrayBuffer:
                  left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))));
                  left.integrate(transaction, 0);
                  break
                case Doc:
                  left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)));
                  left.integrate(transaction, 0);
                  break
                default:
                  if (c instanceof AbstractType) {
                    left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c));
                    left.integrate(transaction, 0);
                  } else {
                    throw new Error('Unexpected content type in insert operation')
                  }
              }
          }
        }
      });
      packJsonContent();
    };

    const lengthExceeded = create$3('Length exceeded!');

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {number} index
     * @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
     *
     * @private
     * @function
     */
    const typeListInsertGenerics = (transaction, parent, index, content) => {
      if (index > parent._length) {
        throw lengthExceeded
      }
      if (index === 0) {
        if (parent._searchMarker) {
          updateMarkerChanges(parent._searchMarker, index, content.length);
        }
        return typeListInsertGenericsAfter(transaction, parent, null, content)
      }
      const startIndex = index;
      const marker = findMarker(parent, index);
      let n = parent._start;
      if (marker !== null) {
        n = marker.p;
        index -= marker.index;
        // we need to iterate one to the left so that the algorithm works
        if (index === 0) {
          // @todo refactor this as it actually doesn't consider formats
          n = n.prev; // important! get the left undeleted item so that we can actually decrease index
          index += (n && n.countable && !n.deleted) ? n.length : 0;
        }
      }
      for (; n !== null; n = n.right) {
        if (!n.deleted && n.countable) {
          if (index <= n.length) {
            if (index < n.length) {
              // insert in-between
              getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index));
            }
            break
          }
          index -= n.length;
        }
      }
      if (parent._searchMarker) {
        updateMarkerChanges(parent._searchMarker, startIndex, content.length);
      }
      return typeListInsertGenericsAfter(transaction, parent, n, content)
    };

    /**
     * Pushing content is special as we generally want to push after the last item. So we don't have to update
     * the serach marker.
     *
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
     *
     * @private
     * @function
     */
    const typeListPushGenerics = (transaction, parent, content) => {
      // Use the marker with the highest index and iterate to the right.
      const marker = (parent._searchMarker || []).reduce((maxMarker, currMarker) => currMarker.index > maxMarker.index ? currMarker : maxMarker, { index: 0, p: parent._start });
      let n = marker.p;
      if (n) {
        while (n.right) {
          n = n.right;
        }
      }
      return typeListInsertGenericsAfter(transaction, parent, n, content)
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {number} index
     * @param {number} length
     *
     * @private
     * @function
     */
    const typeListDelete = (transaction, parent, index, length) => {
      if (length === 0) { return }
      const startIndex = index;
      const startLength = length;
      const marker = findMarker(parent, index);
      let n = parent._start;
      if (marker !== null) {
        n = marker.p;
        index -= marker.index;
      }
      // compute the first item to be deleted
      for (; n !== null && index > 0; n = n.right) {
        if (!n.deleted && n.countable) {
          if (index < n.length) {
            getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index));
          }
          index -= n.length;
        }
      }
      // delete all items until done
      while (length > 0 && n !== null) {
        if (!n.deleted) {
          if (length < n.length) {
            getItemCleanStart(transaction, createID(n.id.client, n.id.clock + length));
          }
          n.delete(transaction);
          length -= n.length;
        }
        n = n.right;
      }
      if (length > 0) {
        throw lengthExceeded
      }
      if (parent._searchMarker) {
        updateMarkerChanges(parent._searchMarker, startIndex, -startLength + length /* in case we remove the above exception */);
      }
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {string} key
     *
     * @private
     * @function
     */
    const typeMapDelete = (transaction, parent, key) => {
      const c = parent._map.get(key);
      if (c !== undefined) {
        c.delete(transaction);
      }
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {string} key
     * @param {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} value
     *
     * @private
     * @function
     */
    const typeMapSet = (transaction, parent, key, value) => {
      const left = parent._map.get(key) || null;
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      let content;
      if (value == null) {
        content = new ContentAny([value]);
      } else {
        switch (value.constructor) {
          case Number:
          case Object:
          case Boolean:
          case Array:
          case String:
            content = new ContentAny([value]);
            break
          case Uint8Array:
            content = new ContentBinary(/** @type {Uint8Array} */ (value));
            break
          case Doc:
            content = new ContentDoc(/** @type {Doc} */ (value));
            break
          default:
            if (value instanceof AbstractType) {
              content = new ContentType(value);
            } else {
              throw new Error('Unexpected content type')
            }
        }
      }
      new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0);
    };

    /**
     * @param {AbstractType<any>} parent
     * @param {string} key
     * @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
     *
     * @private
     * @function
     */
    const typeMapGet = (parent, key) => {
      const val = parent._map.get(key);
      return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
    };

    /**
     * @param {AbstractType<any>} parent
     * @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
     *
     * @private
     * @function
     */
    const typeMapGetAll = (parent) => {
      /**
       * @type {Object<string,any>}
       */
      const res = {};
      parent._map.forEach((value, key) => {
        if (!value.deleted) {
          res[key] = value.content.getContent()[value.length - 1];
        }
      });
      return res
    };

    /**
     * @param {AbstractType<any>} parent
     * @param {string} key
     * @return {boolean}
     *
     * @private
     * @function
     */
    const typeMapHas = (parent, key) => {
      const val = parent._map.get(key);
      return val !== undefined && !val.deleted
    };

    /**
     * @param {Map<string,Item>} map
     * @return {IterableIterator<Array<any>>}
     *
     * @private
     * @function
     */
    const createMapIterator = map => iteratorFilter(map.entries(), /** @param {any} entry */ entry => !entry[1].deleted);

    /**
     * @module YArray
     */

    /**
     * Event that describes the changes on a YArray
     * @template T
     * @extends YEvent<YArray<T>>
     */
    class YArrayEvent extends YEvent {
      /**
       * @param {YArray<T>} yarray The changed type
       * @param {Transaction} transaction The transaction object
       */
      constructor (yarray, transaction) {
        super(yarray, transaction);
        this._transaction = transaction;
      }
    }

    /**
     * A shared Array implementation.
     * @template T
     * @extends AbstractType<YArrayEvent<T>>
     * @implements {Iterable<T>}
     */
    class YArray extends AbstractType {
      constructor () {
        super();
        /**
         * @type {Array<any>?}
         * @private
         */
        this._prelimContent = [];
        /**
         * @type {Array<ArraySearchMarker>}
         */
        this._searchMarker = [];
      }

      /**
       * Construct a new YArray containing the specified items.
       * @template {Object<string,any>|Array<any>|number|null|string|Uint8Array} T
       * @param {Array<T>} items
       * @return {YArray<T>}
       */
      static from (items) {
        /**
         * @type {YArray<T>}
         */
        const a = new YArray();
        a.push(items);
        return a
      }

      /**
       * Integrate this type into the Yjs instance.
       *
       * * Save this struct in the os
       * * This type is sent to other client
       * * Observer functions are fired
       *
       * @param {Doc} y The Yjs instance
       * @param {Item} item
       */
      _integrate (y, item) {
        super._integrate(y, item);
        this.insert(0, /** @type {Array<any>} */ (this._prelimContent));
        this._prelimContent = null;
      }

      /**
       * @return {YArray<T>}
       */
      _copy () {
        return new YArray()
      }

      /**
       * @return {YArray<T>}
       */
      clone () {
        /**
         * @type {YArray<T>}
         */
        const arr = new YArray();
        arr.insert(0, this.toArray().map(el =>
          el instanceof AbstractType ? /** @type {typeof el} */ (el.clone()) : el
        ));
        return arr
      }

      get length () {
        return this._prelimContent === null ? this._length : this._prelimContent.length
      }

      /**
       * Creates YArrayEvent and calls observers.
       *
       * @param {Transaction} transaction
       * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
       */
      _callObserver (transaction, parentSubs) {
        super._callObserver(transaction, parentSubs);
        callTypeObservers(this, transaction, new YArrayEvent(this, transaction));
      }

      /**
       * Inserts new content at an index.
       *
       * Important: This function expects an array of content. Not just a content
       * object. The reason for this "weirdness" is that inserting several elements
       * is very efficient when it is done as a single operation.
       *
       * @example
       *  // Insert character 'a' at position 0
       *  yarray.insert(0, ['a'])
       *  // Insert numbers 1, 2 at position 1
       *  yarray.insert(1, [1, 2])
       *
       * @param {number} index The index to insert content at.
       * @param {Array<T>} content The array of content
       */
      insert (index, content) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeListInsertGenerics(transaction, this, index, /** @type {any} */ (content));
          });
        } else {
          /** @type {Array<any>} */ (this._prelimContent).splice(index, 0, ...content);
        }
      }

      /**
       * Appends content to this YArray.
       *
       * @param {Array<T>} content Array of content to append.
       *
       * @todo Use the following implementation in all types.
       */
      push (content) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeListPushGenerics(transaction, this, /** @type {any} */ (content));
          });
        } else {
          /** @type {Array<any>} */ (this._prelimContent).push(...content);
        }
      }

      /**
       * Preppends content to this YArray.
       *
       * @param {Array<T>} content Array of content to preppend.
       */
      unshift (content) {
        this.insert(0, content);
      }

      /**
       * Deletes elements starting from an index.
       *
       * @param {number} index Index at which to start deleting elements
       * @param {number} length The number of elements to remove. Defaults to 1.
       */
      delete (index, length = 1) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeListDelete(transaction, this, index, length);
          });
        } else {
          /** @type {Array<any>} */ (this._prelimContent).splice(index, length);
        }
      }

      /**
       * Returns the i-th element from a YArray.
       *
       * @param {number} index The index of the element to return from the YArray
       * @return {T}
       */
      get (index) {
        return typeListGet(this, index)
      }

      /**
       * Transforms this YArray to a JavaScript Array.
       *
       * @return {Array<T>}
       */
      toArray () {
        return typeListToArray(this)
      }

      /**
       * Transforms this YArray to a JavaScript Array.
       *
       * @param {number} [start]
       * @param {number} [end]
       * @return {Array<T>}
       */
      slice (start = 0, end = this.length) {
        return typeListSlice(this, start, end)
      }

      /**
       * Transforms this Shared Type to a JSON object.
       *
       * @return {Array<any>}
       */
      toJSON () {
        return this.map(c => c instanceof AbstractType ? c.toJSON() : c)
      }

      /**
       * Returns an Array with the result of calling a provided function on every
       * element of this YArray.
       *
       * @template M
       * @param {function(T,number,YArray<T>):M} f Function that produces an element of the new Array
       * @return {Array<M>} A new array with each element being the result of the
       *                 callback function
       */
      map (f) {
        return typeListMap(this, /** @type {any} */ (f))
      }

      /**
       * Executes a provided function once on overy element of this YArray.
       *
       * @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray.
       */
      forEach (f) {
        typeListForEach(this, f);
      }

      /**
       * @return {IterableIterator<T>}
       */
      [Symbol.iterator] () {
        return typeListCreateIterator(this)
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       */
      _write (encoder) {
        encoder.writeTypeRef(YArrayRefID);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
     *
     * @private
     * @function
     */
    const readYArray = _decoder => new YArray();

    /**
     * @template T
     * @extends YEvent<YMap<T>>
     * Event that describes the changes on a YMap.
     */
    class YMapEvent extends YEvent {
      /**
       * @param {YMap<T>} ymap The YArray that changed.
       * @param {Transaction} transaction
       * @param {Set<any>} subs The keys that changed.
       */
      constructor (ymap, transaction, subs) {
        super(ymap, transaction);
        this.keysChanged = subs;
      }
    }

    /**
     * @template MapType
     * A shared Map implementation.
     *
     * @extends AbstractType<YMapEvent<MapType>>
     * @implements {Iterable<MapType>}
     */
    class YMap extends AbstractType {
      /**
       *
       * @param {Iterable<readonly [string, any]>=} entries - an optional iterable to initialize the YMap
       */
      constructor (entries) {
        super();
        /**
         * @type {Map<string,any>?}
         * @private
         */
        this._prelimContent = null;

        if (entries === undefined) {
          this._prelimContent = new Map();
        } else {
          this._prelimContent = new Map(entries);
        }
      }

      /**
       * Integrate this type into the Yjs instance.
       *
       * * Save this struct in the os
       * * This type is sent to other client
       * * Observer functions are fired
       *
       * @param {Doc} y The Yjs instance
       * @param {Item} item
       */
      _integrate (y, item) {
        super._integrate(y, item)
        ;/** @type {Map<string, any>} */ (this._prelimContent).forEach((value, key) => {
          this.set(key, value);
        });
        this._prelimContent = null;
      }

      /**
       * @return {YMap<MapType>}
       */
      _copy () {
        return new YMap()
      }

      /**
       * @return {YMap<MapType>}
       */
      clone () {
        /**
         * @type {YMap<MapType>}
         */
        const map = new YMap();
        this.forEach((value, key) => {
          map.set(key, value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value);
        });
        return map
      }

      /**
       * Creates YMapEvent and calls observers.
       *
       * @param {Transaction} transaction
       * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
       */
      _callObserver (transaction, parentSubs) {
        callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs));
      }

      /**
       * Transforms this Shared Type to a JSON object.
       *
       * @return {Object<string,any>}
       */
      toJSON () {
        /**
         * @type {Object<string,MapType>}
         */
        const map = {};
        this._map.forEach((item, key) => {
          if (!item.deleted) {
            const v = item.content.getContent()[item.length - 1];
            map[key] = v instanceof AbstractType ? v.toJSON() : v;
          }
        });
        return map
      }

      /**
       * Returns the size of the YMap (count of key/value pairs)
       *
       * @return {number}
       */
      get size () {
        return [...createMapIterator(this._map)].length
      }

      /**
       * Returns the keys for each element in the YMap Type.
       *
       * @return {IterableIterator<string>}
       */
      keys () {
        return iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[0])
      }

      /**
       * Returns the values for each element in the YMap Type.
       *
       * @return {IterableIterator<any>}
       */
      values () {
        return iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
      }

      /**
       * Returns an Iterator of [key, value] pairs
       *
       * @return {IterableIterator<any>}
       */
      entries () {
        return iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => [v[0], v[1].content.getContent()[v[1].length - 1]])
      }

      /**
       * Executes a provided function on once on every key-value pair.
       *
       * @param {function(MapType,string,YMap<MapType>):void} f A function to execute on every element of this YArray.
       */
      forEach (f) {
        this._map.forEach((item, key) => {
          if (!item.deleted) {
            f(item.content.getContent()[item.length - 1], key, this);
          }
        });
      }

      /**
       * Returns an Iterator of [key, value] pairs
       *
       * @return {IterableIterator<any>}
       */
      [Symbol.iterator] () {
        return this.entries()
      }

      /**
       * Remove a specified element from this YMap.
       *
       * @param {string} key The key of the element to remove.
       */
      delete (key) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapDelete(transaction, this, key);
          });
        } else {
          /** @type {Map<string, any>} */ (this._prelimContent).delete(key);
        }
      }

      /**
       * Adds or updates an element with a specified key and value.
       *
       * @param {string} key The key of the element to add to this YMap
       * @param {MapType} value The value of the element to add
       */
      set (key, value) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapSet(transaction, this, key, /** @type {any} */ (value));
          });
        } else {
          /** @type {Map<string, any>} */ (this._prelimContent).set(key, value);
        }
        return value
      }

      /**
       * Returns a specified element from this YMap.
       *
       * @param {string} key
       * @return {MapType|undefined}
       */
      get (key) {
        return /** @type {any} */ (typeMapGet(this, key))
      }

      /**
       * Returns a boolean indicating whether the specified key exists or not.
       *
       * @param {string} key The key to test.
       * @return {boolean}
       */
      has (key) {
        return typeMapHas(this, key)
      }

      /**
       * Removes all elements from this YMap.
       */
      clear () {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            this.forEach(function (_value, key, map) {
              typeMapDelete(transaction, map, key);
            });
          });
        } else {
          /** @type {Map<string, any>} */ (this._prelimContent).clear();
        }
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       */
      _write (encoder) {
        encoder.writeTypeRef(YMapRefID);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
     *
     * @private
     * @function
     */
    const readYMap = _decoder => new YMap();

    /**
     * @param {any} a
     * @param {any} b
     * @return {boolean}
     */
    const equalAttrs = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && equalFlat(a, b));

    class ItemTextListPosition {
      /**
       * @param {Item|null} left
       * @param {Item|null} right
       * @param {number} index
       * @param {Map<string,any>} currentAttributes
       */
      constructor (left, right, index, currentAttributes) {
        this.left = left;
        this.right = right;
        this.index = index;
        this.currentAttributes = currentAttributes;
      }

      /**
       * Only call this if you know that this.right is defined
       */
      forward () {
        if (this.right === null) {
          unexpectedCase();
        }
        switch (this.right.content.constructor) {
          case ContentFormat:
            if (!this.right.deleted) {
              updateCurrentAttributes(this.currentAttributes, /** @type {ContentFormat} */ (this.right.content));
            }
            break
          default:
            if (!this.right.deleted) {
              this.index += this.right.length;
            }
            break
        }
        this.left = this.right;
        this.right = this.right.right;
      }
    }

    /**
     * @param {Transaction} transaction
     * @param {ItemTextListPosition} pos
     * @param {number} count steps to move forward
     * @return {ItemTextListPosition}
     *
     * @private
     * @function
     */
    const findNextPosition = (transaction, pos, count) => {
      while (pos.right !== null && count > 0) {
        switch (pos.right.content.constructor) {
          case ContentFormat:
            if (!pos.right.deleted) {
              updateCurrentAttributes(pos.currentAttributes, /** @type {ContentFormat} */ (pos.right.content));
            }
            break
          default:
            if (!pos.right.deleted) {
              if (count < pos.right.length) {
                // split right
                getItemCleanStart(transaction, createID(pos.right.id.client, pos.right.id.clock + count));
              }
              pos.index += pos.right.length;
              count -= pos.right.length;
            }
            break
        }
        pos.left = pos.right;
        pos.right = pos.right.right;
        // pos.forward() - we don't forward because that would halve the performance because we already do the checks above
      }
      return pos
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {number} index
     * @return {ItemTextListPosition}
     *
     * @private
     * @function
     */
    const findPosition = (transaction, parent, index) => {
      const currentAttributes = new Map();
      const marker = findMarker(parent, index);
      if (marker) {
        const pos = new ItemTextListPosition(marker.p.left, marker.p, marker.index, currentAttributes);
        return findNextPosition(transaction, pos, index - marker.index)
      } else {
        const pos = new ItemTextListPosition(null, parent._start, 0, currentAttributes);
        return findNextPosition(transaction, pos, index)
      }
    };

    /**
     * Negate applied formats
     *
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {ItemTextListPosition} currPos
     * @param {Map<string,any>} negatedAttributes
     *
     * @private
     * @function
     */
    const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes) => {
      // check if we really need to remove attributes
      while (
        currPos.right !== null && (
          currPos.right.deleted === true || (
            currPos.right.content.constructor === ContentFormat &&
            equalAttrs(negatedAttributes.get(/** @type {ContentFormat} */ (currPos.right.content).key), /** @type {ContentFormat} */ (currPos.right.content).value)
          )
        )
      ) {
        if (!currPos.right.deleted) {
          negatedAttributes.delete(/** @type {ContentFormat} */ (currPos.right.content).key);
        }
        currPos.forward();
      }
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      negatedAttributes.forEach((val, key) => {
        const left = currPos.left;
        const right = currPos.right;
        const nextFormat = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val));
        nextFormat.integrate(transaction, 0);
        currPos.right = nextFormat;
        currPos.forward();
      });
    };

    /**
     * @param {Map<string,any>} currentAttributes
     * @param {ContentFormat} format
     *
     * @private
     * @function
     */
    const updateCurrentAttributes = (currentAttributes, format) => {
      const { key, value } = format;
      if (value === null) {
        currentAttributes.delete(key);
      } else {
        currentAttributes.set(key, value);
      }
    };

    /**
     * @param {ItemTextListPosition} currPos
     * @param {Object<string,any>} attributes
     *
     * @private
     * @function
     */
    const minimizeAttributeChanges = (currPos, attributes) => {
      // go right while attributes[right.key] === right.value (or right is deleted)
      while (true) {
        if (currPos.right === null) {
          break
        } else if (currPos.right.deleted || (currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] || null, /** @type {ContentFormat} */ (currPos.right.content).value))) ; else {
          break
        }
        currPos.forward();
      }
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {ItemTextListPosition} currPos
     * @param {Object<string,any>} attributes
     * @return {Map<string,any>}
     *
     * @private
     * @function
     **/
    const insertAttributes = (transaction, parent, currPos, attributes) => {
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      const negatedAttributes = new Map();
      // insert format-start items
      for (const key in attributes) {
        const val = attributes[key];
        const currentVal = currPos.currentAttributes.get(key) || null;
        if (!equalAttrs(currentVal, val)) {
          // save negated attribute (set null if currentVal undefined)
          negatedAttributes.set(key, currentVal);
          const { left, right } = currPos;
          currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val));
          currPos.right.integrate(transaction, 0);
          currPos.forward();
        }
      }
      return negatedAttributes
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {ItemTextListPosition} currPos
     * @param {string|object|AbstractType<any>} text
     * @param {Object<string,any>} attributes
     *
     * @private
     * @function
     **/
    const insertText = (transaction, parent, currPos, text, attributes) => {
      currPos.currentAttributes.forEach((_val, key) => {
        if (attributes[key] === undefined) {
          attributes[key] = null;
        }
      });
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      minimizeAttributeChanges(currPos, attributes);
      const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes);
      // insert content
      const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : (text instanceof AbstractType ? new ContentType(text) : new ContentEmbed(text));
      let { left, right, index } = currPos;
      if (parent._searchMarker) {
        updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength());
      }
      right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content);
      right.integrate(transaction, 0);
      currPos.right = right;
      currPos.index = index;
      currPos.forward();
      insertNegatedAttributes(transaction, parent, currPos, negatedAttributes);
    };

    /**
     * @param {Transaction} transaction
     * @param {AbstractType<any>} parent
     * @param {ItemTextListPosition} currPos
     * @param {number} length
     * @param {Object<string,any>} attributes
     *
     * @private
     * @function
     */
    const formatText = (transaction, parent, currPos, length, attributes) => {
      const doc = transaction.doc;
      const ownClientId = doc.clientID;
      minimizeAttributeChanges(currPos, attributes);
      const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes);
      // iterate until first non-format or null is found
      // delete all formats with attributes[format.key] != null
      // also check the attributes after the first non-format as we do not want to insert redundant negated attributes there
      // eslint-disable-next-line no-labels
      iterationLoop: while (
        currPos.right !== null &&
        (length > 0 ||
          (
            negatedAttributes.size > 0 &&
            (currPos.right.deleted || currPos.right.content.constructor === ContentFormat)
          )
        )
      ) {
        if (!currPos.right.deleted) {
          switch (currPos.right.content.constructor) {
            case ContentFormat: {
              const { key, value } = /** @type {ContentFormat} */ (currPos.right.content);
              const attr = attributes[key];
              if (attr !== undefined) {
                if (equalAttrs(attr, value)) {
                  negatedAttributes.delete(key);
                } else {
                  if (length === 0) {
                    // no need to further extend negatedAttributes
                    // eslint-disable-next-line no-labels
                    break iterationLoop
                  }
                  negatedAttributes.set(key, value);
                }
                currPos.right.delete(transaction);
              } else {
                currPos.currentAttributes.set(key, value);
              }
              break
            }
            default:
              if (length < currPos.right.length) {
                getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length));
              }
              length -= currPos.right.length;
              break
          }
        }
        currPos.forward();
      }
      // Quill just assumes that the editor starts with a newline and that it always
      // ends with a newline. We only insert that newline when a new newline is
      // inserted - i.e when length is bigger than type.length
      if (length > 0) {
        let newlines = '';
        for (; length > 0; length--) {
          newlines += '\n';
        }
        currPos.right = new Item(createID(ownClientId, getState(doc.store, ownClientId)), currPos.left, currPos.left && currPos.left.lastId, currPos.right, currPos.right && currPos.right.id, parent, null, new ContentString(newlines));
        currPos.right.integrate(transaction, 0);
        currPos.forward();
      }
      insertNegatedAttributes(transaction, parent, currPos, negatedAttributes);
    };

    /**
     * Call this function after string content has been deleted in order to
     * clean up formatting Items.
     *
     * @param {Transaction} transaction
     * @param {Item} start
     * @param {Item|null} curr exclusive end, automatically iterates to the next Content Item
     * @param {Map<string,any>} startAttributes
     * @param {Map<string,any>} currAttributes
     * @return {number} The amount of formatting Items deleted.
     *
     * @function
     */
    const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => {
      /**
       * @type {Item|null}
       */
      let end = start;
      /**
       * @type {Map<string,ContentFormat>}
       */
      const endFormats = create$5();
      while (end && (!end.countable || end.deleted)) {
        if (!end.deleted && end.content.constructor === ContentFormat) {
          const cf = /** @type {ContentFormat} */ (end.content);
          endFormats.set(cf.key, cf);
        }
        end = end.right;
      }
      let cleanups = 0;
      let reachedCurr = false;
      while (start !== end) {
        if (curr === start) {
          reachedCurr = true;
        }
        if (!start.deleted) {
          const content = start.content;
          switch (content.constructor) {
            case ContentFormat: {
              const { key, value } = /** @type {ContentFormat} */ (content);
              const startAttrValue = startAttributes.get(key) || null;
              if (endFormats.get(key) !== content || startAttrValue === value) {
                // Either this format is overwritten or it is not necessary because the attribute already existed.
                start.delete(transaction);
                cleanups++;
                if (!reachedCurr && (currAttributes.get(key) || null) === value && startAttrValue !== value) {
                  if (startAttrValue === null) {
                    currAttributes.delete(key);
                  } else {
                    currAttributes.set(key, startAttrValue);
                  }
                }
              }
              if (!reachedCurr && !start.deleted) {
                updateCurrentAttributes(currAttributes, /** @type {ContentFormat} */ (content));
              }
              break
            }
          }
        }
        start = /** @type {Item} */ (start.right);
      }
      return cleanups
    };

    /**
     * @param {Transaction} transaction
     * @param {Item | null} item
     */
    const cleanupContextlessFormattingGap = (transaction, item) => {
      // iterate until item.right is null or content
      while (item && item.right && (item.right.deleted || !item.right.countable)) {
        item = item.right;
      }
      const attrs = new Set();
      // iterate back until a content item is found
      while (item && (item.deleted || !item.countable)) {
        if (!item.deleted && item.content.constructor === ContentFormat) {
          const key = /** @type {ContentFormat} */ (item.content).key;
          if (attrs.has(key)) {
            item.delete(transaction);
          } else {
            attrs.add(key);
          }
        }
        item = item.left;
      }
    };

    /**
     * This function is experimental and subject to change / be removed.
     *
     * Ideally, we don't need this function at all. Formatting attributes should be cleaned up
     * automatically after each change. This function iterates twice over the complete YText type
     * and removes unnecessary formatting attributes. This is also helpful for testing.
     *
     * This function won't be exported anymore as soon as there is confidence that the YText type works as intended.
     *
     * @param {YText} type
     * @return {number} How many formatting attributes have been cleaned up.
     */
    const cleanupYTextFormatting = type => {
      let res = 0;
      transact(/** @type {Doc} */ (type.doc), transaction => {
        let start = /** @type {Item} */ (type._start);
        let end = type._start;
        let startAttributes = create$5();
        const currentAttributes = copy(startAttributes);
        while (end) {
          if (end.deleted === false) {
            switch (end.content.constructor) {
              case ContentFormat:
                updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (end.content));
                break
              default:
                res += cleanupFormattingGap(transaction, start, end, startAttributes, currentAttributes);
                startAttributes = copy(currentAttributes);
                start = end;
                break
            }
          }
          end = end.right;
        }
      });
      return res
    };

    /**
     * @param {Transaction} transaction
     * @param {ItemTextListPosition} currPos
     * @param {number} length
     * @return {ItemTextListPosition}
     *
     * @private
     * @function
     */
    const deleteText = (transaction, currPos, length) => {
      const startLength = length;
      const startAttrs = copy(currPos.currentAttributes);
      const start = currPos.right;
      while (length > 0 && currPos.right !== null) {
        if (currPos.right.deleted === false) {
          switch (currPos.right.content.constructor) {
            case ContentType:
            case ContentEmbed:
            case ContentString:
              if (length < currPos.right.length) {
                getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length));
              }
              length -= currPos.right.length;
              currPos.right.delete(transaction);
              break
          }
        }
        currPos.forward();
      }
      if (start) {
        cleanupFormattingGap(transaction, start, currPos.right, startAttrs, currPos.currentAttributes);
      }
      const parent = /** @type {AbstractType<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent);
      if (parent._searchMarker) {
        updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length);
      }
      return currPos
    };

    /**
     * The Quill Delta format represents changes on a text document with
     * formatting information. For mor information visit {@link https://quilljs.com/docs/delta/|Quill Delta}
     *
     * @example
     *   {
     *     ops: [
     *       { insert: 'Gandalf', attributes: { bold: true } },
     *       { insert: ' the ' },
     *       { insert: 'Grey', attributes: { color: '#cccccc' } }
     *     ]
     *   }
     *
     */

    /**
      * Attributes that can be assigned to a selection of text.
      *
      * @example
      *   {
      *     bold: true,
      *     font-size: '40px'
      *   }
      *
      * @typedef {Object} TextAttributes
      */

    /**
     * @extends YEvent<YText>
     * Event that describes the changes on a YText type.
     */
    class YTextEvent extends YEvent {
      /**
       * @param {YText} ytext
       * @param {Transaction} transaction
       * @param {Set<any>} subs The keys that changed
       */
      constructor (ytext, transaction, subs) {
        super(ytext, transaction);
        /**
         * Whether the children changed.
         * @type {Boolean}
         * @private
         */
        this.childListChanged = false;
        /**
         * Set of all changed attributes.
         * @type {Set<string>}
         */
        this.keysChanged = new Set();
        subs.forEach((sub) => {
          if (sub === null) {
            this.childListChanged = true;
          } else {
            this.keysChanged.add(sub);
          }
        });
      }

      /**
       * @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
       */
      get changes () {
        if (this._changes === null) {
          /**
           * @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string|AbstractType<any>|object, delete?:number, retain?:number}>}}
           */
          const changes = {
            keys: this.keys,
            delta: this.delta,
            added: new Set(),
            deleted: new Set()
          };
          this._changes = changes;
        }
        return /** @type {any} */ (this._changes)
      }

      /**
       * Compute the changes in the delta format.
       * A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
       *
       * @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
       *
       * @public
       */
      get delta () {
        if (this._delta === null) {
          const y = /** @type {Doc} */ (this.target.doc);
          /**
           * @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
           */
          const delta = [];
          transact(y, transaction => {
            const currentAttributes = new Map(); // saves all current attributes for insert
            const oldAttributes = new Map();
            let item = this.target._start;
            /**
             * @type {string?}
             */
            let action = null;
            /**
             * @type {Object<string,any>}
             */
            const attributes = {}; // counts added or removed new attributes for retain
            /**
             * @type {string|object}
             */
            let insert = '';
            let retain = 0;
            let deleteLen = 0;
            const addOp = () => {
              if (action !== null) {
                /**
                 * @type {any}
                 */
                let op = null;
                switch (action) {
                  case 'delete':
                    if (deleteLen > 0) {
                      op = { delete: deleteLen };
                    }
                    deleteLen = 0;
                    break
                  case 'insert':
                    if (typeof insert === 'object' || insert.length > 0) {
                      op = { insert };
                      if (currentAttributes.size > 0) {
                        op.attributes = {};
                        currentAttributes.forEach((value, key) => {
                          if (value !== null) {
                            op.attributes[key] = value;
                          }
                        });
                      }
                    }
                    insert = '';
                    break
                  case 'retain':
                    if (retain > 0) {
                      op = { retain };
                      if (!isEmpty(attributes)) {
                        op.attributes = assign({}, attributes);
                      }
                    }
                    retain = 0;
                    break
                }
                if (op) delta.push(op);
                action = null;
              }
            };
            while (item !== null) {
              switch (item.content.constructor) {
                case ContentType:
                case ContentEmbed:
                  if (this.adds(item)) {
                    if (!this.deletes(item)) {
                      addOp();
                      action = 'insert';
                      insert = item.content.getContent()[0];
                      addOp();
                    }
                  } else if (this.deletes(item)) {
                    if (action !== 'delete') {
                      addOp();
                      action = 'delete';
                    }
                    deleteLen += 1;
                  } else if (!item.deleted) {
                    if (action !== 'retain') {
                      addOp();
                      action = 'retain';
                    }
                    retain += 1;
                  }
                  break
                case ContentString:
                  if (this.adds(item)) {
                    if (!this.deletes(item)) {
                      if (action !== 'insert') {
                        addOp();
                        action = 'insert';
                      }
                      insert += /** @type {ContentString} */ (item.content).str;
                    }
                  } else if (this.deletes(item)) {
                    if (action !== 'delete') {
                      addOp();
                      action = 'delete';
                    }
                    deleteLen += item.length;
                  } else if (!item.deleted) {
                    if (action !== 'retain') {
                      addOp();
                      action = 'retain';
                    }
                    retain += item.length;
                  }
                  break
                case ContentFormat: {
                  const { key, value } = /** @type {ContentFormat} */ (item.content);
                  if (this.adds(item)) {
                    if (!this.deletes(item)) {
                      const curVal = currentAttributes.get(key) || null;
                      if (!equalAttrs(curVal, value)) {
                        if (action === 'retain') {
                          addOp();
                        }
                        if (equalAttrs(value, (oldAttributes.get(key) || null))) {
                          delete attributes[key];
                        } else {
                          attributes[key] = value;
                        }
                      } else if (value !== null) {
                        item.delete(transaction);
                      }
                    }
                  } else if (this.deletes(item)) {
                    oldAttributes.set(key, value);
                    const curVal = currentAttributes.get(key) || null;
                    if (!equalAttrs(curVal, value)) {
                      if (action === 'retain') {
                        addOp();
                      }
                      attributes[key] = curVal;
                    }
                  } else if (!item.deleted) {
                    oldAttributes.set(key, value);
                    const attr = attributes[key];
                    if (attr !== undefined) {
                      if (!equalAttrs(attr, value)) {
                        if (action === 'retain') {
                          addOp();
                        }
                        if (value === null) {
                          delete attributes[key];
                        } else {
                          attributes[key] = value;
                        }
                      } else if (attr !== null) { // this will be cleaned up automatically by the contextless cleanup function
                        item.delete(transaction);
                      }
                    }
                  }
                  if (!item.deleted) {
                    if (action === 'insert') {
                      addOp();
                    }
                    updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (item.content));
                  }
                  break
                }
              }
              item = item.right;
            }
            addOp();
            while (delta.length > 0) {
              const lastOp = delta[delta.length - 1];
              if (lastOp.retain !== undefined && lastOp.attributes === undefined) {
                // retain delta's if they don't assign attributes
                delta.pop();
              } else {
                break
              }
            }
          });
          this._delta = delta;
        }
        return /** @type {any} */ (this._delta)
      }
    }

    /**
     * Type that represents text with formatting information.
     *
     * This type replaces y-richtext as this implementation is able to handle
     * block formats (format information on a paragraph), embeds (complex elements
     * like pictures and videos), and text formats (**bold**, *italic*).
     *
     * @extends AbstractType<YTextEvent>
     */
    class YText extends AbstractType {
      /**
       * @param {String} [string] The initial value of the YText.
       */
      constructor (string) {
        super();
        /**
         * Array of pending operations on this type
         * @type {Array<function():void>?}
         */
        this._pending = string !== undefined ? [() => this.insert(0, string)] : [];
        /**
         * @type {Array<ArraySearchMarker>}
         */
        this._searchMarker = [];
      }

      /**
       * Number of characters of this text type.
       *
       * @type {number}
       */
      get length () {
        return this._length
      }

      /**
       * @param {Doc} y
       * @param {Item} item
       */
      _integrate (y, item) {
        super._integrate(y, item);
        try {
          /** @type {Array<function>} */ (this._pending).forEach(f => f());
        } catch (e) {
          console.error(e);
        }
        this._pending = null;
      }

      _copy () {
        return new YText()
      }

      /**
       * @return {YText}
       */
      clone () {
        const text = new YText();
        text.applyDelta(this.toDelta());
        return text
      }

      /**
       * Creates YTextEvent and calls observers.
       *
       * @param {Transaction} transaction
       * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
       */
      _callObserver (transaction, parentSubs) {
        super._callObserver(transaction, parentSubs);
        const event = new YTextEvent(this, transaction, parentSubs);
        const doc = transaction.doc;
        callTypeObservers(this, transaction, event);
        // If a remote change happened, we try to cleanup potential formatting duplicates.
        if (!transaction.local) {
          // check if another formatting item was inserted
          let foundFormattingItem = false;
          for (const [client, afterClock] of transaction.afterState.entries()) {
            const clock = transaction.beforeState.get(client) || 0;
            if (afterClock === clock) {
              continue
            }
            iterateStructs(transaction, /** @type {Array<Item|GC>} */ (doc.store.clients.get(client)), clock, afterClock, item => {
              if (!item.deleted && /** @type {Item} */ (item).content.constructor === ContentFormat) {
                foundFormattingItem = true;
              }
            });
            if (foundFormattingItem) {
              break
            }
          }
          if (!foundFormattingItem) {
            iterateDeletedStructs(transaction, transaction.deleteSet, item => {
              if (item instanceof GC || foundFormattingItem) {
                return
              }
              if (item.parent === this && item.content.constructor === ContentFormat) {
                foundFormattingItem = true;
              }
            });
          }
          transact(doc, (t) => {
            if (foundFormattingItem) {
              // If a formatting item was inserted, we simply clean the whole type.
              // We need to compute currentAttributes for the current position anyway.
              cleanupYTextFormatting(this);
            } else {
              // If no formatting attribute was inserted, we can make due with contextless
              // formatting cleanups.
              // Contextless: it is not necessary to compute currentAttributes for the affected position.
              iterateDeletedStructs(t, t.deleteSet, item => {
                if (item instanceof GC) {
                  return
                }
                if (item.parent === this) {
                  cleanupContextlessFormattingGap(t, item);
                }
              });
            }
          });
        }
      }

      /**
       * Returns the unformatted string representation of this YText type.
       *
       * @public
       */
      toString () {
        let str = '';
        /**
         * @type {Item|null}
         */
        let n = this._start;
        while (n !== null) {
          if (!n.deleted && n.countable && n.content.constructor === ContentString) {
            str += /** @type {ContentString} */ (n.content).str;
          }
          n = n.right;
        }
        return str
      }

      /**
       * Returns the unformatted string representation of this YText type.
       *
       * @return {string}
       * @public
       */
      toJSON () {
        return this.toString()
      }

      /**
       * Apply a {@link Delta} on this shared YText type.
       *
       * @param {any} delta The changes to apply on this element.
       * @param {object}  opts
       * @param {boolean} [opts.sanitize] Sanitize input delta. Removes ending newlines if set to true.
       *
       *
       * @public
       */
      applyDelta (delta, { sanitize = true } = {}) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            const currPos = new ItemTextListPosition(null, this._start, 0, new Map());
            for (let i = 0; i < delta.length; i++) {
              const op = delta[i];
              if (op.insert !== undefined) {
                // Quill assumes that the content starts with an empty paragraph.
                // Yjs/Y.Text assumes that it starts empty. We always hide that
                // there is a newline at the end of the content.
                // If we omit this step, clients will see a different number of
                // paragraphs, but nothing bad will happen.
                const ins = (!sanitize && typeof op.insert === 'string' && i === delta.length - 1 && currPos.right === null && op.insert.slice(-1) === '\n') ? op.insert.slice(0, -1) : op.insert;
                if (typeof ins !== 'string' || ins.length > 0) {
                  insertText(transaction, this, currPos, ins, op.attributes || {});
                }
              } else if (op.retain !== undefined) {
                formatText(transaction, this, currPos, op.retain, op.attributes || {});
              } else if (op.delete !== undefined) {
                deleteText(transaction, currPos, op.delete);
              }
            }
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.applyDelta(delta));
        }
      }

      /**
       * Returns the Delta representation of this YText type.
       *
       * @param {Snapshot} [snapshot]
       * @param {Snapshot} [prevSnapshot]
       * @param {function('removed' | 'added', ID):any} [computeYChange]
       * @return {any} The Delta representation of this type.
       *
       * @public
       */
      toDelta (snapshot, prevSnapshot, computeYChange) {
        /**
         * @type{Array<any>}
         */
        const ops = [];
        const currentAttributes = new Map();
        const doc = /** @type {Doc} */ (this.doc);
        let str = '';
        let n = this._start;
        function packStr () {
          if (str.length > 0) {
            // pack str with attributes to ops
            /**
             * @type {Object<string,any>}
             */
            const attributes = {};
            let addAttributes = false;
            currentAttributes.forEach((value, key) => {
              addAttributes = true;
              attributes[key] = value;
            });
            /**
             * @type {Object<string,any>}
             */
            const op = { insert: str };
            if (addAttributes) {
              op.attributes = attributes;
            }
            ops.push(op);
            str = '';
          }
        }
        const computeDelta = () => {
          while (n !== null) {
            if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
              switch (n.content.constructor) {
                case ContentString: {
                  const cur = currentAttributes.get('ychange');
                  if (snapshot !== undefined && !isVisible(n, snapshot)) {
                    if (cur === undefined || cur.user !== n.id.client || cur.type !== 'removed') {
                      packStr();
                      currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' });
                    }
                  } else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
                    if (cur === undefined || cur.user !== n.id.client || cur.type !== 'added') {
                      packStr();
                      currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' });
                    }
                  } else if (cur !== undefined) {
                    packStr();
                    currentAttributes.delete('ychange');
                  }
                  str += /** @type {ContentString} */ (n.content).str;
                  break
                }
                case ContentType:
                case ContentEmbed: {
                  packStr();
                  /**
                   * @type {Object<string,any>}
                   */
                  const op = {
                    insert: n.content.getContent()[0]
                  };
                  if (currentAttributes.size > 0) {
                    const attrs = /** @type {Object<string,any>} */ ({});
                    op.attributes = attrs;
                    currentAttributes.forEach((value, key) => {
                      attrs[key] = value;
                    });
                  }
                  ops.push(op);
                  break
                }
                case ContentFormat:
                  if (isVisible(n, snapshot)) {
                    packStr();
                    updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content));
                  }
                  break
              }
            }
            n = n.right;
          }
          packStr();
        };
        if (snapshot || prevSnapshot) {
          // snapshots are merged again after the transaction, so we need to keep the
          // transaction alive until we are done
          transact(doc, transaction => {
            if (snapshot) {
              splitSnapshotAffectedStructs(transaction, snapshot);
            }
            if (prevSnapshot) {
              splitSnapshotAffectedStructs(transaction, prevSnapshot);
            }
            computeDelta();
          }, 'cleanup');
        } else {
          computeDelta();
        }
        return ops
      }

      /**
       * Insert text at a given index.
       *
       * @param {number} index The index at which to start inserting.
       * @param {String} text The text to insert at the specified position.
       * @param {TextAttributes} [attributes] Optionally define some formatting
       *                                    information to apply on the inserted
       *                                    Text.
       * @public
       */
      insert (index, text, attributes) {
        if (text.length <= 0) {
          return
        }
        const y = this.doc;
        if (y !== null) {
          transact(y, transaction => {
            const pos = findPosition(transaction, this, index);
            if (!attributes) {
              attributes = {};
              // @ts-ignore
              pos.currentAttributes.forEach((v, k) => { attributes[k] = v; });
            }
            insertText(transaction, this, pos, text, attributes);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.insert(index, text, attributes));
        }
      }

      /**
       * Inserts an embed at a index.
       *
       * @param {number} index The index to insert the embed at.
       * @param {Object | AbstractType<any>} embed The Object that represents the embed.
       * @param {TextAttributes} attributes Attribute information to apply on the
       *                                    embed
       *
       * @public
       */
      insertEmbed (index, embed, attributes = {}) {
        const y = this.doc;
        if (y !== null) {
          transact(y, transaction => {
            const pos = findPosition(transaction, this, index);
            insertText(transaction, this, pos, embed, attributes);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes));
        }
      }

      /**
       * Deletes text starting from an index.
       *
       * @param {number} index Index at which to start deleting.
       * @param {number} length The number of characters to remove. Defaults to 1.
       *
       * @public
       */
      delete (index, length) {
        if (length === 0) {
          return
        }
        const y = this.doc;
        if (y !== null) {
          transact(y, transaction => {
            deleteText(transaction, findPosition(transaction, this, index), length);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length));
        }
      }

      /**
       * Assigns properties to a range of text.
       *
       * @param {number} index The position where to start formatting.
       * @param {number} length The amount of characters to assign properties to.
       * @param {TextAttributes} attributes Attribute information to apply on the
       *                                    text.
       *
       * @public
       */
      format (index, length, attributes) {
        if (length === 0) {
          return
        }
        const y = this.doc;
        if (y !== null) {
          transact(y, transaction => {
            const pos = findPosition(transaction, this, index);
            if (pos.right === null) {
              return
            }
            formatText(transaction, this, pos, length, attributes);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.format(index, length, attributes));
        }
      }

      /**
       * Removes an attribute.
       *
       * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
       *
       * @param {String} attributeName The attribute name that is to be removed.
       *
       * @public
       */
      removeAttribute (attributeName) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapDelete(transaction, this, attributeName);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.removeAttribute(attributeName));
        }
      }

      /**
       * Sets or updates an attribute.
       *
       * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
       *
       * @param {String} attributeName The attribute name that is to be set.
       * @param {any} attributeValue The attribute value that is to be set.
       *
       * @public
       */
      setAttribute (attributeName, attributeValue) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapSet(transaction, this, attributeName, attributeValue);
          });
        } else {
          /** @type {Array<function>} */ (this._pending).push(() => this.setAttribute(attributeName, attributeValue));
        }
      }

      /**
       * Returns an attribute value that belongs to the attribute name.
       *
       * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
       *
       * @param {String} attributeName The attribute name that identifies the
       *                               queried value.
       * @return {any} The queried attribute value.
       *
       * @public
       */
      getAttribute (attributeName) {
        return /** @type {any} */ (typeMapGet(this, attributeName))
      }

      /**
       * Returns all attribute name/value pairs in a JSON Object.
       *
       * @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
       *
       * @return {Object<string, any>} A JSON Object that describes the attributes.
       *
       * @public
       */
      getAttributes () {
        return typeMapGetAll(this)
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       */
      _write (encoder) {
        encoder.writeTypeRef(YTextRefID);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
     * @return {YText}
     *
     * @private
     * @function
     */
    const readYText = _decoder => new YText();

    /**
     * @module YXml
     */

    /**
     * Define the elements to which a set of CSS queries apply.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
     *
     * @example
     *   query = '.classSelector'
     *   query = 'nodeSelector'
     *   query = '#idSelector'
     *
     * @typedef {string} CSS_Selector
     */

    /**
     * Dom filter function.
     *
     * @callback domFilter
     * @param {string} nodeName The nodeName of the element
     * @param {Map} attributes The map of attributes.
     * @return {boolean} Whether to include the Dom node in the YXmlElement.
     */

    /**
     * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
     * position within them.
     *
     * Can be created with {@link YXmlFragment#createTreeWalker}
     *
     * @public
     * @implements {Iterable<YXmlElement|YXmlText|YXmlElement|YXmlHook>}
     */
    class YXmlTreeWalker {
      /**
       * @param {YXmlFragment | YXmlElement} root
       * @param {function(AbstractType<any>):boolean} [f]
       */
      constructor (root, f = () => true) {
        this._filter = f;
        this._root = root;
        /**
         * @type {Item}
         */
        this._currentNode = /** @type {Item} */ (root._start);
        this._firstCall = true;
      }

      [Symbol.iterator] () {
        return this
      }

      /**
       * Get the next node.
       *
       * @return {IteratorResult<YXmlElement|YXmlText|YXmlHook>} The next node.
       *
       * @public
       */
      next () {
        /**
         * @type {Item|null}
         */
        let n = this._currentNode;
        let type = n && n.content && /** @type {any} */ (n.content).type;
        if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item
          do {
            type = /** @type {any} */ (n.content).type;
            if (!n.deleted && (type.constructor === YXmlElement || type.constructor === YXmlFragment) && type._start !== null) {
              // walk down in the tree
              n = type._start;
            } else {
              // walk right or up in the tree
              while (n !== null) {
                if (n.right !== null) {
                  n = n.right;
                  break
                } else if (n.parent === this._root) {
                  n = null;
                } else {
                  n = /** @type {AbstractType<any>} */ (n.parent)._item;
                }
              }
            }
          } while (n !== null && (n.deleted || !this._filter(/** @type {ContentType} */ (n.content).type)))
        }
        this._firstCall = false;
        if (n === null) {
          // @ts-ignore
          return { value: undefined, done: true }
        }
        this._currentNode = n;
        return { value: /** @type {any} */ (n.content).type, done: false }
      }
    }

    /**
     * Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
     * A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
     * nodeName and it does not have attributes. Though it can be bound to a DOM
     * element - in this case the attributes and the nodeName are not shared.
     *
     * @public
     * @extends AbstractType<YXmlEvent>
     */
    class YXmlFragment extends AbstractType {
      constructor () {
        super();
        /**
         * @type {Array<any>|null}
         */
        this._prelimContent = [];
      }

      /**
       * @type {YXmlElement|YXmlText|null}
       */
      get firstChild () {
        const first = this._first;
        return first ? first.content.getContent()[0] : null
      }

      /**
       * Integrate this type into the Yjs instance.
       *
       * * Save this struct in the os
       * * This type is sent to other client
       * * Observer functions are fired
       *
       * @param {Doc} y The Yjs instance
       * @param {Item} item
       */
      _integrate (y, item) {
        super._integrate(y, item);
        this.insert(0, /** @type {Array<any>} */ (this._prelimContent));
        this._prelimContent = null;
      }

      _copy () {
        return new YXmlFragment()
      }

      /**
       * @return {YXmlFragment}
       */
      clone () {
        const el = new YXmlFragment();
        // @ts-ignore
        el.insert(0, this.toArray().map(item => item instanceof AbstractType ? item.clone() : item));
        return el
      }

      get length () {
        return this._prelimContent === null ? this._length : this._prelimContent.length
      }

      /**
       * Create a subtree of childNodes.
       *
       * @example
       * const walker = elem.createTreeWalker(dom => dom.nodeName === 'div')
       * for (let node in walker) {
       *   // `node` is a div node
       *   nop(node)
       * }
       *
       * @param {function(AbstractType<any>):boolean} filter Function that is called on each child element and
       *                          returns a Boolean indicating whether the child
       *                          is to be included in the subtree.
       * @return {YXmlTreeWalker} A subtree and a position within it.
       *
       * @public
       */
      createTreeWalker (filter) {
        return new YXmlTreeWalker(this, filter)
      }

      /**
       * Returns the first YXmlElement that matches the query.
       * Similar to DOM's {@link querySelector}.
       *
       * Query support:
       *   - tagname
       * TODO:
       *   - id
       *   - attribute
       *
       * @param {CSS_Selector} query The query on the children.
       * @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null.
       *
       * @public
       */
      querySelector (query) {
        query = query.toUpperCase();
        // @ts-ignore
        const iterator = new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query);
        const next = iterator.next();
        if (next.done) {
          return null
        } else {
          return next.value
        }
      }

      /**
       * Returns all YXmlElements that match the query.
       * Similar to Dom's {@link querySelectorAll}.
       *
       * @todo Does not yet support all queries. Currently only query by tagName.
       *
       * @param {CSS_Selector} query The query on the children
       * @return {Array<YXmlElement|YXmlText|YXmlHook|null>} The elements that match this query.
       *
       * @public
       */
      querySelectorAll (query) {
        query = query.toUpperCase();
        // @ts-ignore
        return from(new YXmlTreeWalker(this, element => element.nodeName && element.nodeName.toUpperCase() === query))
      }

      /**
       * Creates YXmlEvent and calls observers.
       *
       * @param {Transaction} transaction
       * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
       */
      _callObserver (transaction, parentSubs) {
        callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction));
      }

      /**
       * Get the string representation of all the children of this YXmlFragment.
       *
       * @return {string} The string representation of all children.
       */
      toString () {
        return typeListMap(this, xml => xml.toString()).join('')
      }

      /**
       * @return {string}
       */
      toJSON () {
        return this.toString()
      }

      /**
       * Creates a Dom Element that mirrors this YXmlElement.
       *
       * @param {Document} [_document=document] The document object (you must define
       *                                        this when calling this method in
       *                                        nodejs)
       * @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
       *                                             are presented in the DOM
       * @param {any} [binding] You should not set this property. This is
       *                               used if DomBinding wants to create a
       *                               association to the created DOM type.
       * @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
       *
       * @public
       */
      toDOM (_document = document, hooks = {}, binding) {
        const fragment = _document.createDocumentFragment();
        if (binding !== undefined) {
          binding._createAssociation(fragment, this);
        }
        typeListForEach(this, xmlType => {
          fragment.insertBefore(xmlType.toDOM(_document, hooks, binding), null);
        });
        return fragment
      }

      /**
       * Inserts new content at an index.
       *
       * @example
       *  // Insert character 'a' at position 0
       *  xml.insert(0, [new Y.XmlText('text')])
       *
       * @param {number} index The index to insert content at
       * @param {Array<YXmlElement|YXmlText>} content The array of content
       */
      insert (index, content) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeListInsertGenerics(transaction, this, index, content);
          });
        } else {
          // @ts-ignore _prelimContent is defined because this is not yet integrated
          this._prelimContent.splice(index, 0, ...content);
        }
      }

      /**
       * Inserts new content at an index.
       *
       * @example
       *  // Insert character 'a' at position 0
       *  xml.insert(0, [new Y.XmlText('text')])
       *
       * @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at
       * @param {Array<YXmlElement|YXmlText>} content The array of content
       */
      insertAfter (ref, content) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            const refItem = (ref && ref instanceof AbstractType) ? ref._item : ref;
            typeListInsertGenericsAfter(transaction, this, refItem, content);
          });
        } else {
          const pc = /** @type {Array<any>} */ (this._prelimContent);
          const index = ref === null ? 0 : pc.findIndex(el => el === ref) + 1;
          if (index === 0 && ref !== null) {
            throw create$3('Reference item not found')
          }
          pc.splice(index, 0, ...content);
        }
      }

      /**
       * Deletes elements starting from an index.
       *
       * @param {number} index Index at which to start deleting elements
       * @param {number} [length=1] The number of elements to remove. Defaults to 1.
       */
      delete (index, length = 1) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeListDelete(transaction, this, index, length);
          });
        } else {
          // @ts-ignore _prelimContent is defined because this is not yet integrated
          this._prelimContent.splice(index, length);
        }
      }

      /**
       * Transforms this YArray to a JavaScript Array.
       *
       * @return {Array<YXmlElement|YXmlText|YXmlHook>}
       */
      toArray () {
        return typeListToArray(this)
      }

      /**
       * Appends content to this YArray.
       *
       * @param {Array<YXmlElement|YXmlText>} content Array of content to append.
       */
      push (content) {
        this.insert(this.length, content);
      }

      /**
       * Preppends content to this YArray.
       *
       * @param {Array<YXmlElement|YXmlText>} content Array of content to preppend.
       */
      unshift (content) {
        this.insert(0, content);
      }

      /**
       * Returns the i-th element from a YArray.
       *
       * @param {number} index The index of the element to return from the YArray
       * @return {YXmlElement|YXmlText}
       */
      get (index) {
        return typeListGet(this, index)
      }

      /**
       * Transforms this YArray to a JavaScript Array.
       *
       * @param {number} [start]
       * @param {number} [end]
       * @return {Array<YXmlElement|YXmlText>}
       */
      slice (start = 0, end = this.length) {
        return typeListSlice(this, start, end)
      }

      /**
       * Executes a provided function on once on overy child element.
       *
       * @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray.
       */
      forEach (f) {
        typeListForEach(this, f);
      }

      /**
       * Transform the properties of this type to binary and write it to an
       * BinaryEncoder.
       *
       * This is called when this Item is sent to a remote peer.
       *
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
       */
      _write (encoder) {
        encoder.writeTypeRef(YXmlFragmentRefID);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder
     * @return {YXmlFragment}
     *
     * @private
     * @function
     */
    const readYXmlFragment = _decoder => new YXmlFragment();

    /**
     * An YXmlElement imitates the behavior of a
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
     *
     * * An YXmlElement has attributes (key value pairs)
     * * An YXmlElement has childElements that must inherit from YXmlElement
     */
    class YXmlElement extends YXmlFragment {
      constructor (nodeName = 'UNDEFINED') {
        super();
        this.nodeName = nodeName;
        /**
         * @type {Map<string, any>|null}
         */
        this._prelimAttrs = new Map();
      }

      /**
       * @type {YXmlElement|YXmlText|null}
       */
      get nextSibling () {
        const n = this._item ? this._item.next : null;
        return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
      }

      /**
       * @type {YXmlElement|YXmlText|null}
       */
      get prevSibling () {
        const n = this._item ? this._item.prev : null;
        return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
      }

      /**
       * Integrate this type into the Yjs instance.
       *
       * * Save this struct in the os
       * * This type is sent to other client
       * * Observer functions are fired
       *
       * @param {Doc} y The Yjs instance
       * @param {Item} item
       */
      _integrate (y, item) {
        super._integrate(y, item)
        ;(/** @type {Map<string, any>} */ (this._prelimAttrs)).forEach((value, key) => {
          this.setAttribute(key, value);
        });
        this._prelimAttrs = null;
      }

      /**
       * Creates an Item with the same effect as this Item (without position effect)
       *
       * @return {YXmlElement}
       */
      _copy () {
        return new YXmlElement(this.nodeName)
      }

      /**
       * @return {YXmlElement}
       */
      clone () {
        const el = new YXmlElement(this.nodeName);
        const attrs = this.getAttributes();
        for (const key in attrs) {
          el.setAttribute(key, attrs[key]);
        }
        // @ts-ignore
        el.insert(0, this.toArray().map(item => item instanceof AbstractType ? item.clone() : item));
        return el
      }

      /**
       * Returns the XML serialization of this YXmlElement.
       * The attributes are ordered by attribute-name, so you can easily use this
       * method to compare YXmlElements
       *
       * @return {string} The string representation of this type.
       *
       * @public
       */
      toString () {
        const attrs = this.getAttributes();
        const stringBuilder = [];
        const keys = [];
        for (const key in attrs) {
          keys.push(key);
        }
        keys.sort();
        const keysLen = keys.length;
        for (let i = 0; i < keysLen; i++) {
          const key = keys[i];
          stringBuilder.push(key + '="' + attrs[key] + '"');
        }
        const nodeName = this.nodeName.toLocaleLowerCase();
        const attrsString = stringBuilder.length > 0 ? ' ' + stringBuilder.join(' ') : '';
        return `<${nodeName}${attrsString}>${super.toString()}</${nodeName}>`
      }

      /**
       * Removes an attribute from this YXmlElement.
       *
       * @param {String} attributeName The attribute name that is to be removed.
       *
       * @public
       */
      removeAttribute (attributeName) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapDelete(transaction, this, attributeName);
          });
        } else {
          /** @type {Map<string,any>} */ (this._prelimAttrs).delete(attributeName);
        }
      }

      /**
       * Sets or updates an attribute.
       *
       * @param {String} attributeName The attribute name that is to be set.
       * @param {String} attributeValue The attribute value that is to be set.
       *
       * @public
       */
      setAttribute (attributeName, attributeValue) {
        if (this.doc !== null) {
          transact(this.doc, transaction => {
            typeMapSet(transaction, this, attributeName, attributeValue);
          });
        } else {
          /** @type {Map<string, any>} */ (this._prelimAttrs).set(attributeName, attributeValue);
        }
      }

      /**
       * Returns an attribute value that belongs to the attribute name.
       *
       * @param {String} attributeName The attribute name that identifies the
       *                               queried value.
       * @return {String} The queried attribute value.
       *
       * @public
       */
      getAttribute (attributeName) {
        return /** @type {any} */ (typeMapGet(this, attributeName))
      }

      /**
       * Returns whether an attribute exists
       *
       * @param {String} attributeName The attribute name to check for existence.
       * @return {boolean} whether the attribute exists.
       *
       * @public
       */
      hasAttribute (attributeName) {
        return /** @type {any} */ (typeMapHas(this, attributeName))
      }

      /**
       * Returns all attribute name/value pairs in a JSON Object.
       *
       * @return {Object<string, any>} A JSON Object that describes the attributes.
       *
       * @public
       */
      getAttributes () {
        return typeMapGetAll(this)
      }

      /**
       * Creates a Dom Element that mirrors this YXmlElement.
       *
       * @param {Document} [_document=document] The document object (you must define
       *                                        this when calling this method in
       *                                        nodejs)
       * @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
       *                                             are presented in the DOM
       * @param {any} [binding] You should not set this property. This is
       *                               used if DomBinding wants to create a
       *                               association to the created DOM type.
       * @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
       *
       * @public
       */
      toDOM (_document = document, hooks = {}, binding) {
        const dom = _document.createElement(this.nodeName);
        const attrs = this.getAttributes();
        for (const key in attrs) {
          dom.setAttribute(key, attrs[key]);
        }
        typeListForEach(this, yxml => {
          dom.appendChild(yxml.toDOM(_document, hooks, binding));
        });
        if (binding !== undefined) {
          binding._createAssociation(dom, this);
        }
        return dom
      }

      /**
       * Transform the properties of this type to binary and write it to an
       * BinaryEncoder.
       *
       * This is called when this Item is sent to a remote peer.
       *
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
       */
      _write (encoder) {
        encoder.writeTypeRef(YXmlElementRefID);
        encoder.writeKey(this.nodeName);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {YXmlElement}
     *
     * @function
     */
    const readYXmlElement = decoder => new YXmlElement(decoder.readKey());

    /**
     * @extends YEvent<YXmlElement|YXmlText|YXmlFragment>
     * An Event that describes changes on a YXml Element or Yxml Fragment
     */
    class YXmlEvent extends YEvent {
      /**
       * @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created.
       * @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
       *                   child list changed.
       * @param {Transaction} transaction The transaction instance with wich the
       *                                  change was created.
       */
      constructor (target, subs, transaction) {
        super(target, transaction);
        /**
         * Whether the children changed.
         * @type {Boolean}
         * @private
         */
        this.childListChanged = false;
        /**
         * Set of all changed attributes.
         * @type {Set<string>}
         */
        this.attributesChanged = new Set();
        subs.forEach((sub) => {
          if (sub === null) {
            this.childListChanged = true;
          } else {
            this.attributesChanged.add(sub);
          }
        });
      }
    }

    /**
     * You can manage binding to a custom type with YXmlHook.
     *
     * @extends {YMap<any>}
     */
    class YXmlHook extends YMap {
      /**
       * @param {string} hookName nodeName of the Dom Node.
       */
      constructor (hookName) {
        super();
        /**
         * @type {string}
         */
        this.hookName = hookName;
      }

      /**
       * Creates an Item with the same effect as this Item (without position effect)
       */
      _copy () {
        return new YXmlHook(this.hookName)
      }

      /**
       * @return {YXmlHook}
       */
      clone () {
        const el = new YXmlHook(this.hookName);
        this.forEach((value, key) => {
          el.set(key, value);
        });
        return el
      }

      /**
       * Creates a Dom Element that mirrors this YXmlElement.
       *
       * @param {Document} [_document=document] The document object (you must define
       *                                        this when calling this method in
       *                                        nodejs)
       * @param {Object.<string, any>} [hooks] Optional property to customize how hooks
       *                                             are presented in the DOM
       * @param {any} [binding] You should not set this property. This is
       *                               used if DomBinding wants to create a
       *                               association to the created DOM type
       * @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
       *
       * @public
       */
      toDOM (_document = document, hooks = {}, binding) {
        const hook = hooks[this.hookName];
        let dom;
        if (hook !== undefined) {
          dom = hook.createDom(this);
        } else {
          dom = document.createElement(this.hookName);
        }
        dom.setAttribute('data-yjs-hook', this.hookName);
        if (binding !== undefined) {
          binding._createAssociation(dom, this);
        }
        return dom
      }

      /**
       * Transform the properties of this type to binary and write it to an
       * BinaryEncoder.
       *
       * This is called when this Item is sent to a remote peer.
       *
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
       */
      _write (encoder) {
        encoder.writeTypeRef(YXmlHookRefID);
        encoder.writeKey(this.hookName);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {YXmlHook}
     *
     * @private
     * @function
     */
    const readYXmlHook = decoder =>
      new YXmlHook(decoder.readKey());

    /**
     * Represents text in a Dom Element. In the future this type will also handle
     * simple formatting information like bold and italic.
     */
    class YXmlText extends YText {
      /**
       * @type {YXmlElement|YXmlText|null}
       */
      get nextSibling () {
        const n = this._item ? this._item.next : null;
        return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
      }

      /**
       * @type {YXmlElement|YXmlText|null}
       */
      get prevSibling () {
        const n = this._item ? this._item.prev : null;
        return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
      }

      _copy () {
        return new YXmlText()
      }

      /**
       * @return {YXmlText}
       */
      clone () {
        const text = new YXmlText();
        text.applyDelta(this.toDelta());
        return text
      }

      /**
       * Creates a Dom Element that mirrors this YXmlText.
       *
       * @param {Document} [_document=document] The document object (you must define
       *                                        this when calling this method in
       *                                        nodejs)
       * @param {Object<string, any>} [hooks] Optional property to customize how hooks
       *                                             are presented in the DOM
       * @param {any} [binding] You should not set this property. This is
       *                               used if DomBinding wants to create a
       *                               association to the created DOM type.
       * @return {Text} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
       *
       * @public
       */
      toDOM (_document = document, hooks, binding) {
        const dom = _document.createTextNode(this.toString());
        if (binding !== undefined) {
          binding._createAssociation(dom, this);
        }
        return dom
      }

      toString () {
        // @ts-ignore
        return this.toDelta().map(delta => {
          const nestedNodes = [];
          for (const nodeName in delta.attributes) {
            const attrs = [];
            for (const key in delta.attributes[nodeName]) {
              attrs.push({ key, value: delta.attributes[nodeName][key] });
            }
            // sort attributes to get a unique order
            attrs.sort((a, b) => a.key < b.key ? -1 : 1);
            nestedNodes.push({ nodeName, attrs });
          }
          // sort node order to get a unique order
          nestedNodes.sort((a, b) => a.nodeName < b.nodeName ? -1 : 1);
          // now convert to dom string
          let str = '';
          for (let i = 0; i < nestedNodes.length; i++) {
            const node = nestedNodes[i];
            str += `<${node.nodeName}`;
            for (let j = 0; j < node.attrs.length; j++) {
              const attr = node.attrs[j];
              str += ` ${attr.key}="${attr.value}"`;
            }
            str += '>';
          }
          str += delta.insert;
          for (let i = nestedNodes.length - 1; i >= 0; i--) {
            str += `</${nestedNodes[i].nodeName}>`;
          }
          return str
        }).join('')
      }

      /**
       * @return {string}
       */
      toJSON () {
        return this.toString()
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       */
      _write (encoder) {
        encoder.writeTypeRef(YXmlTextRefID);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {YXmlText}
     *
     * @private
     * @function
     */
    const readYXmlText = decoder => new YXmlText();

    class AbstractStruct {
      /**
       * @param {ID} id
       * @param {number} length
       */
      constructor (id, length) {
        this.id = id;
        this.length = length;
      }

      /**
       * @type {boolean}
       */
      get deleted () {
        throw methodUnimplemented()
      }

      /**
       * Merge this struct with the item to the right.
       * This method is already assuming that `this.id.clock + this.length === this.id.clock`.
       * Also this method does *not* remove right from StructStore!
       * @param {AbstractStruct} right
       * @return {boolean} wether this merged with right
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
       * @param {number} offset
       * @param {number} encodingRef
       */
      write (encoder, offset, encodingRef) {
        throw methodUnimplemented()
      }

      /**
       * @param {Transaction} transaction
       * @param {number} offset
       */
      integrate (transaction, offset) {
        throw methodUnimplemented()
      }
    }

    const structGCRefNumber = 0;

    /**
     * @private
     */
    class GC extends AbstractStruct {
      get deleted () {
        return true
      }

      delete () {}

      /**
       * @param {GC} right
       * @return {boolean}
       */
      mergeWith (right) {
        if (this.constructor !== right.constructor) {
          return false
        }
        this.length += right.length;
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {number} offset
       */
      integrate (transaction, offset) {
        if (offset > 0) {
          this.id.clock += offset;
          this.length -= offset;
        }
        addStruct(transaction.doc.store, this);
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeInfo(structGCRefNumber);
        encoder.writeLen(this.length - offset);
      }

      /**
       * @param {Transaction} transaction
       * @param {StructStore} store
       * @return {null | number}
       */
      getMissing (transaction, store) {
        return null
      }
    }

    class ContentBinary {
      /**
       * @param {Uint8Array} content
       */
      constructor (content) {
        this.content = content;
      }

      /**
       * @return {number}
       */
      getLength () {
        return 1
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return [this.content]
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentBinary}
       */
      copy () {
        return new ContentBinary(this.content)
      }

      /**
       * @param {number} offset
       * @return {ContentBinary}
       */
      splice (offset) {
        throw methodUnimplemented()
      }

      /**
       * @param {ContentBinary} right
       * @return {boolean}
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {}
      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeBuf(this.content);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 3
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2 } decoder
     * @return {ContentBinary}
     */
    const readContentBinary = decoder => new ContentBinary(decoder.readBuf());

    class ContentDeleted {
      /**
       * @param {number} len
       */
      constructor (len) {
        this.len = len;
      }

      /**
       * @return {number}
       */
      getLength () {
        return this.len
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return []
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return false
      }

      /**
       * @return {ContentDeleted}
       */
      copy () {
        return new ContentDeleted(this.len)
      }

      /**
       * @param {number} offset
       * @return {ContentDeleted}
       */
      splice (offset) {
        const right = new ContentDeleted(this.len - offset);
        this.len = offset;
        return right
      }

      /**
       * @param {ContentDeleted} right
       * @return {boolean}
       */
      mergeWith (right) {
        this.len += right.len;
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {
        addToDeleteSet(transaction.deleteSet, item.id.client, item.id.clock, this.len);
        item.markDeleted();
      }

      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeLen(this.len - offset);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 1
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2 } decoder
     * @return {ContentDeleted}
     */
    const readContentDeleted = decoder => new ContentDeleted(decoder.readLen());

    /**
     * @param {string} guid
     * @param {Object<string, any>} opts
     */
    const createDocFromOpts = (guid, opts) => new Doc({ guid, ...opts, shouldLoad: opts.shouldLoad || opts.autoLoad || false });

    /**
     * @private
     */
    class ContentDoc {
      /**
       * @param {Doc} doc
       */
      constructor (doc) {
        if (doc._item) {
          console.error('This document was already integrated as a sub-document. You should create a second instance instead with the same guid.');
        }
        /**
         * @type {Doc}
         */
        this.doc = doc;
        /**
         * @type {any}
         */
        const opts = {};
        this.opts = opts;
        if (!doc.gc) {
          opts.gc = false;
        }
        if (doc.autoLoad) {
          opts.autoLoad = true;
        }
        if (doc.meta !== null) {
          opts.meta = doc.meta;
        }
      }

      /**
       * @return {number}
       */
      getLength () {
        return 1
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return [this.doc]
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentDoc}
       */
      copy () {
        return new ContentDoc(createDocFromOpts(this.doc.guid, this.opts))
      }

      /**
       * @param {number} offset
       * @return {ContentDoc}
       */
      splice (offset) {
        throw methodUnimplemented()
      }

      /**
       * @param {ContentDoc} right
       * @return {boolean}
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {
        // this needs to be reflected in doc.destroy as well
        this.doc._item = item;
        transaction.subdocsAdded.add(this.doc);
        if (this.doc.shouldLoad) {
          transaction.subdocsLoaded.add(this.doc);
        }
      }

      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {
        if (transaction.subdocsAdded.has(this.doc)) {
          transaction.subdocsAdded.delete(this.doc);
        } else {
          transaction.subdocsRemoved.add(this.doc);
        }
      }

      /**
       * @param {StructStore} store
       */
      gc (store) { }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeString(this.doc.guid);
        encoder.writeAny(this.opts);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 9
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentDoc}
     */
    const readContentDoc = decoder => new ContentDoc(createDocFromOpts(decoder.readString(), decoder.readAny()));

    /**
     * @private
     */
    class ContentEmbed {
      /**
       * @param {Object} embed
       */
      constructor (embed) {
        this.embed = embed;
      }

      /**
       * @return {number}
       */
      getLength () {
        return 1
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return [this.embed]
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentEmbed}
       */
      copy () {
        return new ContentEmbed(this.embed)
      }

      /**
       * @param {number} offset
       * @return {ContentEmbed}
       */
      splice (offset) {
        throw methodUnimplemented()
      }

      /**
       * @param {ContentEmbed} right
       * @return {boolean}
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {}
      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeJSON(this.embed);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 5
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentEmbed}
     */
    const readContentEmbed = decoder => new ContentEmbed(decoder.readJSON());

    /**
     * @private
     */
    class ContentFormat {
      /**
       * @param {string} key
       * @param {Object} value
       */
      constructor (key, value) {
        this.key = key;
        this.value = value;
      }

      /**
       * @return {number}
       */
      getLength () {
        return 1
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return []
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return false
      }

      /**
       * @return {ContentFormat}
       */
      copy () {
        return new ContentFormat(this.key, this.value)
      }

      /**
       * @param {number} offset
       * @return {ContentFormat}
       */
      splice (offset) {
        throw methodUnimplemented()
      }

      /**
       * @param {ContentFormat} right
       * @return {boolean}
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {
        // @todo searchmarker are currently unsupported for rich text documents
        /** @type {AbstractType<any>} */ (item.parent)._searchMarker = null;
      }

      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeKey(this.key);
        encoder.writeJSON(this.value);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 6
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentFormat}
     */
    const readContentFormat = decoder => new ContentFormat(decoder.readKey(), decoder.readJSON());

    /**
     * @private
     */
    class ContentJSON {
      /**
       * @param {Array<any>} arr
       */
      constructor (arr) {
        /**
         * @type {Array<any>}
         */
        this.arr = arr;
      }

      /**
       * @return {number}
       */
      getLength () {
        return this.arr.length
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return this.arr
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentJSON}
       */
      copy () {
        return new ContentJSON(this.arr)
      }

      /**
       * @param {number} offset
       * @return {ContentJSON}
       */
      splice (offset) {
        const right = new ContentJSON(this.arr.slice(offset));
        this.arr = this.arr.slice(0, offset);
        return right
      }

      /**
       * @param {ContentJSON} right
       * @return {boolean}
       */
      mergeWith (right) {
        this.arr = this.arr.concat(right.arr);
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {}
      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        const len = this.arr.length;
        encoder.writeLen(len - offset);
        for (let i = offset; i < len; i++) {
          const c = this.arr[i];
          encoder.writeString(c === undefined ? 'undefined' : JSON.stringify(c));
        }
      }

      /**
       * @return {number}
       */
      getRef () {
        return 2
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentJSON}
     */
    const readContentJSON = decoder => {
      const len = decoder.readLen();
      const cs = [];
      for (let i = 0; i < len; i++) {
        const c = decoder.readString();
        if (c === 'undefined') {
          cs.push(undefined);
        } else {
          cs.push(JSON.parse(c));
        }
      }
      return new ContentJSON(cs)
    };

    class ContentAny {
      /**
       * @param {Array<any>} arr
       */
      constructor (arr) {
        /**
         * @type {Array<any>}
         */
        this.arr = arr;
      }

      /**
       * @return {number}
       */
      getLength () {
        return this.arr.length
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return this.arr
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentAny}
       */
      copy () {
        return new ContentAny(this.arr)
      }

      /**
       * @param {number} offset
       * @return {ContentAny}
       */
      splice (offset) {
        const right = new ContentAny(this.arr.slice(offset));
        this.arr = this.arr.slice(0, offset);
        return right
      }

      /**
       * @param {ContentAny} right
       * @return {boolean}
       */
      mergeWith (right) {
        this.arr = this.arr.concat(right.arr);
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {}
      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        const len = this.arr.length;
        encoder.writeLen(len - offset);
        for (let i = offset; i < len; i++) {
          const c = this.arr[i];
          encoder.writeAny(c);
        }
      }

      /**
       * @return {number}
       */
      getRef () {
        return 8
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentAny}
     */
    const readContentAny = decoder => {
      const len = decoder.readLen();
      const cs = [];
      for (let i = 0; i < len; i++) {
        cs.push(decoder.readAny());
      }
      return new ContentAny(cs)
    };

    /**
     * @private
     */
    class ContentString {
      /**
       * @param {string} str
       */
      constructor (str) {
        /**
         * @type {string}
         */
        this.str = str;
      }

      /**
       * @return {number}
       */
      getLength () {
        return this.str.length
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return this.str.split('')
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentString}
       */
      copy () {
        return new ContentString(this.str)
      }

      /**
       * @param {number} offset
       * @return {ContentString}
       */
      splice (offset) {
        const right = new ContentString(this.str.slice(offset));
        this.str = this.str.slice(0, offset);

        // Prevent encoding invalid documents because of splitting of surrogate pairs: https://github.com/yjs/yjs/issues/248
        const firstCharCode = this.str.charCodeAt(offset - 1);
        if (firstCharCode >= 0xD800 && firstCharCode <= 0xDBFF) {
          // Last character of the left split is the start of a surrogate utf16/ucs2 pair.
          // We don't support splitting of surrogate pairs because this may lead to invalid documents.
          // Replace the invalid character with a unicode replacement character (� / U+FFFD)
          this.str = this.str.slice(0, offset - 1) + '�';
          // replace right as well
          right.str = '�' + right.str.slice(1);
        }
        return right
      }

      /**
       * @param {ContentString} right
       * @return {boolean}
       */
      mergeWith (right) {
        this.str += right.str;
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {}
      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {}
      /**
       * @param {StructStore} store
       */
      gc (store) {}
      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeString(offset === 0 ? this.str : this.str.slice(offset));
      }

      /**
       * @return {number}
       */
      getRef () {
        return 4
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentString}
     */
    const readContentString = decoder => new ContentString(decoder.readString());

    /**
     * @type {Array<function(UpdateDecoderV1 | UpdateDecoderV2):AbstractType<any>>}
     * @private
     */
    const typeRefs = [
      readYArray,
      readYMap,
      readYText,
      readYXmlElement,
      readYXmlFragment,
      readYXmlHook,
      readYXmlText
    ];

    const YArrayRefID = 0;
    const YMapRefID = 1;
    const YTextRefID = 2;
    const YXmlElementRefID = 3;
    const YXmlFragmentRefID = 4;
    const YXmlHookRefID = 5;
    const YXmlTextRefID = 6;

    /**
     * @private
     */
    class ContentType {
      /**
       * @param {AbstractType<any>} type
       */
      constructor (type) {
        /**
         * @type {AbstractType<any>}
         */
        this.type = type;
      }

      /**
       * @return {number}
       */
      getLength () {
        return 1
      }

      /**
       * @return {Array<any>}
       */
      getContent () {
        return [this.type]
      }

      /**
       * @return {boolean}
       */
      isCountable () {
        return true
      }

      /**
       * @return {ContentType}
       */
      copy () {
        return new ContentType(this.type._copy())
      }

      /**
       * @param {number} offset
       * @return {ContentType}
       */
      splice (offset) {
        throw methodUnimplemented()
      }

      /**
       * @param {ContentType} right
       * @return {boolean}
       */
      mergeWith (right) {
        return false
      }

      /**
       * @param {Transaction} transaction
       * @param {Item} item
       */
      integrate (transaction, item) {
        this.type._integrate(transaction.doc, item);
      }

      /**
       * @param {Transaction} transaction
       */
      delete (transaction) {
        let item = this.type._start;
        while (item !== null) {
          if (!item.deleted) {
            item.delete(transaction);
          } else {
            // This will be gc'd later and we want to merge it if possible
            // We try to merge all deleted items after each transaction,
            // but we have no knowledge about that this needs to be merged
            // since it is not in transaction.ds. Hence we add it to transaction._mergeStructs
            transaction._mergeStructs.push(item);
          }
          item = item.right;
        }
        this.type._map.forEach(item => {
          if (!item.deleted) {
            item.delete(transaction);
          } else {
            // same as above
            transaction._mergeStructs.push(item);
          }
        });
        transaction.changed.delete(this.type);
      }

      /**
       * @param {StructStore} store
       */
      gc (store) {
        let item = this.type._start;
        while (item !== null) {
          item.gc(store, true);
          item = item.right;
        }
        this.type._start = null;
        this.type._map.forEach(/** @param {Item | null} item */ (item) => {
          while (item !== null) {
            item.gc(store, true);
            item = item.left;
          }
        });
        this.type._map = new Map();
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        this.type._write(encoder);
      }

      /**
       * @return {number}
       */
      getRef () {
        return 7
      }
    }

    /**
     * @private
     *
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @return {ContentType}
     */
    const readContentType = decoder => new ContentType(typeRefs[decoder.readTypeRef()](decoder));

    /**
     * Split leftItem into two items
     * @param {Transaction} transaction
     * @param {Item} leftItem
     * @param {number} diff
     * @return {Item}
     *
     * @function
     * @private
     */
    const splitItem = (transaction, leftItem, diff) => {
      // create rightItem
      const { client, clock } = leftItem.id;
      const rightItem = new Item(
        createID(client, clock + diff),
        leftItem,
        createID(client, clock + diff - 1),
        leftItem.right,
        leftItem.rightOrigin,
        leftItem.parent,
        leftItem.parentSub,
        leftItem.content.splice(diff)
      );
      if (leftItem.deleted) {
        rightItem.markDeleted();
      }
      if (leftItem.keep) {
        rightItem.keep = true;
      }
      if (leftItem.redone !== null) {
        rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff);
      }
      // update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
      leftItem.right = rightItem;
      // update right
      if (rightItem.right !== null) {
        rightItem.right.left = rightItem;
      }
      // right is more specific.
      transaction._mergeStructs.push(rightItem);
      // update parent._map
      if (rightItem.parentSub !== null && rightItem.right === null) {
        /** @type {AbstractType<any>} */ (rightItem.parent)._map.set(rightItem.parentSub, rightItem);
      }
      leftItem.length = diff;
      return rightItem
    };

    /**
     * Abstract class that represents any content.
     */
    class Item extends AbstractStruct {
      /**
       * @param {ID} id
       * @param {Item | null} left
       * @param {ID | null} origin
       * @param {Item | null} right
       * @param {ID | null} rightOrigin
       * @param {AbstractType<any>|ID|null} parent Is a type if integrated, is null if it is possible to copy parent from left or right, is ID before integration to search for it.
       * @param {string | null} parentSub
       * @param {AbstractContent} content
       */
      constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
        super(id, content.getLength());
        /**
         * The item that was originally to the left of this item.
         * @type {ID | null}
         */
        this.origin = origin;
        /**
         * The item that is currently to the left of this item.
         * @type {Item | null}
         */
        this.left = left;
        /**
         * The item that is currently to the right of this item.
         * @type {Item | null}
         */
        this.right = right;
        /**
         * The item that was originally to the right of this item.
         * @type {ID | null}
         */
        this.rightOrigin = rightOrigin;
        /**
         * @type {AbstractType<any>|ID|null}
         */
        this.parent = parent;
        /**
         * If the parent refers to this item with some kind of key (e.g. YMap, the
         * key is specified here. The key is then used to refer to the list in which
         * to insert this item. If `parentSub = null` type._start is the list in
         * which to insert to. Otherwise it is `parent._map`.
         * @type {String | null}
         */
        this.parentSub = parentSub;
        /**
         * If this type's effect is redone this type refers to the type that undid
         * this operation.
         * @type {ID | null}
         */
        this.redone = null;
        /**
         * @type {AbstractContent}
         */
        this.content = content;
        /**
         * bit1: keep
         * bit2: countable
         * bit3: deleted
         * bit4: mark - mark node as fast-search-marker
         * @type {number} byte
         */
        this.info = this.content.isCountable() ? BIT2 : 0;
      }

      /**
       * This is used to mark the item as an indexed fast-search marker
       *
       * @type {boolean}
       */
      set marker (isMarked) {
        if (((this.info & BIT4) > 0) !== isMarked) {
          this.info ^= BIT4;
        }
      }

      get marker () {
        return (this.info & BIT4) > 0
      }

      /**
       * If true, do not garbage collect this Item.
       */
      get keep () {
        return (this.info & BIT1) > 0
      }

      set keep (doKeep) {
        if (this.keep !== doKeep) {
          this.info ^= BIT1;
        }
      }

      get countable () {
        return (this.info & BIT2) > 0
      }

      /**
       * Whether this item was deleted or not.
       * @type {Boolean}
       */
      get deleted () {
        return (this.info & BIT3) > 0
      }

      set deleted (doDelete) {
        if (this.deleted !== doDelete) {
          this.info ^= BIT3;
        }
      }

      markDeleted () {
        this.info |= BIT3;
      }

      /**
       * Return the creator clientID of the missing op or define missing items and return null.
       *
       * @param {Transaction} transaction
       * @param {StructStore} store
       * @return {null | number}
       */
      getMissing (transaction, store) {
        if (this.origin && this.origin.client !== this.id.client && this.origin.clock >= getState(store, this.origin.client)) {
          return this.origin.client
        }
        if (this.rightOrigin && this.rightOrigin.client !== this.id.client && this.rightOrigin.clock >= getState(store, this.rightOrigin.client)) {
          return this.rightOrigin.client
        }
        if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) {
          return this.parent.client
        }

        // We have all missing ids, now find the items

        if (this.origin) {
          this.left = getItemCleanEnd(transaction, store, this.origin);
          this.origin = this.left.lastId;
        }
        if (this.rightOrigin) {
          this.right = getItemCleanStart(transaction, this.rightOrigin);
          this.rightOrigin = this.right.id;
        }
        if ((this.left && this.left.constructor === GC) || (this.right && this.right.constructor === GC)) {
          this.parent = null;
        }
        // only set parent if this shouldn't be garbage collected
        if (!this.parent) {
          if (this.left && this.left.constructor === Item) {
            this.parent = this.left.parent;
            this.parentSub = this.left.parentSub;
          }
          if (this.right && this.right.constructor === Item) {
            this.parent = this.right.parent;
            this.parentSub = this.right.parentSub;
          }
        } else if (this.parent.constructor === ID) {
          const parentItem = getItem(store, this.parent);
          if (parentItem.constructor === GC) {
            this.parent = null;
          } else {
            this.parent = /** @type {ContentType} */ (parentItem.content).type;
          }
        }
        return null
      }

      /**
       * @param {Transaction} transaction
       * @param {number} offset
       */
      integrate (transaction, offset) {
        if (offset > 0) {
          this.id.clock += offset;
          this.left = getItemCleanEnd(transaction, transaction.doc.store, createID(this.id.client, this.id.clock - 1));
          this.origin = this.left.lastId;
          this.content = this.content.splice(offset);
          this.length -= offset;
        }

        if (this.parent) {
          if ((!this.left && (!this.right || this.right.left !== null)) || (this.left && this.left.right !== this.right)) {
            /**
             * @type {Item|null}
             */
            let left = this.left;

            /**
             * @type {Item|null}
             */
            let o;
            // set o to the first conflicting item
            if (left !== null) {
              o = left.right;
            } else if (this.parentSub !== null) {
              o = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null;
              while (o !== null && o.left !== null) {
                o = o.left;
              }
            } else {
              o = /** @type {AbstractType<any>} */ (this.parent)._start;
            }
            // TODO: use something like DeleteSet here (a tree implementation would be best)
            // @todo use global set definitions
            /**
             * @type {Set<Item>}
             */
            const conflictingItems = new Set();
            /**
             * @type {Set<Item>}
             */
            const itemsBeforeOrigin = new Set();
            // Let c in conflictingItems, b in itemsBeforeOrigin
            // ***{origin}bbbb{this}{c,b}{c,b}{o}***
            // Note that conflictingItems is a subset of itemsBeforeOrigin
            while (o !== null && o !== this.right) {
              itemsBeforeOrigin.add(o);
              conflictingItems.add(o);
              if (compareIDs(this.origin, o.origin)) {
                // case 1
                if (o.id.client < this.id.client) {
                  left = o;
                  conflictingItems.clear();
                } else if (compareIDs(this.rightOrigin, o.rightOrigin)) {
                  // this and o are conflicting and point to the same integration points. The id decides which item comes first.
                  // Since this is to the left of o, we can break here
                  break
                } // else, o might be integrated before an item that this conflicts with. If so, we will find it in the next iterations
              } else if (o.origin !== null && itemsBeforeOrigin.has(getItem(transaction.doc.store, o.origin))) { // use getItem instead of getItemCleanEnd because we don't want / need to split items.
                // case 2
                if (!conflictingItems.has(getItem(transaction.doc.store, o.origin))) {
                  left = o;
                  conflictingItems.clear();
                }
              } else {
                break
              }
              o = o.right;
            }
            this.left = left;
          }
          // reconnect left/right + update parent map/start if necessary
          if (this.left !== null) {
            const right = this.left.right;
            this.right = right;
            this.left.right = this;
          } else {
            let r;
            if (this.parentSub !== null) {
              r = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null;
              while (r !== null && r.left !== null) {
                r = r.left;
              }
            } else {
              r = /** @type {AbstractType<any>} */ (this.parent)._start
              ;/** @type {AbstractType<any>} */ (this.parent)._start = this;
            }
            this.right = r;
          }
          if (this.right !== null) {
            this.right.left = this;
          } else if (this.parentSub !== null) {
            // set as current parent value if right === null and this is parentSub
            /** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this);
            if (this.left !== null) {
              // this is the current attribute value of parent. delete right
              this.left.delete(transaction);
            }
          }
          // adjust length of parent
          if (this.parentSub === null && this.countable && !this.deleted) {
            /** @type {AbstractType<any>} */ (this.parent)._length += this.length;
          }
          addStruct(transaction.doc.store, this);
          this.content.integrate(transaction, this);
          // add parent to transaction.changed
          addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub);
          if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
            // delete if parent is deleted or if this is not the current attribute value of parent
            this.delete(transaction);
          }
        } else {
          // parent is not defined. Integrate GC struct instead
          new GC(this.id, this.length).integrate(transaction, 0);
        }
      }

      /**
       * Returns the next non-deleted item
       */
      get next () {
        let n = this.right;
        while (n !== null && n.deleted) {
          n = n.right;
        }
        return n
      }

      /**
       * Returns the previous non-deleted item
       */
      get prev () {
        let n = this.left;
        while (n !== null && n.deleted) {
          n = n.left;
        }
        return n
      }

      /**
       * Computes the last content address of this Item.
       */
      get lastId () {
        // allocating ids is pretty costly because of the amount of ids created, so we try to reuse whenever possible
        return this.length === 1 ? this.id : createID(this.id.client, this.id.clock + this.length - 1)
      }

      /**
       * Try to merge two items
       *
       * @param {Item} right
       * @return {boolean}
       */
      mergeWith (right) {
        if (
          this.constructor === right.constructor &&
          compareIDs(right.origin, this.lastId) &&
          this.right === right &&
          compareIDs(this.rightOrigin, right.rightOrigin) &&
          this.id.client === right.id.client &&
          this.id.clock + this.length === right.id.clock &&
          this.deleted === right.deleted &&
          this.redone === null &&
          right.redone === null &&
          this.content.constructor === right.content.constructor &&
          this.content.mergeWith(right.content)
        ) {
          const searchMarker = /** @type {AbstractType<any>} */ (this.parent)._searchMarker;
          if (searchMarker) {
            searchMarker.forEach(marker => {
              if (marker.p === right) {
                // right is going to be "forgotten" so we need to update the marker
                marker.p = this;
                // adjust marker index
                if (!this.deleted && this.countable) {
                  marker.index -= this.length;
                }
              }
            });
          }
          if (right.keep) {
            this.keep = true;
          }
          this.right = right.right;
          if (this.right !== null) {
            this.right.left = this;
          }
          this.length += right.length;
          return true
        }
        return false
      }

      /**
       * Mark this Item as deleted.
       *
       * @param {Transaction} transaction
       */
      delete (transaction) {
        if (!this.deleted) {
          const parent = /** @type {AbstractType<any>} */ (this.parent);
          // adjust the length of parent
          if (this.countable && this.parentSub === null) {
            parent._length -= this.length;
          }
          this.markDeleted();
          addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length);
          addChangedTypeToTransaction(transaction, parent, this.parentSub);
          this.content.delete(transaction);
        }
      }

      /**
       * @param {StructStore} store
       * @param {boolean} parentGCd
       */
      gc (store, parentGCd) {
        if (!this.deleted) {
          throw unexpectedCase()
        }
        this.content.gc(store);
        if (parentGCd) {
          replaceStruct(store, this, new GC(this.id, this.length));
        } else {
          this.content = new ContentDeleted(this.length);
        }
      }

      /**
       * Transform the properties of this type to binary and write it to an
       * BinaryEncoder.
       *
       * This is called when this Item is sent to a remote peer.
       *
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
       * @param {number} offset
       */
      write (encoder, offset) {
        const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin;
        const rightOrigin = this.rightOrigin;
        const parentSub = this.parentSub;
        const info = (this.content.getRef() & BITS5) |
          (origin === null ? 0 : BIT8) | // origin is defined
          (rightOrigin === null ? 0 : BIT7) | // right origin is defined
          (parentSub === null ? 0 : BIT6); // parentSub is non-null
        encoder.writeInfo(info);
        if (origin !== null) {
          encoder.writeLeftID(origin);
        }
        if (rightOrigin !== null) {
          encoder.writeRightID(rightOrigin);
        }
        if (origin === null && rightOrigin === null) {
          const parent = /** @type {AbstractType<any>} */ (this.parent);
          if (parent._item !== undefined) {
            const parentItem = parent._item;
            if (parentItem === null) {
              // parent type on y._map
              // find the correct key
              const ykey = findRootTypeKey(parent);
              encoder.writeParentInfo(true); // write parentYKey
              encoder.writeString(ykey);
            } else {
              encoder.writeParentInfo(false); // write parent id
              encoder.writeLeftID(parentItem.id);
            }
          } else if (parent.constructor === String) { // this edge case was added by differential updates
            encoder.writeParentInfo(true); // write parentYKey
            encoder.writeString(parent);
          } else if (parent.constructor === ID) {
            encoder.writeParentInfo(false); // write parent id
            encoder.writeLeftID(parent);
          } else {
            unexpectedCase();
          }
          if (parentSub !== null) {
            encoder.writeString(parentSub);
          }
        }
        this.content.write(encoder, offset);
      }
    }

    /**
     * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
     * @param {number} info
     */
    const readItemContent = (decoder, info) => contentRefs[info & BITS5](decoder);

    /**
     * A lookup map for reading Item content.
     *
     * @type {Array<function(UpdateDecoderV1 | UpdateDecoderV2):AbstractContent>}
     */
    const contentRefs = [
      () => { unexpectedCase(); }, // GC is not ItemContent
      readContentDeleted, // 1
      readContentJSON, // 2
      readContentBinary, // 3
      readContentString, // 4
      readContentEmbed, // 5
      readContentFormat, // 6
      readContentType, // 7
      readContentAny, // 8
      readContentDoc, // 9
      () => { unexpectedCase(); } // 10 - Skip is not ItemContent
    ];

    const structSkipRefNumber = 10;

    /**
     * @private
     */
    class Skip extends AbstractStruct {
      get deleted () {
        return true
      }

      delete () {}

      /**
       * @param {Skip} right
       * @return {boolean}
       */
      mergeWith (right) {
        if (this.constructor !== right.constructor) {
          return false
        }
        this.length += right.length;
        return true
      }

      /**
       * @param {Transaction} transaction
       * @param {number} offset
       */
      integrate (transaction, offset) {
        // skip structs cannot be integrated
        unexpectedCase();
      }

      /**
       * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
       * @param {number} offset
       */
      write (encoder, offset) {
        encoder.writeInfo(structSkipRefNumber);
        // write as VarUint because Skips can't make use of predictable length-encoding
        writeVarUint(encoder.restEncoder, this.length - offset);
      }

      /**
       * @param {Transaction} transaction
       * @param {StructStore} store
       * @return {null | number}
       */
      getMissing (transaction, store) {
        return null
      }
    }

    /** eslint-env browser */

    const glo = /** @type {any} */ (typeof globalThis !== 'undefined'
      ? globalThis
      : typeof window !== 'undefined'
        ? window
        // @ts-ignore
        : typeof global !== 'undefined' ? global : {});

    const importIdentifier = '__ $YJS$ __';

    if (glo[importIdentifier] === true) {
      /**
       * Dear reader of this message. Please take this seriously.
       *
       * If you see this message, make sure that you only import one version of Yjs. In many cases,
       * your package manager installs two versions of Yjs that are used by different packages within your project.
       * Another reason for this message is that some parts of your project use the commonjs version of Yjs
       * and others use the EcmaScript version of Yjs.
       *
       * This often leads to issues that are hard to debug. We often need to perform constructor checks,
       * e.g. `struct instanceof GC`. If you imported different versions of Yjs, it is impossible for us to
       * do the constructor checks anymore - which might break the CRDT algorithm.
       *
       * https://github.com/yjs/yjs/issues/438
       */
      console.error('Yjs was already imported. This breaks constructor checks and will lead to issues! - https://github.com/yjs/yjs/issues/438');
    }
    glo[importIdentifier] = true;

    var LineDirection;
    (function(LineDirection2) {
      LineDirection2[LineDirection2["ROW"] = 0] = "ROW";
      LineDirection2[LineDirection2["COLUMN"] = 1] = "COLUMN";
    })(LineDirection || (LineDirection = {}));
    var DistanceEnum;
    (function(DistanceEnum2) {
      DistanceEnum2[DistanceEnum2["ADSORB"] = 0] = "ADSORB";
      DistanceEnum2[DistanceEnum2["SHOW_LINE"] = 1] = "SHOW_LINE";
      DistanceEnum2[DistanceEnum2["REMOTE"] = 2] = "REMOTE";
    })(DistanceEnum || (DistanceEnum = {}));
    class AlignmentLineService {
      constructor(option, designService) {
        this.option = option;
        this.designService = designService;
        this.boundaryLine = [];
        this.boundaryLine = vue.reactive([]);
        designService.emitter.on("onLayout", this.setPaddingLine.bind(this));
      }
      setPaddingLine() {
        this.designService.emitter.off("onLayout", this.setPaddingLine);
        this.delBoundaryLine("padding");
        const padding = this.designService.modal.pageRect.padding;
        this.boundaryLine.push(...[
          {
            id: "padding-top",
            direction: LineDirection.ROW,
            show: false,
            x: 0,
            y: padding[0],
            width: "100%",
            height: "0"
          },
          {
            id: "padding-bottom",
            direction: LineDirection.ROW,
            show: false,
            x: 0,
            y: this.designService.modal.pageRect.height - padding[2],
            width: "100%",
            height: "0"
          },
          {
            id: "padding-left",
            direction: LineDirection.COLUMN,
            show: false,
            x: padding[3],
            y: 0,
            width: "0",
            height: "100%"
          },
          {
            id: "padding-right",
            direction: LineDirection.COLUMN,
            show: false,
            x: this.designService.modal.pageRect.width - padding[1],
            y: 0,
            width: "0",
            height: "100%"
          }
        ]);
      }
      // 监听newWidget移动的
      onNewWidgetMove(widgets, designService) {
        if (!widgets.length)
          return;
        const boundary = this.designService.utils.getBoundaryByWidget(widgets);
        this.handlerWidgetMove(boundary, this.adsorbNewWidge.bind(this, designService));
      }
      // 监听一组Widget移动
      onWidgetGroupMove(yWidgets) {
        if (!yWidgets.length)
          return;
        const widgets = yWidgets.map((item) => item.toJSON());
        const boundary = this.designService.utils.getBoundaryByWidget(widgets);
        this.handlerWidgetMove(boundary, this.adsorbWidgets.bind(this, yWidgets));
      }
      // 处理widget移动
      handlerWidgetMove(boundary, adsorbHandler) {
        let minRow = null;
        let minCol = null;
        this.boundaryLine.forEach((line) => {
          if (line.direction === LineDirection.ROW) {
            const topDistance = this.getDistance(boundary.top, line.y);
            const bottomDistance = this.getDistance(boundary.bottom, line.y);
            if (topDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (topDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minRow === null || topDistance.distance < minRow.distance) {
                  minRow = {
                    place: "top",
                    line,
                    boundary,
                    distance: topDistance.distance
                  };
                }
              }
            } else if (bottomDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (bottomDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minRow === null || bottomDistance.distance < minRow.distance) {
                  minRow = {
                    place: "bottom",
                    line,
                    boundary,
                    distance: bottomDistance.distance
                  };
                }
              }
            } else {
              line.show = false;
            }
          }
          if (line.direction === LineDirection.COLUMN) {
            const leftDistance = this.getDistance(boundary.left, line.x);
            const rightDistance = this.getDistance(boundary.right, line.x);
            if (leftDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (leftDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minCol === null || leftDistance.distance < minCol.distance) {
                  minCol = {
                    place: "left",
                    line,
                    boundary,
                    distance: leftDistance.distance
                  };
                }
              }
            } else if (rightDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (rightDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minCol === null || rightDistance.distance < minCol.distance) {
                  minCol = {
                    place: "right",
                    line,
                    boundary,
                    distance: rightDistance.distance
                  };
                }
              }
            } else {
              line.show = false;
            }
          }
        });
        minRow && adsorbHandler(minRow.place, minRow.line, minRow.boundary);
        minCol && adsorbHandler(minCol.place, minCol.line, minCol.boundary);
      }
      // 吸附新创建的widget
      adsorbNewWidge(designService, place, line) {
        if (!designService.modal.newWidget)
          return;
        const { x, y } = designService.utils.cavnaseP2NewWidgetP(line);
        switch (place) {
          case "top":
            designService.modal.newWidget.y = y;
            break;
          case "bottom":
            designService.modal.newWidget.y = y - designService.modal.newWidget.height;
            break;
          case "left":
            designService.modal.newWidget.x = x;
            break;
          case "right":
            designService.modal.newWidget.x = x - designService.modal.newWidget.width;
            break;
        }
      }
      // 吸附移动的一组widget
      adsorbWidgets(yWidgets, place, line, boundary) {
        switch (place) {
          case "top":
            yWidgets.forEach((yWidget) => {
              const offset = boundary.top - line.y;
              yWidget.set("y", yWidget.get("baseY") - offset);
            });
            break;
          case "bottom":
            yWidgets.forEach((yWidget) => {
              const offset = boundary.bottom - line.y;
              yWidget.set("y", yWidget.get("baseY") - offset);
            });
            break;
          case "left":
            yWidgets.forEach((yWidget) => {
              const offset = boundary.left - line.x;
              yWidget.set("x", yWidget.get("baseX") - offset);
            });
            break;
          case "right":
            yWidgets.forEach((yWidget) => {
              const offset = boundary.right - line.x;
              yWidget.set("x", yWidget.get("baseX") - offset);
            });
            break;
        }
      }
      // 监听选中区域边界的移动
      onBoundaryMove(selecteds, direct) {
        const widgets = selecteds.map((item) => item.toJSON());
        const boundary = this.designService.utils.getBoundaryByWidget(widgets);
        let minTop = null;
        let minBottom = null;
        let minLeft = null;
        let minRight = null;
        this.boundaryLine.forEach((line) => {
          if (direct[1] === "t") {
            const topDistance = this.getDistance(boundary.top, line.y);
            if (topDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (topDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minTop === null || topDistance.distance < minTop.distance) {
                  minTop = {
                    line,
                    distance: topDistance.distance
                  };
                }
              }
            }
          }
          if (direct[1] === "b") {
            const bottomDistance = this.getDistance(boundary.bottom, line.y);
            if (bottomDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (bottomDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minBottom === null || bottomDistance.distance < minBottom.distance) {
                  minBottom = {
                    line,
                    distance: bottomDistance.distance
                  };
                }
              }
            }
          }
          if (direct[0] === "l") {
            const leftDistance = this.getDistance(boundary.left, line.x);
            if (leftDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (leftDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minLeft === null || leftDistance.distance < minLeft.distance) {
                  minLeft = {
                    line,
                    distance: leftDistance.distance
                  };
                }
              }
            }
          }
          if (direct[0] === "r") {
            const rightDistance = this.getDistance(boundary.right, line.x);
            if (rightDistance.tag <= DistanceEnum.SHOW_LINE) {
              line.show = true;
              if (rightDistance.tag <= DistanceEnum.ADSORB && this.option.enableAdsorb) {
                if (minRight === null || rightDistance.distance < minRight.distance) {
                  minRight = {
                    line,
                    boundary,
                    distance: rightDistance.distance
                  };
                }
              }
            }
          }
        });
        if (minTop !== null) {
          selecteds.forEach((yWidget) => {
            const offset = boundary.top - minTop.line.y;
            yWidget.set("y", yWidget.get("baseY") - offset);
            yWidget.set("height", yWidget.get("height") + offset);
          });
        }
        if (minBottom !== null) {
          selecteds.forEach((yWidget) => {
            const offset = boundary.bottom - minBottom.line.y;
            yWidget.set("y", yWidget.get("baseY"));
            yWidget.set("height", yWidget.get("height") - offset);
          });
        }
        if (minLeft !== null) {
          selecteds.forEach((yWidget) => {
            const offset = boundary.left - minLeft.line.x;
            yWidget.set("x", yWidget.get("baseX") - offset);
            yWidget.set("width", yWidget.get("width") + offset);
          });
        }
        if (minRight !== null) {
          selecteds.forEach((yWidget) => {
            const offset = boundary.right - minRight.line.x;
            yWidget.set("x", yWidget.get("baseX"));
            yWidget.set("width", yWidget.get("width") - offset);
          });
        }
      }
      // 获取Widget离某条线的距离,1:达到吸附距离,2:达到显示距离,3:距离较远
      getDistance(span, line) {
        const distance = Math.abs(span - line);
        let tag = DistanceEnum.REMOTE;
        if (distance < this.option.showAlignSpan) {
          tag = DistanceEnum.SHOW_LINE;
        }
        if (distance < this.option.adsorbSpan) {
          tag = DistanceEnum.ADSORB;
        }
        return {
          distance,
          tag
        };
      }
      // 新增线
      addBoundaryLine(widget) {
        const lineList = [
          {
            id: widget.id + "-top",
            direction: LineDirection.ROW,
            show: false,
            x: 0,
            y: widget.y,
            width: "100%",
            height: "0"
          },
          {
            id: widget.id + "-bottom",
            direction: LineDirection.ROW,
            show: false,
            x: 0,
            y: widget.y + widget.height,
            width: "100%",
            height: "0"
          },
          {
            id: widget.id + "-left",
            direction: LineDirection.COLUMN,
            show: false,
            x: widget.x,
            y: 0,
            width: "0",
            height: "100%"
          },
          {
            id: widget.id + "-right",
            direction: LineDirection.COLUMN,
            show: false,
            x: widget.x + widget.width,
            y: 0,
            width: "0",
            height: "100%"
          }
        ];
        lineList.forEach((line) => {
          const idx = this.boundaryLine.findIndex((item) => item.id === line.id);
          if (idx !== -1) {
            this.boundaryLine[idx] = Object.assign(this.boundaryLine[idx], line);
          } else {
            this.boundaryLine.push(line);
          }
        });
      }
      // 删除线
      delBoundaryLine(id) {
        let idx = this.boundaryLine.findIndex((item) => item.id === id + "-top");
        idx !== -1 && this.boundaryLine.splice(idx, 1);
        idx = this.boundaryLine.findIndex((item) => item.id === id + "-bottom");
        idx !== -1 && this.boundaryLine.splice(idx, 1);
        idx = this.boundaryLine.findIndex((item) => item.id === id + "-left");
        idx !== -1 && this.boundaryLine.splice(idx, 1);
        idx = this.boundaryLine.findIndex((item) => item.id === id + "-right");
        idx !== -1 && this.boundaryLine.splice(idx, 1);
      }
      // 删除所有线
      clearAllLine() {
        this.boundaryLine.length = 0;
      }
      // 隐藏所有线
      hideAllLine() {
        this.boundaryLine.forEach((line) => {
          line.show = false;
        });
      }
      // 根据yjs更新的数据处理对齐线
      handlerAlignmentLine(updateData) {
        var _a, _b;
        if (updateData.type === "array") {
          if (CheckType.isArray((_a = updateData.deltaData) === null || _a === void 0 ? void 0 : _a.insert)) {
            (_b = updateData.deltaData) === null || _b === void 0 ? void 0 : _b.insert.forEach((item) => {
              if (item instanceof YMap) {
                this.addBoundaryLine(item.toJSON());
              }
            });
          }
        } else if (updateData.type === "map") {
          if (updateData.target.get("moveing") === true || updateData.target.get("resizing") === true) {
            this.delBoundaryLine(updateData.target.get("id"));
          } else if (updateData.target.get("moveing") === false || updateData.target.get("resizing") === false) {
            this.addBoundaryLine(updateData.target.toJSON());
            this.hideAllLine();
          }
        }
      }
      // 左对齐
      leftJustify() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { left } = this.designService.utils.getBoundaryByWidget(widgets);
        this.designService.modal.selecteds.forEach((yWidgets) => yWidgets.set("x", left));
      }
      // 右对齐
      rightJustify() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { right } = this.designService.utils.getBoundaryByWidget(widgets);
        this.designService.modal.selecteds.forEach((yWidgets) => {
          const width = yWidgets.get("width");
          yWidgets.set("x", right - width);
        });
      }
      // 上对齐
      topJustify() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { top } = this.designService.utils.getBoundaryByWidget(widgets);
        this.designService.modal.selecteds.forEach((yWidgets) => yWidgets.set("y", top));
      }
      // 下对齐
      bottomJustify() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { bottom } = this.designService.utils.getBoundaryByWidget(widgets);
        this.designService.modal.selecteds.forEach((yWidgets) => {
          const height = yWidgets.get("height");
          yWidgets.set("y", bottom - height);
        });
      }
      // 横向两端对齐
      rowBetween() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { left, right } = this.designService.utils.getBoundaryByWidget(widgets);
        const widgetsWidth = widgets.reduce((total, widget) => total + widget.width, 0);
        const boundaryWidth = right - left;
        const interval = (boundaryWidth - widgetsWidth) / (widgets.length - 1);
        let currentLeft = left;
        this.designService.modal.selecteds.sort((a, b) => a.get("x") - b.get("x")).forEach((yWidget, index) => {
          if (index === 0) {
            currentLeft += yWidget.get("width");
          } else {
            currentLeft += interval;
            yWidget.set("x", currentLeft);
            currentLeft += yWidget.get("width");
          }
        });
      }
      // 纵向两端对齐
      columnBetween() {
        const widgets = this.designService.modal.selecteds.map((item) => item.toJSON());
        const { top, bottom } = this.designService.utils.getBoundaryByWidget(widgets);
        const widgetsHeight = widgets.reduce((total, widget) => total + widget.height, 0);
        const boundaryHeight = bottom - top;
        const interval = (boundaryHeight - widgetsHeight) / (widgets.length - 1);
        let currentTop = top;
        this.designService.modal.selecteds.sort((a, b) => a.get("y") - b.get("y")).forEach((yWidget, index) => {
          if (index === 0) {
            currentTop += yWidget.get("height");
          } else {
            currentTop += interval;
            yWidget.set("y", currentTop);
            currentTop += yWidget.get("height");
          }
        });
      }
    }

    /* eslint-env browser */

    /**
     * @typedef {Object} Channel
     * @property {Set<function(any, any):any>} Channel.subs
     * @property {any} Channel.bc
     */

    /**
     * @type {Map<string, Channel>}
     */
    const channels = new Map();

    /* c8 ignore start */
    class LocalStoragePolyfill {
      /**
       * @param {string} room
       */
      constructor (room) {
        this.room = room;
        /**
         * @type {null|function({data:ArrayBuffer}):void}
         */
        this.onmessage = null;
        /**
         * @param {any} e
         */
        this._onChange = e => e.key === room && this.onmessage !== null && this.onmessage({ data: fromBase64(e.newValue || '') });
        onChange(this._onChange);
      }

      /**
       * @param {ArrayBuffer} buf
       */
      postMessage (buf) {
        varStorage.setItem(this.room, toBase64(createUint8ArrayFromArrayBuffer(buf)));
      }

      close () {
        offChange(this._onChange);
      }
    }
    /* c8 ignore stop */

    // Use BroadcastChannel or Polyfill
    /* c8 ignore next */
    const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel;

    /**
     * @param {string} room
     * @return {Channel}
     */
    const getChannel = room =>
      setIfUndefined(channels, room, () => {
        const subs = create$4();
        const bc = new BC(room);
        /**
         * @param {{data:ArrayBuffer}} e
         */
        /* c8 ignore next */
        bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel'));
        return {
          bc, subs
        }
      });

    /**
     * Subscribe to global `publish` events.
     *
     * @function
     * @param {string} room
     * @param {function(any, any):any} f
     */
    const subscribe = (room, f) => {
      getChannel(room).subs.add(f);
      return f
    };

    /**
     * Unsubscribe from `publish` global events.
     *
     * @function
     * @param {string} room
     * @param {function(any, any):any} f
     */
    const unsubscribe = (room, f) => {
      const channel = getChannel(room);
      const unsubscribed = channel.subs.delete(f);
      if (unsubscribed && channel.subs.size === 0) {
        channel.bc.close();
        channels.delete(room);
      }
      return unsubscribed
    };

    /**
     * Publish data to all subscribers (including subscribers on this tab)
     *
     * @function
     * @param {string} room
     * @param {any} data
     * @param {any} [origin]
     */
    const publish = (room, data, origin = null) => {
      const c = getChannel(room);
      c.bc.postMessage(data);
      c.subs.forEach(sub => sub(data, origin));
    };

    /**
     * @module sync-protocol
     */

    /**
     * @typedef {Map<number, number>} StateMap
     */

    /**
     * Core Yjs defines two message types:
     * • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2.
     * • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it
     *   received all information from the remote client.
     *
     * In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection
     * with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both
     * SyncStep2 and SyncDone, it is assured that it is synced to the remote client.
     *
     * In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1.
     * When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies
     * with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the
     * client knows that the sync is finished.  There are two reasons for this more elaborated sync model: 1. This protocol can
     * easily be implemented on top of http and websockets. 2. The server shoul only reply to requests, and not initiate them.
     * Therefore it is necesarry that the client initiates the sync.
     *
     * Construction of a message:
     * [messageType : varUint, message definition..]
     *
     * Note: A message does not include information about the room name. This must to be handled by the upper layer protocol!
     *
     * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer)
     */

    const messageYjsSyncStep1 = 0;
    const messageYjsSyncStep2 = 1;
    const messageYjsUpdate = 2;

    /**
     * Create a sync step 1 message based on the state of the current shared document.
     *
     * @param {encoding.Encoder} encoder
     * @param {Y.Doc} doc
     */
    const writeSyncStep1 = (encoder, doc) => {
      writeVarUint(encoder, messageYjsSyncStep1);
      const sv = encodeStateVector(doc);
      writeVarUint8Array(encoder, sv);
    };

    /**
     * @param {encoding.Encoder} encoder
     * @param {Y.Doc} doc
     * @param {Uint8Array} [encodedStateVector]
     */
    const writeSyncStep2 = (encoder, doc, encodedStateVector) => {
      writeVarUint(encoder, messageYjsSyncStep2);
      writeVarUint8Array(encoder, encodeStateAsUpdate(doc, encodedStateVector));
    };

    /**
     * Read SyncStep1 message and reply with SyncStep2.
     *
     * @param {decoding.Decoder} decoder The reply to the received message
     * @param {encoding.Encoder} encoder The received message
     * @param {Y.Doc} doc
     */
    const readSyncStep1 = (decoder, encoder, doc) =>
      writeSyncStep2(encoder, doc, readVarUint8Array(decoder));

    /**
     * Read and apply Structs and then DeleteStore to a y instance.
     *
     * @param {decoding.Decoder} decoder
     * @param {Y.Doc} doc
     * @param {any} transactionOrigin
     */
    const readSyncStep2 = (decoder, doc, transactionOrigin) => {
      try {
        applyUpdate(doc, readVarUint8Array(decoder), transactionOrigin);
      } catch (error) {
        // This catches errors that are thrown by event handlers
        console.error('Caught error while handling a Yjs update', error);
      }
    };

    /**
     * @param {encoding.Encoder} encoder
     * @param {Uint8Array} update
     */
    const writeUpdate = (encoder, update) => {
      writeVarUint(encoder, messageYjsUpdate);
      writeVarUint8Array(encoder, update);
    };

    /**
     * Read and apply Structs and then DeleteStore to a y instance.
     *
     * @param {decoding.Decoder} decoder
     * @param {Y.Doc} doc
     * @param {any} transactionOrigin
     */
    const readUpdate = readSyncStep2;

    /**
     * @param {decoding.Decoder} decoder A message received from another client
     * @param {encoding.Encoder} encoder The reply message. Will not be sent if empty.
     * @param {Y.Doc} doc
     * @param {any} transactionOrigin
     */
    const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
      const messageType = readVarUint(decoder);
      switch (messageType) {
        case messageYjsSyncStep1:
          readSyncStep1(decoder, encoder, doc);
          break
        case messageYjsSyncStep2:
          readSyncStep2(decoder, doc, transactionOrigin);
          break
        case messageYjsUpdate:
          readUpdate(decoder, doc, transactionOrigin);
          break
        default:
          throw new Error('Unknown message type')
      }
      return messageType
    };

    const messagePermissionDenied = 0;

    /**
     * @callback PermissionDeniedHandler
     * @param {any} y
     * @param {string} reason
     */

    /**
     *
     * @param {decoding.Decoder} decoder
     * @param {Y.Doc} y
     * @param {PermissionDeniedHandler} permissionDeniedHandler
     */
    const readAuthMessage = (decoder, y, permissionDeniedHandler) => {
      switch (readVarUint(decoder)) {
        case messagePermissionDenied: permissionDeniedHandler(y, readVarString(decoder));
      }
    };

    /**
     * @module awareness-protocol
     */

    const outdatedTimeout = 30000;

    /**
     * @typedef {Object} MetaClientState
     * @property {number} MetaClientState.clock
     * @property {number} MetaClientState.lastUpdated unix timestamp
     */

    /**
     * The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
     * (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
     * remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
     *
     * Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
     * its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
     * applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
     * a remote client is offline, it may propagate a message with
     * `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
     * message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
     *
     * Before a client disconnects, it should propagate a `null` state with an updated clock.
     *
     * Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
     *
     * @extends {Observable<string>}
     */
    class Awareness extends Observable {
      /**
       * @param {Y.Doc} doc
       */
      constructor (doc) {
        super();
        this.doc = doc;
        /**
         * @type {number}
         */
        this.clientID = doc.clientID;
        /**
         * Maps from client id to client state
         * @type {Map<number, Object<string, any>>}
         */
        this.states = new Map();
        /**
         * @type {Map<number, MetaClientState>}
         */
        this.meta = new Map();
        this._checkInterval = /** @type {any} */ (setInterval(() => {
          const now = getUnixTime();
          if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated)) {
            // renew local clock
            this.setLocalState(this.getLocalState());
          }
          /**
           * @type {Array<number>}
           */
          const remove = [];
          this.meta.forEach((meta, clientid) => {
            if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
              remove.push(clientid);
            }
          });
          if (remove.length > 0) {
            removeAwarenessStates(this, remove, 'timeout');
          }
        }, floor(outdatedTimeout / 10)));
        doc.on('destroy', () => {
          this.destroy();
        });
        this.setLocalState({});
      }

      destroy () {
        this.emit('destroy', [this]);
        this.setLocalState(null);
        super.destroy();
        clearInterval(this._checkInterval);
      }

      /**
       * @return {Object<string,any>|null}
       */
      getLocalState () {
        return this.states.get(this.clientID) || null
      }

      /**
       * @param {Object<string,any>|null} state
       */
      setLocalState (state) {
        const clientID = this.clientID;
        const currLocalMeta = this.meta.get(clientID);
        const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
        const prevState = this.states.get(clientID);
        if (state === null) {
          this.states.delete(clientID);
        } else {
          this.states.set(clientID, state);
        }
        this.meta.set(clientID, {
          clock,
          lastUpdated: getUnixTime()
        });
        const added = [];
        const updated = [];
        const filteredUpdated = [];
        const removed = [];
        if (state === null) {
          removed.push(clientID);
        } else if (prevState == null) {
          if (state != null) {
            added.push(clientID);
          }
        } else {
          updated.push(clientID);
          if (!equalityDeep(prevState, state)) {
            filteredUpdated.push(clientID);
          }
        }
        if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
          this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
        }
        this.emit('update', [{ added, updated, removed }, 'local']);
      }

      /**
       * @param {string} field
       * @param {any} value
       */
      setLocalStateField (field, value) {
        const state = this.getLocalState();
        if (state !== null) {
          this.setLocalState({
            ...state,
            [field]: value
          });
        }
      }

      /**
       * @return {Map<number,Object<string,any>>}
       */
      getStates () {
        return this.states
      }
    }

    /**
     * Mark (remote) clients as inactive and remove them from the list of active peers.
     * This change will be propagated to remote clients.
     *
     * @param {Awareness} awareness
     * @param {Array<number>} clients
     * @param {any} origin
     */
    const removeAwarenessStates = (awareness, clients, origin) => {
      const removed = [];
      for (let i = 0; i < clients.length; i++) {
        const clientID = clients[i];
        if (awareness.states.has(clientID)) {
          awareness.states.delete(clientID);
          if (clientID === awareness.clientID) {
            const curMeta = /** @type {MetaClientState} */ (awareness.meta.get(clientID));
            awareness.meta.set(clientID, {
              clock: curMeta.clock + 1,
              lastUpdated: getUnixTime()
            });
          }
          removed.push(clientID);
        }
      }
      if (removed.length > 0) {
        awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
        awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
      }
    };

    /**
     * @param {Awareness} awareness
     * @param {Array<number>} clients
     * @return {Uint8Array}
     */
    const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => {
      const len = clients.length;
      const encoder = createEncoder();
      writeVarUint(encoder, len);
      for (let i = 0; i < len; i++) {
        const clientID = clients[i];
        const state = states.get(clientID) || null;
        const clock = /** @type {MetaClientState} */ (awareness.meta.get(clientID)).clock;
        writeVarUint(encoder, clientID);
        writeVarUint(encoder, clock);
        writeVarString(encoder, JSON.stringify(state));
      }
      return toUint8Array(encoder)
    };

    /**
     * @param {Awareness} awareness
     * @param {Uint8Array} update
     * @param {any} origin This will be added to the emitted change event
     */
    const applyAwarenessUpdate = (awareness, update, origin) => {
      const decoder = createDecoder(update);
      const timestamp = getUnixTime();
      const added = [];
      const updated = [];
      const filteredUpdated = [];
      const removed = [];
      const len = readVarUint(decoder);
      for (let i = 0; i < len; i++) {
        const clientID = readVarUint(decoder);
        let clock = readVarUint(decoder);
        const state = JSON.parse(readVarString(decoder));
        const clientMeta = awareness.meta.get(clientID);
        const prevState = awareness.states.get(clientID);
        const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
        if (currClock < clock || (currClock === clock && state === null && awareness.states.has(clientID))) {
          if (state === null) {
            // never let a remote client remove this local state
            if (clientID === awareness.clientID && awareness.getLocalState() != null) {
              // remote client removed the local state. Do not remote state. Broadcast a message indicating
              // that this client still exists by increasing the clock
              clock++;
            } else {
              awareness.states.delete(clientID);
            }
          } else {
            awareness.states.set(clientID, state);
          }
          awareness.meta.set(clientID, {
            clock,
            lastUpdated: timestamp
          });
          if (clientMeta === undefined && state !== null) {
            added.push(clientID);
          } else if (clientMeta !== undefined && state === null) {
            removed.push(clientID);
          } else if (state !== null) {
            if (!equalityDeep(state, prevState)) {
              filteredUpdated.push(clientID);
            }
            updated.push(clientID);
          }
        }
      }
      if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
        awareness.emit('change', [{
          added, updated: filteredUpdated, removed
        }, origin]);
      }
      if (added.length > 0 || updated.length > 0 || removed.length > 0) {
        awareness.emit('update', [{
          added, updated, removed
        }, origin]);
      }
    };

    /**
     * Utility module to work with urls.
     *
     * @module url
     */

    /**
     * @param {Object<string,string>} params
     * @return {string}
     */
    const encodeQueryParams = params =>
      map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');

    /**
     * @module provider/websocket
     */

    const messageSync = 0;
    const messageQueryAwareness = 3;
    const messageAwareness = 1;
    const messageAuth = 2;

    /**
     *                       encoder,          decoder,          provider,          emitSynced, messageType
     * @type {Array<function(encoding.Encoder, decoding.Decoder, WebsocketProvider, boolean,    number):void>}
     */
    const messageHandlers = [];

    messageHandlers[messageSync] = (
      encoder,
      decoder,
      provider,
      emitSynced,
      _messageType
    ) => {
      writeVarUint(encoder, messageSync);
      const syncMessageType = readSyncMessage(
        decoder,
        encoder,
        provider.doc,
        provider
      );
      if (
        emitSynced && syncMessageType === messageYjsSyncStep2 &&
        !provider.synced
      ) {
        provider.synced = true;
      }
    };

    messageHandlers[messageQueryAwareness] = (
      encoder,
      _decoder,
      provider,
      _emitSynced,
      _messageType
    ) => {
      writeVarUint(encoder, messageAwareness);
      writeVarUint8Array(
        encoder,
        encodeAwarenessUpdate(
          provider.awareness,
          Array.from(provider.awareness.getStates().keys())
        )
      );
    };

    messageHandlers[messageAwareness] = (
      _encoder,
      decoder,
      provider,
      _emitSynced,
      _messageType
    ) => {
      applyAwarenessUpdate(
        provider.awareness,
        readVarUint8Array(decoder),
        provider
      );
    };

    messageHandlers[messageAuth] = (
      _encoder,
      decoder,
      provider,
      _emitSynced,
      _messageType
    ) => {
      readAuthMessage(
        decoder,
        provider.doc,
        (_ydoc, reason) => permissionDeniedHandler(provider, reason)
      );
    };

    // @todo - this should depend on awareness.outdatedTime
    const messageReconnectTimeout = 30000;

    /**
     * @param {WebsocketProvider} provider
     * @param {string} reason
     */
    const permissionDeniedHandler = (provider, reason) =>
      console.warn(`Permission denied to access ${provider.url}.\n${reason}`);

    /**
     * @param {WebsocketProvider} provider
     * @param {Uint8Array} buf
     * @param {boolean} emitSynced
     * @return {encoding.Encoder}
     */
    const readMessage = (provider, buf, emitSynced) => {
      const decoder = createDecoder(buf);
      const encoder = createEncoder();
      const messageType = readVarUint(decoder);
      const messageHandler = provider.messageHandlers[messageType];
      if (/** @type {any} */ (messageHandler)) {
        messageHandler(encoder, decoder, provider, emitSynced, messageType);
      } else {
        console.error('Unable to compute message');
      }
      return encoder
    };

    /**
     * @param {WebsocketProvider} provider
     */
    const setupWS = (provider) => {
      if (provider.shouldConnect && provider.ws === null) {
        const websocket = new provider._WS(provider.url);
        websocket.binaryType = 'arraybuffer';
        provider.ws = websocket;
        provider.wsconnecting = true;
        provider.wsconnected = false;
        provider.synced = false;

        websocket.onmessage = (event) => {
          provider.wsLastMessageReceived = getUnixTime();
          const encoder = readMessage(provider, new Uint8Array(event.data), true);
          if (length(encoder) > 1) {
            websocket.send(toUint8Array(encoder));
          }
        };
        websocket.onerror = (event) => {
          provider.emit('connection-error', [event, provider]);
        };
        websocket.onclose = (event) => {
          provider.emit('connection-close', [event, provider]);
          provider.ws = null;
          provider.wsconnecting = false;
          if (provider.wsconnected) {
            provider.wsconnected = false;
            provider.synced = false;
            // update awareness (all users except local left)
            removeAwarenessStates(
              provider.awareness,
              Array.from(provider.awareness.getStates().keys()).filter((client) =>
                client !== provider.doc.clientID
              ),
              provider
            );
            provider.emit('status', [{
              status: 'disconnected'
            }]);
          } else {
            provider.wsUnsuccessfulReconnects++;
          }
          // Start with no reconnect timeout and increase timeout by
          // using exponential backoff starting with 100ms
          setTimeout(
            setupWS,
            min(
              pow(2, provider.wsUnsuccessfulReconnects) * 100,
              provider.maxBackoffTime
            ),
            provider
          );
        };
        websocket.onopen = () => {
          provider.wsLastMessageReceived = getUnixTime();
          provider.wsconnecting = false;
          provider.wsconnected = true;
          provider.wsUnsuccessfulReconnects = 0;
          provider.emit('status', [{
            status: 'connected'
          }]);
          // always send sync step 1 when connected
          const encoder = createEncoder();
          writeVarUint(encoder, messageSync);
          writeSyncStep1(encoder, provider.doc);
          websocket.send(toUint8Array(encoder));
          // broadcast local awareness state
          if (provider.awareness.getLocalState() !== null) {
            const encoderAwarenessState = createEncoder();
            writeVarUint(encoderAwarenessState, messageAwareness);
            writeVarUint8Array(
              encoderAwarenessState,
              encodeAwarenessUpdate(provider.awareness, [
                provider.doc.clientID
              ])
            );
            websocket.send(toUint8Array(encoderAwarenessState));
          }
        };
        provider.emit('status', [{
          status: 'connecting'
        }]);
      }
    };

    /**
     * @param {WebsocketProvider} provider
     * @param {ArrayBuffer} buf
     */
    const broadcastMessage = (provider, buf) => {
      const ws = provider.ws;
      if (provider.wsconnected && ws && ws.readyState === ws.OPEN) {
        ws.send(buf);
      }
      if (provider.bcconnected) {
        publish(provider.bcChannel, buf, provider);
      }
    };

    /**
     * Websocket Provider for Yjs. Creates a websocket connection to sync the shared document.
     * The document name is attached to the provided url. I.e. the following example
     * creates a websocket connection to http://localhost:1234/my-document-name
     *
     * @example
     *   import * as Y from 'yjs'
     *   import { WebsocketProvider } from 'y-websocket'
     *   const doc = new Y.Doc()
     *   const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc)
     *
     * @extends {Observable<string>}
     */
    class WebsocketProvider extends Observable {
      /**
       * @param {string} serverUrl
       * @param {string} roomname
       * @param {Y.Doc} doc
       * @param {object} opts
       * @param {boolean} [opts.connect]
       * @param {awarenessProtocol.Awareness} [opts.awareness]
       * @param {Object<string,string>} [opts.params]
       * @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill
       * @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds
       * @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff)
       * @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication
       */
      constructor (serverUrl, roomname, doc, {
        connect = true,
        awareness = new Awareness(doc),
        params = {},
        WebSocketPolyfill = WebSocket,
        resyncInterval = -1,
        maxBackoffTime = 2500,
        disableBc = false
      } = {}) {
        super();
        // ensure that url is always ends with /
        while (serverUrl[serverUrl.length - 1] === '/') {
          serverUrl = serverUrl.slice(0, serverUrl.length - 1);
        }
        const encodedParams = encodeQueryParams(params);
        this.maxBackoffTime = maxBackoffTime;
        this.bcChannel = serverUrl + '/' + roomname;
        this.url = serverUrl + '/' + roomname +
          (encodedParams.length === 0 ? '' : '?' + encodedParams);
        this.roomname = roomname;
        this.doc = doc;
        this._WS = WebSocketPolyfill;
        this.awareness = awareness;
        this.wsconnected = false;
        this.wsconnecting = false;
        this.bcconnected = false;
        this.disableBc = disableBc;
        this.wsUnsuccessfulReconnects = 0;
        this.messageHandlers = messageHandlers.slice();
        /**
         * @type {boolean}
         */
        this._synced = false;
        /**
         * @type {WebSocket?}
         */
        this.ws = null;
        this.wsLastMessageReceived = 0;
        /**
         * Whether to connect to other peers or not
         * @type {boolean}
         */
        this.shouldConnect = connect;

        /**
         * @type {number}
         */
        this._resyncInterval = 0;
        if (resyncInterval > 0) {
          this._resyncInterval = /** @type {any} */ (setInterval(() => {
            if (this.ws && this.ws.readyState === WebSocket.OPEN) {
              // resend sync step 1
              const encoder = createEncoder();
              writeVarUint(encoder, messageSync);
              writeSyncStep1(encoder, doc);
              this.ws.send(toUint8Array(encoder));
            }
          }, resyncInterval));
        }

        /**
         * @param {ArrayBuffer} data
         * @param {any} origin
         */
        this._bcSubscriber = (data, origin) => {
          if (origin !== this) {
            const encoder = readMessage(this, new Uint8Array(data), false);
            if (length(encoder) > 1) {
              publish(this.bcChannel, toUint8Array(encoder), this);
            }
          }
        };
        /**
         * Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel)
         * @param {Uint8Array} update
         * @param {any} origin
         */
        this._updateHandler = (update, origin) => {
          if (origin !== this) {
            const encoder = createEncoder();
            writeVarUint(encoder, messageSync);
            writeUpdate(encoder, update);
            broadcastMessage(this, toUint8Array(encoder));
          }
        };
        this.doc.on('update', this._updateHandler);
        /**
         * @param {any} changed
         * @param {any} _origin
         */
        this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => {
          const changedClients = added.concat(updated).concat(removed);
          const encoder = createEncoder();
          writeVarUint(encoder, messageAwareness);
          writeVarUint8Array(
            encoder,
            encodeAwarenessUpdate(awareness, changedClients)
          );
          broadcastMessage(this, toUint8Array(encoder));
        };
        this._unloadHandler = () => {
          removeAwarenessStates(
            this.awareness,
            [doc.clientID],
            'window unload'
          );
        };
        if (typeof window !== 'undefined') {
          window.addEventListener('unload', this._unloadHandler);
        } else if (typeof process !== 'undefined') {
          process.on('exit', this._unloadHandler);
        }
        awareness.on('update', this._awarenessUpdateHandler);
        this._checkInterval = /** @type {any} */ (setInterval(() => {
          if (
            this.wsconnected &&
            messageReconnectTimeout <
              getUnixTime() - this.wsLastMessageReceived
          ) {
            // no message received in a long time - not even your own awareness
            // updates (which are updated every 15 seconds)
            /** @type {WebSocket} */ (this.ws).close();
          }
        }, messageReconnectTimeout / 10));
        if (connect) {
          this.connect();
        }
      }

      /**
       * @type {boolean}
       */
      get synced () {
        return this._synced
      }

      set synced (state) {
        if (this._synced !== state) {
          this._synced = state;
          this.emit('synced', [state]);
          this.emit('sync', [state]);
        }
      }

      destroy () {
        if (this._resyncInterval !== 0) {
          clearInterval(this._resyncInterval);
        }
        clearInterval(this._checkInterval);
        this.disconnect();
        if (typeof window !== 'undefined') {
          window.removeEventListener('unload', this._unloadHandler);
        } else if (typeof process !== 'undefined') {
          process.off('exit', this._unloadHandler);
        }
        this.awareness.off('update', this._awarenessUpdateHandler);
        this.doc.off('update', this._updateHandler);
        super.destroy();
      }

      connectBc () {
        if (this.disableBc) {
          return
        }
        if (!this.bcconnected) {
          subscribe(this.bcChannel, this._bcSubscriber);
          this.bcconnected = true;
        }
        // send sync step1 to bc
        // write sync step 1
        const encoderSync = createEncoder();
        writeVarUint(encoderSync, messageSync);
        writeSyncStep1(encoderSync, this.doc);
        publish(this.bcChannel, toUint8Array(encoderSync), this);
        // broadcast local state
        const encoderState = createEncoder();
        writeVarUint(encoderState, messageSync);
        writeSyncStep2(encoderState, this.doc);
        publish(this.bcChannel, toUint8Array(encoderState), this);
        // write queryAwareness
        const encoderAwarenessQuery = createEncoder();
        writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
        publish(
          this.bcChannel,
          toUint8Array(encoderAwarenessQuery),
          this
        );
        // broadcast local awareness state
        const encoderAwarenessState = createEncoder();
        writeVarUint(encoderAwarenessState, messageAwareness);
        writeVarUint8Array(
          encoderAwarenessState,
          encodeAwarenessUpdate(this.awareness, [
            this.doc.clientID
          ])
        );
        publish(
          this.bcChannel,
          toUint8Array(encoderAwarenessState),
          this
        );
      }

      disconnectBc () {
        // broadcast message with local awareness state set to null (indicating disconnect)
        const encoder = createEncoder();
        writeVarUint(encoder, messageAwareness);
        writeVarUint8Array(
          encoder,
          encodeAwarenessUpdate(this.awareness, [
            this.doc.clientID
          ], new Map())
        );
        broadcastMessage(this, toUint8Array(encoder));
        if (this.bcconnected) {
          unsubscribe(this.bcChannel, this._bcSubscriber);
          this.bcconnected = false;
        }
      }

      disconnect () {
        this.shouldConnect = false;
        this.disconnectBc();
        if (this.ws !== null) {
          this.ws.close();
        }
      }

      connect () {
        this.shouldConnect = true;
        if (!this.wsconnected && this.ws === null) {
          setupWS(this);
          this.connectBc();
        }
      }
    }

    class SynchronizeService {
      constructor(serverUrl = `ws://localhost:1234`, roomName = "root2") {
        this.onDataUpdate = () => {
        };
        this.ydoc = new Doc();
        this.wsProvider = new WebsocketProvider(serverUrl, roomName, this.ydoc, { connect: false });
        this.yWidget = this.ydoc.getArray("arr");
        this.yWidget.observeDeep(this._onDataUpdate.bind(this));
        vue.onBeforeUnmount(() => this.destroy());
      }
      createWidget(widget) {
        const map = new YMap();
        for (let key in widget) {
          map.set(key, widget[key]);
        }
        return map;
      }
      // 监听Widget数组的更新
      _onDataUpdate(YArrayEvent, Transaction) {
        const data = this.yWidget.toJSON();
        let updateData = [];
        YArrayEvent.forEach((event) => {
          const eventData = {
            added: event.changes.added,
            deleted: event.changes.deleted,
            delta: event.changes.delta,
            keys: event.changes.keys,
            path: event.path,
            yWidget: event.currentTarget
          };
          if (eventData.delta.length !== 0) {
            let deltaData = {
              retain: 0
            };
            eventData.delta.forEach((item) => {
              item.retain && (deltaData.retain = item.retain);
              item.insert && (deltaData.insert = item.insert);
              item.delete && (deltaData.delete = item.delete);
            });
            updateData.push({
              type: "array",
              path: eventData.path,
              target: event.target,
              deltaData,
              keysData: void 0,
              handler: this.handlerArray.bind(this, eventData.path, deltaData)
            });
          }
          if (eventData.keys.size !== 0) {
            eventData.keys.forEach((value, key) => {
              const keysData = {
                key,
                action: value.action,
                value: event.target.get(key)
              };
              updateData.push({
                type: "map",
                path: eventData.path,
                target: event.target,
                deltaData: void 0,
                keysData,
                handler: this.handlerMap.bind(this, eventData.path, keysData)
              });
            });
          }
        });
        this.onDataUpdate(data, updateData);
      }
      // 根据path获取当前被修改的对象
      getCurrentByPath(data, path) {
        let current = data;
        while (path.length > 0) {
          const key = path.shift();
          if (!CheckType.isExist(key))
            return current;
          current = current[key];
        }
        return current;
      }
      // 处理数组
      handlerArray(path, deltaData, currentData) {
        const current = this.getCurrentByPath(currentData, path);
        if (!CheckType.isArray(current))
          throw "handlerArray current not array";
        if (deltaData.insert && CheckType.isArray(deltaData.insert)) {
          const insertData = deltaData.insert.map((item) => item instanceof YMap || item instanceof YArray ? item.toJSON() : item);
          current.splice(deltaData.retain || 0, 0, ...insertData);
        } else if (deltaData.delete && CheckType.isNumber(deltaData.delete)) {
          current.splice(deltaData.retain || 0, deltaData.delete);
        }
      }
      // 处理map
      handlerMap(path, keysData, currentData) {
        const current = this.getCurrentByPath(currentData, path);
        if (!CheckType.isObject(current))
          throw "handlerMap current not object";
        if (keysData.action === "delete") {
          Reflect.deleteProperty(current, keysData.key);
        } else {
          if (CheckType.isArray(keysData.value)) {
            const insertData = keysData.value.map((item) => item instanceof YMap || item instanceof YArray ? item.toJSON() : item);
            Reflect.set(current, keysData.key, insertData);
          } else {
            Reflect.set(current, keysData.key, keysData.value);
          }
        }
      }
      destroy() {
        this.yWidget.unobserveDeep(this._onDataUpdate);
        this.wsProvider.destroy();
      }
    }

    function mitt(n){return {all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e]);},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]));},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e);}),(i=n.get("*"))&&i.slice().map(function(n){n(t,e);});}}}

    class UtilsService {
      constructor(service) {
        this.service = service;
        this.newWidgetP2CavnaseP = (point) => {
          return {
            x: (point.x - this.service.canvase2PanelRect.value.x + this.service.modal.scrollLeft) / this.service.modal.scale - this.service.modal.pageRect.x,
            y: (point.y - this.service.canvase2PanelRect.value.y + this.service.modal.scrollTop) / this.service.modal.scale - this.service.modal.pageRect.y
          };
        };
        this.cavnaseP2NewWidgetP = (point) => {
          return {
            x: (point.x + this.service.modal.pageRect.x) * this.service.modal.scale - this.service.modal.scrollLeft + this.service.canvase2PanelRect.value.x,
            y: (point.y + this.service.modal.pageRect.y) * this.service.modal.scale - this.service.modal.scrollTop + this.service.canvase2PanelRect.value.y
          };
        };
      }
      /**
       * 重新计算页面布局
       */
      recountPage() {
        const modal = this.service.modal;
        const viewWidth = modal.pageRect.width * modal.scale + DesignService.SPAN * 2;
        if (viewWidth < modal.canvaseRect.width) {
          modal.pageRect.cwidth = modal.canvaseRect.width / modal.scale;
        } else {
          modal.pageRect.cwidth = viewWidth / modal.scale;
        }
        const viewHeight = (modal.pageRect.height + DesignService.SPAN * 2) * modal.scale;
        if (viewHeight < modal.canvaseRect.height) {
          modal.pageRect.cheight = modal.canvaseRect.height / modal.scale;
        } else {
          modal.pageRect.cheight = viewHeight / modal.scale;
        }
        modal.pageRect.x = (modal.pageRect.cwidth - modal.pageRect.width) / 2;
        modal.pageRect.y = DesignService.SPAN / modal.scale;
        this.service.emit("page-resized", modal.pageRect);
      }
      // 拖动widget时自动拓展高度
      autoHeight(bottom) {
        var _a;
        if (!this.service.props.autoHeight)
          return;
        let span = bottom - this.service.modal.pageRect.height + this.service.modal.pageRect.padding[2];
        if (span >= 0) {
          span = span > 10 ? span : 10;
          this.service.modal.pageRect.height += span;
          this.service.modal.pageRect.cheight += span;
          (_a = this.service.alignLineService) === null || _a === void 0 ? void 0 : _a.setPaddingLine();
          this.service.emit("addHeight", this.service.modal.pageRect.height, span);
        }
      }
      paddingFormat(padding) {
        let p = [0, 0, 0, 0];
        if (padding.length === 1) {
          p = [padding[0], padding[0], padding[0], padding[0]];
        } else if (padding.length === 2) {
          p = [padding[0], padding[1], padding[0], padding[1]];
        } else if (padding.length === 3) {
          p = [padding[0], padding[1], padding[1], padding[2]];
        } else if (padding.length === 4) {
          p = padding;
        }
        return p;
      }
      // 拖动的widgets是否不覆盖页面上的widgets
      isNotOverlap(widgets) {
        const { top, bottom, left, right } = this.getBoundaryByWidget(widgets);
        const pageRect = this.service.modal.pageRect;
        if (left + (right - left) / 2 < 0 || top + (bottom - top) / 2 < 0 || right - (right - left) / 2 > pageRect.width) {
          return false;
        }
        if (widgets.every((item) => item.enableOverlap))
          return true;
        return this.service.modal.widgets.every((widget) => {
          if (widget.enableOverlap)
            return true;
          if (widgets.some((item) => item.id === widget.id))
            return true;
          let span = this.service.props.adsorbSpan - 1;
          span = span < 0 ? 0 : span;
          return !(right > widget.x + span && widget.x + widget.width > left + span && bottom > widget.y + span && widget.y + widget.height > top + span);
        });
      }
      // 计算多个widget构成的边界
      getBoundaryByWidget(widgets) {
        const tList = [];
        const bList = [];
        const lList = [];
        const rList = [];
        widgets.forEach((widget) => {
          const { top: top2, bottom: bottom2, left: left2, right: right2 } = this.widget2Boundary(widget);
          tList.push(top2);
          bList.push(bottom2);
          lList.push(left2);
          rList.push(right2);
        });
        const top = Math.min.apply(null, tList);
        const bottom = Math.max.apply(null, bList);
        const left = Math.min.apply(null, lList);
        const right = Math.max.apply(null, rList);
        return {
          top,
          bottom,
          left,
          right
        };
      }
      // 是否在页面内
      inCanvase(event) {
        const modal = this.service.modal;
        if (modal.newWidget) {
          return event.clientX - modal.canvaseRect.x - modal.pageRect.x > 0 && event.clientY - modal.canvaseRect.y - modal.pageRect.y > 0 && event.clientX < modal.canvaseRect.x + modal.pageRect.x + modal.pageRect.width && event.clientY < modal.canvaseRect.y + modal.pageRect.y + modal.pageRect.height;
        } else {
          return false;
        }
      }
      // 获取widget的边界
      widget2Boundary(widget) {
        return {
          left: widget.x,
          right: widget.x + widget.width,
          top: widget.y,
          bottom: widget.y + widget.height
        };
      }
      getYWidgetById(id) {
        let yWidgets = this.service.syncService.yWidget.toArray();
        const idx = yWidgets.findIndex((item) => item.get("id") === id);
        return {
          yWidget: this.service.syncService.yWidget.get(idx),
          index: idx
        };
      }
    }

    class Publisher {
      constructor() {
        this.subscriberList = [];
      }
      // 发布
      emit(...args) {
        return this.subscriberList.map((item) => {
          if (CheckType.isFunction(item)) {
            return item.apply(this, args);
          } else {
            console.error("Publisher emit is not Function");
            return item;
          }
        });
      }
      // 订阅
      on(callback) {
        this.subscriberList.push(callback);
      }
      // 取消订阅
      off(callback) {
        this.subscriberList = this.subscriberList.filter((fun) => fun !== callback);
      }
      // 清空订阅者
      clear() {
        this.subscriberList.length = 0;
      }
    }

    function debounce(fn, delay) {
      let timer = null;
      return function() {
        const self = this;
        const args = arguments;
        timer && clearTimeout(timer);
        timer = setTimeout(function() {
          fn.apply(self, args);
        }, delay);
      };
    }

    class DesignService {
      constructor(props, emit, slots) {
        this.props = props;
        this.emit = emit;
        this.drawerRef = vue.ref(null);
        this.boundaryLine = computed(() => {
          var _a;
          return ((_a = this.alignLineService) === null || _a === void 0 ? void 0 : _a.boundaryLine) || [];
        });
        this.isNotOverlapInGridPublisher = new Publisher();
        this.canvase2PanelRect = computed(() => {
          return {
            x: this.modal.canvaseRect.x - this.modal.rect.x,
            y: this.modal.canvaseRect.y - this.modal.rect.y,
            ex: this.modal.canvaseRect.x - this.modal.rect.x + this.modal.canvaseRect.width,
            ey: this.modal.canvaseRect.y - this.modal.rect.y + this.modal.canvaseRect.height
          };
        });
        this.emitter = mitt();
        this.newWidgetDragingRegistHandler = (event) => {
          var _a;
          if (!this.modal.newWidget) {
            if (this.selectedNewWidget) {
              this.selectedMousePoint = { x: event.clientX, y: event.clientY };
              let x = event.clientX - this.selectedNewWidget.width / 2 - this.modal.rect.x;
              let y = event.clientY - this.selectedNewWidget.height / 2 - this.modal.rect.y;
              this.modal.newWidget = Object.assign(Object.assign({}, this.selectedNewWidget), { x, y, state: -1, moveing: false, resizing: false, baseX: x, baseY: y, isOverlapping: false });
              this.selectedNewOrgState = Object.assign({}, this.modal.newWidget);
              this.emit("addNewWidget", this.modal.newWidget);
            }
          } else if (this.selectedMousePoint && this.selectedNewOrgState) {
            const hspan = event.clientX - this.selectedMousePoint.x;
            const vspan = event.y - this.selectedMousePoint.y;
            this.modal.newWidget.x = this.selectedNewOrgState.x + hspan;
            this.modal.newWidget.y = this.selectedNewOrgState.y + vspan;
            this.modal.newWidget.baseX = this.selectedNewOrgState.x + hspan;
            this.modal.newWidget.baseY = this.selectedNewOrgState.y + vspan;
            const { x, y } = this.utils.newWidgetP2CavnaseP(this.modal.newWidget);
            const widget = Object.assign(Object.assign({}, this.modal.newWidget), { x, y });
            (_a = this.alignLineService) === null || _a === void 0 ? void 0 : _a.onNewWidgetMove([widget], this);
            const gridNotOverlapping = this.isNotOverlapInGridPublisher.emit([widget]).every((item) => item);
            this.modal.newWidget.isOverlapping = !(this.utils.isNotOverlap([widget]) && gridNotOverlapping);
            this.utils.autoHeight(widget.y + widget.height);
            this.emit("drag-moving", this.modal.newWidget);
          }
        };
        this.newWidgetDropRegistHandler = (event) => {
          window.removeEventListener("mousemove", this.newWidgetDragingRegistHandler, true);
          window.removeEventListener("mouseup", this.newWidgetDropRegistHandler, true);
          if (this.modal.newWidget && !this.modal.newWidget.isOverlapping) {
            const widget = Object.assign({}, this.modal.newWidget);
            widget.state = 0;
            const { x, y } = this.utils.newWidgetP2CavnaseP(widget);
            widget.x = x;
            widget.y = y;
            widget.baseX = x;
            widget.baseY = y;
            const yWidget = this.syncService.createWidget(widget);
            this.syncService.yWidget.push([yWidget]);
            this.setSelected([yWidget]);
            this.emitter.emit("onAddWidget", yWidget);
            this.emit("drag-end", widget);
          } else {
            this.emit("delNewWidget", this.modal.newWidget);
          }
          this.modal.newWidget = void 0;
          this.selectedNewWidget = void 0;
          this.selectedMousePoint = void 0;
          this.selectedNewOrgState = void 0;
        };
        vue.provide(DesignService.token, this);
        vue.provide(DesignService.itemSlot, slots.item);
        this.syncService = new SynchronizeService();
        this.alignLineService = new AlignmentLineService(props, this);
        this.utils = new UtilsService(this);
        let { value: widgets, width, height } = props;
        this.modal = vue.reactive({
          scale: 1,
          rect: { x: 0, y: 0, width: 0, height: 0 },
          canvaseRect: { x: 0, y: 0, width: 0, height: 0 },
          pageRect: {
            x: 0,
            y: 0,
            width,
            height,
            cwidth: 0,
            cheight: 0,
            padding: this.utils.paddingFormat(props.pagePadding)
          },
          scrollLeft: 0,
          scrollTop: 0,
          moveing: false,
          selecteds: [],
          widgets: []
        });
        this.syncService.onDataUpdate = (data, updateData) => {
          updateData.forEach((update) => {
            var _a;
            update.handler(this.modal.widgets);
            (_a = this.alignLineService) === null || _a === void 0 ? void 0 : _a.handlerAlignmentLine(update);
          });
        };
        this.initWidgets(widgets);
        this.modal.canvaseRect.height = height + DesignService.SPAN * 2 / this.modal.scale;
        vue.watch(() => props.value, () => this.initWidgets(props.value));
        this.handlerSelectedChange = this.handlerSelecteds();
      }
      initWidgets(widgets) {
        if (widgets) {
          this.syncService.yWidget.delete(0, this.syncService.yWidget.length);
          this.modal.selecteds = [];
          this.alignLineService.clearAllLine();
          this.alignLineService.setPaddingLine();
          let w = widgets.map((item) => this.syncService.createWidget(Object.assign(Object.assign({}, item), { state: 0, moveing: false, resizing: false, isOverlapping: false, baseX: item.x, baseY: item.y })));
          this.syncService.yWidget.push(w);
        }
      }
      handlerSelecteds() {
        let selectedIds = [];
        const handler = () => {
          selectedIds = [];
          return this.modal.selecteds.map((item) => {
            selectedIds.push(item.get("id"));
            return item.toJSON();
          });
        };
        return debounce(() => {
          if (this.modal.selecteds.length !== selectedIds.length) {
            const selecteds = handler();
            this.emit("selected-change", selecteds);
            return;
          }
          for (const item of this.modal.selecteds) {
            const id = item.get("id");
            if (!selectedIds.includes(id)) {
              const selecteds = handler();
              this.emit("selected-change", selecteds);
              return;
            }
          }
        }, 100);
      }
      createWidgetHandler(widget) {
        this.selectedNewWidget = widget;
        window.addEventListener("mousemove", this.newWidgetDragingRegistHandler, true);
        window.addEventListener("mouseup", this.newWidgetDropRegistHandler, true);
      }
      deleteWidget(id) {
        var _a;
        const widgetIdx = this.syncService.yWidget.toArray().findIndex((item) => item.get("id") === id);
        if (widgetIdx === -1)
          return;
        this.syncService.yWidget.delete(widgetIdx, 1);
        (_a = this.alignLineService) === null || _a === void 0 ? void 0 : _a.delBoundaryLine(id);
      }
      addScale(value) {
        this.setScale(this.modal.scale + value);
      }
      // 设置缩放
      setScale(newscale) {
        if (newscale < 0.2) {
          newscale = 0.2;
        }
        if (newscale > 10) {
          newscale = 10;
        }
        this.modal.scale = newscale;
        this.utils.recountPage();
      }
      // 设置选中区域
      setSelectedArea(begin, end) {
        const pb = {
          x: Math.min(begin.x, end.x),
          y: Math.min(begin.y, end.y)
        };
        const pe = {
          x: Math.max(begin.x, end.x),
          y: Math.max(begin.y, end.y)
        };
        const selecteds = [];
        this.syncService.yWidget.toJSON().forEach((item, index) => {
          const itemEnd = { x: item.x + item.width, y: item.y + item.height };
          if (Math.max(pb.x, item.x) < Math.min(pe.x, itemEnd.x) && Math.max(pb.y, item.y) < Math.min(pe.y, itemEnd.y)) {
            selecteds.push(this.syncService.yWidget.get(index));
          }
        });
        if (selecteds && selecteds.length > 0) {
          this.setSelected(selecteds);
        }
      }
      // 设置选中的widget
      addSelected(widget) {
        if (widget) {
          widget.set("state", 1);
          this.modal.selecteds.push(widget);
          this.handlerSelectedChange();
        }
      }
      // 设置选中的widgets
      setSelected(widgets) {
        this.clearnSelected();
        if (widgets) {
          for (let i = 0; i < widgets.length; i++) {
            widgets[i].set("state", 1);
          }
          this.modal.selecteds.push(...widgets);
          this.handlerSelectedChange();
        }
      }
      clearnSelected() {
        if (this.modal.selecteds && this.modal.selecteds.length > 0) {
          for (let i = 0; i < this.modal.selecteds.length; i++) {
            this.modal.selecteds[i].set("state", 0);
          }
          this.modal.selecteds.splice(0, this.modal.selecteds.length);
          this.handlerSelectedChange();
        }
      }
      // 删除选中的widget
      deleteSelected(id) {
        const idx = this.modal.selecteds.findIndex((widget) => widget.get("id") === id);
        if (idx === -1)
          return;
        this.modal.selecteds[idx].set("state", 0);
        this.modal.selecteds.splice(idx, 1);
      }
      // 修改页面尺寸内边距
      changePageSize(width, height, padding) {
        this.clearnSelected();
        const pageRect = this.modal.pageRect;
        if (pageRect.width !== width || pageRect.padding[1] !== padding[1] || pageRect.padding[3] !== padding[3]) {
          const [oTop, oRight, oBottom, oLeft] = pageRect.padding;
          const [nTop, nRight, nBottom, nLeft] = padding;
          const newWidth = width - nLeft - nRight;
          const oldWidth = pageRect.width - oLeft - oRight;
          const widthRatio = (newWidth - oldWidth) / oldWidth;
          this.syncService.yWidget.forEach((yWidget) => {
            const yWidth = yWidget.get("width");
            const x = yWidget.get("x");
            yWidget.set("width", yWidth + yWidth * widthRatio);
            yWidget.set("x", x + (x + nLeft - oLeft - nLeft) * widthRatio + nLeft - oLeft);
          });
        }
        if (pageRect.height !== height) ;
        if (pageRect.padding[0] !== padding[0]) {
          const oTop = pageRect.padding[0];
          const nTop = padding[0];
          this.syncService.yWidget.forEach((yWidget) => {
            const y = yWidget.get("y");
            yWidget.set("y", y + (nTop - oTop));
          });
        }
        this.modal.pageRect.width = width;
        this.modal.pageRect.height = height;
        this.modal.pageRect.padding = padding;
        this.alignLineService.setPaddingLine();
        this.utils.recountPage();
      }
      getPageWidgets() {
        return this.modal.widgets;
      }
    }
    DesignService.token = Symbol("DesignService");
    DesignService.itemSlot = Symbol();
    DesignService.SPAN = 40;

    class DraggingService {
      constructor(service, emit) {
        this.service = service;
        this.emit = emit;
        this.orgPosition = /* @__PURE__ */ new Map();
        this.copyWidgets = [];
        this.dragHandler = (event) => {
          var _a;
          event.preventDefault();
          event.stopPropagation();
          if (!this.modal.beginDragging) {
            this.modal.beginDragging = true;
            for (let i = 0; i < this.service.modal.selecteds.length; i++) {
              this.service.modal.selecteds[i].set("state", 3);
            }
          }
          const hspan = (event.clientX - this.dragStartPosition.x) / this.service.modal.scale;
          const vspan = (event.clientY - this.dragStartPosition.y) / this.service.modal.scale;
          for (let i = 0; i < this.service.modal.selecteds.length; i++) {
            const yWidget = this.service.modal.selecteds[i];
            const orgPoint = this.orgPosition.get(yWidget.get("id"));
            if (orgPoint && yWidget) {
              yWidget.set("x", orgPoint.x + hspan);
              yWidget.set("y", orgPoint.y + vspan);
              yWidget.set("baseX", orgPoint.x + hspan);
              yWidget.set("baseY", orgPoint.y + vspan);
              this.emit("drag-moving", yWidget.toJSON());
            }
          }
          const widgets = this.service.modal.selecteds.map((item) => item.toJSON());
          const gridNotOverlapping = this.service.isNotOverlapInGridPublisher.emit(widgets).every((item) => item);
          if (!(this.service.utils.isNotOverlap(widgets) && gridNotOverlapping)) {
            this.service.modal.selecteds.forEach((yWidget) => {
              yWidget.set("isOverlapping", true);
            });
            this.modal.isOverlapping = true;
          } else {
            this.service.modal.selecteds.forEach((yWidget) => {
              yWidget.set("isOverlapping", false);
            });
            this.modal.isOverlapping = false;
          }
          (_a = this.service.alignLineService) === null || _a === void 0 ? void 0 : _a.onWidgetGroupMove(this.service.modal.selecteds);
          const { bottom } = this.service.utils.getBoundaryByWidget(this.service.modal.selecteds.map((yWidget) => yWidget.toJSON()));
          this.service.utils.autoHeight(bottom);
        };
        this.dragEndHandler = (event) => {
          event.preventDefault();
          event.stopPropagation();
          window.removeEventListener("mousemove", this.dragHandler, true);
          window.removeEventListener("mouseup", this.dragEndHandler, true);
          this.modal.beginDragging = false;
          for (let i = 0; i < this.service.modal.selecteds.length; i++) {
            const yWidget = this.service.modal.selecteds[i];
            yWidget.set("moveing", false);
            yWidget.set("state", 1);
            if (this.modal.isOverlapping) {
              const copyWidget = this.copyWidgets[i];
              yWidget.set("x", copyWidget.x);
              yWidget.set("y", copyWidget.y);
              yWidget.set("baseX", copyWidget.baseX);
              yWidget.set("baseY", copyWidget.baseY);
              yWidget.set("isOverlapping", false);
            }
            this.emit("drag-end", yWidget.toJSON());
          }
          if (this.service.modal.selecteds.length === 1) {
            this.service.emitter.emit("onAddWidget", this.service.modal.selecteds[0]);
          }
          this.modal.isOverlapping = false;
          this.orgPosition.clear();
        };
        this.modal = vue.reactive({
          beginDragging: false,
          isOverlapping: false
        });
        this.dragStartPosition = { x: -1, y: -1 };
      }
      // 拖动单个组件时、拖动网格子组件时yWidget存在,拖动多个组件yWidget不存在
      mousedownHandler(event, yWidget) {
        event.preventDefault();
        event.stopPropagation();
        window.addEventListener("mousemove", this.dragHandler, true);
        window.addEventListener("mouseup", this.dragEndHandler, true);
        this.dragStartPosition.x = event.clientX;
        this.dragStartPosition.y = event.clientY;
        if (yWidget) {
          if (event.altKey || event.metaKey) {
            this.service.addSelected(yWidget);
          } else {
            this.service.setSelected([yWidget]);
          }
        }
        this.modal.beginDragging = false;
        this.orgPosition.clear();
        for (let i = 0; i < this.service.modal.selecteds.length; i++) {
          const yWidget2 = this.service.modal.selecteds[i];
          yWidget2.set("moveing", true);
          this.orgPosition.set(yWidget2.get("id"), { x: yWidget2.get("x"), y: yWidget2.get("y") });
        }
        this.copyWidgets = this.service.modal.selecteds.map((item) => item.toJSON());
      }
    }

    const MIN_SPAN = 8;
    var SizeBox = vue.defineComponent({
      emits: ["drag-start", "drag-moving", "drag-end", "reszie-start", "resize-end", "resizeing"],
      setup(props, { emit }) {
        let orgMousePosition = { x: 0, y: 0 };
        let direct = [];
        const designService = vue.inject(DesignService.token);
        const service = new DraggingService(designService, emit);
        const position = vue.computed(() => {
          const bxarray = designService.modal.selecteds.map((item) => item.get("x"));
          const byarray = designService.modal.selecteds.map((item) => item.get("y"));
          const begin = {
            x: Math.min(...bxarray),
            y: Math.min(...byarray)
          };
          const exarray = designService.modal.selecteds.map((item) => item.get("x") + item.get("width"));
          const eyarray = designService.modal.selecteds.map((item) => item.get("y") + item.get("height"));
          const end = {
            x: Math.max(...exarray),
            y: Math.max(...eyarray)
          };
          return {
            x: begin.x,
            y: begin.y,
            width: end.x - begin.x,
            height: end.y - begin.y
          };
        });
        let orgPosition = { x: 0, y: 0, width: 0, height: 0 };
        const oldWidgetPosition = /* @__PURE__ */ new Map();
        const onMouseDown = (directparam, event) => {
          event.preventDefault();
          event.stopPropagation();
          window.addEventListener("mousemove", onMouseMoveHandler, true);
          window.addEventListener("mouseup", onMouseUp, true);
          direct = directparam;
          orgMousePosition = { x: event.clientX, y: event.clientY };
          orgPosition = Object.assign({}, position.value);
          oldWidgetPosition.clear();
          for (let i = 0; i < designService.modal.selecteds.length; i++) {
            const yWidget = designService.modal.selecteds[i];
            oldWidgetPosition.set(yWidget.get("id"), {
              x: yWidget.get("x"),
              y: yWidget.get("y"),
              width: yWidget.get("width"),
              height: yWidget.get("height")
            });
            emit("reszie-start", yWidget.toJSON());
          }
        };
        const onMouseMoveHandler = (event) => {
          var _a;
          let changed = false;
          const newposition = { x: 0, y: 0, width: 0, height: 0 };
          if (direct[0] !== "n") {
            let widthDirect = 1;
            const hspan = (event.clientX - orgMousePosition.x) / designService.modal.scale;
            if (direct[0] === "l") {
              widthDirect = -1;
              newposition.x = hspan;
            }
            let widthSpan = hspan * widthDirect;
            if (orgPosition.width + widthSpan < MIN_SPAN) {
              widthSpan = MIN_SPAN - orgPosition.width;
              if (direct[0] === "l") {
                newposition.x = orgPosition.width - MIN_SPAN;
              }
            }
            newposition.width = widthSpan;
            changed = true;
          }
          if (direct[1] !== "n") {
            let heightDirect = 1;
            const vspan = event.clientY - orgMousePosition.y;
            if (direct[1] === "t") {
              heightDirect = -1;
              newposition.y = vspan;
            }
            let heightSpan = vspan * heightDirect;
            if (orgPosition.height + heightSpan < MIN_SPAN) {
              heightSpan = MIN_SPAN - orgPosition.width;
              if (direct[1] === "t") {
                newposition.y = orgPosition.height - MIN_SPAN;
              }
            }
            newposition.height = heightSpan;
            changed = true;
          }
          if (changed) {
            for (let i = 0; i < designService.modal.selecteds.length; i++) {
              const widget = designService.modal.selecteds[i];
              const old = oldWidgetPosition.get(widget.get("id"));
              if (widget && old) {
                widget.set("x", old.x + newposition.x);
                widget.set("y", old.y + newposition.y);
                widget.set("width", old.width + newposition.width);
                widget.set("height", old.height + newposition.height);
                widget.set("baseX", old.x + newposition.x);
                widget.set("baseY", old.y + newposition.y);
                widget.set("resizing", true);
                emit("resizeing", widget.toJSON());
              }
            }
            (_a = designService.alignLineService) === null || _a === void 0 ? void 0 : _a.onBoundaryMove(designService.modal.selecteds, direct);
          }
        };
        const onMouseUp = (event) => {
          window.removeEventListener("mousemove", onMouseMoveHandler, true);
          window.removeEventListener("mouseup", onMouseUp, true);
          for (let i = 0; i < designService.modal.selecteds.length; i++) {
            const widget = designService.modal.selecteds[i];
            widget.set("resizing", false);
            emit("reszie-start", widget.toJSON());
          }
          oldWidgetPosition.clear();
        };
        const hx = vue.computed(() => position.value.x + 4);
        const htop = vue.computed(() => position.value.y - 4);
        const hbottom = vue.computed(() => position.value.y + position.value.height - 4);
        const hwidth = vue.computed(() => position.value.width > 8 ? position.value.width - 8 : 0);
        const vtop = vue.computed(() => position.value.y + 4);
        const vleft = vue.computed(() => position.value.x - 4);
        const vright = vue.computed(() => position.value.x + position.value.width - 4);
        const vheight = vue.computed(() => position.value.height > 8 ? position.value.height - 8 : 0);
        const renderBorders = () => {
          if (designService.modal.selecteds.find((widget) => widget.get("moveing")))
            return;
          return [
            // top
            vue.h("div", {
              class: "size-border-container size-border-h",
              style: {
                left: hx.value + "px",
                top: htop.value + "px",
                width: hwidth.value + "px"
              },
              onmousedown: (event) => onMouseDown(["n", "t"], event)
            }, [
              vue.h("div", {
                class: "size-border-h border"
              })
              // h('sssxxx',{},[JSON.stringify(position.value)])
            ]),
            // right
            vue.h("div", {
              class: "size-border-container size-border-v",
              style: {
                left: vright.value + "px",
                top: vtop.value + "px",
                height: vheight.value + "px"
              },
              onmousedown: (event) => onMouseDown(["r", "n"], event)
            }, [
              vue.h("div", {
                class: "size-border-v border"
              })
            ]),
            // bottom
            vue.h("div", {
              class: "size-border-container size-border-h",
              style: {
                left: hx.value + "px",
                top: hbottom.value + "px",
                width: hwidth.value + "px"
              },
              onmousedown: (event) => onMouseDown(["n", "b"], event)
            }, [
              vue.h("div", {
                class: "size-border-h border"
              })
            ]),
            // left
            vue.h("div", {
              class: "size-border-container size-border-v",
              style: {
                left: vleft.value + "px",
                top: vtop.value + "px",
                height: vheight.value + "px"
              },
              onmousedown: (event) => onMouseDown(["l", "n"], event)
            }, [
              [
                vue.h("div", {
                  class: "size-border-v border"
                })
              ]
            ])
          ];
        };
        const conLTX = vue.computed(() => position.value.x - 6);
        const conLTY = vue.computed(() => position.value.y - 6);
        const conRTX = vue.computed(() => conLTX.value + position.value.width);
        const conRBX = vue.computed(() => conLTY.value + position.value.height);
        const drawConerBox = () => {
          if (designService.modal.selecteds.find((widget) => widget.get("moveing")))
            return;
          return [
            // left top
            vue.h("div", {
              class: "size-border-corn nwse-resize",
              style: {
                left: conLTX.value + "px",
                top: conLTY.value + "px"
              },
              onmousedown: (event) => onMouseDown(["l", "t"], event)
            }, [
              vue.h("div", {
                class: "quart"
              })
            ]),
            // right top
            vue.h("div", {
              class: "size-border-corn nesw-resize",
              style: {
                left: conRTX.value + "px",
                top: conLTY.value + "px"
              },
              onmousedown: (event) => onMouseDown(["r", "t"], event)
            }, [
              vue.h("div", {
                class: "quart"
              })
            ]),
            // right bottom
            vue.h("div", {
              class: "size-border-corn nwse-resize",
              style: {
                left: conRTX.value + "px",
                top: conRBX.value + "px"
              },
              onmousedown: (event) => onMouseDown(["r", "b"], event)
            }, [
              vue.h("div", {
                class: "quart"
              })
            ]),
            // left bottom
            vue.h("div", {
              class: "size-border-corn nesw-resize",
              style: {
                left: conLTX.value + "px",
                top: conRBX.value + "px"
              },
              onmousedown: (event) => onMouseDown(["l", "b"], event)
            }, [
              vue.h("div", {
                class: "quart"
              })
            ])
          ];
        };
        const renderCover = () => {
          if (designService.modal.selecteds.length > 1) {
            return vue.h("div", {
              class: "size-cover",
              style: {
                left: position.value.x + "px",
                top: position.value.y + "px",
                width: position.value.width + "px",
                height: position.value.height + "px",
                background: service.modal.isOverlapping ? "rgba(255, 100, 100, 0.05)" : "",
                zIndex: 1500
              },
              onmousedown: (event) => service.mousedownHandler(event, void 0)
            });
          }
        };
        return { renderBorders, drawConerBox, renderCover };
      },
      render() {
        return vue.h("div", {}, [
          ...this.renderBorders() || "",
          ...this.drawConerBox() || "",
          this.renderCover()
        ]);
      }
    });

    const HEIGHR = 28;
    const WIDTH = 35;
    var OperationBar = vue.defineComponent({
      setup() {
        const designService = vue.inject(DesignService.token);
        const position = vue.computed(() => {
          const bxarray = designService.modal.selecteds.map((item) => item.get("x"));
          const byarray = designService.modal.selecteds.map((item) => item.get("y"));
          const begin = {
            x: Math.min(...bxarray),
            y: Math.min(...byarray)
          };
          const exarray = designService.modal.selecteds.map((item) => item.get("x") + item.get("width"));
          const eyarray = designService.modal.selecteds.map((item) => item.get("y") + item.get("height"));
          const end = {
            x: Math.max(...exarray),
            y: Math.max(...eyarray)
          };
          return {
            x: begin.x,
            y: begin.y,
            width: end.x - begin.x,
            height: end.y - begin.y
          };
        });
        function deleteWidget() {
          const delWidgets = [];
          designService.modal.selecteds.forEach((yWidget) => {
            delWidgets.push(yWidget.toJSON());
            designService.deleteWidget(yWidget.get("id"));
          });
          designService.emit("deleted", delWidgets);
          designService.modal.selecteds = [];
        }
        return {
          position,
          deleteWidget,
          designService
        };
      },
      render() {
        return vue.h("div", {
          class: "operation-bar",
          style: {
            top: this.position.y + 2 + "px",
            left: this.position.x + this.position.width - WIDTH - 2 + "px",
            width: WIDTH + "px",
            height: HEIGHR + "px"
          },
          onmousedown: (event) => {
            event.preventDefault();
            event.stopPropagation();
          }
        }, [
          vue.h("div", {
            class: "del-icon",
            innerHTML: `<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false"><path d="M695.96 953.65h-367.3c-76.07 0-137.74-61.67-137.74-137.74V293.63H833.7v522.28c0 76.07-61.67 137.74-137.74 137.74zM626.6 186.44H398.02c0-63.12 51.17-114.29 114.29-114.29S626.6 123.32 626.6 186.44z" data-spm-anchor-id="a313x.7781069.0.i29"></path><path d="M833.7 243.09H188.97c-19.02 0-34.43-15.42-34.43-34.43s15.41-34.43 34.43-34.43H833.7c19.02 0 34.43 15.42 34.43 34.43s-15.41 34.43-34.43 34.43z" data-spm-anchor-id="a313x.7781069.0.i30"></path><path d="M511.33 813.52c-19.02 0-34.43-15.42-34.43-34.43V466.61c0-19.02 15.42-34.43 34.43-34.43s34.43 15.42 34.43 34.43v312.48c.01 19.01-15.41 34.43-34.43 34.43zm-157.27 1.59c-19.02 0-34.43-15.42-34.43-34.43V586.03c0-19.02 15.42-34.43 34.43-34.43s34.43 15.42 34.43 34.43v194.64c.01 19.02-15.41 34.44-34.43 34.44zm314.54 0c-19.02 0-34.43-15.42-34.43-34.43V586.03c0-19.02 15.42-34.43 34.43-34.43s34.43 15.42 34.43 34.43v194.64c.01 19.02-15.41 34.44-34.43 34.44z" data-spm-anchor-id="a313x.7781069.0.i28"></path></svg>`,
            onClick: () => this.deleteWidget()
          })
        ]);
      }
    });

    var DragContainer = vue.defineComponent({
      name: "DragContainer",
      props: {
        value: {
          type: Object,
          required: true
        },
        widgetIdx: {
          type: Number || void 0,
          default: void 0
        },
        radius: {
          type: Number,
          default: 0
        },
        rotate: {
          type: Number,
          default: 0
        }
      },
      emits: ["drag-start", "drag-moving", "drag-end", "state-changed"],
      setup(props, { emit }) {
        const itemSlot = vue.inject(DesignService.itemSlot);
        const container = vue.ref();
        vue.onMounted(() => {
          if (container.value) {
            container.value.addEventListener("transitionend", () => {
              setTimeout(() => {
                emit("state-changed", { id: props.value.id, state: 0 });
              }, 200);
            }, true);
          }
        });
        const service = vue.inject(DesignService.token);
        const draggingService = new DraggingService(service, emit);
        const containerClass = vue.computed(() => {
          if (props.value.state === 1) {
            return "drag_container selected";
          } else if (props.value.state === 3 || props.value.state === -1) {
            return "drag_container dragging";
          } else if (props.value.state === 4) {
            return "drag_container moving";
          } else {
            return "drag_container";
          }
        });
        const renderCover = () => {
          if (props.value.state !== 2) {
            return vue.h("div", {
              id: props.value.id + "_cover",
              class: {
                cover: true,
                moving: props.value.state === 3,
                hover: props.value.enableDragable && props.value.enableResize
              },
              style: {
                borderRadius: props.radius + "px",
                background: (service.modal.selecteds.length === 1 || props.value.state === -1) && props.value.isOverlapping ? "rgba(255, 100, 100, 0.05)" : ""
              },
              onmousedown: (event) => {
                event.preventDefault();
                event.stopPropagation();
                if (props.value.enableDragable) {
                  if (props.widgetIdx !== void 0) {
                    const yWidget = service.syncService.yWidget.get(props.widgetIdx);
                    yWidget && draggingService.mousedownHandler(event, yWidget);
                  }
                  emit("drag-start", event, props.value);
                }
              }
            });
          }
        };
        const cssTransform = () => {
          let panelPoint = { x: props.value.x, y: props.value.y };
          let transform = "translate(" + panelPoint.x + "px," + panelPoint.y + "px)";
          if (props.value.state === -1) {
            transform += ` scale(${service.modal.scale})`;
          }
          if (props.value.state === 3 || props.value.state === 4) {
            transform += " scale(1)";
          }
          if (props.rotate !== 0) {
            transform += " rotate(" + props.rotate + "deg)";
          }
          return transform;
        };
        return {
          container,
          containerClass,
          renderCover,
          cssTransform,
          service,
          itemSlot,
          state: props.value.state,
          tag: props.value.tag,
          levels: props.value.levels
        };
      },
      render() {
        return vue.h(
          // @ts-ignore
          "div",
          {
            ref: "container",
            class: this.containerClass,
            style: {
              transform: this.cssTransform(),
              width: this.$props.value.width + "px",
              height: this.$props.value.height + "px",
              borderRadius: this.$props.radius + "px",
              // 转成string,防止 levels为0 时 结果为false
              zIndex: this.state === -1 ? 2e3 : String(this.levels) || 100
            }
          },
          [
            this.itemSlot && this.itemSlot(this.$props.value),
            // this.$slots.default && this.$slots.default("hahah"),
            this.renderCover()
            // this.levels
            // this.$props.value.id, h('div'),
            // this.$props.value.x, '-', this.$props.value.y, h('div'),
            // 'state    ', this.$props.value.state, h('div'),
            // 'index    ', this.$props.widgetIdx
            // this.$props.value.moveing+'', h('div'),
            // this.$props.value.resizing+'', h('div'),
            // this.$props.value.baseX, '-', this.$props.value.baseY, h('div'),
            // this.$props.value.width, '-', this.$props.value.height
          ]
        );
      }
    });

    var DesignCanvase = vue.defineComponent({
      name: "DesignCanvase",
      props: {
        square: {
          type: Number,
          default: -1
        },
        scale: {
          type: Number,
          default: 1
        },
        width: {
          type: Number,
          required: true
        },
        height: {
          type: Number,
          required: true
        },
        padding: {
          type: Array,
          default: () => [0]
        },
        drawerBackgroud: {
          type: String,
          default: "#ffffff"
        },
        backgroundColor: {
          type: String,
          default: "#eeeff0"
        },
        children: {
          type: Array,
          default: () => []
        },
        // 是否显示删除按钮
        showDelBut: {
          type: Boolean,
          default: false
        }
      },
      emits: ["drag-start", "drag-moving", "drag-end", "resize-start", "resizeing", "resize-end"],
      setup(props, { emit, slots }) {
        const drawer = vue.ref(null);
        const designService = vue.inject(DesignService.token);
        designService.drawerRef = drawer;
        const containerWidth = computed(() => designService.modal.pageRect.cwidth * designService.modal.scale);
        const containerHeight = computed(() => designService.modal.pageRect.cheight * designService.modal.scale);
        vue.onMounted(() => {
          window.addEventListener("resize", windowsResizeHandler, true);
          windowsResizeHandler();
        });
        const windowsResizeHandler = () => {
          relayoutChildren();
        };
        const relayoutChildren = () => {
        };
        const converClientP2PanelP = (event) => {
          return {
            x: (event.clientX - designService.modal.canvaseRect.x - designService.modal.pageRect.x + designService.modal.scrollLeft) / designService.modal.scale,
            y: (event.clientY - designService.modal.canvaseRect.y - designService.modal.pageRect.y + designService.modal.scrollTop) / designService.modal.scale
          };
        };
        const SELECTED_SPAN = 10;
        const selectedArea = vue.ref({ begin: void 0, end: void 0 });
        const bgmousedownHandler = (event) => {
          if (event.button !== 0)
            return;
          designService.clearnSelected();
          selectedArea.value.begin = converClientP2PanelP(event);
          window.addEventListener("mousemove", bgmousemoveHandler, true);
          window.addEventListener("mouseup", bgmouseupHandler, true);
        };
        const bgmousemoveHandler = (event) => {
          const cpoint = converClientP2PanelP(event);
          if (!selectedArea.value.begin) {
            selectedArea.value.begin = cpoint;
          } else {
            const xspan = Math.abs(cpoint.x - selectedArea.value.begin.x);
            const yspan = Math.abs(cpoint.y - selectedArea.value.begin.y);
            if (xspan > SELECTED_SPAN && yspan > SELECTED_SPAN) {
              selectedArea.value.end = cpoint;
              designService.setSelectedArea(selectedArea.value.begin, selectedArea.value.end);
            } else {
              selectedArea.value.end = void 0;
              designService.clearnSelected();
            }
          }
        };
        const bgmouseupHandler = (event) => {
          window.removeEventListener("mousemove", bgmouseupHandler, true);
          window.removeEventListener("mousemove", bgmousemoveHandler, true);
          selectedArea.value = { begin: void 0, end: void 0 };
        };
        const renderSelectedArea = () => {
          if (selectedArea.value.begin && selectedArea.value.end) {
            const left = Math.min(selectedArea.value.begin.x, selectedArea.value.end.x);
            const top = Math.min(selectedArea.value.begin.y, selectedArea.value.end.y);
            const width = Math.abs(selectedArea.value.begin.x - selectedArea.value.end.x);
            const height = Math.abs(selectedArea.value.begin.y - selectedArea.value.end.y);
            return vue.h("div", {
              class: "selected_area",
              style: {
                left: left + "px",
                top: top + "px",
                width: width + "px",
                height: height + "px"
              }
            });
          }
        };
        const renderDrawer = () => {
          return vue.h("div", {
            ref: "drawer",
            id: "drawer",
            style: {
              background: props.drawerBackgroud,
              left: designService.modal.pageRect.x + "px",
              top: designService.modal.pageRect.y + "px",
              width: designService.modal.pageRect.width + "px",
              height: designService.modal.pageRect.height + "px"
            }
          }, [
            renderWidgets(),
            renderSizeBorders(),
            renderSelectedArea(),
            renderAlignmentLine(),
            renderOperationBar()
          ]);
        };
        const renderWidgets = () => {
          if (designService.modal.widgets) {
            return designService.modal.widgets.map((widget, idx) => vue.h(DragContainer, {
              key: widget.id,
              value: widget,
              widgetIdx: idx,
              onDragMoving: (widget2) => emit("drag-moving", widget2),
              onDragStart: (widget2) => emit("drag-start", widget2),
              onDragEnd: (widget2) => emit("drag-end", widget2)
            }));
          }
        };
        const renderSizeBorders = () => {
          if (!designService.modal.selecteds || designService.modal.selecteds.length === 0) {
            return;
          }
          if (designService.modal.selecteds.find((widget) => !widget.get("enableResize"))) {
            return;
          }
          return vue.h(SizeBox, {
            onDragMoving: (widget) => emit("drag-moving", widget),
            onDragStart: (widget) => emit("drag-start", widget),
            onDragEnd: (widget) => emit("drag-end", widget),
            onResizeStart: (widget) => emit("resize-start", widget),
            onResizeing: (widget) => emit("resizeing", widget),
            onResizeEnd: (widget) => emit("resize-end", widget)
          });
        };
        const renderAlignmentLine = () => {
          if (designService.boundaryLine.value) {
            return designService.boundaryLine.value.map((line) => {
              var _a;
              const option = (_a = designService.alignLineService) === null || _a === void 0 ? void 0 : _a.option;
              const border = `${option === null || option === void 0 ? void 0 : option.alignWeight}px ${option === null || option === void 0 ? void 0 : option.alignColor} dashed`;
              return vue.h("div", {
                id: line.id,
                class: "alignment-line",
                style: {
                  display: line.show && (option === null || option === void 0 ? void 0 : option.showAlign) ? "" : "none",
                  transform: `translate(${line.x}px, ${line.y}px)`,
                  borderLeft: line.direction === LineDirection.COLUMN ? border : "",
                  borderTop: line.direction === LineDirection.ROW ? border : "",
                  width: line.width,
                  height: line.height
                }
              }, [
                // line.x, '-', line.y, '-', line.show
              ]);
            });
          }
          return vue.h("div");
        };
        const renderOperationBar = () => {
          if (!props.showDelBut)
            return;
          if (!designService.modal.selecteds || designService.modal.selecteds.length === 0)
            return;
          return vue.h(OperationBar);
        };
        return { drawer, designService, containerWidth, containerHeight, renderDrawer, bgmousedownHandler };
      },
      render() {
        return vue.h("div", {
          style: {
            width: this.designService.modal.pageRect.cwidth + "px",
            height: this.designService.modal.pageRect.cheight + "px",
            transform: "scale(" + this.designService.modal.scale + ")"
          },
          onmousedown: (event) => this.bgmousedownHandler(event)
        }, [
          this.renderDrawer()
        ]);
      }
    });

    function getClientRect(element) {
      return new Promise((resolve, reject) => {
        if (element) {
          let number = 0;
          let value = element.getBoundingClientRect();
          const interval = setInterval(() => {
            const cvalue = element.getBoundingClientRect();
            if (cvalue.x === value.x && cvalue.y === value.y && cvalue.width === value.width && cvalue.height === value.height) {
              number += 1;
              if (number > 8) {
                clearInterval(interval);
                resolve(cvalue);
              }
            } else {
              number = 0;
            }
            value = cvalue;
          }, 40);
        } else {
          reject(new Error("element must not be null"));
        }
      });
    }

    var CRuler = vue.defineComponent({
      name: "CRuler",
      props: {
        width: {
          type: Number,
          required: true
        },
        height: {
          type: Number,
          required: true
        },
        start: {
          type: Number,
          required: true
        },
        offset: {
          type: Number,
          required: true
        },
        scale: {
          type: Number,
          required: true
        },
        unit: {
          type: String,
          required: true
        },
        backgroundColor: {
          type: String,
          required: true
        },
        rulerColorLight: {
          type: String,
          default: "#848484"
        },
        rulerColorDark: {
          type: String,
          default: "#646464"
        },
        h: {
          type: Boolean,
          defaut: true
        }
      },
      setup(props) {
        const state = vue.reactive({
          canvasContext: null
        });
        const canvas = vue.ref(null);
        vue.onMounted(() => {
          state.canvasContext = canvas.value && canvas.value.getContext("2d");
          updateCanvasContext();
          drawCavaseRuler();
        });
        vue.watch([() => props.width, () => props.height, () => props.start, () => props.scale, () => props.offset], () => {
          updateCanvasContext();
          drawCavaseRuler();
        });
        const updateCanvasContext = () => {
          if (canvas.value) {
            canvas.value.width = props.width;
            canvas.value.height = props.height;
            const ctx = state.canvasContext;
            if (ctx) {
              ctx.font = `${12}px -apple-system, "Helvetica Neue", ".SFNSText-Regular", "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB",
                "Microsoft YaHei", "WenQuanYi Zen Hei", sans-serif`;
              ctx.lineWidth = 1;
              ctx.textBaseline = "middle";
            }
          }
        };
        const getGridSize = (scale) => {
          if (scale <= 0.25)
            return 40;
          if (scale <= 0.5)
            return 20;
          if (scale <= 1)
            return 10;
          if (scale <= 2)
            return 5;
          if (scale <= 4)
            return 2;
          return 1;
        };
        const drawCavaseRuler = () => {
          const ctx = state.canvasContext;
          if (ctx && canvas.value) {
            const { scale, h: h2, backgroundColor, rulerColorLight, rulerColorDark } = props;
            ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
            ctx.fillStyle = backgroundColor;
            ctx.fillRect(0, 0, canvas.value.width, canvas.value.height);
            const gridSize = getGridSize(scale);
            const gridSize10 = gridSize * 10;
            const gridPixel = gridSize * scale;
            const gridPixel10 = gridSize10 * scale;
            let position = props.start - props.offset;
            const minPos = h2 ? props.height : props.width;
            ctx.beginPath();
            ctx.strokeStyle = rulerColorLight;
            position = props.start - gridPixel - props.offset;
            while (position > minPos) {
              drawLine(ctx, h2, position, h2 ? props.height : props.width, false);
              position -= gridPixel;
            }
            position = props.start + gridPixel - props.offset;
            while (position < minPos) {
              position += gridPixel;
            }
            while (h2 ? position < canvas.value.width : position < canvas.value.height) {
              drawLine(ctx, h2, position, h2 ? canvas.value.height : canvas.value.width, false);
              position += gridPixel;
            }
            ctx.stroke();
            ctx.closePath();
            ctx.beginPath();
            ctx.fillStyle = rulerColorDark;
            ctx.strokeStyle = rulerColorDark;
            let value = 0;
            position = props.start - props.offset;
            while (position > minPos) {
              position -= gridPixel10;
              value -= gridSize10;
              drawLine(ctx, h2, position, h2 ? props.height : props.width);
              drawText(ctx, h2, position, h2 ? props.height : props.width, value);
            }
            position = props.start - props.offset;
            value = 0;
            while (position < minPos) {
              position += gridPixel10;
              value += gridSize10;
            }
            while (h2 ? position < canvas.value.width : position < canvas.value.height) {
              drawLine(ctx, h2, position, h2 ? props.height : props.width);
              drawText(ctx, h2, position, h2 ? props.height : props.width, value);
              position += gridPixel10;
              value += gridSize10;
            }
            ctx.stroke();
            ctx.closePath();
            ctx.setTransform(1, 0, 0, 1, 0, 0);
          }
        };
        const drawLine = (ctx, h2, position, length, max = true) => {
          const lineEnd = max ? length * 9 / 16 : length / 3;
          if (h2) {
            ctx.moveTo(position, 0);
            ctx.lineTo(position, lineEnd);
          } else {
            ctx.moveTo(0, position);
            ctx.lineTo(lineEnd, position);
          }
        };
        const drawText = (ctx, h2, position, length, value) => {
          const end = length * 0.4;
          ctx.save();
          h2 ? ctx.translate(position, end) : ctx.translate(end, position);
          if (!h2) {
            ctx.rotate(-Math.PI / 2);
          }
          ctx.fillText(value.toString(), 4, 7);
          ctx.restore();
        };
        return { canvas };
      },
      render() {
        return vue.h("canvas", {
          ref: "canvas",
          class: "ruler_container",
          style: {
            width: this.$props.width + "px",
            height: this.$props.height + "px"
          }
        });
      }
    });

    const RULER_WIDTH = 24;
    var DesignPanel = vue.defineComponent({
      name: "DesignPanel",
      props: {
        value: {
          type: Array,
          default: () => new Array()
        },
        width: {
          type: Number,
          required: true
        },
        height: {
          type: Number,
          required: true,
          default: 500
        },
        unit: {
          type: String,
          default: "px"
        },
        drawerBackgroud: {
          type: String,
          default: "#ffffff"
        },
        backgroundColor: {
          type: String,
          default: "#eeeff0"
        },
        showRuler: {
          type: Boolean,
          default: false
        },
        rulerColorLight: {
          type: String,
          default: "#9a9a9a"
        },
        rulerColorDark: {
          type: String,
          default: "#646464"
        },
        // 是否显示对齐线
        showAlign: {
          type: Boolean,
          default: true
        },
        // 是否吸附
        enableAdsorb: {
          type: Boolean,
          default: true
        },
        // 对齐线宽度
        alignWeight: {
          type: Number,
          default: 1
        },
        // 对齐线颜色
        alignColor: {
          type: String,
          default: "#3ca4fc"
        },
        // 显示对齐线的距离
        showAlignSpan: {
          type: Number,
          default: 20
        },
        // 吸附距离
        adsorbSpan: {
          type: Number,
          default: 10
        },
        // 页面内边距
        pagePadding: {
          type: Array,
          default: [10]
        },
        // 是否显示删除按钮
        showDelBut: {
          type: Boolean,
          default: false
        },
        // 组件拖动到底部时是否拓展高度
        autoHeight: {
          type: Boolean,
          default: false
        }
      },
      emits: [
        "update:value",
        "addHeight",
        "page-resized",
        "added",
        "deleted",
        "drag-start",
        "drag-moving",
        "drag-end",
        "resize-start",
        "resizeing",
        "resize-end",
        "selected-change",
        "addNewWidget",
        "delNewWidget"
      ],
      setup(props, { emit, slots }) {
        const designContainer = vue.ref();
        const designBody = vue.ref();
        const service = new DesignService(props, emit, slots);
        const createWidget = (widget) => {
          service.createWidgetHandler(widget);
          emit("added", widget);
          emit("update:value", service.modal.widgets);
        };
        const getPageWidgets = () => {
          return service.getPageWidgets();
        };
        vue.onMounted(() => {
          window.addEventListener("resize", resizeHandler, true);
          resizeHandler();
        });
        const resizeHandler = () => {
          if (designContainer.value) {
            getClientRect(designContainer.value).then((value) => {
              service.modal.rect = value;
            });
          }
          if (designBody.value) {
            getClientRect(designBody.value).then((value) => {
              service.modal.canvaseRect = value;
              service.utils.recountPage();
              vue.nextTick().then(() => {
                service.emitter.emit("onLayout");
              });
            });
          }
        };
        const renderAddWdiget = () => {
          if (service.modal.newWidget) {
            return vue.h(DragContainer, {
              value: service.modal.newWidget
            });
          }
        };
        const renderHRulter = () => {
          if (props.showRuler) {
            return vue.h(CRuler, {
              width: service.modal.canvaseRect.width,
              height: RULER_WIDTH,
              start: service.modal.pageRect.x * service.modal.scale,
              offset: service.modal.scrollLeft,
              scale: service.modal.scale,
              unit: props.unit,
              h: true,
              backgroundColor: props.backgroundColor,
              rulerColorLight: props.rulerColorLight,
              rulerColorDark: props.rulerColorDark,
              style: {
                left: service.modal.canvaseRect.x - service.modal.rect.x + "px",
                top: service.modal.canvaseRect.y - service.modal.rect.y + "px",
                zIndex: 2e3
              }
            });
          }
        };
        const renderVRulter = () => {
          if (props.showRuler) {
            return vue.h(CRuler, {
              width: RULER_WIDTH,
              height: service.modal.canvaseRect.height,
              start: service.modal.pageRect.y * service.modal.scale,
              offset: service.modal.scrollTop,
              scale: service.modal.scale,
              unit: props.unit,
              backgroundColor: props.backgroundColor,
              rulerColorLight: props.rulerColorLight,
              rulerColorDark: props.rulerColorDark,
              h: false,
              style: {
                left: service.modal.canvaseRect.x - service.modal.rect.x + "px",
                top: service.modal.canvaseRect.y - service.modal.rect.y + "px",
                zIndex: 2e3
              }
            });
          }
        };
        const renderBody = () => {
          return vue.h(DesignCanvase, Object.assign(Object.assign({ id: "free-layout-canvas", scale: service.modal.scale }, props), { onDragMoving: (widget) => {
            emit("drag-moving", widget);
            emit("update:value", service.modal.widgets);
          }, onDragStart: (widget) => {
            emit("drag-start", widget);
            emit("update:value", service.modal.widgets);
          }, onDragEnd: (widget) => {
            emit("drag-end", widget);
            emit("update:value", service.modal.widgets);
          }, onResizeStart: (widget) => {
            emit("resize-start", widget);
            emit("update:value", service.modal.widgets);
          }, onResizeing: (widget) => {
            emit("resizeing", widget);
            emit("update:value", service.modal.widgets);
          }, onResizeEnd: (widget) => {
            emit("resize-end", widget);
            emit("update:value", service.modal.widgets);
          } }));
        };
        const mouseWheelHandler = (event) => {
          if (event.ctrlKey || event.metaKey) {
            event.preventDefault();
            event.stopPropagation();
            service.addScale(event.deltaY / 100);
          }
        };
        return { designContainer, designBody, service, createWidget, getPageWidgets, renderHRulter, renderVRulter, renderBody, renderAddWdiget, mouseWheelHandler };
      },
      render() {
        return vue.h("div", {
          ref: "designContainer",
          class: "design"
        }, [
          this.$slots.header && this.$slots.header(),
          vue.h("div", {
            class: "content"
          }, [
            this.$slots.left && this.$slots.left(),
            vue.h("div", {
              ref: "designBody",
              class: "body",
              onscroll: () => {
                if (this.designBody) {
                  this.service.modal.scrollLeft = this.designBody.scrollLeft;
                  this.service.modal.scrollTop = this.designBody.scrollTop;
                }
              },
              onmousewheel: (event) => {
                this.mouseWheelHandler(event);
              }
            }, [
              this.renderHRulter(),
              this.renderVRulter(),
              this.renderBody()
            ]),
            this.$slots.right && this.$slots.right()
          ]),
          this.renderAddWdiget()
        ]);
      }
    });

    DesignPanel.install = (app) => {
      app.component(DesignPanel.name, DesignPanel);
      return app;
    };

    class GridService {
      constructor(service, props, emit) {
        this.service = service;
        this.props = props;
        this.emit = emit;
        this.containerRef = null;
        this.gridChildWidgets = vue.computed(() => {
          const widgets = [];
          this.model.gridItems.forEach((item) => {
            item.widget && widgets.push(item.widget);
          });
          return widgets;
        });
        this.gridWidget = this.getGridWidget();
        this.model = vue.reactive({
          gridRowGap: [],
          gridColGap: [],
          gridItems: [],
          height: this.gridWidget.height
        });
      }
      getGridWidget() {
        let gridWidget = this.service.modal.widgets.find((item) => item.id === this.props.id);
        if (!gridWidget) {
          gridWidget = this.service.modal.newWidget;
        }
        return gridWidget;
      }
      initGridGap(rowSpan, colSpan, width, height) {
        rowSpan = Number(rowSpan);
        colSpan = Number(colSpan);
        const gridWidgets = this.model.gridItems.map((item) => ({ widget: item.widget, row: item.row, col: item.col }));
        this.model.gridRowGap = [];
        this.model.gridColGap = [];
        this.model.gridItems = [];
        this.model.height = height;
        for (let ri = 0; ri < rowSpan - 1; ri++) {
          this.model.gridRowGap.push({
            y: height / rowSpan * (ri + 1) - 1,
            width
          });
        }
        for (let ci = 0; ci < colSpan - 1; ci++) {
          this.model.gridColGap.push({
            x: width / colSpan * (ci + 1) - 1,
            height
          });
        }
        for (let ri = 0; ri < rowSpan; ri++) {
          for (let ci = 0; ci < colSpan; ci++) {
            const gridWidget = gridWidgets.find((gridWidget2) => gridWidget2.row === ri && gridWidget2.col === ci);
            this.model.gridItems.push({
              x: width / colSpan * ci,
              y: height / rowSpan * ri,
              width: width / colSpan,
              height: height / rowSpan,
              row: ri,
              col: ci,
              active: false,
              widget: (gridWidget === null || gridWidget === void 0 ? void 0 : gridWidget.widget) || null
            });
          }
        }
      }
      resetGridWidgets() {
        this.model.gridItems.forEach(({ widget, x, y, width }) => {
          if (!widget)
            return;
          widget.x = x;
          widget.y = y;
          widget.width = width;
        });
      }
      initGridWidgets() {
        if (!this.props.components.length)
          return;
        for (const item of this.props.components) {
          const cWidget = Object.assign(Object.assign({}, item), { state: 0, moveing: false, resizing: false, isOverlapping: false, baseX: item.x, baseY: item.y });
          const center = this.getCenterPoint(cWidget);
          for (const gridItem of this.model.gridItems) {
            if (this.pointInArea(center, gridItem)) {
              gridItem.widget = cWidget;
              break;
            }
          }
        }
      }
      setGridRowGap(row, height) {
        let addHeight = 0;
        const items = this.model.gridItems.filter((item) => item.row === row);
        items.forEach((item) => {
          const span = height - item.height;
          if (span > addHeight) {
            addHeight = span;
          }
          item.height = height;
        });
        for (let i = row + 1; i <= this.model.gridRowGap.length; i++) {
          const items2 = this.model.gridItems.filter((item) => item.row === i);
          items2.forEach((item) => {
            if (item.widget) {
              item.widget.y += addHeight;
            }
            item.y += addHeight;
          });
        }
        this.model.gridRowGap.forEach((item, index) => {
          if (index >= row) {
            item.y += addHeight;
          }
        });
        this.model.gridColGap.forEach((item) => {
          item.height += addHeight;
        });
        this.model.height += addHeight;
      }
      // 监听组件放下(已排除重叠)
      onAddWidget(yWidget) {
        const widget = yWidget.toJSON();
        if (widget.id === this.props.id)
          return;
        const exclude = this.props.excludeComponents.includes(widget.tag);
        if (exclude && widget.enableOverlap)
          return;
        const centerPoint = this.getCenterPoint(widget);
        if (this.pointInArea(centerPoint, this.gridWidget)) {
          this.model.gridItems.forEach((gridItem) => {
            var _a;
            const point = this.canvasP2GridP(centerPoint);
            gridItem.active = false;
            if (this.pointInArea(point, gridItem)) {
              this.setWidgetPointToGrid(yWidget, gridItem);
            } else {
              if (((_a = gridItem.widget) === null || _a === void 0 ? void 0 : _a.id) === yWidget.get("id")) {
                gridItem.widget = null;
              }
            }
          });
        } else {
          this.model.gridItems.forEach((gridItem) => {
            var _a;
            if (((_a = gridItem.widget) === null || _a === void 0 ? void 0 : _a.id) === yWidget.get("id")) {
              gridItem.widget = null;
            }
          });
        }
      }
      // 监听子组件被拖拽,将网格子组件设置为全局并选中
      onGridChildDragStart(event, widget) {
        var _a;
        const { x, y } = this.gridP2CanvasP(widget);
        const yWidget = this.service.syncService.createWidget(Object.assign(Object.assign({}, widget), { state: 3, x, y }));
        this.service.syncService.yWidget.push([yWidget]);
        for (const gridItem of this.model.gridItems) {
          if (((_a = gridItem.widget) === null || _a === void 0 ? void 0 : _a.id) === widget.id) {
            gridItem.widget = null;
            break;
          }
        }
        const draggingService = new DraggingService(this.service, () => {
        });
        draggingService.mousedownHandler(event, yWidget);
        this.emit("update:active", null);
      }
      // 将拖入网格的组件设置到对应网格位置
      setWidgetPointToGrid(yWidget, gridItem) {
        const widget = Object.assign(Object.assign({}, yWidget.toJSON()), { x: gridItem.x, y: gridItem.y, width: gridItem.width, height: gridItem.height, state: 0 });
        this.service.deleteWidget(yWidget.get("id"));
        this.service.modal.selecteds = [];
        gridItem.widget = widget;
        this.emit("update:active", widget);
        const { yWidget: gridYWidget } = this.service.utils.getYWidgetById(this.props.id);
        this.service.setSelected([gridYWidget]);
      }
      // 设置拖动的组件是否在网格中覆盖了其他组件 覆盖false 不覆盖true
      setNotOverlapInGrid(widgets) {
        if (widgets.length !== 1) {
          return !!widgets.find((w) => w.id === this.props.id);
        }
        const widget = widgets[0];
        if (widget.id === this.props.id)
          return true;
        const exclude = this.props.excludeComponents.includes(widget.tag);
        if (exclude && widget.enableOverlap)
          return true;
        const centerPoint = this.getCenterPoint(widget);
        if (this.pointInArea(centerPoint, this.gridWidget)) {
          if (exclude)
            return false;
          return this.model.gridItems.every((gridItems, index) => {
            const point = this.canvasP2GridP(centerPoint);
            if (this.pointInArea(point, gridItems)) {
              if (gridItems.widget && gridItems.widget.id === widget.id) {
                gridItems.active = false;
                return true;
              } else {
                gridItems.active = !gridItems.widget;
                return !gridItems.widget;
              }
            } else {
              gridItems.active = false;
              return true;
            }
          });
        } else {
          this.model.gridItems.forEach((item) => {
            item.active = false;
          });
          return !(widget.x + widget.width > this.gridWidget.x && this.gridWidget.x + this.gridWidget.width > widget.x && widget.y + widget.height > this.gridWidget.y && this.gridWidget.y + this.gridWidget.height > widget.y);
        }
      }
      deleteWidget(id) {
        for (const item of this.model.gridItems) {
          if (item.widget && item.widget.id === id) {
            item.widget = null;
            break;
          }
        }
        if (this.props.active && this.props.active.id === id) {
          this.emit("update:active", null);
        }
      }
      pointInArea(point, area) {
        return point.x > area.x && point.x < area.x + area.width && point.y > area.y && point.y < area.y + area.height;
      }
      canvasP2GridP(point) {
        return {
          x: point.x - this.gridWidget.x,
          y: point.y - this.gridWidget.y
        };
      }
      gridP2CanvasP(point) {
        return {
          x: point.x + this.gridWidget.x,
          y: point.y + this.gridWidget.y
        };
      }
      getCenterPoint(widget) {
        return {
          x: widget.x + widget.width / 2,
          y: widget.y + widget.height / 2
        };
      }
    }

    var grid_widget = vue.defineComponent({
      name: "GridWidget",
      components: { DragContainer },
      props: {
        id: {
          type: String,
          required: true
        },
        tag: {
          type: String,
          required: true
        },
        active: {
          type: Object || null
        },
        rowSpan: {
          type: String,
          default: "5"
        },
        colSpan: {
          type: String,
          default: "3"
        },
        // 不允许拖入的组件
        excludeComponents: {
          type: Array,
          default: () => []
        },
        gridBorder: {
          type: Array,
          default: () => [
            "INNER_HORIZONTAL",
            "INNER_VERTICAL",
            "UP",
            "DOWN",
            "LEFT",
            "RIGHT"
          ]
        },
        components: {
          type: Array,
          default: () => []
        }
      },
      emits: ["update:active"],
      setup(props, { emit }) {
        const service = vue.inject(DesignService.token);
        const gridService = new GridService(service, props, emit);
        gridService.initGridGap(Number(props.rowSpan), Number(props.colSpan), gridService.gridWidget.width, gridService.gridWidget.height);
        gridService.initGridWidgets();
        vue.watch([
          () => props.rowSpan,
          () => props.colSpan
        ], ([rowSpan, colSpan]) => {
          gridService.initGridGap(rowSpan, colSpan, gridService.gridWidget.width, gridService.gridWidget.height);
          gridService.resetGridWidgets();
        });
        vue.watch([
          () => gridService.gridWidget.width,
          () => gridService.gridWidget.height
        ], ([width, height]) => {
          gridService.initGridGap(props.rowSpan, props.colSpan, width, height);
          gridService.resetGridWidgets();
        });
        const setNotOverlapInGrid = gridService.setNotOverlapInGrid.bind(gridService);
        const onAddWidget = gridService.onAddWidget.bind(gridService);
        service.emitter.on("onAddWidget", onAddWidget);
        service.isNotOverlapInGridPublisher.on(setNotOverlapInGrid);
        vue.onBeforeUnmount(() => {
          service.emitter.off("onAddWidget", onAddWidget);
          service.isNotOverlapInGridPublisher.off(setNotOverlapInGrid);
        });
        function renderGridRowGap() {
          return gridService.model.gridRowGap.map((item) => {
            var _a;
            return vue.h("div", {
              style: {
                position: "absolute",
                top: item.y + "px",
                left: 0,
                height: 0,
                width: item.width - 1 + "px",
                borderBottom: ((_a = props.gridBorder) === null || _a === void 0 ? void 0 : _a.includes("INNER_HORIZONTAL")) ? "1px dashed rgb(201, 201, 201)" : ""
              }
            });
          });
        }
        function renderGridColGap() {
          return gridService.model.gridColGap.map((item) => {
            var _a;
            return vue.h("div", {
              style: {
                position: "absolute",
                left: item.x + "px",
                top: 0,
                height: item.height + "px",
                width: 0,
                borderLeft: ((_a = props.gridBorder) === null || _a === void 0 ? void 0 : _a.includes("INNER_VERTICAL")) ? "1px dashed rgb(201, 201, 201)" : ""
              }
            });
          });
        }
        function renderGridItems() {
          return gridService.model.gridItems.map((item, index) => {
            var _a, _b;
            return vue.h("div", {
              style: {
                position: "absolute",
                left: item.x + "px",
                top: item.y + "px",
                height: item.height + "px",
                width: item.width + "px",
                // zIndex: 50,
                background: props.active && ((_a = props.active) === null || _a === void 0 ? void 0 : _a.id) === ((_b = item.widget) === null || _b === void 0 ? void 0 : _b.id) || item.active ? "#ecf4fc" : ""
              }
            }, []);
          });
        }
        function renderGridWidgets() {
          return gridService.gridChildWidgets.value.map((widget) => (
            // @ts-ignore
            vue.h(DragContainer, {
              key: widget.id,
              value: widget,
              style: {
                zIndex: 1600
              },
              onDragStart: gridService.onGridChildDragStart.bind(gridService)
            })
          ));
        }
        return {
          gridService,
          renderGridRowGap,
          renderGridColGap,
          renderGridItems,
          renderGridWidgets,
          deleteWidget: gridService.deleteWidget.bind(gridService),
          getComponents: () => gridService.gridChildWidgets.value,
          getGridItems: () => gridService.model.gridItems,
          setGridRowGap: gridService.setGridRowGap.bind(gridService),
          getBoundingClientRect: () => {
            var _a;
            return (_a = gridService.containerRef) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
          }
        };
      },
      render() {
        var _a, _b, _c, _d;
        return vue.h("div", {
          ref: (el) => this.gridService.containerRef = el,
          style: {
            position: "relative",
            width: "100%",
            height: this.gridService.model.height + "px",
            borderLeft: ((_a = this.$props.gridBorder) === null || _a === void 0 ? void 0 : _a.includes("LEFT")) ? "1px solid #9a9a9a" : "",
            borderRight: ((_b = this.$props.gridBorder) === null || _b === void 0 ? void 0 : _b.includes("RIGHT")) ? "1px solid #9a9a9a" : "",
            borderTop: ((_c = this.$props.gridBorder) === null || _c === void 0 ? void 0 : _c.includes("UP")) ? "1px solid #9a9a9a" : "",
            borderBottom: ((_d = this.$props.gridBorder) === null || _d === void 0 ? void 0 : _d.includes("DOWN")) ? "1px solid #9a9a9a" : ""
          }
        }, [
          this.renderGridItems(),
          this.renderGridRowGap(),
          this.renderGridColGap(),
          this.renderGridWidgets()
          // h('div',{},[JSON.stringify(this.gridWidget.components)])
        ]);
      }
    });

    const Browser = {
      // Firefox 1.0+
      isFirefox: () => {
        return typeof InstallTrigger !== "undefined";
      },
      // Internet Explorer 6-11
      isIE: () => {
        return navigator.userAgent.indexOf("MSIE") !== -1 || !!document.documentMode;
      },
      // Edge 20+
      isEdge: () => {
        return !Browser.isIE() && !!window.StyleMedia;
      },
      // Chrome 1+
      isChrome: (context = window) => {
        return !!context.chrome;
      },
      // At least Safari 3+: "[object HTMLElementConstructor]"
      isSafari: () => {
        return Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") > 0 || navigator.userAgent.toLowerCase().indexOf("safari") !== -1;
      },
      // IOS Chrome
      isIOSChrome: () => {
        return navigator.userAgent.toLowerCase().indexOf("crios") !== -1;
      }
    };

    function printHTML({ domId, width, height, documentTitle, styleStr, cssLinks }) {
      let page = document.getElementById(domId);
      if (!page) {
        console.error("canvas is " + page);
        return;
      }
      page = cloneElement(page);
      page.style.left = "0";
      page.style.top = "0";
      page.style.overflow = "hidden";
      const iframe = document.createElement("iframe");
      iframe.setAttribute("id", "free-layout-iframe");
      iframe.style.width = width + "px";
      iframe.style.height = height + "px";
      iframe.style.border = "none";
      iframe.srcdoc = "<html><head><title>" + (documentTitle || "") + "</title>";
      if (cssLinks) {
        cssLinks.forEach((file) => {
          iframe.srcdoc += '<link rel="stylesheet" href="' + file + '">';
        });
      }
      iframe.srcdoc += "</head><body></body></html>";
      document.body.appendChild(iframe);
      iframe.onload = () => {
        iframe.contentWindow.document.body.appendChild(page);
        const style = document.createElement("style");
        style.innerText = `
    html,body {
      padding: 0;
      margin:0;
    }
    #drawer{
      position: relative;
      overflow: hidden;
    }
    .drag_container{
      position: absolute;
    }
    ` + (styleStr || "");
        iframe.contentWindow.document.body.appendChild(style);
        performPrint(iframe);
      };
    }
    function performPrint(iframeElement) {
      try {
        iframeElement.focus();
        if (Browser.isEdge() || Browser.isIE()) {
          try {
            iframeElement.contentWindow.document.execCommand("print", false, null);
          } catch (e) {
            iframeElement.contentWindow.print();
          }
        } else {
          iframeElement.contentWindow.print();
        }
      } catch (error) {
        console.error(error);
      } finally {
        if (Browser.isFirefox()) {
          iframeElement.style.visibility = "hidden";
          iframeElement.style.left = "-1px";
        }
        cleanUp();
      }
    }
    function cleanUp() {
      let event = "mouseover";
      if (Browser.isChrome() || Browser.isFirefox()) {
        event = "focus";
      }
      const handler = () => {
        window.removeEventListener(event, handler);
        const iframe = document.getElementById("free-layout-iframe");
        if (iframe) {
          iframe.remove();
        }
      };
      window.addEventListener(event, handler);
    }
    function cloneElement(element) {
      const clone = element.cloneNode();
      const childNodesArray = Array.prototype.slice.call(element.childNodes);
      for (let i = 0; i < childNodesArray.length; i++) {
        const clonedChild = cloneElement(childNodesArray[i]);
        clone.appendChild(clonedChild);
      }
      switch (element.tagName) {
        case "SELECT":
          clone.value = element.value;
          break;
        case "CANVAS":
          clone.getContext("2d").drawImage(element, 0, 0);
          break;
      }
      return clone;
    }

    function useDesignPanel(designPanel) {
      const designService = designPanel ? void 0 : vue.inject(DesignService.token);
      if (!designPanel && !designService)
        console.error("designPanel \u4E0D\u5B58\u5728");
      const service = vue.computed(() => {
        var _a;
        if (designPanel === null || designPanel === void 0 ? void 0 : designPanel.value) {
          return (_a = designPanel === null || designPanel === void 0 ? void 0 : designPanel.value) === null || _a === void 0 ? void 0 : _a.service;
        } else {
          return designService;
        }
      });
      const designModal = vue.computed(() => {
        var _a;
        return (_a = service.value) === null || _a === void 0 ? void 0 : _a.modal;
      });
      function createWidget(widget) {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.createWidgetHandler(widget);
      }
      function getPageWidgets() {
        var _a;
        return (_a = service.value) === null || _a === void 0 ? void 0 : _a.getPageWidgets();
      }
      function getYWidgets() {
        var _a;
        return (_a = service.value) === null || _a === void 0 ? void 0 : _a.syncService.yWidget;
      }
      function updateWidget(widget) {
        if (!service.value)
          return;
        const { yWidget } = service.value.utils.getYWidgetById(widget.id);
        if (yWidget) {
          for (const key in widget) {
            yWidget.set(key, widget[key]);
          }
        } else {
          console.error("\u8BBE\u7F6E\u7684widget\u4E0D\u5B58\u5728" + widget.id);
        }
      }
      function delWidget(id) {
        if (!service.value)
          return;
        service.value.deleteSelected(id);
        service.value.deleteWidget(id);
      }
      function getPageRect() {
        var _a;
        return (_a = designModal.value) === null || _a === void 0 ? void 0 : _a.pageRect;
      }
      function leftJustify() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.leftJustify();
      }
      function rightJustify() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.rightJustify();
      }
      function topJustify() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.topJustify();
      }
      function bottomJustify() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.bottomJustify();
      }
      function rowBetween() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.rowBetween();
      }
      function columnBetween() {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.alignLineService.columnBetween();
      }
      function changePageSize(width, height, padding) {
        var _a;
        const pageRect = designModal.value.pageRect;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.changePageSize(width || pageRect.width, height || pageRect.height, padding || pageRect.padding);
      }
      function print(option) {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.setSelected([]);
        vue.nextTick().then(() => {
          printHTML(Object.assign({ domId: "drawer", width: designModal.value.pageRect.width, height: designModal.value.pageRect.height }, option));
        });
      }
      function onLayout(callback) {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.emitter.on("onLayout", callback);
      }
      vue.onBeforeUnmount(() => {
        var _a;
        (_a = service.value) === null || _a === void 0 ? void 0 : _a.emitter.off("onLayout");
      });
      return {
        designModal,
        getPageRect,
        onLayout,
        createWidget,
        getPageWidgets,
        getYWidgets,
        updateWidget,
        delWidget,
        leftJustify,
        rightJustify,
        topJustify,
        bottomJustify,
        rowBetween,
        columnBetween,
        print,
        changePageSize
      };
    }

    exports.DesignPanel = DesignPanel;
    exports.GridWidget = grid_widget;
    exports.useDesignPanel = useDesignPanel;

    Object.defineProperty(exports, '__esModule', { value: true });

}));