@codemirror/view 6.26.4-edit-context.1 → 6.27.0

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
@@ -745,7 +745,7 @@ var browser = {
745
745
  android: /Android\b/.test(nav.userAgent),
746
746
  webkit,
747
747
  safari,
748
- webkit_version: webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0,
748
+ webkit_version: webkit ? +(/\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
749
749
  tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
750
750
  };
751
751
 
@@ -2368,7 +2368,6 @@ class ScrollTarget {
2368
2368
  }
2369
2369
  }
2370
2370
  const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
2371
- const setEditContextFormatting = state.StateEffect.define();
2372
2371
  /**
2373
2372
  Log or report an unhandled exception in client code. Should
2374
2373
  probably only be used by extension code that allows client code to
@@ -2705,11 +2704,10 @@ class DocView extends ContentView {
2705
2704
  super();
2706
2705
  this.view = view;
2707
2706
  this.decorations = [];
2708
- this.dynamicDecorationMap = [false];
2707
+ this.dynamicDecorationMap = [];
2709
2708
  this.domChanged = null;
2710
2709
  this.hasComposition = null;
2711
2710
  this.markedForComposition = new Set;
2712
- this.editContextFormatting = Decoration.none;
2713
2711
  this.lastCompositionAfterCursor = false;
2714
2712
  // Track a minimum width for the editor. When measuring sizes in
2715
2713
  // measureVisibleLineHeights, this is updated to point at the width
@@ -2748,9 +2746,8 @@ class DocView extends ContentView {
2748
2746
  this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2749
2747
  }
2750
2748
  }
2751
- this.updateEditContextFormatting(update);
2752
2749
  let readCompositionAt = -1;
2753
- if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
2750
+ if (this.view.inputState.composing >= 0) {
2754
2751
  if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
2755
2752
  readCompositionAt = this.domChanged.newSel.head;
2756
2753
  else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
@@ -2858,14 +2855,6 @@ class DocView extends ContentView {
2858
2855
  if (composition)
2859
2856
  this.fixCompositionDOM(composition);
2860
2857
  }
2861
- updateEditContextFormatting(update) {
2862
- this.editContextFormatting = this.editContextFormatting.map(update.changes);
2863
- for (let tr of update.transactions)
2864
- for (let effect of tr.effects)
2865
- if (effect.is(setEditContextFormatting)) {
2866
- this.editContextFormatting = effect.value;
2867
- }
2868
- }
2869
2858
  compositionView(composition) {
2870
2859
  let cur = new TextView(composition.text.nodeValue);
2871
2860
  cur.flags |= 8 /* ViewFlag.Composition */;
@@ -3071,6 +3060,12 @@ class DocView extends ContentView {
3071
3060
  best = child;
3072
3061
  bestPos = start;
3073
3062
  }
3063
+ else if (best && start == pos && end == pos && child instanceof BlockWidgetView && Math.abs(side) < 2) {
3064
+ if (child.deco.startSide < 0)
3065
+ break;
3066
+ else if (i)
3067
+ best = null;
3068
+ }
3074
3069
  off = start;
3075
3070
  }
3076
3071
  return best ? best.coordsAt(pos - bestPos, side) : null;
@@ -3191,7 +3186,7 @@ class DocView extends ContentView {
3191
3186
  return Decoration.set(deco);
3192
3187
  }
