@codemirror/view 0.19.38 → 0.19.42

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.42 (2022-02-05)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a regression in cursor position determination after making an edit next to a widget.
6
+
7
+ ## 0.19.41 (2022-02-04)
8
+
9
+ ### Bug fixes
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
16
+
17
+ Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
18
+
19
+ Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
20
+
21
+ 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.
22
+
23
+ ## 0.19.40 (2022-01-19)
24
+
25
+ ### Bug fixes
26
+
27
+ 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).
28
+
29
+ Fix a bug that cause the editor to get confused about which content was visible after scrolling something into view.
30
+
31
+ Fix a bug where the dummy elements rendered around widgets could end up in a separate set of wrapping marks, and thus become visible.
32
+
33
+ `EditorView.moveVertically` now preserves the `assoc` property of the input range.
34
+
35
+ Get rid of gaps between selection elements drawn by `drawSelection`.
36
+
37
+ Fix an issue where replacing text next to a widget might leak bogus zero-width spaces into the document.
38
+
39
+ Avoid browser selection mishandling when a focused view has `setState` called by eagerly refocusing it.
40
+
41
+ ## 0.19.39 (2022-01-06)
42
+
43
+ ### Bug fixes
44
+
45
+ Make sure the editor signals a `geometryChanged` update when its width changes.
46
+
47
+ ### New features
48
+
49
+ `EditorView.darkTheme` can now be queried to figure out whether the editor is using a dark theme.
50
+
1
51
  ## 0.19.38 (2022-01-05)
2
52
 
