@codemirror/view 0.19.29 → 0.19.33
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 +284 -125
- package/dist/index.d.ts +53 -2
- package/dist/index.js +285 -127
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -101,8 +101,7 @@ function windowRect(win) {
|
|
|
101
101
|
return { left: 0, right: win.innerWidth,
|
|
102
102
|
top: 0, bottom: win.innerHeight };
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
function scrollRectIntoView(dom, rect, side, center) {
|
|
104
|
+
function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
|
|
106
105
|
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
107
106
|
for (let cur = dom; cur;) {
|
|
108
107
|
if (cur.nodeType == 1) { // Element
|
|
@@ -121,38 +120,42 @@ function scrollRectIntoView(dom, rect, side, center) {
|
|
|
121
120
|
top: rect.top, bottom: rect.top + cur.clientHeight };
|
|
122
121
|
}
|
|
123
122
|
let moveX = 0, moveY = 0;
|
|
124
|
-
if (
|
|
123
|
+
if (y == "nearest") {
|
|
124
|
+
if (rect.top < bounding.top) {
|
|
125
|
+
moveY = -(bounding.top - rect.top + yMargin);
|
|
126
|
+
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
127
|
+
moveY = rect.bottom - bounding.bottom + moveY + yMargin;
|
|
128
|
+
}
|
|
129
|
+
else if (rect.bottom > bounding.bottom) {
|
|
130
|
+
moveY = rect.bottom - bounding.bottom + yMargin;
|
|
131
|
+
if (side < 0 && (rect.top - moveY) < bounding.top)
|
|
132
|
+
moveY = -(bounding.top + moveY - rect.top + yMargin);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
125
136
|
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|
126
|
-
let targetTop
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
else if (side < 0)
|
|
130
|
-
targetTop = rect.top - ScrollSpace;
|
|
131
|
-
else
|
|
132
|
-
targetTop = rect.bottom + ScrollSpace - boundingHeight;
|
|
137
|
+
let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
|
|
138
|
+
y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
|
|
139
|
+
rect.bottom - boundingHeight + yMargin;
|
|
133
140
|
moveY = targetTop - bounding.top;
|
|
134
|
-
if (Math.abs(moveY) <= 1)
|
|
135
|
-
moveY = 0;
|
|
136
|
-
}
|
|
137
|
-
else if (rect.top < bounding.top) {
|
|
138
|
-
moveY = -(bounding.top - rect.top + ScrollSpace);
|
|
139
|
-
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|
140
|
-
moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
|
|
141
141
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
if (x == "nearest") {
|
|
143
|
+
if (rect.left < bounding.left) {
|
|
144
|
+
moveX = -(bounding.left - rect.left + xMargin);
|
|
145
|
+
if (side > 0 && rect.right > bounding.right + moveX)
|
|
146
|
+
moveX = rect.right - bounding.right + moveX + xMargin;
|
|
147
|
+
}
|
|
148
|
+
else if (rect.right > bounding.right) {
|
|
149
|
+
moveX = rect.right - bounding.right + xMargin;
|
|
150
|
+
if (side < 0 && rect.left < bounding.left + moveX)
|
|
151
|
+
moveX = -(bounding.left + moveX - rect.left + xMargin);
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
|
-
else
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
else {
|
|
155
|
+
let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
|
|
156
|
+
(x == "start") == ltr ? rect.left - xMargin :
|
|
157
|
+
rect.right - (bounding.right - bounding.left) + xMargin;
|
|
158
|
+
moveX = targetLeft - bounding.left;
|
|
156
159
|
}
|
|
157
160
|
if (moveX || moveY) {
|
|
158
161
|
if (top) {
|
|
@@ -176,7 +179,7 @@ function scrollRectIntoView(dom, rect, side, center) {
|
|
|
176
179
|
if (top)
|
|
177
180
|
break;
|
|
178
181
|
cur = cur.assignedSlot || cur.parentNode;
|
|
179
|
-
|
|
182
|
+
x = y = "nearest";
|
|
180
183
|
}
|
|
181
184
|
else if (cur.nodeType == 11) { // A shadow root
|
|
182
185
|
cur = cur.host;
|
|
@@ -263,6 +266,10 @@ function getRoot(node) {
|
|
|
263
266
|
}
|
|
264
267
|
return null;
|
|
265
268
|
}
|
|
269
|
+
function clearAttributes(node) {
|
|
270
|
+
while (node.attributes.length)
|
|
271
|
+
node.removeAttributeNode(node.attributes[0]);
|
|
272
|
+
}
|
|
266
273
|
|
|
267
274
|
class DOMPos {
|
|
268
275
|
constructor(node, offset, precise = true) {
|
|
@@ -309,14 +316,16 @@ class ContentView {
|
|
|
309
316
|
// given position.
|
|
310
317
|
coordsAt(_pos, _side) { return null; }
|
|
311
318
|
sync(track) {
|
|
312
|
-
var _a;
|
|
313
319
|
if (this.dirty & 2 /* Node */) {
|
|
314
320
|
let parent = this.dom;
|
|
315
321
|
let pos = parent.firstChild;
|
|
316
322
|
for (let child of this.children) {
|
|
317
323
|
if (child.dirty) {
|
|
318
|
-
if (!child.dom && pos
|
|
319
|
-
|
|
324
|
+
if (!child.dom && pos) {
|
|
325
|
+
let contentView = ContentView.get(pos);
|
|
326
|
+
if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
|
|
327
|
+
child.reuseDOM(pos);
|
|
328
|
+
}
|
|
320
329
|
child.sync(track);
|
|
321
330
|
child.dirty = 0 /* Not */;
|
|
322
331
|
}
|
|
@@ -344,7 +353,7 @@ class ContentView {
|
|
|
344
353
|
}
|
|
345
354
|
}
|
|
346
355
|
}
|
|
347
|
-
reuseDOM(_dom) {
|
|
356
|
+
reuseDOM(_dom) { }
|
|
348
357
|
localPosFromDOM(node, offset) {
|
|
349
358
|
let after;
|
|
350
359
|
if (node == this.dom) {
|
|
@@ -643,10 +652,8 @@ class TextView extends ContentView {
|
|
|
643
652
|
}
|
|
644
653
|
}
|
|
645
654
|
reuseDOM(dom) {
|
|
646
|
-
if (dom.nodeType
|
|
647
|
-
|
|
648
|
-
this.createDOM(dom);
|
|
649
|
-
return true;
|
|
655
|
+
if (dom.nodeType == 3)
|
|
656
|
+
this.createDOM(dom);
|
|
650
657
|
}
|
|
651
658
|
merge(from, to, source) {
|
|
652
659
|
if (source && (!(source instanceof TextView) || this.length - (to - from) + source.length > MaxJoinLen))
|
|
@@ -658,6 +665,7 @@ class TextView extends ContentView {
|
|
|
658
665
|
split(from) {
|
|
659
666
|
let result = new TextView(this.text.slice(from));
|
|
660
667
|
this.text = this.text.slice(0, from);
|
|
668
|
+
this.markDirty();
|
|
661
669
|
return result;
|
|
662
670
|
}
|
|
663
671
|
localPosFromDOM(node, offset) {
|
|
@@ -680,18 +688,26 @@ class MarkView extends ContentView {
|
|
|
680
688
|
for (let ch of children)
|
|
681
689
|
ch.setParent(this);
|
|
682
690
|
}
|
|
683
|
-
|
|
684
|
-
|
|
691
|
+
setAttrs(dom) {
|
|
692
|
+
clearAttributes(dom);
|
|
685
693
|
if (this.mark.class)
|
|
686
694
|
dom.className = this.mark.class;
|
|
687
695
|
if (this.mark.attrs)
|
|
688
696
|
for (let name in this.mark.attrs)
|
|
689
697
|
dom.setAttribute(name, this.mark.attrs[name]);
|
|
690
|
-
|
|
698
|
+
return dom;
|
|
699
|
+
}
|
|
700
|
+
reuseDOM(node) {
|
|
701
|
+
if (node.nodeName == this.mark.tagName.toUpperCase()) {
|
|
702
|
+
this.setDOM(node);
|
|
703
|
+
this.dirty |= 4 /* Attrs */ | 2 /* Node */;
|
|
704
|
+
}
|
|
691
705
|
}
|
|
692
706
|
sync(track) {
|
|
693
|
-
if (!this.dom
|
|
694
|
-
this.
|
|
707
|
+
if (!this.dom)
|
|
708
|
+
this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
|
|
709
|
+
else if (this.dirty & 4 /* Attrs */)
|
|
710
|
+
this.setAttrs(this.dom);
|
|
695
711
|
super.sync(track);
|
|
696
712
|
}
|
|
697
713
|
merge(from, to, source, _hasStart, openStart, openEnd) {
|
|
@@ -835,8 +851,7 @@ class WidgetView extends ContentView {
|
|
|
835
851
|
}
|
|
836
852
|
class CompositionView extends WidgetView {
|
|
837
853
|
domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
|
|
838
|
-
sync() {
|
|
839
|
-
this.setDOM(this.widget.toDOM()); }
|
|
854
|
+
sync() { this.setDOM(this.widget.toDOM()); }
|
|
840
855
|
localPosFromDOM(node, offset) {
|
|
841
856
|
return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
|
|
842
857
|
}
|
|
@@ -1301,13 +1316,23 @@ class LineView extends ContentView {
|
|
|
1301
1316
|
domAtPos(pos) {
|
|
1302
1317
|
return inlineDOMAtPos(this.dom, this.children, pos);
|
|
1303
1318
|
}
|
|
1319
|
+
reuseDOM(node) {
|
|
1320
|
+
if (node.nodeName == "DIV") {
|
|
1321
|
+
this.setDOM(node);
|
|
1322
|
+
this.dirty |= 4 /* Attrs */ | 2 /* Node */;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1304
1325
|
sync(track) {
|
|
1305
1326
|
var _a;
|
|
1306
|
-
if (!this.dom
|
|
1327
|
+
if (!this.dom) {
|
|
1307
1328
|
this.setDOM(document.createElement("div"));
|
|
1308
1329
|
this.dom.className = "cm-line";
|
|
1309
1330
|
this.prevAttrs = this.attrs ? null : undefined;
|
|
1310
1331
|
}
|
|
1332
|
+
else if (this.dirty & 4 /* Attrs */) {
|
|
1333
|
+
clearAttributes(this.dom);
|
|
1334
|
+
this.prevAttrs = this.attrs ? null : undefined;
|
|
1335
|
+
}
|
|
1311
1336
|
if (this.prevAttrs !== undefined) {
|
|
1312
1337
|
updateAttrs(this.dom, this.prevAttrs, this.attrs);
|
|
1313
1338
|
this.dom.classList.add("cm-line");
|
|
@@ -1579,12 +1604,27 @@ const mouseSelectionStyle = state.Facet.define();
|
|
|
1579
1604
|
const exceptionSink = state.Facet.define();
|
|
1580
1605
|
const updateListener = state.Facet.define();
|
|
1581
1606
|
const inputHandler = state.Facet.define();
|
|
1607
|
+
// FIXME remove
|
|
1582
1608
|
const scrollTo = state.StateEffect.define({
|
|
1583
1609
|
map: (range, changes) => range.map(changes)
|
|
1584
1610
|
});
|
|
1611
|
+
// FIXME remove
|
|
1585
1612
|
const centerOn = state.StateEffect.define({
|
|
1586
1613
|
map: (range, changes) => range.map(changes)
|
|
1587
1614
|
});
|
|
1615
|
+
class ScrollTarget {
|
|
1616
|
+
constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
|
|
1617
|
+
this.range = range;
|
|
1618
|
+
this.y = y;
|
|
1619
|
+
this.x = x;
|
|
1620
|
+
this.yMargin = yMargin;
|
|
1621
|
+
this.xMargin = xMargin;
|
|
1622
|
+
}
|
|
1623
|
+
map(changes) {
|
|
1624
|
+
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|
1588
1628
|
/**
|
|
1589
1629
|
Log or report an unhandled exception in client code. Should
|
|
1590
1630
|
probably only be used by extension code that allows client code to
|
|
@@ -2398,16 +2438,6 @@ class DocView extends ContentView {
|
|
|
2398
2438
|
return true;
|
|
2399
2439
|
}
|
|
2400
2440
|
}
|
|
2401
|
-
reset(sel) {
|
|
2402
|
-
if (this.dirty) {
|
|
2403
|
-
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2404
|
-
this.dirty = 0 /* Not */;
|
|
2405
|
-
this.updateSelection(true);
|
|
2406
|
-
}
|
|
2407
|
-
else {
|
|
2408
|
-
this.updateSelection();
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
2441
|
// Used by update and the constructor do perform the actual DOM
|
|
2412
2442
|
// update
|
|
2413
2443
|
updateInner(changes, deco, oldLength) {
|
|
@@ -2483,7 +2513,8 @@ class DocView extends ContentView {
|
|
|
2483
2513
|
// inside an uneditable node, and not bring it back when we
|
|
2484
2514
|
// move the cursor to its proper position. This tries to
|
|
2485
2515
|
// restore the keyboard by cycling focus.
|
|
2486
|
-
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
|
|
2516
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
|
|
2517
|
+
inUneditable(domSel.focusNode, this.dom)) {
|
|
2487
2518
|
this.dom.blur();
|
|
2488
2519
|
this.dom.focus({ preventScroll: true });
|
|
2489
2520
|
}
|
|
@@ -2672,7 +2703,8 @@ class DocView extends ContentView {
|
|
|
2672
2703
|
this.view.viewState.lineGapDeco
|
|
2673
2704
|
];
|
|
2674
2705
|
}
|
|
2675
|
-
scrollIntoView(
|
|
2706
|
+
scrollIntoView(target) {
|
|
2707
|
+
let { range } = target;
|
|
2676
2708
|
let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
|
|
2677
2709
|
if (!rect)
|
|
2678
2710
|
return;
|
|
@@ -2692,10 +2724,11 @@ class DocView extends ContentView {
|
|
|
2692
2724
|
if (bottom != null)
|
|
2693
2725
|
mBottom = Math.max(mBottom, bottom);
|
|
2694
2726
|
}
|
|
2695
|
-
|
|
2727
|
+
let targetRect = {
|
|
2696
2728
|
left: rect.left - mLeft, top: rect.top - mTop,
|
|
2697
2729
|
right: rect.right + mRight, bottom: rect.bottom + mBottom
|
|
2698
|
-
}
|
|
2730
|
+
};
|
|
2731
|
+
scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == exports.Direction.LTR);
|
|
2699
2732
|
}
|
|
2700
2733
|
}
|
|
2701
2734
|
function betweenUneditable(pos) {
|
|
@@ -3136,14 +3169,6 @@ class InputState {
|
|
|
3136
3169
|
constructor(view) {
|
|
3137
3170
|
this.lastKeyCode = 0;
|
|
3138
3171
|
this.lastKeyTime = 0;
|
|
3139
|
-
// On Chrome Android, backspace near widgets is just completely
|
|
3140
|
-
// broken, and there are no key events, so we need to handle the
|
|
3141
|
-
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3142
|
-
// events, and interrupting it before it is done just makes
|
|
3143
|
-
// subsequent events even more broken, so again, we hold off doing
|
|
3144
|
-
// anything until the browser is finished with whatever it is trying
|
|
3145
|
-
// to do.
|
|
3146
|
-
this.pendingAndroidKey = undefined;
|
|
3147
3172
|
// On iOS, some keys need to have their default behavior happen
|
|
3148
3173
|
// (after which we retroactively handle them and reset the DOM) to
|
|
3149
3174
|
// avoid messing up the virtual keyboard state.
|
|
@@ -3212,22 +3237,15 @@ class InputState {
|
|
|
3212
3237
|
}
|
|
3213
3238
|
runCustomHandlers(type, view, event) {
|
|
3214
3239
|
for (let set of this.customHandlers) {
|
|
3215
|
-
let handler = set.handlers[type]
|
|
3240
|
+
let handler = set.handlers[type];
|
|
3216
3241
|
if (handler) {
|
|
3217
3242
|
try {
|
|
3218
|
-
|
|
3243
|
+
if (handler.call(set.plugin, event, view) || event.defaultPrevented)
|
|
3244
|
+
return true;
|
|
3219
3245
|
}
|
|
3220
3246
|
catch (e) {
|
|
3221
3247
|
logException(view.state, e);
|
|
3222
3248
|
}
|
|
3223
|
-
if (handled || event.defaultPrevented) {
|
|
3224
|
-
// Chrome for Android often applies a bunch of nonsensical
|
|
3225
|
-
// DOM changes after an enter press, even when
|
|
3226
|
-
// preventDefault-ed. This tries to ignore those.
|
|
3227
|
-
if (browser.android && type == "keydown" && event.keyCode == 13)
|
|
3228
|
-
view.observer.flushSoon();
|
|
3229
|
-
return true;
|
|
3230
|
-
}
|
|
3231
3249
|
}
|
|
3232
3250
|
}
|
|
3233
3251
|
return false;
|
|
@@ -3251,6 +3269,16 @@ class InputState {
|
|
|
3251
3269
|
this.lastKeyTime = Date.now();
|
|
3252
3270
|
if (this.screenKeyEvent(view, event))
|
|
3253
3271
|
return true;
|
|
3272
|
+
// Chrome for Android usually doesn't fire proper key events, but
|
|
3273
|
+
// occasionally does, usually surrounded by a bunch of complicated
|
|
3274
|
+
// composition changes. When an enter or backspace key event is
|
|
3275
|
+
// seen, hold off on handling DOM events for a bit, and then
|
|
3276
|
+
// dispatch it.
|
|
3277
|
+
if (browser.android && browser.chrome && !event.synthetic &&
|
|
3278
|
+
(event.keyCode == 13 || event.keyCode == 8)) {
|
|
3279
|
+
view.observer.delayAndroidKey(event.key, event.keyCode);
|
|
3280
|
+
return true;
|
|
3281
|
+
}
|
|
3254
3282
|
// Prevent the default behavior of Enter on iOS makes the
|
|
3255
3283
|
// virtual keyboard get stuck in the wrong (lowercase)
|
|
3256
3284
|
// state. So we let it go through, and then, in
|
|
@@ -3272,24 +3300,6 @@ class InputState {
|
|
|
3272
3300
|
this.pendingIOSKey = undefined;
|
|
3273
3301
|
return dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3274
3302
|
}
|
|
3275
|
-
// This causes the DOM observer to pause for a bit, and sets an
|
|
3276
|
-
// animation frame (which seems the most reliable way to detect
|
|
3277
|
-
// 'Chrome is done flailing about messing with the DOM') to fire a
|
|
3278
|
-
// fake key event and re-sync the view again.
|
|
3279
|
-
setPendingAndroidKey(view, pending) {
|
|
3280
|
-
this.pendingAndroidKey = pending;
|
|
3281
|
-
requestAnimationFrame(() => {
|
|
3282
|
-
let key = this.pendingAndroidKey;
|
|
3283
|
-
if (!key)
|
|
3284
|
-
return;
|
|
3285
|
-
this.pendingAndroidKey = undefined;
|
|
3286
|
-
view.observer.processRecords();
|
|
3287
|
-
let startState = view.state;
|
|
3288
|
-
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3289
|
-
if (view.state == startState)
|
|
3290
|
-
view.docView.reset(true);
|
|
3291
|
-
});
|
|
3292
|
-
}
|
|
3293
3303
|
ignoreDuringComposition(event) {
|
|
3294
3304
|
if (!/^key/.test(event.type))
|
|
3295
3305
|
return false;
|
|
@@ -3768,12 +3778,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
|
|
|
3768
3778
|
if (view.inputState.compositionFirstChange == null)
|
|
3769
3779
|
view.inputState.compositionFirstChange = true;
|
|
3770
3780
|
if (view.inputState.composing < 0) {
|
|
3781
|
+
// FIXME possibly set a timeout to clear it again on Android
|
|
3782
|
+
view.inputState.composing = 0;
|
|
3771
3783
|
if (view.docView.compositionDeco.size) {
|
|
3772
3784
|
view.observer.flush();
|
|
3773
3785
|
forceClearComposition(view, true);
|
|
3774
3786
|
}
|
|
3775
|
-
// FIXME possibly set a timeout to clear it again on Android
|
|
3776
|
-
view.inputState.composing = 0;
|
|
3777
3787
|
}
|
|
3778
3788
|
};
|
|
3779
3789
|
handlers.compositionend = view => {
|
|
@@ -3799,7 +3809,7 @@ handlers.beforeinput = (view, event) => {
|
|
|
3799
3809
|
// seems to do nothing at all on Chrome).
|
|
3800
3810
|
let pending;
|
|
3801
3811
|
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3802
|
-
view.
|
|
3812
|
+
view.observer.delayAndroidKey(pending.key, pending.keyCode);
|
|
3803
3813
|
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3804
3814
|
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3805
3815
|
setTimeout(() => {
|
|
@@ -4552,15 +4562,6 @@ class LineGapWidget extends WidgetType {
|
|
|
4552
4562
|
}
|
|
4553
4563
|
get estimatedHeight() { return this.vertical ? this.size : -1; }
|
|
4554
4564
|
}
|
|
4555
|
-
class ScrollTarget {
|
|
4556
|
-
constructor(range, center = false) {
|
|
4557
|
-
this.range = range;
|
|
4558
|
-
this.center = center;
|
|
4559
|
-
}
|
|
4560
|
-
map(changes) {
|
|
4561
|
-
return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
|
|
4562
|
-
}
|
|
4563
|
-
}
|
|
4564
4565
|
class ViewState {
|
|
4565
4566
|
constructor(state) {
|
|
4566
4567
|
this.state = state;
|
|
@@ -4635,9 +4636,9 @@ class ViewState {
|
|
|
4635
4636
|
let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
|
|
4636
4637
|
viewport.from != this.viewport.from || viewport.to != this.viewport.to;
|
|
4637
4638
|
this.viewport = viewport;
|
|
4639
|
+
this.updateForViewport();
|
|
4638
4640
|
if (updateLines)
|
|
4639
4641
|
this.updateViewportLines();
|
|
4640
|
-
this.updateForViewport();
|
|
4641
4642
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4642
4643
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4643
4644
|
update.flags |= this.computeVisibleRanges();
|
|
@@ -4669,7 +4670,12 @@ class ViewState {
|
|
|
4669
4670
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
4670
4671
|
let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
|
|
4671
4672
|
this.pixelViewport = pixelViewport;
|
|
4672
|
-
|
|
4673
|
+
let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|
4674
|
+
if (inView != this.inView) {
|
|
4675
|
+
this.inView = inView;
|
|
4676
|
+
if (inView)
|
|
4677
|
+
measureContent = true;
|
|
4678
|
+
}
|
|
4673
4679
|
if (!this.inView)
|
|
4674
4680
|
return 0;
|
|
4675
4681
|
if (measureContent) {
|
|
@@ -4706,9 +4712,9 @@ class ViewState {
|
|
|
4706
4712
|
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
|
|
4707
4713
|
if (viewportChange)
|
|
4708
4714
|
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4715
|
+
this.updateForViewport();
|
|
4709
4716
|
if ((result & 2 /* Height */) || viewportChange)
|
|
4710
4717
|
this.updateViewportLines();
|
|
4711
|
-
this.updateForViewport();
|
|
4712
4718
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4713
4719
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4714
4720
|
result |= this.computeVisibleRanges();
|
|
@@ -4736,9 +4742,9 @@ class ViewState {
|
|
|
4736
4742
|
let { head } = scrollTarget.range, viewHeight = this.editorHeight;
|
|
4737
4743
|
if (head < viewport.from || head > viewport.to) {
|
|
4738
4744
|
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4739
|
-
if (scrollTarget.center)
|
|
4745
|
+
if (scrollTarget.y == "center")
|
|
4740
4746
|
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
4741
|
-
else if (head < viewport.from)
|
|
4747
|
+
else if (scrollTarget.y == "start" || head < viewport.from)
|
|
4742
4748
|
topPos = block.top;
|
|
4743
4749
|
else
|
|
4744
4750
|
topPos = block.bottom - viewHeight;
|
|
@@ -5096,11 +5102,13 @@ const baseTheme = buildTheme("." + baseThemeID, {
|
|
|
5096
5102
|
// recomputation.
|
|
5097
5103
|
"@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
|
|
5098
5104
|
"@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
|
|
5099
|
-
".cm-cursor": {
|
|
5105
|
+
".cm-cursor, .cm-dropCursor": {
|
|
5100
5106
|
position: "absolute",
|
|
5101
5107
|
borderLeft: "1.2px solid black",
|
|
5102
5108
|
marginLeft: "-0.6px",
|
|
5103
5109
|
pointerEvents: "none",
|
|
5110
|
+
},
|
|
5111
|
+
".cm-cursor": {
|
|
5104
5112
|
display: "none"
|
|
5105
5113
|
},
|
|
5106
5114
|
"&dark .cm-cursor": {
|
|
@@ -5188,6 +5196,7 @@ class DOMObserver {
|
|
|
5188
5196
|
this.delayedFlush = -1;
|
|
5189
5197
|
this.resizeTimeout = -1;
|
|
5190
5198
|
this.queue = [];
|
|
5199
|
+
this.delayedAndroidKey = null;
|
|
5191
5200
|
this.scrollTargets = [];
|
|
5192
5201
|
this.intersection = null;
|
|
5193
5202
|
this.resize = null;
|
|
@@ -5241,7 +5250,7 @@ class DOMObserver {
|
|
|
5241
5250
|
this.intersection = new IntersectionObserver(entries => {
|
|
5242
5251
|
if (this.parentCheck < 0)
|
|
5243
5252
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
5244
|
-
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5253
|
+
if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
|
|
5245
5254
|
this.intersecting = !this.intersecting;
|
|
5246
5255
|
if (this.intersecting != this.view.inView)
|
|
5247
5256
|
this.onScrollChanged(document.createEvent("Event"));
|
|
@@ -5271,7 +5280,7 @@ class DOMObserver {
|
|
|
5271
5280
|
}
|
|
5272
5281
|
}
|
|
5273
5282
|
onSelectionChange(event) {
|
|
5274
|
-
if (!this.readSelectionRange())
|
|
5283
|
+
if (!this.readSelectionRange() || this.delayedAndroidKey)
|
|
5275
5284
|
return;
|
|
5276
5285
|
let { view } = this, sel = this.selectionRange;
|
|
5277
5286
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
@@ -5367,6 +5376,32 @@ class DOMObserver {
|
|
|
5367
5376
|
this.queue.length = 0;
|
|
5368
5377
|
this.selectionChanged = false;
|
|
5369
5378
|
}
|
|
5379
|
+
// Chrome Android, especially in combination with GBoard, not only
|
|
5380
|
+
// doesn't reliably fire regular key events, but also often
|
|
5381
|
+
// surrounds the effect of enter or backspace with a bunch of
|
|
5382
|
+
// composition events that, when interrupted, cause text duplication
|
|
5383
|
+
// or other kinds of corruption. This hack makes the editor back off
|
|
5384
|
+
// from handling DOM changes for a moment when such a key is
|
|
5385
|
+
// detected (via beforeinput or keydown), and then dispatches the
|
|
5386
|
+
// key event, throwing away the DOM changes if it gets handled.
|
|
5387
|
+
delayAndroidKey(key, keyCode) {
|
|
5388
|
+
if (!this.delayedAndroidKey)
|
|
5389
|
+
requestAnimationFrame(() => {
|
|
5390
|
+
let key = this.delayedAndroidKey;
|
|
5391
|
+
this.delayedAndroidKey = null;
|
|
5392
|
+
let startState = this.view.state;
|
|
5393
|
+
if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
|
|
5394
|
+
this.processRecords();
|
|
5395
|
+
else
|
|
5396
|
+
this.flush();
|
|
5397
|
+
if (this.view.state == startState)
|
|
5398
|
+
this.view.update([]);
|
|
5399
|
+
});
|
|
5400
|
+
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5401
|
+
// Enter always takes precedence.
|
|
5402
|
+
if (!this.delayedAndroidKey || key == "Enter")
|
|
5403
|
+
this.delayedAndroidKey = { key, keyCode };
|
|
5404
|
+
}
|
|
5370
5405
|
flushSoon() {
|
|
5371
5406
|
if (this.delayedFlush < 0)
|
|
5372
5407
|
this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
|
|
@@ -5403,13 +5438,13 @@ class DOMObserver {
|
|
|
5403
5438
|
}
|
|
5404
5439
|
// Apply pending changes, if any
|
|
5405
5440
|
flush(readSelection = true) {
|
|
5406
|
-
if (readSelection)
|
|
5407
|
-
this.readSelectionRange();
|
|
5408
5441
|
// Completely hold off flushing when pending keys are set—the code
|
|
5409
5442
|
// managing those will make sure processRecords is called and the
|
|
5410
5443
|
// view is resynchronized after
|
|
5411
|
-
if (this.delayedFlush >= 0 || this.
|
|
5444
|
+
if (this.delayedFlush >= 0 || this.delayedAndroidKey)
|
|
5412
5445
|
return;
|
|
5446
|
+
if (readSelection)
|
|
5447
|
+
this.readSelectionRange();
|
|
5413
5448
|
let { from, to, typeOver } = this.processRecords();
|
|
5414
5449
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5415
5450
|
if (from < 0 && !newSel)
|
|
@@ -5815,7 +5850,9 @@ class EditorView {
|
|
|
5815
5850
|
if (e.is(scrollTo))
|
|
5816
5851
|
scrollTarget = new ScrollTarget(e.value);
|
|
5817
5852
|
else if (e.is(centerOn))
|
|
5818
|
-
scrollTarget = new ScrollTarget(e.value,
|
|
5853
|
+
scrollTarget = new ScrollTarget(e.value, "center");
|
|
5854
|
+
else if (e.is(scrollIntoView))
|
|
5855
|
+
scrollTarget = e.value;
|
|
5819
5856
|
}
|
|
5820
5857
|
}
|
|
5821
5858
|
this.viewState.update(update, scrollTarget);
|
|
@@ -6383,6 +6420,14 @@ class EditorView {
|
|
|
6383
6420
|
this.destroyed = true;
|
|
6384
6421
|
}
|
|
6385
6422
|
/**
|
|
6423
|
+
Returns an effect that can be
|
|
6424
|
+
[added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
|
|
6425
|
+
cause it to scroll the given position or range into view.
|
|
6426
|
+
*/
|
|
6427
|
+
static scrollIntoView(pos, options = {}) {
|
|
6428
|
+
return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
|
|
6429
|
+
}
|
|
6430
|
+
/**
|
|
6386
6431
|
Facet that can be used to add DOM event handlers. The value
|
|
6387
6432
|
should be an object mapping event names to handler functions. The
|
|
6388
6433
|
first such function to return true will be assumed to have handled
|
|
@@ -6436,11 +6481,15 @@ class EditorView {
|
|
|
6436
6481
|
/**
|
|
6437
6482
|
Effect that can be [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a
|
|
6438
6483
|
transaction to make it scroll the given range into view.
|
|
6484
|
+
|
|
6485
|
+
*Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
|
|
6439
6486
|
*/
|
|
6440
6487
|
EditorView.scrollTo = scrollTo;
|
|
6441
6488
|
/**
|
|
6442
6489
|
Effect that makes the editor scroll the given range to the
|
|
6443
6490
|
center of the visible view.
|
|
6491
|
+
|
|
6492
|
+
*Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
|
|
6444
6493
|
*/
|
|
6445
6494
|
EditorView.centerOn = centerOn;
|
|
6446
6495
|
/**
|
|
@@ -6909,7 +6958,7 @@ function measureRange(view, range) {
|
|
|
6909
6958
|
let ltr = view.textDirection == exports.Direction.LTR;
|
|
6910
6959
|
let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
|
|
6911
6960
|
let lineStyle = window.getComputedStyle(content.firstChild);
|
|
6912
|
-
let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
|
|
6961
|
+
let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
|
|
6913
6962
|
let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
|
|
6914
6963
|
let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
|
|
6915
6964
|
let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
|
|
@@ -6929,7 +6978,7 @@ function measureRange(view, range) {
|
|
|
6929
6978
|
let between = [];
|
|
6930
6979
|
if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
|
|
6931
6980
|
between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
|
|
6932
|
-
else if (top.bottom < bottom.top &&
|
|
6981
|
+
else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
|
|
6933
6982
|
top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
|
|
6934
6983
|
return pieces(top).concat(between).concat(pieces(bottom));
|
|
6935
6984
|
}
|
|
@@ -6994,6 +7043,98 @@ function measureCursor(view, cursor, primary) {
|
|
|
6994
7043
|
return new Piece(pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top, primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary");
|
|
6995
7044
|
}
|
|
6996
7045
|
|
|
7046
|
+
const setDropCursorPos = state.StateEffect.define({
|
|
7047
|
+
map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
|
|
7048
|
+
});
|
|
7049
|
+
const dropCursorPos = state.StateField.define({
|
|
7050
|
+
create() { return null; },
|
|
7051
|
+
update(pos, tr) {
|
|
7052
|
+
if (pos != null)
|
|
7053
|
+
pos = tr.changes.mapPos(pos);
|
|
7054
|
+
return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
|
|
7055
|
+
}
|
|
7056
|
+
});
|
|
7057
|
+
const drawDropCursor = ViewPlugin.fromClass(class {
|
|
7058
|
+
constructor(view) {
|
|
7059
|
+
this.view = view;
|
|
7060
|
+
this.cursor = null;
|
|
7061
|
+
this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
|
|
7062
|
+
}
|
|
7063
|
+
update(update) {
|
|
7064
|
+
var _a;
|
|
7065
|
+
let cursorPos = update.state.field(dropCursorPos);
|
|
7066
|
+
if (cursorPos == null) {
|
|
7067
|
+
if (this.cursor != null) {
|
|
7068
|
+
(_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
|
|
7069
|
+
this.cursor = null;
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
else {
|
|
7073
|
+
if (!this.cursor) {
|
|
7074
|
+
this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
|
|
7075
|
+
this.cursor.className = "cm-dropCursor";
|
|
7076
|
+
}
|
|
7077
|
+
if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
|
|
7078
|
+
this.view.requestMeasure(this.measureReq);
|
|
7079
|
+
}
|
|
7080
|
+
}
|
|
7081
|
+
readPos() {
|
|
7082
|
+
let pos = this.view.state.field(dropCursorPos);
|
|
7083
|
+
let rect = pos != null && this.view.coordsAtPos(pos);
|
|
7084
|
+
if (!rect)
|
|
7085
|
+
return null;
|
|
7086
|
+
let outer = this.view.scrollDOM.getBoundingClientRect();
|
|
7087
|
+
return {
|
|
7088
|
+
left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
|
|
7089
|
+
top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
|
|
7090
|
+
height: rect.bottom - rect.top
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
drawCursor(pos) {
|
|
7094
|
+
if (this.cursor) {
|
|
7095
|
+
if (pos) {
|
|
7096
|
+
this.cursor.style.left = pos.left + "px";
|
|
7097
|
+
this.cursor.style.top = pos.top + "px";
|
|
7098
|
+
this.cursor.style.height = pos.height + "px";
|
|
7099
|
+
}
|
|
7100
|
+
else {
|
|
7101
|
+
this.cursor.style.left = "-100000px";
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
destroy() {
|
|
7106
|
+
if (this.cursor)
|
|
7107
|
+
this.cursor.remove();
|
|
7108
|
+
}
|
|
7109
|
+
setDropPos(pos) {
|
|
7110
|
+
if (this.view.state.field(dropCursorPos) != pos)
|
|
7111
|
+
this.view.dispatch({ effects: setDropCursorPos.of(pos) });
|
|
7112
|
+
}
|
|
7113
|
+
}, {
|
|
7114
|
+
eventHandlers: {
|
|
7115
|
+
dragover(event) {
|
|
7116
|
+
this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
|
|
7117
|
+
},
|
|
7118
|
+
dragleave(event) {
|
|
7119
|
+
if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
|
|
7120
|
+
this.setDropPos(null);
|
|
7121
|
+
},
|
|
7122
|
+
dragend() {
|
|
7123
|
+
this.setDropPos(null);
|
|
7124
|
+
},
|
|
7125
|
+
drop() {
|
|
7126
|
+
this.setDropPos(null);
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
});
|
|
7130
|
+
/**
|
|
7131
|
+
Draws a cursor at the current drop position when something is
|
|
7132
|
+
dragged over the editor.
|
|
7133
|
+
*/
|
|
7134
|
+
function dropCursor() {
|
|
7135
|
+
return [dropCursorPos, drawDropCursor];
|
|
7136
|
+
}
|
|
7137
|
+
|
|
6997
7138
|
function iterMatches(doc, re, from, to, f) {
|
|
6998
7139
|
re.lastIndex = 0;
|
|
6999
7140
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
@@ -7002,6 +7143,22 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
7002
7143
|
f(pos + m.index, pos + m.index + m[0].length, m);
|
|
7003
7144
|
}
|
|
7004
7145
|
}
|
|
7146
|
+
function matchRanges(view, maxLength) {
|
|
7147
|
+
let visible = view.visibleRanges;
|
|
7148
|
+
if (visible.length == 1 && visible[0].from == view.viewport.from &&
|
|
7149
|
+
visible[0].to == view.viewport.to)
|
|
7150
|
+
return visible;
|
|
7151
|
+
let result = [];
|
|
7152
|
+
for (let { from, to } of visible) {
|
|
7153
|
+
from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
|
|
7154
|
+
to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
|
|
7155
|
+
if (result.length && result[result.length - 1].to >= from)
|
|
7156
|
+
result[result.length - 1].to = to;
|
|
7157
|
+
else
|
|
7158
|
+
result.push({ from, to });
|
|
7159
|
+
}
|
|
7160
|
+
return result;
|
|
7161
|
+
}
|
|
7005
7162
|
/**
|
|
7006
7163
|
Helper class used to make it easier to maintain decorations on
|
|
7007
7164
|
visible code that matches a given regular expression. To be used
|
|
@@ -7013,12 +7170,13 @@ class MatchDecorator {
|
|
|
7013
7170
|
Create a decorator.
|
|
7014
7171
|
*/
|
|
7015
7172
|
constructor(config) {
|
|
7016
|
-
let { regexp, decoration, boundary } = config;
|
|
7173
|
+
let { regexp, decoration, boundary, maxLength = 1000 } = config;
|
|
7017
7174
|
if (!regexp.global)
|
|
7018
7175
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
7019
7176
|
this.regexp = regexp;
|
|
7020
7177
|
this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
7021
7178
|
this.boundary = boundary;
|
|
7179
|
+
this.maxLength = maxLength;
|
|
7022
7180
|
}
|
|
7023
7181
|
/**
|
|
7024
7182
|
Compute the full set of decorations for matches in the given
|
|
@@ -7027,7 +7185,7 @@ class MatchDecorator {
|
|
|
7027
7185
|
*/
|
|
7028
7186
|
createDeco(view) {
|
|
7029
7187
|
let build = new rangeset.RangeSetBuilder();
|
|
7030
|
-
for (let { from, to } of view.
|
|
7188
|
+
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
7031
7189
|
iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
|
|
7032
7190
|
return build.finish();
|
|
7033
7191
|
}
|
|
@@ -7344,6 +7502,7 @@ exports.ViewUpdate = ViewUpdate;
|
|
|
7344
7502
|
exports.WidgetType = WidgetType;
|
|
7345
7503
|
exports.__test = __test;
|
|
7346
7504
|
exports.drawSelection = drawSelection;
|
|
7505
|
+
exports.dropCursor = dropCursor;
|
|
7347
7506
|
exports.highlightActiveLine = highlightActiveLine;
|
|
7348
7507
|
exports.highlightSpecialChars = highlightSpecialChars;
|
|
7349
7508
|
exports.keymap = keymap;
|