blockMapWithoutBlocksToBeMoved = blockMap.withMutations(blocks => {
      const nextSiblingKey = blockToBeMoved.getNextSiblingKey();
      const nextDelimiterBlockKey = getNextDelimiterBlockKey(
        blockToBeMoved,
        blocks,
      );

      blocks
        .toSeq()
        .skipUntil(block => block.getKey() === blockKey)
        .takeWhile(block => {
          const key = block.getKey();
          const isBlockToBeMoved = key === blockKey;
          const hasNextSiblingAndIsNotNextSibling =
            nextSiblingKey && key !== nextSiblingKey;
          const doesNotHaveNextSiblingAndIsNotDelimiter =
            !nextSiblingKey &&
            block.getParentKey() &&
            (!nextDelimiterBlockKey || key !== nextDelimiterBlockKey);

          return !!(
            isBlockToBeMoved ||
            hasNextSiblingAndIsNotNextSibling ||
            doesNotHaveNextSiblingAndIsNotDelimiter
          );
        })
        .forEach(block => {
          blocksToBeMoved.push(block);
          blocks.delete(block.getKey());
        });
    });
const getNextDelimitersBlockKeys = (
  block: ContentBlockNode,
  blockMap: BlockMap,
): Array<string> => {
  const nextDelimiters = [];

  if (!block) {
    return nextDelimiters;
  }

  let nextDelimiter = getNextDelimiterBlockKey(block, blockMap);
  while (nextDelimiter && blockMap.get(nextDelimiter)) {
    const block = blockMap.get(nextDelimiter);
    nextDelimiters.push(nextDelimiter);

    // we do not need to keep checking all root node siblings, just the first occurance
    nextDelimiter = block.getParentKey()
      ? getNextDelimiterBlockKey(block, blockMap)
      : null;
  }

  return nextDelimiters;
};
const removeRangeFromContentState = (
  contentState: ContentState,
  selectionState: SelectionState,
): ContentState => {
  if (selectionState.isCollapsed()) {
    return contentState;
  }

  const blockMap = contentState.getBlockMap();
  const startKey = selectionState.getStartKey();
  const startOffset = selectionState.getStartOffset();
  const endKey = selectionState.getEndKey();
  const endOffset = selectionState.getEndOffset();

  const startBlock = blockMap.get(startKey);
  const endBlock = blockMap.get(endKey);

  // we assume that ContentBlockNode and ContentBlocks are not mixed together
  const isExperimentalTreeBlock = startBlock instanceof ContentBlockNode;

  // used to retain blocks that should not be deleted to avoid orphan children
  let parentAncestors = [];

  if (isExperimentalTreeBlock) {
    const endBlockchildrenKeys = endBlock.getChildKeys();
    const endBlockAncestors = getAncestorsKeys(endKey, blockMap);

    // endBlock has unselected siblings so we can not remove its ancestors parents
    if (endBlock.getNextSiblingKey()) {
      parentAncestors = parentAncestors.concat(endBlockAncestors);
    }

    // endBlock has children so can not remove this block or any of its ancestors
    if (!endBlockchildrenKeys.isEmpty()) {
      parentAncestors = parentAncestors.concat(
        endBlockAncestors.concat([endKey]),
      );
    }

    // we need to retain all ancestors of the next delimiter block
    parentAncestors = parentAncestors.concat(
      getAncestorsKeys(getNextDelimiterBlockKey(endBlock, blockMap), blockMap),
    );
  }

  let characterList;

  if (startBlock === endBlock) {
    characterList = removeFromList(
      startBlock.getCharacterList(),
      startOffset,
      endOffset,
    );
  } else {
    characterList = startBlock
      .getCharacterList()
      .slice(0, startOffset)
      .concat(endBlock.getCharacterList().slice(endOffset));
  }

  const modifiedStart = startBlock.merge({
    text:
      startBlock.getText().slice(0, startOffset) +
      endBlock.getText().slice(endOffset),
    characterList,
  });

  // If cursor (collapsed) is at the start of the first child, delete parent
  // instead of child
  const shouldDeleteParent =
    isExperimentalTreeBlock &&
    startOffset === 0 &&
    endOffset === 0 &&
    endBlock.getParentKey() === startKey &&
    endBlock.getPrevSiblingKey() == null;
  const newBlocks = shouldDeleteParent
    ? Map([[startKey, null]])
    : blockMap
        .toSeq()
        .skipUntil((_, k) => k === startKey)
        .takeUntil((_, k) => k === endKey)
        .filter((_, k) => parentAncestors.indexOf(k) === -1)
        .concat(Map([[endKey, null]]))
        .map((_, k) => {
          return k === startKey ? modifiedStart : null;
        });
  let updatedBlockMap = blockMap.merge(newBlocks).filter(block => !!block);

  // Only update tree block pointers if the range is across blocks
  if (isExperimentalTreeBlock && startBlock !== endBlock) {
    updatedBlockMap = updateBlockMapLinks(
      updatedBlockMap,
      startBlock,
      endBlock,
      blockMap,
    );
  }

  return contentState.merge({
    blockMap: updatedBlockMap,
    selectionBefore: selectionState,
    selectionAfter: selectionState.merge({
      anchorKey: startKey,
      anchorOffset: startOffset,
      focusKey: startKey,
      focusOffset: startOffset,
      isBackward: false,
    }),
  });
};