muryshev's picture
init
79278ec
import React, { useRef, useEffect, useCallback, MouseEventHandler } from "react";
import ContentEditable, { ContentEditableEvent } from "react-contenteditable";
import "./Editable.scss";
import { EditableProps } from "./Editable.interface";
const Editable: React.FC<EditableProps> = ({
className,
placeholder,
content,
onContentChange,
handleClick: handleContentClick,
}) => {
const editableRef = useRef<HTMLElement>(null);
const lastCaretPosition = useRef<number>(0);
const getCaretPosition = (el: HTMLElement): number => {
let caretPosition = 0;
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const preRange = range.cloneRange();
preRange.selectNodeContents(el);
preRange.setEnd(range.endContainer, range.endOffset);
caretPosition = preRange.toString().length;
}
return caretPosition;
};
const setCaretPosition = useCallback((offset: number) => {
const selection = window.getSelection();
const range = document.createRange();
const el = editableRef.current;
let localOffset = offset;
el?.childNodes.forEach((element) => {
if (!element.textContent?.length) {
element.textContent = "";
}
if (localOffset <= element.textContent?.length && localOffset >= 0) {
range.setStart(element.childNodes[0] ?? element, localOffset);
range.collapse(true);
selection?.removeAllRanges();
selection?.addRange(range);
localOffset -= element.textContent?.length;
return;
} else if (localOffset >= 0) {
localOffset -= element.textContent?.length;
}
});
}, []);
const handleChange = (event: ContentEditableEvent) => {
const newText = event.currentTarget.innerText;
const currentCaretPosition = getCaretPosition(editableRef.current!);
if (newText.length <= 500) {
onContentChange(newText);
lastCaretPosition.current = currentCaretPosition;
} else {
const trimmedText = newText.substring(0, 500);
event.currentTarget.innerText = trimmedText;
onContentChange(trimmedText);
lastCaretPosition.current = currentCaretPosition;
setCaretPosition(500);
}
};
const handleClick: MouseEventHandler<HTMLDivElement> = (event) => {
lastCaretPosition.current = getCaretPosition(editableRef.current!);
if (handleContentClick) handleContentClick(event);
};
useEffect(() => {
const contentEditable = editableRef.current;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
mutation.removedNodes.forEach((node) => {
if (node instanceof HTMLElement && node.tagName === "A") {
setCaretPosition(lastCaretPosition.current);
}
});
}
});
});
if (contentEditable) {
observer.observe(contentEditable, { childList: true, subtree: true });
}
return () => {
observer.disconnect();
};
}, [setCaretPosition]);
return (
<ContentEditable
innerRef={editableRef}
className={`editable ${className}`}
onChange={handleChange}
onBlur={() => {}}
html={content.replaceAll("@@@", "")}
data-placeholder={placeholder}
onClick={handleClick}
/>
);
};
export default Editable;