lovasoa / react-contenteditable

React component for a div with editable contents
http://npmjs.com/package/react-contenteditable
Apache License 2.0
1.61k stars 219 forks source link

Cursor jumping to the end #285

Open jackwshepherd opened 1 month ago

jackwshepherd commented 1 month ago

I am 100% sure there is a fix for this as there have been previous posts on this: https://github.com/lovasoa/react-contenteditable/issues/160

However, I don't understand how to implement the resolution. Every time the div class is updated, the cursor jumps to the end...anyone can help?


const processHTML = (paras) => {
    const htmlParas = !paras.includes("<div>") ? `<div>${paras}</div>` : paras;

    const result = htmlParas.replace(/<div>(.*?)<\/div>/g, (match, content) => {
      if (content.startsWith("### "))
        return `<div class="header-3">${content}</div>`;
      if (content.startsWith("## "))
        return `<div class="header-2">${content}</div>`;
      if (content.startsWith("# "))
        return `<div class="header-1">${content}</div>`;

      return match;
    });

    setParagraphs(result);
  };

 <ContentEditable
              innerRef={bottomDivRef}
              className="textarea-input"
              onChange={(event) => {
                processHTML(sanitizeHtml(event.target.value));
              }}
              html={paragraphs}
              onPaste={(paste) => console.log(paste)}
              onMouseOver={trackCursor}
              onKeyDown={trackCursor}
              contentEditable={true}
              onClick={trackCursor}
            />
jackwshepherd commented 1 month ago

I tried this solution, which does not work either. Any advice appreciated

import React, { forwardRef, useState, useEffect, useRef } from "react";
import ContentEditable from "react-contenteditable";

const Editor = forwardRef(({ body = "\n", onChange, trackCursor }, ref) => {
  const contentEditableRef = useRef(null);
  const [html, setHtml] = useState("");

  useEffect(() => {
    const nodes = body
      .split("\n")
      .map((text) => `<div>${text}</div>`)
      .join("")
      .match(/<div.*?>(.*?)<\/div>/g)
      .map((div) => div.replace(/<\/?div.*?>/g, ""));

    const bodyClasses = nodes
      .map((value) => {
        const className = value.startsWith("# ")
          ? "header-1"
          : value.startsWith("## ")
          ? "header-2"
          : value.startsWith("### ")
          ? "header-3"
          : "";
        return `<div class="${className}">${value}</div>`;
      })
      .join("");

    setHtml(bodyClasses);
  }, [body]);

  const handleChange = (evt) => {
    const newHtml = evt.target.value;

    // Preserve cursor position
    const selection = window.getSelection();
    const range = selection.getRangeAt(0);
    const startOffset = range.startOffset;
    const endOffset = range.endOffset;
    const startContainer = range.startContainer;
    const endContainer = range.endContainer;

    setHtml(newHtml);

    // Restore cursor position after DOM update
    setTimeout(() => {
      if (contentEditableRef.current) {
        const newRange = document.createRange();
        const sel = window.getSelection();
        newRange.setStart(startContainer, startOffset);
        newRange.setEnd(endContainer, endOffset);
        sel.removeAllRanges();
        sel.addRange(newRange);
      }
    }, 0);

    onChange && onChange(newHtml);
  };

  return (
    <div className="textarea-container">
      <ContentEditable
        innerRef={contentEditableRef}
        className="textarea-input"
        html={html}
        onChange={handleChange}
        onMouseOver={trackCursor}
        onKeyDown={trackCursor}
        onClick={trackCursor}
      />
    </div>
  );
});

export default Editor;