3193
3188
  updateDeco() {
3194
- let i = 1;
3189
+ let i = 0;
3195
3190
  let allDeco = this.view.state.facet(decorations).map(d => {
3196
3191
  let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
3197
3192
  return dynamic ? d(this.view) : d;
@@ -3207,7 +3202,6 @@ class DocView extends ContentView {
3207
3202
  allDeco.push(state.RangeSet.join(outerDeco));
3208
3203
  }
3209
3204
  this.decorations = [
3210
- this.editContextFormatting,
3211
3205
  ...allDeco,
3212
3206
  this.computeBlockGapDeco(),
3213
3207
  this.view.viewState.lineGapDeco
@@ -3748,9 +3742,16 @@ class InputState {
3748
3742
  // (after which we retroactively handle them and reset the DOM) to
3749
3743
  // avoid messing up the virtual keyboard state.
3750
3744
  this.pendingIOSKey = undefined;
3745
+ /**
3746
+ When enabled (>-1), tab presses are not given to key handlers,
3747
+ leaving the browser's default behavior. If >0, the mode expires
3748
+ at that timestamp, and any other keypress clears it.
3749
+ Esc enables temporary tab focus mode for two seconds when not
3750
+ otherwise handled.
3751
+ */
3752
+ this.tabFocusMode = -1;
3751
3753
  this.lastSelectionOrigin = null;
3752
3754
  this.lastSelectionTime = 0;
3753
- this.lastEscPress = 0;
3754
3755
  this.lastContextMenu = 0;
3755
3756
  this.scrollHandlers = [];
3756
3757
  this.handlers = Object.create(null);
@@ -3830,10 +3831,10 @@ class InputState {
3830
3831
  // Must always run, even if a custom handler handled the event
3831
3832
  this.lastKeyCode = event.keyCode;
3832
3833
  this.lastKeyTime = Date.now();
3833
- if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3834
+ if (event.keyCode == 9 && this.tabFocusMode > -1 && (!this.tabFocusMode || Date.now() <= this.tabFocusMode))
3834
3835
  return true;
3835
- if (event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
3836
- this.view.inputState.lastEscPress = 0;
3836
+ if (this.tabFocusMode > 0 && event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
3837
+ this.tabFocusMode = -1;
3837
3838
  // Chrome for Android usually doesn't fire proper key events, but
3838
3839
  // occasionally does, usually surrounded by a bunch of complicated
3839
3840
  // composition changes. When an enter or backspace key event is
@@ -3894,7 +3895,6 @@ class InputState {
3894
3895
  this.mouseSelection = mouseSelection;
3895
3896
  }
3896
3897
  update(update) {
3897
- this.view.observer.update(update);
3898
3898
  if (this.mouseSelection)
3899
3899
  this.mouseSelection.update(update);
3900
3900
  if (this.draggedContent && update.docChanged)
@@ -4073,7 +4073,9 @@ class MouseSelection {
4073
4073
  this.mustSelect = false;
4074
4074
  }
4075
4075
  update(update) {
4076
- if (this.style.update(update))
4076
+ if (update.transactions.some(tr => tr.isUserEvent("input.type")))
4077
+ this.destroy();
4078
+ else if (this.style.update(update))
4077
4079
  setTimeout(() => this.select(this.lastEvent), 20);
4078
4080
  }
4079
4081
  }
@@ -4170,8 +4172,8 @@ observers.scroll = view => {
4170
4172
  };
4171
4173
  handlers.keydown = (view, event) => {
4172
4174
  view.inputState.setSelectionOrigin("select");
4173
- if (event.keyCode == 27)
4174
- view.inputState.lastEscPress = Date.now();
4175
+ if (event.keyCode == 27 && view.inputState.tabFocusMode != 0)
4176
+ view.inputState.tabFocusMode = Date.now() + 2000;
4175
4177
  return false;
4176
4178
  };
4177
4179
  observers.touchstart = (view, e) => {
@@ -4197,7 +4199,12 @@ handlers.mousedown = (view, event) => {
4197
4199
  let mustFocus = !view.hasFocus;
4198
4200
  view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
4199
4201
  if (mustFocus)
4200
- view.observer.ignore(() => focusPreventScroll(view.contentDOM));
4202
+ view.observer.ignore(() => {
4203
+ focusPreventScroll(view.contentDOM);
4204
+ let active = view.root.activeElement;
4205
+ if (active && !active.contains(view.contentDOM))
4206
+ active.blur();
4207
+ });
4201
4208
  let mouseSel = view.inputState.mouseSelection;
4202
4209
  if (mouseSel) {
4203
4210
  mouseSel.start(event);
@@ -4485,8 +4492,6 @@ observers.blur = view => {
4485
4492
  updateForFocusChange(view);
4486
4493
  };
4487
4494
  observers.compositionstart = observers.compositionupdate = view => {
4488
- if (view.observer.editContext)
4489
- return; // Composition handled by edit context
4490
4495
  if (view.inputState.compositionFirstChange == null)
4491
4496
  view.inputState.compositionFirstChange = true;
4492
4497
  if (view.inputState.composing < 0) {
@@ -4495,8 +4500,6 @@ observers.compositionstart = observers.compositionupdate = view => {
4495
4500
  }
4496
4501
  };
4497
4502
  observers.compositionend = view => {
4498
- if (view.observer.editContext)
4499
- return; // Composition handled by edit context
4500
4503
  view.inputState.composing = -1;
4501
4504
  view.inputState.compositionEndedAt = Date.now();
4502
4505
  view.inputState.compositionPendingKey = true;
@@ -5416,9 +5419,12 @@ class ViewState {
5416
5419
  this.heightOracle = new HeightOracle(guessWrapping);
5417
5420
  this.stateDeco = state$1.facet(decorations).filter(d => typeof d != "function");
5418
5421
  this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, state.Text.empty, this.heightOracle.setDoc(state$1.doc), [new ChangedRange(0, 0, 0, state$1.doc.length)]);
5419
- this.viewport = this.getViewport(0, null);
5422
+ for (let i = 0; i < 2; i++) {
5423
+ this.viewport = this.getViewport(0, null);
5424
+ if (!this.updateForViewport())
5425
+ break;
5426
+ }
5420
5427
  this.updateViewportLines();
5421
- this.updateForViewport();
5422
5428
  this.lineGaps = this.ensureLineGaps([]);
5423
5429
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(this, false)));
5424
5430
  this.computeVisibleRanges();
@@ -5433,13 +5439,18 @@ class ViewState {
5433
5439
  }
5434
5440
  }
5435
5441
  this.viewports = viewports.sort((a, b) => a.from - b.from);
5442
+ return this.updateScaler();
5443
+ }
5444
+ updateScaler() {
5445
+ let scaler = this.scaler;
5436
5446
  this.scaler = this.heightMap.height <= 7000000 /* VP.MaxDOMHeight */ ? IdScaler :
5437
5447
  new BigScaler(this.heightOracle, this.heightMap, this.viewports);
5448
+ return scaler.eq(this.scaler) ? 0 : 2 /* UpdateFlag.Height */;
5438
5449
  }
5439
5450
  updateViewportLines() {
5440
5451
  this.viewportLines = [];
5441
5452
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.heightOracle.setDoc(this.state.doc), 0, 0, block => {
5442
- this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
5453
+ this.viewportLines.push(scaleBlock(block, this.scaler));
5443
5454
  });
5444
5455
  }
5445
5456
  update(update, scrollTarget = null) {
@@ -5465,11 +5476,10 @@ class ViewState {
5465
5476
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
5466
5477
  !this.viewportIsAppropriate(viewport))
5467
5478
  viewport = this.getViewport(0, scrollTarget);
5468
- let updateLines = !update.changes.empty || (update.flags & 2 /* UpdateFlag.Height */) ||
5469
- viewport.from != this.viewport.from || viewport.to != this.viewport.to;
5479
+ let viewportChange = viewport.from != this.viewport.from || viewport.to != this.viewport.to;
5470
5480
  this.viewport = viewport;
5471
- this.updateForViewport();
5472
- if (updateLines)
5481
+ update.flags |= this.updateForViewport();
5482
+ if (viewportChange || !update.changes.empty || (update.flags & 2 /* UpdateFlag.Height */))
5473
5483
  this.updateViewportLines();
5474
5484
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
5475
5485
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -5567,9 +5577,12 @@ class ViewState {
5567
5577
  let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
5568
5578
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from ||
5569
5579
  this.scrollTarget.range.head > this.viewport.to);
5570
- if (viewportChange)
5580
+ if (viewportChange) {
5581
+ if (result & 2 /* UpdateFlag.Height */)
5582
+ result |= this.updateScaler();
5571
5583
  this.viewport = this.getViewport(bias, this.scrollTarget);
5572
- this.updateForViewport();
5584
+ result |= this.updateForViewport();
5585
+ }
5573
5586
  if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
5574
5587
  this.updateViewportLines();
5575
5588
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
@@ -5758,11 +5771,14 @@ class ViewState {
5758
5771
  return changed ? 4 /* UpdateFlag.Viewport */ : 0;
5759
5772
  }
5760
5773
  lineBlockAt(pos) {
5761
- return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
5774
+ return (pos >= this.viewport.from && pos <= this.viewport.to &&
5775
+ this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
5762
5776
  scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.heightOracle, 0, 0), this.scaler);
5763
5777
  }
5764
5778
  lineBlockAtHeight(height) {
5765
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
5779
+ return (height >= this.viewportLines[0].top && height <= this.viewportLines[this.viewportLines.length - 1].bottom &&
5780
+ this.viewportLines.find(l => l.top <= height && l.bottom >= height)) ||
5781
+ scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
5766
5782
  }
5767
5783
  scrollAnchorAt(scrollTop) {
5768
5784
  let block = this.lineBlockAtHeight(scrollTop + 8);
@@ -5837,7 +5853,8 @@ function find(array, f) {
5837
5853
  const IdScaler = {
5838
5854
  toDOM(n) { return n; },
5839
5855
  fromDOM(n) { return n; },
5840
- scale: 1
5856
+ scale: 1,
5857
+ eq(other) { return other == this; }
5841
5858
  };
5842
5859
  // When the height is too big (> VP.MaxDOMHeight), scale down the
5843
5860
  // regions outside the viewports so that the total height is
@@ -5880,6 +5897,12 @@ class BigScaler {
5880
5897
  domBase = vp.domBottom;
5881
5898
  }
5882
5899
  }
5900
+ eq(other) {
5901
+ if (!(other instanceof BigScaler))
5902
+ return false;
5903
+ return this.scale == other.scale && this.viewports.length == other.viewports.length &&
5904
+ this.viewports.every((vp, i) => vp.from == other.viewports[i].from && vp.to == other.viewports[i].to);
5905
+ }
5883
5906
  }
5884
5907
  function scaleBlock(block, scaler) {
5885
5908
  if (scaler.scale == 1)
@@ -6271,6 +6294,7 @@ class DOMChange {
6271
6294
  this.typeOver = typeOver;
6272
6295
  this.bounds = null;
6273
6296
  this.text = "";
6297
+ this.domChanged = start > -1;
6274
6298
  let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
6275
6299
  if (view.state.readOnly && start > -1) {
6276
6300
  // Ignore changes when the editor is read-only
@@ -6373,7 +6397,35 @@ function applyDOMChange(view, domChange) {
6373
6397
  change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
6374
6398
  }
6375
6399
  if (change) {
6376
- return applyDOMChangeInner(view, change, newSel, lastKey);
6400
+ if (browser.ios && view.inputState.flushIOSKey(change))
6401
+ return true;
6402
+ // Android browsers don't fire reasonable key events for enter,
6403
+ // backspace, or delete. So this detects changes that look like
6404
+ // they're caused by those keys, and reinterprets them as key
6405
+ // events. (Some of these keys are also handled by beforeinput
6406
+ // events and the pendingAndroidKey mechanism, but that's not
6407
+ // reliable in all situations.)
6408
+ if (browser.android &&
6409
+ ((change.to == sel.to &&
6410
+ // GBoard will sometimes remove a space it just inserted
6411
+ // after a completion when you press enter
6412
+ (change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
6413
+ change.insert.length == 1 && change.insert.lines == 2 &&
6414
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
6415
+ ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
6416
+ lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
6417
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
6418
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
6419
+ dispatchKey(view.contentDOM, "Delete", 46))))
6420
+ return true;
6421
+ let text = change.insert.toString();
6422
+ if (view.inputState.composing >= 0)
6423
+ view.inputState.composing++;
6424
+ let defaultTr;
6425
+ let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
6426
+ if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
6427
+ view.dispatch(defaultInsert());
6428
+ return true;
6377
6429
  }
6378
6430
  else if (newSel && !newSel.main.eq(sel)) {
6379
6431
  let scrollIntoView = false, userEvent = "select";
@@ -6389,38 +6441,6 @@ function applyDOMChange(view, domChange) {
6389
6441
  return false;
6390
6442
  }
6391
6443
  }
6392
- function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
6393
- if (browser.ios && view.inputState.flushIOSKey(change))
6394
- return true;
6395
- let sel = view.state.selection.main;
6396
- // Android browsers don't fire reasonable key events for enter,
6397
- // backspace, or delete. So this detects changes that look like
6398
- // they're caused by those keys, and reinterprets them as key
6399
- // events. (Some of these keys are also handled by beforeinput
6400
- // events and the pendingAndroidKey mechanism, but that's not
6401
- // reliable in all situations.)
6402
- if (browser.android &&
6403
- ((change.to == sel.to &&
6404
- // GBoard will sometimes remove a space it just inserted
6405
- // after a completion when you press enter
6406
- (change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
6407
- change.insert.length == 1 && change.insert.lines == 2 &&
6408
- dispatchKey(view.contentDOM, "Enter", 13)) ||
6409
- ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
6410
- lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
6411
- dispatchKey(view.contentDOM, "Backspace", 8)) ||
6412
- (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
6413
- dispatchKey(view.contentDOM, "Delete", 46))))
6414
- return true;
6415
- let text = change.insert.toString();
6416
- if (view.inputState.composing >= 0)
6417
- view.inputState.composing++;
6418
- let defaultTr;
6419
- let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
6420
- if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
6421
- view.dispatch(defaultInsert());
6422
- return true;
6423
- }
6424
6444
  function applyDefaultInsert(view, change, newSel) {
6425
6445
  let tr, startState = view.state, sel = startState.selection.main;
6426
6446
  if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
@@ -6547,7 +6567,6 @@ class DOMObserver {
6547
6567
  constructor(view) {
6548
6568
  this.view = view;
6549
6569
  this.active = false;
6550
- this.editContext = null;
6551
6570
  // The known selection. Kept in our own object, as opposed to just
6552
6571
  // directly accessing the selection because:
6553
6572
  // - Safari doesn't report the right selection in shadow DOM
@@ -6592,10 +6611,6 @@ class DOMObserver {
6592
6611
  else
6593
6612
  this.flush();
6594
6613
  });
6595
- if (window.EditContext && view.constructor.EDIT_CONTEXT !== false) {
6596
- this.editContext = new EditContextManager(view);
6597
- view.contentDOM.editContext = this.editContext.editContext;
6598
- }
6599
6614
  if (useCharData)
6600
6615
  this.onCharData = (event) => {
6601
6616
  this.queue.push({ target: event.target,
@@ -6646,8 +6661,6 @@ class DOMObserver {
6646
6661
  onScroll(e) {
6647
6662
  if (this.intersecting)
6648
6663
  this.flush(false);
6649
- if (this.editContext)
6650
- this.view.requestMeasure(this.editContext.measureReq);
6651
6664
  this.onScrollChanged(e);
6652
6665
  }
6653
6666
  onResize() {
@@ -6906,8 +6919,9 @@ class DOMObserver {
6906
6919
  }
6907
6920
  let startState = this.view.state;
6908
6921
  let handled = applyDOMChange(this.view, domChange);
6909
- // The view wasn't updated
6910
- if (this.view.state == startState)
6922
+ // The view wasn't updated but DOM/selection changes were seen. Reset the view.
6923
+ if (this.view.state == startState &&
6924
+ (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
6911
6925
  this.view.update([]);
6912
6926
  return handled;
6913
6927
  }
@@ -6956,10 +6970,6 @@ class DOMObserver {
6956
6970
  win.removeEventListener("beforeprint", this.onPrint);
6957
6971
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6958
6972
  }
6959
- update(update) {
6960
- if (this.editContext)
6961
- this.editContext.update(update);
6962
- }
6963
6973
  destroy() {
6964
6974
  var _a, _b, _c;
6965
6975
  this.stop();
@@ -7019,161 +7029,6 @@ function safariSelectionRangeHack(view, selection) {
7019
7029
  view.contentDOM.removeEventListener("beforeinput", read, true);
7020
7030
  return found ? buildSelectionRangeFromRange(view, found) : null;
7021
7031
  }
7022
- class EditContextManager {
7023
- constructor(view) {
7024
- // The document window for which the text in the context is
7025
- // maintained. For large documents, this may be smaller than the
7026
- // editor document. This window always includes the selection head.
7027
- this.from = 0;
7028
- this.to = 0;
7029
- // When applying a transaction, this is used to compare the change
7030
- // made to the context content to the change in the transaction in
7031
- // order to make the minimal changes to the context (since touching
7032
- // that sometimes breaks series of multiple edits made for a single
7033
- // user action on some Android keyboards)
7034
- this.pendingContextChange = null;
7035
- this.resetRange(view.state);
7036
- let context = this.editContext = new window.EditContext({
7037
- text: view.state.doc.sliceString(this.from, this.to),
7038
- selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
7039
- selectionEnd: this.toContextPos(view.state.selection.main.head)
7040
- });
7041
- context.addEventListener("textupdate", e => {
7042
- let { anchor } = view.state.selection.main;
7043
- let change = { from: this.toEditorPos(e.updateRangeStart),
7044
- to: this.toEditorPos(e.updateRangeEnd),
7045
- insert: state.Text.of(e.text.split("\n")) };
7046
- // If the window doesn't include the anchor, assume changes
7047
- // adjacent to a side go up to the anchor.
7048
- if (change.from == this.from && anchor < this.from)
7049
- change.from = anchor;
7050
- else if (change.to == this.to && anchor > this.to)
7051
- change.to = anchor;
7052
- // Edit context sometimes fire empty changes
7053
- if (change.from == change.to && !change.insert.length)
7054
- return;
7055
- this.pendingContextChange = change;
7056
- applyDOMChangeInner(view, change, state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
7057
- // If the transaction didn't flush our change, revert it so
7058
- // that the context is in sync with the editor state again.
7059
- if (this.pendingContextChange)
7060
- this.revertPending(view.state);
7061
- });
7062
- context.addEventListener("characterboundsupdate", e => {
7063
- let rects = [], prev = null;
7064
- for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
7065
- let rect = view.coordsForChar(i);
7066
- prev = (rect && new DOMRect(rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top))
7067
- || prev || new DOMRect;
7068
- rects.push(prev);
7069
- }
7070
- context.updateCharacterBounds(e.rangeStart, rects);
7071
- });
7072
- context.addEventListener("textformatupdate", e => {
7073
- let deco = [];
7074
- for (let format of e.getTextFormats()) {
7075
- let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
7076
- if (lineStyle != "None" && thickness != "None") {
7077
- let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
7078
- deco.push(Decoration.mark({ attributes: { style } })
7079
- .range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
7080
- }
7081
- }
7082
- view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
7083
- });
7084
- context.addEventListener("compositionstart", () => {
7085
- if (view.inputState.composing < 0) {
7086
- view.inputState.composing = 0;
7087
- view.inputState.compositionFirstChange = true;
7088
- }
7089
- });
7090
- context.addEventListener("compositionend", () => {
7091
- view.inputState.composing = -1;
7092
- view.inputState.compositionFirstChange = null;
7093
- });
7094
- this.measureReq = { read: view => {
7095
- this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
7096
- let sel = getSelection(view.root);
7097
- if (sel && sel.rangeCount)
7098
- this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
7099
- } };
7100
- }
7101
- applyEdits(update) {
7102
- let off = 0, abort = false, pending = this.pendingContextChange;
7103
- update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
7104
- if (abort)
7105
- return;
7106
- let dLen = insert.length - (toA - fromA);
7107
- if (pending && toA >= pending.to) {
7108
- if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
7109
- pending = this.pendingContextChange = null; // Match
7110
- off += dLen;
7111
- return;
7112
- }
7113
- else { // Mismatch, revert
7114
- pending = null;
7115
- this.revertPending(update.state);
7116
- }
7117
- }
7118
- fromA += off;
7119
- toA += off;
7120
- if (toA <= this.from) { // Before the window
7121
- this.from += dLen;
7122
- this.to += dLen;
7123
- }
7124
- else if (fromA < this.to) { // Overlaps with window
7125
- if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
7126
- abort = true;
7127
- return;
7128
- }
7129
- this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
7130
- this.to += dLen;
7131
- }
7132
- off += dLen;
7133
- });
7134
- if (pending && !abort)
7135
- this.revertPending(update.state);
7136
- return !abort;
7137
- }
7138
- update(update) {
7139
- if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
7140
- this.pendingContextChange = null;
7141
- this.resetRange(update.state);
7142
- this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
7143
- this.setSelection(update.state);
7144
- }
7145
- else if (update.docChanged || update.selectionSet) {
7146
- this.setSelection(update.state);
7147
- }
7148
- if (update.geometryChanged || update.docChanged || update.selectionSet)
7149
- update.view.requestMeasure(this.measureReq);
7150
- }
7151
- resetRange(state) {
7152
- let { head } = state.selection.main;
7153
- this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
7154
- this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
7155
- }
7156
- revertPending(state) {
7157
- let pending = this.pendingContextChange;
7158
- this.pendingContextChange = null;
7159
- this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.to + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
7160
- }
7161
- setSelection(state) {
7162
- let { main } = state.selection;
7163
- let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
7164
- let end = this.toContextPos(main.head);
7165
- if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
7166
- this.editContext.updateSelection(start, end);
7167
- }
7168
- rangeIsValid(state) {
7169
- let { head } = state.selection.main;
7170
- return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
7171
- this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
7172
- this.to - this.from > 10000 /* CxVp.Margin */ * 3);
7173
- }
7174
- toEditorPos(contextPos) { return contextPos + this.from; }
7175
- toContextPos(editorPos) { return editorPos - this.from; }
7176
- }
7177
7032
 
7178
7033
  // The editor's update state machine looks something like this:
7179
7034
  //
@@ -8033,6 +7888,25 @@ class EditorView {
8033
7888
  return scrollIntoView.of(new ScrollTarget(state.EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
8034
7889
  }
8035
7890
  /**
7891
+ Enable or disable tab-focus mode, which disables key bindings
7892
+ for Tab and Shift-Tab, letting the browser's default
7893
+ focus-changing behavior go through instead. This is useful to
7894
+ prevent trapping keyboard users in your editor.
7895
+
7896
+ Without argument, this toggles the mode. With a boolean, it
7897
+ enables (true) or disables it (false). Given a number, it
7898
+ temporarily enables the mode until that number of milliseconds
7899
+ have passed or another non-Tab key is pressed.
7900
+ */
7901
+ setTabFocusMode(to) {
7902
+ if (to == null)
7903
+ this.inputState.tabFocusMode = this.inputState.tabFocusMode < 0 ? 0 : -1;
7904
+ else if (typeof to == "boolean")
7905
+ this.inputState.tabFocusMode = to ? 0 : -1;
7906
+ else if (this.inputState.tabFocusMode != 0)
7907
+ this.inputState.tabFocusMode = Date.now() + to;
7908
+ }
7909
+ /**
8036
7910
  Returns an extension that can be used to add DOM event handlers.
8037
7911
  The value should be an object mapping event names to handler
8038
7912
  functions. For any given event, such functions are ordered by
@@ -8604,11 +8478,17 @@ function getBase(view) {
8604
8478
  let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth * view.scaleX;
8605
8479
  return { left: left - view.scrollDOM.scrollLeft * view.scaleX, top: rect.top - view.scrollDOM.scrollTop * view.scaleY };
8606
8480
  }
8607
- function wrappedLine(view, pos, inside) {
8608
- let range = state.EditorSelection.cursor(pos);
8609
- return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
8610
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
8611
- type: exports.BlockType.Text };
8481
+ function wrappedLine(view, pos, side, inside) {
8482
+ let coords = view.coordsAtPos(pos, side * 2);
8483
+ if (!coords)
8484
+ return inside;
8485
+ let editorRect = view.dom.getBoundingClientRect();
8486
+ let y = (coords.top + coords.bottom) / 2;
8487
+ let left = view.posAtCoords({ x: editorRect.left + 1, y });
8488
+ let right = view.posAtCoords({ x: editorRect.right - 1, y });
8489
+ if (left == null || right == null)
8490
+ return inside;
8491
+ return { from: Math.max(inside.from, Math.min(left, right)), to: Math.min(inside.to, Math.max(left, right)) };
8612
8492
  }
8613
8493
  function rectanglesForRange(view, className, range) {
8614
8494
  if (range.to <= view.viewport.from || range.from >= view.viewport.to)
@@ -8624,10 +8504,10 @@ function rectanglesForRange(view, className, range) {
8624
8504
  let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
8625
8505
  let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
8626
8506
  if (visualStart && (view.lineWrapping || startBlock.widgetLineBreaks))
8627
- visualStart = wrappedLine(view, from, visualStart);
8507
+ visualStart = wrappedLine(view, from, 1, visualStart);
8628
8508
  if (visualEnd && (view.lineWrapping || endBlock.widgetLineBreaks))
8629
- visualEnd = wrappedLine(view, to, visualEnd);
8630
- if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
8509
+ visualEnd = wrappedLine(view, to, -1, visualEnd);
8510
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from && visualStart.to == visualEnd.to) {
8631
8511
  return pieces(drawForLine(range.from, range.to, visualStart));
8632
8512
  }
8633
8513
  else {
@@ -8882,14 +8762,19 @@ const selectionLayer = layer({
8882
8762
  });
8883
8763
  const themeSpec = {
8884
8764
  ".cm-line": {
8885
- "& ::selection": { backgroundColor: "transparent !important" },
8886
- "&::selection": { backgroundColor: "transparent !important" }
8765
+ "& ::selection, &::selection": { backgroundColor: "transparent !important" },
8766
+ },
8767
+ ".cm-content": {
8768
+ "& :focus": {
8769
+ caretColor: "initial !important",
8770
+ "&::selection, & ::selection": {
8771
+ backgroundColor: "Highlight !important"
8772
+ }
8773
+ }
8887
8774
  }
8888
8775
  };
8889
- if (CanHidePrimary) {
8890
- themeSpec[".cm-line"].caretColor = "transparent !important";
8891
- themeSpec[".cm-content"] = { caretColor: "transparent !important" };
8892
- }
8776
+ if (CanHidePrimary)
8777
+ themeSpec[".cm-line"].caretColor = themeSpec[".cm-content"].caretColor = "transparent !important";
8893
8778
  const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
8894
8779
 
8895
8780
  const setDropCursorPos = state.StateEffect.define({