@codemirror/view 0.19.11 → 0.19.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## 0.19.15 (2021-11-09)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug where the editor would think it was invisible when the document body was given screen height and scroll behavior.
6
+
7
+ Fix selection reading inside a shadow root on iOS.
8
+
9
+ ## 0.19.14 (2021-11-07)
10
+
11
+ ### Bug fixes
12
+
13
+ Fix an issue where typing into a read-only editor would move the selection.
14
+
15
+ Fix slowness when backspace is held down on iOS.
16
+
17
+ ## 0.19.13 (2021-11-06)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix a bug where backspace, enter, and delete would get applied twice on iOS.
22
+
23
+ ## 0.19.12 (2021-11-04)
24
+
25
+ ### Bug fixes
26
+
27
+ Make sure the workaround for the lost virtual keyboard on Chrome Android also works on slower phones. Slight style change in beforeinput handler
28
+
29
+ Avoid failure cases in viewport-based rendering of very long lines.
30
+
1
31
  ## 0.19.11 (2021-11-03)
2
32
 
3
33
  ### Breaking changes
package/dist/index.cjs CHANGED
@@ -252,24 +252,11 @@ function dispatchKey(elt, name, code) {
252
252
  elt.dispatchEvent(up);
253
253
  return down.defaultPrevented || up.defaultPrevented;
254
254
  }
255
- let _plainTextSupported = null;
256
- function contentEditablePlainTextSupported() {
257
- if (_plainTextSupported == null) {
258
- _plainTextSupported = false;
259
- let dummy = document.createElement("div");
260
- try {
261
- dummy.contentEditable = "plaintext-only";
262
- _plainTextSupported = dummy.contentEditable == "plaintext-only";
263
- }
264
- catch (_) { }
265
- }
266
- return _plainTextSupported;
267
- }
268
255
  function getRoot(node) {
269
256
  while (node) {
270
- node = node.assignedSlot || node.parentNode;
271
257
  if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
272
258
  return node;
259
+ node = node.assignedSlot || node.parentNode;
273
260
  }
274
261
  return null;
275
262
  }
