UNPKG

@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
/** * @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 };