react-simple-wysiwyg
Version:
Simple and lightweight React WYSIWYG editor
455 lines (433 loc) âĒ 21.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('../node_modules/react/index.js')) :
typeof define === 'function' && define.amd ? define(['exports', '../node_modules/react/index.js'], factory) :
(global = global || self, factory(global.ReactSimpleWysiwyg = {}, global.React));
}(this, function (exports, React) { 'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
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);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(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);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
}
function compare(a, b, keys) {
return keys.every(function (key) {
return a[key] === b[key];
});
}
function deepMerge(target) {
var _a, _b;
var sources = [];
for (var _i = 1; _i < arguments.length; _i++) {
sources[_i - 1] = arguments[_i];
}
if (!sources.length) {
return target;
}
var source = sources.shift();
if (isObject(target) && isObject(source)) {
for (var key in source) {
if (!source.hasOwnProperty(key)) {
continue;
}
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, (_a = {}, _a[key] = {}, _a));
}
deepMerge(target[key], source[key]);
}
else {
Object.assign(target, (_b = {}, _b[key] = source[key], _b));
}
}
}
return deepMerge.apply(void 0, [target].concat(sources));
}
function findLastTextNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node;
}
var children = node.childNodes;
for (var i = children.length - 1; i >= 0; i--) {
var textNode = findLastTextNode(children[i]);
if (textNode !== null) {
return textNode;
}
}
return null;
}
function getSelectedNode() {
if (document.selection) {
return document.selection.createRange().parentElement();
}
var selection = window.getSelection();
if (selection.rangeCount > 0) {
return selection.getRangeAt(0).startContainer.parentNode;
}
}
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function normalizeHtml(str) {
return str && str.replace(/ |\u202F|\u00A0/g, ' ');
}
function replaceCaret(el) {
// Place the caret at the end of the element
var target = findLastTextNode(el);
// do not move caret if element was not focused
var isTargetFocused = document.activeElement === el;
if (target !== null && target.nodeValue !== null && isTargetFocused) {
var range = document.createRange();
var sel = window.getSelection();
range.setStart(target, target.nodeValue.length);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
if (el instanceof HTMLElement) {
el.focus();
}
}
}
/**
* Based on https://github.com/lovasoa/react-contenteditable
* A simple component for an html element with editable contents.
*/
var ContentEditable = /** @class */ (function (_super) {
__extends(ContentEditable, _super);
function ContentEditable(props) {
var _this = _super.call(this, props) || this;
_this.previousValue = props.value;
_this.onChange = _this.onChange.bind(_this);
_this.setElementRef = _this.setElementRef.bind(_this);
return _this;
}
ContentEditable.prototype.shouldComponentUpdate = function (nextProps) {
var props = this.props;
if (!this.el) {
return true;
}
if (normalizeHtml(nextProps.value) !== normalizeHtml(this.el.innerHTML)) {
return true;
}
return !compare(props, nextProps, ['disabled', 'tagName', 'className']);
};
ContentEditable.prototype.componentDidUpdate = function () {
if (!this.el) {
return;
}
if (this.props.value !== this.el.innerHTML) {
this.previousValue = this.props.value;
this.el.innerHTML = this.props.value;
}
replaceCaret(this.el);
};
ContentEditable.prototype.setElementRef = function (el) {
var contentEditableRef = this.props.contentEditableRef;
this.el = el;
contentEditableRef && contentEditableRef(el);
};
ContentEditable.prototype.onChange = function (event) {
if (!this.el) {
return;
}
var value = this.el.innerHTML;
var previous = this.previousValue;
this.previousValue = value;
if (this.props.onChange && value !== previous) {
this.props.onChange(__assign({}, event, { target: { value: value } }));
}
};
ContentEditable.prototype.render = function () {
var _a = this.props, contentEditableRef = _a.contentEditableRef, tagName = _a.tagName, value = _a.value, props = __rest(_a, ["contentEditableRef", "tagName", "value"]);
return React.createElement(tagName || 'div', __assign({}, props, { contentEditable: !this.props.disabled, dangerouslySetInnerHTML: { __html: value }, onBlur: this.props.onBlur || this.onChange, onInput: this.onChange, onKeyDown: this.props.onKeyDown || this.onChange, onKeyUp: this.props.onKeyUp || this.onChange, ref: this.setElementRef }));
};
return ContentEditable;
}(React.Component));
var styles = {
button: {
normal: {
backgroundColor: 'unset',
border: 'none',
color: '#222',
height: 24,
outline: 'none',
padding: 0,
verticalAlign: 'top',
width: 24,
},
hovered: {
backgroundColor: '#eaeaea',
},
active: {
backgroundColor: '#e0e0e0',
},
},
contentEditable: {
flex: 1,
outline: 'none',
padding: 5,
},
dropdown: {
boxSizing: 'border-box',
height: 20,
marginTop: 2,
outline: 'none',
verticalAlign: 'top',
},
editor: {
border: '1px solid #ddd',
borderRadius: 3,
display: 'flex',
flexDirection: 'column',
minHeight: 100,
},
separator: {
backgroundColor: '#ddd',
display: 'inline-block',
height: 20,
margin: 2,
verticalAlign: 'top',
width: 1,
},
};
var EditorContext = React.createContext({
styles: styles,
});
var Editor = /** @class */ (function (_super) {
__extends(Editor, _super);
function Editor(props) {
var _this = _super.call(this, props) || this;
_this.state = {};
_this.onClickOutside = _this.onClickOutside.bind(_this);
_this.onTextSelect = _this.onTextSelect.bind(_this);
_this.setContentEditableRef = _this.setContentEditableRef.bind(_this);
return _this;
}
Editor.prototype.componentDidMount = function () {
document.addEventListener('click', this.onClickOutside);
};
Editor.prototype.componentWillUnmount = function () {
document.removeEventListener('click', this.onClickOutside);
};
Editor.prototype.setContentEditableRef = function (el) {
this.setState({ contentEditable: el });
this.props.contentEditableRef && this.props.contentEditableRef(el);
};
Editor.prototype.onClickOutside = function (event) {
var contentEditable = this.state.contentEditable;
if (event.target === contentEditable) {
return;
}
if (contentEditable && contentEditable.contains(event.target)) {
return;
}
this.setState({ selection: null });
};
Editor.prototype.onTextSelect = function (e) {
this.props.onSelect && this.props.onSelect(e);
this.setState({ selection: getSelectedNode() });
};
Editor.prototype.render = function () {
var _a = this.props, children = _a.children, styles$1 = _a.styles, props = __rest(_a, ["children", "styles"]);
var _b = this.state, contentEditable = _b.contentEditable, selection = _b.selection;
var allStyles = deepMerge({}, styles, styles$1);
var context = {
el: contentEditable,
selection: selection,
styles: allStyles,
};
return (React.createElement("div", { style: context.styles.editor },
React.createElement(EditorContext.Provider, { value: context },
children,
React.createElement(ContentEditable, __assign({}, props, { contentEditableRef: this.setContentEditableRef, onSelect: this.onTextSelect, style: allStyles.contentEditable })))));
};
return Editor;
}(React.PureComponent));
function withEditorContext(Component) {
WithEditorContext.displayName =
"withEditorContext(" + (Component.displayName || Component.name) + ")";
return WithEditorContext;
function WithEditorContext(props) {
return (React.createElement(EditorContext.Consumer, null, function (context) { return (React.createElement(Component, __assign({}, props, { el: context.el, selection: context.selection, styles: context.styles }))); }));
}
}
function OrderedListIcon() {
return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", style: { verticalAlign: 'text-top' } },
React.createElement("path", { fill: "currentColor", d: "M 6.99938,12.998L 6.99938,10.998L 20.9994,10.998L 20.9994,12.998L 6.99938,12.998 Z M 6.99938,18.9981L 6.99938,16.9981L 20.9994,16.9981L 20.9994,18.9981L 6.99938,18.9981 Z M 6.99938,6.99809L 6.99938,4.99809L 20.9994,4.99809L 20.9994,6.99809L 6.99938,6.99809 Z M 2.99938,7.99809L 2.99938,4.99809L 1.99938,4.99809L 1.99938,3.99809L 3.99938,3.99809L 3.99938,7.99809L 2.99938,7.99809 Z M 1.99938,16.9981L 1.99938,15.9981L 4.99938,15.9981L 4.99938,19.9981L 1.99938,19.9981L 1.99938,18.9981L 3.99938,18.9981L 3.99938,18.4981L 2.99938,18.4981L 2.99938,17.4981L 3.99938,17.4981L 3.99938,16.9981L 1.99938,16.9981 Z M 4.25,10C 4.66421,10 5,10.3358 5,10.75C 5,10.9524 4.91983,11.1361 4.7895,11.271L 3.11983,13L 5,13L 5,14L 2,14L 2,13.0782L 4,11L 2,11L 2,10L 4.25,10 Z " })));
}
function UnorderedListIcon() {
return (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", style: { verticalAlign: 'text-top' } },
React.createElement("path", { fill: "currentColor", d: "M 7,5L 21,5L 21,7L 7,7L 7,5 Z M 7,13L 7,11L 21,11L 21,13L 7,13 Z M 4,4.50001C 4.83,4.50001 5.5,5.16993 5.5,6.00001C 5.5,6.83008 4.83,7.50001 4,7.50001C 3.17,7.50001 2.5,6.83008 2.5,6.00001C 2.5,5.16993 3.17,4.50001 4,4.50001 Z M 4,10.5C 4.83,10.5 5.5,11.17 5.5,12C 5.5,12.83 4.83,13.5 4,13.5C 3.17,13.5 2.5,12.83 2.5,12C 2.5,11.17 3.17,10.5 4,10.5 Z M 7,19L 7,17L 21,17L 21,19L 7,19 Z M 4,16.5C 4.83,16.5 5.5,17.17 5.5,18C 5.5,18.83 4.83,19.5 4,19.5C 3.17,19.5 2.5,18.83 2.5,18C 2.5,17.17 3.17,16.5 4,16.5 Z " })));
}
// tslint:disable:max-line-length
var BtnBold = createButton('Bold', 'ð', 'bold');
var BtnClearFormatting = createButton('Clear formatting', 'TĖēâ', 'removeFormat');
var BtnItalic = createButton('Italic', 'ð°', 'italic');
var BtnLink = createButton('Link', 'ð', function (selected) {
if (selected && selected.nodeName === 'A') {
document.execCommand('unlink');
}
else {
document.execCommand('createLink', false, prompt('URL'));
}
});
var BtnNumberedList = createButton('Numbered list', React.createElement(OrderedListIcon, null), 'insertOrderedList');
var BtnRedo = createButton('Redo', 'â·', 'redo');
var BtnUnderline = createButton('Underline', React.createElement("span", { style: { textDecoration: 'underline' } }, "\uD835\uDC14"), 'underline');
var BtnUndo = createButton('Undo', 'âķ', 'undo');
var BtnBulletList = createButton('Bullet list', React.createElement(UnorderedListIcon, null), 'insertUnorderedList');
function Button(props) {
var _a = React.useState(false), hovered = _a[0], setHovered = _a[1];
var active = props.active, styles = props.styles, el = props.el, selection = props.selection, inputProps = __rest(props, ["active", "styles", "el", "selection"]);
var style = __assign({}, styles.button.normal, props.style, (hovered ? styles.button.hovered : {}), (hovered ? props.hoverStyle : {}), (active ? styles.button.active : {}));
var onHover = function (e) {
setHovered(true);
props.onMouseEnter && props.onMouseEnter(e);
};
var onUnHover = function (e) {
setHovered(false);
props.onMouseLeave && props.onMouseLeave(e);
};
return (React.createElement("button", __assign({}, inputProps, { style: style, onMouseEnter: onHover, onMouseLeave: onUnHover })));
}
function createButton(title, content, command) {
ButtonFactory.displayName = title.replace(/\s/g, '');
return withEditorContext(ButtonFactory);
function ButtonFactory(props) {
var selection = props.selection, buttonProps = __rest(props, ["selection"]);
var active = false;
if (typeof command === 'string') {
active = !!selection && document.queryCommandState(command);
}
return (React.createElement(Button, __assign({ title: title }, buttonProps, { onMouseDown: action, active: active }), content));
function action() {
if (typeof command === 'function') {
command(selection);
}
else {
document.execCommand(command);
}
}
}
}
var BtnStyles = createDropdown('Styles', [
['Normal', 'formatBlock', 'DIV'],
['ððēðŪðąðēðŋ ð', 'formatBlock', 'H1'],
['Header 2', 'formatBlock', 'H2'],
['ðēððð', 'formatBlock', 'PRE'],
]);
function createDropdown(title, items) {
DropdownFactory.displayName = title;
return withEditorContext(DropdownFactory);
function DropdownFactory(props) {
var selection = props.selection, ddProps = __rest(props, ["selection"]);
return (React.createElement(Dropdown, __assign({}, ddProps, { onChange: onChange, title: title, items: items })));
function onChange(e) {
var selected = parseInt(e.target.value, 10);
var _a = items[selected], command = _a[1], commandArgument = _a[2];
e.preventDefault();
e.target.selectedIndex = 0;
if (typeof command === 'function') {
command(selection);
}
else {
document.execCommand(command, false, commandArgument);
}
}
}
}
function Dropdown(props) {
var el = props.el, items = props.items, selected = props.selected, selection = props.selection, styles = props.styles, inputProps = __rest(props, ["el", "items", "selected", "selection", "styles"]);
var style = __assign({}, styles.dropdown, props.style);
return (React.createElement("select", __assign({}, inputProps, { value: selected, style: style }),
React.createElement("option", { hidden: true }, props.title),
items.map(function (item, index) { return (React.createElement("option", { key: index, value: index }, item[0])); })));
}
function Separator(context) {
return (React.createElement("span", { style: context.styles.separator }));
}
var WrappedSeparator = withEditorContext(Separator);
function Toolbar(props) {
var rootStyle = __assign({}, styles$1.root, props.style);
return (React.createElement("div", __assign({}, props, { style: rootStyle })));
}
var styles$1 = {
root: {
backgroundColor: '#f5f5f5',
borderBottom: '1px solid #ddd',
},
};
function DefaultEditor(props) {
return (React.createElement(Editor, __assign({}, props),
React.createElement(Toolbar, null,
React.createElement(BtnUndo, null),
React.createElement(BtnRedo, null),
React.createElement(WrappedSeparator, null),
React.createElement(BtnBold, null),
React.createElement(BtnItalic, null),
React.createElement(BtnUnderline, null),
React.createElement(WrappedSeparator, null),
React.createElement(BtnNumberedList, null),
React.createElement(BtnBulletList, null),
React.createElement(WrappedSeparator, null),
React.createElement(BtnLink, null),
React.createElement(BtnClearFormatting, null),
React.createElement(WrappedSeparator, null),
React.createElement(BtnStyles, null))));
}
exports.BtnBold = BtnBold;
exports.BtnBulletList = BtnBulletList;
exports.BtnClearFormatting = BtnClearFormatting;
exports.BtnItalic = BtnItalic;
exports.BtnLink = BtnLink;
exports.BtnNumberedList = BtnNumberedList;
exports.BtnRedo = BtnRedo;
exports.BtnStyles = BtnStyles;
exports.BtnUnderline = BtnUnderline;
exports.BtnUndo = BtnUndo;
exports.Button = Button;
exports.ContentEditable = ContentEditable;
exports.DefaultEditor = DefaultEditor;
exports.Dropdown = Dropdown;
exports.Editor = Editor;
exports.Separator = WrappedSeparator;
exports.Toolbar = Toolbar;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=index.umd.js.map