@codemirror/view 0.19.42 → 0.19.45

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,33 @@
1
+ ## 0.19.45 (2022-02-23)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the library failed to call `WidgetType.destroy` on the old widget when replacing a widget with a different widget of the same type.
6
+
7
+ Fix an issue where the editor would compute DOM positions inside composition contexts incorrectly in some cases, causing the selection to be put in the wrong place and needlessly interrupting compositions.
8
+
9
+ Fix leaking of resize event handlers.
10
+
11
+ ## 0.19.44 (2022-02-17)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix a crash that occasionally occurred when drag-selecting in a way that scrolled the editor.
16
+
17
+ ### New features
18
+
19
+ The new `EditorView.compositionStarted` property indicates whether a composition is starting.
20
+
21
+ ## 0.19.43 (2022-02-16)
22
+
23
+ ### Bug fixes
24
+
25
+ Fix several issues where editing or composition went wrong due to our zero-width space kludge characters ending up in unexpected places.
26
+
27
+ Make sure the editor re-measures its dimensions whenever its theme changes.
28
+
29
+ Fix an issue where some keys on Android phones could leave the editor DOM unsynced with the actual document.
30
+
1
31
  ## 0.19.42 (2022-02-05)
2
32
 
3
33
  ### Bug fixes
@@ -88,7 +118,7 @@ Fix an issue where backspacing out a selection on Chrome Android would sometimes
88
118
 
89
119
  ### Bug fixes
90
120
 
91
- Fix a bug where content line elements would in some cases lose their `cm-line` class. Move test to scrollIntoView
121
+ Fix a bug where content line elements would in some cases lose their `cm-line` class.
92
122
 
93
123
  ## 0.19.33 (2021-12-16)
94
124
 
package/dist/index.cjs CHANGED
@@ -781,6 +781,7 @@ class WidgetView extends ContentView {
781
781
  this.widget = widget;
782
782
  this.length = length;
783
783
  this.side = side;
784
+ this.prevWidget = null;
784
785
  }
