@codemirror/view
Version:
DOM view component for the CodeMirror code editor
1,341 lines (1,335 loc) • 470 kB
JavaScript
import { Text, RangeSet, MapMode, RangeValue, findClusterBreak, EditorSelection, Facet, StateEffect, ChangeSet, findColumn, CharCategory, EditorState, Annotation, Transaction, Prec, codePointAt, codePointSize, combineConfig, StateField, RangeSetBuilder, countColumn } from '@codemirror/state';
import { StyleModule } from 'style-mod';
import { keyName, base, shift } from 'w3c-keyname';
import elt from 'crelt';
function getSelection(root) {
let target;
// Browsers differ on whether shadow roots have a getSelection
// method. If it exists, use that, otherwise, call it on the
// document.
if (root.nodeType == 11) { // Shadow root
target = root.getSelection ? root : root.ownerDocument;
}
else {
target = root;
}
return target.getSelection();
}
function contains(dom, node) {
return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
}
function hasSelection(dom, selection) {
if (!selection.anchorNode)
return false;
try {
// Firefox will raise 'permission denied' errors when accessing
// properties of `sel.anchorNode` when it's in a generated CSS
// element.
return contains(dom, selection.anchorNode);
}
catch (_) {
return false;
}
}
function clientRectsFor(dom) {
if (dom.nodeType == 3)
return textRange(dom, 0, dom.nodeValue.length).getClientRects();
else if (dom.nodeType == 1)
return dom.getClientRects();
else
return [];
}
// Scans forward and backward through DOM positions equivalent to the
// given one to see if the two are in the same place (i.e. after a
// text node vs at the end of that text node)
function isEquivalentPosition(node, off, targetNode, targetOff) {
return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
scanFor(node, off, targetNode, targetOff, 1)) : false;
}
function domIndex(node) {
for (var index = 0;; index++) {
node = node.previousSibling;
if (!node)
return index;
}
}
function isBlockElement(node) {
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
}
function scanFor(node, off, targetNode, targetOff, dir) {
for (;;) {
if (node == targetNode && off == targetOff)
return true;
if (off == (dir < 0 ? 0 : maxOffset(node))) {
if (node.nodeName == "DIV")
return false;
let parent = node.parentNode;
if (!parent || parent.nodeType != 1)
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.nodeType == 1 && node.contentEditable == "false")
return false;
off = dir < 0 ? maxOffset(node) : 0;
}
else {
return false;
}
}
}
function maxOffset(node) {
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
}
function flattenRect(rect, left) {
let x = left ? rect.left : rect.right;
return { left: x, right: x, top: rect.top, bottom: rect.bottom };
}
function windowRect(win) {
let vp = win.visualViewport;
if (vp)
return {
left: 0, right: vp.width,
top: 0, bottom: vp.height
};
return { left: 0, right: win.innerWidth,
top: 0, bottom: win.innerHeight };
}
function getScale(elt, rect) {
let scaleX = rect.width / elt.offsetWidth;
let scaleY = rect.height / elt.offsetHeight;
if (scaleX > 0.995 && scaleX < 1.005 || !isFinite(scaleX) || Math.abs(rect.width - elt.offsetWidth) < 1)
scaleX = 1;
if (scaleY > 0.995 && scaleY < 1.005 || !isFinite(scaleY) || Math.abs(rect.height - elt.offsetHeight) < 1)
scaleY = 1;
return { scaleX, scaleY };
}
function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
let doc = dom.ownerDocument, win = doc.defaultView || window;
for (let cur = dom, stop = false; cur && !stop;) {
if (cur.nodeType == 1) { // Element
let bounding, top = cur == doc.body;
let scaleX = 1, scaleY = 1;
if (top) {
bounding = windowRect(win);
}
else {
if (/^(fixed|sticky)$/.test(getComputedStyle(cur).position))
stop = true;
if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
cur = cur.assignedSlot || cur.parentNode;
continue;
}
let rect = cur.getBoundingClientRect();
({ scaleX, scaleY } = getScale(cur, rect));
// Make sure scrollbar width isn't included in the rectangle
bounding = { left: rect.left, right: rect.left + cur.clientWidth * scaleX,
top: rect.top, bottom: rect.top + cur.clientHeight * scaleY };
}
let moveX = 0, moveY = 0;
if (y == "nearest") {
if (rect.top < bounding.top) {
moveY = rect.top - (bounding.top + yMargin);
if (side > 0 && rect.bottom > bounding.bottom + moveY)
moveY = rect.bottom - bounding.bottom + yMargin;
}
else if (rect.bottom > bounding.bottom) {
moveY = rect.bottom - bounding.bottom + yMargin;
if (side < 0 && (rect.top - moveY) < bounding.top)
moveY = rect.top - (bounding.top + yMargin);
}
}
else {
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
rect.bottom - boundingHeight + yMargin;
moveY = targetTop - bounding.top;
}
if (x == "nearest") {
if (rect.left < bounding.left) {
moveX = rect.left - (bounding.left + xMargin);
if (side > 0 && rect.right > bounding.right + moveX)
moveX = rect.right - bounding.right + xMargin;
}
else if (rect.right > bounding.right) {
moveX = rect.right - bounding.right + xMargin;
if (side < 0 && rect.left < bounding.left + moveX)
moveX = rect.left - (bounding.left + xMargin);
}
}
else {
let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
(x == "start") == ltr ? rect.left - xMargin :
rect.right - (bounding.right - bounding.left) + xMargin;
moveX = targetLeft - bounding.left;
}
if (moveX || moveY) {
if (top) {
win.scrollBy(moveX, moveY);
}
else {
let movedX = 0, movedY = 0;
if (moveY) {
let start = cur.scrollTop;
cur.scrollTop += moveY / scaleY;
movedY = (cur.scrollTop - start) * scaleY;
}
if (moveX) {
let start = cur.scrollLeft;
cur.scrollLeft += moveX / scaleX;
movedX = (cur.scrollLeft - start) * scaleX;
}
rect = { left: rect.left - movedX, top: rect.top - movedY,
right: rect.right - movedX, bottom: rect.bottom - movedY };
if (movedX && Math.abs(movedX - moveX) < 1)
x = "nearest";
if (movedY && Math.abs(movedY - moveY) < 1)
y = "nearest";
}
}
if (top)
break;
if (rect.top < bounding.top || rect.bottom > bounding.bottom ||
rect.left < bounding.left || rect.right > bounding.right)
rect = { left: Math.max(rect.left, bounding.left), right: Math.min(rect.right, bounding.right),
top: Math.max(rect.top, bounding.top), bottom: Math.min(rect.bottom, bounding.bottom) };
cur = cur.assignedSlot || cur.parentNode;
}
else if (cur.nodeType == 11) { // A shadow root
cur = cur.host;
}
else {
break;
}
}
}
function scrollableParents(dom) {
let doc = dom.ownerDocument, x, y;
for (let cur = dom.parentNode; cur;) {
if (cur == doc.body || (x && y)) {
break;
}
else if (cur.nodeType == 1) {
if (!y && cur.scrollHeight > cur.clientHeight)
y = cur;
if (!x && cur.scrollWidth > cur.clientWidth)
x = cur;
cur = cur.assignedSlot || cur.parentNode;
}
else if (cur.nodeType == 11) {
cur = cur.host;
}
else {
break;
}
}
return { x, y };
}
class DOMSelectionState {
constructor() {
this.anchorNode = null;
this.anchorOffset = 0;
this.focusNode = null;
this.focusOffset = 0;
}
eq(domSel) {
return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
}
setRange(range) {
let { anchorNode, focusNode } = range;
// Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
}
set(anchorNode, anchorOffset, focusNode, focusOffset) {
this.anchorNode = anchorNode;
this.anchorOffset = anchorOffset;
this.focusNode = focusNode;
this.focusOffset = focusOffset;
}
}
let preventScrollSupported = null;
// Feature-detects support for .focus({preventScroll: true}), and uses
// a fallback kludge when not supported.
function focusPreventScroll(dom) {
if (dom.setActive)
return dom.setActive(); // in IE
if (preventScrollSupported)
return dom.focus(preventScrollSupported);
let stack = [];
for (let cur = dom; cur; cur = cur.parentNode) {
stack.push(cur, cur.scrollTop, cur.scrollLeft);
if (cur == cur.ownerDocument)
break;
}
dom.focus(preventScrollSupported == null ? {
get preventScroll() {
preventScrollSupported = { preventScroll: true };
return true;
}
} : undefined);
if (!preventScrollSupported) {
preventScrollSupported = false;
for (let i = 0; i < stack.length;) {
let elt = stack[i++], top = stack[i++], left = stack[i++];
if (elt.scrollTop != top)
elt.scrollTop = top;
if (elt.scrollLeft != left)
elt.scrollLeft = left;
}
}
}
let scratchRange;
function textRange(node, from, to = from) {
let range = scratchRange || (scratchRange = document.createRange());
range.setEnd(node, to);
range.setStart(node, from);
return range;
}
function dispatchKey(elt, name, code, mods) {
let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
if (mods)
({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
let down = new KeyboardEvent("keydown", options);
down.synthetic = true;
elt.dispatchEvent(down);
let up = new KeyboardEvent("keyup", options);
up.synthetic = true;
elt.dispatchEvent(up);
return down.defaultPrevented || up.defaultPrevented;
}
function getRoot(node) {
while (node) {
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
return node;
node = node.assignedSlot || node.parentNode;
}
return null;
}
function clearAttributes(node) {
while (node.attributes.length)
node.removeAttributeNode(node.attributes[0]);
}
function atElementStart(doc, selection) {
let node = selection.focusNode, offset = selection.focusOffset;
if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
return false;
// Safari can report bogus offsets (#1152)
offset = Math.min(offset, maxOffset(node));
for (;;) {
if (offset) {
if (node.nodeType != 1)
return false;
let prev = node.childNodes[offset - 1];
if (prev.contentEditable == "false")
offset--;
else {
node = prev;
offset = maxOffset(node);
}
}
else if (node == doc) {
return true;
}
else {
offset = domIndex(node);
node = node.parentNode;
}
}
}
function isScrolledToBottom(elt) {
return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
}
function textNodeBefore(startNode, startOffset) {
for (let node = startNode, offset = startOffset;;) {
if (node.nodeType == 3 && offset > 0) {
return { node: node, offset: offset };
}
else if (node.nodeType == 1 && offset > 0) {
if (node.contentEditable == "false")
return null;
node = node.childNodes[offset - 1];
offset = maxOffset(node);
}
else if (node.parentNode && !isBlockElement(node)) {
offset = domIndex(node);
node = node.parentNode;
}
else {
return null;
}
}
}
function textNodeAfter(startNode, startOffset) {
for (let node = startNode, offset = startOffset;;) {
if (node.nodeType == 3 && offset < node.nodeValue.length) {
return { node: node, offset: offset };
}
else if (node.nodeType == 1 && offset < node.childNodes.length) {
if (node.contentEditable == "false")
return null;
node = node.childNodes[offset];
offset = 0;
}
else if (node.parentNode && !isBlockElement(node)) {
offset = domIndex(node) + 1;
node = node.parentNode;
}
else {
return null;
}
}
}
class DOMPos {
constructor(node, offset, precise = true) {
this.node = node;
this.offset = offset;
this.precise = precise;
}
static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
}
const noChildren = [];
class ContentView {
constructor() {
this.parent = null;
this.dom = null;
this.flags = 2 /* ViewFlag.NodeDirty */;
}
get overrideDOMText() { return null; }
get posAtStart() {
return this.parent ? this.parent.posBefore(this) : 0;
}
get posAtEnd() {
return this.posAtStart + this.length;
}
posBefore(view) {
let pos = this.posAtStart;
for (let child of this.children) {
if (child == view)
return pos;
pos += child.length + child.breakAfter;
}
throw new RangeError("Invalid child in posBefore");
}
posAfter(view) {
return this.posBefore(view) + view.length;
}
sync(view, track) {
if (this.flags & 2 /* ViewFlag.NodeDirty */) {
let parent = this.dom;
let prev = null, next;
for (let child of this.children) {
if (child.flags & 7 /* ViewFlag.Dirty */) {
if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
let contentView = ContentView.get(next);
if (!contentView || !contentView.parent && contentView.canReuseDOM(child))
child.reuseDOM(next);
}
child.sync(view, track);
child.flags &= ~7 /* ViewFlag.Dirty */;
}
next = prev ? prev.nextSibling : parent.firstChild;
if (track && !track.written && track.node == parent && next != child.dom)
track.written = true;
if (child.dom.parentNode == parent) {
while (next && next != child.dom)
next = rm$1(next);
}
else {
parent.insertBefore(child.dom, next);
}
prev = child.dom;
}
next = prev ? prev.nextSibling : parent.firstChild;
if (next && track && track.node == parent)
track.written = true;
while (next)
next = rm$1(next);
}
else if (this.flags & 1 /* ViewFlag.ChildDirty */) {
for (let child of this.children)
if (child.flags & 7 /* ViewFlag.Dirty */) {
child.sync(view, track);
child.flags &= ~7 /* ViewFlag.Dirty */;
}
}
}
reuseDOM(_dom) { }
localPosFromDOM(node, offset) {
let after;
if (node == this.dom) {
after = this.dom.childNodes[offset];
}
else {
let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
for (;;) {
let parent = node.parentNode;
if (parent == this.dom)
break;
if (bias == 0 && parent.firstChild != parent.lastChild) {
if (node == parent.firstChild)
bias = -1;
else
bias = 1;
}
node = parent;
}
if (bias < 0)
after = node;
else
after = node.nextSibling;
}
if (after == this.dom.firstChild)
return 0;
while (after && !ContentView.get(after))
after = after.nextSibling;
if (!after)
return this.length;
for (let i = 0, pos = 0;; i++) {
let child = this.children[i];
if (child.dom == after)
return pos;
pos += child.length + child.breakAfter;
}
}
domBoundsAround(from, to, offset = 0) {
let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
for (let i = 0, pos = offset, prevEnd = offset; i < this.children.length; i++) {
let child = this.children[i], end = pos + child.length;
if (pos < from && end > to)
return child.domBoundsAround(from, to, pos);
if (end >= from && fromI == -1) {
fromI = i;
fromStart = pos;
}
if (pos > to && child.dom.parentNode == this.dom) {
toI = i;
toEnd = prevEnd;
break;
}
prevEnd = end;
pos = end + child.breakAfter;
}
return { from: fromStart, to: toEnd < 0 ? offset + this.length : toEnd,
startDOM: (fromI ? this.children[fromI - 1].dom.nextSibling : null) || this.dom.firstChild,
endDOM: toI < this.children.length && toI >= 0 ? this.children[toI].dom : null };
}
markDirty(andParent = false) {
this.flags |= 2 /* ViewFlag.NodeDirty */;
this.markParentsDirty(andParent);
}
markParentsDirty(childList) {
for (let parent = this.parent; parent; parent = parent.parent) {
if (childList)
parent.flags |= 2 /* ViewFlag.NodeDirty */;
if (parent.flags & 1 /* ViewFlag.ChildDirty */)
return;
parent.flags |= 1 /* ViewFlag.ChildDirty */;
childList = false;
}
}
setParent(parent) {
if (this.parent != parent) {
this.parent = parent;
if (this.flags & 7 /* ViewFlag.Dirty */)
this.markParentsDirty(true);
}
}
setDOM(dom) {
if (this.dom == dom)
return;
if (this.dom)
this.dom.cmView = null;
this.dom = dom;
dom.cmView = this;
}
get rootView() {
for (let v = this;;) {
let parent = v.parent;
if (!parent)
return v;
v = parent;
}
}
replaceChildren(from, to, children = noChildren) {
this.markDirty();
for (let i = from; i < to; i++) {
let child = this.children[i];
if (child.parent == this && children.indexOf(child) < 0)
child.destroy();
}
if (children.length < 250)
this.children.splice(from, to - from, ...children);
else
this.children = [].concat(this.children.slice(0, from), children, this.children.slice(to));
for (let i = 0; i < children.length; i++)
children[i].setParent(this);
}
ignoreMutation(_rec) { return false; }
ignoreEvent(_event) { return false; }
childCursor(pos = this.length) {
return new ChildCursor(this.children, pos, this.children.length);
}
childPos(pos, bias = 1) {
return this.childCursor().findPos(pos, bias);
}
toString() {
let name = this.constructor.name.replace("View", "");
return name + (this.children.length ? "(" + this.children.join() + ")" :
this.length ? "[" + (name == "Text" ? this.text : this.length) + "]" : "") +
(this.breakAfter ? "#" : "");
}
static get(node) { return node.cmView; }
get isEditable() { return true; }
get isWidget() { return false; }
get isHidden() { return false; }
merge(from, to, source, hasStart, openStart, openEnd) {
return false;
}
become(other) { return false; }
canReuseDOM(other) {
return other.constructor == this.constructor && !((this.flags | other.flags) & 8 /* ViewFlag.Composition */);
}
// When this is a zero-length view with a side, this should return a
// number <= 0 to indicate it is before its position, or a
// number > 0 when after its position.
getSide() { return 0; }
destroy() {
for (let child of this.children)
if (child.parent == this)
child.destroy();
this.parent = null;
}
}
ContentView.prototype.breakAfter = 0;
// Remove a DOM node and return its next sibling.
function rm$1(dom) {
let next = dom.nextSibling;
dom.parentNode.removeChild(dom);
return next;
}
class ChildCursor {
constructor(children, pos, i) {
this.children = children;
this.pos = pos;
this.i = i;
this.off = 0;
}
findPos(pos, bias = 1) {
for (;;) {
if (pos > this.pos || pos == this.pos &&
(bias > 0 || this.i == 0 || this.children[this.i - 1].breakAfter)) {
this.off = pos - this.pos;
return this;
}
let next = this.children[--this.i];
this.pos -= next.length + next.breakAfter;
}
}
}
function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
let { children } = parent;
let before = children.length ? children[fromI] : null;
let last = insert.length ? insert[insert.length - 1] : null;
let breakAtEnd = last ? last.breakAfter : breakAtStart;
// Change within a single child
if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
return;
if (toI < children.length) {
let after = children[toI];
// Make sure the end of the child after the update is preserved in `after`
if (after && (toOff < after.length || after.breakAfter && (last === null || last === void 0 ? void 0 : last.breakAfter))) {
// If we're splitting a child, separate part of it to avoid that
// being mangled when updating the child before the update.
if (fromI == toI) {
after = after.split(toOff);
toOff = 0;
}
// If the element after the replacement should be merged with
// the last replacing element, update `content`
if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
insert[insert.length - 1] = after;
}
else {
// Remove the start of the after element, if necessary, and
// add it to `content`.
if (toOff || after.children.length && !after.children[0].length)
after.merge(0, toOff, null, false, 0, openEnd);
insert.push(after);
}
}
else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
// The element at `toI` is entirely covered by this range.
// Preserve its line break, if any.
if (last)
last.breakAfter = 1;
else
breakAtStart = 1;
}
// Since we've handled the next element from the current elements
// now, make sure `toI` points after that.
toI++;
}
if (before) {
before.breakAfter = breakAtStart;
if (fromOff > 0) {
if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
before.breakAfter = insert.shift().breakAfter;
}
else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
before.merge(fromOff, before.length, null, false, openStart, 0);
}
fromI++;
}
}
// Try to merge widgets on the boundaries of the replacement
while (fromI < toI && insert.length) {
if (children[toI - 1].become(insert[insert.length - 1])) {
toI--;
insert.pop();
openEnd = insert.length ? 0 : openStart;
}
else if (children[fromI].become(insert[0])) {
fromI++;
insert.shift();
openStart = insert.length ? 0 : openEnd;
}
else {
break;
}
}
if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
fromI--;
if (fromI < toI || insert.length)
parent.replaceChildren(fromI, toI, insert);
}
function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
let cur = parent.childCursor();
let { i: toI, off: toOff } = cur.findPos(to, 1);
let { i: fromI, off: fromOff } = cur.findPos(from, -1);
let dLen = from - to;
for (let view of insert)
dLen += view.length;
parent.length += dLen;
replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
}
let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
const ie = !!(ie_upto10 || ie_11up || ie_edge);
const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
var browser = {
mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
windows: /*@__PURE__*//Win/.test(nav.platform),
linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
ie,
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
gecko,
gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
chrome: !!chrome,
chrome_version: chrome ? +chrome[1] : 0,
ios,
android: /*@__PURE__*//Android\b/.test(nav.userAgent),
webkit,
safari,
webkit_version: webkit ? +(/*@__PURE__*//\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
};
const MaxJoinLen = 256;
class TextView extends ContentView {
constructor(text) {
super();
this.text = text;
}
get length() { return this.text.length; }
createDOM(textDOM) {
this.setDOM(textDOM || document.createTextNode(this.text));
}
sync(view, track) {
if (!this.dom)
this.createDOM();
if (this.dom.nodeValue != this.text) {
if (track && track.node == this.dom)
track.written = true;
this.dom.nodeValue = this.text;
}
}
reuseDOM(dom) {
if (dom.nodeType == 3)
this.createDOM(dom);
}
merge(from, to, source) {
if ((this.flags & 8 /* ViewFlag.Composition */) ||
source && (!(source instanceof TextView) ||
this.length - (to - from) + source.length > MaxJoinLen ||
(source.flags & 8 /* ViewFlag.Composition */)))
return false;
this.text = this.text.slice(0, from) + (source ? source.text : "") + this.text.slice(to);
this.markDirty();
return true;
}
split(from) {
let result = new TextView(this.text.slice(from));
this.text = this.text.slice(0, from);
this.markDirty();
result.flags |= this.flags & 8 /* ViewFlag.Composition */;
return result;
}
localPosFromDOM(node, offset) {
return node == this.dom ? offset : offset ? this.text.length : 0;
}
domAtPos(pos) { return new DOMPos(this.dom, pos); }
domBoundsAround(_from, _to, offset) {
return { from: offset, to: offset + this.length, startDOM: this.dom, endDOM: this.dom.nextSibling };
}
coordsAt(pos, side) {
return textCoords(this.dom, pos, side);
}
}
class MarkView extends ContentView {
constructor(mark, children = [], length = 0) {
super();
this.mark = mark;
this.children = children;
this.length = length;
for (let ch of children)
ch.setParent(this);
}
setAttrs(dom) {
clearAttributes(dom);
if (this.mark.class)
dom.className = this.mark.class;
if (this.mark.attrs)
for (let name in this.mark.attrs)
dom.setAttribute(name, this.mark.attrs[name]);
return dom;
}
canReuseDOM(other) {
return super.canReuseDOM(other) && !((this.flags | other.flags) & 8 /* ViewFlag.Composition */);
}
reuseDOM(node) {
if (node.nodeName == this.mark.tagName.toUpperCase()) {
this.setDOM(node);
this.flags |= 4 /* ViewFlag.AttrsDirty */ | 2 /* ViewFlag.NodeDirty */;
}
}
sync(view, track) {
if (!this.dom)
this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
else if (this.flags & 4 /* ViewFlag.AttrsDirty */)
this.setAttrs(this.dom);
super.sync(view, track);
}
merge(from, to, source, _hasStart, openStart, openEnd) {
if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
(from && openStart <= 0) || (to < this.length && openEnd <= 0)))
return false;
mergeChildrenInto(this, from, to, source ? source.children.slice() : [], openStart - 1, openEnd - 1);
this.markDirty();
return true;
}
split(from) {
let result = [], off = 0, detachFrom = -1, i = 0;
for (let elt of this.children) {
let end = off + elt.length;
if (end > from)
result.push(off < from ? elt.split(from - off) : elt);
if (detachFrom < 0 && off >= from)
detachFrom = i;
off = end;
i++;
}
let length = this.length - from;
this.length = from;
if (detachFrom > -1) {
this.children.length = detachFrom;
this.markDirty();
}
return new MarkView(this.mark, result, length);
}
domAtPos(pos) {
return inlineDOMAtPos(this, pos);
}
coordsAt(pos, side) {
return coordsInChildren(this, pos, side);
}
}
function textCoords(text, pos, side) {
let length = text.nodeValue.length;
if (pos > length)
pos = length;
let from = pos, to = pos, flatten = 0;
if (pos == 0 && side < 0 || pos == length && side >= 0) {
if (!(browser.chrome || browser.gecko)) { // These browsers reliably return valid rectangles for empty ranges
if (pos) {
from--;
flatten = 1;
} // FIXME this is wrong in RTL text
else if (to < length) {
to++;
flatten = -1;
}
}
}
else {
if (side < 0)
from--;
else if (to < length)
to++;
}
let rects = textRange(text, from, to).getClientRects();
if (!rects.length)
return null;
let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
if (browser.safari && !flatten && rect.width == 0)
rect = Array.prototype.find.call(rects, r => r.width) || rect;
return flatten ? flattenRect(rect, flatten < 0) : rect || null;
}
// Also used for collapsed ranges that don't have a placeholder widget!
class WidgetView extends ContentView {
static create(widget, length, side) {
return new WidgetView(widget, length, side);
}
constructor(widget, length, side) {
super();
this.widget = widget;
this.length = length;
this.side = side;
this.prevWidget = null;
}
split(from) {
let result = WidgetView.create(this.widget, this.length - from, this.side);
this.length -= from;
return result;
}
sync(view) {
if (!this.dom || !this.widget.updateDOM(this.dom, view)) {
if (this.dom && this.prevWidget)
this.prevWidget.destroy(this.dom);
this.prevWidget = null;
this.setDOM(this.widget.toDOM(view));
if (!this.widget.editable)
this.dom.contentEditable = "false";
}
}
getSide() { return this.side; }
merge(from, to, source, hasStart, openStart, openEnd) {
if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
return false;
this.length = from + (source ? source.length : 0) + (this.length - to);
return true;
}
become(other) {
if (other instanceof WidgetView && other.side == this.side &&
this.widget.constructor == other.widget.constructor) {
if (!this.widget.compare(other.widget))
this.markDirty(true);
if (this.dom && !this.prevWidget)
this.prevWidget = this.widget;
this.widget = other.widget;
this.length = other.length;
return true;
}
return false;
}
ignoreMutation() { return true; }
ignoreEvent(event) { return this.widget.ignoreEvent(event); }
get overrideDOMText() {
if (this.length == 0)
return Text.empty;
let top = this;
while (top.parent)
top = top.parent;
let { view } = top, text = view && view.state.doc, start = this.posAtStart;
return text ? text.slice(start, start + this.length) : Text.empty;
}
domAtPos(pos) {
return (this.length ? pos == 0 : this.side > 0)
? DOMPos.before(this.dom)
: DOMPos.after(this.dom, pos == this.length);
}
domBoundsAround() { return null; }
coordsAt(pos, side) {
let custom = this.widget.coordsAt(this.dom, pos, side);
if (custom)
return custom;
let rects = this.dom.getClientRects(), rect = null;
if (!rects.length)
return null;
let fromBack = this.side ? this.side < 0 : pos > 0;
for (let i = fromBack ? rects.length - 1 : 0;; i += (fromBack ? -1 : 1)) {
rect = rects[i];
if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
break;
}
return flattenRect(rect, !fromBack);
}
get isEditable() { return false; }
get isWidget() { return true; }
get isHidden() { return this.widget.isHidden; }
destroy() {
super.destroy();
if (this.dom)
this.widget.destroy(this.dom);
}
}
// These are drawn around uneditable widgets to avoid a number of
// browser bugs that show up when the cursor is directly next to
// uneditable inline content.
class WidgetBufferView extends ContentView {
constructor(side) {
super();
this.side = side;
}
get length() { return 0; }
merge() { return false; }
become(other) {
return other instanceof WidgetBufferView && other.side == this.side;
}
split() { return new WidgetBufferView(this.side); }
sync() {
if (!this.dom) {
let dom = document.createElement("img");
dom.className = "cm-widgetBuffer";
dom.setAttribute("aria-hidden", "true");
this.setDOM(dom);
}
}
getSide() { return this.side; }
domAtPos(pos) { return this.side > 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom); }
localPosFromDOM() { return 0; }
domBoundsAround() { return null; }
coordsAt(pos) {
return this.dom.getBoundingClientRect();
}
get overrideDOMText() {
return Text.empty;
}
get isHidden() { return true; }
}
TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
function inlineDOMAtPos(parent, pos) {
let dom = parent.dom, { children } = parent, i = 0;
for (let off = 0; i < children.length; i++) {
let child = children[i], end = off + child.length;
if (end == off && child.getSide() <= 0)
continue;
if (pos > off && pos < end && child.dom.parentNode == dom)
return child.domAtPos(pos - off);
if (pos <= off)
break;
off = end;
}
for (let j = i; j > 0; j--) {
let prev = children[j - 1];
if (prev.dom.parentNode == dom)
return prev.domAtPos(prev.length);
}
for (let j = i; j < children.length; j++) {
let next = children[j];
if (next.dom.parentNode == dom)
return next.domAtPos(0);
}
return new DOMPos(dom, 0);
}
// Assumes `view`, if a mark view, has precisely 1 child.
function joinInlineInto(parent, view, open) {
let last, { children } = parent;
if (open > 0 && view instanceof MarkView && children.length &&
(last = children[children.length - 1]) instanceof MarkView && last.mark.eq(view.mark)) {
joinInlineInto(last, view.children[0], open - 1);
}
else {
children.push(view);
view.setParent(parent);
}
parent.length += view.length;
}
function coordsInChildren(view, pos, side) {
let before = null, beforePos = -1, after = null, afterPos = -1;
function scan(view, pos) {
for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
let child = view.children[i], end = off + child.length;
if (end >= pos) {
if (child.children.length) {
scan(child, pos - off);
}
else if ((!after || after.isHidden && (side > 0 || onSameLine(after, child))) &&
(end > pos || off == end && child.getSide() > 0)) {
after = child;
afterPos = pos - off;
}
else if (off < pos || (off == end && child.getSide() < 0) && !child.isHidden) {
before = child;
beforePos = pos - off;
}
}
off = end;
}
}
scan(view, pos);
let target = (side < 0 ? before : after) || before || after;
if (target)
return target.coordsAt(Math.max(0, target == before ? beforePos : afterPos), side);
return fallbackRect(view);
}
function fallbackRect(view) {
let last = view.dom.lastChild;
if (!last)
return view.dom.getBoundingClientRect();
let rects = clientRectsFor(last);
return rects[rects.length - 1] || null;
}
function onSameLine(a, b) {
let posA = a.coordsAt(0, 1), posB = b.coordsAt(0, 1);
return posA && posB && posB.top < posA.bottom;
}
function combineAttrs(source, target) {
for (let name in source) {
if (name == "class" && target.class)
target.class += " " + source.class;
else if (name == "style" && target.style)
target.style += ";" + source.style;
else
target[name] = source[name];
}
return target;
}
const noAttrs = /*@__PURE__*/Object.create(null);
function attrsEq(a, b, ignore) {
if (a == b)
return true;
if (!a)
a = noAttrs;
if (!b)
b = noAttrs;
let keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length - (ignore && keysA.indexOf(ignore) > -1 ? 1 : 0) !=
keysB.length - (ignore && keysB.indexOf(ignore) > -1 ? 1 : 0))
return false;
for (let key of keysA) {
if (key != ignore && (keysB.indexOf(key) == -1 || a[key] !== b[key]))
return false;
}
return true;
}
function updateAttrs(dom, prev, attrs) {
let changed = false;
if (prev)
for (let name in prev)
if (!(attrs && name in attrs)) {
changed = true;
if (name == "style")
dom.style.cssText = "";
else
dom.removeAttribute(name);
}
if (attrs)
for (let name in attrs)
if (!(prev && prev[name] == attrs[name])) {
changed = true;
if (name == "style")
dom.style.cssText = attrs[name];
else
dom.setAttribute(name, attrs[name]);
}
return changed;
}
function getAttrs(dom) {
let attrs = Object.create(null);
for (let i = 0; i < dom.attributes.length; i++) {
let attr = dom.attributes[i];
attrs[attr.name] = attr.value;
}
return attrs;
}
/**
Widgets added to the content are described by subclasses of this
class. Using a description object like that makes it possible to
delay creating of the DOM structure for a widget until it is
needed, and to avoid redrawing widgets even if the decorations
that define them are recreated.
*/
class WidgetType {
/**
Compare this instance to another instance of the same type.
(TypeScript can't express this, but only instances of the same
specific class will be passed to this method.) This is used to
avoid redrawing widgets when they are replaced by a new
decoration of the same type. The default implementation just
returns `false`, which will cause new instances of the widget to
always be redrawn.
*/
eq(widget) { return false; }
/**
Update a DOM element created by a widget of the same type (but
different, non-`eq` content) to reflect this widget. May return
true to indicate that it could update, false to indicate it
couldn't (in which case the widget will be redrawn). The default
implementation just returns false.
*/
updateDOM(dom, view) { return false; }
/**
@internal
*/
compare(other) {
return this == other || this.constructor == other.constructor && this.eq(other);
}
/**
The estimated height this widget will have, to be used when
estimating the height of content that hasn't been drawn. May
return -1 to indicate you don't know. The default implementation
returns -1.
*/
get estimatedHeight() { return -1; }
/**
For inline widgets that are displayed inline (as opposed to
`inline-block`) and introduce line breaks (through `<br>` tags
or textual newlines), this must indicate the amount of line
breaks they introduce. Defaults to 0.
*/
get lineBreaks() { return 0; }
/**
Can be used to configure which kinds of events inside the widget
should be ignored by the editor. The default is to ignore all
events.
*/
ignoreEvent(event) { return true; }
/**
Override the way screen coordinates for positions at/in the
widget are found. `pos` will be the offset into the widget, and
`side` the side of the position that is being queried—less than
zero for before, greater than zero for after, and zero for
directly at that position.
*/
coordsAt(dom, pos, side) { return null; }
/**
@internal
*/
get isHidden() { return false; }
/**
@internal
*/
get editable() { return false; }
/**
This is called when the an instance of the widget is removed
from the editor view.
*/
destroy(dom) { }
}
/**
The different types of blocks that can occur in an editor view.
*/
var BlockType = /*@__PURE__*/(function (BlockType) {
/**
A line of text.
*/
BlockType[BlockType["Text"] = 0] = "Text";
/**
A block widget associated with the position after it.
*/
BlockType[BlockType["WidgetBefore"] = 1] = "WidgetBefore";
/**
A block widget associated with the position before it.
*/
BlockType[BlockType["WidgetAfter"] = 2] = "WidgetAfter";
/**
A block widget [replacing](https://codemirror.net/6/docs/ref/#view.Decoration^replace) a range of content.
*/
BlockType[BlockType["WidgetRange"] = 3] = "WidgetRange";
return BlockType})(BlockType || (BlockType = {}));
/**
A decoration provides information on how to draw or style a piece
of content. You'll usually use it wrapped in a
[`Range`](https://codemirror.net/6/docs/ref/#state.Range), which adds a start and end position.
@nonabstract
*/
class Decoration extends RangeValue {
constructor(
/**
@internal
*/
startSide,
/**
@internal
*/
endSide,
/**
@internal
*/
widget,
/**
The config object used to create this decoration. You can
include additional properties in there to store metadata about
your decoration.
*/
spec) {
super();
this.startSide = startSide;
this.endSide = endSide;
this.widget = widget;
this.spec = spec;
}
/**
@internal
*/
get heightRelevant() { return false; }
/**
Create a mark decoration, which influences the styling of the
content in its range. Nested mark decorations will cause nested
DOM elements to be created. Nesting order is determined by
precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
the higher-precedence decorations creating the inner DOM nodes.
Such elements are split on line boundaries and on the boundaries
of lower-precedence decorations.
*/
static mark(spec) {
return new MarkDecoration(spec);
}
/**
Create a widget decoration, which displays a DOM element at the
given position.
*/
static widget(spec) {
let side = Math.max(-10000, Math.min(10000, spec.side || 0)), block = !!spec.block;
side += (block && !spec.inlineOrder)
? (side > 0 ? 300000000 /* Side.BlockAfter */ : -400000000 /* Side.BlockBefore */)
: (side > 0 ? 100000000 /* Side.InlineAfter */ : -100000000 /* Side.InlineBefore */);
return new PointDecoration(spec, side, side, block, spec.widget || null, false);
}
/**
Create a replace decoration which replaces the given range with
a widget, or simply hides it.
*/
static replace(spec) {
let block = !!spec.block, startSide, endSide;
if (spec.isBlockGap) {
startSide = -500000000 /* Side.GapStart */;
endSide = 400000000 /* Side.GapEnd */;
}
else {
let { start, end } = getInclusive(spec, block);
startSide = (start ? (block ? -300000000 /* Side.BlockIncStart */ : -1 /* Side.InlineIncStart */) : 500000000 /* Side.NonIncStart */) - 1;
endSide = (end ? (block ? 200000000 /* Side.BlockIncEnd */ : 1 /* Side.InlineIncEnd */) : -600000000 /* Side.NonIncEnd */) + 1;
}
return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
}
/**
Create a line decoration, which can add DOM attributes to the
line starting at the given position.
*/
static line(spec) {
return new LineDecoration(spec