@codemirror/view 6.36.1 → 6.36.3

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,23 @@
1
+ ## 6.36.3 (2025-02-18)
2
+
3
+ ### Bug fixes
4
+
5
+ Make sure event handlers registered with `domEventHandlers` are not called during view updates, to avoid triggering nested update errors.
6
+
7
+ Don't include the window scrollbars in the space available for displaying tooltips.
8
+
9
+ Work around an issue with Chrome's `EditContext` that shows up when using autocompletion while composing with Samsung's virtual Android keyboard.
10
+
11
+ ## 6.36.2 (2025-01-09)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix an issue where some kinds of relayouts could put the editor in a state where it believed it wasn't in window, preventing relayout, though it in fact was.
16
+
17
+ Make sure macOS double-space-to-period conversions are properly suppressed.
18
+
19
+ Fix an issue where native selection changes, such as mobile spacebar-drag, weren't being picked up in edit context mode.
20
+
1
21
  ## 6.36.1 (2024-12-19)
2
22
 
3
23
  ### Bug fixes
package/README.md CHANGED
@@ -16,3 +16,22 @@ We aim to be an inclusive, welcoming community. To make that explicit,
16
16
  we have a [code of
17
17
  conduct](http://contributor-covenant.org/version/1/1/0/) that applies
18
18
  to communication around the project.
19
+
20
+ ## Usage
21
+
22
+ ```javascript
23
+ import {EditorView} from "@codemirror/view"
24
+ import {basicSetup} from "codemirror"
25
+
26
+ const view = new EditorView({
27
+ parent: document.querySelector("#some-node"),
28
+ doc: "Content text",
29
+ extensions: [basicSetup /* ... */]
30
+ })
31
+ ```
32
+
33
+ Add additional extensions, such as a [language
34
+ mode](https://codemirror.net/#languages), to configure the editor.
35
+ Call
36
+ [`view.dispatch`](https://codemirror.net/docs/ref/#view.EditorView.dispatch)
37
+ to update the editor's state.
package/dist/index.cjs CHANGED
@@ -3953,6 +3953,14 @@ function applyDOMChange(view, domChange) {
3953
3953
  // Heuristic to notice typing over a selected character
3954
3954
  change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
3955
3955
  }
3956
+ else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
3957
+ /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
3958
+ // Detect insert-period-on-double-space Mac and Android behavior,
3959
+ // and transform it into a regular space insert.
3960
+ if (newSel && change.insert.length == 2)
3961
+ newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
3962
+ change = { from: change.from, to: change.to, insert: state.Text.of([change.insert.toString().replace(".", " ")]) };
3963
+ }
3956
3964
  else if (change && change.from >= sel.from && change.to <= sel.to &&
3957
3965
  (change.from != sel.from || change.to != sel.to) &&
3958
3966
  (sel.to - sel.from) - (change.to - change.from) <= 4) {
@@ -3964,14 +3972,6 @@ function applyDOMChange(view, domChange) {
3964
3972
  insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
3965
3973
  };
3966
3974
  }
3967
- else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
3968
- /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
3969
- // Detect insert-period-on-double-space Mac and Android behavior,
3970
- // and transform it into a regular space insert.
3971
- if (newSel && change.insert.length == 2)
3972
- newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
3973
- change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
3974
- }
3975
3975
  else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
3976
3976
  change.insert.toString() == "\n " && view.lineWrapping) {
3977
3977
  // In Chrome, if you insert a space at the start of a wrapped
@@ -4209,7 +4209,10 @@ class InputState {
4209
4209
  return;
4210
4210
  if (event.type == "keydown" && this.keydown(event))
4211
4211
  return;
4212
- this.runHandlers(event.type, event);
4212
+ if (this.view.updateState != 0 /* UpdateState.Idle */)
4213
+ Promise.resolve().then(() => this.runHandlers(event.type, event));
4214
+ else
4215
+ this.runHandlers(event.type, event);
4213
4216
  }
4214
4217
  runHandlers(type, event) {
4215
4218
  let handlers = this.handlers[type];
@@ -5774,6 +5777,11 @@ function visiblePixelRange(dom, paddingTop) {
5774
5777
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
5775
5778
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
5776
5779
  }
5780
+ function inWindow(elt) {
5781
+ let rect = elt.getBoundingClientRect(), win = elt.ownerDocument.defaultView || window;
5782
+ return rect.left < win.innerWidth && rect.right > 0 &&
5783
+ rect.top < win.innerHeight && rect.bottom > 0;
5784
+ }
5777
5785
  function fullPixelRange(dom, paddingTop) {
5778
5786
  let rect = dom.getBoundingClientRect();
5779
5787
  return { left: 0, right: rect.right - rect.left,
@@ -5997,7 +6005,7 @@ class ViewState {
5997
6005
  if (inView)
5998
6006
  measureContent = true;
5999
6007
  }
6000
- if (!this.inView && !this.scrollTarget)
6008
+ if (!this.inView && !this.scrollTarget && !inWindow(view.dom))
6001
6009
  return 0;
6002
6010
  let contentWidth = domRect.width;
6003
6011
  if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
@@ -7187,7 +7195,7 @@ class EditContextManager {
7187
7195
  selectionEnd: this.toContextPos(view.state.selection.main.head)
7188
7196
  });
7189
7197
  this.handlers.textupdate = e => {
7190
- let { anchor } = view.state.selection.main;
7198
+ let main = view.state.selection.main, { anchor, head } = main;
7191
7199
  let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd);
7192
7200
  if (view.inputState.composing >= 0 && !this.composing)
7193
7201
  this.composing = { contextBase: e.updateRangeStart, editorBase: from, drifted: false };
@@ -7199,8 +7207,15 @@ class EditContextManager {
7199
7207
  else if (change.to == this.to && anchor > this.to)
7200
7208
  change.to = anchor;
7201
7209
  // Edit contexts sometimes fire empty changes
7202
- if (change.from == change.to && !change.insert.length)
7210
+ if (change.from == change.to && !change.insert.length) {
7211
+ let newSel = state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7212
+ if (!newSel.main.eq(main))
7213
+ view.dispatch({ selection: newSel, userEvent: "select" });
7203
7214
  return;
7215
+ }
7216
+ if ((browser.mac || browser.android) && change.from == head - 1 &&
7217
+ /^\. ?$/.test(e.text) && view.contentDOM.getAttribute("autocorrect") == "off")
7218
+ change = { from, to, insert: state.Text.of([e.text.replace(".", " ")]) };
7204
7219
  this.pendingContextChange = change;
7205
7220
  if (!view.state.readOnly) {
7206
7221
  let newLen = this.to - this.from + (change.to - change.from + change.insert.length);
@@ -7301,8 +7316,11 @@ class EditContextManager {
7301
7316
  return !abort;
7302
7317
  }
7303
7318
  update(update) {
7304
- let reverted = this.pendingContextChange;
7305
- if (this.composing && (this.composing.drifted || update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to)))) {
7319
+ let reverted = this.pendingContextChange, startSel = update.startState.selection.main;
7320
+ if (this.composing &&
7321
+ (this.composing.drifted ||
7322
+ (!update.changes.touchesRange(startSel.from, startSel.to) &&
7323
+ update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to))))) {
7306
7324
  this.composing.drifted = true;
7307
7325
  this.composing.editorBase = update.changes.mapPos(this.composing.editorBase);
7308
7326
  }
@@ -9788,8 +9806,8 @@ function tooltips(config = {}) {
9788
9806
  return tooltipConfig.of(config);
9789
9807
  }
9790
9808
  function windowSpace(view) {
9791
- let { win } = view;
9792
- return { top: 0, left: 0, bottom: win.innerHeight, right: win.innerWidth };
9809
+ let docElt = view.dom.ownerDocument.documentElement;
9810
+ return { top: 0, left: 0, bottom: docElt.clientHeight, right: docElt.clientWidth };
9793
9811
  }
9794
9812
  const tooltipConfig = state.Facet.define({
9795
9813
  combine: values => {
package/dist/index.d.cts CHANGED
@@ -1839,9 +1839,10 @@ declare function tooltips(config?: {
1839
1839
  /**
1840
1840
  By default, when figuring out whether there is room for a
1841
1841
  tooltip at a given position, the extension considers the entire
1842
- space between 0,0 and `innerWidth`,`innerHeight` to be available
1843
- for showing tooltips. You can provide a function here that
1844
- returns an alternative rectangle.
1842
+ space between 0,0 and
1843
+ `documentElement.clientWidth`/`clientHeight` to be available for
1844
+ showing tooltips. You can provide a function here that returns
1845
+ an alternative rectangle.
1845
1846
  */
1846
1847
  tooltipSpace?: (view: EditorView) => Rect;
1847
1848
  }): Extension;
package/dist/index.d.ts CHANGED
@@ -1839,9 +1839,10 @@ declare function tooltips(config?: {
1839
1839
  /**
1840
1840
  By default, when figuring out whether there is room for a
1841
1841
  tooltip at a given position, the extension considers the entire
1842
- space between 0,0 and `innerWidth`,`innerHeight` to be available
1843
- for showing tooltips. You can provide a function here that
1844
- returns an alternative rectangle.
1842
+ space between 0,0 and
1843
+ `documentElement.clientWidth`/`clientHeight` to be available for
1844
+ showing tooltips. You can provide a function here that returns
1845
+ an alternative rectangle.
1845
1846
  */
1846
1847
  tooltipSpace?: (view: EditorView) => Rect;
1847
1848
  }): Extension;
package/dist/index.js CHANGED
@@ -3949,6 +3949,14 @@ function applyDOMChange(view, domChange) {
3949
3949
  // Heuristic to notice typing over a selected character
3950
3950
  change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
3951
3951
  }
3952
+ else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
3953
+ /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
3954
+ // Detect insert-period-on-double-space Mac and Android behavior,
3955
+ // and transform it into a regular space insert.
3956
+ if (newSel && change.insert.length == 2)
3957
+ newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
3958
+ change = { from: change.from, to: change.to, insert: Text.of([change.insert.toString().replace(".", " ")]) };
3959
+ }
3952
3960
  else if (change && change.from >= sel.from && change.to <= sel.to &&
3953
3961
  (change.from != sel.from || change.to != sel.to) &&
3954
3962
  (sel.to - sel.from) - (change.to - change.from) <= 4) {
@@ -3960,14 +3968,6 @@ function applyDOMChange(view, domChange) {
3960
3968
  insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
3961
3969
  };
3962
3970
  }
3963
- else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
3964
- /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
3965
- // Detect insert-period-on-double-space Mac and Android behavior,
3966
- // and transform it into a regular space insert.
3967
- if (newSel && change.insert.length == 2)
3968
- newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
3969
- change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
3970
- }
3971
3971
  else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
3972
3972
  change.insert.toString() == "\n " && view.lineWrapping) {
3973
3973
  // In Chrome, if you insert a space at the start of a wrapped
@@ -4205,7 +4205,10 @@ class InputState {
4205
4205
  return;
4206
4206
  if (event.type == "keydown" && this.keydown(event))
4207
4207
  return;
4208
- this.runHandlers(event.type, event);
4208
+ if (this.view.updateState != 0 /* UpdateState.Idle */)
4209
+ Promise.resolve().then(() => this.runHandlers(event.type, event));
4210
+ else
4211
+ this.runHandlers(event.type, event);
4209
4212
  }
4210
4213
  runHandlers(type, event) {
4211
4214
  let handlers = this.handlers[type];
@@ -5769,6 +5772,11 @@ function visiblePixelRange(dom, paddingTop) {
5769
5772
  return { left: left - rect.left, right: Math.max(left, right) - rect.left,
5770
5773
  top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
5771
5774
  }
5775
+ function inWindow(elt) {
5776
+ let rect = elt.getBoundingClientRect(), win = elt.ownerDocument.defaultView || window;
5777
+ return rect.left < win.innerWidth && rect.right > 0 &&
5778
+ rect.top < win.innerHeight && rect.bottom > 0;
5779
+ }
5772
5780
  function fullPixelRange(dom, paddingTop) {
5773
5781
  let rect = dom.getBoundingClientRect();
5774
5782
  return { left: 0, right: rect.right - rect.left,
@@ -5992,7 +6000,7 @@ class ViewState {
5992
6000
  if (inView)
5993
6001
  measureContent = true;
5994
6002
  }
5995
- if (!this.inView && !this.scrollTarget)
6003
+ if (!this.inView && !this.scrollTarget && !inWindow(view.dom))
5996
6004
  return 0;
5997
6005
  let contentWidth = domRect.width;
5998
6006
  if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
@@ -7182,7 +7190,7 @@ class EditContextManager {
7182
7190
  selectionEnd: this.toContextPos(view.state.selection.main.head)
7183
7191
  });
7184
7192
  this.handlers.textupdate = e => {
7185
- let { anchor } = view.state.selection.main;
7193
+ let main = view.state.selection.main, { anchor, head } = main;
7186
7194
  let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd);
7187
7195
  if (view.inputState.composing >= 0 && !this.composing)
7188
7196
  this.composing = { contextBase: e.updateRangeStart, editorBase: from, drifted: false };
@@ -7194,8 +7202,15 @@ class EditContextManager {
7194
7202
  else if (change.to == this.to && anchor > this.to)
7195
7203
  change.to = anchor;
7196
7204
  // Edit contexts sometimes fire empty changes
7197
- if (change.from == change.to && !change.insert.length)
7205
+ if (change.from == change.to && !change.insert.length) {
7206
+ let newSel = EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7207
+ if (!newSel.main.eq(main))
7208
+ view.dispatch({ selection: newSel, userEvent: "select" });
7198
7209
  return;
7210
+ }
7211
+ if ((browser.mac || browser.android) && change.from == head - 1 &&
7212
+ /^\. ?$/.test(e.text) && view.contentDOM.getAttribute("autocorrect") == "off")
7213
+ change = { from, to, insert: Text.of([e.text.replace(".", " ")]) };
7199
7214
  this.pendingContextChange = change;
7200
7215
  if (!view.state.readOnly) {
7201
7216
  let newLen = this.to - this.from + (change.to - change.from + change.insert.length);
@@ -7296,8 +7311,11 @@ class EditContextManager {
7296
7311
  return !abort;
7297
7312
  }
7298
7313
  update(update) {
7299
- let reverted = this.pendingContextChange;
7300
- if (this.composing && (this.composing.drifted || update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to)))) {
7314
+ let reverted = this.pendingContextChange, startSel = update.startState.selection.main;
7315
+ if (this.composing &&
7316
+ (this.composing.drifted ||
7317
+ (!update.changes.touchesRange(startSel.from, startSel.to) &&
7318
+ update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to))))) {
7301
7319
  this.composing.drifted = true;
7302
7320
  this.composing.editorBase = update.changes.mapPos(this.composing.editorBase);
7303
7321
  }
@@ -9783,8 +9801,8 @@ function tooltips(config = {}) {
9783
9801
  return tooltipConfig.of(config);
9784
9802
  }
9785
9803
  function windowSpace(view) {
9786
- let { win } = view;
9787
- return { top: 0, left: 0, bottom: win.innerHeight, right: win.innerWidth };
9804
+ let docElt = view.dom.ownerDocument.documentElement;
9805
+ return { top: 0, left: 0, bottom: docElt.clientHeight, right: docElt.clientWidth };
9788
9806
  }
9789
9807
  const tooltipConfig = /*@__PURE__*/Facet.define({
9790
9808
  combine: values => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.36.1",
3
+ "version": "6.36.3",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",