3
53
  ### Bug fixes
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);
@@ -1131,8 +1130,8 @@ class Decoration extends rangeset.RangeValue {
1131
1130
  static replace(spec) {
1132
1131
  let block = !!spec.block;
1133
1132
  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 */;
1133
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1134
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1136
1135
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1137
1136
  }
1138
1137
  /**
@@ -1237,7 +1236,7 @@ function widgetsEq(a, b) {
1237
1236
  }
1238
1237
  function addRange(from, to, ranges, margin = 0) {
1239
1238
  let last = ranges.length - 1;
1240
- if (last >= 0 && ranges[last] + margin > from)
1239
+ if (last >= 0 && ranges[last] + margin >= from)
1241
1240
  ranges[last] = Math.max(ranges[last], to);
1242
1241
  else
1243
1242
  ranges.push(from, to);
@@ -1518,7 +1517,7 @@ class ContentBuilder {
1518
1517
  }
1519
1518
  }
1520
1519
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1521
- this.flushBuffer(active);
1520
+ this.flushBuffer(active.slice(0, openStart));
1522
1521
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1523
1522
  this.atCursorPos = true;
1524
1523
  this.textOff += take;
@@ -1577,11 +1576,13 @@ class ContentBuilder {
1577
1576
  this.openStart = openStart;
1578
1577
  }
1579
1578
  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;
1579
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1580
+ if (value.block)
1581
+ throw new RangeError("Block decorations may not be specified via plugins");
1582
+ if (to > this.doc.lineAt(this.pos).to)
1583
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1584
+ }
1585
+ return true;
1585
1586
  }
1586
1587
  static build(text, from, to, decorations, pluginDecorationLength) {
1587
1588
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2305,12 +2306,18 @@ function moveVisually(line, order, dir, start, forward) {
2305
2306
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2306
2307
  }
2307
2308
 
2309
+ const LineBreakPlaceholder = "\uffff";
2308
2310
  class DOMReader {
2309
- constructor(points, view) {
2311
+ constructor(points, state$1) {
2310
2312
  this.points = points;
2311
- this.view = view;
2312
2313
  this.text = "";
2313
- this.lineBreak = view.state.lineBreak;
2314
+ this.lineSeparator = state$1.facet(state.EditorState.lineSeparator);
2315
+ }
2316
+ append(text) {
2317
+ this.text += text;
2318
+ }
2319
+ lineBreak() {
2320
+ this.text += LineBreakPlaceholder;
2314
2321
  }
2315
2322
  readRange(start, end) {
2316
2323
  if (!start)
@@ -2326,33 +2333,61 @@ class DOMReader {
2326
2333
  if (view && nextView ? view.breakAfter :
2327
2334
  (view ? view.breakAfter : isBlockElement(cur)) ||
2328
2335
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2329
- this.text += this.lineBreak;
2336
+ this.lineBreak();
2330
2337
  cur = next;
2331
2338
  }
2332
2339
  this.findPointBefore(parent, end);
2333
2340
  return this;
2334
2341
  }
2342
+ readTextNode(node) {
2343
+ let text = node.nodeValue;
2344
+ for (let point of this.points)
2345
+ if (point.node == node)
2346
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2347
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2348
+ let nextBreak = -1, breakSize = 1, m;
2349
+ if (this.lineSeparator) {
2350
+ nextBreak = text.indexOf(this.lineSeparator, off);
2351
+ breakSize = this.lineSeparator.length;
2352
+ }
2353
+ else if (m = re.exec(text)) {
2354
+ nextBreak = m.index;
2355
+ breakSize = m[0].length;
2356
+ }
2357
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2358
+ if (nextBreak < 0)
2359
+ break;
2360
+ this.lineBreak();
2361
+ if (breakSize > 1)
2362
+ for (let point of this.points)
2363
+ if (point.node == node && point.pos > this.text.length)
2364
+ point.pos -= breakSize - 1;
2365
+ off = nextBreak + breakSize;
2366
+ }
2367
+ }
2335
2368
  readNode(node) {
2336
2369
  if (node.cmIgnore)
2337
2370
  return;
2338
2371
  let view = ContentView.get(node);
2339
2372
  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)
2373
+ if (fromView != null) {
2374
+ this.findPointInside(node, fromView.length);
2375
+ for (let i = fromView.iter(); !i.next().done;) {
2376
+ if (i.lineBreak)
2377
+ this.lineBreak();
2378
+ else
2379
+ this.append(i.value);
2380
+ }
2381
+ }
2382
+ else if (node.nodeType == 3) {
2383
+ this.readTextNode(node);
2384
+ }
2385
+ else if (node.nodeName == "BR") {
2386
+ if (node.nextSibling)
2387
+ this.lineBreak();
2388
+ }
2389
+ else if (node.nodeType == 1) {
2348
2390
  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
2391
  }
2357
2392
  }
2358
2393
  findPointBefore(node, next) {
@@ -2360,10 +2395,10 @@ class DOMReader {
2360
2395
  if (point.node == node && node.childNodes[point.offset] == next)
2361
2396
  point.pos = this.text.length;
2362
2397
  }
2363
- findPointIn(node, maxLen) {
2398
+ findPointInside(node, maxLen) {
2364
2399
  for (let point of this.points)
2365
- if (point.node == node)
2366
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2400
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2401
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2367
2402
  }
2368
2403
  }
2369
2404
  function isBlockElement(node) {
@@ -2768,51 +2803,57 @@ class BlockGapWidget extends WidgetType {
2768
2803
  }
2769
2804
  get estimatedHeight() { return this.height; }
2770
2805
  }
2771
- function computeCompositionDeco(view, changes) {
2806
+ function compositionSurroundingNode(view) {
2772
2807
  let sel = view.observer.selectionRange;
2773
2808
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2774
2809
  if (!textNode)
2775
- return Decoration.none;
2810
+ return null;
2776
2811
  let cView = view.docView.nearest(textNode);
2777
2812
  if (!cView)
2778
- return Decoration.none;
2779
- let from, to, topNode = textNode;
2813
+ return null;
2780
2814
  if (cView instanceof LineView) {
2815
+ let topNode = textNode;
2781
2816
  while (topNode.parentNode != cView.dom)
2782
2817
  topNode = topNode.parentNode;
2783
2818
  let prev = topNode.previousSibling;
2784
2819
  while (prev && !ContentView.get(prev))
2785
2820
  prev = prev.previousSibling;
2786
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2821
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2822
+ return { from: pos, to: pos, node: topNode, text: textNode };
2787
2823
  }
2788
2824
  else {
2789
2825
  for (;;) {
2790
2826
  let { parent } = cView;
2791
2827
  if (!parent)
2792
- return Decoration.none;
2828
+ return null;
2793
2829
  if (parent instanceof LineView)
2794
2830
  break;
2795
2831
  cView = parent;
2796
2832
  }
2797
- from = cView.posAtStart;
2798
- to = from + cView.length;
2799
- topNode = cView.dom;
2833
+ let from = cView.posAtStart;
2834
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2800
2835
  }
2836
+ }
2837
+ function computeCompositionDeco(view, changes) {
2838
+ let surrounding = compositionSurroundingNode(view);
2839
+ if (!surrounding)
2840
+ return Decoration.none;
2841
+ let { from, to, node, text: textNode } = surrounding;
2801
2842
  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;
2843
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2844
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2804
2845
  if (newTo - newFrom < text.length) {
2805
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2846
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2806
2847
  newTo = newFrom + text.length;
2807
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2848
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2808
2849
  newFrom = newTo - text.length;
2809
2850
  else
2810
2851
  return Decoration.none;
2811
2852
  }
2812
- else if (state.sliceDoc(newFrom, newTo) != text) {
2853
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2813
2854
  return Decoration.none;
2814
2855
  }
2815
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2856
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2816
2857
  }
2817
2858
  class CompositionWidget extends WidgetType {
2818
2859
  constructor(top, text) {
@@ -3149,7 +3190,7 @@ function byGroup(view, pos, start) {
3149
3190
  function moveVertically(view, start, forward, distance) {
3150
3191
  let startPos = start.head, dir = forward ? 1 : -1;
3151
3192
  if (startPos == (forward ? view.state.doc.length : 0))
3152
- return state.EditorSelection.cursor(startPos);
3193
+ return state.EditorSelection.cursor(startPos, start.assoc);
3153
3194
  let goal = start.goalColumn, startY;
3154
3195
  let rect = view.contentDOM.getBoundingClientRect();
3155
3196
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3170,7 +3211,7 @@ function moveVertically(view, start, forward, distance) {
3170
3211
  let curY = startY + (dist + extra) * dir;
3171
3212
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3172
3213
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3173
- return state.EditorSelection.cursor(pos, undefined, undefined, goal);
3214
+ return state.EditorSelection.cursor(pos, start.assoc, undefined, goal);
3174
3215
  }
3175
3216
  }
3176
3217
  function skipAtoms(view, oldPos, pos) {
@@ -4599,6 +4640,7 @@ class ViewState {
4599
4640
  this.contentDOMWidth = 0;
4600
4641
  this.contentDOMHeight = 0;
4601
4642
  this.editorHeight = 0;
4643
+ this.editorWidth = 0;
4602
4644
  this.heightOracle = new HeightOracle;
4603
4645
  // See VP.MaxDOMHeight
4604
4646
  this.scaler = IdScaler;
@@ -4681,6 +4723,12 @@ class ViewState {
4681
4723
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4682
4724
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4683
4725
  let result = 0, bias = 0;
4726
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4727
+ if (oracle.lineWrapping)
4728
+ measureContent = true;
4729
+ this.editorWidth = view.scrollDOM.clientWidth;
4730
+ result |= 8 /* Geometry */;
4731
+ }
4684
4732
  if (measureContent) {
4685
4733
  this.mustMeasureContent = false;
4686
4734
  this.contentDOMHeight = dom.clientHeight;
@@ -4693,7 +4741,8 @@ class ViewState {
4693
4741
  }
4694
4742
  }
