@codemirror/view 0.19.44 → 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,13 @@
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
+
1
11
  ## 0.19.44 (2022-02-17)
2
12
 
3
13
  ### Bug fixes
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,8 +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
  }
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
+ }
862
899
  // These are drawn around uneditable widgets to avoid a number of
863
900
  // browser bugs that show up when the cursor is directly next to
864
901
  // uneditable inline content.
@@ -1388,6 +1425,7 @@ class BlockWidgetView extends ContentView {
1388
1425
  this.length = length;
1389
1426
  this.type = type;
1390
1427
  this.breakAfter = 0;
1428
+ this.prevWidget = null;
1391
1429
  }
1392
1430
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1393
1431
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1409,6 +1447,9 @@ class BlockWidgetView extends ContentView {
1409
1447
  get children() { return noChildren; }
1410
1448
  sync() {
1411
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;
1412
1453
  this.setDOM(this.widget.toDOM(this.editorView));
1413
1454
  this.dom.contentEditable = "false";
1414
1455
  }
@@ -1422,6 +1463,8 @@ class BlockWidgetView extends ContentView {
1422
1463
  other.widget.constructor == this.widget.constructor) {
1423
1464
  if (!other.widget.eq(this.widget))
1424
1465
  this.markDirty(true);
1466
+ if (this.dom && !this.prevWidget)
1467
+ this.prevWidget = this.widget;
1425
1468
  this.widget = other.widget;
1426
1469
  this.length = other.length;
1427
1470
  this.breakAfter = other.breakAfter;
@@ -2850,13 +2893,19 @@ function computeCompositionDeco(view, changes) {
2850
2893
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2851
2894
  return Decoration.none;
2852
2895
  }
2853
- 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));
2854
2902
  }
