@codemirror/view 0.19.45 → 0.19.48

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,31 @@
1
+ ## 0.19.48 (2022-03-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where DOM syncing could crash when a DOM node was moved from a parent to a child node (via widgets reusing existing nodes).
6
+
7
+ To avoid interfering with things like a vim mode too much, the editor will now only activate the tab-to-move-focus escape hatch after an escape press that wasn't handled by an event handler.
8
+
9
+ Make sure the view measures itself before the page is printed.
10
+
11
+ Tweak types of view plugin defining functions to avoid TypeScript errors when the plugin value doesn't have any of the interface's properties.
12
+
13
+ ## 0.19.47 (2022-03-08)
14
+
15
+ ### Bug fixes
16
+
17
+ Fix an issue where block widgets at the start of the viewport could break height computations.
18
+
19
+ ## 0.19.46 (2022-03-03)
20
+
21
+ ### Bug fixes
22
+
23
+ Fix a bug where block widgets on the edges of viewports could cause the positioning of content to misalign with the gutter and height computations.
24
+
25
+ Improve cursor height next to widgets.
26
+
27
+ Fix a bug where mapping positions to screen coordinates could return incorred coordinates during composition.
28
+
1
29
  ## 0.19.45 (2022-02-23)
2
30
 
3
31
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -22,7 +22,7 @@ function getSelection(root) {
22
22
  return target.getSelection();
23
23
  }
24
24
  function contains(dom, node) {
25
- return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
25
+ return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
26
26
  }
