// source/node/TreeIterator.ts
var SHOW_ELEMENT = 1;
var SHOW_TEXT = 4;
var SHOW_ELEMENT_OR_TEXT = 5;
var always = () => true;
var TreeIterator = class {
  constructor(root, nodeType, filter) {
    this.root = root;
    this.currentNode = root;
    this.nodeType = nodeType;
    this.filter = filter || always;
  }
  isAcceptableNode(node) {
    const nodeType = node.nodeType;
    const nodeFilterType = nodeType === Node.ELEMENT_NODE ? SHOW_ELEMENT : nodeType === Node.TEXT_NODE ? SHOW_TEXT : 0;
    return !!(nodeFilterType & this.nodeType) && this.filter(node);
  }
  nextNode() {
    const root = this.root;
    let current = this.currentNode;
    let node;
    while (true) {
      node = current.firstChild;
      while (!node && current) {
        if (current === root) {
          break;
        }
        node = current.nextSibling;
        if (!node) {
          current = current.parentNode;
        }
      }
      if (!node) {
        return null;
      }
      if (this.isAcceptableNode(node)) {
        this.currentNode = node;
        return node;
      }
      current = node;
    }
  }
  previousNode() {
    const root = this.root;
    let current = this.currentNode;
    let node;
    while (true) {
      if (current === root) {
        return null;
      }
      node = current.previousSibling;
      if (node) {
        while (current = node.lastChild) {
          node = current;
        }
      } else {
        node = current.parentNode;
      }
      if (!node) {
        return null;
      }
      if (this.isAcceptableNode(node)) {
        this.currentNode = node;
        return node;
      }
      current = node;
    }
  }
  // Previous node in post-order.
  previousPONode() {
    const root = this.root;
    let current = this.currentNode;
    let node;
    while (true) {
      node = current.lastChild;
      while (!node && current) {
        if (current === root) {
          break;
        }
        node = current.previousSibling;
        if (!node) {
          current = current.parentNode;
        }
      }
      if (!node) {
        return null;
      }
      if (this.isAcceptableNode(node)) {
        this.currentNode = node;
        return node;
      }
      current = node;
    }
  }
};

// source/Constants.ts
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var DOCUMENT_FRAGMENT_NODE = 11;
var ZWS = "\u200B";
var ua = navigator.userAgent;
var isMac = /Mac OS X/.test(ua);
var isWin = /Windows NT/.test(ua);
var isIOS = /iP(?:ad|hone|od)/.test(ua) || isMac && !!navigator.maxTouchPoints;
var isAndroid = /Android/.test(ua);
var isGecko = /Gecko\//.test(ua);
var isLegacyEdge = /Edge\//.test(ua);
var isWebKit = !isLegacyEdge && /WebKit\//.test(ua);
var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-";
var cantFocusEmptyTextNodes = isWebKit;
var supportsInputEvents = "onbeforeinput" in document && "inputType" in new InputEvent("input");
var notWS = /[^ \t\r\n\u200B]/;

// source/node/Category.ts
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
var leafNodeNames = /* @__PURE__ */ new Set(["BR", "HR", "IFRAME", "IMG", "INPUT"]);
var UNKNOWN = 0;
var INLINE = 1;
var BLOCK = 2;
var CONTAINER = 3;
var cache = /* @__PURE__ */ new WeakMap();
var resetNodeCategoryCache = () => {
  cache = /* @__PURE__ */ new WeakMap();
};
var isLeaf = (node) => {
  return leafNodeNames.has(node.nodeName);
};
var getNodeCategory = (node) => {
  switch (node.nodeType) {
    case TEXT_NODE:
      return INLINE;
    case ELEMENT_NODE:
    case DOCUMENT_FRAGMENT_NODE:
      if (cache.has(node)) {
        return cache.get(node);
      }
      break;
    default:
      return UNKNOWN;
  }
  let nodeCategory;
  if (!Array.from(node.childNodes).every(isInline)) {
    nodeCategory = CONTAINER;
  } else if (inlineNodeNames.test(node.nodeName)) {
    nodeCategory = INLINE;
  } else {
    nodeCategory = BLOCK;
  }
  cache.set(node, nodeCategory);
  return nodeCategory;
};
var isInline = (node) => {
  return getNodeCategory(node) === INLINE;
};
var isBlock = (node) => {
  return getNodeCategory(node) === BLOCK;
};
var isContainer = (node) => {
  return getNodeCategory(node) === CONTAINER;
};

// source/node/Node.ts
var createElement = (tag, props, children) => {
  const el = document.createElement(tag);
  if (props instanceof Array) {
    children = props;
    props = null;
  }
  if (props) {
    for (const attr in props) {
      const value = props[attr];
      if (value !== void 0) {
        el.setAttribute(attr, value);
      }
    }
  }
  if (children) {
    children.forEach((node) => el.appendChild(node));
  }
  return el;
};
var areAlike = (node, node2) => {
  if (isLeaf(node)) {
    return false;
  }
  if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) {
    return false;
  }
  if (node instanceof HTMLElement && node2 instanceof HTMLElement) {
    return node.nodeName !== "A" && node.className === node2.className && node.style.cssText === node2.style.cssText;
  }
  return true;
};
var hasTagAttributes = (node, tag, attributes) => {
  if (node.nodeName !== tag) {
    return false;
  }
  for (const attr in attributes) {
    if (!("getAttribute" in node) || node.getAttribute(attr) !== attributes[attr]) {
      return false;
    }
  }
  return true;
};
var getNearest = (node, root, tag, attributes) => {
  while (node && node !== root) {
    if (hasTagAttributes(node, tag, attributes)) {
      return node;
    }
    node = node.parentNode;
  }
  return null;
};
var getNodeBeforeOffset = (node, offset) => {
  let children = node.childNodes;
  while (offset && node instanceof Element) {
    node = children[offset - 1];
    children = node.childNodes;
    offset = children.length;
  }
  return node;
};
var getNodeAfterOffset = (node, offset) => {
  let returnNode = node;
  if (returnNode instanceof Element) {
    const children = returnNode.childNodes;
    if (offset < children.length) {
      returnNode = children[offset];
    } else {
      while (returnNode && !returnNode.nextSibling) {
        returnNode = returnNode.parentNode;
      }
      if (returnNode) {
        returnNode = returnNode.nextSibling;
      }
    }
  }
  return returnNode;
};
var getLength = (node) => {
  return node instanceof Element || node instanceof DocumentFragment ? node.childNodes.length : node instanceof CharacterData ? node.length : 0;
};
var empty = (node) => {
  const frag = document.createDocumentFragment();
  let child = node.firstChild;
  while (child) {
    frag.appendChild(child);
    child = node.firstChild;
  }
  return frag;
};
var detach = (node) => {
  const parent = node.parentNode;
  if (parent) {
    parent.removeChild(node);
  }
  return node;
};
var replaceWith = (node, node2) => {
  const parent = node.parentNode;
  if (parent) {
    parent.replaceChild(node2, node);
  }
};

// source/node/Whitespace.ts
var notWSTextNode = (node) => {
  return node instanceof Element ? node.nodeName === "BR" : (
    // okay if data is 'undefined' here.
    notWS.test(node.data)
  );
};
var isLineBreak = (br, isLBIfEmptyBlock) => {
  let block = br.parentNode;
  while (isInline(block)) {
    block = block.parentNode;
  }
  const walker = new TreeIterator(
    block,
    SHOW_ELEMENT_OR_TEXT,
    notWSTextNode
  );
  walker.currentNode = br;
  return !!walker.nextNode() || isLBIfEmptyBlock && !walker.previousNode();
};
var removeZWS = (root, keepNode) => {
  const walker = new TreeIterator(root, SHOW_TEXT);
  let textNode;
  let index;
  while (textNode = walker.nextNode()) {
    while ((index = textNode.data.indexOf(ZWS)) > -1 && // eslint-disable-next-line no-unmodified-loop-condition
    (!keepNode || textNode.parentNode !== keepNode)) {
      if (textNode.length === 1) {
        let node = textNode;
        let parent = node.parentNode;
        while (parent) {
          parent.removeChild(node);
          walker.currentNode = parent;
          if (!isInline(parent) || getLength(parent)) {
            break;
          }
          node = parent;
          parent = node.parentNode;
        }
        break;
      } else {
        textNode.deleteData(index, 1);
      }
    }
  }
};

// source/range/Boundaries.ts
var START_TO_START = 0;
var START_TO_END = 1;
var END_TO_END = 2;
var END_TO_START = 3;
var isNodeContainedInRange = (range, node, partial) => {
  const nodeRange = document.createRange();
  nodeRange.selectNode(node);
  if (partial) {
    const nodeEndBeforeStart = range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;
    const nodeStartAfterEnd = range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;
    return !nodeEndBeforeStart && !nodeStartAfterEnd;
  } else {
    const nodeStartAfterStart = range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;
    const nodeEndBeforeEnd = range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;
    return nodeStartAfterStart && nodeEndBeforeEnd;
  }
};
var moveRangeBoundariesDownTree = (range) => {
  let { startContainer, startOffset, endContainer, endOffset } = range;
  while (!(startContainer instanceof Text)) {
    let child = startContainer.childNodes[startOffset];
    if (!child || isLeaf(child)) {
      if (startOffset) {
        child = startContainer.childNodes[startOffset - 1];
        if (child instanceof Text) {
          let textChild = child;
          let prev;
          while (!textChild.length && (prev = textChild.previousSibling) && prev instanceof Text) {
            textChild.remove();
            textChild = prev;
          }
          startContainer = textChild;
          startOffset = textChild.data.length;
        }
      }
      break;
    }
    startContainer = child;
    startOffset = 0;
  }
  if (endOffset) {
    while (!(endContainer instanceof Text)) {
      const child = endContainer.childNodes[endOffset - 1];
      if (!child || isLeaf(child)) {
        if (child && child.nodeName === "BR" && !isLineBreak(child, false)) {
          endOffset -= 1;
          continue;
        }
        break;
      }
      endContainer = child;
      endOffset = getLength(endContainer);
    }
  } else {
    while (!(endContainer instanceof Text)) {
      const child = endContainer.firstChild;
      if (!child || isLeaf(child)) {
        break;
      }
      endContainer = child;
    }
  }
  range.setStart(startContainer, startOffset);
  range.setEnd(endContainer, endOffset);
};
var moveRangeBoundariesUpTree = (range, startMax, endMax, root) => {
  let startContainer = range.startContainer;
  let startOffset = range.startOffset;
  let endContainer = range.endContainer;
  let endOffset = range.endOffset;
  let parent;
  if (!startMax) {
    startMax = range.commonAncestorContainer;
  }
  if (!endMax) {
    endMax = startMax;
  }
  while (!startOffset && startContainer !== startMax && startContainer !== root) {
    parent = startContainer.parentNode;
    startOffset = Array.from(parent.childNodes).indexOf(
      startContainer
    );
    startContainer = parent;
  }
  while (true) {
    if (endContainer === endMax || endContainer === root) {
      break;
    }
    if (endContainer.nodeType !== TEXT_NODE && endContainer.childNodes[endOffset] && endContainer.childNodes[endOffset].nodeName === "BR" && !isLineBreak(endContainer.childNodes[endOffset], false)) {
      endOffset += 1;
    }
    if (endOffset !== getLength(endContainer)) {
      break;
    }
    parent = endContainer.parentNode;
    endOffset = Array.from(parent.childNodes).indexOf(endContainer) + 1;
    endContainer = parent;
  }
  range.setStart(startContainer, startOffset);
  range.setEnd(endContainer, endOffset);
};
var moveRangeBoundaryOutOf = (range, tag, root) => {
  let parent = getNearest(range.endContainer, root, tag);
  if (parent && (parent = parent.parentNode)) {
    const clone = range.cloneRange();
    moveRangeBoundariesUpTree(clone, parent, parent, root);
    if (clone.endContainer === parent) {
      range.setStart(clone.endContainer, clone.endOffset);
      range.setEnd(clone.endContainer, clone.endOffset);
    }
  }
  return range;
};

// source/node/MergeSplit.ts
var fixCursor = (node) => {
  let fixer = null;
  if (node instanceof Text) {
    return node;
  }
  if (isInline(node)) {
    let child = node.firstChild;
    if (cantFocusEmptyTextNodes) {
      while (child && child instanceof Text && !child.data) {
        node.removeChild(child);
        child = node.firstChild;
      }
    }
    if (!child) {
      if (cantFocusEmptyTextNodes) {
        fixer = document.createTextNode(ZWS);
      } else {
        fixer = document.createTextNode("");
      }
    }
  } else if ((node instanceof Element || node instanceof DocumentFragment) && !node.querySelector("BR") && !notWS.test(node.textContent || "")) {
    fixer = createElement("BR");
    let parent = node;
    let child;
    while ((child = parent.lastElementChild) && !isInline(child)) {
      parent = child;
    }
    node = parent;
  }
  if (fixer) {
    try {
      node.appendChild(fixer);
    } catch (error) {
    }
  }
  return node;
};
var fixContainer = (container, root) => {
  let wrapper = null;
  if (/^(?:TABLE|TBODY|TR|TH|TD|P)/.test(container.nodeName)) {
    return container;
  }
  Array.from(container.childNodes).forEach((child) => {
    const isBR = child.nodeName === "BR";
    if (!isBR && isInline(child)) {
      if (!wrapper) {
        wrapper = createElement("DIV");
      }
      wrapper.appendChild(child);
    } else if (isBR || wrapper) {
      if (!wrapper) {
        wrapper = createElement("DIV");
      }
      fixCursor(wrapper);
      if (isBR) {
        container.replaceChild(wrapper, child);
      } else {
        container.insertBefore(wrapper, child);
      }
      wrapper = null;
    }
    if (isContainer(child)) {
      fixContainer(child, root);
    }
  });
  if (wrapper) {
    container.appendChild(fixCursor(wrapper));
  }
  return container;
};
var split = (node, offset, stopNode, root) => {
  if (node instanceof Text && node !== stopNode) {
    if (typeof offset !== "number") {
      throw new Error("Offset must be a number to split text node!");
    }
    if (!node.parentNode) {
      throw new Error("Cannot split text node with no parent!");
    }
    return split(node.parentNode, node.splitText(offset), stopNode, root);
  }
  let nodeAfterSplit = typeof offset === "number" ? offset < node.childNodes.length ? node.childNodes[offset] : null : offset;
  const parent = node.parentNode;
  if (!parent || node === stopNode || !(node instanceof Element)) {
    return nodeAfterSplit;
  }
  const clone = node.cloneNode(false);
  while (nodeAfterSplit) {
    const next = nodeAfterSplit.nextSibling;
    clone.appendChild(nodeAfterSplit);
    nodeAfterSplit = next;
  }
  if (node instanceof HTMLOListElement && getNearest(node, root, "BLOCKQUOTE")) {
    clone.start = (+node.start || 1) + node.childNodes.length - 1;
  }
  fixCursor(node);
  fixCursor(clone);
  parent.insertBefore(clone, node.nextSibling);
  return split(parent, clone, stopNode, root);
};
var _mergeInlines = (node, fakeRange) => {
  const children = node.childNodes;
  let l = children.length;
  const frags = [];
  while (l--) {
    const child = children[l];
    const prev = l ? children[l - 1] : null;
    if (prev && isInline(child) && areAlike(child, prev)) {
      if (fakeRange.startContainer === child) {
        fakeRange.startContainer = prev;
        fakeRange.startOffset += getLength(prev);
      }
      if (fakeRange.endContainer === child) {
        fakeRange.endContainer = prev;
        fakeRange.endOffset += getLength(prev);
      }
      if (fakeRange.startContainer === node) {
        if (fakeRange.startOffset > l) {
          fakeRange.startOffset -= 1;
        } else if (fakeRange.startOffset === l) {
          fakeRange.startContainer = prev;
          fakeRange.startOffset = getLength(prev);
        }
      }
      if (fakeRange.endContainer === node) {
        if (fakeRange.endOffset > l) {
          fakeRange.endOffset -= 1;
        } else if (fakeRange.endOffset === l) {
          fakeRange.endContainer = prev;
          fakeRange.endOffset = getLength(prev);
        }
      }
      detach(child);
      if (child instanceof Text) {
        prev.appendData(child.data);
      } else {
        frags.push(empty(child));
      }
    } else if (child instanceof Element) {
      let frag;
      while (frag = frags.pop()) {
        child.appendChild(frag);
      }
      _mergeInlines(child, fakeRange);
    }
  }
};
var mergeInlines = (node, range) => {
  const element = node instanceof Text ? node.parentNode : node;
  if (element instanceof Element) {
    const fakeRange = {
      startContainer: range.startContainer,
      startOffset: range.startOffset,
      endContainer: range.endContainer,
      endOffset: range.endOffset
    };
    _mergeInlines(element, fakeRange);
    range.setStart(fakeRange.startContainer, fakeRange.startOffset);
    range.setEnd(fakeRange.endContainer, fakeRange.endOffset);
  }
};
var mergeWithBlock = (block, next, range, root) => {
  let container = next;
  let parent;
  let offset;
  while ((parent = container.parentNode) && parent !== root && parent instanceof Element && parent.childNodes.length === 1) {
    container = parent;
  }
  detach(container);
  offset = block.childNodes.length;
  const last = block.lastChild;
  if (last && last.nodeName === "BR") {
    block.removeChild(last);
    offset -= 1;
  }
  block.appendChild(empty(next));
  range.setStart(block, offset);
  range.collapse(true);
  mergeInlines(block, range);
};
var mergeContainers = (node, root) => {
  const prev = node.previousSibling;
  const first = node.firstChild;
  const isListItem = node.nodeName === "LI";
  if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) {
    return;
  }
  if (prev && areAlike(prev, node)) {
    if (!isContainer(prev)) {
      if (isListItem) {
        const block = createElement("DIV");
        block.appendChild(empty(prev));
        prev.appendChild(block);
      } else {
        return;
      }
    }
    detach(node);
    const needsFix = !isContainer(node);
    prev.appendChild(empty(node));
    if (needsFix) {
      fixContainer(prev, root);
    }
    if (first) {
      mergeContainers(first, root);
    }
  } else if (isListItem) {
    const block = createElement("DIV");
    node.insertBefore(block, first);
    fixCursor(block);
  }
};

// source/Clean.ts
var styleToSemantic = {
  "font-weight": {
    regexp: /^bold|^700/i,
    replace() {
      return createElement("B");
    }
  },
  "font-style": {
    regexp: /^italic/i,
    replace() {
      return createElement("I");
    }
  },
  "font-family": {
    regexp: notWS,
    replace(classNames, family) {
      return createElement("SPAN", {
        class: classNames.fontFamily,
        style: "font-family:" + family
      });
    }
  },
  "font-size": {
    regexp: notWS,
    replace(classNames, size) {
      return createElement("SPAN", {
        class: classNames.fontSize,
        style: "font-size:" + size
      });
    }
  },
  "text-decoration": {
    regexp: /^underline/i,
    replace() {
      return createElement("U");
    }
  }
};
var replaceStyles = (node, _, config) => {
  const style = node.style;
  let newTreeBottom;
  let newTreeTop;
  for (const attr in styleToSemantic) {
    const converter = styleToSemantic[attr];
    const css = style.getPropertyValue(attr);
    if (css && converter.regexp.test(css)) {
      const el = converter.replace(config.classNames, css);
      if (el.nodeName === node.nodeName && el.className === node.className) {
        continue;
      }
      if (!newTreeTop) {
        newTreeTop = el;
      }
      if (newTreeBottom) {
        newTreeBottom.appendChild(el);
      }
      newTreeBottom = el;
      node.style.removeProperty(attr);
    }
  }
  if (newTreeTop && newTreeBottom) {
    newTreeBottom.appendChild(empty(node));
    if (node.style.cssText) {
      node.appendChild(newTreeTop);
    } else {
      replaceWith(node, newTreeTop);
    }
  }
  return newTreeBottom || node;
};
var replaceWithTag = (tag) => {
  return (node, parent) => {
    const el = createElement(tag);
    const attributes = node.attributes;
    for (let i = 0, l = attributes.length; i < l; i += 1) {
      const attribute = attributes[i];
      el.setAttribute(attribute.name, attribute.value);
    }
    parent.replaceChild(el, node);
    el.appendChild(empty(node));
    return el;
  };
};
var fontSizes = {
  "1": "10",
  "2": "13",
  "3": "16",
  "4": "18",
  "5": "24",
  "6": "32",
  "7": "48"
};
var stylesRewriters = {
  STRONG: replaceWithTag("B"),
  EM: replaceWithTag("I"),
  INS: replaceWithTag("U"),
  STRIKE: replaceWithTag("S"),
  SPAN: replaceStyles,
  FONT: (node, parent, config) => {
    const font = node;
    const face = font.face;
    const size = font.size;
    let color = font.color;
    const classNames = config.classNames;
    let fontSpan;
    let sizeSpan;
    let colorSpan;
    let newTreeBottom;
    let newTreeTop;
    if (face) {
      fontSpan = createElement("SPAN", {
        class: classNames.fontFamily,
        style: "font-family:" + face
      });
      newTreeTop = fontSpan;
      newTreeBottom = fontSpan;
    }
    if (size) {
      sizeSpan = createElement("SPAN", {
        class: classNames.fontSize,
        style: "font-size:" + fontSizes[size] + "px"
      });
      if (!newTreeTop) {
        newTreeTop = sizeSpan;
      }
      if (newTreeBottom) {
        newTreeBottom.appendChild(sizeSpan);
      }
      newTreeBottom = sizeSpan;
    }
    if (color && /^#?([\dA-F]{3}){1,2}$/i.test(color)) {
      if (color.charAt(0) !== "#") {
        color = "#" + color;
      }
      colorSpan = createElement("SPAN", {
        class: classNames.color,
        style: "color:" + color
      });
      if (!newTreeTop) {
        newTreeTop = colorSpan;
      }
      if (newTreeBottom) {
        newTreeBottom.appendChild(colorSpan);
      }
      newTreeBottom = colorSpan;
    }
    if (!newTreeTop || !newTreeBottom) {
      newTreeTop = newTreeBottom = createElement("SPAN");
    }
    parent.replaceChild(newTreeTop, font);
    newTreeBottom.appendChild(empty(font));
    return newTreeBottom;
  },
  TT: (node, parent, config) => {
    const el = createElement("SPAN", {
      class: config.classNames.fontFamily,
      style: 'font-family:menlo,consolas,"courier new",monospace'
    });
    parent.replaceChild(el, node);
    el.appendChild(empty(node));
    return el;
  }
};
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;
var blacklist = /^(?:HEAD|META|STYLE)/;
var cleanTree = (node, config, preserveWS) => {
  const children = node.childNodes;
  let nonInlineParent = node;
  while (isInline(nonInlineParent)) {
    nonInlineParent = nonInlineParent.parentNode;
  }
  const walker = new TreeIterator(
    nonInlineParent,
    SHOW_ELEMENT_OR_TEXT
  );
  for (let i = 0, l = children.length; i < l; i += 1) {
    let child = children[i];
    const nodeName = child.nodeName;
    const rewriter = stylesRewriters[nodeName];
    if (child instanceof HTMLElement) {
      const childLength = child.childNodes.length;
      if (rewriter) {
        child = rewriter(child, node, config);
      } else if (blacklist.test(nodeName)) {
        node.removeChild(child);
        i -= 1;
        l -= 1;
        continue;
      } else if (!allowedBlock.test(nodeName) && !isInline(child)) {
        i -= 1;
        l += childLength - 1;
        node.replaceChild(empty(child), child);
        continue;
      }
      if (childLength) {
        cleanTree(child, config, preserveWS || nodeName === "PRE");
      }
    } else {
      if (child instanceof Text) {
        let data = child.data;
        const startsWithWS = !notWS.test(data.charAt(0));
        const endsWithWS = !notWS.test(data.charAt(data.length - 1));
        if (preserveWS || !startsWithWS && !endsWithWS) {
          continue;
        }
        if (startsWithWS) {
          walker.currentNode = child;
          let sibling;
          while (sibling = walker.previousPONode()) {
            if (sibling.nodeName === "IMG" || sibling instanceof Text && notWS.test(sibling.data)) {
              break;
            }
            if (!isInline(sibling)) {
              sibling = null;
              break;
            }
          }
          data = data.replace(/^[ \t\r\n]+/g, sibling ? " " : "");
        }
        if (endsWithWS) {
          walker.currentNode = child;
          let sibling;
          while (sibling = walker.nextNode()) {
            if (sibling.nodeName === "IMG" || sibling instanceof Text && notWS.test(sibling.data)) {
              break;
            }
            if (!isInline(sibling)) {
              sibling = null;
              break;
            }
          }
          data = data.replace(/[ \t\r\n]+$/g, sibling ? " " : "");
        }
        if (data) {
          child.data = data;
          continue;
        }
      }
      node.removeChild(child);
      i -= 1;
      l -= 1;
    }
  }
  return node;
};
var removeEmptyInlines = (node) => {
  const children = node.childNodes;
  let l = children.length;
  while (l--) {
    const child = children[l];
    if (child instanceof Element && !isLeaf(child)) {
      removeEmptyInlines(child);
      if (isInline(child) && !child.firstChild) {
        node.removeChild(child);
      }
    } else if (child instanceof Text && !child.data) {
      node.removeChild(child);
    }
  }
};
var cleanupBRs = (node, root, keepForBlankLine) => {
  const brs = node.querySelectorAll("BR");
  const brBreaksLine = [];
  let l = brs.length;
  for (let i = 0; i < l; i += 1) {
    brBreaksLine[i] = isLineBreak(brs[i], keepForBlankLine);
  }
  while (l--) {
    const br = brs[l];
    const parent = br.parentNode;
    if (!parent) {
      continue;
    }
    if (!brBreaksLine[l]) {
      detach(br);
    } else if (!isInline(parent)) {
      fixContainer(parent, root);
    }
  }
};
var escapeHTML = (text) => {
  return text.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");
};

// source/node/Block.ts
var getBlockWalker = (node, root) => {
  const walker = new TreeIterator(root, SHOW_ELEMENT, isBlock);
  walker.currentNode = node;
  return walker;
};
var getPreviousBlock = (node, root) => {
  const block = getBlockWalker(node, root).previousNode();
  return block !== root ? block : null;
};
var getNextBlock = (node, root) => {
  const block = getBlockWalker(node, root).nextNode();
  return block !== root ? block : null;
};
var isEmptyBlock = (block) => {
  return !block.textContent && !block.querySelector("IMG");
};

// source/range/Block.ts
var getStartBlockOfRange = (range, root) => {
  const container = range.startContainer;
  let block;
  if (isInline(container)) {
    block = getPreviousBlock(container, root);
  } else if (container !== root && container instanceof HTMLElement && isBlock(container)) {
    block = container;
  } else {
    const node = getNodeBeforeOffset(container, range.startOffset);
    block = getNextBlock(node, root);
  }
  return block && isNodeContainedInRange(range, block, true) ? block : null;
};
var getEndBlockOfRange = (range, root) => {
  const container = range.endContainer;
  let block;
  if (isInline(container)) {
    block = getPreviousBlock(container, root);
  } else if (container !== root && container instanceof HTMLElement && isBlock(container)) {
    block = container;
  } else {
    let node = getNodeAfterOffset(container, range.endOffset);
    if (!node || !root.contains(node)) {
      node = root;
      let child;
      while (child = node.lastChild) {
        node = child;
      }
    }
    block = getPreviousBlock(node, root);
  }
  return block && isNodeContainedInRange(range, block, true) ? block : null;
};
var isContent = (node) => {
  return node instanceof Text ? notWS.test(node.data) : node.nodeName === "IMG";
};
var rangeDoesStartAtBlockBoundary = (range, root) => {
  const startContainer = range.startContainer;
  const startOffset = range.startOffset;
  let nodeAfterCursor;
  if (startContainer instanceof Text) {
    const text = startContainer.data;
    for (let i = startOffset; i > 0; i -= 1) {
      if (text.charAt(i - 1) !== ZWS) {
        return false;
      }
    }
    nodeAfterCursor = startContainer;
  } else {
    nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);
    if (nodeAfterCursor && !root.contains(nodeAfterCursor)) {
      nodeAfterCursor = null;
    }
    if (!nodeAfterCursor) {
      nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);
      if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) {
        return false;
      }
    }
  }
  const block = getStartBlockOfRange(range, root);
  if (!block) {
    return false;
  }
  const contentWalker = new TreeIterator(
    block,
    SHOW_ELEMENT_OR_TEXT,
    isContent
  );
  contentWalker.currentNode = nodeAfterCursor;
  return !contentWalker.previousNode();
};
var rangeDoesEndAtBlockBoundary = (range, root) => {
  const endContainer = range.endContainer;
  const endOffset = range.endOffset;
  let currentNode;
  if (endContainer instanceof Text) {
    const text = endContainer.data;
    const length = text.length;
    for (let i = endOffset; i < length; i += 1) {
      if (text.charAt(i) !== ZWS) {
        return false;
      }
    }
    currentNode = endContainer;
  } else {
    currentNode = getNodeBeforeOffset(endContainer, endOffset);
  }
  const block = getEndBlockOfRange(range, root);
  if (!block) {
    return false;
  }
  const contentWalker = new TreeIterator(
    block,
    SHOW_ELEMENT_OR_TEXT,
    isContent
  );
  contentWalker.currentNode = currentNode;
  return !contentWalker.nextNode();
};
var expandRangeToBlockBoundaries = (range, root) => {
  const start = getStartBlockOfRange(range, root);
  const end = getEndBlockOfRange(range, root);
  let parent;
  if (start && end) {
    parent = start.parentNode;
    range.setStart(parent, Array.from(parent.childNodes).indexOf(start));
    parent = end.parentNode;
    range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1);
  }
};