4695
4743
  // Pixel viewport
4696
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4744
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4745
+ : visiblePixelRange(dom, this.paddingTop);
4697
4746
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4698
4747
  this.pixelViewport = pixelViewport;
4699
4748
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4704,11 +4753,16 @@ class ViewState {
4704
4753
  }
4705
4754
  if (!this.inView)
4706
4755
  return 0;
4756
+ let contentWidth = dom.clientWidth;
4757
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4758
+ this.contentDOMWidth = contentWidth;
4759
+ this.editorHeight = view.scrollDOM.clientHeight;
4760
+ result |= 8 /* Geometry */;
4761
+ }
4707
4762
  if (measureContent) {
4708
4763
  let lineHeights = view.docView.measureVisibleLineHeights();
4709
4764
  if (oracle.mustRefreshForHeights(lineHeights))
4710
4765
  refresh = true;
4711
- let contentWidth = dom.clientWidth;
4712
4766
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4713
4767
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4714
4768
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4717,14 +4771,6 @@ class ViewState {
4717
4771
  result |= 8 /* Geometry */;
4718
4772
  }
4719
4773
  }
4720
- if (this.contentDOMWidth != contentWidth) {
4721
- this.contentDOMWidth = contentWidth;
4722
- result |= 8 /* Geometry */;
4723
- }
4724
- if (this.editorHeight != view.scrollDOM.clientHeight) {
4725
- this.editorHeight = view.scrollDOM.clientHeight;
4726
- result |= 8 /* Geometry */;
4727
- }
4728
4774
  if (dTop > 0 && dBottom > 0)
4729
4775
  bias = Math.max(dTop, dBottom);
4730
4776
  else if (dTop < 0 && dBottom < 0)
@@ -4765,8 +4811,9 @@ class ViewState {
4765
4811
  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
4812
  // If scrollTarget is given, make sure the viewport includes that position
4767
4813
  if (scrollTarget) {
4768
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4814
+ let { head } = scrollTarget.range;
4769
4815
  if (head < viewport.from || head > viewport.to) {
4816
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4770
4817
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4771
4818
  if (scrollTarget.y == "center")
4772
4819
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5566,9 +5613,8 @@ function applyDOMChange(view, start, end, typeOver) {
5566
5613
  return;
5567
5614
  let { from, to } = bounds;
5568
5615
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5569
- let reader = new DOMReader(selPoints, view);
5616
+ let reader = new DOMReader(selPoints, view.state);
5570
5617
  reader.readRange(bounds.startDOM, bounds.endDOM);
5571
- newSel = selectionFromPoints(selPoints, from);
5572
5618
  let preferredPos = sel.from, preferredSide = null;
5573
5619
  // Prefer anchoring to end when Backspace is pressed (or, on
5574
5620
  // Android, when something was deleted)
@@ -5577,10 +5623,29 @@ function applyDOMChange(view, start, end, typeOver) {
5577
5623
  preferredPos = sel.to;
5578
5624
  preferredSide = "end";
5579
5625
  }
5580
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5581
- if (diff)
5626
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5627
+ if (diff) {
5628
+ let orig = 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--;
5634
+ // Strip leading and trailing zero-width spaces from the inserted
5635
+ // content, to work around widget buffers being moved into text
5636
+ // nodes by the browser.
5637
+ while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
5638
+ diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
5639
+ selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
5640
+ }
5641
+ while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
5642
+ diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
5643
+ selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
5644
+ }
5582
5645
  change = { from: from + diff.from, to: from + diff.toA,
5583
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5646
+ insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5647
+ }
5648
+ newSel = selectionFromPoints(selPoints, from);
5584
5649
  }
5585
5650
  else if (view.hasFocus || !view.state.facet(editable)) {
5586
5651
  let domSel = view.observer.selectionRange;
@@ -5637,19 +5702,47 @@ function applyDOMChange(view, start, end, typeOver) {
5637
5702
  view.inputState.composing++;
5638
5703
  let tr;
5639
5704
  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)) {
5705
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5706
+ view.inputState.composing < 0) {
5641
5707
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5642
5708
  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));
5709
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5645
5710
  }
