@portabletext/editor
Version:
Portable Text Editor made in React
1,070 lines (1,068 loc) • 704 kB
JavaScript
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
import { c } from "react/compiler-runtime";
import { useSelector, useActorRef } from "@xstate/react";
import React, { useRef, useEffect, useLayoutEffect, useState, createContext, useContext, useReducer, useCallback, memo, forwardRef, useMemo, Component, useSyncExternalStore, startTransition } from "react";
import { isSpan, isTextBlock, compileSchema } from "@portabletext/schema";
import { defineSchema } from "@portabletext/schema";
import { isKeyedSegment, getNode, getChildren, getAncestor, isObjectNode, getAncestors, getNodeChildren } from "./_chunks-es/get-ancestor.js";
import { getLeaf, getSpanNode, getText, getTextBlockNode, getUnionSchema, isLeaf, getFirstChild } from "./_chunks-es/get-first-child.js";
import { isTextBlockNode, isBlock, hasNode, getEnclosingBlock, isAncestorPath, getNodes, getSibling, isInline, parentPath, isSpanNode, getPathSubSchema, getBlock, resolveContainerAt, getParent, getEnclosingContainer } from "./_chunks-es/get-path-sub-schema.js";
import { rangeEdges, isEditableContainer, isBackwardRange, comparePoints, rangesOverlap, isAfterPoint, comparePaths, isSelectionCollapsed as isSelectionCollapsed$1, getFocusInlineObject, getFocusTextBlock, getFocusSpan, getFragment, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock, isOverlappingSelection, getFocusBlock, getSelectedBlocks, isSelectingEntireBlocks, getSelectedValue, isActiveAnnotation, getActiveAnnotationsMarks, getActiveDecorators, getCaretWordSelection, getSelectionStartChild, getSelectionEndChild, getPreviousSpan, getNextSpan, getSelectionStartPoint, getSelectionEndPoint, getFocusBlockObject, getLastBlock, getFirstBlock, isAtTheEndOfBlock, isAtTheStartOfBlock, getMarkState, getFocusListBlock, getNextBlock, getPreviousBlock, getSelectedTextBlocks, getRootAcceptedTypes, isActiveDecorator, getFocusChild, getActiveAnnotations, isActiveListItem, isActiveStyle } from "./_chunks-es/selector.is-at-the-start-of-block.js";
import { isEqualSelectionPoints, blockOffsetToSpanSelectionPoint, getBlockKeyFromSelectionPoint, getBlockEndPoint, getBlockStartPoint, isSelectionCollapsed, getAncestorTextBlock, defaultKeyGenerator, parseBlocks, parseBlock, isListBlock, getSelectionStartPoint as getSelectionStartPoint$1, getSelectionEndPoint as getSelectionEndPoint$1, parseAnnotation, parseMarkDefs, parseSpan, parseInlineObject, isEqualPathSegments } from "./_chunks-es/util.slice-blocks.js";
import rawDebug from "debug";
import scrollIntoView from "scroll-into-view-if-needed";
import { createKeyboardShortcut, undo, redo, code, underline, italic as italic$1, bold as bold$1 } from "@portabletext/keyboard-shortcuts";
import { isEmptyTextBlock, getTextBlockText } from "./_chunks-es/util.is-empty-text-block.js";
import { setup, fromCallback, assign, and, assertEvent, enqueueActions, emit, not, raise as raise$1, createActor } from "xstate";
import { defineBehavior, forward, raise, effect } from "./behaviors/index.js";
import { htmlToPortableText } from "@portabletext/html";
import { toHTML } from "@portabletext/to-html";
import { markdownToPortableText, portableTextToMarkdown } from "@portabletext/markdown";
import { insert, setIfMissing, diffMatchPatch as diffMatchPatch$1, unset, set, applyAll } from "@portabletext/patches";
import { EditorContext as EditorContext$1 } from "./_chunks-es/use-editor.js";
import { useEditor } from "./_chunks-es/use-editor.js";
function isPath(value) {
return Array.isArray(value) && (value.length === 0 || typeof value[0] == "number" || typeof value[0] == "string" || isKeyedSegment(value[0]));
}
const isObject = (value) => typeof value == "object" && value !== null;
function isPoint(value) {
return isObject(value) && typeof value.offset == "number" && isPath(value.path);
}
function isRange(value) {
return isObject(value) && isPoint(value.anchor) && isPoint(value.focus);
}
const EDITOR_BRAND = /* @__PURE__ */ Symbol.for("slate-editor");
function isEditor(value) {
return isObject(value) && value[EDITOR_BRAND] === !0;
}
function point(editor, at, options = {}) {
const {
edge = "start"
} = options;
if (isPath(at)) {
let path2;
const deepest = getLeaf(editor, at, {
edge: edge === "end" ? "end" : "start"
});
if (!deepest)
throw new Error(`Cannot get the ${edge} point in the node at path [${at}] because it has no ${edge} text node.`);
const {
node,
path: nodePath
} = deepest;
if (path2 = nodePath, !isSpan({
schema: editor.schema
}, node) && !isTextBlockNode({
schema: editor.schema
}, node) && !isEditor(node))
return {
path: path2,
offset: 0
};
if (!isSpan({
schema: editor.schema
}, node))
throw new Error(`Cannot get the ${edge} point in the node at path [${at}] because it has no ${edge} text node.`);
return {
path: path2,
offset: edge === "end" ? node.text.length : 0
};
}
if (isRange(at)) {
const [start2, end2] = rangeEdges(at, editor);
return edge === "start" ? start2 : end2;
}
return at;
}
function end(editor, at) {
return point(editor, at, {
edge: "end"
});
}
function start(editor, at) {
return point(editor, at, {
edge: "start"
});
}
function pathEquals(path2, another) {
if (path2.length !== another.length)
return !1;
for (let i = 0; i < path2.length; i++) {
const segment = path2[i], otherSegment = another[i];
if (isKeyedSegment(segment) && isKeyedSegment(otherSegment)) {
if (segment._key !== otherSegment._key)
return !1;
} else if (segment !== otherSegment)
return !1;
}
return !0;
}
function pointEquals(point2, another) {
return point2.offset === another.offset && pathEquals(point2.path, another.path);
}
function resolveSelection(editor, selection) {
if (!selection)
return null;
if (isEqualSelectionPoints(selection.anchor, selection.focus)) {
const anchorPoint2 = resolveSelectionPoint(editor, selection.anchor, selection.backward ? "backward" : "forward");
return anchorPoint2 ? {
anchor: anchorPoint2,
focus: anchorPoint2
} : null;
}
const anchorPoint = resolveSelectionPoint(editor, selection.anchor, selection.backward ? "forward" : "backward"), focusPoint = resolveSelectionPoint(editor, selection.focus, selection.backward ? "backward" : "forward");
return !anchorPoint || !focusPoint ? null : {
anchor: anchorPoint,
focus: focusPoint
};
}
function resolveSelectionPoint(editor, selectionPoint, direction) {
const snapshot = {
context: {
schema: editor.schema,
containers: editor.publicContainers,
value: editor.children
},
blockIndexMap: editor.blockIndexMap
}, entry = getNode(snapshot, selectionPoint.path);
if (entry) {
if (getChildren(snapshot, entry.path).length === 0)
return {
path: entry.path,
offset: isSpan({
schema: editor.schema
}, entry.node) ? Math.min(entry.node.text.length, selectionPoint.offset) : 0
};
const isBlockLevelPath = isBlock(snapshot, selectionPoint.path);
if (isTextBlock({
schema: editor.schema
}, entry.node) && isBlockLevelPath) {
const spanPoint = blockOffsetToSpanSelectionPoint({
snapshot,
blockOffset: {
path: entry.path,
offset: selectionPoint.offset
},
direction
});
if (spanPoint)
return spanPoint;
}
const leaf2 = getLeaf(snapshot, entry.path, {
edge: direction === "forward" ? "start" : "end"
});
return leaf2 ? {
path: leaf2.path,
offset: 0
} : {
path: entry.path,
offset: 0
};
}
const blockKey = getBlockKeyFromSelectionPoint(selectionPoint);
if (!blockKey)
return;
const blockEntry = getNode(snapshot, [{
_key: blockKey
}]);
if (!blockEntry)
return;
const leaf = getLeaf(snapshot, blockEntry.path, {
edge: "start"
});
return leaf ? {
path: leaf.path,
offset: 0
} : {
path: blockEntry.path,
offset: 0
};
}
function applySelect(editor, target) {
const range2 = toRange(editor, target), {
selection
} = editor;
if (selection) {
const oldProps = {}, newProps = {};
range2.anchor != null && !pointEquals(range2.anchor, selection.anchor) && (oldProps.anchor = selection.anchor, newProps.anchor = range2.anchor), range2.focus != null && !pointEquals(range2.focus, selection.focus) && (oldProps.focus = selection.focus, newProps.focus = range2.focus), Object.keys(oldProps).length > 0 && editor.apply({
type: "set_selection",
properties: oldProps,
newProperties: newProps
});
} else
editor.apply({
type: "set_selection",
properties: null,
newProperties: range2
});
}
function applyDeselect(editor) {
const {
selection
} = editor;
selection && editor.apply({
type: "set_selection",
properties: selection,
newProperties: null
});
}
function toRange(editor, target) {
if (isRange(target))
return target;
if (isPoint(target))
return {
anchor: target,
focus: target
};
const start$1 = start(editor, target), end$1 = end(editor, target);
return {
anchor: start$1,
focus: end$1
};
}
const rootName = "pte:";
function createDebugger(name) {
const namespace = `${rootName}${name}`;
return rawDebug && rawDebug.enabled(namespace) ? rawDebug(namespace) : rawDebug(rootName);
}
const debug = {
behaviors: createDebugger("behaviors"),
history: createDebugger("history"),
mutation: createDebugger("mutation"),
normalization: createDebugger("normalization"),
operation: createDebugger("operation"),
selection: createDebugger("selection"),
setup: createDebugger("setup"),
state: createDebugger("state"),
syncValue: createDebugger("sync:value"),
syncPatch: createDebugger("sync:patch")
};
function serializePath(path2) {
return path2.reduce((result, segment, index) => isKeyedSegment(segment) ? `${result}[_key=="${segment._key}"]` : `${result}${index === 0 ? "" : "."}${segment}`, "");
}
function getDomNode(editor, path2) {
const editorElement = editor.domElement;
if (!editorElement)
return;
if (path2.length === 0)
return editorElement;
const serializedPath = serializePath(path2), selector = `[data-pt-path="${CSS.escape(serializedPath)}"]`, blockSegment = path2[0];
if (isKeyedSegment(blockSegment)) {
const blockIndex = editor.blockIndexMap.get(blockSegment._key);
if (blockIndex !== void 0) {
const blockNode = editorElement.children[blockIndex];
if (blockNode instanceof HTMLElement) {
if (blockNode.matches(selector))
return blockNode.closest("[data-pt-editor]") !== editorElement ? void 0 : blockNode;
const domNode2 = blockNode.querySelector(selector);
if (domNode2 instanceof HTMLElement)
return domNode2.closest("[data-pt-editor]") !== editorElement ? void 0 : domNode2;
}
}
}
const domNode = editorElement.querySelector(selector);
if (!(!(domNode instanceof HTMLElement) || domNode.closest("[data-pt-editor]") !== editorElement))
return domNode;
}
const KEYED_SEGMENT_PATTERN = /\[_key=="(.+?)"\]/;
function deserializePath(serializedPath) {
const path2 = [];
let remaining = serializedPath;
for (; remaining.length > 0; ) {
remaining.startsWith(".") && (remaining = remaining.slice(1));
const keyMatch = remaining.match(KEYED_SEGMENT_PATTERN);
if (keyMatch?.[1] && remaining.startsWith("[")) {
path2.push({
_key: keyMatch[1]
}), remaining = remaining.slice(keyMatch[0].length);
continue;
}
const nextBracket = remaining.indexOf("["), nextDot = remaining.indexOf(".");
let end2;
nextBracket === -1 && nextDot === -1 ? end2 = remaining.length : nextBracket === -1 ? end2 = nextDot : nextDot === -1 ? end2 = nextBracket : end2 = Math.min(nextBracket, nextDot);
const fieldName = remaining.slice(0, end2);
fieldName && path2.push(fieldName), remaining = remaining.slice(end2);
}
return path2;
}
function safeStringify(value, space) {
try {
return JSON.stringify(value, null, space);
} catch (error) {
return console.error(error), "JSON.stringify failed";
}
}
function safeParse(text) {
try {
return JSON.parse(text);
} catch (error) {
return console.error(error), "JSON.parse failed";
}
}
function getAncestorObjectNode(snapshot, path2) {
const result = getAncestor(snapshot, path2, (node) => isObjectNode({
schema: snapshot.context.schema
}, node));
if (result && isObjectNode({
schema: snapshot.context.schema
}, result.node))
return {
node: result.node,
path: result.path
};
}
function commonPath(path2, another) {
const common = [];
for (let i = 0; i < path2.length && i < another.length; i++) {
const segment = path2.at(i), otherSegment = another.at(i);
if (segment === void 0 || otherSegment === void 0)
break;
if (isKeyedSegment(segment) && isKeyedSegment(otherSegment)) {
if (segment._key !== otherSegment._key)
break;
common.push(segment);
} else if (segment === otherSegment)
common.push(segment);
else
break;
}
return common;
}
function rangeEnd(range2, root) {
const [, end2] = rangeEdges(range2, root);
return end2;
}
function rangeStart(range2, root) {
const [start2] = rangeEdges(range2, root);
return start2;
}
function path(editor, at, options = {}) {
const {
depth,
edge
} = options;
if (isPath(at) && (edge === "start" || edge === "end")) {
const leaf = getLeaf(editor, at, {
edge
});
leaf && (at = leaf.path);
}
return isRange(at) && (edge === "start" ? at = rangeStart(at, editor) : edge === "end" ? at = rangeEnd(at, editor) : at = commonPath(at.anchor.path, at.focus.path)), isPoint(at) && (at = at.path), depth != null && (at = sliceToNodeDepth(at, depth)), at;
}
function sliceToNodeDepth(nodePath, depth) {
let nodeCount = 0;
for (let i = 0; i < nodePath.length; i++) {
const segment = nodePath[i];
if ((isKeyedSegment(segment) || typeof segment == "number") && (nodeCount++, nodeCount === depth))
return nodePath.slice(0, i + 1);
}
return nodePath;
}
function isVoidNode(snapshot, node, path2) {
return isObjectNode({
schema: snapshot.context.schema
}, node) && !isEditableContainer(snapshot, node, path2);
}
function isCollapsedRange(range2) {
const {
anchor,
focus
} = range2;
return pointEquals(anchor, focus);
}
const IS_IOS = typeof navigator < "u" && typeof window < "u" && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream, IS_ANDROID = typeof navigator < "u" && /Android/.test(navigator.userAgent), IS_FIREFOX = typeof navigator < "u" && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent), IS_WEBKIT = typeof navigator < "u" && /AppleWebKit(?!.*Chrome)/i.test(navigator.userAgent), IS_EDGE_LEGACY = typeof navigator < "u" && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent), IS_CHROME = typeof navigator < "u" && /Chrome/i.test(navigator.userAgent), IS_CHROME_LEGACY = typeof navigator < "u" && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent), IS_ANDROID_CHROME_LEGACY = IS_ANDROID && typeof navigator < "u" && /Chrome?\/(?:[0-5]?\d)(?:\.)/i.test(navigator.userAgent), IS_FIREFOX_LEGACY = typeof navigator < "u" && /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent), IS_UC_MOBILE = typeof navigator < "u" && /.*UCBrowser/.test(navigator.userAgent), IS_WECHATBROWSER = typeof navigator < "u" && /.*Wechat/.test(navigator.userAgent) && !/.*MacWechat/.test(navigator.userAgent) && // avoid lookbehind (buggy in safari < 16.4)
(!IS_CHROME || IS_CHROME_LEGACY), CAN_USE_DOM = typeof window < "u" && typeof window.document < "u" && typeof window.document.createElement < "u", HAS_BEFORE_INPUT_SUPPORT = (!IS_CHROME_LEGACY || !IS_ANDROID_CHROME_LEGACY) && !IS_EDGE_LEGACY && // globalThis is undefined in older browsers
typeof globalThis < "u" && globalThis.InputEvent && typeof globalThis.InputEvent.prototype.getTargetRanges == "function", DOMEditor = {
blur: (editor) => {
const el = getDomNode(editor, []), root = DOMEditor.findDocumentOrShadowRoot(editor);
editor.focused = !1, root.activeElement === el && el.blur();
},
findDocumentOrShadowRoot: (editor) => {
const el = getDomNode(editor, []);
if (!el)
throw new Error("Cannot resolve a DOM node: editor is not mounted");
const root = el.getRootNode();
return root instanceof Document || root instanceof ShadowRoot ? root : el.ownerDocument;
},
focus: (editor, options = {
retries: 5
}) => {
if (editor.focused || !editor.domElement)
return;
if (options.retries <= 0)
throw new Error("Could not set focus, editor seems stuck with pending operations");
if (editor.operations.length > 0) {
setTimeout(() => {
DOMEditor.focus(editor, {
retries: options.retries - 1
});
}, 10);
return;
}
const el = getDomNode(editor, []);
if (!el)
throw new Error("Cannot resolve a DOM node: editor is not mounted");
const root = DOMEditor.findDocumentOrShadowRoot(editor);
if (root.activeElement !== el) {
if (editor.selection && root instanceof Document) {
const domSelection = getSelection(root), domRange = DOMEditor.toDOMRange(editor, editor.selection);
domSelection?.removeAllRanges(), domSelection?.addRange(domRange);
}
if (editor.selection || editor.select(start(editor, [])), editor.focused = !0, el.focus({
preventScroll: !0
}), editor.selection && root instanceof Document) {
const domSelection = getSelection(root), domRange = DOMEditor.toDOMRange(editor, editor.selection);
domSelection?.removeAllRanges(), domSelection?.addRange(domRange);
}
}
},
getWindow: (editor) => {
const window2 = editor.domWindow;
if (!window2)
throw new Error("Unable to find a host window element for this editor");
return window2;
},
hasDOMNode: (editor, target, options = {}) => {
const {
editable = !1
} = options, editorEl = getDomNode(editor, []);
if (!editorEl)
return !1;
let targetEl = null;
try {
targetEl = isDOMElement(target) ? target : target.parentElement;
} catch (err) {
if (err instanceof Error && !err.message.includes('Permission denied to access property "nodeType"'))
throw err;
}
return targetEl ? closestShadowAware(targetEl, "[data-pt-editor]") === editorEl && (!editable || targetEl.isContentEditable ? !0 : typeof targetEl.isContentEditable == "boolean" && // isContentEditable exists only on HTMLElement, and on other nodes it will be undefined
// this is the core logic that lets you know you got the right editor.selection instead of null when editor is contenteditable="false"(readOnly)
closestShadowAware(targetEl, '[contenteditable="false"]') === editorEl || !!targetEl.getAttribute("data-pt-zero-width")) : !1;
},
hasEditableTarget: (editor, target) => isDOMNode(target) && DOMEditor.hasDOMNode(editor, target, {
editable: !0
}),
hasRange: (editor, range2) => {
const {
anchor,
focus
} = range2;
return hasNode(editor, anchor.path) && hasNode(editor, focus.path);
},
hasSelectableTarget: (editor, target) => DOMEditor.hasEditableTarget(editor, target) || DOMEditor.isTargetInsideNonReadonlyVoid(editor, target),
hasTarget: (editor, target) => isDOMNode(target) && DOMEditor.hasDOMNode(editor, target),
isTargetInsideNonReadonlyVoid: (editor, target) => editor.readOnly || !DOMEditor.hasTarget(editor, target) ? !1 : !!(isDOMElement(target) ? target : target.parentElement)?.closest('[data-pt-block="object"], [data-pt-inline="object"]'),
toDOMPoint: (editor, point2) => {
const nodeEntry = getNode(editor, point2.path), el = getDomNode(editor, point2.path);
if (!el)
throw new Error(`Cannot resolve a DOM node from path: ${point2.path}`);
let domPoint;
if (nodeEntry && isVoidNode(editor, nodeEntry.node, point2.path)) {
const spacer = el.querySelector("[data-pt-zero-width]");
if (spacer) {
const domText = spacer.childNodes[0];
if (domText)
return [domText, 0];
}
const parentEl = el.parentNode;
if (parentEl) {
const index = Array.from(parentEl.childNodes).indexOf(el);
if (index !== -1)
return [parentEl, index];
}
return [el, 0];
}
const pointPath = path(editor, point2), pointEntry = getNode(editor, pointPath), pointObjectNode = pointEntry && isVoidNode(editor, pointEntry.node, pointPath) ? pointEntry : getAncestorObjectNode(editor, point2.path);
pointObjectNode && isVoidNode(editor, pointObjectNode.node, pointObjectNode.path) && (point2 = {
path: point2.path,
offset: 0
});
const texts = Array.from(el.querySelectorAll("[data-pt-text], [data-pt-zero-width]"));
let start2 = 0;
for (let i = 0; i < texts.length; i++) {
const text = texts[i], domNode = text.childNodes[0];
if (domNode == null || domNode.textContent == null)
continue;
const {
length
} = domNode.textContent, trueLength = text.hasAttribute("data-pt-zero-width") ? 0 : length, end2 = start2 + trueLength;
if (point2.offset <= end2) {
const offset = Math.min(length, Math.max(0, point2.offset - start2));
domPoint = [domNode, offset];
break;
}
start2 = end2;
}
if (!domPoint)
throw new Error(`Cannot resolve a DOM point from Slate point: ${safeStringify(point2)}`);
return domPoint;
},
toDOMRange: (editor, range2) => {
const {
anchor,
focus
} = range2, isBackward = isBackwardRange(range2, editor), domAnchor = DOMEditor.toDOMPoint(editor, anchor), domFocus = isCollapsedRange(range2) ? domAnchor : DOMEditor.toDOMPoint(editor, focus), domRange = DOMEditor.getWindow(editor).document.createRange(), [startNode, startOffset] = isBackward ? domFocus : domAnchor, [endNode, endOffset] = isBackward ? domAnchor : domFocus, isStartAtZeroWidth = !!(isDOMElement(startNode) ? startNode : startNode.parentElement).getAttribute("data-pt-zero-width"), isEndAtZeroWidth = !!(isDOMElement(endNode) ? endNode : endNode.parentElement).getAttribute("data-pt-zero-width");
return domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset), domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset), domRange;
},
toSlatePoint: (editor, domPoint, options) => {
const {
exactMatch,
suppressThrow,
searchDirection
} = options, [nearestNode, nearestOffset] = exactMatch ? domPoint : normalizeDOMPoint(domPoint), parentNode = nearestNode.parentNode;
let textNode = null, offset = 0;
if (parentNode) {
const editorEl = getDomNode(editor, []);
if (!editorEl)
throw new Error("Cannot resolve a DOM node: editor is not mounted");
const potentialVoidNode = parentNode.closest('[data-pt-block="object"], [data-pt-inline="object"]'), voidNode = potentialVoidNode && containsShadowAware(editorEl, potentialVoidNode) ? potentialVoidNode : null, potentialNonEditableNode = parentNode.closest('[contenteditable="false"]'), nonEditableNode = potentialNonEditableNode && containsShadowAware(editorEl, potentialNonEditableNode) ? potentialNonEditableNode : null;
let leafNode = parentNode.closest("[data-pt-marks]"), domNode = null;
if (leafNode) {
if (textNode = leafNode.closest('[data-pt-inline="span"]'), textNode) {
const range2 = DOMEditor.getWindow(editor).document.createRange();
range2.setStart(textNode, 0), range2.setEnd(nearestNode, nearestOffset);
const contents = range2.cloneContents();
[...Array.prototype.slice.call(contents.querySelectorAll("[data-pt-zero-width]")), ...Array.prototype.slice.call(contents.querySelectorAll("[contenteditable=false]"))].forEach((el) => {
if (IS_ANDROID && !exactMatch && el.hasAttribute("data-pt-zero-width") && el.textContent.length > 0 && el.textContext !== "\uFEFF") {
el.textContent.startsWith("\uFEFF") && (el.textContent = el.textContent.slice(1));
return;
}
el.parentNode.removeChild(el);
}), offset = contents.textContent.length, domNode = textNode;
}
} else if (voidNode) {
const leafNodes = voidNode.querySelectorAll("[data-pt-marks]");
for (let index = 0; index < leafNodes.length; index++) {
const current = leafNodes[index];
if (DOMEditor.hasDOMNode(editor, current)) {
leafNode = current;
break;
}
}
leafNode ? (textNode = leafNode.closest('[data-pt-inline="span"]'), domNode = leafNode, offset = domNode.textContent.length, domNode.querySelectorAll("[data-pt-zero-width]").forEach((el) => {
offset -= el.textContent.length;
})) : offset = 1;
} else if (nonEditableNode) {
const getLeafNodes = (node) => node ? node.querySelectorAll(
// Exclude leaf nodes in nested editors
"[data-pt-marks]:not(:scope [data-pt-editor] [data-pt-marks])"
) : [], elementNode = nonEditableNode.closest("[data-pt-block]");
(searchDirection === "backward" || !searchDirection) && (leafNode = [...getLeafNodes(elementNode?.previousElementSibling), ...getLeafNodes(elementNode)].findLast((leaf) => isBefore(nonEditableNode, leaf)) ?? null), (searchDirection === "forward" || !searchDirection) && (leafNode = [...getLeafNodes(elementNode), ...getLeafNodes(elementNode?.nextElementSibling)].find((leaf) => isAfter(nonEditableNode, leaf)) ?? null), leafNode && (textNode = leafNode.closest('[data-pt-inline="span"]'), domNode = leafNode, searchDirection === "forward" ? offset = 0 : (offset = domNode.textContent.length, domNode.querySelectorAll("[data-pt-zero-width]").forEach((el) => {
offset -= el.textContent.length;
})));
}
domNode && offset === domNode.textContent.length && // COMPAT: Android IMEs might remove the zero width space while composing,
// and we don't add it for line-breaks.
IS_ANDROID && domNode.hasAttribute("data-pt-zero-width") && !domNode.hasAttribute("data-pt-line-break") && domNode.textContent?.startsWith("\uFEFF") && // COMPAT: If the parent node is a Slate zero-width space, editor is
// because the text node should have no characters. However, during IME
// composition the ASCII characters will be prepended to the zero-width
// space, so subtract 1 from the offset to account for the zero-width
// space character.
(parentNode.hasAttribute("data-pt-zero-width") || // COMPAT: In Firefox, `range.cloneContents()` returns an extra trailing '\n'
// when the document ends with a new-line character. This results in the offset
// length being off by one, so we need to subtract one to account for this.
IS_FIREFOX && domNode.textContent?.endsWith(`
`)) && offset--;
}
if (IS_ANDROID && !textNode && !exactMatch) {
const node = parentNode.hasAttribute("data-pt-block") || parentNode.getAttribute("data-pt-inline") === "span" ? parentNode : parentNode.closest('[data-pt-block], [data-pt-inline="span"]');
if (node && DOMEditor.hasDOMNode(editor, node, {
editable: !0
})) {
const nodePath = getDomNodePath(node);
if (!nodePath) {
if (suppressThrow)
return null;
throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`);
}
let {
path: path22,
offset: offset2
} = start(editor, nodePath);
return node.querySelector("[data-pt-marks]") || (offset2 = nearestOffset), {
path: path22,
offset: offset2
};
}
}
if (!textNode && parentNode) {
if (nearestNode instanceof HTMLElement && nearestNode.getAttribute("data-pt-block") === "container") {
const childEl = nearestNode.childNodes[nearestOffset];
if (childEl instanceof HTMLElement && DOMEditor.hasDOMNode(editor, childEl)) {
const voidEl = childEl.closest('[data-pt-block="object"], [data-pt-inline="object"]');
if (voidEl) {
const path22 = getDomNodePath(voidEl);
if (path22)
return {
path: path22,
offset: 0
};
}
}
}
const elementNode = parentNode.closest("[data-pt-block]") ?? (parentNode.hasAttribute("data-pt-block") ? parentNode : null);
if (elementNode && DOMEditor.hasDOMNode(editor, elementNode)) {
const voidEl = elementNode.closest('[data-pt-block="object"], [data-pt-inline="object"]');
if (voidEl) {
const path22 = getDomNodePath(voidEl);
if (path22)
return {
path: path22,
offset: 0
};
}
}
}
if (!textNode) {
if (suppressThrow)
return null;
throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`);
}
const path2 = getDomNodePath(textNode);
if (!path2) {
if (suppressThrow)
return null;
throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`);
}
if (path2.length > 1) {
const parentPath2 = path2.slice(0, -1), parentEntry = getNode(editor, parentPath2);
if (parentEntry && isVoidNode(editor, parentEntry.node, parentPath2))
return {
path: parentPath2,
offset: 0
};
}
return {
path: path2,
offset
};
},
toSlateRange: (editor, domRange, options) => {
const {
exactMatch,
suppressThrow
} = options, el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer;
let anchorNode = null, anchorOffset = 0, focusNode = null, focusOffset = 0, isCollapsed = !1;
if (el)
if (isDOMSelection(domRange)) {
if (IS_FIREFOX && domRange.rangeCount > 1) {
focusNode = domRange.focusNode;
const firstRange = domRange.getRangeAt(0), lastRange = domRange.getRangeAt(domRange.rangeCount - 1);
if (focusNode instanceof HTMLTableRowElement && firstRange.startContainer instanceof HTMLTableRowElement && lastRange.startContainer instanceof HTMLTableRowElement) {
let getLastChildren2 = function(element) {
return element.childElementCount > 0 ? getLastChildren2(element.children[0]) : element;
};
const firstNodeRow = firstRange.startContainer, lastNodeRow = lastRange.startContainer, firstNode = getLastChildren2(firstNodeRow.children[firstRange.startOffset]), lastNode = getLastChildren2(lastNodeRow.children[lastRange.startOffset]);
focusOffset = 0, lastNode.childNodes.length > 0 ? anchorNode = lastNode.childNodes[0] ?? null : anchorNode = lastNode, firstNode.childNodes.length > 0 ? focusNode = firstNode.childNodes[0] ?? null : focusNode = firstNode, lastNode instanceof HTMLElement ? anchorOffset = lastNode.innerHTML.length : anchorOffset = 0;
} else
firstRange.startContainer === focusNode ? (anchorNode = lastRange.endContainer, anchorOffset = lastRange.endOffset, focusOffset = firstRange.startOffset) : (anchorNode = firstRange.startContainer, anchorOffset = firstRange.endOffset, focusOffset = lastRange.startOffset);
} else
anchorNode = domRange.anchorNode, anchorOffset = domRange.anchorOffset, focusNode = domRange.focusNode, focusOffset = domRange.focusOffset;
IS_CHROME && hasShadowRoot(anchorNode) || IS_FIREFOX ? isCollapsed = domRange.anchorNode === domRange.focusNode && domRange.anchorOffset === domRange.focusOffset : isCollapsed = domRange.isCollapsed;
} else
anchorNode = domRange.startContainer, anchorOffset = domRange.startOffset, focusNode = domRange.endContainer, focusOffset = domRange.endOffset, isCollapsed = domRange.collapsed;
if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
if (suppressThrow)
return null;
throw new Error(`Cannot resolve a Slate range from DOM range: ${domRange}`);
}
IS_FIREFOX && focusNode.textContent?.endsWith(`
`) && focusOffset === focusNode.textContent.length && focusOffset--;
const anchor = DOMEditor.toSlatePoint(editor, [anchorNode, anchorOffset], {
exactMatch,
suppressThrow
});
if (!anchor)
return null;
const focusBeforeAnchor = isBefore(anchorNode, focusNode) || anchorNode === focusNode && focusOffset < anchorOffset, focus = isCollapsed ? anchor : DOMEditor.toSlatePoint(editor, [focusNode, focusOffset], {
exactMatch,
suppressThrow,
searchDirection: focusBeforeAnchor ? "forward" : "backward"
});
return focus ? {
anchor,
focus
} : null;
}
}, getDefaultView = (value) => value && value.ownerDocument && value.ownerDocument.defaultView || null, isDOMComment = (value) => isDOMNode(value) && value.nodeType === 8, isDOMElement = (value) => isDOMNode(value) && value.nodeType === 1, isDOMNode = (value) => {
const window2 = getDefaultView(value);
return !!window2 && value instanceof window2.Node;
}, isDOMSelection = (value) => {
const window2 = value && value.anchorNode && getDefaultView(value.anchorNode);
return !!window2 && value instanceof window2.Selection;
}, isPlainTextOnlyPaste = (event) => event.clipboardData && event.clipboardData.getData("text/plain") !== "" && event.clipboardData.types.length === 1, normalizeDOMPoint = (domPoint) => {
let [node, offset] = domPoint;
if (isDOMElement(node) && node.childNodes.length) {
let isLast = offset === node.childNodes.length, index = isLast ? offset - 1 : offset;
for ([node, index] = getEditableChildAndIndex(node, index, isLast ? "backward" : "forward"), isLast = index < offset; isDOMElement(node) && node.childNodes.length; ) {
const i = isLast ? node.childNodes.length - 1 : 0;
node = getEditableChild(node, i, isLast ? "backward" : "forward");
}
offset = isLast && node.textContent != null ? node.textContent.length : 0;
}
return [node, offset];
}, hasShadowRoot = (node) => {
let parent = node && node.parentNode;
for (; parent; ) {
if (parent.toString() === "[object ShadowRoot]")
return !0;
parent = parent.parentNode;
}
return !1;
}, getEditableChildAndIndex = (parent, index, direction) => {
const {
childNodes
} = parent;
let child = childNodes[index], i = index, triedForward = !1, triedBackward = !1;
for (; (isDOMComment(child) || isDOMElement(child) && child.childNodes.length === 0 || isDOMElement(child) && child.getAttribute("contenteditable") === "false") && !(triedForward && triedBackward); ) {
if (i >= childNodes.length) {
triedForward = !0, i = index - 1, direction = "backward";
continue;
}
if (i < 0) {
triedBackward = !0, i = index + 1, direction = "forward";
continue;
}
child = childNodes[i], index = i, i += direction === "forward" ? 1 : -1;
}
return [child, index];
}, getEditableChild = (parent, index, direction) => {
const [child] = getEditableChildAndIndex(parent, index, direction);
return child;
}, getSelection = (root) => "getSelection" in root && typeof root.getSelection == "function" ? root.getSelection() : document.getSelection(), isTrackedMutation = (editor, mutation, batch) => {
const {
target
} = mutation;
if (isDOMElement(target) && target.matches('[contentEditable="false"]'))
return !1;
const {
document: document2
} = DOMEditor.getWindow(editor);
if (containsShadowAware(document2, target))
return DOMEditor.hasDOMNode(editor, target, {
editable: !0
});
const parentMutation = batch.find(({
addedNodes,
removedNodes
}) => {
for (const node of addedNodes)
if (node === target || containsShadowAware(node, target))
return !0;
for (const node of removedNodes)
if (node === target || containsShadowAware(node, target))
return !0;
return !1;
});
return !parentMutation || parentMutation === mutation ? !1 : isTrackedMutation(editor, parentMutation, batch);
}, getActiveElement = () => {
let activeElement = document.activeElement;
for (; activeElement?.shadowRoot && activeElement.shadowRoot?.activeElement; )
activeElement = activeElement?.shadowRoot?.activeElement;
return activeElement;
}, isBefore = (node, otherNode) => !!(node.compareDocumentPosition(otherNode) & Node.DOCUMENT_POSITION_PRECEDING), isAfter = (node, otherNode) => !!(node.compareDocumentPosition(otherNode) & Node.DOCUMENT_POSITION_FOLLOWING), closestShadowAware = (element, selector) => {
if (!element)
return null;
let current = element;
for (; current; ) {
if (current.matches && current.matches(selector))
return current;
if (current.parentElement)
current = current.parentElement;
else if (current.parentNode && "host" in current.parentNode)
current = current.parentNode.host;
else
return null;
}
return null;
}, containsShadowAware = (parent, child) => {
if (!parent || !child)
return !1;
if (parent.contains(child))
return !0;
let current = child;
for (; current; ) {
if (current === parent)
return !0;
if (current.parentNode)
"host" in current.parentNode ? current = current.parentNode.host : current = current.parentNode;
else
return !1;
}
return !1;
};
function getDomNodePath(domNode) {
let element = isDOMElement(domNode) ? domNode : domNode.parentElement;
if (element && !element.hasAttribute("data-pt-path") && (element = element.closest("[data-pt-path]")), !element)
return;
const dataPath = element.getAttribute("data-pt-path");
if (dataPath !== null)
return dataPath === "" ? [] : deserializePath(dataPath);
}
function getEventPosition({
editorActor,
slateEditor,
event
}) {
if (editorActor.getSnapshot().matches({
setup: "setting up"
}))
return;
const eventResult = getEventNode({
slateEditor,
event
});
if (!eventResult)
return;
const {
node: eventNode,
path: eventPath
} = eventResult, eventBlockEntry = getEnclosingBlock(slateEditor, eventPath), eventBlock = eventBlockEntry?.node, eventBlockPath = eventBlockEntry?.path, eventPositionBlock = getEventPositionBlock({
nodePath: eventPath,
slateEditor,
event
}), eventSelection = getSelectionFromEvent(slateEditor, event) ?? null;
if (eventBlock && eventBlockPath && eventPositionBlock && !eventSelection && !isEventContainer(slateEditor, eventNode, eventPath))
return {
block: eventPositionBlock,
isEditor: !1,
isContainer: !1,
selection: {
anchor: getBlockStartPoint({
context: slateEditor,
block: {
node: eventBlock,
path: eventBlockPath
}
}),
focus: getBlockEndPoint({
context: slateEditor,
block: {
node: eventBlock,
path: eventBlockPath
}
})
}
};
if (!eventPositionBlock || !eventSelection)
return;
const eventSelectionFocusBlock = getEnclosingBlock(slateEditor, eventSelection.focus.path);
if (eventSelectionFocusBlock)
return isSelectionCollapsed(eventSelection) && eventBlock && eventBlockPath && eventSelectionFocusBlock.node._key !== eventBlock._key && !isAncestorPath(eventBlockPath, eventSelectionFocusBlock.path) ? {
block: eventPositionBlock,
isEditor: !1,
isContainer: !1,
selection: {
anchor: getBlockStartPoint({
context: slateEditor,
block: {
node: eventBlock,
path: eventBlockPath
}
}),
focus: getBlockEndPoint({
context: slateEditor,
block: {
node: eventBlock,
path: eventBlockPath
}
})
}
} : {
block: eventPositionBlock,
isEditor: isEditor(eventNode),
isContainer: isEditor(eventNode) ? !1 : isEditableContainer(slateEditor, eventNode, eventPath),
selection: eventSelection
};
}
function getEventNode({
slateEditor,
event
}) {
if (DOMEditor.hasTarget(slateEditor, event.target))
try {
const path2 = getDomNodePath(event.target);
if (path2) {
if (path2.length === 0)
return {
node: slateEditor,
path: path2
};
{
const nodeEntry = getNode(slateEditor, path2);
if (nodeEntry)
return {
node: nodeEntry.node,
path: path2
};
}
}
} catch (error) {
console.error(error);
}
}
function getEventPositionBlock({
nodePath,
slateEditor,
event
}) {
const firstBlockEntry = getNode(slateEditor, [0]);
if (!firstBlockEntry)
return;
const firstBlockElement = getDomNode(slateEditor, firstBlockEntry.path);
if (!firstBlockElement)
return;
const firstBlockRect = firstBlockElement.getBoundingClientRect();
if (event.pageY < firstBlockRect.top)
return "start";
const lastBlock = slateEditor.children.at(-1), lastBlockEntry = lastBlock ? getNode(slateEditor, [{
_key: lastBlock._key
}]) : void 0;
if (!lastBlockEntry)
return;
const lastBlockElement = getDomNode(slateEditor, lastBlockEntry.path);
if (!lastBlockElement)
return;
const lastBlockRef = lastBlockElement.getBoundingClientRect();
if (event.pageY > lastBlockRef.bottom)
return "end";
const element = getDomNode(slateEditor, nodePath);
if (!element)
return;
const elementRect = element.getBoundingClientRect(), top = elementRect.top, height = elementRect.height;
return Math.abs(top - event.pageY) < height / 2 ? "start" : "end";
}
function getSelectionFromEvent(editor, event) {
if (!event.target || !isDOMNode(event.target))
return;
const window2 = DOMEditor.getWindow(editor);
let domRange;
if (window2.document.caretPositionFromPoint !== void 0) {
const position = window2.document.caretPositionFromPoint(event.clientX, event.clientY);
if (position)
try {
domRange = window2.document.createRange(), domRange.setStart(position.offsetNode, position.offset), domRange.setEnd(position.offsetNode, position.offset);
} catch {
}
} else if (window2.document.caretRangeFromPoint !== void 0)
domRange = window2.document.caretRangeFromPoint(event.clientX, event.clientY) ?? void 0;
else {
console.warn("Neither caretPositionFromPoint nor caretRangeFromPoint is supported");
return;
}
if (domRange)
try {
return DOMEditor.toSlateRange(editor, domRange, {
exactMatch: !1,
// It can still throw even with this option set to true
suppressThrow: !1
});
} catch {
return;
}
}
function isEventContainer(slateEditor, eventNode, eventPath) {
return isEditor(eventNode) ? !0 : isEditableContainer(slateEditor, eventNode, eventPath);
}
function getVoidAncestor(snapshot, path2) {
return getAncestor(snapshot, path2, (node, ancestorPath) => isVoidNode(snapshot, node, ancestorPath));
}
function collapse(editor, options = {}) {
const {
edge = "anchor"
} = options, {
selection
} = editor;
if (selection) {
if (edge === "anchor")
editor.select(selection.anchor);
else if (edge === "focus")
editor.select(selection.focus);
else if (edge === "start") {
const [start2] = rangeEdges(selection, editor);
editor.select(start2);
} else if (edge === "end") {
const [, end2] = rangeEdges(selection, editor);
editor.select(end2);
}
} else return;
}
function deselect(editor) {
const {
selection
} = editor;
selection && editor.apply({
type: "set_selection",
properties: selection,
newProperties: null
});
}
const getCharacterDistance = (str, isRTL = !1) => {
const isLTR = !isRTL, codepoints = isRTL ? codepointsIteratorRTL(str) : str;
let left = CodepointType.None, right = CodepointType.None, distance = 0, gb11 = null, gb12Or13 = null;
for (const char of codepoints) {
const code2 = char.codePointAt(0);
if (!code2)
break;
const type = getCodepointType(char, code2);
if ([left, right] = isLTR ? [right, type] : [type, left], intersects(left, CodepointType.ZWJ) && intersects(right, CodepointType.ExtPict) && (isLTR ? gb11 = endsWithEmojiZWJ(str.substring(0, distance)) : gb11 = endsWithEmojiZWJ(str.substring(0, str.length - distance)), !gb11) || intersects(left, CodepointType.RI) && intersects(right, CodepointType.RI) && (gb12Or13 !== null ? gb12Or13 = !gb12Or13 : isLTR ? gb12Or13 = !0 : gb12Or13 = endsWithOddNumberOfRIs(str.substring(0, str.length - distance)), !gb12Or13) || left !== CodepointType.None && right !== CodepointType.None && isBoundaryPair(left, right))
break;
distance += char.length;
}
return distance || 1;
}, SPACE = /\s/, PUNCTUATION = /[\u002B\u0021-\u0023\u0025-\u002A\u002C-\u002F\u003A\u003B\u003F\u0040\u005B-\u005D\u005F\u007B\u007D\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]/, CHAMELEON = /['\u2018\u2019]/, getWordDistance = (text, isRTL = !1) => {
let dist = 0, started = !1;
for (; text.length > 0; ) {
const charDist = getCharacterDistance(text, isRTL), [char, remaining] = splitByCharacterDistance(text, charDist, isRTL);
if (isWordCharacter(char, remaining, isRTL))
started = !0, dist += charDist;
else if (!started)
dist += charDist;
else
break;
text = remaining;
}
return dist;
}, splitByCharacterDistance = (str, dist, isRTL) => {
if (isRTL) {
const at = str.length - dist;
return [str.slice(at, str.length), str.slice(0, at)];
}
return [str.slice(0, dist), str.slice(dist)];
}, isWordCharacter = (char, remaining, isRTL = !1) => {
if (SPACE.test(char))
return !1;
if (CHAMELEON.test(char)) {
const charDist = getCharacterDistance(remaining, isRTL), [nextChar, nextRemaining] = splitByCharacterDistance(remaining, charDist, isRTL);
if (isWordCharacter(nextChar, nextRemaining, isRTL))
return !0;
}
return !PUNCTUATION.test(char);
}, codepointsIteratorRTL = function* (str) {
const end2 = str.length - 1;
for (let i = 0; i < str.length; i++) {
const char1 = str.charAt(end2 - i);
if (isLowSurrogate$1(char1.charCodeAt(0))) {
const char2 = str.charAt(end2 - i - 1);
if (isHighSurrogate$1(char2.charCodeAt(0))) {
yield char2 + char1, i++;
continue;
}
}
yield char1;
}
}, isHighSurrogate$1 = (charCode) => charCode >= 55296 && charCode <= 56319, isLowSurrogate$1 = (charCode) => charCode >= 56320 && charCode <= 57343, CodepointType = {
None: 0,
Extend: 1,
ZWJ: 2,
RI: 4,
Prepend: 8,
SpacingMark: 16,
L: 32,
V: 64,
T: 128,
LV: 256,
LVT: 512,
ExtPict: 1024,
Any: 2048
}, reExtend = /^[\p{Gr_Ext}\p{EMod}]$/u, rePrepend = /^[\u0600-\u0605\u06DD\u070F\u0890-\u0891\u08E2\u0D4E\u{110BD}\u{110CD}\u{111C2}-\u{111C3}\u{1193F}\u{11941}\u{11A3A}\u{11A84}-\u{11A89}\u{11D46}]$/u, reSpacingMark = /^[\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E-\u094F\u0982-\u0983\u09BF-\u09C0\u09C7-\u09C8\u09CB-\u09CC\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB-\u0ACC\u0B02-\u0B03\u0B40\u0B47-\u0B48\u0B4B-\u0B4C\u0BBF\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0C01-\u0C03\u0C41-\u0C44\u0C82-\u0C83\u0CBE\u0CC0-\u0CC1\u0CC3-\u0CC4\u0CC7-\u0CC8\u0CCA-\u0CCB\u0D02-\u0D03\u0D3F-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D82-\u0D83\u0DD0-\u0DD1\u0DD8-\u0DDE\u0DF2-\u0DF3\u0E33\u0EB3\u0F3E-\u0F3F\u0F7F\u1031\u103B-\u103C\u1056-\u1057\u1084\u1715\u1734\u17B6\u17BE-\u17C5\u17C7-\u17C8\u1923-\u1926\u1929-\u192B\u1930-\u1931\u1933-\u1938\u1A19-\u1A1A\u1A55\u1A57\u1A6D-\u1A72\u1B04\u1B3B\u1B3D-\u1B41\u1B43-\u1B44\u1B82\u1BA1\u1BA6-\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2-\u1BF3\u1C24-\u1C2B\u1C34-\u1C35\u1CE1\u1CF7\uA823-\uA824\uA827\uA880-\uA881\uA8B4-\uA8C3\uA952-\uA953\uA983\uA9B4-\uA9B5\uA9BA-\uA9BB\uA9BE-\uA9C0\uAA2F-\uAA30\uAA33-\uAA34\uAA4D\uAAEB\uAAEE-\uAAEF\uAAF5\uABE3-\uABE4\uABE6-\uABE7\uABE9-\uABEA\uABEC\u{11000}\u{11002}\u{11082}\u{110B0}-\u{110B2}\u{110B7}-\u{110B8}\u{1112C}\u{11145}-\u{11146}\u{11182}\u{111B3}-\u{111B5}\u{111BF}-\u{111C0}\u{111CE}\u{1122C}-\u{1122E}\u{11232}-\u{11233}\u{11235}\u{112E0}-\u{112E2}\u{11302}-\u{11303}\u{1133F}\u{11341}-\u{11344}\u{11347}-\u{11348}\u{1134B}-\u{1134D}\u{11362}-\u{11363}\u{11435}-\u{11437}\u{11440}-\u{11441}\u{11445}\u{114B1}-\u{114B2}\u{114B9}\u{114BB}-\u{114BC}\u{114BE}\u{114C1}\u{115B0}-\u{115B1}\u{115B8}-\u{115B