// source/range/InsertDelete.ts
function createRange(startContainer, startOffset, endContainer, endOffset) {
  const range = document.createRange();
  range.setStart(startContainer, startOffset);
  if (endContainer && typeof endOffset === "number") {
    range.setEnd(endContainer, endOffset);
  } else {
    range.setEnd(startContainer, startOffset);
  }
  return range;
}
var insertNodeInRange = (range, node) => {
  let { startContainer, startOffset, endContainer, endOffset } = range;
  let children;
  if (startContainer instanceof Text) {
    const parent = startContainer.parentNode;
    children = parent.childNodes;
    if (startOffset === startContainer.length) {
      startOffset = Array.from(children).indexOf(startContainer) + 1;
      if (range.collapsed) {
        endContainer = parent;
        endOffset = startOffset;
      }
    } else {
      if (startOffset) {
        const afterSplit = startContainer.splitText(startOffset);
        if (endContainer === startContainer) {
          endOffset -= startOffset;
          endContainer = afterSplit;
        } else if (endContainer === parent) {
          endOffset += 1;
        }
        startContainer = afterSplit;
      }
      startOffset = Array.from(children).indexOf(
        startContainer
      );
    }
    startContainer = parent;
  } else {
    children = startContainer.childNodes;
  }
  const childCount = children.length;
  if (startOffset === childCount) {
    startContainer.appendChild(node);
  } else {
    startContainer.insertBefore(node, children[startOffset]);
  }
  if (startContainer === endContainer) {
    endOffset += children.length - childCount;
  }
  range.setStart(startContainer, startOffset);
  range.setEnd(endContainer, endOffset);
};
var extractContentsOfRange = (range, common, root) => {
  const frag = document.createDocumentFragment();
  if (range.collapsed) {
    return frag;
  }
  if (!common) {
    common = range.commonAncestorContainer;
  }
  if (common instanceof Text) {
    common = common.parentNode;
  }
  const startContainer = range.startContainer;
  const startOffset = range.startOffset;
  let endContainer = split(range.endContainer, range.endOffset, common, root);
  let endOffset = 0;
  let node = split(startContainer, startOffset, common, root);
  while (node && node !== endContainer) {
    const next = node.nextSibling;
    frag.appendChild(node);
    node = next;
  }
  node = endContainer && endContainer.previousSibling;
  if (node && node instanceof Text && endContainer instanceof Text) {
    endOffset = node.length;
    node.appendData(endContainer.data);
    detach(endContainer);
    endContainer = node;
  }
  range.setStart(startContainer, startOffset);
  if (endContainer) {
    range.setEnd(endContainer, endOffset);
  } else {
    range.setEnd(common, common.childNodes.length);
  }
  fixCursor(common);
  return frag;
};
var getAdjacentInlineNode = (iterator, method, node) => {
  iterator.currentNode = node;
  let nextNode;
  while (nextNode = iterator[method]()) {
    if (nextNode instanceof Text || isLeaf(nextNode)) {
      return nextNode;
    }
    if (!isInline(nextNode)) {
      return null;
    }
  }
  return null;
};
var deleteContentsOfRange = (range, root) => {
  const startBlock = getStartBlockOfRange(range, root);
  let endBlock = getEndBlockOfRange(range, root);
  const needsMerge = startBlock !== endBlock;
  if (startBlock && endBlock) {
    moveRangeBoundariesDownTree(range);
    moveRangeBoundariesUpTree(range, startBlock, endBlock, root);
  }
  const frag = extractContentsOfRange(range, null, root);
  moveRangeBoundariesDownTree(range);
  if (needsMerge) {
    endBlock = getEndBlockOfRange(range, root);
    if (startBlock && endBlock && startBlock !== endBlock) {
      mergeWithBlock(startBlock, endBlock, range, root);
    }
  }
  if (startBlock) {
    fixCursor(startBlock);
  }
  const child = root.firstChild;
  if (!child || child.nodeName === "BR") {
    fixCursor(root);
    if (root.firstChild) {
      range.selectNodeContents(root.firstChild);
    }
  }
  range.collapse(true);
  const startContainer = range.startContainer;
  const startOffset = range.startOffset;
  const iterator = new TreeIterator(root, SHOW_ELEMENT_OR_TEXT);
  let afterNode = startContainer;
  let afterOffset = startOffset;
  if (!(afterNode instanceof Text) || afterOffset === afterNode.data.length) {
    afterNode = getAdjacentInlineNode(iterator, "nextNode", afterNode);
    afterOffset = 0;
  }
  let beforeNode = startContainer;
  let beforeOffset = startOffset - 1;
  if (!(beforeNode instanceof Text) || beforeOffset === -1) {
    beforeNode = getAdjacentInlineNode(
      iterator,
      "previousPONode",
      afterNode || (startContainer instanceof Text ? startContainer : startContainer.childNodes[startOffset] || startContainer)
    );
    if (beforeNode instanceof Text) {
      beforeOffset = beforeNode.data.length;
    }
  }
  let node = null;
  let offset = 0;
  if (afterNode instanceof Text && afterNode.data.charAt(afterOffset) === " " && rangeDoesStartAtBlockBoundary(range, root)) {
    node = afterNode;
    offset = afterOffset;
  } else if (beforeNode instanceof Text && beforeNode.data.charAt(beforeOffset) === " ") {
    if (afterNode instanceof Text && afterNode.data.charAt(afterOffset) === " " || rangeDoesEndAtBlockBoundary(range, root)) {
      node = beforeNode;
      offset = beforeOffset;
    }
  }
  if (node) {
    node.replaceData(offset, 1, "\xA0");
  }
  range.setStart(startContainer, startOffset);
  range.collapse(true);
  return frag;
};
var insertTreeFragmentIntoRange = (range, frag, root) => {
  const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);
  let node;
  fixContainer(frag, root);
  node = frag;
  while (node = getNextBlock(node, root)) {
    fixCursor(node);
  }
  if (!range.collapsed) {
    deleteContentsOfRange(range, root);
  }
  moveRangeBoundariesDownTree(range);
  range.collapse(false);
  const stopPoint = getNearest(range.endContainer, root, "BLOCKQUOTE") || root;
  let block = getStartBlockOfRange(range, root);
  let blockContentsAfterSplit = null;
  const firstBlockInFrag = getNextBlock(frag, frag);
  const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);
  if (block && firstBlockInFrag && !replaceBlock && // Don't merge table cells or PRE elements into block
  !getNearest(firstBlockInFrag, frag, "PRE") && !getNearest(firstBlockInFrag, frag, "TABLE")) {
    moveRangeBoundariesUpTree(range, block, block, root);
    range.collapse(true);
    let container = range.endContainer;
    let offset = range.endOffset;
    cleanupBRs(block, root, false);
    if (isInline(container)) {
      const nodeAfterSplit = split(
        container,
        offset,
        getPreviousBlock(container, root) || root,
        root
      );
      container = nodeAfterSplit.parentNode;
      offset = Array.from(container.childNodes).indexOf(
        nodeAfterSplit
      );
    }
    if (
      /*isBlock( container ) && */
      offset !== getLength(container)
    ) {
      blockContentsAfterSplit = document.createDocumentFragment();
      while (node = container.childNodes[offset]) {
        blockContentsAfterSplit.appendChild(node);
      }
    }
    mergeWithBlock(container, firstBlockInFrag, range, root);
    offset = Array.from(container.parentNode.childNodes).indexOf(
      container
    ) + 1;
    container = container.parentNode;
    range.setEnd(container, offset);
  }
  if (getLength(frag)) {
    if (replaceBlock && block) {
      range.setEndBefore(block);
      range.collapse(false);
      detach(block);
    }
    moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);
    let nodeAfterSplit = split(
      range.endContainer,
      range.endOffset,
      stopPoint,
      root
    );
    const nodeBeforeSplit = nodeAfterSplit ? nodeAfterSplit.previousSibling : stopPoint.lastChild;
    stopPoint.insertBefore(frag, nodeAfterSplit);
    if (nodeAfterSplit) {
      range.setEndBefore(nodeAfterSplit);
    } else {
      range.setEnd(stopPoint, getLength(stopPoint));
    }
    block = getEndBlockOfRange(range, root);
    moveRangeBoundariesDownTree(range);
    const container = range.endContainer;
    const offset = range.endOffset;
    if (nodeAfterSplit && isContainer(nodeAfterSplit)) {
      mergeContainers(nodeAfterSplit, root);
    }
    nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;
    if (nodeAfterSplit && isContainer(nodeAfterSplit)) {
      mergeContainers(nodeAfterSplit, root);
    }
    range.setEnd(container, offset);
  }
  if (blockContentsAfterSplit && block) {
    const tempRange = range.cloneRange();
    fixCursor(blockContentsAfterSplit);
    mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);
    range.setEnd(tempRange.endContainer, tempRange.endOffset);
  }
  moveRangeBoundariesDownTree(range);
};

// source/range/Contents.ts
var getTextContentsOfRange = (range) => {
  if (range.collapsed) {
    return "";
  }
  const startContainer = range.startContainer;
  const endContainer = range.endContainer;
  const walker = new TreeIterator(
    range.commonAncestorContainer,
    SHOW_ELEMENT_OR_TEXT,
    (node2) => {
      return isNodeContainedInRange(range, node2, true);
    }
  );
  walker.currentNode = startContainer;
  let node = startContainer;
  let textContent = "";
  let addedTextInBlock = false;
  let value;
  if (!(node instanceof Element) && !(node instanceof Text) || !walker.filter(node)) {
    node = walker.nextNode();
  }
  while (node) {
    if (node instanceof Text) {
      value = node.data;
      if (value && /\S/.test(value)) {
        if (node === endContainer) {
          value = value.slice(0, range.endOffset);
        }
        if (node === startContainer) {
          value = value.slice(range.startOffset);
        }
        textContent += value;
        addedTextInBlock = true;
      }
    } else if (node.nodeName === "BR" || addedTextInBlock && !isInline(node)) {
      textContent += "\n";
      addedTextInBlock = false;
    }
    node = walker.nextNode();
  }
  textContent = textContent.replace(/ /g, " ");
  return textContent;
};

// source/Clipboard.ts
var indexOf = Array.prototype.indexOf;
var extractRangeToClipboard = (event, range, root, removeRangeFromDocument, toCleanHTML, toPlainText, plainTextOnly) => {
  const clipboardData = event.clipboardData;
  if (isLegacyEdge || !clipboardData) {
    return false;
  }
  let text = toPlainText ? "" : getTextContentsOfRange(range);
  const startBlock = getStartBlockOfRange(range, root);
  const endBlock = getEndBlockOfRange(range, root);
  let copyRoot = root;
  if (startBlock === endBlock && startBlock?.contains(range.commonAncestorContainer)) {
    copyRoot = startBlock;
  }
  let contents;
  if (removeRangeFromDocument) {
    contents = deleteContentsOfRange(range, root);
  } else {
    contents = range.cloneContents();
  }
  let parent = range.commonAncestorContainer;
  if (parent instanceof Text) {
    parent = parent.parentNode;
  }
  while (parent && parent !== copyRoot) {
    const newContents = parent.cloneNode(false);
    newContents.appendChild(contents);
    contents = newContents;
    parent = parent.parentNode;
  }
  let html;
  if (contents.childNodes.length === 1 && contents.childNodes[0] instanceof Text) {
    text = contents.childNodes[0].data.replace(/ /g, " ");
    plainTextOnly = true;
  } else {
    const node = createElement("DIV");
    node.appendChild(contents);
    html = node.innerHTML;
    if (toCleanHTML) {
      html = toCleanHTML(html);
    }
  }
  if (toPlainText && html !== void 0) {
    text = toPlainText(html);
  }
  if (isWin) {
    text = text.replace(/\r?\n/g, "\r\n");
  }
  if (!plainTextOnly && html && text !== html) {
    html = "<!-- squire -->" + html;
    clipboardData.setData("text/html", html);
  }
  clipboardData.setData("text/plain", text);
  event.preventDefault();
  return true;
};
var _onCut = function(event) {
  const range = this.getSelection();
  const root = this._root;
  if (range.collapsed) {
    event.preventDefault();
    return;
  }
  this.saveUndoState(range);
  const handled = extractRangeToClipboard(
    event,
    range,
    root,
    true,
    this._config.willCutCopy,
    this._config.toPlainText,
    false
  );
  if (!handled) {
    setTimeout(() => {
      try {
        this._ensureBottomLine();
      } catch (error) {
        this._config.didError(error);
      }
    }, 0);
  }
  this.setSelection(range);
};
var _onCopy = function(event) {
  extractRangeToClipboard(
    event,
    this.getSelection(),
    this._root,
    false,
    this._config.willCutCopy,
    this._config.toPlainText,
    false
  );
};
var _monitorShiftKey = function(event) {
  this._isShiftDown = event.shiftKey;
};
var _onPaste = function(event) {
  const clipboardData = event.clipboardData;
  const items = clipboardData?.items;
  const choosePlain = this._isShiftDown;
  let hasRTF = false;
  let hasImage = false;
  let plainItem = null;
  let htmlItem = null;
  if (items) {
    let l = items.length;
    while (l--) {
      const item = items[l];
      const type = item.type;
      if (type === "text/html") {
        htmlItem = item;
      } else if (type === "text/plain" || type === "text/uri-list") {
        plainItem = item;
      } else if (type === "text/rtf") {
        hasRTF = true;
      } else if (/^image\/.*/.test(type)) {
        hasImage = true;
      }
    }
    if (hasImage && !(hasRTF && htmlItem)) {
      event.preventDefault();
      this.fireEvent("pasteImage", {
        clipboardData
      });
      return;
    }
    if (!isLegacyEdge) {
      event.preventDefault();
      if (htmlItem && (!choosePlain || !plainItem)) {
        htmlItem.getAsString((html) => {
          this.insertHTML(html, true);
        });
      } else if (plainItem) {
        plainItem.getAsString((text) => {
          const range2 = this.getSelection();
          if (!range2.collapsed && notWS.test(range2.toString())) {
            const match = this.linkRegExp.exec(text);
            const isLink = !!match && match[0].length === text.length;
            if (isLink) {
              const href = match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0];
              this.makeLink(href);
              return;
            }
          }
          this.insertPlainText(text, true);
        });
      }
      return;
    }
  }
  const types = clipboardData?.types;
  if (!isLegacyEdge && types && (indexOf.call(types, "text/html") > -1 || !isGecko && indexOf.call(types, "text/plain") > -1 && indexOf.call(types, "text/rtf") < 0)) {
    event.preventDefault();
    let data;
    if (!choosePlain && (data = clipboardData.getData("text/html"))) {
      this.insertHTML(data, true);
    } else if ((data = clipboardData.getData("text/plain")) || (data = clipboardData.getData("text/uri-list"))) {
      this.insertPlainText(data, true);
    }
    return;
  }
  const body = document.body;
  const range = this.getSelection();
  const startContainer = range.startContainer;
  const startOffset = range.startOffset;
  const endContainer = range.endContainer;
  const endOffset = range.endOffset;
  let pasteArea = createElement("DIV", {
    contenteditable: "true",
    style: "position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;"
  });
  body.appendChild(pasteArea);
  range.selectNodeContents(pasteArea);
  this.setSelection(range);
  setTimeout(() => {
    try {
      let html = "";
      let next = pasteArea;
      let first;
      while (pasteArea = next) {
        next = pasteArea.nextSibling;
        detach(pasteArea);
        first = pasteArea.firstChild;
        if (first && first === pasteArea.lastChild && first instanceof HTMLDivElement) {
          pasteArea = first;
        }
        html += pasteArea.innerHTML;
      }
      this.setSelection(
        createRange(
          startContainer,
          startOffset,
          endContainer,
          endOffset
        )
      );
      if (html) {
        this.insertHTML(html, true);
      }
    } catch (error) {
      this._config.didError(error);
    }
  }, 0);
};
var _onDrop = function(event) {
  if (!event.dataTransfer) {
    return;
  }
  const types = event.dataTransfer.types;
  let l = types.length;
  let hasPlain = false;
  let hasHTML = false;
  while (l--) {
    switch (types[l]) {
      case "text/plain":
        hasPlain = true;
        break;
      case "text/html":
        hasHTML = true;
        break;
      default:
        return;
    }
  }
  if (hasHTML || hasPlain && this.saveUndoState) {
    this.saveUndoState();
  }
};

// source/keyboard/Enter.ts
var Enter = (self, event, range) => {
  event.preventDefault();
  self.splitBlock(event.shiftKey, range);
};

