@codemirror/view 0.19.36 → 0.19.40

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,47 @@
1
+ ## 0.19.40 (2022-01-19)
2
+
3
+ ### Bug fixes
4
+
5
+ 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).
6
+
7
+ Fix a bug that cause the editor to get confused about which content was visible after scrolling something into view.
8
+
9
+ Fix a bug where the dummy elements rendered around widgets could end up in a separate set of wrapping marks, and thus become visible.
10
+
11
+ `EditorView.moveVertically` now preserves the `assoc` property of the input range.
12
+
13
+ Get rid of gaps between selection elements drawn by `drawSelection`.
14
+
15
+ Fix an issue where replacing text next to a widget might leak bogus zero-width spaces into the document.
16
+
17
+ Avoid browser selection mishandling when a focused view has `setState` called by eagerly refocusing it.
18
+
19
+ ## 0.19.39 (2022-01-06)
20
+
21
+ ### Bug fixes
22
+
23
+ Make sure the editor signals a `geometryChanged` update when its width changes.
24
+
25
+ ### New features
26
+
27
+ `EditorView.darkTheme` can now be queried to figure out whether the editor is using a dark theme.
28
+
29
+ ## 0.19.38 (2022-01-05)
30
+
31
+ ### Bug fixes
32
+
33
+ Fix a bug that caused line decorations with a `class` property to suppress all other line decorations for that line.
34
+
35
+ Fix a bug that caused scroll effects to be corrupted when further updates came in before they were applied.
36
+
37
+ 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.
38
+
39
+ ## 0.19.37 (2021-12-22)
40
+
41
+ ### Bug fixes
42
+
43
+ Fix regression where plugin replacing decorations that span to the end of the line are ignored.
44
+
1
45
  ## 0.19.36 (2021-12-22)
2
46
 
3
47
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1237,7 +1237,7 @@ function widgetsEq(a, b) {
1237
1237
  }
