@codemirror/view 6.0.3 → 6.1.2

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,27 @@
1
+ ## 6.1.2 (2022-07-27)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where double tapping enter to confirm IME input and insert a newline on iOS would sometimes insert two newlines.
6
+
7
+ Fix an issue on iOS where a composition could get aborted if the editor scrolled on backspace.
8
+
9
+ ## 6.1.1 (2022-07-25)
10
+
11
+ ### Bug fixes
12
+
13
+ Make `highlightSpecialChars` replace directional isolate characters by default.
14
+
15
+ The editor will now try to suppress browsers' native behavior of resetting the selection in the editable content when the editable element is focused (programmatically, with tab, etc).
16
+
17
+ Fix a CSS issue that made it possible, when the gutters were wide enough, for them to overlap with the content.
18
+
19
+ ## 6.1.0 (2022-07-19)
20
+
21
+ ### New features
22
+
23
+ `MatchDecorator` now supports a `decorate` option that can be used to customize the way decorations are added for each match.
24
+
1
25
  ## 6.0.3 (2022-07-08)
2
26
 
3
27
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -268,6 +268,31 @@ function clearAttributes(node) {
268
268
  while (node.attributes.length)
269
269
  node.removeAttributeNode(node.attributes[0]);
270
270
  }
271
+ function atElementStart(doc, selection) {
272
+ let node = selection.focusNode, offset = selection.focusOffset;
273
+ if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
274
+ return false;
275
+ for (;;) {
276
+ if (offset) {
277
+ if (node.nodeType != 1)
278
+ return false;
279
+ let prev = node.childNodes[offset - 1];
280
+ if (prev.contentEditable == "false")
281
+ offset--;
282
+ else {
283
+ node = prev;
284
+ offset = maxOffset(node);
285
+ }
286
+ }
287
+ else if (node == doc) {
288
+ return true;
289
+ }
290
+ else {
291
+ offset = domIndex(node);
292
+ node = node.parentNode;
293
+ }
294
+ }
295
+ }
271
296
 
272
297
  class DOMPos {
273
298
  constructor(node, offset, precise = true) {
@@ -3289,6 +3314,10 @@ class InputState {
3289
3314
  constructor(view) {
3290
3315
  this.lastKeyCode = 0;
3291
3316
  this.lastKeyTime = 0;
3317
+ this.lastTouchTime = 0;
3318
+ this.lastFocusTime = 0;
3319
+ this.lastScrollTop = 0;
3320
+ this.lastScrollLeft = 0;
3292
3321
  this.chromeScrollHack = -1;
3293
3322
  // On iOS, some keys need to have their default behavior happen
3294
3323
  // (after which we retroactively handle them and reset the DOM) to
@@ -3327,10 +3356,10 @@ class InputState {
3327
3356
  event.preventDefault();
3328
3357
  else
3329
3358
  handler(view, event);
3330
- });
3359
+ }, handlerOptions[type]);
3331
3360
  this.registeredEvents.push(type);
3332
3361
  }