27
27
  function deepActiveElement() {
28
28
  let elt = document.activeElement;
@@ -318,32 +318,34 @@ class ContentView {
318
318
  sync(track) {
319
319
  if (this.dirty & 2 /* Node */) {
320
320
  let parent = this.dom;
321
- let pos = parent.firstChild;
321
+ let prev = null, next;
322
322
  for (let child of this.children) {
323
323
  if (child.dirty) {
324
- if (!child.dom && pos) {
325
- let contentView = ContentView.get(pos);
324
+ if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
325
+ let contentView = ContentView.get(next);
326
326
  if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
327
- child.reuseDOM(pos);
327
+ child.reuseDOM(next);
328
328
  }
329
329
  child.sync(track);
330
330
  child.dirty = 0 /* Not */;
331
331
  }
332
- if (track && !track.written && track.node == parent && pos != child.dom)
332
+ next = prev ? prev.nextSibling : parent.firstChild;
333
+ if (track && !track.written && track.node == parent && next != child.dom)
333
334
  track.written = true;
334
335
  if (child.dom.parentNode == parent) {
335
- while (pos && pos != child.dom)
336
- pos = rm(pos);
337
- pos = child.dom.nextSibling;
336
+ while (next && next != child.dom)
337
+ next = rm(next);
338
338
  }
339
339
  else {
340
- parent.insertBefore(child.dom, pos);
340
+ parent.insertBefore(child.dom, next);
341
341
  }
342
+ prev = child.dom;
342
343
  }
343
- if (pos && track && track.node == parent)
344
+ next = prev ? prev.nextSibling : parent.firstChild;
345
+ if (next && track && track.node == parent)
344
346
  track.written = true;
345
- while (pos)
346
- pos = rm(pos);
347
+ while (next)
348
+ next = rm(next);
347
349
  }
348
350
  else if (this.dirty & 1 /* Child */) {
349
351
  for (let child of this.children)
@@ -856,18 +858,26 @@ class WidgetView extends ContentView {
856
858
  }
857
859
  class CompositionView extends WidgetView {
858
860
  domAtPos(pos) {
859
- let { topView } = this.widget;
861
+ let { topView, text } = this.widget;
860
862
  if (!topView)
861
- return new DOMPos(this.widget.text, Math.min(pos, this.widget.text.nodeValue.length));
862
- return posInCompositionTree(pos, topView, this.widget.text);
863
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
864
+ return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
863
865
  }
864
866
  sync() { this.setDOM(this.widget.toDOM()); }
865
867
  localPosFromDOM(node, offset) {
866
- return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
868
+ let { topView, text } = this.widget;
869
+ if (!topView)
870
+ return Math.min(offset, this.length);
871
+ return posFromDOMInCompositionTree(node, offset, topView, text);
867
872
  }
868
873
  ignoreMutation() { return false; }
869
874
  get overrideDOMText() { return null; }
870
- coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
875
+ coordsAt(pos, side) {
876
+ let { topView, text } = this.widget;
877
+ if (!topView)
878
+ return textCoords(text, pos, side);
879
+ return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
880
+ }
871
881
  destroy() {
872
882
  var _a;
873
883
  super.destroy();
@@ -878,24 +888,38 @@ class CompositionView extends WidgetView {
878
888
  // Uses the old structure of a chunk of content view frozen for
879
889
  // composition to try and find a reasonable DOM location for the given
880
890
  // offset.
881
- function posInCompositionTree(pos, view, text) {
891
+ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
882
892
  if (view instanceof MarkView) {
883
893
  for (let child of view.children) {
884
- let hasComp = child.dom == text || child.dom.contains(text.parentNode);
894
+ let hasComp = contains(child.dom, text);
885
895
  let len = hasComp ? text.nodeValue.length : child.length;
886
896
  if (pos < len || pos == len && child.getSide() <= 0)
887
- return hasComp ? posInCompositionTree(pos, child, text) : child.domAtPos(pos);
897
+ return hasComp ? scanCompositionTree(pos, side, child, text, enterView, fromText) : enterView(child, pos, side);
888
898
  pos -= len;
889
899
  }
890
- return view.domAtPos(view.length);
900
+ return enterView(view, view.length, -1);
891
901
  }
892
902
  else if (view.dom == text) {
893
- return new DOMPos(text, Math.min(pos, text.nodeValue.length));
903
+ return fromText(pos, side);
894
904
  }
895
905
  else {
896
- return view.domAtPos(pos);
906
+ return enterView(view, pos, side);
897
907
  }
898
908
  }
909
+ function posFromDOMInCompositionTree(node, offset, view, text) {
910
+ if (view instanceof MarkView) {
911
+ for (let child of view.children) {
912
+ let pos = 0, hasComp = contains(child.dom, text);
913
+ if (contains(child.dom, node))
914
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
915
+ pos += hasComp ? text.nodeValue.length : child.length;
916
+ }
917
+ }
918
+ else if (view.dom == text) {
919
+ return Math.min(offset, text.nodeValue.length);
920
+ }
921
+ return view.localPosFromDOM(node, offset);
922
+ }
899
923
  // These are drawn around uneditable widgets to avoid a number of
900
924
  // browser bugs that show up when the cursor is directly next to
901
925
  // uneditable inline content.
@@ -914,6 +938,7 @@ class WidgetBufferView extends ContentView {
914
938
  if (!this.dom) {
915
939
  let dom = document.createElement("img");
916
940
  dom.className = "cm-widgetBuffer";
941
+ dom.setAttribute("aria-hidden", "true");
917
942
  this.setDOM(dom);
918
943
  }
919
944
  }
@@ -922,13 +947,43 @@ class WidgetBufferView extends ContentView {
922
947
  localPosFromDOM() { return 0; }
923
948
  domBoundsAround() { return null; }
924
949
  coordsAt(pos) {
925
- return this.dom.getBoundingClientRect();
950
+ let imgRect = this.dom.getBoundingClientRect();
951
+ // Since the <img> height doesn't correspond to text height, try
952
+ // to borrow the height from some sibling node.
953
+ let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
954
+ return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
955
+ ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
926
956
  }
927
957
  get overrideDOMText() {
928
958
  return text.Text.empty;
929
959
  }
930
960
  }
931
961
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
962
+ function inlineSiblingRect(view, side) {
963
+ let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
964
+ while (parent && index >= 0) {
965
+ if (side < 0 ? index > 0 : index < parent.children.length) {
966
+ let next = parent.children[index + side];
967
+ if (next instanceof TextView) {
968
+ let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
969
+ if (nextRect)
970
+ return nextRect;
971
+ }
972
+ index += side;
973
+ }
974
+ else if (parent instanceof MarkView && parent.parent) {
975
+ index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
976
+ parent = parent.parent;
977
+ }
978
+ else {
979
+ let last = parent.dom.lastChild;
980
+ if (last && last.nodeName == "BR")
981
+ return last.getClientRects()[0];
982
+ break;
983
+ }
984
+ }
985
+ return undefined;
986
+ }
932
987
  function inlineDOMAtPos(dom, children, pos) {
933
988
  let i = 0;
934
989
  for (let off = 0; i < children.length; i++) {
@@ -1162,10 +1217,16 @@ class Decoration extends rangeset.RangeValue {
1162
1217
  a widget, or simply hides it.
1163
1218
  */
1164
1219
  static replace(spec) {
1165
- let block = !!spec.block;
1166
- let { start, end } = getInclusive(spec, block);
1167
- let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1168
- let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1220
+ let block = !!spec.block, startSide, endSide;
1221
+ if (spec.isBlockGap) {
1222
+ startSide = -500000000 /* GapStart */;
1223
+ endSide = 400000000 /* GapEnd */;
1224
+ }
1225
+ else {
1226
+ let { start, end } = getInclusive(spec, block);
1227
+ startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 500000000 /* NonIncStart */) - 1;
1228
+ endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -600000000 /* NonIncEnd */) + 1;
1229
+ }
1169
1230
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1170
1231
  }
1171
1232
  /**
@@ -1195,7 +1256,7 @@ Decoration.none = rangeset.RangeSet.empty;
1195
1256
  class MarkDecoration extends Decoration {
1196
1257
  constructor(spec) {
1197
1258
  let { start, end } = getInclusive(spec);
1198
- super(start ? -1 /* InlineIncStart */ : 400000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -500000000 /* NonIncEnd */, null, spec);
1259
+ super(start ? -1 /* InlineIncStart */ : 500000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -600000000 /* NonIncEnd */, null, spec);
1199
1260
  this.tagName = spec.tagName || "span";
1200
1261
  this.class = spec.class || "";
1201
1262
  this.attrs = spec.attributes || null;
@@ -1376,7 +1437,7 @@ class LineView extends ContentView {
1376
1437
  let last = this.dom.lastChild;
1377
1438
  while (last && ContentView.get(last) instanceof MarkView)
1378
1439
  last = last.lastChild;
1379
- if (!last ||
1440
+ if (!last || !this.length ||
1380
1441
  last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1381
1442
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1382
1443
  let hack = document.createElement("BR");
@@ -2774,7 +2835,12 @@ class DocView extends ContentView {
2774
2835
  let end = next ? next.from - 1 : this.length;
2775
2836
  if (end > pos) {
2776
2837
  let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2777
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2838
+ deco.push(Decoration.replace({
2839
+ widget: new BlockGapWidget(height),
2840
+ block: true,
2841
+ inclusive: true,
2842
+ isBlockGap: true,
2843
+ }).range(pos, end));
2778
2844
  }
2779
2845
  if (!next)
2780
2846
  break;
@@ -3309,10 +3375,10 @@ class InputState {
3309
3375
  for (let type in handlers) {
3310
3376
  let handler = handlers[type];
3311
3377
  view.contentDOM.addEventListener(type, (event) => {
3312
- if (type == "keydown" && this.keydown(view, event))
3313
- return;
3314
3378
  if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3315
3379
  return;
3380
+ if (type == "keydown" && this.keydown(view, event))
3381
+ return;
3316
3382
  if (this.mustFlushObserver(event))
3317
3383
  view.observer.forceFlush();
3318
3384
  if (this.runCustomHandlers(type, view, event))
@@ -3380,7 +3446,7 @@ class InputState {
3380
3446
  // Must always run, even if a custom handler handled the event
3381
3447
  this.lastKeyCode = event.keyCode;
3382
3448
  this.lastKeyTime = Date.now();
3383
- if (this.screenKeyEvent(view, event))
3449
+ if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3384
3450
  return true;
3385
3451
  // Chrome for Android usually doesn't fire proper key events, but
3386
3452
  // occasionally does, usually surrounded by a bunch of complicated
@@ -3430,14 +3496,6 @@ class InputState {
3430
3496
  }
3431
3497
  return false;
3432
3498
  }
3433
- screenKeyEvent(view, event) {
3434
- let protectedTab = event.keyCode == 9 && Date.now() < this.lastEscPress + 2000;
3435
- if (event.keyCode == 27)
3436
- this.lastEscPress = Date.now();
3437
- else if (modifierCodes.indexOf(event.keyCode) < 0)
3438
- this.lastEscPress = 0;
3439
- return protectedTab;
3440
- }
3441
3499
  mustFlushObserver(event) {
3442
3500
  return (event.type == "keydown" && event.keyCode != 229) ||
3443
3501
  event.type == "compositionend" && !browser.ios;
@@ -3611,6 +3669,10 @@ function doPaste(view, input) {
3611
3669
  }
3612
3670
  handlers.keydown = (view, event) => {
3613
3671
  view.inputState.setSelectionOrigin("select");
3672
+ if (event.keyCode == 27)
3673
+ view.inputState.lastEscPress = Date.now();
3674
+ else if (modifierCodes.indexOf(event.keyCode) < 0)
3675
+ view.inputState.lastEscPress = 0;
3614
3676
  };
3615
3677
  let lastTouch = 0;
3616
3678
  handlers.touchstart = (view, e) => {
@@ -3868,14 +3930,6 @@ handlers.focus = handlers.blur = view => {
3868
3930
  view.update([]);
3869
3931
  }, 10);
3870
3932
  };
3871
- handlers.beforeprint = view => {
3872
- view.viewState.printing = true;
3873
- view.requestMeasure();
3874
- setTimeout(() => {
3875
- view.viewState.printing = false;
3876
- view.requestMeasure();
3877
- }, 2000);
3878
- };
3879
3933
  function forceClearComposition(view, rapid) {
3880
3934
  if (view.docView.compositionDeco.size) {
3881
3935
  view.inputState.rapidCompositionStart = rapid;
@@ -4631,6 +4685,11 @@ function visiblePixelRange(dom, paddingTop) {
4631
4685
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4632
4686
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4633
4687
  }
4688
+ function fullPixelRange(dom, paddingTop) {
4689
+ let rect = dom.getBoundingClientRect();
4690
+ return { left: 0, right: rect.right - rect.left,
4691
+ top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
4692
+ }
4634
4693
  // Line gaps are placeholder widgets used to hide pieces of overlong
4635
4694
  // lines within the viewport, as a kludge to keep the editor
4636
4695
  // responsive when a ridiculously long line is loaded into it.
@@ -4787,8 +4846,7 @@ class ViewState {
4787
4846
  }
4788
4847
  }
4789
4848
  // Pixel viewport
4790
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4791
- : visiblePixelRange(dom, this.paddingTop);
4849
+ let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
4792
4850
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4793
4851
  this.pixelViewport = pixelViewport;
4794
4852
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -5365,6 +5423,7 @@ class DOMObserver {
5365
5423
  });
5366
5424
  this.resize.observe(view.scrollDOM);
5367
5425
  }
5426
+ window.addEventListener("beforeprint", this.onPrint = this.onPrint.bind(this));
5368
5427
  this.start();
5369
5428
  window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5370
5429
  if (typeof IntersectionObserver == "function") {
@@ -5399,6 +5458,14 @@ class DOMObserver {
5399
5458
  this.view.requestMeasure();
5400
5459
  }, 50);
5401
5460
  }
5461
+ onPrint() {
5462
+ this.view.viewState.printing = true;
5463
+ this.view.measure();
5464
+ setTimeout(() => {
5465
+ this.view.viewState.printing = false;
5466
+ this.view.requestMeasure();
5467
+ }, 500);
5468
+ }
5402
5469
  updateGaps(gaps) {
5403
5470
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5404
5471
  this.gapIntersection.disconnect();
@@ -5616,6 +5683,7 @@ class DOMObserver {
5616
5683
  dom.removeEventListener("scroll", this.onScroll);
5617
5684
  window.removeEventListener("scroll", this.onScroll);
5618
5685
  window.removeEventListener("resize", this.onResize);
5686
+ window.removeEventListener("beforeprint", this.onPrint);
5619
5687
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5620
5688
  clearTimeout(this.parentCheck);
5621
5689
  clearTimeout(this.resizeTimeout);
package/dist/index.d.ts CHANGED
@@ -413,12 +413,12 @@ declare class ViewPlugin<V extends PluginValue> {
413
413
  Define a plugin from a constructor function that creates the
414
414
  plugin's value, given an editor view.
415
415
  */
416
- static define<V extends PluginValue>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
416
+ static define<V extends PluginValue & object>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
417
417
  /**
418
418
  Create a plugin for a class whose constructor takes a single
419
419
  editor view as argument.
420
420
  */
421
- static fromClass<V extends PluginValue>(cls: {
421
+ static fromClass<V extends PluginValue & object>(cls: {
422
422
  new (view: EditorView): V;
423
423
  }, spec?: PluginSpec<V>): ViewPlugin<V>;
424
424
  }
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ function getSelection(root) {
19
19
  return target.getSelection();
20
20
  }
21
21
  function contains(dom, node) {
22
- return node ? dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
22
+ return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
23
23
  }
24
24
  function deepActiveElement() {
25
25
  let elt = document.activeElement;
@@ -315,32 +315,34 @@ class ContentView {
315
315
  sync(track) {
316
316
  if (this.dirty & 2 /* Node */) {
317
317
  let parent = this.dom;
318
- let pos = parent.firstChild;
318
+ let prev = null, next;
319
319
  for (let child of this.children) {
320
320
  if (child.dirty) {
321
- if (!child.dom && pos) {
322
- let contentView = ContentView.get(pos);
321
+ if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
322
+ let contentView = ContentView.get(next);
323
323
  if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
324
- child.reuseDOM(pos);
324
+ child.reuseDOM(next);
325
325
  }
326
326
  child.sync(track);
327
327
  child.dirty = 0 /* Not */;
328
328
  }
329
- if (track && !track.written && track.node == parent && pos != child.dom)
329
+ next = prev ? prev.nextSibling : parent.firstChild;
330
+ if (track && !track.written && track.node == parent && next != child.dom)
330
331
  track.written = true;
331
332
  if (child.dom.parentNode == parent) {
332
- while (pos && pos != child.dom)
333
- pos = rm(pos);
334
- pos = child.dom.nextSibling;
333
+ while (next && next != child.dom)
334
+ next = rm(next);
335
335
  }
336
336
  else {
337
- parent.insertBefore(child.dom, pos);
337
+ parent.insertBefore(child.dom, next);
338
338
  }
339
+ prev = child.dom;
339
340
  }
340
- if (pos && track && track.node == parent)
341
+ next = prev ? prev.nextSibling : parent.firstChild;
342
+ if (next && track && track.node == parent)
341
343
  track.written = true;
342
- while (pos)
343
- pos = rm(pos);
344
+ while (next)
345
+ next = rm(next);
344
346
  }
345
347
  else if (this.dirty & 1 /* Child */) {
346
348
  for (let child of this.children)
@@ -853,18 +855,26 @@ class WidgetView extends ContentView {
853
855
  }
854
856
  class CompositionView extends WidgetView {
855
857
  domAtPos(pos) {
856
- let { topView } = this.widget;
858
+ let { topView, text } = this.widget;
857
859
  if (!topView)
858
- return new DOMPos(this.widget.text, Math.min(pos, this.widget.text.nodeValue.length));
859
- return posInCompositionTree(pos, topView, this.widget.text);
860
+ return new DOMPos(text, Math.min(pos, text.nodeValue.length));
861
+ return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
860
862
  }
861
863
  sync() { this.setDOM(this.widget.toDOM()); }
862
864
  localPosFromDOM(node, offset) {
863
- return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
865
+ let { topView, text } = this.widget;
866
+ if (!topView)
867
+ return Math.min(offset, this.length);
868
+ return posFromDOMInCompositionTree(node, offset, topView, text);
864
869
  }
865
870
  ignoreMutation() { return false; }
866
871
  get overrideDOMText() { return null; }
867
- coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
872
+ coordsAt(pos, side) {
873
+ let { topView, text } = this.widget;
874
+ if (!topView)
875
+ return textCoords(text, pos, side);
876
+ return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
877
+ }
868
878
  destroy() {
869
879
  var _a;
870
880
  super.destroy();
@@ -875,24 +885,38 @@ class CompositionView extends WidgetView {
875
885
  // Uses the old structure of a chunk of content view frozen for
876
886
  // composition to try and find a reasonable DOM location for the given
877
887
  // offset.
878
- function posInCompositionTree(pos, view, text) {
888
+ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
879
889
  if (view instanceof MarkView) {
880
890
  for (let child of view.children) {
881
- let hasComp = child.dom == text || child.dom.contains(text.parentNode);
891
+ let hasComp = contains(child.dom, text);
882
892
  let len = hasComp ? text.nodeValue.length : child.length;
883
893
  if (pos < len || pos == len && child.getSide() <= 0)
884
- return hasComp ? posInCompositionTree(pos, child, text) : child.domAtPos(pos);
894
+ return hasComp ? scanCompositionTree(pos, side, child, text, enterView, fromText) : enterView(child, pos, side);
885
895
  pos -= len;
886
896
  }
887
- return view.domAtPos(view.length);
897
+ return enterView(view, view.length, -1);
888
898
  }
889
899
  else if (view.dom == text) {
890
- return new DOMPos(text, Math.min(pos, text.nodeValue.length));
900
+ return fromText(pos, side);
891
901
  }
892
902
  else {
893
- return view.domAtPos(pos);
903
+ return enterView(view, pos, side);
894
904
  }
895
905
  }
906
+ function posFromDOMInCompositionTree(node, offset, view, text) {
907
+ if (view instanceof MarkView) {
908
+ for (let child of view.children) {
909
+ let pos = 0, hasComp = contains(child.dom, text);
910
+ if (contains(child.dom, node))
911
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
912
+ pos += hasComp ? text.nodeValue.length : child.length;
913
+ }
914
+ }
915
+ else if (view.dom == text) {
916
+ return Math.min(offset, text.nodeValue.length);
917
+ }
918
+ return view.localPosFromDOM(node, offset);
919
+ }
896
920
  // These are drawn around uneditable widgets to avoid a number of
897
921
  // browser bugs that show up when the cursor is directly next to
898
922
  // uneditable inline content.
@@ -911,6 +935,7 @@ class WidgetBufferView extends ContentView {
911
935
  if (!this.dom) {
912
936
  let dom = document.createElement("img");
913
937
  dom.className = "cm-widgetBuffer";
938
+ dom.setAttribute("aria-hidden", "true");
914
939
  this.setDOM(dom);
915
940
  }
916
941
  }
@@ -919,13 +944,43 @@ class WidgetBufferView extends ContentView {
919
944
  localPosFromDOM() { return 0; }
920
945
  domBoundsAround() { return null; }
921
946
  coordsAt(pos) {
922
- return this.dom.getBoundingClientRect();
947
+ let imgRect = this.dom.getBoundingClientRect();
948
+ // Since the <img> height doesn't correspond to text height, try
949
+ // to borrow the height from some sibling node.
950
+ let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
951
+ return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
952
+ ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
923
953
  }
924
954
  get overrideDOMText() {
925
955
  return Text.empty;
926
956
  }
927
957
  }
928
958
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
959
+ function inlineSiblingRect(view, side) {
960
+ let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
961
+ while (parent && index >= 0) {
962
+ if (side < 0 ? index > 0 : index < parent.children.length) {
963
+ let next = parent.children[index + side];
964
+ if (next instanceof TextView) {
965
+ let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
966
+ if (nextRect)
967
+ return nextRect;
968
+ }
969
+ index += side;
970
+ }
971
+ else if (parent instanceof MarkView && parent.parent) {
972
+ index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
973
+ parent = parent.parent;
974
+ }
975
+ else {
976
+ let last = parent.dom.lastChild;
977
+ if (last && last.nodeName == "BR")
978
+ return last.getClientRects()[0];
979
+ break;
980
+ }
981
+ }
982
+ return undefined;
983
+ }
929
984
  function inlineDOMAtPos(dom, children, pos) {
930
985
  let i = 0;
931
986
  for (let off = 0; i < children.length; i++) {
@@ -1158,10 +1213,16 @@ class Decoration extends RangeValue {
1158
1213
  a widget, or simply hides it.
1159
1214
  */
1160
1215
  static replace(spec) {
1161
- let block = !!spec.block;
1162
- let { start, end } = getInclusive(spec, block);
1163
- let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1164
- let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1216
+ let block = !!spec.block, startSide, endSide;
1217
+ if (spec.isBlockGap) {
1218
+ startSide = -500000000 /* GapStart */;
1219
+ endSide = 400000000 /* GapEnd */;
1220
+ }
1221
+ else {
1222
+ let { start, end } = getInclusive(spec, block);
1223
+ startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 500000000 /* NonIncStart */) - 1;
1224
+ endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -600000000 /* NonIncEnd */) + 1;
1225
+ }
1165
1226
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1166
1227
  }
1167
1228
  /**
@@ -1191,7 +1252,7 @@ Decoration.none = RangeSet.empty;
1191
1252
  class MarkDecoration extends Decoration {
1192
1253
  constructor(spec) {
1193
1254
  let { start, end } = getInclusive(spec);
1194
- super(start ? -1 /* InlineIncStart */ : 400000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -500000000 /* NonIncEnd */, null, spec);
1255
+ super(start ? -1 /* InlineIncStart */ : 500000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -600000000 /* NonIncEnd */, null, spec);
1195
1256
  this.tagName = spec.tagName || "span";
1196
1257
  this.class = spec.class || "";
1197
1258
  this.attrs = spec.attributes || null;
@@ -1372,7 +1433,7 @@ class LineView extends ContentView {
1372
1433
  let last = this.dom.lastChild;
1373
1434
  while (last && ContentView.get(last) instanceof MarkView)
1374
1435
  last = last.lastChild;
1375
- if (!last ||
1436
+ if (!last || !this.length ||
1376
1437
  last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1377
1438
  (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1378
1439
  let hack = document.createElement("BR");
@@ -2769,7 +2830,12 @@ class DocView extends ContentView {
2769
2830
  let end = next ? next.from - 1 : this.length;
2770
2831
  if (end > pos) {
2771
2832
  let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2772
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2833
+ deco.push(Decoration.replace({
2834
+ widget: new BlockGapWidget(height),
2835
+ block: true,
2836
+ inclusive: true,
2837
+ isBlockGap: true,
2838
+ }).range(pos, end));
2773
2839
  }
2774
2840
  if (!next)
2775
2841
  break;
@@ -3304,10 +3370,10 @@ class InputState {
3304
3370
  for (let type in handlers) {
3305
3371
  let handler = handlers[type];
3306
3372
  view.contentDOM.addEventListener(type, (event) => {
3307
- if (type == "keydown" && this.keydown(view, event))
3308
- return;
3309
3373
  if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3310
3374
  return;
3375
+ if (type == "keydown" && this.keydown(view, event))
3376
+ return;
3311
3377
  if (this.mustFlushObserver(event))
3312
3378
  view.observer.forceFlush();
3313
3379
  if (this.runCustomHandlers(type, view, event))
@@ -3375,7 +3441,7 @@ class InputState {
3375
3441
  // Must always run, even if a custom handler handled the event
3376
3442
  this.lastKeyCode = event.keyCode;
3377
3443
  this.lastKeyTime = Date.now();
3378
- if (this.screenKeyEvent(view, event))
3444
+ if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3379
3445
  return true;
3380
3446
  // Chrome for Android usually doesn't fire proper key events, but
3381
3447
  // occasionally does, usually surrounded by a bunch of complicated
@@ -3425,14 +3491,6 @@ class InputState {
3425
3491
  }
3426
3492
  return false;
3427
3493
  }
3428
- screenKeyEvent(view, event) {
3429
- let protectedTab = event.keyCode == 9 && Date.now() < this.lastEscPress + 2000;
3430
- if (event.keyCode == 27)
3431
- this.lastEscPress = Date.now();
3432
- else if (modifierCodes.indexOf(event.keyCode) < 0)
3433
- this.lastEscPress = 0;
3434
- return protectedTab;
3435
- }
3436
3494
  mustFlushObserver(event) {
3437
3495
  return (event.type == "keydown" && event.keyCode != 229) ||
3438
3496
  event.type == "compositionend" && !browser.ios;
@@ -3606,6 +3664,10 @@ function doPaste(view, input) {
3606
3664
  }
3607
3665
  handlers.keydown = (view, event) => {
3608
3666
  view.inputState.setSelectionOrigin("select");
3667
+ if (event.keyCode == 27)
3668
+ view.inputState.lastEscPress = Date.now();
3669
+ else if (modifierCodes.indexOf(event.keyCode) < 0)
3670
+ view.inputState.lastEscPress = 0;
3609
3671
  };
3610
3672
  let lastTouch = 0;
3611
3673
  handlers.touchstart = (view, e) => {
@@ -3863,14 +3925,6 @@ handlers.focus = handlers.blur = view => {
3863
3925
  view.update([]);
3864
3926
  }, 10);
3865
3927
  };
3866
- handlers.beforeprint = view => {
3867
- view.viewState.printing = true;
3868
- view.requestMeasure();
3869
- setTimeout(() => {
3870
- view.viewState.printing = false;
3871
- view.requestMeasure();
3872
- }, 2000);
3873
- };
3874
3928
  function forceClearComposition(view, rapid) {
3875
3929
  if (view.docView.compositionDeco.size) {
3876
3930
  view.inputState.rapidCompositionStart = rapid;
@@ -4625,6 +4679,11 @@ function visiblePixelRange(dom, paddingTop) {
4625
4679
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4626
4680
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4627
4681
  }
4682
+ function fullPixelRange(dom, paddingTop) {
4683
+ let rect = dom.getBoundingClientRect();
4684
+ return { left: 0, right: rect.right - rect.left,
4685
+ top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
4686
+ }
4628
4687
  // Line gaps are placeholder widgets used to hide pieces of overlong
4629
4688
  // lines within the viewport, as a kludge to keep the editor
4630
4689
  // responsive when a ridiculously long line is loaded into it.
@@ -4781,8 +4840,7 @@ class ViewState {
4781
4840
  }
4782
4841
  }
4783
4842
  // Pixel viewport
4784
- let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
4785
- : visiblePixelRange(dom, this.paddingTop);
4843
+ let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
4786
4844
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4787
4845
  this.pixelViewport = pixelViewport;
4788
4846
  let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
@@ -5359,6 +5417,7 @@ class DOMObserver {
5359
5417
  });
5360
5418
  this.resize.observe(view.scrollDOM);
5361
5419
  }
5420
+ window.addEventListener("beforeprint", this.onPrint = this.onPrint.bind(this));
5362
5421
  this.start();
5363
5422
  window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5364
5423
  if (typeof IntersectionObserver == "function") {
@@ -5393,6 +5452,14 @@ class DOMObserver {
5393
5452
  this.view.requestMeasure();
5394
5453
  }, 50);
5395
5454
  }
5455
+ onPrint() {
5456
+ this.view.viewState.printing = true;
5457
+ this.view.measure();
5458
+ setTimeout(() => {
5459
+ this.view.viewState.printing = false;
5460
+ this.view.requestMeasure();
5461
+ }, 500);
5462
+ }
5396
5463
  updateGaps(gaps) {
5397
5464
  if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5398
5465
  this.gapIntersection.disconnect();
@@ -5610,6 +5677,7 @@ class DOMObserver {
5610
5677
  dom.removeEventListener("scroll", this.onScroll);
5611
5678
  window.removeEventListener("scroll", this.onScroll);
5612
5679
  window.removeEventListener("resize", this.onResize);
5680
+ window.removeEventListener("beforeprint", this.onPrint);
5613
5681
  this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5614
5682
  clearTimeout(this.parentCheck);
5615
5683
  clearTimeout(this.resizeTimeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.45",
3
+ "version": "0.19.48",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",