@codemirror/view 0.19.40 → 0.19.44

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,45 @@
1
+ ## 0.19.44 (2022-02-17)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a crash that occasionally occurred when drag-selecting in a way that scrolled the editor.
6
+
7
+ ### New features
8
+
9
+ The new `EditorView.compositionStarted` property indicates whether a composition is starting.
10
+
11
+ ## 0.19.43 (2022-02-16)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix several issues where editing or composition went wrong due to our zero-width space kludge characters ending up in unexpected places.
16
+
17
+ Make sure the editor re-measures its dimensions whenever its theme changes.
18
+
19
+ Fix an issue where some keys on Android phones could leave the editor DOM unsynced with the actual document.
20
+
21
+ ## 0.19.42 (2022-02-05)
22
+
23
+ ### Bug fixes
24
+
25
+ Fix a regression in cursor position determination after making an edit next to a widget.
26
+
27
+ ## 0.19.41 (2022-02-04)
28
+
29
+ ### Bug fixes
30
+
31
+ 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.
32
+
33
+ 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.
34
+
35
+ Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
36
+
37
+ Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
38
+
39
+ Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
40
+
41
+ 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.
42
+
1
43
  ## 0.19.40 (2022-01-19)
2
44
 
3
45
  ### Bug fixes
@@ -66,7 +108,7 @@ Fix an issue where backspacing out a selection on Chrome Android would sometimes
66
108
 
67
109
  ### Bug fixes
68
110
 
69
- Fix a bug where content line elements would in some cases lose their `cm-line` class. Move test to scrollIntoView
111
+ Fix a bug where content line elements would in some cases lose their `cm-line` class.
70
112
 
71
113
  ## 0.19.33 (2021-12-16)
72
114
 
package/dist/index.cjs CHANGED
@@ -602,9 +602,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
602
602
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
603
603
  }
604
604
 
605
- let [nav, doc] = typeof navigator != "undefined"
606
- ? [navigator, document]
607
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
605
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
606
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
608
607
  const ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
609
608
  const ie_upto10 = /MSIE \d/.test(nav.userAgent);
610
609
  const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -860,9 +859,6 @@ class CompositionView extends WidgetView {
860
859
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
861
860
  get isEditable() { return true; }
862
861
  }
863
- // Use two characters on Android, to prevent Chrome from closing the
864
- // virtual keyboard when backspacing after a widget (#602).
865
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
866
862
  // These are drawn around uneditable widgets to avoid a number of
867
863
  // browser bugs that show up when the cursor is directly next to
868
864
  // uneditable inline content.
@@ -878,21 +874,21 @@ class WidgetBufferView extends ContentView {
878
874
  }
879
875
  split() { return new WidgetBufferView(this.side); }
880
876
  sync() {
881
- if (!this.dom)
882
- this.setDOM(document.createTextNode(ZeroWidthSpace));
883
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
884
- this.dom.nodeValue = ZeroWidthSpace;
877
+ if (!this.dom) {
878
+ let dom = document.createElement("img");
879
+ dom.className = "cm-widgetBuffer";
880
+ this.setDOM(dom);
881
+ }
885
882
  }
886
883
  getSide() { return this.side; }
887
884
  domAtPos(pos) { return DOMPos.before(this.dom); }
888
885
  localPosFromDOM() { return 0; }
889
886
  domBoundsAround() { return null; }
890
887
  coordsAt(pos) {
891
- let rects = clientRectsFor(this.dom);
892
- return rects[rects.length - 1] || null;
888
+ return this.dom.getBoundingClientRect();
893
889
  }
894
890
  get overrideDOMText() {
895
- return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
891
+ return text.Text.empty;
896
892
  }
897
893
  }
898
894
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -939,7 +935,7 @@ function coordsInChildren(view, pos, side) {
939
935
  continue;
940
936
  flatten = side = -child.getSide();
941
937
  }
942
- let rect = child.coordsAt(pos - off, side);
938
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
943
939
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
944
940
  }
945
941
  off = end;