5646
5711
  else {
5647
5712
  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
- };
5713
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5714
+ ? newSel.main : undefined;
5715
+ // Try to apply a composition change to all cursors
5716
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5717
+ change.to <= sel.to && change.to >= sel.to - 10) {
5718
+ let replaced = view.state.sliceDoc(change.from, change.to);
5719
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5720
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5721
+ tr = startState.changeByRange(range => {
5722
+ if (range.from == sel.from && range.to == sel.to)
5723
+ return { changes, range: mainSel || range.map(changes) };
5724
+ let to = range.to - offset, from = to - replaced.length;
5725
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5726
+ // Unfortunately, there's no way to make multiple
5727
+ // changes in the same node work without aborting
5728
+ // composition, so cursors in the composition range are
5729
+ // ignored.
5730
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5731
+ return { range };
5732
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5733
+ return {
5734
+ changes: rangeChanges,
5735
+ range: !mainSel ? range.map(rangeChanges) :
5736
+ state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5737
+ };
5738
+ });
5739
+ }
5740
+ else {
5741
+ tr = {
5742
+ changes,
5743
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5744
+ };
5745
+ }
5653
5746
  }
5654
5747
  let userEvent = "input.type";
5655
5748
  if (view.composing) {
@@ -5920,6 +6013,7 @@ class EditorView {
5920
6013
  return;
5921
6014
  }
5922
6015
  this.updateState = 2 /* Updating */;
6016
+ let hadFocus = this.hasFocus;
5923
6017
  try {
5924
6018
  for (let plugin of this.plugins)
5925
6019
  plugin.destroy(this);
@@ -5937,6 +6031,8 @@ class EditorView {
5937
6031
  finally {
5938
6032
  this.updateState = 0 /* Idle */;
5939
6033
  }
6034
+ if (hadFocus)
6035
+ this.focus();
5940
6036
  this.requestMeasure();
5941
6037
  }
5942
6038
  updatePlugins(update) {
@@ -6006,7 +6102,7 @@ class EditorView {
6006
6102
  return BadMeasure;
6007
6103
  }
6008
6104
  });
6009
- let update = new ViewUpdate(this, this.state), redrawn = false;
6105
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6010
6106
  update.flags |= changed;
6011
6107
  if (!updated)
6012
6108
  updated = update;
@@ -6033,11 +6129,12 @@ class EditorView {
6033
6129
  if (this.viewState.scrollTarget) {
6034
6130
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6035
6131
  this.viewState.scrollTarget = null;
6132
+ scrolled = true;
6036
6133
  }
6037
6134
  if (redrawn)
6038
6135
  this.docView.updateSelection(true);
6039
6136
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6040
- this.measureRequests.length == 0)
6137
+ !scrolled && this.measureRequests.length == 0)
6041
6138
  break;
6042
6139
  }
6043
6140
  }
