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