@codemirror/view 0.19.29 → 0.19.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig } from '@codemirror/state';
1
+ import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
@@ -98,8 +98,7 @@ function windowRect(win) {
98
98
  return { left: 0, right: win.innerWidth,
99
99
  top: 0, bottom: win.innerHeight };
100
100
  }
101
- const ScrollSpace = 5;
102
- function scrollRectIntoView(dom, rect, side, center) {
101
+ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
103
102
  let doc = dom.ownerDocument, win = doc.defaultView;
104
103
  for (let cur = dom; cur;) {
105
104
  if (cur.nodeType == 1) { // Element
@@ -118,38 +117,42 @@ function scrollRectIntoView(dom, rect, side, center) {
118
117
  top: rect.top, bottom: rect.top + cur.clientHeight };
119
118
  }
120
119
  let moveX = 0, moveY = 0;
121
- if (center) {
120
+ if (y == "nearest") {
121
+ if (rect.top < bounding.top) {
122
+ moveY = -(bounding.top - rect.top + yMargin);
123
+ if (side > 0 && rect.bottom > bounding.bottom + moveY)
124
+ moveY = rect.bottom - bounding.bottom + moveY + yMargin;
125
+ }
126
+ else if (rect.bottom > bounding.bottom) {
127
+ moveY = rect.bottom - bounding.bottom + yMargin;
128
+ if (side < 0 && (rect.top - moveY) < bounding.top)
129
+ moveY = -(bounding.top + moveY - rect.top + yMargin);
130
+ }
131
+ }
132
+ else {
122
133
  let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
123
- let targetTop;
124
- if (rectHeight <= boundingHeight)
125
- targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
126
- else if (side < 0)
127
- targetTop = rect.top - ScrollSpace;
128
- else
129
- targetTop = rect.bottom + ScrollSpace - boundingHeight;
134
+ let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
135
+ y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
136
+ rect.bottom - boundingHeight + yMargin;
130
137
  moveY = targetTop - bounding.top;
131
- if (Math.abs(moveY) <= 1)
132
- moveY = 0;
133
- }
134
- else if (rect.top < bounding.top) {
135
- moveY = -(bounding.top - rect.top + ScrollSpace);
136
- if (side > 0 && rect.bottom > bounding.bottom + moveY)
137
- moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
138
138
  }
139
- else if (rect.bottom > bounding.bottom) {
140
- moveY = rect.bottom - bounding.bottom + ScrollSpace;
141
- if (side < 0 && (rect.top - moveY) < bounding.top)
142
- moveY = -(bounding.top + moveY - rect.top + ScrollSpace);
143
- }
144
- if (rect.left < bounding.left) {
145
- moveX = -(bounding.left - rect.left + ScrollSpace);
146
- if (side > 0 && rect.right > bounding.right + moveX)
147
- moveX = rect.right - bounding.right + moveX + ScrollSpace;
139
+ if (x == "nearest") {
140
+ if (rect.left < bounding.left) {
141
+ moveX = -(bounding.left - rect.left + xMargin);
142
+ if (side > 0 && rect.right > bounding.right + moveX)
143
+ moveX = rect.right - bounding.right + moveX + xMargin;
144
+ }
145
+ else if (rect.right > bounding.right) {
146
+ moveX = rect.right - bounding.right + xMargin;
147
+ if (side < 0 && rect.left < bounding.left + moveX)
148
+ moveX = -(bounding.left + moveX - rect.left + xMargin);
149
+ }
148
150
  }
149
- else if (rect.right > bounding.right) {
150
- moveX = rect.right - bounding.right + ScrollSpace;
151
- if (side < 0 && rect.left < bounding.left + moveX)
152
- moveX = -(bounding.left + moveX - rect.left + ScrollSpace);
151
+ else {
152
+ let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
153
+ (x == "start") == ltr ? rect.left - xMargin :
154
+ rect.right - (bounding.right - bounding.left) + xMargin;
155
+ moveX = targetLeft - bounding.left;
153
156
  }
154
157
  if (moveX || moveY) {
155
158
  if (top) {
@@ -173,7 +176,7 @@ function scrollRectIntoView(dom, rect, side, center) {
173
176
  if (top)
174
177
  break;
175
178
  cur = cur.assignedSlot || cur.parentNode;
176
- center = false;
179
+ x = y = "nearest";
177
180
  }
178
181
  else if (cur.nodeType == 11) { // A shadow root
179
182
  cur = cur.host;
@@ -260,6 +263,10 @@ function getRoot(node) {
260
263
  }
261
264
  return null;
262
265
  }
266
+ function clearAttributes(node) {
267
+ while (node.attributes.length)
268
+ node.removeAttributeNode(node.attributes[0]);
269
+ }
263
270
 
264
271
  class DOMPos {
265
272
  constructor(node, offset, precise = true) {
@@ -306,14 +313,16 @@ class ContentView {
306
313
  // given position.
307
314
  coordsAt(_pos, _side) { return null; }
308
315
  sync(track) {
309
- var _a;
310
316
  if (this.dirty & 2 /* Node */) {
311
317
  let parent = this.dom;
312
318
  let pos = parent.firstChild;
313
319
  for (let child of this.children) {
314
320
  if (child.dirty) {
315
- if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
316
- child.reuseDOM(pos);
321
+ if (!child.dom && pos) {
322
+ let contentView = ContentView.get(pos);
323
+ if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
324
+ child.reuseDOM(pos);
325
+ }
317
326
  child.sync(track);
318
327
  child.dirty = 0 /* Not */;
319
328
  }
@@ -341,7 +350,7 @@ class ContentView {
341
350
  }
342
351
  }
343
352
  }
344
- reuseDOM(_dom) { return false; }
353
+ reuseDOM(_dom) { }
345
354
  localPosFromDOM(node, offset) {
346
355
  let after;
347
356
  if (node == this.dom) {
@@ -640,10 +649,8 @@ class TextView extends ContentView {
640
649
  }
641
650
  }
642
651
  reuseDOM(dom) {
643
- if (dom.nodeType != 3)
644
- return false;
645
- this.createDOM(dom);
646
- return true;
652
+ if (dom.nodeType == 3)
653
+ this.createDOM(dom);
647
654
  }
648
655
  merge(from, to, source) {
649
656
  if (source && (!(source instanceof TextView) || this.length - (to - from) + source.length > MaxJoinLen))
@@ -655,6 +662,7 @@ class TextView extends ContentView {
655
662
  split(from) {
656
663
  let result = new TextView(this.text.slice(from));
657
664
  this.text = this.text.slice(0, from);
665
+ this.markDirty();
658
666
  return result;
659
667
  }
660
668
  localPosFromDOM(node, offset) {
@@ -677,18 +685,26 @@ class MarkView extends ContentView {
677
685
  for (let ch of children)
678
686
  ch.setParent(this);
679
687
  }
680
- createDOM() {
681
- let dom = document.createElement(this.mark.tagName);
688
+ setAttrs(dom) {
689
+ clearAttributes(dom);
682
690
  if (this.mark.class)
683
691
  dom.className = this.mark.class;
684
692
  if (this.mark.attrs)
685
693
  for (let name in this.mark.attrs)
686
694
  dom.setAttribute(name, this.mark.attrs[name]);
687
- this.setDOM(dom);
695
+ return dom;
696
+ }
697
+ reuseDOM(node) {
698
+ if (node.nodeName == this.mark.tagName.toUpperCase()) {
699
+ this.setDOM(node);
700
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
701
+ }
688
702
  }
689
703
  sync(track) {
690
- if (!this.dom || (this.dirty & 4 /* Attrs */))
691
- this.createDOM();
704
+ if (!this.dom)
705
+ this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
706
+ else if (this.dirty & 4 /* Attrs */)
707
+ this.setAttrs(this.dom);
692
708
  super.sync(track);
693
709
  }
694
710
  merge(from, to, source, _hasStart, openStart, openEnd) {
@@ -832,8 +848,7 @@ class WidgetView extends ContentView {
832
848
  }
833
849
  class CompositionView extends WidgetView {
834
850
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
835
- sync() { if (!this.dom)
836
- this.setDOM(this.widget.toDOM()); }
851
+ sync() { this.setDOM(this.widget.toDOM()); }
837
852
  localPosFromDOM(node, offset) {
838
853
  return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
839
854
  }
@@ -1297,13 +1312,23 @@ class LineView extends ContentView {
1297
1312
  domAtPos(pos) {
1298
1313
  return inlineDOMAtPos(this.dom, this.children, pos);
1299
1314
  }
1315
+ reuseDOM(node) {
1316
+ if (node.nodeName == "DIV") {
1317
+ this.setDOM(node);
1318
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
1319
+ }
1320
+ }
1300
1321
  sync(track) {
1301
1322
  var _a;
1302
- if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1323
+ if (!this.dom) {
1303
1324
  this.setDOM(document.createElement("div"));
1304
1325
  this.dom.className = "cm-line";
1305
1326
  this.prevAttrs = this.attrs ? null : undefined;
1306
1327
  }
1328
+ else if (this.dirty & 4 /* Attrs */) {
1329
+ clearAttributes(this.dom);
1330
+ this.prevAttrs = this.attrs ? null : undefined;
1331
+ }
1307
1332
  if (this.prevAttrs !== undefined) {
1308
1333
  updateAttrs(this.dom, this.prevAttrs, this.attrs);
1309
1334
  this.dom.classList.add("cm-line");
@@ -1575,12 +1600,27 @@ const mouseSelectionStyle = /*@__PURE__*/Facet.define();
1575
1600
  const exceptionSink = /*@__PURE__*/Facet.define();
1576
1601
  const updateListener = /*@__PURE__*/Facet.define();
1577
1602
  const inputHandler = /*@__PURE__*/Facet.define();
1603
+ // FIXME remove
1578
1604
  const scrollTo = /*@__PURE__*/StateEffect.define({
1579
1605
  map: (range, changes) => range.map(changes)
1580
1606
  });
1607
+ // FIXME remove
1581
1608
  const centerOn = /*@__PURE__*/StateEffect.define({
1582
1609
  map: (range, changes) => range.map(changes)
1583
1610
  });
1611
+ class ScrollTarget {
1612
+ constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1613
+ this.range = range;
1614
+ this.y = y;
1615
+ this.x = x;
1616
+ this.yMargin = yMargin;
1617
+ this.xMargin = xMargin;
1618
+ }
1619
+ map(changes) {
1620
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1621
+ }
1622
+ }
1623
+ const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
1584
1624
  /**
1585
1625
  Log or report an unhandled exception in client code. Should
1586
1626
  probably only be used by extension code that allows client code to
@@ -2393,16 +2433,6 @@ class DocView extends ContentView {
2393
2433
  return true;
2394
2434
  }
2395
2435
  }
2396
- reset(sel) {
2397
- if (this.dirty) {
2398
- this.view.observer.ignore(() => this.view.docView.sync());
2399
- this.dirty = 0 /* Not */;
2400
- this.updateSelection(true);
2401
- }
2402
- else {
2403
- this.updateSelection();
2404
- }
2405
- }
2406
2436
  // Used by update and the constructor do perform the actual DOM
2407
2437
  // update
2408
2438
  updateInner(changes, deco, oldLength) {
@@ -2478,7 +2508,8 @@ class DocView extends ContentView {
2478
2508
  // inside an uneditable node, and not bring it back when we
2479
2509
  // move the cursor to its proper position. This tries to
2480
2510
  // restore the keyboard by cycling focus.
2481
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2511
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2512
+ inUneditable(domSel.focusNode, this.dom)) {
2482
2513
  this.dom.blur();
2483
2514
  this.dom.focus({ preventScroll: true });
2484
2515
  }
@@ -2667,7 +2698,8 @@ class DocView extends ContentView {
2667
2698
  this.view.viewState.lineGapDeco
2668
2699
  ];
2669
2700
  }
2670
- scrollIntoView({ range, center }) {
2701
+ scrollIntoView(target) {
2702
+ let { range } = target;
2671
2703
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2672
2704
  if (!rect)
2673
2705
  return;
@@ -2687,10 +2719,11 @@ class DocView extends ContentView {
2687
2719
  if (bottom != null)
2688
2720
  mBottom = Math.max(mBottom, bottom);
2689
2721
  }
2690
- scrollRectIntoView(this.view.scrollDOM, {
2722
+ let targetRect = {
2691
2723
  left: rect.left - mLeft, top: rect.top - mTop,
2692
2724
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2693
- }, range.head < range.anchor ? -1 : 1, center);
2725
+ };
2726
+ scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == Direction.LTR);
2694
2727
  }
2695
2728
  }
2696
2729
  function betweenUneditable(pos) {
@@ -3131,14 +3164,6 @@ class InputState {
3131
3164
  constructor(view) {
3132
3165
  this.lastKeyCode = 0;
3133
3166
  this.lastKeyTime = 0;
3134
- // On Chrome Android, backspace near widgets is just completely
3135
- // broken, and there are no key events, so we need to handle the
3136
- // beforeinput event. Deleting stuff will often create a flurry of
3137
- // events, and interrupting it before it is done just makes
3138
- // subsequent events even more broken, so again, we hold off doing
3139
- // anything until the browser is finished with whatever it is trying
3140
- // to do.
3141
- this.pendingAndroidKey = undefined;
3142
3167
  // On iOS, some keys need to have their default behavior happen
3143
3168
  // (after which we retroactively handle them and reset the DOM) to
3144
3169
  // avoid messing up the virtual keyboard state.
@@ -3207,22 +3232,15 @@ class InputState {
3207
3232
  }
3208
3233
  runCustomHandlers(type, view, event) {
3209
3234
  for (let set of this.customHandlers) {
3210
- let handler = set.handlers[type], handled = false;
3235
+ let handler = set.handlers[type];
3211
3236
  if (handler) {
3212
3237
  try {
3213
- handled = handler.call(set.plugin, event, view);
3238
+ if (handler.call(set.plugin, event, view) || event.defaultPrevented)
3239
+ return true;
3214
3240
  }
3215
3241
  catch (e) {
3216
3242
  logException(view.state, e);
3217
3243
  }
3218
- if (handled || event.defaultPrevented) {
3219
- // Chrome for Android often applies a bunch of nonsensical
3220
- // DOM changes after an enter press, even when
3221
- // preventDefault-ed. This tries to ignore those.
3222
- if (browser.android && type == "keydown" && event.keyCode == 13)
3223
- view.observer.flushSoon();
3224
- return true;
3225
- }
3226
3244
  }
3227
3245
  }
3228
3246
  return false;
@@ -3246,6 +3264,16 @@ class InputState {
3246
3264
  this.lastKeyTime = Date.now();
3247
3265
  if (this.screenKeyEvent(view, event))
3248
3266
  return true;
3267
+ // Chrome for Android usually doesn't fire proper key events, but
3268
+ // occasionally does, usually surrounded by a bunch of complicated
3269
+ // composition changes. When an enter or backspace key event is
3270
+ // seen, hold off on handling DOM events for a bit, and then
3271
+ // dispatch it.
3272
+ if (browser.android && browser.chrome && !event.synthetic &&
3273
+ (event.keyCode == 13 || event.keyCode == 8)) {
3274
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3275
+ return true;
3276
+ }
3249
3277
  // Prevent the default behavior of Enter on iOS makes the
3250
3278
  // virtual keyboard get stuck in the wrong (lowercase)
3251
3279
  // state. So we let it go through, and then, in
@@ -3267,24 +3295,6 @@ class InputState {
3267
3295
  this.pendingIOSKey = undefined;
3268
3296
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3269
3297
  }
3270
- // This causes the DOM observer to pause for a bit, and sets an
3271
- // animation frame (which seems the most reliable way to detect
3272
- // 'Chrome is done flailing about messing with the DOM') to fire a
3273
- // fake key event and re-sync the view again.
3274
- setPendingAndroidKey(view, pending) {
3275
- this.pendingAndroidKey = pending;
3276
- requestAnimationFrame(() => {
3277
- let key = this.pendingAndroidKey;
3278
- if (!key)
3279
- return;
3280
- this.pendingAndroidKey = undefined;
3281
- view.observer.processRecords();
3282
- let startState = view.state;
3283
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3284
- if (view.state == startState)
3285
- view.docView.reset(true);
3286
- });
3287
- }
3288
3298
  ignoreDuringComposition(event) {
3289
3299
  if (!/^key/.test(event.type))
3290
3300
  return false;
@@ -3763,12 +3773,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3763
3773
  if (view.inputState.compositionFirstChange == null)
3764
3774
  view.inputState.compositionFirstChange = true;
3765
3775
  if (view.inputState.composing < 0) {
3776
+ // FIXME possibly set a timeout to clear it again on Android
3777
+ view.inputState.composing = 0;
3766
3778
  if (view.docView.compositionDeco.size) {
3767
3779
  view.observer.flush();
3768
3780
  forceClearComposition(view, true);
3769
3781
  }
3770
- // FIXME possibly set a timeout to clear it again on Android
3771
- view.inputState.composing = 0;
3772
3782
  }
3773
3783
  };
3774
3784
  handlers.compositionend = view => {
@@ -3794,7 +3804,7 @@ handlers.beforeinput = (view, event) => {
3794
3804
  // seems to do nothing at all on Chrome).
3795
3805
  let pending;
3796
3806
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3797
- view.inputState.setPendingAndroidKey(view, pending);
3807
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3798
3808
  if (pending.key == "Backspace" || pending.key == "Delete") {
3799
3809
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3800
3810
  setTimeout(() => {
@@ -4546,15 +4556,6 @@ class LineGapWidget extends WidgetType {
4546
4556
  }
4547
4557
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4548
4558
  }
4549
- class ScrollTarget {
4550
- constructor(range, center = false) {
4551
- this.range = range;
4552
- this.center = center;
4553
- }
4554
- map(changes) {
4555
- return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4556
- }
4557
- }
4558
4559
  class ViewState {
4559
4560
  constructor(state) {
4560
4561
  this.state = state;
@@ -4629,9 +4630,9 @@ class ViewState {
4629
4630
  let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4630
4631
  viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4631
4632
  this.viewport = viewport;
4633
+ this.updateForViewport();
4632
4634
  if (updateLines)
4633
4635
  this.updateViewportLines();
4634
- this.updateForViewport();
4635
4636
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4636
4637
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4637
4638
  update.flags |= this.computeVisibleRanges();
@@ -4663,7 +4664,12 @@ class ViewState {
4663
4664
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4664
4665
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4665
4666
  this.pixelViewport = pixelViewport;
4666
- this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4667
+ let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4668
+ if (inView != this.inView) {
4669
+ this.inView = inView;
4670
+ if (inView)
4671
+ measureContent = true;
4672
+ }
4667
4673
  if (!this.inView)
4668
4674
  return 0;
4669
4675
  if (measureContent) {
@@ -4700,9 +4706,9 @@ class ViewState {
4700
4706
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4701
4707
  if (viewportChange)
4702
4708
  this.viewport = this.getViewport(bias, this.scrollTarget);
4709
+ this.updateForViewport();
4703
4710
  if ((result & 2 /* Height */) || viewportChange)
4704
4711
  this.updateViewportLines();
4705
- this.updateForViewport();
4706
4712
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4707
4713
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4708
4714
  result |= this.computeVisibleRanges();
@@ -4730,9 +4736,9 @@ class ViewState {
4730
4736
  let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4731
4737
  if (head < viewport.from || head > viewport.to) {
4732
4738
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4733
- if (scrollTarget.center)
4739
+ if (scrollTarget.y == "center")
4734
4740
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4735
- else if (head < viewport.from)
4741
+ else if (scrollTarget.y == "start" || head < viewport.from)
4736
4742
  topPos = block.top;
4737
4743
  else
4738
4744
  topPos = block.bottom - viewHeight;
@@ -5090,11 +5096,13 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5090
5096
  // recomputation.
5091
5097
  "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5092
5098
  "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5093
- ".cm-cursor": {
5099
+ ".cm-cursor, .cm-dropCursor": {
5094
5100
  position: "absolute",
5095
5101
  borderLeft: "1.2px solid black",
5096
5102
  marginLeft: "-0.6px",
5097
5103
  pointerEvents: "none",
5104
+ },
5105
+ ".cm-cursor": {
5098
5106
  display: "none"
5099
5107
  },
5100
5108
  "&dark .cm-cursor": {
@@ -5182,6 +5190,7 @@ class DOMObserver {
5182
5190
  this.delayedFlush = -1;
5183
5191
  this.resizeTimeout = -1;
5184
5192
  this.queue = [];
5193
+ this.delayedAndroidKey = null;
5185
5194
  this.scrollTargets = [];
5186
5195
  this.intersection = null;
5187
5196
  this.resize = null;
@@ -5235,7 +5244,7 @@ class DOMObserver {
5235
5244
  this.intersection = new IntersectionObserver(entries => {
5236
5245
  if (this.parentCheck < 0)
5237
5246
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
5238
- if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5247
+ if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
5239
5248
  this.intersecting = !this.intersecting;
5240
5249
  if (this.intersecting != this.view.inView)
5241
5250
  this.onScrollChanged(document.createEvent("Event"));
@@ -5265,7 +5274,7 @@ class DOMObserver {
5265
5274
  }
5266
5275
  }
5267
5276
  onSelectionChange(event) {
5268
- if (!this.readSelectionRange())
5277
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5269
5278
  return;
5270
5279
  let { view } = this, sel = this.selectionRange;
5271
5280
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5361,6 +5370,32 @@ class DOMObserver {
5361
5370
  this.queue.length = 0;
5362
5371
  this.selectionChanged = false;
5363
5372
  }
5373
+ // Chrome Android, especially in combination with GBoard, not only
5374
+ // doesn't reliably fire regular key events, but also often
5375
+ // surrounds the effect of enter or backspace with a bunch of
5376
+ // composition events that, when interrupted, cause text duplication
5377
+ // or other kinds of corruption. This hack makes the editor back off
5378
+ // from handling DOM changes for a moment when such a key is
5379
+ // detected (via beforeinput or keydown), and then dispatches the
5380
+ // key event, throwing away the DOM changes if it gets handled.
5381
+ delayAndroidKey(key, keyCode) {
5382
+ if (!this.delayedAndroidKey)
5383
+ requestAnimationFrame(() => {
5384
+ let key = this.delayedAndroidKey;
5385
+ this.delayedAndroidKey = null;
5386
+ let startState = this.view.state;
5387
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5388
+ this.processRecords();
5389
+ else
5390
+ this.flush();
5391
+ if (this.view.state == startState)
5392
+ this.view.update([]);
5393
+ });
5394
+ // Since backspace beforeinput is sometimes signalled spuriously,
5395
+ // Enter always takes precedence.
5396
+ if (!this.delayedAndroidKey || key == "Enter")
5397
+ this.delayedAndroidKey = { key, keyCode };
5398
+ }
5364
5399
  flushSoon() {
5365
5400
  if (this.delayedFlush < 0)
5366
5401
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5397,13 +5432,13 @@ class DOMObserver {
5397
5432
  }
5398
5433
  // Apply pending changes, if any
5399
5434
  flush(readSelection = true) {
5400
- if (readSelection)
5401
- this.readSelectionRange();
5402
5435
  // Completely hold off flushing when pending keys are set—the code
5403
5436
  // managing those will make sure processRecords is called and the
5404
5437
  // view is resynchronized after
5405
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5438
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5406
5439
  return;
5440
+ if (readSelection)
5441
+ this.readSelectionRange();
5407
5442
  let { from, to, typeOver } = this.processRecords();
5408
5443
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5409
5444
  if (from < 0 && !newSel)
@@ -5809,7 +5844,9 @@ class EditorView {
5809
5844
  if (e.is(scrollTo))
5810
5845
  scrollTarget = new ScrollTarget(e.value);
5811
5846
  else if (e.is(centerOn))
5812
- scrollTarget = new ScrollTarget(e.value, true);
5847
+ scrollTarget = new ScrollTarget(e.value, "center");
5848
+ else if (e.is(scrollIntoView))
5849
+ scrollTarget = e.value;
5813
5850
  }
5814
5851
  }
5815
5852
  this.viewState.update(update, scrollTarget);
@@ -6377,6 +6414,14 @@ class EditorView {
6377
6414
  this.destroyed = true;
6378
6415
  }
6379
6416
  /**
6417
+ Returns an effect that can be
6418
+ [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
6419
+ cause it to scroll the given position or range into view.
6420
+ */
6421
+ static scrollIntoView(pos, options = {}) {
6422
+ return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
6423
+ }
6424
+ /**
6380
6425
  Facet that can be used to add DOM event handlers. The value
6381
6426
  should be an object mapping event names to handler functions. The
6382
6427
  first such function to return true will be assumed to have handled
@@ -6430,11 +6475,15 @@ class EditorView {
6430
6475
  /**
6431
6476
  Effect that can be [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a
6432
6477
  transaction to make it scroll the given range into view.
6478
+
6479
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6433
6480
  */
6434
6481
  EditorView.scrollTo = scrollTo;
6435
6482
  /**
6436
6483
  Effect that makes the editor scroll the given range to the
6437
6484
  center of the visible view.
6485
+
6486
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6438
6487
  */
6439
6488
  EditorView.centerOn = centerOn;
6440
6489
  /**
@@ -6903,7 +6952,7 @@ function measureRange(view, range) {
6903
6952
  let ltr = view.textDirection == Direction.LTR;
6904
6953
  let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
6905
6954
  let lineStyle = window.getComputedStyle(content.firstChild);
6906
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6955
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
6907
6956
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6908
6957
  let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6909
6958
  let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
@@ -6923,7 +6972,7 @@ function measureRange(view, range) {
6923
6972
  let between = [];
6924
6973
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6925
6974
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6926
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == BlockType.Text)
6975
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
6927
6976
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6928
6977
  return pieces(top).concat(between).concat(pieces(bottom));
6929
6978
  }
@@ -6988,6 +7037,98 @@ function measureCursor(view, cursor, primary) {
6988
7037
  return new Piece(pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top, primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary");
6989
7038
  }
6990
7039
 
7040
+ const setDropCursorPos = /*@__PURE__*/StateEffect.define({
7041
+ map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
7042
+ });
7043
+ const dropCursorPos = /*@__PURE__*/StateField.define({
7044
+ create() { return null; },
7045
+ update(pos, tr) {
7046
+ if (pos != null)
7047
+ pos = tr.changes.mapPos(pos);
7048
+ return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7049
+ }
7050
+ });
7051
+ const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
7052
+ constructor(view) {
7053
+ this.view = view;
7054
+ this.cursor = null;
7055
+ this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7056
+ }
7057
+ update(update) {
7058
+ var _a;
7059
+ let cursorPos = update.state.field(dropCursorPos);
7060
+ if (cursorPos == null) {
7061
+ if (this.cursor != null) {
7062
+ (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7063
+ this.cursor = null;
7064
+ }
7065
+ }
7066
+ else {
7067
+ if (!this.cursor) {
7068
+ this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7069
+ this.cursor.className = "cm-dropCursor";
7070
+ }
7071
+ if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7072
+ this.view.requestMeasure(this.measureReq);
7073
+ }
7074
+ }
7075
+ readPos() {
7076
+ let pos = this.view.state.field(dropCursorPos);
7077
+ let rect = pos != null && this.view.coordsAtPos(pos);
7078
+ if (!rect)
7079
+ return null;
7080
+ let outer = this.view.scrollDOM.getBoundingClientRect();
7081
+ return {
7082
+ left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
7083
+ top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
7084
+ height: rect.bottom - rect.top
7085
+ };
7086
+ }
7087
+ drawCursor(pos) {
7088
+ if (this.cursor) {
7089
+ if (pos) {
7090
+ this.cursor.style.left = pos.left + "px";
7091
+ this.cursor.style.top = pos.top + "px";
7092
+ this.cursor.style.height = pos.height + "px";
7093
+ }
7094
+ else {
7095
+ this.cursor.style.left = "-100000px";
7096
+ }
7097
+ }
7098
+ }
7099
+ destroy() {
7100
+ if (this.cursor)
7101
+ this.cursor.remove();
7102
+ }
7103
+ setDropPos(pos) {
7104
+ if (this.view.state.field(dropCursorPos) != pos)
7105
+ this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7106
+ }
7107
+ }, {
7108
+ eventHandlers: {
7109
+ dragover(event) {
7110
+ this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7111
+ },
7112
+ dragleave(event) {
7113
+ if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7114
+ this.setDropPos(null);
7115
+ },
7116
+ dragend() {
7117
+ this.setDropPos(null);
7118
+ },
7119
+ drop() {
7120
+ this.setDropPos(null);
7121
+ }
7122
+ }
7123
+ });
7124
+ /**
7125
+ Draws a cursor at the current drop position when something is
7126
+ dragged over the editor.
7127
+ */
7128
+ function dropCursor() {
7129
+ return [dropCursorPos, drawDropCursor];
7130
+ }
7131
+
6991
7132
  function iterMatches(doc, re, from, to, f) {
6992
7133
  re.lastIndex = 0;
6993
7134
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@@ -6996,6 +7137,22 @@ function iterMatches(doc, re, from, to, f) {
6996
7137
  f(pos + m.index, pos + m.index + m[0].length, m);
6997
7138
  }
6998
7139
  }
7140
+ function matchRanges(view, maxLength) {
7141
+ let visible = view.visibleRanges;
7142
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7143
+ visible[0].to == view.viewport.to)
7144
+ return visible;
7145
+ let result = [];
7146
+ for (let { from, to } of visible) {
7147
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7148
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7149
+ if (result.length && result[result.length - 1].to >= from)
7150
+ result[result.length - 1].to = to;
7151
+ else
7152
+ result.push({ from, to });
7153
+ }
7154
+ return result;
7155
+ }
6999
7156
  /**
7000
7157
  Helper class used to make it easier to maintain decorations on
7001
7158
  visible code that matches a given regular expression. To be used
@@ -7007,12 +7164,13 @@ class MatchDecorator {
7007
7164
  Create a decorator.
7008
7165
  */
7009
7166
  constructor(config) {
7010
- let { regexp, decoration, boundary } = config;
7167
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
7011
7168
  if (!regexp.global)
7012
7169
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7013
7170
  this.regexp = regexp;
7014
7171
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7015
7172
  this.boundary = boundary;
7173
+ this.maxLength = maxLength;
7016
7174
  }
7017
7175
  /**
7018
7176
  Compute the full set of decorations for matches in the given
@@ -7021,7 +7179,7 @@ class MatchDecorator {
7021
7179
  */
7022
7180
  createDeco(view) {
7023
7181
  let build = new RangeSetBuilder();
7024
- for (let { from, to } of view.visibleRanges)
7182
+ for (let { from, to } of matchRanges(view, this.maxLength))
7025
7183
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7026
7184
  return build.finish();
7027
7185
  }
@@ -7322,4 +7480,4 @@ function placeholder(content) {
7322
7480
  */
7323
7481
  const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder, moveVisually };
7324
7482
 
7325
- export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };
7483
+ export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, dropCursor, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };