UNPKG

@lobehub/editor

Version:

A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.

455 lines 475 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */'use strict';/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ // Do not require this module directly! Use normal `invariant` calls. var _Symbol$iterator,_Symbol$iterator2;function _get(){if(typeof Reflect!=="undefined"&&Reflect.get){_get=Reflect.get.bind();}else{_get=function _get(target,property,receiver){var base=_superPropBase(target,property);if(!base)return;var desc=Object.getOwnPropertyDescriptor(base,property);if(desc.get){return desc.get.call(arguments.length<3?target:receiver);}return desc.value;};}return _get.apply(this,arguments);}function _superPropBase(object,property){while(!Object.prototype.hasOwnProperty.call(object,property)){object=_getPrototypeOf(object);if(object===null)break;}return object;}function _toConsumableArray(arr){return _arrayWithoutHoles(arr)||_iterableToArray(arr)||_unsupportedIterableToArray(arr)||_nonIterableSpread();}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}function _iterableToArray(iter){if(typeof Symbol!=="undefined"&&iter[Symbol.iterator]!=null||iter["@@iterator"]!=null)return Array.from(iter);}function _arrayWithoutHoles(arr){if(Array.isArray(arr))return _arrayLikeToArray(arr);}function _inherits(subClass,superClass){if(typeof superClass!=="function"&&superClass!==null){throw new TypeError("Super expression must either be null or a function");}subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,writable:true,configurable:true}});Object.defineProperty(subClass,"prototype",{writable:false});if(superClass)_setPrototypeOf(subClass,superClass);}function _setPrototypeOf(o,p){_setPrototypeOf=Object.setPrototypeOf?Object.setPrototypeOf.bind():function _setPrototypeOf(o,p){o.__proto__=p;return o;};return _setPrototypeOf(o,p);}function _createSuper(Derived){var hasNativeReflectConstruct=_isNativeReflectConstruct();return function _createSuperInternal(){var Super=_getPrototypeOf(Derived),result;if(hasNativeReflectConstruct){var NewTarget=_getPrototypeOf(this).constructor;result=Reflect.construct(Super,arguments,NewTarget);}else{result=Super.apply(this,arguments);}return _possibleConstructorReturn(this,result);};}function _possibleConstructorReturn(self,call){if(call&&(_typeof(call)==="object"||typeof call==="function")){return call;}else if(call!==void 0){throw new TypeError("Derived constructors may only return object or undefined");}return _assertThisInitialized(self);}function _assertThisInitialized(self){if(self===void 0){throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return self;}function _isNativeReflectConstruct(){if(typeof Reflect==="undefined"||!Reflect.construct)return false;if(Reflect.construct.sham)return false;if(typeof Proxy==="function")return true;try{Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}));return true;}catch(e){return false;}}function _getPrototypeOf(o){_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf.bind():function _getPrototypeOf(o){return o.__proto__||Object.getPrototypeOf(o);};return _getPrototypeOf(o);}function _typeof(o){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o;}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o;},_typeof(o);}function ownKeys(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);r&&(o=o.filter(function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable;})),t.push.apply(t,o);}return t;}function _objectSpread(e){for(var r=1;r<arguments.length;r++){var t=null!=arguments[r]?arguments[r]:{};r%2?ownKeys(Object(t),!0).forEach(function(r){_defineProperty(e,r,t[r]);}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):ownKeys(Object(t)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r));});}return e;}function _defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value"in descriptor)descriptor.writable=true;Object.defineProperty(target,_toPropertyKey(descriptor.key),descriptor);}}function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);Object.defineProperty(Constructor,"prototype",{writable:false});return Constructor;}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}function _defineProperty(obj,key,value){key=_toPropertyKey(key);if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}function _toPropertyKey(t){var i=_toPrimitive(t,"string");return"symbol"==_typeof(i)?i:String(i);}function _toPrimitive(t,r){if("object"!=_typeof(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||"default");if("object"!=_typeof(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.");}return("string"===r?String:Number)(t);}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_unsupportedIterableToArray(arr,i)||_nonIterableRest();}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}function _iterableToArrayLimit(r,l){var t=null==r?null:"undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(null!=t){var e,n,i,u,a=[],f=!0,o=!1;try{if(i=(t=t.call(r)).next,0===l){if(Object(t)!==t)return;f=!1;}else for(;!(f=(e=i.call(t)).done)&&(a.push(e.value),a.length!==l);f=!0);}catch(r){o=!0,n=r;}finally{try{if(!f&&null!=t.return&&(u=t.return(),Object(u)!==u))return;}finally{if(o)throw n;}}return a;}}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr;}function _createForOfIteratorHelper(o,allowArrayLike){var it=typeof Symbol!=="undefined"&&o[Symbol.iterator]||o["@@iterator"];if(!it){if(Array.isArray(o)||(it=_unsupportedIterableToArray(o))||allowArrayLike&&o&&typeof o.length==="number"){if(it)o=it;var i=0;var F=function F(){};return{s:F,n:function n(){if(i>=o.length)return{done:true};return{done:false,value:o[i++]};},e:function e(_e2){throw _e2;},f:F};}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}var normalCompletion=true,didErr=false,err;return{s:function s(){it=it.call(o);},n:function n(){var step=it.next();normalCompletion=step.done;return step;},e:function e(_e3){didErr=true;err=_e3;},f:function f(){try{if(!normalCompletion&&it.return!=null)it.return();}finally{if(didErr)throw err;}}};}function _unsupportedIterableToArray(o,minLen){if(!o)return;if(typeof o==="string")return _arrayLikeToArray(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);if(n==="Object"&&o.constructor)n=o.constructor.name;if(n==="Map"||n==="Set")return Array.from(o);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return _arrayLikeToArray(o,minLen);}function _arrayLikeToArray(arr,len){if(len==null||len>arr.length)len=arr.length;for(var i=0,arr2=new Array(len);i<len;i++)arr2[i]=arr[i];return arr2;}function formatDevErrorMessage(message){throw new Error(message);}/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */var CAN_USE_DOM=typeof window!=='undefined'&&typeof window.document!=='undefined'&&typeof window.document.createElement!=='undefined';/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */var documentMode=CAN_USE_DOM&&'documentMode'in document?document.documentMode:null;var IS_APPLE=CAN_USE_DOM&&/Mac|iPod|iPhone|iPad/.test(navigator.platform);var IS_FIREFOX=CAN_USE_DOM&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);var CAN_USE_BEFORE_INPUT=CAN_USE_DOM&&'InputEvent'in window&&!documentMode?'getTargetRanges'in new window.InputEvent('input'):false;var IS_SAFARI=CAN_USE_DOM&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent);var IS_IOS=CAN_USE_DOM&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream;var IS_ANDROID=CAN_USE_DOM&&/Android/.test(navigator.userAgent);// Keep these in case we need to use them in the future. // export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform); var IS_CHROME=CAN_USE_DOM&&/^(?=.*Chrome).*/i.test(navigator.userAgent);// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode; var IS_ANDROID_CHROME=CAN_USE_DOM&&IS_ANDROID&&IS_CHROME;var IS_APPLE_WEBKIT=CAN_USE_DOM&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&IS_APPLE&&!IS_CHROME;/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */function normalizeClassNames(){var rval=[];for(var _len=arguments.length,classNames=new Array(_len),_key=0;_key<_len;_key++){classNames[_key]=arguments[_key];}for(var _i=0,_classNames=classNames;_i<_classNames.length;_i++){var className=_classNames[_i];if(className&&typeof className==='string'){var _iterator=_createForOfIteratorHelper(className.matchAll(/\S+/g)),_step;try{for(_iterator.s();!(_step=_iterator.n()).done;){var _step$value=_slicedToArray(_step.value,1),s=_step$value[0];rval.push(s);}}catch(err){_iterator.e(err);}finally{_iterator.f();}}}return rval;}/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ // DOM var DOM_ELEMENT_TYPE=1;var DOM_TEXT_TYPE=3;var DOM_DOCUMENT_TYPE=9;var DOM_DOCUMENT_FRAGMENT_TYPE=11;// Reconciling var NO_DIRTY_NODES=0;var HAS_DIRTY_NODES=1;var FULL_RECONCILE=2;// Text node modes var IS_NORMAL=0;var IS_TOKEN=1;var IS_SEGMENTED=2;// IS_INERT = 3 // Text node formatting var IS_BOLD=1;var IS_ITALIC=1<<1;var IS_STRIKETHROUGH=1<<2;var IS_UNDERLINE=1<<3;var IS_CODE=1<<4;var IS_SUBSCRIPT=1<<5;var IS_SUPERSCRIPT=1<<6;var IS_HIGHLIGHT=1<<7;var IS_LOWERCASE=1<<8;var IS_UPPERCASE=1<<9;var IS_CAPITALIZE=1<<10;var IS_ALL_FORMATTING=IS_BOLD|IS_ITALIC|IS_STRIKETHROUGH|IS_UNDERLINE|IS_CODE|IS_SUBSCRIPT|IS_SUPERSCRIPT|IS_HIGHLIGHT|IS_LOWERCASE|IS_UPPERCASE|IS_CAPITALIZE;// Text node details var IS_DIRECTIONLESS=1;var IS_UNMERGEABLE=1<<1;// Element node formatting var IS_ALIGN_LEFT=1;var IS_ALIGN_CENTER=2;var IS_ALIGN_RIGHT=3;var IS_ALIGN_JUSTIFY=4;var IS_ALIGN_START=5;var IS_ALIGN_END=6;// Reconciliation var NON_BREAKING_SPACE="\xA0";var ZERO_WIDTH_SPACE="\u200B";// For iOS/Safari we use a non breaking space, otherwise the cursor appears // overlapping the composed text. var COMPOSITION_SUFFIX=IS_SAFARI||IS_IOS||IS_APPLE_WEBKIT?NON_BREAKING_SPACE:ZERO_WIDTH_SPACE;var DOUBLE_LINE_BREAK='\n\n';// For FF, we need to use a non-breaking space, or it gets composition // in a stuck state. var COMPOSITION_START_CHAR=IS_FIREFOX?NON_BREAKING_SPACE:COMPOSITION_SUFFIX;var RTL="\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";var LTR="A-Za-z\xC0-\xD6\xD8-\xF6"+"\xF8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C"+"\uFE00-\uFE6F\uFEFD-\uFFFF";// eslint-disable-next-line no-misleading-character-class var RTL_REGEX=new RegExp('^[^'+LTR+']*['+RTL+']');// eslint-disable-next-line no-misleading-character-class var LTR_REGEX=new RegExp('^[^'+RTL+']*['+LTR+']');var TEXT_TYPE_TO_FORMAT={bold:IS_BOLD,capitalize:IS_CAPITALIZE,code:IS_CODE,highlight:IS_HIGHLIGHT,italic:IS_ITALIC,lowercase:IS_LOWERCASE,strikethrough:IS_STRIKETHROUGH,subscript:IS_SUBSCRIPT,superscript:IS_SUPERSCRIPT,underline:IS_UNDERLINE,uppercase:IS_UPPERCASE};var DETAIL_TYPE_TO_DETAIL={directionless:IS_DIRECTIONLESS,unmergeable:IS_UNMERGEABLE};var ELEMENT_TYPE_TO_FORMAT={center:IS_ALIGN_CENTER,end:IS_ALIGN_END,justify:IS_ALIGN_JUSTIFY,left:IS_ALIGN_LEFT,right:IS_ALIGN_RIGHT,start:IS_ALIGN_START};var ELEMENT_FORMAT_TO_TYPE=_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({},IS_ALIGN_CENTER,'center'),IS_ALIGN_END,'end'),IS_ALIGN_JUSTIFY,'justify'),IS_ALIGN_LEFT,'left'),IS_ALIGN_RIGHT,'right'),IS_ALIGN_START,'start');var TEXT_MODE_TO_TYPE={normal:IS_NORMAL,segmented:IS_SEGMENTED,token:IS_TOKEN};var TEXT_TYPE_TO_MODE=_defineProperty(_defineProperty(_defineProperty({},IS_NORMAL,'normal'),IS_SEGMENTED,'segmented'),IS_TOKEN,'token');var NODE_STATE_KEY='$';var PROTOTYPE_CONFIG_METHOD='$config';/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */function $garbageCollectDetachedDecorators(editor,pendingEditorState){var currentDecorators=editor._decorators;var pendingDecorators=editor._pendingDecorators;var decorators=pendingDecorators||currentDecorators;var nodeMap=pendingEditorState._nodeMap;var key;for(key in decorators){if(!nodeMap.has(key)){if(decorators===currentDecorators){decorators=cloneDecorators(editor);}delete decorators[key];}}}function $garbageCollectDetachedDeepChildNodes(node,parentKey,prevNodeMap,nodeMap,nodeMapDelete,dirtyNodes){var child=node.getFirstChild();while(child!==null){var childKey=child.__key;// TODO Revise condition below, redundant? LexicalNode already cleans up children when moving Nodes if(child.__parent===parentKey){if($isElementNode(child)){$garbageCollectDetachedDeepChildNodes(child,childKey,prevNodeMap,nodeMap,nodeMapDelete,dirtyNodes);}// If we have created a node and it was dereferenced, then also // remove it from out dirty nodes Set. if(!prevNodeMap.has(childKey)){dirtyNodes.delete(childKey);}nodeMapDelete.push(childKey);}child=child.getNextSibling();}}function $garbageCollectDetachedNodes(prevEditorState,editorState,dirtyLeaves,dirtyElements){var prevNodeMap=prevEditorState._nodeMap;var nodeMap=editorState._nodeMap;// Store dirtyElements in a queue for later deletion; deleting dirty subtrees too early will // hinder accessing .__next on child nodes var nodeMapDelete=[];var _iterator2=_createForOfIteratorHelper(dirtyElements),_step2;try{for(_iterator2.s();!(_step2=_iterator2.n()).done;){var _step2$value=_slicedToArray(_step2.value,1),_nodeKey=_step2$value[0];var node=nodeMap.get(_nodeKey);if(node!==undefined){// Garbage collect node and its children if they exist if(!node.isAttached()){if($isElementNode(node)){$garbageCollectDetachedDeepChildNodes(node,_nodeKey,prevNodeMap,nodeMap,nodeMapDelete,dirtyElements);}// If we have created a node and it was dereferenced, then also // remove it from out dirty nodes Set. if(!prevNodeMap.has(_nodeKey)){dirtyElements.delete(_nodeKey);}nodeMapDelete.push(_nodeKey);}}}}catch(err){_iterator2.e(err);}finally{_iterator2.f();}for(var _i2=0,_nodeMapDelete=nodeMapDelete;_i2<_nodeMapDelete.length;_i2++){var nodeKey=_nodeMapDelete[_i2];nodeMap.delete(nodeKey);}var _iterator3=_createForOfIteratorHelper(dirtyLeaves),_step3;try{for(_iterator3.s();!(_step3=_iterator3.n()).done;){var _nodeKey2=_step3.value;var _node2=nodeMap.get(_nodeKey2);if(_node2!==undefined&&!_node2.isAttached()){if(!prevNodeMap.has(_nodeKey2)){dirtyLeaves.delete(_nodeKey2);}nodeMap.delete(_nodeKey2);}}}catch(err){_iterator3.e(err);}finally{_iterator3.f();}}/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ // The time between a text entry event and the mutation observer firing. var TEXT_MUTATION_VARIANCE=100;var isProcessingMutations=false;var lastTextEntryTimeStamp=0;function getIsProcessingMutations(){return isProcessingMutations;}function updateTimeStamp(event){lastTextEntryTimeStamp=event.timeStamp;}function initTextEntryListener(editor){if(lastTextEntryTimeStamp===0){getWindow(editor).addEventListener('textInput',updateTimeStamp,true);}}function isManagedLineBreak(dom,target,editor){var isBR=dom.nodeName==='BR';var lexicalLineBreak=target.__lexicalLineBreak;return lexicalLineBreak&&(dom===lexicalLineBreak||isBR&&dom.previousSibling===lexicalLineBreak)||isBR&&getNodeKeyFromDOMNode(dom,editor)!==undefined;}function getLastSelection(editor){return editor.getEditorState().read(function(){var selection=$getSelection();return selection!==null?selection.clone():null;});}function $handleTextMutation(target,node,editor){var domSelection=getDOMSelection(getWindow(editor));var anchorOffset=null;var focusOffset=null;if(domSelection!==null&&domSelection.anchorNode===target){anchorOffset=domSelection.anchorOffset;focusOffset=domSelection.focusOffset;}var text=target.nodeValue;if(text!==null){$updateTextNodeFromDOMContent(node,text,anchorOffset,focusOffset,false);}}function shouldUpdateTextNodeFromMutation(selection,targetDOM,targetNode){if($isRangeSelection(selection)){var anchorNode=selection.anchor.getNode();if(anchorNode.is(targetNode)&&selection.format!==anchorNode.getFormat()){return false;}}return isDOMTextNode(targetDOM)&&targetNode.isAttached();}function $getNearestManagedNodePairFromDOMNode(startingDOM,editor,editorState,rootElement){for(var dom=startingDOM;dom&&!isDOMUnmanaged(dom);dom=getParentElement(dom)){var key=getNodeKeyFromDOMNode(dom,editor);if(key!==undefined){var node=$getNodeByKey(key,editorState);if(node){// All decorator nodes are unmanaged return $isDecoratorNode(node)||!isHTMLElement(dom)?undefined:[dom,node];}}else if(dom===rootElement){return[rootElement,internalGetRoot(editorState)];}}}function flushMutations(editor,mutations,observer){isProcessingMutations=true;var shouldFlushTextMutations=performance.now()-lastTextEntryTimeStamp>TEXT_MUTATION_VARIANCE;try{updateEditorSync(editor,function(){var selection=$getSelection()||getLastSelection(editor);var badDOMTargets=new Map();var rootElement=editor.getRootElement();// We use the current editor state, as that reflects what is // actually "on screen". var currentEditorState=editor._editorState;var blockCursorElement=editor._blockCursorElement;var shouldRevertSelection=false;var possibleTextForFirefoxPaste='';for(var i=0;i<mutations.length;i++){var mutation=mutations[i];var type=mutation.type;var targetDOM=mutation.target;var pair=$getNearestManagedNodePairFromDOMNode(targetDOM,editor,currentEditorState,rootElement);if(!pair){continue;}var _pair=_slicedToArray(pair,2),nodeDOM=_pair[0],targetNode=_pair[1];if(type==='characterData'){// Text mutations are deferred and passed to mutation listeners to be // processed outside of the Lexical engine. if(// TODO there is an edge case here if a mutation happens too quickly // after text input, it may never be handled since we do not // track the ignored mutations in any way shouldFlushTextMutations&&$isTextNode(targetNode)&&isDOMTextNode(targetDOM)&&shouldUpdateTextNodeFromMutation(selection,targetDOM,targetNode)){$handleTextMutation(targetDOM,targetNode,editor);}}else if(type==='childList'){shouldRevertSelection=true;// We attempt to "undo" any changes that have occurred outside // of Lexical. We want Lexical's editor state to be source of truth. // To the user, these will look like no-ops. var addedDOMs=mutation.addedNodes;for(var s=0;s<addedDOMs.length;s++){var addedDOM=addedDOMs[s];var node=$getNodeFromDOMNode(addedDOM);var parentDOM=addedDOM.parentNode;if(parentDOM!=null&&addedDOM!==blockCursorElement&&node===null&&!isManagedLineBreak(addedDOM,parentDOM,editor)){if(IS_FIREFOX){var possibleText=(isHTMLElement(addedDOM)?addedDOM.innerText:null)||addedDOM.nodeValue;if(possibleText){possibleTextForFirefoxPaste+=possibleText;}}parentDOM.removeChild(addedDOM);}}var removedDOMs=mutation.removedNodes;var removedDOMsLength=removedDOMs.length;if(removedDOMsLength>0){var unremovedBRs=0;for(var _s=0;_s<removedDOMsLength;_s++){var removedDOM=removedDOMs[_s];if(isManagedLineBreak(removedDOM,targetDOM,editor)||blockCursorElement===removedDOM){targetDOM.appendChild(removedDOM);unremovedBRs++;}}if(removedDOMsLength!==unremovedBRs){badDOMTargets.set(nodeDOM,targetNode);}}}}// Now we process each of the unique target nodes, attempting // to restore their contents back to the source of truth, which // is Lexical's "current" editor state. This is basically like // an internal revert on the DOM. if(badDOMTargets.size>0){var _iterator4=_createForOfIteratorHelper(badDOMTargets),_step4;try{for(_iterator4.s();!(_step4=_iterator4.n()).done;){var _step4$value=_slicedToArray(_step4.value,2),_nodeDOM=_step4$value[0],_targetNode=_step4$value[1];_targetNode.reconcileObservedMutation(_nodeDOM,editor);}}catch(err){_iterator4.e(err);}finally{_iterator4.f();}}// Capture all the mutations made during this function. This // also prevents us having to process them on the next cycle // of onMutation, as these mutations were made by us. var records=observer.takeRecords();// Check for any random auto-added <br> elements, and remove them. // These get added by the browser when we undo the above mutations // and this can lead to a broken UI. if(records.length>0){for(var _i3=0;_i3<records.length;_i3++){var record=records[_i3];var addedNodes=record.addedNodes;var target=record.target;for(var _s2=0;_s2<addedNodes.length;_s2++){var _addedDOM=addedNodes[_s2];var _parentDOM=_addedDOM.parentNode;if(_parentDOM!=null&&_addedDOM.nodeName==='BR'&&!isManagedLineBreak(_addedDOM,target,editor)){_parentDOM.removeChild(_addedDOM);}}}// Clear any of those removal mutations observer.takeRecords();}if(selection!==null){if(shouldRevertSelection){$setSelection(selection);}if(IS_FIREFOX&&isFirefoxClipboardEvents(editor)){selection.insertRawText(possibleTextForFirefoxPaste);}}});}finally{isProcessingMutations=false;}}function flushRootMutations(editor){var observer=editor._observer;if(observer!==null){var mutations=observer.takeRecords();flushMutations(editor,mutations,observer);}}function initMutationObserver(editor){initTextEntryListener(editor);editor._observer=new MutationObserver(function(mutations,observer){flushMutations(editor,mutations,observer);});}/** * Get the value type (V) from a StateConfig */ /** * Get the key type (K) from a StateConfig */ /** * A value type, or an updater for that value type. For use with * {@link $setState} or any user-defined wrappers around it. */ /** * A type alias to make it easier to define setter methods on your node class * * @example * ```ts * const fooState = createState("foo", { parse: ... }); * class MyClass extends TextNode { * // ... * setFoo(valueOrUpdater: StateValueOrUpdater<typeof fooState>): this { * return $setState(this, fooState, valueOrUpdater); * } * } * ``` */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */ /** * The NodeState JSON produced by this LexicalNode */ /** * Configure a value to be used with StateConfig. * * The value type should be inferred from the definition of parse. * * If the value type is not JSON serializable, then unparse must also be provided. * * Values should be treated as immutable, much like React.useState. Mutating * stored values directly will cause unpredictable behavior, is not supported, * and may trigger errors in the future. * * @example * ```ts * const numberOrNullState = createState('numberOrNull', {parse: (v) => typeof v === 'number' ? v : null}); * // ^? State<'numberOrNull', StateValueConfig<number | null>> * const numberState = createState('number', {parse: (v) => typeof v === 'number' ? v : 0}); * // ^? State<'number', StateValueConfig<number>> * ``` * * Only the parse option is required, it is generally not useful to * override `unparse` or `isEqual`. However, if you are using * non-primitive types such as Array, Object, Date, or something * more exotic then you would want to override this. In these * cases you might want to reach for third party libraries. * * @example * ```ts * const isoDateState = createState('isoDate', { * parse: (v): null | Date => { * const date = typeof v === 'string' ? new Date(v) : null; * return date && !isNaN(date.valueOf()) ? date : null; * } * isEqual: (a, b) => a === b || (a && b && a.valueOf() === b.valueOf()), * unparse: (v) => v && v.toString() * }); * ``` * * You may find it easier to write a parse function using libraries like * zod, valibot, ajv, Effect, TypeBox, etc. perhaps with a wrapper function. */ /** * The return value of {@link createState}, for use with * {@link $getState} and {@link $setState}. */var StateConfig=/*#__PURE__*/_createClass(function StateConfig(key,stateValueConfig){_classCallCheck(this,StateConfig);/** The string key used when serializing this state to JSON */_defineProperty(this,"key",void 0);/** The parse function from the StateValueConfig passed to createState */_defineProperty(this,"parse",void 0);/** * The unparse function from the StateValueConfig passed to createState, * with a default that is simply a pass-through that assumes the value is * JSON serializable. */_defineProperty(this,"unparse",void 0);/** * An equality function from the StateValueConfig, with a default of * Object.is. */_defineProperty(this,"isEqual",void 0);/** * The result of `stateValueConfig.parse(undefined)`, which is computed only * once and used as the default value. When the current value `isEqual` to * the `defaultValue`, it will not be serialized to JSON. */_defineProperty(this,"defaultValue",void 0);this.key=key;this.parse=stateValueConfig.parse.bind(stateValueConfig);this.unparse=(stateValueConfig.unparse||coerceToJSON).bind(stateValueConfig);this.isEqual=(stateValueConfig.isEqual||Object.is).bind(stateValueConfig);this.defaultValue=this.parse(undefined);});/** * For advanced use cases, using this type is not recommended unless * it is required (due to TypeScript's lack of features like * higher-kinded types). * * A {@link StateConfig} type with any key and any value that can be * used in situations where the key and value type can not be known, * such as in a generic constraint when working with a collection of * StateConfig. * * {@link StateConfigKey} and {@link StateConfigValue} will be * useful when this is used as a generic constraint. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any /** * Create a StateConfig for the given string key and StateValueConfig. * * The key must be locally unique. In dev you will get a key collision error * when you use two separate StateConfig on the same node with the same key. * * The returned StateConfig value should be used with {@link $getState} and * {@link $setState}. * * @param key The key to use * @param valueConfig Configuration for the value type * @returns a StateConfig * * @__NO_SIDE_EFFECTS__ */function createState(key,valueConfig){return new StateConfig(key,valueConfig);}/** * The accessor for working with node state. This will read the value for the * state on the given node, and will return `stateConfig.defaultValue` if the * state has never been set on this node. * * The `version` parameter is optional and should generally be `'latest'`, * consistent with the behavior of other node methods and functions, * but for certain use cases such as `updateDOM` you may have a need to * use `'direct'` to read the state from a previous version of the node. * * For very advanced use cases, you can expect that 'direct' does not * require an editor state, just like directly accessing other properties * of a node without an accessor (e.g. `textNode.__text`). * * @param node Any LexicalNode * @param stateConfig The configuration of the state to read * @param version The default value 'latest' will read the latest version of the node state, 'direct' will read the version that is stored on this LexicalNode which not reflect the version used in the current editor state * @returns The current value from the state, or the default value provided by the configuration. */function $getState(node,stateConfig){var version=arguments.length>2&&arguments[2]!==undefined?arguments[2]:'latest';var latestOrDirectNode=version==='latest'?node.getLatest():node;var state=latestOrDirectNode.__state;if(state){$checkCollision(node,stateConfig,state);return state.getValue(stateConfig);}return stateConfig.defaultValue;}/** * Given two versions of a node and a stateConfig, compare their state values * using `$getState(nodeVersion, stateConfig, 'direct')`. * If the values are equal according to `stateConfig.isEqual`, return `null`, * otherwise return `[value, prevValue]`. * * This is useful for implementing updateDOM. Note that the `'direct'` * version argument is used for both nodes. * * @param node Any LexicalNode * @param prevNode A previous version of node * @param stateConfig The configuration of the state to read * @returns `[value, prevValue]` if changed, otherwise `null` */function $getStateChange(node,prevNode,stateConfig){var value=$getState(node,stateConfig,'direct');var prevValue=$getState(prevNode,stateConfig,'direct');return stateConfig.isEqual(value,prevValue)?null:[value,prevValue];}/** * Set the state defined by stateConfig on node. Like with `React.useState` * you may directly specify the value or use an updater function that will * be called with the previous value of the state on that node (which will * be the `stateConfig.defaultValue` if not set). * * When an updater function is used, the node will only be marked dirty if * `stateConfig.isEqual(prevValue, value)` is false. * * @example * ```ts * const toggle = createState('toggle', {parse: Boolean}); * // set it direction * $setState(node, counterState, true); * // use an updater * $setState(node, counterState, (prev) => !prev); * ``` * * @param node The LexicalNode to set the state on * @param stateConfig The configuration for this state * @param valueOrUpdater The value or updater function * @returns node */function $setState(node,stateConfig,valueOrUpdater){errorOnReadOnly();var value;if(typeof valueOrUpdater==='function'){var latest=node.getLatest();var prevValue=$getState(latest,stateConfig);value=valueOrUpdater(prevValue);if(stateConfig.isEqual(prevValue,value)){return latest;}}else{value=valueOrUpdater;}var writable=node.getWritable();var state=$getWritableNodeState(writable);$checkCollision(node,stateConfig,state);state.updateFromKnown(stateConfig,value);return writable;}/** * @internal * * Register the config to this node's sharedConfigMap and throw an exception in * `true` when a collision is detected. */function $checkCollision(node,stateConfig,state){{var collision=state.sharedNodeState.sharedConfigMap.get(stateConfig.key);if(collision!==undefined&&collision!==stateConfig){{formatDevErrorMessage("$setState: State key collision ".concat(JSON.stringify(stateConfig.key)," detected in ").concat(node.constructor.name," node with type ").concat(node.getType()," and key ").concat(node.getKey(),". Only one StateConfig with a given key should be used on a node."));}}}}/** * @internal * * Opaque state to be stored on the editor's RegisterNode for use by NodeState */ /** * @internal * * Create the state to store on RegisteredNode */function createSharedNodeState(nodeConfig){var sharedConfigMap=new Map();var flatKeys=new Set();for(var klass=typeof nodeConfig==='function'?nodeConfig:nodeConfig.replace;klass.prototype&&klass.prototype.getType!==undefined;klass=Object.getPrototypeOf(klass)){var _getStaticNodeConfig=getStaticNodeConfig(klass),ownNodeConfig=_getStaticNodeConfig.ownNodeConfig;if(ownNodeConfig&&ownNodeConfig.stateConfigs){var _iterator5=_createForOfIteratorHelper(ownNodeConfig.stateConfigs),_step5;try{for(_iterator5.s();!(_step5=_iterator5.n()).done;){var requiredStateConfig=_step5.value;var stateConfig=void 0;if('stateConfig'in requiredStateConfig){stateConfig=requiredStateConfig.stateConfig;if(requiredStateConfig.flat){flatKeys.add(stateConfig.key);}}else{stateConfig=requiredStateConfig;}sharedConfigMap.set(stateConfig.key,stateConfig);}}catch(err){_iterator5.e(err);}finally{_iterator5.f();}}}return{flatKeys:flatKeys,sharedConfigMap:sharedConfigMap};}/** * @internal * * A Map of string keys to state configurations to be shared across nodes * and/or node versions. */ /** * @internal */var NodeState=/*#__PURE__*/function(){/** * @internal */function NodeState(node,sharedNodeState){var unknownState=arguments.length>2&&arguments[2]!==undefined?arguments[2]:undefined;var knownState=arguments.length>3&&arguments[3]!==undefined?arguments[3]:new Map();var size=arguments.length>4&&arguments[4]!==undefined?arguments[4]:undefined;_classCallCheck(this,NodeState);/** * @internal * * Track the (versioned) node that this NodeState was created for, to * facilitate copy-on-write for NodeState. When a LexicalNode is cloned, * it will *reference* the NodeState from its prevNode. From the nextNode * you can continue to read state without copying, but the first $setState * will trigger a copy of the prevNode's NodeState with the node property * updated. */_defineProperty(this,"node",void 0);/** * @internal * * State that has already been parsed in a get state, so it is safe. (can be returned with * just a cast since the proof was given before). * * Note that it uses StateConfig, so in addition to (1) the CURRENT VALUE, it has access to * (2) the State key (3) the DEFAULT VALUE and (4) the PARSE FUNCTION */_defineProperty(this,"knownState",void 0);/** * @internal * * A copy of serializedNode[NODE_STATE_KEY] that is made when JSON is * imported but has not been parsed yet. * * It stays here until a get state requires us to parse it, and since we * then know the value is safe we move it to knownState. * * Note that since only string keys are used here, we can only allow this * state to pass-through on export or on the next version since there is * no known value configuration. This pass-through is to support scenarios * where multiple versions of the editor code are working in parallel so * an old version of your code doesnt erase metadata that was * set by a newer version of your code. */_defineProperty(this,"unknownState",void 0);/** * @internal * * This sharedNodeState is preserved across all instances of a given * node type in an editor and remains writable. It is how keys are resolved * to configuration. */_defineProperty(this,"sharedNodeState",void 0);/** * @internal * * The count of known or unknown keys in this state, ignoring the * intersection between the two sets. */_defineProperty(this,"size",void 0);this.node=node;this.sharedNodeState=sharedNodeState;this.unknownState=unknownState;this.knownState=knownState;var sharedConfigMap=this.sharedNodeState.sharedConfigMap;var computedSize=size!==undefined?size:computeSize(sharedConfigMap,unknownState,knownState);{if(!(size===undefined||computedSize===size)){formatDevErrorMessage("NodeState: size != computedSize (".concat(String(size)," != ").concat(String(computedSize),")"));}var _iterator6=_createForOfIteratorHelper(knownState.keys()),_step6;try{for(_iterator6.s();!(_step6=_iterator6.n()).done;){var stateConfig=_step6.value;if(!sharedConfigMap.has(stateConfig.key)){formatDevErrorMessage("NodeState: sharedConfigMap missing knownState key ".concat(stateConfig.key));}}}catch(err){_iterator6.e(err);}finally{_iterator6.f();}}this.size=computedSize;}/** * @internal * * Get the value from knownState, or parse it from unknownState * if it contains the given key. * * Updates the sharedConfigMap when no known state is found. * Updates unknownState and knownState when an unknownState is parsed. */_createClass(NodeState,[{key:"getValue",value:function getValue(stateConfig){var known=this.knownState.get(stateConfig);if(known!==undefined){return known;}this.sharedNodeState.sharedConfigMap.set(stateConfig.key,stateConfig);var parsed=stateConfig.defaultValue;if(this.unknownState&&stateConfig.key in this.unknownState){var jsonValue=this.unknownState[stateConfig.key];if(jsonValue!==undefined){parsed=stateConfig.parse(jsonValue);}// Only update if the key was unknown this.updateFromKnown(stateConfig,parsed);}return parsed;}/** * @internal * * Used only for advanced use cases, such as collab. The intent here is to * allow you to diff states with a more stable interface than the properties * of this class. */},{key:"getInternalState",value:function getInternalState(){return[this.unknownState,this.knownState];}/** * Encode this NodeState to JSON in the format that its node expects. * This returns `{[NODE_STATE_KEY]?: UnknownStateRecord}` rather than * `UnknownStateRecord | undefined` so that we can support flattening * specific entries in the future when nodes can declare what * their required StateConfigs are. */},{key:"toJSON",value:function toJSON(){var state=_objectSpread({},this.unknownState);var flatState={};var _iterator7=_createForOfIteratorHelper(this.knownState),_step7;try{for(_iterator7.s();!(_step7=_iterator7.n()).done;){var _step7$value=_slicedToArray(_step7.value,2),stateConfig=_step7$value[0],v=_step7$value[1];if(stateConfig.isEqual(v,stateConfig.defaultValue)){delete state[stateConfig.key];}else{state[stateConfig.key]=stateConfig.unparse(v);}}}catch(err){_iterator7.e(err);}finally{_iterator7.f();}var _iterator8=_createForOfIteratorHelper(this.sharedNodeState.flatKeys),_step8;try{for(_iterator8.s();!(_step8=_iterator8.n()).done;){var key=_step8.value;if(key in state){flatState[key]=state[key];delete state[key];}}}catch(err){_iterator8.e(err);}finally{_iterator8.f();}if(undefinedIfEmpty(state)){flatState[NODE_STATE_KEY]=state;}return flatState;}/** * @internal * * A NodeState is writable when the node to update matches * the node associated with the NodeState. This basically * mirrors how the EditorState NodeMap works, but in a * bottom-up organization rather than a top-down organization. * * This allows us to implement the same "copy on write" * pattern for state, without having the state version * update every time the node version changes (e.g. when * its parent or siblings change). * * @param node The node to associate with the state * @returns The next writable state */},{key:"getWritable",value:function getWritable(node){if(this.node===node){return this;}var sharedNodeState=this.sharedNodeState,unknownState=this.unknownState;var nextKnownState=new Map(this.knownState);return new NodeState(node,sharedNodeState,parseAndPruneNextUnknownState(sharedNodeState.sharedConfigMap,nextKnownState,unknownState),nextKnownState,this.size);}/** @internal */},{key:"updateFromKnown",value:function updateFromKnown(stateConfig,value){var key=stateConfig.key;this.sharedNodeState.sharedConfigMap.set(key,stateConfig);var knownState=this.knownState,unknownState=this.unknownState;if(!(knownState.has(stateConfig)||unknownState&&key in unknownState)){if(unknownState){delete unknownState[key];this.unknownState=undefinedIfEmpty(unknownState);}this.size++;}knownState.set(stateConfig,value);}/** * @internal * * This is intended for advanced use cases only, such * as collab or dev tools. * * Update a single key value pair from unknown state, * parsing it if the key is known to this node. This is * basically like updateFromJSON, but the effect is * isolated to a single entry. * * @param k The string key from an UnknownStateRecord * @param v The unknown value from an UnknownStateRecord */},{key:"updateFromUnknown",value:function updateFromUnknown(k,v){var stateConfig=this.sharedNodeState.sharedConfigMap.get(k);if(stateConfig){this.updateFromKnown(stateConfig,stateConfig.parse(v));}else{this.unknownState=this.unknownState||{};if(!(k in this.unknownState)){this.size++;}this.unknownState[k]=v;}}/** * @internal * * Reset all existing state to default or empty values, * and perform any updates from the given unknownState. * * This is used when initializing a node's state from JSON, * or when resetting a node's state from JSON. * * @param unknownState The new state in serialized form */},{key:"updateFromJSON",value:function updateFromJSON(unknownState){var knownState=this.knownState;// Reset all known state to defaults var _iterator9=_createForOfIteratorHelper(knownState.keys()),_step9;try{for(_iterator9.s();!(_step9=_iterator9.n()).done;){var stateConfig=_step9.value;knownState.set(stateConfig,stateConfig.defaultValue);}// Since we are resetting all state to this new record, // the size starts at the number of known keys // and will be updated as we traverse the new state }catch(err){_iterator9.e(err);}finally{_iterator9.f();}this.size=knownState.size;this.unknownState=undefined;if(unknownState){for(var _i4=0,_Object$entries=Object.entries(unknownState);_i4<_Object$entries.length;_i4++){var _Object$entries$_i=_slicedToArray(_Object$entries[_i4],2),k=_Object$entries$_i[0],v=_Object$entries$_i[1];this.updateFromUnknown(k,v);}}}}]);return NodeState;}();/** * @internal * * Only for direct use in very advanced integrations, such as lexical-yjs. * Typically you would only use {@link createState}, {@link $getState}, and * {@link $setState}. This is effectively the preamble for {@link $setState}. */function $getWritableNodeState(node){var writable=node.getWritable();var state=writable.__state?writable.__state.getWritable(writable):new NodeState(writable,$getSharedNodeState(writable));writable.__state=state;return state;}/** * @internal * * Get the SharedNodeState for a node on this editor */function $getSharedNodeState(node){return node.__state?node.__state.sharedNodeState:getRegisteredNodeOrThrow($getEditor(),node.getType()).sharedNodeState;}/** * @internal * * This is used to implement LexicalNode.updateFromJSON and is * not intended to be exported from the package. * * @param node any LexicalNode * @param unknownState undefined or a serialized State * @returns A writable version of node, with the state set. */function $updateStateFromJSON(node,serialized){var writable=node.getWritable();var unknownState=serialized[NODE_STATE_KEY];var parseState=unknownState;var _iterator10=_createForOfIteratorHelper($getSharedNodeState(writable).flatKeys),_step10;try{for(_iterator10.s();!(_step10=_iterator10.n()).done;){var k=_step10.value;if(k in serialized){if(parseState===undefined||parseState===unknownState){parseState=_objectSpread({},unknownState);}parseState[k]=serialized[k];}}}catch(err){_iterator10.e(err);}finally{_iterator10.f();}if(writable.__state||parseState){$getWritableNodeState(node).updateFromJSON(parseState);}return writable;}/** * @internal * * Return true if the two nodes have equivalent NodeState, to be used * to determine when TextNode are being merged, not a lot of use cases * otherwise. */function nodeStatesAreEquivalent(a,b){if(a===b){return true;}if(a&&b&&a.size!==b.size){return false;}var keys=new Set();return!(a&&hasUnequalMapEntry(keys,a,b)||b&&hasUnequalMapEntry(keys,b,a)||a&&hasUnequalRecordEntry(keys,a,b)||b&&hasUnequalRecordEntry(keys,b,a));}/** * Compute the number of distinct keys that will be in a NodeState */function computeSize(sharedConfigMap,unknownState,knownState){var size=knownState.size;if(unknownState){for(var k in unknownState){var sharedConfig=sharedConfigMap.get(k);if(!sharedConfig||!knownState.has(sharedConfig)){size++;}}}return size;}/** * @internal * * Return obj if it is an object with at least one property, otherwise * return undefined. */function undefinedIfEmpty(obj){if(obj){for(var key in obj){return obj;}}return undefined;}/** * @internal * * Cast the given v to unknown */function coerceToJSON(v){return v;}/** * @internal * * Parse all knowable values in an UnknownStateRecord into nextKnownState * and return the unparsed values in a new UnknownStateRecord. Returns * undefined if no unknown values remain. */function parseAndPruneNextUnknownState(sharedConfigMap,nextKnownState,unknownState){var nextUnknownState=undefined;if(unknownState){for(var _i5=0,_Object$entries2=Object.entries(unknownState);_i5<_Object$entries2.length;_i5++){var _Object$entries2$_i=_slicedToArray(_Object$entries2[_i5],2),k=_Object$entries2$_i[0],v=_Object$entries2$_i[1];var stateConfig=sharedConfigMap.get(k);if(stateConfig){if(!nextKnownState.has(stateConfig)){nextKnownState.set(stateConfig,stateConfig.parse(v));}}else{nextUnknownState=nextUnknownState||{};nextUnknownState[k]=v;}}}return nextUnknownState;}/** * @internal * * Compare each entry of sourceState.knownState that is not in keys to * otherState (or the default value if otherState is undefined. * Note that otherState will return the defaultValue as well if it * has never been set. Any checked entry's key will be added to keys. * * @returns true if any difference is found, false otherwise */function hasUnequalMapEntry(keys,sourceState,otherState){var _iterator11=_createForOfIteratorHelper(sourceState.knownState),_step11;try{for(_iterator11.s();!(_step11=_iterator11.n()).done;){var _step11$value=_slicedToArray(_step11.value,2),stateConfig=_step11$value[0],value=_step11$value[1];if(keys.has(stateConfig.key)){continue;}keys.add(stateConfig.key);var otherValue=otherState?otherState.getValue(stateConfig):stateConfig.defaultValue;if(otherValue!==value&&!stateConfig.isEqual(otherValue,value)){return true;}}}catch(err){_iterator11.e(err);}finally{_iterator11.f();}return false;}/** * @internal * * Compare each entry of sourceState.unknownState that is not in keys to * otherState.unknownState (or undefined if otherState is undefined). * Any checked entry's key will be added to keys. * * Notably since we have already checked hasUnequalMapEntry on both sides, * we do not do any parsing or checking of knownState. * * @returns true if any difference is found, false otherwise */function hasUnequalRecordEntry(keys,sourceState,otherState){var unknownState=sourceState.unknownState;var otherUnknownState=otherState?otherState.unknownState:undefined;if(unknownState){for(var _i6=0,_Object$entries3=Object.entries(unknownState);_i6<_Object$entries3.length;_i6++){var _Object$entries3$_i=_slicedToArray(_Object$entries3[_i6],2),key=_Object$entries3$_i[0],value=_Object$entries3$_i[1];if(keys.has(key)){continue;}keys.add(key);var otherValue=otherUnknownState?otherUnknownState[key]:undefined;if(value!==otherValue){return true;}}}return false;}/** * @internal * * Clones the NodeState for a given node. Handles aliasing if the state references the from node. */function $cloneNodeState(from,to){var state=from.__state;return state&&state.node===from?state.getWritable(to):state;}/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */function $canSimpleTextNodesBeMerged(node1,node2){var node1Mode=node1.__mode;var node1Format=node1.__format;var node1Style=node1.__style;var node2Mode=node2.__mode;var node2Format=node2.__format;var node2Style=node2.__style;var node1State=node1.__state;var node2State=node2.__state;return(node1Mode===null||node1Mode===node2Mode)&&(node1Format===null||node1Format===node2Format)&&(node1Style===null||node1Style===node2Style)&&(node1.__state===null||node1State===node2State||nodeStatesAreEquivalent(node1State,node2State));}function $mergeTextNodes(node1,node2){var writableNode1=node1.mergeWithSibling(node2);var normalizedNodes=getActiveEditor()._normalizedNodes;normalizedNodes.add(node1.__key);normalizedNodes.add(node2.__key);return writableNode1;}function $normalizeTextNode(textNode){var node=textNode;if(node.__text===''&&node.isSimpleText()&&!node.isUnmergeable()){node.remove();return;}// Backward var previousNode;while((previousNode=node.getPreviousSibling())!==null&&$isTextNode(previousNode)&&previousNode.isSimpleText()&&!previousNode.isUnmergeable()){if(previousNode.__text===''){previousNode.remove();}else if($canSimpleTextNodesBeMerged(previousNode,node)){node=$mergeTextNodes(previousNode,node);break;}else{break;}}// Forward var nextNode;while((nextNode=node.getNextSibling())!==null&&$isTextNode(nextNode)&&nextNode.isSimpleText()&&!nextNode.isUnmergeable()){if(nextNode.__text===''){nextNode.remove();}else if($canSimpleTextNodesBeMerged(node,nextNode)){node=$mergeTextNodes(node,nextNode);break;}else{break;}}}function $normalizeSelection(selection){$normalizePoint(selection.anchor);$normalizePoint(selection.focus);return selection;}function $normalizePoint(point){while(point.type==='element'){var node=point.getNode();var offset=point.offset;var nextNode=void 0;var nextOffsetAtEnd=void 0;if(offset===node.getChildrenSize()){nextNode=node.getChildAtIndex(offset-1);nextOffsetAtEnd=true;}else{nextNode=node.getChildAtIndex(offset);nextOffsetAtEnd=false;}if($isTextNode(nextNode)){point.set(nextNode.__key,nextOffsetAtEnd?nextNode.getTextContentSize():0,'text',true);break;}else if(!$isElementNode(nextNode)){break;}point.set(nextNode.__key,nextOffsetAtEnd?nextNo