@@ -6173,7 +6270,7 @@ class EditorView {
6173
6270
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6174
6271
  queries.
6175
6272
 
6176
- *Deprecated: use `blockAtHeight` instead.*
6273
+ *Deprecated: use `elementAtHeight` instead.*
6177
6274
  */
6178
6275
  blockAtHeight(height, docTop) {
6179
6276
  let top = ensureTop(docTop, this);
@@ -6585,6 +6682,13 @@ mechanism for providing decorations.
6585
6682
  */
6586
6683
  EditorView.decorations = decorations;
6587
6684
  /**
6685
+ This facet records whether a dark theme is active. The extension
6686
+ returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
6687
+ includes an instance of this when the `dark` option is set to
6688
+ true.
6689
+ */
6690
+ EditorView.darkTheme = darkTheme;
6691
+ /**
6588
6692
  Facet that provides additional DOM attributes for the editor's
6589
6693
  editable DOM element.
6590
6694
  */
@@ -7011,7 +7115,7 @@ function measureRange(view, range) {
7011
7115
  return pieces(top).concat(between).concat(pieces(bottom));
7012
7116
  }
7013
7117
  function piece(left, top, right, bottom) {
7014
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7118
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7015
7119
  }
7016
7120
  function pieces({ top, bottom, horizontal }) {
7017
7121
  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
  /**
@@ -1156,6 +1156,13 @@ declare class EditorView {
1156
1156
  dark?: boolean;
1157
1157
  }): Extension;
1158
1158
  /**
1159
+ This facet records whether a dark theme is active. The extension
1160
+ returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
1161
+ includes an instance of this when the `dark` option is set to
1162
+ true.
1163
+ */
1164
+ static darkTheme: Facet<boolean, boolean>;
1165
+ /**
1159
1166
  Create an extension that adds styles to the base theme. Like
1160
1167
  with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
1161
1168
  place of the editor wrapper element when directly targeting
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);
@@ -1127,8 +1126,8 @@ class Decoration extends RangeValue {
1127
1126
  static replace(spec) {
1128
1127
  let block = !!spec.block;
1129
1128
  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 */;
1129
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1130
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1132
1131
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1133
1132
  }
1134
1133
  /**
@@ -1233,7 +1232,7 @@ function widgetsEq(a, b) {
1233
1232
  }
1234
1233
  function addRange(from, to, ranges, margin = 0) {
1235
1234
  let last = ranges.length - 1;
1236
- if (last >= 0 && ranges[last] + margin > from)
1235
+ if (last >= 0 && ranges[last] + margin >= from)
1237
1236
  ranges[last] = Math.max(ranges[last], to);
1238
1237
  else
1239
1238
  ranges.push(from, to);
@@ -1514,7 +1513,7 @@ class ContentBuilder {
1514
1513
  }
1515
1514
  }
1516
1515
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1517
- this.flushBuffer(active);
1516
+ this.flushBuffer(active.slice(0, openStart));
1518
1517
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1519
1518
  this.atCursorPos = true;
1520
1519
  this.textOff += take;
@@ -1573,11 +1572,13 @@ class ContentBuilder {
1573
1572
  this.openStart = openStart;
1574
1573
  }
1575
1574
  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;
1575
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1576
+ if (value.block)
1577
+ throw new RangeError("Block decorations may not be specified via plugins");
1578
+ if (to > this.doc.lineAt(this.pos).to)
1579
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1580
+ }
1581
+ return true;
1581
1582
  }
1582
1583
  static build(text, from, to, decorations, pluginDecorationLength) {
1583
1584
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2300,12 +2301,18 @@ function moveVisually(line, order, dir, start, forward) {
2300
2301
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2301
2302
  }
2302
2303
 
2304
+ const LineBreakPlaceholder = "\uffff";
2303
2305
  class DOMReader {
2304
- constructor(points, view) {
2306
+ constructor(points, state) {
2305
2307
  this.points = points;
2306
- this.view = view;
2307
2308
  this.text = "";
2308
- this.lineBreak = view.state.lineBreak;
2309
+ this.lineSeparator = state.facet(EditorState.lineSeparator);
2310
+ }
2311
+ append(text) {
2312
+ this.text += text;
2313
+ }
2314
+ lineBreak() {
2315
+ this.text += LineBreakPlaceholder;
2309
2316
  }
2310
2317
  readRange(start, end) {
2311
2318
  if (!start)
@@ -2321,33 +2328,61 @@ class DOMReader {
2321
2328
  if (view && nextView ? view.breakAfter :
2322
2329
  (view ? view.breakAfter : isBlockElement(cur)) ||
2323
2330
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2324
- this.text += this.lineBreak;
2331
+ this.lineBreak();
2325
2332
  cur = next;
2326
2333
  }
2327
2334
  this.findPointBefore(parent, end);
2328
2335
  return this;
2329
2336
  }
2337
+ readTextNode(node) {
2338
+ let text = node.nodeValue;
2339
+ for (let point of this.points)
2340
+ if (point.node == node)
2341
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2342
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2343
+ let nextBreak = -1, breakSize = 1, m;
2344
+ if (this.lineSeparator) {
2345
+ nextBreak = text.indexOf(this.lineSeparator, off);
2346
+ breakSize = this.lineSeparator.length;
2347
+ }
2348
+ else if (m = re.exec(text)) {
2349
+ nextBreak = m.index;
2350
+ breakSize = m[0].length;
2351
+ }
2352
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2353
+ if (nextBreak < 0)
2354
+ break;
2355
+ this.lineBreak();
2356
+ if (breakSize > 1)
2357
+ for (let point of this.points)
2358
+ if (point.node == node && point.pos > this.text.length)
2359
+ point.pos -= breakSize - 1;
2360
+ off = nextBreak + breakSize;
2361
+ }
2362
+ }
2330
2363
  readNode(node) {
2331
2364
  if (node.cmIgnore)
2332
2365
  return;
2333
2366
  let view = ContentView.get(node);
2334
2367
  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)
2368
+ if (fromView != null) {
2369
+ this.findPointInside(node, fromView.length);
2370
+ for (let i = fromView.iter(); !i.next().done;) {
2371
+ if (i.lineBreak)
2372
+ this.lineBreak();
2373
+ else
2374
+ this.append(i.value);
2375
+ }
2376
+ }
2377
+ else if (node.nodeType == 3) {
2378
+ this.readTextNode(node);
2379
+ }
2380
+ else if (node.nodeName == "BR") {
2381
+ if (node.nextSibling)
2382
+ this.lineBreak();
2383
+ }
2384
+ else if (node.nodeType == 1) {
2343
2385
  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
2386
  }
2352
2387
  }
2353
2388
  findPointBefore(node, next) {
@@ -2355,10 +2390,10 @@ class DOMReader {
2355
2390
  if (point.node == node && node.childNodes[point.offset] == next)
2356
2391
  point.pos = this.text.length;
2357
2392
  }
2358
- findPointIn(node, maxLen) {
2393
+ findPointInside(node, maxLen) {
2359
2394
  for (let point of this.points)
2360
- if (point.node == node)
2361
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2395
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2396
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2362
2397
  }
2363
2398
  }
2364
2399
  function isBlockElement(node) {
@@ -2763,51 +2798,57 @@ class BlockGapWidget extends WidgetType {
2763
2798
  }
2764
2799
  get estimatedHeight() { return this.height; }
2765
2800
  }
2766
- function computeCompositionDeco(view, changes) {
2801
+ function compositionSurroundingNode(view) {
2767
2802
  let sel = view.observer.selectionRange;
2768
2803
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2769
2804
  if (!textNode)
2770
- return Decoration.none;
2805
+ return null;
2771
2806
  let cView = view.docView.nearest(textNode);
2772
2807
  if (!cView)
2773
- return Decoration.none;
2774
- let from, to, topNode = textNode;
2808
+ return null;
2775
2809
  if (cView instanceof LineView) {
2810
+ let topNode = textNode;
2776
2811
  while (topNode.parentNode != cView.dom)
2777
2812
  topNode = topNode.parentNode;
2778
2813
  let prev = topNode.previousSibling;
2779
2814
  while (prev && !ContentView.get(prev))
2780
2815
  prev = prev.previousSibling;
2781
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2816
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2817
+ return { from: pos, to: pos, node: topNode, text: textNode };
2782
2818
  }
2783
2819
  else {
2784
2820
  for (;;) {
2785
2821
  let { parent } = cView;
2786
2822
  if (!parent)
2787
- return Decoration.none;
2823
+ return null;
2788
2824
  if (parent instanceof LineView)
2789
2825
  break;
2790
2826
  cView = parent;
2791
2827
  }
2792
- from = cView.posAtStart;
2793
- to = from + cView.length;
2794
- topNode = cView.dom;
2828
+ let from = cView.posAtStart;
2829
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2795
2830
  }
2831
+ }
2832
+ function computeCompositionDeco(view, changes) {
2833
+ let surrounding = compositionSurroundingNode(view);
2834
+ if (!surrounding)
2835
+ return Decoration.none;
2836
+ let { from, to, node, text: textNode } = surrounding;
2796
2837
  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;
2838
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2839
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2799
2840
  if (newTo - newFrom < text.length) {
2800
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2841
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2801
2842
  newTo = newFrom + text.length;
2802
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2843
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2803
2844
  newFrom = newTo - text.length;
2804
2845
  else
2805
2846
  return Decoration.none;
2806
2847
  }
2807
- else if (state.sliceDoc(newFrom, newTo) != text) {
2848
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2808
2849
  return Decoration.none;
2809
2850
  }
2810
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2851
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2811
2852
  }
2812
2853
  class CompositionWidget extends WidgetType {
2813
2854
  constructor(top, text) {
@@ -3144,7 +3185,7 @@ function byGroup(view, pos, start) {
3144
3185
  function moveVertically(view, start, forward, distance) {
3145
3186
  let startPos = start.head, dir = forward ? 1 : -1;
3146
3187
  if (startPos == (forward ? view.state.doc.length : 0))
3147
- return EditorSelection.cursor(startPos);
3188
+ return EditorSelection.cursor(startPos, start.assoc);
3148
3189
  let goal = start.goalColumn, startY;
3149
3190
  let rect = view.contentDOM.getBoundingClientRect();
3150
3191
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3165,7 +3206,7 @@ function moveVertically(view, start, forward, distance) {
3165
3206
  let curY = startY + (dist + extra) * dir;
3166
3207
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3167
3208
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3168
- return EditorSelection.cursor(pos, undefined, undefined, goal);
3209
+ return EditorSelection.cursor(pos, start.assoc, undefined, goal);
3169
3210
  }
3170
3211
  }
3171
3212
  function skipAtoms(view, oldPos, pos) {
@@ -4593,6 +4634,7 @@ class ViewState {
4593
4634
  this.contentDOMWidth = 0;
4594
4635
  this.contentDOMHeight = 0;
4595
4636
  this.editorHeight = 0;
4637
+ this.editorWidth = 0;
4596
4638
  this.heightOracle = new HeightOracle;
4597
4639
  // See VP.MaxDOMHeight
4598
4640
  this.scaler = IdScaler;
@@ -4675,6 +4717,12 @@ class ViewState {
4675
4717
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4676
4718
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4677
4719
  let result = 0, bias = 0;
4720
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4721
+ if (oracle.lineWrapping)
4722
+ measureContent = true;
4723
+ this.editorWidth = view.scrollDOM.clientWidth;
4724
+ result |= 8 /* Geometry */;
4725
+ }
4678
4726
  if (measureContent) {
4679
4727
  this.mustMeasureContent = false;
4680
4728
  this.contentDOMHeight = dom.clientHeight;
@@ -4687,7 +4735,8 @@ class ViewState {
4687
4735
  }
4688
4736
  }
4689
4737
  // Pixel viewport
4690
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4738
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4739
+ : visiblePixelRange(dom, this.paddingTop);
4691
4740
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4692
4741
  this.pixelViewport = pixelViewport;
4693
4742
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4698,11 +4747,16 @@ class ViewState {
4698
4747
  }
4699
4748
  if (!this.inView)
4700
4749
  return 0;
4750
+ let contentWidth = dom.clientWidth;
4751
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4752
+ this.contentDOMWidth = contentWidth;
4753
+ this.editorHeight = view.scrollDOM.clientHeight;
4754
+ result |= 8 /* Geometry */;
4755
+ }
4701
4756
  if (measureContent) {
4702
4757
  let lineHeights = view.docView.measureVisibleLineHeights();
4703
4758
  if (oracle.mustRefreshForHeights(lineHeights))
4704
4759
  refresh = true;
4705
- let contentWidth = dom.clientWidth;
4706
4760
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4707
4761
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4708
4762
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4711,14 +4765,6 @@ class ViewState {
4711
4765
  result |= 8 /* Geometry */;
4712
4766
  }
4713
4767
  }
