Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bandicoot

Package Overview
Dependencies
Maintainers
5
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bandicoot - npm Package Compare versions

Comparing version 4.2.0 to 4.2.1

1144

dist/bandicoot.esm.js

@@ -1,2 +0,1144 @@

import React,{useRef,useEffect,forwardRef,useContext,useState}from"react";function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _extends(){return _extends=Object.assign||function(a){for(var b,c=1;c<arguments.length;c++)for(var d in b=arguments[c],b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d]);return a},_extends.apply(this,arguments)}function ownKeys(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function _objectSpread2(a){for(var b,c=1;c<arguments.length;c++)b=null==arguments[c]?{}:arguments[c],c%2?ownKeys(b,!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function RichTextContainer(a){const b=useRef(Object.assign({},defaultContextValue)),c=b.current,d=useRef([]),e=useRef([]),f=useRef([]),g=useRef([]);return useEffect(()=>{const a=c.getContentEditableElement();a&&a.innerHTML&&c.fireNewHTML()},[c]),c.addSelectionChangedListener=a=>{d.current.push(a)},c.removeSelectionChangedListener=a=>{d.current=d.current.filter(b=>b!==a)},c.fireSelectionChanged=()=>{d.current.forEach(a=>a())},c.addBlurListener=a=>{e.current.push(a)},c.removeBlurListener=a=>{e.current=e.current.filter(b=>b!==a)},c.fireBlur=()=>{e.current.forEach(a=>a())},c.addNewHTMLListener=a=>{f.current.push(a)},c.removeNewHTMLListener=a=>{f.current=f.current.filter(b=>b!==a)},c.fireNewHTML=()=>{f.current.forEach(a=>a())},c.numSerializers=()=>g.current.length,c.addSerializer=a=>{g.current.push(a)},c.removeSerializer=a=>{g.current=g.current.filter(b=>b!==a)},c.serialize=a=>(g.current.forEach(b=>b(a)),a.innerHTML),React.createElement(RichTextContext.Provider,{value:c},a.children)}const noop=()=>{},defaultContextValue={addSelectionChangedListener:noop,removeSelectionChangedListener:noop,fireSelectionChanged:noop,selectRangeFromBeforeBlur:noop,getRangeBeforeBlur:noop,addBlurListener:noop,removeBlurListener:noop,fireBlur:noop,addNewHTMLListener:noop,removeNewHTMLListener:noop,fireNewHTML:noop,isFocused:noop,getContentEditableElement:noop,numSerializers:()=>0,addSerializer:noop,removeSerializer:noop,serialize:noop,sanitizeHTML:noop},RichTextContext=React.createContext(defaultContextValue),noop$1=()=>{},noopWithReturn=a=>a;let globalBandicootId=0;const RichTextEditor=forwardRef((a,b)=>{function c(b){b.preventDefault(),b.stopPropagation();let c=m.sanitizeHTML((window.clipboardData||b.clipboardData).getData("text/html"),"pasteHTML"),d=a.pasteFn(c);!1!==d&&document.execCommand("insertHTML",null,d)}function d(){const a=document.createRange();a.selectNodeContents(n.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function e(){if(p()){const a=window.getSelection();0<a.rangeCount&&(o.current=window.getSelection().getRangeAt(0)),m.fireSelectionChanged()}}function f(){const b=j();b!==s&&(t(b),a.save(b))}function g(){let a=n.current.innerHTML;if(0<m.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=m.serialize(b.body)}return a}function h(b,c){c?(n.current.innerHTML=m.sanitizeHTML(b,"setHTML"),a.autoFocus&&k()):(d(),n.current.innerHTML=m.sanitizeHTML(b,"setHTML"),k()),m.fireNewHTML()}function i(){h("")}function j(){return m.sanitizeHTML(g(),"getHTML")}function k(){n.current.focus(),q(!0)}function l(){return!window.chrome||!window.chrome.webstore&&!window.chrome.runtime?{opacity:"0.54"}:{color:"rgb(117, 117, 117)"}}if("function"!=typeof a.sanitizeHTML)throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");const m=useContext(RichTextContext);m.sanitizeHTML=a.sanitizeHTML;const n=useRef(null),o=useRef(null),{isFocused:p,setFocused:q}=useSynchronousFocusState(),r=useRef(globalBandicootId++),[s,t]=useState(()=>m.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[u,v]=useState(!1);if(b){const a={setHTML:h,resetEditor:i,getHTML:j,focus:k};"function"==typeof b?b({current:a}):b.current=a}useEffect(()=>(n.current.addEventListener("paste",c),()=>n.current.removeEventListener("paste",c)),[a.pasteFn]),useEffect(()=>(document.addEventListener("selectionchange",e),()=>document.removeEventListener("selectionchange",e))),useEffect(()=>{if(n.current)for(let a=n.current;a.parentNode;)a=a.parentNode,"SPAN"===a.tagName&&console.warn("A span tag has been detected in the parents of <RichTextEditor>. This has been known to cause issues. https://github.com/CanopyTax/bandicoot/issues/69")},[n.current]),useEffect(()=>{if(a.save&&a.unchangedInterval&&n.current&&p()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(f,a.unchangedInterval)});return c.observe(n.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,n.current,p()]),useEffect(()=>{if(!1===p()){const a=setTimeout(()=>{m.fireBlur(),f()},100);return()=>{clearTimeout(a)}}},[p()]),useEffect(()=>{m.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(n.current&&document.activeElement!==n.current&&!n.current.contains(document.activeElement)){if(a.usePreviousRange&&o.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(o.current)}else n.current.focus();q(!0),setTimeout(e)}},m.getRangeFromBeforeBlur=()=>o.current,m.isFocused=p,m.getContentEditableElement=()=>n.current}),useEffect(()=>{!u&&a.initialHTML&&(v(!0),h(a.initialHTML,!0))},[u,h,m]),useEffect(()=>{if(a.placeholder){const b=document.createElement("style");b.textContent=".bandicoot-id-".concat(r.current,":empty:before { content: attr(data-placeholder); }"),document.head.appendChild(b);const c=Array.prototype.slice.call(document.styleSheets).find(a=>a.ownerNode===b),d=c.cssRules[0].style,e=a.placeholderStyle?a.placeholderStyle:l();for(let b in e)d[b]=a.placeholderStyle?a.placeholderStyle[b]:e[b];return()=>b.parentNode.removeChild(b)}},[a.placeholder,a.placeholderStyle,r.current]);const w=a.style||{};return React.createElement("div",{contentEditable:!a.disabled,onBlur:()=>q(!1),onFocus:function(){q(!0);const a=window.getSelection();0<a.rangeCount&&(o.current=a.getRangeAt(0))},ref:n,className:a.className+" bandicoot-id-"+r.current,style:_objectSpread2({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},w),"data-placeholder":a.placeholder})});function useSynchronousFocusState(){const a=useRef(null),[b,c]=useState(!1);return{isFocused:function(){return a.current},setFocused:function(d){a.current=d,c(!b)}}}RichTextEditor.defaultProps={className:"",initialHTML:"",save:noop$1,placeholder:"",pasteFn:noopWithReturn};function useDocumentExecCommand(a){const b=useContext(RichTextContext);return{performCommand(c){b.selectRangeFromBeforeBlur(),document.execCommand(a)},performCommandWithValue(c){b.selectRangeFromBeforeBlur();document.execCommand(a,null,c)}}}function useFormatBlock(){const{performCommandWithValue:a}=useDocumentExecCommand("formatBlock");return{formatBlock(b){b===document.queryCommandValue("formatBlock")?a("div"):a(b)}}}const defaultActiveInfo={isActive:!1,value:!1};function useDocumentQueryCommandState(a,b){function c(){const c=document.queryCommandValue(a),d=b?b===c:document.queryCommandState(a);(d!==e.isActive||c!==e.value)&&f({isActive:d,value:c})}function d(){f(defaultActiveInfo)}const[e,f]=useState(defaultActiveInfo),g=useContext(RichTextContext);return useEffect(()=>(g.addSelectionChangedListener(c),()=>g.removeSelectionChangedListener(c)),[e,f]),useEffect(()=>(g.addBlurListener(d),()=>g.removeBlurListener(d)),[e,f]),{isActive:e.isActive,activeValue:e.value}}function useFontSize(a){let{defaultFontSize:c="14px",fontSizes:b}=a;if(7<b.length)throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");const[d,e]=useState(c),[f,g]=useState(null),{performCommandWithValue:h}=useDocumentExecCommand("fontSize"),i=useContext(RichTextContext),j=function(){return useEffect(()=>{function a(){const a=window.getSelection();if((!f||a.anchorNode!==f.anchorNode||a.anchorOffset!==f.anchorOffset||a.focusNode!==f.focusNode||a.focusOffset!==f.focusOffset)&&(g({anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),0<a.rangeCount)){let b=a.getRangeAt(0).startContainer;1!==b.nodeType&&(b=b.parentElement);const c=window.getComputedStyle(b).fontSize,f=c;f!==d&&e(f)}}return i.addSelectionChangedListener(a),()=>i.removeSelectionChangedListener(a)},[d,e,f]),d}();return function(){useEffect(()=>{const a=b.reduce((a,b,c)=>"".concat(a," font[size=\"").concat(c+1,"\"] {font-size: ").concat(b,"}"),""),c=document.createElement("style");return c.textContent=a,document.head.appendChild(c),()=>document.head.removeChild(c)},[b]),useEffect(()=>{function a(a){const c=a.querySelectorAll("font");for(let d=0;d<c.length;d++){const a=c[d],e=+a.getAttribute("size");if(e>b.length)throw Error("Cannot find fontSize for integer size '".concat(e,"'"));const f=b[e-1];a.removeAttribute("size"),a.style.fontSize=f,a.dataset.integerSize=e}}return i.addSerializer(a),()=>i.removeSerializer(a)},[b]),useEffect(()=>{function a(){const a=i.getContentEditableElement().querySelectorAll("font");for(let c=0;c<a.length;c++){const d=a[c],e=d.style.fontSize,f=b.findIndex(a=>a===e)+1;0<f&&(d.style.fontSize="",d.setAttribute("size",f))}}return i.addNewHTMLListener(a),()=>i.removeNewHTMLListener(a)},[b])}(),{currentlySelectedFontSize:j,setSize(a){const c=b.findIndex(b=>b===a)+1;if(0>=c)throw Error("Cannot set font size since '".concat(a,"' was not passed in the fontSizes array"));e(a),h(c)}}}const noop$2=()=>{},defaultAcceptImgTypes=".jpg, .png, image/*",defaultOpts={processImgElement:noop$2,fileBlobToUrl:defaultFileBlobToUrl,acceptImgTypes:defaultAcceptImgTypes};function useImage(){function a(a){a.style.cursor="pointer",a.style.maxWidth="100%",c(a)}function b(a){a.querySelectorAll("img").forEach(a=>{i.current[a.src]&&(a.src=i.current[a.src])})}let{processImgElement:c=noop$2,fileBlobToUrl:d=defaultFileBlobToUrl,acceptImgTypes:e=defaultAcceptImgTypes}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOpts;const{performCommandWithValue:f}=useDocumentExecCommand("insertImage"),g=useContext(RichTextContext),h=useRef(null),i=React.useRef({});return function(){useEffect(()=>{function b(){const b=g.getContentEditableElement().querySelectorAll("img:not([data-text-as-image])");b.forEach(a)}return g.addNewHTMLListener(b),()=>g.removeNewHTMLListener(b)},[c])}(),function(){useEffect(()=>{h.current=document.createElement("input");const b=h.current;b.type="file",b.accept=e,b.multiple=!1,b.addEventListener("change",()=>{b.files&&0<b.files.length&&d(b.files[0],c=>{f(c);const d=document.querySelector("img[src=\"".concat(c,"\"]"));if(d.src&&d.src.startsWith("blob:")){const a=new FileReader;a.addEventListener("load",()=>{i.current[d.src]=a.result}),a.readAsDataURL(b.files[0])}a(d)})})},[d,c,e])}(),function(){useEffect(()=>(g.addSerializer(b),()=>g.removeSerializer(b)))}(),{chooseFile(a){h.current.click()},removeImage(a){const b=document.createRange();b.selectNode(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b),document.execCommand("delete")}}}function defaultFileBlobToUrl(a,b){b(URL.createObjectURL(a))}let tempId=0;const noop$3=()=>{},defaultOptions={processAnchorElement:noop$3};function useLink(){function a(a,d){c.selectRangeFromBeforeBlur({usePreviousRange:!0});const f="rte-link-temp-id-".concat(tempId++);e("<a href=\"".concat(a,"\" id=\"").concat(f,"\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(d,"</a>"));const g=document.getElementById(f);g.removeAttribute("id"),b(g)}let{processAnchorElement:b=noop$3}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOptions;const c=useContext(RichTextContext),{performCommand:d}=useDocumentExecCommand("unlink"),{performCommandWithValue:e}=useDocumentExecCommand("insertHTML");return function(){useEffect(()=>{function a(){const a=c.getContentEditableElement().querySelectorAll("a");a.forEach(b)}return c.addNewHTMLListener(a),()=>c.removeNewHTMLListener(a)},[b])}(),{getTextFromBeforeBlur:function(){const a=c.getRangeFromBeforeBlur();return a?a.toString():null},selectEntireLink:function(a){const b=document.createRange();b.selectNodeContents(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)},unlink:function(){d(),(window.navigator.userAgent.includes("Edge/14")||window.navigator.userAgent.includes("Edge/15")||window.navigator.userAgent.includes("Edge/16")||window.navigator.userAgent.includes("Edge/17"))&&document.execCommand("removeFormat")},insertLink:a}}let tempId$1=0;const noop$4=()=>{},defaultOptions$1={processContentEditableFalseElement:noop$4};function useContentEditableFalse(){function a(a){if(a.removeAttribute("id"),a.contentEditable=!1,a.addEventListener("click",()=>selectRangeAfterNode(a)),!a.previousSibling&&a.parentElement===d.getContentEditableElement()){const b=document.createElement("span");a.parentElement.insertBefore(b,a)}if(!a.nextSibling&&a.parentElement===d.getContentEditableElement()){const b=document.createElement("span");a.insertAdjacentElement("afterend",b)}selectRangeAfterNode(a),b(a)}let{processContentEditableFalseElement:b=noop$4}=0<arguments.length&&arguments[0]!==void 0?arguments[0]:defaultOptions$1;const{performCommandWithValue:c}=useDocumentExecCommand("insertHTML"),d=useContext(RichTextContext);return{insertContentEditableFalseElement(b){const e="rte-ce-false-temp-id-"+tempId$1++,f="<span id=\"".concat(e,"\">").concat(d.sanitizeHTML(b,"insertContentEditableFalseHTML"),"</span>");c(f);const g=document.getElementById(e);a(g)}}}function selectRangeAfterNode(a){const b=document.createRange();b.setStartAfter(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)}const noop$5=()=>{},defaultOptions$2={processSerializedElement:noop$5,fontFamily:null,fillStyle:"#00bf4b",fontWeight:"bold",textTop:null,backgroundColor:"transparent"};function useTextAsImage(){let{processSerializedElement:f=noop$5,fontFamily:a,fillStyle:b,fontWeight:c,textBottom:d,backgroundColor:e}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOptions$2;const{performCommandWithValue:g}=useDocumentExecCommand("insertImage"),h=useContext(RichTextContext);return function(){useEffect(()=>{function f(){const f=h.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let g=0;g<f.length;g++){const h=f[g],i=textToUrl({text:h.dataset.textAsImage,referenceEl:h.previousElementSibling||h.nextElementSibling||h.parentElement,fontFamily:a,fontWeight:c,fillStyle:b,textBottom:d}),j=document.createElement("img");j.src=i,processImgElement(j,h.dataset.textAsImage,e),h.parentNode.replaceChild(j,h)}}return h.addNewHTMLListener(f),()=>h.removeNewHTMLListener(f)})}(),function(){useEffect(()=>{function a(a){const b=a.querySelectorAll("img[data-text-as-image]");for(let c=0;c<b.length;c++){const a=b[c],d=document.createElement("span");d.dataset.textAsImage=a.dataset.textAsImage,f(d,d.dataset.textAsImage),a.parentNode.replaceChild(d,a)}}return h.addSerializer(a),()=>h.removeSerializer(a)},[])}(),{insertTextAsImage(f){h.selectRangeFromBeforeBlur({usePreviousRange:!0});const i=textToUrl({text:f,referenceEl:getSelectedElement(),fontFamily:a,fontWeight:c,fillStyle:b,textBottom:d});g(i);const j=document.querySelector("img[src=\"".concat(i,"\"]:not([data-text-as-image])"));processImgElement(j,f,e)}}}function textToUrl(a){let{text:b,referenceEl:d,fontFamily:e,fillStyle:f,fontWeight:g,textBottom:h}=a;const i=window.getComputedStyle(d),j=+i.fontSize.replace("px",""),k=+i.lineHeight.replace("px",""),l=e||i.fontFamily,m="".concat(g," ").concat(j,"px ").concat(l),n=document.createElement("div");n.style.font=m,n.style.position="absolute",n.style.visibility="hidden",n.style.whiteSpace="nowrap",n.textContent=b,document.body.appendChild(n);const o=document.createElement("canvas");o.width=n.clientWidth+1,o.height=k;const p=o.getContext("2d");p.font=m,p.fillStyle=f||defaultOptions$2.fillStyle,p.textBaseline="bottom";return p.fillText(b,0,h||k-3),document.body.removeChild(n),p.canvas.toDataURL()}function processImgElement(a,b,c){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,a.style.backgroundColor=c||defaultOptions$2.backgroundColor,a.addEventListener("click",b=>{const c=a.getBoundingClientRect(),d=c.left+c.width/2,e=document.createRange();b.x<d?e.setStartBefore(a):e.setStartAfter(a);const f=window.getSelection();f.removeAllRanges(),f.addRange(e)})}function getSelectedElement(){let a=getSelection().getRangeAt(0).commonAncestorContainer;return 1===a.nodeType?a:a.parentElement}function useElementDeletionDetection(a,b){const c=useContext(RichTextContext);useEffect(()=>{if(a){function d(){a.isConnected||a._bandicoot_delete_callback_called||(a._bandicoot_delete_callback_called=!0,b(a))}return c.addSelectionChangedListener(d),()=>c.removeSelectionChangedListener(d)}},[a,b])}function styleInject(a,b){void 0===b&&(b={});var c=b.insertAt;if(a&&"undefined"!=typeof document){var d=document.head||document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css","top"===c?d.firstChild?d.insertBefore(e,d.firstChild):d.appendChild(e):d.appendChild(e),e.styleSheet?e.styleSheet.cssText=a:e.appendChild(document.createTextNode(a))}}var css=".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}",style={bandicootButton:"icon-button_bandicootButton__2IZP5"};styleInject(".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}");function IconButton(a){return React.createElement("button",_extends({},a,{className:"\n ".concat(style.bandicootButton,"\n ").concat(a.isActive?"active-control-button":"","\n ").concat(a.className||"","\n "),onClick:a.onClick}),a.children)}BoldIcon.defaultProps={width:24,height:24};function BoldIcon(a){return React.createElement("svg",{width:a.width,height:a.height,xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",viewBox:"0 0 317.41 317.41"},React.createElement("path",{d:"M281.4 158.7c21.7-15.1 36-40.3 36-68.7 0-46.2-37.6-83.7-83.7-83.7h-40H115 45 0v30h45v245H0v30h45 70 78.7 40c46.2 0 83.8-37.6 83.8-83.7C317.4 199 303.2 173.9 281.4 158.7zM193.7 36.2c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115v-107.5H193.7zM115 173.7h78.7c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115V173.7z"}))}function BoldButton(a){const{performCommand:b}=useDocumentExecCommand("bold"),{isActive:c}=useDocumentQueryCommandState("bold");return React.createElement(IconButton,{onClick:b,isActive:c},React.createElement(BoldIcon,a))}export{BoldButton,RichTextContainer,RichTextContext,RichTextEditor,useContentEditableFalse,useDocumentExecCommand,useDocumentQueryCommandState,useElementDeletionDetection,useFontSize,useFormatBlock,useImage,useLink,useTextAsImage};
import React, { useRef, useEffect, forwardRef, useContext, useState } from 'react';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function RichTextContainer(props) {
const contextValueRef = useRef(Object.assign({}, defaultContextValue));
const contextValue = contextValueRef.current;
const selectionChangedListenersRef = useRef([]);
const blurListenersRef = useRef([]);
const newHTMLListenersRef = useRef([]);
const serializers = useRef([]);
useEffect(() => {
const contentEditableElement = contextValue.getContentEditableElement();
if (contentEditableElement && contentEditableElement.innerHTML) {
contextValue.fireNewHTML();
}
}, [contextValue]);
contextValue.addSelectionChangedListener = listener => {
selectionChangedListenersRef.current.push(listener);
};
contextValue.removeSelectionChangedListener = listener => {
selectionChangedListenersRef.current = selectionChangedListenersRef.current.filter(l => l !== listener);
};
contextValue.fireSelectionChanged = () => {
selectionChangedListenersRef.current.forEach(listener => listener());
};
contextValue.addBlurListener = listener => {
blurListenersRef.current.push(listener);
};
contextValue.removeBlurListener = listener => {
blurListenersRef.current = blurListenersRef.current.filter(l => l !== listener);
};
contextValue.fireBlur = () => {
blurListenersRef.current.forEach(listener => listener());
};
contextValue.addNewHTMLListener = listener => {
newHTMLListenersRef.current.push(listener);
};
contextValue.removeNewHTMLListener = listener => {
newHTMLListenersRef.current = newHTMLListenersRef.current.filter(l => l !== listener);
};
contextValue.fireNewHTML = () => {
newHTMLListenersRef.current.forEach(l => l());
};
contextValue.numSerializers = () => serializers.current.length;
contextValue.addSerializer = serializer => {
serializers.current.push(serializer);
};
contextValue.removeSerializer = serializer => {
serializers.current = serializers.current.filter(s => s !== serializer);
};
contextValue.serialize = dom => {
serializers.current.forEach(serializer => serializer(dom));
return dom.innerHTML;
};
return React.createElement(RichTextContext.Provider, {
value: contextValue
}, props.children);
}
const noop = () => {};
const defaultContextValue = {
addSelectionChangedListener: noop,
removeSelectionChangedListener: noop,
fireSelectionChanged: noop,
selectRangeFromBeforeBlur: noop,
getRangeBeforeBlur: noop,
addBlurListener: noop,
removeBlurListener: noop,
fireBlur: noop,
addNewHTMLListener: noop,
removeNewHTMLListener: noop,
fireNewHTML: noop,
isFocused: noop,
getContentEditableElement: noop,
numSerializers: () => 0,
addSerializer: noop,
removeSerializer: noop,
serialize: noop,
sanitizeHTML: noop
};
const RichTextContext = React.createContext(defaultContextValue);
const noop$1 = () => {};
const noopWithReturn = value => value;
let globalBandicootId = 0;
const RichTextEditor = forwardRef((props, editorRef) => {
if (typeof props.sanitizeHTML !== "function") {
throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");
}
const richTextContext = useContext(RichTextContext);
richTextContext.sanitizeHTML = props.sanitizeHTML;
const divRef = useRef(null);
const selectionRangeBeforeBlurRef = useRef(null);
const {
isFocused,
setFocused
} = useSynchronousFocusState();
const bandicootId = useRef(globalBandicootId++);
const [lastSavedHTML, setLastSavedHTML] = useState(() => richTextContext.sanitizeHTML(props.initialHTML, 'initialSetLastSavedHTML'));
const [hasSetInitialHTML, setHasSetInitialHTML] = useState(false);
if (editorRef) {
const current = {
setHTML,
resetEditor,
getHTML,
focus
};
if (typeof editorRef === 'function') {
editorRef({
current
});
} else {
editorRef.current = current;
}
}
function interceptPaste(event) {
// https://developer.mozilla.org/en-US/docs/Web/Events/paste
event.preventDefault();
event.stopPropagation();
const clipboardData = event.clipboardData || window.clipboardData;
const pasteData = clipboardData.getData('text/html') || clipboardData.getData('text');
let paste = richTextContext.sanitizeHTML(pasteData, 'pasteHTML');
let newPaste = props.pasteFn(paste);
if (newPaste !== false) {
document.execCommand('insertHTML', null, newPaste);
}
}
useEffect(() => {
divRef.current.addEventListener('paste', interceptPaste);
return () => divRef.current.removeEventListener('paste', interceptPaste);
}, [props.pasteFn]);
function emptyEditor() {
// do it with selection and execCommand so it can be undone with Ctrl Z
const range = document.createRange();
range.selectNodeContents(divRef.current);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('removeFormat');
document.execCommand('delete');
}
useEffect(() => {
document.addEventListener('selectionchange', handleSelectionChange);
return () => document.removeEventListener('selectionchange', handleSelectionChange);
});
useEffect(() => {
if (divRef.current) {
let el = divRef.current;
while (el.parentNode) {
el = el.parentNode;
if (el.tagName === 'SPAN') console.warn('A span tag has been detected in the parents of <RichTextEditor>. This has been known to cause issues. https://github.com/CanopyTax/bandicoot/issues/69');
}
}
}, [divRef.current]);
useEffect(() => {
if (props.save && props.unchangedInterval && divRef.current && isFocused()) {
const mutationConfig = {
attributes: true,
childList: true,
subtree: true,
characterData: true
};
let timeout;
const observer = new MutationObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(save, props.unchangedInterval);
});
observer.observe(divRef.current, mutationConfig);
return () => {
observer.disconnect();
clearTimeout(timeout);
};
}
}, [props.unchangedInterval, props.save, divRef.current, isFocused()]);
useEffect(() => {
// Clicking on bandicoot richtext buttons triggers a blur event that will setFocus to false we want to delay the
// save event that is triggered by blur events. 100ms is arbitrary. Whenever react rerenders the rich-text-editor
// due to focused state changing we need to either clear the blurTimeout to prevent a save action from firing
// (in the case of a quick refocus triggered by the rich text buttons) or fire a save event after waiting 100ms
if (isFocused() === false) {
const timeout = setTimeout(() => {
richTextContext.fireBlur();
save();
}, 100);
return () => {
clearTimeout(timeout);
};
}
}, [isFocused()]);
useEffect(() => {
richTextContext.selectRangeFromBeforeBlur = function () {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
usePreviousRange: false
};
if (divRef.current && document.activeElement !== divRef.current && !divRef.current.contains(document.activeElement)) {
if (options.usePreviousRange && selectionRangeBeforeBlurRef.current) {
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(selectionRangeBeforeBlurRef.current);
} else {
divRef.current.focus();
}
setFocused(true); // We are calling handleSelectionChange manually because calling divRef.current.focus() programatically does not trigger
// a focus event like it normally would if a user did it. And we need the bandicoot hooks to know that the selection has changed,
// which is done by calling fireSelectionChanged (which is what handleSelectionChange does).
//
// And we're using setTimeout because if we don't wait a tick of the event loop, the browser will tell us the old
// command state from before we focused.
setTimeout(handleSelectionChange);
}
};
richTextContext.getRangeFromBeforeBlur = () => {
return selectionRangeBeforeBlurRef.current;
};
richTextContext.isFocused = isFocused;
richTextContext.getContentEditableElement = () => divRef.current;
});
useEffect(() => {
if (!hasSetInitialHTML && props.initialHTML) {
setHasSetInitialHTML(true);
setHTML(props.initialHTML, true);
}
}, [hasSetInitialHTML, setHTML, richTextContext]);
useEffect(() => {
if (props.placeholder) {
const styleElement = document.createElement('style');
styleElement.textContent = ".bandicoot-id-".concat(bandicootId.current, ":empty:before { content: attr(data-placeholder); }");
document.head.appendChild(styleElement);
const styleSheet = Array.prototype.slice.call(document.styleSheets).find(s => s.ownerNode === styleElement);
const styleDeclaration = styleSheet.cssRules[0].style;
const styles = props.placeholderStyle ? props.placeholderStyle : getDefaultPlaceholderStyles();
for (let propName in styles) {
styleDeclaration[propName] = props.placeholderStyle ? props.placeholderStyle[propName] : styles[propName];
}
return () => styleElement.parentNode.removeChild(styleElement);
}
}, [props.placeholder, props.placeholderStyle, bandicootId.current]);
const divStyles = props.style || {};
return React.createElement("div", {
contentEditable: !props.disabled,
onBlur: () => setFocused(false),
onFocus: onFocus,
ref: divRef,
className: props.className + " bandicoot-id-" + bandicootId.current,
style: _objectSpread2({
wordBreak: 'break-word',
wordWrap: 'break-word',
overflowWrap: 'break-word'
}, divStyles),
"data-placeholder": props.placeholder
});
function onFocus() {
setFocused(true);
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectionRangeBeforeBlurRef.current = selection.getRangeAt(0);
}
}
function handleSelectionChange(evt) {
if (isFocused()) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectionRangeBeforeBlurRef.current = window.getSelection().getRangeAt(0);
}
richTextContext.fireSelectionChanged();
}
}
function save() {
const html = getHTML();
if (html !== lastSavedHTML) {
setLastSavedHTML(html);
props.save(html);
}
}
function serialize() {
let html = divRef.current.innerHTML;
if (richTextContext.numSerializers() > 0) {
const dom = new DOMParser().parseFromString(html, 'text/html');
html = richTextContext.serialize(dom.body);
}
return html;
}
function setHTML(html, isInitialMount) {
// both emptyEditor() and focus() result in unwanted autofocus on contentEditable on first render
// if the consumer wants autofocus, give us the prop
if (!isInitialMount) {
emptyEditor();
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML');
focus();
} else {
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML');
if (props.autoFocus) focus();
}
richTextContext.fireNewHTML();
}
function resetEditor() {
setHTML('');
}
function getHTML() {
return richTextContext.sanitizeHTML(serialize(), 'getHTML');
}
function focus() {
divRef.current.focus();
setFocused(true);
}
function getDefaultPlaceholderStyles() {
if (!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) {
return {
color: 'rgb(117, 117, 117)'
}; // default chrome style
} else {
return {
opacity: '0.54'
}; // firefox
}
}
}); // This hook allows you to change the focused value synchronously instead of queuing it
// up with a set state. This is necessary since the selectionchange listener depends on
// the blur and focus listeners to update the state synchronously (before the selectionchange
// listener is fired)
function useSynchronousFocusState() {
const focusedRef = useRef(null); // To be able to trigger a re-render when the ref value changes synchronously
const [bool, setBool] = useState(false);
return {
isFocused,
setFocused
};
function isFocused() {
return focusedRef.current;
}
function setFocused(val) {
focusedRef.current = val;
setBool(!bool);
}
}
RichTextEditor.defaultProps = {
className: '',
initialHTML: '',
save: noop$1,
placeholder: '',
pasteFn: noopWithReturn
};
function useDocumentExecCommand(commandName) {
const richTextContext = useContext(RichTextContext);
return {
performCommand(evt) {
richTextContext.selectRangeFromBeforeBlur();
document.execCommand(commandName);
},
performCommandWithValue(value) {
richTextContext.selectRangeFromBeforeBlur();
const showDefaultUI = null;
document.execCommand(commandName, showDefaultUI, value);
}
};
}
function useFormatBlock() {
const {
performCommandWithValue
} = useDocumentExecCommand('formatBlock');
return {
formatBlock(value) {
if (value === document.queryCommandValue('formatBlock')) {
performCommandWithValue('div');
} else {
performCommandWithValue(value);
}
}
};
}
const defaultActiveInfo = {
isActive: false,
value: false
};
function useDocumentQueryCommandState(commandName, activeValueMatch) {
const [activeInfo, setActiveInfo] = useState(defaultActiveInfo);
const richTextContext = useContext(RichTextContext);
useEffect(() => {
richTextContext.addSelectionChangedListener(recheckActive);
return () => richTextContext.removeSelectionChangedListener(recheckActive);
}, [activeInfo, setActiveInfo]);
useEffect(() => {
richTextContext.addBlurListener(setInactive);
return () => richTextContext.removeBlurListener(setInactive);
}, [activeInfo, setActiveInfo]);
function recheckActive() {
const actualActiveValue = document.queryCommandValue(commandName);
const isActuallyActive = activeValueMatch ? activeValueMatch === actualActiveValue : document.queryCommandState(commandName);
if (isActuallyActive !== activeInfo.isActive || actualActiveValue !== activeInfo.value) {
setActiveInfo({
isActive: isActuallyActive,
value: actualActiveValue
});
}
}
function setInactive() {
setActiveInfo(defaultActiveInfo);
}
return {
isActive: activeInfo.isActive,
activeValue: activeInfo.value
};
}
function useFontSize(_ref) {
let {
defaultFontSize = '14px',
fontSizes
} = _ref;
if (fontSizes.length > 7) {
throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");
}
const [fontSize, setFontSize] = useState(defaultFontSize);
const [lastSelection, setLastSelection] = useState(null);
const {
performCommandWithValue
} = useDocumentExecCommand('fontSize');
const richTextContext = useContext(RichTextContext);
const currentlySelectedFontSize = useCurrentlySelectedFontSize();
useSizeOverrides();
return {
currentlySelectedFontSize,
setSize(fontSize) {
const integerSize = fontSizes.findIndex(size => size === fontSize) + 1;
if (integerSize <= 0) {
throw Error("Cannot set font size since '".concat(fontSize, "' was not passed in the fontSizes array"));
}
setFontSize(fontSize);
performCommandWithValue(integerSize);
}
};
function useCurrentlySelectedFontSize() {
useEffect(() => {
richTextContext.addSelectionChangedListener(selectionChanged);
return () => richTextContext.removeSelectionChangedListener(selectionChanged);
function selectionChanged() {
const selection = window.getSelection(); // If the selection has 'actually' changed (i.e. not just due to the editor bluring and focusing)
if (!lastSelection || selection.anchorNode !== lastSelection.anchorNode || selection.anchorOffset !== lastSelection.anchorOffset || selection.focusNode !== lastSelection.focusNode || selection.focusOffset !== lastSelection.focusOffset) {
setLastSelection({
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset
}); // If there is no selection, we won't change the font size
if (selection.rangeCount > 0) {
let selectionNode = selection.getRangeAt(0).startContainer;
if (selectionNode.nodeType !== 1) {
// we've got a text node or comment node or other type of node that's not an element
selectionNode = selectionNode.parentElement;
}
const stringFontSize = window.getComputedStyle(selectionNode).fontSize;
const newSize = stringFontSize;
if (newSize !== fontSize) {
setFontSize(newSize);
}
}
}
}
}, [fontSize, setFontSize, lastSelection]);
return fontSize;
}
function useSizeOverrides() {
useEffect(() => {
const cssString = fontSizes.reduce((acc, style, index) => {
return "".concat(acc, " font[size=\"").concat(index + 1, "\"] {font-size: ").concat(style, "}");
}, '');
const styleEl = document.createElement('style');
styleEl.textContent = cssString;
document.head.appendChild(styleEl);
return () => document.head.removeChild(styleEl);
}, [fontSizes]);
useEffect(() => {
richTextContext.addSerializer(serializer);
return () => richTextContext.removeSerializer(serializer);
function serializer(dom) {
const fontEls = dom.querySelectorAll('font');
for (let i = 0; i < fontEls.length; i++) {
const fontEl = fontEls[i];
const integerSize = Number(fontEl.getAttribute('size'));
if (integerSize > fontSizes.length) {
throw Error("Cannot find fontSize for integer size '".concat(integerSize, "'"));
}
const size = fontSizes[integerSize - 1];
fontEl.removeAttribute('size');
fontEl.style.fontSize = size;
fontEl.dataset.integerSize = integerSize;
}
}
}, [fontSizes]);
useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const fontEls = richTextContext.getContentEditableElement().querySelectorAll('font');
for (let i = 0; i < fontEls.length; i++) {
const fontEl = fontEls[i];
const cssFontSize = fontEl.style.fontSize;
const integerSize = fontSizes.findIndex(size => size === cssFontSize) + 1;
if (integerSize > 0) {
fontEl.style.fontSize = '';
fontEl.setAttribute('size', integerSize);
}
}
}
}, [fontSizes]);
}
}
const noop$2 = () => {};
const defaultAcceptImgTypes = '.jpg, .png, image/*';
const defaultOpts = {
processImgElement: noop$2,
fileBlobToUrl: defaultFileBlobToUrl,
acceptImgTypes: defaultAcceptImgTypes
};
function useImage() {
let {
processImgElement = noop$2,
fileBlobToUrl = defaultFileBlobToUrl,
acceptImgTypes = defaultAcceptImgTypes
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOpts;
const {
performCommandWithValue
} = useDocumentExecCommand('insertImage');
const richTextContext = useContext(RichTextContext);
const fileInputRef = useRef(null);
const dataUrls = React.useRef({});
useNewHtmlHandler();
useFileChooserInput();
useSerializer();
return {
chooseFile(evt) {
fileInputRef.current.click();
},
removeImage(imgElement) {
const range = document.createRange();
range.selectNode(imgElement);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete');
}
};
function handleImageElement(imgElement) {
imgElement.style.cursor = 'pointer';
imgElement.style.maxWidth = '100%';
processImgElement(imgElement);
}
function useNewHtmlHandler() {
useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const imgElements = richTextContext.getContentEditableElement().querySelectorAll('img:not([data-text-as-image])');
imgElements.forEach(handleImageElement);
}
}, [processImgElement]);
}
function useFileChooserInput() {
useEffect(() => {
fileInputRef.current = document.createElement('input');
const fileInputElement = fileInputRef.current;
fileInputElement.type = 'file';
fileInputElement.accept = acceptImgTypes;
fileInputElement.multiple = false;
fileInputElement.addEventListener('change', () => {
if (fileInputElement.files && fileInputElement.files.length > 0) {
fileBlobToUrl(fileInputElement.files[0], imgUrl => {
performCommandWithValue(imgUrl);
const imgElement = document.querySelector("img[src=\"".concat(imgUrl, "\"]"));
if (imgElement.src && imgElement.src.startsWith('blob:')) {
const reader = new FileReader();
reader.addEventListener('load', () => {
dataUrls.current[imgElement.src] = reader.result;
});
reader.readAsDataURL(fileInputElement.files[0]);
}
handleImageElement(imgElement);
});
}
});
}, [fileBlobToUrl, processImgElement, acceptImgTypes]);
}
function useSerializer() {
useEffect(() => {
richTextContext.addSerializer(serializer);
return () => richTextContext.removeSerializer(serializer);
});
}
function serializer(dom) {
dom.querySelectorAll('img').forEach(imgEl => {
if (dataUrls.current[imgEl.src]) {
imgEl.src = dataUrls.current[imgEl.src];
}
});
}
}
function defaultFileBlobToUrl(file, cbk) {
cbk(URL.createObjectURL(file));
}
let tempId = 0;
const noop$3 = () => {};
const defaultOptions = {
processAnchorElement: noop$3
};
function useLink() {
let {
processAnchorElement = noop$3
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions;
const richTextContext = useContext(RichTextContext);
const {
performCommand
} = useDocumentExecCommand('unlink');
const {
performCommandWithValue
} = useDocumentExecCommand('insertHTML');
useNewHtmlHandler();
return {
getTextFromBeforeBlur,
selectEntireLink,
unlink,
insertLink
};
function getTextFromBeforeBlur() {
const range = richTextContext.getRangeFromBeforeBlur();
if (range) {
return range.toString();
} else {
return null;
}
}
function selectEntireLink(anchorElement) {
const range = document.createRange();
range.selectNodeContents(anchorElement);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function insertLink(link, displayedText) {
richTextContext.selectRangeFromBeforeBlur({
usePreviousRange: true
});
const id = "rte-link-temp-id-".concat(tempId++);
performCommandWithValue("<a href=\"".concat(link, "\" id=\"").concat(id, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(displayedText, "</a>"));
const anchorElement = document.getElementById(id);
anchorElement.removeAttribute('id');
processAnchorElement(anchorElement);
}
function useNewHtmlHandler() {
useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const anchorElements = richTextContext.getContentEditableElement().querySelectorAll('a');
anchorElements.forEach(processAnchorElement);
}
}, [processAnchorElement]);
}
function unlink() {
performCommand();
if (window.navigator.userAgent.includes('Edge/14') || window.navigator.userAgent.includes('Edge/15') || window.navigator.userAgent.includes('Edge/16') || window.navigator.userAgent.includes('Edge/17')) {
// Older versions of Edge remove the <a> when you unlink, but keep the text blue and underlined so it looks like a link.
// Ideally we'd be super smart about working around this, but for now I'm just removing all rich text formatting from the
// text that used to be a link when you've got an older version of Edge.
document.execCommand('removeFormat');
}
}
}
let tempId$1 = 0;
const noop$4 = () => {};
const defaultOptions$1 = {
processContentEditableFalseElement: noop$4
};
function useContentEditableFalse() {
let {
processContentEditableFalseElement = noop$4
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$1;
const {
performCommandWithValue
} = useDocumentExecCommand('insertHTML');
const richTextContext = useContext(RichTextContext);
return {
insertContentEditableFalseElement(innerHTML) {
const id = "rte-ce-false-temp-id-" + tempId$1++;
const htmlToInsert = "<span id=\"".concat(id, "\">").concat(richTextContext.sanitizeHTML(innerHTML, 'insertContentEditableFalseHTML'), "</span>");
performCommandWithValue(htmlToInsert);
const contentEditableFalseElement = document.getElementById(id);
handleContentEditableFalseElement(contentEditableFalseElement);
}
};
function handleContentEditableFalseElement(contentEditableFalseElement) {
contentEditableFalseElement.removeAttribute('id');
contentEditableFalseElement.contentEditable = false;
contentEditableFalseElement.addEventListener('click', () => selectRangeAfterNode(contentEditableFalseElement)); // if we are inserting this at the start of the rich text content editable container, we need to make sure a
// cursor still appears and works when you select the beginning of the rich text container. This is done with
// an empty span that *is* contentEditable
if (!contentEditableFalseElement.previousSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) {
const editableElementBefore = document.createElement('span');
contentEditableFalseElement.parentElement.insertBefore(editableElementBefore, contentEditableFalseElement);
} // if we are inserting this at the end of the rich text content editable container, we need to make sure a
// cursor still appears and works when you select the end of the rich text container. This is done with
// an empty span that *is* contentEditable
if (!contentEditableFalseElement.nextSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) {
const editableElementAfter = document.createElement('span');
contentEditableFalseElement.insertAdjacentElement('afterend', editableElementAfter);
}
selectRangeAfterNode(contentEditableFalseElement);
processContentEditableFalseElement(contentEditableFalseElement);
}
}
function selectRangeAfterNode(node) {
const range = document.createRange();
range.setStartAfter(node);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
const noop$5 = () => {};
const defaultOptions$2 = {
processSerializedElement: noop$5,
fontFamily: null,
fillStyle: '#00bf4b',
fontWeight: 'bold',
textBottom: null,
backgroundColor: 'transparent'
};
function useTextAsImage() {
let {
processSerializedElement = noop$5,
fontFamily,
fillStyle,
fontWeight,
textBottom,
backgroundColor
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$2;
const {
performCommandWithValue
} = useDocumentExecCommand('insertImage');
const richTextContext = useContext(RichTextContext);
useNewHtmlHandler();
useSerializer();
return {
insertTextAsImage(text) {
richTextContext.selectRangeFromBeforeBlur({
usePreviousRange: true
});
const url = textToUrl({
text,
referenceEl: getSelectedElement(),
fontFamily,
fontWeight,
fillStyle,
textBottom
});
performCommandWithValue(url);
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])"));
processImgElement(imgElement, text, backgroundColor);
}
};
function useNewHtmlHandler() {
useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const spanEls = richTextContext.getContentEditableElement().querySelectorAll('span[data-text-as-image]');
for (let i = 0; i < spanEls.length; i++) {
const spanEl = spanEls[i];
const url = textToUrl({
text: spanEl.dataset.textAsImage,
referenceEl: spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement,
fontFamily,
fontWeight,
fillStyle,
textBottom
});
const imgEl = document.createElement('img');
imgEl.src = url;
processImgElement(imgEl, spanEl.dataset.textAsImage, backgroundColor);
spanEl.parentNode.replaceChild(imgEl, spanEl);
}
}
});
}
function useSerializer() {
useEffect(() => {
richTextContext.addSerializer(htmlSerializer);
return () => richTextContext.removeSerializer(htmlSerializer);
function htmlSerializer(dom) {
const textAsImage = dom.querySelectorAll('img[data-text-as-image]');
for (let i = 0; i < textAsImage.length; i++) {
const imgEl = textAsImage[i];
const spanEl = document.createElement('span');
spanEl.dataset.textAsImage = imgEl.dataset.textAsImage;
processSerializedElement(spanEl, spanEl.dataset.textAsImage);
imgEl.parentNode.replaceChild(spanEl, imgEl);
}
}
}, []);
}
}
function textToUrl(_ref) {
let {
text,
referenceEl,
fontFamily,
fillStyle,
fontWeight,
textBottom
} = _ref;
const computedStyle = window.getComputedStyle(referenceEl);
const currentFontSize = Number(computedStyle.fontSize.replace('px', ''));
const currentLineHeight = Number(computedStyle.lineHeight.replace('px', ''));
const currentFontFamily = fontFamily || computedStyle.fontFamily;
const font = "".concat(fontWeight, " ").concat(currentFontSize, "px ").concat(currentFontFamily);
const testDiv = document.createElement('div');
testDiv.style.font = font;
testDiv.style.position = 'absolute';
testDiv.style.visibility = 'hidden';
testDiv.style.whiteSpace = 'nowrap';
testDiv.textContent = text;
document.body.appendChild(testDiv);
const canvas = document.createElement('canvas');
canvas.width = testDiv.clientWidth + 1;
canvas.height = currentLineHeight;
const c = canvas.getContext('2d');
c.font = font;
c.fillStyle = fillStyle || defaultOptions$2.fillStyle;
c.textBaseline = 'bottom';
const yPosition = textBottom || currentLineHeight - 3;
c.fillText(text, 0, yPosition);
document.body.removeChild(testDiv);
return c.canvas.toDataURL();
}
function processImgElement(imgElement, text, backgroundColor) {
imgElement.style.verticalAlign = 'bottom';
imgElement.dataset.textAsImage = text;
imgElement.style.backgroundColor = backgroundColor || defaultOptions$2.backgroundColor;
imgElement.addEventListener('click', evt => {
const rect = imgElement.getBoundingClientRect();
const middlePoint = rect.left + rect.width / 2;
const range = document.createRange();
if (evt.x < middlePoint) {
range.setStartBefore(imgElement);
} else {
range.setStartAfter(imgElement);
}
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
}
function getSelectedElement() {
let result = getSelection().getRangeAt(0).commonAncestorContainer;
return result.nodeType === 1 ? result : result.parentElement;
}
function useElementDeletionDetection(domElement, cbk) {
const richTextContext = useContext(RichTextContext);
useEffect(() => {
if (domElement) {
richTextContext.addSelectionChangedListener(checkElementConnected);
return () => richTextContext.removeSelectionChangedListener(checkElementConnected);
function checkElementConnected() {
if (!domElement.isConnected && !domElement._bandicoot_delete_callback_called) {
domElement._bandicoot_delete_callback_called = true;
cbk(domElement);
}
}
}
}, [domElement, cbk]);
}
function styleInject(css, ref) {
if (ref === void 0) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') {
return;
}
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = ".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}";
var style = {"bandicootButton":"icon-button_bandicootButton__2IZP5"};
styleInject(css);
function IconButton(props) {
return React.createElement("button", _extends({}, props, {
className: "\n ".concat(style.bandicootButton, "\n ").concat(props.isActive ? 'active-control-button' : '', "\n ").concat(props.className || '', "\n "),
onClick: props.onClick
}), props.children);
}
BoldIcon.defaultProps = {
width: 24,
height: 24
};
function BoldIcon(props) {
return React.createElement("svg", {
width: props.width,
height: props.height,
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
viewBox: "0 0 317.41 317.41"
}, React.createElement("path", {
d: "M281.4 158.7c21.7-15.1 36-40.3 36-68.7 0-46.2-37.6-83.7-83.7-83.7h-40H115 45 0v30h45v245H0v30h45 70 78.7 40c46.2 0 83.8-37.6 83.8-83.7C317.4 199 303.2 173.9 281.4 158.7zM193.7 36.2c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115v-107.5H193.7zM115 173.7h78.7c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115V173.7z"
}));
}
function BoldButton(props) {
const {
performCommand
} = useDocumentExecCommand('bold');
const {
isActive
} = useDocumentQueryCommandState('bold');
return React.createElement(IconButton, {
onClick: performCommand,
isActive: isActive
}, React.createElement(BoldIcon, props));
}
export { BoldButton, RichTextContainer, RichTextContext, RichTextEditor, useContentEditableFalse, useDocumentExecCommand, useDocumentQueryCommandState, useElementDeletionDetection, useFontSize, useFormatBlock, useImage, useLink, useTextAsImage };
//# sourceMappingURL=bandicoot.esm.js.map

@@ -1,2 +0,1166 @@

(function(a,b){"object"==typeof exports&&"undefined"!=typeof module?b(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],b):(a=a||self,b(a.bandicoot={},a.React))})(this,function(a,b){'use strict';function c(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function d(){return d=Object.assign||function(a){for(var b,c=1;c<arguments.length;c++)for(var d in b=arguments[c],b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d]);return a},d.apply(this,arguments)}function e(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function f(a){for(var b,d=1;d<arguments.length;d++)b=null==arguments[d]?{}:arguments[d],d%2?e(b,!0).forEach(function(d){c(a,d,b[d])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):e(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function g(){const a=b.useRef(null),[c,d]=b.useState(!1);return{isFocused:function(){return a.current},setFocused:function(b){a.current=b,d(!c)}}}function h(a){const c=b.useContext(t);return{performCommand(b){c.selectRangeFromBeforeBlur(),document.execCommand(a)},performCommandWithValue(b){c.selectRangeFromBeforeBlur();document.execCommand(a,null,b)}}}function i(a,c){function d(){const b=document.queryCommandValue(a),d=c?c===b:document.queryCommandState(a);(d!==f.isActive||b!==f.value)&&g({isActive:d,value:b})}function e(){g(w)}const[f,g]=b.useState(w),h=b.useContext(t);return b.useEffect(()=>(h.addSelectionChangedListener(d),()=>h.removeSelectionChangedListener(d)),[f,g]),b.useEffect(()=>(h.addBlurListener(e),()=>h.removeBlurListener(e)),[f,g]),{isActive:f.isActive,activeValue:f.value}}function j(a,b){b(URL.createObjectURL(a))}function k(a){const b=document.createRange();b.setStartAfter(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)}function l(a){let{text:b,referenceEl:d,fontFamily:e,fillStyle:f,fontWeight:g,textBottom:h}=a;const i=window.getComputedStyle(d),j=+i.fontSize.replace("px",""),k=+i.lineHeight.replace("px",""),l=e||i.fontFamily,m="".concat(g," ").concat(j,"px ").concat(l),n=document.createElement("div");n.style.font=m,n.style.position="absolute",n.style.visibility="hidden",n.style.whiteSpace="nowrap",n.textContent=b,document.body.appendChild(n);const o=document.createElement("canvas");o.width=n.clientWidth+1,o.height=k;const p=o.getContext("2d");p.font=m,p.fillStyle=f||H.fillStyle,p.textBaseline="bottom";return p.fillText(b,0,h||k-3),document.body.removeChild(n),p.canvas.toDataURL()}function m(a,b,c){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,a.style.backgroundColor=c||H.backgroundColor,a.addEventListener("click",b=>{const c=a.getBoundingClientRect(),d=c.left+c.width/2,e=document.createRange();b.x<d?e.setStartBefore(a):e.setStartAfter(a);const f=window.getSelection();f.removeAllRanges(),f.addRange(e)})}function n(){let a=getSelection().getRangeAt(0).commonAncestorContainer;return 1===a.nodeType?a:a.parentElement}function o(a){return q.createElement("button",d({},a,{className:"\n ".concat(I.bandicootButton,"\n ").concat(a.isActive?"active-control-button":"","\n ").concat(a.className||"","\n "),onClick:a.onClick}),a.children)}function p(a){return q.createElement("svg",{width:a.width,height:a.height,xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",viewBox:"0 0 317.41 317.41"},q.createElement("path",{d:"M281.4 158.7c21.7-15.1 36-40.3 36-68.7 0-46.2-37.6-83.7-83.7-83.7h-40H115 45 0v30h45v245H0v30h45 70 78.7 40c46.2 0 83.8-37.6 83.8-83.7C317.4 199 303.2 173.9 281.4 158.7zM193.7 36.2c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115v-107.5H193.7zM115 173.7h78.7c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115V173.7z"}))}var q="default"in b?b["default"]:b;const r=()=>{},s={addSelectionChangedListener:r,removeSelectionChangedListener:r,fireSelectionChanged:r,selectRangeFromBeforeBlur:r,getRangeBeforeBlur:r,addBlurListener:r,removeBlurListener:r,fireBlur:r,addNewHTMLListener:r,removeNewHTMLListener:r,fireNewHTML:r,isFocused:r,getContentEditableElement:r,numSerializers:()=>0,addSerializer:r,removeSerializer:r,serialize:r,sanitizeHTML:r},t=q.createContext(s);let u=0;const v=b.forwardRef((a,c)=>{function d(b){b.preventDefault(),b.stopPropagation();let c=p.sanitizeHTML((window.clipboardData||b.clipboardData).getData("text/html"),"pasteHTML"),d=a.pasteFn(c);!1!==d&&document.execCommand("insertHTML",null,d)}function e(){const a=document.createRange();a.selectNodeContents(r.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function h(){if(v()){const a=window.getSelection();0<a.rangeCount&&(s.current=window.getSelection().getRangeAt(0)),p.fireSelectionChanged()}}function i(){const b=m();b!==y&&(z(b),a.save(b))}function j(){let a=r.current.innerHTML;if(0<p.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=p.serialize(b.body)}return a}function k(b,c){c?(r.current.innerHTML=p.sanitizeHTML(b,"setHTML"),a.autoFocus&&n()):(e(),r.current.innerHTML=p.sanitizeHTML(b,"setHTML"),n()),p.fireNewHTML()}function l(){k("")}function m(){return p.sanitizeHTML(j(),"getHTML")}function n(){r.current.focus(),w(!0)}function o(){return!window.chrome||!window.chrome.webstore&&!window.chrome.runtime?{opacity:"0.54"}:{color:"rgb(117, 117, 117)"}}if("function"!=typeof a.sanitizeHTML)throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");const p=b.useContext(t);p.sanitizeHTML=a.sanitizeHTML;const r=b.useRef(null),s=b.useRef(null),{isFocused:v,setFocused:w}=g(),x=b.useRef(u++),[y,z]=b.useState(()=>p.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[A,B]=b.useState(!1);if(c){const a={setHTML:k,resetEditor:l,getHTML:m,focus:n};"function"==typeof c?c({current:a}):c.current=a}b.useEffect(()=>(r.current.addEventListener("paste",d),()=>r.current.removeEventListener("paste",d)),[a.pasteFn]),b.useEffect(()=>(document.addEventListener("selectionchange",h),()=>document.removeEventListener("selectionchange",h))),b.useEffect(()=>{if(r.current)for(let a=r.current;a.parentNode;)a=a.parentNode,"SPAN"===a.tagName&&console.warn("A span tag has been detected in the parents of <RichTextEditor>. This has been known to cause issues. https://github.com/CanopyTax/bandicoot/issues/69")},[r.current]),b.useEffect(()=>{if(a.save&&a.unchangedInterval&&r.current&&v()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(i,a.unchangedInterval)});return c.observe(r.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,r.current,v()]),b.useEffect(()=>{if(!1===v()){const a=setTimeout(()=>{p.fireBlur(),i()},100);return()=>{clearTimeout(a)}}},[v()]),b.useEffect(()=>{p.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(r.current&&document.activeElement!==r.current&&!r.current.contains(document.activeElement)){if(a.usePreviousRange&&s.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(s.current)}else r.current.focus();w(!0),setTimeout(h)}},p.getRangeFromBeforeBlur=()=>s.current,p.isFocused=v,p.getContentEditableElement=()=>r.current}),b.useEffect(()=>{!A&&a.initialHTML&&(B(!0),k(a.initialHTML,!0))},[A,k,p]),b.useEffect(()=>{if(a.placeholder){const b=document.createElement("style");b.textContent=".bandicoot-id-".concat(x.current,":empty:before { content: attr(data-placeholder); }"),document.head.appendChild(b);const c=Array.prototype.slice.call(document.styleSheets).find(a=>a.ownerNode===b),d=c.cssRules[0].style,e=a.placeholderStyle?a.placeholderStyle:o();for(let b in e)d[b]=a.placeholderStyle?a.placeholderStyle[b]:e[b];return()=>b.parentNode.removeChild(b)}},[a.placeholder,a.placeholderStyle,x.current]);const C=a.style||{};return q.createElement("div",{contentEditable:!a.disabled,onBlur:()=>w(!1),onFocus:function(){w(!0);const a=window.getSelection();0<a.rangeCount&&(s.current=a.getRangeAt(0))},ref:r,className:a.className+" bandicoot-id-"+x.current,style:f({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},C),"data-placeholder":a.placeholder})});v.defaultProps={className:"",initialHTML:"",save:()=>{},placeholder:"",pasteFn:a=>a};const w={isActive:!1,value:!1},x=()=>{},y=".jpg, .png, image/*",z={processImgElement:x,fileBlobToUrl:j,acceptImgTypes:y};let A=0;const B=()=>{},C={processAnchorElement:B};let D=0;const E=()=>{},F={processContentEditableFalseElement:E},G=()=>{},H={processSerializedElement:G,fontFamily:null,fillStyle:"#00bf4b",fontWeight:"bold",textTop:null,backgroundColor:"transparent"};var I={bandicootButton:"icon-button_bandicootButton__2IZP5"};(function(a,b){void 0===b&&(b={});var c=b.insertAt;if(a&&"undefined"!=typeof document){var d=document.head||document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css","top"===c?d.firstChild?d.insertBefore(e,d.firstChild):d.appendChild(e):d.appendChild(e),e.styleSheet?e.styleSheet.cssText=a:e.appendChild(document.createTextNode(a))}})(".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}"),p.defaultProps={width:24,height:24},a.BoldButton=function(a){const{performCommand:b}=h("bold"),{isActive:c}=i("bold");return q.createElement(o,{onClick:b,isActive:c},q.createElement(p,a))},a.RichTextContainer=function(a){const c=b.useRef(Object.assign({},s)),d=c.current,e=b.useRef([]),f=b.useRef([]),g=b.useRef([]),h=b.useRef([]);return b.useEffect(()=>{const a=d.getContentEditableElement();a&&a.innerHTML&&d.fireNewHTML()},[d]),d.addSelectionChangedListener=a=>{e.current.push(a)},d.removeSelectionChangedListener=a=>{e.current=e.current.filter(b=>b!==a)},d.fireSelectionChanged=()=>{e.current.forEach(a=>a())},d.addBlurListener=a=>{f.current.push(a)},d.removeBlurListener=a=>{f.current=f.current.filter(b=>b!==a)},d.fireBlur=()=>{f.current.forEach(a=>a())},d.addNewHTMLListener=a=>{g.current.push(a)},d.removeNewHTMLListener=a=>{g.current=g.current.filter(b=>b!==a)},d.fireNewHTML=()=>{g.current.forEach(a=>a())},d.numSerializers=()=>h.current.length,d.addSerializer=a=>{h.current.push(a)},d.removeSerializer=a=>{h.current=h.current.filter(b=>b!==a)},d.serialize=a=>(h.current.forEach(b=>b(a)),a.innerHTML),q.createElement(t.Provider,{value:d},a.children)},a.RichTextContext=t,a.RichTextEditor=v,a.useContentEditableFalse=function(){function a(a){if(a.removeAttribute("id"),a.contentEditable=!1,a.addEventListener("click",()=>k(a)),!a.previousSibling&&a.parentElement===e.getContentEditableElement()){const b=document.createElement("span");a.parentElement.insertBefore(b,a)}if(!a.nextSibling&&a.parentElement===e.getContentEditableElement()){const b=document.createElement("span");a.insertAdjacentElement("afterend",b)}k(a),c(a)}let{processContentEditableFalseElement:c=E}=0<arguments.length&&arguments[0]!==void 0?arguments[0]:F;const{performCommandWithValue:d}=h("insertHTML"),e=b.useContext(t);return{insertContentEditableFalseElement(b){const c="rte-ce-false-temp-id-"+D++,f="<span id=\"".concat(c,"\">").concat(e.sanitizeHTML(b,"insertContentEditableFalseHTML"),"</span>");d(f);const g=document.getElementById(c);a(g)}}},a.useDocumentExecCommand=h,a.useDocumentQueryCommandState=i,a.useElementDeletionDetection=function(a,c){const d=b.useContext(t);b.useEffect(()=>{if(a){function b(){a.isConnected||a._bandicoot_delete_callback_called||(a._bandicoot_delete_callback_called=!0,c(a))}return d.addSelectionChangedListener(b),()=>d.removeSelectionChangedListener(b)}},[a,c])},a.useFontSize=function(a){let{defaultFontSize:d="14px",fontSizes:c}=a;if(7<c.length)throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");const[e,f]=b.useState(d),[g,i]=b.useState(null),{performCommandWithValue:j}=h("fontSize"),k=b.useContext(t),l=function(){return b.useEffect(()=>{function a(){const a=window.getSelection();if((!g||a.anchorNode!==g.anchorNode||a.anchorOffset!==g.anchorOffset||a.focusNode!==g.focusNode||a.focusOffset!==g.focusOffset)&&(i({anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),0<a.rangeCount)){let b=a.getRangeAt(0).startContainer;1!==b.nodeType&&(b=b.parentElement);const c=window.getComputedStyle(b).fontSize,d=c;d!==e&&f(d)}}return k.addSelectionChangedListener(a),()=>k.removeSelectionChangedListener(a)},[e,f,g]),e}();return function(){b.useEffect(()=>{const a=c.reduce((a,b,c)=>"".concat(a," font[size=\"").concat(c+1,"\"] {font-size: ").concat(b,"}"),""),b=document.createElement("style");return b.textContent=a,document.head.appendChild(b),()=>document.head.removeChild(b)},[c]),b.useEffect(()=>{function a(a){const b=a.querySelectorAll("font");for(let d=0;d<b.length;d++){const a=b[d],e=+a.getAttribute("size");if(e>c.length)throw Error("Cannot find fontSize for integer size '".concat(e,"'"));const f=c[e-1];a.removeAttribute("size"),a.style.fontSize=f,a.dataset.integerSize=e}}return k.addSerializer(a),()=>k.removeSerializer(a)},[c]),b.useEffect(()=>{function a(){const a=k.getContentEditableElement().querySelectorAll("font");for(let b=0;b<a.length;b++){const d=a[b],e=d.style.fontSize,f=c.findIndex(a=>a===e)+1;0<f&&(d.style.fontSize="",d.setAttribute("size",f))}}return k.addNewHTMLListener(a),()=>k.removeNewHTMLListener(a)},[c])}(),{currentlySelectedFontSize:l,setSize(a){const b=c.findIndex(b=>b===a)+1;if(0>=b)throw Error("Cannot set font size since '".concat(a,"' was not passed in the fontSizes array"));f(a),j(b)}}},a.useFormatBlock=function(){const{performCommandWithValue:a}=h("formatBlock");return{formatBlock(b){b===document.queryCommandValue("formatBlock")?a("div"):a(b)}}},a.useImage=function(){function a(a){a.style.cursor="pointer",a.style.maxWidth="100%",d(a)}function c(a){a.querySelectorAll("img").forEach(a=>{l.current[a.src]&&(a.src=l.current[a.src])})}let{processImgElement:d=x,fileBlobToUrl:e=j,acceptImgTypes:f=y}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:z;const{performCommandWithValue:g}=h("insertImage"),i=b.useContext(t),k=b.useRef(null),l=q.useRef({});return function(){b.useEffect(()=>{function b(){const b=i.getContentEditableElement().querySelectorAll("img:not([data-text-as-image])");b.forEach(a)}return i.addNewHTMLListener(b),()=>i.removeNewHTMLListener(b)},[d])}(),function(){b.useEffect(()=>{k.current=document.createElement("input");const b=k.current;b.type="file",b.accept=f,b.multiple=!1,b.addEventListener("change",()=>{b.files&&0<b.files.length&&e(b.files[0],c=>{g(c);const d=document.querySelector("img[src=\"".concat(c,"\"]"));if(d.src&&d.src.startsWith("blob:")){const a=new FileReader;a.addEventListener("load",()=>{l.current[d.src]=a.result}),a.readAsDataURL(b.files[0])}a(d)})})},[e,d,f])}(),function(){b.useEffect(()=>(i.addSerializer(c),()=>i.removeSerializer(c)))}(),{chooseFile(a){k.current.click()},removeImage(a){const b=document.createRange();b.selectNode(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b),document.execCommand("delete")}}},a.useLink=function(){function a(a,b){d.selectRangeFromBeforeBlur({usePreviousRange:!0});const e="rte-link-temp-id-".concat(A++);f("<a href=\"".concat(a,"\" id=\"").concat(e,"\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(b,"</a>"));const g=document.getElementById(e);g.removeAttribute("id"),c(g)}let{processAnchorElement:c=B}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:C;const d=b.useContext(t),{performCommand:e}=h("unlink"),{performCommandWithValue:f}=h("insertHTML");return function(){b.useEffect(()=>{function a(){const a=d.getContentEditableElement().querySelectorAll("a");a.forEach(c)}return d.addNewHTMLListener(a),()=>d.removeNewHTMLListener(a)},[c])}(),{getTextFromBeforeBlur:function(){const a=d.getRangeFromBeforeBlur();return a?a.toString():null},selectEntireLink:function(a){const b=document.createRange();b.selectNodeContents(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)},unlink:function(){e(),(window.navigator.userAgent.includes("Edge/14")||window.navigator.userAgent.includes("Edge/15")||window.navigator.userAgent.includes("Edge/16")||window.navigator.userAgent.includes("Edge/17"))&&document.execCommand("removeFormat")},insertLink:a}},a.useTextAsImage=function(){let{processSerializedElement:g=G,fontFamily:a,fillStyle:c,fontWeight:d,textBottom:e,backgroundColor:f}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:H;const{performCommandWithValue:i}=h("insertImage"),j=b.useContext(t);return function(){b.useEffect(()=>{function b(){const b=j.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let g=0;g<b.length;g++){const h=b[g],i=l({text:h.dataset.textAsImage,referenceEl:h.previousElementSibling||h.nextElementSibling||h.parentElement,fontFamily:a,fontWeight:d,fillStyle:c,textBottom:e}),j=document.createElement("img");j.src=i,m(j,h.dataset.textAsImage,f),h.parentNode.replaceChild(j,h)}}return j.addNewHTMLListener(b),()=>j.removeNewHTMLListener(b)})}(),function(){b.useEffect(()=>{function a(a){const b=a.querySelectorAll("img[data-text-as-image]");for(let c=0;c<b.length;c++){const a=b[c],d=document.createElement("span");d.dataset.textAsImage=a.dataset.textAsImage,g(d,d.dataset.textAsImage),a.parentNode.replaceChild(d,a)}}return j.addSerializer(a),()=>j.removeSerializer(a)},[])}(),{insertTextAsImage(b){j.selectRangeFromBeforeBlur({usePreviousRange:!0});const g=l({text:b,referenceEl:n(),fontFamily:a,fontWeight:d,fillStyle:c,textBottom:e});i(g);const h=document.querySelector("img[src=\"".concat(g,"\"]:not([data-text-as-image])"));m(h,b,f)}}},Object.defineProperty(a,"__esModule",{value:!0})});
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
(global = global || self, factory(global.bandicoot = {}, global.React));
}(this, (function (exports, React) { 'use strict';
var React__default = 'default' in React ? React['default'] : React;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function RichTextContainer(props) {
const contextValueRef = React.useRef(Object.assign({}, defaultContextValue));
const contextValue = contextValueRef.current;
const selectionChangedListenersRef = React.useRef([]);
const blurListenersRef = React.useRef([]);
const newHTMLListenersRef = React.useRef([]);
const serializers = React.useRef([]);
React.useEffect(() => {
const contentEditableElement = contextValue.getContentEditableElement();
if (contentEditableElement && contentEditableElement.innerHTML) {
contextValue.fireNewHTML();
}
}, [contextValue]);
contextValue.addSelectionChangedListener = listener => {
selectionChangedListenersRef.current.push(listener);
};
contextValue.removeSelectionChangedListener = listener => {
selectionChangedListenersRef.current = selectionChangedListenersRef.current.filter(l => l !== listener);
};
contextValue.fireSelectionChanged = () => {
selectionChangedListenersRef.current.forEach(listener => listener());
};
contextValue.addBlurListener = listener => {
blurListenersRef.current.push(listener);
};
contextValue.removeBlurListener = listener => {
blurListenersRef.current = blurListenersRef.current.filter(l => l !== listener);
};
contextValue.fireBlur = () => {
blurListenersRef.current.forEach(listener => listener());
};
contextValue.addNewHTMLListener = listener => {
newHTMLListenersRef.current.push(listener);
};
contextValue.removeNewHTMLListener = listener => {
newHTMLListenersRef.current = newHTMLListenersRef.current.filter(l => l !== listener);
};
contextValue.fireNewHTML = () => {
newHTMLListenersRef.current.forEach(l => l());
};
contextValue.numSerializers = () => serializers.current.length;
contextValue.addSerializer = serializer => {
serializers.current.push(serializer);
};
contextValue.removeSerializer = serializer => {
serializers.current = serializers.current.filter(s => s !== serializer);
};
contextValue.serialize = dom => {
serializers.current.forEach(serializer => serializer(dom));
return dom.innerHTML;
};
return React__default.createElement(RichTextContext.Provider, {
value: contextValue
}, props.children);
}
const noop = () => {};
const defaultContextValue = {
addSelectionChangedListener: noop,
removeSelectionChangedListener: noop,
fireSelectionChanged: noop,
selectRangeFromBeforeBlur: noop,
getRangeBeforeBlur: noop,
addBlurListener: noop,
removeBlurListener: noop,
fireBlur: noop,
addNewHTMLListener: noop,
removeNewHTMLListener: noop,
fireNewHTML: noop,
isFocused: noop,
getContentEditableElement: noop,
numSerializers: () => 0,
addSerializer: noop,
removeSerializer: noop,
serialize: noop,
sanitizeHTML: noop
};
const RichTextContext = React__default.createContext(defaultContextValue);
const noop$1 = () => {};
const noopWithReturn = value => value;
let globalBandicootId = 0;
const RichTextEditor = React.forwardRef((props, editorRef) => {
if (typeof props.sanitizeHTML !== "function") {
throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");
}
const richTextContext = React.useContext(RichTextContext);
richTextContext.sanitizeHTML = props.sanitizeHTML;
const divRef = React.useRef(null);
const selectionRangeBeforeBlurRef = React.useRef(null);
const {
isFocused,
setFocused
} = useSynchronousFocusState();
const bandicootId = React.useRef(globalBandicootId++);
const [lastSavedHTML, setLastSavedHTML] = React.useState(() => richTextContext.sanitizeHTML(props.initialHTML, 'initialSetLastSavedHTML'));
const [hasSetInitialHTML, setHasSetInitialHTML] = React.useState(false);
if (editorRef) {
const current = {
setHTML,
resetEditor,
getHTML,
focus
};
if (typeof editorRef === 'function') {
editorRef({
current
});
} else {
editorRef.current = current;
}
}
function interceptPaste(event) {
// https://developer.mozilla.org/en-US/docs/Web/Events/paste
event.preventDefault();
event.stopPropagation();
const clipboardData = event.clipboardData || window.clipboardData;
const pasteData = clipboardData.getData('text/html') || clipboardData.getData('text');
let paste = richTextContext.sanitizeHTML(pasteData, 'pasteHTML');
let newPaste = props.pasteFn(paste);
if (newPaste !== false) {
document.execCommand('insertHTML', null, newPaste);
}
}
React.useEffect(() => {
divRef.current.addEventListener('paste', interceptPaste);
return () => divRef.current.removeEventListener('paste', interceptPaste);
}, [props.pasteFn]);
function emptyEditor() {
// do it with selection and execCommand so it can be undone with Ctrl Z
const range = document.createRange();
range.selectNodeContents(divRef.current);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('removeFormat');
document.execCommand('delete');
}
React.useEffect(() => {
document.addEventListener('selectionchange', handleSelectionChange);
return () => document.removeEventListener('selectionchange', handleSelectionChange);
});
React.useEffect(() => {
if (divRef.current) {
let el = divRef.current;
while (el.parentNode) {
el = el.parentNode;
if (el.tagName === 'SPAN') console.warn('A span tag has been detected in the parents of <RichTextEditor>. This has been known to cause issues. https://github.com/CanopyTax/bandicoot/issues/69');
}
}
}, [divRef.current]);
React.useEffect(() => {
if (props.save && props.unchangedInterval && divRef.current && isFocused()) {
const mutationConfig = {
attributes: true,
childList: true,
subtree: true,
characterData: true
};
let timeout;
const observer = new MutationObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(save, props.unchangedInterval);
});
observer.observe(divRef.current, mutationConfig);
return () => {
observer.disconnect();
clearTimeout(timeout);
};
}
}, [props.unchangedInterval, props.save, divRef.current, isFocused()]);
React.useEffect(() => {
// Clicking on bandicoot richtext buttons triggers a blur event that will setFocus to false we want to delay the
// save event that is triggered by blur events. 100ms is arbitrary. Whenever react rerenders the rich-text-editor
// due to focused state changing we need to either clear the blurTimeout to prevent a save action from firing
// (in the case of a quick refocus triggered by the rich text buttons) or fire a save event after waiting 100ms
if (isFocused() === false) {
const timeout = setTimeout(() => {
richTextContext.fireBlur();
save();
}, 100);
return () => {
clearTimeout(timeout);
};
}
}, [isFocused()]);
React.useEffect(() => {
richTextContext.selectRangeFromBeforeBlur = function () {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
usePreviousRange: false
};
if (divRef.current && document.activeElement !== divRef.current && !divRef.current.contains(document.activeElement)) {
if (options.usePreviousRange && selectionRangeBeforeBlurRef.current) {
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(selectionRangeBeforeBlurRef.current);
} else {
divRef.current.focus();
}
setFocused(true); // We are calling handleSelectionChange manually because calling divRef.current.focus() programatically does not trigger
// a focus event like it normally would if a user did it. And we need the bandicoot hooks to know that the selection has changed,
// which is done by calling fireSelectionChanged (which is what handleSelectionChange does).
//
// And we're using setTimeout because if we don't wait a tick of the event loop, the browser will tell us the old
// command state from before we focused.
setTimeout(handleSelectionChange);
}
};
richTextContext.getRangeFromBeforeBlur = () => {
return selectionRangeBeforeBlurRef.current;
};
richTextContext.isFocused = isFocused;
richTextContext.getContentEditableElement = () => divRef.current;
});
React.useEffect(() => {
if (!hasSetInitialHTML && props.initialHTML) {
setHasSetInitialHTML(true);
setHTML(props.initialHTML, true);
}
}, [hasSetInitialHTML, setHTML, richTextContext]);
React.useEffect(() => {
if (props.placeholder) {
const styleElement = document.createElement('style');
styleElement.textContent = ".bandicoot-id-".concat(bandicootId.current, ":empty:before { content: attr(data-placeholder); }");
document.head.appendChild(styleElement);
const styleSheet = Array.prototype.slice.call(document.styleSheets).find(s => s.ownerNode === styleElement);
const styleDeclaration = styleSheet.cssRules[0].style;
const styles = props.placeholderStyle ? props.placeholderStyle : getDefaultPlaceholderStyles();
for (let propName in styles) {
styleDeclaration[propName] = props.placeholderStyle ? props.placeholderStyle[propName] : styles[propName];
}
return () => styleElement.parentNode.removeChild(styleElement);
}
}, [props.placeholder, props.placeholderStyle, bandicootId.current]);
const divStyles = props.style || {};
return React__default.createElement("div", {
contentEditable: !props.disabled,
onBlur: () => setFocused(false),
onFocus: onFocus,
ref: divRef,
className: props.className + " bandicoot-id-" + bandicootId.current,
style: _objectSpread2({
wordBreak: 'break-word',
wordWrap: 'break-word',
overflowWrap: 'break-word'
}, divStyles),
"data-placeholder": props.placeholder
});
function onFocus() {
setFocused(true);
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectionRangeBeforeBlurRef.current = selection.getRangeAt(0);
}
}
function handleSelectionChange(evt) {
if (isFocused()) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
selectionRangeBeforeBlurRef.current = window.getSelection().getRangeAt(0);
}
richTextContext.fireSelectionChanged();
}
}
function save() {
const html = getHTML();
if (html !== lastSavedHTML) {
setLastSavedHTML(html);
props.save(html);
}
}
function serialize() {
let html = divRef.current.innerHTML;
if (richTextContext.numSerializers() > 0) {
const dom = new DOMParser().parseFromString(html, 'text/html');
html = richTextContext.serialize(dom.body);
}
return html;
}
function setHTML(html, isInitialMount) {
// both emptyEditor() and focus() result in unwanted autofocus on contentEditable on first render
// if the consumer wants autofocus, give us the prop
if (!isInitialMount) {
emptyEditor();
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML');
focus();
} else {
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML');
if (props.autoFocus) focus();
}
richTextContext.fireNewHTML();
}
function resetEditor() {
setHTML('');
}
function getHTML() {
return richTextContext.sanitizeHTML(serialize(), 'getHTML');
}
function focus() {
divRef.current.focus();
setFocused(true);
}
function getDefaultPlaceholderStyles() {
if (!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) {
return {
color: 'rgb(117, 117, 117)'
}; // default chrome style
} else {
return {
opacity: '0.54'
}; // firefox
}
}
}); // This hook allows you to change the focused value synchronously instead of queuing it
// up with a set state. This is necessary since the selectionchange listener depends on
// the blur and focus listeners to update the state synchronously (before the selectionchange
// listener is fired)
function useSynchronousFocusState() {
const focusedRef = React.useRef(null); // To be able to trigger a re-render when the ref value changes synchronously
const [bool, setBool] = React.useState(false);
return {
isFocused,
setFocused
};
function isFocused() {
return focusedRef.current;
}
function setFocused(val) {
focusedRef.current = val;
setBool(!bool);
}
}
RichTextEditor.defaultProps = {
className: '',
initialHTML: '',
save: noop$1,
placeholder: '',
pasteFn: noopWithReturn
};
function useDocumentExecCommand(commandName) {
const richTextContext = React.useContext(RichTextContext);
return {
performCommand(evt) {
richTextContext.selectRangeFromBeforeBlur();
document.execCommand(commandName);
},
performCommandWithValue(value) {
richTextContext.selectRangeFromBeforeBlur();
const showDefaultUI = null;
document.execCommand(commandName, showDefaultUI, value);
}
};
}
function useFormatBlock() {
const {
performCommandWithValue
} = useDocumentExecCommand('formatBlock');
return {
formatBlock(value) {
if (value === document.queryCommandValue('formatBlock')) {
performCommandWithValue('div');
} else {
performCommandWithValue(value);
}
}
};
}
const defaultActiveInfo = {
isActive: false,
value: false
};
function useDocumentQueryCommandState(commandName, activeValueMatch) {
const [activeInfo, setActiveInfo] = React.useState(defaultActiveInfo);
const richTextContext = React.useContext(RichTextContext);
React.useEffect(() => {
richTextContext.addSelectionChangedListener(recheckActive);
return () => richTextContext.removeSelectionChangedListener(recheckActive);
}, [activeInfo, setActiveInfo]);
React.useEffect(() => {
richTextContext.addBlurListener(setInactive);
return () => richTextContext.removeBlurListener(setInactive);
}, [activeInfo, setActiveInfo]);
function recheckActive() {
const actualActiveValue = document.queryCommandValue(commandName);
const isActuallyActive = activeValueMatch ? activeValueMatch === actualActiveValue : document.queryCommandState(commandName);
if (isActuallyActive !== activeInfo.isActive || actualActiveValue !== activeInfo.value) {
setActiveInfo({
isActive: isActuallyActive,
value: actualActiveValue
});
}
}
function setInactive() {
setActiveInfo(defaultActiveInfo);
}
return {
isActive: activeInfo.isActive,
activeValue: activeInfo.value
};
}
function useFontSize(_ref) {
let {
defaultFontSize = '14px',
fontSizes
} = _ref;
if (fontSizes.length > 7) {
throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");
}
const [fontSize, setFontSize] = React.useState(defaultFontSize);
const [lastSelection, setLastSelection] = React.useState(null);
const {
performCommandWithValue
} = useDocumentExecCommand('fontSize');
const richTextContext = React.useContext(RichTextContext);
const currentlySelectedFontSize = useCurrentlySelectedFontSize();
useSizeOverrides();
return {
currentlySelectedFontSize,
setSize(fontSize) {
const integerSize = fontSizes.findIndex(size => size === fontSize) + 1;
if (integerSize <= 0) {
throw Error("Cannot set font size since '".concat(fontSize, "' was not passed in the fontSizes array"));
}
setFontSize(fontSize);
performCommandWithValue(integerSize);
}
};
function useCurrentlySelectedFontSize() {
React.useEffect(() => {
richTextContext.addSelectionChangedListener(selectionChanged);
return () => richTextContext.removeSelectionChangedListener(selectionChanged);
function selectionChanged() {
const selection = window.getSelection(); // If the selection has 'actually' changed (i.e. not just due to the editor bluring and focusing)
if (!lastSelection || selection.anchorNode !== lastSelection.anchorNode || selection.anchorOffset !== lastSelection.anchorOffset || selection.focusNode !== lastSelection.focusNode || selection.focusOffset !== lastSelection.focusOffset) {
setLastSelection({
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset
}); // If there is no selection, we won't change the font size
if (selection.rangeCount > 0) {
let selectionNode = selection.getRangeAt(0).startContainer;
if (selectionNode.nodeType !== 1) {
// we've got a text node or comment node or other type of node that's not an element
selectionNode = selectionNode.parentElement;
}
const stringFontSize = window.getComputedStyle(selectionNode).fontSize;
const newSize = stringFontSize;
if (newSize !== fontSize) {
setFontSize(newSize);
}
}
}
}
}, [fontSize, setFontSize, lastSelection]);
return fontSize;
}
function useSizeOverrides() {
React.useEffect(() => {
const cssString = fontSizes.reduce((acc, style, index) => {
return "".concat(acc, " font[size=\"").concat(index + 1, "\"] {font-size: ").concat(style, "}");
}, '');
const styleEl = document.createElement('style');
styleEl.textContent = cssString;
document.head.appendChild(styleEl);
return () => document.head.removeChild(styleEl);
}, [fontSizes]);
React.useEffect(() => {
richTextContext.addSerializer(serializer);
return () => richTextContext.removeSerializer(serializer);
function serializer(dom) {
const fontEls = dom.querySelectorAll('font');
for (let i = 0; i < fontEls.length; i++) {
const fontEl = fontEls[i];
const integerSize = Number(fontEl.getAttribute('size'));
if (integerSize > fontSizes.length) {
throw Error("Cannot find fontSize for integer size '".concat(integerSize, "'"));
}
const size = fontSizes[integerSize - 1];
fontEl.removeAttribute('size');
fontEl.style.fontSize = size;
fontEl.dataset.integerSize = integerSize;
}
}
}, [fontSizes]);
React.useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const fontEls = richTextContext.getContentEditableElement().querySelectorAll('font');
for (let i = 0; i < fontEls.length; i++) {
const fontEl = fontEls[i];
const cssFontSize = fontEl.style.fontSize;
const integerSize = fontSizes.findIndex(size => size === cssFontSize) + 1;
if (integerSize > 0) {
fontEl.style.fontSize = '';
fontEl.setAttribute('size', integerSize);
}
}
}
}, [fontSizes]);
}
}
const noop$2 = () => {};
const defaultAcceptImgTypes = '.jpg, .png, image/*';
const defaultOpts = {
processImgElement: noop$2,
fileBlobToUrl: defaultFileBlobToUrl,
acceptImgTypes: defaultAcceptImgTypes
};
function useImage() {
let {
processImgElement = noop$2,
fileBlobToUrl = defaultFileBlobToUrl,
acceptImgTypes = defaultAcceptImgTypes
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOpts;
const {
performCommandWithValue
} = useDocumentExecCommand('insertImage');
const richTextContext = React.useContext(RichTextContext);
const fileInputRef = React.useRef(null);
const dataUrls = React__default.useRef({});
useNewHtmlHandler();
useFileChooserInput();
useSerializer();
return {
chooseFile(evt) {
fileInputRef.current.click();
},
removeImage(imgElement) {
const range = document.createRange();
range.selectNode(imgElement);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete');
}
};
function handleImageElement(imgElement) {
imgElement.style.cursor = 'pointer';
imgElement.style.maxWidth = '100%';
processImgElement(imgElement);
}
function useNewHtmlHandler() {
React.useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const imgElements = richTextContext.getContentEditableElement().querySelectorAll('img:not([data-text-as-image])');
imgElements.forEach(handleImageElement);
}
}, [processImgElement]);
}
function useFileChooserInput() {
React.useEffect(() => {
fileInputRef.current = document.createElement('input');
const fileInputElement = fileInputRef.current;
fileInputElement.type = 'file';
fileInputElement.accept = acceptImgTypes;
fileInputElement.multiple = false;
fileInputElement.addEventListener('change', () => {
if (fileInputElement.files && fileInputElement.files.length > 0) {
fileBlobToUrl(fileInputElement.files[0], imgUrl => {
performCommandWithValue(imgUrl);
const imgElement = document.querySelector("img[src=\"".concat(imgUrl, "\"]"));
if (imgElement.src && imgElement.src.startsWith('blob:')) {
const reader = new FileReader();
reader.addEventListener('load', () => {
dataUrls.current[imgElement.src] = reader.result;
});
reader.readAsDataURL(fileInputElement.files[0]);
}
handleImageElement(imgElement);
});
}
});
}, [fileBlobToUrl, processImgElement, acceptImgTypes]);
}
function useSerializer() {
React.useEffect(() => {
richTextContext.addSerializer(serializer);
return () => richTextContext.removeSerializer(serializer);
});
}
function serializer(dom) {
dom.querySelectorAll('img').forEach(imgEl => {
if (dataUrls.current[imgEl.src]) {
imgEl.src = dataUrls.current[imgEl.src];
}
});
}
}
function defaultFileBlobToUrl(file, cbk) {
cbk(URL.createObjectURL(file));
}
let tempId = 0;
const noop$3 = () => {};
const defaultOptions = {
processAnchorElement: noop$3
};
function useLink() {
let {
processAnchorElement = noop$3
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions;
const richTextContext = React.useContext(RichTextContext);
const {
performCommand
} = useDocumentExecCommand('unlink');
const {
performCommandWithValue
} = useDocumentExecCommand('insertHTML');
useNewHtmlHandler();
return {
getTextFromBeforeBlur,
selectEntireLink,
unlink,
insertLink
};
function getTextFromBeforeBlur() {
const range = richTextContext.getRangeFromBeforeBlur();
if (range) {
return range.toString();
} else {
return null;
}
}
function selectEntireLink(anchorElement) {
const range = document.createRange();
range.selectNodeContents(anchorElement);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function insertLink(link, displayedText) {
richTextContext.selectRangeFromBeforeBlur({
usePreviousRange: true
});
const id = "rte-link-temp-id-".concat(tempId++);
performCommandWithValue("<a href=\"".concat(link, "\" id=\"").concat(id, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(displayedText, "</a>"));
const anchorElement = document.getElementById(id);
anchorElement.removeAttribute('id');
processAnchorElement(anchorElement);
}
function useNewHtmlHandler() {
React.useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const anchorElements = richTextContext.getContentEditableElement().querySelectorAll('a');
anchorElements.forEach(processAnchorElement);
}
}, [processAnchorElement]);
}
function unlink() {
performCommand();
if (window.navigator.userAgent.includes('Edge/14') || window.navigator.userAgent.includes('Edge/15') || window.navigator.userAgent.includes('Edge/16') || window.navigator.userAgent.includes('Edge/17')) {
// Older versions of Edge remove the <a> when you unlink, but keep the text blue and underlined so it looks like a link.
// Ideally we'd be super smart about working around this, but for now I'm just removing all rich text formatting from the
// text that used to be a link when you've got an older version of Edge.
document.execCommand('removeFormat');
}
}
}
let tempId$1 = 0;
const noop$4 = () => {};
const defaultOptions$1 = {
processContentEditableFalseElement: noop$4
};
function useContentEditableFalse() {
let {
processContentEditableFalseElement = noop$4
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$1;
const {
performCommandWithValue
} = useDocumentExecCommand('insertHTML');
const richTextContext = React.useContext(RichTextContext);
return {
insertContentEditableFalseElement(innerHTML) {
const id = "rte-ce-false-temp-id-" + tempId$1++;
const htmlToInsert = "<span id=\"".concat(id, "\">").concat(richTextContext.sanitizeHTML(innerHTML, 'insertContentEditableFalseHTML'), "</span>");
performCommandWithValue(htmlToInsert);
const contentEditableFalseElement = document.getElementById(id);
handleContentEditableFalseElement(contentEditableFalseElement);
}
};
function handleContentEditableFalseElement(contentEditableFalseElement) {
contentEditableFalseElement.removeAttribute('id');
contentEditableFalseElement.contentEditable = false;
contentEditableFalseElement.addEventListener('click', () => selectRangeAfterNode(contentEditableFalseElement)); // if we are inserting this at the start of the rich text content editable container, we need to make sure a
// cursor still appears and works when you select the beginning of the rich text container. This is done with
// an empty span that *is* contentEditable
if (!contentEditableFalseElement.previousSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) {
const editableElementBefore = document.createElement('span');
contentEditableFalseElement.parentElement.insertBefore(editableElementBefore, contentEditableFalseElement);
} // if we are inserting this at the end of the rich text content editable container, we need to make sure a
// cursor still appears and works when you select the end of the rich text container. This is done with
// an empty span that *is* contentEditable
if (!contentEditableFalseElement.nextSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) {
const editableElementAfter = document.createElement('span');
contentEditableFalseElement.insertAdjacentElement('afterend', editableElementAfter);
}
selectRangeAfterNode(contentEditableFalseElement);
processContentEditableFalseElement(contentEditableFalseElement);
}
}
function selectRangeAfterNode(node) {
const range = document.createRange();
range.setStartAfter(node);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
const noop$5 = () => {};
const defaultOptions$2 = {
processSerializedElement: noop$5,
fontFamily: null,
fillStyle: '#00bf4b',
fontWeight: 'bold',
textBottom: null,
backgroundColor: 'transparent'
};
function useTextAsImage() {
let {
processSerializedElement = noop$5,
fontFamily,
fillStyle,
fontWeight,
textBottom,
backgroundColor
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$2;
const {
performCommandWithValue
} = useDocumentExecCommand('insertImage');
const richTextContext = React.useContext(RichTextContext);
useNewHtmlHandler();
useSerializer();
return {
insertTextAsImage(text) {
richTextContext.selectRangeFromBeforeBlur({
usePreviousRange: true
});
const url = textToUrl({
text,
referenceEl: getSelectedElement(),
fontFamily,
fontWeight,
fillStyle,
textBottom
});
performCommandWithValue(url);
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])"));
processImgElement(imgElement, text, backgroundColor);
}
};
function useNewHtmlHandler() {
React.useEffect(() => {
richTextContext.addNewHTMLListener(newHtml);
return () => richTextContext.removeNewHTMLListener(newHtml);
function newHtml() {
const spanEls = richTextContext.getContentEditableElement().querySelectorAll('span[data-text-as-image]');
for (let i = 0; i < spanEls.length; i++) {
const spanEl = spanEls[i];
const url = textToUrl({
text: spanEl.dataset.textAsImage,
referenceEl: spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement,
fontFamily,
fontWeight,
fillStyle,
textBottom
});
const imgEl = document.createElement('img');
imgEl.src = url;
processImgElement(imgEl, spanEl.dataset.textAsImage, backgroundColor);
spanEl.parentNode.replaceChild(imgEl, spanEl);
}
}
});
}
function useSerializer() {
React.useEffect(() => {
richTextContext.addSerializer(htmlSerializer);
return () => richTextContext.removeSerializer(htmlSerializer);
function htmlSerializer(dom) {
const textAsImage = dom.querySelectorAll('img[data-text-as-image]');
for (let i = 0; i < textAsImage.length; i++) {
const imgEl = textAsImage[i];
const spanEl = document.createElement('span');
spanEl.dataset.textAsImage = imgEl.dataset.textAsImage;
processSerializedElement(spanEl, spanEl.dataset.textAsImage);
imgEl.parentNode.replaceChild(spanEl, imgEl);
}
}
}, []);
}
}
function textToUrl(_ref) {
let {
text,
referenceEl,
fontFamily,
fillStyle,
fontWeight,
textBottom
} = _ref;
const computedStyle = window.getComputedStyle(referenceEl);
const currentFontSize = Number(computedStyle.fontSize.replace('px', ''));
const currentLineHeight = Number(computedStyle.lineHeight.replace('px', ''));
const currentFontFamily = fontFamily || computedStyle.fontFamily;
const font = "".concat(fontWeight, " ").concat(currentFontSize, "px ").concat(currentFontFamily);
const testDiv = document.createElement('div');
testDiv.style.font = font;
testDiv.style.position = 'absolute';
testDiv.style.visibility = 'hidden';
testDiv.style.whiteSpace = 'nowrap';
testDiv.textContent = text;
document.body.appendChild(testDiv);
const canvas = document.createElement('canvas');
canvas.width = testDiv.clientWidth + 1;
canvas.height = currentLineHeight;
const c = canvas.getContext('2d');
c.font = font;
c.fillStyle = fillStyle || defaultOptions$2.fillStyle;
c.textBaseline = 'bottom';
const yPosition = textBottom || currentLineHeight - 3;
c.fillText(text, 0, yPosition);
document.body.removeChild(testDiv);
return c.canvas.toDataURL();
}
function processImgElement(imgElement, text, backgroundColor) {
imgElement.style.verticalAlign = 'bottom';
imgElement.dataset.textAsImage = text;
imgElement.style.backgroundColor = backgroundColor || defaultOptions$2.backgroundColor;
imgElement.addEventListener('click', evt => {
const rect = imgElement.getBoundingClientRect();
const middlePoint = rect.left + rect.width / 2;
const range = document.createRange();
if (evt.x < middlePoint) {
range.setStartBefore(imgElement);
} else {
range.setStartAfter(imgElement);
}
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
}
function getSelectedElement() {
let result = getSelection().getRangeAt(0).commonAncestorContainer;
return result.nodeType === 1 ? result : result.parentElement;
}
function useElementDeletionDetection(domElement, cbk) {
const richTextContext = React.useContext(RichTextContext);
React.useEffect(() => {
if (domElement) {
richTextContext.addSelectionChangedListener(checkElementConnected);
return () => richTextContext.removeSelectionChangedListener(checkElementConnected);
function checkElementConnected() {
if (!domElement.isConnected && !domElement._bandicoot_delete_callback_called) {
domElement._bandicoot_delete_callback_called = true;
cbk(domElement);
}
}
}
}, [domElement, cbk]);
}
function styleInject(css, ref) {
if (ref === void 0) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') {
return;
}
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = ".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}";
var style = {"bandicootButton":"icon-button_bandicootButton__2IZP5"};
styleInject(css);
function IconButton(props) {
return React__default.createElement("button", _extends({}, props, {
className: "\n ".concat(style.bandicootButton, "\n ").concat(props.isActive ? 'active-control-button' : '', "\n ").concat(props.className || '', "\n "),
onClick: props.onClick
}), props.children);
}
BoldIcon.defaultProps = {
width: 24,
height: 24
};
function BoldIcon(props) {
return React__default.createElement("svg", {
width: props.width,
height: props.height,
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink",
viewBox: "0 0 317.41 317.41"
}, React__default.createElement("path", {
d: "M281.4 158.7c21.7-15.1 36-40.3 36-68.7 0-46.2-37.6-83.7-83.7-83.7h-40H115 45 0v30h45v245H0v30h45 70 78.7 40c46.2 0 83.8-37.6 83.8-83.7C317.4 199 303.2 173.9 281.4 158.7zM193.7 36.2c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115v-107.5H193.7zM115 173.7h78.7c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115V173.7z"
}));
}
function BoldButton(props) {
const {
performCommand
} = useDocumentExecCommand('bold');
const {
isActive
} = useDocumentQueryCommandState('bold');
return React__default.createElement(IconButton, {
onClick: performCommand,
isActive: isActive
}, React__default.createElement(BoldIcon, props));
}
exports.BoldButton = BoldButton;
exports.RichTextContainer = RichTextContainer;
exports.RichTextContext = RichTextContext;
exports.RichTextEditor = RichTextEditor;
exports.useContentEditableFalse = useContentEditableFalse;
exports.useDocumentExecCommand = useDocumentExecCommand;
exports.useDocumentQueryCommandState = useDocumentQueryCommandState;
exports.useElementDeletionDetection = useElementDeletionDetection;
exports.useFontSize = useFontSize;
exports.useFormatBlock = useFormatBlock;
exports.useImage = useImage;
exports.useLink = useLink;
exports.useTextAsImage = useTextAsImage;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=bandicoot.umd.js.map

2

package.json
{
"name": "bandicoot",
"version": "4.2.0",
"version": "4.2.1",
"description": "React rich text editor",

@@ -5,0 +5,0 @@ "main": "dist/bandicoot.umd.js",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc