@codemirror/view 6.0.2 → 6.1.1
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 +26 -0
- package/dist/index.cjs +115 -26
- package/dist/index.d.ts +13 -2
- package/dist/index.js +115 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## 6.1.1 (2022-07-25)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Make `highlightSpecialChars` replace directional isolate characters by default.
|
|
6
|
+
|
|
7
|
+
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).
|
|
8
|
+
|
|
9
|
+
Fix a CSS issue that made it possible, when the gutters were wide enough, for them to overlap with the content.
|
|
10
|
+
|
|
11
|
+
## 6.1.0 (2022-07-19)
|
|
12
|
+
|
|
13
|
+
### New features
|
|
14
|
+
|
|
15
|
+
`MatchDecorator` now supports a `decorate` option that can be used to customize the way decorations are added for each match.
|
|
16
|
+
|
|
17
|
+
## 6.0.3 (2022-07-08)
|
|
18
|
+
|
|
19
|
+
### Bug fixes
|
|
20
|
+
|
|
21
|
+
Fix a problem where `posAtCoords` could incorrectly return the start of the next line when querying positions between lines.
|
|
22
|
+
|
|
23
|
+
Fix an issue where registering a high-precedence keymap made keymap handling take precedence over other keydown event handlers.
|
|
24
|
+
|
|
25
|
+
Ctrl/Cmd-clicking can now remove ranges from a multi-range selection.
|
|
26
|
+
|
|
1
27
|
## 6.0.2 (2022-06-23)
|
|
2
28
|
|
|
3
29
|
### 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) {
|
|
@@ -3137,7 +3162,8 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
3137
3162
|
let range = doc.caretRangeFromPoint(x, y);
|
|
3138
3163
|
if (range) {
|
|
3139
3164
|
({ startContainer: node, startOffset: offset } = range);
|
|
3140
|
-
if (browser.safari &&
|
|
3165
|
+
if (browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3166
|
+
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
|
|
3141
3167
|
node = undefined;
|
|
3142
3168
|
}
|
|
3143
3169
|
}
|
|
@@ -3164,7 +3190,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3164
3190
|
// the space between lines as belonging to the last character of the
|
|
3165
3191
|
// line before. This is used to detect such a result so that it can be
|
|
3166
3192
|
// ignored (issue #401).
|
|
3167
|
-
function
|
|
3193
|
+
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3168
3194
|
let len;
|
|
3169
3195
|
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3170
3196
|
return false;
|
|
@@ -3173,6 +3199,22 @@ function isSuspiciousCaretResult(node, offset, x) {
|
|
|
3173
3199
|
return false;
|
|
3174
3200
|
return textRange(node, len - 1, len).getBoundingClientRect().left > x;
|
|
3175
3201
|
}
|
|
3202
|
+
// Chrome will move positions between lines to the start of the next line
|
|
3203
|
+
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3204
|
+
if (offset != 0)
|
|
3205
|
+
return false;
|
|
3206
|
+
for (let cur = node;;) {
|
|
3207
|
+
let parent = cur.parentNode;
|
|
3208
|
+
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
3209
|
+
return false;
|
|
3210
|
+
if (parent.classList.contains("cm-line"))
|
|
3211
|
+
break;
|
|
3212
|
+
cur = parent;
|
|
3213
|
+
}
|
|
3214
|
+
let rect = node.nodeType == 1 ? node.getBoundingClientRect()
|
|
3215
|
+
: textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
|
|
3216
|
+
return x - rect.left > 5;
|
|
3217
|
+
}
|
|
3176
3218
|
function moveToLineBoundary(view, start, forward, includeWrap) {
|
|
3177
3219
|
let line = view.state.doc.lineAt(start.head);
|
|
3178
3220
|
let coords = !includeWrap || !view.lineWrapping ? null
|
|
@@ -3272,6 +3314,10 @@ class InputState {
|
|
|
3272
3314
|
constructor(view) {
|
|
3273
3315
|
this.lastKeyCode = 0;
|
|
3274
3316
|
this.lastKeyTime = 0;
|
|
3317
|
+
this.lastTouchTime = 0;
|
|
3318
|
+
this.lastFocusTime = 0;
|
|
3319
|
+
this.lastScrollTop = 0;
|
|
3320
|
+
this.lastScrollLeft = 0;
|
|
3275
3321
|
this.chromeScrollHack = -1;
|
|
3276
3322
|
// On iOS, some keys need to have their default behavior happen
|
|
3277
3323
|
// (after which we retroactively handle them and reset the DOM) to
|
|
@@ -3373,6 +3419,8 @@ class InputState {
|
|
|
3373
3419
|
return false;
|
|
3374
3420
|
}
|
|
3375
3421
|
runScrollHandlers(view, event) {
|
|
3422
|
+
this.lastScrollTop = view.scrollDOM.scrollTop;
|
|
3423
|
+
this.lastScrollLeft = view.scrollDOM.scrollLeft;
|
|
3376
3424
|
for (let set of this.customHandlers) {
|
|
3377
3425
|
let handler = set.handlers.scroll;
|
|
3378
3426
|
if (handler) {
|
|
@@ -3617,9 +3665,8 @@ handlers.keydown = (view, event) => {
|
|
|
3617
3665
|
else if (modifierCodes.indexOf(event.keyCode) < 0)
|
|
3618
3666
|
view.inputState.lastEscPress = 0;
|
|
3619
3667
|
};
|
|
3620
|
-
let lastTouch = 0;
|
|
3621
3668
|
handlers.touchstart = (view, e) => {
|
|
3622
|
-
|
|
3669
|
+
view.inputState.lastTouchTime = Date.now();
|
|
3623
3670
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3624
3671
|
};
|
|
3625
3672
|
handlers.touchmove = view => {
|
|
@@ -3627,7 +3674,7 @@ handlers.touchmove = view => {
|
|
|
3627
3674
|
};
|
|
3628
3675
|
handlers.mousedown = (view, event) => {
|
|
3629
3676
|
view.observer.flush();
|
|
3630
|
-
if (
|
|
3677
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
|
|
3631
3678
|
return; // Ignore touch interaction
|
|
3632
3679
|
let style = null;
|
|
3633
3680
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -3731,6 +3778,8 @@ function basicMouseSelection(view, event) {
|
|
|
3731
3778
|
}
|
|
3732
3779
|
if (extend)
|
|
3733
3780
|
return startSel.replaceRange(startSel.main.extend(range.from, range.to));
|
|
3781
|
+
else if (multiple && startSel.ranges.length > 1 && startSel.ranges.some(r => r.eq(range)))
|
|
3782
|
+
return removeRange(startSel, range);
|
|
3734
3783
|
else if (multiple)
|
|
3735
3784
|
return startSel.addRange(range);
|
|
3736
3785
|
else
|
|
@@ -3738,6 +3787,12 @@ function basicMouseSelection(view, event) {
|
|
|
3738
3787
|
}
|
|
3739
3788
|
};
|
|
3740
3789
|
}
|
|
3790
|
+
function removeRange(sel, range) {
|
|
3791
|
+
for (let i = 0;; i++) {
|
|
3792
|
+
if (sel.ranges[i].eq(range))
|
|
3793
|
+
return state.EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3741
3796
|
handlers.dragstart = (view, event) => {
|
|
3742
3797
|
let { selection: { main } } = view.state;
|
|
3743
3798
|
let { mouseSelection } = view.inputState;
|
|
@@ -3873,7 +3928,15 @@ function updateForFocusChange(view) {
|
|
|
3873
3928
|
view.update([]);
|
|
3874
3929
|
}, 10);
|
|
3875
3930
|
}
|
|
3876
|
-
handlers.focus =
|
|
3931
|
+
handlers.focus = view => {
|
|
3932
|
+
view.inputState.lastFocusTime = Date.now();
|
|
3933
|
+
// When focusing reset the scroll position, move it back to where it was
|
|
3934
|
+
if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
|
|
3935
|
+
view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
|
|
3936
|
+
view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
|
|
3937
|
+
}
|
|
3938
|
+
updateForFocusChange(view);
|
|
3939
|
+
};
|
|
3877
3940
|
handlers.blur = view => {
|
|
3878
3941
|
view.observer.clearSelectionRange();
|
|
3879
3942
|
updateForFocusChange(view);
|
|
@@ -5222,8 +5285,8 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
|
|
|
5222
5285
|
// Two animations defined so that we can switch between them to
|
|
5223
5286
|
// restart the animation without forcing another style
|
|
5224
5287
|
// recomputation.
|
|
5225
|
-
"@keyframes cm-blink": { "0%": {}, "50%": {
|
|
5226
|
-
"@keyframes cm-blink2": { "0%": {}, "50%": {
|
|
5288
|
+
"@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|
5289
|
+
"@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|
5227
5290
|
".cm-cursor, .cm-dropCursor": {
|
|
5228
5291
|
position: "absolute",
|
|
5229
5292
|
borderLeft: "1.2px solid black",
|
|
@@ -5244,6 +5307,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
|
|
|
5244
5307
|
"&light .cm-specialChar": { color: "red" },
|
|
5245
5308
|
"&dark .cm-specialChar": { color: "#f78" },
|
|
5246
5309
|
".cm-gutters": {
|
|
5310
|
+
flexShrink: 0,
|
|
5247
5311
|
display: "flex",
|
|
5248
5312
|
height: "100%",
|
|
5249
5313
|
boxSizing: "border-box",
|
|
@@ -5500,15 +5564,28 @@ class DOMObserver {
|
|
|
5500
5564
|
this.flush(false);
|
|
5501
5565
|
}
|
|
5502
5566
|
readSelectionRange() {
|
|
5503
|
-
let {
|
|
5567
|
+
let { view } = this;
|
|
5504
5568
|
// The Selection object is broken in shadow roots in Safari. See
|
|
5505
5569
|
// https://github.com/codemirror/dev/issues/414
|
|
5506
|
-
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.
|
|
5507
|
-
safariSelectionRangeHack(this.view) || getSelection(root);
|
|
5570
|
+
let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
|
|
5571
|
+
safariSelectionRangeHack(this.view) || getSelection(view.root);
|
|
5508
5572
|
if (!range || this.selectionRange.eq(range))
|
|
5509
5573
|
return false;
|
|
5574
|
+
let local = hasSelection(this.dom, range);
|
|
5575
|
+
// Detect the situation where the browser has, on focus, moved the
|
|
5576
|
+
// selection to the start of the content element. Reset it to the
|
|
5577
|
+
// position from the editor state.
|
|
5578
|
+
if (local && !this.selectionChanged && this.selectionRange.focusNode &&
|
|
5579
|
+
view.inputState.lastFocusTime > Date.now() - 200 &&
|
|
5580
|
+
view.inputState.lastTouchTime < Date.now() - 300 &&
|
|
5581
|
+
atElementStart(this.dom, range)) {
|
|
5582
|
+
view.docView.updateSelection();
|
|
5583
|
+
return false;
|
|
5584
|
+
}
|
|
5510
5585
|
this.selectionRange.setRange(range);
|
|
5511
|
-
|
|
5586
|
+
if (local)
|
|
5587
|
+
this.selectionChanged = true;
|
|
5588
|
+
return true;
|
|
5512
5589
|
}
|
|
5513
5590
|
setSelectionRange(anchor, head) {
|
|
5514
5591
|
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
@@ -5595,7 +5672,7 @@ class DOMObserver {
|
|
|
5595
5672
|
this.delayedAndroidKey = null;
|
|
5596
5673
|
this.delayedFlush = -1;
|
|
5597
5674
|
if (!this.flush())
|
|
5598
|
-
dispatchKey(this.
|
|
5675
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5599
5676
|
});
|
|
5600
5677
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5601
5678
|
// Enter always takes precedence.
|
|
@@ -5649,6 +5726,7 @@ class DOMObserver {
|
|
|
5649
5726
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5650
5727
|
if (from < 0 && !newSel)
|
|
5651
5728
|
return;
|
|
5729
|
+
this.view.inputState.lastFocusTime = 0;
|
|
5652
5730
|
this.selectionChanged = false;
|
|
5653
5731
|
let startState = this.view.state;
|
|
5654
5732
|
let handled = this.onChange(from, to, typeOver);
|
|
@@ -6890,11 +6968,11 @@ function modifiers(name, event, shift) {
|
|
|
6890
6968
|
name = "Shift-" + name;
|
|
6891
6969
|
return name;
|
|
6892
6970
|
}
|
|
6893
|
-
const handleKeyEvents = EditorView.domEventHandlers({
|
|
6971
|
+
const handleKeyEvents = state.Prec.default(EditorView.domEventHandlers({
|
|
6894
6972
|
keydown(event, view) {
|
|
6895
6973
|
return runHandlers(getKeymap(view.state), event, view, "editor");
|
|
6896
6974
|
}
|
|
6897
|
-
});
|
|
6975
|
+
}));
|
|
6898
6976
|
/**
|
|
6899
6977
|
Facet used for registering keymaps.
|
|
6900
6978
|
|
|
@@ -7356,7 +7434,7 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
7356
7434
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
7357
7435
|
if (!cursor.lineBreak)
|
|
7358
7436
|
while (m = re.exec(cursor.value))
|
|
7359
|
-
f(pos + m.index,
|
|
7437
|
+
f(pos + m.index, m);
|
|
7360
7438
|
}
|
|
7361
7439
|
}
|
|
7362
7440
|
function matchRanges(view, maxLength) {
|
|
@@ -7386,11 +7464,20 @@ class MatchDecorator {
|
|
|
7386
7464
|
Create a decorator.
|
|
7387
7465
|
*/
|
|
7388
7466
|
constructor(config) {
|
|
7389
|
-
|
|
7467
|
+
const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
|
|
7390
7468
|
if (!regexp.global)
|
|
7391
7469
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
7392
7470
|
this.regexp = regexp;
|
|
7393
|
-
|
|
7471
|
+
if (decorate) {
|
|
7472
|
+
this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
|
|
7473
|
+
}
|
|
7474
|
+
else if (decoration) {
|
|
7475
|
+
let getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
7476
|
+
this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
|
|
7477
|
+
}
|
|
7478
|
+
else {
|
|
7479
|
+
throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
|
|
7480
|
+
}
|
|
7394
7481
|
this.boundary = boundary;
|
|
7395
7482
|
this.maxLength = maxLength;
|
|
7396
7483
|
}
|
|
@@ -7400,9 +7487,9 @@ class MatchDecorator {
|
|
|
7400
7487
|
plugin.
|
|
7401
7488
|
*/
|
|
7402
7489
|
createDeco(view) {
|
|
7403
|
-
let build = new state.RangeSetBuilder();
|
|
7490
|
+
let build = new state.RangeSetBuilder(), add = build.add.bind(build);
|
|
7404
7491
|
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
7405
|
-
iterMatches(view.state.doc, this.regexp, from, to, (
|
|
7492
|
+
iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
|
|
7406
7493
|
return build.finish();
|
|
7407
7494
|
}
|
|
7408
7495
|
/**
|
|
@@ -7444,15 +7531,14 @@ class MatchDecorator {
|
|
|
7444
7531
|
}
|
|
7445
7532
|
}
|
|
7446
7533
|
let ranges = [], m;
|
|
7534
|
+
let add = (from, to, deco) => ranges.push(deco.range(from, to));
|
|
7447
7535
|
if (fromLine == toLine) {
|
|
7448
7536
|
this.regexp.lastIndex = start - fromLine.from;
|
|
7449
|
-
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7450
|
-
|
|
7451
|
-
ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
|
|
7452
|
-
}
|
|
7537
|
+
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7538
|
+
this.addMatch(m, view, m.index + fromLine.from, add);
|
|
7453
7539
|
}
|
|
7454
7540
|
else {
|
|
7455
|
-
iterMatches(view.state.doc, this.regexp, start, end, (from,
|
|
7541
|
+
iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
|
|
7456
7542
|
}
|
|
7457
7543
|
deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
|
|
7458
7544
|
}
|
|
@@ -7462,7 +7548,7 @@ class MatchDecorator {
|
|
|
7462
7548
|
}
|
|
7463
7549
|
|
|
7464
7550
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
7465
|
-
const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7551
|
+
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);
|
|
7466
7552
|
const Names = {
|
|
7467
7553
|
0: "null",
|
|
7468
7554
|
7: "bell",
|
|
@@ -7479,6 +7565,9 @@ const Names = {
|
|
|
7479
7565
|
8232: "line separator",
|
|
7480
7566
|
8237: "left-to-right override",
|
|
7481
7567
|
8238: "right-to-left override",
|
|
7568
|
+
8294: "left-to-right isolate",
|
|
7569
|
+
8295: "right-to-left isolate",
|
|
7570
|
+
8297: "pop directional isolate",
|
|
7482
7571
|
8233: "paragraph separator",
|
|
7483
7572
|
65279: "zero width no-break space",
|
|
7484
7573
|
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) {
|
|
@@ -3131,7 +3156,8 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
3131
3156
|
let range = doc.caretRangeFromPoint(x, y);
|
|
3132
3157
|
if (range) {
|
|
3133
3158
|
({ startContainer: node, startOffset: offset } = range);
|
|
3134
|
-
if (browser.safari &&
|
|
3159
|
+
if (browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3160
|
+
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
|
|
3135
3161
|
node = undefined;
|
|
3136
3162
|
}
|
|
3137
3163
|
}
|
|
@@ -3158,7 +3184,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3158
3184
|
// the space between lines as belonging to the last character of the
|
|
3159
3185
|
// line before. This is used to detect such a result so that it can be
|
|
3160
3186
|
// ignored (issue #401).
|
|
3161
|
-
function
|
|
3187
|
+
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3162
3188
|
let len;
|
|
3163
3189
|
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3164
3190
|
return false;
|
|
@@ -3167,6 +3193,22 @@ function isSuspiciousCaretResult(node, offset, x) {
|
|
|
3167
3193
|
return false;
|
|
3168
3194
|
return textRange(node, len - 1, len).getBoundingClientRect().left > x;
|
|
3169
3195
|
}
|
|
3196
|
+
// Chrome will move positions between lines to the start of the next line
|
|
3197
|
+
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3198
|
+
if (offset != 0)
|
|
3199
|
+
return false;
|
|
3200
|
+
for (let cur = node;;) {
|
|
3201
|
+
let parent = cur.parentNode;
|
|
3202
|
+
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
3203
|
+
return false;
|
|
3204
|
+
if (parent.classList.contains("cm-line"))
|
|
3205
|
+
break;
|
|
3206
|
+
cur = parent;
|
|
3207
|
+
}
|
|
3208
|
+
let rect = node.nodeType == 1 ? node.getBoundingClientRect()
|
|
3209
|
+
: textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
|
|
3210
|
+
return x - rect.left > 5;
|
|
3211
|
+
}
|
|
3170
3212
|
function moveToLineBoundary(view, start, forward, includeWrap) {
|
|
3171
3213
|
let line = view.state.doc.lineAt(start.head);
|
|
3172
3214
|
let coords = !includeWrap || !view.lineWrapping ? null
|
|
@@ -3266,6 +3308,10 @@ class InputState {
|
|
|
3266
3308
|
constructor(view) {
|
|
3267
3309
|
this.lastKeyCode = 0;
|
|
3268
3310
|
this.lastKeyTime = 0;
|
|
3311
|
+
this.lastTouchTime = 0;
|
|
3312
|
+
this.lastFocusTime = 0;
|
|
3313
|
+
this.lastScrollTop = 0;
|
|
3314
|
+
this.lastScrollLeft = 0;
|
|
3269
3315
|
this.chromeScrollHack = -1;
|
|
3270
3316
|
// On iOS, some keys need to have their default behavior happen
|
|
3271
3317
|
// (after which we retroactively handle them and reset the DOM) to
|
|
@@ -3367,6 +3413,8 @@ class InputState {
|
|
|
3367
3413
|
return false;
|
|
3368
3414
|
}
|
|
3369
3415
|
runScrollHandlers(view, event) {
|
|
3416
|
+
this.lastScrollTop = view.scrollDOM.scrollTop;
|
|
3417
|
+
this.lastScrollLeft = view.scrollDOM.scrollLeft;
|
|
3370
3418
|
for (let set of this.customHandlers) {
|
|
3371
3419
|
let handler = set.handlers.scroll;
|
|
3372
3420
|
if (handler) {
|
|
@@ -3611,9 +3659,8 @@ handlers.keydown = (view, event) => {
|
|
|
3611
3659
|
else if (modifierCodes.indexOf(event.keyCode) < 0)
|
|
3612
3660
|
view.inputState.lastEscPress = 0;
|
|
3613
3661
|
};
|
|
3614
|
-
let lastTouch = 0;
|
|
3615
3662
|
handlers.touchstart = (view, e) => {
|
|
3616
|
-
|
|
3663
|
+
view.inputState.lastTouchTime = Date.now();
|
|
3617
3664
|
view.inputState.setSelectionOrigin("select.pointer");
|
|
3618
3665
|
};
|
|
3619
3666
|
handlers.touchmove = view => {
|
|
@@ -3621,7 +3668,7 @@ handlers.touchmove = view => {
|
|
|
3621
3668
|
};
|
|
3622
3669
|
handlers.mousedown = (view, event) => {
|
|
3623
3670
|
view.observer.flush();
|
|
3624
|
-
if (
|
|
3671
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
|
|
3625
3672
|
return; // Ignore touch interaction
|
|
3626
3673
|
let style = null;
|
|
3627
3674
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -3725,6 +3772,8 @@ function basicMouseSelection(view, event) {
|
|
|
3725
3772
|
}
|
|
3726
3773
|
if (extend)
|
|
3727
3774
|
return startSel.replaceRange(startSel.main.extend(range.from, range.to));
|
|
3775
|
+
else if (multiple && startSel.ranges.length > 1 && startSel.ranges.some(r => r.eq(range)))
|
|
3776
|
+
return removeRange(startSel, range);
|
|
3728
3777
|
else if (multiple)
|
|
3729
3778
|
return startSel.addRange(range);
|
|
3730
3779
|
else
|
|
@@ -3732,6 +3781,12 @@ function basicMouseSelection(view, event) {
|
|
|
3732
3781
|
}
|
|
3733
3782
|
};
|
|
3734
3783
|
}
|
|
3784
|
+
function removeRange(sel, range) {
|
|
3785
|
+
for (let i = 0;; i++) {
|
|
3786
|
+
if (sel.ranges[i].eq(range))
|
|
3787
|
+
return EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3735
3790
|
handlers.dragstart = (view, event) => {
|
|
3736
3791
|
let { selection: { main } } = view.state;
|
|
3737
3792
|
let { mouseSelection } = view.inputState;
|
|
@@ -3867,7 +3922,15 @@ function updateForFocusChange(view) {
|
|
|
3867
3922
|
view.update([]);
|
|
3868
3923
|
}, 10);
|
|
3869
3924
|
}
|
|
3870
|
-
handlers.focus =
|
|
3925
|
+
handlers.focus = view => {
|
|
3926
|
+
view.inputState.lastFocusTime = Date.now();
|
|
3927
|
+
// When focusing reset the scroll position, move it back to where it was
|
|
3928
|
+
if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
|
|
3929
|
+
view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
|
|
3930
|
+
view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
|
|
3931
|
+
}
|
|
3932
|
+
updateForFocusChange(view);
|
|
3933
|
+
};
|
|
3871
3934
|
handlers.blur = view => {
|
|
3872
3935
|
view.observer.clearSelectionRange();
|
|
3873
3936
|
updateForFocusChange(view);
|
|
@@ -5215,8 +5278,8 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5215
5278
|
// Two animations defined so that we can switch between them to
|
|
5216
5279
|
// restart the animation without forcing another style
|
|
5217
5280
|
// recomputation.
|
|
5218
|
-
"@keyframes cm-blink": { "0%": {}, "50%": {
|
|
5219
|
-
"@keyframes cm-blink2": { "0%": {}, "50%": {
|
|
5281
|
+
"@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|
5282
|
+
"@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|
5220
5283
|
".cm-cursor, .cm-dropCursor": {
|
|
5221
5284
|
position: "absolute",
|
|
5222
5285
|
borderLeft: "1.2px solid black",
|
|
@@ -5237,6 +5300,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5237
5300
|
"&light .cm-specialChar": { color: "red" },
|
|
5238
5301
|
"&dark .cm-specialChar": { color: "#f78" },
|
|
5239
5302
|
".cm-gutters": {
|
|
5303
|
+
flexShrink: 0,
|
|
5240
5304
|
display: "flex",
|
|
5241
5305
|
height: "100%",
|
|
5242
5306
|
boxSizing: "border-box",
|
|
@@ -5493,15 +5557,28 @@ class DOMObserver {
|
|
|
5493
5557
|
this.flush(false);
|
|
5494
5558
|
}
|
|
5495
5559
|
readSelectionRange() {
|
|
5496
|
-
let {
|
|
5560
|
+
let { view } = this;
|
|
5497
5561
|
// The Selection object is broken in shadow roots in Safari. See
|
|
5498
5562
|
// https://github.com/codemirror/dev/issues/414
|
|
5499
|
-
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.
|
|
5500
|
-
safariSelectionRangeHack(this.view) || getSelection(root);
|
|
5563
|
+
let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
|
|
5564
|
+
safariSelectionRangeHack(this.view) || getSelection(view.root);
|
|
5501
5565
|
if (!range || this.selectionRange.eq(range))
|
|
5502
5566
|
return false;
|
|
5567
|
+
let local = hasSelection(this.dom, range);
|
|
5568
|
+
// Detect the situation where the browser has, on focus, moved the
|
|
5569
|
+
// selection to the start of the content element. Reset it to the
|
|
5570
|
+
// position from the editor state.
|
|
5571
|
+
if (local && !this.selectionChanged && this.selectionRange.focusNode &&
|
|
5572
|
+
view.inputState.lastFocusTime > Date.now() - 200 &&
|
|
5573
|
+
view.inputState.lastTouchTime < Date.now() - 300 &&
|
|
5574
|
+
atElementStart(this.dom, range)) {
|
|
5575
|
+
view.docView.updateSelection();
|
|
5576
|
+
return false;
|
|
5577
|
+
}
|
|
5503
5578
|
this.selectionRange.setRange(range);
|
|
5504
|
-
|
|
5579
|
+
if (local)
|
|
5580
|
+
this.selectionChanged = true;
|
|
5581
|
+
return true;
|
|
5505
5582
|
}
|
|
5506
5583
|
setSelectionRange(anchor, head) {
|
|
5507
5584
|
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
@@ -5588,7 +5665,7 @@ class DOMObserver {
|
|
|
5588
5665
|
this.delayedAndroidKey = null;
|
|
5589
5666
|
this.delayedFlush = -1;
|
|
5590
5667
|
if (!this.flush())
|
|
5591
|
-
dispatchKey(this.
|
|
5668
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5592
5669
|
});
|
|
5593
5670
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5594
5671
|
// Enter always takes precedence.
|
|
@@ -5642,6 +5719,7 @@ class DOMObserver {
|
|
|
5642
5719
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5643
5720
|
if (from < 0 && !newSel)
|
|
5644
5721
|
return;
|
|
5722
|
+
this.view.inputState.lastFocusTime = 0;
|
|
5645
5723
|
this.selectionChanged = false;
|
|
5646
5724
|
let startState = this.view.state;
|
|
5647
5725
|
let handled = this.onChange(from, to, typeOver);
|
|
@@ -6883,11 +6961,11 @@ function modifiers(name, event, shift) {
|
|
|
6883
6961
|
name = "Shift-" + name;
|
|
6884
6962
|
return name;
|
|
6885
6963
|
}
|
|
6886
|
-
const handleKeyEvents = /*@__PURE__*/EditorView.domEventHandlers({
|
|
6964
|
+
const handleKeyEvents = /*@__PURE__*/Prec.default(/*@__PURE__*/EditorView.domEventHandlers({
|
|
6887
6965
|
keydown(event, view) {
|
|
6888
6966
|
return runHandlers(getKeymap(view.state), event, view, "editor");
|
|
6889
6967
|
}
|
|
6890
|
-
});
|
|
6968
|
+
}));
|
|
6891
6969
|
/**
|
|
6892
6970
|
Facet used for registering keymaps.
|
|
6893
6971
|
|
|
@@ -7349,7 +7427,7 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
7349
7427
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
7350
7428
|
if (!cursor.lineBreak)
|
|
7351
7429
|
while (m = re.exec(cursor.value))
|
|
7352
|
-
f(pos + m.index,
|
|
7430
|
+
f(pos + m.index, m);
|
|
7353
7431
|
}
|
|
7354
7432
|
}
|
|
7355
7433
|
function matchRanges(view, maxLength) {
|
|
@@ -7379,11 +7457,20 @@ class MatchDecorator {
|
|
|
7379
7457
|
Create a decorator.
|
|
7380
7458
|
*/
|
|
7381
7459
|
constructor(config) {
|
|
7382
|
-
|
|
7460
|
+
const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
|
|
7383
7461
|
if (!regexp.global)
|
|
7384
7462
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
7385
7463
|
this.regexp = regexp;
|
|
7386
|
-
|
|
7464
|
+
if (decorate) {
|
|
7465
|
+
this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
|
|
7466
|
+
}
|
|
7467
|
+
else if (decoration) {
|
|
7468
|
+
let getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
7469
|
+
this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
|
|
7470
|
+
}
|
|
7471
|
+
else {
|
|
7472
|
+
throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
|
|
7473
|
+
}
|
|
7387
7474
|
this.boundary = boundary;
|
|
7388
7475
|
this.maxLength = maxLength;
|
|
7389
7476
|
}
|
|
@@ -7393,9 +7480,9 @@ class MatchDecorator {
|
|
|
7393
7480
|
plugin.
|
|
7394
7481
|
*/
|
|
7395
7482
|
createDeco(view) {
|
|
7396
|
-
let build = new RangeSetBuilder();
|
|
7483
|
+
let build = new RangeSetBuilder(), add = build.add.bind(build);
|
|
7397
7484
|
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
7398
|
-
iterMatches(view.state.doc, this.regexp, from, to, (
|
|
7485
|
+
iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
|
|
7399
7486
|
return build.finish();
|
|
7400
7487
|
}
|
|
7401
7488
|
/**
|
|
@@ -7437,15 +7524,14 @@ class MatchDecorator {
|
|
|
7437
7524
|
}
|
|
7438
7525
|
}
|
|
7439
7526
|
let ranges = [], m;
|
|
7527
|
+
let add = (from, to, deco) => ranges.push(deco.range(from, to));
|
|
7440
7528
|
if (fromLine == toLine) {
|
|
7441
7529
|
this.regexp.lastIndex = start - fromLine.from;
|
|
7442
|
-
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7443
|
-
|
|
7444
|
-
ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
|
|
7445
|
-
}
|
|
7530
|
+
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|
7531
|
+
this.addMatch(m, view, m.index + fromLine.from, add);
|
|
7446
7532
|
}
|
|
7447
7533
|
else {
|
|
7448
|
-
iterMatches(view.state.doc, this.regexp, start, end, (from,
|
|
7534
|
+
iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
|
|
7449
7535
|
}
|
|
7450
7536
|
deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
|
|
7451
7537
|
}
|
|
@@ -7455,7 +7541,7 @@ class MatchDecorator {
|
|
|
7455
7541
|
}
|
|
7456
7542
|
|
|
7457
7543
|
const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|
7458
|
-
const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
|
|
7544
|
+
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);
|
|
7459
7545
|
const Names = {
|
|
7460
7546
|
0: "null",
|
|
7461
7547
|
7: "bell",
|
|
@@ -7472,6 +7558,9 @@ const Names = {
|
|
|
7472
7558
|
8232: "line separator",
|
|
7473
7559
|
8237: "left-to-right override",
|
|
7474
7560
|
8238: "right-to-left override",
|
|
7561
|
+
8294: "left-to-right isolate",
|
|
7562
|
+
8295: "right-to-left isolate",
|
|
7563
|
+
8297: "pop directional isolate",
|
|
7475
7564
|
8233: "paragraph separator",
|
|
7476
7565
|
65279: "zero width no-break space",
|
|
7477
7566
|
65532: "object replacement"
|