// source/keyboard/KeyHelpers.ts
var afterDelete = (self, range) => {
  try {
    if (!range) {
      range = self.getSelection();
    }
    let node = range.startContainer;
    if (node instanceof Text) {
      node = node.parentNode;
    }
    let parent = node;
    while (isInline(parent) && (!parent.textContent || parent.textContent === ZWS)) {
      node = parent;
      parent = node.parentNode;
    }
    if (node !== parent) {
      range.setStart(
        parent,
        Array.from(parent.childNodes).indexOf(node)
      );
      range.collapse(true);
      parent.removeChild(node);
      if (!isBlock(parent)) {
        parent = getPreviousBlock(parent, self._root) || self._root;
      }
      fixCursor(parent);
      moveRangeBoundariesDownTree(range);
    }
    if (node === self._root && (node = node.firstChild) && node.nodeName === "BR") {
      detach(node);
    }
    self._ensureBottomLine();
    self.setSelection(range);
    self._updatePath(range, true);
  } catch (error) {
    self._config.didError(error);
  }
};
var detachUneditableNode = (node, root) => {
  let parent;
  while (parent = node.parentNode) {
    if (parent === root || parent.isContentEditable) {
      break;
    }
    node = parent;
  }
  detach(node);
};
var linkifyText = (self, textNode, offset) => {
  if (getNearest(textNode, self._root, "A")) {
    return;
  }
  const data = textNode.data || "";
  const searchFrom = Math.max(
    data.lastIndexOf(" ", offset - 1),
    data.lastIndexOf("\xA0", offset - 1)
  ) + 1;
  const searchText = data.slice(searchFrom, offset);
  const match = self.linkRegExp.exec(searchText);
  if (match) {
    const selection = self.getSelection();
    self._docWasChanged();
    self._recordUndoState(selection);
    self._getRangeAndRemoveBookmark(selection);
    const index = searchFrom + match.index;
    const endIndex = index + match[0].length;
    const needsSelectionUpdate = selection.startContainer === textNode;
    const newSelectionOffset = selection.startOffset - endIndex;
    if (index) {
      textNode = textNode.splitText(index);
    }
    const defaultAttributes = self._config.tagAttributes.a;
    const link = createElement(
      "A",
      Object.assign(
        {
          href: match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0]
        },
        defaultAttributes
      )
    );
    link.textContent = data.slice(index, endIndex);
    textNode.parentNode.insertBefore(link, textNode);
    textNode.data = data.slice(endIndex);
    if (needsSelectionUpdate) {
      selection.setStart(textNode, newSelectionOffset);
      selection.setEnd(textNode, newSelectionOffset);
    }
    self.setSelection(selection);
  }
};

// source/keyboard/Backspace.ts
var Backspace = (self, event, range) => {
  const root = self._root;
  self._removeZWS();
  self.saveUndoState(range);
  if (!range.collapsed) {
    event.preventDefault();
    deleteContentsOfRange(range, root);
    afterDelete(self, range);
  } else if (rangeDoesStartAtBlockBoundary(range, root)) {
    event.preventDefault();
    const startBlock = getStartBlockOfRange(range, root);
    if (!startBlock) {
      return;
    }
    let current = startBlock;
    fixContainer(current.parentNode, root);
    const previous = getPreviousBlock(current, root);
    if (previous) {
      if (!previous.isContentEditable) {
        detachUneditableNode(previous, root);
        return;
      }
      mergeWithBlock(previous, current, range, root);
      current = previous.parentNode;
      while (current !== root && !current.nextSibling) {
        current = current.parentNode;
      }
      if (current !== root && (current = current.nextSibling)) {
        mergeContainers(current, root);
      }
      self.setSelection(range);
    } else if (current) {
      if (getNearest(current, root, "UL") || getNearest(current, root, "OL")) {
        self.decreaseListLevel(range);
        return;
      } else if (getNearest(current, root, "BLOCKQUOTE")) {
        self.removeQuote(range);
        return;
      }
      self.setSelection(range);
      self._updatePath(range, true);
    }
  } else {
    moveRangeBoundariesDownTree(range);
    const text = range.startContainer;
    const offset = range.startOffset;
    const a = text.parentNode;
    if (text instanceof Text && a instanceof HTMLAnchorElement && offset && a.href.includes(text.data)) {
      text.deleteData(offset - 1, 1);
      self.setSelection(range);
      self.removeLink();
      event.preventDefault();
    } else {
      self.setSelection(range);
      setTimeout(() => {
        afterDelete(self);
      }, 0);
    }
  }
};

// source/keyboard/Delete.ts
var Delete = (self, event, range) => {
  const root = self._root;
  let current;
  let next;
  let originalRange;
  let cursorContainer;
  let cursorOffset;
  let nodeAfterCursor;
  self._removeZWS();
  self.saveUndoState(range);
  if (!range.collapsed) {
    event.preventDefault();
    deleteContentsOfRange(range, root);
    afterDelete(self, range);
  } else if (rangeDoesEndAtBlockBoundary(range, root)) {
    event.preventDefault();
    current = getStartBlockOfRange(range, root);
    if (!current) {
      return;
    }
    fixContainer(current.parentNode, root);
    next = getNextBlock(current, root);
    if (next) {
      if (!next.isContentEditable) {
        detachUneditableNode(next, root);
        return;
      }
      mergeWithBlock(current, next, range, root);
      next = current.parentNode;
      while (next !== root && !next.nextSibling) {
        next = next.parentNode;
      }
      if (next !== root && (next = next.nextSibling)) {
        mergeContainers(next, root);
      }
      self.setSelection(range);
      self._updatePath(range, true);
    }
  } else {
    originalRange = range.cloneRange();
    moveRangeBoundariesUpTree(range, root, root, root);
    cursorContainer = range.endContainer;
    cursorOffset = range.endOffset;
    if (cursorContainer instanceof Element) {
      nodeAfterCursor = cursorContainer.childNodes[cursorOffset];
      if (nodeAfterCursor && nodeAfterCursor.nodeName === "IMG") {
        event.preventDefault();
        detach(nodeAfterCursor);
        moveRangeBoundariesDownTree(range);
        afterDelete(self, range);
        return;
      }
    }
    self.setSelection(originalRange);
    setTimeout(() => {
      afterDelete(self);
    }, 0);
  }
};

// source/keyboard/Tab.ts
var Tab = (self, event, range) => {
  const root = self._root;
  self._removeZWS();
  if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {
    let node = getStartBlockOfRange(range, root);
    let parent;
    while (parent = node.parentNode) {
      if (parent.nodeName === "UL" || parent.nodeName === "OL") {
        event.preventDefault();
        self.increaseListLevel(range);
        break;
      }
      node = parent;
    }
  }
};
var ShiftTab = (self, event, range) => {
  const root = self._root;
  self._removeZWS();
  if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) {
    const node = range.startContainer;
    if (getNearest(node, root, "UL") || getNearest(node, root, "OL")) {
      event.preventDefault();
      self.decreaseListLevel(range);
    }
  }
};

// source/keyboard/Space.ts
var Space = (self, event, range) => {
  let node;
  const root = self._root;
  self._recordUndoState(range);
  self._getRangeAndRemoveBookmark(range);
  if (!range.collapsed) {
    deleteContentsOfRange(range, root);
    self._ensureBottomLine();
    self.setSelection(range);
    self._updatePath(range, true);
  } else if (rangeDoesEndAtBlockBoundary(range, root)) {
    const block = getStartBlockOfRange(range, root);
    if (block && block.nodeName !== "PRE") {
      const text = block.textContent?.trimEnd().replace(ZWS, "");
      if (text === "*" || text === "1.") {
        event.preventDefault();
        self.insertPlainText(" ", false);
        self._docWasChanged();
        self.saveUndoState(range);
        const walker = new TreeIterator(block, SHOW_TEXT);
        let textNode;
        while (textNode = walker.nextNode()) {
          detach(textNode);
        }
        if (text === "*") {
          self.makeUnorderedList();
        } else {
          self.makeOrderedList();
        }
        return;
      }
    }
  }
  node = range.endContainer;
  if (range.endOffset === getLength(node)) {
    do {
      if (node.nodeName === "A") {
        range.setStartAfter(node);
        break;
      }
    } while (!node.nextSibling && (node = node.parentNode) && node !== root);
  }
  if (self._config.addLinks) {
    const linkRange = range.cloneRange();
    moveRangeBoundariesDownTree(linkRange);
    const textNode = linkRange.startContainer;
    const offset = linkRange.startOffset;
    setTimeout(() => {
      linkifyText(self, textNode, offset);
    }, 0);
  }
  self.setSelection(range);
};

// source/keyboard/KeyHandlers.ts
var _onKey = function(event) {
  if (event.defaultPrevented || event.isComposing) {
    return;
  }
  let key = event.key;
  let modifiers = "";
  const code = event.code;
  if (/^Digit\d$/.test(code)) {
    key = code.slice(-1);
  }
  if (key !== "Backspace" && key !== "Delete") {
    if (event.altKey) {
      modifiers += "Alt-";
    }
    if (event.ctrlKey) {
      modifiers += "Ctrl-";
    }
    if (event.metaKey) {
      modifiers += "Meta-";
    }
    if (event.shiftKey) {
      modifiers += "Shift-";
    }
  }
  if (isWin && event.shiftKey && key === "Delete") {
    modifiers += "Shift-";
  }
  key = modifiers + key;
  const range = this.getSelection();
  const handler = this._keyHandlers[key];
  if (handler) {
    handler(this, event, range);
  } else if (!range.collapsed && !event.ctrlKey && !event.metaKey && key.length === 1) {
    this.saveUndoState(range);
    deleteContentsOfRange(range, this._root);
    this._ensureBottomLine();
    this.setSelection(range);
    this._updatePath(range, true);
  }
};
var keyHandlers = {
  "Backspace": Backspace,
  "Delete": Delete,
  "Tab": Tab,
  "Shift-Tab": ShiftTab,
  " ": Space,
  "ArrowLeft"(self) {
    self._removeZWS();
  },
  "ArrowRight"(self, event, range) {
    self._removeZWS();
    const root = self.getRoot();
    if (rangeDoesEndAtBlockBoundary(range, root)) {
      moveRangeBoundariesDownTree(range);
      let node = range.endContainer;
      do {
        if (node.nodeName === "CODE") {
          let next = node.nextSibling;
          if (!(next instanceof Text)) {
            const textNode = document.createTextNode("\xA0");
            node.parentNode.insertBefore(textNode, next);
            next = textNode;
          }
          range.setStart(next, 1);
          self.setSelection(range);
          event.preventDefault();
          break;
        }
      } while (!node.nextSibling && (node = node.parentNode) && node !== root);
    }
  }
};
if (!supportsInputEvents) {
  keyHandlers.Enter = Enter;
  keyHandlers["Shift-Enter"] = Enter;
}
if (!isMac && !isIOS) {
  keyHandlers.PageUp = (self) => {
    self.moveCursorToStart();
  };
  keyHandlers.PageDown = (self) => {
    self.moveCursorToEnd();
  };
}
var mapKeyToFormat = (tag, remove) => {
  remove = remove || null;
  return (self, event) => {
    event.preventDefault();
    const range = self.getSelection();
    if (self.hasFormat(tag, null, range)) {
      self.changeFormat(null, { tag }, range);
    } else {
      self.changeFormat({ tag }, remove, range);
    }
  };
};
keyHandlers[ctrlKey + "b"] = mapKeyToFormat("B");
keyHandlers[ctrlKey + "i"] = mapKeyToFormat("I");
keyHandlers[ctrlKey + "u"] = mapKeyToFormat("U");
keyHandlers[ctrlKey + "Shift-7"] = mapKeyToFormat("S");
keyHandlers[ctrlKey + "Shift-5"] = mapKeyToFormat("SUB", { tag: "SUP" });
keyHandlers[ctrlKey + "Shift-6"] = mapKeyToFormat("SUP", { tag: "SUB" });
keyHandlers[ctrlKey + "Shift-8"] = (self, event) => {
  event.preventDefault();
  const path = self.getPath();
  if (!/(?:^|>)UL/.test(path)) {
    self.makeUnorderedList();
  } else {
    self.removeList();
  }
};
keyHandlers[ctrlKey + "Shift-9"] = (self, event) => {
  event.preventDefault();
  const path = self.getPath();
  if (!/(?:^|>)OL/.test(path)) {
    self.makeOrderedList();
  } else {
    self.removeList();
  }
};
keyHandlers[ctrlKey + "["] = (self, event) => {
  event.preventDefault();
  const path = self.getPath();
  if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {
    self.decreaseQuoteLevel();
  } else {
    self.decreaseListLevel();
  }
};
keyHandlers[ctrlKey + "]"] = (self, event) => {
  event.preventDefault();
  const path = self.getPath();
  if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) {
    self.increaseQuoteLevel();
  } else {
    self.increaseListLevel();
  }
};
keyHandlers[ctrlKey + "d"] = (self, event) => {
  event.preventDefault();
  self.toggleCode();
};
keyHandlers[ctrlKey + "z"] = (self, event) => {
  event.preventDefault();
  self.undo();
};
keyHandlers[ctrlKey + "y"] = // Depending on platform, the Shift may cause the key to come through as
// upper case, but sometimes not. Just add both as shortcuts — the browser
// will only ever fire one or the other.
keyHandlers[ctrlKey + "Shift-z"] = keyHandlers[ctrlKey + "Shift-Z"] = (self, event) => {
  event.preventDefault();
  self.redo();
};

