@codemirror/view 6.1.0 → 6.1.1

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,13 @@
1
+ ## 6.1.1 (2022-07-25)
2
+
3
+ ### Bug fixes
4
+
5
+ Make `highlightSpecialChars` replace directional isolate characters by default.
6
+
7
+ 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).
8
+
9
+ Fix a CSS issue that made it possible, when the gutters were wide enough, for them to overlap with the content.
10
+
1
11
  ## 6.1.0 (2022-07-19)
2
12
 
3
13
  ### New features
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
@@ -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) {
@@ -3634,9 +3665,8 @@ handlers.keydown = (view, event) => {
3634
3665
  else if (modifierCodes.indexOf(event.keyCode) < 0)
3635
3666
  view.inputState.lastEscPress = 0;
3636
3667
  };
3637
- let lastTouch = 0;
3638
3668
  handlers.touchstart = (view, e) => {
3639
- lastTouch = Date.now();
3669
+ view.inputState.lastTouchTime = Date.now();
3640
3670
  view.inputState.setSelectionOrigin("select.pointer");
3641
3671
  };
3642
3672
  handlers.touchmove = view => {
@@ -3644,7 +3674,7 @@ handlers.touchmove = view => {
3644
3674
  };
3645
3675
  handlers.mousedown = (view, event) => {
3646
3676
  view.observer.flush();
3647
- if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3677
+ if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
3648
3678
  return; // Ignore touch interaction
3649
3679
  let style = null;
3650
3680
  for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
@@ -3898,7 +3928,15 @@ function updateForFocusChange(view) {
3898
3928
  view.update([]);
3899
3929
  }, 10);
3900
3930
  }
3901
- handlers.focus = updateForFocusChange;
3931
+ handlers.focus = view => {
3932
+ view.inputState.lastFocusTime = Date.now();
3933
+ // When focusing reset the scroll position, move it back to where it was
3934
+ if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
3935
+ view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
3936
+ view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
3937
+ }
3938
+ updateForFocusChange(view);
3939
+ };
3902
3940
  handlers.blur = view => {
3903
3941
  view.observer.clearSelectionRange();
3904
3942
  updateForFocusChange(view);
@@ -5269,6 +5307,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5269
5307
  "&light .cm-specialChar": { color: "red" },
5270
5308
  "&dark .cm-specialChar": { color: "#f78" },
5271
5309
  ".cm-gutters": {
5310
+ flexShrink: 0,
5272
5311
  display: "flex",
5273
5312
  height: "100%",
5274
5313
  boxSizing: "border-box",
@@ -5525,15 +5564,28 @@ class DOMObserver {
5525
5564
  this.flush(false);
5526
5565
  }
5527
5566
  readSelectionRange() {
5528
- let { root } = this.view;
5567
+ let { view } = this;
5529
5568
  // The Selection object is broken in shadow roots in Safari. See
5530
5569
  // 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);
5570
+ let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
5571
+ safariSelectionRangeHack(this.view) || getSelection(view.root);
5533
5572
  if (!range || this.selectionRange.eq(range))
5534
5573
  return false;
5574
+ let local = hasSelection(this.dom, range);
5575
+ // Detect the situation where the browser has, on focus, moved the
5576
+ // selection to the start of the content element. Reset it to the
5577
+ // position from the editor state.
5578
+ if (local && !this.selectionChanged && this.selectionRange.focusNode &&
5579
+ view.inputState.lastFocusTime > Date.now() - 200 &&
5580
+ view.inputState.lastTouchTime < Date.now() - 300 &&
5581
+ atElementStart(this.dom, range)) {
5582
+ view.docView.updateSelection();
5583
+ return false;
5584
+ }
5535
5585
  this.selectionRange.setRange(range);
5536
- return this.selectionChanged = true;
5586
+ if (local)
5587
+ this.selectionChanged = true;
5588
+ return true;
5537
5589
  }
5538
5590
  setSelectionRange(anchor, head) {
5539
5591
  this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
@@ -5620,7 +5672,7 @@ class DOMObserver {
5620
5672
  this.delayedAndroidKey = null;
5621
5673
  this.delayedFlush = -1;
5622
5674
  if (!this.flush())
5623
- dispatchKey(this.view.contentDOM, key.key, key.keyCode);
5675
+ dispatchKey(this.dom, key.key, key.keyCode);
5624
5676
  });
5625
5677
  // Since backspace beforeinput is sometimes signalled spuriously,
5626
5678
  // Enter always takes precedence.
@@ -5674,6 +5726,7 @@ class DOMObserver {
5674
5726
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5675
5727
  if (from < 0 && !newSel)
5676
5728
  return;
5729
+ this.view.inputState.lastFocusTime = 0;
5677
5730
  this.selectionChanged = false;
5678
5731
  let startState = this.view.state;
5679
5732
  let handled = this.onChange(from, to, typeOver);
@@ -7495,7 +7548,7 @@ class MatchDecorator {
7495
7548
  }
7496
7549
 
7497
7550
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
7498
- const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7551
+ 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);
7499
7552
  const Names = {
7500
7553
  0: "null",
7501
7554
  7: "bell",
@@ -7512,6 +7565,9 @@ const Names = {
7512
7565
  8232: "line separator",
7513
7566
  8237: "left-to-right override",
7514
7567
  8238: "right-to-left override",
7568
+ 8294: "left-to-right isolate",
7569
+ 8295: "right-to-left isolate",
7570
+ 8297: "pop directional isolate",
7515
7571
  8233: "paragraph separator",
7516
7572
  65279: "zero width no-break space",
7517
7573
  65532: "object replacement"
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
@@ -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) {
@@ -3628,9 +3659,8 @@ handlers.keydown = (view, event) => {
3628
3659
  else if (modifierCodes.indexOf(event.keyCode) < 0)
3629
3660
  view.inputState.lastEscPress = 0;
3630
3661
  };
3631
- let lastTouch = 0;
3632
3662
  handlers.touchstart = (view, e) => {
3633
- lastTouch = Date.now();
3663
+ view.inputState.lastTouchTime = Date.now();
3634
3664
  view.inputState.setSelectionOrigin("select.pointer");
3635
3665
  };
3636
3666
  handlers.touchmove = view => {
@@ -3638,7 +3668,7 @@ handlers.touchmove = view => {
3638
3668
  };
3639
3669
  handlers.mousedown = (view, event) => {
3640
3670
  view.observer.flush();
3641
- if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3671
+ if (view.inputState.lastTouchTime > Date.now() - 2000 && getClickType(event) == 1)
3642
3672
  return; // Ignore touch interaction
3643
3673
  let style = null;
3644
3674
  for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
@@ -3892,7 +3922,15 @@ function updateForFocusChange(view) {
3892
3922
  view.update([]);
3893
3923
  }, 10);
3894
3924
  }
3895
- handlers.focus = updateForFocusChange;
3925
+ handlers.focus = view => {
3926
+ view.inputState.lastFocusTime = Date.now();
3927
+ // When focusing reset the scroll position, move it back to where it was
3928
+ if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
3929
+ view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
3930
+ view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
3931
+ }
3932
+ updateForFocusChange(view);
3933
+ };
3896
3934
  handlers.blur = view => {
3897
3935
  view.observer.clearSelectionRange();
3898
3936
  updateForFocusChange(view);
@@ -5262,6 +5300,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5262
5300
  "&light .cm-specialChar": { color: "red" },
5263
5301
  "&dark .cm-specialChar": { color: "#f78" },
5264
5302
  ".cm-gutters": {
5303
+ flexShrink: 0,
5265
5304
  display: "flex",
5266
5305
  height: "100%",
5267
5306
  boxSizing: "border-box",
@@ -5518,15 +5557,28 @@ class DOMObserver {
5518
5557
  this.flush(false);
5519
5558
  }
5520
5559
  readSelectionRange() {
5521
- let { root } = this.view;
5560
+ let { view } = this;
5522
5561
  // The Selection object is broken in shadow roots in Safari. See
5523
5562
  // 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);
5563
+ let range = browser.safari && view.root.nodeType == 11 && deepActiveElement() == this.dom &&
5564
+ safariSelectionRangeHack(this.view) || getSelection(view.root);
5526
5565
  if (!range || this.selectionRange.eq(range))
5527
5566
  return false;
5567
+ let local = hasSelection(this.dom, range);
5568
+ // Detect the situation where the browser has, on focus, moved the
5569
+ // selection to the start of the content element. Reset it to the
5570
+ // position from the editor state.
5571
+ if (local && !this.selectionChanged && this.selectionRange.focusNode &&
5572
+ view.inputState.lastFocusTime > Date.now() - 200 &&
5573
+ view.inputState.lastTouchTime < Date.now() - 300 &&
5574
+ atElementStart(this.dom, range)) {
5575
+ view.docView.updateSelection();
5576
+ return false;
5577
+ }
5528
5578
  this.selectionRange.setRange(range);
5529
- return this.selectionChanged = true;
5579
+ if (local)
5580
+ this.selectionChanged = true;
5581
+ return true;
5530
5582
  }
5531
5583
  setSelectionRange(anchor, head) {
5532
5584
  this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
@@ -5613,7 +5665,7 @@ class DOMObserver {
5613
5665
  this.delayedAndroidKey = null;
5614
5666
  this.delayedFlush = -1;
5615
5667
  if (!this.flush())
5616
- dispatchKey(this.view.contentDOM, key.key, key.keyCode);
5668
+ dispatchKey(this.dom, key.key, key.keyCode);
5617
5669
  });
5618
5670
  // Since backspace beforeinput is sometimes signalled spuriously,
5619
5671
  // Enter always takes precedence.
@@ -5667,6 +5719,7 @@ class DOMObserver {
5667
5719
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5668
5720
  if (from < 0 && !newSel)
5669
5721
  return;
5722
+ this.view.inputState.lastFocusTime = 0;
5670
5723
  this.selectionChanged = false;
5671
5724
  let startState = this.view.state;
5672
5725
  let handled = this.onChange(from, to, typeOver);
@@ -7488,7 +7541,7 @@ class MatchDecorator {
7488
7541
  }
7489
7542
 
7490
7543
  const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
7491
- const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7544
+ 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);
7492
7545
  const Names = {
7493
7546
  0: "null",
7494
7547
  7: "bell",
@@ -7505,6 +7558,9 @@ const Names = {
7505
7558
  8232: "line separator",
7506
7559
  8237: "left-to-right override",
7507
7560
  8238: "right-to-left override",
7561
+ 8294: "left-to-right isolate",
7562
+ 8295: "right-to-left isolate",
7563
+ 8297: "pop directional isolate",
7508
7564
  8233: "paragraph separator",
7509
7565
  65279: "zero width no-break space",
7510
7566
  65532: "object replacement"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.1.0",
3
+ "version": "6.1.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",