UNPKG

react-quill

Version:

The Quill rich-text editor as a React component.

421 lines 17.8 kB
"use strict"; /* React-Quill https://github.com/zenoamaro/react-quill */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var react_1 = __importDefault(require("react")); var react_dom_1 = __importDefault(require("react-dom")); var isEqual_1 = __importDefault(require("lodash/isEqual")); var quill_1 = __importDefault(require("quill")); var ReactQuill = /** @class */ (function (_super) { __extends(ReactQuill, _super); function ReactQuill(props) { var _this = _super.call(this, props) || this; /* Changing one of these props should cause a full re-render and a re-instantiation of the Quill editor. */ _this.dirtyProps = [ 'modules', 'formats', 'bounds', 'theme', 'children', ]; /* Changing one of these props should cause a regular update. These are mostly props that act on the container, rather than the quillized editing area. */ _this.cleanProps = [ 'id', 'className', 'style', 'placeholder', 'tabIndex', 'onChange', 'onChangeSelection', 'onFocus', 'onBlur', 'onKeyPress', 'onKeyDown', 'onKeyUp', ]; _this.state = { generation: 0, }; /* Tracks the internal selection of the Quill editor */ _this.selection = null; _this.onEditorChange = function (eventName, rangeOrDelta, oldRangeOrDelta, source) { var _a, _b, _c, _d; if (eventName === 'text-change') { (_b = (_a = _this).onEditorChangeText) === null || _b === void 0 ? void 0 : _b.call(_a, _this.editor.root.innerHTML, rangeOrDelta, source, _this.unprivilegedEditor); } else if (eventName === 'selection-change') { (_d = (_c = _this).onEditorChangeSelection) === null || _d === void 0 ? void 0 : _d.call(_c, rangeOrDelta, source, _this.unprivilegedEditor); } }; var value = _this.isControlled() ? props.value : props.defaultValue; _this.value = (value !== null && value !== void 0 ? value : ''); return _this; } ReactQuill.prototype.validateProps = function (props) { var _a; if (react_1.default.Children.count(props.children) > 1) throw new Error('The Quill editing area can only be composed of a single React element.'); if (react_1.default.Children.count(props.children)) { var child = react_1.default.Children.only(props.children); if (((_a = child) === null || _a === void 0 ? void 0 : _a.type) === 'textarea') throw new Error('Quill does not support editing on a <textarea>. Use a <div> instead.'); } if (this.lastDeltaChangeSet && props.value === this.lastDeltaChangeSet) throw new Error('You are passing the `delta` object from the `onChange` event back ' + 'as `value`. You most probably want `editor.getContents()` instead. ' + 'See: https://github.com/zenoamaro/react-quill#using-deltas'); }; ReactQuill.prototype.shouldComponentUpdate = function (nextProps, nextState) { var _this = this; var _a; this.validateProps(nextProps); // If the editor hasn't been instantiated yet, or the component has been // regenerated, we already know we should update. if (!this.editor || this.state.generation !== nextState.generation) { return true; } // Handle value changes in-place if ('value' in nextProps) { var prevContents = this.getEditorContents(); var nextContents = (_a = nextProps.value, (_a !== null && _a !== void 0 ? _a : '')); // NOTE: Seeing that Quill is missing a way to prevent edits, we have to // settle for a hybrid between controlled and uncontrolled mode. We // can't prevent the change, but we'll still override content // whenever `value` differs from current state. // NOTE: Comparing an HTML string and a Quill Delta will always trigger a // change, regardless of whether they represent the same document. if (!this.isEqualValue(nextContents, prevContents)) { this.setEditorContents(this.editor, nextContents); } } // Handle read-only changes in-place if (nextProps.readOnly !== this.props.readOnly) { this.setEditorReadOnly(this.editor, nextProps.readOnly); } // Clean and Dirty props require a render return __spreadArrays(this.cleanProps, this.dirtyProps).some(function (prop) { return !isEqual_1.default(nextProps[prop], _this.props[prop]); }); }; ReactQuill.prototype.shouldComponentRegenerate = function (nextProps) { var _this = this; // Whenever a `dirtyProp` changes, the editor needs reinstantiation. return this.dirtyProps.some(function (prop) { return !isEqual_1.default(nextProps[prop], _this.props[prop]); }); }; ReactQuill.prototype.componentDidMount = function () { this.instantiateEditor(); this.setEditorContents(this.editor, this.getEditorContents()); }; ReactQuill.prototype.componentWillUnmount = function () { this.destroyEditor(); }; ReactQuill.prototype.componentDidUpdate = function (prevProps, prevState) { var _this = this; // If we're changing one of the `dirtyProps`, the entire Quill Editor needs // to be re-instantiated. Regenerating the editor will cause the whole tree, // including the container, to be cleaned up and re-rendered from scratch. // Store the contents so they can be restored later. if (this.editor && this.shouldComponentRegenerate(prevProps)) { var delta = this.editor.getContents(); var selection = this.editor.getSelection(); this.regenerationSnapshot = { delta: delta, selection: selection }; this.setState({ generation: this.state.generation + 1 }); this.destroyEditor(); } // The component has been regenerated, so it must be re-instantiated, and // its content must be restored to the previous values from the snapshot. if (this.state.generation !== prevState.generation) { var _a = this.regenerationSnapshot, delta = _a.delta, selection_1 = _a.selection; delete this.regenerationSnapshot; this.instantiateEditor(); var editor_1 = this.editor; editor_1.setContents(delta); postpone(function () { return _this.setEditorSelection(editor_1, selection_1); }); } }; ReactQuill.prototype.instantiateEditor = function () { if (this.editor) { this.hookEditor(this.editor); } else { this.editor = this.createEditor(this.getEditingArea(), this.getEditorConfig()); } }; ReactQuill.prototype.destroyEditor = function () { if (!this.editor) return; this.unhookEditor(this.editor); }; /* We consider the component to be controlled if `value` is being sent in props. */ ReactQuill.prototype.isControlled = function () { return 'value' in this.props; }; ReactQuill.prototype.getEditorConfig = function () { return { bounds: this.props.bounds, formats: this.props.formats, modules: this.props.modules, placeholder: this.props.placeholder, readOnly: this.props.readOnly, scrollingContainer: this.props.scrollingContainer, tabIndex: this.props.tabIndex, theme: this.props.theme, }; }; ReactQuill.prototype.getEditor = function () { if (!this.editor) throw new Error('Accessing non-instantiated editor'); return this.editor; }; /** Creates an editor on the given element. The editor will be passed the configuration, have its events bound, */ ReactQuill.prototype.createEditor = function (element, config) { var editor = new quill_1.default(element, config); if (config.tabIndex != null) { this.setEditorTabIndex(editor, config.tabIndex); } this.hookEditor(editor); return editor; }; ReactQuill.prototype.hookEditor = function (editor) { // Expose the editor on change events via a weaker, unprivileged proxy // object that does not allow accidentally modifying editor state. this.unprivilegedEditor = this.makeUnprivilegedEditor(editor); // Using `editor-change` allows picking up silent updates, like selection // changes on typing. editor.on('editor-change', this.onEditorChange); }; ReactQuill.prototype.unhookEditor = function (editor) { editor.off('editor-change', this.onEditorChange); }; ReactQuill.prototype.getEditorContents = function () { return this.value; }; ReactQuill.prototype.getEditorSelection = function () { return this.selection; }; /* True if the value is a Delta instance or a Delta look-alike. */ ReactQuill.prototype.isDelta = function (value) { return value && value.ops; }; /* Special comparison function that knows how to compare Deltas. */ ReactQuill.prototype.isEqualValue = function (value, nextValue) { if (this.isDelta(value) && this.isDelta(nextValue)) { return isEqual_1.default(value.ops, nextValue.ops); } else { return isEqual_1.default(value, nextValue); } }; /* Replace the contents of the editor, but keep the previous selection hanging around so that the cursor won't move. */ ReactQuill.prototype.setEditorContents = function (editor, value) { var _this = this; this.value = value; var sel = this.getEditorSelection(); if (typeof value === 'string') { editor.setContents(editor.clipboard.convert(value)); } else { editor.setContents(value); } postpone(function () { return _this.setEditorSelection(editor, sel); }); }; ReactQuill.prototype.setEditorSelection = function (editor, range) { this.selection = range; if (range) { // Validate bounds before applying. var length_1 = editor.getLength(); range.index = Math.max(0, Math.min(range.index, length_1 - 1)); range.length = Math.max(0, Math.min(range.length, (length_1 - 1) - range.index)); editor.setSelection(range); } }; ReactQuill.prototype.setEditorTabIndex = function (editor, tabIndex) { var _a, _b; if ((_b = (_a = editor) === null || _a === void 0 ? void 0 : _a.scroll) === null || _b === void 0 ? void 0 : _b.domNode) { editor.scroll.domNode.tabIndex = tabIndex; } }; ReactQuill.prototype.setEditorReadOnly = function (editor, value) { if (value) { editor.disable(); } else { editor.enable(); } }; /* Returns a weaker, unprivileged proxy object that only exposes read-only accessors found on the editor instance, without any state-modifying methods. */ ReactQuill.prototype.makeUnprivilegedEditor = function (editor) { var e = editor; return { getHTML: function () { return e.root.innerHTML; }, getLength: e.getLength.bind(e), getText: e.getText.bind(e), getContents: e.getContents.bind(e), getSelection: e.getSelection.bind(e), getBounds: e.getBounds.bind(e), }; }; ReactQuill.prototype.getEditingArea = function () { if (!this.editingArea) { throw new Error('Instantiating on missing editing area'); } var element = react_dom_1.default.findDOMNode(this.editingArea); if (!element) { throw new Error('Cannot find element for editing area'); } if (element.nodeType === 3) { throw new Error('Editing area cannot be a text node'); } return element; }; /* Renders an editor area, unless it has been provided one to clone. */ ReactQuill.prototype.renderEditingArea = function () { var _this = this; var _a = this.props, children = _a.children, preserveWhitespace = _a.preserveWhitespace; var generation = this.state.generation; var properties = { key: generation, ref: function (instance) { _this.editingArea = instance; }, }; if (react_1.default.Children.count(children)) { return react_1.default.cloneElement(react_1.default.Children.only(children), properties); } return preserveWhitespace ? react_1.default.createElement("pre", __assign({}, properties)) : react_1.default.createElement("div", __assign({}, properties)); }; ReactQuill.prototype.render = function () { var _a; return (react_1.default.createElement("div", { id: this.props.id, style: this.props.style, key: this.state.generation, className: "quill " + (_a = this.props.className, (_a !== null && _a !== void 0 ? _a : '')), onKeyPress: this.props.onKeyPress, onKeyDown: this.props.onKeyDown, onKeyUp: this.props.onKeyUp }, this.renderEditingArea())); }; ReactQuill.prototype.onEditorChangeText = function (value, delta, source, editor) { var _a, _b; if (!this.editor) return; // We keep storing the same type of value as what the user gives us, // so that value comparisons will be more stable and predictable. var nextContents = this.isDelta(this.value) ? editor.getContents() : editor.getHTML(); if (nextContents !== this.getEditorContents()) { // Taint this `delta` object, so we can recognize whether the user // is trying to send it back as `value`, preventing a likely loop. this.lastDeltaChangeSet = delta; this.value = nextContents; (_b = (_a = this.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, value, delta, source, editor); } }; ReactQuill.prototype.onEditorChangeSelection = function (nextSelection, source, editor) { var _a, _b, _c, _d, _e, _f; if (!this.editor) return; var currentSelection = this.getEditorSelection(); var hasGainedFocus = !currentSelection && nextSelection; var hasLostFocus = currentSelection && !nextSelection; if (isEqual_1.default(nextSelection, currentSelection)) return; this.selection = nextSelection; (_b = (_a = this.props).onChangeSelection) === null || _b === void 0 ? void 0 : _b.call(_a, nextSelection, source, editor); if (hasGainedFocus) { (_d = (_c = this.props).onFocus) === null || _d === void 0 ? void 0 : _d.call(_c, nextSelection, source, editor); } else if (hasLostFocus) { (_f = (_e = this.props).onBlur) === null || _f === void 0 ? void 0 : _f.call(_e, currentSelection, source, editor); } }; ReactQuill.prototype.focus = function () { if (!this.editor) return; this.editor.focus(); }; ReactQuill.prototype.blur = function () { if (!this.editor) return; this.selection = null; this.editor.blur(); }; ReactQuill.displayName = 'React Quill'; /* Export Quill to be able to call `register` */ ReactQuill.Quill = quill_1.default; ReactQuill.defaultProps = { theme: 'snow', modules: {}, readOnly: false, }; return ReactQuill; }(react_1.default.Component)); /* Small helper to execute a function in the next micro-tick. */ function postpone(fn) { Promise.resolve().then(fn); } module.exports = ReactQuill; //# sourceMappingURL=index.js.map