@codemirror/view 0.19.9 → 0.19.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/index.cjs +361 -154
- package/dist/index.d.ts +12 -4
- package/dist/index.js +361 -154
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -102,9 +102,9 @@ function windowRect(win) {
|
|
|
102
102
|
top: 0, bottom: win.innerHeight };
|
|
103
103
|
}
|
|
104
104
|
const ScrollSpace = 5;
|
|
105
|
-
function scrollRectIntoView(dom, rect, side) {
|
|
105
|
+
function scrollRectIntoView(dom, rect, side, center) {
|
|
106
106
|
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
107
|
-
for (let cur = dom
|
|
107
|
+
for (let cur = dom; cur;) {
|
|
108
108
|
if (cur.nodeType == 1) { // Element
|
|
109
109
|
let bounding, top = cur == doc.body;
|
|
110
110
|
if (top) {
|
|
@@ -121,7 +121,20 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
121
121
|
top: rect.top, bottom: rect.top + cur.clientHeight };
|
|
122
122
|
}
|
|
123
123
|
let moveX = 0, moveY = 0;
|
|
124
|
-
if (
|
|
124
|
+
if (center) {
|
|
125
|
+
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|
126
|
+
let targetTop;
|
|
127
|
+
if (rectHeight <= boundingHeight)
|
|
128
|
+
targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
|
|
129
|
+
else if (side < 0)
|
|
130
|
+
targetTop = rect.top - ScrollSpace;
|
|
131
|
+
else
|
|
132
|
+
targetTop = rect.bottom + ScrollSpace - boundingHeight;
|
|
133
|
+
moveY = targetTop - bounding.top;
|
|
134
|
+
if (Math.abs(moveY) <= 1)
|
|
135
|
+
moveY = 0;
|
|
136
|
+
}
|
|
137
|
+
else if (rect.top < bounding.top) {
|
|
125
138
|
moveY = -(bounding.top - rect.top + ScrollSpace);
|
|
126
139
|
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
127
140
|
moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
|
|
@@ -163,6 +176,7 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
163
176
|
if (top)
|
|
164
177
|
break;
|
|
165
178
|
cur = cur.assignedSlot || cur.parentNode;
|
|
179
|
+
center = false;
|
|
166
180
|
}
|
|
167
181
|
else if (cur.nodeType == 11) { // A shadow root
|
|
168
182
|
cur = cur.host;
|
|
@@ -251,6 +265,14 @@ function contentEditablePlainTextSupported() {
|
|
|
251
265
|
}
|
|
252
266
|
return _plainTextSupported;
|
|
253
267
|
}
|
|
268
|
+
function getRoot(node) {
|
|
269
|
+
while (node) {
|
|
270
|
+
node = node.assignedSlot || node.parentNode;
|
|
271
|
+
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
254
276
|
|
|
255
277
|
class DOMPos {
|
|
256
278
|
constructor(node, offset, precise = true) {
|
|
@@ -299,25 +321,30 @@ class ContentView {
|
|
|
299
321
|
sync(track) {
|
|
300
322
|
var _a;
|
|
301
323
|
if (this.dirty & 2 /* Node */) {
|
|
302
|
-
let parent = this.dom
|
|
324
|
+
let parent = this.dom;
|
|
325
|
+
let pos = parent.firstChild;
|
|
303
326
|
for (let child of this.children) {
|
|
304
327
|
if (child.dirty) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
child.reuseDOM(next);
|
|
328
|
+
if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
|
|
329
|
+
child.reuseDOM(pos);
|
|
308
330
|
child.sync(track);
|
|
309
331
|
child.dirty = 0 /* Not */;
|
|
310
332
|
}
|
|
311
|
-
if (track && track.node == parent && pos != child.dom)
|
|
333
|
+
if (track && !track.written && track.node == parent && pos != child.dom)
|
|
312
334
|
track.written = true;
|
|
313
|
-
|
|
314
|
-
|
|
335
|
+
if (child.dom.parentNode == parent) {
|
|
336
|
+
while (pos && pos != child.dom)
|
|
337
|
+
pos = rm(pos);
|
|
338
|
+
pos = child.dom.nextSibling;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
parent.insertBefore(child.dom, pos);
|
|
342
|
+
}
|
|
315
343
|
}
|
|
316
|
-
|
|
317
|
-
if (next && track && track.node == parent)
|
|
344
|
+
if (pos && track && track.node == parent)
|
|
318
345
|
track.written = true;
|
|
319
|
-
while (
|
|
320
|
-
|
|
346
|
+
while (pos)
|
|
347
|
+
pos = rm(pos);
|
|
321
348
|
}
|
|
322
349
|
else if (this.dirty & 1 /* Child */) {
|
|
323
350
|
for (let child of this.children)
|
|
@@ -448,6 +475,7 @@ class ContentView {
|
|
|
448
475
|
(this.breakAfter ? "#" : "");
|
|
449
476
|
}
|
|
450
477
|
static get(node) { return node.cmView; }
|
|
478
|
+
get isEditable() { return true; }
|
|
451
479
|
}
|
|
452
480
|
ContentView.prototype.breakAfter = 0;
|
|
453
481
|
// Remove a DOM node and return its next sibling.
|
|
@@ -456,14 +484,6 @@ function rm(dom) {
|
|
|
456
484
|
dom.parentNode.removeChild(dom);
|
|
457
485
|
return next;
|
|
458
486
|
}
|
|
459
|
-
function syncNodeInto(parent, after, dom) {
|
|
460
|
-
let next = after ? after.nextSibling : parent.firstChild;
|
|
461
|
-
if (dom.parentNode == parent)
|
|
462
|
-
while (next != dom)
|
|
463
|
-
next = rm(next);
|
|
464
|
-
else
|
|
465
|
-
parent.insertBefore(dom, next);
|
|
466
|
-
}
|
|
467
487
|
class ChildCursor {
|
|
468
488
|
constructor(children, pos, i) {
|
|
469
489
|
this.children = children;
|
|
@@ -495,15 +515,18 @@ const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
|
|
|
495
515
|
const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
|
|
496
516
|
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|
497
517
|
const safari = !ie && /Apple Computer/.test(nav.vendor);
|
|
518
|
+
const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|
498
519
|
var browser = {
|
|
499
|
-
mac: /Mac/.test(nav.platform),
|
|
520
|
+
mac: ios || /Mac/.test(nav.platform),
|
|
521
|
+
windows: /Win/.test(nav.platform),
|
|
522
|
+
linux: /Linux|X11/.test(nav.platform),
|
|
500
523
|
ie,
|
|
501
524
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|
502
525
|
gecko,
|
|
503
526
|
gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|
504
527
|
chrome: !!chrome,
|
|
505
528
|
chrome_version: chrome ? +chrome[1] : 0,
|
|
506
|
-
ios
|
|
529
|
+
ios,
|
|
507
530
|
android: /Android\b/.test(nav.userAgent),
|
|
508
531
|
webkit,
|
|
509
532
|
safari,
|
|
@@ -725,6 +748,7 @@ class WidgetView extends InlineView {
|
|
|
725
748
|
}
|
|
726
749
|
return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
|
|
727
750
|
}
|
|
751
|
+
get isEditable() { return false; }
|
|
728
752
|
}
|
|
729
753
|
class CompositionView extends WidgetView {
|
|
730
754
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
@@ -736,6 +760,38 @@ class CompositionView extends WidgetView {
|
|
|
736
760
|
ignoreMutation() { return false; }
|
|
737
761
|
get overrideDOMText() { return null; }
|
|
738
762
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
763
|
+
get isEditable() { return true; }
|
|
764
|
+
}
|
|
765
|
+
// These are drawn around uneditable widgets to avoid a number of
|
|
766
|
+
// browser bugs that show up when the cursor is directly next to
|
|
767
|
+
// uneditable inline content.
|
|
768
|
+
class WidgetBufferView extends InlineView {
|
|
769
|
+
constructor(side) {
|
|
770
|
+
super();
|
|
771
|
+
this.side = side;
|
|
772
|
+
}
|
|
773
|
+
get length() { return 0; }
|
|
774
|
+
merge() { return false; }
|
|
775
|
+
become(other) {
|
|
776
|
+
return other instanceof WidgetBufferView && other.side == this.side;
|
|
777
|
+
}
|
|
778
|
+
slice() { return new WidgetBufferView(this.side); }
|
|
779
|
+
sync() {
|
|
780
|
+
if (!this.dom)
|
|
781
|
+
this.setDOM(document.createTextNode("\u200b"));
|
|
782
|
+
else if (this.dirty && this.dom.nodeValue != "\u200b")
|
|
783
|
+
this.dom.nodeValue = "\u200b";
|
|
784
|
+
}
|
|
785
|
+
getSide() { return this.side; }
|
|
786
|
+
domAtPos(pos) { return DOMPos.before(this.dom); }
|
|
787
|
+
domBoundsAround() { return null; }
|
|
788
|
+
coordsAt(pos) {
|
|
789
|
+
let rects = clientRectsFor(this.dom);
|
|
790
|
+
return rects[rects.length - 1];
|
|
791
|
+
}
|
|
792
|
+
get overrideDOMText() {
|
|
793
|
+
return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
|
|
794
|
+
}
|
|
739
795
|
}
|
|
740
796
|
function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
|
|
741
797
|
let cur = parent.childCursor();
|
|
@@ -1231,14 +1287,17 @@ class LineView extends ContentView {
|
|
|
1231
1287
|
}
|
|
1232
1288
|
// Only called when building a line view in ContentBuilder
|
|
1233
1289
|
addLineDeco(deco) {
|
|
1234
|
-
let attrs = deco.spec.attributes;
|
|
1290
|
+
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|
1235
1291
|
if (attrs)
|
|
1236
1292
|
this.attrs = combineAttrs(attrs, this.attrs || {});
|
|
1293
|
+
if (cls)
|
|
1294
|
+
this.attrs = combineAttrs(attrs, { class: cls });
|
|
1237
1295
|
}
|
|
1238
1296
|
domAtPos(pos) {
|
|
1239
1297
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1240
1298
|
}
|
|
1241
1299
|
sync(track) {
|
|
1300
|
+
var _a;
|
|
1242
1301
|
if (!this.dom || (this.dirty & 4 /* Attrs */)) {
|
|
1243
1302
|
this.setDOM(document.createElement("div"));
|
|
1244
1303
|
this.dom.className = "cm-line";
|
|
@@ -1254,7 +1313,7 @@ class LineView extends ContentView {
|
|
|
1254
1313
|
while (last && ContentView.get(last) instanceof MarkView)
|
|
1255
1314
|
last = last.lastChild;
|
|
1256
1315
|
if (!last ||
|
|
1257
|
-
last.nodeName != "BR" && ContentView.get(last)
|
|
1316
|
+
last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
|
|
1258
1317
|
(!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
|
|
1259
1318
|
let hack = document.createElement("BR");
|
|
1260
1319
|
hack.cmIgnore = true;
|
|
@@ -1353,6 +1412,9 @@ class ContentBuilder {
|
|
|
1353
1412
|
this.content = [];
|
|
1354
1413
|
this.curLine = null;
|
|
1355
1414
|
this.breakAtStart = 0;
|
|
1415
|
+
this.pendingBuffer = 0 /* No */;
|
|
1416
|
+
// Set to false directly after a widget that covers the position after it
|
|
1417
|
+
this.atCursorPos = true;
|
|
1356
1418
|
this.openStart = -1;
|
|
1357
1419
|
this.openEnd = -1;
|
|
1358
1420
|
this.text = "";
|
|
@@ -1367,23 +1429,31 @@ class ContentBuilder {
|
|
|
1367
1429
|
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == exports.BlockType.WidgetBefore);
|
|
1368
1430
|
}
|
|
1369
1431
|
getLine() {
|
|
1370
|
-
if (!this.curLine)
|
|
1432
|
+
if (!this.curLine) {
|
|
1371
1433
|
this.content.push(this.curLine = new LineView);
|
|
1434
|
+
this.atCursorPos = true;
|
|
1435
|
+
}
|
|
1372
1436
|
return this.curLine;
|
|
1373
1437
|
}
|
|
1374
|
-
|
|
1438
|
+
flushBuffer(active) {
|
|
1439
|
+
if (this.pendingBuffer) {
|
|
1440
|
+
this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
|
|
1441
|
+
this.pendingBuffer = 0 /* No */;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
addBlockWidget(view) {
|
|
1445
|
+
this.flushBuffer([]);
|
|
1375
1446
|
this.curLine = null;
|
|
1376
1447
|
this.content.push(view);
|
|
1377
1448
|
}
|
|
1378
|
-
finish() {
|
|
1449
|
+
finish(openEnd) {
|
|
1450
|
+
if (!openEnd)
|
|
1451
|
+
this.flushBuffer([]);
|
|
1452
|
+
else
|
|
1453
|
+
this.pendingBuffer = 0 /* No */;
|
|
1379
1454
|
if (!this.posCovered())
|
|
1380
1455
|
this.getLine();
|
|
1381
1456
|
}
|
|
1382
|
-
wrapMarks(view, active) {
|
|
1383
|
-
for (let mark of active)
|
|
1384
|
-
view = new MarkView(mark, [view], view.length);
|
|
1385
|
-
return view;
|
|
1386
|
-
}
|
|
1387
1457
|
buildText(length, active, openStart) {
|
|
1388
1458
|
while (length > 0) {
|
|
1389
1459
|
if (this.textOff == this.text.length) {
|
|
@@ -1398,6 +1468,7 @@ class ContentBuilder {
|
|
|
1398
1468
|
this.content[this.content.length - 1].breakAfter = 1;
|
|
1399
1469
|
else
|
|
1400
1470
|
this.breakAtStart = 1;
|
|
1471
|
+
this.flushBuffer([]);
|
|
1401
1472
|
this.curLine = null;
|
|
1402
1473
|
length--;
|
|
1403
1474
|
continue;
|
|
@@ -1408,7 +1479,9 @@ class ContentBuilder {
|
|
|
1408
1479
|
}
|
|
1409
1480
|
}
|
|
1410
1481
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1411
|
-
this.
|
|
1482
|
+
this.flushBuffer(active);
|
|
1483
|
+
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1484
|
+
this.atCursorPos = true;
|
|
1412
1485
|
this.textOff += take;
|
|
1413
1486
|
length -= take;
|
|
1414
1487
|
openStart = 0;
|
|
@@ -1427,11 +1500,23 @@ class ContentBuilder {
|
|
|
1427
1500
|
let { type } = deco;
|
|
1428
1501
|
if (type == exports.BlockType.WidgetAfter && !this.posCovered())
|
|
1429
1502
|
this.getLine();
|
|
1430
|
-
this.
|
|
1503
|
+
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1431
1504
|
}
|
|
1432
1505
|
else {
|
|
1433
|
-
let
|
|
1434
|
-
this.
|
|
1506
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1507
|
+
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1508
|
+
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1509
|
+
let line = this.getLine();
|
|
1510
|
+
if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
|
|
1511
|
+
this.pendingBuffer = 0 /* No */;
|
|
1512
|
+
this.flushBuffer(active);
|
|
1513
|
+
if (cursorBefore) {
|
|
1514
|
+
line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
|
|
1515
|
+
openStart = active.length + Math.max(0, openStart - active.length);
|
|
1516
|
+
}
|
|
1517
|
+
line.append(wrapMarks(view, active), openStart);
|
|
1518
|
+
this.atCursorPos = cursorAfter;
|
|
1519
|
+
this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
|
|
1435
1520
|
}
|
|
1436
1521
|
}
|
|
1437
1522
|
else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
|
|
@@ -1457,10 +1542,15 @@ class ContentBuilder {
|
|
|
1457
1542
|
builder.openEnd = rangeset.RangeSet.spans(decorations, from, to, builder);
|
|
1458
1543
|
if (builder.openStart < 0)
|
|
1459
1544
|
builder.openStart = builder.openEnd;
|
|
1460
|
-
builder.finish();
|
|
1545
|
+
builder.finish(builder.openEnd);
|
|
1461
1546
|
return builder;
|
|
1462
1547
|
}
|
|
1463
1548
|
}
|
|
1549
|
+
function wrapMarks(view, active) {
|
|
1550
|
+
for (let mark of active)
|
|
1551
|
+
view = new MarkView(mark, [view], view.length);
|
|
1552
|
+
return view;
|
|
1553
|
+
}
|
|
1464
1554
|
class NullWidget extends WidgetType {
|
|
1465
1555
|
constructor(tag) {
|
|
1466
1556
|
super();
|
|
@@ -1481,6 +1571,9 @@ const inputHandler = state.Facet.define();
|
|
|
1481
1571
|
const scrollTo = state.StateEffect.define({
|
|
1482
1572
|
map: (range, changes) => range.map(changes)
|
|
1483
1573
|
});
|
|
1574
|
+
const centerOn = state.StateEffect.define({
|
|
1575
|
+
map: (range, changes) => range.map(changes)
|
|
1576
|
+
});
|
|
1484
1577
|
/**
|
|
1485
1578
|
Log or report an unhandled exception in client code. Should
|
|
1486
1579
|
probably only be used by extension code that allows client code to
|
|
@@ -1907,6 +2000,14 @@ class DocView extends ContentView {
|
|
|
1907
2000
|
return true;
|
|
1908
2001
|
}
|
|
1909
2002
|
}
|
|
2003
|
+
reset(sel) {
|
|
2004
|
+
if (this.dirty) {
|
|
2005
|
+
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2006
|
+
this.dirty = 0 /* Not */;
|
|
2007
|
+
}
|
|
2008
|
+
if (sel)
|
|
2009
|
+
this.updateSelection();
|
|
2010
|
+
}
|
|
1910
2011
|
// Used both by update and checkLayout do perform the actual DOM
|
|
1911
2012
|
// update
|
|
1912
2013
|
updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
|
|
@@ -2036,6 +2137,14 @@ class DocView extends ContentView {
|
|
|
2036
2137
|
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2037
2138
|
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2038
2139
|
this.view.observer.ignore(() => {
|
|
2140
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2141
|
+
// inside an uneditable node, and not bring it back when we
|
|
2142
|
+
// move the cursor to its proper position. This tries to
|
|
2143
|
+
// restore the keyboard by cycling focus.
|
|
2144
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
|
|
2145
|
+
this.dom.blur();
|
|
2146
|
+
this.dom.focus({ preventScroll: true });
|
|
2147
|
+
}
|
|
2039
2148
|
let rawSel = getSelection(this.root);
|
|
2040
2149
|
if (main.empty) {
|
|
2041
2150
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
@@ -2209,7 +2318,7 @@ class DocView extends ContentView {
|
|
|
2209
2318
|
this.view.viewState.lineGapDeco
|
|
2210
2319
|
];
|
|
2211
2320
|
}
|
|
2212
|
-
|
|
2321
|
+
scrollIntoView({ range, center }) {
|
|
2213
2322
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
2214
2323
|
if (!rect)
|
|
2215
2324
|
return;
|
|
@@ -2229,10 +2338,10 @@ class DocView extends ContentView {
|
|
|
2229
2338
|
if (bottom != null)
|
|
2230
2339
|
mBottom = Math.max(mBottom, bottom);
|
|
2231
2340
|
}
|
|
2232
|
-
scrollRectIntoView(this.
|
|
2341
|
+
scrollRectIntoView(this.view.scrollDOM, {
|
|
2233
2342
|
left: rect.left - mLeft, top: rect.top - mTop,
|
|
2234
2343
|
right: rect.right + mRight, bottom: rect.bottom + mBottom
|
|
2235
|
-
}, range.head < range.anchor ? -1 : 1);
|
|
2344
|
+
}, range.head < range.anchor ? -1 : 1, center);
|
|
2236
2345
|
}
|
|
2237
2346
|
}
|
|
2238
2347
|
function betweenUneditable(pos) {
|
|
@@ -2343,6 +2452,14 @@ function findChangedDeco(a, b, diff) {
|
|
|
2343
2452
|
rangeset.RangeSet.compare(a, b, diff, comp);
|
|
2344
2453
|
return comp.changes;
|
|
2345
2454
|
}
|
|
2455
|
+
function inUneditable(node, inside) {
|
|
2456
|
+
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|
2457
|
+
if (cur.nodeType == 1 && cur.contentEditable == 'false') {
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return false;
|
|
2462
|
+
}
|
|
2346
2463
|
|
|
2347
2464
|
/**
|
|
2348
2465
|
Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|
@@ -2789,6 +2906,7 @@ function domPosInText(node, x, y) {
|
|
|
2789
2906
|
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
2790
2907
|
}
|
|
2791
2908
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2909
|
+
var _a;
|
|
2792
2910
|
let content = view.contentDOM.getBoundingClientRect(), block;
|
|
2793
2911
|
let halfLine = view.defaultLineHeight / 2;
|
|
2794
2912
|
for (let bounced = false;;) {
|
|
@@ -2819,7 +2937,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2819
2937
|
// There's visible editor content under the point, so we can try
|
|
2820
2938
|
// using caret(Position|Range)FromPoint as a shortcut
|
|
2821
2939
|
let node, offset = -1;
|
|
2822
|
-
if (element && view.contentDOM.contains(element) &&
|
|
2940
|
+
if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
|
|
2823
2941
|
if (doc.caretPositionFromPoint) {
|
|
2824
2942
|
let pos = doc.caretPositionFromPoint(x, y);
|
|
2825
2943
|
if (pos)
|
|
@@ -2960,7 +3078,23 @@ class InputState {
|
|
|
2960
3078
|
constructor(view) {
|
|
2961
3079
|
this.lastKeyCode = 0;
|
|
2962
3080
|
this.lastKeyTime = 0;
|
|
2963
|
-
|
|
3081
|
+
// On iOS, some keys need to have their default behavior happen
|
|
3082
|
+
// (after which we retroactively handle them and reset the DOM) to
|
|
3083
|
+
// avoid messing up the virtual keyboard state.
|
|
3084
|
+
//
|
|
3085
|
+
// On Chrome Android, backspace near widgets is just completely
|
|
3086
|
+
// broken, and there are no key events, so we need to handle the
|
|
3087
|
+
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3088
|
+
// events, and interrupting it before it is done just makes
|
|
3089
|
+
// subsequent events even more broken, so again, we hold off doing
|
|
3090
|
+
// anything until the browser is finished with whatever it is trying
|
|
3091
|
+
// to do.
|
|
3092
|
+
//
|
|
3093
|
+
// setPendingKey sets this, causing the DOM observer to pause for a
|
|
3094
|
+
// bit, and setting an animation frame (which seems the most
|
|
3095
|
+
// reliable way to detect 'browser is done flailing') to fire a fake
|
|
3096
|
+
// key event and re-sync the view again.
|
|
3097
|
+
this.pendingKey = undefined;
|
|
2964
3098
|
this.lastSelectionOrigin = null;
|
|
2965
3099
|
this.lastSelectionTime = 0;
|
|
2966
3100
|
this.lastEscPress = 0;
|
|
@@ -3069,20 +3203,31 @@ class InputState {
|
|
|
3069
3203
|
// state. So we let it go through, and then, in
|
|
3070
3204
|
// applyDOMChange, notify key handlers of it and reset to
|
|
3071
3205
|
// the state they produce.
|
|
3072
|
-
|
|
3206
|
+
let pending;
|
|
3207
|
+
if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
|
|
3073
3208
|
!(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
|
|
3074
|
-
this.
|
|
3075
|
-
setTimeout(() => this.flushIOSKey(view), 250);
|
|
3209
|
+
this.setPendingKey(view, pending);
|
|
3076
3210
|
return true;
|
|
3077
3211
|
}
|
|
3078
3212
|
return false;
|
|
3079
3213
|
}
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3214
|
+
setPendingKey(view, pending) {
|
|
3215
|
+
this.pendingKey = pending;
|
|
3216
|
+
let flush = () => {
|
|
3217
|
+
if (!this.pendingKey)
|
|
3218
|
+
return false;
|
|
3219
|
+
let key = this.pendingKey;
|
|
3220
|
+
this.pendingKey = undefined;
|
|
3221
|
+
view.observer.processRecords();
|
|
3222
|
+
let startState = view.state;
|
|
3223
|
+
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3224
|
+
if (view.state == startState)
|
|
3225
|
+
view.docView.reset(true);
|
|
3226
|
+
};
|
|
3227
|
+
if (browser.ios)
|
|
3228
|
+
setTimeout(() => requestAnimationFrame(flush), 50);
|
|
3229
|
+
else
|
|
3230
|
+
requestAnimationFrame(flush);
|
|
3086
3231
|
}
|
|
3087
3232
|
ignoreDuringComposition(event) {
|
|
3088
3233
|
if (!/^key/.test(event.type))
|
|
@@ -3129,6 +3274,11 @@ class InputState {
|
|
|
3129
3274
|
this.mouseSelection.destroy();
|
|
3130
3275
|
}
|
|
3131
3276
|
}
|
|
3277
|
+
const PendingKeys = [
|
|
3278
|
+
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|
3279
|
+
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|
3280
|
+
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|
3281
|
+
];
|
|
3132
3282
|
// Key codes for modifier keys
|
|
3133
3283
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3134
3284
|
class MouseSelection {
|
|
@@ -3574,6 +3724,33 @@ handlers.compositionend = view => {
|
|
|
3574
3724
|
handlers.contextmenu = view => {
|
|
3575
3725
|
view.inputState.lastContextMenu = Date.now();
|
|
3576
3726
|
};
|
|
3727
|
+
handlers.beforeinput = (view, event) => {
|
|
3728
|
+
var _a;
|
|
3729
|
+
// Because Chrome Android doesn't fire useful key events, use
|
|
3730
|
+
// beforeinput to detect backspace (and possibly enter and delete,
|
|
3731
|
+
// but those usually don't even seem to fire beforeinput events at
|
|
3732
|
+
// the moment) and fake a key event for it.
|
|
3733
|
+
//
|
|
3734
|
+
// (preventDefault on beforeinput, though supported in the spec,
|
|
3735
|
+
// seems to do nothing at all on Chrome).
|
|
3736
|
+
let pending;
|
|
3737
|
+
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3738
|
+
view.inputState.setPendingKey(view, pending);
|
|
3739
|
+
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3740
|
+
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3741
|
+
setTimeout(() => {
|
|
3742
|
+
var _a;
|
|
3743
|
+
// Backspacing near uneditable nodes on Chrome Android sometimes
|
|
3744
|
+
// closes the virtual keyboard. This tries to crudely detect
|
|
3745
|
+
// that and refocus to get it back.
|
|
3746
|
+
if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|
3747
|
+
view.contentDOM.blur();
|
|
3748
|
+
view.focus();
|
|
3749
|
+
}
|
|
3750
|
+
}, 100);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
};
|
|
3577
3754
|
|
|
3578
3755
|
const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
|
|
3579
3756
|
class HeightOracle {
|
|
@@ -4289,6 +4466,15 @@ class LineGapWidget extends WidgetType {
|
|
|
4289
4466
|
}
|
|
4290
4467
|
get estimatedHeight() { return this.vertical ? this.size : -1; }
|
|
4291
4468
|
}
|
|
4469
|
+
class ScrollTarget {
|
|
4470
|
+
constructor(range, center = false) {
|
|
4471
|
+
this.range = range;
|
|
4472
|
+
this.center = center;
|
|
4473
|
+
}
|
|
4474
|
+
map(changes) {
|
|
4475
|
+
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4292
4478
|
class ViewState {
|
|
4293
4479
|
constructor(state) {
|
|
4294
4480
|
this.state = state;
|
|
@@ -4301,7 +4487,7 @@ class ViewState {
|
|
|
4301
4487
|
this.heightOracle = new HeightOracle;
|
|
4302
4488
|
// See VP.MaxDOMHeight
|
|
4303
4489
|
this.scaler = IdScaler;
|
|
4304
|
-
this.
|
|
4490
|
+
this.scrollTarget = null;
|
|
4305
4491
|
// Briefly set to true when printing, to disable viewport limiting
|
|
4306
4492
|
this.printing = false;
|
|
4307
4493
|
this.visibleRanges = [];
|
|
@@ -4334,7 +4520,7 @@ class ViewState {
|
|
|
4334
4520
|
this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
|
|
4335
4521
|
new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
|
|
4336
4522
|
}
|
|
4337
|
-
update(update,
|
|
4523
|
+
update(update, scrollTarget = null) {
|
|
4338
4524
|
let prev = this.state;
|
|
4339
4525
|
this.state = update.state;
|
|
4340
4526
|
let newDeco = this.state.facet(decorations);
|
|
@@ -4345,15 +4531,16 @@ class ViewState {
|
|
|
4345
4531
|
if (this.heightMap.height != prevHeight)
|
|
4346
4532
|
update.flags |= 2 /* Height */;
|
|
4347
4533
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|
4348
|
-
if (
|
|
4349
|
-
|
|
4534
|
+
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
|
|
4535
|
+
!this.viewportIsAppropriate(viewport))
|
|
4536
|
+
viewport = this.getViewport(0, scrollTarget);
|
|
4350
4537
|
this.viewport = viewport;
|
|
4351
4538
|
this.updateForViewport();
|
|
4352
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4539
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4353
4540
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4354
4541
|
update.flags |= this.computeVisibleRanges();
|
|
4355
|
-
if (
|
|
4356
|
-
this.
|
|
4542
|
+
if (scrollTarget)
|
|
4543
|
+
this.scrollTarget = scrollTarget;
|
|
4357
4544
|
if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
|
|
4358
4545
|
update.state.selection.main.empty && update.state.selection.main.assoc)
|
|
4359
4546
|
this.mustEnforceCursorAssoc = true;
|
|
@@ -4406,10 +4593,10 @@ class ViewState {
|
|
|
4406
4593
|
if (oracle.heightChanged)
|
|
4407
4594
|
result |= 2 /* Height */;
|
|
4408
4595
|
if (!this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4409
|
-
this.
|
|
4410
|
-
this.viewport = this.getViewport(bias, this.
|
|
4596
|
+
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
|
|
4597
|
+
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4411
4598
|
this.updateForViewport();
|
|
4412
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4599
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4413
4600
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4414
4601
|
result |= this.computeVisibleRanges();
|
|
4415
4602
|
if (this.mustEnforceCursorAssoc) {
|
|
@@ -4424,22 +4611,25 @@ class ViewState {
|
|
|
4424
4611
|
}
|
|
4425
4612
|
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
|
|
4426
4613
|
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
|
|
4427
|
-
getViewport(bias,
|
|
4614
|
+
getViewport(bias, scrollTarget) {
|
|
4428
4615
|
// This will divide VP.Margin between the top and the
|
|
4429
4616
|
// bottom, depending on the bias (the change in viewport position
|
|
4430
4617
|
// since the last update). It'll hold a number between 0 and 1
|
|
4431
4618
|
let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
|
|
4432
4619
|
let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
|
|
4433
4620
|
let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
|
|
4434
|
-
// If
|
|
4435
|
-
if (
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4621
|
+
// If scrollTarget is given, make sure the viewport includes that position
|
|
4622
|
+
if (scrollTarget) {
|
|
4623
|
+
let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
|
|
4624
|
+
if (head < viewport.from || head > viewport.to) {
|
|
4625
|
+
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4626
|
+
if (scrollTarget.center)
|
|
4627
|
+
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
4628
|
+
else if (head < viewport.from)
|
|
4629
|
+
topPos = block.top;
|
|
4630
|
+
else
|
|
4631
|
+
topPos = block.bottom - viewHeight;
|
|
4632
|
+
viewport = new Viewport(map.lineAt(topPos - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(topPos + viewHeight + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
|
|
4443
4633
|
}
|
|
4444
4634
|
}
|
|
4445
4635
|
return viewport;
|
|
@@ -4481,52 +4671,50 @@ class ViewState {
|
|
|
4481
4671
|
if (this.heightOracle.direction != exports.Direction.LTR)
|
|
4482
4672
|
return gaps;
|
|
4483
4673
|
this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
|
|
4484
|
-
if (line.length <
|
|
4674
|
+
if (line.length < 4000 /* DoubleMargin */)
|
|
4485
4675
|
return;
|
|
4486
4676
|
let structure = lineStructure(line.from, line.to, this.state);
|
|
4487
|
-
if (structure.total <
|
|
4677
|
+
if (structure.total < 4000 /* DoubleMargin */)
|
|
4488
4678
|
return;
|
|
4489
4679
|
let viewFrom, viewTo;
|
|
4490
4680
|
if (this.heightOracle.lineWrapping) {
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
|
|
4495
|
-
if (line.to != this.viewport.to)
|
|
4496
|
-
viewTo = line.to;
|
|
4497
|
-
else
|
|
4498
|
-
viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
|
|
4681
|
+
let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
4682
|
+
viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
|
|
4683
|
+
viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
|
|
4499
4684
|
}
|
|
4500
4685
|
else {
|
|
4501
4686
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
4502
|
-
|
|
4503
|
-
|
|
4687
|
+
let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
|
|
4688
|
+
viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
|
|
4689
|
+
viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
|
|
4504
4690
|
}
|
|
4691
|
+
let outside = [];
|
|
4692
|
+
if (viewFrom > line.from)
|
|
4693
|
+
outside.push({ from: line.from, to: viewFrom });
|
|
4694
|
+
if (viewTo < line.to)
|
|
4695
|
+
outside.push({ from: viewTo, to: line.to });
|
|
4505
4696
|
let sel = this.state.selection.main;
|
|
4506
|
-
// Make sure the
|
|
4507
|
-
if (sel.from
|
|
4508
|
-
|
|
4509
|
-
if (sel.
|
|
4510
|
-
|
|
4511
|
-
let
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
gap.from < gapFrom + 5000 /* HalfMargin */) ||
|
|
4518
|
-
new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
|
|
4697
|
+
// Make sure the gaps don't cover a selection end
|
|
4698
|
+
if (sel.from >= line.from && sel.from <= line.to)
|
|
4699
|
+
cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
|
|
4700
|
+
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
4701
|
+
cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
|
|
4702
|
+
for (let { from, to } of outside)
|
|
4703
|
+
if (to - from > 1000 /* HalfMargin */) {
|
|
4704
|
+
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
4705
|
+
Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
|
|
4706
|
+
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
4707
|
+
}
|
|
4519
4708
|
});
|
|
4520
4709
|
return gaps;
|
|
4521
4710
|
}
|
|
4522
|
-
gapSize(line,
|
|
4711
|
+
gapSize(line, from, to, structure) {
|
|
4712
|
+
let fraction = findFraction(structure, to) - findFraction(structure, from);
|
|
4523
4713
|
if (this.heightOracle.lineWrapping) {
|
|
4524
|
-
|
|
4525
|
-
return start ? height : line.height - height;
|
|
4714
|
+
return line.height * fraction;
|
|
4526
4715
|
}
|
|
4527
4716
|
else {
|
|
4528
|
-
|
|
4529
|
-
return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
|
|
4717
|
+
return structure.total * this.heightOracle.charWidth * fraction;
|
|
4530
4718
|
}
|
|
4531
4719
|
}
|
|
4532
4720
|
updateLineGaps(gaps) {
|
|
@@ -4620,6 +4808,20 @@ function findFraction(structure, pos) {
|
|
|
4620
4808
|
}
|
|
4621
4809
|
return counted / structure.total;
|
|
4622
4810
|
}
|
|
4811
|
+
function cutRange(ranges, from, to) {
|
|
4812
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
4813
|
+
let r = ranges[i];
|
|
4814
|
+
if (r.from < to && r.to > from) {
|
|
4815
|
+
let pieces = [];
|
|
4816
|
+
if (r.from < from)
|
|
4817
|
+
pieces.push({ from: r.from, to: from });
|
|
4818
|
+
if (r.to > to)
|
|
4819
|
+
pieces.push({ from: to, to: r.to });
|
|
4820
|
+
ranges.splice(i, 1, ...pieces);
|
|
4821
|
+
i += pieces.length - 1;
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4623
4825
|
function find(array, f) {
|
|
4624
4826
|
for (let val of array)
|
|
4625
4827
|
if (f(val))
|
|
@@ -4702,7 +4904,7 @@ function buildTheme(main, spec, scopes) {
|
|
|
4702
4904
|
});
|
|
4703
4905
|
}
|
|
4704
4906
|
const baseTheme = buildTheme("." + baseThemeID, {
|
|
4705
|
-
"
|
|
4907
|
+
"&.cm-editor": {
|
|
4706
4908
|
position: "relative !important",
|
|
4707
4909
|
boxSizing: "border-box",
|
|
4708
4910
|
"&.cm-focused": {
|
|
@@ -4911,7 +5113,7 @@ class DOMObserver {
|
|
|
4911
5113
|
this.intersection = new IntersectionObserver(entries => {
|
|
4912
5114
|
if (this.parentCheck < 0)
|
|
4913
5115
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
4914
|
-
if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5116
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
4915
5117
|
this.intersecting = !this.intersecting;
|
|
4916
5118
|
if (this.intersecting != this.view.inView)
|
|
4917
5119
|
this.onScrollChanged(document.createEvent("Event"));
|
|
@@ -4919,7 +5121,7 @@ class DOMObserver {
|
|
|
4919
5121
|
}, {});
|
|
4920
5122
|
this.intersection.observe(this.dom);
|
|
4921
5123
|
this.gapIntersection = new IntersectionObserver(entries => {
|
|
4922
|
-
if (entries[entries.length - 1].intersectionRatio > 0)
|
|
5124
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|
4923
5125
|
this.onScrollChanged(document.createEvent("Event"));
|
|
4924
5126
|
}, {});
|
|
4925
5127
|
}
|
|
@@ -5053,20 +5255,12 @@ class DOMObserver {
|
|
|
5053
5255
|
this.flush();
|
|
5054
5256
|
}
|
|
5055
5257
|
}
|
|
5056
|
-
|
|
5057
|
-
flush() {
|
|
5058
|
-
if (this.delayedFlush >= 0)
|
|
5059
|
-
return;
|
|
5060
|
-
this.lastFlush = Date.now();
|
|
5258
|
+
processRecords() {
|
|
5061
5259
|
let records = this.queue;
|
|
5062
5260
|
for (let mut of this.observer.takeRecords())
|
|
5063
5261
|
records.push(mut);
|
|
5064
5262
|
if (records.length)
|
|
5065
5263
|
this.queue = [];
|
|
5066
|
-
let selection = this.selectionRange;
|
|
5067
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5068
|
-
if (records.length == 0 && !newSel)
|
|
5069
|
-
return;
|
|
5070
5264
|
let from = -1, to = -1, typeOver = false;
|
|
5071
5265
|
for (let record of records) {
|
|
5072
5266
|
let range = this.readMutation(record);
|
|
@@ -5082,17 +5276,26 @@ class DOMObserver {
|
|
|
5082
5276
|
to = Math.max(range.to, to);
|
|
5083
5277
|
}
|
|
5084
5278
|
}
|
|
5279
|
+
return { from, to, typeOver };
|
|
5280
|
+
}
|
|
5281
|
+
// Apply pending changes, if any
|
|
5282
|
+
flush() {
|
|
5283
|
+
// Completely hold off flushing when pending keys are set—the code
|
|
5284
|
+
// managing those will make sure processRecords is called and the
|
|
5285
|
+
// view is resynchronized after
|
|
5286
|
+
if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
|
|
5287
|
+
return;
|
|
5288
|
+
this.lastFlush = Date.now();
|
|
5289
|
+
let { from, to, typeOver } = this.processRecords();
|
|
5290
|
+
let selection = this.selectionRange;
|
|
5291
|
+
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5292
|
+
if (from < 0 && !newSel)
|
|
5293
|
+
return;
|
|
5085
5294
|
let startState = this.view.state;
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
if (this.view.state == startState)
|
|
5089
|
-
|
|
5090
|
-
this.ignore(() => this.view.docView.sync());
|
|
5091
|
-
this.view.docView.dirty = 0 /* Not */;
|
|
5092
|
-
}
|
|
5093
|
-
if (newSel)
|
|
5094
|
-
this.view.docView.updateSelection();
|
|
5095
|
-
}
|
|
5295
|
+
this.onChange(from, to, typeOver);
|
|
5296
|
+
// The view wasn't updated
|
|
5297
|
+
if (this.view.state == startState)
|
|
5298
|
+
this.view.docView.reset(newSel);
|
|
5096
5299
|
this.clearSelection();
|
|
5097
5300
|
}
|
|
5098
5301
|
readMutation(rec) {
|
|
@@ -5229,9 +5432,9 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5229
5432
|
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5230
5433
|
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5231
5434
|
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5232
|
-
dispatchKey(view.contentDOM, "Delete", 46)))
|
|
5233
|
-
browser.ios && view.inputState.flushIOSKey(view))
|
|
5435
|
+
dispatchKey(view.contentDOM, "Delete", 46)))) {
|
|
5234
5436
|
return;
|
|
5437
|
+
}
|
|
5235
5438
|
let text = change.insert.toString();
|
|
5236
5439
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5237
5440
|
return;
|
|
@@ -5452,7 +5655,7 @@ class EditorView {
|
|
|
5452
5655
|
this.dom.appendChild(this.scrollDOM);
|
|
5453
5656
|
this._dispatch = config.dispatch || ((tr) => this.update([tr]));
|
|
5454
5657
|
this.dispatch = this.dispatch.bind(this);
|
|
5455
|
-
this.root = (config.root || document);
|
|
5658
|
+
this.root = (config.root || getRoot(config.parent) || document);
|
|
5456
5659
|
this.viewState = new ViewState(config.state || state.EditorState.create());
|
|
5457
5660
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
|
|
5458
5661
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
@@ -5533,21 +5736,24 @@ class EditorView {
|
|
|
5533
5736
|
if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
|
|
5534
5737
|
return this.setState(state$1);
|
|
5535
5738
|
update = new ViewUpdate(this, state$1, transactions);
|
|
5536
|
-
let
|
|
5739
|
+
let scrollTarget = null;
|
|
5537
5740
|
try {
|
|
5538
5741
|
this.updateState = 2 /* Updating */;
|
|
5539
5742
|
for (let tr of transactions) {
|
|
5540
|
-
if (
|
|
5541
|
-
|
|
5743
|
+
if (scrollTarget)
|
|
5744
|
+
scrollTarget = scrollTarget.map(tr.changes);
|
|
5542
5745
|
if (tr.scrollIntoView) {
|
|
5543
5746
|
let { main } = tr.state.selection;
|
|
5544
|
-
|
|
5747
|
+
scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
|
|
5545
5748
|
}
|
|
5546
|
-
for (let e of tr.effects)
|
|
5749
|
+
for (let e of tr.effects) {
|
|
5547
5750
|
if (e.is(scrollTo))
|
|
5548
|
-
|
|
5751
|
+
scrollTarget = new ScrollTarget(e.value);
|
|
5752
|
+
else if (e.is(centerOn))
|
|
5753
|
+
scrollTarget = new ScrollTarget(e.value, true);
|
|
5754
|
+
}
|
|
5549
5755
|
}
|
|
5550
|
-
this.viewState.update(update,
|
|
5756
|
+
this.viewState.update(update, scrollTarget);
|
|
5551
5757
|
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
|
|
5552
5758
|
if (!update.empty) {
|
|
5553
5759
|
this.updatePlugins(update);
|
|
@@ -5562,7 +5768,7 @@ class EditorView {
|
|
|
5562
5768
|
finally {
|
|
5563
5769
|
this.updateState = 0 /* Idle */;
|
|
5564
5770
|
}
|
|
5565
|
-
if (redrawn ||
|
|
5771
|
+
if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
|
|
5566
5772
|
this.requestMeasure();
|
|
5567
5773
|
if (!update.empty)
|
|
5568
5774
|
for (let listener of this.state.facet(updateListener))
|
|
@@ -5644,7 +5850,7 @@ class EditorView {
|
|
|
5644
5850
|
this.updateState = 1 /* Measuring */;
|
|
5645
5851
|
let oldViewport = this.viewport;
|
|
5646
5852
|
let changed = this.viewState.measure(this.docView, i > 0);
|
|
5647
|
-
if (!changed && !this.measureRequests.length && this.viewState.
|
|
5853
|
+
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5648
5854
|
break;
|
|
5649
5855
|
if (i > 5) {
|
|
5650
5856
|
console.warn("Viewport failed to stabilize");
|
|
@@ -5686,9 +5892,9 @@ class EditorView {
|
|
|
5686
5892
|
logException(this.state, e);
|
|
5687
5893
|
}
|
|
5688
5894
|
}
|
|
5689
|
-
if (this.viewState.
|
|
5690
|
-
this.docView.
|
|
5691
|
-
this.viewState.
|
|
5895
|
+
if (this.viewState.scrollTarget) {
|
|
5896
|
+
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5897
|
+
this.viewState.scrollTarget = null;
|
|
5692
5898
|
}
|
|
5693
5899
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5694
5900
|
break;
|
|
@@ -5720,6 +5926,7 @@ class EditorView {
|
|
|
5720
5926
|
spellcheck: "false",
|
|
5721
5927
|
autocorrect: "off",
|
|
5722
5928
|
autocapitalize: "off",
|
|
5929
|
+
translate: "no",
|
|
5723
5930
|
contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
|
|
5724
5931
|
class: "cm-content",
|
|
5725
5932
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|
@@ -5910,12 +6117,9 @@ class EditorView {
|
|
|
5910
6117
|
moveVertically(start, forward, distance) {
|
|
5911
6118
|
return skipAtoms(this, start, moveVertically(this, start, forward, distance));
|
|
5912
6119
|
}
|
|
5913
|
-
|
|
5914
|
-
Scroll the given document position into view.
|
|
5915
|
-
*/
|
|
6120
|
+
// FIXME remove on next major version
|
|
5916
6121
|
scrollPosIntoView(pos) {
|
|
5917
|
-
this.
|
|
5918
|
-
this.requestMeasure();
|
|
6122
|
+
this.dispatch({ effects: scrollTo.of(state.EditorSelection.cursor(pos)) });
|
|
5919
6123
|
}
|
|
5920
6124
|
/**
|
|
5921
6125
|
Find the DOM parent node and offset (child offset if `node` is
|
|
@@ -6082,7 +6286,7 @@ class EditorView {
|
|
|
6082
6286
|
target editors with a dark or light theme.
|
|
6083
6287
|
*/
|
|
6084
6288
|
static baseTheme(spec) {
|
|
6085
|
-
return state.Prec.
|
|
6289
|
+
return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
|
|
6086
6290
|
}
|
|
6087
6291
|
}
|
|
6088
6292
|
/**
|
|
@@ -6091,6 +6295,11 @@ transaction to make it scroll the given range into view.
|
|
|
6091
6295
|
*/
|
|
6092
6296
|
EditorView.scrollTo = scrollTo;
|
|
6093
6297
|
/**
|
|
6298
|
+
Effect that makes the editor scroll the given range to the
|
|
6299
|
+
center of the visible view.
|
|
6300
|
+
*/
|
|
6301
|
+
EditorView.centerOn = centerOn;
|
|
6302
|
+
/**
|
|
6094
6303
|
Facet to add a [style
|
|
6095
6304
|
module](https://github.com/marijnh/style-mod#documentation) to
|
|
6096
6305
|
an editor view. The view will ensure that the module is
|
|
@@ -6220,11 +6429,7 @@ class CachedOrder {
|
|
|
6220
6429
|
}
|
|
6221
6430
|
}
|
|
6222
6431
|
|
|
6223
|
-
const currentPlatform =
|
|
6224
|
-
: /Mac/.test(navigator.platform) ? "mac"
|
|
6225
|
-
: /Win/.test(navigator.platform) ? "win"
|
|
6226
|
-
: /Linux|X11/.test(navigator.platform) ? "linux"
|
|
6227
|
-
: "key";
|
|
6432
|
+
const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|
6228
6433
|
function normalizeKeyName(name, platform) {
|
|
6229
6434
|
const parts = name.split(/-(?!$)/);
|
|
6230
6435
|
let result = parts[parts.length - 1];
|
|
@@ -6523,7 +6728,7 @@ const themeSpec = {
|
|
|
6523
6728
|
};
|
|
6524
6729
|
if (CanHidePrimary)
|
|
6525
6730
|
themeSpec[".cm-line"].caretColor = "transparent !important";
|
|
6526
|
-
const hideNativeSelection = state.Prec.
|
|
6731
|
+
const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
|
|
6527
6732
|
function getBase(view) {
|
|
6528
6733
|
let rect = view.scrollDOM.getBoundingClientRect();
|
|
6529
6734
|
let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
|
|
@@ -6730,7 +6935,7 @@ class MatchDecorator {
|
|
|
6730
6935
|
}
|
|
6731
6936
|
|
|
6732
6937
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
6733
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6938
|
+
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6734
6939
|
const Names = {
|
|
6735
6940
|
0: "null",
|
|
6736
6941
|
7: "bell",
|
|
@@ -6745,6 +6950,8 @@ const Names = {
|
|
|
6745
6950
|
8206: "left-to-right mark",
|
|
6746
6951
|
8207: "right-to-left mark",
|
|
6747
6952
|
8232: "line separator",
|
|
6953
|
+
8237: "left-to-right override",
|
|
6954
|
+
8238: "right-to-left override",
|
|
6748
6955
|
8233: "paragraph separator",
|
|
6749
6956
|
65279: "zero width no-break space",
|
|
6750
6957
|
65532: "object replacement"
|
|
@@ -6911,7 +7118,7 @@ DOM class.
|
|
|
6911
7118
|
function highlightActiveLine() {
|
|
6912
7119
|
return activeLineHighlighter;
|
|
6913
7120
|
}
|
|
6914
|
-
const lineDeco = Decoration.line({
|
|
7121
|
+
const lineDeco = Decoration.line({ class: "cm-activeLine" });
|
|
6915
7122
|
const activeLineHighlighter = ViewPlugin.fromClass(class {
|
|
6916
7123
|
constructor(view) {
|
|
6917
7124
|
this.decorations = this.getDeco(view);
|