@codemirror/view 0.19.37 → 0.19.41

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,57 @@
1
+ ## 0.19.41 (2022-02-04)
2
+
3
+ ### Bug fixes
4
+
5
+ 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.
6
+
7
+ 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.
8
+
9
+ Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
10
+
11
+ Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
12
+
13
+ Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
14
+
15
+ 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.
16
+
17
+ ## 0.19.40 (2022-01-19)
18
+
19
+ ### Bug fixes
20
+
21
+ 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).
22
+
23
+ Fix a bug that cause the editor to get confused about which content was visible after scrolling something into view.
24
+
25
+ Fix a bug where the dummy elements rendered around widgets could end up in a separate set of wrapping marks, and thus become visible.
26
+
27
+ `EditorView.moveVertically` now preserves the `assoc` property of the input range.
28
+
29
+ Get rid of gaps between selection elements drawn by `drawSelection`.
30
+
31
+ Fix an issue where replacing text next to a widget might leak bogus zero-width spaces into the document.
32
+
33
+ Avoid browser selection mishandling when a focused view has `setState` called by eagerly refocusing it.
34
+
35
+ ## 0.19.39 (2022-01-06)
36
+
37
+ ### Bug fixes
38
+
39
+ Make sure the editor signals a `geometryChanged` update when its width changes.
40
+
41
+ ### New features
42
+
43
+ `EditorView.darkTheme` can now be queried to figure out whether the editor is using a dark theme.
44
+
45
+ ## 0.19.38 (2022-01-05)
46
+
47
+ ### Bug fixes
48
+
49
+ Fix a bug that caused line decorations with a `class` property to suppress all other line decorations for that line.
50
+
51
+ Fix a bug that caused scroll effects to be corrupted when further updates came in before they were applied.
52
+
53
+ Fix an issue where, depending on which way a floating point rounding error fell, `posAtCoords` (and thus vertical cursor motion) for positions outside of the vertical range of the document might or might not return the start/end of the document.
54
+
1
55
  ## 0.19.37 (2021-12-22)
2
56
 
3
57
  ### 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);
@@ -1311,7 +1310,7 @@ class LineView extends ContentView {
1311
1310
  if (attrs)
1312
1311
  this.attrs = combineAttrs(attrs, this.attrs || {});
1313
1312
  if (cls)
1314
- this.attrs = combineAttrs(attrs, { class: cls });
1313
+ this.attrs = combineAttrs({ class: cls }, this.attrs || {});
1315
1314
  }
