@codemirror/view 0.18.15 → 0.18.19

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,47 @@
1
+ ## 0.18.19 (2021-07-12)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a regression where `EditorView.editable.of(false)` didn't disable editing on Webkit-based browsers.
6
+
7
+ ## 0.18.18 (2021-07-06)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix a bug that caused `EditorView.moveVertically` to only move by one line, even when given a custom distance, in some cases.
12
+
13
+ Hide Safari's native bold/italic/underline controls for the content.
14
+
15
+ Fix a CSS problem that prevented Safari from breaking words longer than the line in line-wrapping mode.
16
+
17
+ Avoid a problem where composition would be inappropriately abored on Safari.
18
+
19
+ Fix drag-selection that scrolls the content by dragging past the visible viewport.
20
+
21
+ ### New features
22
+
23
+ `posAtCoords` now has an imprecise mode where it'll return an approximate position even for parts of the document that aren't currently rendered.
24
+
25
+ ## 0.18.17 (2021-06-14)
26
+
27
+ ### Bug fixes
28
+
29
+ Make `drawSelection` behave properly when lines are split by block widgets.
30
+
31
+ Make sure drawn selections that span a single line break don't leave a gap between the lines.
32
+
33
+ ## 0.18.16 (2021-06-03)
34
+
35
+ ### Bug fixes
36
+
37
+ Fix a crash that could occur when the document changed during mouse selection.
38
+
39
+ Fix a bug where composition inside styled content would sometimes be inappropriately aborted by editor DOM updates.
40
+
41
+ ### New features
42
+
43
+ `MouseSelectionStyle.update` may now return true to indicate it should be queried for a new selection after the update.
44
+
1
45
  ## 0.18.15 (2021-06-01)
2
46
 
3
47
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -216,6 +216,19 @@ function dispatchKey(elt, name, code) {
216
216
  elt.dispatchEvent(up);
217
217
  return down.defaultPrevented || up.defaultPrevented;
218
218
  }
219
+ let _plainTextSupported = null;
220
+ function contentEditablePlainTextSupported() {
221
+ if (_plainTextSupported == null) {
222
+ _plainTextSupported = false;
223
+ let dummy = document.createElement("div");
224
+ try {
225
+ dummy.contentEditable = "plaintext-only";
226
+ _plainTextSupported = dummy.contentEditable == "plaintext-only";
227
+ }
228
+ catch (_) { }
229
+ }
230
+ return _plainTextSupported;
231
+ }
219
232
 
