@codemirror/view 0.19.43 → 0.19.46

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.46 (2022-03-03)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug where block widgets on the edges of viewports could cause the positioning of content to misalign with the gutter and height computations.
6
+
7
+ Improve cursor height next to widgets.
8
+
9
+ Fix a bug where mapping positions to screen coordinates could return incorred coordinates during composition.
10
+
11
+ ## 0.19.45 (2022-02-23)
12
+
13
+ ### Bug fixes
14
+
15
+ 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.
16
+
17
+ 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.
18
+
19
+ Fix leaking of resize event handlers.
20
+
21
+ ## 0.19.44 (2022-02-17)
22
+
23
+ ### Bug fixes
24
+
25
+ Fix a crash that occasionally occurred when drag-selecting in a way that scrolled the editor.
26
+
27
+ ### New features
28
+
29
+ The new `EditorView.compositionStarted` property indicates whether a composition is starting.
30
+
1
31
  ## 0.19.43 (2022-02-16)
2
32
 
3
33
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -22,7 +22,7 @@ function getSelection(root) {
22
22
  return target.getSelection();
23
23
  }
24
24
  function contains(dom, node) {
25
- return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
25
+ return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
26
26
  }
27
27
  function deepActiveElement() {
28
28
  let elt = document.activeElement;
@@ -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,16 +855,69 @@ 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, text } = this.widget;
860
+ if (!topView)
861
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
862
+ return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
863
+ }
853
864
  sync() { this.setDOM(this.widget.toDOM()); }
854
865
  localPosFromDOM(node, offset) {
855
- return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
866
+ let { topView, text } = this.widget;
867
+ if (!topView)
868
+ return Math.min(offset, this.length);
869
+ return posFromDOMInCompositionTree(node, offset, topView, text);
856
870
  }
857
871
  ignoreMutation() { return false; }
858
872
  get overrideDOMText() { return null; }
859
- coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
873
+ coordsAt(pos, side) {
874
+ let { topView, text } = this.widget;
875
+ if (!topView)
876
+ return textCoords(text, pos, side);
877
+ return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
878
+ }
879
+ destroy() {
880
+ var _a;
881
+ super.destroy();
882
+ (_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
883
+ }
860
884
  get isEditable() { return true; }
861
885
  }
886
+ // Uses the old structure of a chunk of content view frozen for
887
+ // composition to try and find a reasonable DOM location for the given
888
+ // offset.
889
+ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
890
+ if (view instanceof MarkView) {
891
+ for (let child of view.children) {
892
+ let hasComp = contains(child.dom, text);
893
+ let len = hasComp ? text.nodeValue.length : child.length;
894
+ if (pos < len || pos == len && child.getSide() <= 0)
895
+ return hasComp ? scanCompositionTree(pos, side, child, text, enterView, fromText) : enterView(child, pos, side);
896
+ pos -= len;
897
+ }
898
+ return enterView(view, view.length, -1);
899
+ }
900
+ else if (view.dom == text) {
901
+ return fromText(pos, side);
902
+ }
903
+ else {
904
+ return enterView(view, pos, side);
905
+ }
906
+ }
907
+ function posFromDOMInCompositionTree(node, offset, view, text) {
908
+ if (view instanceof MarkView) {
909
+ for (let child of view.children) {
910
+ let pos = 0, hasComp = contains(child.dom, text);
911
+ if (contains(child.dom, node))
912
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
913
+ pos += hasComp ? text.nodeValue.length : child.length;
914
+ }
915
+ }
916
+ else if (view.dom == text) {
917
+ return Math.min(offset, text.nodeValue.length);
918
+ }
919
+ return view.localPosFromDOM(node, offset);
920
+ }
862
921
  // These are drawn around uneditable widgets to avoid a number of
863
922
  // browser bugs that show up when the cursor is directly next to
864
923
  // uneditable inline content.
@@ -885,13 +944,43 @@ class WidgetBufferView extends ContentView {
885
944
  localPosFromDOM() { return 0; }
886
945
  domBoundsAround() { return null; }
887
946
  coordsAt(pos) {
888
- return this.dom.getBoundingClientRect();
947
+ let imgRect = this.dom.getBoundingClientRect();
948
+ // Since the <img> height doesn't correspond to text height, try
949
+ // to borrow the height from some sibling node.
950
+ let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
951
+ return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
952
+ ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
889
953
  }
890
954
  get overrideDOMText() {
891
955
  return text.Text.empty;
892
956
  }
893
957
  }
