@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.cjs CHANGED
@@ -101,8 +101,7 @@ function windowRect(win) {
101
101
  return { left: 0, right: win.innerWidth,
102
102
  top: 0, bottom: win.innerHeight };
103
103
  }
104
- const ScrollSpace = 5;
105
- function scrollRectIntoView(dom, rect, side, center) {
104
+ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
106
105
  let doc = dom.ownerDocument, win = doc.defaultView;
107
106
  for (let cur = dom; cur;) {
108
107
  if (cur.nodeType == 1) { // Element
@@ -121,38 +120,42 @@ function scrollRectIntoView(dom, rect, side, center) {
121
120
  top: rect.top, bottom: rect.top + cur.clientHeight };
122
121
  }
123
122
  let moveX = 0, moveY = 0;
124
- if (center) {
123
+ if (y == "nearest") {
124
+ if (rect.top < bounding.top) {
125
+ moveY = -(bounding.top - rect.top + yMargin);
126
+ if (side > 0 && rect.bottom > bounding.bottom + moveY)
127
+ moveY = rect.bottom - bounding.bottom + moveY + yMargin;
128
+ }
129
+ else if (rect.bottom > bounding.bottom) {
130
+ moveY = rect.bottom - bounding.bottom + yMargin;
131
+ if (side < 0 && (rect.top - moveY) < bounding.top)
132
+ moveY = -(bounding.top + moveY - rect.top + yMargin);
133
+ }
134
+ }
135
+ else {
125
136
  let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
126
- let targetTop;
127
- if (rectHeight <= boundingHeight)
128
- targetTop = rect.top + rectHeight / 2 - boundingHeight / 2;
129
- else if (side < 0)
130
- targetTop = rect.top - ScrollSpace;
131
- else
132
- targetTop = rect.bottom + ScrollSpace - boundingHeight;
137
+ let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
138
+ y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
139
+ rect.bottom - boundingHeight + yMargin;
133
140
  moveY = targetTop - bounding.top;
134
- if (Math.abs(moveY) <= 1)
135
- moveY = 0;
136
- }
137
- else if (rect.top < bounding.top) {
138
- moveY = -(bounding.top - rect.top + ScrollSpace);
139
- if (side > 0 && rect.bottom > bounding.bottom + moveY)
140
- moveY = rect.bottom - bounding.bottom + moveY + ScrollSpace;
141
141
  }
142
- else if (rect.bottom > bounding.bottom) {
143
- moveY = rect.bottom - bounding.bottom + ScrollSpace;
144
- if (side < 0 && (rect.top - moveY) < bounding.top)
145
- moveY = -(bounding.top + moveY - rect.top + ScrollSpace);
146
- }
147
- if (rect.left < bounding.left) {
148
- moveX = -(bounding.left - rect.left + ScrollSpace);
149
- if (side > 0 && rect.right > bounding.right + moveX)
150
- moveX = rect.right - bounding.right + moveX + ScrollSpace;
142
+ if (x == "nearest") {
143
+ if (rect.left < bounding.left) {
144
+ moveX = -(bounding.left - rect.left + xMargin);
145
+ if (side > 0 && rect.right > bounding.right + moveX)
146
+ moveX = rect.right - bounding.right + moveX + xMargin;
147
+ }
148
+ else if (rect.right > bounding.right) {
149
+ moveX = rect.right - bounding.right + xMargin;
150
+ if (side < 0 && rect.left < bounding.left + moveX)
151
+ moveX = -(bounding.left + moveX - rect.left + xMargin);
152
+ }
151
153
  }
152
- else if (rect.right > bounding.right) {
153
- moveX = rect.right - bounding.right + ScrollSpace;
154
- if (side < 0 && rect.left < bounding.left + moveX)
155
- moveX = -(bounding.left + moveX - rect.left + ScrollSpace);
154
+ else {
155
+ let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
156
+ (x == "start") == ltr ? rect.left - xMargin :
157
+ rect.right - (bounding.right - bounding.left) + xMargin;
158
+ moveX = targetLeft - bounding.left;
156
159
  }
157
160
  if (moveX || moveY) {
158
161
  if (top) {
@@ -176,7 +179,7 @@ function scrollRectIntoView(dom, rect, side, center) {
176
179
  if (top)
177
180
  break;
178
181
  cur = cur.assignedSlot || cur.parentNode;
179
- center = false;
182
+ x = y = "nearest";
180
183
  }
181
184
  else if (cur.nodeType == 11) { // A shadow root
182
185
  cur = cur.host;
@@ -263,6 +266,10 @@ function getRoot(node) {
263
266
  }
264
267
  return null;
265
268
  }
269
+ function clearAttributes(node) {
270
+ while (node.attributes.length)
271
+ node.removeAttributeNode(node.attributes[0]);
272
+ }
266
273
 
267
274
  class DOMPos {
268
275
  constructor(node, offset, precise = true) {
@@ -309,14 +316,16 @@ class ContentView {
309
316
  // given position.
310
317
  coordsAt(_pos, _side) { return null; }
311
318
  sync(track) {
312
- var _a;
313
319
  if (this.dirty & 2 /* Node */) {
314
320
  let parent = this.dom;
315
321
  let pos = parent.firstChild;
316
322
  for (let child of this.children) {
317
323
  if (child.dirty) {
318
- if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
319
- child.reuseDOM(pos);
324
+ if (!child.dom && pos) {
325
+ let contentView = ContentView.get(pos);
326
+ if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
327
+ child.reuseDOM(pos);
328
+ }
320
329
  child.sync(track);
321
330
  child.dirty = 0 /* Not */;
322
331
  }
@@ -344,7 +353,7 @@ class ContentView {
344
353
  }
345
354
  }
346
355
  }
347
- reuseDOM(_dom) { return false; }
356
+ reuseDOM(_dom) { }
348
357
  localPosFromDOM(node, offset) {
349
358
  let after;
350
359
  if (node == this.dom) {
@@ -643,10 +652,8 @@ class TextView extends ContentView {
643
652
  }
644
653
  }
645
654
  reuseDOM(dom) {
646
- if (dom.nodeType != 3)
647
- return false;
648
- this.createDOM(dom);
649
- return true;
655
+ if (dom.nodeType == 3)
656
+ this.createDOM(dom);
650
657
  }
651
658
  merge(from, to, source) {
652
659
  if (source && (!(source instanceof TextView) || this.length - (to - from) + source.length > MaxJoinLen))
@@ -658,6 +665,7 @@ class TextView extends ContentView {
658
665
  split(from) {
659
666
  let result = new TextView(this.text.slice(from));
660
667
  this.text = this.text.slice(0, from);
668
+ this.markDirty();
661
669
  return result;
662
670
  }
663
671
  localPosFromDOM(node, offset) {
@@ -680,18 +688,26 @@ class MarkView extends ContentView {
680
688
  for (let ch of children)
681
689
  ch.setParent(this);
682
690
  }
683
- createDOM() {
684
- let dom = document.createElement(this.mark.tagName);
691
+ setAttrs(dom) {
692
+ clearAttributes(dom);
685
693
  if (this.mark.class)
686
694
  dom.className = this.mark.class;
687
695
  if (this.mark.attrs)
688
696
  for (let name in this.mark.attrs)
689
697
  dom.setAttribute(name, this.mark.attrs[name]);
690
- this.setDOM(dom);
698
+ return dom;
699
+ }
700
+ reuseDOM(node) {
701
+ if (node.nodeName == this.mark.tagName.toUpperCase()) {
702
+ this.setDOM(node);
703
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
704
+ }
691
705
  }
692
706
  sync(track) {
693
- if (!this.dom || (this.dirty & 4 /* Attrs */))
694
- this.createDOM();
707
+ if (!this.dom)
708
+ this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
709
+ else if (this.dirty & 4 /* Attrs */)
710
+ this.setAttrs(this.dom);
695
711
  super.sync(track);
696
712
  }
697
713
  merge(from, to, source, _hasStart, openStart, openEnd) {
@@ -835,8 +851,7 @@ class WidgetView extends ContentView {
835
851
  }
836
852
  class CompositionView extends WidgetView {
837
853
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
838
- sync() { if (!this.dom)
839
- this.setDOM(this.widget.toDOM()); }
854
+ sync() { this.setDOM(this.widget.toDOM()); }
840
855
  localPosFromDOM(node, offset) {
841
856
  return !offset ? 0 : node.nodeType == 3 ? Math.min(offset, this.length) : this.length;
842
857
  }
@@ -1301,13 +1316,23 @@ class LineView extends ContentView {
1301
1316
  domAtPos(pos) {
1302
1317
  return inlineDOMAtPos(this.dom, this.children, pos);
1303
1318
  }
1319
+ reuseDOM(node) {
1320
+ if (node.nodeName == "DIV") {
1321
+ this.setDOM(node);
1322
+ this.dirty |= 4 /* Attrs */ | 2 /* Node */;
1323
+ }
1324
+ }
1304
1325
  sync(track) {
1305
1326
  var _a;
1306
- if (!this.dom || (this.dirty & 4 /* Attrs */)) {
1327
+ if (!this.dom) {
1307
1328
  this.setDOM(document.createElement("div"));
1308
1329
  this.dom.className = "cm-line";
1309
1330
  this.prevAttrs = this.attrs ? null : undefined;
1310
1331
  }
1332
+ else if (this.dirty & 4 /* Attrs */) {
1333
+ clearAttributes(this.dom);
1334
+ this.prevAttrs = this.attrs ? null : undefined;
1335
+ }
1311
1336
  if (this.prevAttrs !== undefined) {
1312
1337
  updateAttrs(this.dom, this.prevAttrs, this.attrs);
1313
1338
  this.dom.classList.add("cm-line");
@@ -1579,12 +1604,27 @@ const mouseSelectionStyle = state.Facet.define();
1579
1604
  const exceptionSink = state.Facet.define();
1580
1605
  const updateListener = state.Facet.define();
1581
1606
  const inputHandler = state.Facet.define();
1607
+ // FIXME remove
1582
1608
  const scrollTo = state.StateEffect.define({
1583
1609
  map: (range, changes) => range.map(changes)
1584
1610
  });
1611
+ // FIXME remove
1585
1612
  const centerOn = state.StateEffect.define({
1586
1613
  map: (range, changes) => range.map(changes)
1587
1614
  });
1615
+ class ScrollTarget {
1616
+ constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1617
+ this.range = range;
1618
+ this.y = y;
1619
+ this.x = x;
1620
+ this.yMargin = yMargin;
1621
+ this.xMargin = xMargin;
1622
+ }
1623
+ map(changes) {
1624
+ return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1625
+ }
1626
+ }
1627
+ const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
1588
1628
  /**
1589
1629
  Log or report an unhandled exception in client code. Should
1590
1630
  probably only be used by extension code that allows client code to
@@ -2398,16 +2438,6 @@ class DocView extends ContentView {
2398
2438
  return true;
2399
2439
  }
2400
2440
  }
2401
- reset(sel) {
2402
- if (this.dirty) {
2403
- this.view.observer.ignore(() => this.view.docView.sync());
2404
- this.dirty = 0 /* Not */;
2405
- this.updateSelection(true);
2406
- }
2407
- else {
2408
- this.updateSelection();
2409
- }
2410
- }
2411
2441
  // Used by update and the constructor do perform the actual DOM
2412
2442
  // update
2413
2443
  updateInner(changes, deco, oldLength) {
@@ -2483,7 +2513,8 @@ class DocView extends ContentView {
2483
2513
  // inside an uneditable node, and not bring it back when we
2484
2514
  // move the cursor to its proper position. This tries to
2485
2515
  // restore the keyboard by cycling focus.
2486
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2516
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2517
+ inUneditable(domSel.focusNode, this.dom)) {
2487
2518
  this.dom.blur();
2488
2519
  this.dom.focus({ preventScroll: true });
2489
2520
  }
@@ -2672,7 +2703,8 @@ class DocView extends ContentView {
2672
2703
  this.view.viewState.lineGapDeco
2673
2704
  ];
2674
2705
  }
2675
- scrollIntoView({ range, center }) {
2706
+ scrollIntoView(target) {
2707
+ let { range } = target;
2676
2708
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2677
2709
  if (!rect)
2678
2710
  return;
@@ -2692,10 +2724,11 @@ class DocView extends ContentView {
2692
2724
  if (bottom != null)
2693
2725
  mBottom = Math.max(mBottom, bottom);
2694
2726
  }
2695
- scrollRectIntoView(this.view.scrollDOM, {
2727
+ let targetRect = {
2696
2728
  left: rect.left - mLeft, top: rect.top - mTop,
2697
2729
  right: rect.right + mRight, bottom: rect.bottom + mBottom
2698
- }, range.head < range.anchor ? -1 : 1, center);
2730
+ };
2731
+ scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == exports.Direction.LTR);
2699
2732
  }
2700
2733
  }
2701
2734
  function betweenUneditable(pos) {
@@ -3136,14 +3169,6 @@ class InputState {
3136
3169
  constructor(view) {
3137
3170
  this.lastKeyCode = 0;
3138
3171
  this.lastKeyTime = 0;
3139
- // On Chrome Android, backspace near widgets is just completely
3140
- // broken, and there are no key events, so we need to handle the
3141
- // beforeinput event. Deleting stuff will often create a flurry of
3142
- // events, and interrupting it before it is done just makes
3143
- // subsequent events even more broken, so again, we hold off doing
3144
- // anything until the browser is finished with whatever it is trying
3145
- // to do.
3146
- this.pendingAndroidKey = undefined;
3147
3172
  // On iOS, some keys need to have their default behavior happen
3148
3173
  // (after which we retroactively handle them and reset the DOM) to
3149
3174
  // avoid messing up the virtual keyboard state.
@@ -3212,22 +3237,15 @@ class InputState {
3212
3237
  }
3213
3238
  runCustomHandlers(type, view, event) {
3214
3239
  for (let set of this.customHandlers) {
3215
- let handler = set.handlers[type], handled = false;
3240
+ let handler = set.handlers[type];
3216
3241
  if (handler) {
3217
3242
  try {
3218
- handled = handler.call(set.plugin, event, view);
3243
+ if (handler.call(set.plugin, event, view) || event.defaultPrevented)
3244
+ return true;
3219
3245
  }
3220
3246
  catch (e) {
3221
3247
  logException(view.state, e);
3222
3248
  }
3223
- if (handled || event.defaultPrevented) {
3224
- // Chrome for Android often applies a bunch of nonsensical
3225
- // DOM changes after an enter press, even when
3226
- // preventDefault-ed. This tries to ignore those.
3227
- if (browser.android && type == "keydown" && event.keyCode == 13)
3228
- view.observer.flushSoon();
3229
- return true;
3230
- }
3231
3249
  }
3232
3250
  }
3233
3251
  return false;
@@ -3251,6 +3269,16 @@ class InputState {
3251
3269
  this.lastKeyTime = Date.now();
3252
3270
  if (this.screenKeyEvent(view, event))
3253
3271
  return true;
3272
+ // Chrome for Android usually doesn't fire proper key events, but
3273
+ // occasionally does, usually surrounded by a bunch of complicated
3274
+ // composition changes. When an enter or backspace key event is
3275
+ // seen, hold off on handling DOM events for a bit, and then
3276
+ // dispatch it.
3277
+ if (browser.android && browser.chrome && !event.synthetic &&
3278
+ (event.keyCode == 13 || event.keyCode == 8)) {
3279
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3280
+ return true;
3281
+ }
3254
3282
  // Prevent the default behavior of Enter on iOS makes the
3255
3283
  // virtual keyboard get stuck in the wrong (lowercase)
3256
3284
  // state. So we let it go through, and then, in
@@ -3272,24 +3300,6 @@ class InputState {
3272
3300
  this.pendingIOSKey = undefined;
3273
3301
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3274
3302
  }
3275
- // This causes the DOM observer to pause for a bit, and sets an
3276
- // animation frame (which seems the most reliable way to detect
3277
- // 'Chrome is done flailing about messing with the DOM') to fire a
3278
- // fake key event and re-sync the view again.
3279
- setPendingAndroidKey(view, pending) {
3280
- this.pendingAndroidKey = pending;
3281
- requestAnimationFrame(() => {
3282
- let key = this.pendingAndroidKey;
3283
- if (!key)
3284
- return;
3285
- this.pendingAndroidKey = undefined;
3286
- view.observer.processRecords();
3287
- let startState = view.state;
3288
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3289
- if (view.state == startState)
3290
- view.docView.reset(true);
3291
- });
3292
- }
3293
3303
  ignoreDuringComposition(event) {
3294
3304
  if (!/^key/.test(event.type))
3295
3305
  return false;
@@ -3768,12 +3778,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3768
3778
  if (view.inputState.compositionFirstChange == null)
3769
3779
  view.inputState.compositionFirstChange = true;
3770
3780
  if (view.inputState.composing < 0) {
3781
+ // FIXME possibly set a timeout to clear it again on Android
3782
+ view.inputState.composing = 0;
3771
3783
  if (view.docView.compositionDeco.size) {
3772
3784
  view.observer.flush();
3773
3785
  forceClearComposition(view, true);
3774
3786
  }
3775
- // FIXME possibly set a timeout to clear it again on Android
3776
- view.inputState.composing = 0;
3777
3787
  }
3778
3788
  };
3779
3789
  handlers.compositionend = view => {
@@ -3799,7 +3809,7 @@ handlers.beforeinput = (view, event) => {
3799
3809
  // seems to do nothing at all on Chrome).
3800
3810
  let pending;
3801
3811
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3802
- view.inputState.setPendingAndroidKey(view, pending);
3812
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3803
3813
  if (pending.key == "Backspace" || pending.key == "Delete") {
3804
3814
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3805
3815
  setTimeout(() => {
@@ -4552,15 +4562,6 @@ class LineGapWidget extends WidgetType {
4552
4562
  }
4553
4563
  get estimatedHeight() { return this.vertical ? this.size : -1; }
4554
4564
  }
4555
- class ScrollTarget {
4556
- constructor(range, center = false) {
4557
- this.range = range;
4558
- this.center = center;
4559
- }
4560
- map(changes) {
4561
- return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.center);
4562
- }
4563
- }
4564
4565
  class ViewState {
4565
4566
  constructor(state) {
4566
4567
  this.state = state;
@@ -4635,9 +4636,9 @@ class ViewState {
4635
4636
  let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4636
4637
  viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4637
4638
  this.viewport = viewport;
4639
+ this.updateForViewport();
4638
4640
  if (updateLines)
4639
4641
  this.updateViewportLines();
4640
- this.updateForViewport();
4641
4642
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4642
4643
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4643
4644
  update.flags |= this.computeVisibleRanges();
@@ -4669,7 +4670,12 @@ class ViewState {
4669
4670
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4670
4671
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4671
4672
  this.pixelViewport = pixelViewport;
4672
- this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4673
+ let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4674
+ if (inView != this.inView) {
4675
+ this.inView = inView;
4676
+ if (inView)
4677
+ measureContent = true;
4678
+ }
4673
4679
  if (!this.inView)
4674
4680
  return 0;
4675
4681
  if (measureContent) {
@@ -4706,9 +4712,9 @@ class ViewState {
4706
4712
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4707
4713
  if (viewportChange)
4708
4714
  this.viewport = this.getViewport(bias, this.scrollTarget);
4715
+ this.updateForViewport();
4709
4716
  if ((result & 2 /* Height */) || viewportChange)
4710
4717
  this.updateViewportLines();
4711
- this.updateForViewport();
4712
4718
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4713
4719
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4714
4720
  result |= this.computeVisibleRanges();
@@ -4736,9 +4742,9 @@ class ViewState {
4736
4742
  let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4737
4743
  if (head < viewport.from || head > viewport.to) {
4738
4744
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4739
- if (scrollTarget.center)
4745
+ if (scrollTarget.y == "center")
4740
4746
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4741
- else if (head < viewport.from)
4747
+ else if (scrollTarget.y == "start" || head < viewport.from)
4742
4748
  topPos = block.top;
4743
4749
  else
4744
4750
  topPos = block.bottom - viewHeight;
@@ -5096,11 +5102,13 @@ const baseTheme = buildTheme("." + baseThemeID, {
5096
5102
  // recomputation.
5097
5103
  "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5098
5104
  "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5099
- ".cm-cursor": {
5105
+ ".cm-cursor, .cm-dropCursor": {
5100
5106
  position: "absolute",
5101
5107
  borderLeft: "1.2px solid black",
5102
5108
  marginLeft: "-0.6px",
5103
5109
  pointerEvents: "none",
5110
+ },
5111
+ ".cm-cursor": {
5104
5112
  display: "none"
5105
5113
  },
5106
5114
  "&dark .cm-cursor": {
@@ -5188,6 +5196,7 @@ class DOMObserver {
5188
5196
  this.delayedFlush = -1;
5189
5197
  this.resizeTimeout = -1;
5190
5198
  this.queue = [];
5199
+ this.delayedAndroidKey = null;
5191
5200
  this.scrollTargets = [];
5192
5201
  this.intersection = null;
5193
5202
  this.resize = null;
@@ -5241,7 +5250,7 @@ class DOMObserver {
5241
5250
  this.intersection = new IntersectionObserver(entries => {
5242
5251
  if (this.parentCheck < 0)
5243
5252
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
5244
- if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5253
+ if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
5245
5254
  this.intersecting = !this.intersecting;
5246
5255
  if (this.intersecting != this.view.inView)
5247
5256
  this.onScrollChanged(document.createEvent("Event"));
@@ -5271,7 +5280,7 @@ class DOMObserver {
5271
5280
  }
5272
5281
  }
5273
5282
  onSelectionChange(event) {
5274
- if (!this.readSelectionRange())
5283
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5275
5284
  return;
5276
5285
  let { view } = this, sel = this.selectionRange;
5277
5286
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5367,6 +5376,32 @@ class DOMObserver {
5367
5376
  this.queue.length = 0;
5368
5377
  this.selectionChanged = false;
5369
5378
  }
5379
+ // Chrome Android, especially in combination with GBoard, not only
5380
+ // doesn't reliably fire regular key events, but also often
5381
+ // surrounds the effect of enter or backspace with a bunch of
5382
+ // composition events that, when interrupted, cause text duplication
5383
+ // or other kinds of corruption. This hack makes the editor back off
5384
+ // from handling DOM changes for a moment when such a key is
5385
+ // detected (via beforeinput or keydown), and then dispatches the
5386
+ // key event, throwing away the DOM changes if it gets handled.
5387
+ delayAndroidKey(key, keyCode) {
5388
+ if (!this.delayedAndroidKey)
5389
+ requestAnimationFrame(() => {
5390
+ let key = this.delayedAndroidKey;
5391
+ this.delayedAndroidKey = null;
5392
+ let startState = this.view.state;
5393
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5394
+ this.processRecords();
5395
+ else
5396
+ this.flush();
5397
+ if (this.view.state == startState)
5398
+ this.view.update([]);
5399
+ });
5400
+ // Since backspace beforeinput is sometimes signalled spuriously,
5401
+ // Enter always takes precedence.
5402
+ if (!this.delayedAndroidKey || key == "Enter")
5403
+ this.delayedAndroidKey = { key, keyCode };
5404
+ }
5370
5405
  flushSoon() {
5371
5406
  if (this.delayedFlush < 0)
5372
5407
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5403,13 +5438,13 @@ class DOMObserver {
5403
5438
  }
5404
5439
  // Apply pending changes, if any
5405
5440
  flush(readSelection = true) {
5406
- if (readSelection)
5407
- this.readSelectionRange();
5408
5441
  // Completely hold off flushing when pending keys are set—the code
5409
5442
  // managing those will make sure processRecords is called and the
5410
5443
  // view is resynchronized after
5411
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5444
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5412
5445
  return;
5446
+ if (readSelection)
5447
+ this.readSelectionRange();
5413
5448
  let { from, to, typeOver } = this.processRecords();
5414
5449
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5415
5450
  if (from < 0 && !newSel)
@@ -5815,7 +5850,9 @@ class EditorView {
5815
5850
  if (e.is(scrollTo))
5816
5851
  scrollTarget = new ScrollTarget(e.value);
5817
5852
  else if (e.is(centerOn))
5818
- scrollTarget = new ScrollTarget(e.value, true);
5853
+ scrollTarget = new ScrollTarget(e.value, "center");
5854
+ else if (e.is(scrollIntoView))
5855
+ scrollTarget = e.value;
5819
5856
  }
5820
5857
  }
5821
5858
  this.viewState.update(update, scrollTarget);
@@ -6383,6 +6420,14 @@ class EditorView {
6383
6420
  this.destroyed = true;
6384
6421
  }
6385
6422
  /**
6423
+ Returns an effect that can be
6424
+ [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
6425
+ cause it to scroll the given position or range into view.
6426
+ */
6427
+ static scrollIntoView(pos, options = {}) {
6428
+ return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
6429
+ }
6430
+ /**
6386
6431
  Facet that can be used to add DOM event handlers. The value
6387
6432
  should be an object mapping event names to handler functions. The
6388
6433
  first such function to return true will be assumed to have handled
@@ -6436,11 +6481,15 @@ class EditorView {
6436
6481
  /**
6437
6482
  Effect that can be [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a
6438
6483
  transaction to make it scroll the given range into view.
6484
+
6485
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6439
6486
  */
6440
6487
  EditorView.scrollTo = scrollTo;
6441
6488
  /**
6442
6489
  Effect that makes the editor scroll the given range to the
6443
6490
  center of the visible view.
6491
+
6492
+ *Deprecated*. Use [`scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) instead.
6444
6493
  */
6445
6494
  EditorView.centerOn = centerOn;
6446
6495
  /**
@@ -6909,7 +6958,7 @@ function measureRange(view, range) {
6909
6958
  let ltr = view.textDirection == exports.Direction.LTR;
6910
6959
  let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
6911
6960
  let lineStyle = window.getComputedStyle(content.firstChild);
6912
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6961
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
6913
6962
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6914
6963
  let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6915
6964
  let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
@@ -6929,7 +6978,7 @@ function measureRange(view, range) {
6929
6978
  let between = [];
6930
6979
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6931
6980
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6932
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6981
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6933
6982
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6934
6983
  return pieces(top).concat(between).concat(pieces(bottom));
6935
6984
  }
@@ -6994,6 +7043,98 @@ function measureCursor(view, cursor, primary) {
6994
7043
  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");
6995
7044
  }
6996
7045
 
7046
+ const setDropCursorPos = state.StateEffect.define({
7047
+ map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
7048
+ });
7049
+ const dropCursorPos = state.StateField.define({
7050
+ create() { return null; },
7051
+ update(pos, tr) {
7052
+ if (pos != null)
7053
+ pos = tr.changes.mapPos(pos);
7054
+ return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7055
+ }
7056
+ });
7057
+ const drawDropCursor = ViewPlugin.fromClass(class {
7058
+ constructor(view) {
7059
+ this.view = view;
7060
+ this.cursor = null;
7061
+ this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7062
+ }
7063
+ update(update) {
7064
+ var _a;
7065
+ let cursorPos = update.state.field(dropCursorPos);
7066
+ if (cursorPos == null) {
7067
+ if (this.cursor != null) {
7068
+ (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7069
+ this.cursor = null;
7070
+ }
7071
+ }
7072
+ else {
7073
+ if (!this.cursor) {
7074
+ this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7075
+ this.cursor.className = "cm-dropCursor";
7076
+ }
7077
+ if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7078
+ this.view.requestMeasure(this.measureReq);
7079
+ }
7080
+ }
7081
+ readPos() {
7082
+ let pos = this.view.state.field(dropCursorPos);
7083
+ let rect = pos != null && this.view.coordsAtPos(pos);
7084
+ if (!rect)
7085
+ return null;
7086
+ let outer = this.view.scrollDOM.getBoundingClientRect();
7087
+ return {
7088
+ left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
7089
+ top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
7090
+ height: rect.bottom - rect.top
7091
+ };
7092
+ }
7093
+ drawCursor(pos) {
7094
+ if (this.cursor) {
7095
+ if (pos) {
7096
+ this.cursor.style.left = pos.left + "px";
7097
+ this.cursor.style.top = pos.top + "px";
7098
+ this.cursor.style.height = pos.height + "px";
7099
+ }
7100
+ else {
7101
+ this.cursor.style.left = "-100000px";
7102
+ }
7103
+ }
7104
+ }
7105
+ destroy() {
7106
+ if (this.cursor)
7107
+ this.cursor.remove();
7108
+ }
7109
+ setDropPos(pos) {
7110
+ if (this.view.state.field(dropCursorPos) != pos)
7111
+ this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7112
+ }
7113
+ }, {
7114
+ eventHandlers: {
7115
+ dragover(event) {
7116
+ this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7117
+ },
7118
+ dragleave(event) {
7119
+ if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7120
+ this.setDropPos(null);
7121
+ },
7122
+ dragend() {
7123
+ this.setDropPos(null);
7124
+ },
7125
+ drop() {
7126
+ this.setDropPos(null);
7127
+ }
7128
+ }
7129
+ });
7130
+ /**
7131
+ Draws a cursor at the current drop position when something is
7132
+ dragged over the editor.
7133
+ */
7134
+ function dropCursor() {
7135
+ return [dropCursorPos, drawDropCursor];
7136
+ }
7137
+
6997
7138
  function iterMatches(doc, re, from, to, f) {
6998
7139
  re.lastIndex = 0;
6999
7140
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@@ -7002,6 +7143,22 @@ function iterMatches(doc, re, from, to, f) {
7002
7143
  f(pos + m.index, pos + m.index + m[0].length, m);
7003
7144
  }
7004
7145
  }
7146
+ function matchRanges(view, maxLength) {
7147
+ let visible = view.visibleRanges;
7148
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7149
+ visible[0].to == view.viewport.to)
7150
+ return visible;
7151
+ let result = [];
7152
+ for (let { from, to } of visible) {
7153
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7154
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7155
+ if (result.length && result[result.length - 1].to >= from)
7156
+ result[result.length - 1].to = to;
7157
+ else
7158
+ result.push({ from, to });
7159
+ }
7160
+ return result;
7161
+ }
7005
7162
  /**
7006
7163
  Helper class used to make it easier to maintain decorations on
7007
7164
  visible code that matches a given regular expression. To be used
@@ -7013,12 +7170,13 @@ class MatchDecorator {
7013
7170
  Create a decorator.
7014
7171
  */
7015
7172
  constructor(config) {
7016
- let { regexp, decoration, boundary } = config;
7173
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
7017
7174
  if (!regexp.global)
7018
7175
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7019
7176
  this.regexp = regexp;
7020
7177
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7021
7178
  this.boundary = boundary;
7179
+ this.maxLength = maxLength;
7022
7180
  }
7023
7181
  /**
7024
7182
  Compute the full set of decorations for matches in the given
@@ -7027,7 +7185,7 @@ class MatchDecorator {
7027
7185
  */
7028
7186
  createDeco(view) {
7029
7187
  let build = new rangeset.RangeSetBuilder();
7030
- for (let { from, to } of view.visibleRanges)
7188
+ for (let { from, to } of matchRanges(view, this.maxLength))
7031
7189
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7032
7190
  return build.finish();
7033
7191
  }
@@ -7344,6 +7502,7 @@ exports.ViewUpdate = ViewUpdate;
7344
7502
  exports.WidgetType = WidgetType;
7345
7503
  exports.__test = __test;
7346
7504
  exports.drawSelection = drawSelection;
7505
+ exports.dropCursor = dropCursor;
7347
7506
  exports.highlightActiveLine = highlightActiveLine;
7348
7507
  exports.highlightSpecialChars = highlightSpecialChars;
7349
7508
  exports.keymap = keymap;