@codemirror/view 0.19.28 → 0.19.29

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,15 @@
1
+ ## 0.19.29 (2021-12-09)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug that could cause out-of-view editors to get a nonsensical viewport and fail to scroll into view when asked to.
6
+
7
+ Fix a bug where would return 0 when clicking below the content if the last line was replaced with a block widget decoration.
8
+
9
+ Fix an issue where clicking at the position of the previous cursor in a blurred editor would cause the selection to reset to the start of the document.
10
+
11
+ Fix an issue where composition could be interrupted if the browser created a new node inside a mark decoration node.
12
+
1
13
  ## 0.19.28 (2021-12-08)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2254,6 +2254,78 @@ function moveVisually(line, order, dir, start, forward) {
2254
2254
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2255
2255
  }
2256
2256
 
2257
+ class DOMReader {
2258
+ constructor(points, view) {
2259
+ this.points = points;
2260
+ this.view = view;
2261
+ this.text = "";
2262
+ this.lineBreak = view.state.lineBreak;
2263
+ }
2264
+ readRange(start, end) {
2265
+ if (!start)
2266
+ return this;
2267
+ let parent = start.parentNode;
2268
+ for (let cur = start;;) {
2269
+ this.findPointBefore(parent, cur);
2270
+ this.readNode(cur);
2271
+ let next = cur.nextSibling;
2272
+ if (next == end)
2273
+ break;
2274
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2275
+ if (view && nextView ? view.breakAfter :
2276
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2277
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2278
+ this.text += this.lineBreak;
2279
+ cur = next;
2280
+ }
2281
+ this.findPointBefore(parent, end);
2282
+ return this;
2283
+ }
2284
+ readNode(node) {
2285
+ if (node.cmIgnore)
2286
+ return;
2287
+ let view = ContentView.get(node);
2288
+ let fromView = view && view.overrideDOMText;
2289
+ let text;
2290
+ if (fromView != null)
2291
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2292
+ else if (node.nodeType == 3)
2293
+ text = node.nodeValue;
2294
+ else if (node.nodeName == "BR")
2295
+ text = node.nextSibling ? this.lineBreak : "";
2296
+ else if (node.nodeType == 1)
2297
+ this.readRange(node.firstChild, null);
2298
+ if (text != null) {
2299
+ this.findPointIn(node, text.length);
2300
+ this.text += text;
2301
+ // Chrome inserts two newlines when pressing shift-enter at the
2302
+ // end of a line. This drops one of those.
2303
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2304
+ this.text = this.text.slice(0, -1);
2305
+ }
2306
+ }
2307
+ findPointBefore(node, next) {
2308
+ for (let point of this.points)
2309
+ if (point.node == node && node.childNodes[point.offset] == next)
2310
+ point.pos = this.text.length;
2311
+ }
2312
+ findPointIn(node, maxLen) {
2313
+ for (let point of this.points)
2314
+ if (point.node == node)
2315
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2316
+ }
2317
+ }
2318
+ function isBlockElement(node) {
2319
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2320
+ }
2321
+ class DOMPoint {
2322
+ constructor(node, offset) {
2323
+ this.node = node;
2324
+ this.offset = offset;
2325
+ this.pos = -1;
2326
+ }
2327
+ }
2328
+
2257
2329
  class DocView extends ContentView {
2258
2330
  constructor(view) {
2259
2331
  super();
@@ -2303,7 +2375,7 @@ class DocView extends ContentView {
2303
2375
  }
2304
2376
  if (this.view.inputState.composing < 0)
2305
2377
  this.compositionDeco = Decoration.none;
2306
- else if (update.transactions.length)
2378
+ else if (update.transactions.length || this.dirty)
2307
2379
  this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2308
2380
  // When the DOM nodes around the selection are moved to another
2309
2381
  // parent, Chrome sometimes reports a different selection through
@@ -2454,7 +2526,7 @@ class DocView extends ContentView {
2454
2526
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2455
2527
  }
2456
2528
  enforceCursorAssoc() {
2457
- if (this.view.composing)
2529
+ if (this.compositionDeco.size)
2458
2530
  return;
2459
2531
  let cursor = this.view.state.selection.main;
2460
2532
  let sel = getSelection(this.root);
@@ -2679,7 +2751,8 @@ function computeCompositionDeco(view, changes) {
2679
2751
  topNode = cView.dom;
2680
2752
  }
2681
2753
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2682
- let text = textNode.nodeValue, { state } = view;
2754
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2755
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2683
2756
  if (newTo - newFrom < text.length) {
2684
2757
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2685
2758
  newTo = newFrom + text.length;
@@ -2883,21 +2956,29 @@ function domPosInText(node, x, y) {
2883
2956
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2884
2957
  var _a;
2885
2958
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2886
- let halfLine = view.defaultLineHeight / 2;
2887
- let block, yOffset = y - docTop;
2888
- for (let bounced = false;;) {
2959
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2960
+ if (yOffset < 0 || yOffset > docHeight) {
2961
+ if (precise)
2962
+ return null;
2963
+ yOffset = yOffset < 0 ? 0 : docHeight;
2964
+ }
2965
+ // Scan for a text block near the queried y position
2966
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2889
2967
  block = view.elementAtHeight(yOffset);
2890
- if (block.top > yOffset || block.bottom < yOffset) {
2891
- bias = block.top > yOffset ? -1 : 1;
2892
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2968
+ if (block.type == exports.BlockType.Text)
2969
+ break;
2970
+ for (;;) {
2971
+ // Move the y position out of this block
2972
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2973
+ if (yOffset >= 0 && yOffset <= docHeight)
2974
+ break;
2975
+ // If the document consists entirely of replaced widgets, we
2976
+ // won't find a text block, so return 0
2893
2977
  if (bounced)
2894
2978
  return precise ? null : 0;
2895
- else
2896
- bounced = true;
2979
+ bounced = true;
2980
+ bias = -bias;
2897
2981
  }
2898
- if (block.type == exports.BlockType.Text)
2899
- break;
2900
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2901
2982
  }
2902
2983
  y = docTop + yOffset;
2903
2984
  let lineStart = block.from;
@@ -3238,10 +3319,10 @@ class InputState {
3238
3319
  return (event.type == "keydown" && event.keyCode != 229) ||
3239
3320
  event.type == "compositionend" && !browser.ios;
3240
3321
  }
3241
- startMouseSelection(view, event, style) {
3322
+ startMouseSelection(mouseSelection) {
3242
3323
  if (this.mouseSelection)
3243
3324
  this.mouseSelection.destroy();
3244
- this.mouseSelection = new MouseSelection(this, view, event, style);
3325
+ this.mouseSelection = mouseSelection;
3245
3326
  }
3246
3327
  update(update) {
3247
3328
  if (this.mouseSelection)
@@ -3262,10 +3343,10 @@ const PendingKeys = [
3262
3343
  // Key codes for modifier keys
3263
3344
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3264
3345
  class MouseSelection {
3265
- constructor(inputState, view, startEvent, style) {
3266
- this.inputState = inputState;
3346
+ constructor(view, startEvent, style, mustSelect) {
3267
3347
  this.view = view;
3268
3348
  this.style = style;
3349
+ this.mustSelect = mustSelect;
3269
3350
  this.lastEvent = startEvent;
3270
3351
  let doc = view.contentDOM.ownerDocument;
3271
3352
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3299,16 +3380,18 @@ class MouseSelection {
3299
3380
  let doc = this.view.contentDOM.ownerDocument;
3300
3381
  doc.removeEventListener("mousemove", this.move);
3301
3382
  doc.removeEventListener("mouseup", this.up);
3302
- this.inputState.mouseSelection = null;
3383
+ this.view.inputState.mouseSelection = null;
3303
3384
  }
3304
3385
  select(event) {
3305
3386
  let selection = this.style.get(event, this.extend, this.multiple);
3306
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3387
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3388
+ selection.main.assoc != this.view.state.selection.main.assoc)
3307
3389
  this.view.dispatch({
3308
3390
  selection,
3309
3391
  userEvent: "select.pointer",
3310
3392
  scrollIntoView: true
3311
3393
  });
3394
+ this.mustSelect = false;
3312
3395
  }
3313
3396
  update(update) {
3314
3397
  if (update.docChanged && this.dragging)
@@ -3427,9 +3510,10 @@ handlers.mousedown = (view, event) => {
3427
3510
  if (!style && event.button == 0)
3428
3511
  style = basicMouseSelection(view, event);
3429
3512
  if (style) {
3430
- if (view.root.activeElement != view.contentDOM)
3513
+ let mustFocus = view.root.activeElement != view.contentDOM;
3514
+ if (mustFocus)
3431
3515
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3432
- view.inputState.startMouseSelection(view, event, style);
3516
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3433
3517
  }
3434
3518
  };
3435
3519
  function rangeForClick(view, pos, bias, type) {
@@ -4421,8 +4505,8 @@ function visiblePixelRange(dom, paddingTop) {
4421
4505
  break;
4422
4506
  }
4423
4507
  }
4424
- return { left: left - rect.left, right: right - rect.left,
4425
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4508
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4509
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4426
4510
  }
4427
4511
  // Line gaps are placeholder widgets used to hide pieces of overlong
4428
4512
  // lines within the viewport, as a kludge to keep the editor
@@ -4649,7 +4733,7 @@ class ViewState {
4649
4733
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4650
4734
  // If scrollTarget is given, make sure the viewport includes that position
4651
4735
  if (scrollTarget) {
4652
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4736
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4653
4737
  if (head < viewport.from || head > viewport.to) {
4654
4738
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4655
4739
  if (scrollTarget.center)
@@ -4670,6 +4754,8 @@ class ViewState {
4670
4754
  // Checks if a given viewport covers the visible part of the
4671
4755
  // document and not too much beyond that.
4672
4756
  viewportIsAppropriate({ from, to }, bias = 0) {
4757
+ if (!this.inView)
4758
+ return true;
4673
4759
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4674
4760
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4675
4761
  let { visibleTop, visibleBottom } = this;
@@ -4776,8 +4862,11 @@ class ViewState {
4776
4862
  elementAtHeight(height) {
4777
4863
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4778
4864
  }
4865
+ get docHeight() {
4866
+ return this.scaler.toDOM(this.heightMap.height);
4867
+ }
4779
4868
  get contentHeight() {
4780
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4869
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4781
4870
  }
4782
4871
  }
4783
4872
  class Viewport {
@@ -5330,7 +5419,7 @@ class DOMObserver {
5330
5419
  this.onChange(from, to, typeOver);
5331
5420
  // The view wasn't updated
5332
5421
  if (this.view.state == startState)
5333
- this.view.docView.reset(newSel);
5422
+ this.view.update([]);
5334
5423
  }
5335
5424
  readMutation(rec) {
5336
5425
  let cView = this.view.docView.nearest(rec.target);
@@ -5549,76 +5638,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5549
5638
  }
5550
5639
  return { from, toA, toB };
5551
5640
  }
5552
- class DOMReader {
5553
- constructor(points, view) {
5554
- this.points = points;
5555
- this.view = view;
5556
- this.text = "";
5557
- this.lineBreak = view.state.lineBreak;
5558
- }
5559
- readRange(start, end) {
5560
- if (!start)
5561
- return;
5562
- let parent = start.parentNode;
5563
- for (let cur = start;;) {
5564
- this.findPointBefore(parent, cur);
5565
- this.readNode(cur);
5566
- let next = cur.nextSibling;
5567
- if (next == end)
5568
- break;
5569
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5570
- if (view && nextView ? view.breakAfter :
5571
- (view ? view.breakAfter : isBlockElement(cur)) ||
5572
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5573
- this.text += this.lineBreak;
5574
- cur = next;
5575
- }
5576
- this.findPointBefore(parent, end);
5577
- }
5578
- readNode(node) {
5579
- if (node.cmIgnore)
5580
- return;
5581
- let view = ContentView.get(node);
5582
- let fromView = view && view.overrideDOMText;
5583
- let text;
5584
- if (fromView != null)
5585
- text = fromView.sliceString(0, undefined, this.lineBreak);
5586
- else if (node.nodeType == 3)
5587
- text = node.nodeValue;
5588
- else if (node.nodeName == "BR")
5589
- text = node.nextSibling ? this.lineBreak : "";
5590
- else if (node.nodeType == 1)
5591
- this.readRange(node.firstChild, null);
5592
- if (text != null) {
5593
- this.findPointIn(node, text.length);
5594
- this.text += text;
5595
- // Chrome inserts two newlines when pressing shift-enter at the
5596
- // end of a line. This drops one of those.
5597
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5598
- this.text = this.text.slice(0, -1);
5599
- }
5600
- }
5601
- findPointBefore(node, next) {
5602
- for (let point of this.points)
5603
- if (point.node == node && node.childNodes[point.offset] == next)
5604
- point.pos = this.text.length;
5605
- }
5606
- findPointIn(node, maxLen) {
5607
- for (let point of this.points)
5608
- if (point.node == node)
5609
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5610
- }
5611
- }
5612
- function isBlockElement(node) {
5613
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5614
- }
5615
- class DOMPoint {
5616
- constructor(node, offset) {
5617
- this.node = node;
5618
- this.offset = offset;
5619
- this.pos = -1;
5620
- }
5621
- }
5622
5641
  function selectionPoints(view) {
5623
5642
  let result = [];
5624
5643
  if (view.root.activeElement != view.contentDOM)
@@ -5904,7 +5923,9 @@ class EditorView {
5904
5923
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5905
5924
  break;
5906
5925
  if (i > 5) {
5907
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5926
+ console.warn(this.measureRequests.length
5927
+ ? "Measure loop restarted more than 5 times"
5928
+ : "Viewport failed to stabilize");
5908
5929
  break;
5909
5930
  }
5910
5931
  let measuring = [];
@@ -5950,7 +5971,8 @@ class EditorView {
5950
5971
  }
5951
5972
  if (redrawn)
5952
5973
  this.docView.updateSelection(true);
5953
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5974
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5975
+ this.measureRequests.length == 0)
5954
5976
  break;
5955
5977
  }
5956
5978
  }
@@ -6242,6 +6264,11 @@ class EditorView {
6242
6264
  Find the DOM parent node and offset (child offset if `node` is
6243
6265
  an element, character offset when it is a text node) at the
6244
6266
  given document position.
6267
+
6268
+ Note that for positions that aren't currently in
6269
+ `visibleRanges`, the resulting DOM position isn't necessarily
6270
+ meaningful (it may just point before or after a placeholder
6271
+ element).
6245
6272
  */
6246
6273
  domAtPos(pos) {
6247
6274
  return this.docView.domAtPos(pos);
package/dist/index.d.ts CHANGED
@@ -919,6 +919,11 @@ declare class EditorView {
919
919
  Find the DOM parent node and offset (child offset if `node` is
920
920
  an element, character offset when it is a text node) at the
921
921
  given document position.
922
+
923
+ Note that for positions that aren't currently in
924
+ `visibleRanges`, the resulting DOM position isn't necessarily
925
+ meaningful (it may just point before or after a placeholder
926
+ element).
922
927
  */
923
928
  domAtPos(pos: number): {
924
929
  node: Node;
package/dist/index.js CHANGED
@@ -2249,6 +2249,78 @@ function moveVisually(line, order, dir, start, forward) {
2249
2249
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2250
2250
  }
2251
2251
 
2252
+ class DOMReader {
2253
+ constructor(points, view) {
2254
+ this.points = points;
2255
+ this.view = view;
2256
+ this.text = "";
2257
+ this.lineBreak = view.state.lineBreak;
2258
+ }
2259
+ readRange(start, end) {
2260
+ if (!start)
2261
+ return this;
2262
+ let parent = start.parentNode;
2263
+ for (let cur = start;;) {
2264
+ this.findPointBefore(parent, cur);
2265
+ this.readNode(cur);
2266
+ let next = cur.nextSibling;
2267
+ if (next == end)
2268
+ break;
2269
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2270
+ if (view && nextView ? view.breakAfter :
2271
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2272
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2273
+ this.text += this.lineBreak;
2274
+ cur = next;
2275
+ }
2276
+ this.findPointBefore(parent, end);
2277
+ return this;
2278
+ }
2279
+ readNode(node) {
2280
+ if (node.cmIgnore)
2281
+ return;
2282
+ let view = ContentView.get(node);
2283
+ let fromView = view && view.overrideDOMText;
2284
+ let text;
2285
+ if (fromView != null)
2286
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2287
+ else if (node.nodeType == 3)
2288
+ text = node.nodeValue;
2289
+ else if (node.nodeName == "BR")
2290
+ text = node.nextSibling ? this.lineBreak : "";
2291
+ else if (node.nodeType == 1)
2292
+ this.readRange(node.firstChild, null);
2293
+ if (text != null) {
2294
+ this.findPointIn(node, text.length);
2295
+ this.text += text;
2296
+ // Chrome inserts two newlines when pressing shift-enter at the
2297
+ // end of a line. This drops one of those.
2298
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2299
+ this.text = this.text.slice(0, -1);
2300
+ }
2301
+ }
2302
+ findPointBefore(node, next) {
2303
+ for (let point of this.points)
2304
+ if (point.node == node && node.childNodes[point.offset] == next)
2305
+ point.pos = this.text.length;
2306
+ }
2307
+ findPointIn(node, maxLen) {
2308
+ for (let point of this.points)
2309
+ if (point.node == node)
2310
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2311
+ }
2312
+ }
2313
+ function isBlockElement(node) {
2314
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2315
+ }
2316
+ class DOMPoint {
2317
+ constructor(node, offset) {
2318
+ this.node = node;
2319
+ this.offset = offset;
2320
+ this.pos = -1;
2321
+ }
2322
+ }
2323
+
2252
2324
  class DocView extends ContentView {
2253
2325
  constructor(view) {
2254
2326
  super();
@@ -2298,7 +2370,7 @@ class DocView extends ContentView {
2298
2370
  }
2299
2371
  if (this.view.inputState.composing < 0)
2300
2372
  this.compositionDeco = Decoration.none;
2301
- else if (update.transactions.length)
2373
+ else if (update.transactions.length || this.dirty)
2302
2374
  this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2303
2375
  // When the DOM nodes around the selection are moved to another
2304
2376
  // parent, Chrome sometimes reports a different selection through
@@ -2449,7 +2521,7 @@ class DocView extends ContentView {
2449
2521
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2450
2522
  }
2451
2523
  enforceCursorAssoc() {
2452
- if (this.view.composing)
2524
+ if (this.compositionDeco.size)
2453
2525
  return;
2454
2526
  let cursor = this.view.state.selection.main;
2455
2527
  let sel = getSelection(this.root);
@@ -2674,7 +2746,8 @@ function computeCompositionDeco(view, changes) {
2674
2746
  topNode = cView.dom;
2675
2747
  }
2676
2748
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2677
- let text = textNode.nodeValue, { state } = view;
2749
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2750
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2678
2751
  if (newTo - newFrom < text.length) {
2679
2752
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2680
2753
  newTo = newFrom + text.length;
@@ -2878,21 +2951,29 @@ function domPosInText(node, x, y) {
2878
2951
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2879
2952
  var _a;
2880
2953
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2881
- let halfLine = view.defaultLineHeight / 2;
2882
- let block, yOffset = y - docTop;
2883
- for (let bounced = false;;) {
2954
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2955
+ if (yOffset < 0 || yOffset > docHeight) {
2956
+ if (precise)
2957
+ return null;
2958
+ yOffset = yOffset < 0 ? 0 : docHeight;
2959
+ }
2960
+ // Scan for a text block near the queried y position
2961
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2884
2962
  block = view.elementAtHeight(yOffset);
2885
- if (block.top > yOffset || block.bottom < yOffset) {
2886
- bias = block.top > yOffset ? -1 : 1;
2887
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2963
+ if (block.type == BlockType.Text)
2964
+ break;
2965
+ for (;;) {
2966
+ // Move the y position out of this block
2967
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2968
+ if (yOffset >= 0 && yOffset <= docHeight)
2969
+ break;
2970
+ // If the document consists entirely of replaced widgets, we
2971
+ // won't find a text block, so return 0
2888
2972
  if (bounced)
2889
2973
  return precise ? null : 0;
2890
- else
2891
- bounced = true;
2974
+ bounced = true;
2975
+ bias = -bias;
2892
2976
  }
2893
- if (block.type == BlockType.Text)
2894
- break;
2895
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2896
2977
  }
2897
2978
  y = docTop + yOffset;
2898
2979
  let lineStart = block.from;
@@ -3233,10 +3314,10 @@ class InputState {
3233
3314
  return (event.type == "keydown" && event.keyCode != 229) ||
3234
3315
  event.type == "compositionend" && !browser.ios;
3235
3316
  }
3236
- startMouseSelection(view, event, style) {
3317
+ startMouseSelection(mouseSelection) {
3237
3318
  if (this.mouseSelection)
3238
3319
  this.mouseSelection.destroy();
3239
- this.mouseSelection = new MouseSelection(this, view, event, style);
3320
+ this.mouseSelection = mouseSelection;
3240
3321
  }
3241
3322
  update(update) {
3242
3323
  if (this.mouseSelection)
@@ -3257,10 +3338,10 @@ const PendingKeys = [
3257
3338
  // Key codes for modifier keys
3258
3339
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3259
3340
  class MouseSelection {
3260
- constructor(inputState, view, startEvent, style) {
3261
- this.inputState = inputState;
3341
+ constructor(view, startEvent, style, mustSelect) {
3262
3342
  this.view = view;
3263
3343
  this.style = style;
3344
+ this.mustSelect = mustSelect;
3264
3345
  this.lastEvent = startEvent;
3265
3346
  let doc = view.contentDOM.ownerDocument;
3266
3347
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3294,16 +3375,18 @@ class MouseSelection {
3294
3375
  let doc = this.view.contentDOM.ownerDocument;
3295
3376
  doc.removeEventListener("mousemove", this.move);
3296
3377
  doc.removeEventListener("mouseup", this.up);
3297
- this.inputState.mouseSelection = null;
3378
+ this.view.inputState.mouseSelection = null;
3298
3379
  }
3299
3380
  select(event) {
3300
3381
  let selection = this.style.get(event, this.extend, this.multiple);
3301
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3382
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3383
+ selection.main.assoc != this.view.state.selection.main.assoc)
3302
3384
  this.view.dispatch({
3303
3385
  selection,
3304
3386
  userEvent: "select.pointer",
3305
3387
  scrollIntoView: true
3306
3388
  });
3389
+ this.mustSelect = false;
3307
3390
  }
3308
3391
  update(update) {
3309
3392
  if (update.docChanged && this.dragging)
@@ -3422,9 +3505,10 @@ handlers.mousedown = (view, event) => {
3422
3505
  if (!style && event.button == 0)
3423
3506
  style = basicMouseSelection(view, event);
3424
3507
  if (style) {
3425
- if (view.root.activeElement != view.contentDOM)
3508
+ let mustFocus = view.root.activeElement != view.contentDOM;
3509
+ if (mustFocus)
3426
3510
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3427
- view.inputState.startMouseSelection(view, event, style);
3511
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3428
3512
  }
3429
3513
  };
3430
3514
  function rangeForClick(view, pos, bias, type) {
@@ -4415,8 +4499,8 @@ function visiblePixelRange(dom, paddingTop) {
4415
4499
  break;
4416
4500
  }
4417
4501
  }
4418
- return { left: left - rect.left, right: right - rect.left,
4419
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4502
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4503
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4420
4504
  }
4421
4505
  // Line gaps are placeholder widgets used to hide pieces of overlong
4422
4506
  // lines within the viewport, as a kludge to keep the editor
@@ -4643,7 +4727,7 @@ class ViewState {
4643
4727
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4644
4728
  // If scrollTarget is given, make sure the viewport includes that position
4645
4729
  if (scrollTarget) {
4646
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4730
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4647
4731
  if (head < viewport.from || head > viewport.to) {
4648
4732
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4649
4733
  if (scrollTarget.center)
@@ -4664,6 +4748,8 @@ class ViewState {
4664
4748
  // Checks if a given viewport covers the visible part of the
4665
4749
  // document and not too much beyond that.
4666
4750
  viewportIsAppropriate({ from, to }, bias = 0) {
4751
+ if (!this.inView)
4752
+ return true;
4667
4753
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4668
4754
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4669
4755
  let { visibleTop, visibleBottom } = this;
@@ -4770,8 +4856,11 @@ class ViewState {
4770
4856
  elementAtHeight(height) {
4771
4857
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4772
4858
  }
4859
+ get docHeight() {
4860
+ return this.scaler.toDOM(this.heightMap.height);
4861
+ }
4773
4862
  get contentHeight() {
4774
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4863
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4775
4864
  }
4776
4865
  }
4777
4866
  class Viewport {
@@ -5324,7 +5413,7 @@ class DOMObserver {
5324
5413
  this.onChange(from, to, typeOver);
5325
5414
  // The view wasn't updated
5326
5415
  if (this.view.state == startState)
5327
- this.view.docView.reset(newSel);
5416
+ this.view.update([]);
5328
5417
  }
5329
5418
  readMutation(rec) {
5330
5419
  let cView = this.view.docView.nearest(rec.target);
@@ -5543,76 +5632,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5543
5632
  }
5544
5633
  return { from, toA, toB };
5545
5634
  }
5546
- class DOMReader {
5547
- constructor(points, view) {
5548
- this.points = points;
5549
- this.view = view;
5550
- this.text = "";
5551
- this.lineBreak = view.state.lineBreak;
5552
- }
5553
- readRange(start, end) {
5554
- if (!start)
5555
- return;
5556
- let parent = start.parentNode;
5557
- for (let cur = start;;) {
5558
- this.findPointBefore(parent, cur);
5559
- this.readNode(cur);
5560
- let next = cur.nextSibling;
5561
- if (next == end)
5562
- break;
5563
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5564
- if (view && nextView ? view.breakAfter :
5565
- (view ? view.breakAfter : isBlockElement(cur)) ||
5566
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5567
- this.text += this.lineBreak;
5568
- cur = next;
5569
- }
5570
- this.findPointBefore(parent, end);
5571
- }
5572
- readNode(node) {
5573
- if (node.cmIgnore)
5574
- return;
5575
- let view = ContentView.get(node);
5576
- let fromView = view && view.overrideDOMText;
5577
- let text;
5578
- if (fromView != null)
5579
- text = fromView.sliceString(0, undefined, this.lineBreak);
5580
- else if (node.nodeType == 3)
5581
- text = node.nodeValue;
5582
- else if (node.nodeName == "BR")
5583
- text = node.nextSibling ? this.lineBreak : "";
5584
- else if (node.nodeType == 1)
5585
- this.readRange(node.firstChild, null);
5586
- if (text != null) {
5587
- this.findPointIn(node, text.length);
5588
- this.text += text;
5589
- // Chrome inserts two newlines when pressing shift-enter at the
5590
- // end of a line. This drops one of those.
5591
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5592
- this.text = this.text.slice(0, -1);
5593
- }
5594
- }
5595
- findPointBefore(node, next) {
5596
- for (let point of this.points)
5597
- if (point.node == node && node.childNodes[point.offset] == next)
5598
- point.pos = this.text.length;
5599
- }
5600
- findPointIn(node, maxLen) {
5601
- for (let point of this.points)
5602
- if (point.node == node)
5603
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5604
- }
5605
- }
5606
- function isBlockElement(node) {
5607
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5608
- }
5609
- class DOMPoint {
5610
- constructor(node, offset) {
5611
- this.node = node;
5612
- this.offset = offset;
5613
- this.pos = -1;
5614
- }
5615
- }
5616
5635
  function selectionPoints(view) {
5617
5636
  let result = [];
5618
5637
  if (view.root.activeElement != view.contentDOM)
@@ -5898,7 +5917,9 @@ class EditorView {
5898
5917
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5899
5918
  break;
5900
5919
  if (i > 5) {
5901
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5920
+ console.warn(this.measureRequests.length
5921
+ ? "Measure loop restarted more than 5 times"
5922
+ : "Viewport failed to stabilize");
5902
5923
  break;
5903
5924
  }
5904
5925
  let measuring = [];
@@ -5944,7 +5965,8 @@ class EditorView {
5944
5965
  }
5945
5966
  if (redrawn)
5946
5967
  this.docView.updateSelection(true);
5947
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5968
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5969
+ this.measureRequests.length == 0)
5948
5970
  break;
5949
5971
  }
5950
5972
  }
@@ -6236,6 +6258,11 @@ class EditorView {
6236
6258
  Find the DOM parent node and offset (child offset if `node` is
6237
6259
  an element, character offset when it is a text node) at the
6238
6260
  given document position.
6261
+
6262
+ Note that for positions that aren't currently in
6263
+ `visibleRanges`, the resulting DOM position isn't necessarily
6264
+ meaningful (it may just point before or after a placeholder
6265
+ element).
6239
6266
  */
6240
6267
  domAtPos(pos) {
6241
6268
  return this.docView.domAtPos(pos);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.28",
3
+ "version": "0.19.29",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",