894
958
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
959
+ function inlineSiblingRect(view, side) {
960
+ let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
961
+ while (parent && index >= 0) {
962
+ if (side < 0 ? index > 0 : index < parent.children.length) {
963
+ let next = parent.children[index + side];
964
+ if (next instanceof TextView) {
965
+ let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
966
+ if (nextRect)
967
+ return nextRect;
968
+ }
969
+ index += side;
970
+ }
971
+ else if (parent instanceof MarkView && parent.parent) {
972
+ index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
973
+ parent = parent.parent;
974
+ }
975
+ else {
976
+ let last = parent.dom.lastChild;
977
+ if (last && last.nodeName == "BR")
978
+ return last.getClientRects()[0];
979
+ break;
980
+ }
981
+ }
982
+ return undefined;
983
+ }
895
984
  function inlineDOMAtPos(dom, children, pos) {
896
985
  let i = 0;
897
986
  for (let off = 0; i < children.length; i++) {
@@ -935,7 +1024,7 @@ function coordsInChildren(view, pos, side) {
935
1024
  continue;
936
1025
  flatten = side = -child.getSide();
937
1026
  }
938
- let rect = child.coordsAt(pos - off, side);
1027
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
939
1028
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
940
1029
  }
941
1030
  off = end;
@@ -1117,7 +1206,7 @@ class Decoration extends rangeset.RangeValue {
1117
1206
  */
1118
1207
  static widget(spec) {
1119
1208
  let side = spec.side || 0, block = !!spec.block;
1120
- side += block ? (side > 0 ? 300000000 /* BlockAfter */ : -400000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1209
+ side += block ? (side > 0 ? 400000000 /* BlockAfter */ : -500000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1121
1210
  return new PointDecoration(spec, side, side, block, spec.widget || null, false);
1122
1211
  }
1123
1212
  /**
@@ -1125,10 +1214,16 @@ class Decoration extends rangeset.RangeValue {
1125
1214
  a widget, or simply hides it.
1126
1215
  */
1127
1216
  static replace(spec) {
1128
- let block = !!spec.block;
1129
- let { start, end } = getInclusive(spec, block);
1130
- let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1131
- let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1217
+ let block = !!spec.block, startSide, endSide;
1218
+ if (spec.isBlockGap) {
1219
+ startSide = -400000000 /* GapStart */;
1220
+ endSide = 300000000 /* GapEnd */;
1221
+ }
1222
+ else {
1223
+ let { start, end } = getInclusive(spec, block);
1224
+ startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 500000000 /* NonIncStart */) - 1;
1225
+ endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -600000000 /* NonIncEnd */) + 1;
1226
+ }
1132
1227
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1133
1228
  }
1134
1229
  /**
@@ -1158,7 +1253,7 @@ Decoration.none = rangeset.RangeSet.empty;
1158
1253
  class MarkDecoration extends Decoration {
1159
1254
  constructor(spec) {
1160
1255
  let { start, end } = getInclusive(spec);
1161
- super(start ? -1 /* InlineIncStart */ : 400000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -500000000 /* NonIncEnd */, null, spec);
1256
+ super(start ? -1 /* InlineIncStart */ : 500000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -600000000 /* NonIncEnd */, null, spec);
1162
1257
  this.tagName = spec.tagName || "span";
1163
1258
  this.class = spec.class || "";
1164
1259
  this.attrs = spec.attributes || null;
@@ -1388,6 +1483,7 @@ class BlockWidgetView extends ContentView {
1388
1483
  this.length = length;
1389
1484
  this.type = type;
1390
1485
  this.breakAfter = 0;
1486
+ this.prevWidget = null;
1391
1487
  }
1392
1488
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1393
1489
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1409,6 +1505,9 @@ class BlockWidgetView extends ContentView {
1409
1505
  get children() { return noChildren; }
1410
1506
  sync() {
1411
1507
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1508
+ if (this.dom && this.prevWidget)
1509
+ this.prevWidget.destroy(this.dom);
1510
+ this.prevWidget = null;
1412
1511
  this.setDOM(this.widget.toDOM(this.editorView));
1413
1512
  this.dom.contentEditable = "false";
1414
1513
  }
@@ -1422,6 +1521,8 @@ class BlockWidgetView extends ContentView {
1422
1521
  other.widget.constructor == this.widget.constructor) {
1423
1522
  if (!other.widget.eq(this.widget))
1424
1523
  this.markDirty(true);
1524
+ if (this.dom && !this.prevWidget)
1525
+ this.prevWidget = this.widget;
1425
1526
  this.widget = other.widget;
1426
1527
  this.length = other.length;
1427
1528
  this.breakAfter = other.breakAfter;
@@ -2731,7 +2832,12 @@ class DocView extends ContentView {
2731
2832
  let end = next ? next.from - 1 : this.length;
2732
2833
  if (end > pos) {
2733
2834
  let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2734
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2835
+ deco.push(Decoration.replace({
2836
+ widget: new BlockGapWidget(height),
2837
+ block: true,
2838
+ inclusive: true,
2839
+ isBlockGap: true,
2840
+ }).range(pos, end));
2735
2841
  }
2736
2842
  if (!next)
2737
2843
  break;
@@ -2850,13 +2956,19 @@ function computeCompositionDeco(view, changes) {
2850
2956
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2851
2957
  return Decoration.none;
2852
2958
  }
2853
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2959
+ let topView = ContentView.get(node);
2960
+ if (topView instanceof CompositionView)
2961
+ topView = topView.widget.topView;
2962
+ else if (topView)
2963
+ topView.parent = null;
2964
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode, topView) }).range(newFrom, newTo));
2854
2965
  }