220
233
  class DOMPos {
221
234
  constructor(node, offset, precise = true) {
@@ -1957,6 +1970,8 @@ class DocView extends ContentView {
1957
1970
  // FIXME need to handle the case where the selection falls inside a block range
1958
1971
  let anchor = this.domAtPos(main.anchor);
1959
1972
  let head = main.empty ? anchor : this.domAtPos(main.head);
1973
+ // Always reset on Firefox when next to an uneditable node to
1974
+ // avoid invisible cursor bugs (#111)
1960
1975
  if (browser.gecko && main.empty && betweenUneditable(anchor)) {
1961
1976
  let dummy = document.createTextNode("");
1962
1977
  this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
@@ -1966,9 +1981,6 @@ class DocView extends ContentView {
1966
1981
  let domSel = this.view.observer.selectionRange;
1967
1982
  // If the selection is already here, or in an equivalent position, don't touch it
1968
1983
  if (force || !domSel.focusNode ||
1969
- // Always reset on Firefox when next to an uneditable node to
1970
- // avoid invisible cursor bugs (#111)
1971
- (browser.gecko && main.empty && nextToUneditable(domSel.focusNode, domSel.focusOffset)) ||
1972
1984
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
1973
1985
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
1974
1986
  this.view.observer.ignore(() => {
@@ -2011,6 +2023,8 @@ class DocView extends ContentView {
2011
2023
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2012
2024
  }
2013
2025
  enforceCursorAssoc() {
2026
+ if (this.view.composing)
2027
+ return;
2014
2028
  let cursor = this.view.state.selection.main;
2015
2029
  let sel = getSelection(this.root);
2016
2030
  if (!cursor.empty || !cursor.assoc || !sel.modify)
@@ -2061,7 +2075,10 @@ class DocView extends ContentView {
2061
2075
  coordsAt(pos, side) {
2062
2076
  for (let off = this.length, i = this.children.length - 1;; i--) {
2063
2077
  let child = this.children[i], start = off - child.breakAfter - child.length;
2064
- if (pos > start || pos == start && (child.type == exports.BlockType.Text || !i || this.children[i - 1].breakAfter))
2078
+ if (pos > start ||
2079
+ (pos == start && child.type != exports.BlockType.WidgetBefore && child.type != exports.BlockType.WidgetAfter &&
2080
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2081
+ (this.children[i - 1].type == exports.BlockType.WidgetBefore && side > -2))))
2065
2082
  return child.coordsAt(pos - start, side);
2066
2083
  off = start;
2067
2084
  }
@@ -2133,11 +2150,11 @@ class DocView extends ContentView {
2133
2150
  }
2134
2151
  updateDeco() {
2135
2152
  return this.decorations = [
2136
- this.computeBlockGapDeco(),
2137
- this.view.viewState.lineGapDeco,
2138
- this.compositionDeco,
2153
+ ...this.view.pluginField(PluginField.decorations),
2139
2154
  ...this.view.state.facet(decorations),
2140
- ...this.view.pluginField(PluginField.decorations)
2155
+ this.compositionDeco,
2156
+ this.computeBlockGapDeco(),
2157
+ this.view.viewState.lineGapDeco
2141
2158
  ];
2142
2159
  }
2143
2160
  scrollPosIntoView(pos, side) {
@@ -2719,7 +2736,7 @@ function domPosInText(node, x, y) {
2719
2736
  }
2720
2737
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2721
2738
  }
2722
- function posAtCoords(view, { x, y }, bias = -1) {
2739
+ function posAtCoords(view, { x, y }, precise, bias = -1) {
2723
2740
  let content = view.contentDOM.getBoundingClientRect(), block;
2724
2741
  let halfLine = view.defaultLineHeight / 2;
2725
2742
  for (let bounced = false;;) {
@@ -2728,7 +2745,7 @@ function posAtCoords(view, { x, y }, bias = -1) {
2728
2745
  bias = block.top > y ? -1 : 1;
2729
2746
  y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2730
2747
  if (bounced)
2731
- return null;
2748
+ return precise ? null : 0;
2732
2749
  else
2733
2750
  bounced = true;
2734
2751
  }
@@ -2737,13 +2754,13 @@ function posAtCoords(view, { x, y }, bias = -1) {
2737
2754
  y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2738
2755
  }
2739
2756
  let lineStart = block.from;
2757
+ x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2740
2758
  // If this is outside of the rendered viewport, we can't determine a position
2741
2759
  if (lineStart < view.viewport.from)
2742
- return view.viewport.from == 0 ? 0 : null;
2760
+ return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
2743
2761
  if (lineStart > view.viewport.to)
2744
- return view.viewport.to == view.state.doc.length ? view.state.doc.length : null;
2762
+ return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
2745
2763
  // Clip x to the viewport sides
2746
- x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2747
2764
  let root = view.root, element = root.elementFromPoint(x, y);
2748
2765
  // There's visible editor content under the point, so we can try
2749
2766
  // using caret(Position|Range)FromPoint as a shortcut
@@ -2770,6 +2787,15 @@ function posAtCoords(view, { x, y }, bias = -1) {
2770
2787
  }
2771
2788
  return view.docView.posFromDOM(node, offset);
2772
2789
  }
2790
+ function posAtCoordsImprecise(view, contentRect, block, x, y) {
2791
+ let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
2792
+ if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
2793
+ let line = Math.floor((y - block.top) / view.defaultLineHeight);
2794
+ into += line * view.viewState.heightOracle.lineLength;
2795
+ }
2796
+ let content = view.state.sliceDoc(block.from, block.to);
2797
+ return block.from + text.findColumn(content, into, view.state.tabSize);
2798
+ }
2773
2799
  // In case of a high line height, Safari's caretRangeFromPoint treats
2774
2800
  // the space between lines as belonging to the last character of the
2775
2801
  // line before. This is used to detect such a result so that it can be
@@ -2832,48 +2858,31 @@ function byGroup(view, pos, start) {
2832
2858
  };
2833
2859
  }
2834
2860
  function moveVertically(view, start, forward, distance) {
2835
- var _a;
2836
2861
  let startPos = start.head, dir = forward ? 1 : -1;
2837
2862
  if (startPos == (forward ? view.state.doc.length : 0))
2838
2863
  return state.EditorSelection.cursor(startPos);
2864
+ let goal = start.goalColumn, startY;
2865
+ let rect = view.contentDOM.getBoundingClientRect();
2839
2866
  let startCoords = view.coordsAtPos(startPos);
2840
2867
  if (startCoords) {
2841
- let rect = view.dom.getBoundingClientRect();
2842
- let goal = (_a = start.goalColumn) !== null && _a !== void 0 ? _a : startCoords.left - rect.left;
2843
- let resolvedGoal = rect.left + goal;
2844
- let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
2845
- for (let startY = dir < 0 ? startCoords.top : startCoords.bottom, extra = 0; extra < 50; extra += 10) {
2846
- let pos = posAtCoords(view, { x: resolvedGoal, y: startY + (dist + extra) * dir }, dir);
2847
- if (pos == null)
2848
- break;
2849
- if (pos != startPos)
2850
- return state.EditorSelection.cursor(pos, undefined, undefined, goal);
2851
- }
2852
- }
2853
- // Outside of the drawn viewport, use a crude column-based approach
2854
- let { doc } = view.state, line = doc.lineAt(startPos), tabSize = view.state.tabSize;
2855
- let goal = start.goalColumn, goalCol = 0;
2856
- if (goal == null) {
2857
- for (const iter = doc.iterRange(line.from, startPos); !iter.next().done;)
2858
- goalCol = text.countColumn(iter.value, goalCol, tabSize);
2859
- goal = goalCol * view.defaultCharacterWidth;
2868
+ if (goal == null)
2869
+ goal = startCoords.left - rect.left;
2870
+ startY = dir < 0 ? startCoords.top : startCoords.bottom;
2860
2871
  }
2861
2872
  else {
2862
- goalCol = Math.round(goal / view.defaultCharacterWidth);
2863
- }
2864
- if (dir < 0 && line.from == 0)
2865
- return state.EditorSelection.cursor(0);
2866
- else if (dir > 0 && line.to == doc.length)
2867
- return state.EditorSelection.cursor(line.to);
2868
- let otherLine = doc.line(line.number + dir);
2869
- let result = otherLine.from;
2870
- let seen = 0;
2871
- for (const iter = doc.iterRange(otherLine.from, otherLine.to); seen >= goalCol && !iter.next().done;) {
2872
- const { offset, leftOver } = text.findColumn(iter.value, seen, goalCol, tabSize);
2873
- seen = goalCol - leftOver;
2874
- result += offset;
2875
- }
2876
- return state.EditorSelection.cursor(result, undefined, undefined, goal);
2873
+ let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
2874
+ if (goal == null)
2875
+ goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
2876
+ startY = dir < 0 ? line.top : line.bottom;
2877
+ }
2878
+ let resolvedGoal = rect.left + goal;
2879
+ let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
2880
+ for (let extra = 0;; extra += 10) {
2881
+ let curY = startY + (dist + extra) * dir;
2882
+ let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
2883
+ if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
2884
+ return state.EditorSelection.cursor(pos, undefined, undefined, goal);
2885
+ }
2877
2886
  }
2878
2887
  function skipAtoms(view, oldPos, pos) {
2879
2888
  let atoms = view.pluginField(PluginField.atomicRanges);
@@ -3053,7 +3062,8 @@ class InputState {
3053
3062
  update(update) {
3054
3063
  if (this.mouseSelection)
3055
3064
  this.mouseSelection.update(update);
3056
- this.lastKeyCode = this.lastSelectionTime = 0;
3065
+ if (update.transactions.length)
3066
+ this.lastKeyCode = this.lastSelectionTime = 0;
3057
3067
  }
3058
3068
  destroy() {
3059
3069
  if (this.mouseSelection)
@@ -3066,8 +3076,8 @@ class MouseSelection {
3066
3076
  constructor(inputState, view, startEvent, style) {
3067
3077
  this.inputState = inputState;
3068
3078
  this.view = view;
3069
- this.startEvent = startEvent;
3070
3079
  this.style = style;
3080
+ this.lastEvent = startEvent;
3071
3081
  let doc = view.contentDOM.ownerDocument;
3072
3082
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
3073
3083
  doc.addEventListener("mouseup", this.up = this.up.bind(this));
@@ -3087,11 +3097,11 @@ class MouseSelection {
3087
3097
  return this.destroy();
3088
3098
  if (this.dragging !== false)
3089
3099
  return;
3090
- this.select(event);
3100
+ this.select(this.lastEvent = event);
3091
3101
  }
3092
3102
  up(event) {
3093
3103
  if (this.dragging == null)
3094
- this.select(this.startEvent);
3104
+ this.select(this.lastEvent);
3095
3105
  if (!this.dragging)
3096
3106
  event.preventDefault();
3097
3107
  this.destroy();
@@ -3114,7 +3124,8 @@ class MouseSelection {
3114
3124
  update(update) {
3115
3125
  if (update.docChanged && this.dragging)
3116
3126
  this.dragging = this.dragging.map(update.changes);
3117
- this.style.update(update);
3127
+ if (this.style.update(update))
3128
+ setTimeout(() => this.select(this.lastEvent), 20);
3118
3129
  }
3119
3130
  }
3120
3131
  function addsSelectionRange(view, event) {
@@ -3274,9 +3285,7 @@ function findPositionSide(view, pos, x, y) {
3274
3285
  return before && insideY(y, before) ? -1 : 1;
3275
3286
  }
3276
3287
  function queryPos(view, event) {
3277
- let pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
3278
- if (pos == null)
3279
- return null;
3288
+ let pos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
3280
3289
  return { pos, bias: findPositionSide(view, pos, event.clientX, event.clientY) };
3281
3290
  }
3282
3291
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -3300,11 +3309,12 @@ function basicMouseSelection(view, event) {
3300
3309
  if (start)
3301
3310
  start.pos = update.changes.mapPos(start.pos);
3302
3311
  startSel = startSel.map(update.changes);
3312
+ lastEvent = null;
3303
3313
  }
3304
3314
  },
3305
3315
  get(event, extend, multiple) {
3306
3316
  let cur;
3307
- if (event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3317
+ if (lastEvent && event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3308
3318
  cur = last;
3309
3319
  else {
3310
3320
  cur = last = queryPos(view, event);
@@ -4676,6 +4686,7 @@ const baseTheme = buildTheme("." + baseThemeID, {
4676
4686
  },
4677
4687
  ".cm-lineWrapping": {
4678
4688
  whiteSpace: "pre-wrap",
4689
+ wordBreak: "break-word",
4679
4690
  overflowWrap: "anywhere"
4680
4691
  },
4681
4692
  "&light .cm-content": { caretColor: "black" },
@@ -5008,7 +5019,8 @@ class DOMObserver {
5008
5019
  this.ignore(() => this.view.docView.sync());
5009
5020
  this.view.docView.dirty = 0 /* Not */;
5010
5021
  }
5011
- this.view.docView.updateSelection();
5022
+ if (newSel)
5023
+ this.view.docView.updateSelection();
5012
5024
  }
5013
5025
  this.clearSelection();
5014
5026
  }
@@ -5436,8 +5448,10 @@ class EditorView {
5436
5448
  scrollTo = transactions.some(tr => tr.scrollIntoView) ? state$1.selection.main : null;
5437
5449
  this.viewState.update(update, scrollTo);
5438
5450
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5439
- if (!update.empty)
5451
+ if (!update.empty) {
5440
5452
  this.updatePlugins(update);
5453
+ this.inputState.update(update);
5454
+ }
5441
5455
  redrawn = this.docView.update(update);
5442
5456
  if (this.state.facet(styleModule) != this.styleModules)
5443
5457
  this.mountStyles();
@@ -5511,10 +5525,12 @@ class EditorView {
5511
5525
  /**
5512
5526
  @internal
5513
5527
  */
5514
- measure() {
5528
+ measure(flush = true) {
5515
5529
  if (this.measureScheduled > -1)
5516
5530
  cancelAnimationFrame(this.measureScheduled);
5517
5531
  this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
5532
+ if (flush)
5533
+ this.observer.flush();
5518
5534
  let updated = null;
5519
5535
  try {
5520
5536
  for (let i = 0;; i++) {
@@ -5544,8 +5560,10 @@ class EditorView {
5544
5560
  else
5545
5561
  updated.flags |= changed;
5546
5562
  this.updateState = 2 /* Updating */;
5547
- if (!update.empty)
5563
+ if (!update.empty) {
5548
5564
  this.updatePlugins(update);
5565
+ this.inputState.update(update);
5566
+ }
5549
5567
  this.updateAttrs();
5550
5568
  if (changed)
5551
5569
  this.docView.update(update);
@@ -5593,7 +5611,7 @@ class EditorView {
5593
5611
  spellcheck: "false",
5594
5612
  autocorrect: "off",
5595
5613
  autocapitalize: "off",
5596
- contenteditable: String(this.state.facet(editable)),
5614
+ contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5597
5615
  class: "cm-content",
5598
5616
  style: `${browser.tabSize}: ${this.state.tabSize}`,
5599
5617
  role: "textbox",
@@ -5622,7 +5640,7 @@ class EditorView {
5622
5640
  if (this.updateState == 2 /* Updating */)
5623
5641
  throw new Error("Reading the editor layout isn't allowed during an update");
5624
5642
  if (this.updateState == 0 /* Idle */ && this.measureScheduled > -1)
5625
- this.measure();
5643
+ this.measure(false);
5626
5644
  }
5627
5645
  /**
5628
5646
  Schedule a layout measurement, optionally providing callbacks to
@@ -5709,11 +5727,9 @@ class EditorView {
5709
5727
  this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
5710
5728
  }
5711
5729
  /**
5712
- Find the extent and height of the visual line (the content shown
5713
- in the editor as a line, which may be smaller than a document
5714
- line when broken up by block widgets, or bigger than a document
5715
- line when line breaks are covered by replaced decorations) at
5716
- the given position.
5730
+ Find the extent and height of the visual line (a range delimited
5731
+ on both sides by either non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range)
5732
+ line breaks, or the start/end of the document) at the given position.
5717
5733
 
5718
5734
  Vertical positions are computed relative to the `docTop`
5719
5735
  argument, which defaults to 0 for this method. You can pass
@@ -5805,13 +5821,9 @@ class EditorView {
5805
5821
  posAtDOM(node, offset = 0) {
5806
5822
  return this.docView.posFromDOM(node, offset);
5807
5823
  }
5808
- /**
5809
- Get the document position at the given screen coordinates.
5810
- Returns null if no valid position could be found.
5811
- */
5812
- posAtCoords(coords) {
5824
+ posAtCoords(coords, precise = true) {
5813
5825
  this.readMeasured();
5814
- return posAtCoords(this, coords);
5826
+ return posAtCoords(this, coords, precise);
5815
5827
  }
5816
5828
  /**
5817
5829
  Get the screen coordinates at the given document position.
@@ -6401,7 +6413,17 @@ function getBase(view) {
6401
6413
  function wrappedLine(view, pos, inside) {
6402
6414
  let range = state.EditorSelection.cursor(pos);
6403
6415
  return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
6404
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from) };
6416
+ to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
6417
+ type: exports.BlockType.Text };
6418
+ }
6419
+ function blockAt(view, pos) {
6420
+ let line = view.visualLineAt(pos);
6421
+ if (Array.isArray(line.type))
6422
+ for (let l of line.type) {
6423
+ if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
6424
+ return l;
6425
+ }
6426
+ return line;
6405
6427
  }
6406
6428
  function measureRange(view, range) {
6407
6429
  if (range.to <= view.viewport.from || range.from >= view.viewport.to)
@@ -6412,22 +6434,25 @@ function measureRange(view, range) {
6412
6434
  let lineStyle = window.getComputedStyle(content.firstChild);
6413
6435
  let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6414
6436
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6415
- let visualStart = view.visualLineAt(from);
6416
- let visualEnd = view.visualLineAt(to);
6437
+ let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6438
+ let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
6439
+ let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
6417
6440
  if (view.lineWrapping) {
6418
- visualStart = wrappedLine(view, from, visualStart);
6419
- visualEnd = wrappedLine(view, to, visualEnd);
6441
+ if (visualStart)
6442
+ visualStart = wrappedLine(view, from, visualStart);
6443
+ if (visualEnd)
6444
+ visualEnd = wrappedLine(view, to, visualEnd);
6420
6445
  }
6421
- if (visualStart.from == visualEnd.from) {
6446
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
6422
6447
  return pieces(drawForLine(range.from, range.to, visualStart));
6423
6448
  }
6424
6449
  else {
6425
- let top = drawForLine(range.from, null, visualStart);
6426
- let bottom = drawForLine(null, range.to, visualEnd);
6450
+ let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
6451
+ let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
6427
6452
  let between = [];
6428
- if (visualStart.to < visualEnd.from - 1)
6453
+ if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6429
6454
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6430
- else if (top.bottom < bottom.top && bottom.top - top.bottom < 4)
6455
+ else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6431
6456
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6432
6457
  return pieces(top).concat(between).concat(pieces(bottom));
6433
6458
  }
@@ -6444,8 +6469,12 @@ function measureRange(view, range) {
6444
6469
  function drawForLine(from, to, line) {
6445
6470
  let top = 1e9, bottom = -1e9, horizontal = [];
6446
6471
  function addSpan(from, fromOpen, to, toOpen, dir) {
6447
- let fromCoords = view.coordsAtPos(from, from == line.to ? -1 : 1);
6448
- let toCoords = view.coordsAtPos(to, to == line.from ? 1 : -1);
6472
+ // Passing 2/-2 is a kludge to force the view to return
6473
+ // coordinates on the proper side of block widgets, since
6474
+ // normalizing the side there, though appropriate for most
6475
+ // coordsAtPos queries, would break selection drawing.
6476
+ let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
6477
+ let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
6449
6478
  top = Math.min(fromCoords.top, toCoords.top, top);
6450
6479
  bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
6451
6480
  if (dir == exports.Direction.LTR)
@@ -6475,6 +6504,10 @@ function measureRange(view, range) {
6475
6504
  addSpan(start, from == null, end, to == null, view.textDirection);
6476
6505
  return { top, bottom, horizontal };
6477
6506
  }
6507
+ function drawForWidget(block, top) {
6508
+ let y = contentRect.top + (top ? block.top : block.bottom);
6509
+ return { top: y, bottom: y, horizontal: [] };
6510
+ }
6478
6511
  }
6479
6512
  function measureCursor(view, cursor, primary) {
6480
6513
  let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
package/dist/index.d.ts CHANGED
@@ -501,8 +501,14 @@ interface MouseSelectionStyle {
501
501
  progress. When the document changes, it may be necessary to map
502
502
  some data (like the original selection or start position)
503
503
  through the changes.
504
+
505
+ This may return `true` to indicate that the `get` method should
506
+ get queried again after the update, because something in the
507
+ update could change its result. Be wary of infinite loops when
508
+ using this (where `get` returns a new selection, which will
509
+ trigger `update`, which schedules another `get` in response).
504
510
  */
505
- update: (update: ViewUpdate) => void;
511
+ update: (update: ViewUpdate) => boolean | void;
506
512
  }
507
513
  declare type MakeSelectionStyle = (view: EditorView, event: MouseEvent) => MouseSelectionStyle | null;
508
514
 
@@ -782,11 +788,9 @@ declare class EditorView {
782
788
  */
783
789
  viewportLines(f: (line: BlockInfo) => void, docTop?: number): void;
784
790
  /**
785
- Find the extent and height of the visual line (the content shown
786
- in the editor as a line, which may be smaller than a document
787
- line when broken up by block widgets, or bigger than a document
788
- line when line breaks are covered by replaced decorations) at
789
- the given position.
791
+ Find the extent and height of the visual line (a range delimited
792
+ on both sides by either non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range)
793
+ line breaks, or the start/end of the document) at the given position.
790
794
 
791
795
  Vertical positions are computed relative to the `docTop`
792
796
  argument, which defaults to 0 for this method. You can pass
@@ -866,6 +870,10 @@ declare class EditorView {
866
870
  Get the document position at the given screen coordinates.
867
871
  Returns null if no valid position could be found.
868
872
  */
873
+ posAtCoords(coords: {
874
+ x: number;
875
+ y: number;
876
+ }, precise: false): number;
869
877
  posAtCoords(coords: {
870
878
  x: number;
871
879
  y: number;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { MapMode, Text as Text$1, Facet, ChangeSet, Transaction, EditorSelection
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { RangeValue, RangeSet, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
5
- import { Text, findClusterBreak, countColumn, findColumn, codePointAt } from '@codemirror/text';
5
+ import { Text, findClusterBreak, findColumn, codePointAt, countColumn } from '@codemirror/text';
6
6
  import { keyName, base } from 'w3c-keyname';
7
7
 
8
8
  function getSelection(root) {
@@ -213,6 +213,19 @@ function dispatchKey(elt, name, code) {
213
213
  elt.dispatchEvent(up);
214
214
  return down.defaultPrevented || up.defaultPrevented;
215
215
  }
216
+ let _plainTextSupported = null;
217
+ function contentEditablePlainTextSupported() {
218
+ if (_plainTextSupported == null) {
219
+ _plainTextSupported = false;
220
+ let dummy = document.createElement("div");
221
+ try {
222
+ dummy.contentEditable = "plaintext-only";
223
+ _plainTextSupported = dummy.contentEditable == "plaintext-only";
224
+ }
225
+ catch (_) { }
226
+ }
227
+ return _plainTextSupported;
228
+ }
216
229
 
217
230
  class DOMPos {
218
231
  constructor(node, offset, precise = true) {
@@ -1953,6 +1966,8 @@ class DocView extends ContentView {
1953
1966
  // FIXME need to handle the case where the selection falls inside a block range
1954
1967
  let anchor = this.domAtPos(main.anchor);
1955
1968
  let head = main.empty ? anchor : this.domAtPos(main.head);
1969
+ // Always reset on Firefox when next to an uneditable node to
1970
+ // avoid invisible cursor bugs (#111)
1956
1971
  if (browser.gecko && main.empty && betweenUneditable(anchor)) {
1957
1972
  let dummy = document.createTextNode("");
1958
1973
  this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
@@ -1962,9 +1977,6 @@ class DocView extends ContentView {
1962
1977
  let domSel = this.view.observer.selectionRange;
1963
1978
  // If the selection is already here, or in an equivalent position, don't touch it
1964
1979
  if (force || !domSel.focusNode ||
1965
- // Always reset on Firefox when next to an uneditable node to
1966
- // avoid invisible cursor bugs (#111)
1967
- (browser.gecko && main.empty && nextToUneditable(domSel.focusNode, domSel.focusOffset)) ||
1968
1980
  !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
1969
1981
  !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
1970
1982
  this.view.observer.ignore(() => {
@@ -2007,6 +2019,8 @@ class DocView extends ContentView {
2007
2019
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2008
2020
  }
2009
2021
  enforceCursorAssoc() {
2022
+ if (this.view.composing)
2023
+ return;
2010
2024
  let cursor = this.view.state.selection.main;
2011
2025
  let sel = getSelection(this.root);
2012
2026
  if (!cursor.empty || !cursor.assoc || !sel.modify)
@@ -2057,7 +2071,10 @@ class DocView extends ContentView {
2057
2071
  coordsAt(pos, side) {
2058
2072
  for (let off = this.length, i = this.children.length - 1;; i--) {
2059
2073
  let child = this.children[i], start = off - child.breakAfter - child.length;
2060
- if (pos > start || pos == start && (child.type == BlockType.Text || !i || this.children[i - 1].breakAfter))
2074
+ if (pos > start ||
2075
+ (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2076
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2077
+ (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2061
2078
  return child.coordsAt(pos - start, side);
2062
2079
  off = start;
2063
2080
  }
@@ -2129,11 +2146,11 @@ class DocView extends ContentView {
2129
2146
  }
2130
2147
  updateDeco() {
2131
2148
  return this.decorations = [
2132
- this.computeBlockGapDeco(),
2133
- this.view.viewState.lineGapDeco,
2134
- this.compositionDeco,
2149
+ ...this.view.pluginField(PluginField.decorations),
2135
2150
  ...this.view.state.facet(decorations),
2136
- ...this.view.pluginField(PluginField.decorations)
2151
+ this.compositionDeco,
2152
+ this.computeBlockGapDeco(),
2153
+ this.view.viewState.lineGapDeco
2137
2154
  ];
2138
2155
  }
2139
2156
  scrollPosIntoView(pos, side) {
@@ -2714,7 +2731,7 @@ function domPosInText(node, x, y) {
2714
2731
  }
2715
2732
  return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
2716
2733
  }
2717
- function posAtCoords(view, { x, y }, bias = -1) {
2734
+ function posAtCoords(view, { x, y }, precise, bias = -1) {
2718
2735
  let content = view.contentDOM.getBoundingClientRect(), block;
2719
2736
  let halfLine = view.defaultLineHeight / 2;
2720
2737
  for (let bounced = false;;) {
@@ -2723,7 +2740,7 @@ function posAtCoords(view, { x, y }, bias = -1) {
2723
2740
  bias = block.top > y ? -1 : 1;
2724
2741
  y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2725
2742
  if (bounced)
2726
- return null;
2743
+ return precise ? null : 0;
2727
2744
  else
2728
2745
  bounced = true;
2729
2746
  }
@@ -2732,13 +2749,13 @@ function posAtCoords(view, { x, y }, bias = -1) {
2732
2749
  y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2733
2750
  }
2734
2751
  let lineStart = block.from;
2752
+ x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2735
2753
  // If this is outside of the rendered viewport, we can't determine a position
2736
2754
  if (lineStart < view.viewport.from)
2737
- return view.viewport.from == 0 ? 0 : null;
2755
+ return view.viewport.from == 0 ? 0 : posAtCoordsImprecise(view, content, block, x, y);
2738
2756
  if (lineStart > view.viewport.to)
2739
- return view.viewport.to == view.state.doc.length ? view.state.doc.length : null;
2757
+ return view.viewport.to == view.state.doc.length ? view.state.doc.length : posAtCoordsImprecise(view, content, block, x, y);
2740
2758
  // Clip x to the viewport sides
2741
- x = Math.max(content.left + 1, Math.min(content.right - 1, x));
2742
2759
  let root = view.root, element = root.elementFromPoint(x, y);
2743
2760
  // There's visible editor content under the point, so we can try
2744
2761
  // using caret(Position|Range)FromPoint as a shortcut
@@ -2765,6 +2782,15 @@ function posAtCoords(view, { x, y }, bias = -1) {
2765
2782
  }
2766
2783
  return view.docView.posFromDOM(node, offset);
2767
2784
  }
2785
+ function posAtCoordsImprecise(view, contentRect, block, x, y) {
2786
+ let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
2787
+ if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
2788
+ let line = Math.floor((y - block.top) / view.defaultLineHeight);
2789
+ into += line * view.viewState.heightOracle.lineLength;
2790
+ }
2791
+ let content = view.state.sliceDoc(block.from, block.to);
2792
+ return block.from + findColumn(content, into, view.state.tabSize);
2793
+ }
2768
2794
  // In case of a high line height, Safari's caretRangeFromPoint treats
2769
2795
  // the space between lines as belonging to the last character of the
2770
2796
  // line before. This is used to detect such a result so that it can be
@@ -2827,48 +2853,31 @@ function byGroup(view, pos, start) {
2827
2853
  };
2828
2854
  }
2829
2855
  function moveVertically(view, start, forward, distance) {
2830
- var _a;
2831
2856
  let startPos = start.head, dir = forward ? 1 : -1;
2832
2857
  if (startPos == (forward ? view.state.doc.length : 0))
2833
2858
  return EditorSelection.cursor(startPos);
2859
+ let goal = start.goalColumn, startY;
2860
+ let rect = view.contentDOM.getBoundingClientRect();
2834
2861
  let startCoords = view.coordsAtPos(startPos);
2835
2862
  if (startCoords) {
2836
- let rect = view.dom.getBoundingClientRect();
2837
- let goal = (_a = start.goalColumn) !== null && _a !== void 0 ? _a : startCoords.left - rect.left;
2838
- let resolvedGoal = rect.left + goal;
2839
- let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
2840
- for (let startY = dir < 0 ? startCoords.top : startCoords.bottom, extra = 0; extra < 50; extra += 10) {
2841
- let pos = posAtCoords(view, { x: resolvedGoal, y: startY + (dist + extra) * dir }, dir);
2842
- if (pos == null)
2843
- break;
2844
- if (pos != startPos)
2845
- return EditorSelection.cursor(pos, undefined, undefined, goal);
2846
- }
2847
- }
2848
- // Outside of the drawn viewport, use a crude column-based approach
2849
- let { doc } = view.state, line = doc.lineAt(startPos), tabSize = view.state.tabSize;
2850
- let goal = start.goalColumn, goalCol = 0;
2851
- if (goal == null) {
2852
- for (const iter = doc.iterRange(line.from, startPos); !iter.next().done;)
2853
- goalCol = countColumn(iter.value, goalCol, tabSize);
2854
- goal = goalCol * view.defaultCharacterWidth;
2863
+ if (goal == null)
2864
+ goal = startCoords.left - rect.left;
2865
+ startY = dir < 0 ? startCoords.top : startCoords.bottom;
2855
2866
  }
2856
2867
  else {
2857
- goalCol = Math.round(goal / view.defaultCharacterWidth);
2858
- }
2859
- if (dir < 0 && line.from == 0)
2860
- return EditorSelection.cursor(0);
2861
- else if (dir > 0 && line.to == doc.length)
2862
- return EditorSelection.cursor(line.to);
2863
- let otherLine = doc.line(line.number + dir);
2864
- let result = otherLine.from;
2865
- let seen = 0;
2866
- for (const iter = doc.iterRange(otherLine.from, otherLine.to); seen >= goalCol && !iter.next().done;) {
2867
- const { offset, leftOver } = findColumn(iter.value, seen, goalCol, tabSize);
2868
- seen = goalCol - leftOver;
2869
- result += offset;
2870
- }
2871
- return EditorSelection.cursor(result, undefined, undefined, goal);
2868
+ let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
2869
+ if (goal == null)
2870
+ goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
2871
+ startY = dir < 0 ? line.top : line.bottom;
2872
+ }
2873
+ let resolvedGoal = rect.left + goal;
2874
+ let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
2875
+ for (let extra = 0;; extra += 10) {
2876
+ let curY = startY + (dist + extra) * dir;
2877
+ let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
2878
+ if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
2879
+ return EditorSelection.cursor(pos, undefined, undefined, goal);
2880
+ }
2872
2881
  }
2873
2882
  function skipAtoms(view, oldPos, pos) {
2874
2883
  let atoms = view.pluginField(PluginField.atomicRanges);
@@ -3048,7 +3057,8 @@ class InputState {
3048
3057
  update(update) {
3049
3058
  if (this.mouseSelection)
3050
3059
  this.mouseSelection.update(update);
3051
- this.lastKeyCode = this.lastSelectionTime = 0;
3060
+ if (update.transactions.length)
3061
+ this.lastKeyCode = this.lastSelectionTime = 0;
3052
3062
  }
3053
3063
  destroy() {
3054
3064
  if (this.mouseSelection)
@@ -3061,8 +3071,8 @@ class MouseSelection {
3061
3071
  constructor(inputState, view, startEvent, style) {
3062
3072
  this.inputState = inputState;
3063
3073
  this.view = view;
3064
- this.startEvent = startEvent;
3065
3074
  this.style = style;
3075
+ this.lastEvent = startEvent;
3066
3076
  let doc = view.contentDOM.ownerDocument;
3067
3077
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
3068
3078
  doc.addEventListener("mouseup", this.up = this.up.bind(this));
@@ -3082,11 +3092,11 @@ class MouseSelection {
3082
3092
  return this.destroy();
3083
3093
  if (this.dragging !== false)
3084
3094
  return;
3085
- this.select(event);
3095
+ this.select(this.lastEvent = event);
3086
3096
  }
3087
3097
  up(event) {
3088
3098
  if (this.dragging == null)
3089
- this.select(this.startEvent);
3099
+ this.select(this.lastEvent);
3090
3100
  if (!this.dragging)
3091
3101
  event.preventDefault();
3092
3102
  this.destroy();
@@ -3109,7 +3119,8 @@ class MouseSelection {
3109
3119
  update(update) {
3110
3120
  if (update.docChanged && this.dragging)
3111
3121
  this.dragging = this.dragging.map(update.changes);
3112
- this.style.update(update);
3122
+ if (this.style.update(update))
3123
+ setTimeout(() => this.select(this.lastEvent), 20);
3113
3124
  }
3114
3125
  }
3115
3126
  function addsSelectionRange(view, event) {
@@ -3269,9 +3280,7 @@ function findPositionSide(view, pos, x, y) {
3269
3280
  return before && insideY(y, before) ? -1 : 1;
3270
3281
  }
3271
3282
  function queryPos(view, event) {
3272
- let pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
3273
- if (pos == null)
3274
- return null;
3283
+ let pos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
3275
3284
  return { pos, bias: findPositionSide(view, pos, event.clientX, event.clientY) };
3276
3285
  }
3277
3286
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -3295,11 +3304,12 @@ function basicMouseSelection(view, event) {
3295
3304
  if (start)
3296
3305
  start.pos = update.changes.mapPos(start.pos);
3297
3306
  startSel = startSel.map(update.changes);
3307
+ lastEvent = null;
3298
3308
  }
3299
3309
  },
3300
3310
  get(event, extend, multiple) {
3301
3311
  let cur;
3302
- if (event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3312
+ if (lastEvent && event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3303
3313
  cur = last;
3304
3314
  else {
3305
3315
  cur = last = queryPos(view, event);
@@ -4670,6 +4680,7 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
4670
4680
  },
4671
4681
  ".cm-lineWrapping": {
4672
4682
  whiteSpace: "pre-wrap",
4683
+ wordBreak: "break-word",
4673
4684
  overflowWrap: "anywhere"
4674
4685
  },
4675
4686
  "&light .cm-content": { caretColor: "black" },
@@ -5002,7 +5013,8 @@ class DOMObserver {
5002
5013
  this.ignore(() => this.view.docView.sync());
5003
5014
  this.view.docView.dirty = 0 /* Not */;
5004
5015
  }
5005
- this.view.docView.updateSelection();
5016
+ if (newSel)
5017
+ this.view.docView.updateSelection();
5006
5018
  }
5007
5019
  this.clearSelection();
5008
5020
  }
@@ -5430,8 +5442,10 @@ class EditorView {
5430
5442
  scrollTo = transactions.some(tr => tr.scrollIntoView) ? state.selection.main : null;
5431
5443
  this.viewState.update(update, scrollTo);
5432
5444
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
5433
- if (!update.empty)
5445
+ if (!update.empty) {
5434
5446
  this.updatePlugins(update);
5447
+ this.inputState.update(update);
5448
+ }
5435
5449
  redrawn = this.docView.update(update);
5436
5450
  if (this.state.facet(styleModule) != this.styleModules)
5437
5451
  this.mountStyles();
@@ -5505,10 +5519,12 @@ class EditorView {
5505
5519
  /**
5506
5520
  @internal
5507
5521
  */
5508
- measure() {
5522
+ measure(flush = true) {
5509
5523
  if (this.measureScheduled > -1)
5510
5524
  cancelAnimationFrame(this.measureScheduled);
5511
5525
  this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
5526
+ if (flush)
5527
+ this.observer.flush();
5512
5528
  let updated = null;
5513
5529
  try {
5514
5530
  for (let i = 0;; i++) {
@@ -5538,8 +5554,10 @@ class EditorView {
5538
5554
  else
5539
5555
  updated.flags |= changed;
5540
5556
  this.updateState = 2 /* Updating */;
5541
- if (!update.empty)
5557
+ if (!update.empty) {
5542
5558
  this.updatePlugins(update);
5559
+ this.inputState.update(update);
5560
+ }
5543
5561
  this.updateAttrs();
5544
5562
  if (changed)
5545
5563
  this.docView.update(update);
@@ -5587,7 +5605,7 @@ class EditorView {
5587
5605
  spellcheck: "false",
5588
5606
  autocorrect: "off",
5589
5607
  autocapitalize: "off",
5590
- contenteditable: String(this.state.facet(editable)),
5608
+ contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5591
5609
  class: "cm-content",
5592
5610
  style: `${browser.tabSize}: ${this.state.tabSize}`,
5593
5611
  role: "textbox",
@@ -5616,7 +5634,7 @@ class EditorView {
5616
5634
  if (this.updateState == 2 /* Updating */)
5617
5635
  throw new Error("Reading the editor layout isn't allowed during an update");
5618
5636
  if (this.updateState == 0 /* Idle */ && this.measureScheduled > -1)
5619
- this.measure();
5637
+ this.measure(false);
5620
5638
  }
5621
5639
  /**
5622
5640
  Schedule a layout measurement, optionally providing callbacks to
@@ -5703,11 +5721,9 @@ class EditorView {
5703
5721
  this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
5704
5722
  }
5705
5723
  /**
5706
- Find the extent and height of the visual line (the content shown
5707
- in the editor as a line, which may be smaller than a document
5708
- line when broken up by block widgets, or bigger than a document
5709
- line when line breaks are covered by replaced decorations) at
5710
- the given position.
5724
+ Find the extent and height of the visual line (a range delimited
5725
+ on both sides by either non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range)
5726
+ line breaks, or the start/end of the document) at the given position.
5711
5727
 
5712
5728
  Vertical positions are computed relative to the `docTop`
5713
5729
  argument, which defaults to 0 for this method. You can pass
@@ -5799,13 +5815,9 @@ class EditorView {
5799
5815
  posAtDOM(node, offset = 0) {
5800
5816
  return this.docView.posFromDOM(node, offset);
5801
5817
  }
5802
- /**
5803
- Get the document position at the given screen coordinates.
5804
- Returns null if no valid position could be found.
5805
- */
5806
- posAtCoords(coords) {
5818
+ posAtCoords(coords, precise = true) {
5807
5819
  this.readMeasured();
5808
- return posAtCoords(this, coords);
5820
+ return posAtCoords(this, coords, precise);
5809
5821
  }
5810
5822
  /**
5811
5823
  Get the screen coordinates at the given document position.
@@ -6395,7 +6407,17 @@ function getBase(view) {
6395
6407
  function wrappedLine(view, pos, inside) {
6396
6408
  let range = EditorSelection.cursor(pos);
6397
6409
  return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
6398
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from) };
6410
+ to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
6411
+ type: BlockType.Text };
6412
+ }
6413
+ function blockAt(view, pos) {
6414
+ let line = view.visualLineAt(pos);
6415
+ if (Array.isArray(line.type))
6416
+ for (let l of line.type) {
6417
+ if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
6418
+ return l;
6419
+ }
6420
+ return line;
6399
6421
  }
6400
6422
  function measureRange(view, range) {
6401
6423
  if (range.to <= view.viewport.from || range.from >= view.viewport.to)
@@ -6406,22 +6428,25 @@ function measureRange(view, range) {
6406
6428
  let lineStyle = window.getComputedStyle(content.firstChild);
6407
6429
  let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6408
6430
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6409
- let visualStart = view.visualLineAt(from);
6410
- let visualEnd = view.visualLineAt(to);
6431
+ let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6432
+ let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
6433
+ let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
6411
6434
  if (view.lineWrapping) {
6412
- visualStart = wrappedLine(view, from, visualStart);
6413
- visualEnd = wrappedLine(view, to, visualEnd);
6435
+ if (visualStart)
6436
+ visualStart = wrappedLine(view, from, visualStart);
6437
+ if (visualEnd)
6438
+ visualEnd = wrappedLine(view, to, visualEnd);
6414
6439
  }
6415
- if (visualStart.from == visualEnd.from) {
6440
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
6416
6441
  return pieces(drawForLine(range.from, range.to, visualStart));
6417
6442
  }
6418
6443
  else {
6419
- let top = drawForLine(range.from, null, visualStart);
6420
- let bottom = drawForLine(null, range.to, visualEnd);
6444
+ let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
6445
+ let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
6421
6446
  let between = [];
6422
- if (visualStart.to < visualEnd.from - 1)
6447
+ if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6423
6448
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6424
- else if (top.bottom < bottom.top && bottom.top - top.bottom < 4)
6449
+ else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == BlockType.Text)
6425
6450
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6426
6451
  return pieces(top).concat(between).concat(pieces(bottom));
6427
6452
  }
@@ -6438,8 +6463,12 @@ function measureRange(view, range) {
6438
6463
  function drawForLine(from, to, line) {
6439
6464
  let top = 1e9, bottom = -1e9, horizontal = [];
6440
6465
  function addSpan(from, fromOpen, to, toOpen, dir) {
6441
- let fromCoords = view.coordsAtPos(from, from == line.to ? -1 : 1);
6442
- let toCoords = view.coordsAtPos(to, to == line.from ? 1 : -1);
6466
+ // Passing 2/-2 is a kludge to force the view to return
6467
+ // coordinates on the proper side of block widgets, since
6468
+ // normalizing the side there, though appropriate for most
6469
+ // coordsAtPos queries, would break selection drawing.
6470
+ let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
6471
+ let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
6443
6472
  top = Math.min(fromCoords.top, toCoords.top, top);
6444
6473
  bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
6445
6474
  if (dir == Direction.LTR)
@@ -6469,6 +6498,10 @@ function measureRange(view, range) {
6469
6498
  addSpan(start, from == null, end, to == null, view.textDirection);
6470
6499
  return { top, bottom, horizontal };
6471
6500
  }
6501
+ function drawForWidget(block, top) {
6502
+ let y = contentRect.top + (top ? block.top : block.bottom);
6503
+ return { top: y, bottom: y, horizontal: [] };
6504
+ }
6472
6505
  }
6473
6506
  function measureCursor(view, cursor, primary) {
6474
6507
  let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.18.15",
3
+ "version": "0.18.19",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@codemirror/rangeset": "^0.18.2",
30
30
  "@codemirror/state": "^0.18.0",
31
- "@codemirror/text": "^0.18.0",
31
+ "@codemirror/text": "^0.18.1",
32
32
  "style-mod": "^4.0.0",
33
33
  "w3c-keyname": "^2.2.4"
34
34
  },