4714
- if (this.contentDOMWidth != contentWidth) {
4715
- this.contentDOMWidth = contentWidth;
4716
- result |= 8 /* Geometry */;
4717
- }
4718
- if (this.editorHeight != view.scrollDOM.clientHeight) {
4719
- this.editorHeight = view.scrollDOM.clientHeight;
4720
- result |= 8 /* Geometry */;
4721
- }
4722
4768
  if (dTop > 0 && dBottom > 0)
4723
4769
  bias = Math.max(dTop, dBottom);
4724
4770
  else if (dTop < 0 && dBottom < 0)
@@ -4759,8 +4805,9 @@ class ViewState {
4759
4805
  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
4806
  // If scrollTarget is given, make sure the viewport includes that position
4761
4807
  if (scrollTarget) {
4762
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4808
+ let { head } = scrollTarget.range;
4763
4809
  if (head < viewport.from || head > viewport.to) {
4810
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4764
4811
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4765
4812
  if (scrollTarget.y == "center")
4766
4813
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5560,9 +5607,8 @@ function applyDOMChange(view, start, end, typeOver) {
5560
5607
  return;
5561
5608
  let { from, to } = bounds;
5562
5609
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5563
- let reader = new DOMReader(selPoints, view);
5610
+ let reader = new DOMReader(selPoints, view.state);
5564
5611
  reader.readRange(bounds.startDOM, bounds.endDOM);
5565
- newSel = selectionFromPoints(selPoints, from);
5566
5612
  let preferredPos = sel.from, preferredSide = null;
5567
5613
  // Prefer anchoring to end when Backspace is pressed (or, on
5568
5614
  // Android, when something was deleted)
@@ -5571,10 +5617,29 @@ function applyDOMChange(view, start, end, typeOver) {
5571
5617
  preferredPos = sel.to;
5572
5618
  preferredSide = "end";
5573
5619
  }
5574
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5575
- if (diff)
5620
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5621
+ if (diff) {
5622
+ let orig = 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--;
5628
+ // Strip leading and trailing zero-width spaces from the inserted
5629
+ // content, to work around widget buffers being moved into text
5630
+ // nodes by the browser.
5631
+ while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
5632
+ diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
5633
+ selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
5634
+ }
5635
+ while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
5636
+ diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
5637
+ selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
5638
+ }
5576
5639
  change = { from: from + diff.from, to: from + diff.toA,
5577
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5640
+ insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5641
+ }
5642
+ newSel = selectionFromPoints(selPoints, from);
5578
5643
  }
5579
5644
  else if (view.hasFocus || !view.state.facet(editable)) {
5580
5645
  let domSel = view.observer.selectionRange;
@@ -5631,19 +5696,47 @@ function applyDOMChange(view, start, end, typeOver) {
5631
5696
  view.inputState.composing++;
5632
5697
  let tr;
5633
5698
  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)) {
5699
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5700
+ view.inputState.composing < 0) {
5635
5701
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5636
5702
  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));
5703
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5639
5704
  }