2855
2966
  class CompositionWidget extends WidgetType {
2856
- constructor(top, text) {
2967
+ constructor(top, text, topView) {
2857
2968
  super();
2858
2969
  this.top = top;
2859
2970
  this.text = text;
2971
+ this.topView = topView;
2860
2972
  }
2861
2973
  eq(other) { return this.top == other.top && this.text == other.text; }
2862
2974
  toDOM() { return this.top; }
@@ -5197,8 +5309,9 @@ const baseTheme = buildTheme("." + baseThemeID, {
5197
5309
  verticalAlign: "bottom"
5198
5310
  },
5199
5311
  ".cm-widgetBuffer": {
5200
- verticalAlign: "text-bottom",
5312
+ verticalAlign: "text-top",
5201
5313
  height: "1em",
5314
+ display: "inline"
5202
5315
  },
5203
5316
  ".cm-placeholder": {
5204
5317
  color: "#888",
@@ -5307,19 +5420,16 @@ class DOMObserver {
5307
5420
  this.flushSoon();
5308
5421
  };
5309
5422
  this.onSelectionChange = this.onSelectionChange.bind(this);
5423
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5310
5424
  if (typeof ResizeObserver == "function") {
5311
5425
  this.resize = new ResizeObserver(() => {
5312
- if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5313
- this.resizeTimeout = setTimeout(() => {
5314
- this.resizeTimeout = -1;
5315
- this.view.requestMeasure();
5316
- }, 50);
5426
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5427
+ this.onResize();
5317
5428
  });
5318
5429
  this.resize.observe(view.scrollDOM);
5319
5430
  }
5320
5431
  this.start();
5321
- this.onScroll = this.onScroll.bind(this);
5322
- window.addEventListener("scroll", this.onScroll);
5432
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5323
5433
  if (typeof IntersectionObserver == "function") {
5324
5434
  this.intersection = new IntersectionObserver(entries => {
5325
5435
  if (this.parentCheck < 0)
@@ -5345,6 +5455,13 @@ class DOMObserver {
5345
5455
  this.flush(false);
5346
5456
  this.onScrollChanged(e);
5347
5457
  }
5458
+ onResize() {
5459
+ if (this.resizeTimeout < 0)
5460
+ this.resizeTimeout = setTimeout(() => {
5461
+ this.resizeTimeout = -1;
5462
+ this.view.requestMeasure();
5463
+ }, 50);
5464
+ }
5348
5465
  updateGaps(gaps) {
5349
5466
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5350
5467
  this.gapIntersection.disconnect();
@@ -5561,6 +5678,7 @@ class DOMObserver {
5561
5678
  for (let dom of this.scrollTargets)
5562
5679
  dom.removeEventListener("scroll", this.onScroll);
5563
5680
  window.removeEventListener("scroll", this.onScroll);
5681
+ window.removeEventListener("resize", this.onResize);
5564
5682
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5565
5683
  clearTimeout(this.parentCheck);
5566
5684
  clearTimeout(this.resizeTimeout);
@@ -5880,7 +5998,6 @@ class EditorView {
5880
5998
  this.mountStyles();
5881
5999
  this.updateAttrs();
5882
6000
  this.updateState = 0 /* Idle */;
5883
- ensureGlobalHandler();
5884
6001
  this.requestMeasure();
5885
6002
  if (config.parent)
5886
6003
  config.parent.appendChild(this.dom);
@@ -5913,9 +6030,17 @@ class EditorView {
5913
6030
  get inView() { return this.viewState.inView; }
5914
6031
  /**
5915
6032
  Indicates whether the user is currently composing text via
5916
- [IME](https://en.wikipedia.org/wiki/Input_method).
6033
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
6034
+ one change has been made in the current composition.
5917
6035
  */
5918
6036
  get composing() { return this.inputState.composing > 0; }
6037
+ /**
6038
+ Indicates whether the user is currently in composing state. Note
6039
+ that on some platforms, like Android, this will be the case a
6040
+ lot, since just putting the cursor on a word starts a
6041
+ composition there.
6042
+ */
6043
+ get compositionStarted() { return this.inputState.composing >= 0; }
5919
6044
  dispatch(...input) {
5920
6045
  this._dispatch(input.length == 1 && input[0] instanceof state.Transaction ? input[0]
5921
6046
  : this.state.update(...input));
@@ -6709,22 +6834,6 @@ const MaxBidiLine = 4096;
6709
6834
  function ensureTop(given, view) {
6710
6835
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6711
6836
  }
6712
- let resizeDebounce = -1;
6713
- function ensureGlobalHandler() {
6714
- window.addEventListener("resize", () => {
6715
- if (resizeDebounce == -1)
6716
- resizeDebounce = setTimeout(handleResize, 50);
6717
- });
6718
- }
6719
- function handleResize() {
6720
- resizeDebounce = -1;
6721
- let found = document.querySelectorAll(".cm-content");
6722
- for (let i = 0; i < found.length; i++) {
6723
- let docView = ContentView.get(found[i]);
6724
- if (docView)
6725
- docView.editorView.requestMeasure();
6726
- }
6727
- }
6728
6837
  const BadMeasure = {};
6729
6838
  class CachedOrder {
6730
6839
  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
@@ -19,7 +19,7 @@ function getSelection(root) {
19
19
  return target.getSelection();
20
20
  }
21
21
  function contains(dom, node) {
22
- return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
22
+ return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
23
23
  }
24
24
  function deepActiveElement() {
25
25
  let elt = document.activeElement;
@@ -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,16 +852,69 @@ 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, text } = this.widget;
857
+ if (!topView)
858
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
859
+ return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
860
+ }
850
861
  sync() { this.setDOM(this.widget.toDOM()); }
851
862
  localPosFromDOM(node, offset) {
852
- return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
863
+ let { topView, text } = this.widget;
864
+ if (!topView)
865
+ return Math.min(offset, this.length);
866
+ return posFromDOMInCompositionTree(node, offset, topView, text);
853
867
  }
854
868
  ignoreMutation() { return false; }
855
869
  get overrideDOMText() { return null; }
856
- coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
870
+ coordsAt(pos, side) {
871
+ let { topView, text } = this.widget;
872
+ if (!topView)
873
+ return textCoords(text, pos, side);
874
+ return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
875
+ }
876
+ destroy() {
877
+ var _a;
878
+ super.destroy();
879
+ (_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
880
+ }
857
881
  get isEditable() { return true; }
858
882
  }
883
+ // Uses the old structure of a chunk of content view frozen for
884
+ // composition to try and find a reasonable DOM location for the given
885
+ // offset.
886
+ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
887
+ if (view instanceof MarkView) {
888
+ for (let child of view.children) {
889
+ let hasComp = contains(child.dom, text);
890
+ let len = hasComp ? text.nodeValue.length : child.length;
891
+ if (pos < len || pos == len && child.getSide() <= 0)
892
+ return hasComp ? scanCompositionTree(pos, side, child, text, enterView, fromText) : enterView(child, pos, side);
893
+ pos -= len;
894
+ }
895
+ return enterView(view, view.length, -1);
896
+ }
897
+ else if (view.dom == text) {
898
+ return fromText(pos, side);
899
+ }
900
+ else {
901
+ return enterView(view, pos, side);
902
+ }
903
+ }
904
+ function posFromDOMInCompositionTree(node, offset, view, text) {
905
+ if (view instanceof MarkView) {
906
+ for (let child of view.children) {
907
+ let pos = 0, hasComp = contains(child.dom, text);
908
+ if (contains(child.dom, node))
909
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
910
+ pos += hasComp ? text.nodeValue.length : child.length;
911
+ }
912
+ }
913
+ else if (view.dom == text) {
914
+ return Math.min(offset, text.nodeValue.length);
915
+ }
916
+ return view.localPosFromDOM(node, offset);
917
+ }
859
918
  // These are drawn around uneditable widgets to avoid a number of
860
919
  // browser bugs that show up when the cursor is directly next to
861
920
  // uneditable inline content.
@@ -882,13 +941,43 @@ class WidgetBufferView extends ContentView {
882
941
  localPosFromDOM() { return 0; }
883
942
  domBoundsAround() { return null; }
884
943
  coordsAt(pos) {
885
- return this.dom.getBoundingClientRect();
944
+ let imgRect = this.dom.getBoundingClientRect();
945
+ // Since the <img> height doesn't correspond to text height, try
946
+ // to borrow the height from some sibling node.
947
+ let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
948
+ return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
949
+ ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
886
950
  }
887
951
  get overrideDOMText() {
888
952
  return Text.empty;
889
953
  }
890
954
  }
891
955
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
956
+ function inlineSiblingRect(view, side) {
957
+ let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
958
+ while (parent && index >= 0) {
959
+ if (side < 0 ? index > 0 : index < parent.children.length) {
960
+ let next = parent.children[index + side];
961
+ if (next instanceof TextView) {
962
+ let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
963
+ if (nextRect)
964
+ return nextRect;
965
+ }
966
+ index += side;
967
+ }
968
+ else if (parent instanceof MarkView && parent.parent) {
969
+ index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
970
+ parent = parent.parent;
971
+ }
972
+ else {
973
+ let last = parent.dom.lastChild;
974
+ if (last && last.nodeName == "BR")
975
+ return last.getClientRects()[0];
976
+ break;
977
+ }
978
+ }
979
+ return undefined;
980
+ }
892
981
  function inlineDOMAtPos(dom, children, pos) {
893
982
  let i = 0;
894
983
  for (let off = 0; i < children.length; i++) {
@@ -932,7 +1021,7 @@ function coordsInChildren(view, pos, side) {
932
1021
  continue;
933
1022
  flatten = side = -child.getSide();
934
1023
  }
935
- let rect = child.coordsAt(pos - off, side);
1024
+ let rect = child.coordsAt(Math.max(0, pos - off), side);
936
1025
  return flatten && rect ? flattenRect(rect, side < 0) : rect;
937
1026
  }
938
1027
  off = end;
@@ -1113,7 +1202,7 @@ class Decoration extends RangeValue {
1113
1202
  */
1114
1203
  static widget(spec) {
1115
1204
  let side = spec.side || 0, block = !!spec.block;
1116
- side += block ? (side > 0 ? 300000000 /* BlockAfter */ : -400000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1205
+ side += block ? (side > 0 ? 400000000 /* BlockAfter */ : -500000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1117
1206
  return new PointDecoration(spec, side, side, block, spec.widget || null, false);
1118
1207
  }
1119
1208
  /**
@@ -1121,10 +1210,16 @@ class Decoration extends RangeValue {
1121
1210
  a widget, or simply hides it.
1122
1211
  */
1123
1212
  static replace(spec) {
1124
- let block = !!spec.block;
1125
- let { start, end } = getInclusive(spec, block);
1126
- let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1127
- let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1213
+ let block = !!spec.block, startSide, endSide;
1214
+ if (spec.isBlockGap) {
1215
+ startSide = -400000000 /* GapStart */;
1216
+ endSide = 300000000 /* GapEnd */;
1217
+ }
1218
+ else {
1219
+ let { start, end } = getInclusive(spec, block);
1220
+ startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 500000000 /* NonIncStart */) - 1;
1221
+ endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -600000000 /* NonIncEnd */) + 1;
1222
+ }
1128
1223
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1129
1224
  }
1130
1225
  /**
@@ -1154,7 +1249,7 @@ Decoration.none = RangeSet.empty;
1154
1249
  class MarkDecoration extends Decoration {
1155
1250
  constructor(spec) {
1156
1251
  let { start, end } = getInclusive(spec);
1157
- super(start ? -1 /* InlineIncStart */ : 400000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -500000000 /* NonIncEnd */, null, spec);
1252
+ super(start ? -1 /* InlineIncStart */ : 500000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -600000000 /* NonIncEnd */, null, spec);
1158
1253
  this.tagName = spec.tagName || "span";
1159
1254
  this.class = spec.class || "";
1160
1255
  this.attrs = spec.attributes || null;
@@ -1384,6 +1479,7 @@ class BlockWidgetView extends ContentView {
1384
1479
  this.length = length;
1385
1480
  this.type = type;
1386
1481
  this.breakAfter = 0;
1482
+ this.prevWidget = null;
1387
1483
  }
1388
1484
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1389
1485
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1405,6 +1501,9 @@ class BlockWidgetView extends ContentView {
1405
1501
  get children() { return noChildren; }
1406
1502
  sync() {
1407
1503
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1504
+ if (this.dom && this.prevWidget)
1505
+ this.prevWidget.destroy(this.dom);
1506
+ this.prevWidget = null;
1408
1507
  this.setDOM(this.widget.toDOM(this.editorView));
1409
1508
  this.dom.contentEditable = "false";
1410
1509
  }
@@ -1418,6 +1517,8 @@ class BlockWidgetView extends ContentView {
1418
1517
  other.widget.constructor == this.widget.constructor) {
1419
1518
  if (!other.widget.eq(this.widget))
1420
1519
  this.markDirty(true);
1520
+ if (this.dom && !this.prevWidget)
1521
+ this.prevWidget = this.widget;
1421
1522
  this.widget = other.widget;
1422
1523
  this.length = other.length;
1423
1524
  this.breakAfter = other.breakAfter;
@@ -2726,7 +2827,12 @@ class DocView extends ContentView {
2726
2827
  let end = next ? next.from - 1 : this.length;
2727
2828
  if (end > pos) {
2728
2829
  let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2729
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2830
+ deco.push(Decoration.replace({
2831
+ widget: new BlockGapWidget(height),
2832
+ block: true,
2833
+ inclusive: true,
2834
+ isBlockGap: true,
2835
+ }).range(pos, end));
2730
2836
  }
2731
2837
  if (!next)
2732
2838
  break;
@@ -2845,13 +2951,19 @@ function computeCompositionDeco(view, changes) {
2845
2951
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2846
2952
  return Decoration.none;
2847
2953
  }
2848
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
2954
+ let topView = ContentView.get(node);
2955
+ if (topView instanceof CompositionView)
2956
+ topView = topView.widget.topView;
2957
+ else if (topView)
2958
+ topView.parent = null;
2959
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode, topView) }).range(newFrom, newTo));
2849
2960
  }
2850
2961
  class CompositionWidget extends WidgetType {
2851
- constructor(top, text) {
2962
+ constructor(top, text, topView) {
2852
2963
  super();
2853
2964
  this.top = top;
2854
2965
  this.text = text;
2966
+ this.topView = topView;
2855
2967
  }
2856
2968
  eq(other) { return this.top == other.top && this.text == other.text; }
2857
2969
  toDOM() { return this.top; }
@@ -5191,8 +5303,9 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5191
5303
  verticalAlign: "bottom"
5192
5304
  },
5193
5305
  ".cm-widgetBuffer": {
5194
- verticalAlign: "text-bottom",
5306
+ verticalAlign: "text-top",
5195
5307
  height: "1em",
5308
+ display: "inline"
5196
5309
  },
5197
5310
  ".cm-placeholder": {
5198
5311
  color: "#888",
@@ -5301,19 +5414,16 @@ class DOMObserver {
5301
5414
  this.flushSoon();
5302
5415
  };
5303
5416
  this.onSelectionChange = this.onSelectionChange.bind(this);
5417
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5304
5418
  if (typeof ResizeObserver == "function") {
5305
5419
  this.resize = new ResizeObserver(() => {
5306
- if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5307
- this.resizeTimeout = setTimeout(() => {
5308
- this.resizeTimeout = -1;
5309
- this.view.requestMeasure();
5310
- }, 50);
5420
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5421
+ this.onResize();
5311
5422
  });
5312
5423
  this.resize.observe(view.scrollDOM);
5313
5424
  }
5314
5425
  this.start();
5315
- this.onScroll = this.onScroll.bind(this);
5316
- window.addEventListener("scroll", this.onScroll);
5426
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5317
5427
  if (typeof IntersectionObserver == "function") {
5318
5428
  this.intersection = new IntersectionObserver(entries => {
5319
5429
  if (this.parentCheck < 0)
@@ -5339,6 +5449,13 @@ class DOMObserver {
5339
5449
  this.flush(false);
5340
5450
  this.onScrollChanged(e);
5341
5451
  }
5452
+ onResize() {
5453
+ if (this.resizeTimeout < 0)
5454
+ this.resizeTimeout = setTimeout(() => {
5455
+ this.resizeTimeout = -1;
5456
+ this.view.requestMeasure();
5457
+ }, 50);
5458
+ }
5342
5459
  updateGaps(gaps) {
5343
5460
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5344
5461
  this.gapIntersection.disconnect();
@@ -5555,6 +5672,7 @@ class DOMObserver {
5555
5672
  for (let dom of this.scrollTargets)
5556
5673
  dom.removeEventListener("scroll", this.onScroll);
5557
5674
  window.removeEventListener("scroll", this.onScroll);
5675
+ window.removeEventListener("resize", this.onResize);
5558
5676
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5559
5677
  clearTimeout(this.parentCheck);
5560
5678
  clearTimeout(this.resizeTimeout);
@@ -5874,7 +5992,6 @@ class EditorView {
5874
5992
  this.mountStyles();
5875
5993
  this.updateAttrs();
5876
5994
  this.updateState = 0 /* Idle */;
5877
- ensureGlobalHandler();
5878
5995
  this.requestMeasure();
5879
5996
  if (config.parent)
5880
5997
  config.parent.appendChild(this.dom);
@@ -5907,9 +6024,17 @@ class EditorView {
5907
6024
  get inView() { return this.viewState.inView; }
5908
6025
  /**
5909
6026
  Indicates whether the user is currently composing text via
5910
- [IME](https://en.wikipedia.org/wiki/Input_method).
6027
+ [IME](https://en.wikipedia.org/wiki/Input_method), and at least
6028
+ one change has been made in the current composition.
5911
6029
  */
5912
6030
  get composing() { return this.inputState.composing > 0; }
6031
+ /**
6032
+ Indicates whether the user is currently in composing state. Note
6033
+ that on some platforms, like Android, this will be the case a
6034
+ lot, since just putting the cursor on a word starts a
6035
+ composition there.
6036
+ */
6037
+ get compositionStarted() { return this.inputState.composing >= 0; }
5913
6038
  dispatch(...input) {
5914
6039
  this._dispatch(input.length == 1 && input[0] instanceof Transaction ? input[0]
5915
6040
  : this.state.update(...input));
@@ -6703,22 +6828,6 @@ const MaxBidiLine = 4096;
6703
6828
  function ensureTop(given, view) {
6704
6829
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6705
6830
  }
6706
- let resizeDebounce = -1;
6707
- function ensureGlobalHandler() {
6708
- window.addEventListener("resize", () => {
6709
- if (resizeDebounce == -1)
6710
- resizeDebounce = setTimeout(handleResize, 50);
6711
- });
6712
- }
6713
- function handleResize() {
6714
- resizeDebounce = -1;
6715
- let found = document.querySelectorAll(".cm-content");
6716
- for (let i = 0; i < found.length; i++) {
6717
- let docView = ContentView.get(found[i]);
6718
- if (docView)
6719
- docView.editorView.requestMeasure();
6720
- }
6721
- }
6722
6831
  const BadMeasure = {};
6723
6832
  class CachedOrder {
6724
6833
  constructor(from, to, dir, order) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.43",
3
+ "version": "0.19.46",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",