1238
1238
  function addRange(from, to, ranges, margin = 0) {
1239
1239
  let last = ranges.length - 1;
1240
- if (last >= 0 && ranges[last] + margin > from)
1240
+ if (last >= 0 && ranges[last] + margin >= from)
1241
1241
  ranges[last] = Math.max(ranges[last], to);
1242
1242
  else
1243
1243
  ranges.push(from, to);
@@ -1311,7 +1311,7 @@ class LineView extends ContentView {
1311
1311
  if (attrs)
1312
1312
  this.attrs = combineAttrs(attrs, this.attrs || {});
1313
1313
  if (cls)
1314
- this.attrs = combineAttrs(attrs, { class: cls });
1314
+ this.attrs = combineAttrs({ class: cls }, this.attrs || {});
1315
1315
  }
1316
1316
  domAtPos(pos) {
1317
1317
  return inlineDOMAtPos(this.dom, this.children, pos);
@@ -1518,7 +1518,7 @@ class ContentBuilder {
1518
1518
  }
1519
1519
  }
1520
1520
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1521
- this.flushBuffer(active);
1521
+ this.flushBuffer(active.slice(0, openStart));
1522
1522
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1523
1523
  this.atCursorPos = true;
1524
1524
  this.textOff += take;
@@ -1581,7 +1581,7 @@ class ContentBuilder {
1581
1581
  return true;
1582
1582
  if (value.block)
1583
1583
  throw new RangeError("Block decorations may not be specified via plugins");
1584
- return to < this.doc.lineAt(this.pos).to;
1584
+ return to <= this.doc.lineAt(this.pos).to;
1585
1585
  }
1586
1586
  static build(text, from, to, decorations, pluginDecorationLength) {
1587
1587
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2332,6 +2332,15 @@ class DOMReader {
2332
2332
  this.findPointBefore(parent, end);
2333
2333
  return this;
2334
2334
  }
2335
+ readTextNode(node) {
2336
+ var _a, _b;
2337
+ let text = node.nodeValue;
2338
+ if (/^\u200b/.test(text) && ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.contentEditable) == "false")
2339
+ text = text.slice(1);
2340
+ if (/\u200b$/.test(text) && ((_b = node.nextSibling) === null || _b === void 0 ? void 0 : _b.contentEditable) == "false")
2341
+ text = text.slice(0, text.length - 1);
2342
+ return text;
2343
+ }
2335
2344
  readNode(node) {
2336
2345
  if (node.cmIgnore)
2337
2346
  return;
@@ -2341,7 +2350,7 @@ class DOMReader {
2341
2350
  if (fromView != null)
2342
2351
  text = fromView.sliceString(0, undefined, this.lineBreak);
2343
2352
  else if (node.nodeType == 3)
2344
- text = node.nodeValue;
2353
+ text = this.readTextNode(node);
2345
2354
  else if (node.nodeName == "BR")
2346
2355
  text = node.nextSibling ? this.lineBreak : "";
2347
2356
  else if (node.nodeType == 1)
@@ -2768,39 +2777,45 @@ class BlockGapWidget extends WidgetType {
2768
2777
  }
2769
2778
  get estimatedHeight() { return this.height; }
2770
2779
  }
2771
- function computeCompositionDeco(view, changes) {
2780
+ function compositionSurroundingNode(view) {
2772
2781
  let sel = view.observer.selectionRange;
2773
2782
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2774
2783
  if (!textNode)
2775
- return Decoration.none;
2784
+ return null;
2776
2785
  let cView = view.docView.nearest(textNode);
2777
2786
  if (!cView)
2778
- return Decoration.none;
2779
- let from, to, topNode = textNode;
2787
+ return null;
2780
2788
  if (cView instanceof LineView) {
2789
+ let topNode = textNode;
2781
2790
  while (topNode.parentNode != cView.dom)
2782
2791
  topNode = topNode.parentNode;
2783
2792
  let prev = topNode.previousSibling;
2784
2793
  while (prev && !ContentView.get(prev))
2785
2794
  prev = prev.previousSibling;
2786
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2795
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2796
+ return { from: pos, to: pos, node: topNode, text: textNode };
2787
2797
  }
2788
2798
  else {
2789
2799
  for (;;) {
2790
2800
  let { parent } = cView;
2791
2801
  if (!parent)
2792
- return Decoration.none;
2802
+ return null;
2793
2803
  if (parent instanceof LineView)
2794
2804
  break;
2795
2805
  cView = parent;
2796
2806
  }
2797
- from = cView.posAtStart;
2798
- to = from + cView.length;
2799
- topNode = cView.dom;
2807
+ let from = cView.posAtStart;
2808
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2800
2809
  }
2810
+ }
2811
+ function computeCompositionDeco(view, changes) {
2812
+ let surrounding = compositionSurroundingNode(view);
2813
+ if (!surrounding)
2814
+ return Decoration.none;
2815
+ let { from, to, node, text: textNode } = surrounding;
2801
2816
  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;
2817
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2818
+ new DOMReader([], view).readRange(node.firstChild, null).text;
2804
2819
  if (newTo - newFrom < text.length) {
2805
2820
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2806
2821
  newTo = newFrom + text.length;
@@ -2812,7 +2827,7 @@ function computeCompositionDeco(view, changes) {
2812
2827
  else if (state.sliceDoc(newFrom, newTo) != text) {
2813
2828
  return Decoration.none;
2814
2829
  }
2815
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2830
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2816
2831
  }
2817
2832
  class CompositionWidget extends WidgetType {
2818
2833
  constructor(top, text) {
@@ -3005,7 +3020,11 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3005
3020
  var _a;
3006
3021
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3007
3022
  let block, { docHeight } = view.viewState;
3008
- let yOffset = Math.max(0, Math.min(y - docTop, docHeight));
3023
+ let yOffset = y - docTop;
3024
+ if (yOffset < 0)
3025
+ return 0;
3026
+ if (yOffset > docHeight)
3027
+ return view.state.doc.length;
3009
3028
  // Scan for a text block near the queried y position
3010
3029
  for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
3011
3030
  block = view.elementAtHeight(yOffset);
@@ -3145,7 +3164,7 @@ function byGroup(view, pos, start) {
3145
3164
  function moveVertically(view, start, forward, distance) {
3146
3165
  let startPos = start.head, dir = forward ? 1 : -1;
3147
3166
  if (startPos == (forward ? view.state.doc.length : 0))
3148
- return state.EditorSelection.cursor(startPos);
3167
+ return state.EditorSelection.cursor(startPos, start.assoc);
3149
3168
  let goal = start.goalColumn, startY;
3150
3169
  let rect = view.contentDOM.getBoundingClientRect();
3151
3170
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3166,7 +3185,7 @@ function moveVertically(view, start, forward, distance) {
3166
3185
  let curY = startY + (dist + extra) * dir;
3167
3186
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3168
3187
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3169
- return state.EditorSelection.cursor(pos, undefined, undefined, goal);
3188
+ return state.EditorSelection.cursor(pos, start.assoc, undefined, goal);
3170
3189
  }
3171
3190
  }
3172
3191
  function skipAtoms(view, oldPos, pos) {
@@ -4595,6 +4614,7 @@ class ViewState {
4595
4614
  this.contentDOMWidth = 0;
4596
4615
  this.contentDOMHeight = 0;
4597
4616
  this.editorHeight = 0;
4617
+ this.editorWidth = 0;
4598
4618
  this.heightOracle = new HeightOracle;
4599
4619
  // See VP.MaxDOMHeight
4600
4620
  this.scaler = IdScaler;
@@ -4689,7 +4709,8 @@ class ViewState {
4689
4709
  }
4690
4710
  }
4691
4711
  // Pixel viewport
4692
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4712
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4713
+ : visiblePixelRange(dom, this.paddingTop);
4693
4714
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4694
4715
  this.pixelViewport = pixelViewport;
4695
4716
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4700,11 +4721,18 @@ class ViewState {
4700
4721
  }
4701
4722
  if (!this.inView)
4702
4723
  return 0;
4724
+ let contentWidth = dom.clientWidth;
4725
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4726
+ this.editorWidth != view.scrollDOM.clientWidth) {
4727
+ this.contentDOMWidth = contentWidth;
4728
+ this.editorHeight = view.scrollDOM.clientHeight;
4729
+ this.editorWidth = view.scrollDOM.clientWidth;
4730
+ result |= 8 /* Geometry */;
4731
+ }
4703
4732
  if (measureContent) {
4704
4733
  let lineHeights = view.docView.measureVisibleLineHeights();
4705
4734
  if (oracle.mustRefreshForHeights(lineHeights))
4706
4735
  refresh = true;
4707
- let contentWidth = dom.clientWidth;
4708
4736
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4709
4737
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4710
4738
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4713,14 +4741,6 @@ class ViewState {
4713
4741
  result |= 8 /* Geometry */;
4714
4742
  }
4715
4743
  }
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
4744
  if (dTop > 0 && dBottom > 0)
4725
4745
  bias = Math.max(dTop, dBottom);
4726
4746
  else if (dTop < 0 && dBottom < 0)
@@ -5633,19 +5653,47 @@ function applyDOMChange(view, start, end, typeOver) {
5633
5653
  view.inputState.composing++;
5634
5654
  let tr;
5635
5655
  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)) {
5656
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5657
+ view.inputState.composing < 0) {
5637
5658
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5638
5659
  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));
5660
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5641
5661
  }
5642
5662
  else {
5643
5663
  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
- };
5664
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5665
+ ? newSel.main : undefined;
5666
+ // Try to apply a composition change to all cursors
5667
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5668
+ change.to <= sel.to && change.to >= sel.to - 10) {
5669
+ let replaced = view.state.sliceDoc(change.from, change.to);
5670
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5671
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5672
+ tr = startState.changeByRange(range => {
5673
+ if (range.from == sel.from && range.to == sel.to)
5674
+ return { changes, range: mainSel || range.map(changes) };
5675
+ let to = range.to - offset, from = to - replaced.length;
5676
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5677
+ // Unfortunately, there's no way to make multiple
5678
+ // changes in the same node work without aborting
5679
+ // composition, so cursors in the composition range are
5680
+ // ignored.
5681
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5682
+ return { range };
5683
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5684
+ return {
5685
+ changes: rangeChanges,
5686
+ range: !mainSel ? range.map(rangeChanges) :
5687
+ state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5688
+ };
5689
+ });
5690
+ }
5691
+ else {
5692
+ tr = {
5693
+ changes,
5694
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5695
+ };
5696
+ }
5649
5697
  }
5650
5698
  let userEvent = "input.type";
5651
5699
  if (view.composing) {
@@ -5860,7 +5908,7 @@ class EditorView {
5860
5908
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
5861
5909
  return this.setState(state$1);
5862
5910
  update = new ViewUpdate(this, state$1, transactions);
5863
- let scrollTarget = null;
5911
+ let scrollTarget = this.viewState.scrollTarget;
5864
5912
  try {
5865
5913
  this.updateState = 2 /* Updating */;
5866
5914
  for (let tr of transactions) {
@@ -5916,6 +5964,7 @@ class EditorView {
5916
5964
  return;
5917
5965
  }
5918
5966
  this.updateState = 2 /* Updating */;
5967
+ let hadFocus = this.hasFocus;
5919
5968
  try {
5920
5969
  for (let plugin of this.plugins)
5921
5970
  plugin.destroy(this);
@@ -5933,6 +5982,8 @@ class EditorView {
5933
5982
  finally {
5934
5983
  this.updateState = 0 /* Idle */;
5935
5984
  }
5985
+ if (hadFocus)
5986
+ this.focus();
5936
5987
  this.requestMeasure();
5937
5988
  }
5938
5989
  updatePlugins(update) {
@@ -6002,7 +6053,7 @@ class EditorView {
6002
6053
  return BadMeasure;
6003
6054
  }
6004
6055
  });
6005
- let update = new ViewUpdate(this, this.state), redrawn = false;
6056
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6006
6057
  update.flags |= changed;
6007
6058
  if (!updated)
6008
6059
  updated = update;
@@ -6029,11 +6080,12 @@ class EditorView {
6029
6080
  if (this.viewState.scrollTarget) {
6030
6081
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6031
6082
  this.viewState.scrollTarget = null;
6083
+ scrolled = true;
6032
6084
  }
6033
6085
  if (redrawn)
6034
6086
  this.docView.updateSelection(true);
6035
6087
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6036
- this.measureRequests.length == 0)
6088
+ !scrolled && this.measureRequests.length == 0)
6037
6089
  break;
6038
6090
  }
6039
6091
  }
@@ -6169,7 +6221,7 @@ class EditorView {
6169
6221
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6170
6222
  queries.
6171
6223
 
6172
- *Deprecated: use `blockAtHeight` instead.*
6224
+ *Deprecated: use `elementAtHeight` instead.*
6173
6225
  */
6174
6226
  blockAtHeight(height, docTop) {
6175
6227
  let top = ensureTop(docTop, this);
@@ -6581,6 +6633,13 @@ mechanism for providing decorations.
6581
6633
  */
6582
6634
  EditorView.decorations = decorations;
6583
6635
  /**
6636
+ This facet records whether a dark theme is active. The extension
6637
+ returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
6638
+ includes an instance of this when the `dark` option is set to
6639
+ true.
6640
+ */
6641
+ EditorView.darkTheme = darkTheme;
6642
+ /**
6584
6643
  Facet that provides additional DOM attributes for the editor's
6585
6644
  editable DOM element.
6586
6645
  */
@@ -7007,7 +7066,7 @@ function measureRange(view, range) {
7007
7066
  return pieces(top).concat(between).concat(pieces(bottom));
7008
7067
  }
7009
7068
  function piece(left, top, right, bottom) {
7010
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7069
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7011
7070
  }
7012
7071
  function pieces({ top, bottom, horizontal }) {
7013
7072
  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
@@ -1233,7 +1233,7 @@ function widgetsEq(a, b) {
1233
1233
  }
1234
1234
  function addRange(from, to, ranges, margin = 0) {
1235
1235
  let last = ranges.length - 1;
1236
- if (last >= 0 && ranges[last] + margin > from)
1236
+ if (last >= 0 && ranges[last] + margin >= from)
1237
1237
  ranges[last] = Math.max(ranges[last], to);
1238
1238
  else
1239
1239
  ranges.push(from, to);
@@ -1307,7 +1307,7 @@ class LineView extends ContentView {
1307
1307
  if (attrs)
1308
1308
  this.attrs = combineAttrs(attrs, this.attrs || {});
1309
1309
  if (cls)
1310
- this.attrs = combineAttrs(attrs, { class: cls });
1310
+ this.attrs = combineAttrs({ class: cls }, this.attrs || {});
1311
1311
  }
1312
1312
  domAtPos(pos) {
1313
1313
  return inlineDOMAtPos(this.dom, this.children, pos);
@@ -1514,7 +1514,7 @@ class ContentBuilder {
1514
1514
  }
1515
1515
  }
1516
1516
  let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1517
- this.flushBuffer(active);
1517
+ this.flushBuffer(active.slice(0, openStart));
1518
1518
  this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1519
1519
  this.atCursorPos = true;
1520
1520
  this.textOff += take;
@@ -1577,7 +1577,7 @@ class ContentBuilder {
1577
1577
  return true;
1578
1578
  if (value.block)
1579
1579
  throw new RangeError("Block decorations may not be specified via plugins");
1580
- return to < this.doc.lineAt(this.pos).to;
1580
+ return to <= this.doc.lineAt(this.pos).to;
1581
1581
  }
1582
1582
  static build(text, from, to, decorations, pluginDecorationLength) {
1583
1583
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2327,6 +2327,15 @@ class DOMReader {
2327
2327
  this.findPointBefore(parent, end);
2328
2328
  return this;
2329
2329
  }
2330
+ readTextNode(node) {
2331
+ var _a, _b;
2332
+ let text = node.nodeValue;
2333
+ if (/^\u200b/.test(text) && ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.contentEditable) == "false")
2334
+ text = text.slice(1);
2335
+ if (/\u200b$/.test(text) && ((_b = node.nextSibling) === null || _b === void 0 ? void 0 : _b.contentEditable) == "false")
2336
+ text = text.slice(0, text.length - 1);
2337
+ return text;
2338
+ }
2330
2339
  readNode(node) {
2331
2340
  if (node.cmIgnore)
2332
2341
  return;
@@ -2336,7 +2345,7 @@ class DOMReader {
2336
2345
  if (fromView != null)
2337
2346
  text = fromView.sliceString(0, undefined, this.lineBreak);
2338
2347
  else if (node.nodeType == 3)
2339
- text = node.nodeValue;
2348
+ text = this.readTextNode(node);
2340
2349
  else if (node.nodeName == "BR")
2341
2350
  text = node.nextSibling ? this.lineBreak : "";
2342
2351
  else if (node.nodeType == 1)
@@ -2763,39 +2772,45 @@ class BlockGapWidget extends WidgetType {
2763
2772
  }
2764
2773
  get estimatedHeight() { return this.height; }
2765
2774
  }
2766
- function computeCompositionDeco(view, changes) {
2775
+ function compositionSurroundingNode(view) {
2767
2776
  let sel = view.observer.selectionRange;
2768
2777
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2769
2778
  if (!textNode)
2770
- return Decoration.none;
2779
+ return null;
2771
2780
  let cView = view.docView.nearest(textNode);
2772
2781
  if (!cView)
2773
- return Decoration.none;
2774
- let from, to, topNode = textNode;
2782
+ return null;
2775
2783
  if (cView instanceof LineView) {
2784
+ let topNode = textNode;
2776
2785
  while (topNode.parentNode != cView.dom)
2777
2786
  topNode = topNode.parentNode;
2778
2787
  let prev = topNode.previousSibling;
2779
2788
  while (prev && !ContentView.get(prev))
2780
2789
  prev = prev.previousSibling;
2781
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2790
+ let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2791
+ return { from: pos, to: pos, node: topNode, text: textNode };
2782
2792
  }
2783
2793
  else {
2784
2794
  for (;;) {
2785
2795
  let { parent } = cView;
2786
2796
  if (!parent)
2787
- return Decoration.none;
2797
+ return null;
2788
2798
  if (parent instanceof LineView)
2789
2799
  break;
2790
2800
  cView = parent;
2791
2801
  }
2792
- from = cView.posAtStart;
2793
- to = from + cView.length;
2794
- topNode = cView.dom;
2802
+ let from = cView.posAtStart;
2803
+ return { from, to: from + cView.length, node: cView.dom, text: textNode };
2795
2804
  }
2805
+ }
2806
+ function computeCompositionDeco(view, changes) {
2807
+ let surrounding = compositionSurroundingNode(view);
2808
+ if (!surrounding)
2809
+ return Decoration.none;
2810
+ let { from, to, node, text: textNode } = surrounding;
2796
2811
  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;
2812
+ let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2813
+ new DOMReader([], view).readRange(node.firstChild, null).text;
2799
2814
  if (newTo - newFrom < text.length) {
2800
2815
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2801
2816
  newTo = newFrom + text.length;
@@ -2807,7 +2822,7 @@ function computeCompositionDeco(view, changes) {
2807
2822
  else if (state.sliceDoc(newFrom, newTo) != text) {
2808
2823
  return Decoration.none;
2809
2824
  }
2810
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2825
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2811
2826
  }
2812
2827
  class CompositionWidget extends WidgetType {
2813
2828
  constructor(top, text) {
@@ -3000,7 +3015,11 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
3000
3015
  var _a;
3001
3016
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3002
3017
  let block, { docHeight } = view.viewState;
3003
- let yOffset = Math.max(0, Math.min(y - docTop, docHeight));
3018
+ let yOffset = y - docTop;
3019
+ if (yOffset < 0)
3020
+ return 0;
3021
+ if (yOffset > docHeight)
3022
+ return view.state.doc.length;
3004
3023
  // Scan for a text block near the queried y position
3005
3024
  for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
3006
3025
  block = view.elementAtHeight(yOffset);
@@ -3140,7 +3159,7 @@ function byGroup(view, pos, start) {
3140
3159
  function moveVertically(view, start, forward, distance) {
3141
3160
  let startPos = start.head, dir = forward ? 1 : -1;
3142
3161
  if (startPos == (forward ? view.state.doc.length : 0))
3143
- return EditorSelection.cursor(startPos);
3162
+ return EditorSelection.cursor(startPos, start.assoc);
3144
3163
  let goal = start.goalColumn, startY;
3145
3164
  let rect = view.contentDOM.getBoundingClientRect();
3146
3165
  let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
@@ -3161,7 +3180,7 @@ function moveVertically(view, start, forward, distance) {
3161
3180
  let curY = startY + (dist + extra) * dir;
3162
3181
  let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3163
3182
  if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3164
- return EditorSelection.cursor(pos, undefined, undefined, goal);
3183
+ return EditorSelection.cursor(pos, start.assoc, undefined, goal);
3165
3184
  }
3166
3185
  }
3167
3186
  function skipAtoms(view, oldPos, pos) {
@@ -4589,6 +4608,7 @@ class ViewState {
4589
4608
  this.contentDOMWidth = 0;
4590
4609
  this.contentDOMHeight = 0;
4591
4610
  this.editorHeight = 0;
4611
+ this.editorWidth = 0;
4592
4612
  this.heightOracle = new HeightOracle;
4593
4613
  // See VP.MaxDOMHeight
4594
4614
  this.scaler = IdScaler;
@@ -4683,7 +4703,8 @@ class ViewState {
4683
4703
  }
4684
4704
  }
4685
4705
  // Pixel viewport
4686
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4706
+ let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4707
+ : visiblePixelRange(dom, this.paddingTop);
4687
4708
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4688
4709
  this.pixelViewport = pixelViewport;
4689
4710
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -4694,11 +4715,18 @@ class ViewState {
4694
4715
  }
4695
4716
  if (!this.inView)
4696
4717
  return 0;
4718
+ let contentWidth = dom.clientWidth;
4719
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4720
+ this.editorWidth != view.scrollDOM.clientWidth) {
4721
+ this.contentDOMWidth = contentWidth;
4722
+ this.editorHeight = view.scrollDOM.clientHeight;
4723
+ this.editorWidth = view.scrollDOM.clientWidth;
4724
+ result |= 8 /* Geometry */;
4725
+ }
4697
4726
  if (measureContent) {
4698
4727
  let lineHeights = view.docView.measureVisibleLineHeights();
4699
4728
  if (oracle.mustRefreshForHeights(lineHeights))
4700
4729
  refresh = true;
4701
- let contentWidth = dom.clientWidth;
4702
4730
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4703
4731
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4704
4732
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
@@ -4707,14 +4735,6 @@ class ViewState {
4707
4735
  result |= 8 /* Geometry */;
4708
4736
  }
4709
4737
  }
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
4738
  if (dTop > 0 && dBottom > 0)
4719
4739
  bias = Math.max(dTop, dBottom);
4720
4740
  else if (dTop < 0 && dBottom < 0)
@@ -5627,19 +5647,47 @@ function applyDOMChange(view, start, end, typeOver) {
5627
5647
  view.inputState.composing++;
5628
5648
  let tr;
5629
5649
  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)) {
5650
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5651
+ view.inputState.composing < 0) {
5631
5652
  let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5632
5653
  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));
5654
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5635
5655
  }
5636
5656
  else {
5637
5657
  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
- };
5658
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5659
+ ? newSel.main : undefined;
5660
+ // Try to apply a composition change to all cursors
5661
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5662
+ change.to <= sel.to && change.to >= sel.to - 10) {
5663
+ let replaced = view.state.sliceDoc(change.from, change.to);
5664
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5665
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5666
+ tr = startState.changeByRange(range => {
5667
+ if (range.from == sel.from && range.to == sel.to)
5668
+ return { changes, range: mainSel || range.map(changes) };
5669
+ let to = range.to - offset, from = to - replaced.length;
5670
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5671
+ // Unfortunately, there's no way to make multiple
5672
+ // changes in the same node work without aborting
5673
+ // composition, so cursors in the composition range are
5674
+ // ignored.
5675
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5676
+ return { range };
5677
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5678
+ return {
5679
+ changes: rangeChanges,
5680
+ range: !mainSel ? range.map(rangeChanges) :
5681
+ EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5682
+ };
5683
+ });
5684
+ }
5685
+ else {
5686
+ tr = {
5687
+ changes,
5688
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5689
+ };
5690
+ }
5643
5691
  }
5644
5692
  let userEvent = "input.type";
5645
5693
  if (view.composing) {
@@ -5854,7 +5902,7 @@ class EditorView {
5854
5902
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
5855
5903
  return this.setState(state);
5856
5904
  update = new ViewUpdate(this, state, transactions);
5857
- let scrollTarget = null;
5905
+ let scrollTarget = this.viewState.scrollTarget;
5858
5906
  try {
5859
5907
  this.updateState = 2 /* Updating */;
5860
5908
  for (let tr of transactions) {
@@ -5910,6 +5958,7 @@ class EditorView {
5910
5958
  return;
5911
5959
  }
5912
5960
  this.updateState = 2 /* Updating */;
5961
+ let hadFocus = this.hasFocus;
5913
5962
  try {
5914
5963
  for (let plugin of this.plugins)
5915
5964
  plugin.destroy(this);
@@ -5927,6 +5976,8 @@ class EditorView {
5927
5976
  finally {
5928
5977
  this.updateState = 0 /* Idle */;
5929
5978
  }
5979
+ if (hadFocus)
5980
+ this.focus();
5930
5981
  this.requestMeasure();
5931
5982
  }
5932
5983
  updatePlugins(update) {
@@ -5996,7 +6047,7 @@ class EditorView {
5996
6047
  return BadMeasure;
5997
6048
  }
5998
6049
  });
5999
- let update = new ViewUpdate(this, this.state), redrawn = false;
6050
+ let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
6000
6051
  update.flags |= changed;
6001
6052
  if (!updated)
6002
6053
  updated = update;
@@ -6023,11 +6074,12 @@ class EditorView {
6023
6074
  if (this.viewState.scrollTarget) {
6024
6075
  this.docView.scrollIntoView(this.viewState.scrollTarget);
6025
6076
  this.viewState.scrollTarget = null;
6077
+ scrolled = true;
6026
6078
  }
6027
6079
  if (redrawn)
6028
6080
  this.docView.updateSelection(true);
6029
6081
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6030
- this.measureRequests.length == 0)
6082
+ !scrolled && this.measureRequests.length == 0)
6031
6083
  break;
6032
6084
  }
6033
6085
  }
@@ -6163,7 +6215,7 @@ class EditorView {
6163
6215
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6164
6216
  queries.
6165
6217
 
6166
- *Deprecated: use `blockAtHeight` instead.*
6218
+ *Deprecated: use `elementAtHeight` instead.*
6167
6219
  */
6168
6220
  blockAtHeight(height, docTop) {
6169
6221
  let top = ensureTop(docTop, this);
@@ -6575,6 +6627,13 @@ mechanism for providing decorations.
6575
6627
  */
6576
6628
  EditorView.decorations = decorations;
6577
6629
  /**
6630
+ This facet records whether a dark theme is active. The extension
6631
+ returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
6632
+ includes an instance of this when the `dark` option is set to
6633
+ true.
6634
+ */
6635
+ EditorView.darkTheme = darkTheme;
6636
+ /**
6578
6637
  Facet that provides additional DOM attributes for the editor's
6579
6638
  editable DOM element.
6580
6639
  */
@@ -7001,7 +7060,7 @@ function measureRange(view, range) {
7001
7060
  return pieces(top).concat(between).concat(pieces(bottom));
7002
7061
  }
7003
7062
  function piece(left, top, right, bottom) {
7004
- return new Piece(left - base.left, top - base.top, right - left, bottom - top, "cm-selectionBackground");
7063
+ return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7005
7064
  }
7006
7065
  function pieces({ top, bottom, horizontal }) {
7007
7066
  let pieces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.36",
3
+ "version": "0.19.40",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",