@codemirror/view 0.19.39 → 0.19.43

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 CHANGED
@@ -1,3 +1,53 @@
1
+ ## 0.19.43 (2022-02-16)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix several issues where editing or composition went wrong due to our zero-width space kludge characters ending up in unexpected places.
6
+
7
+ Make sure the editor re-measures its dimensions whenever its theme changes.
8
+
9
+ Fix an issue where some keys on Android phones could leave the editor DOM unsynced with the actual document.
10
+
11
+ ## 0.19.42 (2022-02-05)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix a regression in cursor position determination after making an edit next to a widget.
16
+
17
+ ## 0.19.41 (2022-02-04)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix an issue where the editor's view of its content height could go out of sync with the DOM when a line-wrapping editor had its width changed, causing wrapping to change.
22
+
23
+ Fix a bug that caused the editor to draw way too much content when scrolling to a position in an editor (much) taller than the window.
24
+
25
+ Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
26
+
27
+ Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
28
+
29
+ Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
30
+
31
+ Fix a bug where, on Chrome, replacement across line boundaries and next to widgets could cause bogus zero-width characters to appear in the content.
32
+
33
+ ## 0.19.40 (2022-01-19)
34
+
35
+ ### Bug fixes
36
+
37
+ Make composition input properly appear at secondary cursors (except when those are in the DOM node with the composition, in which case the browser won't allow us to intervene without aborting the composition).
38
+
39
+ Fix a bug that cause the editor to get confused about which content was visible after scrolling something into view.
40
+
41
+ Fix a bug where the dummy elements rendered around widgets could end up in a separate set of wrapping marks, and thus become visible.
42
+
43
+ `EditorView.moveVertically` now preserves the `assoc` property of the input range.
44
+
45
+ Get rid of gaps between selection elements drawn by `drawSelection`.
46
+
47
+ Fix an issue where replacing text next to a widget might leak bogus zero-width spaces into the document.
48
+
49
+ Avoid browser selection mishandling when a focused view has `setState` called by eagerly refocusing it.
50
+
1
51
  ## 0.19.39 (2022-01-06)
2
52
 
3
53
  ### Bug fixes
@@ -48,7 +98,7 @@ Fix an issue where backspacing out a selection on Chrome Android would sometimes
48
98
 
49
99
  ### Bug fixes
50
100
 
51
- Fix a bug where content line elements would in some cases lose their `cm-line` class. Move test to scrollIntoView
101
+ Fix a bug where content line elements would in some cases lose their `cm-line` class.
52
102
 
53
103
  ## 0.19.33 (2021-12-16)
54
104
 
package/dist/index.cjs CHANGED
@@ -602,9 +602,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
602
602
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
603
603
  }
604
604
 
605
- let [nav, doc] = typeof navigator != "undefined"
606
- ? [navigator, document]
607
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
605
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
606
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
608
607
  const ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
609
608
  const ie_upto10 = /MSIE \d/.test(nav.userAgent);
610
609
  const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -860,9 +859,6 @@ class CompositionView extends WidgetView {
860
859
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
861
860
  get isEditable() { return true; }
862
861
  }
863
- // Use two characters on Android, to prevent Chrome from closing the
864
- // virtual keyboard when backspacing after a widget (#602).
865
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
866
862
  // These are drawn around uneditable widgets to avoid a number of
867
863
  // browser bugs that show up when the cursor is directly next to
868
864
  // uneditable inline content.
@@ -878,21 +874,21 @@ class WidgetBufferView extends ContentView {
878
874
  }
879
875
  split() { return new WidgetBufferView(this.side); }
880
876
  sync() {
881
- if (!this.dom)
882
- this.setDOM(document.createTextNode(ZeroWidthSpace));
883
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
884
- this.dom.nodeValue = ZeroWidthSpace;
877
+ if (!this.dom) {
878
+ let dom = document.createElement("img");
879
+ dom.className = "cm-widgetBuffer";
880
+ this.setDOM(dom);
881
+ }
885
882
  }
886
883
  getSide() { return this.side; }
887
884
  domAtPos(pos) { return DOMPos.before(this.dom); }
888
885
  localPosFromDOM() { return 0; }
889
886
  domBoundsAround() { return null; }
890
887
  coordsAt(pos) {
891
- let rects = clientRectsFor(this.dom);
892
- return rects[rects.length - 1] || null;
888
+ return this.dom.getBoundingClientRect();
893
889
  }
894
890
  get overrideDOMText() {
895
- return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
891
+ return text.Text.empty;
896
892
  }
897
893
  }