@@ -321,25 +308,30 @@ class ContentView {
321
308
  sync(track) {
322
309
  var _a;
323
310
  if (this.dirty & 2 /* Node */) {
324
- let parent = this.dom, pos = null;
311
+ let parent = this.dom;
312
+ let pos = parent.firstChild;
325
313
  for (let child of this.children) {
326
314
  if (child.dirty) {
327
- let next = pos ? pos.nextSibling : parent.firstChild;
328
- if (!child.dom && next && !((_a = ContentView.get(next)) === null || _a === void 0 ? void 0 : _a.parent))
329
- child.reuseDOM(next);
315
+ if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
316
+ child.reuseDOM(pos);
330
317
  child.sync(track);
331
318
  child.dirty = 0 /* Not */;
332
319
  }
333
- if (track && track.node == parent && pos != child.dom)
320
+ if (track && !track.written && track.node == parent && pos != child.dom)
334
321
  track.written = true;
335
- syncNodeInto(parent, pos, child.dom);
336
- pos = child.dom;
322
+ if (child.dom.parentNode == parent) {
323
+ while (pos && pos != child.dom)
324
+ pos = rm(pos);
325
+ pos = child.dom.nextSibling;
326
+ }
327
+ else {
328
+ parent.insertBefore(child.dom, pos);
329
+ }
337
330
  }
338
- let next = pos ? pos.nextSibling : parent.firstChild;
339
- if (next && track && track.node == parent)
331
+ if (pos && track && track.node == parent)
340
332
  track.written = true;
341
- while (next)
342
- next = rm(next);
333
+ while (pos)
334
+ pos = rm(pos);
343
335
  }
344
336
  else if (this.dirty & 1 /* Child */) {
345
337
  for (let child of this.children)
@@ -479,14 +471,6 @@ function rm(dom) {
479
471
  dom.parentNode.removeChild(dom);
480
472
  return next;
481
473
  }
482
- function syncNodeInto(parent, after, dom) {
483
- let next = after ? after.nextSibling : parent.firstChild;
484
- if (dom.parentNode == parent)
485
- while (next != dom)
486
- next = rm(next);
487
- else
488
- parent.insertBefore(dom, next);
489
- }
490
474
  class ChildCursor {
491
475
  constructor(children, pos, i) {
492
476
  this.children = children;
@@ -3081,10 +3065,6 @@ class InputState {
3081
3065
  constructor(view) {
3082
3066
  this.lastKeyCode = 0;
3083
3067
  this.lastKeyTime = 0;
3084
- // On iOS, some keys need to have their default behavior happen
3085
- // (after which we retroactively handle them and reset the DOM) to
3086
- // avoid messing up the virtual keyboard state.
3087
- //
3088
3068
  // On Chrome Android, backspace near widgets is just completely
3089
3069
  // broken, and there are no key events, so we need to handle the
3090
3070
  // beforeinput event. Deleting stuff will often create a flurry of
@@ -3092,12 +3072,11 @@ class InputState {
3092
3072
  // subsequent events even more broken, so again, we hold off doing
3093
3073
  // anything until the browser is finished with whatever it is trying
3094
3074
  // to do.
3095
- //
3096
- // setPendingKey sets this, causing the DOM observer to pause for a
3097
- // bit, and setting an animation frame (which seems the most
3098
- // reliable way to detect 'browser is done flailing') to fire a fake
3099
- // key event and re-sync the view again.
3100
- this.pendingKey = undefined;
3075
+ this.pendingAndroidKey = undefined;
3076
+ // On iOS, some keys need to have their default behavior happen
3077
+ // (after which we retroactively handle them and reset the DOM) to
3078
+ // avoid messing up the virtual keyboard state.
3079
+ this.pendingIOSKey = undefined;
3101
3080
  this.lastSelectionOrigin = null;
3102
3081
  this.lastSelectionTime = 0;
3103
3082
  this.lastEscPress = 0;
@@ -3209,18 +3188,30 @@ class InputState {
3209
3188
  let pending;
3210
3189
  if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3211
3190
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3212
- this.setPendingKey(view, pending);
3191
+ this.pendingIOSKey = pending;
3192
+ setTimeout(() => this.flushIOSKey(view), 250);
3213
3193
  return true;
3214
3194
  }
3215
3195
  return false;
3216
3196
  }
3217
- setPendingKey(view, pending) {
3218
- this.pendingKey = pending;
3197
+ flushIOSKey(view) {
3198
+ let key = this.pendingIOSKey;
3199
+ if (!key)
3200
+ return false;
3201
+ this.pendingIOSKey = undefined;
3202
+ return dispatchKey(view.contentDOM, key.key, key.keyCode);
3203
+ }
3204
+ // This causes the DOM observer to pause for a bit, and sets an
3205
+ // animation frame (which seems the most reliable way to detect
3206
+ // 'Chrome is done flailing about messing with the DOM') to fire a
3207
+ // fake key event and re-sync the view again.
3208
+ setPendingAndroidKey(view, pending) {
3209
+ this.pendingAndroidKey = pending;
3219
3210
  requestAnimationFrame(() => {
3220
- if (!this.pendingKey)
3221
- return false;
3222
- let key = this.pendingKey;
3223
- this.pendingKey = undefined;
3211
+ let key = this.pendingAndroidKey;
3212
+ if (!key)
3213
+ return;
3214
+ this.pendingAndroidKey = undefined;
3224
3215
  view.observer.processRecords();
3225
3216
  let startState = view.state;
3226
3217
  dispatchKey(view.contentDOM, key.key, key.keyCode);
@@ -3734,18 +3725,20 @@ handlers.beforeinput = (view, event) => {
3734
3725
  // seems to do nothing at all on Chrome).
3735
3726
  let pending;
3736
3727
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3737
- view.inputState.setPendingKey(view, pending);
3738
- let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3739
- setTimeout(() => {
3740
- var _a;
3741
- // Backspacing near uneditable nodes on Chrome Android sometimes
3742
- // closes the virtual keyboard. This tries to crudely detect
3743
- // that and refocus to get it back.
3744
- if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3745
- view.contentDOM.blur();
3746
- view.focus();
3747
- }
3748
- }, 50);
3728
+ view.inputState.setPendingAndroidKey(view, pending);
3729
+ if (pending.key == "Backspace" || pending.key == "Delete") {
3730
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3731
+ setTimeout(() => {
3732
+ var _a;
3733
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3734
+ // closes the virtual keyboard. This tries to crudely detect
3735
+ // that and refocus to get it back.
3736
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3737
+ view.contentDOM.blur();
3738
+ view.focus();
3739
+ }
3740
+ }, 100);
3741
+ }
3749
3742
  }
3750
3743
  };
3751
3744
 
@@ -4396,18 +4389,20 @@ function visiblePixelRange(dom, paddingTop) {
4396
4389
  let rect = dom.getBoundingClientRect();
4397
4390
  let left = Math.max(0, rect.left), right = Math.min(innerWidth, rect.right);
4398
4391
  let top = Math.max(0, rect.top), bottom = Math.min(innerHeight, rect.bottom);
4399
- for (let parent = dom.parentNode; parent;) { // (Cast to any because TypeScript is useless with Node types)
4392
+ let body = dom.ownerDocument.body;
4393
+ for (let parent = dom.parentNode; parent && parent != body;) {
4400
4394
  if (parent.nodeType == 1) {
4401
- let style = window.getComputedStyle(parent);
4402
- if ((parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth) &&
4395
+ let elt = parent;
4396
+ let style = window.getComputedStyle(elt);
4397
+ if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
4403
4398
  style.overflow != "visible") {
4404
- let parentRect = parent.getBoundingClientRect();
4399
+ let parentRect = elt.getBoundingClientRect();
4405
4400
  left = Math.max(left, parentRect.left);
4406
4401
  right = Math.min(right, parentRect.right);
4407
4402
  top = Math.max(top, parentRect.top);
4408
4403
  bottom = Math.min(bottom, parentRect.bottom);
4409
4404
  }
4410
- parent = style.position == "absolute" || style.position == "fixed" ? parent.offsetParent : parent.parentNode;
4405
+ parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
4411
4406
  }
4412
4407
  else if (parent.nodeType == 11) { // Shadow root
4413
4408
  parent = parent.host;
@@ -4533,7 +4528,7 @@ class ViewState {
4533
4528
  viewport = this.getViewport(0, scrollTarget);
4534
4529
  this.viewport = viewport;
4535
4530
  this.updateForViewport();
4536
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4531
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4537
4532
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4538
4533
  update.flags |= this.computeVisibleRanges();
4539
4534
  if (scrollTarget)
@@ -4593,7 +4588,7 @@ class ViewState {
4593
4588
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4594
4589
  this.viewport = this.getViewport(bias, this.scrollTarget);
4595
4590
  this.updateForViewport();
4596
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4591
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4597
4592
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4598
4593
  result |= this.computeVisibleRanges();
4599
4594
  if (this.mustEnforceCursorAssoc) {
@@ -4668,52 +4663,50 @@ class ViewState {
4668
4663
  if (this.heightOracle.direction != exports.Direction.LTR)
4669
4664
  return gaps;
4670
4665
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4671
- if (line.length < 10000 /* Margin */)
4666
+ if (line.length < 4000 /* DoubleMargin */)
4672
4667
  return;
4673
4668
  let structure = lineStructure(line.from, line.to, this.state);
4674
- if (structure.total < 10000 /* Margin */)
4669
+ if (structure.total < 4000 /* DoubleMargin */)
4675
4670
  return;
4676
4671
  let viewFrom, viewTo;
4677
4672
  if (this.heightOracle.lineWrapping) {
4678
- if (line.from != this.viewport.from)
4679
- viewFrom = line.from;
4680
- else
4681
- viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
4682
- if (line.to != this.viewport.to)
4683
- viewTo = line.to;
4684
- else
4685
- viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
4673
+ let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4674
+ viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4675
+ viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4686
4676
  }
4687
4677
  else {
4688
4678
  let totalWidth = structure.total * this.heightOracle.charWidth;
4689
- viewFrom = findPosition(structure, this.pixelViewport.left / totalWidth);
4690
- viewTo = findPosition(structure, this.pixelViewport.right / totalWidth);
4679
+ let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4680
+ viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4681
+ viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4691
4682
  }
4683
+ let outside = [];
4684
+ if (viewFrom > line.from)
4685
+ outside.push({ from: line.from, to: viewFrom });
4686
+ if (viewTo < line.to)
4687
+ outside.push({ from: viewTo, to: line.to });
4692
4688
  let sel = this.state.selection.main;
4693
- // Make sure the gap doesn't cover a selection end
4694
- if (sel.from <= viewFrom && sel.to >= line.from)
4695
- viewFrom = sel.from;
4696
- if (sel.from <= line.to && sel.to >= viewTo)
4697
- viewTo = sel.to;
4698
- let gapTo = viewFrom - 10000 /* Margin */, gapFrom = viewTo + 10000 /* Margin */;
4699
- if (gapTo > line.from + 5000 /* HalfMargin */)
4700
- gaps.push(find(current, gap => gap.from == line.from && gap.to > gapTo - 5000 /* HalfMargin */ && gap.to < gapTo + 5000 /* HalfMargin */) ||
4701
- new LineGap(line.from, gapTo, this.gapSize(line, gapTo, true, structure)));
4702
- if (gapFrom < line.to - 5000 /* HalfMargin */)
4703
- gaps.push(find(current, gap => gap.to == line.to && gap.from > gapFrom - 5000 /* HalfMargin */ &&
4704
- gap.from < gapFrom + 5000 /* HalfMargin */) ||
4705
- new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
4689
+ // Make sure the gaps don't cover a selection end
4690
+ if (sel.from >= line.from && sel.from <= line.to)
4691
+ cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4692
+ if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4693
+ cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4694
+ for (let { from, to } of outside)
4695
+ if (to - from > 1000 /* HalfMargin */) {
4696
+ gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4697
+ Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4698
+ new LineGap(from, to, this.gapSize(line, from, to, structure)));
4699
+ }
4706
4700
  });
4707
4701
  return gaps;
4708
4702
  }
4709
- gapSize(line, pos, start, structure) {
4703
+ gapSize(line, from, to, structure) {
4704
+ let fraction = findFraction(structure, to) - findFraction(structure, from);
4710
4705
  if (this.heightOracle.lineWrapping) {
4711
- let height = line.height * findFraction(structure, pos);
4712
- return start ? height : line.height - height;
4706
+ return line.height * fraction;
4713
4707
  }
4714
4708
  else {
4715
- let ratio = findFraction(structure, pos);
4716
- return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
4709
+ return structure.total * this.heightOracle.charWidth * fraction;
4717
4710
  }
4718
4711
  }
4719
4712
  updateLineGaps(gaps) {
@@ -4807,6 +4800,20 @@ function findFraction(structure, pos) {
4807
4800
  }
4808
4801
  return counted / structure.total;
4809
4802
  }
4803
+ function cutRange(ranges, from, to) {
4804
+ for (let i = 0; i < ranges.length; i++) {
4805
+ let r = ranges[i];
4806
+ if (r.from < to && r.to > from) {
4807
+ let pieces = [];
4808
+ if (r.from < from)
4809
+ pieces.push({ from: r.from, to: from });
4810
+ if (r.to > to)
4811
+ pieces.push({ from: to, to: r.to });
4812
+ ranges.splice(i, 1, ...pieces);
4813
+ i += pieces.length - 1;
4814
+ }
4815
+ }
4816
+ }
4810
4817
  function find(array, f) {
4811
4818
  for (let val of array)
4812
4819
  if (f(val))
@@ -4925,7 +4932,10 @@ const baseTheme = buildTheme("." + baseThemeID, {
4925
4932
  wordWrap: "normal",
4926
4933
  boxSizing: "border-box",
4927
4934
  padding: "4px 0",
4928
- outline: "none"
4935
+ outline: "none",
4936
+ "&[contenteditable=true]": {
4937
+ WebkitUserModify: "read-write-plaintext-only",
4938
+ }
4929
4939
  },
4930
4940
  ".cm-lineWrapping": {
4931
4941
  whiteSpace: "pre-wrap",
@@ -5268,7 +5278,7 @@ class DOMObserver {
5268
5278
  // Completely hold off flushing when pending keys are set—the code
5269
5279
  // managing those will make sure processRecords is called and the
5270
5280
  // view is resynchronized after
5271
- if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5281
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5272
5282
  return;
5273
5283
  this.lastFlush = Date.now();
5274
5284
  let { from, to, typeOver } = this.processRecords();
@@ -5355,8 +5365,11 @@ function safariSelectionRangeHack(view) {
5355
5365
 
5356
5366
  function applyDOMChange(view, start, end, typeOver) {
5357
5367
  let change, newSel;
5358
- let sel = view.state.selection.main, bounds;
5359
- if (start > -1 && !view.state.readOnly && (bounds = view.docView.domBoundsAround(start, end, 0))) {
5368
+ let sel = view.state.selection.main;
5369
+ if (start > -1) {
5370
+ let bounds = view.docView.domBoundsAround(start, end, 0);
5371
+ if (!bounds || view.state.readOnly)
5372
+ return;
5360
5373
  let { from, to } = bounds;
5361
5374
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5362
5375
  let reader = new DOMReader(selPoints, view);
@@ -5410,16 +5423,8 @@ function applyDOMChange(view, start, end, typeOver) {
5410
5423
  // backspace, or delete. So this detects changes that look like
5411
5424
  // they're caused by those keys, and reinterprets them as key
5412
5425
  // events.
5413
- if (browser.android &&
5414
- ((change.from == sel.from && change.to == sel.to &&
5415
- change.insert.length == 1 && change.insert.lines == 2 &&
5416
- dispatchKey(view.contentDOM, "Enter", 13)) ||
5417
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5418
- dispatchKey(view.contentDOM, "Backspace", 8)) ||
5419
- (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5420
- dispatchKey(view.contentDOM, "Delete", 46)))) {
5426
+ if (browser.ios && view.inputState.flushIOSKey(view))
5421
5427
  return;
5422
- }
5423
5428
  let text = change.insert.toString();
5424
5429
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5425
5430
  return;
@@ -5912,7 +5917,7 @@ class EditorView {
5912
5917
  autocorrect: "off",
5913
5918
  autocapitalize: "off",
5914
5919
  translate: "no",
5915
- contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5920
+ contenteditable: !this.state.facet(editable) ? "false" : "true",
5916
5921
  class: "cm-content",
5917
5922
  style: `${browser.tabSize}: ${this.state.tabSize}`,
5918
5923
  role: "textbox",
@@ -6271,7 +6276,7 @@ class EditorView {
6271
6276
  target editors with a dark or light theme.
6272
6277
  */
6273
6278
  static baseTheme(spec) {
6274
- return state.Prec.fallback(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6279
+ return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6275
6280
  }
6276
6281
  }
6277
6282
  /**
@@ -6713,7 +6718,7 @@ const themeSpec = {
6713
6718
  };
6714
6719
  if (CanHidePrimary)
6715
6720
  themeSpec[".cm-line"].caretColor = "transparent !important";
6716
- const hideNativeSelection = state.Prec.override(EditorView.theme(themeSpec));
6721
+ const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
6717
6722
  function getBase(view) {
6718
6723
  let rect = view.scrollDOM.getBoundingClientRect();
6719
6724
  let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
package/dist/index.js CHANGED
@@ -249,24 +249,11 @@ function dispatchKey(elt, name, code) {
249
249
  elt.dispatchEvent(up);
250
250
  return down.defaultPrevented || up.defaultPrevented;
251
251
  }
252
- let _plainTextSupported = null;
253
- function contentEditablePlainTextSupported() {
254
- if (_plainTextSupported == null) {
255
- _plainTextSupported = false;
256
- let dummy = document.createElement("div");
257
- try {
258
- dummy.contentEditable = "plaintext-only";
259
- _plainTextSupported = dummy.contentEditable == "plaintext-only";
260
- }
261
- catch (_) { }
262
- }
263
- return _plainTextSupported;
264
- }
265
252
  function getRoot(node) {
266
253
  while (node) {
267
- node = node.assignedSlot || node.parentNode;
268
254
  if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
269
255
  return node;
256
+ node = node.assignedSlot || node.parentNode;
270
257
  }
271
258
  return null;
272
259
  }
@@ -318,25 +305,30 @@ class ContentView {
318
305
  sync(track) {
319
306
  var _a;
320
307
  if (this.dirty & 2 /* Node */) {
321
- let parent = this.dom, pos = null;
308
+ let parent = this.dom;
309
+ let pos = parent.firstChild;
322
310
  for (let child of this.children) {
323
311
  if (child.dirty) {
324
- let next = pos ? pos.nextSibling : parent.firstChild;
325
- if (!child.dom && next && !((_a = ContentView.get(next)) === null || _a === void 0 ? void 0 : _a.parent))
326
- child.reuseDOM(next);
312
+ if (!child.dom && pos && !((_a = ContentView.get(pos)) === null || _a === void 0 ? void 0 : _a.parent))
313
+ child.reuseDOM(pos);
327
314
  child.sync(track);
328
315
  child.dirty = 0 /* Not */;
329
316
  }
330
- if (track && track.node == parent && pos != child.dom)
317
+ if (track && !track.written && track.node == parent && pos != child.dom)
331
318
  track.written = true;
332
- syncNodeInto(parent, pos, child.dom);
333
- pos = child.dom;
319
+ if (child.dom.parentNode == parent) {
320
+ while (pos && pos != child.dom)
321
+ pos = rm(pos);
322
+ pos = child.dom.nextSibling;
323
+ }
324
+ else {
325
+ parent.insertBefore(child.dom, pos);
326
+ }
334
327
  }
335
- let next = pos ? pos.nextSibling : parent.firstChild;
336
- if (next && track && track.node == parent)
328
+ if (pos && track && track.node == parent)
337
329
  track.written = true;
338
- while (next)
339
- next = rm(next);
330
+ while (pos)
331
+ pos = rm(pos);
340
332
  }
341
333
  else if (this.dirty & 1 /* Child */) {
342
334
  for (let child of this.children)
@@ -476,14 +468,6 @@ function rm(dom) {
476
468
  dom.parentNode.removeChild(dom);
477
469
  return next;
478
470
  }
479
- function syncNodeInto(parent, after, dom) {
480
- let next = after ? after.nextSibling : parent.firstChild;
481
- if (dom.parentNode == parent)
482
- while (next != dom)
483
- next = rm(next);
484
- else
485
- parent.insertBefore(dom, next);
486
- }
487
471
  class ChildCursor {
488
472
  constructor(children, pos, i) {
489
473
  this.children = children;
@@ -3076,10 +3060,6 @@ class InputState {
3076
3060
  constructor(view) {
3077
3061
  this.lastKeyCode = 0;
3078
3062
  this.lastKeyTime = 0;
3079
- // On iOS, some keys need to have their default behavior happen
3080
- // (after which we retroactively handle them and reset the DOM) to
3081
- // avoid messing up the virtual keyboard state.
3082
- //
3083
3063
  // On Chrome Android, backspace near widgets is just completely
3084
3064
  // broken, and there are no key events, so we need to handle the
3085
3065
  // beforeinput event. Deleting stuff will often create a flurry of
@@ -3087,12 +3067,11 @@ class InputState {
3087
3067
  // subsequent events even more broken, so again, we hold off doing
3088
3068
  // anything until the browser is finished with whatever it is trying
3089
3069
  // to do.
3090
- //
3091
- // setPendingKey sets this, causing the DOM observer to pause for a
3092
- // bit, and setting an animation frame (which seems the most
3093
- // reliable way to detect 'browser is done flailing') to fire a fake
3094
- // key event and re-sync the view again.
3095
- this.pendingKey = undefined;
3070
+ this.pendingAndroidKey = undefined;
3071
+ // On iOS, some keys need to have their default behavior happen
3072
+ // (after which we retroactively handle them and reset the DOM) to
3073
+ // avoid messing up the virtual keyboard state.
3074
+ this.pendingIOSKey = undefined;
3096
3075
  this.lastSelectionOrigin = null;
3097
3076
  this.lastSelectionTime = 0;
3098
3077
  this.lastEscPress = 0;
@@ -3204,18 +3183,30 @@ class InputState {
3204
3183
  let pending;
3205
3184
  if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3206
3185
  !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3207
- this.setPendingKey(view, pending);
3186
+ this.pendingIOSKey = pending;
3187
+ setTimeout(() => this.flushIOSKey(view), 250);
3208
3188
  return true;
3209
3189
  }
3210
3190
  return false;
3211
3191
  }
3212
- setPendingKey(view, pending) {
3213
- this.pendingKey = pending;
3192
+ flushIOSKey(view) {
3193
+ let key = this.pendingIOSKey;
3194
+ if (!key)
3195
+ return false;
3196
+ this.pendingIOSKey = undefined;
3197
+ return dispatchKey(view.contentDOM, key.key, key.keyCode);
3198
+ }
3199
+ // This causes the DOM observer to pause for a bit, and sets an
3200
+ // animation frame (which seems the most reliable way to detect
3201
+ // 'Chrome is done flailing about messing with the DOM') to fire a
3202
+ // fake key event and re-sync the view again.
3203
+ setPendingAndroidKey(view, pending) {
3204
+ this.pendingAndroidKey = pending;
3214
3205
  requestAnimationFrame(() => {
3215
- if (!this.pendingKey)
3216
- return false;
3217
- let key = this.pendingKey;
3218
- this.pendingKey = undefined;
3206
+ let key = this.pendingAndroidKey;
3207
+ if (!key)
3208
+ return;
3209
+ this.pendingAndroidKey = undefined;
3219
3210
  view.observer.processRecords();
3220
3211
  let startState = view.state;
3221
3212
  dispatchKey(view.contentDOM, key.key, key.keyCode);
@@ -3729,18 +3720,20 @@ handlers.beforeinput = (view, event) => {
3729
3720
  // seems to do nothing at all on Chrome).
3730
3721
  let pending;
3731
3722
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3732
- view.inputState.setPendingKey(view, pending);
3733
- let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3734
- setTimeout(() => {
3735
- var _a;
3736
- // Backspacing near uneditable nodes on Chrome Android sometimes
3737
- // closes the virtual keyboard. This tries to crudely detect
3738
- // that and refocus to get it back.
3739
- if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3740
- view.contentDOM.blur();
3741
- view.focus();
3742
- }
3743
- }, 50);
3723
+ view.inputState.setPendingAndroidKey(view, pending);
3724
+ if (pending.key == "Backspace" || pending.key == "Delete") {
3725
+ let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3726
+ setTimeout(() => {
3727
+ var _a;
3728
+ // Backspacing near uneditable nodes on Chrome Android sometimes
3729
+ // closes the virtual keyboard. This tries to crudely detect
3730
+ // that and refocus to get it back.
3731
+ if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3732
+ view.contentDOM.blur();
3733
+ view.focus();
3734
+ }
3735
+ }, 100);
3736
+ }
3744
3737
  }
3745
3738
  };
3746
3739
 
@@ -4390,18 +4383,20 @@ function visiblePixelRange(dom, paddingTop) {
4390
4383
  let rect = dom.getBoundingClientRect();
4391
4384
  let left = Math.max(0, rect.left), right = Math.min(innerWidth, rect.right);
4392
4385
  let top = Math.max(0, rect.top), bottom = Math.min(innerHeight, rect.bottom);
4393
- for (let parent = dom.parentNode; parent;) { // (Cast to any because TypeScript is useless with Node types)
4386
+ let body = dom.ownerDocument.body;
4387
+ for (let parent = dom.parentNode; parent && parent != body;) {
4394
4388
  if (parent.nodeType == 1) {
4395
- let style = window.getComputedStyle(parent);
4396
- if ((parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth) &&
4389
+ let elt = parent;
4390
+ let style = window.getComputedStyle(elt);
4391
+ if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
4397
4392
  style.overflow != "visible") {
4398
- let parentRect = parent.getBoundingClientRect();
4393
+ let parentRect = elt.getBoundingClientRect();
4399
4394
  left = Math.max(left, parentRect.left);
4400
4395
  right = Math.min(right, parentRect.right);
4401
4396
  top = Math.max(top, parentRect.top);
4402
4397
  bottom = Math.min(bottom, parentRect.bottom);
4403
4398
  }
4404
- parent = style.position == "absolute" || style.position == "fixed" ? parent.offsetParent : parent.parentNode;
4399
+ parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
4405
4400
  }
4406
4401
  else if (parent.nodeType == 11) { // Shadow root
4407
4402
  parent = parent.host;
@@ -4527,7 +4522,7 @@ class ViewState {
4527
4522
  viewport = this.getViewport(0, scrollTarget);
4528
4523
  this.viewport = viewport;
4529
4524
  this.updateForViewport();
4530
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4525
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4531
4526
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4532
4527
  update.flags |= this.computeVisibleRanges();
4533
4528
  if (scrollTarget)
@@ -4587,7 +4582,7 @@ class ViewState {
4587
4582
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4588
4583
  this.viewport = this.getViewport(bias, this.scrollTarget);
4589
4584
  this.updateForViewport();
4590
- if (this.lineGaps.length || this.viewport.to - this.viewport.from > 15000 /* MinViewPort */)
4585
+ if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4591
4586
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4592
4587
  result |= this.computeVisibleRanges();
4593
4588
  if (this.mustEnforceCursorAssoc) {
@@ -4662,52 +4657,50 @@ class ViewState {
4662
4657
  if (this.heightOracle.direction != Direction.LTR)
4663
4658
  return gaps;
4664
4659
  this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4665
- if (line.length < 10000 /* Margin */)
4660
+ if (line.length < 4000 /* DoubleMargin */)
4666
4661
  return;
4667
4662
  let structure = lineStructure(line.from, line.to, this.state);
4668
- if (structure.total < 10000 /* Margin */)
4663
+ if (structure.total < 4000 /* DoubleMargin */)
4669
4664
  return;
4670
4665
  let viewFrom, viewTo;
4671
4666
  if (this.heightOracle.lineWrapping) {
4672
- if (line.from != this.viewport.from)
4673
- viewFrom = line.from;
4674
- else
4675
- viewFrom = findPosition(structure, (this.visibleTop - line.top) / line.height);
4676
- if (line.to != this.viewport.to)
4677
- viewTo = line.to;
4678
- else
4679
- viewTo = findPosition(structure, (this.visibleBottom - line.top) / line.height);
4667
+ let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4668
+ viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4669
+ viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4680
4670
  }
4681
4671
  else {
4682
4672
  let totalWidth = structure.total * this.heightOracle.charWidth;
4683
- viewFrom = findPosition(structure, this.pixelViewport.left / totalWidth);
4684
- viewTo = findPosition(structure, this.pixelViewport.right / totalWidth);
4673
+ let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4674
+ viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4675
+ viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4685
4676
  }
4677
+ let outside = [];
4678
+ if (viewFrom > line.from)
4679
+ outside.push({ from: line.from, to: viewFrom });
4680
+ if (viewTo < line.to)
4681
+ outside.push({ from: viewTo, to: line.to });
4686
4682
  let sel = this.state.selection.main;
4687
- // Make sure the gap doesn't cover a selection end
4688
- if (sel.from <= viewFrom && sel.to >= line.from)
4689
- viewFrom = sel.from;
4690
- if (sel.from <= line.to && sel.to >= viewTo)
4691
- viewTo = sel.to;
4692
- let gapTo = viewFrom - 10000 /* Margin */, gapFrom = viewTo + 10000 /* Margin */;
4693
- if (gapTo > line.from + 5000 /* HalfMargin */)
4694
- gaps.push(find(current, gap => gap.from == line.from && gap.to > gapTo - 5000 /* HalfMargin */ && gap.to < gapTo + 5000 /* HalfMargin */) ||
4695
- new LineGap(line.from, gapTo, this.gapSize(line, gapTo, true, structure)));
4696
- if (gapFrom < line.to - 5000 /* HalfMargin */)
4697
- gaps.push(find(current, gap => gap.to == line.to && gap.from > gapFrom - 5000 /* HalfMargin */ &&
4698
- gap.from < gapFrom + 5000 /* HalfMargin */) ||
4699
- new LineGap(gapFrom, line.to, this.gapSize(line, gapFrom, false, structure)));
4683
+ // Make sure the gaps don't cover a selection end
4684
+ if (sel.from >= line.from && sel.from <= line.to)
4685
+ cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4686
+ if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4687
+ cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4688
+ for (let { from, to } of outside)
4689
+ if (to - from > 1000 /* HalfMargin */) {
4690
+ gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4691
+ Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4692
+ new LineGap(from, to, this.gapSize(line, from, to, structure)));
4693
+ }
4700
4694
  });
4701
4695
  return gaps;
4702
4696
  }
4703
- gapSize(line, pos, start, structure) {
4697
+ gapSize(line, from, to, structure) {
4698
+ let fraction = findFraction(structure, to) - findFraction(structure, from);
4704
4699
  if (this.heightOracle.lineWrapping) {
4705
- let height = line.height * findFraction(structure, pos);
4706
- return start ? height : line.height - height;
4700
+ return line.height * fraction;
4707
4701
  }
4708
4702
  else {
4709
- let ratio = findFraction(structure, pos);
4710
- return structure.total * this.heightOracle.charWidth * (start ? ratio : 1 - ratio);
4703
+ return structure.total * this.heightOracle.charWidth * fraction;
4711
4704
  }
4712
4705
  }
4713
4706
  updateLineGaps(gaps) {
@@ -4801,6 +4794,20 @@ function findFraction(structure, pos) {
4801
4794
  }
4802
4795
  return counted / structure.total;
4803
4796
  }
4797
+ function cutRange(ranges, from, to) {
4798
+ for (let i = 0; i < ranges.length; i++) {
4799
+ let r = ranges[i];
4800
+ if (r.from < to && r.to > from) {
4801
+ let pieces = [];
4802
+ if (r.from < from)
4803
+ pieces.push({ from: r.from, to: from });
4804
+ if (r.to > to)
4805
+ pieces.push({ from: to, to: r.to });
4806
+ ranges.splice(i, 1, ...pieces);
4807
+ i += pieces.length - 1;
4808
+ }
4809
+ }
4810
+ }
4804
4811
  function find(array, f) {
4805
4812
  for (let val of array)
4806
4813
  if (f(val))
@@ -4919,7 +4926,10 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
4919
4926
  wordWrap: "normal",
4920
4927
  boxSizing: "border-box",
4921
4928
  padding: "4px 0",
4922
- outline: "none"
4929
+ outline: "none",
4930
+ "&[contenteditable=true]": {
4931
+ WebkitUserModify: "read-write-plaintext-only",
4932
+ }
4923
4933
  },
4924
4934
  ".cm-lineWrapping": {
4925
4935
  whiteSpace: "pre-wrap",
@@ -5262,7 +5272,7 @@ class DOMObserver {
5262
5272
  // Completely hold off flushing when pending keys are set—the code
5263
5273
  // managing those will make sure processRecords is called and the
5264
5274
  // view is resynchronized after
5265
- if (this.delayedFlush >= 0 || this.view.inputState.pendingKey)
5275
+ if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5266
5276
  return;
5267
5277
  this.lastFlush = Date.now();
5268
5278
  let { from, to, typeOver } = this.processRecords();
@@ -5349,8 +5359,11 @@ function safariSelectionRangeHack(view) {
5349
5359
 
5350
5360
  function applyDOMChange(view, start, end, typeOver) {
5351
5361
  let change, newSel;
5352
- let sel = view.state.selection.main, bounds;
5353
- if (start > -1 && !view.state.readOnly && (bounds = view.docView.domBoundsAround(start, end, 0))) {
5362
+ let sel = view.state.selection.main;
5363
+ if (start > -1) {
5364
+ let bounds = view.docView.domBoundsAround(start, end, 0);
5365
+ if (!bounds || view.state.readOnly)
5366
+ return;
5354
5367
  let { from, to } = bounds;
5355
5368
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5356
5369
  let reader = new DOMReader(selPoints, view);
@@ -5404,16 +5417,8 @@ function applyDOMChange(view, start, end, typeOver) {
5404
5417
  // backspace, or delete. So this detects changes that look like
5405
5418
  // they're caused by those keys, and reinterprets them as key
5406
5419
  // events.
5407
- if (browser.android &&
5408
- ((change.from == sel.from && change.to == sel.to &&
5409
- change.insert.length == 1 && change.insert.lines == 2 &&
5410
- dispatchKey(view.contentDOM, "Enter", 13)) ||
5411
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5412
- dispatchKey(view.contentDOM, "Backspace", 8)) ||
5413
- (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5414
- dispatchKey(view.contentDOM, "Delete", 46)))) {
5420
+ if (browser.ios && view.inputState.flushIOSKey(view))
5415
5421
  return;
5416
- }
5417
5422
  let text = change.insert.toString();
5418
5423
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5419
5424
  return;
@@ -5906,7 +5911,7 @@ class EditorView {
5906
5911
  autocorrect: "off",
5907
5912
  autocapitalize: "off",
5908
5913
  translate: "no",
5909
- contenteditable: !this.state.facet(editable) ? "false" : contentEditablePlainTextSupported() ? "plaintext-only" : "true",
5914
+ contenteditable: !this.state.facet(editable) ? "false" : "true",
5910
5915
  class: "cm-content",
5911
5916
  style: `${browser.tabSize}: ${this.state.tabSize}`,
5912
5917
  role: "textbox",
@@ -6265,7 +6270,7 @@ class EditorView {
6265
6270
  target editors with a dark or light theme.
6266
6271
  */
6267
6272
  static baseTheme(spec) {
6268
- return Prec.fallback(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6273
+ return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6269
6274
  }
6270
6275
  }
6271
6276
  /**
@@ -6707,7 +6712,7 @@ const themeSpec = {
6707
6712
  };
6708
6713
  if (CanHidePrimary)
6709
6714
  themeSpec[".cm-line"].caretColor = "transparent !important";
6710
- const hideNativeSelection = /*@__PURE__*/Prec.override(/*@__PURE__*/EditorView.theme(themeSpec));
6715
+ const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
6711
6716
  function getBase(view) {
6712
6717
  let rect = view.scrollDOM.getBoundingClientRect();
6713
6718
  let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.11",
3
+ "version": "0.19.15",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -27,7 +27,7 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@codemirror/rangeset": "^0.19.0",
30
- "@codemirror/state": "^0.19.2",
30
+ "@codemirror/state": "^0.19.3",
31
31
  "@codemirror/text": "^0.19.0",
32
32
  "style-mod": "^4.0.0",
33
33
  "w3c-keyname": "^2.2.4"