// source/ImageResize.ts
var RESIZE_HANDLE_SIZE = 8;
var MIN_IMAGE_SIZE = 40;
var MAX_IMAGE_SIZE = 1200;
var handlePositions = [
  { pos: "nw", cursor: "nwse-resize" },
  { pos: "n", cursor: "ns-resize" },
  { pos: "ne", cursor: "nesw-resize" },
  { pos: "e", cursor: "ew-resize" },
  { pos: "se", cursor: "nwse-resize" },
  { pos: "s", cursor: "ns-resize" },
  { pos: "sw", cursor: "nesw-resize" },
  { pos: "w", cursor: "ew-resize" }
];
var keyHandlers2 = {
  ArrowUp: (editor, range, root) => {
    const block = getStartBlockOfRange(range, root);
    if (block) {
      const prev = getPreviousBlock(block, root);
      if (prev) {
        range.selectNodeContents(prev);
        range.collapse(false);
        editor.setSelection(range).focus();
      }
    }
  },
  ArrowDown: (editor, range, root) => {
    const block = getStartBlockOfRange(range, root);
    if (block) {
      const next = getNextBlock(block, root);
      if (next) {
        range.selectNodeContents(next);
        range.collapse(true);
        editor.setSelection(range).focus();
      }
    }
  },
  Delete: (editor, range) => {
    editor.replaceWithBlankLine(range);
  },
  Backspace: (editor, range) => {
    editor.replaceWithBlankLine(range);
  }
};
var ImageResizer = class {
  constructor(root, editor) {
    this._currentImage = null;
    this._resizeContainer = null;
    this._handles = null;
    this._currentHandle = null;
    this._startX = 0;
    this._startY = 0;
    this._startWidth = 0;
    this._startHeight = 0;
    this._maxWidth = MAX_IMAGE_SIZE;
    this._originalRatio = 1;
    this._editor = editor;
    this._root = root;
    document.addEventListener("click", this);
    editor.addEventListener("drop", this);
  }
  destroy() {
    this._deselectImage();
    this._editor.removeEventListener("drop", this);
    document.removeEventListener("click", this);
  }
  // EventListener interface implementation
  handleEvent(event) {
    switch (event.type) {
      case "click":
        this._onClick(event);
        break;
      case "pointerdown":
        this._onPointerDown(event);
        break;
      case "pointermove":
        this._onPointerMove(event);
        break;
      case "pointercancel":
      case "pointerup":
        this._onPointerUp(event);
        break;
      case "keydown":
        this._onKeyDown(event);
        break;
      case "drop":
        this._deselectImage();
        break;
    }
  }
  // ---
  _onClick(event) {
    const target = event.target;
    if (target.nodeName === "IMG" && this._root.contains(target)) {
      event.stopPropagation();
      this._selectImage(target);
    } else if (this._currentImage && this._handles && !this._handles.some((handle) => handle.element === target)) {
      this._deselectImage();
    }
  }
  _deselectImage() {
    if (!this._currentImage) {
      return;
    }
    document.removeEventListener("keydown", this);
    if (this._currentHandle) {
      this._onPointerUp({
        preventDefault() {
        },
        target: this._currentHandle.element
      });
    }
    if (this._handles) {
      this._handles.forEach(
        ({ element }) => element.removeEventListener("pointerdown", this)
      );
    }
    if (this._resizeContainer) {
      this._resizeContainer.remove();
    }
    this._handles = null;
    this._resizeContainer = null;
    this._currentImage = null;
  }
  _selectImage(image) {
    if (this._currentImage === image) {
      return;
    }
    this._deselectImage();
    this._root.blur();
    const handles = handlePositions.map(({ pos, cursor }) => {
      const offset = RESIZE_HANDLE_SIZE / 2;
      let positionStyle = "";
      switch (pos) {
        case "nw":
          positionStyle = `left: -${offset}px; top: -${offset}px;`;
          break;
        case "n":
          positionStyle = `left: calc(50% - ${offset}px); top: -${offset}px;`;
          break;
        case "ne":
          positionStyle = `right: -${offset}px; top: -${offset}px;`;
          break;
        case "e":
          positionStyle = `right: -${offset}px; top: calc(50% - ${offset}px);`;
          break;
        case "se":
          positionStyle = `right: -${offset}px; bottom: -${offset}px;`;
          break;
        case "s":
          positionStyle = `left: calc(50% - ${offset}px); bottom: -${offset}px;`;
          break;
        case "sw":
          positionStyle = `left: -${offset}px; bottom: -${offset}px;`;
          break;
        case "w":
          positionStyle = `left: -${offset}px; top: calc(50% - ${offset}px);`;
          break;
      }
      const handle = createElement("div", {
        class: `squire-resize-handle squire-resize-handle-${pos}`,
        style: `
                    position: absolute;
                    width: ${RESIZE_HANDLE_SIZE}px;
                    height: ${RESIZE_HANDLE_SIZE}px;
                    background: #0067b9;
                    border: 1px solid #fff;
                    cursor: ${cursor};
                    pointer-events: auto;
                    touch-action: none;
                    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
                    ${positionStyle}
                `
      });
      handle.addEventListener("pointerdown", this);
      return {
        element: handle,
        cursor,
        position: pos
      };
    });
    const resizeContainer = createElement(
      "div",
      {
        class: "squire-image-resize-container",
        style: "position: absolute; pointer-events: none; z-index: 1000;"
      },
      handles.map((handle) => handle.element)
    );
    this._currentImage = image;
    this._resizeContainer = resizeContainer;
    this._handles = handles;
    this._root.appendChild(this._resizeContainer);
    const naturalWidth = image.naturalWidth;
    this._originalRatio = naturalWidth / image.naturalHeight;
    this._maxWidth = Math.min(
      naturalWidth * 2,
      image.parentElement ? image.parentElement.offsetWidth : MAX_IMAGE_SIZE,
      MAX_IMAGE_SIZE
    );
    this._positionResizeContainer();
    document.addEventListener("keydown", this);
  }
  _positionResizeContainer() {
    const resizeContainer = this._resizeContainer;
    const root = this._root;
    const currentImage = this._currentImage;
    if (!resizeContainer || !currentImage) {
      return;
    }
    const rootRect = root.getBoundingClientRect();
    const imageRect = currentImage.getBoundingClientRect();
    const top = imageRect.top - rootRect.top + root.scrollTop;
    const left = imageRect.left - rootRect.left + root.scrollLeft;
    const width = imageRect.width;
    const height = imageRect.height;
    resizeContainer.style.top = top + "px";
    resizeContainer.style.left = left + "px";
    resizeContainer.style.width = width + "px";
    resizeContainer.style.height = height + "px";
  }
  _onPointerDown(event) {
    if (this._currentHandle || !this._handles) {
      return;
    }
    const target = event.target;
    const currentHandle = this._handles.find((h) => h.element === target) || null;
    if (!currentHandle) {
      return;
    }
    const currentImage = this._currentImage;
    if (!currentImage) {
      return;
    }
    event.preventDefault();
    event.stopPropagation();
    target.addEventListener("pointermove", this);
    target.addEventListener("pointerup", this);
    target.addEventListener("pointercancel", this);
    target.setPointerCapture(event.pointerId);
    this._currentHandle = currentHandle;
    this._startX = event.clientX;
    this._startY = event.clientY;
    const style = getComputedStyle(currentImage);
    this._startWidth = parseFloat(style.width);
    this._startHeight = parseFloat(style.height);
    document.body.style.cursor = currentHandle.cursor;
  }
  _onPointerMove(event) {
    event.preventDefault();
    const currentHandle = this._currentHandle;
    const currentImage = this._currentImage;
    if (!currentHandle || !currentImage) {
      return;
    }
    const deltaX = event.clientX - this._startX;
    const deltaY = event.clientY - this._startY;
    const maxWidth = this._maxWidth;
    const originalRatio = this._originalRatio;
    let newWidth = this._startWidth;
    let newHeight = this._startHeight;
    switch (currentHandle.position) {
      case "sw":
      case "nw":
      case "w":
        newWidth -= 2 * deltaX;
        break;
      case "se":
      case "ne":
      case "e":
        newWidth += 2 * deltaX;
        break;
      case "n":
        newHeight -= deltaY;
        newWidth = newHeight * originalRatio;
        break;
      case "s":
        newHeight += deltaY;
        newWidth = newHeight * originalRatio;
        break;
    }
    if (newWidth < MIN_IMAGE_SIZE) {
      newWidth = MIN_IMAGE_SIZE;
    } else if (newWidth > maxWidth) {
      newWidth = maxWidth;
    }
    const currentImageStyle = currentImage.style;
    currentImageStyle.width = newWidth + "px";
    currentImageStyle.height = "auto";
    this._positionResizeContainer();
  }
  _onPointerUp(event) {
    event.preventDefault();
    const target = event.target;
    if (target) {
      target.removeEventListener("pointermove", this);
      target.removeEventListener("pointerup", this);
      target.removeEventListener("pointercancel", this);
    }
    this._currentHandle = null;
    document.body.style.cursor = "";
  }
  _onKeyDown(event) {
    event.preventDefault();
    event.stopPropagation();
    const keyHandler = keyHandlers2[event.key];
    if (!keyHandler) {
      return;
    }
    const image = this._currentImage;
    if (!image) {
      return;
    }
    const editor = this._editor;
    const root = this._root;
    this._deselectImage();
    const range = editor.getSelection();
    range.selectNode(image);
    keyHandler(editor, range, root);
  }
};

