@codemirror/view 6.0.3 → 6.1.2
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 +24 -0
- package/dist/index.cjs +91 -25
- package/dist/index.d.ts +13 -2
- package/dist/index.js +91 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## 6.1.2 (2022-07-27)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix an issue where double tapping enter to confirm IME input and insert a newline on iOS would sometimes insert two newlines.
|
|
6
|
+
|
|
7
|
+
Fix an issue on iOS where a composition could get aborted if the editor scrolled on backspace.
|
|
8
|
+
|
|
9
|
+
## 6.1.1 (2022-07-25)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Make `highlightSpecialChars` replace directional isolate characters by default.
|
|
14
|
+
|
|
15
|
+
The editor will now try to suppress browsers' native behavior of resetting the selection in the editable content when the editable element is focused (programmatically, with tab, etc).
|
|
16
|
+
|
|
17
|
+
Fix a CSS issue that made it possible, when the gutters were wide enough, for them to overlap with the content.
|
|
18
|
+
|
|
19
|
+
## 6.1.0 (2022-07-19)
|
|
20
|
+
|
|
21
|
+
### New features
|
|
22
|
+
|
|
23
|
+
`MatchDecorator` now supports a `decorate` option that can be used to customize the way decorations are added for each match.
|
|
24
|
+
|
|
1
25
|
## 6.0.3 (2022-07-08)
|
|
2
26
|
|
|
3
27
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -268,6 +268,31 @@ function clearAttributes(node) {
|
|
|
268
268
|
while (node.attributes.length)
|
|
269
269
|
node.removeAttributeNode(node.attributes[0]);
|
|
270
270
|
}
|
|
271
|
+
function atElementStart(doc, selection) {
|
|
272
|
+
let node = selection.focusNode, offset = selection.focusOffset;
|
|
273
|
+
if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
|
|
274
|
+
return false;
|
|
275
|
+
for (;;) {
|
|
276
|
+
if (offset) {
|
|
277
|
+
if (node.nodeType != 1)
|
|
278
|
+
return false;
|
|
279
|
+
let prev = node.childNodes[offset - 1];
|
|
280
|
+
if (prev.contentEditable == "false")
|
|
281
|
+
offset--;
|
|
282
|
+
else {
|
|
283
|
+
node = prev;
|
|
284
|
+
offset = maxOffset(node);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (node == doc) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
offset = domIndex(node);
|
|
292
|
+
node = node.parentNode;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
271
296
|
|
|
272
297
|
class DOMPos {
|
|
273
298
|
constructor(node, offset, precise = true) {
|
|
@@ -3289,6 +3314,10 @@ class InputState {
|
|
|
3289
3314
|
constructor(view) {
|
|
3290
3315
|
this.lastKeyCode = 0;
|
|
3291
3316
|
this.lastKeyTime = 0;
|
|
3317
|
+
this.lastTouchTime = 0;
|
|
3318
|
+
this.lastFocusTime = 0;
|
|
3319
|
+
this.lastScrollTop = 0;
|
|
3320
|
+
this.lastScrollLeft = 0;
|
|
3292
3321
|
this.chromeScrollHack = -1;
|
|
3293
3322
|
// On iOS, some keys need to have their default behavior happen
|
|
3294
3323
|
// (after which we retroactively handle them and reset the DOM) to
|
|
@@ -3327,10 +3356,10 @@ class InputState {
|
|
|
3327
3356
|
event.preventDefault();
|
|
3328
3357
|
else
|
|
3329
3358
|
handler(view, event);
|
|
3330
|
-
});
|
|
3359
|
+
}, handlerOptions[type]);
|
|
3331
3360
|
this.registeredEvents.push(type);
|
|
3332
3361
|
}
|
|
3333
|
-
if (browser.chrome && browser.chrome_version
|
|
3362
|
+
if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
|
|
3334
3363
|
// On Chrome 102, viewport updates somehow stop wheel-based
|
|
3335
3364
|
// scrolling. Turning off pointer events during the scroll seems
|
|
3336
3365
|
// to avoid the issue.
|
|
@@ -3390,6 +3419,8 @@ class InputState {
|
|
|
3390
3419
|
return false;
|
|
3391
3420
|
}
|
|
3392
3421
|
runScrollHandlers(view, event) {
|
|
3422
|
+
this.lastScrollTop = view.scrollDOM.scrollTop;
|
|
3423
|
+
this.lastScrollLeft = view.scrollDOM.scrollLeft;
|
|
3393
3424
|
for (let set of this.customHandlers) {
|
|
3394
3425
|
let handler = set.handlers.scroll;
|
|
3395
3426
|
if (handler) {
|
|
@@ -3450,7 +3481,7 @@ class InputState {
|
|
|
3450
3481
|
// compositionend and keydown events are sometimes emitted in the
|
|
3451
3482
|
// wrong order. The key event should still be ignored, even when
|
|
3452
3483
|
// it happens after the compositionend event.
|
|
3453
|
-
if (browser.safari && Date.now() - this.compositionEndedAt < 100) {
|
|
3484
|
+
if (browser.safari && !browser.ios && Date.now() - this.compositionEndedAt < 100) {
|
|
3454
3485
|
this.compositionEndedAt = 0;
|
|
3455
3486
|
return true;
|
|
3456
3487
|
}
|
|
@@ -3578,6 +3609,7 @@ function eventBelongsToEditor(view, event) {
|
|
|
3578
3609
|
return true;
|
|
3579
3610
|
}
|
|
3580
3611
|
const handlers = Object.create(null);
|
|
3612
|
+
const handlerOptions = Object.create(null);
|
|
3581
3613
|
// This is very crude, but unfortunately both these browsers _pretend_
|
|
3582
3614
|
// that they have a clipboard API—all the objects and methods are
|
|
3583
3615
|
// there, they just don't work, and they are hard to test.
|
|
@@ -3634,17 +3666,17 @@ handlers.keydown = (view, event) => {
|
|
|
3634
3666
|
else if (modifierCodes.indexOf(event.keyCode) < 0)
|
|
3635
3667
|
view.inputState.lastEscPress = 0;
|
|
3636
3668
|
};
|
|
3637
|
-
let lastTouch = 0;
|
|
3638
3669
|
handlers.touchstart = (view, e) => {
|
|
3639
|
-
|
|
3670
|
+
view.inputState.lastTouchTime = Date.now();
|
|
3640
3671
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3641
3672
|
};
|
|
3642
3673
|
handlers.touchmove = view => {
|
|
3643
3674
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3644
3675
|
};
|
|
3676
|
+
handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
|
|
3645
3677
|
handlers.mousedown = (view, event) => {
|
|
3646
3678
|
view.observer.flush();
|
|
3647
|
-
if (
|
|
3679
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
|
|
3648
3680
|
return; // Ignore touch interaction
|
|
3649
3681
|
let style = null;
|
|
3650
3682
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -3898,7 +3930,15 @@ function updateForFocusChange(view) {
|
|
|
3898
3930
|
view.update([]);
|
|
3899
3931
|
}, 10);
|
|
3900
3932
|
}
|
|
3901
|
-
handlers.focus =
|
|
3933
|
+
handlers.focus = view => {
|
|
3934
|
+
view.inputState.lastFocusTime = Date.now();
|
|
3935
|
+
// When focusing reset the scroll position, move it back to where it was
|
|
3936
|
+
if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
|
|
3937
|
+
view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
|
|
3938
|
+
view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
|
|
3939
|
+
}
|
|
3940
|
+
updateForFocusChange(view);
|
|
3941
|
+
};
|
|
3902
3942
|
handlers.blur = view => {
|
|
3903
3943
|
view.observer.clearSelectionRange();
|
|
3904
3944
|
updateForFocusChange(view);
|
|
@@ -5269,6 +5309,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
|
|
|
5269
5309
|
"&light .cm-specialChar": { color: "red" },
|
|
5270
5310
|
"&dark .cm-specialChar": { color: "#f78" },
|
|
5271
5311
|
".cm-gutters": {
|
|
5312
|
+
flexShrink: 0,
|
|
5272
5313
|
display: "flex",
|
|
5273
5314
|
height: "100%",
|
|
5274
5315
|
boxSizing: "border-box",
|
|
@@ -5525,15 +5566,28 @@ class DOMObserver {
|
|
|
5525
5566
|
this.flush(false);
|
|
5526
5567
|
}
|
|
5527
5568
|
readSelectionRange() {
|
|
5528
|
-
let {
|
|
5569
|
+
let { view } = this;
|
|
5529
5570
|
// The Selection object is broken in shadow roots in Safari. See
|
|
5530
5571
|
// https://github.com/codemirror/dev/issues/414
|
|
5531
|
-
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.
|
|
5532
|
-
safariSelectionRangeHack(this.view) || getSelection(root);
|
|
5572
|
+
let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
|
|
5573
|
+
safariSelectionRangeHack(this.view) || getSelection(view.root);
|
|
5533
5574
|
if (!range || this.selectionRange.eq(range))
|
|
5534
5575
|
return false;
|
|
5576
|
+
let local = hasSelection(this.dom, range);
|
|
5577
|
+
// Detect the situation where the browser has, on focus, moved the
|
|
5578
|
+
// selection to the start of the content element. Reset it to the
|
|
5579
|
+
// position from the editor state.
|
|
5580
|
+
if (local && !this.selectionChanged && this.selectionRange.focusNode &&
|
|
5581
|
+
view.inputState.lastFocusTime > Date.now() - 200 &&
|
|
5582
|
+
view.inputState.lastTouchTime < Date.now() - 300 &&
|
|
5583
|
+
atElementStart(this.dom, range)) {
|
|
5584
|
+
view.docView.updateSelection();
|
|
5585
|
+
return false;
|
|
5586
|
+
}
|
|
5535
5587
|
this.selectionRange.setRange(range);
|
|
5536
|
-
|
|
5588
|
+
if (local)
|
|
5589
|
+
this.selectionChanged = true;
|
|
5590
|
+
return true;
|
|
5537
5591
|
}
|
|
5538
5592
|
setSelectionRange(anchor, head) {
|
|
5539
5593
|
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
@@ -5620,7 +5674,7 @@ class DOMObserver {
|
|
|
5620
5674
|
this.delayedAndroidKey = null;
|
|
5621
5675
|
this.delayedFlush = -1;
|
|
5622
5676
|
if (!this.flush())
|
|
5623
|
-
dispatchKey(this.
|
|
5677
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5624
5678
|
});
|
|
5625
5679
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5626
5680
|
// Enter always takes precedence.
|
|
@@ -5635,8 +5689,8 @@ class DOMObserver {
|
|
|
5635
5689
|
if (this.delayedFlush >= 0) {
|
|
5636
5690
|
window.clearTimeout(this.delayedFlush);
|
|
5637
5691
|
this.delayedFlush = -1;
|
|
5638
|
-
this.flush();
|
|
5639
5692
|
}
|
|
5693
|
+
this.flush();
|
|
5640
5694
|
}
|
|
5641
5695
|
processRecords() {
|
|
5642
5696
|
let records = this.queue;
|
|
@@ -5674,6 +5728,7 @@ class DOMObserver {
|
|
|
5674
5728
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5675
5729
|
if (from < 0 && !newSel)
|
|
5676
5730
|
return;
|
|
5731
|
+
this.view.inputState.lastFocusTime = 0;
|
|
5677
5732
|
this.selectionChanged = false;
|
|
5678
5733
|
let startState = this.view.state;
|
|
5679
5734
|
let handled = this.onChange(from, to, typeOver);
|
|
@@ -6227,7 +6282,7 @@ class EditorView {
|
|
|
6227
6282
|
cancelAnimationFrame(this.measureScheduled);
|
|
6228
6283
|
this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
|
|
6229
6284
|
if (flush)
|
|
6230
|
-
this.observer.
|
|
6285
|
+
this.observer.forceFlush();
|
|
6231
6286
|
let updated = null;
|
|
6232
6287
|
try {
|
|
6233
6288
|
for (let i = 0;; i++) {
|
|
@@ -7381,7 +7436,7 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
7381
7436
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
7382
7437
|
if (!cursor.lineBreak)
|
|
7383
7438
|
while (m = re.exec(cursor.value))
|
|
7384
|
-
f(pos + m.index,
|
|
7439
|
+
f(pos + m.index, m);
|
|
7385
7440
|
}
|
|
7386
7441
|
}
|
|
7387
7442
|
function matchRanges(view, maxLength) {
|
|
@@ -7411,11 +7466,20 @@ class MatchDecorator {
|
|
|
7411
7466
|
Create a decorator.
|
|
7412
7467
|
*/
|
|
7413
7468
|
constructor(config) {
|
|
7414
|
-
|
|
7469
|
+
const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
|
|
7415
7470
|
if (!regexp.global)
|
|
7416
7471
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
7417
7472
|
this.regexp = regexp;
|
|
7418
|
-
|
|
7473
|
+
if (decorate) {
|
|
7474
|
+
this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
|
|
7475
|
+
}
|
|
7476
|
+
else if (decoration) {
|
|
7477
|
+
let getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
7478
|
+
this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
|
|
7479
|
+
}
|
|
7480
|
+
else {
|
|
7481
|
+
throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
|
|
7482
|
+
}
|
|
7419
7483
|
this.boundary = boundary;
|
|
7420
7484
|
this.maxLength = maxLength;
|
|
7421
7485
|
}
|
|
@@ -7425,9 +7489,9 @@ class MatchDecorator {
|
|
|
7425
7489
|
plugin.
|
|
7426
7490
|
*/
|
|
7427
7491
|
createDeco(view) {
|
|
7428
|
-
let build = new state.RangeSetBuilder();
|
|
7492
|
+
let build = new state.RangeSetBuilder(), add = build.add.bind(build);
|
|
7429
7493
|
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
7430
|
-
iterMatches(view.state.doc, this.regexp, from, to, (
|
|
7494
|
+
iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
|
|
7431
7495
|
return build.finish();
|
|
7432
7496
|
}
|
|
7433
7497
|
/**
|
|
@@ -7469,15 +7533,14 @@ class MatchDecorator {
|
|
|
7469
7533
|
}
|
|
7470
7534
|
}
|
|
7471
7535
|
let ranges = [], m;
|
|
7536
|
+
let add = (from, to, deco) => ranges.push(deco.range(from, to));
|
|
7472
7537
|
if (fromLine == toLine) {
|
|
7473
7538
|
this.regexp.lastIndex = start - fromLine.from;
|
|
7474
|
-
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7475
|
-
|
|
7476
|
-
ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
|
|
7477
|
-
}
|
|
7539
|
+
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7540
|
+
this.addMatch(m, view, m.index + fromLine.from, add);
|
|
7478
7541
|
}
|
|
7479
7542
|
else {
|
|
7480
|
-
iterMatches(view.state.doc, this.regexp, start, end, (from,
|
|
7543
|
+
iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
|
|
7481
7544
|
}
|
|
7482
7545
|
deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
|
|
7483
7546
|
}
|
|
@@ -7487,7 +7550,7 @@ class MatchDecorator {
|
|
|
7487
7550
|
}
|
|
7488
7551
|
|
|
7489
7552
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
7490
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7553
|
+
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7491
7554
|
const Names = {
|
|
7492
7555
|
0: "null",
|
|
7493
7556
|
7: "bell",
|
|
@@ -7504,6 +7567,9 @@ const Names = {
|
|
|
7504
7567
|
8232: "line separator",
|
|
7505
7568
|
8237: "left-to-right override",
|
|
7506
7569
|
8238: "right-to-left override",
|
|
7570
|
+
8294: "left-to-right isolate",
|
|
7571
|
+
8295: "right-to-left isolate",
|
|
7572
|
+
8297: "pop directional isolate",
|
|
7507
7573
|
8233: "paragraph separator",
|
|
7508
7574
|
65279: "zero width no-break space",
|
|
7509
7575
|
65532: "object replacement"
|
package/dist/index.d.ts
CHANGED
|
@@ -1357,7 +1357,7 @@ represent a matching configuration.
|
|
|
1357
1357
|
*/
|
|
1358
1358
|
declare class MatchDecorator {
|
|
1359
1359
|
private regexp;
|
|
1360
|
-
private
|
|
1360
|
+
private addMatch;
|
|
1361
1361
|
private boundary;
|
|
1362
1362
|
private maxLength;
|
|
1363
1363
|
/**
|
|
@@ -1374,7 +1374,18 @@ declare class MatchDecorator {
|
|
|
1374
1374
|
The decoration to apply to matches, either directly or as a
|
|
1375
1375
|
function of the match.
|
|
1376
1376
|
*/
|
|
1377
|
-
decoration
|
|
1377
|
+
decoration?: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
|
|
1378
|
+
/**
|
|
1379
|
+
Customize the way decorations are added for matches. This
|
|
1380
|
+
function, when given, will be called for matches and should
|
|
1381
|
+
call `add` to create decorations for them. Note that the
|
|
1382
|
+
decorations should appear *in* the given range, and the
|
|
1383
|
+
function should have no side effects beyond calling `add`.
|
|
1384
|
+
|
|
1385
|
+
The `decoration` option is ignored when `decorate` is
|
|
1386
|
+
provided.
|
|
1387
|
+
*/
|
|
1388
|
+
decorate?: (add: (from: number, to: number, decoration: Decoration) => void, from: number, to: number, match: RegExpExecArray, view: EditorView) => void;
|
|
1378
1389
|
/**
|
|
1379
1390
|
By default, changed lines are re-matched entirely. You can
|
|
1380
1391
|
provide a boundary expression, which should match single
|
package/dist/index.js
CHANGED
|
@@ -264,6 +264,31 @@ function clearAttributes(node) {
|
|
|
264
264
|
while (node.attributes.length)
|
|
265
265
|
node.removeAttributeNode(node.attributes[0]);
|
|
266
266
|
}
|
|
267
|
+
function atElementStart(doc, selection) {
|
|
268
|
+
let node = selection.focusNode, offset = selection.focusOffset;
|
|
269
|
+
if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
|
|
270
|
+
return false;
|
|
271
|
+
for (;;) {
|
|
272
|
+
if (offset) {
|
|
273
|
+
if (node.nodeType != 1)
|
|
274
|
+
return false;
|
|
275
|
+
let prev = node.childNodes[offset - 1];
|
|
276
|
+
if (prev.contentEditable == "false")
|
|
277
|
+
offset--;
|
|
278
|
+
else {
|
|
279
|
+
node = prev;
|
|
280
|
+
offset = maxOffset(node);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (node == doc) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
offset = domIndex(node);
|
|
288
|
+
node = node.parentNode;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
267
292
|
|
|
268
293
|
class DOMPos {
|
|
269
294
|
constructor(node, offset, precise = true) {
|
|
@@ -3283,6 +3308,10 @@ class InputState {
|
|
|
3283
3308
|
constructor(view) {
|
|
3284
3309
|
this.lastKeyCode = 0;
|
|
3285
3310
|
this.lastKeyTime = 0;
|
|
3311
|
+
this.lastTouchTime = 0;
|
|
3312
|
+
this.lastFocusTime = 0;
|
|
3313
|
+
this.lastScrollTop = 0;
|
|
3314
|
+
this.lastScrollLeft = 0;
|
|
3286
3315
|
this.chromeScrollHack = -1;
|
|
3287
3316
|
// On iOS, some keys need to have their default behavior happen
|
|
3288
3317
|
// (after which we retroactively handle them and reset the DOM) to
|
|
@@ -3321,10 +3350,10 @@ class InputState {
|
|
|
3321
3350
|
event.preventDefault();
|
|
3322
3351
|
else
|
|
3323
3352
|
handler(view, event);
|
|
3324
|
-
});
|
|
3353
|
+
}, handlerOptions[type]);
|
|
3325
3354
|
this.registeredEvents.push(type);
|
|
3326
3355
|
}
|
|
3327
|
-
if (browser.chrome && browser.chrome_version
|
|
3356
|
+
if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
|
|
3328
3357
|
// On Chrome 102, viewport updates somehow stop wheel-based
|
|
3329
3358
|
// scrolling. Turning off pointer events during the scroll seems
|
|
3330
3359
|
// to avoid the issue.
|
|
@@ -3384,6 +3413,8 @@ class InputState {
|
|
|
3384
3413
|
return false;
|
|
3385
3414
|
}
|
|
3386
3415
|
runScrollHandlers(view, event) {
|
|
3416
|
+
this.lastScrollTop = view.scrollDOM.scrollTop;
|
|
3417
|
+
this.lastScrollLeft = view.scrollDOM.scrollLeft;
|
|
3387
3418
|
for (let set of this.customHandlers) {
|
|
3388
3419
|
let handler = set.handlers.scroll;
|
|
3389
3420
|
if (handler) {
|
|
@@ -3444,7 +3475,7 @@ class InputState {
|
|
|
3444
3475
|
// compositionend and keydown events are sometimes emitted in the
|
|
3445
3476
|
// wrong order. The key event should still be ignored, even when
|
|
3446
3477
|
// it happens after the compositionend event.
|
|
3447
|
-
if (browser.safari && Date.now() - this.compositionEndedAt < 100) {
|
|
3478
|
+
if (browser.safari && !browser.ios && Date.now() - this.compositionEndedAt < 100) {
|
|
3448
3479
|
this.compositionEndedAt = 0;
|
|
3449
3480
|
return true;
|
|
3450
3481
|
}
|
|
@@ -3572,6 +3603,7 @@ function eventBelongsToEditor(view, event) {
|
|
|
3572
3603
|
return true;
|
|
3573
3604
|
}
|
|
3574
3605
|
const handlers = /*@__PURE__*/Object.create(null);
|
|
3606
|
+
const handlerOptions = /*@__PURE__*/Object.create(null);
|
|
3575
3607
|
// This is very crude, but unfortunately both these browsers _pretend_
|
|
3576
3608
|
// that they have a clipboard API—all the objects and methods are
|
|
3577
3609
|
// there, they just don't work, and they are hard to test.
|
|
@@ -3628,17 +3660,17 @@ handlers.keydown = (view, event) => {
|
|
|
3628
3660
|
else if (modifierCodes.indexOf(event.keyCode) < 0)
|
|
3629
3661
|
view.inputState.lastEscPress = 0;
|
|
3630
3662
|
};
|
|
3631
|
-
let lastTouch = 0;
|
|
3632
3663
|
handlers.touchstart = (view, e) => {
|
|
3633
|
-
|
|
3664
|
+
view.inputState.lastTouchTime = Date.now();
|
|
3634
3665
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3635
3666
|
};
|
|
3636
3667
|
handlers.touchmove = view => {
|
|
3637
3668
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3638
3669
|
};
|
|
3670
|
+
handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
|
|
3639
3671
|
handlers.mousedown = (view, event) => {
|
|
3640
3672
|
view.observer.flush();
|
|
3641
|
-
if (
|
|
3673
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
|
|
3642
3674
|
return; // Ignore touch interaction
|
|
3643
3675
|
let style = null;
|
|
3644
3676
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -3892,7 +3924,15 @@ function updateForFocusChange(view) {
|
|
|
3892
3924
|
view.update([]);
|
|
3893
3925
|
}, 10);
|
|
3894
3926
|
}
|
|
3895
|
-
handlers.focus =
|
|
3927
|
+
handlers.focus = view => {
|
|
3928
|
+
view.inputState.lastFocusTime = Date.now();
|
|
3929
|
+
// When focusing reset the scroll position, move it back to where it was
|
|
3930
|
+
if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
|
|
3931
|
+
view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
|
|
3932
|
+
view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
|
|
3933
|
+
}
|
|
3934
|
+
updateForFocusChange(view);
|
|
3935
|
+
};
|
|
3896
3936
|
handlers.blur = view => {
|
|
3897
3937
|
view.observer.clearSelectionRange();
|
|
3898
3938
|
updateForFocusChange(view);
|
|
@@ -5262,6 +5302,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5262
5302
|
"&light .cm-specialChar": { color: "red" },
|
|
5263
5303
|
"&dark .cm-specialChar": { color: "#f78" },
|
|
5264
5304
|
".cm-gutters": {
|
|
5305
|
+
flexShrink: 0,
|
|
5265
5306
|
display: "flex",
|
|
5266
5307
|
height: "100%",
|
|
5267
5308
|
boxSizing: "border-box",
|
|
@@ -5518,15 +5559,28 @@ class DOMObserver {
|
|
|
5518
5559
|
this.flush(false);
|
|
5519
5560
|
}
|
|
5520
5561
|
readSelectionRange() {
|
|
5521
|
-
let {
|
|
5562
|
+
let { view } = this;
|
|
5522
5563
|
// The Selection object is broken in shadow roots in Safari. See
|
|
5523
5564
|
// https://github.com/codemirror/dev/issues/414
|
|
5524
|
-
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.
|
|
5525
|
-
safariSelectionRangeHack(this.view) || getSelection(root);
|
|
5565
|
+
let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
|
|
5566
|
+
safariSelectionRangeHack(this.view) || getSelection(view.root);
|
|
5526
5567
|
if (!range || this.selectionRange.eq(range))
|
|
5527
5568
|
return false;
|
|
5569
|
+
let local = hasSelection(this.dom, range);
|
|
5570
|
+
// Detect the situation where the browser has, on focus, moved the
|
|
5571
|
+
// selection to the start of the content element. Reset it to the
|
|
5572
|
+
// position from the editor state.
|
|
5573
|
+
if (local && !this.selectionChanged && this.selectionRange.focusNode &&
|
|
5574
|
+
view.inputState.lastFocusTime > Date.now() - 200 &&
|
|
5575
|
+
view.inputState.lastTouchTime < Date.now() - 300 &&
|
|
5576
|
+
atElementStart(this.dom, range)) {
|
|
5577
|
+
view.docView.updateSelection();
|
|
5578
|
+
return false;
|
|
5579
|
+
}
|
|
5528
5580
|
this.selectionRange.setRange(range);
|
|
5529
|
-
|
|
5581
|
+
if (local)
|
|
5582
|
+
this.selectionChanged = true;
|
|
5583
|
+
return true;
|
|
5530
5584
|
}
|
|
5531
5585
|
setSelectionRange(anchor, head) {
|
|
5532
5586
|
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
@@ -5613,7 +5667,7 @@ class DOMObserver {
|
|
|
5613
5667
|
this.delayedAndroidKey = null;
|
|
5614
5668
|
this.delayedFlush = -1;
|
|
5615
5669
|
if (!this.flush())
|
|
5616
|
-
dispatchKey(this.
|
|
5670
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5617
5671
|
});
|
|
5618
5672
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5619
5673
|
// Enter always takes precedence.
|
|
@@ -5628,8 +5682,8 @@ class DOMObserver {
|
|
|
5628
5682
|
if (this.delayedFlush >= 0) {
|
|
5629
5683
|
window.clearTimeout(this.delayedFlush);
|
|
5630
5684
|
this.delayedFlush = -1;
|
|
5631
|
-
this.flush();
|
|
5632
5685
|
}
|
|
5686
|
+
this.flush();
|
|
5633
5687
|
}
|
|
5634
5688
|
processRecords() {
|
|
5635
5689
|
let records = this.queue;
|
|
@@ -5667,6 +5721,7 @@ class DOMObserver {
|
|
|
5667
5721
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5668
5722
|
if (from < 0 && !newSel)
|
|
5669
5723
|
return;
|
|
5724
|
+
this.view.inputState.lastFocusTime = 0;
|
|
5670
5725
|
this.selectionChanged = false;
|
|
5671
5726
|
let startState = this.view.state;
|
|
5672
5727
|
let handled = this.onChange(from, to, typeOver);
|
|
@@ -6220,7 +6275,7 @@ class EditorView {
|
|
|
6220
6275
|
cancelAnimationFrame(this.measureScheduled);
|
|
6221
6276
|
this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
|
|
6222
6277
|
if (flush)
|
|
6223
|
-
this.observer.
|
|
6278
|
+
this.observer.forceFlush();
|
|
6224
6279
|
let updated = null;
|
|
6225
6280
|
try {
|
|
6226
6281
|
for (let i = 0;; i++) {
|
|
@@ -7374,7 +7429,7 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
7374
7429
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
7375
7430
|
if (!cursor.lineBreak)
|
|
7376
7431
|
while (m = re.exec(cursor.value))
|
|
7377
|
-
f(pos + m.index,
|
|
7432
|
+
f(pos + m.index, m);
|
|
7378
7433
|
}
|
|
7379
7434
|
}
|
|
7380
7435
|
function matchRanges(view, maxLength) {
|
|
@@ -7404,11 +7459,20 @@ class MatchDecorator {
|
|
|
7404
7459
|
Create a decorator.
|
|
7405
7460
|
*/
|
|
7406
7461
|
constructor(config) {
|
|
7407
|
-
|
|
7462
|
+
const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
|
|
7408
7463
|
if (!regexp.global)
|
|
7409
7464
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
7410
7465
|
this.regexp = regexp;
|
|
7411
|
-
|
|
7466
|
+
if (decorate) {
|
|
7467
|
+
this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
|
|
7468
|
+
}
|
|
7469
|
+
else if (decoration) {
|
|
7470
|
+
let getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
7471
|
+
this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
|
|
7472
|
+
}
|
|
7473
|
+
else {
|
|
7474
|
+
throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
|
|
7475
|
+
}
|
|
7412
7476
|
this.boundary = boundary;
|
|
7413
7477
|
this.maxLength = maxLength;
|
|
7414
7478
|
}
|
|
@@ -7418,9 +7482,9 @@ class MatchDecorator {
|
|
|
7418
7482
|
plugin.
|
|
7419
7483
|
*/
|
|
7420
7484
|
createDeco(view) {
|
|
7421
|
-
let build = new RangeSetBuilder();
|
|
7485
|
+
let build = new RangeSetBuilder(), add = build.add.bind(build);
|
|
7422
7486
|
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
7423
|
-
iterMatches(view.state.doc, this.regexp, from, to, (
|
|
7487
|
+
iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
|
|
7424
7488
|
return build.finish();
|
|
7425
7489
|
}
|
|
7426
7490
|
/**
|
|
@@ -7462,15 +7526,14 @@ class MatchDecorator {
|
|
|
7462
7526
|
}
|
|
7463
7527
|
}
|
|
7464
7528
|
let ranges = [], m;
|
|
7529
|
+
let add = (from, to, deco) => ranges.push(deco.range(from, to));
|
|
7465
7530
|
if (fromLine == toLine) {
|
|
7466
7531
|
this.regexp.lastIndex = start - fromLine.from;
|
|
7467
|
-
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7468
|
-
|
|
7469
|
-
ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
|
|
7470
|
-
}
|
|
7532
|
+
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7533
|
+
this.addMatch(m, view, m.index + fromLine.from, add);
|
|
7471
7534
|
}
|
|
7472
7535
|
else {
|
|
7473
|
-
iterMatches(view.state.doc, this.regexp, start, end, (from,
|
|
7536
|
+
iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
|
|
7474
7537
|
}
|
|
7475
7538
|
deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
|
|
7476
7539
|
}
|
|
@@ -7480,7 +7543,7 @@ class MatchDecorator {
|
|
|
7480
7543
|
}
|
|
7481
7544
|
|
|
7482
7545
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
7483
|
-
const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7546
|
+
const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7484
7547
|
const Names = {
|
|
7485
7548
|
0: "null",
|
|
7486
7549
|
7: "bell",
|
|
@@ -7497,6 +7560,9 @@ const Names = {
|
|
|
7497
7560
|
8232: "line separator",
|
|
7498
7561
|
8237: "left-to-right override",
|
|
7499
7562
|
8238: "right-to-left override",
|
|
7563
|
+
8294: "left-to-right isolate",
|
|
7564
|
+
8295: "right-to-left isolate",
|
|
7565
|
+
8297: "pop directional isolate",
|
|
7500
7566
|
8233: "paragraph separator",
|
|
7501
7567
|
65279: "zero width no-break space",
|
|
7502
7568
|
65532: "object replacement"
|