rhino-editor
Version:
A custom element wrapped rich text editor
1,366 lines (1,365 loc) • 350 kB
JavaScript
import {
AllSelection,
DOMParser,
DOMSerializer,
EditorState,
Fragment,
Mark,
Node,
NodeRange,
NodeSelection,
Plugin,
PluginKey,
RemoveMarkStep,
ReplaceAroundStep,
ReplaceStep,
Schema,
Selection,
Slice,
TextSelection,
Transform,
canJoin,
canSplit,
dropPoint,
findWrapping,
joinPoint,
liftTarget,
replaceStep
} from "./chunk-XG3W5CFT.js";
// node_modules/.pnpm/prosemirror-view@1.28.3/node_modules/prosemirror-view/dist/index.js
var domIndex = function(node) {
for (var index = 0; ; index++) {
node = node.previousSibling;
if (!node)
return index;
}
};
var parentNode = function(node) {
let parent = node.assignedSlot || node.parentNode;
return parent && parent.nodeType == 11 ? parent.host : parent;
};
var reusedRange = null;
var textRange = function(node, from, to) {
let range = reusedRange || (reusedRange = document.createRange());
range.setEnd(node, to == null ? node.nodeValue.length : to);
range.setStart(node, from || 0);
return range;
};
var isEquivalentPosition = function(node, off, targetNode, targetOff) {
return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1));
};
var atomElements = /^(img|br|input|textarea|hr)$/i;
function scanFor(node, off, targetNode, targetOff, dir) {
for (; ; ) {
if (node == targetNode && off == targetOff)
return true;
if (off == (dir < 0 ? 0 : nodeSize(node))) {
let parent = node.parentNode;
if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) || node.contentEditable == "false")
return false;
off = domIndex(node) + (dir < 0 ? 0 : 1);
node = parent;
} else if (node.nodeType == 1) {
node = node.childNodes[off + (dir < 0 ? -1 : 0)];
if (node.contentEditable == "false")
return false;
off = dir < 0 ? nodeSize(node) : 0;
} else {
return false;
}
}
}
function nodeSize(node) {
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
}
function isOnEdge(node, offset, parent) {
for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd; ) {
if (node == parent)
return true;
let index = domIndex(node);
node = node.parentNode;
if (!node)
return false;
atStart = atStart && index == 0;
atEnd = atEnd && index == nodeSize(node);
}
}
function hasBlockDesc(dom) {
let desc;
for (let cur = dom; cur; cur = cur.parentNode)
if (desc = cur.pmViewDesc)
break;
return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom);
}
var selectionCollapsed = function(domSel) {
return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset);
};
function keyEvent(keyCode, key) {
let event = document.createEvent("Event");
event.initEvent("keydown", true, true);
event.keyCode = keyCode;
event.key = event.code = key;
return event;
}
function deepActiveElement(doc2) {
let elt = doc2.activeElement;
while (elt && elt.shadowRoot)
elt = elt.shadowRoot.activeElement;
return elt;
}
var nav = typeof navigator != "undefined" ? navigator : null;
var doc = typeof document != "undefined" ? document : null;
var agent = nav && nav.userAgent || "";
var ie_edge = /Edge\/(\d+)/.exec(agent);
var ie_upto10 = /MSIE \d/.exec(agent);
var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
var ie = !!(ie_upto10 || ie_11up || ie_edge);
var ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
var gecko = !ie && /gecko\/(\d+)/i.test(agent);
gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
var _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
var chrome = !!_chrome;
var chrome_version = _chrome ? +_chrome[1] : 0;
var safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
var ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
var mac = ios || (nav ? /Mac/.test(nav.platform) : false);
var android = /Android \d/.test(agent);
var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
function windowRect(doc2) {
return {
left: 0,
right: doc2.documentElement.clientWidth,
top: 0,
bottom: doc2.documentElement.clientHeight
};
}
function getSide(value, side) {
return typeof value == "number" ? value : value[side];
}
function clientRect(node) {
let rect = node.getBoundingClientRect();
let scaleX = rect.width / node.offsetWidth || 1;
let scaleY = rect.height / node.offsetHeight || 1;
return {
left: rect.left,
right: rect.left + node.clientWidth * scaleX,
top: rect.top,
bottom: rect.top + node.clientHeight * scaleY
};
}
function scrollRectIntoView(view, rect, startDOM) {
let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5;
let doc2 = view.dom.ownerDocument;
for (let parent = startDOM || view.dom; ; parent = parentNode(parent)) {
if (!parent)
break;
if (parent.nodeType != 1)
continue;
let elt = parent;
let atTop = elt == doc2.body;
let bounding = atTop ? windowRect(doc2) : clientRect(elt);
let moveX = 0, moveY = 0;
if (rect.top < bounding.top + getSide(scrollThreshold, "top"))
moveY = -(bounding.top - rect.top + getSide(scrollMargin, "top"));
else if (rect.bottom > bounding.bottom - getSide(scrollThreshold, "bottom"))
moveY = rect.bottom - bounding.bottom + getSide(scrollMargin, "bottom");
if (rect.left < bounding.left + getSide(scrollThreshold, "left"))
moveX = -(bounding.left - rect.left + getSide(scrollMargin, "left"));
else if (rect.right > bounding.right - getSide(scrollThreshold, "right"))
moveX = rect.right - bounding.right + getSide(scrollMargin, "right");
if (moveX || moveY) {
if (atTop) {
doc2.defaultView.scrollBy(moveX, moveY);
} else {
let startX = elt.scrollLeft, startY = elt.scrollTop;
if (moveY)
elt.scrollTop += moveY;
if (moveX)
elt.scrollLeft += moveX;
let dX = elt.scrollLeft - startX, dY = elt.scrollTop - startY;
rect = { left: rect.left - dX, top: rect.top - dY, right: rect.right - dX, bottom: rect.bottom - dY };
}
}
if (atTop)
break;
}
}
function storeScrollPos(view) {
let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top);
let refDOM, refTop;
for (let x = (rect.left + rect.right) / 2, y = startY + 1; y < Math.min(innerHeight, rect.bottom); y += 5) {
let dom = view.root.elementFromPoint(x, y);
if (!dom || dom == view.dom || !view.dom.contains(dom))
continue;
let localRect = dom.getBoundingClientRect();
if (localRect.top >= startY - 20) {
refDOM = dom;
refTop = localRect.top;
break;
}
}
return { refDOM, refTop, stack: scrollStack(view.dom) };
}
function scrollStack(dom) {
let stack = [], doc2 = dom.ownerDocument;
for (let cur = dom; cur; cur = parentNode(cur)) {
stack.push({ dom: cur, top: cur.scrollTop, left: cur.scrollLeft });
if (dom == doc2)
break;
}
return stack;
}
function resetScrollPos({ refDOM, refTop, stack }) {
let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0;
restoreScrollStack(stack, newRefTop == 0 ? 0 : newRefTop - refTop);
}
function restoreScrollStack(stack, dTop) {
for (let i = 0; i < stack.length; i++) {
let { dom, top, left } = stack[i];
if (dom.scrollTop != top + dTop)
dom.scrollTop = top + dTop;
if (dom.scrollLeft != left)
dom.scrollLeft = left;
}
}
var preventScrollSupported = null;
function focusPreventScroll(dom) {
if (dom.setActive)
return dom.setActive();
if (preventScrollSupported)
return dom.focus(preventScrollSupported);
let stored = scrollStack(dom);
dom.focus(preventScrollSupported == null ? {
get preventScroll() {
preventScrollSupported = { preventScroll: true };
return true;
}
} : void 0);
if (!preventScrollSupported) {
preventScrollSupported = false;
restoreScrollStack(stored, 0);
}
}
function findOffsetInNode(node, coords) {
let closest, dxClosest = 2e8, coordsClosest, offset = 0;
let rowBot = coords.top, rowTop = coords.top;
for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
let rects;
if (child.nodeType == 1)
rects = child.getClientRects();
else if (child.nodeType == 3)
rects = textRange(child).getClientRects();
else
continue;
for (let i = 0; i < rects.length; i++) {
let rect = rects[i];
if (rect.top <= rowBot && rect.bottom >= rowTop) {
rowBot = Math.max(rect.bottom, rowBot);
rowTop = Math.min(rect.top, rowTop);
let dx = rect.left > coords.left ? rect.left - coords.left : rect.right < coords.left ? coords.left - rect.right : 0;
if (dx < dxClosest) {
closest = child;
dxClosest = dx;
coordsClosest = dx && closest.nodeType == 3 ? {
left: rect.right < coords.left ? rect.right : rect.left,
top: coords.top
} : coords;
if (child.nodeType == 1 && dx)
offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0);
continue;
}
}
if (!closest && (coords.left >= rect.right && coords.top >= rect.top || coords.left >= rect.left && coords.top >= rect.bottom))
offset = childIndex + 1;
}
}
if (closest && closest.nodeType == 3)
return findOffsetInText(closest, coordsClosest);
if (!closest || dxClosest && closest.nodeType == 1)
return { node, offset };
return findOffsetInNode(closest, coordsClosest);
}
function findOffsetInText(node, coords) {
let len = node.nodeValue.length;
let range = document.createRange();
for (let i = 0; i < len; i++) {
range.setEnd(node, i + 1);
range.setStart(node, i);
let rect = singleRect(range, 1);
if (rect.top == rect.bottom)
continue;
if (inRect(coords, rect))
return { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) };
}
return { node, offset: 0 };
}
function inRect(coords, rect) {
return coords.left >= rect.left - 1 && coords.left <= rect.right + 1 && coords.top >= rect.top - 1 && coords.top <= rect.bottom + 1;
}
function targetKludge(dom, coords) {
let parent = dom.parentNode;
if (parent && /^li$/i.test(parent.nodeName) && coords.left < dom.getBoundingClientRect().left)
return parent;
return dom;
}
function posFromElement(view, elt, coords) {
let { node, offset } = findOffsetInNode(elt, coords), bias = -1;
if (node.nodeType == 1 && !node.firstChild) {
let rect = node.getBoundingClientRect();
bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1;
}
return view.docView.posFromDOM(node, offset, bias);
}
function posFromCaret(view, node, offset, coords) {
let outside = -1;
for (let cur = node; ; ) {
if (cur == view.dom)
break;
let desc = view.docView.nearestDesc(cur, true);
if (!desc)
return null;
if (desc.node.isBlock && desc.parent) {
let rect = desc.dom.getBoundingClientRect();
if (rect.left > coords.left || rect.top > coords.top)
outside = desc.posBefore;
else if (rect.right < coords.left || rect.bottom < coords.top)
outside = desc.posAfter;
else
break;
}
cur = desc.dom.parentNode;
}
return outside > -1 ? outside : view.docView.posFromDOM(node, offset, 1);
}
function elementFromPoint(element, coords, box) {
let len = element.childNodes.length;
if (len && box.top < box.bottom) {
for (let startI = Math.max(0, Math.min(len - 1, Math.floor(len * (coords.top - box.top) / (box.bottom - box.top)) - 2)), i = startI; ; ) {
let child = element.childNodes[i];
if (child.nodeType == 1) {
let rects = child.getClientRects();
for (let j = 0; j < rects.length; j++) {
let rect = rects[j];
if (inRect(coords, rect))
return elementFromPoint(child, coords, rect);
}
}
if ((i = (i + 1) % len) == startI)
break;
}
}
return element;
}
function posAtCoords(view, coords) {
let doc2 = view.dom.ownerDocument, node, offset = 0;
if (doc2.caretPositionFromPoint) {
try {
let pos2 = doc2.caretPositionFromPoint(coords.left, coords.top);
if (pos2)
({ offsetNode: node, offset } = pos2);
} catch (_) {
}
}
if (!node && doc2.caretRangeFromPoint) {
let range = doc2.caretRangeFromPoint(coords.left, coords.top);
if (range)
({ startContainer: node, startOffset: offset } = range);
}
let elt = (view.root.elementFromPoint ? view.root : doc2).elementFromPoint(coords.left, coords.top);
let pos;
if (!elt || !view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) {
let box = view.dom.getBoundingClientRect();
if (!inRect(coords, box))
return null;
elt = elementFromPoint(view.dom, coords, box);
if (!elt)
return null;
}
if (safari) {
for (let p = elt; node && p; p = parentNode(p))
if (p.draggable)
node = void 0;
}
elt = targetKludge(elt, coords);
if (node) {
if (gecko && node.nodeType == 1) {
offset = Math.min(offset, node.childNodes.length);
if (offset < node.childNodes.length) {
let next = node.childNodes[offset], box;
if (next.nodeName == "IMG" && (box = next.getBoundingClientRect()).right <= coords.left && box.bottom > coords.top)
offset++;
}
}
if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild.nodeType == 1 && coords.top > node.lastChild.getBoundingClientRect().bottom)
pos = view.state.doc.content.size;
else if (offset == 0 || node.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR")
pos = posFromCaret(view, node, offset, coords);
}
if (pos == null)
pos = posFromElement(view, elt, coords);
let desc = view.docView.nearestDesc(elt, true);
return { pos, inside: desc ? desc.posAtStart - desc.border : -1 };
}
function singleRect(target, bias) {
let rects = target.getClientRects();
return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1];
}
var BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
function coordsAtPos(view, pos, side) {
let { node, offset, atom } = view.docView.domFromPos(pos, side < 0 ? -1 : 1);
let supportEmptyRange = webkit || gecko;
if (node.nodeType == 3) {
if (supportEmptyRange && (BIDI.test(node.nodeValue) || (side < 0 ? !offset : offset == node.nodeValue.length))) {
let rect = singleRect(textRange(node, offset, offset), side);
if (gecko && offset && /\s/.test(node.nodeValue[offset - 1]) && offset < node.nodeValue.length) {
let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1);
if (rectBefore.top == rect.top) {
let rectAfter = singleRect(textRange(node, offset, offset + 1), -1);
if (rectAfter.top != rect.top)
return flattenV(rectAfter, rectAfter.left < rectBefore.left);
}
}
return rect;
} else {
let from = offset, to = offset, takeSide = side < 0 ? 1 : -1;
if (side < 0 && !offset) {
to++;
takeSide = -1;
} else if (side >= 0 && offset == node.nodeValue.length) {
from--;
takeSide = 1;
} else if (side < 0) {
from--;
} else {
to++;
}
return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
}
}
let $dom = view.state.doc.resolve(pos - (atom || 0));
if (!$dom.parent.inlineContent) {
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
let before = node.childNodes[offset - 1];
if (before.nodeType == 1)
return flattenH(before.getBoundingClientRect(), false);
}
if (atom == null && offset < nodeSize(node)) {
let after = node.childNodes[offset];
if (after.nodeType == 1)
return flattenH(after.getBoundingClientRect(), true);
}
return flattenH(node.getBoundingClientRect(), side >= 0);
}
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
let before = node.childNodes[offset - 1];
let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1)) : before.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null;
if (target)
return flattenV(singleRect(target, 1), false);
}
if (atom == null && offset < nodeSize(node)) {
let after = node.childNodes[offset];
while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords)
after = after.nextSibling;
let target = !after ? null : after.nodeType == 3 ? textRange(after, 0, supportEmptyRange ? 0 : 1) : after.nodeType == 1 ? after : null;
if (target)
return flattenV(singleRect(target, -1), true);
}
return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, -side), side >= 0);
}
function flattenV(rect, left) {
if (rect.width == 0)
return rect;
let x = left ? rect.left : rect.right;
return { top: rect.top, bottom: rect.bottom, left: x, right: x };
}
function flattenH(rect, top) {
if (rect.height == 0)
return rect;
let y = top ? rect.top : rect.bottom;
return { top: y, bottom: y, left: rect.left, right: rect.right };
}
function withFlushedState(view, state, f) {
let viewState = view.state, active = view.root.activeElement;
if (viewState != state)
view.updateState(state);
if (active != view.dom)
view.focus();
try {
return f();
} finally {
if (viewState != state)
view.updateState(viewState);
if (active != view.dom && active)
active.focus();
}
}
function endOfTextblockVertical(view, state, dir) {
let sel = state.selection;
let $pos = dir == "up" ? sel.$from : sel.$to;
return withFlushedState(view, state, () => {
let { node: dom } = view.docView.domFromPos($pos.pos, dir == "up" ? -1 : 1);
for (; ; ) {
let nearest = view.docView.nearestDesc(dom, true);
if (!nearest)
break;
if (nearest.node.isBlock) {
dom = nearest.dom;
break;
}
dom = nearest.dom.parentNode;
}
let coords = coordsAtPos(view, $pos.pos, 1);
for (let child = dom.firstChild; child; child = child.nextSibling) {
let boxes;
if (child.nodeType == 1)
boxes = child.getClientRects();
else if (child.nodeType == 3)
boxes = textRange(child, 0, child.nodeValue.length).getClientRects();
else
continue;
for (let i = 0; i < boxes.length; i++) {
let box = boxes[i];
if (box.bottom > box.top + 1 && (dir == "up" ? coords.top - box.top > (box.bottom - coords.top) * 2 : box.bottom - coords.bottom > (coords.bottom - box.top) * 2))
return false;
}
}
return true;
});
}
var maybeRTL = /[\u0590-\u08ac]/;
function endOfTextblockHorizontal(view, state, dir) {
let { $head } = state.selection;
if (!$head.parent.isTextblock)
return false;
let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size;
let sel = view.domSelection();
if (!maybeRTL.test($head.parent.textContent) || !sel.modify)
return dir == "left" || dir == "backward" ? atStart : atEnd;
return withFlushedState(view, state, () => {
let { focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset } = view.domSelectionRange();
let oldBidiLevel = sel.caretBidiLevel;
sel.modify("move", dir, "character");
let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom;
let { focusNode: newNode, focusOffset: newOff } = view.domSelectionRange();
let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) || oldNode == newNode && oldOff == newOff;
try {
sel.collapse(anchorNode, anchorOffset);
if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend)
sel.extend(oldNode, oldOff);
} catch (_) {
}
if (oldBidiLevel != null)
sel.caretBidiLevel = oldBidiLevel;
return result;
});
}
var cachedState = null;
var cachedDir = null;
var cachedResult = false;
function endOfTextblock(view, state, dir) {
if (cachedState == state && cachedDir == dir)
return cachedResult;
cachedState = state;
cachedDir = dir;
return cachedResult = dir == "up" || dir == "down" ? endOfTextblockVertical(view, state, dir) : endOfTextblockHorizontal(view, state, dir);
}
var NOT_DIRTY = 0;
var CHILD_DIRTY = 1;
var CONTENT_DIRTY = 2;
var NODE_DIRTY = 3;
var ViewDesc = class {
constructor(parent, children, dom, contentDOM) {
this.parent = parent;
this.children = children;
this.dom = dom;
this.contentDOM = contentDOM;
this.dirty = NOT_DIRTY;
dom.pmViewDesc = this;
}
// Used to check whether a given description corresponds to a
// widget/mark/node.
matchesWidget(widget) {
return false;
}
matchesMark(mark) {
return false;
}
matchesNode(node, outerDeco, innerDeco) {
return false;
}
matchesHack(nodeName) {
return false;
}
// When parsing in-editor content (in domchange.js), we allow
// descriptions to determine the parse rules that should be used to
// parse them.
parseRule() {
return null;
}
// Used by the editor's event handler to ignore events that come
// from certain descs.
stopEvent(event) {
return false;
}
// The size of the content represented by this desc.
get size() {
let size = 0;
for (let i = 0; i < this.children.length; i++)
size += this.children[i].size;
return size;
}
// For block nodes, this represents the space taken up by their
// start/end tokens.
get border() {
return 0;
}
destroy() {
this.parent = void 0;
if (this.dom.pmViewDesc == this)
this.dom.pmViewDesc = void 0;
for (let i = 0; i < this.children.length; i++)
this.children[i].destroy();
}
posBeforeChild(child) {
for (let i = 0, pos = this.posAtStart; ; i++) {
let cur = this.children[i];
if (cur == child)
return pos;
pos += cur.size;
}
}
get posBefore() {
return this.parent.posBeforeChild(this);
}
get posAtStart() {
return this.parent ? this.parent.posBeforeChild(this) + this.border : 0;
}
get posAfter() {
return this.posBefore + this.size;
}
get posAtEnd() {
return this.posAtStart + this.size - 2 * this.border;
}
localPosFromDOM(dom, offset, bias) {
if (this.contentDOM && this.contentDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode)) {
if (bias < 0) {
let domBefore, desc;
if (dom == this.contentDOM) {
domBefore = dom.childNodes[offset - 1];
} else {
while (dom.parentNode != this.contentDOM)
dom = dom.parentNode;
domBefore = dom.previousSibling;
}
while (domBefore && !((desc = domBefore.pmViewDesc) && desc.parent == this))
domBefore = domBefore.previousSibling;
return domBefore ? this.posBeforeChild(desc) + desc.size : this.posAtStart;
} else {
let domAfter, desc;
if (dom == this.contentDOM) {
domAfter = dom.childNodes[offset];
} else {
while (dom.parentNode != this.contentDOM)
dom = dom.parentNode;
domAfter = dom.nextSibling;
}
while (domAfter && !((desc = domAfter.pmViewDesc) && desc.parent == this))
domAfter = domAfter.nextSibling;
return domAfter ? this.posBeforeChild(desc) : this.posAtEnd;
}
}
let atEnd;
if (dom == this.dom && this.contentDOM) {
atEnd = offset > domIndex(this.contentDOM);
} else if (this.contentDOM && this.contentDOM != this.dom && this.dom.contains(this.contentDOM)) {
atEnd = dom.compareDocumentPosition(this.contentDOM) & 2;
} else if (this.dom.firstChild) {
if (offset == 0)
for (let search = dom; ; search = search.parentNode) {
if (search == this.dom) {
atEnd = false;
break;
}
if (search.previousSibling)
break;
}
if (atEnd == null && offset == dom.childNodes.length)
for (let search = dom; ; search = search.parentNode) {
if (search == this.dom) {
atEnd = true;
break;
}
if (search.nextSibling)
break;
}
}
return (atEnd == null ? bias > 0 : atEnd) ? this.posAtEnd : this.posAtStart;
}
// Scan up the dom finding the first desc that is a descendant of
// this one.
nearestDesc(dom, onlyNodes = false) {
for (let first2 = true, cur = dom; cur; cur = cur.parentNode) {
let desc = this.getDesc(cur), nodeDOM;
if (desc && (!onlyNodes || desc.node)) {
if (first2 && (nodeDOM = desc.nodeDOM) && !(nodeDOM.nodeType == 1 ? nodeDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode) : nodeDOM == dom))
first2 = false;
else
return desc;
}
}
}
getDesc(dom) {
let desc = dom.pmViewDesc;
for (let cur = desc; cur; cur = cur.parent)
if (cur == this)
return desc;
}
posFromDOM(dom, offset, bias) {
for (let scan = dom; scan; scan = scan.parentNode) {
let desc = this.getDesc(scan);
if (desc)
return desc.localPosFromDOM(dom, offset, bias);
}
return -1;
}
// Find the desc for the node after the given pos, if any. (When a
// parent node overrode rendering, there might not be one.)
descAt(pos) {
for (let i = 0, offset = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (offset == pos && end != offset) {
while (!child.border && child.children.length)
child = child.children[0];
return child;
}
if (pos < end)
return child.descAt(pos - offset - child.border);
offset = end;
}
}
domFromPos(pos, side) {
if (!this.contentDOM)
return { node: this.dom, offset: 0, atom: pos + 1 };
let i = 0, offset = 0;
for (let curPos = 0; i < this.children.length; i++) {
let child = this.children[i], end = curPos + child.size;
if (end > pos || child instanceof TrailingHackViewDesc) {
offset = pos - curPos;
break;
}
curPos = end;
}
if (offset)
return this.children[i].domFromPos(offset - this.children[i].border, side);
for (let prev; i && !(prev = this.children[i - 1]).size && prev instanceof WidgetViewDesc && prev.side >= 0; i--) {
}
if (side <= 0) {
let prev, enter2 = true;
for (; ; i--, enter2 = false) {
prev = i ? this.children[i - 1] : null;
if (!prev || prev.dom.parentNode == this.contentDOM)
break;
}
if (prev && side && enter2 && !prev.border && !prev.domAtom)
return prev.domFromPos(prev.size, side);
return { node: this.contentDOM, offset: prev ? domIndex(prev.dom) + 1 : 0 };
} else {
let next, enter2 = true;
for (; ; i++, enter2 = false) {
next = i < this.children.length ? this.children[i] : null;
if (!next || next.dom.parentNode == this.contentDOM)
break;
}
if (next && enter2 && !next.border && !next.domAtom)
return next.domFromPos(0, side);
return { node: this.contentDOM, offset: next ? domIndex(next.dom) : this.contentDOM.childNodes.length };
}
}
// Used to find a DOM range in a single parent for a given changed
// range.
parseRange(from, to, base2 = 0) {
if (this.children.length == 0)
return { node: this.contentDOM, from, to, fromOffset: 0, toOffset: this.contentDOM.childNodes.length };
let fromOffset = -1, toOffset = -1;
for (let offset = base2, i = 0; ; i++) {
let child = this.children[i], end = offset + child.size;
if (fromOffset == -1 && from <= end) {
let childBase = offset + child.border;
if (from >= childBase && to <= end - child.border && child.node && child.contentDOM && this.contentDOM.contains(child.contentDOM))
return child.parseRange(from, to, childBase);
from = offset;
for (let j = i; j > 0; j--) {
let prev = this.children[j - 1];
if (prev.size && prev.dom.parentNode == this.contentDOM && !prev.emptyChildAt(1)) {
fromOffset = domIndex(prev.dom) + 1;
break;
}
from -= prev.size;
}
if (fromOffset == -1)
fromOffset = 0;
}
if (fromOffset > -1 && (end > to || i == this.children.length - 1)) {
to = end;
for (let j = i + 1; j < this.children.length; j++) {
let next = this.children[j];
if (next.size && next.dom.parentNode == this.contentDOM && !next.emptyChildAt(-1)) {
toOffset = domIndex(next.dom);
break;
}
to += next.size;
}
if (toOffset == -1)
toOffset = this.contentDOM.childNodes.length;
break;
}
offset = end;
}
return { node: this.contentDOM, from, to, fromOffset, toOffset };
}
emptyChildAt(side) {
if (this.border || !this.contentDOM || !this.children.length)
return false;
let child = this.children[side < 0 ? 0 : this.children.length - 1];
return child.size == 0 || child.emptyChildAt(side);
}
domAfterPos(pos) {
let { node, offset } = this.domFromPos(pos, 0);
if (node.nodeType != 1 || offset == node.childNodes.length)
throw new RangeError("No node after pos " + pos);
return node.childNodes[offset];
}
// View descs are responsible for setting any selection that falls
// entirely inside of them, so that custom implementations can do
// custom things with the selection. Note that this falls apart when
// a selection starts in such a node and ends in another, in which
// case we just use whatever domFromPos produces as a best effort.
setSelection(anchor, head, root, force = false) {
let from = Math.min(anchor, head), to = Math.max(anchor, head);
for (let i = 0, offset = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (from > offset && to < end)
return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force);
offset = end;
}
let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
let domSel = root.getSelection();
let brKludge = false;
if ((gecko || safari) && anchor == head) {
let { node, offset } = anchorDOM;
if (node.nodeType == 3) {
brKludge = !!(offset && node.nodeValue[offset - 1] == "\n");
if (brKludge && offset == node.nodeValue.length) {
for (let scan = node, after; scan; scan = scan.parentNode) {
if (after = scan.nextSibling) {
if (after.nodeName == "BR")
anchorDOM = headDOM = { node: after.parentNode, offset: domIndex(after) + 1 };
break;
}
let desc = scan.pmViewDesc;
if (desc && desc.node && desc.node.isBlock)
break;
}
}
} else {
let prev = node.childNodes[offset - 1];
brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false");
}
}
if (gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
let after = domSel.focusNode.childNodes[domSel.focusOffset];
if (after && after.contentEditable == "false")
force = true;
}
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset))
return;
let domSelExtended = false;
if ((domSel.extend || anchor == head) && !brKludge) {
domSel.collapse(anchorDOM.node, anchorDOM.offset);
try {
if (anchor != head)
domSel.extend(headDOM.node, headDOM.offset);
domSelExtended = true;
} catch (_) {
}
}
if (!domSelExtended) {
if (anchor > head) {
let tmp = anchorDOM;
anchorDOM = headDOM;
headDOM = tmp;
}
let range = document.createRange();
range.setEnd(headDOM.node, headDOM.offset);
range.setStart(anchorDOM.node, anchorDOM.offset);
domSel.removeAllRanges();
domSel.addRange(range);
}
}
ignoreMutation(mutation) {
return !this.contentDOM && mutation.type != "selection";
}
get contentLost() {
return this.contentDOM && this.contentDOM != this.dom && !this.dom.contains(this.contentDOM);
}
// Remove a subtree of the element tree that has been touched
// by a DOM change, so that the next update will redraw it.
markDirty(from, to) {
for (let offset = 0, i = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (offset == end ? from <= end && to >= offset : from < end && to > offset) {
let startInside = offset + child.border, endInside = end - child.border;
if (from >= startInside && to <= endInside) {
this.dirty = from == offset || to == end ? CONTENT_DIRTY : CHILD_DIRTY;
if (from == startInside && to == endInside && (child.contentLost || child.dom.parentNode != this.contentDOM))
child.dirty = NODE_DIRTY;
else
child.markDirty(from - startInside, to - startInside);
return;
} else {
child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM && !child.children.length ? CONTENT_DIRTY : NODE_DIRTY;
}
}
offset = end;
}
this.dirty = CONTENT_DIRTY;
}
markParentsDirty() {
let level = 1;
for (let node = this.parent; node; node = node.parent, level++) {
let dirty = level == 1 ? CONTENT_DIRTY : CHILD_DIRTY;
if (node.dirty < dirty)
node.dirty = dirty;
}
}
get domAtom() {
return false;
}
get ignoreForCoords() {
return false;
}
};
var WidgetViewDesc = class extends ViewDesc {
constructor(parent, widget, view, pos) {
let self, dom = widget.type.toDOM;
if (typeof dom == "function")
dom = dom(view, () => {
if (!self)
return pos;
if (self.parent)
return self.parent.posBeforeChild(self);
});
if (!widget.type.spec.raw) {
if (dom.nodeType != 1) {
let wrap = document.createElement("span");
wrap.appendChild(dom);
dom = wrap;
}
dom.contentEditable = "false";
dom.classList.add("ProseMirror-widget");
}
super(parent, [], dom, null);
this.widget = widget;
this.widget = widget;
self = this;
}
matchesWidget(widget) {
return this.dirty == NOT_DIRTY && widget.type.eq(this.widget.type);
}
parseRule() {
return { ignore: true };
}
stopEvent(event) {
let stop = this.widget.spec.stopEvent;
return stop ? stop(event) : false;
}
ignoreMutation(mutation) {
return mutation.type != "selection" || this.widget.spec.ignoreSelection;
}
destroy() {
this.widget.type.destroy(this.dom);
super.destroy();
}
get domAtom() {
return true;
}
get side() {
return this.widget.type.side;
}
};
var CompositionViewDesc = class extends ViewDesc {
constructor(parent, dom, textDOM, text) {
super(parent, [], dom, null);
this.textDOM = textDOM;
this.text = text;
}
get size() {
return this.text.length;
}
localPosFromDOM(dom, offset) {
if (dom != this.textDOM)
return this.posAtStart + (offset ? this.size : 0);
return this.posAtStart + offset;
}
domFromPos(pos) {
return { node: this.textDOM, offset: pos };
}
ignoreMutation(mut) {
return mut.type === "characterData" && mut.target.nodeValue == mut.oldValue;
}
};
var MarkViewDesc = class _MarkViewDesc extends ViewDesc {
constructor(parent, mark, dom, contentDOM) {
super(parent, [], dom, contentDOM);
this.mark = mark;
}
static create(parent, mark, inline, view) {
let custom = view.nodeViews[mark.type.name];
let spec = custom && custom(mark, view, inline);
if (!spec || !spec.dom)
spec = DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline));
return new _MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
}
parseRule() {
if (this.dirty & NODE_DIRTY || this.mark.type.spec.reparseInView)
return null;
return { mark: this.mark.type.name, attrs: this.mark.attrs, contentElement: this.contentDOM || void 0 };
}
matchesMark(mark) {
return this.dirty != NODE_DIRTY && this.mark.eq(mark);
}
markDirty(from, to) {
super.markDirty(from, to);
if (this.dirty != NOT_DIRTY) {
let parent = this.parent;
while (!parent.node)
parent = parent.parent;
if (parent.dirty < this.dirty)
parent.dirty = this.dirty;
this.dirty = NOT_DIRTY;
}
}
slice(from, to, view) {
let copy = _MarkViewDesc.create(this.parent, this.mark, true, view);
let nodes = this.children, size = this.size;
if (to < size)
nodes = replaceNodes(nodes, to, size, view);
if (from > 0)
nodes = replaceNodes(nodes, 0, from, view);
for (let i = 0; i < nodes.length; i++)
nodes[i].parent = copy;
copy.children = nodes;
return copy;
}
};
var NodeViewDesc = class _NodeViewDesc extends ViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos) {
super(parent, [], dom, contentDOM);
this.node = node;
this.outerDeco = outerDeco;
this.innerDeco = innerDeco;
this.nodeDOM = nodeDOM;
if (contentDOM)
this.updateChildren(view, pos);
}
// By default, a node is rendered using the `toDOM` method from the
// node type spec. But client code can use the `nodeViews` spec to
// supply a custom node view, which can influence various aspects of
// the way the node works.
//
// (Using subclassing for this was intentionally decided against,
// since it'd require exposing a whole slew of finicky
// implementation details to the user code that they probably will
// never need.)
static create(parent, node, outerDeco, innerDeco, view, pos) {
let custom = view.nodeViews[node.type.name], descObj;
let spec = custom && custom(node, view, () => {
if (!descObj)
return pos;
if (descObj.parent)
return descObj.parent.posBeforeChild(descObj);
}, outerDeco, innerDeco);
let dom = spec && spec.dom, contentDOM = spec && spec.contentDOM;
if (node.isText) {
if (!dom)
dom = document.createTextNode(node.text);
else if (dom.nodeType != 3)
throw new RangeError("Text must be rendered as a DOM text node");
} else if (!dom) {
({ dom, contentDOM } = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node)));
}
if (!contentDOM && !node.isText && dom.nodeName != "BR") {
if (!dom.hasAttribute("contenteditable"))
dom.contentEditable = "false";
if (node.type.spec.draggable)
dom.draggable = true;
}
let nodeDOM = dom;
dom = applyOuterDeco(dom, outerDeco, node);
if (spec)
return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec, view, pos + 1);
else if (node.isText)
return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view);
else
return new _NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1);
}
parseRule() {
if (this.node.type.spec.reparseInView)
return null;
let rule = { node: this.node.type.name, attrs: this.node.attrs };
if (this.node.type.whitespace == "pre")
rule.preserveWhitespace = "full";
if (!this.contentDOM) {
rule.getContent = () => this.node.content;
} else if (!this.contentLost) {
rule.contentElement = this.contentDOM;
} else {
for (let i = this.children.length - 1; i >= 0; i--) {
let child = this.children[i];
if (this.dom.contains(child.dom.parentNode)) {
rule.contentElement = child.dom.parentNode;
break;
}
}
if (!rule.contentElement)
rule.getContent = () => Fragment.empty;
}
return rule;
}
matchesNode(node, outerDeco, innerDeco) {
return this.dirty == NOT_DIRTY && node.eq(this.node) && sameOuterDeco(outerDeco, this.outerDeco) && innerDeco.eq(this.innerDeco);
}
get size() {
return this.node.nodeSize;
}
get border() {
return this.node.isLeaf ? 0 : 1;
}
// Syncs `this.children` to match `this.node.content` and the local
// decorations, possibly introducing nesting for marks. Then, in a
// separate step, syncs the DOM inside `this.contentDOM` to
// `this.children`.
updateChildren(view, pos) {
let inline = this.node.inlineContent, off = pos;
let composition = view.composing ? this.localCompositionInfo(view, pos) : null;
let localComposition = composition && composition.pos > -1 ? composition : null;
let compositionInChild = composition && composition.pos < 0;
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
if (widget.spec.marks)
updater.syncToMarks(widget.spec.marks, inline, view);
else if (widget.type.side >= 0 && !insideNode)
updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view);
updater.placeWidget(widget, view, off);
}, (child, outerDeco, innerDeco, i) => {
updater.syncToMarks(child.marks, inline, view);
let compIndex;
if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ;
else if (compositionInChild && view.state.selection.from > off && view.state.selection.to < off + child.nodeSize && (compIndex = updater.findIndexWithChild(composition.node)) > -1 && updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ;
else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) ;
else {
updater.addNode(child, outerDeco, innerDeco, view, off);
}
off += child.nodeSize;
});
updater.syncToMarks([], inline, view);
if (this.node.isTextblock)
updater.addTextblockHacks();
updater.destroyRest();
if (updater.changed || this.dirty == CONTENT_DIRTY) {
if (localComposition)
this.protectLocalComposition(view, localComposition);
renderDescs(this.contentDOM, this.children, view);
if (ios)
iosHacks(this.dom);
}
}
localCompositionInfo(view, pos) {
let { from, to } = view.state.selection;
if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size)
return null;
let sel = view.domSelectionRange();
let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
if (!textNode || !this.dom.contains(textNode.parentNode))
return null;
if (this.node.inlineContent) {
let text = textNode.nodeValue;
let textPos = findTextInFragment(this.node.content, text, from - pos, to - pos);
return textPos < 0 ? null : { node: textNode, pos: textPos, text };
} else {
return { node: textNode, pos: -1, text: "" };
}
}
protectLocalComposition(view, { node, pos, text }) {
if (this.getDesc(node))
return;
let topNode = node;
for (; ; topNode = topNode.parentNode) {
if (topNode.parentNode == this.contentDOM)
break;
while (topNode.previousSibling)
topNode.parentNode.removeChild(topNode.previousSibling);
while (topNode.nextSibling)
topNode.parentNode.removeChild(topNode.nextSibling);
if (topNode.pmViewDesc)
topNode.pmViewDesc = void 0;
}
let desc = new CompositionViewDesc(this, topNode, node, text);
view.input.compositionNodes.push(desc);
this.children = replaceNodes(this.children, pos, pos + text.length, view, desc);
}
// If this desc must be updated to match the given node decoration,
// do so and return true.
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY || !node.sameMarkup(this.node))
return false;
this.updateInner(node, outerDeco, innerDeco, view);
return true;
}
updateInner(node, outerDeco, innerDeco, view) {
this.updateOuterDeco(outerDeco);
this.node = node;
this.innerDeco = innerDeco;
if (this.contentDOM)
this.updateChildren(view, this.posAtStart);
this.dirty = NOT_DIRTY;
}
updateOuterDeco(outerDeco) {
if (sameOuterDeco(outerDeco, this.outerDeco))
return;
let needsWrap = this.nodeDOM.nodeType != 1;
let oldDOM = this.dom;
this.dom = patchOuterDeco(this.dom, this.nodeDOM, computeOuterDeco(this.outerDeco, this.node, needsWrap), computeOuterDeco(outerDeco, this.node, needsWrap));
if (this.dom != oldDOM) {
oldDOM.pmViewDesc = void 0;
this.dom.pmViewDesc = this;
}
this.outerDeco = outerDeco;
}
// Mark this node as being the selected node.
selectNode() {
if (this.nodeDOM.nodeType == 1)
this.nodeDOM.classList.add("ProseMirror-selectednode");
if (this.contentDOM || !this.node.type.spec.draggable)
this.dom.draggable = true;
}
// Remove selected node marking from this node.
deselectNode() {
if (this.nodeDOM.nodeType == 1)
this.nodeDOM.classList.remove("ProseMirror-selectednode");
if (this.contentDOM || !this.node.type.spec.draggable)
this.dom.removeAttribute("draggable");
}
get domAtom() {
return this.node.isAtom;
}
};
function docViewDesc(doc2, outerDeco, innerDeco, dom, view) {
applyOuterDeco(dom, outerDeco, doc2);
return new NodeViewDesc(void 0, doc2, outerDeco, innerDeco, dom, dom, dom, view, 0);
}
var TextViewDesc = class _TextViewDesc extends NodeViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0);
}
parseRule() {
let skip = this.nodeDOM.parentNode;
while (skip && skip != this.dom && !skip.pmIsDeco)
skip = skip.parentNode;
return { skip: skip || true };
}
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node))
return false;
this.updateOuterDeco(outerDeco);
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.nodeDOM.nodeValue) {
this.nodeDOM.nodeValue = node.text;
if (view.trackWrites == this.nodeDOM)
view.trackWrites = null;
}
this.node = node;
this.dirty = NOT_DIRTY;
return true;
}
inParent() {
let parentDOM = this.parent.contentDOM;
for (let n = this.nodeDOM; n; n = n.parentNode)
if (n == parentDOM)
return true;
return false;
}
domFromPos(pos) {
return { node: this.nodeDOM, offset: pos };
}
localPosFromDOM(dom, offset, bias) {
if (dom == this.nodeDOM)
return this.posAtStart + Math.min(offset, this.node.text.length);
return super.localPosFromDOM(dom, offset, bias);
}
ignoreMutation(mutation) {
return mutation.type != "characterData" && mutation.type != "selection";
}
slice(from, to, view) {
let node = this.node.cut(from, to), dom = document.createTextNode(node.text);
return new _TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view);
}
markDirty(from, to) {
super.markDirty(from, to);
if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length))
this.dirty = NODE_DIRTY;
}
get domAtom() {
return false;
}
};
var TrailingHackViewDesc = class extends ViewDesc {
parseRule() {
return { ignore: true };
}
matchesHack(nodeName) {
return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName;
}
get domAtom() {
return true;
}
get ignoreForCoords() {
return this.dom.nodeName == "IMG";
}
};
var CustomNodeViewDesc = class extends NodeViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view, pos) {
super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos);
this.spec = spec;
}
// A custom `update` method gets to decide whether the update goes
// through. If it does, and there's a `contentDOM` node, our logic
// updates the children.
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY)
return false;
if (this.spec.update) {
let result = this.spec.update(node, outerDeco, innerDeco);
if (result)
this.updateInner(node, outerDeco, innerDeco, view);
return result;
} else if (!this.contentDOM && !node.isLeaf) {
return false;
} else {
return super.update(node, outerDeco, innerDeco, view);
}
}
selectNode() {
this.spec.selectNode ? this.spec.selectNode() : super.selectNode();
}
deselectNode() {
this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode();
}
setSelection(anchor, head, root, force) {
this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : super.setSelection(anchor, head, root, force);
}
destroy() {
if (this.spec.destroy)
this.spec.destroy();
super