@codemirror/view 6.38.2 → 6.38.4
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 +18 -0
- package/dist/index.cjs +40 -18
- package/dist/index.js +40 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 6.38.4 (2025-09-28)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Work around a Chrome Android issue where the browser doesn't properly fire composition end events, leaving CodeMirror to believe the user was still composing.
|
|
6
|
+
|
|
7
|
+
## 6.38.3 (2025-09-22)
|
|
8
|
+
|
|
9
|
+
### Bug fixes
|
|
10
|
+
|
|
11
|
+
Work around a rendering bug in Mobile Safari by completely hiding empty layers.
|
|
12
|
+
|
|
13
|
+
Fix vertical cursor motion in Chrome around decorations with bottom borders or margins.
|
|
14
|
+
|
|
15
|
+
Fix an issue that caused mark decorations longer than 512 characters to needlessly be split.
|
|
16
|
+
|
|
17
|
+
Move the cursor out of atomic ranges when text input happens.
|
|
18
|
+
|
|
1
19
|
## 6.38.2 (2025-09-01)
|
|
2
20
|
|
|
3
21
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -1786,13 +1786,14 @@ class ContentBuilder {
|
|
|
1786
1786
|
this.textOff = 0;
|
|
1787
1787
|
}
|
|
1788
1788
|
}
|
|
1789
|
-
let
|
|
1789
|
+
let remaining = Math.min(this.text.length - this.textOff, length);
|
|
1790
|
+
let take = Math.min(remaining, 512 /* T.Chunk */);
|
|
1790
1791
|
this.flushBuffer(active.slice(active.length - openStart));
|
|
1791
1792
|
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1792
1793
|
this.atCursorPos = true;
|
|
1793
1794
|
this.textOff += take;
|
|
1794
1795
|
length -= take;
|
|
1795
|
-
openStart = 0;
|
|
1796
|
+
openStart = remaining <= take ? 0 : active.length;
|
|
1796
1797
|
}
|
|
1797
1798
|
}
|
|
1798
1799
|
span(from, to, active, openStart) {
|
|
@@ -3607,14 +3608,13 @@ function posAtCoords(view, coords, precise, bias = -1) {
|
|
|
3607
3608
|
}
|
|
3608
3609
|
else if (doc.caretRangeFromPoint) {
|
|
3609
3610
|
let range = doc.caretRangeFromPoint(x, y);
|
|
3610
|
-
if (range)
|
|
3611
|
+
if (range)
|
|
3611
3612
|
({ startContainer: node, startOffset: offset } = range);
|
|
3612
|
-
if (!view.contentDOM.contains(node) ||
|
|
3613
|
-
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3614
|
-
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
|
|
3615
|
-
node = undefined;
|
|
3616
|
-
}
|
|
3617
3613
|
}
|
|
3614
|
+
if (node && (!view.contentDOM.contains(node) ||
|
|
3615
|
+
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3616
|
+
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x)))
|
|
3617
|
+
node = undefined;
|
|
3618
3618
|
// Chrome will return offsets into <input> elements without child
|
|
3619
3619
|
// nodes, which will lead to a null deref below, so clip the
|
|
3620
3620
|
// offset to the node size.
|
|
@@ -3650,11 +3650,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3650
3650
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3651
3651
|
return block.from + state.findColumn(content, into, view.state.tabSize);
|
|
3652
3652
|
}
|
|
3653
|
-
|
|
3654
|
-
// the space between lines as belonging to the last character of the
|
|
3655
|
-
// line before. This is used to detect such a result so that it can be
|
|
3656
|
-
// ignored (issue #401).
|
|
3657
|
-
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3653
|
+
function isEndOfLineBefore(node, offset, x) {
|
|
3658
3654
|
let len, scan = node;
|
|
3659
3655
|
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3660
3656
|
return false;
|
|
@@ -3674,10 +3670,17 @@ function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
|
3674
3670
|
}
|
|
3675
3671
|
return textRange(node, len - 1, len).getBoundingClientRect().right > x;
|
|
3676
3672
|
}
|
|
3673
|
+
// In case of a high line height, Safari's caretRangeFromPoint treats
|
|
3674
|
+
// the space between lines as belonging to the last character of the
|
|
3675
|
+
// line before. This is used to detect such a result so that it can be
|
|
3676
|
+
// ignored (issue #401).
|
|
3677
|
+
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3678
|
+
return isEndOfLineBefore(node, offset, x);
|
|
3679
|
+
}
|
|
3677
3680
|
// Chrome will move positions between lines to the start of the next line
|
|
3678
3681
|
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3679
3682
|
if (offset != 0)
|
|
3680
|
-
return
|
|
3683
|
+
return isEndOfLineBefore(node, offset, x);
|
|
3681
3684
|
for (let cur = node;;) {
|
|
3682
3685
|
let parent = cur.parentNode;
|
|
3683
3686
|
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
@@ -4103,8 +4106,20 @@ function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
|
4103
4106
|
return true;
|
|
4104
4107
|
}
|
|
4105
4108
|
function applyDefaultInsert(view, change, newSel) {
|
|
4106
|
-
let tr, startState = view.state, sel = startState.selection.main;
|
|
4107
|
-
if (change.from
|
|
4109
|
+
let tr, startState = view.state, sel = startState.selection.main, inAtomic = -1;
|
|
4110
|
+
if (change.from == change.to && change.from < sel.from || change.from > sel.to) {
|
|
4111
|
+
let side = change.from < sel.from ? -1 : 1, pos = side < 0 ? sel.from : sel.to;
|
|
4112
|
+
let moved = skipAtomicRanges(startState.facet(atomicRanges).map(f => f(view)), pos, side);
|
|
4113
|
+
if (change.from == moved)
|
|
4114
|
+
inAtomic = moved;
|
|
4115
|
+
}
|
|
4116
|
+
if (inAtomic > -1) {
|
|
4117
|
+
tr = {
|
|
4118
|
+
changes: change,
|
|
4119
|
+
selection: state.EditorSelection.cursor(change.from + change.insert.length, -1)
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
else if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
4108
4123
|
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
4109
4124
|
view.inputState.composing < 0) {
|
|
4110
4125
|
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
@@ -7298,6 +7313,10 @@ class EditContextManager {
|
|
|
7298
7313
|
this.revertPending(view.state);
|
|
7299
7314
|
this.setSelection(view.state);
|
|
7300
7315
|
}
|
|
7316
|
+
// Work around missed compositionend events. See https://discuss.codemirror.net/t/a/9514
|
|
7317
|
+
if (change.from < change.to && !change.insert.length && view.inputState.composing >= 0 &&
|
|
7318
|
+
!/[\\p{Alphabetic}\\p{Number}_]/.test(context.text.slice(Math.max(0, e.updateRangeStart - 1), Math.min(context.text.length, e.updateRangeStart + 1))))
|
|
7319
|
+
this.handlers.compositionend(e);
|
|
7301
7320
|
};
|
|
7302
7321
|
this.handlers.characterboundsupdate = e => {
|
|
7303
7322
|
let rects = [], prev = null;
|
|
@@ -7313,10 +7332,11 @@ class EditContextManager {
|
|
|
7313
7332
|
let deco = [];
|
|
7314
7333
|
for (let format of e.getTextFormats()) {
|
|
7315
7334
|
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7316
|
-
if (lineStyle
|
|
7335
|
+
if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
|
|
7317
7336
|
let from = this.toEditorPos(format.rangeStart), to = this.toEditorPos(format.rangeEnd);
|
|
7318
7337
|
if (from < to) {
|
|
7319
|
-
|
|
7338
|
+
// These values changed from capitalized custom strings to lower-case CSS keywords in 2025
|
|
7339
|
+
let style = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
|
|
7320
7340
|
deco.push(Decoration.mark({ attributes: { style } }).range(from, to));
|
|
7321
7341
|
}
|
|
7322
7342
|
}
|
|
@@ -9083,6 +9103,8 @@ class LayerView {
|
|
|
9083
9103
|
old = next;
|
|
9084
9104
|
}
|
|
9085
9105
|
this.drawn = markers;
|
|
9106
|
+
if (browser.ios) // Issue #1600
|
|
9107
|
+
this.dom.style.display = this.dom.firstChild ? "" : "none";
|
|
9086
9108
|
}
|
|
9087
9109
|
}
|
|
9088
9110
|
destroy() {
|
package/dist/index.js
CHANGED
|
@@ -1783,13 +1783,14 @@ class ContentBuilder {
|
|
|
1783
1783
|
this.textOff = 0;
|
|
1784
1784
|
}
|
|
1785
1785
|
}
|
|
1786
|
-
let
|
|
1786
|
+
let remaining = Math.min(this.text.length - this.textOff, length);
|
|
1787
|
+
let take = Math.min(remaining, 512 /* T.Chunk */);
|
|
1787
1788
|
this.flushBuffer(active.slice(active.length - openStart));
|
|
1788
1789
|
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1789
1790
|
this.atCursorPos = true;
|
|
1790
1791
|
this.textOff += take;
|
|
1791
1792
|
length -= take;
|
|
1792
|
-
openStart = 0;
|
|
1793
|
+
openStart = remaining <= take ? 0 : active.length;
|
|
1793
1794
|
}
|
|
1794
1795
|
}
|
|
1795
1796
|
span(from, to, active, openStart) {
|
|
@@ -3603,14 +3604,13 @@ function posAtCoords(view, coords, precise, bias = -1) {
|
|
|
3603
3604
|
}
|
|
3604
3605
|
else if (doc.caretRangeFromPoint) {
|
|
3605
3606
|
let range = doc.caretRangeFromPoint(x, y);
|
|
3606
|
-
if (range)
|
|
3607
|
+
if (range)
|
|
3607
3608
|
({ startContainer: node, startOffset: offset } = range);
|
|
3608
|
-
if (!view.contentDOM.contains(node) ||
|
|
3609
|
-
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3610
|
-
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
|
|
3611
|
-
node = undefined;
|
|
3612
|
-
}
|
|
3613
3609
|
}
|
|
3610
|
+
if (node && (!view.contentDOM.contains(node) ||
|
|
3611
|
+
browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
|
|
3612
|
+
browser.chrome && isSuspiciousChromeCaretResult(node, offset, x)))
|
|
3613
|
+
node = undefined;
|
|
3614
3614
|
// Chrome will return offsets into <input> elements without child
|
|
3615
3615
|
// nodes, which will lead to a null deref below, so clip the
|
|
3616
3616
|
// offset to the node size.
|
|
@@ -3646,11 +3646,7 @@ function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|
|
3646
3646
|
let content = view.state.sliceDoc(block.from, block.to);
|
|
3647
3647
|
return block.from + findColumn(content, into, view.state.tabSize);
|
|
3648
3648
|
}
|
|
3649
|
-
|
|
3650
|
-
// the space between lines as belonging to the last character of the
|
|
3651
|
-
// line before. This is used to detect such a result so that it can be
|
|
3652
|
-
// ignored (issue #401).
|
|
3653
|
-
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3649
|
+
function isEndOfLineBefore(node, offset, x) {
|
|
3654
3650
|
let len, scan = node;
|
|
3655
3651
|
if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
|
|
3656
3652
|
return false;
|
|
@@ -3670,10 +3666,17 @@ function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
|
3670
3666
|
}
|
|
3671
3667
|
return textRange(node, len - 1, len).getBoundingClientRect().right > x;
|
|
3672
3668
|
}
|
|
3669
|
+
// In case of a high line height, Safari's caretRangeFromPoint treats
|
|
3670
|
+
// the space between lines as belonging to the last character of the
|
|
3671
|
+
// line before. This is used to detect such a result so that it can be
|
|
3672
|
+
// ignored (issue #401).
|
|
3673
|
+
function isSuspiciousSafariCaretResult(node, offset, x) {
|
|
3674
|
+
return isEndOfLineBefore(node, offset, x);
|
|
3675
|
+
}
|
|
3673
3676
|
// Chrome will move positions between lines to the start of the next line
|
|
3674
3677
|
function isSuspiciousChromeCaretResult(node, offset, x) {
|
|
3675
3678
|
if (offset != 0)
|
|
3676
|
-
return
|
|
3679
|
+
return isEndOfLineBefore(node, offset, x);
|
|
3677
3680
|
for (let cur = node;;) {
|
|
3678
3681
|
let parent = cur.parentNode;
|
|
3679
3682
|
if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
|
|
@@ -4099,8 +4102,20 @@ function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
|
4099
4102
|
return true;
|
|
4100
4103
|
}
|
|
4101
4104
|
function applyDefaultInsert(view, change, newSel) {
|
|
4102
|
-
let tr, startState = view.state, sel = startState.selection.main;
|
|
4103
|
-
if (change.from
|
|
4105
|
+
let tr, startState = view.state, sel = startState.selection.main, inAtomic = -1;
|
|
4106
|
+
if (change.from == change.to && change.from < sel.from || change.from > sel.to) {
|
|
4107
|
+
let side = change.from < sel.from ? -1 : 1, pos = side < 0 ? sel.from : sel.to;
|
|
4108
|
+
let moved = skipAtomicRanges(startState.facet(atomicRanges).map(f => f(view)), pos, side);
|
|
4109
|
+
if (change.from == moved)
|
|
4110
|
+
inAtomic = moved;
|
|
4111
|
+
}
|
|
4112
|
+
if (inAtomic > -1) {
|
|
4113
|
+
tr = {
|
|
4114
|
+
changes: change,
|
|
4115
|
+
selection: EditorSelection.cursor(change.from + change.insert.length, -1)
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
else if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
4104
4119
|
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
4105
4120
|
view.inputState.composing < 0) {
|
|
4106
4121
|
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
@@ -7293,6 +7308,10 @@ class EditContextManager {
|
|
|
7293
7308
|
this.revertPending(view.state);
|
|
7294
7309
|
this.setSelection(view.state);
|
|
7295
7310
|
}
|
|
7311
|
+
// Work around missed compositionend events. See https://discuss.codemirror.net/t/a/9514
|
|
7312
|
+
if (change.from < change.to && !change.insert.length && view.inputState.composing >= 0 &&
|
|
7313
|
+
!/[\\p{Alphabetic}\\p{Number}_]/.test(context.text.slice(Math.max(0, e.updateRangeStart - 1), Math.min(context.text.length, e.updateRangeStart + 1))))
|
|
7314
|
+
this.handlers.compositionend(e);
|
|
7296
7315
|
};
|
|
7297
7316
|
this.handlers.characterboundsupdate = e => {
|
|
7298
7317
|
let rects = [], prev = null;
|
|
@@ -7308,10 +7327,11 @@ class EditContextManager {
|
|
|
7308
7327
|
let deco = [];
|
|
7309
7328
|
for (let format of e.getTextFormats()) {
|
|
7310
7329
|
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7311
|
-
if (lineStyle
|
|
7330
|
+
if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
|
|
7312
7331
|
let from = this.toEditorPos(format.rangeStart), to = this.toEditorPos(format.rangeEnd);
|
|
7313
7332
|
if (from < to) {
|
|
7314
|
-
|
|
7333
|
+
// These values changed from capitalized custom strings to lower-case CSS keywords in 2025
|
|
7334
|
+
let style = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
|
|
7315
7335
|
deco.push(Decoration.mark({ attributes: { style } }).range(from, to));
|
|
7316
7336
|
}
|
|
7317
7337
|
}
|
|
@@ -9078,6 +9098,8 @@ class LayerView {
|
|
|
9078
9098
|
old = next;
|
|
9079
9099
|
}
|
|
9080
9100
|
this.drawn = markers;
|
|
9101
|
+
if (browser.ios) // Issue #1600
|
|
9102
|
+
this.dom.style.display = this.dom.firstChild ? "" : "none";
|
|
9081
9103
|
}
|
|
9082
9104
|
}
|
|
9083
9105
|
destroy() {
|