jodit / jodit-angular

Angular wrapper for Jodit && Hey. Due to the fact that I do not use Angular in my projects, I cannot fix plugin errors in a timely manner. If you want the plugin to develop, send PR or better become a contributor
MIT License
49 stars 35 forks source link

Backspace not working in Safari #132

Open jakub-wisniowski-widelab opened 3 weeks ago

jakub-wisniowski-widelab commented 3 weeks ago

We are using jodit with encapsulation: ViewEncapsulation.ShadowDom. In Safari shadowRoot.getSelection() does not work, which results in various problems. One of them is backspace key not working in the editor.

Component throws the following error: HierarchyRequestError: The operation would yield an incorrect node tree.

We were able to override the backspace key logic with the following code:

  handleKeyDown(event) {
    // getSelection does not pierce shadowDom in Safari
    // backspace clicks must be handled by the code below in Safari
    if (!('getSelection' in this.shadowRoot) && event.args[0].keyCode === 8) {
      const selection = window.getSelection() as any;
      if (selection) {
        const range = selection.getComposedRanges(this.elementRef.nativeElement.shadowRoot)[0];
        this.isPreviewModeActive = true;
        if (range.collapsed) {
          this.handleNoTextSelected(range);
        } else {
          this.handleTextSelected(range);
        }
      }
    }
    this.updateCaretWithDebounce$.next();
  }

  private handleNoTextSelected(range: StaticRange): void {
    const { startOffset } = range;
    const value = range.startContainer.nodeValue;
    range.startContainer.nodeValue = value.slice(0, startOffset - 1) + value.slice(startOffset);
  }

  private handleTextSelected(range: StaticRange): void {
    const { startOffset, endOffset } = range;
    const sValue = range.startContainer.nodeValue;
    const eValue = range.endContainer.nodeValue;

    // text from one node is selected
    if (range.startContainer === range.endContainer) {
      range.startContainer.nodeValue = sValue.slice(0, startOffset) + sValue.slice(endOffset);
    }
    // text from multiple nodes is selected
    else {
      this.removeNodesBetweenStartAndEnd(range);
      range.startContainer.nodeValue = sValue.slice(0, startOffset);
      range.endContainer.nodeValue = eValue.slice(endOffset);
    }
  }

  private removeNodesBetweenStartAndEnd(range: StaticRange): void {
    while (this.nextNodeIsNotEndNode(range)) {
      range.startContainer.nextSibling
        ? range.startContainer.nextSibling.remove()
        : range.startContainer.parentElement.nextSibling.remove();
    }
  }

  private nextNodeIsNotEndNode(range: StaticRange): boolean {
    return (
      range.startContainer.nextSibling !== range.endContainer &&
      range.startContainer.nextSibling !== range.endContainer.parentElement &&
      range.startContainer.parentElement.nextSibling !== range.endContainer &&
      range.startContainer.parentElement.nextSibling !== range.endContainer.parentElement
    );
  }

But even though backspace deletes content, the editor loses focus right after backspace is clicked.