785
786
  static create(widget, length, side) {
786
787
  return new (widget.customView || WidgetView)(widget, length, side);
@@ -792,6 +793,9 @@ class WidgetView extends ContentView {
792
793
  }
793
794
  sync() {
794
795
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
796
+ if (this.dom && this.prevWidget)
797
+ this.prevWidget.destroy(this.dom);
798
+ this.prevWidget = null;
795
799
  this.setDOM(this.widget.toDOM(this.editorView));
796
800
  this.dom.contentEditable = "false";
797
801
  }
@@ -809,6 +813,8 @@ class WidgetView extends ContentView {
809
813
  if (this.widget.constructor == other.widget.constructor) {
810
814
  if (!this.widget.eq(other.widget))
811
815
  this.markDirty(true);
816
+ if (this.dom && !this.prevWidget)
817
+ this.prevWidget = this.widget;
812
818
  this.widget = other.widget;
813
819
  return true;
814
820
  }
@@ -849,7 +855,12 @@ class WidgetView extends ContentView {
849
855
  }
850
856
  }
851
857
  class CompositionView extends WidgetView {
852
- domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
858
+ domAtPos(pos) {
859
+ let { topView } = this.widget;
860
+ if (!topView)
861
+ return new DOMPos(this.widget.text, Math.min(pos, this.widget.text.nodeValue.length));
862
+ return posInCompositionTree(pos, topView, this.widget.text);
863
+ }
853
864
  sync() { this.setDOM(this.widget.toDOM()); }
854
865
  localPosFromDOM(node, offset) {
855
866
  return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
@@ -857,11 +868,34 @@ class CompositionView extends WidgetView {
857
868
  ignoreMutation() { return false; }
858
869
  get overrideDOMText() { return null; }
859
870
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
871
+ destroy() {
872
+ var _a;
873
+ super.destroy();
874
+ (_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
875
+ }
860
876
  get isEditable() { return true; }
861
877
  }
862
- // Use two characters on Android, to prevent Chrome from closing the
863
- // virtual keyboard when backspacing after a widget (#602).
864
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
878
+ // Uses the old structure of a chunk of content view frozen for
879
+ // composition to try and find a reasonable DOM location for the given
880
+ // offset.
881
+ function posInCompositionTree(pos, view, text) {
882
+ if (view instanceof MarkView) {
883
+ for (let child of view.children) {
884
+ let hasComp = child.dom == text || child.dom.contains(text.parentNode);
885
+ let len = hasComp ? text.nodeValue.length : child.length;
886
+ if (pos < len || pos == len && child.getSide() <= 0)
887
+ return hasComp ? posInCompositionTree(pos, child, text) : child.domAtPos(pos);
888
+ pos -= len;
889
+ }
890
+ return view.domAtPos(view.length);
891
+ }
892
+ else if (view.dom == text) {
893
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
894
+ }
895
+ else {
896
+ return view.domAtPos(pos);
897
+ }
898
+ }
865
899
  // These are drawn around uneditable widgets to avoid a number of
866
900
  // browser bugs that show up when the cursor is directly next to
867
901
  // uneditable inline content.
@@ -877,21 +911,21 @@ class WidgetBufferView extends ContentView {
877
911
  }
878
912
  split() { return new WidgetBufferView(this.side); }
879
913
  sync() {
880
- if (!this.dom)
881
- this.setDOM(document.createTextNode(ZeroWidthSpace));
882
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
883
- this.dom.nodeValue = ZeroWidthSpace;
914
+ if (!this.dom) {
915
+ let dom = document.createElement("img");
916
+ dom.className = "cm-widgetBuffer";
917
+ this.setDOM(dom);
918
+ }
884
919
  }
885
920
  getSide() { return this.side; }
886
921
  domAtPos(pos) { return DOMPos.before(this.dom); }
887
922
  localPosFromDOM() { return 0; }
888
923
  domBoundsAround() { return null; }
889
924
  coordsAt(pos) {
890
- let rects = clientRectsFor(this.dom);
891
- return rects[rects.length - 1] || null;
925
+ return this.dom.getBoundingClientRect();
892
926
  }
893
927
  get overrideDOMText() {
894
- return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
928
+ return text.Text.empty;
895
929
  }
896
930
  }
897
931
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -938,7 +972,7 @@ function coordsInChildren(view, pos, side) {
938
972
  continue;
939
973
  flatten = side = -child.getSide();
940
974
  }
941
- let rect = child.coordsAt(pos - off, side);
975
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
942
976
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
943
977
  }
944
978
  off = end;
@@ -1391,6 +1425,7 @@ class BlockWidgetView extends ContentView {
1391
1425
  this.length = length;
1392
1426
  this.type = type;
1393
1427
  this.breakAfter = 0;
1428
+ this.prevWidget = null;
1394
1429
  }
1395
1430
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1396
1431
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1412,6 +1447,9 @@ class BlockWidgetView extends ContentView {
1412
1447
  get children() { return noChildren; }
1413
1448
  sync() {
1414
1449
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1450
+ if (this.dom && this.prevWidget)
1451
+ this.prevWidget.destroy(this.dom);
1452
+ this.prevWidget = null;
1415
1453
  this.setDOM(this.widget.toDOM(this.editorView));
1416
1454
  this.dom.contentEditable = "false";
1417
1455
  }
@@ -1425,6 +1463,8 @@ class BlockWidgetView extends ContentView {
1425
1463
  other.widget.constructor == this.widget.constructor) {
1426
1464
  if (!other.widget.eq(this.widget))
1427
1465
  this.markDirty(true);
1466
+ if (this.dom && !this.prevWidget)
1467
+ this.prevWidget = this.widget;
1428
1468
  this.widget = other.widget;
1429
1469
  this.length = other.length;
1430
1470
  this.breakAfter = other.breakAfter;
@@ -2853,13 +2893,19 @@ function computeCompositionDeco(view, changes) {
2853
2893
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2854
2894
  return Decoration.none;
2855
2895
  }
2856
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2896
+ let topView = ContentView.get(node);
2897
+ if (topView instanceof CompositionView)
2898
+ topView = topView.widget.topView;
2899
+ else if (topView)
2900
+ topView.parent = null;
2901
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode, topView) }).range(newFrom, newTo));
2857
2902
  }
2858
2903
  class CompositionWidget extends WidgetType {
2859
- constructor(top, text) {
2904
+ constructor(top, text, topView) {
2860
2905
  super();
2861
2906
  this.top = top;
2862
2907
  this.text = text;
2908
+ this.topView = topView;
2863
2909
  }
2864
2910
  eq(other) { return this.top == other.top && this.text == other.text; }
2865
2911
  toDOM() { return this.top; }
@@ -5199,6 +5245,11 @@ const baseTheme = buildTheme("." + baseThemeID, {
5199
5245
  overflow: "hidden",
5200
5246
  verticalAlign: "bottom"
5201
5247
  },
5248
+ ".cm-widgetBuffer": {
5249
+ verticalAlign: "text-top",
5250
+ height: "1em",
5251
+ display: "inline"
5252
+ },
5202
5253
  ".cm-placeholder": {
5203
5254
  color: "#888",
5204
5255
  display: "inline-block",
@@ -5306,19 +5357,16 @@ class DOMObserver {
5306
5357
  this.flushSoon();
5307
5358
  };
5308
5359
  this.onSelectionChange = this.onSelectionChange.bind(this);
5360
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5309
5361
  if (typeof ResizeObserver == "function") {
5310
5362
  this.resize = new ResizeObserver(() => {
5311
- if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5312
- this.resizeTimeout = setTimeout(() => {
5313
- this.resizeTimeout = -1;
5314
- this.view.requestMeasure();
5315
- }, 50);
5363
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5364
+ this.onResize();
5316
5365
  });
5317
5366
  this.resize.observe(view.scrollDOM);
5318
5367
  }
5319
5368
  this.start();
5320
- this.onScroll = this.onScroll.bind(this);
5321
- window.addEventListener("scroll", this.onScroll);
5369
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5322
5370
  if (typeof IntersectionObserver == "function") {
5323
5371
  this.intersection = new IntersectionObserver(entries => {
5324
5372
  if (this.parentCheck < 0)
@@ -5344,6 +5392,13 @@ class DOMObserver {
5344
5392
  this.flush(false);
5345
5393
  this.onScrollChanged(e);
5346
5394
  }
5395
+ onResize() {
5396
+ if (this.resizeTimeout < 0)
5397
+ this.resizeTimeout = setTimeout(() => {
5398
+ this.resizeTimeout = -1;
5399
+ this.view.requestMeasure();
5400
+ }, 50);
5401
+ }
5347
5402
  updateGaps(gaps) {
5348
5403
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5349
5404
  this.gapIntersection.disconnect();
@@ -5447,7 +5502,7 @@ class DOMObserver {
5447
5502
  }
5448
5503
  // Throw away any pending changes
5449
5504
  clear() {
5450
- this.observer.takeRecords();
5505
+ this.processRecords();
5451
5506
  this.queue.length = 0;
5452
5507
  this.selectionChanged = false;
5453
5508
  }
@@ -5560,6 +5615,7 @@ class DOMObserver {
5560
5615
  for (let dom of this.scrollTargets)
5561
5616
  dom.removeEventListener("scroll", this.onScroll);
5562
5617
  window.removeEventListener("scroll", this.onScroll);
5618
+ window.removeEventListener("resize", this.onResize);
5563
5619
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5564
5620
  clearTimeout(this.parentCheck);
5565
5621
  clearTimeout(this.resizeTimeout);
@@ -5625,23 +5681,11 @@ function applyDOMChange(view, start, end, typeOver) {
5625
5681
  }
5626
5682
  let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5627
5683
  if (diff) {
5628
- let orig = diff;
5629
5684
  // Chrome inserts two newlines when pressing shift-enter at the
5630
5685
  // end of a line. This drops one of those.
5631
5686
  if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5632
5687
  diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5633
5688
  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
- }
5645
5689
  change = { from: from + diff.from, to: from + diff.toA,
5646
5690
  insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5647
5691
  }
@@ -5891,7 +5935,6 @@ class EditorView {
5891
5935
  this.mountStyles();
5892
5936
  this.updateAttrs();
5893
5937
  this.updateState = 0 /* Idle */;
5894
- ensureGlobalHandler();
5895
5938
  this.requestMeasure();
5896
5939
  if (config.parent)
5897
5940
  config.parent.appendChild(this.dom);
@@ -5924,9 +5967,17 @@ class EditorView {
5924
5967
  get inView() { return this.viewState.inView; }
5925
5968
  /**
5926
5969
  Indicates whether the user is currently composing text via
5927
- [IME](https://en.wikipedia.org/wiki/Input_method).
5970
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
5971
+ one change has been made in the current composition.
5928
5972
  */
5929
5973
  get composing() { return this.inputState.composing > 0; }
5974
+ /**
5975
+ Indicates whether the user is currently in composing state. Note
5976
+ that on some platforms, like Android, this will be the case a
5977
+ lot, since just putting the cursor on a word starts a
5978
+ composition there.
5979
+ */
5980
+ get compositionStarted() { return this.inputState.composing >= 0; }
5930
5981
  dispatch(...input) {
5931
5982
  this._dispatch(input.length == 1 && input[0] instanceof state.Transaction ? input[0]
5932
5983
  : this.state.update(...input));
@@ -5992,7 +6043,9 @@ class EditorView {
5992
6043
  finally {
5993
6044
  this.updateState = 0 /* Idle */;
5994
6045
  }
5995
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
6046
+ if (update.startState.facet(theme) != update.state.facet(theme))
6047
+ this.viewState.mustMeasureContent = true;
6048
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5996
6049
  this.requestMeasure();
5997
6050
  if (!update.empty)
5998
6051
  for (let listener of this.state.facet(updateListener))
@@ -6718,22 +6771,6 @@ const MaxBidiLine = 4096;
6718
6771
  function ensureTop(given, view) {
6719
6772
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6720
6773
  }
6721
- let resizeDebounce = -1;
6722
- function ensureGlobalHandler() {
6723
- window.addEventListener("resize", () => {
6724
- if (resizeDebounce == -1)
6725
- resizeDebounce = setTimeout(handleResize, 50);
6726
- });
6727
- }
6728
- function handleResize() {
6729
- resizeDebounce = -1;
6730
- let found = document.querySelectorAll(".cm-content");
6731
- for (let i = 0; i < found.length; i++) {
6732
- let docView = ContentView.get(found[i]);
6733
- if (docView)
6734
- docView.editorView.requestMeasure();
6735
- }
6736
- }
6737
6774
  const BadMeasure = {};
6738
6775
  class CachedOrder {
6739
6776
  constructor(from, to, dir, order) {
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
@@ -778,6 +778,7 @@ class WidgetView extends ContentView {
778
778
  this.widget = widget;
779
779
  this.length = length;
780
780
  this.side = side;
781
+ this.prevWidget = null;
781
782
  }
782
783
  static create(widget, length, side) {
783
784
  return new (widget.customView || WidgetView)(widget, length, side);
@@ -789,6 +790,9 @@ class WidgetView extends ContentView {
789
790
  }
790
791
  sync() {
791
792
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
793
+ if (this.dom && this.prevWidget)
794
+ this.prevWidget.destroy(this.dom);
795
+ this.prevWidget = null;
792
796
  this.setDOM(this.widget.toDOM(this.editorView));
793
797
  this.dom.contentEditable = "false";
794
798
  }
@@ -806,6 +810,8 @@ class WidgetView extends ContentView {
806
810
  if (this.widget.constructor == other.widget.constructor) {
807
811
  if (!this.widget.eq(other.widget))
808
812
  this.markDirty(true);
813
+ if (this.dom && !this.prevWidget)
814
+ this.prevWidget = this.widget;
809
815
  this.widget = other.widget;
810
816
  return true;
811
817
  }
@@ -846,7 +852,12 @@ class WidgetView extends ContentView {
846
852
  }
847
853
  }
848
854
  class CompositionView extends WidgetView {
849
- domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
855
+ domAtPos(pos) {
856
+ let { topView } = this.widget;
857
+ if (!topView)
858
+ return new DOMPos(this.widget.text, Math.min(pos, this.widget.text.nodeValue.length));
859
+ return posInCompositionTree(pos, topView, this.widget.text);
860
+ }
850
861
  sync() { this.setDOM(this.widget.toDOM()); }
851
862
  localPosFromDOM(node, offset) {
852
863
  return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
@@ -854,11 +865,34 @@ class CompositionView extends WidgetView {
854
865
  ignoreMutation() { return false; }
855
866
  get overrideDOMText() { return null; }
856
867
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
868
+ destroy() {
869
+ var _a;
870
+ super.destroy();
871
+ (_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
872
+ }
857
873
  get isEditable() { return true; }
858
874
  }
859
- // Use two characters on Android, to prevent Chrome from closing the
860
- // virtual keyboard when backspacing after a widget (#602).
861
- const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
875
+ // Uses the old structure of a chunk of content view frozen for
876
+ // composition to try and find a reasonable DOM location for the given
877
+ // offset.
878
+ function posInCompositionTree(pos, view, text) {
879
+ if (view instanceof MarkView) {
880
+ for (let child of view.children) {
881
+ let hasComp = child.dom == text || child.dom.contains(text.parentNode);
882
+ let len = hasComp ? text.nodeValue.length : child.length;
883
+ if (pos < len || pos == len && child.getSide() <= 0)
884
+ return hasComp ? posInCompositionTree(pos, child, text) : child.domAtPos(pos);
885
+ pos -= len;
886
+ }
887
+ return view.domAtPos(view.length);
888
+ }
889
+ else if (view.dom == text) {
890
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
891
+ }
892
+ else {
893
+ return view.domAtPos(pos);
894
+ }
895
+ }
862
896
  // These are drawn around uneditable widgets to avoid a number of
863
897
  // browser bugs that show up when the cursor is directly next to
864
898
  // uneditable inline content.
@@ -874,21 +908,21 @@ class WidgetBufferView extends ContentView {
874
908
  }
875
909
  split() { return new WidgetBufferView(this.side); }
876
910
  sync() {
877
- if (!this.dom)
878
- this.setDOM(document.createTextNode(ZeroWidthSpace));
879
- else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
880
- this.dom.nodeValue = ZeroWidthSpace;
911
+ if (!this.dom) {
912
+ let dom = document.createElement("img");
913
+ dom.className = "cm-widgetBuffer";
914
+ this.setDOM(dom);
915
+ }
881
916
  }
882
917
  getSide() { return this.side; }
883
918
  domAtPos(pos) { return DOMPos.before(this.dom); }
884
919
  localPosFromDOM() { return 0; }
885
920
  domBoundsAround() { return null; }
886
921
  coordsAt(pos) {
887
- let rects = clientRectsFor(this.dom);
888
- return rects[rects.length - 1] || null;
922
+ return this.dom.getBoundingClientRect();
889
923
  }
890
924
  get overrideDOMText() {
891
- return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
925
+ return Text.empty;
892
926
  }
893
927
  }
894
928
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
@@ -935,7 +969,7 @@ function coordsInChildren(view, pos, side) {
935
969
  continue;
936
970
  flatten = side = -child.getSide();
937
971
  }
938
- let rect = child.coordsAt(pos - off, side);
972
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
939
973
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
940
974
  }
941
975
  off = end;
@@ -1387,6 +1421,7 @@ class BlockWidgetView extends ContentView {
1387
1421
  this.length = length;
1388
1422
  this.type = type;
1389
1423
  this.breakAfter = 0;
1424
+ this.prevWidget = null;
1390
1425
  }
1391
1426
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1392
1427
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1408,6 +1443,9 @@ class BlockWidgetView extends ContentView {
1408
1443
  get children() { return noChildren; }
1409
1444
  sync() {
1410
1445
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1446
+ if (this.dom && this.prevWidget)
1447
+ this.prevWidget.destroy(this.dom);
1448
+ this.prevWidget = null;
1411
1449
  this.setDOM(this.widget.toDOM(this.editorView));
1412
1450
  this.dom.contentEditable = "false";
1413
1451
  }
@@ -1421,6 +1459,8 @@ class BlockWidgetView extends ContentView {
1421
1459
  other.widget.constructor == this.widget.constructor) {
1422
1460
  if (!other.widget.eq(this.widget))
1423
1461
  this.markDirty(true);
1462
+ if (this.dom && !this.prevWidget)
1463
+ this.prevWidget = this.widget;
1424
1464
  this.widget = other.widget;
1425
1465
  this.length = other.length;
1426
1466
  this.breakAfter = other.breakAfter;
@@ -2848,13 +2888,19 @@ function computeCompositionDeco(view, changes) {
2848
2888
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2849
2889
  return Decoration.none;
2850
2890
  }
2851
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2891
+ let topView = ContentView.get(node);
2892
+ if (topView instanceof CompositionView)
2893
+ topView = topView.widget.topView;
2894
+ else if (topView)
2895
+ topView.parent = null;
2896
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode, topView) }).range(newFrom, newTo));
2852
2897
  }
2853
2898
  class CompositionWidget extends WidgetType {
2854
- constructor(top, text) {
2899
+ constructor(top, text, topView) {
2855
2900
  super();
2856
2901
  this.top = top;
2857
2902
  this.text = text;
2903
+ this.topView = topView;
2858
2904
  }
2859
2905
  eq(other) { return this.top == other.top && this.text == other.text; }
2860
2906
  toDOM() { return this.top; }
@@ -5193,6 +5239,11 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5193
5239
  overflow: "hidden",
5194
5240
  verticalAlign: "bottom"
5195
5241
  },
5242
+ ".cm-widgetBuffer": {
5243
+ verticalAlign: "text-top",
5244
+ height: "1em",
5245
+ display: "inline"
5246
+ },
5196
5247
  ".cm-placeholder": {
5197
5248
  color: "#888",
5198
5249
  display: "inline-block",
@@ -5300,19 +5351,16 @@ class DOMObserver {
5300
5351
  this.flushSoon();
5301
5352
  };
5302
5353
  this.onSelectionChange = this.onSelectionChange.bind(this);
5354
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5303
5355
  if (typeof ResizeObserver == "function") {
5304
5356
  this.resize = new ResizeObserver(() => {
5305
- if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5306
- this.resizeTimeout = setTimeout(() => {
5307
- this.resizeTimeout = -1;
5308
- this.view.requestMeasure();
5309
- }, 50);
5357
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5358
+ this.onResize();
5310
5359
  });
5311
5360
  this.resize.observe(view.scrollDOM);
5312
5361
  }
5313
5362
  this.start();
5314
- this.onScroll = this.onScroll.bind(this);
5315
- window.addEventListener("scroll", this.onScroll);
5363
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5316
5364
  if (typeof IntersectionObserver == "function") {
5317
5365
  this.intersection = new IntersectionObserver(entries => {
5318
5366
  if (this.parentCheck < 0)
@@ -5338,6 +5386,13 @@ class DOMObserver {
5338
5386
  this.flush(false);
5339
5387
  this.onScrollChanged(e);
5340
5388
  }
5389
+ onResize() {
5390
+ if (this.resizeTimeout < 0)
5391
+ this.resizeTimeout = setTimeout(() => {
5392
+ this.resizeTimeout = -1;
5393
+ this.view.requestMeasure();
5394
+ }, 50);
5395
+ }
5341
5396
  updateGaps(gaps) {
5342
5397
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5343
5398
  this.gapIntersection.disconnect();
@@ -5441,7 +5496,7 @@ class DOMObserver {
5441
5496
  }
5442
5497
  // Throw away any pending changes
5443
5498
  clear() {
5444
- this.observer.takeRecords();
5499
+ this.processRecords();
5445
5500
  this.queue.length = 0;
5446
5501
  this.selectionChanged = false;
5447
5502
  }
@@ -5554,6 +5609,7 @@ class DOMObserver {
5554
5609
  for (let dom of this.scrollTargets)
5555
5610
  dom.removeEventListener("scroll", this.onScroll);
5556
5611
  window.removeEventListener("scroll", this.onScroll);
5612
+ window.removeEventListener("resize", this.onResize);
5557
5613
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5558
5614
  clearTimeout(this.parentCheck);
5559
5615
  clearTimeout(this.resizeTimeout);
@@ -5619,23 +5675,11 @@ function applyDOMChange(view, start, end, typeOver) {
5619
5675
  }
5620
5676
  let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5621
5677
  if (diff) {
5622
- let orig = diff;
5623
5678
  // Chrome inserts two newlines when pressing shift-enter at the
5624
5679
  // end of a line. This drops one of those.
5625
5680
  if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5626
5681
  diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5627
5682
  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
- }
5639
5683
  change = { from: from + diff.from, to: from + diff.toA,
5640
5684
  insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5641
5685
  }
@@ -5885,7 +5929,6 @@ class EditorView {
5885
5929
  this.mountStyles();
5886
5930
  this.updateAttrs();
5887
5931
  this.updateState = 0 /* Idle */;
5888
- ensureGlobalHandler();
5889
5932
  this.requestMeasure();
5890
5933
  if (config.parent)
5891
5934
  config.parent.appendChild(this.dom);
@@ -5918,9 +5961,17 @@ class EditorView {
5918
5961
  get inView() { return this.viewState.inView; }
5919
5962
  /**
5920
5963
  Indicates whether the user is currently composing text via
5921
- [IME](https://en.wikipedia.org/wiki/Input_method).
5964
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
5965
+ one change has been made in the current composition.
5922
5966
  */
5923
5967
  get composing() { return this.inputState.composing > 0; }
5968
+ /**
5969
+ Indicates whether the user is currently in composing state. Note
5970
+ that on some platforms, like Android, this will be the case a
5971
+ lot, since just putting the cursor on a word starts a
5972
+ composition there.
5973
+ */
5974
+ get compositionStarted() { return this.inputState.composing >= 0; }
5924
5975
  dispatch(...input) {
5925
5976
  this._dispatch(input.length == 1 && input[0] instanceof Transaction ? input[0]
5926
5977
  : this.state.update(...input));
@@ -5986,7 +6037,9 @@ class EditorView {
5986
6037
  finally {
5987
6038
  this.updateState = 0 /* Idle */;
5988
6039
  }
5989
- if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc)
6040
+ if (update.startState.facet(theme) != update.state.facet(theme))
6041
+ this.viewState.mustMeasureContent = true;
6042
+ if (redrawn || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
5990
6043
  this.requestMeasure();
5991
6044
  if (!update.empty)
5992
6045
  for (let listener of this.state.facet(updateListener))
@@ -6712,22 +6765,6 @@ const MaxBidiLine = 4096;
6712
6765
  function ensureTop(given, view) {
6713
6766
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6714
6767
  }
6715
- let resizeDebounce = -1;
6716
- function ensureGlobalHandler() {
6717
- window.addEventListener("resize", () => {
6718
- if (resizeDebounce == -1)
6719
- resizeDebounce = setTimeout(handleResize, 50);
6720
- });
6721
- }
6722
- function handleResize() {
6723
- resizeDebounce = -1;
6724
- let found = document.querySelectorAll(".cm-content");
6725
- for (let i = 0; i < found.length; i++) {
6726
- let docView = ContentView.get(found[i]);
6727
- if (docView)
6728
- docView.editorView.requestMeasure();
6729
- }
6730
- }
6731
6768
  const BadMeasure = {};
6732
6769
  class CachedOrder {
6733
6770
  constructor(from, to, dir, order) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.42",
3
+ "version": "0.19.45",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",