898
894
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -1131,8 +1127,8 @@ class Decoration extends rangeset.RangeValue {
1131
1127
  static replace(spec) {
1132
1128
  let block = !!spec.block;
1133
1129
  let { start, end } = getInclusive(spec, block);
1134
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1135
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1130
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1131
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1136
1132
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1137
1133
  }
1138
1134
  /**
@@ -1237,7 +1233,7 @@ function widgetsEq(a, b) {
1237
1233
  }
1238
1234
  function addRange(from, to, ranges, margin = 0) {
1239
1235
  let last = ranges.length - 1;
1240
- if (last >= 0 && ranges[last] + margin > from)
1236
+ if (last >= 0 && ranges[last] + margin >= from)
1241
1237
  ranges[last] = Math.max(ranges[last], to);
1242
1238
  else
1243
1239
  ranges.push(from, to);
@@ -1518,7 +1514,7 @@ class ContentBuilder {
1518
1514
  }
1519
1515
  }
1520
1516
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1521
- this.flushBuffer(active);
1517
+ this.flushBuffer(active.slice(0, openStart));
1522
1518
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1523
1519
  this.atCursorPos = true;
1524
1520
  this.textOff += take;
@@ -1577,11 +1573,13 @@ class ContentBuilder {
1577
1573
  this.openStart = openStart;
1578
1574
  }
1579
1575
  filterPoint(from, to, value, index) {
1580
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1581
- return true;
1582
- if (value.block)
1583
- throw new RangeError("Block decorations may not be specified via plugins");
1584
- return to <= this.doc.lineAt(this.pos).to;
1576
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1577
+ if (value.block)
1578
+ throw new RangeError("Block decorations may not be specified via plugins");
1579
+ if (to > this.doc.lineAt(this.pos).to)
1580
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1581
+ }
1582
+ return true;
1585
1583
  }
1586
1584
  static build(text, from, to, decorations, pluginDecorationLength) {
1587
1585
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2305,12 +2303,18 @@ function moveVisually(line, order, dir, start, forward) {
2305
2303
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2306
2304
  }
2307
2305
 
2306
+ const LineBreakPlaceholder = "\uffff";
2308
2307
  class DOMReader {
2309
- constructor(points, view) {
2308
+ constructor(points, state$1) {
2310
2309
  this.points = points;
2311
- this.view = view;
2312
2310
  this.text = "";
2313
- this.lineBreak = view.state.lineBreak;
2311
+ this.lineSeparator = state$1.facet(state.EditorState.lineSeparator);
2312
+ }
2313
+ append(text) {
2314
+ this.text += text;
2315
+ }
2316
+ lineBreak() {
2317
+ this.text += LineBreakPlaceholder;
2314
2318
  }
2315
2319
  readRange(start, end) {
2316
2320
  if (!start)
@@ -2326,33 +2330,61 @@ class DOMReader {
2326
2330
  if (view && nextView ? view.breakAfter :
2327
2331
  (view ? view.breakAfter : isBlockElement(cur)) ||
2328
2332
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2329
- this.text += this.lineBreak;
2333
+ this.lineBreak();
2330
2334
  cur = next;
2331
2335
  }
2332
2336
  this.findPointBefore(parent, end);
2333
2337
  return this;
2334
2338
  }
2339
+ readTextNode(node) {
2340
+ let text = node.nodeValue;
2341
+ for (let point of this.points)
2342
+ if (point.node == node)
2343
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2344
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2345
+ let nextBreak = -1, breakSize = 1, m;
2346
+ if (this.lineSeparator) {
2347
+ nextBreak = text.indexOf(this.lineSeparator, off);
2348
+ breakSize = this.lineSeparator.length;
2349
+ }
2350
+ else if (m = re.exec(text)) {
2351
+ nextBreak = m.index;
2352
+ breakSize = m[0].length;
2353
+ }
2354
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2355
+ if (nextBreak < 0)
2356
+ break;
2357
+ this.lineBreak();
2358
+ if (breakSize > 1)
2359
+ for (let point of this.points)
2360
+ if (point.node == node && point.pos > this.text.length)
2361
+ point.pos -= breakSize - 1;
2362
+ off = nextBreak + breakSize;
2363
+ }
2364
+ }
2335
2365
  readNode(node) {
2336
2366
  if (node.cmIgnore)
2337
2367
  return;
2338
2368
  let view = ContentView.get(node);
2339
2369
  let fromView = view && view.overrideDOMText;
2340
- let text;
2341
- if (fromView != null)
2342
- text = fromView.sliceString(0, undefined, this.lineBreak);
2343
- else if (node.nodeType == 3)
2344
- text = node.nodeValue;
2345
- else if (node.nodeName == "BR")
2346
- text = node.nextSibling ? this.lineBreak : "";
2347
- else if (node.nodeType == 1)
2370
+ if (fromView != null) {
2371
+ this.findPointInside(node, fromView.length);
2372
+ for (let i = fromView.iter(); !i.next().done;) {
2373
+ if (i.lineBreak)
2374
+ this.lineBreak();
2375
+ else
2376
+ this.append(i.value);
2377
+ }
2378
+ }
2379
+ else if (node.nodeType == 3) {
2380
+ this.readTextNode(node);
2381
+ }
2382
+ else if (node.nodeName == "BR") {
2383
+ if (node.nextSibling)
2384
+ this.lineBreak();
2385
+ }
2386
+ else if (node.nodeType == 1) {
2348
2387
  this.readRange(node.firstChild, null);
2349
- if (text != null) {
2350
- this.findPointIn(node, text.length);
2351
- this.text += text;
2352
- // Chrome inserts two newlines when pressing shift-enter at the
2353
- // end of a line. This drops one of those.
2354
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2355
- this.text = this.text.slice(0, -1);
2356
2388
  }
2357
2389
  }
2358
2390
  findPointBefore(node, next) {
@@ -2360,10 +2392,10 @@ class DOMReader {
2360
2392
  if (point.node == node && node.childNodes[point.offset] == next)
2361
2393
  point.pos = this.text.length;
2362
2394
  }
2363
- findPointIn(node, maxLen) {
2395
+ findPointInside(node, maxLen) {
2364
2396
  for (let point of this.points)
2365
- if (point.node == node)
2366
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2397
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2398
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2367
2399
  }
2368
2400
  }
2369
2401
  function isBlockElement(node) {
@@ -2768,51 +2800,57 @@ class BlockGapWidget extends WidgetType {
2768
2800
  }
2769
2801
  get estimatedHeight() { return this.height; }
2770
2802
  }
2771
- function computeCompositionDeco(view, changes) {
2803
+ function compositionSurroundingNode(view) {
2772
2804
  let sel = view.observer.selectionRange;
2773
2805
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2774
2806
  if (!textNode)
2775
- return Decoration.none;
2807
+ return null;
2776
2808
  let cView = view.docView.nearest(textNode);
2777
2809
  if (!cView)
2778
- return Decoration.none;
2779
- let from, to, topNode = textNode;
2810
+ return null;
2780
2811
  if (cView instanceof LineView) {
2812
+ let topNode = textNode;
2781
2813
  while (topNode.parentNode != cView.dom)
2782
2814
  topNode = topNode.parentNode;
2783
2815
  let prev = topNode.previousSibling;
2784
2816
  while (prev && !ContentView.get(prev))
2785
2817
  prev = prev.previousSibling;
2786
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2818
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2819
+ return { from: pos, to: pos, node: topNode, text: textNode };
2787
2820
  }
2788
2821
  else {
2789
2822
  for (;;) {
2790
2823
  let { parent } = cView;
2791
2824
  if (!parent)
2792
- return Decoration.none;
2825
+ return null;
2793
2826
  if (parent instanceof LineView)
2794
2827
  break;
2795
2828
  cView = parent;
2796
2829
  }
2797
- from = cView.posAtStart;
2798
- to = from + cView.length;
2799
- topNode = cView.dom;
2830
+ let from = cView.posAtStart;
2831
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2800
2832
  }
2833
+ }
2834
+ function computeCompositionDeco(view, changes) {
2835
+ let surrounding = compositionSurroundingNode(view);
2836
+ if (!surrounding)
2837
+ return Decoration.none;
2838
+ let { from, to, node, text: textNode } = surrounding;
2801
2839
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2802
- let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2803
- new DOMReader([], view).readRange(topNode.firstChild, null).text;
2840
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2841
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2804
2842
  if (newTo - newFrom < text.length) {
2805
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2843
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2806
2844
  newTo = newFrom + text.length;
2807
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2845
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2808
2846
  newFrom = newTo - text.length;
2809
2847
  else
2810
2848
  return Decoration.none;
2811
2849
  }
2812
- else if (state.sliceDoc(newFrom, newTo) != text) {
2850
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2813
2851
  return Decoration.none;
2814
2852
  }
2815
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2853
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2816
2854
  }
2817
2855
  class CompositionWidget extends WidgetType {
2818
2856
  constructor(top, text) {
@@ -3149,7 +3187,7 @@ function byGroup(view, pos, start) {
3149
3187
  function moveVertically(view, start, forward, distance) {
3150
3188
  let startPos = start.head, dir = forward ? 1 : -1;
3151
3189
  if (startPos == (forward ? view.state.doc.length : 0))
3152
- return state.EditorSelection.cursor(startPos);
3190
+ return state.EditorSelection.cursor(startPos, start.assoc);
3153
3191
  let goal = start.goalColumn, startY;
3154
3192
  let rect = view.contentDOM.getBoundingClientRect();
3155
3193
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3170,7 +3208,7 @@ function moveVertically(view, start, forward, distance) {
3170
3208
  let curY = startY + (dist + extra) * dir;
3171
3209
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3172
3210
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3173
- return state.EditorSelection.cursor(pos, undefined, undefined, goal);
3211
+ return state.EditorSelection.cursor(pos, start.assoc, undefined, goal);
3174
3212
  }
3175
3213
  }
3176
3214
  function skipAtoms(view, oldPos, pos) {
@@ -4682,6 +4720,12 @@ class ViewState {
4682
4720
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4683
4721
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4684
4722
  let result = 0, bias = 0;
4723
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4724
+ if (oracle.lineWrapping)
4725
+ measureContent = true;
4726
+ this.editorWidth = view.scrollDOM.clientWidth;
4727
+ result |= 8 /* Geometry */;
4728
+ }
4685
4729
  if (measureContent) {
4686
4730
  this.mustMeasureContent = false;
4687
4731
  this.contentDOMHeight = dom.clientHeight;
@@ -4694,7 +4738,8 @@ class ViewState {
4694
4738
  }
4695
4739
  }