3333
- if (browser.chrome && browser.chrome_version >= 102) {
3362
+ if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3334
3363
  // On Chrome 102, viewport updates somehow stop wheel-based
3335
3364
  // scrolling. Turning off pointer events during the scroll seems
3336
3365
  // to avoid the issue.
@@ -3390,6 +3419,8 @@ class InputState {
3390
3419
  return false;
3391
3420
  }
3392
3421
  runScrollHandlers(view, event) {
3422
+ this.lastScrollTop = view.scrollDOM.scrollTop;
3423
+ this.lastScrollLeft = view.scrollDOM.scrollLeft;
3393
3424
  for (let set of this.customHandlers) {
3394
3425
  let handler = set.handlers.scroll;
3395
3426
  if (handler) {
@@ -3450,7 +3481,7 @@ class InputState {
3450
3481
  // compositionend and keydown events are sometimes emitted in the
3451
3482
  // wrong order. The key event should still be ignored, even when
3452
3483
  // it happens after the compositionend event.
3453
- if (browser.safari && Date.now() - this.compositionEndedAt < 100) {
3484
+ if (browser.safari && !browser.ios && Date.now() - this.compositionEndedAt < 100) {
3454
3485
  this.compositionEndedAt = 0;
3455
3486
  return true;
3456
3487
  }
@@ -3578,6 +3609,7 @@ function eventBelongsToEditor(view, event) {
3578
3609
  return true;
3579
3610
  }
3580
3611
  const handlers = Object.create(null);
3612
+ const handlerOptions = Object.create(null);
3581
3613
  // This is very crude, but unfortunately both these browsers _pretend_
3582
3614
  // that they have a clipboard API—all the objects and methods are
3583
3615
  // there, they just don't work, and they are hard to test.
@@ -3634,17 +3666,17 @@ handlers.keydown = (view, event) => {
3634
3666
  else if (modifierCodes.indexOf(event.keyCode) < 0)
3635
3667
  view.inputState.lastEscPress = 0;
3636
3668
  };
3637
- let lastTouch = 0;
3638
3669
  handlers.touchstart = (view, e) => {
3639
- lastTouch = Date.now();
3670
+ view.inputState.lastTouchTime = Date.now();
3640
3671
  view.inputState.setSelectionOrigin("select.pointer");
3641
3672
  };
3642
3673
  handlers.touchmove = view => {
3643
3674
  view.inputState.setSelectionOrigin("select.pointer");
3644
3675
  };
3676
+ handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
3645
3677
  handlers.mousedown = (view, event) => {
3646
3678
  view.observer.flush();
3647
- if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3679
+ if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
3648
3680
  return; // Ignore touch interaction
3649
3681
  let style = null;
3650
3682
  for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
@@ -3898,7 +3930,15 @@ function updateForFocusChange(view) {
3898
3930
  view.update([]);
3899
3931
  }, 10);
3900
3932
  }
3901
- handlers.focus = updateForFocusChange;
3933
+ handlers.focus = view => {
3934
+ view.inputState.lastFocusTime = Date.now();
3935
+ // When focusing reset the scroll position, move it back to where it was
3936
+ if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
3937
+ view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
3938
+ view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
3939
+ }
3940
+ updateForFocusChange(view);
3941
+ };
3902
3942
  handlers.blur = view => {
3903
3943
  view.observer.clearSelectionRange();
3904
3944
  updateForFocusChange(view);
@@ -5269,6 +5309,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5269
5309
  "&light .cm-specialChar": { color: "red" },
5270
5310
  "&dark .cm-specialChar": { color: "#f78" },
5271
5311
  ".cm-gutters": {
5312
+ flexShrink: 0,
5272
5313
  display: "flex",
5273
5314
  height: "100%",
5274
5315
  boxSizing: "border-box",
@@ -5525,15 +5566,28 @@ class DOMObserver {
5525
5566
  this.flush(false);
5526
5567
  }
5527
5568
  readSelectionRange() {
5528
- let { root } = this.view;
5569
+ let { view } = this;
5529
5570
  // The Selection object is broken in shadow roots in Safari. See
5530
5571
  // https://github.com/codemirror/dev/issues/414
5531
- let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5532
- safariSelectionRangeHack(this.view) || getSelection(root);
5572
+ let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
5573
+ safariSelectionRangeHack(this.view) || getSelection(view.root);
5533
5574
  if (!range || this.selectionRange.eq(range))
5534
5575
  return false;
5576
+ let local = hasSelection(this.dom, range);
5577
+ // Detect the situation where the browser has, on focus, moved the
5578
+ // selection to the start of the content element. Reset it to the
5579
+ // position from the editor state.
5580
+ if (local && !this.selectionChanged && this.selectionRange.focusNode &&
5581
+ view.inputState.lastFocusTime > Date.now() - 200 &&
5582
+ view.inputState.lastTouchTime < Date.now() - 300 &&
5583
+ atElementStart(this.dom, range)) {
5584
+ view.docView.updateSelection();
5585
+ return false;
5586
+ }
5535
5587
  this.selectionRange.setRange(range);
5536
- return this.selectionChanged = true;
5588
+ if (local)
5589
+ this.selectionChanged = true;
5590
+ return true;
5537
5591
  }
5538
5592
  setSelectionRange(anchor, head) {
5539
5593
  this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
@@ -5620,7 +5674,7 @@ class DOMObserver {
5620
5674
  this.delayedAndroidKey = null;
5621
5675
  this.delayedFlush = -1;
5622
5676
  if (!this.flush())
5623
- dispatchKey(this.view.contentDOM, key.key, key.keyCode);
5677
+ dispatchKey(this.dom, key.key, key.keyCode);
5624
5678
  });
5625
5679
  // Since backspace beforeinput is sometimes signalled spuriously,
5626
5680
  // Enter always takes precedence.
@@ -5635,8 +5689,8 @@ class DOMObserver {
5635
5689
  if (this.delayedFlush >= 0) {
5636
5690
  window.clearTimeout(this.delayedFlush);
5637
5691
  this.delayedFlush = -1;
5638
- this.flush();
5639
5692
  }
5693
+ this.flush();
5640
5694
  }
5641
5695
  processRecords() {
5642
5696
  let records = this.queue;
@@ -5674,6 +5728,7 @@ class DOMObserver {
5674
5728
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5675
5729
  if (from < 0 && !newSel)
5676
5730
  return;
5731
+ this.view.inputState.lastFocusTime = 0;
5677
5732
  this.selectionChanged = false;
5678
5733
  let startState = this.view.state;
5679
5734
  let handled = this.onChange(from, to, typeOver);
@@ -6227,7 +6282,7 @@ class EditorView {
6227
6282
  cancelAnimationFrame(this.measureScheduled);
6228
6283
  this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
6229
6284
  if (flush)
6230
- this.observer.flush();
6285
+ this.observer.forceFlush();
6231
6286
  let updated = null;
6232
6287
  try {
6233
6288
  for (let i = 0;; i++) {
@@ -7381,7 +7436,7 @@ function iterMatches(doc, re, from, to, f) {
7381
7436
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
7382
7437
  if (!cursor.lineBreak)
7383
7438
  while (m = re.exec(cursor.value))
7384
- f(pos + m.index, pos + m.index + m[0].length, m);
7439
+ f(pos + m.index, m);
7385
7440
  }
7386
7441
  }
7387
7442
  function matchRanges(view, maxLength) {
@@ -7411,11 +7466,20 @@ class MatchDecorator {
7411
7466
  Create a decorator.
7412
7467
  */
7413
7468
  constructor(config) {
7414
- let { regexp, decoration, boundary, maxLength = 1000 } = config;
7469
+ const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
7415
7470
  if (!regexp.global)
7416
7471
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7417
7472
  this.regexp = regexp;
7418
- this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7473
+ if (decorate) {
7474
+ this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
7475
+ }
7476
+ else if (decoration) {
7477
+ let getDeco = typeof decoration == "function" ? decoration : () => decoration;
7478
+ this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
7479
+ }
7480
+ else {
7481
+ throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
7482
+ }
7419
7483
  this.boundary = boundary;
7420
7484
  this.maxLength = maxLength;
7421
7485
  }
@@ -7425,9 +7489,9 @@ class MatchDecorator {
7425
7489
  plugin.
7426
7490
  */
7427
7491
  createDeco(view) {
7428
- let build = new state.RangeSetBuilder();
7492
+ let build = new state.RangeSetBuilder(), add = build.add.bind(build);
7429
7493
  for (let { from, to } of matchRanges(view, this.maxLength))
7430
- iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7494
+ iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
7431
7495
  return build.finish();
7432
7496
  }
7433
7497
  /**
@@ -7469,15 +7533,14 @@ class MatchDecorator {
7469
7533
  }
7470
7534
  }
7471
7535
  let ranges = [], m;
7536
+ let add = (from, to, deco) => ranges.push(deco.range(from, to));
7472
7537
  if (fromLine == toLine) {
7473
7538
  this.regexp.lastIndex = start - fromLine.from;
7474
- while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from) {
7475
- let pos = m.index + fromLine.from;
7476
- ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
7477
- }
7539
+ while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
7540
+ this.addMatch(m, view, m.index + fromLine.from, add);
7478
7541
  }
7479
7542
  else {
7480
- iterMatches(view.state.doc, this.regexp, start, end, (from, to, m) => ranges.push(this.getDeco(m, view, from).range(from, to)));
7543
+ iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
7481
7544
  }
7482
7545
  deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
7483
7546
  }
@@ -7487,7 +7550,7 @@ class MatchDecorator {
7487
7550
  }
7488
7551
 
7489
7552
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
7490
- const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7553
+ const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7491
7554
  const Names = {
7492
7555
  0: "null",
7493
7556
  7: "bell",
@@ -7504,6 +7567,9 @@ const Names = {
7504
7567
  8232: "line separator",
7505
7568
  8237: "left-to-right override",
7506
7569
  8238: "right-to-left override",
7570
+ 8294: "left-to-right isolate",
7571
+ 8295: "right-to-left isolate",
7572
+ 8297: "pop directional isolate",
7507
7573
  8233: "paragraph separator",
7508
7574
  65279: "zero width no-break space",
7509
7575
  65532: "object replacement"
package/dist/index.d.ts CHANGED
@@ -1357,7 +1357,7 @@ represent a matching configuration.
1357
1357
  */
1358
1358
  declare class MatchDecorator {
1359
1359
  private regexp;
1360
- private getDeco;
1360
+ private addMatch;
1361
1361
  private boundary;
1362
1362
  private maxLength;
1363
1363
  /**
@@ -1374,7 +1374,18 @@ declare class MatchDecorator {
1374
1374
  The decoration to apply to matches, either directly or as a
1375
1375
  function of the match.
1376
1376
  */
1377
- decoration: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
1377
+ decoration?: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
1378
+ /**
1379
+ Customize the way decorations are added for matches. This
1380
+ function, when given, will be called for matches and should
1381
+ call `add` to create decorations for them. Note that the
1382
+ decorations should appear *in* the given range, and the
1383
+ function should have no side effects beyond calling `add`.
1384
+
1385
+ The `decoration` option is ignored when `decorate` is
1386
+ provided.
1387
+ */
1388
+ decorate?: (add: (from: number, to: number, decoration: Decoration) => void, from: number, to: number, match: RegExpExecArray, view: EditorView) => void;
1378
1389
  /**
1379
1390
  By default, changed lines are re-matched entirely. You can
1380
1391
  provide a boundary expression, which should match single
package/dist/index.js CHANGED
@@ -264,6 +264,31 @@ function clearAttributes(node) {
264
264
  while (node.attributes.length)
265
265
  node.removeAttributeNode(node.attributes[0]);
266
266
  }
267
+ function atElementStart(doc, selection) {
268
+ let node = selection.focusNode, offset = selection.focusOffset;
269
+ if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
270
+ return false;
271
+ for (;;) {
272
+ if (offset) {
273
+ if (node.nodeType != 1)
274
+ return false;
275
+ let prev = node.childNodes[offset - 1];
276
+ if (prev.contentEditable == "false")
277
+ offset--;
278
+ else {
279
+ node = prev;
280
+ offset = maxOffset(node);
281
+ }
282
+ }
283
+ else if (node == doc) {
284
+ return true;
285
+ }
286
+ else {
287
+ offset = domIndex(node);
288
+ node = node.parentNode;
289
+ }
290
+ }
291
+ }
267
292
 
268
293
  class DOMPos {
269
294
  constructor(node, offset, precise = true) {
@@ -3283,6 +3308,10 @@ class InputState {
3283
3308
  constructor(view) {
3284
3309
  this.lastKeyCode = 0;
3285
3310
  this.lastKeyTime = 0;
3311
+ this.lastTouchTime = 0;
3312
+ this.lastFocusTime = 0;
3313
+ this.lastScrollTop = 0;
3314
+ this.lastScrollLeft = 0;
3286
3315
  this.chromeScrollHack = -1;
3287
3316
  // On iOS, some keys need to have their default behavior happen
3288
3317
  // (after which we retroactively handle them and reset the DOM) to
@@ -3321,10 +3350,10 @@ class InputState {
3321
3350
  event.preventDefault();
3322
3351
  else
3323
3352
  handler(view, event);
3324
- });
3353
+ }, handlerOptions[type]);
3325
3354
  this.registeredEvents.push(type);
3326
3355
  }
3327
- if (browser.chrome && browser.chrome_version >= 102) {
3356
+ if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3328
3357
  // On Chrome 102, viewport updates somehow stop wheel-based
3329
3358
  // scrolling. Turning off pointer events during the scroll seems
3330
3359
  // to avoid the issue.
@@ -3384,6 +3413,8 @@ class InputState {
3384
3413
  return false;
3385
3414
  }
3386
3415
  runScrollHandlers(view, event) {
3416
+ this.lastScrollTop = view.scrollDOM.scrollTop;
3417
+ this.lastScrollLeft = view.scrollDOM.scrollLeft;
3387
3418
  for (let set of this.customHandlers) {
3388
3419
  let handler = set.handlers.scroll;
3389
3420
  if (handler) {
@@ -3444,7 +3475,7 @@ class InputState {
3444
3475
  // compositionend and keydown events are sometimes emitted in the
3445
3476
  // wrong order. The key event should still be ignored, even when
3446
3477
  // it happens after the compositionend event.
3447
- if (browser.safari && Date.now() - this.compositionEndedAt < 100) {
3478
+ if (browser.safari && !browser.ios && Date.now() - this.compositionEndedAt < 100) {
3448
3479
  this.compositionEndedAt = 0;
3449
3480
  return true;
3450
3481
  }
@@ -3572,6 +3603,7 @@ function eventBelongsToEditor(view, event) {
3572
3603
  return true;
3573
3604
  }
3574
3605
  const handlers = /*@__PURE__*/Object.create(null);
3606
+ const handlerOptions = /*@__PURE__*/Object.create(null);
3575
3607
  // This is very crude, but unfortunately both these browsers _pretend_
3576
3608
  // that they have a clipboard API—all the objects and methods are
3577
3609
  // there, they just don't work, and they are hard to test.
@@ -3628,17 +3660,17 @@ handlers.keydown = (view, event) => {
3628
3660
  else if (modifierCodes.indexOf(event.keyCode) < 0)
3629
3661
  view.inputState.lastEscPress = 0;
3630
3662
  };
3631
- let lastTouch = 0;
3632
3663
  handlers.touchstart = (view, e) => {
3633
- lastTouch = Date.now();
3664
+ view.inputState.lastTouchTime = Date.now();
3634
3665
  view.inputState.setSelectionOrigin("select.pointer");
3635
3666
  };
3636
3667
  handlers.touchmove = view => {
3637
3668
  view.inputState.setSelectionOrigin("select.pointer");
3638
3669
  };
3670
+ handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
3639
3671
  handlers.mousedown = (view, event) => {
3640
3672
  view.observer.flush();
3641
- if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3673
+ if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
3642
3674
  return; // Ignore touch interaction
3643
3675
  let style = null;
3644
3676
  for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
@@ -3892,7 +3924,15 @@ function updateForFocusChange(view) {
3892
3924
  view.update([]);
3893
3925
  }, 10);
3894
3926
  }
3895
- handlers.focus = updateForFocusChange;
3927
+ handlers.focus = view => {
3928
+ view.inputState.lastFocusTime = Date.now();
3929
+ // When focusing reset the scroll position, move it back to where it was
3930
+ if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
3931
+ view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
3932
+ view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
3933
+ }
3934
+ updateForFocusChange(view);
3935
+ };
3896
3936
  handlers.blur = view => {
3897
3937
  view.observer.clearSelectionRange();
3898
3938
  updateForFocusChange(view);
@@ -5262,6 +5302,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5262
5302
  "&light .cm-specialChar": { color: "red" },
5263
5303
  "&dark .cm-specialChar": { color: "#f78" },
5264
5304
  ".cm-gutters": {
5305
+ flexShrink: 0,
5265
5306
  display: "flex",
5266
5307
  height: "100%",
5267
5308
  boxSizing: "border-box",
@@ -5518,15 +5559,28 @@ class DOMObserver {
5518
5559
  this.flush(false);
5519
5560
  }
5520
5561
  readSelectionRange() {
5521
- let { root } = this.view;
5562
+ let { view } = this;
5522
5563
  // The Selection object is broken in shadow roots in Safari. See
5523
5564
  // https://github.com/codemirror/dev/issues/414
5524
- let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5525
- safariSelectionRangeHack(this.view) || getSelection(root);
5565
+ let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
5566
+ safariSelectionRangeHack(this.view) || getSelection(view.root);
5526
5567
  if (!range || this.selectionRange.eq(range))
5527
5568
  return false;
5569
+ let local = hasSelection(this.dom, range);
5570
+ // Detect the situation where the browser has, on focus, moved the
5571
+ // selection to the start of the content element. Reset it to the
5572
+ // position from the editor state.
5573
+ if (local && !this.selectionChanged && this.selectionRange.focusNode &&
5574
+ view.inputState.lastFocusTime > Date.now() - 200 &&
5575
+ view.inputState.lastTouchTime < Date.now() - 300 &&
5576
+ atElementStart(this.dom, range)) {
5577
+ view.docView.updateSelection();
5578
+ return false;
5579
+ }
5528
5580
  this.selectionRange.setRange(range);
5529
- return this.selectionChanged = true;
5581
+ if (local)
5582
+ this.selectionChanged = true;
5583
+ return true;
5530
5584
  }
5531
5585
  setSelectionRange(anchor, head) {
5532
5586
  this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
@@ -5613,7 +5667,7 @@ class DOMObserver {
5613
5667
  this.delayedAndroidKey = null;
5614
5668
  this.delayedFlush = -1;
5615
5669
  if (!this.flush())
5616
- dispatchKey(this.view.contentDOM, key.key, key.keyCode);
5670
+ dispatchKey(this.dom, key.key, key.keyCode);
5617
5671
  });
5618
5672
  // Since backspace beforeinput is sometimes signalled spuriously,
5619
5673
  // Enter always takes precedence.
@@ -5628,8 +5682,8 @@ class DOMObserver {
5628
5682
  if (this.delayedFlush >= 0) {
5629
5683
  window.clearTimeout(this.delayedFlush);
5630
5684
  this.delayedFlush = -1;
5631
- this.flush();
5632
5685
  }
5686
+ this.flush();
5633
5687
  }
5634
5688
  processRecords() {
5635
5689
  let records = this.queue;
@@ -5667,6 +5721,7 @@ class DOMObserver {
5667
5721
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5668
5722
  if (from < 0 && !newSel)
5669
5723
  return;
5724
+ this.view.inputState.lastFocusTime = 0;
5670
5725
  this.selectionChanged = false;
5671
5726
  let startState = this.view.state;
5672
5727
  let handled = this.onChange(from, to, typeOver);
@@ -6220,7 +6275,7 @@ class EditorView {
6220
6275
  cancelAnimationFrame(this.measureScheduled);
6221
6276
  this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
6222
6277
  if (flush)
6223
- this.observer.flush();
6278
+ this.observer.forceFlush();
6224
6279
  let updated = null;
6225
6280
  try {
6226
6281
  for (let i = 0;; i++) {
@@ -7374,7 +7429,7 @@ function iterMatches(doc, re, from, to, f) {
7374
7429
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
7375
7430
  if (!cursor.lineBreak)
7376
7431
  while (m = re.exec(cursor.value))
7377
- f(pos + m.index, pos + m.index + m[0].length, m);
7432
+ f(pos + m.index, m);
7378
7433
  }
7379
7434
  }
7380
7435
  function matchRanges(view, maxLength) {
@@ -7404,11 +7459,20 @@ class MatchDecorator {
7404
7459
  Create a decorator.
7405
7460
  */
7406
7461
  constructor(config) {
7407
- let { regexp, decoration, boundary, maxLength = 1000 } = config;
7462
+ const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
7408
7463
  if (!regexp.global)
7409
7464
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7410
7465
  this.regexp = regexp;
7411
- this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7466
+ if (decorate) {
7467
+ this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
7468
+ }
7469
+ else if (decoration) {
7470
+ let getDeco = typeof decoration == "function" ? decoration : () => decoration;
7471
+ this.addMatch = (match, view, from, add) => add(from, from + match[0].length, getDeco(match, view, from));
7472
+ }
7473
+ else {
7474
+ throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
7475
+ }
7412
7476
  this.boundary = boundary;
7413
7477
  this.maxLength = maxLength;
7414
7478
  }
@@ -7418,9 +7482,9 @@ class MatchDecorator {
7418
7482
  plugin.
7419
7483
  */
7420
7484
  createDeco(view) {
7421
- let build = new RangeSetBuilder();
7485
+ let build = new RangeSetBuilder(), add = build.add.bind(build);
7422
7486
  for (let { from, to } of matchRanges(view, this.maxLength))
7423
- iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7487
+ iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
7424
7488
  return build.finish();
7425
7489
  }
7426
7490
  /**
@@ -7462,15 +7526,14 @@ class MatchDecorator {
7462
7526
  }
7463
7527
  }
7464
7528
  let ranges = [], m;
7529
+ let add = (from, to, deco) => ranges.push(deco.range(from, to));
7465
7530
  if (fromLine == toLine) {
7466
7531
  this.regexp.lastIndex = start - fromLine.from;
7467
- while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from) {
7468
- let pos = m.index + fromLine.from;
7469
- ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
7470
- }
7532
+ while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
7533
+ this.addMatch(m, view, m.index + fromLine.from, add);
7471
7534
  }
7472
7535
  else {
7473
- iterMatches(view.state.doc, this.regexp, start, end, (from, to, m) => ranges.push(this.getDeco(m, view, from).range(from, to)));
7536
+ iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
7474
7537
  }
7475
7538
  deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
7476
7539
  }
@@ -7480,7 +7543,7 @@ class MatchDecorator {
7480
7543
  }
7481
7544
 
7482
7545
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
7483
- const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7546
+ const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7484
7547
  const Names = {
7485
7548
  0: "null",
7486
7549
  7: "bell",
@@ -7497,6 +7560,9 @@ const Names = {
7497
7560
  8232: "line separator",
7498
7561
  8237: "left-to-right override",
7499
7562
  8238: "right-to-left override",
7563
+ 8294: "left-to-right isolate",
7564
+ 8295: "right-to-left isolate",
7565
+ 8297: "pop directional isolate",
7500
7566
  8233: "paragraph separator",
7501
7567
  65279: "zero width no-break space",
7502
7568
  65532: "object replacement"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.0.3",
3
+ "version": "6.1.2",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",