5640
5705
  else {
5641
5706
  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
- };
5707
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5708
+ ? newSel.main : undefined;
5709
+ // Try to apply a composition change to all cursors
5710
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5711
+ change.to <= sel.to && change.to >= sel.to - 10) {
5712
+ let replaced = view.state.sliceDoc(change.from, change.to);
5713
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5714
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5715
+ tr = startState.changeByRange(range => {
5716
+ if (range.from == sel.from && range.to == sel.to)
5717
+ return { changes, range: mainSel || range.map(changes) };
5718
+ let to = range.to - offset, from = to - replaced.length;
5719
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5720
+ // Unfortunately, there's no way to make multiple
5721
+ // changes in the same node work without aborting
5722
+ // composition, so cursors in the composition range are
5723
+ // ignored.
5724
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5725
+ return { range };
5726
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5727
+ return {
5728
+ changes: rangeChanges,
5729
+ range: !mainSel ? range.map(rangeChanges) :
5730
+ EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5731
+ };
5732
+ });
5733
+ }
5734
+ else {
5735
+ tr = {
5736
+ changes,
5737
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5738
+ };
5739
+ }
5647
5740
  }
5648
5741
  let userEvent = "input.type";
5649
5742
  if (view.composing) {
@@ -5914,6 +6007,7 @@ class EditorView {
5914
6007
  return;
5915
6008
  }
5916
6009
  this.updateState = 2 /* Updating */;
6010
+ let hadFocus = this.hasFocus;
5917
6011
  try {
5918
6012
  for (let plugin of this.plugins)
5919
6013
  plugin.destroy(this);
@@ -5931,6 +6025,8 @@ class EditorView {
5931
6025
  finally {
5932
6026
  this.updateState = 0 /* Idle */;
5933
6027
  }
6028
+ if (hadFocus)
6029
+ this.focus();
5934
6030
  this.requestMeasure();
5935
6031
  }
5936
6032
  updatePlugins(update) {
@@ -6000,7 +6096,7 @@ class EditorView {
6000
6096
  return BadMeasure;
6001
6097
  }
6002
6098
  });
6003
- let update = new ViewUpdate(this, this.state), redrawn = false;
6099
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6004
6100
  update.flags |= changed;
6005
6101
  if (!updated)
6006
6102
  updated = update;
@@ -6027,11 +6123,12 @@ class EditorView {
6027
6123
  if (this.viewState.scrollTarget) {
6028
6124
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6029
6125
  this.viewState.scrollTarget = null;
6126
+ scrolled = true;
6030
6127
  }
6031
6128
  if (redrawn)
6032
6129
  this.docView.updateSelection(true);
6033
6130
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6034
- this.measureRequests.length == 0)
6131
+ !scrolled && this.measureRequests.length == 0)
6035
6132
  break;
