@progress/kendo-react-editor
Version:
React Editor enables users to create rich text content through a WYSIWYG interface. KendoReact Editor package
328 lines (327 loc) • 11.8 kB
JavaScript
/**
* @license
*-------------------------------------------------------------------------------------------
* Copyright © 2026 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the package root for more information
*-------------------------------------------------------------------------------------------
*/
import * as l from "react";
import i from "prop-types";
import { ToolbarSeparator as D, ButtonGroup as B, Toolbar as F, toolbarButtons as I } from "@progress/kendo-react-buttons";
import { validatePackage as L, getLicenseMessage as M, classNames as W, WatermarkOverlay as x } from "@progress/kendo-react-common";
import { Plugin as w, PluginKey as _, spacesFix as H, caretColor as z, history as j, dropCursor as K, gapCursor as A, tableEditing as V, cspFix as U, Schema as q, marks as G, nodes as $, EditorState as R, keymap as S, baseKeymap as J, EditorView as Q, getMark as X } from "@progress/kendo-editor-common";
import { defaultStyle as Y, tablesStyles as Z, rtlStyles as tt } from "./config/defaultStyles.mjs";
import { EditorToolsSettings as et } from "./config/toolsSettings.mjs";
import { EditorDialogs as it } from "./dialogs/index.mjs";
import { EditorUtils as b } from "./utils/index.mjs";
import { editorPropsKey as T } from "./utils/props-key.mjs";
import { updateEditorValue as st } from "./utils/controlled-value.mjs";
import { packageMetadata as O } from "./package-metadata.mjs";
import { keys as N, messages as ot } from "./messages/index.mjs";
import { provideLocalizationService as nt, registerForLocalization as rt } from "@progress/kendo-react-intl";
const { link: P, bold: at, italic: lt, underline: ht } = et, k = class k extends l.Component {
constructor(h) {
super(h), this.state = {
view: void 0,
linkDialog: !1
}, this._element = null, this._contentElement = null, this.iframe = null, this.trOnChange = null, this.htmlOnChange = null, this.showLicenseWatermark = !1, this.focus = () => {
this.view && this.view.focus();
}, this.renderDialog = (t, e, s) => this.state[s] && /* @__PURE__ */ l.createElement(
t,
{
view: this.view,
settings: e,
dir: this.props.dir,
onClose: () => this.setState({ [s]: !1 })
}
), this.renderTool = (t, e, s = !1) => {
const c = /* @__PURE__ */ l.createElement(
t,
{
view: this.view,
dir: this.props.dir,
key: e,
className: s ? "k-toolbar-button" : void 0
}
);
return c.type === D ? /* @__PURE__ */ l.createElement(t, { key: e }) : c;
}, this.updateTools = (t, e) => {
this.setState({ view: t });
}, this.filterTransaction = (t, e) => {
const s = { target: this, transaction: t, state: e };
return (this.props.onExecute && this.props.onExecute.call(void 0, s)) !== !1;
}, this.onPasteHtml = (t) => {
if (this.props.onPasteHtml && this.pasteEvent) {
const e = {
target: this,
pastedHtml: t,
nativeEvent: this.pasteEvent
}, s = this.props.onPasteHtml.call(void 0, e);
if (this.pasteEvent = void 0, typeof s == "string")
return s;
}
return t;
}, this.dispatchTransaction = (t) => {
const e = t.docChanged;
if (this.props.onChange && e) {
this.trOnChange = t;
const s = t.doc, c = t.doc.type.schema, m = {
target: this,
value: s,
html: "",
transaction: t,
schema: c
};
Object.defineProperty(m, "html", {
get: () => (this.htmlOnChange = b.getHtml({ doc: s, schema: c }), this.htmlOnChange)
}), this.props.onChange.call(void 0, m);
}
this.view && (this.props.value === void 0 || !e) && this.view.updateState(this.view.state.apply(t));
}, this.onFocus = (t, e) => {
if (this.props.onFocus) {
const s = {
target: this,
nativeEvent: e
};
this.props.onFocus.call(void 0, s);
}
return !1;
}, this.onBlur = (t, e) => {
if (this.props.onBlur) {
const s = {
target: this,
nativeEvent: e
};
this.props.onBlur.call(void 0, s);
}
return !1;
}, this.onPaste = (t, e) => (this.props.onPasteHtml && (this.pasteEvent = e), !1), this.onIFrameInit = (t) => {
if (this.props.onIFrameInit) {
const e = {
target: this,
iframe: t
};
this.props.onIFrameInit.call(void 0, e);
}
return !1;
}, this.showLicenseWatermark = !L(O, { component: "Editor" }), this.licenseMessage = M(O);
}
/**
* The value of the Editor.
*/
get value() {
return this.trOnChange !== null ? this.trOnChange.doc : this.props.value !== void 0 ? this.props.value : this.view ? this.view.state.doc : this.props.defaultContent || "";
}
/**
* Returns the DOM element of the Editor.
*/
get element() {
return this._element;
}
/**
* Returns the content-editable DOM element of the Editor.
*/
get contentElement() {
return this._contentElement;
}
/**
* Returns the `view` object of the Editor.
*/
get view() {
return this._view;
}
/**
* @hidden
*/
componentDidMount() {
this.initialize();
}
/**
* @hidden
*/
componentDidUpdate(h) {
const { value: t } = this.props, e = this.view;
t === void 0 || !e || (st(e, t, h.value, this.trOnChange, this.htmlOnChange), this.trOnChange = null, this.htmlOnChange = null);
}
/**
* @hidden
*/
componentWillUnmount() {
var t;
this.view && this.view.destroy(), this._view = void 0;
const h = (t = this.iframe) == null ? void 0 : t.contentWindow;
if (h) {
this._contentElement && this._contentElement.parentNode && this._contentElement.parentNode.removeChild(this._contentElement);
const e = h.document.head;
for (; e && e.firstChild; )
e.removeChild(e.firstChild);
}
}
/**
* @hidden
*/
render() {
const { tools: h = [], defaultEditMode: t = "iframe", preserveWhitespace: e = "full", style: s, className: c } = this.props, m = nt(this);
if (this.view) {
const o = T.getState(this.view.state);
o.preserveWhitespace = e;
}
let p = this.props.contentStyle;
p === void 0 && (s || {}).height === void 0 && (p = { height: "300px" });
const u = (o, d) => o.displayName || o.name || `tool-${d}`, y = (o, d) => o.map((n, r) => u(n, r)).join("-") || `group-${d}`, f = h.map(
(o, d) => Array.isArray(o) ? /* @__PURE__ */ l.createElement(B, { key: y(o, d), className: "k-toolbar-button-group" }, o.map((n, r) => this.renderTool(n, u(n, r)))) : this.renderTool(o, u(o, d), !0)
);
return /* @__PURE__ */ l.createElement(
"div",
{
ref: (o) => {
this._element = o;
},
className: W("k-editor", c, { "k-editor-resizable": this.props.resizable }),
dir: this.props.dir,
style: s
},
f.length > 0 && /* @__PURE__ */ l.createElement(F, { className: "k-editor-toolbar", keyboardNavigation: this.props.keyboardNavigation }, f),
t === "iframe" ? /* @__PURE__ */ l.createElement("div", { className: "k-editor-content" }, /* @__PURE__ */ l.createElement(
"iframe",
{
ref: (o) => {
this.iframe = o;
},
frameBorder: "0",
title: m.toLanguageString(N.iframeTitle, ot[N.iframeTitle]),
style: p,
className: "k-iframe"
}
)) : /* @__PURE__ */ l.createElement("div", { style: p, className: "k-editor-content" }, /* @__PURE__ */ l.createElement(
"div",
{
ref: (o) => {
this._contentElement = o;
},
suppressContentEditableWarning: !0,
role: "textbox",
"aria-labelledby": this.props.ariaLabelledBy,
"aria-describedby": this.props.ariaDescribedBy,
"aria-label": this.props.ariaLabel
}
)),
this.renderDialog(it.InsertLinkDialog, P, "linkDialog"),
this.showLicenseWatermark && /* @__PURE__ */ l.createElement(x, { message: this.licenseMessage })
);
}
initialize() {
const h = this.iframe && this.iframe.contentWindow;
if (h) {
const n = h.document, r = n.createElement("meta");
r.setAttribute("charset", "utf-8"), n.head.appendChild(r), this._contentElement = n.createElement("div"), n.body.appendChild(this._contentElement), this._contentElement.classList.add("k-content"), this.iframe && this.props.onIFrameInit ? this.onIFrameInit(this.iframe) : [Y, Z, this.props.dir === "rtl" ? tt : void 0].forEach((a) => {
if (a) {
const v = n.createElement("style");
v.appendChild(n.createTextNode(a)), n.head.appendChild(v);
}
});
}
const t = this._contentElement;
if (!t)
return;
const { preserveWhitespace: e = "full" } = this.props, s = [
// https://prosemirror.net/docs/ref/#state.PluginSpec
new w({
view: (() => ({ update: this.updateTools })),
key: new _("toolbar-tools-update-plugin")
}),
new w({
filterTransaction: this.filterTransaction,
key: new _("onExecute-event-plugin")
}),
new w({
key: T,
state: {
init: () => ({ preserveWhitespace: e }),
apply: (n, r) => r
}
}),
H(),
z(),
j(),
K(),
A(),
V(),
U()
], c = {
...b.getShortcuts({
types: { listItem: "list_item", hardBreak: "hard_break" },
toolsSettings: { bold: at, italic: lt, underline: ht }
}),
"Mod-k": () => {
const { linkDialog: n } = this.state, r = this.view;
if (r) {
const a = r.state, v = a.selection.empty, g = X(a, a.schema.marks[P.mark]);
!n && !(v && !g) && this.setState({ linkDialog: !0 });
}
return !n;
},
"Alt-F10": () => {
var r;
const n = (r = this.element) == null ? void 0 : r.querySelector(".k-toolbar");
if (n) {
const a = n.querySelector(I.join(","));
if (a)
return a.focus(), !0;
}
return !1;
}
}, { defaultContent: m = "", value: p, onMount: u } = this.props, y = p && typeof p != "string" ? p : b.createDocument(new q({ nodes: $, marks: G }), p || m, {
preserveWhitespace: e
}), f = {
state: R.create({
plugins: [...s, S(c), S(J)],
doc: y
}),
transformPastedHTML: this.onPasteHtml,
dispatchTransaction: this.dispatchTransaction,
handleDOMEvents: {
focus: this.onFocus,
blur: this.onBlur,
paste: this.onPaste
},
handleDrop: (n, r, a, v) => {
let g = !1;
return a.content.nodesBetween(0, a.content.size, (C) => {
g = g || C.type.name === "table_caption_external";
}), g;
}
}, o = { plugins: s, shortcuts: c, target: this, viewProps: f, dom: t }, d = this._view = u && u.call(void 0, o) || new Q({ mount: t }, f);
this.setState({
view: d
});
}
};
k.propTypes = {
defaultContent: i.string,
value: i.oneOfType([i.object, i.string]),
defaultEditMode: i.oneOf(["iframe", "div"]),
contentStyle: i.object,
dir: i.string,
className: i.string,
ariaDescribedBy: i.string,
ariaLabelledBy: i.string,
ariaLabel: i.string,
style: i.object,
tools: i.arrayOf(i.any),
keyboardNavigation: i.bool,
resizable: i.bool,
preserveWhitespace: i.oneOf([!0, !1, "full"]),
onMount: i.func,
onFocus: i.func,
onBlur: i.func,
onChange: i.func,
onPasteHtml: i.func,
onExecute: i.func,
onIFrameInit: i.func
};
let E = k;
rt(E);
export {
E as Editor
};