react-basic-contenteditable
Version:
React contenteditable component. Super-customizable!
216 lines (215 loc) • 7.3 kB
JavaScript
import { jsxs as Q, jsx as k } from "react/jsx-runtime";
import { useState as X, useRef as E, useEffect as p, useCallback as A } from "react";
const I = (w, T) => T ? w.length <= T : !0, F = ({
containerClassName: w,
contentEditableClassName: T,
placeholderClassName: V,
charsCounterClassName: z,
placeholder: N,
disabled: m,
updatedContent: d,
maxLength: u,
autoFocus: b,
onChange: C,
onKeyUp: D,
onKeyDown: B,
onFocus: P,
onBlur: K,
onContentExternalUpdate: x
}) => {
const [s, f] = X(""), n = E(null), g = E([]), R = E([]);
p(() => {
d != null && (f(d), n.current && (n.current.innerText = d), x && x(d));
}, [d, x]), p(() => {
n.current && (n.current.style.height = "auto", C && I(s, u) && C(s));
}, [s, C, u]), p(() => {
n.current && b && n.current.focus();
}, [b]), p(() => {
g.current.push(s);
}, [s]);
const S = A(
(e) => {
if (e.ctrlKey && e.key === "z") {
if (e.preventDefault(), g.current.length > 1) {
R.current.push(g.current.pop());
const t = g.current[g.current.length - 1];
f(t), n.current && (n.current.innerText = t, v(n.current));
}
} else if ((e.ctrlKey && e.key === "y" || e.ctrlKey && e.shiftKey && e.key === "Z") && (e.preventDefault(), R.current.length > 0)) {
const t = R.current.pop();
g.current.push(t), f(t), n.current && (n.current.innerText = t, v(n.current));
}
},
[f]
);
p(() => (document.addEventListener("keydown", S), () => document.removeEventListener("keydown", S)), [S]);
const O = A((e) => {
if (e.ownerDocument.activeElement !== e) return !1;
const t = e.ownerDocument.defaultView;
if (!t) return !1;
const r = t.getSelection();
if (!r || r.rangeCount === 0) return !1;
const c = r.getRangeAt(0);
if (c.toString().length > 0) return !1;
const l = c.getBoundingClientRect(), i = document.createRange();
i.selectNodeContents(e);
let o = i.endContainer, a = 0;
for (; o.hasChildNodes() && !(o instanceof Text); )
o.lastChild && (o = o.lastChild, a = o instanceof Text ? o.length : 0);
i.setEnd(o, a), i.setStart(o, a);
const h = i.getBoundingClientRect();
return l.bottom === h.bottom;
}, []), y = A(
(e) => {
if (!n.current) return;
const t = n.current;
switch (e.keyCode) {
case 38:
t.selectionStart === 0 && (t.scrollTop = 0);
break;
case 13:
case 40:
O(t) && (t.scrollTop = t.scrollHeight);
break;
}
},
[O]
);
function M(e) {
var i, o;
e.preventDefault();
const r = (e.clipboardData || window.clipboardData).getData("text/plain"), c = window.getSelection(), l = ((i = n.current) == null ? void 0 : i.innerText) || "";
if (c && c.rangeCount) {
const a = c.getRangeAt(0), h = a.toString(), J = u ? u - (l.length - h.length) : r.length, j = r.slice(0, J);
if (j.length > 0) {
a.deleteContents();
const H = document.createTextNode(j);
a.insertNode(H), a.setStartAfter(H), c.removeAllRanges(), c.addRange(a), f(((o = n.current) == null ? void 0 : o.innerText) ?? "");
}
} else {
const a = u ? u - l.length : r.length, h = r.slice(0, a);
h.length > 0 && W(h);
}
}
function W(e) {
if (!n.current) return;
const t = $(n.current);
n.current.innerText = n.current.innerText.slice(0, t) + e + n.current.innerText.slice(t), f(n.current.innerText), n.current.scrollTop = n.current.scrollHeight, Z(n.current, t + e.length);
}
function Z(e, t) {
const r = document.createRange(), c = e.childNodes[0];
if (c != null) {
r.setStart(c, t), r.setEnd(c, t);
const l = window.getSelection();
if (!l) return;
l.removeAllRanges(), l.addRange(r);
} else
e.focus();
}
function v(e) {
const t = document.createRange(), r = window.getSelection();
e.lastChild && r && (t.setStartAfter(e.lastChild), t.collapse(!0), r.removeAllRanges(), r.addRange(t));
}
function $(e) {
var c, l;
let t = 0, r;
if (window.getSelection) {
const i = window.getSelection();
i && i.rangeCount && (r = i.getRangeAt(0), r.commonAncestorContainer.parentNode === e && (t = r.endOffset));
} else if (document.getSelection() && ((c = document.getSelection()) != null && c.getRangeAt) && (r = (l = document.getSelection()) == null ? void 0 : l.getRangeAt(0), r && r.commonAncestorContainer.parentNode === e)) {
const i = document.createElement("span");
e.insertBefore(i, e.firstChild);
const o = r.cloneRange();
o.moveToElementText(i), o.setEndPoint("EndToEnd", r), t = o.text.length;
}
return t;
}
function q(e) {
B && B(e), n.current && (["Delete", "Backspace"].includes(e.key) && G() || e.key === "Backspace" && s.length === 1 || e.key === "Delete" && $(n.current) === 0 && s.length === 1) && (e.preventDefault(), n.current.innerText = "", f(""));
}
const G = () => {
var r, c;
const e = window.getSelection(), t = (((r = n.current) == null ? void 0 : r.innerText.match(/\n(\n|$)/g)) || []).length;
return e ? e.toString().length + t === ((c = n.current) == null ? void 0 : c.innerText.length) : !1;
};
return p(() => (document.addEventListener("keyup", y), () => document.removeEventListener("keyup", y)), [y]), /* @__PURE__ */ Q(
"div",
{
className: w,
style: { display: "flex", alignItems: "center", position: "relative" },
children: [
/* @__PURE__ */ k(
"div",
{
ref: n,
contentEditable: !0,
defaultValue: s,
"aria-disabled": m,
dir: "auto",
role: "textbox",
"aria-label": N ?? "",
className: T,
style: {
padding: "0.85rem",
overflow: "auto",
height: "auto",
textAlign: "initial",
wordBreak: "break-word",
unicodeBidi: "plaintext"
},
onInput: (e) => {
const t = e.currentTarget.innerText;
if (m || !I(t, u)) {
n.current && (n.current.innerText = s, v(n.current));
return;
}
f(t);
},
onPaste: (e) => {
m || M(e);
},
onFocus: (e) => {
P && P(e);
},
onBlur: (e) => {
K && K(e);
},
onKeyUp: (e) => {
m || D && D(e);
},
onKeyDown: (e) => {
m || q(e);
}
}
),
!s && /* @__PURE__ */ k(
"span",
{
dir: "auto",
className: V,
style: {
position: "absolute",
pointerEvents: "none",
textAlign: "initial"
},
children: N ?? ""
}
),
!!u && /* @__PURE__ */ k(
"span",
{
dir: "auto",
className: z,
style: {
marginLeft: "1rem"
},
children: `${s.length ?? 0}/${u}`
}
)
]
}
);
};
export {
F as default
};