6036
6133
  }
6037
6134
  }
@@ -6167,7 +6264,7 @@ class EditorView {
6167
6264
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6168
6265
  queries.
6169
6266
 
6170
- *Deprecated: use `blockAtHeight` instead.*
6267
+ *Deprecated: use `elementAtHeight` instead.*
6171
6268
  */
6172
6269
  blockAtHeight(height, docTop) {
6173
6270
  let top = ensureTop(docTop, this);
@@ -6579,6 +6676,13 @@ mechanism for providing decorations.
6579
6676
  */
6580
6677
  EditorView.decorations = decorations;
6581
6678
  /**
6679
+ This facet records whether a dark theme is active. The extension
6680
+ returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
6681
+ includes an instance of this when the `dark` option is set to
6682
+ true.
6683
+ */
6684
+ EditorView.darkTheme = darkTheme;
6685
+ /**
6582
6686
  Facet that provides additional DOM attributes for the editor's
6583
6687
  editable DOM element.
6584
6688
  */
@@ -7005,7 +7109,7 @@ function measureRange(view, range) {
7005
7109
  return pieces(top).concat(between).concat(pieces(bottom));
7006
7110
  }
7007
7111
  function piece(left, top, right, bottom) {
7008
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7112
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7009
7113
  }
7010
7114
  function pieces({ top, bottom, horizontal }) {
7011
7115
  let pieces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.38",
3
+ "version": "0.19.42",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",