@codemirror/view 0.19.8 → 0.19.12
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 +50 -0
- package/dist/index.cjs +390 -158
- package/dist/index.d.ts +12 -4
- package/dist/index.js +390 -158
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -99,9 +99,9 @@ function windowRect(win) {
|
|
|
99
99
|
top: 0, bottom: win.innerHeight };
|
|
100
100
|
}
|
|
101
101
|
const ScrollSpace = 5;
|
|
102
|
-
function scrollRectIntoView(dom, rect, side) {
|
|
102
|
+
function scrollRectIntoView(dom, rect, side, center) {
|
|
103
103
|
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
104
|
-
for (let cur = dom
|
|
104
|
+
for (let cur = dom; cur;) {
|
|
105
105
|
if (cur.nodeType == 1) { // Element
|
|
106
106
|
let bounding, top = cur == doc.body;
|
|
107
107
|
if (top) {
|
|
@@ -118,7 +118,20 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
118
118
|
top: rect.top, bottom: rect.top + cur.clientHeight };
|
|
119
119
|
}
|
|
120
120
|
let moveX = 0, moveY = 0;
|
|
121
|
-
if (
|
|
121
|
+
if (center) {
|
|
122
|
+
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|
123
|
+
let targetTop;
|
|
124
|
+
if (rectHeight <= boundingHeight)
|
|
125
|
+
targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
|
|
126
|
+
else if (side < 0)
|
|
127
|
+
targetTop = rect.top - ScrollSpace;
|
|
128
|
+
else
|
|
129
|
+
targetTop = rect.bottom + ScrollSpace - boundingHeight;
|
|
130
|
+
moveY = targetTop - bounding.top;
|
|
131
|
+
if (Math.abs(moveY) <= 1)
|
|
132
|
+
moveY = 0;
|
|
133
|
+
}
|
|
134
|
+
else if (rect.top < bounding.top) {
|
|
122
135
|
moveY = -(bounding.top - rect.top + ScrollSpace);
|
|
123
136
|
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
124
137
|
moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
|
|
@@ -160,6 +173,7 @@ function scrollRectIntoView(dom, rect, side) {
|
|
|
160
173
|
if (top)
|
|
161
174
|
break;
|
|
162
175
|
cur = cur.assignedSlot || cur.parentNode;
|
|
176
|
+
center = false;
|
|
163
177
|
}
|
|
164
178
|
else if (cur.nodeType == 11) { // A shadow root
|
|
165
179
|
cur = cur.host;
|
|
@@ -248,6 +262,14 @@ function contentEditablePlainTextSupported() {
|
|
|
248
262
|
}
|
|
249
263
|
return _plainTextSupported;
|
|
250
264
|
}
|
|
265
|
+
function getRoot(node) {
|
|
266
|
+
while (node) {
|
|
267
|
+
node = node.assignedSlot || node.parentNode;
|
|
268
|
+
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|
269
|
+
return node;
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
251
273
|
|
|
252
274
|
class DOMPos {
|
|
253
275
|
constructor(node, offset, precise = true) {
|
|
@@ -296,25 +318,30 @@ class ContentView {
|
|
|
296
318
|
sync(track) {
|
|
297
319
|
var _a;
|
|
298
320
|
if (this.dirty & 2 /* Node */) {
|
|
299
|
-
let parent = this.dom
|
|
321
|
+
let parent = this.dom;
|
|
322
|
+
let pos = parent.firstChild;
|
|
300
323
|
for (let child of this.children) {
|
|
301
324
|
if (child.dirty) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
child.reuseDOM(next);
|
|
325
|
+
if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
|
|
326
|
+
child.reuseDOM(pos);
|
|
305
327
|
child.sync(track);
|
|
306
328
|
child.dirty = 0 /* Not */;
|
|
307
329
|
}
|
|
308
|
-
if (track && track.node == parent && pos != child.dom)
|
|
330
|
+
if (track && !track.written && track.node == parent && pos != child.dom)
|
|
309
331
|
track.written = true;
|
|
310
|
-
|
|
311
|
-
|
|
332
|
+
if (child.dom.parentNode == parent) {
|
|
333
|
+
while (pos && pos != child.dom)
|
|
334
|
+
pos = rm(pos);
|
|
335
|
+
pos = child.dom.nextSibling;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
parent.insertBefore(child.dom, pos);
|
|
339
|
+
}
|
|
312
340
|
}
|
|
313
|
-
|
|
314
|
-
if (next && track && track.node == parent)
|
|
341
|
+
if (pos && track && track.node == parent)
|
|
315
342
|
track.written = true;
|
|
316
|
-
while (
|
|
317
|
-
|
|
343
|
+
while (pos)
|
|
344
|
+
pos = rm(pos);
|
|
318
345
|
}
|
|
319
346
|
else if (this.dirty & 1 /* Child */) {
|
|
320
347
|
for (let child of this.children)
|
|
@@ -445,6 +472,7 @@ class ContentView {
|
|
|
445
472
|
(this.breakAfter ? "#" : "");
|
|
446
473
|
}
|
|
447
474
|
static get(node) { return node.cmView; }
|
|
475
|
+
get isEditable() { return true; }
|
|
448
476
|
}
|
|
449
477
|
ContentView.prototype.breakAfter = 0;
|
|
450
478
|
// Remove a DOM node and return its next sibling.
|
|
@@ -453,14 +481,6 @@ function rm(dom) {
|
|
|
453
481
|
dom.parentNode.removeChild(dom);
|
|
454
482
|
return next;
|
|
455
483
|
}
|
|
456
|
-
function syncNodeInto(parent, after, dom) {
|
|
457
|
-
let next = after ? after.nextSibling : parent.firstChild;
|
|
458
|
-
if (dom.parentNode == parent)
|
|
459
|
-
while (next != dom)
|
|
460
|
-
next = rm(next);
|
|
461
|
-
else
|
|
462
|
-
parent.insertBefore(dom, next);
|
|
463
|
-
}
|
|
464
484
|
class ChildCursor {
|
|
465
485
|
constructor(children, pos, i) {
|
|
466
486
|
this.children = children;
|
|
@@ -492,15 +512,18 @@ const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
|
|
|
492
512
|
const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
|
|
493
513
|
const webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|
494
514
|
const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
|
|
515
|
+
const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|
495
516
|
var browser = {
|
|
496
|
-
mac: /*@__PURE__*//Mac/.test(nav.platform),
|
|
517
|
+
mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
|
|
518
|
+
windows: /*@__PURE__*//Win/.test(nav.platform),
|
|
519
|
+
linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
|
|
497
520
|
ie,
|
|
498
521
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|
499
522
|
gecko,
|
|
500
523
|
gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|
501
524
|
chrome: !!chrome,
|
|
502
525
|
chrome_version: chrome ? +chrome[1] : 0,
|
|
503
|
-
ios
|
|
526
|
+
ios,
|
|
504
527
|
android: /*@__PURE__*//Android\b/.test(nav.userAgent),
|
|
505
528
|
webkit,
|
|
506
529
|
safari,
|
|
@@ -722,6 +745,7 @@ class WidgetView extends InlineView {
|
|
|
722
745
|
}
|
|
723
746
|
return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
|
|
724
747
|
}
|
|
748
|
+
get isEditable() { return false; }
|
|
725
749
|
}
|
|
726
750
|
class CompositionView extends WidgetView {
|
|
727
751
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
@@ -733,6 +757,38 @@ class CompositionView extends WidgetView {
|
|
|
733
757
|
ignoreMutation() { return false; }
|
|
734
758
|
get overrideDOMText() { return null; }
|
|
735
759
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
760
|
+
get isEditable() { return true; }
|
|
761
|
+
}
|
|
762
|
+
// These are drawn around uneditable widgets to avoid a number of
|
|
763
|
+
// browser bugs that show up when the cursor is directly next to
|
|
764
|
+
// uneditable inline content.
|
|
765
|
+
class WidgetBufferView extends InlineView {
|
|
766
|
+
constructor(side) {
|
|
767
|
+
super();
|
|
768
|
+
this.side = side;
|
|
769
|
+
}
|
|
770
|
+
get length() { return 0; }
|
|
771
|
+
merge() { return false; }
|
|
772
|
+
become(other) {
|
|
773
|
+
return other instanceof WidgetBufferView && other.side == this.side;
|
|
774
|
+
}
|
|
775
|
+
slice() { return new WidgetBufferView(this.side); }
|
|
776
|
+
sync() {
|
|
777
|
+
if (!this.dom)
|
|
778
|
+
this.setDOM(document.createTextNode("\u200b"));
|
|
779
|
+
else if (this.dirty && this.dom.nodeValue != "\u200b")
|
|
780
|
+
this.dom.nodeValue = "\u200b";
|
|
781
|
+
}
|
|
782
|
+
getSide() { return this.side; }
|
|
783
|
+
domAtPos(pos) { return DOMPos.before(this.dom); }
|
|
784
|
+
domBoundsAround() { return null; }
|
|
785
|
+
coordsAt(pos) {
|
|
786
|
+
let rects = clientRectsFor(this.dom);
|
|
787
|
+
return rects[rects.length - 1];
|
|
788
|
+
}
|
|
789
|
+
get overrideDOMText() {
|
|
790
|
+
return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
|
|
791
|
+
}
|
|
736
792
|
}
|
|
737
793
|
function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
|
|
738
794
|
let cur = parent.childCursor();
|
|
@@ -1227,14 +1283,17 @@ class LineView extends ContentView {
|
|
|
1227
1283
|
}
|
|
1228
1284
|
// Only called when building a line view in ContentBuilder
|
|
1229
1285
|
addLineDeco(deco) {
|
|
1230
|
-
let attrs = deco.spec.attributes;
|
|
1286
|
+
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|
1231
1287
|
if (attrs)
|
|
1232
1288
|
this.attrs = combineAttrs(attrs, this.attrs || {});
|
|
1289
|
+
if (cls)
|
|
1290
|
+
this.attrs = combineAttrs(attrs, { class: cls });
|
|
1233
1291
|
}
|
|
1234
1292
|
domAtPos(pos) {
|
|
1235
1293
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1236
1294
|
}
|
|
1237
1295
|
sync(track) {
|
|
1296
|
+
var _a;
|
|
1238
1297
|
if (!this.dom || (this.dirty & 4 /* Attrs */)) {
|
|
1239
1298
|
this.setDOM(document.createElement("div"));
|
|
1240
1299
|
this.dom.className = "cm-line";
|
|
@@ -1250,7 +1309,7 @@ class LineView extends ContentView {
|
|
|
1250
1309
|
while (last && ContentView.get(last) instanceof MarkView)
|
|
1251
1310
|
last = last.lastChild;
|
|
1252
1311
|
if (!last ||
|
|
1253
|
-
last.nodeName != "BR" && ContentView.get(last)
|
|
1312
|
+
last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
|
|
1254
1313
|
(!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
|
|
1255
1314
|
let hack = document.createElement("BR");
|
|
1256
1315
|
hack.cmIgnore = true;
|
|
@@ -1349,6 +1408,9 @@ class ContentBuilder {
|
|
|
1349
1408
|
this.content = [];
|
|
1350
1409
|
this.curLine = null;
|
|
1351
1410
|
this.breakAtStart = 0;
|
|
1411
|
+
this.pendingBuffer = 0 /* No */;
|
|
1412
|
+
// Set to false directly after a widget that covers the position after it
|
|
1413
|
+
this.atCursorPos = true;
|
|
1352
1414
|
this.openStart = -1;
|
|
1353
1415
|
this.openEnd = -1;
|
|
1354
1416
|
this.text = "";
|
|
@@ -1363,23 +1425,31 @@ class ContentBuilder {
|
|
|
1363
1425
|
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore);
|
|
1364
1426
|
}
|
|
1365
1427
|
getLine() {
|
|
1366
|
-
if (!this.curLine)
|
|
1428
|
+
if (!this.curLine) {
|
|
1367
1429
|
this.content.push(this.curLine = new LineView);
|
|
1430
|
+
this.atCursorPos = true;
|
|
1431
|
+
}
|
|
1368
1432
|
return this.curLine;
|
|
1369
1433
|
}
|
|
1370
|
-
|
|
1434
|
+
flushBuffer(active) {
|
|
1435
|
+
if (this.pendingBuffer) {
|
|
1436
|
+
this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
|
|
1437
|
+
this.pendingBuffer = 0 /* No */;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
addBlockWidget(view) {
|
|
1441
|
+
this.flushBuffer([]);
|
|
1371
1442
|
this.curLine = null;
|
|
1372
1443
|
this.content.push(view);
|
|
1373
1444
|
}
|
|
1374
|
-
finish() {
|
|
1445
|
+
finish(openEnd) {
|
|
1446
|
+
if (!openEnd)
|
|
1447
|
+
this.flushBuffer([]);
|
|
1448
|
+
else
|
|
1449
|
+
this.pendingBuffer = 0 /* No */;
|
|
1375
1450
|
if (!this.posCovered())
|
|
1376
1451
|
this.getLine();
|
|
1377
1452
|
}
|
|
1378
|
-
wrapMarks(view, active) {
|
|
1379
|
-
for (let mark of active)
|
|
1380
|
-
view = new MarkView(mark, [view], view.length);
|
|
1381
|
-
return view;
|
|
1382
|
-
}
|
|
1383
1453
|
buildText(length, active, openStart) {
|
|
1384
1454
|
while (length > 0) {
|
|
1385
1455
|
if (this.textOff == this.text.length) {
|
|
@@ -1394,6 +1464,7 @@ class ContentBuilder {
|
|
|
1394
1464
|
this.content[this.content.length - 1].breakAfter = 1;
|
|
1395
1465
|
else
|
|
1396
1466
|
this.breakAtStart = 1;
|
|
1467
|
+
this.flushBuffer([]);
|
|
1397
1468
|
this.curLine = null;
|
|
1398
1469
|
length--;
|
|
1399
1470
|
continue;
|
|
@@ -1404,7 +1475,9 @@ class ContentBuilder {
|
|
|
1404
1475
|
}
|
|
1405
1476
|
}
|
|
1406
1477
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1407
|
-
this.
|
|
1478
|
+
this.flushBuffer(active);
|
|
1479
|
+
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1480
|
+
this.atCursorPos = true;
|
|
1408
1481
|
this.textOff += take;
|
|
1409
1482
|
length -= take;
|
|
1410
1483
|
openStart = 0;
|
|
@@ -1423,11 +1496,23 @@ class ContentBuilder {
|
|
|
1423
1496
|
let { type } = deco;
|
|
1424
1497
|
if (type == BlockType.WidgetAfter && !this.posCovered())
|
|
1425
1498
|
this.getLine();
|
|
1426
|
-
this.
|
|
1499
|
+
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1427
1500
|
}
|
|
1428
1501
|
else {
|
|
1429
|
-
let
|
|
1430
|
-
this.
|
|
1502
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1503
|
+
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1504
|
+
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1505
|
+
let line = this.getLine();
|
|
1506
|
+
if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
|
|
1507
|
+
this.pendingBuffer = 0 /* No */;
|
|
1508
|
+
this.flushBuffer(active);
|
|
1509
|
+
if (cursorBefore) {
|
|
1510
|
+
line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
|
|
1511
|
+
openStart = active.length + Math.max(0, openStart - active.length);
|
|
1512
|
+
}
|
|
1513
|
+
line.append(wrapMarks(view, active), openStart);
|
|
1514
|
+
this.atCursorPos = cursorAfter;
|
|
1515
|
+
this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
|
|
1431
1516
|
}
|
|
1432
1517
|
}
|
|
1433
1518
|
else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
|
|
@@ -1453,10 +1538,15 @@ class ContentBuilder {
|
|
|
1453
1538
|
builder.openEnd = RangeSet.spans(decorations, from, to, builder);
|
|
1454
1539
|
if (builder.openStart < 0)
|
|
1455
1540
|
builder.openStart = builder.openEnd;
|
|
1456
|
-
builder.finish();
|
|
1541
|
+
builder.finish(builder.openEnd);
|
|
1457
1542
|
return builder;
|
|
1458
1543
|
}
|
|
1459
1544
|
}
|
|
1545
|
+
function wrapMarks(view, active) {
|
|
1546
|
+
for (let mark of active)
|
|
1547
|
+
view = new MarkView(mark, [view], view.length);
|
|
1548
|
+
return view;
|
|
1549
|
+
}
|
|
1460
1550
|
class NullWidget extends WidgetType {
|
|
1461
1551
|
constructor(tag) {
|
|
1462
1552
|
super();
|
|
@@ -1477,6 +1567,9 @@ const inputHandler = /*@__PURE__*/Facet.define();
|
|
|
1477
1567
|
const scrollTo = /*@__PURE__*/StateEffect.define({
|
|
1478
1568
|
map: (range, changes) => range.map(changes)
|
|
1479
1569
|
});
|
|
1570
|
+
const centerOn = /*@__PURE__*/StateEffect.define({
|
|
1571
|
+
map: (range, changes) => range.map(changes)
|
|
1572
|
+
});
|
|
1480
1573
|
/**
|
|
1481
1574
|
Log or report an unhandled exception in client code. Should
|
|
1482
1575
|
probably only be used by extension code that allows client code to
|
|
@@ -1903,6 +1996,14 @@ class DocView extends ContentView {
|
|
|
1903
1996
|
return true;
|
|
1904
1997
|
}
|
|
1905
1998
|
}
|
|
1999
|
+
reset(sel) {
|
|
2000
|
+
if (this.dirty) {
|
|
2001
|
+
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2002
|
+
this.dirty = 0 /* Not */;
|
|
2003
|
+
}
|
|
2004
|
+
if (sel)
|
|
2005
|
+
this.updateSelection();
|
|
2006
|
+
}
|
|
1906
2007
|
// Used both by update and checkLayout do perform the actual DOM
|
|
1907
2008
|
// update
|
|
1908
2009
|
updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
|
|
@@ -1927,6 +2028,12 @@ class DocView extends ContentView {
|
|
|
1927
2028
|
this.updateSelection(forceSelection, pointerSel);
|
|
1928
2029
|
this.dom.style.height = "";
|
|
1929
2030
|
});
|
|
2031
|
+
let gaps = [];
|
|
2032
|
+
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
|
|
2033
|
+
for (let child of this.children)
|
|
2034
|
+
if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
|
|
2035
|
+
gaps.push(child.dom);
|
|
2036
|
+
observer.updateGaps(gaps);
|
|
1930
2037
|
}
|
|
1931
2038
|
updateChildren(changes, deco, oldLength) {
|
|
1932
2039
|
let cursor = this.childCursor(oldLength);
|
|
@@ -2026,6 +2133,14 @@ class DocView extends ContentView {
|
|
|
2026
2133
|
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2027
2134
|
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2028
2135
|
this.view.observer.ignore(() => {
|
|
2136
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2137
|
+
// inside an uneditable node, and not bring it back when we
|
|
2138
|
+
// move the cursor to its proper position. This tries to
|
|
2139
|
+
// restore the keyboard by cycling focus.
|
|
2140
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
|
|
2141
|
+
this.dom.blur();
|
|
2142
|
+
this.dom.focus({ preventScroll: true });
|
|
2143
|
+
}
|
|
2029
2144
|
let rawSel = getSelection(this.root);
|
|
2030
2145
|
if (main.empty) {
|
|
2031
2146
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
@@ -2199,7 +2314,7 @@ class DocView extends ContentView {
|
|
|
2199
2314
|
this.view.viewState.lineGapDeco
|
|
2200
2315
|
];
|
|
2201
2316
|
}
|
|
2202
|
-
|
|
2317
|
+
scrollIntoView({ range, center }) {
|
|
2203
2318
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
2204
2319
|
if (!rect)
|
|
2205
2320
|
return;
|
|
@@ -2219,10 +2334,10 @@ class DocView extends ContentView {
|
|
|
2219
2334
|
if (bottom != null)
|
|
2220
2335
|
mBottom = Math.max(mBottom, bottom);
|
|
2221
2336
|
}
|
|
2222
|
-
scrollRectIntoView(this.
|
|
2337
|
+
scrollRectIntoView(this.view.scrollDOM, {
|
|
2223
2338
|
left: rect.left - mLeft, top: rect.top - mTop,
|
|
2224
2339
|
right: rect.right + mRight, bottom: rect.bottom + mBottom
|
|
2225
|
-
}, range.head < range.anchor ? -1 : 1);
|
|
2340
|
+
}, range.head < range.anchor ? -1 : 1, center);
|
|
2226
2341
|
}
|
|
2227
2342
|
}
|
|
2228
2343
|
function betweenUneditable(pos) {
|
|
@@ -2333,6 +2448,14 @@ function findChangedDeco(a, b, diff) {
|
|
|
2333
2448
|
RangeSet.compare(a, b, diff, comp);
|
|
2334
2449
|
return comp.changes;
|
|
2335
2450
|
}
|
|
2451
|
+
function inUneditable(node, inside) {
|
|
2452
|
+
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|
2453
|
+
if (cur.nodeType == 1 && cur.contentEditable == 'false') {
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2336
2459
|
|
|
2337
2460
|
/**
|
|
2338
2461
|
Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|
@@ -2778,6 +2901,7 @@ function domPosInText(node, x, y) {
|
|
|
2778
2901
|
return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
|
|
2779
2902
|
}
|
|
2780
2903
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2904
|
+
var _a;
|
|
2781
2905
|
let content = view.contentDOM.getBoundingClientRect(), block;
|
|
2782
2906
|
let halfLine = view.defaultLineHeight / 2;
|
|
2783
2907
|
for (let bounced = false;;) {
|
|
@@ -2808,7 +2932,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2808
2932
|
// There's visible editor content under the point, so we can try
|
|
2809
2933
|
// using caret(Position|Range)FromPoint as a shortcut
|
|
2810
2934
|
let node, offset = -1;
|
|
2811
|
-
if (element && view.contentDOM.contains(element) &&
|
|
2935
|
+
if (element && view.contentDOM.contains(element) && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
|
|
2812
2936
|
if (doc.caretPositionFromPoint) {
|
|
2813
2937
|
let pos = doc.caretPositionFromPoint(x, y);
|
|
2814
2938
|
if (pos)
|
|
@@ -2949,7 +3073,23 @@ class InputState {
|
|
|
2949
3073
|
constructor(view) {
|
|
2950
3074
|
this.lastKeyCode = 0;
|
|
2951
3075
|
this.lastKeyTime = 0;
|
|
2952
|
-
|
|
3076
|
+
// On iOS, some keys need to have their default behavior happen
|
|
3077
|
+
// (after which we retroactively handle them and reset the DOM) to
|
|
3078
|
+
// avoid messing up the virtual keyboard state.
|
|
3079
|
+
//
|
|
3080
|
+
// On Chrome Android, backspace near widgets is just completely
|
|
3081
|
+
// broken, and there are no key events, so we need to handle the
|
|
3082
|
+
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3083
|
+
// events, and interrupting it before it is done just makes
|
|
3084
|
+
// subsequent events even more broken, so again, we hold off doing
|
|
3085
|
+
// anything until the browser is finished with whatever it is trying
|
|
3086
|
+
// to do.
|
|
3087
|
+
//
|
|
3088
|
+
// setPendingKey sets this, causing the DOM observer to pause for a
|
|
3089
|
+
// bit, and setting an animation frame (which seems the most
|
|
3090
|
+
// reliable way to detect 'browser is done flailing') to fire a fake
|
|
3091
|
+
// key event and re-sync the view again.
|
|
3092
|
+
this.pendingKey = undefined;
|
|
2953
3093
|
this.lastSelectionOrigin = null;
|
|
2954
3094
|
this.lastSelectionTime = 0;
|
|
2955
3095
|
this.lastEscPress = 0;
|
|
@@ -3058,20 +3198,27 @@ class InputState {
|
|
|
3058
3198
|
// state. So we let it go through, and then, in
|
|
3059
3199
|
// applyDOMChange, notify key handlers of it and reset to
|
|
3060
3200
|
// the state they produce.
|
|
3061
|
-
|
|
3201
|
+
let pending;
|
|
3202
|
+
if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
|
|
3062
3203
|
!(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
|
|
3063
|
-
this.
|
|
3064
|
-
setTimeout(() => this.flushIOSKey(view), 250);
|
|
3204
|
+
this.setPendingKey(view, pending);
|
|
3065
3205
|
return true;
|
|
3066
3206
|
}
|
|
3067
3207
|
return false;
|
|
3068
3208
|
}
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3209
|
+
setPendingKey(view, pending) {
|
|
3210
|
+
this.pendingKey = pending;
|
|
3211
|
+
requestAnimationFrame(() => {
|
|
3212
|
+
if (!this.pendingKey)
|
|
3213
|
+
return false;
|
|
3214
|
+
let key = this.pendingKey;
|
|
3215
|
+
this.pendingKey = undefined;
|
|
3216
|
+
view.observer.processRecords();
|
|
3217
|
+
let startState = view.state;
|
|
3218
|
+
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3219
|
+
if (view.state == startState)
|
|
3220
|
+
view.docView.reset(true);
|
|
3221
|
+
});
|
|
3075
3222
|
}
|
|
3076
3223
|
ignoreDuringComposition(event) {
|
|
3077
3224
|
if (!/^key/.test(event.type))
|
|
@@ -3118,6 +3265,11 @@ class InputState {
|
|
|
3118
3265
|
this.mouseSelection.destroy();
|
|
3119
3266
|
}
|
|
3120
3267
|
}
|
|
3268
|
+
const PendingKeys = [
|
|
3269
|
+
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|
3270
|
+
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|
3271
|
+
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|
3272
|
+
];
|
|
3121
3273
|
// Key codes for modifier keys
|
|
3122
3274
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3123
3275
|
class MouseSelection {
|
|
@@ -3563,6 +3715,33 @@ handlers.compositionend = view => {
|
|
|
3563
3715
|
handlers.contextmenu = view => {
|
|
3564
3716
|
view.inputState.lastContextMenu = Date.now();
|
|
3565
3717
|
};
|
|
3718
|
+
handlers.beforeinput = (view, event) => {
|
|
3719
|
+
var _a;
|
|
3720
|
+
// Because Chrome Android doesn't fire useful key events, use
|
|
3721
|
+
// beforeinput to detect backspace (and possibly enter and delete,
|
|
3722
|
+
// but those usually don't even seem to fire beforeinput events at
|
|
3723
|
+
// the moment) and fake a key event for it.
|
|
3724
|
+
//
|
|
3725
|
+
// (preventDefault on beforeinput, though supported in the spec,
|
|
3726
|
+
// seems to do nothing at all on Chrome).
|
|
3727
|
+
let pending;
|
|
3728
|
+
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3729
|
+
view.inputState.setPendingKey(view, pending);
|
|
3730
|
+
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3731
|
+
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3732
|
+
setTimeout(() => {
|
|
3733
|
+
var _a;
|
|
3734
|
+
// Backspacing near uneditable nodes on Chrome Android sometimes
|
|
3735
|
+
// closes the virtual keyboard. This tries to crudely detect
|
|
3736
|
+
// that and refocus to get it back.
|
|
3737
|
+
if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|
3738
|
+
view.contentDOM.blur();
|
|
3739
|
+
view.focus();
|
|
3740
|
+
}
|
|
3741
|
+
}, 100);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
};
|
|
3566
3745
|
|
|
3567
3746
|
const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line"];
|
|
3568
3747
|
class HeightOracle {
|
|
@@ -4277,6 +4456,15 @@ class LineGapWidget extends WidgetType {
|
|
|
4277
4456
|
}
|
|
4278
4457
|
get estimatedHeight() { return this.vertical ? this.size : -1; }
|
|
4279
4458
|
}
|
|
4459
|
+
class ScrollTarget {
|
|
4460
|
+
constructor(range, center = false) {
|
|
4461
|
+
this.range = range;
|
|
4462
|
+
this.center = center;
|
|
4463
|
+
}
|
|
4464
|
+
map(changes) {
|
|
4465
|
+
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4280
4468
|
class ViewState {
|
|
4281
4469
|
constructor(state) {
|
|
4282
4470
|
this.state = state;
|
|
@@ -4289,7 +4477,7 @@ class ViewState {
|
|
|
4289
4477
|
this.heightOracle = new HeightOracle;
|
|
4290
4478
|
// See VP.MaxDOMHeight
|
|
4291
4479
|
this.scaler = IdScaler;
|
|
4292
|
-
this.
|
|
4480
|
+
this.scrollTarget = null;
|
|
4293
4481
|
// Briefly set to true when printing, to disable viewport limiting
|
|
4294
4482
|
this.printing = false;
|
|
4295
4483
|
this.visibleRanges = [];
|
|
@@ -4322,7 +4510,7 @@ class ViewState {
|
|
|
4322
4510
|
this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
|
|
4323
4511
|
new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
|
|
4324
4512
|
}
|
|
4325
|
-
update(update,
|
|
4513
|
+
update(update, scrollTarget = null) {
|
|
4326
4514
|
let prev = this.state;
|
|
4327
4515
|
this.state = update.state;
|
|
4328
4516
|
let newDeco = this.state.facet(decorations);
|
|
@@ -4333,27 +4521,33 @@ class ViewState {
|
|
|
4333
4521
|
if (this.heightMap.height != prevHeight)
|
|
4334
4522
|
update.flags |= 2 /* Height */;
|
|
4335
4523
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|
4336
|
-
if (
|
|
4337
|
-
|
|
4524
|
+
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
|
|
4525
|
+
!this.viewportIsAppropriate(viewport))
|
|
4526
|
+
viewport = this.getViewport(0, scrollTarget);
|
|
4338
4527
|
this.viewport = viewport;
|
|
4339
4528
|
this.updateForViewport();
|
|
4340
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4529
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4341
4530
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4342
4531
|
update.flags |= this.computeVisibleRanges();
|
|
4343
|
-
if (
|
|
4344
|
-
this.
|
|
4532
|
+
if (scrollTarget)
|
|
4533
|
+
this.scrollTarget = scrollTarget;
|
|
4345
4534
|
if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
|
|
4346
4535
|
update.state.selection.main.empty && update.state.selection.main.assoc)
|
|
4347
4536
|
this.mustEnforceCursorAssoc = true;
|
|
4348
4537
|
}
|
|
4349
4538
|
measure(docView, repeated) {
|
|
4350
4539
|
let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
|
|
4540
|
+
let result = 0;
|
|
4351
4541
|
if (!repeated) {
|
|
4352
4542
|
// Vertical padding
|
|
4353
4543
|
let style = window.getComputedStyle(dom);
|
|
4354
4544
|
whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
|
|
4355
|
-
|
|
4356
|
-
this.
|
|
4545
|
+
let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
|
|
4546
|
+
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|
4547
|
+
result |= 8 /* Geometry */;
|
|
4548
|
+
this.paddingTop = paddingTop;
|
|
4549
|
+
this.paddingBottom = paddingBottom;
|
|
4550
|
+
}
|
|
4357
4551
|
}
|
|
4358
4552
|
// Pixel viewport
|
|
4359
4553
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
@@ -4363,7 +4557,7 @@ class ViewState {
|
|
|
4363
4557
|
if (!this.inView)
|
|
4364
4558
|
return 0;
|
|
4365
4559
|
let lineHeights = docView.measureVisibleLineHeights();
|
|
4366
|
-
let refresh = false, bias = 0,
|
|
4560
|
+
let refresh = false, bias = 0, oracle = this.heightOracle;
|
|
4367
4561
|
if (!repeated) {
|
|
4368
4562
|
let contentWidth = docView.dom.clientWidth;
|
|
4369
4563
|
if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
|
|
@@ -4389,10 +4583,10 @@ class ViewState {
|
|
|
4389
4583
|
if (oracle.heightChanged)
|
|
4390
4584
|
result |= 2 /* Height */;
|
|
4391
4585
|
if (!this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4392
|
-
this.
|
|
4393
|
-
this.viewport = this.getViewport(bias, this.
|
|
4586
|
+
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
|
|
4587
|
+
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4394
4588
|
this.updateForViewport();
|
|
4395
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4589
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4396
4590
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4397
4591
|
result |= this.computeVisibleRanges();
|
|
4398
4592
|
if (this.mustEnforceCursorAssoc) {
|
|
@@ -4407,22 +4601,25 @@ class ViewState {
|
|
|
4407
4601
|
}
|
|
4408
4602
|
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
|
|
4409
4603
|
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
|
|
4410
|
-
getViewport(bias,
|
|
4604
|
+
getViewport(bias, scrollTarget) {
|
|
4411
4605
|
// This will divide VP.Margin between the top and the
|
|
4412
4606
|
// bottom, depending on the bias (the change in viewport position
|
|
4413
4607
|
// since the last update). It'll hold a number between 0 and 1
|
|
4414
4608
|
let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
|
|
4415
4609
|
let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
|
|
4416
4610
|
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);
|
|
4417
|
-
// If
|
|
4418
|
-
if (
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4611
|
+
// If scrollTarget is given, make sure the viewport includes that position
|
|
4612
|
+
if (scrollTarget) {
|
|
4613
|
+
let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
|
|
4614
|
+
if (head < viewport.from || head > viewport.to) {
|
|
4615
|
+
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4616
|
+
if (scrollTarget.center)
|
|
4617
|
+
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
4618
|
+
else if (head < viewport.from)
|
|
4619
|
+
topPos = block.top;
|
|
4620
|
+
else
|
|
4621
|
+
topPos = block.bottom - viewHeight;
|
|
4622
|
+
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);
|
|
4426
4623
|
}
|
|
4427
4624
|
}
|
|
4428
4625
|
return viewport;
|
|
@@ -4464,52 +4661,50 @@ class ViewState {
|
|
|
4464
4661
|
if (this.heightOracle.direction != Direction.LTR)
|
|
4465
4662
|
return gaps;
|
|
4466
4663
|
this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
|
|
4467
|
-
if (line.length <
|
|
4664
|
+
if (line.length < 4000 /* DoubleMargin */)
|
|
4468
4665
|
return;
|
|
4469
4666
|
let structure = lineStructure(line.from, line.to, this.state);
|
|
4470
|
-
if (structure.total <
|
|
4667
|
+
if (structure.total < 4000 /* DoubleMargin */)
|
|
4471
4668
|
return;
|
|
4472
4669
|
let viewFrom, viewTo;
|
|
4473
4670
|
if (this.heightOracle.lineWrapping) {
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
|
|
4478
|
-
if (line.to != this.viewport.to)
|
|
4479
|
-
viewTo = line.to;
|
|
4480
|
-
else
|
|
4481
|
-
viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
|
|
4671
|
+
let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
4672
|
+
viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
|
|
4673
|
+
viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
|
|
4482
4674
|
}
|
|
4483
4675
|
else {
|
|
4484
4676
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
4485
|
-
|
|
4486
|
-
|
|
4677
|
+
let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
|
|
4678
|
+
viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
|
|
4679
|
+
viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
|
|
4487
4680
|
}
|
|
4681
|
+
let outside = [];
|
|
4682
|
+
if (viewFrom > line.from)
|
|
4683
|
+
outside.push({ from: line.from, to: viewFrom });
|
|
4684
|
+
if (viewTo < line.to)
|
|
4685
|
+
outside.push({ from: viewTo, to: line.to });
|
|
4488
4686
|
let sel = this.state.selection.main;
|
|
4489
|
-
// Make sure the
|
|
4490
|
-
if (sel.from
|
|
4491
|
-
|
|
4492
|
-
if (sel.
|
|
4493
|
-
|
|
4494
|
-
let
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
gap.from < gapFrom + 5000 /* HalfMargin */) ||
|
|
4501
|
-
new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
|
|
4687
|
+
// Make sure the gaps don't cover a selection end
|
|
4688
|
+
if (sel.from >= line.from && sel.from <= line.to)
|
|
4689
|
+
cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
|
|
4690
|
+
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
4691
|
+
cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
|
|
4692
|
+
for (let { from, to } of outside)
|
|
4693
|
+
if (to - from > 1000 /* HalfMargin */) {
|
|
4694
|
+
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
4695
|
+
Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
|
|
4696
|
+
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
4697
|
+
}
|
|
4502
4698
|
});
|
|
4503
4699
|
return gaps;
|
|
4504
4700
|
}
|
|
4505
|
-
gapSize(line,
|
|
4701
|
+
gapSize(line, from, to, structure) {
|
|
4702
|
+
let fraction = findFraction(structure, to) - findFraction(structure, from);
|
|
4506
4703
|
if (this.heightOracle.lineWrapping) {
|
|
4507
|
-
|
|
4508
|
-
return start ? height : line.height - height;
|
|
4704
|
+
return line.height * fraction;
|
|
4509
4705
|
}
|
|
4510
4706
|
else {
|
|
4511
|
-
|
|
4512
|
-
return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
|
|
4707
|
+
return structure.total * this.heightOracle.charWidth * fraction;
|
|
4513
4708
|
}
|
|
4514
4709
|
}
|
|
4515
4710
|
updateLineGaps(gaps) {
|
|
@@ -4603,6 +4798,20 @@ function findFraction(structure, pos) {
|
|
|
4603
4798
|
}
|
|
4604
4799
|
return counted / structure.total;
|
|
4605
4800
|
}
|
|
4801
|
+
function cutRange(ranges, from, to) {
|
|
4802
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
4803
|
+
let r = ranges[i];
|
|
4804
|
+
if (r.from < to && r.to > from) {
|
|
4805
|
+
let pieces = [];
|
|
4806
|
+
if (r.from < from)
|
|
4807
|
+
pieces.push({ from: r.from, to: from });
|
|
4808
|
+
if (r.to > to)
|
|
4809
|
+
pieces.push({ from: to, to: r.to });
|
|
4810
|
+
ranges.splice(i, 1, ...pieces);
|
|
4811
|
+
i += pieces.length - 1;
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4606
4815
|
function find(array, f) {
|
|
4607
4816
|
for (let val of array)
|
|
4608
4817
|
if (f(val))
|
|
@@ -4685,7 +4894,7 @@ function buildTheme(main, spec, scopes) {
|
|
|
4685
4894
|
});
|
|
4686
4895
|
}
|
|
4687
4896
|
const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
4688
|
-
"
|
|
4897
|
+
"&.cm-editor": {
|
|
4689
4898
|
position: "relative !important",
|
|
4690
4899
|
boxSizing: "border-box",
|
|
4691
4900
|
"&.cm-focused": {
|
|
@@ -4852,6 +5061,8 @@ class DOMObserver {
|
|
|
4852
5061
|
this.scrollTargets = [];
|
|
4853
5062
|
this.intersection = null;
|
|
4854
5063
|
this.intersecting = false;
|
|
5064
|
+
this.gapIntersection = null;
|
|
5065
|
+
this.gaps = [];
|
|
4855
5066
|
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
4856
5067
|
this._selectionRange = null;
|
|
4857
5068
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
@@ -4892,13 +5103,17 @@ class DOMObserver {
|
|
|
4892
5103
|
this.intersection = new IntersectionObserver(entries => {
|
|
4893
5104
|
if (this.parentCheck < 0)
|
|
4894
5105
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
4895
|
-
if (entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5106
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
4896
5107
|
this.intersecting = !this.intersecting;
|
|
4897
5108
|
if (this.intersecting != this.view.inView)
|
|
4898
5109
|
this.onScrollChanged(document.createEvent("Event"));
|
|
4899
5110
|
}
|
|
4900
5111
|
}, {});
|
|
4901
5112
|
this.intersection.observe(this.dom);
|
|
5113
|
+
this.gapIntersection = new IntersectionObserver(entries => {
|
|
5114
|
+
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|
5115
|
+
this.onScrollChanged(document.createEvent("Event"));
|
|
5116
|
+
}, {});
|
|
4902
5117
|
}
|
|
4903
5118
|
this.listenForScroll();
|
|
4904
5119
|
}
|
|
@@ -4907,6 +5122,14 @@ class DOMObserver {
|
|
|
4907
5122
|
this.flush();
|
|
4908
5123
|
this.onScrollChanged(e);
|
|
4909
5124
|
}
|
|
5125
|
+
updateGaps(gaps) {
|
|
5126
|
+
if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
|
|
5127
|
+
this.gapIntersection.disconnect();
|
|
5128
|
+
for (let gap of gaps)
|
|
5129
|
+
this.gapIntersection.observe(gap);
|
|
5130
|
+
this.gaps = gaps;
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
4910
5133
|
onSelectionChange(event) {
|
|
4911
5134
|
if (this.lastFlush < Date.now() - 50)
|
|
4912
5135
|
this._selectionRange = null;
|
|
@@ -5022,20 +5245,12 @@ class DOMObserver {
|
|
|
5022
5245
|
this.flush();
|
|
5023
5246
|
}
|
|
5024
5247
|
}
|
|
5025
|
-
|
|
5026
|
-
flush() {
|
|
5027
|
-
if (this.delayedFlush >= 0)
|
|
5028
|
-
return;
|
|
5029
|
-
this.lastFlush = Date.now();
|
|
5248
|
+
processRecords() {
|
|
5030
5249
|
let records = this.queue;
|
|
5031
5250
|
for (let mut of this.observer.takeRecords())
|
|
5032
5251
|
records.push(mut);
|
|
5033
5252
|
if (records.length)
|
|
5034
5253
|
this.queue = [];
|
|
5035
|
-
let selection = this.selectionRange;
|
|
5036
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5037
|
-
if (records.length == 0 && !newSel)
|
|
5038
|
-
return;
|
|
5039
5254
|
let from = -1, to = -1, typeOver = false;
|
|
5040
5255
|
for (let record of records) {
|
|
5041
5256
|
let range = this.readMutation(record);
|
|
@@ -5051,17 +5266,26 @@ class DOMObserver {
|
|
|
5051
5266
|
to = Math.max(range.to, to);
|
|
5052
5267
|
}
|
|
5053
5268
|
}
|
|
5269
|
+
return { from, to, typeOver };
|
|
5270
|
+
}
|
|
5271
|
+
// Apply pending changes, if any
|
|
5272
|
+
flush() {
|
|
5273
|
+
// Completely hold off flushing when pending keys are set—the code
|
|
5274
|
+
// managing those will make sure processRecords is called and the
|
|
5275
|
+
// view is resynchronized after
|
|
5276
|
+
if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
|
|
5277
|
+
return;
|
|
5278
|
+
this.lastFlush = Date.now();
|
|
5279
|
+
let { from, to, typeOver } = this.processRecords();
|
|
5280
|
+
let selection = this.selectionRange;
|
|
5281
|
+
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5282
|
+
if (from < 0 && !newSel)
|
|
5283
|
+
return;
|
|
5054
5284
|
let startState = this.view.state;
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
if (this.view.state == startState)
|
|
5058
|
-
|
|
5059
|
-
this.ignore(() => this.view.docView.sync());
|
|
5060
|
-
this.view.docView.dirty = 0 /* Not */;
|
|
5061
|
-
}
|
|
5062
|
-
if (newSel)
|
|
5063
|
-
this.view.docView.updateSelection();
|
|
5064
|
-
}
|
|
5285
|
+
this.onChange(from, to, typeOver);
|
|
5286
|
+
// The view wasn't updated
|
|
5287
|
+
if (this.view.state == startState)
|
|
5288
|
+
this.view.docView.reset(newSel);
|
|
5065
5289
|
this.clearSelection();
|
|
5066
5290
|
}
|
|
5067
5291
|
readMutation(rec) {
|
|
@@ -5088,6 +5312,8 @@ class DOMObserver {
|
|
|
5088
5312
|
this.stop();
|
|
5089
5313
|
if (this.intersection)
|
|
5090
5314
|
this.intersection.disconnect();
|
|
5315
|
+
if (this.gapIntersection)
|
|
5316
|
+
this.gapIntersection.disconnect();
|
|
5091
5317
|
for (let dom of this.scrollTargets)
|
|
5092
5318
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5093
5319
|
window.removeEventListener("scroll", this.onScroll);
|
|
@@ -5196,9 +5422,9 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5196
5422
|
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5197
5423
|
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5198
5424
|
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5199
|
-
dispatchKey(view.contentDOM, "Delete", 46)))
|
|
5200
|
-
browser.ios && view.inputState.flushIOSKey(view))
|
|
5425
|
+
dispatchKey(view.contentDOM, "Delete", 46)))) {
|
|
5201
5426
|
return;
|
|
5427
|
+
}
|
|
5202
5428
|
let text = change.insert.toString();
|
|
5203
5429
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5204
5430
|
return;
|
|
@@ -5419,7 +5645,7 @@ class EditorView {
|
|
|
5419
5645
|
this.dom.appendChild(this.scrollDOM);
|
|
5420
5646
|
this._dispatch = config.dispatch || ((tr) => this.update([tr]));
|
|
5421
5647
|
this.dispatch = this.dispatch.bind(this);
|
|
5422
|
-
this.root = (config.root || document);
|
|
5648
|
+
this.root = (config.root || getRoot(config.parent) || document);
|
|
5423
5649
|
this.viewState = new ViewState(config.state || EditorState.create());
|
|
5424
5650
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
|
|
5425
5651
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
@@ -5500,21 +5726,24 @@ class EditorView {
|
|
|
5500
5726
|
if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
|
|
5501
5727
|
return this.setState(state);
|
|
5502
5728
|
update = new ViewUpdate(this, state, transactions);
|
|
5503
|
-
let
|
|
5729
|
+
let scrollTarget = null;
|
|
5504
5730
|
try {
|
|
5505
5731
|
this.updateState = 2 /* Updating */;
|
|
5506
5732
|
for (let tr of transactions) {
|
|
5507
|
-
if (
|
|
5508
|
-
|
|
5733
|
+
if (scrollTarget)
|
|
5734
|
+
scrollTarget = scrollTarget.map(tr.changes);
|
|
5509
5735
|
if (tr.scrollIntoView) {
|
|
5510
5736
|
let { main } = tr.state.selection;
|
|
5511
|
-
|
|
5737
|
+
scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
|
|
5512
5738
|
}
|
|
5513
|
-
for (let e of tr.effects)
|
|
5739
|
+
for (let e of tr.effects) {
|
|
5514
5740
|
if (e.is(scrollTo))
|
|
5515
|
-
|
|
5741
|
+
scrollTarget = new ScrollTarget(e.value);
|
|
5742
|
+
else if (e.is(centerOn))
|
|
5743
|
+
scrollTarget = new ScrollTarget(e.value, true);
|
|
5744
|
+
}
|
|
5516
5745
|
}
|
|
5517
|
-
this.viewState.update(update,
|
|
5746
|
+
this.viewState.update(update, scrollTarget);
|
|
5518
5747
|
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
|
|
5519
5748
|
if (!update.empty) {
|
|
5520
5749
|
this.updatePlugins(update);
|
|
@@ -5529,7 +5758,7 @@ class EditorView {
|
|
|
5529
5758
|
finally {
|
|
5530
5759
|
this.updateState = 0 /* Idle */;
|
|
5531
5760
|
}
|
|
5532
|
-
if (redrawn ||
|
|
5761
|
+
if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
|
|
5533
5762
|
this.requestMeasure();
|
|
5534
5763
|
if (!update.empty)
|
|
5535
5764
|
for (let listener of this.state.facet(updateListener))
|
|
@@ -5611,14 +5840,16 @@ class EditorView {
|
|
|
5611
5840
|
this.updateState = 1 /* Measuring */;
|
|
5612
5841
|
let oldViewport = this.viewport;
|
|
5613
5842
|
let changed = this.viewState.measure(this.docView, i > 0);
|
|
5614
|
-
|
|
5615
|
-
if (!changed && !measuring.length && this.viewState.scrollTo == null)
|
|
5843
|
+
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5616
5844
|
break;
|
|
5617
|
-
this.measureRequests = [];
|
|
5618
5845
|
if (i > 5) {
|
|
5619
5846
|
console.warn("Viewport failed to stabilize");
|
|
5620
5847
|
break;
|
|
5621
5848
|
}
|
|
5849
|
+
let measuring = [];
|
|
5850
|
+
// Only run measure requests in this cycle when the viewport didn't change
|
|
5851
|
+
if (!(changed & 4 /* Viewport */))
|
|
5852
|
+
[this.measureRequests, measuring] = [measuring, this.measureRequests];
|
|
5622
5853
|
let measured = measuring.map(m => {
|
|
5623
5854
|
try {
|
|
5624
5855
|
return m.read(this);
|
|
@@ -5651,9 +5882,9 @@ class EditorView {
|
|
|
5651
5882
|
logException(this.state, e);
|
|
5652
5883
|
}
|
|
5653
5884
|
}
|
|
5654
|
-
if (this.viewState.
|
|
5655
|
-
this.docView.
|
|
5656
|
-
this.viewState.
|
|
5885
|
+
if (this.viewState.scrollTarget) {
|
|
5886
|
+
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5887
|
+
this.viewState.scrollTarget = null;
|
|
5657
5888
|
}
|
|
5658
5889
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5659
5890
|
break;
|
|
@@ -5685,6 +5916,7 @@ class EditorView {
|
|
|
5685
5916
|
spellcheck: "false",
|
|
5686
5917
|
autocorrect: "off",
|
|
5687
5918
|
autocapitalize: "off",
|
|
5919
|
+
translate: "no",
|
|
5688
5920
|
contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
|
|
5689
5921
|
class: "cm-content",
|
|
5690
5922
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|
@@ -5875,12 +6107,9 @@ class EditorView {
|
|
|
5875
6107
|
moveVertically(start, forward, distance) {
|
|
5876
6108
|
return skipAtoms(this, start, moveVertically(this, start, forward, distance));
|
|
5877
6109
|
}
|
|
5878
|
-
|
|
5879
|
-
Scroll the given document position into view.
|
|
5880
|
-
*/
|
|
6110
|
+
// FIXME remove on next major version
|
|
5881
6111
|
scrollPosIntoView(pos) {
|
|
5882
|
-
this.
|
|
5883
|
-
this.requestMeasure();
|
|
6112
|
+
this.dispatch({ effects: scrollTo.of(EditorSelection.cursor(pos)) });
|
|
5884
6113
|
}
|
|
5885
6114
|
/**
|
|
5886
6115
|
Find the DOM parent node and offset (child offset if `node` is
|
|
@@ -6047,7 +6276,7 @@ class EditorView {
|
|
|
6047
6276
|
target editors with a dark or light theme.
|
|
6048
6277
|
*/
|
|
6049
6278
|
static baseTheme(spec) {
|
|
6050
|
-
return Prec.
|
|
6279
|
+
return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
|
|
6051
6280
|
}
|
|
6052
6281
|
}
|
|
6053
6282
|
/**
|
|
@@ -6056,6 +6285,11 @@ transaction to make it scroll the given range into view.
|
|
|
6056
6285
|
*/
|
|
6057
6286
|
EditorView.scrollTo = scrollTo;
|
|
6058
6287
|
/**
|
|
6288
|
+
Effect that makes the editor scroll the given range to the
|
|
6289
|
+
center of the visible view.
|
|
6290
|
+
*/
|
|
6291
|
+
EditorView.centerOn = centerOn;
|
|
6292
|
+
/**
|
|
6059
6293
|
Facet to add a [style
|
|
6060
6294
|
module](https://github.com/marijnh/style-mod#documentation) to
|
|
6061
6295
|
an editor view. The view will ensure that the module is
|
|
@@ -6185,11 +6419,7 @@ class CachedOrder {
|
|
|
6185
6419
|
}
|
|
6186
6420
|
}
|
|
6187
6421
|
|
|
6188
|
-
const currentPlatform =
|
|
6189
|
-
: /*@__PURE__*//Mac/.test(navigator.platform) ? "mac"
|
|
6190
|
-
: /*@__PURE__*//Win/.test(navigator.platform) ? "win"
|
|
6191
|
-
: /*@__PURE__*//Linux|X11/.test(navigator.platform) ? "linux"
|
|
6192
|
-
: "key";
|
|
6422
|
+
const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|
6193
6423
|
function normalizeKeyName(name, platform) {
|
|
6194
6424
|
const parts = name.split(/-(?!$)/);
|
|
6195
6425
|
let result = parts[parts.length - 1];
|
|
@@ -6488,7 +6718,7 @@ const themeSpec = {
|
|
|
6488
6718
|
};
|
|
6489
6719
|
if (CanHidePrimary)
|
|
6490
6720
|
themeSpec[".cm-line"].caretColor = "transparent !important";
|
|
6491
|
-
const hideNativeSelection = /*@__PURE__*/Prec.
|
|
6721
|
+
const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
|
|
6492
6722
|
function getBase(view) {
|
|
6493
6723
|
let rect = view.scrollDOM.getBoundingClientRect();
|
|
6494
6724
|
let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
|
|
@@ -6695,7 +6925,7 @@ class MatchDecorator {
|
|
|
6695
6925
|
}
|
|
6696
6926
|
|
|
6697
6927
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
6698
|
-
const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6928
|
+
const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
6699
6929
|
const Names = {
|
|
6700
6930
|
0: "null",
|
|
6701
6931
|
7: "bell",
|
|
@@ -6710,6 +6940,8 @@ const Names = {
|
|
|
6710
6940
|
8206: "left-to-right mark",
|
|
6711
6941
|
8207: "right-to-left mark",
|
|
6712
6942
|
8232: "line separator",
|
|
6943
|
+
8237: "left-to-right override",
|
|
6944
|
+
8238: "right-to-left override",
|
|
6713
6945
|
8233: "paragraph separator",
|
|
6714
6946
|
65279: "zero width no-break space",
|
|
6715
6947
|
65532: "object replacement"
|
|
@@ -6876,7 +7108,7 @@ DOM class.
|
|
|
6876
7108
|
function highlightActiveLine() {
|
|
6877
7109
|
return activeLineHighlighter;
|
|
6878
7110
|
}
|
|
6879
|
-
const lineDeco = /*@__PURE__*/Decoration.line({
|
|
7111
|
+
const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
|
|
6880
7112
|
const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
6881
7113
|
constructor(view) {
|
|
6882
7114
|
this.decorations = this.getDeco(view);
|