@codemirror/view 0.19.25 → 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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.19.26 (2021-12-03)
2
+
3
+ ### New features
4
+
5
+ Widgets can now define a `destroy` method that is called when they are removed from the view.
6
+
1
7
  ## 0.19.25 (2021-12-02)
2
8
 
3
9
  ### Bug fixes
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.
@@ -1167,12 +1182,12 @@ class PointDecoration extends Decoration {
1167
1182
  super(startSide, endSide, widget, spec);
1168
1183
  this.block = block;
1169
1184
  this.isReplace = isReplace;
1170
- 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;
1171
1186
  }
1172
1187
  // Only relevant when this.block == true
1173
1188
  get type() {
1174
1189
  return this.startSide < this.endSide ? exports.BlockType.WidgetRange
1175
- : this.startSide < 0 ? exports.BlockType.WidgetBefore : exports.BlockType.WidgetAfter;
1190
+ : this.startSide <= 0 ? exports.BlockType.WidgetBefore : exports.BlockType.WidgetAfter;
1176
1191
  }
1177
1192
  get heightRelevant() { return this.block || !!this.widget && this.widget.estimatedHeight >= 5; }
1178
1193
  eq(other) {
@@ -1182,7 +1197,7 @@ class PointDecoration extends Decoration {
1182
1197
  this.startSide == other.startSide && this.endSide == other.endSide;
1183
1198
  }
1184
1199
  range(from, to = from) {
1185
- 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)))
1186
1201
  throw new RangeError("Invalid range for replacement decoration");
1187
1202
  if (!this.isReplace && to != from)
1188
1203
  throw new RangeError("Widget decorations can only have zero-length ranges");
@@ -1219,16 +1234,16 @@ class LineView extends ContentView {
1219
1234
  this.breakAfter = 0;
1220
1235
  }
1221
1236
  // Consumes source
1222
- merge(from, to, source, takeDeco, openStart, openEnd) {
1237
+ merge(from, to, source, hasStart, openStart, openEnd) {
1223
1238
  if (source) {
1224
1239
  if (!(source instanceof LineView))
1225
1240
  return false;
1226
1241
  if (!this.dom)
1227
1242
  source.transferDOM(this); // Reuse source.dom when appropriate
1228
1243
  }
1229
- if (takeDeco)
1244
+ if (hasStart)
1230
1245
  this.setDeco(source ? source.attrs : null);
1231
- mergeInlineChildren(this, from, to, source ? source.children : none$1, openStart, openEnd);
1246
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart, openEnd);
1232
1247
  return true;
1233
1248
  }
1234
1249
  split(at) {
@@ -1238,16 +1253,14 @@ class LineView extends ContentView {
1238
1253
  return end;
1239
1254
  let { i, off } = this.childPos(at);
1240
1255
  if (off) {
1241
- end.append(this.children[i].slice(off), 0);
1242
- 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);
1243
1258
  i++;
1244
1259
  }
1245
1260
  for (let j = i; j < this.children.length; j++)
1246
1261
  end.append(this.children[j], 0);
1247
- while (i > 0 && this.children[i - 1].length == 0) {
1248
- this.children[i - 1].parent = null;
1249
- i--;
1250
- }
1262
+ while (i > 0 && this.children[i - 1].length == 0)
1263
+ this.children[--i].destroy();
1251
1264
  this.children.length = i;
1252
1265
  this.markDirty();
1253
1266
  this.length = at;
@@ -1270,7 +1283,6 @@ class LineView extends ContentView {
1270
1283
  this.attrs = attrs;
1271
1284
  }
1272
1285
  }
1273
- // Only called when building a line view in ContentBuilder
1274
1286
  append(child, openStart) {
1275
1287
  joinInlineInto(this, child, openStart);
1276
1288
  }
@@ -1327,7 +1339,7 @@ class LineView extends ContentView {
1327
1339
  coordsAt(pos, side) {
1328
1340
  return coordsInChildren(this, pos, side);
1329
1341
  }
1330
- match(_other) { return false; }
1342
+ become(_other) { return false; }
1331
1343
  get type() { return exports.BlockType.Text; }
1332
1344
  static find(docView, pos) {
1333
1345
  for (let i = 0, off = 0;; i++) {
@@ -1342,7 +1354,6 @@ class LineView extends ContentView {
1342
1354
  }
1343
1355
  }
1344
1356
  }
1345
- const none$1 = [];
1346
1357
  class BlockWidgetView extends ContentView {
1347
1358
  constructor(widget, length, type) {
1348
1359
  super();
@@ -1368,7 +1379,7 @@ class BlockWidgetView extends ContentView {
1368
1379
  end.breakAfter = this.breakAfter;
1369
1380
  return end;
1370
1381
  }
1371
- get children() { return none$1; }
1382
+ get children() { return noChildren; }
1372
1383
  sync() {
1373
1384
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1374
1385
  this.setDOM(this.widget.toDOM(this.editorView));
@@ -1379,7 +1390,7 @@ class BlockWidgetView extends ContentView {
1379
1390
  return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : state.Text.empty;
1380
1391
  }
1381
1392
  domBoundsAround() { return null; }
1382
- match(other) {
1393
+ become(other) {
1383
1394
  if (other instanceof BlockWidgetView && other.type == this.type &&
1384
1395
  other.widget.constructor == this.widget.constructor) {
1385
1396
  if (!other.widget.eq(this.widget))
@@ -1393,6 +1404,11 @@ class BlockWidgetView extends ContentView {
1393
1404
  }
1394
1405
  ignoreMutation() { return true; }
1395
1406
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1407
+ destroy() {
1408
+ super.destroy();
1409
+ if (this.dom)
1410
+ this.widget.destroy(this.dom);
1411
+ }
1396
1412
  }
1397
1413
 
1398
1414
  class ContentBuilder {
@@ -2045,71 +2061,9 @@ class DocView extends ContentView {
2045
2061
  let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2046
2062
  let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2047
2063
  let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2048
- this.replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2064
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2049
2065
  }
2050
2066
  }
2051
- replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd) {
2052
- let before = this.children[fromI], last = content.length ? content[content.length - 1] : null;
2053
- let breakAtEnd = last ? last.breakAfter : breakAtStart;
2054
- // Change within a single line
2055
- if (fromI == toI && !breakAtStart && !breakAtEnd && content.length < 2 &&
2056
- before.merge(fromOff, toOff, content.length ? last : null, fromOff == 0, openStart, openEnd))
2057
- return;
2058
- let after = this.children[toI];
2059
- // Make sure the end of the line after the update is preserved in `after`
2060
- if (toOff < after.length) {
2061
- // If we're splitting a line, separate part of the start line to
2062
- // avoid that being mangled when updating the start line.
2063
- if (fromI == toI) {
2064
- after = after.split(toOff);
2065
- toOff = 0;
2066
- }
2067
- // If the element after the replacement should be merged with
2068
- // the last replacing element, update `content`
2069
- if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
2070
- content[content.length - 1] = after;
2071
- }
2072
- else {
2073
- // Remove the start of the after element, if necessary, and
2074
- // add it to `content`.
2075
- if (toOff)
2076
- after.merge(0, toOff, null, false, 0, openEnd);
2077
- content.push(after);
2078
- }
2079
- }
2080
- else if (after.breakAfter) {
2081
- // The element at `toI` is entirely covered by this range.
2082
- // Preserve its line break, if any.
2083
- if (last)
2084
- last.breakAfter = 1;
2085
- else
2086
- breakAtStart = 1;
2087
- }
2088
- // Since we've handled the next element from the current elements
2089
- // now, make sure `toI` points after that.
2090
- toI++;
2091
- before.breakAfter = breakAtStart;
2092
- if (fromOff > 0) {
2093
- if (!breakAtStart && content.length && before.merge(fromOff, before.length, content[0], false, openStart, 0)) {
2094
- before.breakAfter = content.shift().breakAfter;
2095
- }
2096
- else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
2097
- before.merge(fromOff, before.length, null, false, openStart, 0);
2098
- }
2099
- fromI++;
2100
- }
2101
- // Try to merge widgets on the boundaries of the replacement
2102
- while (fromI < toI && content.length) {
2103
- if (this.children[toI - 1].match(content[content.length - 1]))
2104
- toI--, content.pop();
2105
- else if (this.children[fromI].match(content[0]))
2106
- fromI++, content.shift();
2107
- else
2108
- break;
2109
- }
2110
- if (fromI < toI || content.length)
2111
- this.replaceChildren(fromI, toI, content);
2112
- }
2113
2067
  // Sync the DOM selection to this.state.selection
2114
2068
  updateSelection(mustRead = false, fromPointer = false) {
2115
2069
  if (mustRead)
@@ -2372,15 +2326,10 @@ function computeCompositionDeco(view, changes) {
2372
2326
  if (!textNode)
2373
2327
  return Decoration.none;
2374
2328
  let cView = view.docView.nearest(textNode);
2329
+ if (!cView)
2330
+ return Decoration.none;
2375
2331
  let from, to, topNode = textNode;
2376
- if (cView instanceof InlineView) {
2377
- while (cView.parent instanceof InlineView)
2378
- cView = cView.parent;
2379
- from = cView.posAtStart;
2380
- to = from + cView.length;
2381
- topNode = cView.dom;
2382
- }
2383
- else if (cView instanceof LineView) {
2332
+ if (cView instanceof LineView) {
2384
2333
  while (topNode.parentNode != cView.dom)
2385
2334
  topNode = topNode.parentNode;
2386
2335
  let prev = topNode.previousSibling;
@@ -2389,7 +2338,17 @@ function computeCompositionDeco(view, changes) {
2389
2338
  from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2390
2339
  }
2391
2340
  else {
2392
- 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;
2393
2352
  }
2394
2353
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2395
2354
  let text = textNode.nodeValue, { state } = view;
package/dist/index.d.ts CHANGED
@@ -171,6 +171,11 @@ declare abstract class WidgetType {
171
171
  events.
172
172
  */
173
173
  ignoreEvent(_event: Event): boolean;
174
+ /**
175
+ This is called when the an instance of the widget is removed
176
+ from the editor view.
177
+ */
178
+ destroy(_dom: HTMLElement): void;
174
179
  }
175
180
  /**
176
181
  A decoration set represents a collection of decorated ranges,
package/dist/index.js CHANGED
@@ -270,7 +270,7 @@ class DOMPos {
270
270
  static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
271
271
  static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
272
272
  }
273
- const none$3 = [];
273
+ const noChildren = [];
274
274
  class ContentView {
275
275
  constructor() {
276
276
  this.parent = null;
@@ -436,12 +436,12 @@ class ContentView {
436
436
  v = parent;
437
437
  }
438
438
  }
439
- replaceChildren(from, to, children = none$3) {
439
+ replaceChildren(from, to, children = noChildren) {
440
440
  this.markDirty();
441
441
  for (let i = from; i < to; i++) {
442
442
  let child = this.children[i];
443
443
  if (child.parent == this)
444
- child.parent = null;
444
+ child.destroy();
445
445
  }
446
446
  this.children.splice(from, to - from, ...children);
447
447
  for (let i = 0; i < children.length; i++)
@@ -463,6 +463,17 @@ class ContentView {
463
463
  }
464
464
  static get(node) { return node.cmView; }
465
465
  get isEditable() { return true; }
466
+ merge(from, to, source, hasStart, openStart, openEnd) {
467
+ return false;
468
+ }
469
+ become(other) { return false; }
470
+ // When this is a zero-length view with a side, this should return a
471
+ // number <= 0 to indicate it is before its position, or a
472
+ // number > 0 when after its position.
473
+ getSide() { return 0; }
474
+ destroy() {
475
+ this.parent = null;
476
+ }
466
477
  }
467
478
  ContentView.prototype.breakAfter = 0;
468
479
  // Remove a DOM node and return its next sibling.
@@ -490,6 +501,94 @@ class ChildCursor {
490
501
  }
491
502
  }
492
503
  }
504
+ function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
505
+ let { children } = parent;
506
+ let before = children.length ? children[fromI] : null;
507
+ let last = insert.length ? insert[insert.length - 1] : null;
508
+ let breakAtEnd = last ? last.breakAfter : breakAtStart;
509
+ // Change within a single child
510
+ if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
511
+ before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
512
+ return;
513
+ if (toI < children.length) {
514
+ let after = children[toI];
515
+ // Make sure the end of the child after the update is preserved in `after`
516
+ if (after && toOff < after.length) {
517
+ // If we're splitting a child, separate part of it to avoid that
518
+ // being mangled when updating the child before the update.
519
+ if (fromI == toI) {
520
+ after = after.split(toOff);
521
+ toOff = 0;
522
+ }
523
+ // If the element after the replacement should be merged with
524
+ // the last replacing element, update `content`
525
+ if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
526
+ insert[insert.length - 1] = after;
527
+ }
528
+ else {
529
+ // Remove the start of the after element, if necessary, and
530
+ // add it to `content`.
531
+ if (toOff)
532
+ after.merge(0, toOff, null, false, 0, openEnd);
533
+ insert.push(after);
534
+ }
535
+ }
536
+ else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
537
+ // The element at `toI` is entirely covered by this range.
538
+ // Preserve its line break, if any.
539
+ if (last)
540
+ last.breakAfter = 1;
541
+ else
542
+ breakAtStart = 1;
543
+ }
544
+ // Since we've handled the next element from the current elements
545
+ // now, make sure `toI` points after that.
546
+ toI++;
547
+ }
548
+ if (before) {
549
+ before.breakAfter = breakAtStart;
550
+ if (fromOff > 0) {
551
+ if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
552
+ before.breakAfter = insert.shift().breakAfter;
553
+ }
554
+ else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
555
+ before.merge(fromOff, before.length, null, false, openStart, 0);
556
+ }
557
+ fromI++;
558
+ }
559
+ }
560
+ // Try to merge widgets on the boundaries of the replacement
561
+ while (fromI < toI && insert.length) {
562
+ if (children[toI - 1].become(insert[insert.length - 1])) {
563
+ toI--;
564
+ insert.pop();
565
+ openEnd = insert.length ? 0 : openStart;
566
+ }
567
+ else if (children[fromI].become(insert[0])) {
568
+ fromI++;
569
+ insert.shift();
570
+ openStart = insert.length ? 0 : openEnd;
571
+ }
572
+ else {
573
+ break;
574
+ }
575
+ }
576
+ if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
577
+ children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
578
+ fromI--;
579
+ if (fromI < toI || insert.length)
580
+ parent.replaceChildren(fromI, toI, insert);
581
+ }
582
+ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
583
+ let cur = parent.childCursor();
584
+ let { i: toI, off: toOff } = cur.findPos(to, 1);
585
+ let { i: fromI, off: fromOff } = cur.findPos(from, -1);
586
+ let dLen = from - to;
587
+ for (let view of insert)
588
+ dLen += view.length;
589
+ parent.length += dLen;
590
+ replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
591
+ }
493
592
 
494
593
  let [nav, doc] = typeof navigator != "undefined"
495
594
  ? [navigator, document]
@@ -521,21 +620,8 @@ var browser = {
521
620
  tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
522
621
  };
523
622
 
524
- const none$2 = [];
525
- class InlineView extends ContentView {
526
- /**
527
- Return true when this view is equivalent to `other` and can take
528
- on its role.
529
- */
530
- become(_other) { return false; }
531
- // When this is a zero-length view with a side, this should return a
532
- // negative number to indicate it is before its position, or a
533
- // positive number when after its position.
534
- getSide() { return 0; }
535
- }
536
- InlineView.prototype.children = none$2;
537
623
  const MaxJoinLen = 256;
538
- class TextView extends InlineView {
624
+ class TextView extends ContentView {
539
625
  constructor(text) {
540
626
  super();
541
627
  this.text = text;
@@ -566,7 +652,7 @@ class TextView extends InlineView {
566
652
  this.markDirty();
567
653
  return true;
568
654
  }
569
- slice(from) {
655
+ split(from) {
570
656
  let result = new TextView(this.text.slice(from));
571
657
  this.text = this.text.slice(0, from);
572
658
  return result;
@@ -582,7 +668,7 @@ class TextView extends InlineView {
582
668
  return textCoords(this.dom, pos, side);
583
669
  }
584
670
  }
585
- class MarkView extends InlineView {
671
+ class MarkView extends ContentView {
586
672
  constructor(mark, children = [], length = 0) {
587
673
  super();
588
674
  this.mark = mark;
@@ -605,20 +691,20 @@ class MarkView extends InlineView {
605
691
  this.createDOM();
606
692
  super.sync(track);
607
693
  }
608
- merge(from, to, source, openStart, openEnd) {
694
+ merge(from, to, source, _hasStart, openStart, openEnd) {
609
695
  if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
610
696
  (from && openStart <= 0) || (to < this.length && openEnd <= 0)))
611
697
  return false;
612
- mergeInlineChildren(this, from, to, source ? source.children : none$2, openStart - 1, openEnd - 1);
698
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart - 1, openEnd - 1);
613
699
  this.markDirty();
614
700
  return true;
615
701
  }
616
- slice(from) {
702
+ split(from) {
617
703
  let result = [], off = 0, detachFrom = -1, i = 0;
618
704
  for (let elt of this.children) {
619
705
  let end = off + elt.length;
620
706
  if (end > from)
621
- result.push(off < from ? elt.slice(from - off) : elt);
707
+ result.push(off < from ? elt.split(from - off) : elt);
622
708
  if (detachFrom < 0 && off >= from)
623
709
  detachFrom = i;
624
710
  off = end;
@@ -626,8 +712,10 @@ class MarkView extends InlineView {
626
712
  }
627
713
  let length = this.length - from;
628
714
  this.length = from;
629
- if (detachFrom > -1)
630
- this.replaceChildren(detachFrom, this.children.length);
715
+ if (detachFrom > -1) {
716
+ this.children.length = detachFrom;
717
+ this.markDirty();
718
+ }
631
719
  return new MarkView(this.mark, result, length);
632
720
  }
633
721
  domAtPos(pos) {
@@ -669,7 +757,7 @@ function textCoords(text, pos, side) {
669
757
  return flatten ? flattenRect(rect, flatten < 0) : rect || null;
670
758
  }
671
759
  // Also used for collapsed ranges that don't have a placeholder widget!
672
- class WidgetView extends InlineView {
760
+ class WidgetView extends ContentView {
673
761
  constructor(widget, length, side) {
674
762
  super();
675
763
  this.widget = widget;
@@ -679,7 +767,7 @@ class WidgetView extends InlineView {
679
767
  static create(widget, length, side) {
680
768
  return new (widget.customView || WidgetView)(widget, length, side);
681
769
  }
682
- slice(from) {
770
+ split(from) {
683
771
  let result = WidgetView.create(this.widget, this.length - from, this.side);
684
772
  this.length -= from;
685
773
  return result;
@@ -691,7 +779,7 @@ class WidgetView extends InlineView {
691
779
  }
692
780
  }
693
781
  getSide() { return this.side; }
694
- merge(from, to, source, openStart, openEnd) {
782
+ merge(from, to, source, hasStart, openStart, openEnd) {
695
783
  if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
696
784
  from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
697
785
  return false;
@@ -736,6 +824,11 @@ class WidgetView extends InlineView {
736
824
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
737
825
  }
738
826
  get isEditable() { return false; }
827
+ destroy() {
828
+ super.destroy();
829
+ if (this.dom)
830
+ this.widget.destroy(this.dom);
831
+ }
739
832
  }
740
833
  class CompositionView extends WidgetView {
741
834
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -752,7 +845,7 @@ class CompositionView extends WidgetView {
752
845
  // These are drawn around uneditable widgets to avoid a number of
753
846
  // browser bugs that show up when the cursor is directly next to
754
847
  // uneditable inline content.
755
- class WidgetBufferView extends InlineView {
848
+ class WidgetBufferView extends ContentView {
756
849
  constructor(side) {
757
850
  super();
758
851
  this.side = side;
@@ -762,7 +855,7 @@ class WidgetBufferView extends InlineView {
762
855
  become(other) {
763
856
  return other instanceof WidgetBufferView && other.side == this.side;
764
857
  }
765
- slice() { return new WidgetBufferView(this.side); }
858
+ split() { return new WidgetBufferView(this.side); }
766
859
  sync() {
767
860
  if (!this.dom)
768
861
  this.setDOM(document.createTextNode("\u200b"));
@@ -780,90 +873,7 @@ class WidgetBufferView extends InlineView {
780
873
  return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
781
874
  }
782
875
  }
783
- function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
784
- let cur = parent.childCursor();
785
- let { i: toI, off: toOff } = cur.findPos(to, 1);
786
- let { i: fromI, off: fromOff } = cur.findPos(from, -1);
787
- let dLen = from - to;
788
- for (let view of elts)
789
- dLen += view.length;
790
- parent.length += dLen;
791
- let { children } = parent;
792
- // Both from and to point into the same child view
793
- if (fromI == toI && fromOff) {
794
- let start = children[fromI];
795
- // Maybe just update that view and be done
796
- if (elts.length == 1 && start.merge(fromOff, toOff, elts[0], openStart, openEnd))
797
- return;
798
- if (elts.length == 0) {
799
- start.merge(fromOff, toOff, null, openStart, openEnd);
800
- return;
801
- }
802
- // Otherwise split it, so that we don't have to worry about aliasing front/end afterwards
803
- let after = start.slice(toOff);
804
- if (after.merge(0, 0, elts[elts.length - 1], 0, openEnd))
805
- elts[elts.length - 1] = after;
806
- else
807
- elts.push(after);
808
- toI++;
809
- openEnd = toOff = 0;
810
- }
811
- // Make sure start and end positions fall on node boundaries
812
- // (fromOff/toOff are no longer used after this), and that if the
813
- // start or end of the elts can be merged with adjacent nodes,
814
- // this is done
815
- if (toOff) {
816
- let end = children[toI];
817
- if (elts.length && end.merge(0, toOff, elts[elts.length - 1], 0, openEnd)) {
818
- elts.pop();
819
- openEnd = elts.length ? 0 : openStart;
820
- }
821
- else {
822
- end.merge(0, toOff, null, 0, 0);
823
- }
824
- }
825
- else if (toI < children.length && elts.length &&
826
- children[toI].merge(0, 0, elts[elts.length - 1], 0, openEnd)) {
827
- elts.pop();
828
- openEnd = elts.length ? 0 : openStart;
829
- }
830
- if (fromOff) {
831
- let start = children[fromI];
832
- if (elts.length && start.merge(fromOff, start.length, elts[0], openStart, 0)) {
833
- elts.shift();
834
- openStart = elts.length ? 0 : openEnd;
835
- }
836
- else {
837
- start.merge(fromOff, start.length, null, 0, 0);
838
- }
839
- fromI++;
840
- }
841
- else if (fromI && elts.length) {
842
- let end = children[fromI - 1];
843
- if (end.merge(end.length, end.length, elts[0], openStart, 0)) {
844
- elts.shift();
845
- openStart = elts.length ? 0 : openEnd;
846
- }
847
- }
848
- // Then try to merge any mergeable nodes at the start and end of
849
- // the changed range
850
- while (fromI < toI && elts.length && children[toI - 1].become(elts[elts.length - 1])) {
851
- elts.pop();
852
- toI--;
853
- openEnd = elts.length ? 0 : openStart;
854
- }
855
- while (fromI < toI && elts.length && children[fromI].become(elts[0])) {
856
- elts.shift();
857
- fromI++;
858
- openStart = elts.length ? 0 : openEnd;
859
- }
860
- if (!elts.length && fromI && toI < children.length &&
861
- children[toI].merge(0, 0, children[fromI - 1], openStart, openEnd))
862
- fromI--;
863
- // And if anything remains, splice the child array to insert the new elts
864
- if (elts.length || fromI != toI)
865
- parent.replaceChildren(fromI, toI, elts);
866
- }
876
+ TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
867
877
  function inlineDOMAtPos(dom, children, pos) {
868
878
  let i = 0;
869
879
  for (let off = 0; i < children.length; i++) {
@@ -1004,6 +1014,11 @@ class WidgetType {
1004
1014
  @internal
1005
1015
  */
1006
1016
  get customView() { return null; }
1017
+ /**
1018
+ This is called when the an instance of the widget is removed
1019
+ from the editor view.
1020
+ */
1021
+ destroy(_dom) { }
1007
1022
  }
1008
1023
  /**
1009
1024
  The different types of blocks that can occur in an editor view.
@@ -1163,12 +1178,12 @@ class PointDecoration extends Decoration {
1163
1178
  super(startSide, endSide, widget, spec);
1164
1179
  this.block = block;
1165
1180
  this.isReplace = isReplace;
1166
- this.mapMode = !block ? MapMode.TrackDel : startSide < 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
1181
+ this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
1167
1182
  }
1168
1183
  // Only relevant when this.block == true
1169
1184
  get type() {
1170
1185
  return this.startSide < this.endSide ? BlockType.WidgetRange
1171
- : this.startSide < 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
1186
+ : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
1172
1187
  }
1173
1188
  get heightRelevant() { return this.block || !!this.widget && this.widget.estimatedHeight >= 5; }
1174
1189
  eq(other) {
@@ -1178,7 +1193,7 @@ class PointDecoration extends Decoration {
1178
1193
  this.startSide == other.startSide && this.endSide == other.endSide;
1179
1194
  }
1180
1195
  range(from, to = from) {
1181
- if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide < 0)))
1196
+ if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
1182
1197
  throw new RangeError("Invalid range for replacement decoration");
1183
1198
  if (!this.isReplace && to != from)
1184
1199
  throw new RangeError("Widget decorations can only have zero-length ranges");
@@ -1215,16 +1230,16 @@ class LineView extends ContentView {
1215
1230
  this.breakAfter = 0;
1216
1231
  }
1217
1232
  // Consumes source
1218
- merge(from, to, source, takeDeco, openStart, openEnd) {
1233
+ merge(from, to, source, hasStart, openStart, openEnd) {
1219
1234
  if (source) {
1220
1235
  if (!(source instanceof LineView))
1221
1236
  return false;
1222
1237
  if (!this.dom)
1223
1238
  source.transferDOM(this); // Reuse source.dom when appropriate
1224
1239
  }
1225
- if (takeDeco)
1240
+ if (hasStart)
1226
1241
  this.setDeco(source ? source.attrs : null);
1227
- mergeInlineChildren(this, from, to, source ? source.children : none$1, openStart, openEnd);
1242
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart, openEnd);
1228
1243
  return true;
1229
1244
  }
1230
1245
  split(at) {
@@ -1234,16 +1249,14 @@ class LineView extends ContentView {
1234
1249
  return end;
1235
1250
  let { i, off } = this.childPos(at);
1236
1251
  if (off) {
1237
- end.append(this.children[i].slice(off), 0);
1238
- this.children[i].merge(off, this.children[i].length, null, 0, 0);
1252
+ end.append(this.children[i].split(off), 0);
1253
+ this.children[i].merge(off, this.children[i].length, null, false, 0, 0);
1239
1254
  i++;
1240
1255
  }
1241
1256
  for (let j = i; j < this.children.length; j++)
1242
1257
  end.append(this.children[j], 0);
1243
- while (i > 0 && this.children[i - 1].length == 0) {
1244
- this.children[i - 1].parent = null;
1245
- i--;
1246
- }
1258
+ while (i > 0 && this.children[i - 1].length == 0)
1259
+ this.children[--i].destroy();
1247
1260
  this.children.length = i;
1248
1261
  this.markDirty();
1249
1262
  this.length = at;
@@ -1266,7 +1279,6 @@ class LineView extends ContentView {
1266
1279
  this.attrs = attrs;
1267
1280
  }
1268
1281
  }
1269
- // Only called when building a line view in ContentBuilder
1270
1282
  append(child, openStart) {
1271
1283
  joinInlineInto(this, child, openStart);
1272
1284
  }
@@ -1323,7 +1335,7 @@ class LineView extends ContentView {
1323
1335
  coordsAt(pos, side) {
1324
1336
  return coordsInChildren(this, pos, side);
1325
1337
  }
1326
- match(_other) { return false; }
1338
+ become(_other) { return false; }
1327
1339
  get type() { return BlockType.Text; }
1328
1340
  static find(docView, pos) {
1329
1341
  for (let i = 0, off = 0;; i++) {
@@ -1338,7 +1350,6 @@ class LineView extends ContentView {
1338
1350
  }
1339
1351
  }
1340
1352
  }
1341
- const none$1 = [];
1342
1353
  class BlockWidgetView extends ContentView {
1343
1354
  constructor(widget, length, type) {
1344
1355
  super();
@@ -1364,7 +1375,7 @@ class BlockWidgetView extends ContentView {
1364
1375
  end.breakAfter = this.breakAfter;
1365
1376
  return end;
1366
1377
  }
1367
- get children() { return none$1; }
1378
+ get children() { return noChildren; }
1368
1379
  sync() {
1369
1380
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1370
1381
  this.setDOM(this.widget.toDOM(this.editorView));
@@ -1375,7 +1386,7 @@ class BlockWidgetView extends ContentView {
1375
1386
  return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : Text$1.empty;
1376
1387
  }
1377
1388
  domBoundsAround() { return null; }
1378
- match(other) {
1389
+ become(other) {
1379
1390
  if (other instanceof BlockWidgetView && other.type == this.type &&
1380
1391
  other.widget.constructor == this.widget.constructor) {
1381
1392
  if (!other.widget.eq(this.widget))
@@ -1389,6 +1400,11 @@ class BlockWidgetView extends ContentView {
1389
1400
  }
1390
1401
  ignoreMutation() { return true; }
1391
1402
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1403
+ destroy() {
1404
+ super.destroy();
1405
+ if (this.dom)
1406
+ this.widget.destroy(this.dom);
1407
+ }
1392
1408
  }
1393
1409
 
1394
1410
  class ContentBuilder {
@@ -2041,71 +2057,9 @@ class DocView extends ContentView {
2041
2057
  let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2042
2058
  let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2043
2059
  let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2044
- this.replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2060
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2045
2061
  }
2046
2062
  }
2047
- replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd) {
2048
- let before = this.children[fromI], last = content.length ? content[content.length - 1] : null;
2049
- let breakAtEnd = last ? last.breakAfter : breakAtStart;
2050
- // Change within a single line
2051
- if (fromI == toI && !breakAtStart && !breakAtEnd && content.length < 2 &&
2052
- before.merge(fromOff, toOff, content.length ? last : null, fromOff == 0, openStart, openEnd))
2053
- return;
2054
- let after = this.children[toI];
2055
- // Make sure the end of the line after the update is preserved in `after`
2056
- if (toOff < after.length) {
2057
- // If we're splitting a line, separate part of the start line to
2058
- // avoid that being mangled when updating the start line.
2059
- if (fromI == toI) {
2060
- after = after.split(toOff);
2061
- toOff = 0;
2062
- }
2063
- // If the element after the replacement should be merged with
2064
- // the last replacing element, update `content`
2065
- if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
2066
- content[content.length - 1] = after;
2067
- }
2068
- else {
2069
- // Remove the start of the after element, if necessary, and
2070
- // add it to `content`.
2071
- if (toOff)
2072
- after.merge(0, toOff, null, false, 0, openEnd);
2073
- content.push(after);
2074
- }
2075
- }
2076
- else if (after.breakAfter) {
2077
- // The element at `toI` is entirely covered by this range.
2078
- // Preserve its line break, if any.
2079
- if (last)
2080
- last.breakAfter = 1;
2081
- else
2082
- breakAtStart = 1;
2083
- }
2084
- // Since we've handled the next element from the current elements
2085
- // now, make sure `toI` points after that.
2086
- toI++;
2087
- before.breakAfter = breakAtStart;
2088
- if (fromOff > 0) {
2089
- if (!breakAtStart && content.length && before.merge(fromOff, before.length, content[0], false, openStart, 0)) {
2090
- before.breakAfter = content.shift().breakAfter;
2091
- }
2092
- else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
2093
- before.merge(fromOff, before.length, null, false, openStart, 0);
2094
- }
2095
- fromI++;
2096
- }
2097
- // Try to merge widgets on the boundaries of the replacement
2098
- while (fromI < toI && content.length) {
2099
- if (this.children[toI - 1].match(content[content.length - 1]))
2100
- toI--, content.pop();
2101
- else if (this.children[fromI].match(content[0]))
2102
- fromI++, content.shift();
2103
- else
2104
- break;
2105
- }
2106
- if (fromI < toI || content.length)
2107
- this.replaceChildren(fromI, toI, content);
2108
- }
2109
2063
  // Sync the DOM selection to this.state.selection
2110
2064
  updateSelection(mustRead = false, fromPointer = false) {
2111
2065
  if (mustRead)
@@ -2368,15 +2322,10 @@ function computeCompositionDeco(view, changes) {
2368
2322
  if (!textNode)
2369
2323
  return Decoration.none;
2370
2324
  let cView = view.docView.nearest(textNode);
2325
+ if (!cView)
2326
+ return Decoration.none;
2371
2327
  let from, to, topNode = textNode;
2372
- if (cView instanceof InlineView) {
2373
- while (cView.parent instanceof InlineView)
2374
- cView = cView.parent;
2375
- from = cView.posAtStart;
2376
- to = from + cView.length;
2377
- topNode = cView.dom;
2378
- }
2379
- else if (cView instanceof LineView) {
2328
+ if (cView instanceof LineView) {
2380
2329
  while (topNode.parentNode != cView.dom)
2381
2330
  topNode = topNode.parentNode;
2382
2331
  let prev = topNode.previousSibling;
@@ -2385,7 +2334,17 @@ function computeCompositionDeco(view, changes) {
2385
2334
  from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2386
2335
  }
2387
2336
  else {
2388
- return Decoration.none;
2337
+ for (;;) {
2338
+ let { parent } = cView;
2339
+ if (!parent)
2340
+ return Decoration.none;
2341
+ if (parent instanceof LineView)
2342
+ break;
2343
+ cView = parent;
2344
+ }
2345
+ from = cView.posAtStart;
2346
+ to = from + cView.length;
2347
+ topNode = cView.dom;
2389
2348
  }
2390
2349
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2391
2350
  let text = textNode.nodeValue, { state } = view;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.25",
3
+ "version": "0.19.26",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",