react-simple-wysiwyg
Version:
Simple and lightweight React WYSIWYG editor
400 lines (375 loc) âĒ 18.7 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
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 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function autoconfigureTextDirection(el) {
if (el) {
var text = el.textContent;
var rtlPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
el.style.direction = text && rtlPattern.test(text[0]) ? 'rtl' : 'ltr';
}
}
function cls() {
var classNames = [];
for (var _i = 0; _i < arguments.length; _i++) {
classNames[_i] = arguments[_i];
}
return classNames.filter(Boolean).join(' ');
}
function getSelectedNode() {
if (document.selection) {
return document.selection.createRange().parentElement();
}
var selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
return selection.getRangeAt(0).startContainer.parentNode || undefined;
}
return undefined;
}
function normalizeHtml(str) {
return str
? str.replace(/ |\u202F|\u00A0/g, ' ').replace(/<br \/>/g, '<br>')
: '';
}
function replaceCaret(el) {
// Place the caret at the end of the element
var target = document.createTextNode('');
el.appendChild(target);
// do not move caret if element was not focused
var isTargetFocused = document.activeElement === el;
if (target !== null && target.nodeValue !== null && isTargetFocused) {
var sel = window.getSelection();
if (sel !== null) {
var range = document.createRange();
range.setStart(target, target.nodeValue.length);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
if (el instanceof HTMLElement)
el.focus();
}
}
function setForwardRef(el, ref) {
if (typeof ref === 'function') {
ref(el);
}
else if (typeof ref === 'object' && ref) {
// eslint-disable-next-line no-param-reassign
ref.current = el;
}
}
/**
* Based on https://github.com/lovasoa/react-contenteditable
* A simple component for a html element with editable contents.
*/
var ContentEditable = React.memo(React.forwardRef(function ContentEditable(_a, ref) {
var
// Some properties are used here only as useMemo dependencies
className = _a.className, disabled = _a.disabled, tagName = _a.tagName, _b = _a.value, value = _b === void 0 ? '' : _b, placeholder = _a.placeholder, rest = __rest(_a, ["className", "disabled", "tagName", "value", "placeholder"]);
var elRef = React.useRef(null);
var htmlRef = React.useRef(value);
var restRef = React.useRef(rest);
React.useEffect(function () {
restRef.current = rest;
var el = elRef.current;
if (el && normalizeHtml(htmlRef.current) !== normalizeHtml(value)) {
htmlRef.current = value;
el.innerHTML = value;
replaceCaret(el);
}
});
return React.useMemo(function () {
function onSetRef($el) {
elRef.current = $el;
autoconfigureTextDirection($el);
setForwardRef($el, ref);
}
function onChange(event) {
var _a, _b;
var el = elRef.current;
if (!el) {
return;
}
var elementHtml = el.innerHTML;
if (elementHtml !== htmlRef.current) {
(_b = (_a = restRef.current).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, __assign(__assign({}, event), { target: {
value: elementHtml,
name: rest.name,
} }));
}
autoconfigureTextDirection(el);
htmlRef.current = elementHtml;
}
var cssClass = cls('rsw-ce', className);
return React.createElement(tagName || 'div', __assign(__assign({}, rest), { className: cssClass, contentEditable: !disabled, dangerouslySetInnerHTML: { __html: value }, onBlur: function (e) {
return (restRef.current.onBlur || onChange)(e);
}, onInput: onChange, onKeyDown: function (e) {
return (restRef.current.onKeyDown || onChange)(e);
}, onKeyUp: function (e) {
return (restRef.current.onKeyUp || onChange)(e);
}, placeholder: placeholder, ref: onSetRef }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [className, disabled, placeholder, tagName]);
}));
var EditorContext = React.createContext(undefined);
function EditorProvider(_a) {
var children = _a.children;
var _b = React.useState({
htmlMode: false,
update: update,
}), state = _b[0], setState = _b[1];
function update(attrs) {
setState(function (prevState) {
return __assign(__assign({}, prevState), attrs);
});
}
return (React.createElement(EditorContext.Provider, { value: state }, children));
}
function useEditorState() {
var context = React.useContext(EditorContext);
if (!context) {
throw new Error('You should wrap your component by EditorProvider');
}
return context;
}
var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=true===r.prepend?"prepend":"append",d=true===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
var css = ".rsw-editor{border:1px solid #ddd;border-radius:.375rem;display:flex;flex-direction:column;min-height:100px;overflow:hidden}.rsw-ce{flex:1 0 auto;padding:.5rem}.rsw-ce:focus{outline:1px solid #668}.rsw-ce[contentEditable=true]:empty:not(:focus):before{color:grey;content:attr(placeholder);pointer-events:none}.rsw-html{background:transparent;border:none;font-family:monospace,Courier New}.rsw-separator{align-self:stretch;border-right:1px solid #ddd;display:flex;margin:0 3px}.rsw-dd{box-sizing:border-box;outline:none}.rsw-btn{background:transparent;border:0;color:#222;cursor:pointer;font-size:1em;height:2em;outline:none;padding:0;width:2em}.rsw-btn:hover{background:#eaeaea}.rsw-btn[data-active=true]{background:#e0e0e0}.rsw-toolbar{align-items:center;background-color:#f5f5f5;border-bottom:1px solid #ddd;display:flex}";
n(css,{});
var Editor = React.forwardRef(function Editor(_a, ref) {
var autoFocus = _a.autoFocus, children = _a.children, containerProps = _a.containerProps, onSelect = _a.onSelect, rest = __rest(_a, ["autoFocus", "children", "containerProps", "onSelect"]);
var editorState = useEditorState();
React.useEffect(function () {
document.addEventListener('click', onClickOutside);
return function () { return document.removeEventListener('click', onClickOutside); };
});
function onClickOutside(event) {
var _a;
if (event.target === editorState.$el) {
return;
}
if ((_a = editorState.$el) === null || _a === void 0 ? void 0 : _a.contains(event.target)) {
return;
}
editorState.update({ $selection: undefined });
}
function onTextSelect(event) {
onSelect === null || onSelect === void 0 ? void 0 : onSelect(event);
editorState.update({ $selection: getSelectedNode() });
}
function setContentEditableRef($el) {
editorState.update({ $el: $el });
setForwardRef($el, ref);
if (autoFocus && $el && editorState.$el === undefined) {
$el.focus();
}
}
var cssClass = cls('rsw-editor', containerProps === null || containerProps === void 0 ? void 0 : containerProps.className);
if (editorState.htmlMode) {
return (React.createElement("div", __assign({}, containerProps, { className: cssClass }),
children,
React.createElement("textarea", __assign({}, rest, { className: "rsw-ce rsw-html" }))));
}
return (React.createElement("div", __assign({}, containerProps, { className: cssClass }),
children,
React.createElement(ContentEditable, __assign({}, rest, { ref: setContentEditableRef, onSelect: onTextSelect }))));
});
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: "M6.99938 12.998v-2H20.9994v2H6.99938zm0 6.0001v-2H20.9994v2H6.99938zm0-12.00001v-2H20.9994v2H6.99938zm-4 1v-3h-1v-1h2v4h-1zm-1 9.00001v-1h3v4h-3v-1h2v-.5h-1v-1h1v-.5h-2zM4.25 10c.41421 0 .75.3358.75.75 0 .2024-.08017.3861-.2105.521L3.11983 13H5v1H2v-.9218L4 11H2v-1h2.25z" })));
}
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: "M7 5h14v2H7V5zm0 8v-2h14v2H7zM4 4.50001c.83 0 1.5.66992 1.5 1.5 0 .83007-.67 1.5-1.5 1.5s-1.5-.66993-1.5-1.5c0-.83008.67-1.5 1.5-1.5zM4 10.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5zM7 19v-2h14v2H7zm-3-2.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5z" })));
}
var BtnBold = createButton('Bold', 'ð', 'bold');
var BtnBulletList = createButton('Bullet list', React.createElement(UnorderedListIcon, null), 'insertUnorderedList');
var BtnClearFormatting = createButton('Clear formatting', 'TĖēâ', 'removeFormat');
var BtnItalic = createButton('Italic', 'ð°', 'italic');
var BtnStrikeThrough = createButton('Strike through', React.createElement("s", null, "ab"), 'strikeThrough');
var BtnLink = createButton('Link', 'ð', function (_a) {
var $selection = _a.$selection;
if (($selection === null || $selection === void 0 ? void 0 : $selection.nodeName) === 'A') {
document.execCommand('unlink');
}
else {
// eslint-disable-next-line no-alert
document.execCommand('createLink', false, prompt('URL', '') || undefined);
}
});
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');
function createButton(title, content, command) {
ButtonFactory.displayName = title.replace(/\s/g, '');
return ButtonFactory;
function ButtonFactory(props) {
var editorState = useEditorState();
var $el = editorState.$el, $selection = editorState.$selection;
var active = false;
if (typeof command === 'string') {
active = !!$selection && document.queryCommandState(command);
}
function onAction(e) {
e.preventDefault();
if (document.activeElement !== $el) {
$el === null || $el === void 0 ? void 0 : $el.focus();
}
if (typeof command === 'function') {
command(editorState);
}
else {
document.execCommand(command);
}
}
if (editorState.htmlMode) {
return null;
}
return (React.createElement("button", __assign({ className: "rsw-btn", "data-active": active, onMouseDown: onAction, tabIndex: -1, title: title, type: "button" }, props), content));
}
}
var BtnStyles = createDropdown('Styles', [
['Normal', 'formatBlock', 'DIV'],
['ððēðŪðąðēðŋ ð', 'formatBlock', 'H1'],
['Header 2', 'formatBlock', 'H2'],
['ðēððð', 'formatBlock', 'PRE'],
]);
function createDropdown(title, items) {
DropdownFactory.displayName = title;
return DropdownFactory;
function DropdownFactory(props) {
var editorState = useEditorState();
var $el = editorState.$el, $selection = editorState.$selection, htmlMode = editorState.htmlMode;
if (htmlMode) {
return null;
}
var activeIndex = items.findIndex(function (item) { return item[1] === 'formatBlock' && ($selection === null || $selection === void 0 ? void 0 : $selection.nodeName) === item[2]; });
return (React.createElement(Dropdown, __assign({}, props, { items: items, onChange: onChange, selected: activeIndex, tabIndex: -1, title: title })));
function onChange(e) {
var target = e.target;
var selectedValue = target.value;
var selectedIndex = parseInt(selectedValue, 10);
var _a = items[selectedIndex] || [], command = _a[1], commandArgument = _a[2];
e.preventDefault();
if (document.activeElement !== $el) {
$el === null || $el === void 0 ? void 0 : $el.focus();
}
if (typeof command === 'function') {
command(editorState);
}
else if (command) {
document.execCommand(command, false, commandArgument);
}
setTimeout(function () { return (target.value = selectedValue); }, 10);
}
}
}
function Dropdown(_a) {
var items = _a.items, selected = _a.selected, inputProps = __rest(_a, ["items", "selected"]);
return (React.createElement("select", __assign({ className: "rsw-dd" }, inputProps, { value: selected }),
React.createElement("option", { hidden: true }, inputProps.title),
items.map(function (item, index) { return (React.createElement("option", { key: item[2], value: index }, item[0])); })));
}
function HtmlButton(_a) {
var rest = __rest(_a, []);
var editorState = useEditorState();
function onClick() {
editorState.update({
htmlMode: !editorState.htmlMode,
});
}
return (React.createElement("button", __assign({ className: "rsw-btn", "data-active": editorState.htmlMode, onClick: onClick, tabIndex: -1, title: "HTML mode", type: "button" }, rest), "</>"));
}
function Separator(props) {
var editorState = useEditorState();
if (editorState.htmlMode) {
return null;
}
return React.createElement("div", __assign({ className: "rsw-separator" }, props));
}
function Toolbar(props) {
return React.createElement("div", __assign({ className: "rsw-toolbar" }, props));
}
var DefaultEditor = React.forwardRef(function DefaultEditor(props, ref) {
return (React.createElement(EditorProvider, null,
React.createElement(Editor, __assign({}, props, { ref: ref }), props.children || (React.createElement(Toolbar, null,
React.createElement(BtnUndo, null),
React.createElement(BtnRedo, null),
React.createElement(Separator, null),
React.createElement(BtnBold, null),
React.createElement(BtnItalic, null),
React.createElement(BtnUnderline, null),
React.createElement(BtnStrikeThrough, null),
React.createElement(Separator, null),
React.createElement(BtnNumberedList, null),
React.createElement(BtnBulletList, null),
React.createElement(Separator, null),
React.createElement(BtnLink, null),
React.createElement(BtnClearFormatting, null),
React.createElement(HtmlButton, null),
React.createElement(Separator, 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.BtnStrikeThrough = BtnStrikeThrough;
exports.BtnStyles = BtnStyles;
exports.BtnUnderline = BtnUnderline;
exports.BtnUndo = BtnUndo;
exports.ContentEditable = ContentEditable;
exports.DefaultEditor = DefaultEditor;
exports.Dropdown = Dropdown;
exports.Editor = Editor;
exports.EditorContext = EditorContext;
exports.EditorProvider = EditorProvider;
exports.HtmlButton = HtmlButton;
exports.Separator = Separator;
exports.Toolbar = Toolbar;
exports.createButton = createButton;
exports.createDropdown = createDropdown;
exports.default = DefaultEditor;
exports.useEditorState = useEditorState;
//# sourceMappingURL=index.cjs.js.map
;