@@ -1131,8 +1127,8 @@ class Decoration extends rangeset.RangeValue {
1131
1127
  static replace(spec) {
1132
1128
  let block = !!spec.block;
1133
1129
  let { start, end } = getInclusive(spec, block);
1134
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1135
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1130
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1131
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1136
1132
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1137
1133
  }
1138
1134
  /**
@@ -1577,11 +1573,13 @@ class ContentBuilder {
1577
1573
  this.openStart = openStart;
1578
1574
  }
1579
1575
  filterPoint(from, to, value, index) {
1580
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1581
- return true;
1582
- if (value.block)
1583
- throw new RangeError("Block decorations may not be specified via plugins");
1584
- return to <= this.doc.lineAt(this.pos).to;
1576
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1577
+ if (value.block)
1578
+ throw new RangeError("Block decorations may not be specified via plugins");
1579
+ if (to > this.doc.lineAt(this.pos).to)
1580
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1581
+ }
1582
+ return true;
1585
1583
  }
1586
1584
  static build(text, from, to, decorations, pluginDecorationLength) {
1587
1585
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2305,12 +2303,18 @@ function moveVisually(line, order, dir, start, forward) {
2305
2303
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2306
2304
  }
2307
2305
 
2306
+ const LineBreakPlaceholder = "\uffff";
2308
2307
  class DOMReader {
2309
- constructor(points, view) {
2308
+ constructor(points, state$1) {
2310
2309
  this.points = points;
2311
- this.view = view;
2312
2310
  this.text = "";
2313
- this.lineBreak = view.state.lineBreak;
2311
+ this.lineSeparator = state$1.facet(state.EditorState.lineSeparator);
2312
+ }
2313
+ append(text) {
2314
+ this.text += text;
2315
+ }
2316
+ lineBreak() {
2317
+ this.text += LineBreakPlaceholder;
2314
2318
  }
2315
2319
  readRange(start, end) {
2316
2320
  if (!start)
@@ -2326,42 +2330,61 @@ class DOMReader {
2326
2330
  if (view && nextView ? view.breakAfter :
2327
2331
  (view ? view.breakAfter : isBlockElement(cur)) ||
2328
2332
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2329
- this.text += this.lineBreak;
2333
+ this.lineBreak();
2330
2334
  cur = next;
2331
2335
  }
2332
2336
  this.findPointBefore(parent, end);
2333
2337
  return this;
2334
2338
  }
2335
2339
  readTextNode(node) {
2336
- var _a, _b;
2337
2340
  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;
2341
+ for (let point of this.points)
2342
+ if (point.node == node)
2343
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2344
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2345
+ let nextBreak = -1, breakSize = 1, m;
2346
+ if (this.lineSeparator) {
2347
+ nextBreak = text.indexOf(this.lineSeparator, off);
2348
+ breakSize = this.lineSeparator.length;
2349
+ }
2350
+ else if (m = re.exec(text)) {
2351
+ nextBreak = m.index;
2352
+ breakSize = m[0].length;
2353
+ }
2354
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2355
+ if (nextBreak < 0)
2356
+ break;
2357
+ this.lineBreak();
2358
+ if (breakSize > 1)
2359
+ for (let point of this.points)
2360
+ if (point.node == node && point.pos > this.text.length)
2361
+ point.pos -= breakSize - 1;
2362
+ off = nextBreak + breakSize;
2363
+ }
2343
2364
  }
2344
2365
  readNode(node) {
2345
2366
  if (node.cmIgnore)
2346
2367
  return;
2347
2368
  let view = ContentView.get(node);
2348
2369
  let fromView = view && view.overrideDOMText;
2349
- let text;
2350
- if (fromView != null)
2351
- text = fromView.sliceString(0, undefined, this.lineBreak);
2352
- else if (node.nodeType == 3)
2353
- text = this.readTextNode(node);
2354
- else if (node.nodeName == "BR")
2355
- text = node.nextSibling ? this.lineBreak : "";
2356
- else if (node.nodeType == 1)
2370
+ if (fromView != null) {
2371
+ this.findPointInside(node, fromView.length);
2372
+ for (let i = fromView.iter(); !i.next().done;) {
2373
+ if (i.lineBreak)
2374
+ this.lineBreak();
2375
+ else
2376
+ this.append(i.value);
2377
+ }
2378
+ }
2379
+ else if (node.nodeType == 3) {
2380
+ this.readTextNode(node);
2381
+ }
2382
+ else if (node.nodeName == "BR") {
2383
+ if (node.nextSibling)
2384
+ this.lineBreak();
2385
+ }
2386
+ else if (node.nodeType == 1) {
2357
2387
  this.readRange(node.firstChild, null);
2358
- if (text != null) {
2359
- this.findPointIn(node, text.length);
2360
- this.text += text;
2361
- // Chrome inserts two newlines when pressing shift-enter at the
2362
- // end of a line. This drops one of those.
2363
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2364
- this.text = this.text.slice(0, -1);
2365
2388
  }
2366
2389
  }
2367
2390
  findPointBefore(node, next) {
@@ -2369,10 +2392,10 @@ class DOMReader {
2369
2392
  if (point.node == node && node.childNodes[point.offset] == next)
2370
2393
  point.pos = this.text.length;
2371
2394
  }
2372
- findPointIn(node, maxLen) {
2395
+ findPointInside(node, maxLen) {
2373
2396
  for (let point of this.points)
2374
- if (point.node == node)
2375
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2397
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2398
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2376
2399
  }
2377
2400
  }
2378
2401
  function isBlockElement(node) {
@@ -2815,16 +2838,16 @@ function computeCompositionDeco(view, changes) {
2815
2838
  let { from, to, node, text: textNode } = surrounding;
2816
2839
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2817
2840
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2818
- new DOMReader([], view).readRange(node.firstChild, null).text;
2841
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2819
2842
  if (newTo - newFrom < text.length) {
2820
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2843
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2821
2844
  newTo = newFrom + text.length;
2822
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2845
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2823
2846
  newFrom = newTo - text.length;
2824
2847
  else
2825
2848
  return Decoration.none;
2826
2849
  }
2827
- else if (state.sliceDoc(newFrom, newTo) != text) {
2850
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2828
2851
  return Decoration.none;
2829
2852
  }
2830
2853
  return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
@@ -4697,6 +4720,12 @@ class ViewState {
4697
4720
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4698
4721
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4699
4722
  let result = 0, bias = 0;
4723
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4724
+ if (oracle.lineWrapping)
4725
+ measureContent = true;
4726
+ this.editorWidth = view.scrollDOM.clientWidth;
4727
+ result |= 8 /* Geometry */;
4728
+ }
4700
4729
  if (measureContent) {
4701
4730
  this.mustMeasureContent = false;
4702
4731
  this.contentDOMHeight = dom.clientHeight;
@@ -4722,11 +4751,9 @@ class ViewState {
4722
4751
  if (!this.inView)
4723
4752
  return 0;
4724
4753
  let contentWidth = dom.clientWidth;
4725
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4726
- this.editorWidth != view.scrollDOM.clientWidth) {
4754
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4727
4755
  this.contentDOMWidth = contentWidth;
4728
4756
  this.editorHeight = view.scrollDOM.clientHeight;
4729
- this.editorWidth = view.scrollDOM.clientWidth;
4730
4757
  result |= 8 /* Geometry */;
4731
4758
  }
4732
4759
  if (measureContent) {
@@ -4781,8 +4808,9 @@ class ViewState {
4781
4808
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4782
4809
  // If scrollTarget is given, make sure the viewport includes that position
4783
4810
  if (scrollTarget) {
4784
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4811
+ let { head } = scrollTarget.range;
4785
4812
  if (head < viewport.from || head > viewport.to) {
4813
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4786
4814
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4787
4815
  if (scrollTarget.y == "center")
4788
4816
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5168,6 +5196,10 @@ const baseTheme = buildTheme("." + baseThemeID, {
5168
5196
  overflow: "hidden",
5169
5197
  verticalAlign: "bottom"
5170
5198
  },
5199
+ ".cm-widgetBuffer": {
5200
+ verticalAlign: "text-bottom",
5201
+ height: "1em",
5202
+ },
5171
5203
  ".cm-placeholder": {
5172
5204
  color: "#888",
5173
5205
  display: "inline-block",
@@ -5416,7 +5448,7 @@ class DOMObserver {
5416
5448
  }
5417
5449
  // Throw away any pending changes
5418
5450
  clear() {
5419
- this.observer.takeRecords();
5451
+ this.processRecords();
5420
5452
  this.queue.length = 0;
5421
5453
  this.selectionChanged = false;
5422
5454
  }
@@ -5582,9 +5614,8 @@ function applyDOMChange(view, start, end, typeOver) {
5582
5614
  return;
5583
5615
  let { from, to } = bounds;
5584
5616
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5585
- let reader = new DOMReader(selPoints, view);
5617
+ let reader = new DOMReader(selPoints, view.state);
5586
5618
  reader.readRange(bounds.startDOM, bounds.endDOM);
5587
- newSel = selectionFromPoints(selPoints, from);
5588
5619
  let preferredPos = sel.from, preferredSide = null;
5589
5620
  // Prefer anchoring to end when Backspace is pressed (or, on
5590
5621
  // Android, when something was deleted)
@@ -5593,10 +5624,17 @@ function applyDOMChange(view, start, end, typeOver) {
5593
5624
  preferredPos = sel.to;
5594
5625
  preferredSide = "end";
5595
5626
  }
5596
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5597
- if (diff)
5627
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5628
+ if (diff) {
5629
+ // Chrome inserts two newlines when pressing shift-enter at the
5630
+ // end of a line. This drops one of those.
5631
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5632
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5633
+ diff.toB--;
5598
5634
  change = { from: from + diff.from, to: from + diff.toA,
5599
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5635
+ insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5636
+ }
5637
+ newSel = selectionFromPoints(selPoints, from);
5600
5638
  }
5601
5639
  else if (view.hasFocus || !view.state.facet(editable)) {
5602
5640
  let domSel = view.observer.selectionRange;
@@ -5875,9 +5913,17 @@ class EditorView {
5875
5913
  get inView() { return this.viewState.inView; }
5876
5914
  /**
5877
5915
  Indicates whether the user is currently composing text via
5878
- [IME](https://en.wikipedia.org/wiki/Input_method).
5916
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
5917
+ one change has been made in the current composition.
5879
5918
  */
5880
5919
  get composing() { return this.inputState.composing > 0; }
5920
+ /**
5921
+ Indicates whether the user is currently in composing state. Note
5922
+ that on some platforms, like Android, this will be the case a
5923
+ lot, since just putting the cursor on a word starts a
5924
+ composition there.
5925
+ */
5926
+ get compositionStarted() { return this.inputState.composing >= 0; }
5881
5927
  dispatch(...input) {
5882
5928
  this._dispatch(input.length == 1 && input[0] instanceof state.Transaction ? input[0]
5883
5929
  : this.state.update(...input));
@@ -5943,7 +5989,9 @@ class EditorView {
5943
5989
  finally {
5944
5990
  this.updateState = 0 /* Idle */;
5945
5991
  }
5946
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5992
+ if (update.startState.facet(theme) != update.state.facet(theme))
5993
+ this.viewState.mustMeasureContent = true;
5994
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5947
5995
  this.requestMeasure();
5948
5996
  if (!update.empty)
5949
5997
  for (let listener of this.state.facet(updateListener))
package/dist/index.d.ts CHANGED
@@ -676,9 +676,17 @@ declare class EditorView {
676
676
  get inView(): boolean;
677
677
  /**
678
678
  Indicates whether the user is currently composing text via
679
- [IME](https://en.wikipedia.org/wiki/Input_method).
679
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
680
+ one change has been made in the current composition.
680
681
  */
681
682
  get composing(): boolean;
683
+ /**
684
+ Indicates whether the user is currently in composing state. Note
685
+ that on some platforms, like Android, this will be the case a
686
+ lot, since just putting the cursor on a word starts a
687
+ composition there.
688
+ */
689
+ get compositionStarted(): boolean;
682
690
  private _dispatch;
683
691
  /**
684
692
  The document or shadow root that the view lives in.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
1
+ import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, EditorState, CharCategory, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
@@ -599,9 +599,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
599
599
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
600
600
  }
601
601
 
602
- let [nav, doc] = typeof navigator != "undefined"
603
- ? [navigator, document]
604
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
602
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
603
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
605
604
  const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
606
605
  const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
607
606
  const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -857,9 +856,6 @@ class CompositionView extends WidgetView {
857
856
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
858
857
  get isEditable() { return true; }
859
858
  }
860
- // Use two characters on Android, to prevent Chrome from closing the
861
- // virtual keyboard when backspacing after a widget (#602).
862
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
863
859
  // These are drawn around uneditable widgets to avoid a number of
864
860
  // browser bugs that show up when the cursor is directly next to
865
861
  // uneditable inline content.
@@ -875,21 +871,21 @@ class WidgetBufferView extends ContentView {
875
871
  }
876
872
  split() { return new WidgetBufferView(this.side); }
877
873
  sync() {
878
- if (!this.dom)
879
- this.setDOM(document.createTextNode(ZeroWidthSpace));
880
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
881
- this.dom.nodeValue = ZeroWidthSpace;
874
+ if (!this.dom) {
875
+ let dom = document.createElement("img");
876
+ dom.className = "cm-widgetBuffer";
877
+ this.setDOM(dom);
878
+ }
882
879
  }
883
880
  getSide() { return this.side; }
884
881
  domAtPos(pos) { return DOMPos.before(this.dom); }
885
882
  localPosFromDOM() { return 0; }
886
883
  domBoundsAround() { return null; }
887
884
  coordsAt(pos) {
888
- let rects = clientRectsFor(this.dom);
889
- return rects[rects.length - 1] || null;
885
+ return this.dom.getBoundingClientRect();
890
886
  }
891
887
  get overrideDOMText() {
892
- return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
888
+ return Text.empty;
893
889
  }
894
890
  }
895
891
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -936,7 +932,7 @@ function coordsInChildren(view, pos, side) {
936
932
  continue;
937
933
  flatten = side = -child.getSide();
938
934
  }
939
- let rect = child.coordsAt(pos - off, side);
935
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
940
936
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
941
937
  }
942
938
  off = end;
@@ -1127,8 +1123,8 @@ class Decoration extends RangeValue {
1127
1123
  static replace(spec) {
1128
1124
  let block = !!spec.block;
1129
1125
  let { start, end } = getInclusive(spec, block);
1130
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1131
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1126
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1127
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1132
1128
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1133
1129
  }
1134
1130
  /**
@@ -1573,11 +1569,13 @@ class ContentBuilder {
1573
1569
  this.openStart = openStart;
1574
1570
  }
1575
1571
  filterPoint(from, to, value, index) {
1576
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1577
- return true;
1578
- if (value.block)
1579
- throw new RangeError("Block decorations may not be specified via plugins");
1580
- return to <= this.doc.lineAt(this.pos).to;
1572
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1573
+ if (value.block)
1574
+ throw new RangeError("Block decorations may not be specified via plugins");
1575
+ if (to > this.doc.lineAt(this.pos).to)
1576
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1577
+ }
1578
+ return true;
1581
1579
  }
1582
1580
  static build(text, from, to, decorations, pluginDecorationLength) {
1583
1581
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2300,12 +2298,18 @@ function moveVisually(line, order, dir, start, forward) {
2300
2298
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2301
2299
  }
2302
2300
 
2301
+ const LineBreakPlaceholder = "\uffff";
2303
2302
  class DOMReader {
2304
- constructor(points, view) {
2303
+ constructor(points, state) {
2305
2304
  this.points = points;
2306
- this.view = view;
2307
2305
  this.text = "";
2308
- this.lineBreak = view.state.lineBreak;
2306
+ this.lineSeparator = state.facet(EditorState.lineSeparator);
2307
+ }
2308
+ append(text) {
2309
+ this.text += text;
2310
+ }
2311
+ lineBreak() {
2312
+ this.text += LineBreakPlaceholder;
2309
2313
  }
2310
2314
  readRange(start, end) {
2311
2315
  if (!start)
@@ -2321,42 +2325,61 @@ class DOMReader {
2321
2325
  if (view && nextView ? view.breakAfter :
2322
2326
  (view ? view.breakAfter : isBlockElement(cur)) ||
2323
2327
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2324
- this.text += this.lineBreak;
2328
+ this.lineBreak();
2325
2329
  cur = next;
2326
2330
  }
2327
2331
  this.findPointBefore(parent, end);
2328
2332
  return this;
2329
2333
  }
2330
2334
  readTextNode(node) {
2331
- var _a, _b;
2332
2335
  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;
2336
+ for (let point of this.points)
2337
+ if (point.node == node)
2338
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2339
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2340
+ let nextBreak = -1, breakSize = 1, m;
2341
+ if (this.lineSeparator) {
2342
+ nextBreak = text.indexOf(this.lineSeparator, off);
2343
+ breakSize = this.lineSeparator.length;
2344
+ }
2345
+ else if (m = re.exec(text)) {
2346
+ nextBreak = m.index;
2347
+ breakSize = m[0].length;
2348
+ }
2349
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2350
+ if (nextBreak < 0)
2351
+ break;
2352
+ this.lineBreak();
2353
+ if (breakSize > 1)
2354
+ for (let point of this.points)
2355
+ if (point.node == node && point.pos > this.text.length)
2356
+ point.pos -= breakSize - 1;
2357
+ off = nextBreak + breakSize;
2358
+ }
2338
2359
  }
2339
2360
  readNode(node) {
2340
2361
  if (node.cmIgnore)
2341
2362
  return;
2342
2363
  let view = ContentView.get(node);
2343
2364
  let fromView = view && view.overrideDOMText;
2344
- let text;
2345
- if (fromView != null)
2346
- text = fromView.sliceString(0, undefined, this.lineBreak);
2347
- else if (node.nodeType == 3)
2348
- text = this.readTextNode(node);
2349
- else if (node.nodeName == "BR")
2350
- text = node.nextSibling ? this.lineBreak : "";
2351
- else if (node.nodeType == 1)
2365
+ if (fromView != null) {
2366
+ this.findPointInside(node, fromView.length);
2367
+ for (let i = fromView.iter(); !i.next().done;) {
2368
+ if (i.lineBreak)
2369
+ this.lineBreak();
2370
+ else
2371
+ this.append(i.value);
2372
+ }
2373
+ }
2374
+ else if (node.nodeType == 3) {
2375
+ this.readTextNode(node);
2376
+ }
2377
+ else if (node.nodeName == "BR") {
2378
+ if (node.nextSibling)
2379
+ this.lineBreak();
2380
+ }
2381
+ else if (node.nodeType == 1) {
2352
2382
  this.readRange(node.firstChild, null);
2353
- if (text != null) {
2354
- this.findPointIn(node, text.length);
2355
- this.text += text;
2356
- // Chrome inserts two newlines when pressing shift-enter at the
2357
- // end of a line. This drops one of those.
2358
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2359
- this.text = this.text.slice(0, -1);
2360
2383
  }
2361
2384
  }
2362
2385
  findPointBefore(node, next) {
@@ -2364,10 +2387,10 @@ class DOMReader {
2364
2387
  if (point.node == node && node.childNodes[point.offset] == next)
2365
2388
  point.pos = this.text.length;
2366
2389
  }
2367
- findPointIn(node, maxLen) {
2390
+ findPointInside(node, maxLen) {
2368
2391
  for (let point of this.points)
2369
- if (point.node == node)
2370
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2392
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2393
+ point.pos = this.text.length + Math.min(maxLen, point.offset);
2371
2394
  }
2372
2395
  }
2373
2396
  function isBlockElement(node) {
@@ -2810,16 +2833,16 @@ function computeCompositionDeco(view, changes) {
2810
2833
  let { from, to, node, text: textNode } = surrounding;
2811
2834
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2812
2835
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2813
- new DOMReader([], view).readRange(node.firstChild, null).text;
2836
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2814
2837
  if (newTo - newFrom < text.length) {
2815
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2838
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2816
2839
  newTo = newFrom + text.length;
2817
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2840
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2818
2841
  newFrom = newTo - text.length;
2819
2842
  else
2820
2843
  return Decoration.none;
2821
2844
  }
2822
- else if (state.sliceDoc(newFrom, newTo) != text) {
2845
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2823
2846
  return Decoration.none;
2824
2847
  }
2825
2848
  return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
@@ -4691,6 +4714,12 @@ class ViewState {
4691
4714
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4692
4715
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4693
4716
  let result = 0, bias = 0;
4717
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4718
+ if (oracle.lineWrapping)
4719
+ measureContent = true;
4720
+ this.editorWidth = view.scrollDOM.clientWidth;
4721
+ result |= 8 /* Geometry */;
4722
+ }
4694
4723
  if (measureContent) {
4695
4724
  this.mustMeasureContent = false;
4696
4725
  this.contentDOMHeight = dom.clientHeight;
@@ -4716,11 +4745,9 @@ class ViewState {
4716
4745
  if (!this.inView)
4717
4746
  return 0;
4718
4747
  let contentWidth = dom.clientWidth;
4719
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4720
- this.editorWidth != view.scrollDOM.clientWidth) {
4748
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4721
4749
  this.contentDOMWidth = contentWidth;
4722
4750
  this.editorHeight = view.scrollDOM.clientHeight;
4723
- this.editorWidth = view.scrollDOM.clientWidth;
4724
4751
  result |= 8 /* Geometry */;
4725
4752
  }
4726
4753
  if (measureContent) {
@@ -4775,8 +4802,9 @@ class ViewState {
4775
4802
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4776
4803
  // If scrollTarget is given, make sure the viewport includes that position
4777
4804
  if (scrollTarget) {
4778
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4805
+ let { head } = scrollTarget.range;
4779
4806
  if (head < viewport.from || head > viewport.to) {
4807
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4780
4808
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4781
4809
  if (scrollTarget.y == "center")
4782
4810
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5162,6 +5190,10 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5162
5190
  overflow: "hidden",
5163
5191
  verticalAlign: "bottom"
5164
5192
  },
5193
+ ".cm-widgetBuffer": {
5194
+ verticalAlign: "text-bottom",
5195
+ height: "1em",
5196
+ },
5165
5197
  ".cm-placeholder": {
5166
5198
  color: "#888",
5167
5199
  display: "inline-block",
@@ -5410,7 +5442,7 @@ class DOMObserver {
5410
5442
  }
5411
5443
  // Throw away any pending changes
5412
5444
  clear() {
5413
- this.observer.takeRecords();
5445
+ this.processRecords();
5414
5446
  this.queue.length = 0;
5415
5447
  this.selectionChanged = false;
5416
5448
  }
@@ -5576,9 +5608,8 @@ function applyDOMChange(view, start, end, typeOver) {
5576
5608
  return;
5577
5609
  let { from, to } = bounds;
5578
5610
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5579
- let reader = new DOMReader(selPoints, view);
5611
+ let reader = new DOMReader(selPoints, view.state);
5580
5612
  reader.readRange(bounds.startDOM, bounds.endDOM);
5581
- newSel = selectionFromPoints(selPoints, from);
5582
5613
  let preferredPos = sel.from, preferredSide = null;
5583
5614
  // Prefer anchoring to end when Backspace is pressed (or, on
5584
5615
  // Android, when something was deleted)
@@ -5587,10 +5618,17 @@ function applyDOMChange(view, start, end, typeOver) {
5587
5618
  preferredPos = sel.to;
5588
5619
  preferredSide = "end";
5589
5620
  }
5590
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5591
- if (diff)
5621
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5622
+ if (diff) {
5623
+ // Chrome inserts two newlines when pressing shift-enter at the
5624
+ // end of a line. This drops one of those.
5625
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5626
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5627
+ diff.toB--;
5592
5628
  change = { from: from + diff.from, to: from + diff.toA,
5593
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5629
+ insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5630
+ }
5631
+ newSel = selectionFromPoints(selPoints, from);
5594
5632
  }
5595
5633
  else if (view.hasFocus || !view.state.facet(editable)) {
5596
5634
  let domSel = view.observer.selectionRange;
@@ -5869,9 +5907,17 @@ class EditorView {
5869
5907
  get inView() { return this.viewState.inView; }
5870
5908
  /**
5871
5909
  Indicates whether the user is currently composing text via
5872
- [IME](https://en.wikipedia.org/wiki/Input_method).
5910
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
5911
+ one change has been made in the current composition.
5873
5912
  */
5874
5913
  get composing() { return this.inputState.composing > 0; }
5914
+ /**
5915
+ Indicates whether the user is currently in composing state. Note
5916
+ that on some platforms, like Android, this will be the case a
5917
+ lot, since just putting the cursor on a word starts a
5918
+ composition there.
5919
+ */
5920
+ get compositionStarted() { return this.inputState.composing >= 0; }
5875
5921
  dispatch(...input) {
5876
5922
  this._dispatch(input.length == 1 && input[0] instanceof Transaction ? input[0]
5877
5923
  : this.state.update(...input));
@@ -5937,7 +5983,9 @@ class EditorView {
5937
5983
  finally {
5938
5984
  this.updateState = 0 /* Idle */;
5939
5985
  }
5940
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
5986
+ if (update.startState.facet(theme) != update.state.facet(theme))
5987
+ this.viewState.mustMeasureContent = true;
5988
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5941
5989
  this.requestMeasure();
5942
5990
  if (!update.empty)
5943
5991
  for (let listener of this.state.facet(updateListener))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.40",
3
+ "version": "0.19.44",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",