4696
4740
  // Pixel viewport
4697
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4741
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4742
+ : visiblePixelRange(dom, this.paddingTop);
4698
4743
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4699
4744
  this.pixelViewport = pixelViewport;
4700
4745
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4706,11 +4751,9 @@ class ViewState {
4706
4751
  if (!this.inView)
4707
4752
  return 0;
4708
4753
  let contentWidth = dom.clientWidth;
4709
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4710
- this.editorWidth != view.scrollDOM.clientWidth) {
4754
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4711
4755
  this.contentDOMWidth = contentWidth;
4712
4756
  this.editorHeight = view.scrollDOM.clientHeight;
4713
- this.editorWidth = view.scrollDOM.clientWidth;
4714
4757
  result |= 8 /* Geometry */;
4715
4758
  }
4716
4759
  if (measureContent) {
@@ -4765,8 +4808,9 @@ class ViewState {
4765
4808
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4766
4809
  // If scrollTarget is given, make sure the viewport includes that position
4767
4810
  if (scrollTarget) {
4768
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4811
+ let { head } = scrollTarget.range;
4769
4812
  if (head < viewport.from || head > viewport.to) {
4813
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4770
4814
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4771
4815
  if (scrollTarget.y == "center")
4772
4816
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5152,6 +5196,10 @@ const baseTheme = buildTheme("." + baseThemeID, {
5152
5196
  overflow: "hidden",
5153
5197
  verticalAlign: "bottom"
5154
5198
  },
5199
+ ".cm-widgetBuffer": {
5200
+ verticalAlign: "text-bottom",
5201
+ height: "1em",
5202
+ },
5155
5203
  ".cm-placeholder": {
5156
5204
  color: "#888",
5157
5205
  display: "inline-block",
@@ -5400,7 +5448,7 @@ class DOMObserver {
5400
5448
  }
5401
5449
  // Throw away any pending changes
5402
5450
  clear() {
5403
- this.observer.takeRecords();
5451
+ this.processRecords();
5404
5452
  this.queue.length = 0;
5405
5453
  this.selectionChanged = false;
5406
5454
  }
@@ -5566,9 +5614,8 @@ function applyDOMChange(view, start, end, typeOver) {
5566
5614
  return;
5567
5615
  let { from, to } = bounds;
5568
5616
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5569
- let reader = new DOMReader(selPoints, view);
5617
+ let reader = new DOMReader(selPoints, view.state);
5570
5618
  reader.readRange(bounds.startDOM, bounds.endDOM);
5571
- newSel = selectionFromPoints(selPoints, from);
5572
5619
  let preferredPos = sel.from, preferredSide = null;
5573
5620
  // Prefer anchoring to end when Backspace is pressed (or, on
5574
5621
  // Android, when something was deleted)
@@ -5577,10 +5624,17 @@ function applyDOMChange(view, start, end, typeOver) {
5577
5624
  preferredPos = sel.to;
5578
5625
  preferredSide = "end";
5579
5626
  }
5580
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5581
- if (diff)
5627
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5628
+ if (diff) {
5629
+ // Chrome inserts two newlines when pressing shift-enter at the
5630
+ // end of a line. This drops one of those.
5631
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5632
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5633
+ diff.toB--;
5582
5634
  change = { from: from + diff.from, to: from + diff.toA,
5583
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5635
+ insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5636
+ }
5637
+ newSel = selectionFromPoints(selPoints, from);
5584
5638
  }
5585
5639
  else if (view.hasFocus || !view.state.facet(editable)) {
5586
5640
  let domSel = view.observer.selectionRange;
@@ -5637,19 +5691,47 @@ function applyDOMChange(view, start, end, typeOver) {
5637
5691
  view.inputState.composing++;
5638
5692
  let tr;
5639
5693
  if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5640
- (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length)) {
5694
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5695
+ view.inputState.composing < 0) {
5641
5696
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5642
5697
  let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5643
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
5644
- after));
5698
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5645
5699
  }
5646
5700
  else {
5647
5701
  let changes = startState.changes(change);
5648
- tr = {
5649
- changes,
5650
- selection: newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5651
- ? startState.selection.replaceRange(newSel.main) : undefined
5652
- };
5702
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5703
+ ? newSel.main : undefined;
5704
+ // Try to apply a composition change to all cursors
5705
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5706
+ change.to <= sel.to && change.to >= sel.to - 10) {
5707
+ let replaced = view.state.sliceDoc(change.from, change.to);
5708
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5709
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5710
+ tr = startState.changeByRange(range => {
5711
+ if (range.from == sel.from && range.to == sel.to)
5712
+ return { changes, range: mainSel || range.map(changes) };
5713
+ let to = range.to - offset, from = to - replaced.length;
5714
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5715
+ // Unfortunately, there's no way to make multiple
5716
+ // changes in the same node work without aborting
5717
+ // composition, so cursors in the composition range are
5718
+ // ignored.
5719
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5720
+ return { range };
5721
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5722
+ return {
5723
+ changes: rangeChanges,
5724
+ range: !mainSel ? range.map(rangeChanges) :
5725
+ state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5726
+ };
5727
+ });
5728
+ }
5729
+ else {
5730
+ tr = {
5731
+ changes,
5732
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5733
+ };
5734
+ }
5653
5735
  }
5654
5736
  let userEvent = "input.type";
5655
5737
  if (view.composing) {
@@ -5899,7 +5981,9 @@ class EditorView {
5899
5981
  finally {
5900
5982
  this.updateState = 0 /* Idle */;
5901
5983
  }
5902
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5984
+ if (update.startState.facet(theme) != update.state.facet(theme))
5985
+ this.viewState.mustMeasureContent = true;
5986
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5903
5987
  this.requestMeasure();
5904
5988
  if (!update.empty)
5905
5989
  for (let listener of this.state.facet(updateListener))
@@ -5920,6 +6004,7 @@ class EditorView {
5920
6004
  return;
5921
6005
  }
5922
6006
  this.updateState = 2 /* Updating */;
6007
+ let hadFocus = this.hasFocus;
5923
6008
  try {
5924
6009
  for (let plugin of this.plugins)
5925
6010
  plugin.destroy(this);
@@ -5937,6 +6022,8 @@ class EditorView {
5937
6022
  finally {
5938
6023
  this.updateState = 0 /* Idle */;
5939
6024
  }
6025
+ if (hadFocus)
6026
+ this.focus();
5940
6027
  this.requestMeasure();
5941
6028
  }
5942
6029
  updatePlugins(update) {
@@ -6006,7 +6093,7 @@ class EditorView {
6006
6093
  return BadMeasure;
6007
6094
  }
6008
6095
  });
6009
- let update = new ViewUpdate(this, this.state), redrawn = false;
6096
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6010
6097
  update.flags |= changed;
6011
6098
  if (!updated)
6012
6099
  updated = update;
@@ -6033,11 +6120,12 @@ class EditorView {
6033
6120
  if (this.viewState.scrollTarget) {
6034
6121
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6035
6122
  this.viewState.scrollTarget = null;
6123
+ scrolled = true;
6036
6124
  }
6037
6125
  if (redrawn)
6038
6126
  this.docView.updateSelection(true);
6039
6127
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6040
- this.measureRequests.length == 0)
6128
+ !scrolled && this.measureRequests.length == 0)
6041
6129
  break;
6042
6130
  }
6043
6131
  }
@@ -6173,7 +6261,7 @@ class EditorView {
6173
6261
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6174
6262
  queries.
6175
6263
 
6176
- *Deprecated: use `blockAtHeight` instead.*
6264
+ *Deprecated: use `elementAtHeight` instead.*
6177
6265
  */
6178
6266
  blockAtHeight(height, docTop) {
6179
6267
  let top = ensureTop(docTop, this);
@@ -7018,7 +7106,7 @@ function measureRange(view, range) {
7018
7106
  return pieces(top).concat(between).concat(pieces(bottom));
7019
7107
  }
7020
7108
  function piece(left, top, right, bottom) {
7021
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7109
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7022
7110
  }
7023
7111
  function pieces({ top, bottom, horizontal }) {
7024
7112
  let pieces = [];
package/dist/index.d.ts CHANGED
@@ -802,7 +802,7 @@ declare class EditorView {
802
802
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
803
803
  queries.
804
804
 
805
- *Deprecated: use `blockAtHeight` instead.*
805
+ *Deprecated: use `elementAtHeight` instead.*
806
806
  */
807
807
  blockAtHeight(height: number, docTop?: number): BlockInfo;
808
808
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
1
+ import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, EditorState, CharCategory, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
@@ -599,9 +599,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
599
599
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
600
600
  }
601
601
 
602
- let [nav, doc] = typeof navigator != "undefined"
603
- ? [navigator, document]
604
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
602
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
603
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
605
604
  const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
606
605
  const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
607
606
  const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -857,9 +856,6 @@ class CompositionView extends WidgetView {
857
856
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
858
857
  get isEditable() { return true; }
859
858
  }
860
- // Use two characters on Android, to prevent Chrome from closing the
861
- // virtual keyboard when backspacing after a widget (#602).
862
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
863
859
  // These are drawn around uneditable widgets to avoid a number of
864
860
  // browser bugs that show up when the cursor is directly next to
865
861
  // uneditable inline content.
@@ -875,21 +871,21 @@ class WidgetBufferView extends ContentView {
875
871
  }
876
872
  split() { return new WidgetBufferView(this.side); }
877
873
  sync() {
878
- if (!this.dom)
879
- this.setDOM(document.createTextNode(ZeroWidthSpace));
880
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
881
- this.dom.nodeValue = ZeroWidthSpace;
874
+ if (!this.dom) {
875
+ let dom = document.createElement("img");
876
+ dom.className = "cm-widgetBuffer";
877
+ this.setDOM(dom);
878
+ }
882
879
  }
883
880
  getSide() { return this.side; }
884
881
  domAtPos(pos) { return DOMPos.before(this.dom); }
885
882
  localPosFromDOM() { return 0; }
886
883
  domBoundsAround() { return null; }
887
884
  coordsAt(pos) {
888
- let rects = clientRectsFor(this.dom);
889
- return rects[rects.length - 1] || null;
885
+ return this.dom.getBoundingClientRect();
890
886
  }
891
887
  get overrideDOMText() {
892
- return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
888
+ return Text.empty;
893
889
  }
894
890
  }
895
891
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -1127,8 +1123,8 @@ class Decoration extends RangeValue {
1127
1123
  static replace(spec) {
1128
1124
  let block = !!spec.block;
1129
1125
  let { start, end } = getInclusive(spec, block);
1130
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1131
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1126
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1127
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1132
1128
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1133
1129
  }
1134
1130
  /**
@@ -1233,7 +1229,7 @@ function widgetsEq(a, b) {
1233
1229
  }
1234
1230
  function addRange(from, to, ranges, margin = 0) {
1235
1231
  let last = ranges.length - 1;
1236
- if (last >= 0 && ranges[last] + margin > from)
1232
+ if (last >= 0 && ranges[last] + margin >= from)
1237
1233
  ranges[last] = Math.max(ranges[last], to);
1238
1234
  else
1239
1235
  ranges.push(from, to);
@@ -1514,7 +1510,7 @@ class ContentBuilder {
1514
1510
  }
1515
1511
  }
1516
1512
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1517
- this.flushBuffer(active);
1513
+ this.flushBuffer(active.slice(0, openStart));
1518
1514
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1519
1515
  this.atCursorPos = true;
1520
1516
  this.textOff += take;
@@ -1573,11 +1569,13 @@ class ContentBuilder {
1573
1569
  this.openStart = openStart;
1574
1570
  }
1575
1571
  filterPoint(from, to, value, index) {
1576
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1577
- return true;
1578
- if (value.block)
1579
- throw new RangeError("Block decorations may not be specified via plugins");
1580
- return to <= this.doc.lineAt(this.pos).to;
1572
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1573
+ if (value.block)
1574
+ throw new RangeError("Block decorations may not be specified via plugins");
1575
+ if (to > this.doc.lineAt(this.pos).to)
1576
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1577
+ }
1578
+ return true;
1581
1579
  }
1582
1580
  static build(text, from, to, decorations, pluginDecorationLength) {
1583
1581
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2300,12 +2298,18 @@ function moveVisually(line, order, dir, start, forward) {
2300
2298
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2301
2299
  }
2302
2300
 
2301
+ const LineBreakPlaceholder = "\uffff";
2303
2302
  class DOMReader {
2304
- constructor(points, view) {
2303
+ constructor(points, state) {
2305
2304
  this.points = points;
2306
- this.view = view;
2307
2305
  this.text = "";
2308
- this.lineBreak = view.state.lineBreak;
2306
+ this.lineSeparator = state.facet(EditorState.lineSeparator);
2307
+ }
2308
+ append(text) {
2309
+ this.text += text;
2310
+ }
2311
+ lineBreak() {
2312
+ this.text += LineBreakPlaceholder;
2309
2313
  }
2310
2314
  readRange(start, end) {
2311
2315
  if (!start)
@@ -2321,33 +2325,61 @@ class DOMReader {
2321
2325
  if (view && nextView ? view.breakAfter :
2322
2326
  (view ? view.breakAfter : isBlockElement(cur)) ||
2323
2327
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2324
- this.text += this.lineBreak;
2328
+ this.lineBreak();
2325
2329
  cur = next;
2326
2330
  }
2327
2331
  this.findPointBefore(parent, end);
2328
2332
  return this;
2329
2333
  }
2334
+ readTextNode(node) {
2335
+ let text = node.nodeValue;
2336
+ for (let point of this.points)
2337
+ if (point.node == node)
2338
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2339
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2340
+ let nextBreak = -1, breakSize = 1, m;
2341
+ if (this.lineSeparator) {
2342
+ nextBreak = text.indexOf(this.lineSeparator, off);
2343
+ breakSize = this.lineSeparator.length;
2344
+ }
2345
+ else if (m = re.exec(text)) {
2346
+ nextBreak = m.index;
2347
+ breakSize = m[0].length;
2348
+ }
2349
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2350
+ if (nextBreak < 0)
2351
+ break;
2352
+ this.lineBreak();
2353
+ if (breakSize > 1)
2354
+ for (let point of this.points)
2355
+ if (point.node == node && point.pos > this.text.length)
2356
+ point.pos -= breakSize - 1;
2357
+ off = nextBreak + breakSize;
2358
+ }
2359
+ }
2330
2360
  readNode(node) {
2331
2361
  if (node.cmIgnore)
2332
2362
  return;
2333
2363
  let view = ContentView.get(node);
2334
2364
  let fromView = view && view.overrideDOMText;
2335
- let text;
2336
- if (fromView != null)
2337
- text = fromView.sliceString(0, undefined, this.lineBreak);
2338
- else if (node.nodeType == 3)
2339
- text = node.nodeValue;
2340
- else if (node.nodeName == "BR")
2341
- text = node.nextSibling ? this.lineBreak : "";
2342
- else if (node.nodeType == 1)
2365
+ if (fromView != null) {
2366
+ this.findPointInside(node, fromView.length);
2367
+ for (let i = fromView.iter(); !i.next().done;) {
2368
+ if (i.lineBreak)
2369
+ this.lineBreak();
2370
+ else
2371
+ this.append(i.value);
2372
+ }
2373
+ }
2374
+ else if (node.nodeType == 3) {
2375
+ this.readTextNode(node);
2376
+ }
2377
+ else if (node.nodeName == "BR") {
2378
+ if (node.nextSibling)
2379
+ this.lineBreak();
2380
+ }
2381
+ else if (node.nodeType == 1) {
2343
2382
  this.readRange(node.firstChild, null);
2344
- if (text != null) {
2345
- this.findPointIn(node, text.length);
2346
- this.text += text;
2347
- // Chrome inserts two newlines when pressing shift-enter at the
2348
- // end of a line. This drops one of those.
2349
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2350
- this.text = this.text.slice(0, -1);
2351
2383
  }
2352
2384
  }
2353
2385
  findPointBefore(node, next) {
@@ -2355,10 +2387,10 @@ class DOMReader {
2355
2387
  if (point.node == node && node.childNodes[point.offset] == next)
2356
2388
  point.pos = this.text.length;
2357
2389
  }
2358
- findPointIn(node, maxLen) {
2390
+ findPointInside(node, maxLen) {
2359
2391
  for (let point of this.points)
2360
- if (point.node == node)
2361
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2392
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2393
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2362
2394
  }
2363
2395
  }
2364
2396
  function isBlockElement(node) {
@@ -2763,51 +2795,57 @@ class BlockGapWidget extends WidgetType {
2763
2795
  }
2764
2796
  get estimatedHeight() { return this.height; }
2765
2797
  }
2766
- function computeCompositionDeco(view, changes) {
2798
+ function compositionSurroundingNode(view) {
2767
2799
  let sel = view.observer.selectionRange;
2768
2800
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2769
2801
  if (!textNode)
2770
- return Decoration.none;
2802
+ return null;
2771
2803
  let cView = view.docView.nearest(textNode);
2772
2804
  if (!cView)
2773
- return Decoration.none;
2774
- let from, to, topNode = textNode;
2805
+ return null;
2775
2806
  if (cView instanceof LineView) {
2807
+ let topNode = textNode;
2776
2808
  while (topNode.parentNode != cView.dom)
2777
2809
  topNode = topNode.parentNode;
2778
2810
  let prev = topNode.previousSibling;
2779
2811
  while (prev && !ContentView.get(prev))
2780
2812
  prev = prev.previousSibling;
2781
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2813
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2814
+ return { from: pos, to: pos, node: topNode, text: textNode };
2782
2815
  }
2783
2816
  else {
2784
2817
  for (;;) {
2785
2818
  let { parent } = cView;
2786
2819
  if (!parent)
2787
- return Decoration.none;
2820
+ return null;
2788
2821
  if (parent instanceof LineView)
2789
2822
  break;
2790
2823
  cView = parent;
2791
2824
  }
2792
- from = cView.posAtStart;
2793
- to = from + cView.length;
2794
- topNode = cView.dom;
2825
+ let from = cView.posAtStart;
2826
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2795
2827
  }
2828
+ }
2829
+ function computeCompositionDeco(view, changes) {
2830
+ let surrounding = compositionSurroundingNode(view);
2831
+ if (!surrounding)
2832
+ return Decoration.none;
2833
+ let { from, to, node, text: textNode } = surrounding;
2796
2834
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2797
- let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2798
- new DOMReader([], view).readRange(topNode.firstChild, null).text;
2835
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2836
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2799
2837
  if (newTo - newFrom < text.length) {
2800
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2838
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2801
2839
  newTo = newFrom + text.length;
2802
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2840
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2803
2841
  newFrom = newTo - text.length;
2804
2842
  else
2805
2843
  return Decoration.none;
2806
2844
  }
2807
- else if (state.sliceDoc(newFrom, newTo) != text) {
2845
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2808
2846
  return Decoration.none;
2809
2847
  }
2810
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2848
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2811
2849
  }
2812
2850
  class CompositionWidget extends WidgetType {
2813
2851
  constructor(top, text) {
@@ -3144,7 +3182,7 @@ function byGroup(view, pos, start) {
3144
3182
  function moveVertically(view, start, forward, distance) {
3145
3183
  let startPos = start.head, dir = forward ? 1 : -1;
3146
3184
  if (startPos == (forward ? view.state.doc.length : 0))
3147
- return EditorSelection.cursor(startPos);
3185
+ return EditorSelection.cursor(startPos, start.assoc);
3148
3186
  let goal = start.goalColumn, startY;
3149
3187
  let rect = view.contentDOM.getBoundingClientRect();
3150
3188
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3165,7 +3203,7 @@ function moveVertically(view, start, forward, distance) {
3165
3203
  let curY = startY + (dist + extra) * dir;
3166
3204
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3167
3205
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3168
- return EditorSelection.cursor(pos, undefined, undefined, goal);
3206
+ return EditorSelection.cursor(pos, start.assoc, undefined, goal);
3169
3207
  }
3170
3208
  }
3171
3209
  function skipAtoms(view, oldPos, pos) {
@@ -4676,6 +4714,12 @@ class ViewState {
4676
4714
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4677
4715
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4678
4716
  let result = 0, bias = 0;
4717
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4718
+ if (oracle.lineWrapping)
4719
+ measureContent = true;
4720
+ this.editorWidth = view.scrollDOM.clientWidth;
4721
+ result |= 8 /* Geometry */;
4722
+ }
4679
4723
  if (measureContent) {
4680
4724
  this.mustMeasureContent = false;
4681
4725
  this.contentDOMHeight = dom.clientHeight;
@@ -4688,7 +4732,8 @@ class ViewState {
4688
4732
  }
4689
4733
  }
4690
4734
  // Pixel viewport
4691
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4735
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4736
+ : visiblePixelRange(dom, this.paddingTop);
4692
4737
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4693
4738
  this.pixelViewport = pixelViewport;
4694
4739
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4700,11 +4745,9 @@ class ViewState {
4700
4745
  if (!this.inView)
4701
4746
  return 0;
4702
4747
  let contentWidth = dom.clientWidth;
4703
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4704
- this.editorWidth != view.scrollDOM.clientWidth) {
4748
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4705
4749
  this.contentDOMWidth = contentWidth;
4706
4750
  this.editorHeight = view.scrollDOM.clientHeight;
4707
- this.editorWidth = view.scrollDOM.clientWidth;
4708
4751
  result |= 8 /* Geometry */;
4709
4752
  }
4710
4753
  if (measureContent) {
@@ -4759,8 +4802,9 @@ class ViewState {
4759
4802
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4760
4803
  // If scrollTarget is given, make sure the viewport includes that position
4761
4804
  if (scrollTarget) {
4762
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4805
+ let { head } = scrollTarget.range;
4763
4806
  if (head < viewport.from || head > viewport.to) {
4807
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4764
4808
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4765
4809
  if (scrollTarget.y == "center")
4766
4810
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5146,6 +5190,10 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5146
5190
  overflow: "hidden",
5147
5191
  verticalAlign: "bottom"
5148
5192
  },
5193
+ ".cm-widgetBuffer": {
5194
+ verticalAlign: "text-bottom",
5195
+ height: "1em",
5196
+ },
5149
5197
  ".cm-placeholder": {
5150
5198
  color: "#888",
5151
5199
  display: "inline-block",
@@ -5394,7 +5442,7 @@ class DOMObserver {
5394
5442
  }
5395
5443
  // Throw away any pending changes
5396
5444
  clear() {
5397
- this.observer.takeRecords();
5445
+ this.processRecords();
5398
5446
  this.queue.length = 0;
5399
5447
  this.selectionChanged = false;
5400
5448
  }
@@ -5560,9 +5608,8 @@ function applyDOMChange(view, start, end, typeOver) {
5560
5608
  return;
5561
5609
  let { from, to } = bounds;
5562
5610
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5563
- let reader = new DOMReader(selPoints, view);
5611
+ let reader = new DOMReader(selPoints, view.state);
5564
5612
  reader.readRange(bounds.startDOM, bounds.endDOM);
5565
- newSel = selectionFromPoints(selPoints, from);
5566
5613
  let preferredPos = sel.from, preferredSide = null;
5567
5614
  // Prefer anchoring to end when Backspace is pressed (or, on
5568
5615
  // Android, when something was deleted)
@@ -5571,10 +5618,17 @@ function applyDOMChange(view, start, end, typeOver) {
5571
5618
  preferredPos = sel.to;
5572
5619
  preferredSide = "end";
5573
5620
  }
5574
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5575
- if (diff)
5621
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5622
+ if (diff) {
5623
+ // Chrome inserts two newlines when pressing shift-enter at the
5624
+ // end of a line. This drops one of those.
5625
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5626
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5627
+ diff.toB--;
5576
5628
  change = { from: from + diff.from, to: from + diff.toA,
5577
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5629
+ insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5630
+ }
5631
+ newSel = selectionFromPoints(selPoints, from);
5578
5632
  }
5579
5633
  else if (view.hasFocus || !view.state.facet(editable)) {
5580
5634
  let domSel = view.observer.selectionRange;
@@ -5631,19 +5685,47 @@ function applyDOMChange(view, start, end, typeOver) {
5631
5685
  view.inputState.composing++;
5632
5686
  let tr;
5633
5687
  if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5634
- (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length)) {
5688
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5689
+ view.inputState.composing < 0) {
5635
5690
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5636
5691
  let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5637
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
5638
- after));
5692
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5639
5693
  }
5640
5694
  else {
5641
5695
  let changes = startState.changes(change);
5642
- tr = {
5643
- changes,
5644
- selection: newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5645
- ? startState.selection.replaceRange(newSel.main) : undefined
5646
- };
5696
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5697
+ ? newSel.main : undefined;
5698
+ // Try to apply a composition change to all cursors
5699
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5700
+ change.to <= sel.to && change.to >= sel.to - 10) {
5701
+ let replaced = view.state.sliceDoc(change.from, change.to);
5702
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5703
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5704
+ tr = startState.changeByRange(range => {
5705
+ if (range.from == sel.from && range.to == sel.to)
5706
+ return { changes, range: mainSel || range.map(changes) };
5707
+ let to = range.to - offset, from = to - replaced.length;
5708
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5709
+ // Unfortunately, there's no way to make multiple
5710
+ // changes in the same node work without aborting
5711
+ // composition, so cursors in the composition range are
5712
+ // ignored.
5713
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5714
+ return { range };
5715
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5716
+ return {
5717
+ changes: rangeChanges,
5718
+ range: !mainSel ? range.map(rangeChanges) :
5719
+ EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5720
+ };
5721
+ });
5722
+ }
5723
+ else {
5724
+ tr = {
5725
+ changes,
5726
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5727
+ };
5728
+ }
5647
5729
  }
5648
5730
  let userEvent = "input.type";
5649
5731
  if (view.composing) {
@@ -5893,7 +5975,9 @@ class EditorView {
5893
5975
  finally {
5894
5976
  this.updateState = 0 /* Idle */;
5895
5977
  }
5896
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5978
+ if (update.startState.facet(theme) != update.state.facet(theme))
5979
+ this.viewState.mustMeasureContent = true;
5980
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5897
5981
  this.requestMeasure();
5898
5982
  if (!update.empty)
5899
5983
  for (let listener of this.state.facet(updateListener))
@@ -5914,6 +5998,7 @@ class EditorView {
5914
5998
  return;
5915
5999
  }
5916
6000
  this.updateState = 2 /* Updating */;
6001
+ let hadFocus = this.hasFocus;
5917
6002
  try {
5918
6003
  for (let plugin of this.plugins)
5919
6004
  plugin.destroy(this);
@@ -5931,6 +6016,8 @@ class EditorView {
5931
6016
  finally {
5932
6017
  this.updateState = 0 /* Idle */;
5933
6018
  }
6019
+ if (hadFocus)
6020
+ this.focus();
5934
6021
  this.requestMeasure();
5935
6022
  }
5936
6023
  updatePlugins(update) {
@@ -6000,7 +6087,7 @@ class EditorView {
6000
6087
  return BadMeasure;
6001
6088
  }
6002
6089
  });
6003
- let update = new ViewUpdate(this, this.state), redrawn = false;
6090
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6004
6091
  update.flags |= changed;
6005
6092
  if (!updated)
6006
6093
  updated = update;
@@ -6027,11 +6114,12 @@ class EditorView {
6027
6114
  if (this.viewState.scrollTarget) {
6028
6115
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6029
6116
  this.viewState.scrollTarget = null;
6117
+ scrolled = true;
6030
6118
  }
6031
6119
  if (redrawn)
6032
6120
  this.docView.updateSelection(true);
6033
6121
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6034
- this.measureRequests.length == 0)
6122
+ !scrolled && this.measureRequests.length == 0)
6035
6123
  break;
6036
6124
  }
6037
6125
  }
@@ -6167,7 +6255,7 @@ class EditorView {
6167
6255
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6168
6256
  queries.
6169
6257
 
6170
- *Deprecated: use `blockAtHeight` instead.*
6258
+ *Deprecated: use `elementAtHeight` instead.*
6171
6259
  */
6172
6260
  blockAtHeight(height, docTop) {
6173
6261
  let top = ensureTop(docTop, this);
@@ -7012,7 +7100,7 @@ function measureRange(view, range) {
7012
7100
  return pieces(top).concat(between).concat(pieces(bottom));
7013
7101
  }
7014
7102
  function piece(left, top, right, bottom) {
7015
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7103
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7016
7104
  }
7017
7105
  function pieces({ top, bottom, horizontal }) {
7018
7106
  let pieces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.39",
3
+ "version": "0.19.43",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",