@codemirror/view 0.19.28 → 0.19.32

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';
@@ -655,6 +655,7 @@ class TextView extends ContentView {
655
655
  split(from) {
656
656
  let result = new TextView(this.text.slice(from));
657
657
  this.text = this.text.slice(0, from);
658
+ this.markDirty();
658
659
  return result;
659
660
  }
660
661
  localPosFromDOM(node, offset) {
@@ -2249,6 +2250,78 @@ function moveVisually(line, order, dir, start, forward) {
2249
2250
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2250
2251
  }
2251
2252
 
2253
+ class DOMReader {
2254
+ constructor(points, view) {
2255
+ this.points = points;
2256
+ this.view = view;
2257
+ this.text = "";
2258
+ this.lineBreak = view.state.lineBreak;
2259
+ }
2260
+ readRange(start, end) {
2261
+ if (!start)
2262
+ return this;
2263
+ let parent = start.parentNode;
2264
+ for (let cur = start;;) {
2265
+ this.findPointBefore(parent, cur);
2266
+ this.readNode(cur);
2267
+ let next = cur.nextSibling;
2268
+ if (next == end)
2269
+ break;
2270
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2271
+ if (view && nextView ? view.breakAfter :
2272
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2273
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2274
+ this.text += this.lineBreak;
2275
+ cur = next;
2276
+ }
2277
+ this.findPointBefore(parent, end);
2278
+ return this;
2279
+ }
2280
+ readNode(node) {
2281
+ if (node.cmIgnore)
2282
+ return;
2283
+ let view = ContentView.get(node);
2284
+ let fromView = view && view.overrideDOMText;
2285
+ let text;
2286
+ if (fromView != null)
2287
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2288
+ else if (node.nodeType == 3)
2289
+ text = node.nodeValue;
2290
+ else if (node.nodeName == "BR")
2291
+ text = node.nextSibling ? this.lineBreak : "";
2292
+ else if (node.nodeType == 1)
2293
+ this.readRange(node.firstChild, null);
2294
+ if (text != null) {
2295
+ this.findPointIn(node, text.length);
2296
+ this.text += text;
2297
+ // Chrome inserts two newlines when pressing shift-enter at the
2298
+ // end of a line. This drops one of those.
2299
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2300
+ this.text = this.text.slice(0, -1);
2301
+ }
2302
+ }
2303
+ findPointBefore(node, next) {
2304
+ for (let point of this.points)
2305
+ if (point.node == node && node.childNodes[point.offset] == next)
2306
+ point.pos = this.text.length;
2307
+ }
2308
+ findPointIn(node, maxLen) {
2309
+ for (let point of this.points)
2310
+ if (point.node == node)
2311
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2312
+ }
2313
+ }
2314
+ function isBlockElement(node) {
2315
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2316
+ }
2317
+ class DOMPoint {
2318
+ constructor(node, offset) {
2319
+ this.node = node;
2320
+ this.offset = offset;
2321
+ this.pos = -1;
2322
+ }
2323
+ }
2324
+
2252
2325
  class DocView extends ContentView {
2253
2326
  constructor(view) {
2254
2327
  super();
@@ -2298,7 +2371,7 @@ class DocView extends ContentView {
2298
2371
  }
2299
2372
  if (this.view.inputState.composing < 0)
2300
2373
  this.compositionDeco = Decoration.none;
2301
- else if (update.transactions.length)
2374
+ else if (update.transactions.length || this.dirty)
2302
2375
  this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2303
2376
  // When the DOM nodes around the selection are moved to another
2304
2377
  // parent, Chrome sometimes reports a different selection through
@@ -2321,16 +2394,6 @@ class DocView extends ContentView {
2321
2394
  return true;
2322
2395
  }
2323
2396
  }
2324
- reset(sel) {
2325
- if (this.dirty) {
2326
- this.view.observer.ignore(() => this.view.docView.sync());
2327
- this.dirty = 0 /* Not */;
2328
- this.updateSelection(true);
2329
- }
2330
- else {
2331
- this.updateSelection();
2332
- }
2333
- }
2334
2397
  // Used by update and the constructor do perform the actual DOM
2335
2398
  // update
2336
2399
  updateInner(changes, deco, oldLength) {
@@ -2406,7 +2469,8 @@ class DocView extends ContentView {
2406
2469
  // inside an uneditable node, and not bring it back when we
2407
2470
  // move the cursor to its proper position. This tries to
2408
2471
  // restore the keyboard by cycling focus.
2409
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2472
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2473
+ inUneditable(domSel.focusNode, this.dom)) {
2410
2474
  this.dom.blur();
2411
2475
  this.dom.focus({ preventScroll: true });
2412
2476
  }
@@ -2449,7 +2513,7 @@ class DocView extends ContentView {
2449
2513
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2450
2514
  }
2451
2515
  enforceCursorAssoc() {
2452
- if (this.view.composing)
2516
+ if (this.compositionDeco.size)
2453
2517
  return;
2454
2518
  let cursor = this.view.state.selection.main;
2455
2519
  let sel = getSelection(this.root);
@@ -2674,7 +2738,8 @@ function computeCompositionDeco(view, changes) {
2674
2738
  topNode = cView.dom;
2675
2739
  }
2676
2740
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2677
- let text = textNode.nodeValue, { state } = view;
2741
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2742
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2678
2743
  if (newTo - newFrom < text.length) {
2679
2744
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2680
2745
  newTo = newFrom + text.length;
@@ -2878,21 +2943,29 @@ function domPosInText(node, x, y) {
2878
2943
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2879
2944
  var _a;
2880
2945
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2881
- let halfLine = view.defaultLineHeight / 2;
2882
- let block, yOffset = y - docTop;
2883
- for (let bounced = false;;) {
2946
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2947
+ if (yOffset < 0 || yOffset > docHeight) {
2948
+ if (precise)
2949
+ return null;
2950
+ yOffset = yOffset < 0 ? 0 : docHeight;
2951
+ }
2952
+ // Scan for a text block near the queried y position
2953
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2884
2954
  block = view.elementAtHeight(yOffset);
2885
- if (block.top > yOffset || block.bottom < yOffset) {
2886
- bias = block.top > yOffset ? -1 : 1;
2887
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2955
+ if (block.type == BlockType.Text)
2956
+ break;
2957
+ for (;;) {
2958
+ // Move the y position out of this block
2959
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2960
+ if (yOffset >= 0 && yOffset <= docHeight)
2961
+ break;
2962
+ // If the document consists entirely of replaced widgets, we
2963
+ // won't find a text block, so return 0
2888
2964
  if (bounced)
2889
2965
  return precise ? null : 0;
2890
- else
2891
- bounced = true;
2966
+ bounced = true;
2967
+ bias = -bias;
2892
2968
  }
2893
- if (block.type == BlockType.Text)
2894
- break;
2895
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2896
2969
  }
2897
2970
  y = docTop + yOffset;
2898
2971
  let lineStart = block.from;
@@ -3050,14 +3123,6 @@ class InputState {
3050
3123
  constructor(view) {
3051
3124
  this.lastKeyCode = 0;
3052
3125
  this.lastKeyTime = 0;
3053
- // On Chrome Android, backspace near widgets is just completely
3054
- // broken, and there are no key events, so we need to handle the
3055
- // beforeinput event. Deleting stuff will often create a flurry of
3056
- // events, and interrupting it before it is done just makes
3057
- // subsequent events even more broken, so again, we hold off doing
3058
- // anything until the browser is finished with whatever it is trying
3059
- // to do.
3060
- this.pendingAndroidKey = undefined;
3061
3126
  // On iOS, some keys need to have their default behavior happen
3062
3127
  // (after which we retroactively handle them and reset the DOM) to
3063
3128
  // avoid messing up the virtual keyboard state.
@@ -3126,22 +3191,15 @@ class InputState {
3126
3191
  }
3127
3192
  runCustomHandlers(type, view, event) {
3128
3193
  for (let set of this.customHandlers) {
3129
- let handler = set.handlers[type], handled = false;
3194
+ let handler = set.handlers[type];
3130
3195
  if (handler) {
3131
3196
  try {
3132
- handled = handler.call(set.plugin, event, view);
3197
+ if (handler.call(set.plugin, event, view) || event.defaultPrevented)
3198
+ return true;
3133
3199
  }
3134
3200
  catch (e) {
3135
3201
  logException(view.state, e);
3136
3202
  }
3137
- if (handled || event.defaultPrevented) {
3138
- // Chrome for Android often applies a bunch of nonsensical
3139
- // DOM changes after an enter press, even when
3140
- // preventDefault-ed. This tries to ignore those.
3141
- if (browser.android && type == "keydown" && event.keyCode == 13)
3142
- view.observer.flushSoon();
3143
- return true;
3144
- }
3145
3203
  }
3146
3204
  }
3147
3205
  return false;
@@ -3165,6 +3223,16 @@ class InputState {
3165
3223
  this.lastKeyTime = Date.now();
3166
3224
  if (this.screenKeyEvent(view, event))
3167
3225
  return true;
3226
+ // Chrome for Android usually doesn't fire proper key events, but
3227
+ // occasionally does, usually surrounded by a bunch of complicated
3228
+ // composition changes. When an enter or backspace key event is
3229
+ // seen, hold off on handling DOM events for a bit, and then
3230
+ // dispatch it.
3231
+ if (browser.android && browser.chrome && !event.synthetic &&
3232
+ (event.keyCode == 13 || event.keyCode == 8)) {
3233
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3234
+ return true;
3235
+ }
3168
3236
  // Prevent the default behavior of Enter on iOS makes the
3169
3237
  // virtual keyboard get stuck in the wrong (lowercase)
3170
3238
  // state. So we let it go through, and then, in
@@ -3186,24 +3254,6 @@ class InputState {
3186
3254
  this.pendingIOSKey = undefined;
3187
3255
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3188
3256
  }
3189
- // This causes the DOM observer to pause for a bit, and sets an
3190
- // animation frame (which seems the most reliable way to detect
3191
- // 'Chrome is done flailing about messing with the DOM') to fire a
3192
- // fake key event and re-sync the view again.
3193
- setPendingAndroidKey(view, pending) {
3194
- this.pendingAndroidKey = pending;
3195
- requestAnimationFrame(() => {
3196
- let key = this.pendingAndroidKey;
3197
- if (!key)
3198
- return;
3199
- this.pendingAndroidKey = undefined;
3200
- view.observer.processRecords();
3201
- let startState = view.state;
3202
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3203
- if (view.state == startState)
3204
- view.docView.reset(true);
3205
- });
3206
- }
3207
3257
  ignoreDuringComposition(event) {
3208
3258
  if (!/^key/.test(event.type))
3209
3259
  return false;
@@ -3233,10 +3283,10 @@ class InputState {
3233
3283
  return (event.type == "keydown" && event.keyCode != 229) ||
3234
3284
  event.type == "compositionend" && !browser.ios;
3235
3285
  }
3236
- startMouseSelection(view, event, style) {
3286
+ startMouseSelection(mouseSelection) {
3237
3287
  if (this.mouseSelection)
3238
3288
  this.mouseSelection.destroy();
3239
- this.mouseSelection = new MouseSelection(this, view, event, style);
3289
+ this.mouseSelection = mouseSelection;
3240
3290
  }
3241
3291
  update(update) {
3242
3292
  if (this.mouseSelection)
@@ -3257,10 +3307,10 @@ const PendingKeys = [
3257
3307
  // Key codes for modifier keys
3258
3308
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3259
3309
  class MouseSelection {
3260
- constructor(inputState, view, startEvent, style) {
3261
- this.inputState = inputState;
3310
+ constructor(view, startEvent, style, mustSelect) {
3262
3311
  this.view = view;
3263
3312
  this.style = style;
3313
+ this.mustSelect = mustSelect;
3264
3314
  this.lastEvent = startEvent;
3265
3315
  let doc = view.contentDOM.ownerDocument;
3266
3316
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3294,16 +3344,18 @@ class MouseSelection {
3294
3344
  let doc = this.view.contentDOM.ownerDocument;
3295
3345
  doc.removeEventListener("mousemove", this.move);
3296
3346
  doc.removeEventListener("mouseup", this.up);
3297
- this.inputState.mouseSelection = null;
3347
+ this.view.inputState.mouseSelection = null;
3298
3348
  }
3299
3349
  select(event) {
3300
3350
  let selection = this.style.get(event, this.extend, this.multiple);
3301
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3351
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3352
+ selection.main.assoc != this.view.state.selection.main.assoc)
3302
3353
  this.view.dispatch({
3303
3354
  selection,
3304
3355
  userEvent: "select.pointer",
3305
3356
  scrollIntoView: true
3306
3357
  });
3358
+ this.mustSelect = false;
3307
3359
  }
3308
3360
  update(update) {
3309
3361
  if (update.docChanged && this.dragging)
@@ -3422,9 +3474,10 @@ handlers.mousedown = (view, event) => {
3422
3474
  if (!style && event.button == 0)
3423
3475
  style = basicMouseSelection(view, event);
3424
3476
  if (style) {
3425
- if (view.root.activeElement != view.contentDOM)
3477
+ let mustFocus = view.root.activeElement != view.contentDOM;
3478
+ if (mustFocus)
3426
3479
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3427
- view.inputState.startMouseSelection(view, event, style);
3480
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3428
3481
  }
3429
3482
  };
3430
3483
  function rangeForClick(view, pos, bias, type) {
@@ -3679,12 +3732,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3679
3732
  if (view.inputState.compositionFirstChange == null)
3680
3733
  view.inputState.compositionFirstChange = true;
3681
3734
  if (view.inputState.composing < 0) {
3735
+ // FIXME possibly set a timeout to clear it again on Android
3736
+ view.inputState.composing = 0;
3682
3737
  if (view.docView.compositionDeco.size) {
3683
3738
  view.observer.flush();
3684
3739
  forceClearComposition(view, true);
3685
3740
  }
3686
- // FIXME possibly set a timeout to clear it again on Android
3687
- view.inputState.composing = 0;
3688
3741
  }
3689
3742
  };
3690
3743
  handlers.compositionend = view => {
@@ -3710,7 +3763,7 @@ handlers.beforeinput = (view, event) => {
3710
3763
  // seems to do nothing at all on Chrome).
3711
3764
  let pending;
3712
3765
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3713
- view.inputState.setPendingAndroidKey(view, pending);
3766
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3714
3767
  if (pending.key == "Backspace" || pending.key == "Delete") {
3715
3768
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3716
3769
  setTimeout(() => {
@@ -4415,8 +4468,8 @@ function visiblePixelRange(dom, paddingTop) {
4415
4468
  break;
4416
4469
  }
4417
4470
  }
4418
- return { left: left - rect.left, right: right - rect.left,
4419
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4471
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4472
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4420
4473
  }
4421
4474
  // Line gaps are placeholder widgets used to hide pieces of overlong
4422
4475
  // lines within the viewport, as a kludge to keep the editor
@@ -4545,9 +4598,9 @@ class ViewState {
4545
4598
  let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4546
4599
  viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4547
4600
  this.viewport = viewport;
4601
+ this.updateForViewport();
4548
4602
  if (updateLines)
4549
4603
  this.updateViewportLines();
4550
- this.updateForViewport();
4551
4604
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4552
4605
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4553
4606
  update.flags |= this.computeVisibleRanges();
@@ -4579,7 +4632,12 @@ class ViewState {
4579
4632
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4580
4633
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4581
4634
  this.pixelViewport = pixelViewport;
4582
- this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4635
+ let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4636
+ if (inView != this.inView) {
4637
+ this.inView = inView;
4638
+ if (inView)
4639
+ measureContent = true;
4640
+ }
4583
4641
  if (!this.inView)
4584
4642
  return 0;
4585
4643
  if (measureContent) {
@@ -4616,9 +4674,9 @@ class ViewState {
4616
4674
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4617
4675
  if (viewportChange)
4618
4676
  this.viewport = this.getViewport(bias, this.scrollTarget);
4677
+ this.updateForViewport();
4619
4678
  if ((result & 2 /* Height */) || viewportChange)
4620
4679
  this.updateViewportLines();
4621
- this.updateForViewport();
4622
4680
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4623
4681
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4624
4682
  result |= this.computeVisibleRanges();
@@ -4643,7 +4701,7 @@ class ViewState {
4643
4701
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4644
4702
  // If scrollTarget is given, make sure the viewport includes that position
4645
4703
  if (scrollTarget) {
4646
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4704
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4647
4705
  if (head < viewport.from || head > viewport.to) {
4648
4706
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4649
4707
  if (scrollTarget.center)
@@ -4664,6 +4722,8 @@ class ViewState {
4664
4722
  // Checks if a given viewport covers the visible part of the
4665
4723
  // document and not too much beyond that.
4666
4724
  viewportIsAppropriate({ from, to }, bias = 0) {
4725
+ if (!this.inView)
4726
+ return true;
4667
4727
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4668
4728
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4669
4729
  let { visibleTop, visibleBottom } = this;
@@ -4770,8 +4830,11 @@ class ViewState {
4770
4830
  elementAtHeight(height) {
4771
4831
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4772
4832
  }
4833
+ get docHeight() {
4834
+ return this.scaler.toDOM(this.heightMap.height);
4835
+ }
4773
4836
  get contentHeight() {
4774
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4837
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4775
4838
  }
4776
4839
  }
4777
4840
  class Viewport {
@@ -5001,11 +5064,13 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5001
5064
  // recomputation.
5002
5065
  "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5003
5066
  "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5004
- ".cm-cursor": {
5067
+ ".cm-cursor, .cm-dropCursor": {
5005
5068
  position: "absolute",
5006
5069
  borderLeft: "1.2px solid black",
5007
5070
  marginLeft: "-0.6px",
5008
5071
  pointerEvents: "none",
5072
+ },
5073
+ ".cm-cursor": {
5009
5074
  display: "none"
5010
5075
  },
5011
5076
  "&dark .cm-cursor": {
@@ -5093,6 +5158,7 @@ class DOMObserver {
5093
5158
  this.delayedFlush = -1;
5094
5159
  this.resizeTimeout = -1;
5095
5160
  this.queue = [];
5161
+ this.delayedAndroidKey = null;
5096
5162
  this.scrollTargets = [];
5097
5163
  this.intersection = null;
5098
5164
  this.resize = null;
@@ -5146,7 +5212,7 @@ class DOMObserver {
5146
5212
  this.intersection = new IntersectionObserver(entries => {
5147
5213
  if (this.parentCheck < 0)
5148
5214
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
5149
- if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5215
+ if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
5150
5216
  this.intersecting = !this.intersecting;
5151
5217
  if (this.intersecting != this.view.inView)
5152
5218
  this.onScrollChanged(document.createEvent("Event"));
@@ -5176,7 +5242,7 @@ class DOMObserver {
5176
5242
  }
5177
5243
  }
5178
5244
  onSelectionChange(event) {
5179
- if (!this.readSelectionRange())
5245
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5180
5246
  return;
5181
5247
  let { view } = this, sel = this.selectionRange;
5182
5248
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5272,6 +5338,32 @@ class DOMObserver {
5272
5338
  this.queue.length = 0;
5273
5339
  this.selectionChanged = false;
5274
5340
  }
5341
+ // Chrome Android, especially in combination with GBoard, not only
5342
+ // doesn't reliably fire regular key events, but also often
5343
+ // surrounds the effect of enter or backspace with a bunch of
5344
+ // composition events that, when interrupted, cause text duplication
5345
+ // or other kinds of corruption. This hack makes the editor back off
5346
+ // from handling DOM changes for a moment when such a key is
5347
+ // detected (via beforeinput or keydown), and then dispatches the
5348
+ // key event, throwing away the DOM changes if it gets handled.
5349
+ delayAndroidKey(key, keyCode) {
5350
+ if (!this.delayedAndroidKey)
5351
+ requestAnimationFrame(() => {
5352
+ let key = this.delayedAndroidKey;
5353
+ this.delayedAndroidKey = null;
5354
+ let startState = this.view.state;
5355
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5356
+ this.processRecords();
5357
+ else
5358
+ this.flush();
5359
+ if (this.view.state == startState)
5360
+ this.view.update([]);
5361
+ });
5362
+ // Since backspace beforeinput is sometimes signalled spuriously,
5363
+ // Enter always takes precedence.
5364
+ if (!this.delayedAndroidKey || key == "Enter")
5365
+ this.delayedAndroidKey = { key, keyCode };
5366
+ }
5275
5367
  flushSoon() {
5276
5368
  if (this.delayedFlush < 0)
5277
5369
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5308,13 +5400,13 @@ class DOMObserver {
5308
5400
  }
5309
5401
  // Apply pending changes, if any
5310
5402
  flush(readSelection = true) {
5311
- if (readSelection)
5312
- this.readSelectionRange();
5313
5403
  // Completely hold off flushing when pending keys are set—the code
5314
5404
  // managing those will make sure processRecords is called and the
5315
5405
  // view is resynchronized after
5316
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5406
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5317
5407
  return;
5408
+ if (readSelection)
5409
+ this.readSelectionRange();
5318
5410
  let { from, to, typeOver } = this.processRecords();
5319
5411
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5320
5412
  if (from < 0 && !newSel)
@@ -5324,7 +5416,7 @@ class DOMObserver {
5324
5416
  this.onChange(from, to, typeOver);
5325
5417
  // The view wasn't updated
5326
5418
  if (this.view.state == startState)
5327
- this.view.docView.reset(newSel);
5419
+ this.view.update([]);
5328
5420
  }
5329
5421
  readMutation(rec) {
5330
5422
  let cView = this.view.docView.nearest(rec.target);
@@ -5543,76 +5635,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5543
5635
  }
5544
5636
  return { from, toA, toB };
5545
5637
  }
5546
- class DOMReader {
5547
- constructor(points, view) {
5548
- this.points = points;
5549
- this.view = view;
5550
- this.text = "";
5551
- this.lineBreak = view.state.lineBreak;
5552
- }
5553
- readRange(start, end) {
5554
- if (!start)
5555
- return;
5556
- let parent = start.parentNode;
5557
- for (let cur = start;;) {
5558
- this.findPointBefore(parent, cur);
5559
- this.readNode(cur);
5560
- let next = cur.nextSibling;
5561
- if (next == end)
5562
- break;
5563
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5564
- if (view && nextView ? view.breakAfter :
5565
- (view ? view.breakAfter : isBlockElement(cur)) ||
5566
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5567
- this.text += this.lineBreak;
5568
- cur = next;
5569
- }
5570
- this.findPointBefore(parent, end);
5571
- }
5572
- readNode(node) {
5573
- if (node.cmIgnore)
5574
- return;
5575
- let view = ContentView.get(node);
5576
- let fromView = view && view.overrideDOMText;
5577
- let text;
5578
- if (fromView != null)
5579
- text = fromView.sliceString(0, undefined, this.lineBreak);
5580
- else if (node.nodeType == 3)
5581
- text = node.nodeValue;
5582
- else if (node.nodeName == "BR")
5583
- text = node.nextSibling ? this.lineBreak : "";
5584
- else if (node.nodeType == 1)
5585
- this.readRange(node.firstChild, null);
5586
- if (text != null) {
5587
- this.findPointIn(node, text.length);
5588
- this.text += text;
5589
- // Chrome inserts two newlines when pressing shift-enter at the
5590
- // end of a line. This drops one of those.
5591
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5592
- this.text = this.text.slice(0, -1);
5593
- }
5594
- }
5595
- findPointBefore(node, next) {
5596
- for (let point of this.points)
5597
- if (point.node == node && node.childNodes[point.offset] == next)
5598
- point.pos = this.text.length;
5599
- }
5600
- findPointIn(node, maxLen) {
5601
- for (let point of this.points)
5602
- if (point.node == node)
5603
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5604
- }
5605
- }
5606
- function isBlockElement(node) {
5607
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5608
- }
5609
- class DOMPoint {
5610
- constructor(node, offset) {
5611
- this.node = node;
5612
- this.offset = offset;
5613
- this.pos = -1;
5614
- }
5615
- }
5616
5638
  function selectionPoints(view) {
5617
5639
  let result = [];
5618
5640
  if (view.root.activeElement != view.contentDOM)
@@ -5898,7 +5920,9 @@ class EditorView {
5898
5920
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5899
5921
  break;
5900
5922
  if (i > 5) {
5901
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5923
+ console.warn(this.measureRequests.length
5924
+ ? "Measure loop restarted more than 5 times"
5925
+ : "Viewport failed to stabilize");
5902
5926
  break;
5903
5927
  }
5904
5928
  let measuring = [];
@@ -5944,7 +5968,8 @@ class EditorView {
5944
5968
  }
5945
5969
  if (redrawn)
5946
5970
  this.docView.updateSelection(true);
5947
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5971
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5972
+ this.measureRequests.length == 0)
5948
5973
  break;
5949
5974
  }
5950
5975
  }
@@ -6236,6 +6261,11 @@ class EditorView {
6236
6261
  Find the DOM parent node and offset (child offset if `node` is
6237
6262
  an element, character offset when it is a text node) at the
6238
6263
  given document position.
6264
+
6265
+ Note that for positions that aren't currently in
6266
+ `visibleRanges`, the resulting DOM position isn't necessarily
6267
+ meaningful (it may just point before or after a placeholder
6268
+ element).
6239
6269
  */
6240
6270
  domAtPos(pos) {
6241
6271
  return this.docView.domAtPos(pos);
@@ -6876,7 +6906,7 @@ function measureRange(view, range) {
6876
6906
  let ltr = view.textDirection == Direction.LTR;
6877
6907
  let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
6878
6908
  let lineStyle = window.getComputedStyle(content.firstChild);
6879
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6909
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
6880
6910
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6881
6911
  let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6882
6912
  let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
@@ -6896,7 +6926,7 @@ function measureRange(view, range) {
6896
6926
  let between = [];
6897
6927
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6898
6928
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6899
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == BlockType.Text)
6929
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
6900
6930
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6901
6931
  return pieces(top).concat(between).concat(pieces(bottom));
6902
6932
  }
@@ -6961,6 +6991,98 @@ function measureCursor(view, cursor, primary) {
6961
6991
  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");
6962
6992
  }
6963
6993
 
6994
+ const setDropCursorPos = /*@__PURE__*/StateEffect.define({
6995
+ map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
6996
+ });
6997
+ const dropCursorPos = /*@__PURE__*/StateField.define({
6998
+ create() { return null; },
6999
+ update(pos, tr) {
7000
+ if (pos != null)
7001
+ pos = tr.changes.mapPos(pos);
7002
+ return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7003
+ }
7004
+ });
7005
+ const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
7006
+ constructor(view) {
7007
+ this.view = view;
7008
+ this.cursor = null;
7009
+ this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7010
+ }
7011
+ update(update) {
7012
+ var _a;
7013
+ let cursorPos = update.state.field(dropCursorPos);
7014
+ if (cursorPos == null) {
7015
+ if (this.cursor != null) {
7016
+ (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7017
+ this.cursor = null;
7018
+ }
7019
+ }
7020
+ else {
7021
+ if (!this.cursor) {
7022
+ this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7023
+ this.cursor.className = "cm-dropCursor";
7024
+ }
7025
+ if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7026
+ this.view.requestMeasure(this.measureReq);
7027
+ }
7028
+ }
7029
+ readPos() {
7030
+ let pos = this.view.state.field(dropCursorPos);
7031
+ let rect = pos != null && this.view.coordsAtPos(pos);
7032
+ if (!rect)
7033
+ return null;
7034
+ let outer = this.view.scrollDOM.getBoundingClientRect();
7035
+ return {
7036
+ left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
7037
+ top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
7038
+ height: rect.bottom - rect.top
7039
+ };
7040
+ }
7041
+ drawCursor(pos) {
7042
+ if (this.cursor) {
7043
+ if (pos) {
7044
+ this.cursor.style.left = pos.left + "px";
7045
+ this.cursor.style.top = pos.top + "px";
7046
+ this.cursor.style.height = pos.height + "px";
7047
+ }
7048
+ else {
7049
+ this.cursor.style.left = "-100000px";
7050
+ }
7051
+ }
7052
+ }
7053
+ destroy() {
7054
+ if (this.cursor)
7055
+ this.cursor.remove();
7056
+ }
7057
+ setDropPos(pos) {
7058
+ if (this.view.state.field(dropCursorPos) != pos)
7059
+ this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7060
+ }
7061
+ }, {
7062
+ eventHandlers: {
7063
+ dragover(event) {
7064
+ this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7065
+ },
7066
+ dragleave(event) {
7067
+ if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7068
+ this.setDropPos(null);
7069
+ },
7070
+ dragend() {
7071
+ this.setDropPos(null);
7072
+ },
7073
+ drop() {
7074
+ this.setDropPos(null);
7075
+ }
7076
+ }
7077
+ });
7078
+ /**
7079
+ Draws a cursor at the current drop position when something is
7080
+ dragged over the editor.
7081
+ */
7082
+ function dropCursor() {
7083
+ return [dropCursorPos, drawDropCursor];
7084
+ }
7085
+
6964
7086
  function iterMatches(doc, re, from, to, f) {
6965
7087
  re.lastIndex = 0;
6966
7088
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@@ -6969,6 +7091,22 @@ function iterMatches(doc, re, from, to, f) {
6969
7091
  f(pos + m.index, pos + m.index + m[0].length, m);
6970
7092
  }
6971
7093
  }
7094
+ function matchRanges(view, maxLength) {
7095
+ let visible = view.visibleRanges;
7096
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7097
+ visible[0].to == view.viewport.to)
7098
+ return visible;
7099
+ let result = [];
7100
+ for (let { from, to } of visible) {
7101
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7102
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7103
+ if (result.length && result[result.length - 1].to >= from)
7104
+ result[result.length - 1].to = to;
7105
+ else
7106
+ result.push({ from, to });
7107
+ }
7108
+ return result;
7109
+ }
6972
7110
  /**
6973
7111
  Helper class used to make it easier to maintain decorations on
6974
7112
  visible code that matches a given regular expression. To be used
@@ -6980,12 +7118,13 @@ class MatchDecorator {
6980
7118
  Create a decorator.
6981
7119
  */
6982
7120
  constructor(config) {
6983
- let { regexp, decoration, boundary } = config;
7121
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
6984
7122
  if (!regexp.global)
6985
7123
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
6986
7124
  this.regexp = regexp;
6987
7125
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
6988
7126
  this.boundary = boundary;
7127
+ this.maxLength = maxLength;
6989
7128
  }
6990
7129
  /**
6991
7130
  Compute the full set of decorations for matches in the given
@@ -6994,7 +7133,7 @@ class MatchDecorator {
6994
7133
  */
6995
7134
  createDeco(view) {
6996
7135
  let build = new RangeSetBuilder();
6997
- for (let { from, to } of view.visibleRanges)
7136
+ for (let { from, to } of matchRanges(view, this.maxLength))
6998
7137
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
6999
7138
  return build.finish();
7000
7139
  }
@@ -7295,4 +7434,4 @@ function placeholder(content) {
7295
7434
  */
7296
7435
  const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder, moveVisually };
7297
7436
 
7298
- export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };
7437
+ export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, dropCursor, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };