@codemirror/view 0.19.22 → 0.19.26

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/dist/index.cjs CHANGED
@@ -273,7 +273,7 @@ class DOMPos {
273
273
  static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
274
274
  static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
275
275
  }
276
- const none$3 = [];
276
+ const noChildren = [];
277
277
  class ContentView {
278
278
  constructor() {
279
279
  this.parent = null;
@@ -439,12 +439,12 @@ class ContentView {
439
439
  v = parent;
440
440
  }
441
441
  }
442
- replaceChildren(from, to, children = none$3) {
442
+ replaceChildren(from, to, children = noChildren) {
443
443
  this.markDirty();
444
444
  for (let i = from; i < to; i++) {
445
445
  let child = this.children[i];
446
446
  if (child.parent == this)
447
- child.parent = null;
447
+ child.destroy();
448
448
  }
449
449
  this.children.splice(from, to - from, ...children);
450
450
  for (let i = 0; i < children.length; i++)
@@ -466,6 +466,17 @@ class ContentView {
466
466
  }
467
467
  static get(node) { return node.cmView; }
468
468
  get isEditable() { return true; }
469
+ merge(from, to, source, hasStart, openStart, openEnd) {
470
+ return false;
471
+ }
472
+ become(other) { return false; }
473
+ // When this is a zero-length view with a side, this should return a
474
+ // number <= 0 to indicate it is before its position, or a
475
+ // number > 0 when after its position.
476
+ getSide() { return 0; }
477
+ destroy() {
478
+ this.parent = null;
479
+ }
469
480
  }
470
481
  ContentView.prototype.breakAfter = 0;
471
482
  // Remove a DOM node and return its next sibling.
@@ -493,6 +504,94 @@ class ChildCursor {
493
504
  }
494
505
  }
495
506
  }
507
+ function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
508
+ let { children } = parent;
509
+ let before = children.length ? children[fromI] : null;
510
+ let last = insert.length ? insert[insert.length - 1] : null;
511
+ let breakAtEnd = last ? last.breakAfter : breakAtStart;
512
+ // Change within a single child
513
+ if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
514
+ before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
515
+ return;
516
+ if (toI < children.length) {
517
+ let after = children[toI];
518
+ // Make sure the end of the child after the update is preserved in `after`
519
+ if (after && toOff < after.length) {
520
+ // If we're splitting a child, separate part of it to avoid that
521
+ // being mangled when updating the child before the update.
522
+ if (fromI == toI) {
523
+ after = after.split(toOff);
524
+ toOff = 0;
525
+ }
526
+ // If the element after the replacement should be merged with
527
+ // the last replacing element, update `content`
528
+ if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
529
+ insert[insert.length - 1] = after;
530
+ }
531
+ else {
532
+ // Remove the start of the after element, if necessary, and
533
+ // add it to `content`.
534
+ if (toOff)
535
+ after.merge(0, toOff, null, false, 0, openEnd);
536
+ insert.push(after);
537
+ }
538
+ }
539
+ else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
540
+ // The element at `toI` is entirely covered by this range.
541
+ // Preserve its line break, if any.
542
+ if (last)
543
+ last.breakAfter = 1;
544
+ else
545
+ breakAtStart = 1;
546
+ }
547
+ // Since we've handled the next element from the current elements
548
+ // now, make sure `toI` points after that.
549
+ toI++;
550
+ }
551
+ if (before) {
552
+ before.breakAfter = breakAtStart;
553
+ if (fromOff > 0) {
554
+ if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
555
+ before.breakAfter = insert.shift().breakAfter;
556
+ }
557
+ else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
558
+ before.merge(fromOff, before.length, null, false, openStart, 0);
559
+ }
560
+ fromI++;
561
+ }
562
+ }
563
+ // Try to merge widgets on the boundaries of the replacement
564
+ while (fromI < toI && insert.length) {
565
+ if (children[toI - 1].become(insert[insert.length - 1])) {
566
+ toI--;
567
+ insert.pop();
568
+ openEnd = insert.length ? 0 : openStart;
569
+ }
570
+ else if (children[fromI].become(insert[0])) {
571
+ fromI++;
572
+ insert.shift();
573
+ openStart = insert.length ? 0 : openEnd;
574
+ }
575
+ else {
576
+ break;
577
+ }
578
+ }
579
+ if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
580
+ children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
581
+ fromI--;
582
+ if (fromI < toI || insert.length)
583
+ parent.replaceChildren(fromI, toI, insert);
584
+ }
585
+ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
586
+ let cur = parent.childCursor();
587
+ let { i: toI, off: toOff } = cur.findPos(to, 1);
588
+ let { i: fromI, off: fromOff } = cur.findPos(from, -1);
589
+ let dLen = from - to;
590
+ for (let view of insert)
591
+ dLen += view.length;
592
+ parent.length += dLen;
593
+ replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
594
+ }
496
595
 
497
596
  let [nav, doc] = typeof navigator != "undefined"
498
597
  ? [navigator, document]
@@ -524,21 +623,8 @@ var browser = {
524
623
  tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
525
624
  };
526
625
 
527
- const none$2 = [];
528
- class InlineView extends ContentView {
529
- /**
530
- Return true when this view is equivalent to `other` and can take
531
- on its role.
532
- */
533
- become(_other) { return false; }
534
- // When this is a zero-length view with a side, this should return a
535
- // negative number to indicate it is before its position, or a
536
- // positive number when after its position.
537
- getSide() { return 0; }
538
- }
539
- InlineView.prototype.children = none$2;
540
626
  const MaxJoinLen = 256;
541
- class TextView extends InlineView {
627
+ class TextView extends ContentView {
542
628
  constructor(text) {
543
629
  super();
544
630
  this.text = text;
@@ -569,7 +655,7 @@ class TextView extends InlineView {
569
655
  this.markDirty();
570
656
  return true;
571
657
  }
572
- slice(from) {
658
+ split(from) {
573
659
  let result = new TextView(this.text.slice(from));
574
660
  this.text = this.text.slice(0, from);
575
661
  return result;
@@ -585,7 +671,7 @@ class TextView extends InlineView {
585
671
  return textCoords(this.dom, pos, side);
586
672
  }
587
673
  }
588
- class MarkView extends InlineView {
674
+ class MarkView extends ContentView {
589
675
  constructor(mark, children = [], length = 0) {
590
676
  super();
591
677
  this.mark = mark;
@@ -608,20 +694,20 @@ class MarkView extends InlineView {
608
694
  this.createDOM();
609
695
  super.sync(track);
610
696
  }
611
- merge(from, to, source, openStart, openEnd) {
697
+ merge(from, to, source, _hasStart, openStart, openEnd) {
612
698
  if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
613
699
  (from && openStart <= 0) || (to < this.length && openEnd <= 0)))
614
700
  return false;
615
- mergeInlineChildren(this, from, to, source ? source.children : none$2, openStart - 1, openEnd - 1);
701
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart - 1, openEnd - 1);
616
702
  this.markDirty();
617
703
  return true;
618
704
  }
619
- slice(from) {
705
+ split(from) {
620
706
  let result = [], off = 0, detachFrom = -1, i = 0;
621
707
  for (let elt of this.children) {
622
708
  let end = off + elt.length;
623
709
  if (end > from)
624
- result.push(off < from ? elt.slice(from - off) : elt);
710
+ result.push(off < from ? elt.split(from - off) : elt);
625
711
  if (detachFrom < 0 && off >= from)
626
712
  detachFrom = i;
627
713
  off = end;
@@ -629,8 +715,10 @@ class MarkView extends InlineView {
629
715
  }
630
716
  let length = this.length - from;
631
717
  this.length = from;
632
- if (detachFrom > -1)
633
- this.replaceChildren(detachFrom, this.children.length);
718
+ if (detachFrom > -1) {
719
+ this.children.length = detachFrom;
720
+ this.markDirty();
721
+ }
634
722
  return new MarkView(this.mark, result, length);
635
723
  }
636
724
  domAtPos(pos) {
@@ -672,7 +760,7 @@ function textCoords(text, pos, side) {
672
760
  return flatten ? flattenRect(rect, flatten < 0) : rect || null;
673
761
  }
674
762
  // Also used for collapsed ranges that don't have a placeholder widget!
675
- class WidgetView extends InlineView {
763
+ class WidgetView extends ContentView {
676
764
  constructor(widget, length, side) {
677
765
  super();
678
766
  this.widget = widget;
@@ -682,7 +770,7 @@ class WidgetView extends InlineView {
682
770
  static create(widget, length, side) {
683
771
  return new (widget.customView || WidgetView)(widget, length, side);
684
772
  }
685
- slice(from) {
773
+ split(from) {
686
774
  let result = WidgetView.create(this.widget, this.length - from, this.side);
687
775
  this.length -= from;
688
776
  return result;
@@ -694,7 +782,7 @@ class WidgetView extends InlineView {
694
782
  }
695
783
  }
696
784
  getSide() { return this.side; }
697
- merge(from, to, source, openStart, openEnd) {
785
+ merge(from, to, source, hasStart, openStart, openEnd) {
698
786
  if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
699
787
  from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
700
788
  return false;
@@ -739,6 +827,11 @@ class WidgetView extends InlineView {
739
827
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
740
828
  }
741
829
  get isEditable() { return false; }
830
+ destroy() {
831
+ super.destroy();
832
+ if (this.dom)
833
+ this.widget.destroy(this.dom);
834
+ }
742
835
  }
743
836
  class CompositionView extends WidgetView {
744
837
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -755,7 +848,7 @@ class CompositionView extends WidgetView {
755
848
  // These are drawn around uneditable widgets to avoid a number of
756
849
  // browser bugs that show up when the cursor is directly next to
757
850
  // uneditable inline content.
758
- class WidgetBufferView extends InlineView {
851
+ class WidgetBufferView extends ContentView {
759
852
  constructor(side) {
760
853
  super();
761
854
  this.side = side;
@@ -765,7 +858,7 @@ class WidgetBufferView extends InlineView {
765
858
  become(other) {
766
859
  return other instanceof WidgetBufferView && other.side == this.side;
767
860
  }
768
- slice() { return new WidgetBufferView(this.side); }
861
+ split() { return new WidgetBufferView(this.side); }
769
862
  sync() {
770
863
  if (!this.dom)
771
864
  this.setDOM(document.createTextNode("\u200b"));
@@ -783,90 +876,7 @@ class WidgetBufferView extends InlineView {
783
876
  return text.Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
784
877
  }
785
878
  }
786
- function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
787
- let cur = parent.childCursor();
788
- let { i: toI, off: toOff } = cur.findPos(to, 1);
789
- let { i: fromI, off: fromOff } = cur.findPos(from, -1);
790
- let dLen = from - to;
791
- for (let view of elts)
792
- dLen += view.length;
793
- parent.length += dLen;
794
- let { children } = parent;
795
- // Both from and to point into the same child view
796
- if (fromI == toI && fromOff) {
797
- let start = children[fromI];
798
- // Maybe just update that view and be done
799
- if (elts.length == 1 && start.merge(fromOff, toOff, elts[0], openStart, openEnd))
800
- return;
801
- if (elts.length == 0) {
802
- start.merge(fromOff, toOff, null, openStart, openEnd);
803
- return;
804
- }
805
- // Otherwise split it, so that we don't have to worry about aliasing front/end afterwards
806
- let after = start.slice(toOff);
807
- if (after.merge(0, 0, elts[elts.length - 1], 0, openEnd))
808
- elts[elts.length - 1] = after;
809
- else
810
- elts.push(after);
811
- toI++;
812
- openEnd = toOff = 0;
813
- }
814
- // Make sure start and end positions fall on node boundaries
815
- // (fromOff/toOff are no longer used after this), and that if the
816
- // start or end of the elts can be merged with adjacent nodes,
817
- // this is done
818
- if (toOff) {
819
- let end = children[toI];
820
- if (elts.length && end.merge(0, toOff, elts[elts.length - 1], 0, openEnd)) {
821
- elts.pop();
822
- openEnd = elts.length ? 0 : openStart;
823
- }
824
- else {
825
- end.merge(0, toOff, null, 0, 0);
826
- }
827
- }
828
- else if (toI < children.length && elts.length &&
829
- children[toI].merge(0, 0, elts[elts.length - 1], 0, openEnd)) {
830
- elts.pop();
831
- openEnd = elts.length ? 0 : openStart;
832
- }
833
- if (fromOff) {
834
- let start = children[fromI];
835
- if (elts.length && start.merge(fromOff, start.length, elts[0], openStart, 0)) {
836
- elts.shift();
837
- openStart = elts.length ? 0 : openEnd;
838
- }
839
- else {
840
- start.merge(fromOff, start.length, null, 0, 0);
841
- }
842
- fromI++;
843
- }
844
- else if (fromI && elts.length) {
845
- let end = children[fromI - 1];
846
- if (end.merge(end.length, end.length, elts[0], openStart, 0)) {
847
- elts.shift();
848
- openStart = elts.length ? 0 : openEnd;
849
- }
850
- }
851
- // Then try to merge any mergeable nodes at the start and end of
852
- // the changed range
853
- while (fromI < toI && elts.length && children[toI - 1].become(elts[elts.length - 1])) {
854
- elts.pop();
855
- toI--;
856
- openEnd = elts.length ? 0 : openStart;
857
- }
858
- while (fromI < toI && elts.length && children[fromI].become(elts[0])) {
859
- elts.shift();
860
- fromI++;
861
- openStart = elts.length ? 0 : openEnd;
862
- }
863
- if (!elts.length && fromI && toI < children.length &&
864
- children[toI].merge(0, 0, children[fromI - 1], openStart, openEnd))
865
- fromI--;
866
- // And if anything remains, splice the child array to insert the new elts
867
- if (elts.length || fromI != toI)
868
- parent.replaceChildren(fromI, toI, elts);
869
- }
879
+ TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
870
880
  function inlineDOMAtPos(dom, children, pos) {
871
881
  let i = 0;
872
882
  for (let off = 0; i < children.length; i++) {
@@ -1007,6 +1017,11 @@ class WidgetType {
1007
1017
  @internal
1008
1018
  */
1009
1019
  get customView() { return null; }
1020
+ /**
1021
+ This is called when the an instance of the widget is removed
1022
+ from the editor view.
1023
+ */
1024
+ destroy(_dom) { }
1010
1025
  }
1011
1026
  /**
1012
1027
  The different types of blocks that can occur in an editor view.
@@ -1086,8 +1101,9 @@ class Decoration extends rangeset.RangeValue {
1086
1101
  position.
1087
1102
  */
1088
1103
  static widget(spec) {
1089
- let side = spec.side || 0;
1090
- return new PointDecoration(spec, side, side, !!spec.block, spec.widget || null, false);
1104
+ let side = spec.side || 0, block = !!spec.block;
1105
+ side += block ? (side > 0 ? 300000000 /* BlockAfter */ : -400000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1106
+ return new PointDecoration(spec, side, side, block, spec.widget || null, false);
1091
1107
  }
1092
1108
  /**
1093
1109
  Create a replace decoration which replaces the given range with
@@ -1096,8 +1112,8 @@ class Decoration extends rangeset.RangeValue {
1096
1112
  static replace(spec) {
1097
1113
  let block = !!spec.block;
1098
1114
  let { start, end } = getInclusive(spec, block);
1099
- let startSide = 100000000 /* Big */ * (start ? -1 : 1) * (block ? 2 : 1);
1100
- let endSide = 100000000 /* Big */ * (end ? 1 : -1) * (block ? 2 : 1);
1115
+ let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1116
+ let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1101
1117
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1102
1118
  }
1103
1119
  /**
@@ -1127,7 +1143,7 @@ Decoration.none = rangeset.RangeSet.empty;
1127
1143
  class MarkDecoration extends Decoration {
1128
1144
  constructor(spec) {
1129
1145
  let { start, end } = getInclusive(spec);
1130
- super(100000000 /* Big */ * (start ? -1 : 1), 100000000 /* Big */ * (end ? 1 : -1), null, spec);
1146
+ super(start ? -1 /* InlineIncStart */ : 400000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -500000000 /* NonIncEnd */, null, spec);
1131
1147
  this.tagName = spec.tagName || "span";
1132
1148
  this.class = spec.class || "";
1133
1149
  this.attrs = spec.attributes || null;
@@ -1148,7 +1164,7 @@ class MarkDecoration extends Decoration {
1148
1164
  MarkDecoration.prototype.point = false;
1149
1165
  class LineDecoration extends Decoration {
1150
1166
  constructor(spec) {
1151
- super(-100000000 /* Big */, -100000000 /* Big */, null, spec);
1167
+ super(-200000000 /* Line */, -200000000 /* Line */, null, spec);
1152
1168
  }
1153
1169
  eq(other) {
1154
1170
  return other instanceof LineDecoration && attrsEq(this.spec.attributes, other.spec.attributes);
@@ -1166,12 +1182,12 @@ class PointDecoration extends Decoration {
1166
1182
  super(startSide, endSide, widget, spec);
1167
1183
  this.block = block;
1168
1184
  this.isReplace = isReplace;
1169
- this.mapMode = !block ? state.MapMode.TrackDel : startSide < 0 ? state.MapMode.TrackBefore : state.MapMode.TrackAfter;
1185
+ this.mapMode = !block ? state.MapMode.TrackDel : startSide <= 0 ? state.MapMode.TrackBefore : state.MapMode.TrackAfter;
1170
1186
  }
1171
1187
  // Only relevant when this.block == true
1172
1188
  get type() {
1173
1189
  return this.startSide < this.endSide ? exports.BlockType.WidgetRange
1174
- : this.startSide < 0 ? exports.BlockType.WidgetBefore : exports.BlockType.WidgetAfter;
1190
+ : this.startSide <= 0 ? exports.BlockType.WidgetBefore : exports.BlockType.WidgetAfter;
1175
1191
  }
1176
1192
  get heightRelevant() { return this.block || !!this.widget && this.widget.estimatedHeight >= 5; }
1177
1193
  eq(other) {
@@ -1181,7 +1197,7 @@ class PointDecoration extends Decoration {
1181
1197
  this.startSide == other.startSide && this.endSide == other.endSide;
1182
1198
  }
1183
1199
  range(from, to = from) {
1184
- if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide < 0)))
1200
+ if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
1185
1201
  throw new RangeError("Invalid range for replacement decoration");
1186
1202
  if (!this.isReplace && to != from)
1187
1203
  throw new RangeError("Widget decorations can only have zero-length ranges");
@@ -1218,16 +1234,16 @@ class LineView extends ContentView {
1218
1234
  this.breakAfter = 0;
1219
1235
  }
1220
1236
  // Consumes source
1221
- merge(from, to, source, takeDeco, openStart, openEnd) {
1237
+ merge(from, to, source, hasStart, openStart, openEnd) {
1222
1238
  if (source) {
1223
1239
  if (!(source instanceof LineView))
1224
1240
  return false;
1225
1241
  if (!this.dom)
1226
1242
  source.transferDOM(this); // Reuse source.dom when appropriate
1227
1243
  }
1228
- if (takeDeco)
1244
+ if (hasStart)
1229
1245
  this.setDeco(source ? source.attrs : null);
1230
- mergeInlineChildren(this, from, to, source ? source.children : none$1, openStart, openEnd);
1246
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart, openEnd);
1231
1247
  return true;
1232
1248
  }
1233
1249
  split(at) {
@@ -1237,16 +1253,14 @@ class LineView extends ContentView {
1237
1253
  return end;
1238
1254
  let { i, off } = this.childPos(at);
1239
1255
  if (off) {
1240
- end.append(this.children[i].slice(off), 0);
1241
- this.children[i].merge(off, this.children[i].length, null, 0, 0);
1256
+ end.append(this.children[i].split(off), 0);
1257
+ this.children[i].merge(off, this.children[i].length, null, false, 0, 0);
1242
1258
  i++;
1243
1259
  }
1244
1260
  for (let j = i; j < this.children.length; j++)
1245
1261
  end.append(this.children[j], 0);
1246
- while (i > 0 && this.children[i - 1].length == 0) {
1247
- this.children[i - 1].parent = null;
1248
- i--;
1249
- }
1262
+ while (i > 0 && this.children[i - 1].length == 0)
1263
+ this.children[--i].destroy();
1250
1264
  this.children.length = i;
1251
1265
  this.markDirty();
1252
1266
  this.length = at;
@@ -1269,7 +1283,6 @@ class LineView extends ContentView {
1269
1283
  this.attrs = attrs;
1270
1284
  }
1271
1285
  }
1272
- // Only called when building a line view in ContentBuilder
1273
1286
  append(child, openStart) {
1274
1287
  joinInlineInto(this, child, openStart);
1275
1288
  }
@@ -1326,7 +1339,7 @@ class LineView extends ContentView {
1326
1339
  coordsAt(pos, side) {
1327
1340
  return coordsInChildren(this, pos, side);
1328
1341
  }
1329
- match(_other) { return false; }
1342
+ become(_other) { return false; }
1330
1343
  get type() { return exports.BlockType.Text; }
1331
1344
  static find(docView, pos) {
1332
1345
  for (let i = 0, off = 0;; i++) {
@@ -1341,7 +1354,6 @@ class LineView extends ContentView {
1341
1354
  }
1342
1355
  }
1343
1356
  }
1344
- const none$1 = [];
1345
1357
  class BlockWidgetView extends ContentView {
1346
1358
  constructor(widget, length, type) {
1347
1359
  super();
@@ -1367,7 +1379,7 @@ class BlockWidgetView extends ContentView {
1367
1379
  end.breakAfter = this.breakAfter;
1368
1380
  return end;
1369
1381
  }
1370
- get children() { return none$1; }
1382
+ get children() { return noChildren; }
1371
1383
  sync() {
1372
1384
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1373
1385
  this.setDOM(this.widget.toDOM(this.editorView));
@@ -1378,7 +1390,7 @@ class BlockWidgetView extends ContentView {
1378
1390
  return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : state.Text.empty;
1379
1391
  }
1380
1392
  domBoundsAround() { return null; }
1381
- match(other) {
1393
+ become(other) {
1382
1394
  if (other instanceof BlockWidgetView && other.type == this.type &&
1383
1395
  other.widget.constructor == this.widget.constructor) {
1384
1396
  if (!other.widget.eq(this.widget))
@@ -1392,6 +1404,11 @@ class BlockWidgetView extends ContentView {
1392
1404
  }
1393
1405
  ignoreMutation() { return true; }
1394
1406
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1407
+ destroy() {
1408
+ super.destroy();
1409
+ if (this.dom)
1410
+ this.widget.destroy(this.dom);
1411
+ }
1395
1412
  }
1396
1413
 
1397
1414
  class ContentBuilder {
@@ -1727,36 +1744,39 @@ class PluginInstance {
1727
1744
  this.value = null;
1728
1745
  }
1729
1746
  takeField(type, target) {
1730
- for (let { field, get } of this.spec.fields)
1731
- if (field == type)
1732
- target.push(get(this.value));
1747
+ if (this.spec)
1748
+ for (let { field, get } of this.spec.fields)
1749
+ if (field == type)
1750
+ target.push(get(this.value));
1733
1751
  }
1734
1752
  update(view) {
1735
1753
  if (!this.value) {
1736
- try {
1737
- this.value = this.spec.create(view);
1738
- }
1739
- catch (e) {
1740
- logException(view.state, e, "CodeMirror plugin crashed");
1741
- return PluginInstance.dummy;
1754
+ if (this.spec) {
1755
+ try {
1756
+ this.value = this.spec.create(view);
1757
+ }
1758
+ catch (e) {
1759
+ logException(view.state, e, "CodeMirror plugin crashed");
1760
+ this.deactivate();
1761
+ }
1742
1762
  }
1743
1763
  }
1744
1764
  else if (this.mustUpdate) {
1745
1765
  let update = this.mustUpdate;
1746
1766
  this.mustUpdate = null;
1747
- if (!this.value.update)
1748
- return this;
1749
- try {
1750
- this.value.update(update);
1751
- }
1752
- catch (e) {
1753
- logException(update.state, e, "CodeMirror plugin crashed");
1754
- if (this.value.destroy)
1755
- try {
1756
- this.value.destroy();
1757
- }
1758
- catch (_) { }
1759
- return PluginInstance.dummy;
1767
+ if (this.value.update) {
1768
+ try {
1769
+ this.value.update(update);
1770
+ }
1771
+ catch (e) {
1772
+ logException(update.state, e, "CodeMirror plugin crashed");
1773
+ if (this.value.destroy)
1774
+ try {
1775
+ this.value.destroy();
1776
+ }
1777
+ catch (_) { }
1778
+ this.deactivate();
1779
+ }
1760
1780
  }
1761
1781
  }
1762
1782
  return this;
@@ -1772,20 +1792,12 @@ class PluginInstance {
1772
1792
  }
1773
1793
  }
1774
1794
  }
1795
+ deactivate() {
1796
+ this.spec = this.value = null;
1797
+ }
1775
1798
  }
1776
- PluginInstance.dummy = new PluginInstance(ViewPlugin.define(() => ({})));
1777
- function combineFacetAttrs(values) {
1778
- let result = {};
1779
- for (let i = values.length - 1; i >= 0; i--)
1780
- combineAttrs(values[i], result);
1781
- return result;
1782
- }
1783
- const editorAttributes = state.Facet.define({
1784
- combine: combineFacetAttrs
1785
- });
1786
- const contentAttributes = state.Facet.define({
1787
- combine: combineFacetAttrs
1788
- });
1799
+ const editorAttributes = state.Facet.define();
1800
+ const contentAttributes = state.Facet.define();
1789
1801
  // Provide decorations
1790
1802
  const decorations = state.Facet.define();
1791
1803
  const styleModule = state.Facet.define();
@@ -1912,7 +1924,7 @@ class ViewUpdate {
1912
1924
  Whether the document changed in this update.
1913
1925
  */
1914
1926
  get docChanged() {
1915
- return this.transactions.some(tr => tr.docChanged);
1927
+ return !this.changes.empty;
1916
1928
  }
1917
1929
  /**
1918
1930
  Whether the selection was explicitly set in this update.
@@ -1933,12 +1945,12 @@ class DocView extends ContentView {
1933
1945
  this.compositionDeco = Decoration.none;
1934
1946
  this.decorations = [];
1935
1947
  // Track a minimum width for the editor. When measuring sizes in
1936
- // checkLayout, this is updated to point at the width of a given
1937
- // element and its extent in the document. When a change happens in
1938
- // that range, these are reset. That way, once we've seen a
1939
- // line/element of a given length, we keep the editor wide enough to
1940
- // fit at least that element, until it is changed, at which point we
1941
- // forget it again.
1948
+ // measureVisibleLineHeights, this is updated to point at the width
1949
+ // of a given element and its extent in the document. When a change
1950
+ // happens in that range, these are reset. That way, once we've seen
1951
+ // a line/element of a given length, we keep the editor wide enough
1952
+ // to fit at least that element, until it is changed, at which point
1953
+ // we forget it again.
1942
1954
  this.minWidth = 0;
1943
1955
  this.minWidthFrom = 0;
1944
1956
  this.minWidthTo = 0;
@@ -2008,7 +2020,7 @@ class DocView extends ContentView {
2008
2020
  this.updateSelection();
2009
2021
  }
2010
2022
  }
2011
- // Used both by update and checkLayout do perform the actual DOM
2023
+ // Used by update and the constructor do perform the actual DOM
2012
2024
  // update
2013
2025
  updateInner(changes, deco, oldLength) {
2014
2026
  this.view.viewState.mustMeasureContent = true;
@@ -2049,71 +2061,9 @@ class DocView extends ContentView {
2049
2061
  let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2050
2062
  let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2051
2063
  let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2052
- this.replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2064
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2053
2065
  }
2054
2066
  }
2055
- replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd) {
2056
- let before = this.children[fromI], last = content.length ? content[content.length - 1] : null;
2057
- let breakAtEnd = last ? last.breakAfter : breakAtStart;
2058
- // Change within a single line
2059
- if (fromI == toI && !breakAtStart && !breakAtEnd && content.length < 2 &&
2060
- before.merge(fromOff, toOff, content.length ? last : null, fromOff == 0, openStart, openEnd))
2061
- return;
2062
- let after = this.children[toI];
2063
- // Make sure the end of the line after the update is preserved in `after`
2064
- if (toOff < after.length) {
2065
- // If we're splitting a line, separate part of the start line to
2066
- // avoid that being mangled when updating the start line.
2067
- if (fromI == toI) {
2068
- after = after.split(toOff);
2069
- toOff = 0;
2070
- }
2071
- // If the element after the replacement should be merged with
2072
- // the last replacing element, update `content`
2073
- if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
2074
- content[content.length - 1] = after;
2075
- }
2076
- else {
2077
- // Remove the start of the after element, if necessary, and
2078
- // add it to `content`.
2079
- if (toOff)
2080
- after.merge(0, toOff, null, false, 0, openEnd);
2081
- content.push(after);
2082
- }
2083
- }
2084
- else if (after.breakAfter) {
2085
- // The element at `toI` is entirely covered by this range.
2086
- // Preserve its line break, if any.
2087
- if (last)
2088
- last.breakAfter = 1;
2089
- else
2090
- breakAtStart = 1;
2091
- }
2092
- // Since we've handled the next element from the current elements
2093
- // now, make sure `toI` points after that.
2094
- toI++;
2095
- before.breakAfter = breakAtStart;
2096
- if (fromOff > 0) {
2097
- if (!breakAtStart && content.length && before.merge(fromOff, before.length, content[0], false, openStart, 0)) {
2098
- before.breakAfter = content.shift().breakAfter;
2099
- }
2100
- else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
2101
- before.merge(fromOff, before.length, null, false, openStart, 0);
2102
- }
2103
- fromI++;
2104
- }
2105
- // Try to merge widgets on the boundaries of the replacement
2106
- while (fromI < toI && content.length) {
2107
- if (this.children[toI - 1].match(content[content.length - 1]))
2108
- toI--, content.pop();
2109
- else if (this.children[fromI].match(content[0]))
2110
- fromI++, content.shift();
2111
- else
2112
- break;
2113
- }
2114
- if (fromI < toI || content.length)
2115
- this.replaceChildren(fromI, toI, content);
2116
- }
2117
2067
  // Sync the DOM selection to this.state.selection
2118
2068
  updateSelection(mustRead = false, fromPointer = false) {
2119
2069
  if (mustRead)
@@ -2376,15 +2326,10 @@ function computeCompositionDeco(view, changes) {
2376
2326
  if (!textNode)
2377
2327
  return Decoration.none;
2378
2328
  let cView = view.docView.nearest(textNode);
2329
+ if (!cView)
2330
+ return Decoration.none;
2379
2331
  let from, to, topNode = textNode;
2380
- if (cView instanceof InlineView) {
2381
- while (cView.parent instanceof InlineView)
2382
- cView = cView.parent;
2383
- from = cView.posAtStart;
2384
- to = from + cView.length;
2385
- topNode = cView.dom;
2386
- }
2387
- else if (cView instanceof LineView) {
2332
+ if (cView instanceof LineView) {
2388
2333
  while (topNode.parentNode != cView.dom)
2389
2334
  topNode = topNode.parentNode;
2390
2335
  let prev = topNode.previousSibling;
@@ -2393,7 +2338,17 @@ function computeCompositionDeco(view, changes) {
2393
2338
  from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2394
2339
  }
2395
2340
  else {
2396
- return Decoration.none;
2341
+ for (;;) {
2342
+ let { parent } = cView;
2343
+ if (!parent)
2344
+ return Decoration.none;
2345
+ if (parent instanceof LineView)
2346
+ break;
2347
+ cView = parent;
2348
+ }
2349
+ from = cView.posAtStart;
2350
+ to = from + cView.length;
2351
+ topNode = cView.dom;
2397
2352
  }
2398
2353
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2399
2354
  let text = textNode.nodeValue, { state } = view;
@@ -2931,7 +2886,7 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2931
2886
  y = docTop + yOffset;
2932
2887
  let lineStart = block.from;
2933
2888
  // Clip x to the viewport sides
2934
- x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2889
+ x = Math.max(content.left + 1, Math.min(Math.max(content.right, content.left + view.docView.minWidth) - 1, x));
2935
2890
  // If this is outside of the rendered viewport, we can't determine a position
2936
2891
  if (lineStart < view.viewport.from)
2937
2892
  return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
@@ -4575,10 +4530,11 @@ class ViewState {
4575
4530
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4576
4531
  !this.viewportIsAppropriate(viewport))
4577
4532
  viewport = this.getViewport(0, scrollTarget);
4578
- if (!update.changes.empty || (update.flags & 2 /* Height */) ||
4579
- viewport.from != this.viewport.from || viewport.to != this.viewport.to)
4580
- this.updateViewportLines();
4533
+ let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4534
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4581
4535
  this.viewport = viewport;
4536
+ if (updateLines)
4537
+ this.updateViewportLines();
4582
4538
  this.updateForViewport();
4583
4539
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4584
4540
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -4793,7 +4749,7 @@ class ViewState {
4793
4749
  return changed ? 4 /* Viewport */ : 0;
4794
4750
  }
4795
4751
  lineBlockAt(pos) {
4796
- return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to <= pos)) ||
4752
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
4797
4753
  scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4798
4754
  }
4799
4755
  lineBlockAtHeight(height) {
@@ -5064,7 +5020,7 @@ const baseTheme = buildTheme("." + baseThemeID, {
5064
5020
  color: "inherit",
5065
5021
  fontSize: "70%",
5066
5022
  padding: ".2em 1em",
5067
- borderRadius: "3px"
5023
+ borderRadius: "1px"
5068
5024
  },
5069
5025
  "&light .cm-button": {
5070
5026
  backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
@@ -5386,6 +5342,7 @@ class DOMObserver {
5386
5342
  for (let dom of this.scrollTargets)
5387
5343
  dom.removeEventListener("scroll", this.onScroll);
5388
5344
  window.removeEventListener("scroll", this.onScroll);
5345
+ this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5389
5346
  clearTimeout(this.parentCheck);
5390
5347
  clearTimeout(this.resizeTimeout);
5391
5348
  }
@@ -5682,6 +5639,7 @@ class EditorView {
5682
5639
  */
5683
5640
  config = {}) {
5684
5641
  this.plugins = [];
5642
+ this.pluginMap = new Map;
5685
5643
  this.editorAttrs = {};
5686
5644
  this.contentAttrs = {};
5687
5645
  this.bidiCache = [];
@@ -5851,6 +5809,7 @@ class EditorView {
5851
5809
  plugin.destroy(this);
5852
5810
  this.viewState = new ViewState(newState);
5853
5811
  this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5812
+ this.pluginMap.clear();
5854
5813
  this.docView = new DocView(this);
5855
5814
  this.inputState.ensureHandlers(this);
5856
5815
  this.mountStyles();
@@ -5881,6 +5840,7 @@ class EditorView {
5881
5840
  if (plugin.mustUpdate != update)
5882
5841
  plugin.destroy(this);
5883
5842
  this.plugins = newPlugins;
5843
+ this.pluginMap.clear();
5884
5844
  this.inputState.ensureHandlers(this);
5885
5845
  }
5886
5846
  else {
@@ -5888,7 +5848,7 @@ class EditorView {
5888
5848
  p.mustUpdate = update;
5889
5849
  }
5890
5850
  for (let i = 0; i < this.plugins.length; i++)
5891
- this.plugins[i] = this.plugins[i].update(this);
5851
+ this.plugins[i].update(this);
5892
5852
  }
5893
5853
  /**
5894
5854
  @internal
@@ -5977,7 +5937,7 @@ class EditorView {
5977
5937
  this.state.facet(theme);
5978
5938
  }
5979
5939
  updateAttrs() {
5980
- let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5940
+ let editorAttrs = attrsFromFacet(this, editorAttributes, {
5981
5941
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5982
5942
  });
5983
5943
  let contentAttrs = {
@@ -5993,7 +5953,7 @@ class EditorView {
5993
5953
  };
5994
5954
  if (this.state.readOnly)
5995
5955
  contentAttrs["aria-readonly"] = "true";
5996
- combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5956
+ attrsFromFacet(this, contentAttributes, contentAttrs);
5997
5957
  this.observer.ignore(() => {
5998
5958
  updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5999
5959
  updateAttrs(this.dom, this.editorAttrs, editorAttrs);
@@ -6062,19 +6022,26 @@ class EditorView {
6062
6022
  the return value of this method.
6063
6023
  */
6064
6024
  plugin(plugin) {
6065
- for (let inst of this.plugins)
6066
- if (inst.spec == plugin)
6067
- return inst.update(this).value;
6068
- return null;
6025
+ let known = this.pluginMap.get(plugin);
6026
+ if (known === undefined || known && known.spec != plugin)
6027
+ this.pluginMap.set(plugin, known = this.plugins.find(p => p.spec == plugin) || null);
6028
+ return known && known.update(this).value;
6069
6029
  }
6070
6030
  /**
6071
6031
  The top position of the document, in screen coordinates. This
6072
- may be negative when the editor is scrolled down.
6032
+ may be negative when the editor is scrolled down. Points
6033
+ directly to the top of the first line, not above the padding.
6073
6034
  */
6074
6035
  get documentTop() {
6075
6036
  return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6076
6037
  }
6077
6038
  /**
6039
+ Reports the padding above and below the document.
6040
+ */
6041
+ get documentPadding() {
6042
+ return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
6043
+ }
6044
+ /**
6078
6045
  Find the line or block widget at the given vertical position.
6079
6046
 
6080
6047
  By default, this position is interpreted as a screen position,
@@ -6545,6 +6512,14 @@ class CachedOrder {
6545
6512
  return result;
6546
6513
  }
6547
6514
  }
6515
+ function attrsFromFacet(view, facet, base) {
6516
+ for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
6517
+ let source = sources[i], value = typeof source == "function" ? source(view) : source;
6518
+ if (value)
6519
+ combineAttrs(value, base);
6520
+ }
6521
+ return base;
6522
+ }
6548
6523
 
6549
6524
  const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6550
6525
  function normalizeKeyName(name, platform) {
@@ -7197,35 +7172,29 @@ class TabWidget extends WidgetType {
7197
7172
  }
7198
7173
 
7199
7174
  const plugin = ViewPlugin.fromClass(class {
7200
- constructor(view) {
7201
- this.height = -1;
7202
- this.measure = {
7203
- read: view => Math.max(0, view.scrollDOM.clientHeight - view.defaultLineHeight),
7204
- write: (value, view) => {
7205
- if (Math.abs(value - this.height) > 1) {
7206
- this.height = value;
7207
- view.contentDOM.style.paddingBottom = value + "px";
7208
- }
7209
- }
7210
- };
7211
- view.requestMeasure(this.measure);
7175
+ constructor() {
7176
+ this.height = 1000;
7177
+ this.attrs = { style: "padding-bottom: 1000px" };
7212
7178
  }
7213
7179
  update(update) {
7214
- if (update.geometryChanged)
7215
- update.view.requestMeasure(this.measure);
7180
+ let height = update.view.viewState.editorHeight - update.view.defaultLineHeight;
7181
+ if (height != this.height) {
7182
+ this.height = height;
7183
+ this.attrs = { style: `padding-bottom: ${height}px` };
7184
+ }
7216
7185
  }
7217
7186
  });
7218
7187
  /**
7219
- Returns a plugin that makes sure the content has a bottom margin
7220
- equivalent to the height of the editor, minus one line height, so
7221
- that every line in the document can be scrolled to the top of the
7222
- editor.
7188
+ Returns an extension that makes sure the content has a bottom
7189
+ margin equivalent to the height of the editor, minus one line
7190
+ height, so that every line in the document can be scrolled to the
7191
+ top of the editor.
7223
7192
 
7224
7193
  This is only meaningful when the editor is scrollable, and should
7225
7194
  not be enabled in editors that take the size of their content.
7226
7195
  */
7227
7196
  function scrollPastEnd() {
7228
- return plugin;
7197
+ return [plugin, contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null; })];
7229
7198
  }
7230
7199
 
7231
7200
  /**