@codemirror/view 0.19.20 → 0.19.21
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 +6 -0
- package/dist/index.cjs +78 -60
- package/dist/index.js +78 -60
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -186,7 +186,7 @@ function scrollRectIntoView(dom, rect, side, center) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
class
|
|
189
|
+
class DOMSelectionState {
|
|
190
190
|
constructor() {
|
|
191
191
|
this.anchorNode = null;
|
|
192
192
|
this.anchorOffset = 0;
|
|
@@ -197,11 +197,14 @@ class DOMSelection {
|
|
|
197
197
|
return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
|
|
198
198
|
this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
this.anchorNode
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.
|
|
200
|
+
setRange(range) {
|
|
201
|
+
this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
|
|
202
|
+
}
|
|
203
|
+
set(anchorNode, anchorOffset, focusNode, focusOffset) {
|
|
204
|
+
this.anchorNode = anchorNode;
|
|
205
|
+
this.anchorOffset = anchorOffset;
|
|
206
|
+
this.focusNode = focusNode;
|
|
207
|
+
this.focusOffset = focusOffset;
|
|
205
208
|
}
|
|
206
209
|
}
|
|
207
210
|
let preventScrollSupported = null;
|
|
@@ -1943,9 +1946,10 @@ class DocView extends ContentView {
|
|
|
1943
1946
|
// we don't mess it up when reading it back it
|
|
1944
1947
|
this.impreciseAnchor = null;
|
|
1945
1948
|
this.impreciseHead = null;
|
|
1949
|
+
this.forceSelection = false;
|
|
1946
1950
|
// Used by the resize observer to ignore resizes that we caused
|
|
1947
1951
|
// ourselves
|
|
1948
|
-
this.lastUpdate =
|
|
1952
|
+
this.lastUpdate = Date.now();
|
|
1949
1953
|
this.setDOM(view.contentDOM);
|
|
1950
1954
|
this.children = [new LineView];
|
|
1951
1955
|
this.children[0].setParent(this);
|
|
@@ -1959,7 +1963,6 @@ class DocView extends ContentView {
|
|
|
1959
1963
|
// position, if we know the editor is going to scroll that position
|
|
1960
1964
|
// into view.
|
|
1961
1965
|
update(update) {
|
|
1962
|
-
this.lastUpdate = Date.now();
|
|
1963
1966
|
let changedRanges = update.changedRanges;
|
|
1964
1967
|
if (this.minWidth > 0 && changedRanges.length) {
|
|
1965
1968
|
if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
|
|
@@ -1979,21 +1982,22 @@ class DocView extends ContentView {
|
|
|
1979
1982
|
// getSelection than the one that it actually shows to the user.
|
|
1980
1983
|
// This forces a selection update when lines are joined to work
|
|
1981
1984
|
// around that. Issue #54
|
|
1982
|
-
|
|
1983
|
-
update.state.doc.lines != update.startState.doc.lines
|
|
1985
|
+
if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
|
|
1986
|
+
update.state.doc.lines != update.startState.doc.lines)
|
|
1987
|
+
this.forceSelection = true;
|
|
1984
1988
|
let prevDeco = this.decorations, deco = this.updateDeco();
|
|
1985
1989
|
let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
|
|
1986
1990
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|
1987
|
-
let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
|
|
1988
1991
|
if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
|
|
1989
1992
|
!(update.flags & 4 /* Viewport */) &&
|
|
1990
1993
|
update.state.selection.main.from >= this.view.viewport.from &&
|
|
1991
1994
|
update.state.selection.main.to <= this.view.viewport.to) {
|
|
1992
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
1993
1995
|
return false;
|
|
1994
1996
|
}
|
|
1995
1997
|
else {
|
|
1996
|
-
this.updateInner(changedRanges, deco, update.startState.doc.length
|
|
1998
|
+
this.updateInner(changedRanges, deco, update.startState.doc.length);
|
|
1999
|
+
if (update.transactions.length)
|
|
2000
|
+
this.lastUpdate = Date.now();
|
|
1997
2001
|
return true;
|
|
1998
2002
|
}
|
|
1999
2003
|
}
|
|
@@ -2001,13 +2005,15 @@ class DocView extends ContentView {
|
|
|
2001
2005
|
if (this.dirty) {
|
|
2002
2006
|
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2003
2007
|
this.dirty = 0 /* Not */;
|
|
2008
|
+
this.updateSelection(true);
|
|
2004
2009
|
}
|
|
2005
|
-
|
|
2010
|
+
else {
|
|
2006
2011
|
this.updateSelection();
|
|
2012
|
+
}
|
|
2007
2013
|
}
|
|
2008
2014
|
// Used both by update and checkLayout do perform the actual DOM
|
|
2009
2015
|
// update
|
|
2010
|
-
updateInner(changes, deco, oldLength
|
|
2016
|
+
updateInner(changes, deco, oldLength) {
|
|
2011
2017
|
this.updateChildren(changes, deco, oldLength);
|
|
2012
2018
|
let { observer } = this.view;
|
|
2013
2019
|
observer.ignore(() => {
|
|
@@ -2025,8 +2031,7 @@ class DocView extends ContentView {
|
|
|
2025
2031
|
this.sync(track);
|
|
2026
2032
|
this.dirty = 0 /* Not */;
|
|
2027
2033
|
if (track && (track.written || observer.selectionRange.focusNode != track.node))
|
|
2028
|
-
forceSelection = true;
|
|
2029
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
2034
|
+
this.forceSelection = true;
|
|
2030
2035
|
this.dom.style.height = "";
|
|
2031
2036
|
});
|
|
2032
2037
|
let gaps = [];
|
|
@@ -2112,10 +2117,14 @@ class DocView extends ContentView {
|
|
|
2112
2117
|
this.replaceChildren(fromI, toI, content);
|
|
2113
2118
|
}
|
|
2114
2119
|
// Sync the DOM selection to this.state.selection
|
|
2115
|
-
updateSelection(
|
|
2120
|
+
updateSelection(mustRead = false, fromPointer = false) {
|
|
2121
|
+
if (mustRead)
|
|
2122
|
+
this.view.observer.readSelectionRange();
|
|
2116
2123
|
if (!(fromPointer || this.mayControlSelection()) ||
|
|
2117
2124
|
browser.ios && this.view.inputState.rapidCompositionStart)
|
|
2118
2125
|
return;
|
|
2126
|
+
let force = this.forceSelection;
|
|
2127
|
+
this.forceSelection = false;
|
|
2119
2128
|
let main = this.view.state.selection.main;
|
|
2120
2129
|
// FIXME need to handle the case where the selection falls inside a block range
|
|
2121
2130
|
let anchor = this.domAtPos(main.anchor);
|
|
@@ -5075,25 +5084,30 @@ class DOMObserver {
|
|
|
5075
5084
|
this.onChange = onChange;
|
|
5076
5085
|
this.onScrollChanged = onScrollChanged;
|
|
5077
5086
|
this.active = false;
|
|
5078
|
-
|
|
5087
|
+
// The known selection. Kept in our own object, as opposed to just
|
|
5088
|
+
// directly accessing the selection because:
|
|
5089
|
+
// - Safari doesn't report the right selection in shadow DOM
|
|
5090
|
+
// - Reading from the selection forces a DOM layout
|
|
5091
|
+
// - This way, we can ignore selectionchange events if we have
|
|
5092
|
+
// already seen the 'new' selection
|
|
5093
|
+
this.selectionRange = new DOMSelectionState;
|
|
5094
|
+
// Set when a selection change is detected, cleared on flush
|
|
5095
|
+
this.selectionChanged = false;
|
|
5079
5096
|
this.delayedFlush = -1;
|
|
5097
|
+
this.resizeTimeout = -1;
|
|
5080
5098
|
this.queue = [];
|
|
5081
|
-
this.lastFlush = 0;
|
|
5082
5099
|
this.scrollTargets = [];
|
|
5083
5100
|
this.intersection = null;
|
|
5084
5101
|
this.resize = null;
|
|
5085
5102
|
this.intersecting = false;
|
|
5086
5103
|
this.gapIntersection = null;
|
|
5087
5104
|
this.gaps = [];
|
|
5088
|
-
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5089
|
-
this._selectionRange = null;
|
|
5090
5105
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
5091
5106
|
this.parentCheck = -1;
|
|
5092
5107
|
this.dom = view.contentDOM;
|
|
5093
5108
|
this.observer = new MutationObserver(mutations => {
|
|
5094
5109
|
for (let mut of mutations)
|
|
5095
5110
|
this.queue.push(mut);
|
|
5096
|
-
this._selectionRange = null;
|
|
5097
5111
|
// IE11 will sometimes (on typing over a selection or
|
|
5098
5112
|
// backspacing out a single character text node) call the
|
|
5099
5113
|
// observer callback before actually updating the DOM.
|
|
@@ -5120,8 +5134,11 @@ class DOMObserver {
|
|
|
5120
5134
|
this.onSelectionChange = this.onSelectionChange.bind(this);
|
|
5121
5135
|
if (typeof ResizeObserver == "function") {
|
|
5122
5136
|
this.resize = new ResizeObserver(() => {
|
|
5123
|
-
if (this.view.docView.lastUpdate < Date.now() -
|
|
5124
|
-
this.
|
|
5137
|
+
if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
|
|
5138
|
+
this.resizeTimeout = setTimeout(() => {
|
|
5139
|
+
this.resizeTimeout = -1;
|
|
5140
|
+
this.view.requestMeasure();
|
|
5141
|
+
}, 50);
|
|
5125
5142
|
});
|
|
5126
5143
|
this.resize.observe(view.scrollDOM);
|
|
5127
5144
|
}
|
|
@@ -5145,10 +5162,12 @@ class DOMObserver {
|
|
|
5145
5162
|
}, {});
|
|
5146
5163
|
}
|
|
5147
5164
|
this.listenForScroll();
|
|
5165
|
+
this.readSelectionRange();
|
|
5166
|
+
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5148
5167
|
}
|
|
5149
5168
|
onScroll(e) {
|
|
5150
5169
|
if (this.intersecting)
|
|
5151
|
-
this.flush();
|
|
5170
|
+
this.flush(false);
|
|
5152
5171
|
this.onScrollChanged(e);
|
|
5153
5172
|
}
|
|
5154
5173
|
updateGaps(gaps) {
|
|
@@ -5160,8 +5179,8 @@ class DOMObserver {
|
|
|
5160
5179
|
}
|
|
5161
5180
|
}
|
|
5162
5181
|
onSelectionChange(event) {
|
|
5163
|
-
if (this.
|
|
5164
|
-
|
|
5182
|
+
if (!this.readSelectionRange())
|
|
5183
|
+
return;
|
|
5165
5184
|
let { view } = this, sel = this.selectionRange;
|
|
5166
5185
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
5167
5186
|
return;
|
|
@@ -5176,24 +5195,22 @@ class DOMObserver {
|
|
|
5176
5195
|
sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
|
|
5177
5196
|
this.flushSoon();
|
|
5178
5197
|
else
|
|
5179
|
-
this.flush();
|
|
5180
|
-
}
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
return this.
|
|
5198
|
+
this.flush(false);
|
|
5199
|
+
}
|
|
5200
|
+
readSelectionRange() {
|
|
5201
|
+
let { root } = this.view, domSel = getSelection(root);
|
|
5202
|
+
// The Selection object is broken in shadow roots in Safari. See
|
|
5203
|
+
// https://github.com/codemirror/codemirror.next/issues/414
|
|
5204
|
+
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
|
|
5205
|
+
safariSelectionRangeHack(this.view) || domSel;
|
|
5206
|
+
if (this.selectionRange.eq(range))
|
|
5207
|
+
return false;
|
|
5208
|
+
this.selectionRange.setRange(range);
|
|
5209
|
+
return this.selectionChanged = true;
|
|
5191
5210
|
}
|
|
5192
5211
|
setSelectionRange(anchor, head) {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
|
|
5196
|
-
focusNode: head.node, focusOffset: head.offset };
|
|
5212
|
+
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
5213
|
+
this.selectionChanged = false;
|
|
5197
5214
|
}
|
|
5198
5215
|
listenForScroll() {
|
|
5199
5216
|
this.parentCheck = -1;
|
|
@@ -5240,7 +5257,6 @@ class DOMObserver {
|
|
|
5240
5257
|
if (this.active)
|
|
5241
5258
|
return;
|
|
5242
5259
|
this.observer.observe(this.dom, observeOptions);
|
|
5243
|
-
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5244
5260
|
if (useCharData)
|
|
5245
5261
|
this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5246
5262
|
this.active = true;
|
|
@@ -5250,18 +5266,14 @@ class DOMObserver {
|
|
|
5250
5266
|
return;
|
|
5251
5267
|
this.active = false;
|
|
5252
5268
|
this.observer.disconnect();
|
|
5253
|
-
this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
|
|
5254
5269
|
if (useCharData)
|
|
5255
5270
|
this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5256
5271
|
}
|
|
5257
|
-
clearSelection() {
|
|
5258
|
-
this.ignoreSelection.set(this.selectionRange);
|
|
5259
|
-
}
|
|
5260
5272
|
// Throw away any pending changes
|
|
5261
5273
|
clear() {
|
|
5262
5274
|
this.observer.takeRecords();
|
|
5263
5275
|
this.queue.length = 0;
|
|
5264
|
-
this.
|
|
5276
|
+
this.selectionChanged = false;
|
|
5265
5277
|
}
|
|
5266
5278
|
flushSoon() {
|
|
5267
5279
|
if (this.delayedFlush < 0)
|
|
@@ -5298,24 +5310,24 @@ class DOMObserver {
|
|
|
5298
5310
|
return { from, to, typeOver };
|
|
5299
5311
|
}
|
|
5300
5312
|
// Apply pending changes, if any
|
|
5301
|
-
flush() {
|
|
5313
|
+
flush(readSelection = true) {
|
|
5314
|
+
if (readSelection)
|
|
5315
|
+
this.readSelectionRange();
|
|
5302
5316
|
// Completely hold off flushing when pending keys are set—the code
|
|
5303
5317
|
// managing those will make sure processRecords is called and the
|
|
5304
5318
|
// view is resynchronized after
|
|
5305
5319
|
if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
|
|
5306
5320
|
return;
|
|
5307
|
-
this.lastFlush = Date.now();
|
|
5308
5321
|
let { from, to, typeOver } = this.processRecords();
|
|
5309
|
-
let
|
|
5310
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5322
|
+
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5311
5323
|
if (from < 0 && !newSel)
|
|
5312
5324
|
return;
|
|
5325
|
+
this.selectionChanged = false;
|
|
5313
5326
|
let startState = this.view.state;
|
|
5314
5327
|
this.onChange(from, to, typeOver);
|
|
5315
5328
|
// The view wasn't updated
|
|
5316
5329
|
if (this.view.state == startState)
|
|
5317
5330
|
this.view.docView.reset(newSel);
|
|
5318
|
-
this.clearSelection();
|
|
5319
5331
|
}
|
|
5320
5332
|
readMutation(rec) {
|
|
5321
5333
|
let cView = this.view.docView.nearest(rec.target);
|
|
@@ -5347,6 +5359,7 @@ class DOMObserver {
|
|
|
5347
5359
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5348
5360
|
window.removeEventListener("scroll", this.onScroll);
|
|
5349
5361
|
clearTimeout(this.parentCheck);
|
|
5362
|
+
clearTimeout(this.resizeTimeout);
|
|
5350
5363
|
}
|
|
5351
5364
|
}
|
|
5352
5365
|
function findChild(cView, dom, dir) {
|
|
@@ -5359,6 +5372,7 @@ function findChild(cView, dom, dir) {
|
|
|
5359
5372
|
}
|
|
5360
5373
|
return null;
|
|
5361
5374
|
}
|
|
5375
|
+
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5362
5376
|
function safariSelectionRangeHack(view) {
|
|
5363
5377
|
let found = null;
|
|
5364
5378
|
// Because Safari (at least in 2018-2021) doesn't provide regular
|
|
@@ -5778,6 +5792,7 @@ class EditorView {
|
|
|
5778
5792
|
this.mountStyles();
|
|
5779
5793
|
this.updateAttrs();
|
|
5780
5794
|
this.showAnnouncements(transactions);
|
|
5795
|
+
this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
|
|
5781
5796
|
}
|
|
5782
5797
|
finally {
|
|
5783
5798
|
this.updateState = 0 /* Idle */;
|
|
@@ -5895,8 +5910,7 @@ class EditorView {
|
|
|
5895
5910
|
this.inputState.update(update);
|
|
5896
5911
|
}
|
|
5897
5912
|
this.updateAttrs();
|
|
5898
|
-
|
|
5899
|
-
this.docView.update(update);
|
|
5913
|
+
let redrawn = changed > 0 && this.docView.update(update);
|
|
5900
5914
|
for (let i = 0; i < measuring.length; i++)
|
|
5901
5915
|
if (measured[i] != BadMeasure) {
|
|
5902
5916
|
try {
|
|
@@ -5910,6 +5924,8 @@ class EditorView {
|
|
|
5910
5924
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5911
5925
|
this.viewState.scrollTarget = null;
|
|
5912
5926
|
}
|
|
5927
|
+
if (changed)
|
|
5928
|
+
this.docView.updateSelection(redrawn);
|
|
5913
5929
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5914
5930
|
break;
|
|
5915
5931
|
}
|
|
@@ -5934,8 +5950,6 @@ class EditorView {
|
|
|
5934
5950
|
let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
|
|
5935
5951
|
class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
|
|
5936
5952
|
});
|
|
5937
|
-
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5938
|
-
this.editorAttrs = editorAttrs;
|
|
5939
5953
|
let contentAttrs = {
|
|
5940
5954
|
spellcheck: "false",
|
|
5941
5955
|
autocorrect: "off",
|
|
@@ -5950,7 +5964,11 @@ class EditorView {
|
|
|
5950
5964
|
if (this.state.readOnly)
|
|
5951
5965
|
contentAttrs["aria-readonly"] = "true";
|
|
5952
5966
|
combineAttrs(this.state.facet(contentAttributes), contentAttrs);
|
|
5953
|
-
|
|
5967
|
+
this.observer.ignore(() => {
|
|
5968
|
+
updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
|
|
5969
|
+
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5970
|
+
});
|
|
5971
|
+
this.editorAttrs = editorAttrs;
|
|
5954
5972
|
this.contentAttrs = contentAttrs;
|
|
5955
5973
|
}
|
|
5956
5974
|
showAnnouncements(trs) {
|
package/dist/index.js
CHANGED
|
@@ -183,7 +183,7 @@ function scrollRectIntoView(dom, rect, side, center) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
class
|
|
186
|
+
class DOMSelectionState {
|
|
187
187
|
constructor() {
|
|
188
188
|
this.anchorNode = null;
|
|
189
189
|
this.anchorOffset = 0;
|
|
@@ -194,11 +194,14 @@ class DOMSelection {
|
|
|
194
194
|
return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
|
|
195
195
|
this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
this.anchorNode
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.
|
|
197
|
+
setRange(range) {
|
|
198
|
+
this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
|
|
199
|
+
}
|
|
200
|
+
set(anchorNode, anchorOffset, focusNode, focusOffset) {
|
|
201
|
+
this.anchorNode = anchorNode;
|
|
202
|
+
this.anchorOffset = anchorOffset;
|
|
203
|
+
this.focusNode = focusNode;
|
|
204
|
+
this.focusOffset = focusOffset;
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
let preventScrollSupported = null;
|
|
@@ -1939,9 +1942,10 @@ class DocView extends ContentView {
|
|
|
1939
1942
|
// we don't mess it up when reading it back it
|
|
1940
1943
|
this.impreciseAnchor = null;
|
|
1941
1944
|
this.impreciseHead = null;
|
|
1945
|
+
this.forceSelection = false;
|
|
1942
1946
|
// Used by the resize observer to ignore resizes that we caused
|
|
1943
1947
|
// ourselves
|
|
1944
|
-
this.lastUpdate =
|
|
1948
|
+
this.lastUpdate = Date.now();
|
|
1945
1949
|
this.setDOM(view.contentDOM);
|
|
1946
1950
|
this.children = [new LineView];
|
|
1947
1951
|
this.children[0].setParent(this);
|
|
@@ -1955,7 +1959,6 @@ class DocView extends ContentView {
|
|
|
1955
1959
|
// position, if we know the editor is going to scroll that position
|
|
1956
1960
|
// into view.
|
|
1957
1961
|
update(update) {
|
|
1958
|
-
this.lastUpdate = Date.now();
|
|
1959
1962
|
let changedRanges = update.changedRanges;
|
|
1960
1963
|
if (this.minWidth > 0 && changedRanges.length) {
|
|
1961
1964
|
if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
|
|
@@ -1975,21 +1978,22 @@ class DocView extends ContentView {
|
|
|
1975
1978
|
// getSelection than the one that it actually shows to the user.
|
|
1976
1979
|
// This forces a selection update when lines are joined to work
|
|
1977
1980
|
// around that. Issue #54
|
|
1978
|
-
|
|
1979
|
-
update.state.doc.lines != update.startState.doc.lines
|
|
1981
|
+
if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
|
|
1982
|
+
update.state.doc.lines != update.startState.doc.lines)
|
|
1983
|
+
this.forceSelection = true;
|
|
1980
1984
|
let prevDeco = this.decorations, deco = this.updateDeco();
|
|
1981
1985
|
let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
|
|
1982
1986
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|
1983
|
-
let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
|
|
1984
1987
|
if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
|
|
1985
1988
|
!(update.flags & 4 /* Viewport */) &&
|
|
1986
1989
|
update.state.selection.main.from >= this.view.viewport.from &&
|
|
1987
1990
|
update.state.selection.main.to <= this.view.viewport.to) {
|
|
1988
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
1989
1991
|
return false;
|
|
1990
1992
|
}
|
|
1991
1993
|
else {
|
|
1992
|
-
this.updateInner(changedRanges, deco, update.startState.doc.length
|
|
1994
|
+
this.updateInner(changedRanges, deco, update.startState.doc.length);
|
|
1995
|
+
if (update.transactions.length)
|
|
1996
|
+
this.lastUpdate = Date.now();
|
|
1993
1997
|
return true;
|
|
1994
1998
|
}
|
|
1995
1999
|
}
|
|
@@ -1997,13 +2001,15 @@ class DocView extends ContentView {
|
|
|
1997
2001
|
if (this.dirty) {
|
|
1998
2002
|
this.view.observer.ignore(() => this.view.docView.sync());
|
|
1999
2003
|
this.dirty = 0 /* Not */;
|
|
2004
|
+
this.updateSelection(true);
|
|
2000
2005
|
}
|
|
2001
|
-
|
|
2006
|
+
else {
|
|
2002
2007
|
this.updateSelection();
|
|
2008
|
+
}
|
|
2003
2009
|
}
|
|
2004
2010
|
// Used both by update and checkLayout do perform the actual DOM
|
|
2005
2011
|
// update
|
|
2006
|
-
updateInner(changes, deco, oldLength
|
|
2012
|
+
updateInner(changes, deco, oldLength) {
|
|
2007
2013
|
this.updateChildren(changes, deco, oldLength);
|
|
2008
2014
|
let { observer } = this.view;
|
|
2009
2015
|
observer.ignore(() => {
|
|
@@ -2021,8 +2027,7 @@ class DocView extends ContentView {
|
|
|
2021
2027
|
this.sync(track);
|
|
2022
2028
|
this.dirty = 0 /* Not */;
|
|
2023
2029
|
if (track && (track.written || observer.selectionRange.focusNode != track.node))
|
|
2024
|
-
forceSelection = true;
|
|
2025
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
2030
|
+
this.forceSelection = true;
|
|
2026
2031
|
this.dom.style.height = "";
|
|
2027
2032
|
});
|
|
2028
2033
|
let gaps = [];
|
|
@@ -2108,10 +2113,14 @@ class DocView extends ContentView {
|
|
|
2108
2113
|
this.replaceChildren(fromI, toI, content);
|
|
2109
2114
|
}
|
|
2110
2115
|
// Sync the DOM selection to this.state.selection
|
|
2111
|
-
updateSelection(
|
|
2116
|
+
updateSelection(mustRead = false, fromPointer = false) {
|
|
2117
|
+
if (mustRead)
|
|
2118
|
+
this.view.observer.readSelectionRange();
|
|
2112
2119
|
if (!(fromPointer || this.mayControlSelection()) ||
|
|
2113
2120
|
browser.ios && this.view.inputState.rapidCompositionStart)
|
|
2114
2121
|
return;
|
|
2122
|
+
let force = this.forceSelection;
|
|
2123
|
+
this.forceSelection = false;
|
|
2115
2124
|
let main = this.view.state.selection.main;
|
|
2116
2125
|
// FIXME need to handle the case where the selection falls inside a block range
|
|
2117
2126
|
let anchor = this.domAtPos(main.anchor);
|
|
@@ -5069,25 +5078,30 @@ class DOMObserver {
|
|
|
5069
5078
|
this.onChange = onChange;
|
|
5070
5079
|
this.onScrollChanged = onScrollChanged;
|
|
5071
5080
|
this.active = false;
|
|
5072
|
-
|
|
5081
|
+
// The known selection. Kept in our own object, as opposed to just
|
|
5082
|
+
// directly accessing the selection because:
|
|
5083
|
+
// - Safari doesn't report the right selection in shadow DOM
|
|
5084
|
+
// - Reading from the selection forces a DOM layout
|
|
5085
|
+
// - This way, we can ignore selectionchange events if we have
|
|
5086
|
+
// already seen the 'new' selection
|
|
5087
|
+
this.selectionRange = new DOMSelectionState;
|
|
5088
|
+
// Set when a selection change is detected, cleared on flush
|
|
5089
|
+
this.selectionChanged = false;
|
|
5073
5090
|
this.delayedFlush = -1;
|
|
5091
|
+
this.resizeTimeout = -1;
|
|
5074
5092
|
this.queue = [];
|
|
5075
|
-
this.lastFlush = 0;
|
|
5076
5093
|
this.scrollTargets = [];
|
|
5077
5094
|
this.intersection = null;
|
|
5078
5095
|
this.resize = null;
|
|
5079
5096
|
this.intersecting = false;
|
|
5080
5097
|
this.gapIntersection = null;
|
|
5081
5098
|
this.gaps = [];
|
|
5082
|
-
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5083
|
-
this._selectionRange = null;
|
|
5084
5099
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
5085
5100
|
this.parentCheck = -1;
|
|
5086
5101
|
this.dom = view.contentDOM;
|
|
5087
5102
|
this.observer = new MutationObserver(mutations => {
|
|
5088
5103
|
for (let mut of mutations)
|
|
5089
5104
|
this.queue.push(mut);
|
|
5090
|
-
this._selectionRange = null;
|
|
5091
5105
|
// IE11 will sometimes (on typing over a selection or
|
|
5092
5106
|
// backspacing out a single character text node) call the
|
|
5093
5107
|
// observer callback before actually updating the DOM.
|
|
@@ -5114,8 +5128,11 @@ class DOMObserver {
|
|
|
5114
5128
|
this.onSelectionChange = this.onSelectionChange.bind(this);
|
|
5115
5129
|
if (typeof ResizeObserver == "function") {
|
|
5116
5130
|
this.resize = new ResizeObserver(() => {
|
|
5117
|
-
if (this.view.docView.lastUpdate < Date.now() -
|
|
5118
|
-
this.
|
|
5131
|
+
if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
|
|
5132
|
+
this.resizeTimeout = setTimeout(() => {
|
|
5133
|
+
this.resizeTimeout = -1;
|
|
5134
|
+
this.view.requestMeasure();
|
|
5135
|
+
}, 50);
|
|
5119
5136
|
});
|
|
5120
5137
|
this.resize.observe(view.scrollDOM);
|
|
5121
5138
|
}
|
|
@@ -5139,10 +5156,12 @@ class DOMObserver {
|
|
|
5139
5156
|
}, {});
|
|
5140
5157
|
}
|
|
5141
5158
|
this.listenForScroll();
|
|
5159
|
+
this.readSelectionRange();
|
|
5160
|
+
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5142
5161
|
}
|
|
5143
5162
|
onScroll(e) {
|
|
5144
5163
|
if (this.intersecting)
|
|
5145
|
-
this.flush();
|
|
5164
|
+
this.flush(false);
|
|
5146
5165
|
this.onScrollChanged(e);
|
|
5147
5166
|
}
|
|
5148
5167
|
updateGaps(gaps) {
|
|
@@ -5154,8 +5173,8 @@ class DOMObserver {
|
|
|
5154
5173
|
}
|
|
5155
5174
|
}
|
|
5156
5175
|
onSelectionChange(event) {
|
|
5157
|
-
if (this.
|
|
5158
|
-
|
|
5176
|
+
if (!this.readSelectionRange())
|
|
5177
|
+
return;
|
|
5159
5178
|
let { view } = this, sel = this.selectionRange;
|
|
5160
5179
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
5161
5180
|
return;
|
|
@@ -5170,24 +5189,22 @@ class DOMObserver {
|
|
|
5170
5189
|
sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
|
|
5171
5190
|
this.flushSoon();
|
|
5172
5191
|
else
|
|
5173
|
-
this.flush();
|
|
5174
|
-
}
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
return this.
|
|
5192
|
+
this.flush(false);
|
|
5193
|
+
}
|
|
5194
|
+
readSelectionRange() {
|
|
5195
|
+
let { root } = this.view, domSel = getSelection(root);
|
|
5196
|
+
// The Selection object is broken in shadow roots in Safari. See
|
|
5197
|
+
// https://github.com/codemirror/codemirror.next/issues/414
|
|
5198
|
+
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
|
|
5199
|
+
safariSelectionRangeHack(this.view) || domSel;
|
|
5200
|
+
if (this.selectionRange.eq(range))
|
|
5201
|
+
return false;
|
|
5202
|
+
this.selectionRange.setRange(range);
|
|
5203
|
+
return this.selectionChanged = true;
|
|
5185
5204
|
}
|
|
5186
5205
|
setSelectionRange(anchor, head) {
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
|
|
5190
|
-
focusNode: head.node, focusOffset: head.offset };
|
|
5206
|
+
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
5207
|
+
this.selectionChanged = false;
|
|
5191
5208
|
}
|
|
5192
5209
|
listenForScroll() {
|
|
5193
5210
|
this.parentCheck = -1;
|
|
@@ -5234,7 +5251,6 @@ class DOMObserver {
|
|
|
5234
5251
|
if (this.active)
|
|
5235
5252
|
return;
|
|
5236
5253
|
this.observer.observe(this.dom, observeOptions);
|
|
5237
|
-
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5238
5254
|
if (useCharData)
|
|
5239
5255
|
this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5240
5256
|
this.active = true;
|
|
@@ -5244,18 +5260,14 @@ class DOMObserver {
|
|
|
5244
5260
|
return;
|
|
5245
5261
|
this.active = false;
|
|
5246
5262
|
this.observer.disconnect();
|
|
5247
|
-
this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
|
|
5248
5263
|
if (useCharData)
|
|
5249
5264
|
this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5250
5265
|
}
|
|
5251
|
-
clearSelection() {
|
|
5252
|
-
this.ignoreSelection.set(this.selectionRange);
|
|
5253
|
-
}
|
|
5254
5266
|
// Throw away any pending changes
|
|
5255
5267
|
clear() {
|
|
5256
5268
|
this.observer.takeRecords();
|
|
5257
5269
|
this.queue.length = 0;
|
|
5258
|
-
this.
|
|
5270
|
+
this.selectionChanged = false;
|
|
5259
5271
|
}
|
|
5260
5272
|
flushSoon() {
|
|
5261
5273
|
if (this.delayedFlush < 0)
|
|
@@ -5292,24 +5304,24 @@ class DOMObserver {
|
|
|
5292
5304
|
return { from, to, typeOver };
|
|
5293
5305
|
}
|
|
5294
5306
|
// Apply pending changes, if any
|
|
5295
|
-
flush() {
|
|
5307
|
+
flush(readSelection = true) {
|
|
5308
|
+
if (readSelection)
|
|
5309
|
+
this.readSelectionRange();
|
|
5296
5310
|
// Completely hold off flushing when pending keys are set—the code
|
|
5297
5311
|
// managing those will make sure processRecords is called and the
|
|
5298
5312
|
// view is resynchronized after
|
|
5299
5313
|
if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
|
|
5300
5314
|
return;
|
|
5301
|
-
this.lastFlush = Date.now();
|
|
5302
5315
|
let { from, to, typeOver } = this.processRecords();
|
|
5303
|
-
let
|
|
5304
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5316
|
+
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5305
5317
|
if (from < 0 && !newSel)
|
|
5306
5318
|
return;
|
|
5319
|
+
this.selectionChanged = false;
|
|
5307
5320
|
let startState = this.view.state;
|
|
5308
5321
|
this.onChange(from, to, typeOver);
|
|
5309
5322
|
// The view wasn't updated
|
|
5310
5323
|
if (this.view.state == startState)
|
|
5311
5324
|
this.view.docView.reset(newSel);
|
|
5312
|
-
this.clearSelection();
|
|
5313
5325
|
}
|
|
5314
5326
|
readMutation(rec) {
|
|
5315
5327
|
let cView = this.view.docView.nearest(rec.target);
|
|
@@ -5341,6 +5353,7 @@ class DOMObserver {
|
|
|
5341
5353
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5342
5354
|
window.removeEventListener("scroll", this.onScroll);
|
|
5343
5355
|
clearTimeout(this.parentCheck);
|
|
5356
|
+
clearTimeout(this.resizeTimeout);
|
|
5344
5357
|
}
|
|
5345
5358
|
}
|
|
5346
5359
|
function findChild(cView, dom, dir) {
|
|
@@ -5353,6 +5366,7 @@ function findChild(cView, dom, dir) {
|
|
|
5353
5366
|
}
|
|
5354
5367
|
return null;
|
|
5355
5368
|
}
|
|
5369
|
+
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5356
5370
|
function safariSelectionRangeHack(view) {
|
|
5357
5371
|
let found = null;
|
|
5358
5372
|
// Because Safari (at least in 2018-2021) doesn't provide regular
|
|
@@ -5772,6 +5786,7 @@ class EditorView {
|
|
|
5772
5786
|
this.mountStyles();
|
|
5773
5787
|
this.updateAttrs();
|
|
5774
5788
|
this.showAnnouncements(transactions);
|
|
5789
|
+
this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
|
|
5775
5790
|
}
|
|
5776
5791
|
finally {
|
|
5777
5792
|
this.updateState = 0 /* Idle */;
|
|
@@ -5889,8 +5904,7 @@ class EditorView {
|
|
|
5889
5904
|
this.inputState.update(update);
|
|
5890
5905
|
}
|
|
5891
5906
|
this.updateAttrs();
|
|
5892
|
-
|
|
5893
|
-
this.docView.update(update);
|
|
5907
|
+
let redrawn = changed > 0 && this.docView.update(update);
|
|
5894
5908
|
for (let i = 0; i < measuring.length; i++)
|
|
5895
5909
|
if (measured[i] != BadMeasure) {
|
|
5896
5910
|
try {
|
|
@@ -5904,6 +5918,8 @@ class EditorView {
|
|
|
5904
5918
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5905
5919
|
this.viewState.scrollTarget = null;
|
|
5906
5920
|
}
|
|
5921
|
+
if (changed)
|
|
5922
|
+
this.docView.updateSelection(redrawn);
|
|
5907
5923
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5908
5924
|
break;
|
|
5909
5925
|
}
|
|
@@ -5928,8 +5944,6 @@ class EditorView {
|
|
|
5928
5944
|
let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
|
|
5929
5945
|
class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
|
|
5930
5946
|
});
|
|
5931
|
-
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5932
|
-
this.editorAttrs = editorAttrs;
|
|
5933
5947
|
let contentAttrs = {
|
|
5934
5948
|
spellcheck: "false",
|
|
5935
5949
|
autocorrect: "off",
|
|
@@ -5944,7 +5958,11 @@ class EditorView {
|
|
|
5944
5958
|
if (this.state.readOnly)
|
|
5945
5959
|
contentAttrs["aria-readonly"] = "true";
|
|
5946
5960
|
combineAttrs(this.state.facet(contentAttributes), contentAttrs);
|
|
5947
|
-
|
|
5961
|
+
this.observer.ignore(() => {
|
|
5962
|
+
updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
|
|
5963
|
+
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5964
|
+
});
|
|
5965
|
+
this.editorAttrs = editorAttrs;
|
|
5948
5966
|
this.contentAttrs = contentAttrs;
|
|
5949
5967
|
}
|
|
5950
5968
|
showAnnouncements(trs) {
|