UNPKG

@progress/kendo-react-editor

Version:

React Editor enables users to create rich text content through a WYSIWYG interface. KendoReact Editor package

344 lines (343 loc) 12.4 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 c from "react"; import i from "prop-types"; import { ToolbarSeparator as A, ButtonGroup as N, Toolbar as x, toolbarButtons as F } from "@progress/kendo-react-buttons"; import { validatePackage as I, getLicenseMessage as M, classNames as W, WatermarkOverlay as H } from "@progress/kendo-react-common"; import { Plugin as E, PluginKey as S, spacesFix as z, caretColor as j, history as K, dropCursor as V, gapCursor as U, tableEditing as q, cspFix as G, Schema as $, marks as R, nodes as J, EditorState as Q, keymap as T, baseKeymap as X, EditorView as Y, getMark as Z } from "@progress/kendo-editor-common"; import { defaultStyle as tt, tablesStyles as et, rtlStyles as it } from "./config/defaultStyles.mjs"; import { EditorToolsSettings as st } from "./config/toolsSettings.mjs"; import { EditorDialogs as nt } from "./dialogs/index.mjs"; import { EditorUtils as k } from "./utils/index.mjs"; import { editorPropsKey as B } from "./utils/props-key.mjs"; import { updateEditorValue as ot } from "./utils/controlled-value.mjs"; import { packageMetadata as D } from "./package-metadata.mjs"; import { keys as d, messages as w } from "./messages/index.mjs"; import { provideLocalizationService as O, registerForLocalization as rt } from "@progress/kendo-react-intl"; const { link: P, bold: at, italic: lt, underline: ht } = st, _ = class _ extends c.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__ */ c.createElement( t, { view: this.view, settings: e, dir: this.props.dir, onClose: () => this.setState({ [s]: !1 }) } ), this.renderTool = (t, e, s = !1) => { const h = /* @__PURE__ */ c.createElement( t, { view: this.view, dir: this.props.dir, key: e, className: s ? "k-toolbar-button" : void 0 } ); return h.type === A ? /* @__PURE__ */ c.createElement(t, { key: e }) : h; }, 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, h = t.doc.type.schema, p = { target: this, value: s, html: "", transaction: t, schema: h }; Object.defineProperty(p, "html", { get: () => (this.htmlOnChange = k.getHtml({ doc: s, schema: h }), this.htmlOnChange) }), this.props.onChange.call(void 0, p); } 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 = !I(D, { component: "Editor" }), this.licenseMessage = M(D); } /** * 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(l) { const { value: t } = this.props, e = this.view; t === void 0 || !e || (ot(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: h } = this.props, p = O(this), u = this.props.ariaLabel || p.toLanguageString(d.contentAreaLabel, w[d.contentAreaLabel]); if (this.view) { const o = B.getState(this.view.state); o.preserveWhitespace = e; } let m = this.props.contentStyle; m === void 0 && (s || {}).height === void 0 && (m = { height: "300px" }); const f = (o, n) => o.displayName || o.name || `tool-${n}`, b = (o, n) => o.map((r, a) => f(r, a)).join("-") || `group-${n}`, y = l.map( (o, n) => Array.isArray(o) ? /* @__PURE__ */ c.createElement(N, { key: b(o, n), className: "k-toolbar-button-group" }, o.map((r, a) => this.renderTool(r, f(r, a)))) : this.renderTool(o, f(o, n), !0) ); return /* @__PURE__ */ c.createElement( "div", { ref: (o) => { this._element = o; }, className: W("k-editor", h, { "k-editor-resizable": this.props.resizable }), dir: this.props.dir, style: s }, y.length > 0 && /* @__PURE__ */ c.createElement( x, { className: "k-editor-toolbar", ariaLabel: p.toLanguageString(d.toolbarAriaLabel, w[d.toolbarAriaLabel]), keyboardNavigation: this.props.keyboardNavigation }, y ), t === "iframe" ? /* @__PURE__ */ c.createElement( "iframe", { ref: (o) => { this.iframe = o; }, frameBorder: "0", title: p.toLanguageString(d.iframeTitle, w[d.iframeTitle]), style: m, className: "k-iframe" } ) : /* @__PURE__ */ c.createElement( "div", { ref: (o) => { this._contentElement = o; }, style: m, className: "k-editor-content", role: "textbox", "aria-labelledby": this.props.ariaLabelledBy, "aria-describedby": this.props.ariaDescribedBy, "aria-label": u, suppressContentEditableWarning: !0 } ), this.renderDialog(nt.InsertLinkDialog, P, "linkDialog"), this.showLicenseWatermark && /* @__PURE__ */ c.createElement(H, { message: this.licenseMessage }) ); } initialize() { const l = this.iframe && this.iframe.contentWindow; if (l) { const n = l.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._contentElement.setAttribute("role", "textbox"), this.props.ariaLabelledBy && this._contentElement.setAttribute("aria-labelledby", this.props.ariaLabelledBy), this.props.ariaDescribedBy && this._contentElement.setAttribute("aria-describedby", this.props.ariaDescribedBy), this._contentElement.setAttribute( "aria-label", this.props.ariaLabel || O(this).toLanguageString( d.contentAreaLabel, w[d.contentAreaLabel] ) ), this.iframe && this.props.onIFrameInit ? this.onIFrameInit(this.iframe) : [tt, et, this.props.dir === "rtl" ? it : 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 E({ view: (() => ({ update: this.updateTools })), key: new S("toolbar-tools-update-plugin") }), new E({ filterTransaction: this.filterTransaction, key: new S("onExecute-event-plugin") }), new E({ key: B, state: { init: () => ({ preserveWhitespace: e }), apply: (n, r) => r } }), z(), j(), K(), V(), U(), q(), G() ], h = { ...k.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 = Z(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(F.join(",")); if (a) return a.focus(), !0; } return !1; } }, { defaultContent: p = "", value: u, onMount: m } = this.props, f = u && typeof u != "string" ? u : k.createDocument(new $({ nodes: J, marks: R }), u || p, { preserveWhitespace: e }), b = { state: Q.create({ plugins: [...s, T(h), T(X)], doc: f }), 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, (L) => { g = g || L.type.name === "table_caption_external"; }), g; } }, y = { plugins: s, shortcuts: h, target: this, viewProps: b, dom: t }, o = this._view = m && m.call(void 0, y) || new Y({ mount: t }, b); this.setState({ view: o }); } }; _.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 C = _; rt(C); export { C as Editor };