// source/Editor.ts
var Squire = class {
  constructor(root, config) {
    /**
     * Subscribing to these events won't automatically add a listener to the
     * document node, since these events are fired in a custom manner by the
     * editor code.
     */
    this.customEvents = /* @__PURE__ */ new Set([
      "pathChange",
      "select",
      "input",
      "pasteImage",
      "undoStateChange"
    ]);
    // ---
    this.startSelectionId = "squire-selection-start";
    this.endSelectionId = "squire-selection-end";
    /*
    linkRegExp = new RegExp(
        // Only look on boundaries
        '\\b(?:' +
        // Capture group 1: URLs
        '(' +
            // Add links to URLS
            // Starts with:
            '(?:' +
                // http(s):// or ftp://
                '(?:ht|f)tps?:\\/\\/' +
                // or
                '|' +
                // www.
                'www\\d{0,3}[.]' +
                // or
                '|' +
                // foo90.com/
                '[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/' +
            ')' +
            // Then we get one or more:
            '(?:' +
                // Run of non-spaces, non ()<>
                '[^\\s()<>]+' +
                // or
                '|' +
                // balanced parentheses (one level deep only)
                '\\([^\\s()<>]+\\)' +
            ')+' +
            // And we finish with
            '(?:' +
                // Not a space or punctuation character
                '[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]' +
                // or
                '|' +
                // Balanced parentheses.
                '\\([^\\s()<>]+\\)' +
            ')' +
        // Capture group 2: Emails
        ')|(' +
            // Add links to emails
            '[\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b' +
            // Allow query parameters in the mailto: style
            '(?:' +
                '[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]+' +
                '(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]+)*' +
            ')?' +
        '))',
        'i'
    );
    */
    this.linkRegExp = /\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i;
    this.tagAfterSplit = {
      DT: "DD",
      DD: "DT",
      LI: "LI",
      PRE: "PRE"
    };
    this._root = root;
    this._config = this._makeConfig(config);
    this._isFocused = false;
    this._lastSelection = createRange(root, 0);
    this._willRestoreSelection = false;
    this._mayHaveZWS = false;
    this._lastAnchorNode = null;
    this._lastFocusNode = null;
    this._path = "";
    this._events = /* @__PURE__ */ new Map();
    this._undoIndex = -1;
    this._undoStack = [];
    this._undoStackLength = 0;
    this._isInUndoState = false;
    this._ignoreChange = false;
    this._ignoreAllChanges = false;
    this.addEventListener("selectionchange", this._updatePathOnEvent);
    this.addEventListener("blur", this._enableRestoreSelection);
    this.addEventListener("mousedown", this._disableRestoreSelection);
    this.addEventListener("touchstart", this._disableRestoreSelection);
    this.addEventListener("focus", this._restoreSelection);
    this.addEventListener("blur", this._removeZWS);
    this._isShiftDown = false;
    this.addEventListener("cut", _onCut);
    this.addEventListener("copy", _onCopy);
    this.addEventListener("paste", _onPaste);
    this.addEventListener("drop", _onDrop);
    this.addEventListener(
      "keydown",
      _monitorShiftKey
    );
    this.addEventListener("keyup", _monitorShiftKey);
    this.addEventListener("keydown", _onKey);
    this._keyHandlers = Object.create(keyHandlers);
    const mutation = new MutationObserver(() => this._docWasChanged());
    mutation.observe(root, {
      childList: true,
      attributes: true,
      characterData: true,
      subtree: true
    });
    this._mutation = mutation;
    root.setAttribute("contenteditable", "true");
    this.addEventListener(
      "beforeinput",
      this._beforeInput
    );
    this._imageResizer = new ImageResizer(root, this);
    this.setHTML("");
  }
  destroy() {
    this._events.forEach((_, type) => {
      this.removeEventListener(type);
    });
    this._mutation.disconnect();
    this._imageResizer.destroy();
    this._undoIndex = -1;
    this._undoStack = [];
    this._undoStackLength = 0;
  }
  _makeConfig(userConfig) {
    const config = {
      blockTag: "DIV",
      blockAttributes: null,
      tagAttributes: {},
      classNames: {
        color: "color",
        fontFamily: "font",
        fontSize: "size",
        highlight: "highlight"
      },
      undo: {
        documentSizeThreshold: -1,
        // -1 means no threshold
        undoLimit: -1
        // -1 means no limit
      },
      addLinks: true,
      willCutCopy: null,
      toPlainText: null,
      sanitizeToDOMFragment: (html) => {
        const frag = DOMPurify.sanitize(html, {
          ALLOW_UNKNOWN_PROTOCOLS: true,
          WHOLE_DOCUMENT: false,
          RETURN_DOM: true,
          RETURN_DOM_FRAGMENT: true,
          FORCE_BODY: false
        });
        return frag ? document.importNode(frag, true) : document.createDocumentFragment();
      },
      didError: (error) => console.log(error)
    };
    if (userConfig) {
      Object.assign(config, userConfig);
      config.blockTag = config.blockTag.toUpperCase();
    }
    return config;
  }
  setKeyHandler(key, fn) {
    this._keyHandlers[key] = fn;
    return this;
  }
  _beforeInput(event) {
    switch (event.inputType) {
      case "insertLineBreak":
        event.preventDefault();
        this.splitBlock(true);
        break;
      case "insertParagraph":
        event.preventDefault();
        this.splitBlock(false);
        break;
      case "insertOrderedList":
        event.preventDefault();
        this.makeOrderedList();
        break;
      case "insertUnoderedList":
        event.preventDefault();
        this.makeUnorderedList();
        break;
      case "historyUndo":
        event.preventDefault();
        this.undo();
        break;
      case "historyRedo":
        event.preventDefault();
        this.redo();
        break;
      case "formatBold":
        event.preventDefault();
        this.bold();
        break;
      case "formatItalic":
        event.preventDefault();
        this.italic();
        break;
      case "formatUnderline":
        event.preventDefault();
        this.underline();
        break;
      case "formatStrikeThrough":
        event.preventDefault();
        this.strikethrough();
        break;
      case "formatSuperscript":
        event.preventDefault();
        this.superscript();
        break;
      case "formatSubscript":
        event.preventDefault();
        this.subscript();
        break;
      case "formatJustifyFull":
      case "formatJustifyCenter":
      case "formatJustifyRight":
      case "formatJustifyLeft": {
        event.preventDefault();
        let alignment = event.inputType.slice(13).toLowerCase();
        if (alignment === "full") {
          alignment = "justify";
        }
        this.setTextAlignment(alignment);
        break;
      }
      case "formatRemove":
        event.preventDefault();
        this.removeAllFormatting();
        break;
      case "formatSetBlockTextDirection": {
        event.preventDefault();
        let dir = event.data;
        if (dir === "null") {
          dir = null;
        }
        this.setTextDirection(dir);
        break;
      }
      case "formatBackColor":
        event.preventDefault();
        this.setHighlightColor(event.data);
        break;
      case "formatFontColor":
        event.preventDefault();
        this.setTextColor(event.data);
        break;
      case "formatFontName":
        event.preventDefault();
        this.setFontFace(event.data);
        break;
    }
  }
  // --- Events
  handleEvent(event) {
    this.fireEvent(event.type, event);
  }
  fireEvent(type, detail) {
    let handlers = this._events.get(type);
    if (/^(?:focus|blur)/.test(type)) {
      const isFocused = this._root === document.activeElement;
      if (type === "focus") {
        if (!isFocused || this._isFocused) {
          return this;
        }
        this._isFocused = true;
      } else {
        if (isFocused || !this._isFocused) {
          return this;
        }
        this._isFocused = false;
      }
    }
    if (handlers) {
      const event = detail instanceof Event ? detail : new CustomEvent(type, {
        detail
      });
      handlers = handlers.slice();
      for (const handler of handlers) {
        try {
          if ("handleEvent" in handler) {
            handler.handleEvent(event);
          } else {
            handler.call(this, event);
          }
        } catch (error) {
          this._config.didError(error);
        }
      }
    }
    return this;
  }
  addEventListener(type, fn) {
    let handlers = this._events.get(type);
    let target = this._root;
    if (!handlers) {
      handlers = [];
      this._events.set(type, handlers);
      if (!this.customEvents.has(type)) {
        if (type === "selectionchange") {
          target = document;
        }
        target.addEventListener(type, this, true);
      }
    }
    handlers.push(fn);
    return this;
  }
  removeEventListener(type, fn) {
    const handlers = this._events.get(type);
    let target = this._root;
    if (handlers) {
      if (fn) {
        let l = handlers.length;
        while (l--) {
          if (handlers[l] === fn) {
            handlers.splice(l, 1);
          }
        }
      } else {
        handlers.length = 0;
      }
      if (!handlers.length) {
        this._events.delete(type);
        if (!this.customEvents.has(type)) {
          if (type === "selectionchange") {
            target = document;
          }
          target.removeEventListener(type, this, true);
        }
      }
    }
    return this;
  }
  // --- Focus
  focus() {
    this._root.focus({ preventScroll: true });
    return this;
  }
  blur() {
    this._root.blur();
    return this;
  }
  // --- Selection and bookmarking
  _enableRestoreSelection() {
    this._willRestoreSelection = true;
  }
  _disableRestoreSelection() {
    this._willRestoreSelection = false;
  }
  _restoreSelection() {
    if (this._willRestoreSelection) {
      this.setSelection(this._lastSelection);
    }
  }
  // ---
  _removeZWS() {
    if (!this._mayHaveZWS) {
      return;
    }
    removeZWS(this._root);
    this._mayHaveZWS = false;
  }
  _saveRangeToBookmark(range) {
    let startNode = createElement("INPUT", {
      id: this.startSelectionId,
      type: "hidden"
    });
    let endNode = createElement("INPUT", {
      id: this.endSelectionId,
      type: "hidden"
    });
    let temp;
    insertNodeInRange(range, startNode);
    range.collapse(false);
    insertNodeInRange(range, endNode);
    if (startNode.compareDocumentPosition(endNode) & Node.DOCUMENT_POSITION_PRECEDING) {
      startNode.id = this.endSelectionId;
      endNode.id = this.startSelectionId;
      temp = startNode;
      startNode = endNode;
      endNode = temp;
    }
    range.setStartAfter(startNode);
    range.setEndBefore(endNode);
  }
  _getRangeAndRemoveBookmark(range) {
    const root = this._root;
    const start = root.querySelector("#" + this.startSelectionId);
    const end = root.querySelector("#" + this.endSelectionId);
    if (start && end) {
      let startContainer = start.parentNode;
      let endContainer = end.parentNode;
      const startOffset = Array.from(startContainer.childNodes).indexOf(
        start
      );
      let endOffset = Array.from(endContainer.childNodes).indexOf(end);
      if (startContainer === endContainer) {
        endOffset -= 1;
      }
      start.remove();
      end.remove();
      if (!range) {
        range = document.createRange();
      }
      range.setStart(startContainer, startOffset);
      range.setEnd(endContainer, endOffset);
      mergeInlines(startContainer, range);
      if (startContainer !== endContainer) {
        mergeInlines(endContainer, range);
      }
      if (range.collapsed) {
        startContainer = range.startContainer;
        if (startContainer instanceof Text) {
          endContainer = startContainer.childNodes[range.startOffset];
          if (!endContainer || !(endContainer instanceof Text)) {
            endContainer = startContainer.childNodes[range.startOffset - 1];
          }
          if (endContainer && endContainer instanceof Text) {
            range.setStart(endContainer, 0);
            range.collapse(true);
          }
        }
      }
    }
    return range || null;
  }
  getSelection() {
    const selection = window.getSelection();
    const root = this._root;
    let range = null;
    if (this._isFocused && selection && selection.rangeCount) {
      range = selection.getRangeAt(0).cloneRange();
      const startContainer = range.startContainer;
      const endContainer = range.endContainer;
      if (startContainer && isLeaf(startContainer)) {
        range.setStartBefore(startContainer);
      }
      if (endContainer && isLeaf(endContainer)) {
        range.setEndBefore(endContainer);
      }
    }
    if (range && root.contains(range.commonAncestorContainer)) {
      this._lastSelection = range;
    } else {
      range = this._lastSelection;
      if (!document.contains(range.commonAncestorContainer)) {
        range = null;
      }
    }
    if (!range) {
      range = createRange(root.firstElementChild || root, 0);
    }
    return range;
  }
  setSelection(range) {
    this._lastSelection = range;
    if (!this._isFocused) {
      this._enableRestoreSelection();
    } else {
      const selection = window.getSelection();
      if (selection) {
        if ("setBaseAndExtent" in Selection.prototype) {
          selection.setBaseAndExtent(
            range.startContainer,
            range.startOffset,
            range.endContainer,
            range.endOffset
          );
        } else {
          selection.removeAllRanges();
          selection.addRange(range);
        }
      }
    }
    return this;
  }
  // ---
  _moveCursorTo(toStart) {
    const root = this._root;
    const range = createRange(root, toStart ? 0 : root.childNodes.length);
    moveRangeBoundariesDownTree(range);
    this.setSelection(range);
    return this;
  }
  moveCursorToStart() {
    return this._moveCursorTo(true);
  }
  moveCursorToEnd() {
    return this._moveCursorTo(false);
  }
  // ---
  getCursorPosition() {
    const range = this.getSelection();
    let rect = range.getBoundingClientRect();
    if (rect && !rect.top) {
      this._ignoreChange = true;
      const node = createElement("SPAN");
      node.textContent = ZWS;
      insertNodeInRange(range, node);
      rect = node.getBoundingClientRect();
      const parent = node.parentNode;
      parent.removeChild(node);
      mergeInlines(parent, range);
    }
    return rect;
  }
  // --- Path
  getPath() {
    return this._path;
  }
  _updatePathOnEvent() {
    if (this._isFocused) {
      this._updatePath(this.getSelection());
    }
  }
  _updatePath(range, force) {
    const anchor = range.startContainer;
    const focus = range.endContainer;
    let newPath;
    if (force || anchor !== this._lastAnchorNode || focus !== this._lastFocusNode) {
      this._lastAnchorNode = anchor;
      this._lastFocusNode = focus;
      newPath = anchor && focus ? anchor === focus ? this._getPath(focus) : "(selection)" : "";
      if (this._path !== newPath || anchor !== focus) {
        this._path = newPath;
        this.fireEvent("pathChange", {
          path: newPath
        });
      }
    }
    this.fireEvent(range.collapsed ? "cursor" : "select", {
      range
    });
  }
  _getPath(node) {
    const root = this._root;
    const config = this._config;
    let path = "";
    if (node && node !== root) {
      const parent = node.parentNode;
      path = parent ? this._getPath(parent) : "";
      if (node instanceof HTMLElement) {
        const id = node.id;
        const classList = node.classList;
        const classNames = Array.from(classList).sort();
        const dir = node.dir;
        const styleNames = config.classNames;
        path += (path ? ">" : "") + node.nodeName;
        if (id) {
          path += "#" + id;
        }
        if (classNames.length) {
          path += ".";
          path += classNames.join(".");
        }
        if (dir) {
          path += "[dir=" + dir + "]";
        }
        if (classList.contains(styleNames.highlight)) {
          path += "[backgroundColor=" + node.style.backgroundColor.replace(/ /g, "") + "]";
        }
        if (classList.contains(styleNames.color)) {
          path += "[color=" + node.style.color.replace(/ /g, "") + "]";
        }
        if (classList.contains(styleNames.fontFamily)) {
          path += "[fontFamily=" + node.style.fontFamily.replace(/ /g, "") + "]";
        }
        if (classList.contains(styleNames.fontSize)) {
          path += "[fontSize=" + node.style.fontSize + "]";
        }
      }
    }
    return path;
  }
  // --- History
  modifyDocument(modificationFn) {
    const mutation = this._mutation;
    if (mutation) {
      if (mutation.takeRecords().length) {
        this._docWasChanged();
      }
      mutation.disconnect();
    }
    this._ignoreAllChanges = true;
    modificationFn();
    this._ignoreAllChanges = false;
    if (mutation) {
      mutation.observe(this._root, {
        childList: true,
        attributes: true,
        characterData: true,
        subtree: true
      });
      this._ignoreChange = false;
    }
    return this;
  }
  _docWasChanged() {
    resetNodeCategoryCache();
    this._mayHaveZWS = true;
    if (this._ignoreAllChanges) {
      return;
    }
    if (this._ignoreChange) {
      this._ignoreChange = false;
      return;
    }
    if (this._isInUndoState) {
      this._isInUndoState = false;
      this.fireEvent("undoStateChange", {
        canUndo: true,
        canRedo: false
      });
    }
    this.fireEvent("input");
  }
  /**
   * Leaves bookmark.
   */
  _recordUndoState(range, replace) {
    const isInUndoState = this._isInUndoState;
    if (!isInUndoState || replace) {
      let undoIndex = this._undoIndex + 1;
      const undoStack = this._undoStack;
      const undoConfig = this._config.undo;
      const undoThreshold = undoConfig.documentSizeThreshold;
      const undoLimit = undoConfig.undoLimit;
      if (undoIndex < this._undoStackLength) {
        undoStack.length = this._undoStackLength = undoIndex;
      }
      if (range) {
        this._saveRangeToBookmark(range);
      }
      if (isInUndoState) {
        return this;
      }
      const html = this._getRawHTML();
      if (replace) {
        undoIndex -= 1;
      }
      if (undoThreshold > -1 && html.length * 2 > undoThreshold) {
        if (undoLimit > -1 && undoIndex > undoLimit) {
          undoStack.splice(0, undoIndex - undoLimit);
          undoIndex = undoLimit;
          this._undoStackLength = undoLimit;
        }
      }
      undoStack[undoIndex] = html;
      this._undoIndex = undoIndex;
      this._undoStackLength += 1;
      this._isInUndoState = true;
    }
    return this;
  }
  saveUndoState(range) {
    let rangeIsFromSelection = false;
    if (!range) {
      range = this.getSelection();
      rangeIsFromSelection = true;
    }
    this._recordUndoState(range, this._isInUndoState);
    this._getRangeAndRemoveBookmark(range);
    if (rangeIsFromSelection) {
      this.setSelection(range);
    }
    return this;
  }
  undo() {
    if (this._undoIndex !== 0 || !this._isInUndoState) {
      this._recordUndoState(this.getSelection(), false);
      this._undoIndex -= 1;
      this._setRawHTML(this._undoStack[this._undoIndex]);
      const range = this._getRangeAndRemoveBookmark();
      if (range) {
        this.setSelection(range);
      }
      this._isInUndoState = true;
      this.fireEvent("undoStateChange", {
        canUndo: this._undoIndex !== 0,
        canRedo: true
      });
      this.fireEvent("input");
    }
    return this.focus();
  }
  redo() {
    const undoIndex = this._undoIndex;
    const undoStackLength = this._undoStackLength;
    if (undoIndex + 1 < undoStackLength && this._isInUndoState) {
      this._undoIndex += 1;
      this._setRawHTML(this._undoStack[this._undoIndex]);
      const range = this._getRangeAndRemoveBookmark();
      if (range) {
        this.setSelection(range);
      }
      this.fireEvent("undoStateChange", {
        canUndo: true,
        canRedo: undoIndex + 2 < undoStackLength
      });
      this.fireEvent("input");
    }
    return this.focus();
  }
  // --- Get and set data
  getRoot() {
    return this._root;
  }
  _getRawHTML() {
    return this._root.innerHTML;
  }
  _setRawHTML(html) {
    const root = this._root;
    root.innerHTML = html;
    let node = root;
    const child = node.firstChild;
    if (!child || child.nodeName === "BR") {
      const block = this.createDefaultBlock();
      if (child) {
        node.replaceChild(block, child);
      } else {
        node.appendChild(block);
      }
    } else {
      while (node = getNextBlock(node, root)) {
        fixCursor(node);
      }
    }
    this._ignoreChange = true;
    return this;
  }
  getHTML(withBookmark) {
    let range;
    let html = "";
    this.modifyDocument(() => {
      if (withBookmark) {
        range = this.getSelection();
        this._saveRangeToBookmark(range);
      }
      const resizeContainer = this._root.querySelector(
        ".squire-image-resize-container"
      );
      if (resizeContainer) {
        resizeContainer.remove();
      }
      html = this._getRawHTML().replace(/\u200B/g, "");
      if (resizeContainer) {
        this._root.appendChild(resizeContainer);
      }
      if (withBookmark) {
        this._getRangeAndRemoveBookmark(range);
      }
    });
    return html;
  }
  setHTML(html) {
    const frag = this._config.sanitizeToDOMFragment(html, this);
    const root = this._root;
    cleanTree(frag, this._config);
    cleanupBRs(frag, root, false);
    fixContainer(frag, root);
    let node = frag;
    let child = node.firstChild;
    if (!child || child.nodeName === "BR") {
      const block = this.createDefaultBlock();
      if (child) {
        node.replaceChild(block, child);
      } else {
        node.appendChild(block);
      }
    } else {
      while (node = getNextBlock(node, root)) {
        fixCursor(node);
      }
    }
    this._ignoreChange = true;
    while (child = root.lastChild) {
      root.removeChild(child);
    }
    root.appendChild(frag);
    this._undoIndex = -1;
    this._undoStack.length = 0;
    this._undoStackLength = 0;
    this._isInUndoState = false;
    const range = this._getRangeAndRemoveBookmark() || createRange(root.firstElementChild || root, 0);
    this.saveUndoState(range);
    this.setSelection(range);
    this._updatePath(range, true);
    return this;
  }
  /**
   * Insert HTML at the cursor location. If the selection is not collapsed
   * insertTreeFragmentIntoRange will delete the selection so that it is
   * replaced by the html being inserted.
   */
  insertHTML(html, isPaste) {
    const config = this._config;
    let frag = config.sanitizeToDOMFragment(html, this);
    const range = this.getSelection();
    this.saveUndoState(range);
    try {
      const root = this._root;
      if (config.addLinks) {
        this.addDetectedLinks(frag, frag);
      }
      cleanTree(frag, this._config);
      cleanupBRs(frag, root, false);
      removeEmptyInlines(frag);
      frag.normalize();
      let node = frag;
      while (node = getNextBlock(node, frag)) {
        fixCursor(node);
      }
      let doInsert = true;
      if (isPaste) {
        const event = new CustomEvent("willPaste", {
          cancelable: true,
          detail: {
            html,
            fragment: frag
          }
        });
        this.fireEvent("willPaste", event);
        frag = event.detail.fragment;
        doInsert = !event.defaultPrevented;
      }
      if (doInsert) {
        insertTreeFragmentIntoRange(range, frag, root);
        range.collapse(false);
        moveRangeBoundaryOutOf(range, "A", root);
        this._ensureBottomLine();
      }
      this.setSelection(range);
      this._updatePath(range, true);
      if (isPaste) {
        this.focus();
      }
    } catch (error) {
      this._config.didError(error);
    }
    return this;
  }
  insertElement(el, range) {
    if (!range) {
      range = this.getSelection();
    }
    range.collapse(true);
    if (isInline(el)) {
      insertNodeInRange(range, el);
      range.setStartAfter(el);
    } else {
      const root = this._root;
      const startNode = getStartBlockOfRange(
        range,
        root
      );
      let splitNode = startNode || root;
      let nodeAfterSplit = null;
      while (splitNode !== root && !splitNode.nextSibling) {
        splitNode = splitNode.parentNode;
      }
      if (splitNode !== root) {
        const parent = splitNode.parentNode;
        nodeAfterSplit = split(
          parent,
          splitNode.nextSibling,
          root,
          root
        );
      }
      if (startNode && isEmptyBlock(startNode)) {
        detach(startNode);
      }
      root.insertBefore(el, nodeAfterSplit);
      const blankLine = this.createDefaultBlock();
      root.insertBefore(blankLine, nodeAfterSplit);
      range.setStart(blankLine, 0);
      range.setEnd(blankLine, 0);
      moveRangeBoundariesDownTree(range);
    }
    this.focus();
    this.setSelection(range);
    this._updatePath(range);
    return this;
  }
  insertImage(src, attributes) {
    const img = createElement(
      "IMG",
      Object.assign(
        {
          src
        },
        attributes
      )
    );
    this.insertElement(img);
    return img;
  }
  insertPlainText(plainText, isPaste) {
    const range = this.getSelection();
    if (range.collapsed && getNearest(range.startContainer, this._root, "PRE")) {
      const startContainer = range.startContainer;
      let offset = range.startOffset;
      let textNode;
      if (!startContainer || !(startContainer instanceof Text)) {
        const text = document.createTextNode("");
        startContainer.insertBefore(
          text,
          startContainer.childNodes[offset]
        );
        textNode = text;
        offset = 0;
      } else {
        textNode = startContainer;
      }
      let doInsert = true;
      if (isPaste) {
        const event = new CustomEvent("willPaste", {
          cancelable: true,
          detail: {
            text: plainText
          }
        });
        this.fireEvent("willPaste", event);
        plainText = event.detail.text;
        doInsert = !event.defaultPrevented;
      }
      if (doInsert) {
        textNode.insertData(offset, plainText);
        range.setStart(textNode, offset + plainText.length);
        range.collapse(true);
      }
      this.setSelection(range);
      return this;
    }
    const lines = plainText.split("\n");
    const config = this._config;
    const tag = config.blockTag;
    const attributes = config.blockAttributes;
    const closeBlock = "</" + tag + ">";
    let openBlock = "<" + tag;
    for (const attr in attributes) {
      openBlock += " " + attr + '="' + escapeHTML(attributes[attr]) + '"';
    }
    openBlock += ">";
    for (let i = 0, l = lines.length; i < l; i += 1) {
      let line = lines[i];
      line = escapeHTML(line).replace(/ (?=(?: |$))/g, "&nbsp;");
      if (i) {
        line = openBlock + (line || "<BR>") + closeBlock;
      }
      lines[i] = line;
    }
    return this.insertHTML(lines.join(""), isPaste);
  }
  getSelectedText(range) {
    return getTextContentsOfRange(range || this.getSelection());
  }
  // --- Inline formatting
  /**
   * Extracts the font-family and font-size (if any) of the element
   * holding the cursor. If there's a selection, returns an empty object.
   */
  getFontInfo(range) {
    const fontInfo = {
      color: void 0,
      backgroundColor: void 0,
      fontFamily: void 0,
      fontSize: void 0
    };
    if (!range) {
      range = this.getSelection();
    }
    moveRangeBoundariesDownTree(range);
    let seenAttributes = 0;
    let element = range.commonAncestorContainer;
    if (range.collapsed || element instanceof Text) {
      if (element instanceof Text) {
        element = element.parentNode;
      }
      while (seenAttributes < 4 && element) {
        const style = element.style;
        if (style) {
          const color = style.color;
          if (!fontInfo.color && color) {
            fontInfo.color = color;
            seenAttributes += 1;
          }
          const backgroundColor = style.backgroundColor;
          if (!fontInfo.backgroundColor && backgroundColor) {
            fontInfo.backgroundColor = backgroundColor;
            seenAttributes += 1;
          }
          const fontFamily = style.fontFamily;
          if (!fontInfo.fontFamily && fontFamily) {
            fontInfo.fontFamily = fontFamily;
            seenAttributes += 1;
          }
          const fontSize = style.fontSize;
          if (!fontInfo.fontSize && fontSize) {
            fontInfo.fontSize = fontSize;
            seenAttributes += 1;
          }
        }
        element = element.parentNode;
      }
    }
    return fontInfo;
  }
  /**
   * Looks for matching tag and attributes, so won't work if <strong>
   * instead of <b> etc.
   */
  hasFormat(tag, attributes, range) {
    tag = tag.toUpperCase();
    if (!attributes) {
      attributes = {};
    }
    if (!range) {
      range = this.getSelection();
    }
    if (!range.collapsed && range.startContainer instanceof Text && range.startOffset === range.startContainer.length && range.startContainer.nextSibling) {
      range.setStartBefore(range.startContainer.nextSibling);
    }
    if (!range.collapsed && range.endContainer instanceof Text && range.endOffset === 0 && range.endContainer.previousSibling) {
      range.setEndAfter(range.endContainer.previousSibling);
    }
    const root = this._root;
    const common = range.commonAncestorContainer;
    if (getNearest(common, root, tag, attributes)) {
      return true;
    }
    if (common instanceof Text) {
      return false;
    }
    const walker = new TreeIterator(common, SHOW_TEXT, (node2) => {
      return isNodeContainedInRange(range, node2, true);
    });
    let seenNode = false;
    let node;
    while (node = walker.nextNode()) {
      if (!getNearest(node, root, tag, attributes)) {
        return false;
      }
      seenNode = true;
    }
    return seenNode;
  }
  changeFormat(add, remove, range, partial) {
    if (!range) {
      range = this.getSelection();
    }
    this.saveUndoState(range);
    if (remove) {
      range = this._removeFormat(
        remove.tag.toUpperCase(),
        remove.attributes || {},
        range,
        partial
      );
    }
    if (add) {
      range = this._addFormat(
        add.tag.toUpperCase(),
        add.attributes || {},
        range
      );
    }
    this.setSelection(range);
    this._updatePath(range, true);
    return this.focus();
  }
  _addFormat(tag, attributes, range) {
    const root = this._root;
    if (range.collapsed) {
      const el = fixCursor(createElement(tag, attributes));
      insertNodeInRange(range, el);
      const focusNode = el.firstChild || el;
      const focusOffset = focusNode instanceof Text ? focusNode.length : 0;
      range.setStart(focusNode, focusOffset);
      range.collapse(true);
      let block = el;
      while (isInline(block)) {
        block = block.parentNode;
      }
      removeZWS(block, el);
    } else {
      const walker = new TreeIterator(
        range.commonAncestorContainer,
        SHOW_ELEMENT_OR_TEXT,
        (node) => {
          return (node instanceof Text || node.nodeName === "BR" || node.nodeName === "IMG") && isNodeContainedInRange(range, node, true);
        }
      );
      let { startContainer, startOffset, endContainer, endOffset } = range;
      walker.currentNode = startContainer;
      if (!(startContainer instanceof Element) && !(startContainer instanceof Text) || !walker.filter(startContainer)) {
        const next = walker.nextNode();
        if (!next) {
          return range;
        }
        startContainer = next;
        startOffset = 0;
      }
      do {
        let node = walker.currentNode;
        const needsFormat = !getNearest(node, root, tag, attributes);
        if (needsFormat) {
          if (node === endContainer && node.length > endOffset) {
            node.splitText(endOffset);
          }
          if (node === startContainer && startOffset) {
            node = node.splitText(startOffset);
            if (endContainer === startContainer) {
              endContainer = node;
              endOffset -= startOffset;
            } else if (endContainer === startContainer.parentNode) {
              endOffset += 1;
            }
            startContainer = node;
            startOffset = 0;
          }
          const el = createElement(tag, attributes);
          replaceWith(node, el);
          el.appendChild(node);
        }
      } while (walker.nextNode());
      range = createRange(
        startContainer,
        startOffset,
        endContainer,
        endOffset
      );
    }
    return range;
  }
  _removeFormat(tag, attributes, range, partial) {
    this._saveRangeToBookmark(range);
    let fixer;
    if (range.collapsed) {
      if (cantFocusEmptyTextNodes) {
        fixer = document.createTextNode(ZWS);
      } else {
        fixer = document.createTextNode("");
      }
      insertNodeInRange(range, fixer);
    }
    let root = range.commonAncestorContainer;
    while (isInline(root)) {
      root = root.parentNode;
    }
    const startContainer = range.startContainer;
    const startOffset = range.startOffset;
    const endContainer = range.endContainer;
    const endOffset = range.endOffset;
    const toWrap = [];
    const examineNode = (node, exemplar) => {
      if (isNodeContainedInRange(range, node, false)) {
        return;
      }
      let child;
      let next;
      if (!isNodeContainedInRange(range, node, true)) {
        if (!(node instanceof HTMLInputElement) && (!(node instanceof Text) || node.data)) {
          toWrap.push([exemplar, node]);
        }
        return;
      }
      if (node instanceof Text) {
        if (node === endContainer && endOffset !== node.length) {
          toWrap.push([exemplar, node.splitText(endOffset)]);
        }
        if (node === startContainer && startOffset) {
          node.splitText(startOffset);
          toWrap.push([exemplar, node]);
        }
      } else {
        for (child = node.firstChild; child; child = next) {
          next = child.nextSibling;
          examineNode(child, exemplar);
        }
      }
    };
    const formatTags = Array.from(
      root.getElementsByTagName(tag)
    ).filter((el) => {
      return isNodeContainedInRange(range, el, true) && hasTagAttributes(el, tag, attributes);
    });
    if (!partial) {
      formatTags.forEach((node) => {
        examineNode(node, node);
      });
    }
    toWrap.forEach(([el, node]) => {
      el = el.cloneNode(false);
      replaceWith(node, el);
      el.appendChild(node);
    });
    formatTags.forEach((el) => {
      replaceWith(el, empty(el));
    });
    if (cantFocusEmptyTextNodes && fixer) {
      fixer = fixer.parentNode;
      let block = fixer;
      while (block && isInline(block)) {
        block = block.parentNode;
      }
      if (block) {
        removeZWS(block, fixer);
      }
    }
    this._getRangeAndRemoveBookmark(range);
    if (fixer) {
      range.collapse(false);
    }
    mergeInlines(root, range);
    return range;
  }
  // ---
  bold() {
    return this.changeFormat({ tag: "B" });
  }
  removeBold() {
    return this.changeFormat(null, { tag: "B" });
  }
  italic() {
    return this.changeFormat({ tag: "I" });
  }
  removeItalic() {
    return this.changeFormat(null, { tag: "I" });
  }
  underline() {
    return this.changeFormat({ tag: "U" });
  }
  removeUnderline() {
    return this.changeFormat(null, { tag: "U" });
  }
  strikethrough() {
    return this.changeFormat({ tag: "S" });
  }
  removeStrikethrough() {
    return this.changeFormat(null, { tag: "S" });
  }
  subscript() {
    return this.changeFormat({ tag: "SUB" }, { tag: "SUP" });
  }
  removeSubscript() {
    return this.changeFormat(null, { tag: "SUB" });
  }
  superscript() {
    return this.changeFormat({ tag: "SUP" }, { tag: "SUB" });
  }
  removeSuperscript() {
    return this.changeFormat(null, { tag: "SUP" });
  }
  // ---
  makeLink(url, attributes) {
    const range = this.getSelection();
    if (range.collapsed) {
      let protocolEnd = url.indexOf(":") + 1;
      if (protocolEnd) {
        while (url[protocolEnd] === "/") {
          protocolEnd += 1;
        }
      }
      insertNodeInRange(
        range,
        document.createTextNode(url.slice(protocolEnd))
      );
    }
    attributes = Object.assign(
      {
        href: url
      },
      this._config.tagAttributes.a,
      attributes
    );
    return this.changeFormat(
      {
        tag: "A",
        attributes
      },
      {
        tag: "A"
      },
      range
    );
  }
  removeLink() {
    return this.changeFormat(
      null,
      {
        tag: "A"
      },
      this.getSelection(),
      true
    );
  }
  addDetectedLinks(searchInNode, root) {
    const walker = new TreeIterator(
      searchInNode,
      SHOW_TEXT,
      (node2) => !getNearest(node2, root || this._root, "A")
    );
    const linkRegExp = this.linkRegExp;
    const defaultAttributes = this._config.tagAttributes.a;
    let node;
    while (node = walker.nextNode()) {
      const parent = node.parentNode;
      let data = node.data;
      let match;
      while (match = linkRegExp.exec(data)) {
        const index = match.index;
        const endIndex = index + match[0].length;
        if (index) {
          parent.insertBefore(
            document.createTextNode(data.slice(0, index)),
            node
          );
        }
        const child = createElement(
          "A",
          Object.assign(
            {
              href: match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0]
            },
            defaultAttributes
          )
        );
        child.textContent = data.slice(index, endIndex);
        parent.insertBefore(child, node);
        node.data = data = data.slice(endIndex);
      }
    }
    return this;
  }
  // ---
  setFontFace(name) {
    const className = this._config.classNames.fontFamily;
    return this.changeFormat(
      name ? {
        tag: "SPAN",
        attributes: {
          class: className,
          style: "font-family: " + name + ", sans-serif;"
        }
      } : null,
      {
        tag: "SPAN",
        attributes: { class: className }
      }
    );
  }
  setFontSize(size) {
    const className = this._config.classNames.fontSize;
    return this.changeFormat(
      size ? {
        tag: "SPAN",
        attributes: {
          class: className,
          style: "font-size: " + (typeof size === "number" ? size + "px" : size)
        }
      } : null,
      {
        tag: "SPAN",
        attributes: { class: className }
      }
    );
  }
  setTextColor(color) {
    const className = this._config.classNames.color;
    return this.changeFormat(
      color ? {
        tag: "SPAN",
        attributes: {
          class: className,
          style: "color:" + color
        }
      } : null,
      {
        tag: "SPAN",
        attributes: { class: className }
      }
    );
  }
  setHighlightColor(color) {
    const className = this._config.classNames.highlight;
    return this.changeFormat(
      color ? {
        tag: "SPAN",
        attributes: {
          class: className,
          style: "background-color:" + color
        }
      } : null,
      {
        tag: "SPAN",
        attributes: { class: className }
      }
    );
  }
  // --- Block formatting
  _ensureBottomLine() {
    const root = this._root;
    const last = root.lastElementChild;
    if (!last || last.nodeName !== this._config.blockTag || !isBlock(last)) {
      root.appendChild(this.createDefaultBlock());
    }
  }
  createDefaultBlock(children) {
    const config = this._config;
    return fixCursor(
      createElement(config.blockTag, config.blockAttributes, children)
    );
  }
  splitBlock(lineBreakOnly, range) {
    if (!range) {
      range = this.getSelection();
    }
    const root = this._root;
    let block;
    let parent;
    let node;
    let nodeAfterSplit;
    this._recordUndoState(range);
    this._removeZWS();
    this._getRangeAndRemoveBookmark(range);
    if (!range.collapsed) {
      deleteContentsOfRange(range, root);
    }
    if (this._config.addLinks) {
      moveRangeBoundariesDownTree(range);
      const textNode = range.startContainer;
      const offset2 = range.startOffset;
      setTimeout(() => {
        linkifyText(this, textNode, offset2);
      }, 0);
    }
    block = getStartBlockOfRange(range, root);
    if (block && (parent = getNearest(block, root, "PRE"))) {
      moveRangeBoundariesDownTree(range);
      node = range.startContainer;
      const offset2 = range.startOffset;
      if (!(node instanceof Text)) {
        node = document.createTextNode("");
        parent.insertBefore(node, parent.firstChild);
      }
      if (!lineBreakOnly && node instanceof Text && (node.data.charAt(offset2 - 1) === "\n" || rangeDoesStartAtBlockBoundary(range, root)) && (node.data.charAt(offset2) === "\n" || rangeDoesEndAtBlockBoundary(range, root))) {
        node.deleteData(offset2 && offset2 - 1, offset2 ? 2 : 1);
        nodeAfterSplit = split(
          node,
          offset2 && offset2 - 1,
          root,
          root
        );
        node = nodeAfterSplit.previousSibling;
        if (!node.textContent) {
          detach(node);
        }
        node = this.createDefaultBlock();
        nodeAfterSplit.parentNode.insertBefore(node, nodeAfterSplit);
        if (!nodeAfterSplit.textContent) {
          detach(nodeAfterSplit);
        }
        range.setStart(node, 0);
      } else {
        node.insertData(offset2, "\n");
        if (!node.nextSibling) {
          parent.appendChild(createElement("BR"));
        }
        if (node.length === offset2 + 1) {
          range.setStartAfter(node);
        } else {
          range.setStart(node, offset2 + 1);
        }
      }
      range.collapse(true);
      this.setSelection(range);
      this._updatePath(range, true);
      this._docWasChanged();
      return this;
    }
    if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {
      moveRangeBoundaryOutOf(range, "A", root);
      insertNodeInRange(range, createElement("BR"));
      range.collapse(false);
      this.setSelection(range);
      this._updatePath(range, true);
      return this;
    }
    if (parent = getNearest(block, root, "LI")) {
      block = parent;
    }
    if (isEmptyBlock(block)) {
      if (getNearest(block, root, "UL") || getNearest(block, root, "OL")) {
        this.decreaseListLevel(range);
        return this;
      } else if (getNearest(block, root, "BLOCKQUOTE")) {
        this.replaceWithBlankLine(range);
        return this;
      }
    }
    node = range.startContainer;
    const offset = range.startOffset;
    let splitTag = this.tagAfterSplit[block.nodeName];
    nodeAfterSplit = split(
      node,
      offset,
      block.parentNode,
      this._root
    );
    const config = this._config;
    let splitProperties = null;
    if (!splitTag) {
      splitTag = config.blockTag;
      splitProperties = config.blockAttributes;
    }
    if (!hasTagAttributes(nodeAfterSplit, splitTag, splitProperties)) {
      block = createElement(splitTag, splitProperties);
      if (nodeAfterSplit.dir) {
        block.dir = nodeAfterSplit.dir;
      }
      replaceWith(nodeAfterSplit, block);
      block.appendChild(empty(nodeAfterSplit));
      nodeAfterSplit = block;
    }
    removeZWS(block);
    removeEmptyInlines(block);
    fixCursor(block);
    while (nodeAfterSplit instanceof Element) {
      let child = nodeAfterSplit.firstChild;
      let next;
      if (nodeAfterSplit.nodeName === "A" && (!nodeAfterSplit.textContent || nodeAfterSplit.textContent === ZWS)) {
        child = document.createTextNode("");
        replaceWith(nodeAfterSplit, child);
        nodeAfterSplit = child;
        break;
      }
      while (child && child instanceof Text && !child.data) {
        next = child.nextSibling;
        if (!next || next.nodeName === "BR") {
          break;
        }
        detach(child);
        child = next;
      }
      if (!child || child.nodeName === "BR" || child instanceof Text) {
        break;
      }
      nodeAfterSplit = child;
    }
    range = createRange(nodeAfterSplit, 0);
    this.setSelection(range);
    this._updatePath(range, true);
    return this;
  }
  forEachBlock(fn, mutates, range) {
    if (!range) {
      range = this.getSelection();
    }
    if (mutates) {
      this.saveUndoState(range);
    }
    const root = this._root;
    let start = getStartBlockOfRange(range, root);
    const end = getEndBlockOfRange(range, root);
    if (start && end) {
      do {
        if (fn(start) || start === end) {
          break;
        }
      } while (start = getNextBlock(start, root));
    }
    if (mutates) {
      this.setSelection(range);
      this._updatePath(range, true);
    }
    return this;
  }
  modifyBlocks(modify, range) {
    if (!range) {
      range = this.getSelection();
    }
    this._recordUndoState(range, this._isInUndoState);
    const root = this._root;
    expandRangeToBlockBoundaries(range, root);
    moveRangeBoundariesUpTree(range, root, root, root);
    const frag = extractContentsOfRange(range, root, root);
    if (!range.collapsed) {
      let node = range.endContainer;
      if (node === root) {
        range.collapse(false);
      } else {
        while (node.parentNode !== root) {
          node = node.parentNode;
        }
        range.setStartBefore(node);
        range.collapse(true);
      }
    }
    insertNodeInRange(range, modify.call(this, frag));
    if (range.endOffset < range.endContainer.childNodes.length) {
      mergeContainers(
        range.endContainer.childNodes[range.endOffset],
        root
      );
    }
    mergeContainers(
      range.startContainer.childNodes[range.startOffset],
      root
    );
    this._getRangeAndRemoveBookmark(range);
    this.setSelection(range);
    this._updatePath(range, true);
    return this;
  }
  // ---
  setTextAlignment(alignment) {
    this.forEachBlock((block) => {
      const className = block.className.split(/\s+/).filter((klass) => {
        return !!klass && !/^align/.test(klass);
      }).join(" ");
      if (alignment) {
        block.className = className + " align-" + alignment;
        block.style.textAlign = alignment;
      } else {
        block.className = className;
        block.style.textAlign = "";
      }
    }, true);
    return this.focus();
  }
  setTextDirection(direction) {
    this.forEachBlock((block) => {
      if (direction) {
        block.dir = direction;
      } else {
        block.removeAttribute("dir");
      }
    }, true);
    return this.focus();
  }
  // ---
  _getListSelection(range, root) {
    let list = range.commonAncestorContainer;
    let startLi = range.startContainer;
    let endLi = range.endContainer;
    while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) {
      list = list.parentNode;
    }
    if (!list || list === root) {
      return null;
    }
    if (startLi === list) {
      startLi = startLi.childNodes[range.startOffset];
    }
    if (endLi === list) {
      endLi = endLi.childNodes[range.endOffset];
    }
    while (startLi && startLi.parentNode !== list) {
      startLi = startLi.parentNode;
    }
    while (endLi && endLi.parentNode !== list) {
      endLi = endLi.parentNode;
    }
    return [list, startLi, endLi];
  }
  increaseListLevel(range) {
    if (!range) {
      range = this.getSelection();
    }
    const root = this._root;
    const listSelection = this._getListSelection(range, root);
    if (!listSelection) {
      return this.focus();
    }
    let [list, startLi, endLi] = listSelection;
    if (!startLi || startLi === list.firstChild) {
      return this.focus();
    }
    this._recordUndoState(range, this._isInUndoState);
    const type = list.nodeName;
    let newParent = startLi.previousSibling;
    let listAttrs;
    let next;
    if (newParent.nodeName !== type) {
      listAttrs = this._config.tagAttributes[type.toLowerCase()];
      newParent = createElement(type, listAttrs);
      list.insertBefore(newParent, startLi);
    }
    do {
      next = startLi === endLi ? null : startLi.nextSibling;
      newParent.appendChild(startLi);
    } while (startLi = next);
    next = newParent.nextSibling;
    if (next) {
      mergeContainers(next, root);
    }
    this._getRangeAndRemoveBookmark(range);
    this.setSelection(range);
    this._updatePath(range, true);
    return this.focus();
  }
  decreaseListLevel(range) {
    if (!range) {
      range = this.getSelection();
    }
    const root = this._root;
    const listSelection = this._getListSelection(range, root);
    if (!listSelection) {
      return this.focus();
    }
    let [list, startLi, endLi] = listSelection;
    if (!startLi) {
      startLi = list.firstChild;
    }
    if (!endLi) {
      endLi = list.lastChild;
    }
    this._recordUndoState(range, this._isInUndoState);
    let next;
    let insertBefore = null;
    if (startLi) {
      let newParent = list.parentNode;
      insertBefore = !endLi.nextSibling ? list.nextSibling : split(list, endLi.nextSibling, newParent, root);
      if (newParent !== root && newParent.nodeName === "LI") {
        newParent = newParent.parentNode;
        while (insertBefore) {
          next = insertBefore.nextSibling;
          endLi.appendChild(insertBefore);
          insertBefore = next;
        }
        insertBefore = list.parentNode.nextSibling;
      }
      const makeNotList = !/^[OU]L$/.test(newParent.nodeName);
      do {
        next = startLi === endLi ? null : startLi.nextSibling;
        list.removeChild(startLi);
        if (makeNotList && startLi.nodeName === "LI") {
          startLi = this.createDefaultBlock([empty(startLi)]);
        }
        newParent.insertBefore(startLi, insertBefore);
      } while (startLi = next);
    }
    if (!list.firstChild) {
      detach(list);
    }
    if (insertBefore) {
      mergeContainers(insertBefore, root);
    }
    this._getRangeAndRemoveBookmark(range);
    this.setSelection(range);
    this._updatePath(range, true);
    return this.focus();
  }
  _makeList(frag, type) {
    const walker = getBlockWalker(frag, this._root);
    const tagAttributes = this._config.tagAttributes;
    const listAttrs = tagAttributes[type.toLowerCase()];
    const listItemAttrs = tagAttributes.li;
    let node;
    while (node = walker.nextNode()) {
      if (node.parentNode instanceof HTMLLIElement) {
        node = node.parentNode;
        walker.currentNode = node.lastChild;
      }
      if (!(node instanceof HTMLLIElement)) {
        const newLi = createElement("LI", listItemAttrs);
        if (node.dir) {
          newLi.dir = node.dir;
        }
        const prev = node.previousSibling;
        if (prev && prev.nodeName === type) {
          prev.appendChild(newLi);
          detach(node);
        } else {
          replaceWith(node, createElement(type, listAttrs, [newLi]));
        }
        newLi.appendChild(empty(node));
        walker.currentNode = newLi;
      } else {
        node = node.parentNode;
        const tag = node.nodeName;
        if (tag !== type && /^[OU]L$/.test(tag)) {
          replaceWith(
            node,
            createElement(type, listAttrs, [empty(node)])
          );
        }
      }
    }
    return frag;
  }
  makeUnorderedList() {
    this.modifyBlocks((frag) => this._makeList(frag, "UL"));
    return this.focus();
  }
  makeOrderedList() {
    this.modifyBlocks((frag) => this._makeList(frag, "OL"));
    return this.focus();
  }
  removeList() {
    this.modifyBlocks((frag) => {
      const lists = frag.querySelectorAll("UL, OL");
      const items = frag.querySelectorAll("LI");
      const root = this._root;
      for (let i = 0, l = lists.length; i < l; i += 1) {
        const list = lists[i];
        const listFrag = empty(list);
        fixContainer(listFrag, root);
        replaceWith(list, listFrag);
      }
      for (let i = 0, l = items.length; i < l; i += 1) {
        const item = items[i];
        if (isBlock(item)) {
          replaceWith(item, this.createDefaultBlock([empty(item)]));
        } else {
          fixContainer(item, root);
          replaceWith(item, empty(item));
        }
      }
      return frag;
    });
    return this.focus();
  }
  // ---
  increaseQuoteLevel(range) {
    this.modifyBlocks(
      (frag) => createElement(
        "BLOCKQUOTE",
        this._config.tagAttributes.blockquote,
        [frag]
      ),
      range
    );
    return this.focus();
  }
  decreaseQuoteLevel(range) {
    this.modifyBlocks((frag) => {
      Array.from(frag.querySelectorAll("blockquote")).filter((el) => {
        return !getNearest(el.parentNode, frag, "BLOCKQUOTE");
      }).forEach((el) => {
        replaceWith(el, empty(el));
      });
      return frag;
    }, range);
    return this.focus();
  }
  removeQuote(range) {
    this.modifyBlocks((frag) => {
      Array.from(frag.querySelectorAll("blockquote")).forEach(
        (el) => {
          replaceWith(el, empty(el));
        }
      );
      return frag;
    }, range);
    return this.focus();
  }
  replaceWithBlankLine(range) {
    this.modifyBlocks(
      () => this.createDefaultBlock([
        createElement("INPUT", {
          id: this.startSelectionId,
          type: "hidden"
        }),
        createElement("INPUT", {
          id: this.endSelectionId,
          type: "hidden"
        })
      ]),
      range
    );
    return this.focus();
  }
  // ---
  code() {
    const range = this.getSelection();
    if (range.collapsed || isContainer(range.commonAncestorContainer)) {
      this.modifyBlocks((frag) => {
        const root = this._root;
        const output = document.createDocumentFragment();
        const blockWalker = getBlockWalker(frag, root);
        let node;
        while (node = blockWalker.nextNode()) {
          let nodes = node.querySelectorAll("BR");
          const brBreaksLine = [];
          let l = nodes.length;
          for (let i = 0; i < l; i += 1) {
            brBreaksLine[i] = isLineBreak(nodes[i], false);
          }
          while (l--) {
            const br = nodes[l];
            if (!brBreaksLine[l]) {
              detach(br);
            } else {
              replaceWith(br, document.createTextNode("\n"));
            }
          }
          nodes = node.querySelectorAll("CODE");
          l = nodes.length;
          while (l--) {
            replaceWith(nodes[l], empty(nodes[l]));
          }
          if (output.childNodes.length) {
            output.appendChild(document.createTextNode("\n"));
          }
          output.appendChild(empty(node));
        }
        const textWalker = new TreeIterator(output, SHOW_TEXT);
        while (node = textWalker.nextNode()) {
          node.data = node.data.replace(/ /g, " ");
        }
        output.normalize();
        return fixCursor(
          createElement("PRE", this._config.tagAttributes.pre, [
            output
          ])
        );
      }, range);
      this.focus();
    } else {
      this.changeFormat(
        {
          tag: "CODE",
          attributes: this._config.tagAttributes.code
        },
        null,
        range
      );
    }
    return this;
  }
  removeCode() {
    const range = this.getSelection();
    const ancestor = range.commonAncestorContainer;
    const inPre = getNearest(ancestor, this._root, "PRE");
    if (inPre) {
      this.modifyBlocks((frag) => {
        const root = this._root;
        const pres = frag.querySelectorAll("PRE");
        let l = pres.length;
        while (l--) {
          const pre = pres[l];
          const walker = new TreeIterator(pre, SHOW_TEXT);
          let node;
          while (node = walker.nextNode()) {
            let value = node.data;
            value = value.replace(/ (?= )/g, "\xA0");
            const contents = document.createDocumentFragment();
            let index;
            while ((index = value.indexOf("\n")) > -1) {
              contents.appendChild(
                document.createTextNode(value.slice(0, index))
              );
              contents.appendChild(createElement("BR"));
              value = value.slice(index + 1);
            }
            node.parentNode.insertBefore(contents, node);
            node.data = value;
          }
          fixContainer(pre, root);
          replaceWith(pre, empty(pre));
        }
        return frag;
      }, range);
      this.focus();
    } else {
      this.changeFormat(null, { tag: "CODE" }, range);
    }
    return this;
  }
  toggleCode() {
    if (this.hasFormat("PRE") || this.hasFormat("CODE")) {
      this.removeCode();
    } else {
      this.code();
    }
    return this;
  }
  // ---
  _removeFormatting(root, clean) {
    for (let node = root.firstChild, next; node; node = next) {
      next = node.nextSibling;
      if (isInline(node)) {
        if (node instanceof Text || node.nodeName === "BR" || node.nodeName === "IMG") {
          clean.appendChild(node);
          continue;
        }
      } else if (isBlock(node)) {
        clean.appendChild(
          this.createDefaultBlock([
            this._removeFormatting(
              node,
              document.createDocumentFragment()
            )
          ])
        );
        continue;
      }
      this._removeFormatting(node, clean);
    }
    return clean;
  }
  removeAllFormatting(range) {
    if (!range) {
      range = this.getSelection();
    }
    if (range.collapsed) {
      return this.focus();
    }
    const root = this._root;
    let stopNode = range.commonAncestorContainer;
    while (stopNode && !isBlock(stopNode)) {
      stopNode = stopNode.parentNode;
    }
    if (!stopNode) {
      expandRangeToBlockBoundaries(range, root);
      stopNode = root;
    }
    if (stopNode instanceof Text) {
      return this.focus();
    }
    this.saveUndoState(range);
    moveRangeBoundariesUpTree(range, stopNode, stopNode, root);
    const startContainer = range.startContainer;
    let startOffset = range.startOffset;
    const endContainer = range.endContainer;
    let endOffset = range.endOffset;
    const formattedNodes = document.createDocumentFragment();
    const cleanNodes = document.createDocumentFragment();
    const nodeAfterSplit = split(endContainer, endOffset, stopNode, root);
    let nodeInSplit = split(startContainer, startOffset, stopNode, root);
    let nextNode;
    while (nodeInSplit !== nodeAfterSplit) {
      nextNode = nodeInSplit.nextSibling;
      formattedNodes.appendChild(nodeInSplit);
      nodeInSplit = nextNode;
    }
    this._removeFormatting(formattedNodes, cleanNodes);
    cleanNodes.normalize();
    nodeInSplit = cleanNodes.firstChild;
    nextNode = cleanNodes.lastChild;
    if (nodeInSplit) {
      stopNode.insertBefore(cleanNodes, nodeAfterSplit);
      const childNodes = Array.from(stopNode.childNodes);
      startOffset = childNodes.indexOf(nodeInSplit);
      endOffset = nextNode ? childNodes.indexOf(nextNode) + 1 : 0;
    } else if (nodeAfterSplit) {
      const childNodes = Array.from(stopNode.childNodes);
      startOffset = childNodes.indexOf(nodeAfterSplit);
      endOffset = startOffset;
    }
    range.setStart(stopNode, startOffset);
    range.setEnd(stopNode, endOffset);
    mergeInlines(stopNode, range);
    moveRangeBoundariesDownTree(range);
    this.setSelection(range);
    this._updatePath(range, true);
    return this.focus();
  }
};

// source/Squire.ts
var Squire_default = Squire;
export {
  Squire_default as default
};