1316
1315
  domAtPos(pos) {
1317
1316
  return inlineDOMAtPos(this.dom, this.children, pos);
@@ -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);
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) {
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;
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) {
@@ -3005,7 +3046,11 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3005
3046
  var _a;
3006
3047
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3007
3048
  let block, { docHeight } = view.viewState;
3008
- let yOffset = Math.max(0, Math.min(y - docTop, docHeight));
3049
+ let yOffset = y - docTop;
3050
+ if (yOffset < 0)
3051
+ return 0;
3052
+ if (yOffset > docHeight)
3053
+ return view.state.doc.length;
3009
3054
  // Scan for a text block near the queried y position
3010
3055
  for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
3011
3056
  block = view.elementAtHeight(yOffset);
@@ -3145,7 +3190,7 @@ function byGroup(view, pos, start) {
3145
3190
  function moveVertically(view, start, forward, distance) {
3146
3191
  let startPos = start.head, dir = forward ? 1 : -1;
3147
3192
  if (startPos == (forward ? view.state.doc.length : 0))
3148
- return state.EditorSelection.cursor(startPos);
3193
+ return state.EditorSelection.cursor(startPos, start.assoc);
3149
3194
  let goal = start.goalColumn, startY;
3150
3195
  let rect = view.contentDOM.getBoundingClientRect();
3151
3196
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3166,7 +3211,7 @@ function moveVertically(view, start, forward, distance) {
3166
3211
  let curY = startY + (dist + extra) * dir;
3167
3212
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3168
3213
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3169
- return state.EditorSelection.cursor(pos, undefined, undefined, goal);
3214
+ return state.EditorSelection.cursor(pos, start.assoc, undefined, goal);
3170
3215
  }
3171
3216
  }
3172
3217
  function skipAtoms(view, oldPos, pos) {
@@ -4595,6 +4640,7 @@ class ViewState {
4595
4640
  this.contentDOMWidth = 0;
4596
4641
  this.contentDOMHeight = 0;
4597
4642
  this.editorHeight = 0;
4643
+ this.editorWidth = 0;
4598
4644
  this.heightOracle = new HeightOracle;
4599
4645
  // See VP.MaxDOMHeight
4600
4646
  this.scaler = IdScaler;
@@ -4677,6 +4723,12 @@ class ViewState {
4677
4723
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4678
4724
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4679
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
+ }
4680
4732
  if (measureContent) {
4681
4733
  this.mustMeasureContent = false;
4682
4734
  this.contentDOMHeight = dom.clientHeight;
@@ -4689,7 +4741,8 @@ class ViewState {
4689
4741
  }
4690
4742
  }
4691
4743
  // Pixel viewport
4692
- 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);
4693
4746
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4694
4747
  this.pixelViewport = pixelViewport;
4695
4748
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4700,11 +4753,16 @@ class ViewState {
4700
4753
  }
4701
4754
  if (!this.inView)
4702
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
+ }
4703
4762
  if (measureContent) {
4704
4763
  let lineHeights = view.docView.measureVisibleLineHeights();
4705
4764
  if (oracle.mustRefreshForHeights(lineHeights))
4706
4765
  refresh = true;
4707
- let contentWidth = dom.clientWidth;
4708
4766
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4709
4767
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4710
4768
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4713,14 +4771,6 @@ class ViewState {
4713
4771
  result |= 8 /* Geometry */;
4714
4772
  }
4715
4773
  }
4716
- if (this.contentDOMWidth != contentWidth) {
4717
- this.contentDOMWidth = contentWidth;
4718
- result |= 8 /* Geometry */;
4719
- }
4720
- if (this.editorHeight != view.scrollDOM.clientHeight) {
4721
- this.editorHeight = view.scrollDOM.clientHeight;
4722
- result |= 8 /* Geometry */;
4723
- }
4724
4774
  if (dTop > 0 && dBottom > 0)
4725
4775
  bias = Math.max(dTop, dBottom);
4726
4776
  else if (dTop < 0 && dBottom < 0)
@@ -4761,8 +4811,9 @@ class ViewState {
4761
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);
4762
4812
  // If scrollTarget is given, make sure the viewport includes that position
4763
4813
  if (scrollTarget) {
4764
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4814
+ let { head } = scrollTarget.range;
4765
4815
  if (head < viewport.from || head > viewport.to) {
4816
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4766
4817
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4767
4818
  if (scrollTarget.y == "center")
4768
4819
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5562,9 +5613,8 @@ function applyDOMChange(view, start, end, typeOver) {
5562
5613
  return;
5563
5614
  let { from, to } = bounds;
5564
5615
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5565
- let reader = new DOMReader(selPoints, view);
5616
+ let reader = new DOMReader(selPoints, view.state);
5566
5617
  reader.readRange(bounds.startDOM, bounds.endDOM);
5567
- newSel = selectionFromPoints(selPoints, from);
5568
5618
  let preferredPos = sel.from, preferredSide = null;
5569
5619
  // Prefer anchoring to end when Backspace is pressed (or, on
5570
5620
  // Android, when something was deleted)
@@ -5573,10 +5623,29 @@ function applyDOMChange(view, start, end, typeOver) {
5573
5623
  preferredPos = sel.to;
5574
5624
  preferredSide = "end";
5575
5625
  }
5576
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5577
- 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
+ }
5578
5645
  change = { from: from + diff.from, to: from + diff.toA,
5579
- 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);
5580
5649
  }
5581
5650
  else if (view.hasFocus || !view.state.facet(editable)) {
5582
5651
  let domSel = view.observer.selectionRange;
@@ -5633,19 +5702,47 @@ function applyDOMChange(view, start, end, typeOver) {
5633
5702
  view.inputState.composing++;
5634
5703
  let tr;
5635
5704
  if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5636
- (!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) {
5637
5707
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5638
5708
  let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5639
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
5640
- after));
5709
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5641
5710
  }
5642
5711
  else {
5643
5712
  let changes = startState.changes(change);
5644
- tr = {
5645
- changes,
5646
- selection: newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5647
- ? startState.selection.replaceRange(newSel.main) : undefined
5648
- };
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
+ }
5649
5746
  }
5650
5747
  let userEvent = "input.type";
5651
5748
  if (view.composing) {
@@ -5860,7 +5957,7 @@ class EditorView {
5860
5957
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
5861
5958
  return this.setState(state$1);
5862
5959
  update = new ViewUpdate(this, state$1, transactions);
5863
- let scrollTarget = null;
5960
+ let scrollTarget = this.viewState.scrollTarget;
5864
5961
  try {
5865
5962
  this.updateState = 2 /* Updating */;
5866
5963
  for (let tr of transactions) {
@@ -5916,6 +6013,7 @@ class EditorView {
5916
6013
  return;
5917
6014
  }
5918
6015
  this.updateState = 2 /* Updating */;
6016
+ let hadFocus = this.hasFocus;
5919
6017
  try {
5920
6018
  for (let plugin of this.plugins)
5921
6019
  plugin.destroy(this);
@@ -5933,6 +6031,8 @@ class EditorView {
5933
6031
  finally {
5934
6032
  this.updateState = 0 /* Idle */;
5935
6033
  }
6034
+ if (hadFocus)
6035
+ this.focus();
5936
6036
  this.requestMeasure();
5937
6037
  }
5938
6038
  updatePlugins(update) {
@@ -6002,7 +6102,7 @@ class EditorView {
6002
6102
  return BadMeasure;
6003
6103
  }
6004
6104
  });
6005
- let update = new ViewUpdate(this, this.state), redrawn = false;
6105
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6006
6106
  update.flags |= changed;
6007
6107
  if (!updated)
6008
6108
  updated = update;
@@ -6029,11 +6129,12 @@ class EditorView {
6029
6129
  if (this.viewState.scrollTarget) {
6030
6130
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6031
6131
  this.viewState.scrollTarget = null;
6132
+ scrolled = true;
6032
6133
  }
6033
6134
  if (redrawn)
6034
6135
  this.docView.updateSelection(true);
6035
6136
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6036
- this.measureRequests.length == 0)
6137
+ !scrolled && this.measureRequests.length == 0)
6037
6138
  break;
6038
6139
  }
6039
6140
  }
@@ -6169,7 +6270,7 @@ class EditorView {
6169
6270
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6170
6271
  queries.
6171
6272
 
6172
- *Deprecated: use `blockAtHeight` instead.*
6273
+ *Deprecated: use `elementAtHeight` instead.*
6173
6274
  */
6174
6275
  blockAtHeight(height, docTop) {
6175
6276
  let top = ensureTop(docTop, this);
@@ -6581,6 +6682,13 @@ mechanism for providing decorations.
6581
6682
  */
6582
6683
  EditorView.decorations = decorations;
6583
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
+ /**
6584
6692
  Facet that provides additional DOM attributes for the editor's
6585
6693
  editable DOM element.
6586
6694
  */
@@ -7007,7 +7115,7 @@ function measureRange(view, range) {
7007
7115
  return pieces(top).concat(between).concat(pieces(bottom));
7008
7116
  }
7009
7117
  function piece(left, top, right, bottom) {
7010
- 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");
7011
7119
  }
7012
7120
  function pieces({ top, bottom, horizontal }) {
7013
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);
@@ -1307,7 +1306,7 @@ class LineView extends ContentView {
1307
1306
  if (attrs)
1308
1307
  this.attrs = combineAttrs(attrs, this.attrs || {});
1309
1308
  if (cls)
1310
- this.attrs = combineAttrs(attrs, { class: cls });
1309
+ this.attrs = combineAttrs({ class: cls }, this.attrs || {});
1311
1310
  }
1312
1311
  domAtPos(pos) {
1313
1312
  return inlineDOMAtPos(this.dom, this.children, pos);
@@ -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);
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) {
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;
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) {
@@ -3000,7 +3041,11 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3000
3041
  var _a;
3001
3042
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3002
3043
  let block, { docHeight } = view.viewState;
3003
- let yOffset = Math.max(0, Math.min(y - docTop, docHeight));
3044
+ let yOffset = y - docTop;
3045
+ if (yOffset < 0)
3046
+ return 0;
3047
+ if (yOffset > docHeight)
3048
+ return view.state.doc.length;
3004
3049
  // Scan for a text block near the queried y position
3005
3050
  for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
3006
3051
  block = view.elementAtHeight(yOffset);
@@ -3140,7 +3185,7 @@ function byGroup(view, pos, start) {
3140
3185
  function moveVertically(view, start, forward, distance) {
3141
3186
  let startPos = start.head, dir = forward ? 1 : -1;
3142
3187
  if (startPos == (forward ? view.state.doc.length : 0))
3143
- return EditorSelection.cursor(startPos);
3188
+ return EditorSelection.cursor(startPos, start.assoc);
3144
3189
  let goal = start.goalColumn, startY;
3145
3190
  let rect = view.contentDOM.getBoundingClientRect();
3146
3191
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3161,7 +3206,7 @@ function moveVertically(view, start, forward, distance) {
3161
3206
  let curY = startY + (dist + extra) * dir;
3162
3207
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3163
3208
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3164
- return EditorSelection.cursor(pos, undefined, undefined, goal);
3209
+ return EditorSelection.cursor(pos, start.assoc, undefined, goal);
3165
3210
  }
3166
3211
  }
3167
3212
  function skipAtoms(view, oldPos, pos) {
@@ -4589,6 +4634,7 @@ class ViewState {
4589
4634
  this.contentDOMWidth = 0;
4590
4635
  this.contentDOMHeight = 0;
4591
4636
  this.editorHeight = 0;
4637
+ this.editorWidth = 0;
4592
4638
  this.heightOracle = new HeightOracle;
4593
4639
  // See VP.MaxDOMHeight
4594
4640
  this.scaler = IdScaler;
@@ -4671,6 +4717,12 @@ class ViewState {
4671
4717
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4672
4718
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4673
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
+ }
4674
4726
  if (measureContent) {
4675
4727
  this.mustMeasureContent = false;
4676
4728
  this.contentDOMHeight = dom.clientHeight;
@@ -4683,7 +4735,8 @@ class ViewState {
4683
4735
  }
4684
4736
  }
4685
4737
  // Pixel viewport
4686
- 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);
4687
4740
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4688
4741
  this.pixelViewport = pixelViewport;
4689
4742
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4694,11 +4747,16 @@ class ViewState {
4694
4747
  }
4695
4748
  if (!this.inView)
4696
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
+ }
4697
4756
  if (measureContent) {
4698
4757
  let lineHeights = view.docView.measureVisibleLineHeights();
4699
4758
  if (oracle.mustRefreshForHeights(lineHeights))
4700
4759
  refresh = true;
4701
- let contentWidth = dom.clientWidth;
4702
4760
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4703
4761
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4704
4762
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4707,14 +4765,6 @@ class ViewState {
4707
4765
  result |= 8 /* Geometry */;
4708
4766
  }
4709
4767
  }
4710
- if (this.contentDOMWidth != contentWidth) {
4711
- this.contentDOMWidth = contentWidth;
4712
- result |= 8 /* Geometry */;
4713
- }
4714
- if (this.editorHeight != view.scrollDOM.clientHeight) {
4715
- this.editorHeight = view.scrollDOM.clientHeight;
4716
- result |= 8 /* Geometry */;
4717
- }
4718
4768
  if (dTop > 0 && dBottom > 0)
4719
4769
  bias = Math.max(dTop, dBottom);
4720
4770
  else if (dTop < 0 && dBottom < 0)
@@ -4755,8 +4805,9 @@ class ViewState {
4755
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);
4756
4806
  // If scrollTarget is given, make sure the viewport includes that position
4757
4807
  if (scrollTarget) {
4758
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4808
+ let { head } = scrollTarget.range;
4759
4809
  if (head < viewport.from || head > viewport.to) {
4810
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4760
4811
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4761
4812
  if (scrollTarget.y == "center")
4762
4813
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5556,9 +5607,8 @@ function applyDOMChange(view, start, end, typeOver) {
5556
5607
  return;
5557
5608
  let { from, to } = bounds;
5558
5609
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5559
- let reader = new DOMReader(selPoints, view);
5610
+ let reader = new DOMReader(selPoints, view.state);
5560
5611
  reader.readRange(bounds.startDOM, bounds.endDOM);
5561
- newSel = selectionFromPoints(selPoints, from);
5562
5612
  let preferredPos = sel.from, preferredSide = null;
5563
5613
  // Prefer anchoring to end when Backspace is pressed (or, on
5564
5614
  // Android, when something was deleted)
@@ -5567,10 +5617,29 @@ function applyDOMChange(view, start, end, typeOver) {
5567
5617
  preferredPos = sel.to;
5568
5618
  preferredSide = "end";
5569
5619
  }
5570
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5571
- 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
+ }
5572
5639
  change = { from: from + diff.from, to: from + diff.toA,
5573
- 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);
5574
5643
  }
5575
5644
  else if (view.hasFocus || !view.state.facet(editable)) {
5576
5645
  let domSel = view.observer.selectionRange;
@@ -5627,19 +5696,47 @@ function applyDOMChange(view, start, end, typeOver) {
5627
5696
  view.inputState.composing++;
5628
5697
  let tr;
5629
5698
  if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5630
- (!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) {
5631
5701
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5632
5702
  let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5633
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
5634
- after));
5703
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5635
5704
  }
5636
5705
  else {
5637
5706
  let changes = startState.changes(change);
5638
- tr = {
5639
- changes,
5640
- selection: newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5641
- ? startState.selection.replaceRange(newSel.main) : undefined
5642
- };
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
+ }
5643
5740
  }
5644
5741
  let userEvent = "input.type";
5645
5742
  if (view.composing) {
@@ -5854,7 +5951,7 @@ class EditorView {
5854
5951
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
5855
5952
  return this.setState(state);
5856
5953
  update = new ViewUpdate(this, state, transactions);
5857
- let scrollTarget = null;
5954
+ let scrollTarget = this.viewState.scrollTarget;
5858
5955
  try {
5859
5956
  this.updateState = 2 /* Updating */;
5860
5957
  for (let tr of transactions) {
@@ -5910,6 +6007,7 @@ class EditorView {
5910
6007
  return;
5911
6008
  }
5912
6009
  this.updateState = 2 /* Updating */;
6010
+ let hadFocus = this.hasFocus;
5913
6011
  try {
5914
6012
  for (let plugin of this.plugins)
5915
6013
  plugin.destroy(this);
@@ -5927,6 +6025,8 @@ class EditorView {
5927
6025
  finally {
5928
6026
  this.updateState = 0 /* Idle */;
5929
6027
  }
6028
+ if (hadFocus)
6029
+ this.focus();
5930
6030
  this.requestMeasure();
5931
6031
  }
5932
6032
  updatePlugins(update) {
@@ -5996,7 +6096,7 @@ class EditorView {
5996
6096
  return BadMeasure;
5997
6097
  }
5998
6098
  });
5999
- let update = new ViewUpdate(this, this.state), redrawn = false;
6099
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6000
6100
  update.flags |= changed;
6001
6101
  if (!updated)
6002
6102
  updated = update;
@@ -6023,11 +6123,12 @@ class EditorView {
6023
6123
  if (this.viewState.scrollTarget) {
6024
6124
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6025
6125
  this.viewState.scrollTarget = null;
6126
+ scrolled = true;
6026
6127
  }
6027
6128
  if (redrawn)
6028
6129
  this.docView.updateSelection(true);
6029
6130
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6030
- this.measureRequests.length == 0)
6131
+ !scrolled && this.measureRequests.length == 0)
6031
6132
  break;
6032
6133
  }
6033
6134
  }
@@ -6163,7 +6264,7 @@ class EditorView {
6163
6264
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6164
6265
  queries.
6165
6266
 
6166
- *Deprecated: use `blockAtHeight` instead.*
6267
+ *Deprecated: use `elementAtHeight` instead.*
6167
6268
  */
6168
6269
  blockAtHeight(height, docTop) {
6169
6270
  let top = ensureTop(docTop, this);
@@ -6575,6 +6676,13 @@ mechanism for providing decorations.
6575
6676
  */
6576
6677
  EditorView.decorations = decorations;
6577
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
+ /**
6578
6686
  Facet that provides additional DOM attributes for the editor's
6579
6687
  editable DOM element.
6580
6688
  */
@@ -7001,7 +7109,7 @@ function measureRange(view, range) {
7001
7109
  return pieces(top).concat(between).concat(pieces(bottom));
7002
7110
  }
7003
7111
  function piece(left, top, right, bottom) {
7004
- 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");
7005
7113
  }
7006
7114
  function pieces({ top, bottom, horizontal }) {
7007
7115
  let pieces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.37",
3
+ "version": "0.19.41",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",