2855
2903
  class CompositionWidget extends WidgetType {
2856
- constructor(top, text) {
2904
+ constructor(top, text, topView) {
2857
2905
  super();
2858
2906
  this.top = top;
2859
2907
  this.text = text;
2908
+ this.topView = topView;
2860
2909
  }
2861
2910
  eq(other) { return this.top == other.top && this.text == other.text; }
2862
2911
  toDOM() { return this.top; }
@@ -5197,8 +5246,9 @@ const baseTheme = buildTheme("." + baseThemeID, {
5197
5246
  verticalAlign: "bottom"
5198
5247
  },
5199
5248
  ".cm-widgetBuffer": {
5200
- verticalAlign: "text-bottom",
5249
+ verticalAlign: "text-top",
5201
5250
  height: "1em",
5251
+ display: "inline"
5202
5252
  },
5203
5253
  ".cm-placeholder": {
5204
5254
  color: "#888",
@@ -5307,19 +5357,16 @@ class DOMObserver {
5307
5357
  this.flushSoon();
5308
5358
  };
5309
5359
  this.onSelectionChange = this.onSelectionChange.bind(this);
5360
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5310
5361
  if (typeof ResizeObserver == "function") {
5311
5362
  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);
5363
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5364
+ this.onResize();
5317
5365
  });
5318
5366
  this.resize.observe(view.scrollDOM);
5319
5367
  }
5320
5368
  this.start();
5321
- this.onScroll = this.onScroll.bind(this);
5322
- window.addEventListener("scroll", this.onScroll);
5369
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5323
5370
  if (typeof IntersectionObserver == "function") {
5324
5371
  this.intersection = new IntersectionObserver(entries => {
5325
5372
  if (this.parentCheck < 0)
@@ -5345,6 +5392,13 @@ class DOMObserver {
5345
5392
  this.flush(false);
5346
5393
  this.onScrollChanged(e);
5347
5394
  }
5395
+ onResize() {
5396
+ if (this.resizeTimeout < 0)
5397
+ this.resizeTimeout = setTimeout(() => {
5398
+ this.resizeTimeout = -1;
5399
+ this.view.requestMeasure();
5400
+ }, 50);
5401
+ }
5348
5402
  updateGaps(gaps) {
5349
5403
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5350
5404
  this.gapIntersection.disconnect();
@@ -5561,6 +5615,7 @@ class DOMObserver {
5561
5615
  for (let dom of this.scrollTargets)
5562
5616
  dom.removeEventListener("scroll", this.onScroll);
5563
5617
  window.removeEventListener("scroll", this.onScroll);
5618
+ window.removeEventListener("resize", this.onResize);
5564
5619
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5565
5620
  clearTimeout(this.parentCheck);
5566
5621
  clearTimeout(this.resizeTimeout);
@@ -5880,7 +5935,6 @@ class EditorView {
5880
5935
  this.mountStyles();
5881
5936
  this.updateAttrs();
5882
5937
  this.updateState = 0 /* Idle */;
5883
- ensureGlobalHandler();
5884
5938
  this.requestMeasure();
5885
5939
  if (config.parent)
5886
5940
  config.parent.appendChild(this.dom);
@@ -6717,22 +6771,6 @@ const MaxBidiLine = 4096;
6717
6771
  function ensureTop(given, view) {
6718
6772
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6719
6773
  }
6720
- let resizeDebounce = -1;
6721
- function ensureGlobalHandler() {
6722
- window.addEventListener("resize", () => {
6723
- if (resizeDebounce == -1)
6724
- resizeDebounce = setTimeout(handleResize, 50);
6725
- });
6726
- }
6727
- function handleResize() {
6728
- resizeDebounce = -1;
6729
- let found = document.querySelectorAll(".cm-content");
6730
- for (let i = 0; i < found.length; i++) {
6731
- let docView = ContentView.get(found[i]);
6732
- if (docView)
6733
- docView.editorView.requestMeasure();
6734
- }
6735
- }
6736
6774
  const BadMeasure = {};
6737
6775
  class CachedOrder {
6738
6776
  constructor(from, to, dir, order) {
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,8 +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
  }
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
+ }
859
896
  // These are drawn around uneditable widgets to avoid a number of
860
897
  // browser bugs that show up when the cursor is directly next to
861
898
  // uneditable inline content.
@@ -1384,6 +1421,7 @@ class BlockWidgetView extends ContentView {
1384
1421
  this.length = length;
1385
1422
  this.type = type;
1386
1423
  this.breakAfter = 0;
1424
+ this.prevWidget = null;
1387
1425
  }
1388
1426
  merge(from, to, source, _takeDeco, openStart, openEnd) {
1389
1427
  if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
@@ -1405,6 +1443,9 @@ class BlockWidgetView extends ContentView {
1405
1443
  get children() { return noChildren; }
1406
1444
  sync() {
1407
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;
1408
1449
  this.setDOM(this.widget.toDOM(this.editorView));
1409
1450
  this.dom.contentEditable = "false";
1410
1451
  }
@@ -1418,6 +1459,8 @@ class BlockWidgetView extends ContentView {
1418
1459
  other.widget.constructor == this.widget.constructor) {
1419
1460
  if (!other.widget.eq(this.widget))
1420
1461
  this.markDirty(true);
1462
+ if (this.dom && !this.prevWidget)
1463
+ this.prevWidget = this.widget;
1421
1464
  this.widget = other.widget;
1422
1465
  this.length = other.length;
1423
1466
  this.breakAfter = other.breakAfter;
@@ -2845,13 +2888,19 @@ function computeCompositionDeco(view, changes) {
2845
2888
  else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2846
2889
  return Decoration.none;
2847
2890
  }
2848
- 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));
2849
2897
  }
2850
2898
  class CompositionWidget extends WidgetType {
2851
- constructor(top, text) {
2899
+ constructor(top, text, topView) {
2852
2900
  super();
2853
2901
  this.top = top;
2854
2902
  this.text = text;
2903
+ this.topView = topView;
2855
2904
  }
2856
2905
  eq(other) { return this.top == other.top && this.text == other.text; }
2857
2906
  toDOM() { return this.top; }
@@ -5191,8 +5240,9 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5191
5240
  verticalAlign: "bottom"
5192
5241
  },
5193
5242
  ".cm-widgetBuffer": {
5194
- verticalAlign: "text-bottom",
5243
+ verticalAlign: "text-top",
5195
5244
  height: "1em",
5245
+ display: "inline"
5196
5246
  },
5197
5247
  ".cm-placeholder": {
5198
5248
  color: "#888",
@@ -5301,19 +5351,16 @@ class DOMObserver {
5301
5351
  this.flushSoon();
5302
5352
  };
5303
5353
  this.onSelectionChange = this.onSelectionChange.bind(this);
5354
+ window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5304
5355
  if (typeof ResizeObserver == "function") {
5305
5356
  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);
5357
+ if (this.view.docView.lastUpdate < Date.now() - 75)
5358
+ this.onResize();
5311
5359
  });
5312
5360
  this.resize.observe(view.scrollDOM);
5313
5361
  }
5314
5362
  this.start();
5315
- this.onScroll = this.onScroll.bind(this);
5316
- window.addEventListener("scroll", this.onScroll);
5363
+ window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5317
5364
  if (typeof IntersectionObserver == "function") {
5318
5365
  this.intersection = new IntersectionObserver(entries => {
5319
5366
  if (this.parentCheck < 0)
@@ -5339,6 +5386,13 @@ class DOMObserver {
5339
5386
  this.flush(false);
5340
5387
  this.onScrollChanged(e);
5341
5388
  }
5389
+ onResize() {
5390
+ if (this.resizeTimeout < 0)
5391
+ this.resizeTimeout = setTimeout(() => {
5392
+ this.resizeTimeout = -1;
5393
+ this.view.requestMeasure();
5394
+ }, 50);
5395
+ }
5342
5396
  updateGaps(gaps) {
5343
5397
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5344
5398
  this.gapIntersection.disconnect();
@@ -5555,6 +5609,7 @@ class DOMObserver {
5555
5609
  for (let dom of this.scrollTargets)
5556
5610
  dom.removeEventListener("scroll", this.onScroll);
5557
5611
  window.removeEventListener("scroll", this.onScroll);
5612
+ window.removeEventListener("resize", this.onResize);
5558
5613
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5559
5614
  clearTimeout(this.parentCheck);
5560
5615
  clearTimeout(this.resizeTimeout);
@@ -5874,7 +5929,6 @@ class EditorView {
5874
5929
  this.mountStyles();
5875
5930
  this.updateAttrs();
5876
5931
  this.updateState = 0 /* Idle */;
5877
- ensureGlobalHandler();
5878
5932
  this.requestMeasure();
5879
5933
  if (config.parent)
5880
5934
  config.parent.appendChild(this.dom);
@@ -6711,22 +6765,6 @@ const MaxBidiLine = 4096;
6711
6765
  function ensureTop(given, view) {
6712
6766
  return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6713
6767
  }
6714
- let resizeDebounce = -1;
6715
- function ensureGlobalHandler() {
6716
- window.addEventListener("resize", () => {
6717
- if (resizeDebounce == -1)
6718
- resizeDebounce = setTimeout(handleResize, 50);
6719
- });
6720
- }
6721
- function handleResize() {
6722
- resizeDebounce = -1;
6723
- let found = document.querySelectorAll(".cm-content");
6724
- for (let i = 0; i < found.length; i++) {
6725
- let docView = ContentView.get(found[i]);
6726
- if (docView)
6727
- docView.editorView.requestMeasure();
6728
- }
6729
- }
6730
6768
  const BadMeasure = {};
6731
6769
  class CachedOrder {
6732
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.44",
3
+ "version": "0.19.45",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",