@codemirror/view 6.37.2 → 6.38.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,17 @@
1
+ ## 6.38.1 (2025-07-15)
2
+
3
+ ### Bug fixes
4
+
5
+ Make the keymap not dispatch Alt key combos on macOS by key code, because those are generally used to type special characters.
6
+
7
+ Fix a layout bug that could occur with very narrow editors.
8
+
9
+ ## 6.38.0 (2025-06-27)
10
+
11
+ ### New features
12
+
13
+ Gutters can now specify that they should be displayed after the content (which would be to the right in a left-to-right layout).
14
+
1
15
  ## 6.37.2 (2025-06-12)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -5095,7 +5095,7 @@ class HeightOracle {
5095
5095
  heightForLine(length) {
5096
5096
  if (!this.lineWrapping)
5097
5097
  return this.lineHeight;
5098
- let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / (this.lineLength - 5)));
5098
+ let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / Math.max(1, this.lineLength - 5)));
5099
5099
  return lines * this.lineHeight;
5100
5100
  }
5101
5101
  setDoc(doc) { this.doc = doc; return this; }
@@ -6066,7 +6066,7 @@ class ViewState {
6066
6066
  refresh = true;
6067
6067
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
6068
6068
  let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
6069
- refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, contentWidth / charWidth, lineHeights);
6069
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, Math.max(5, contentWidth / charWidth), lineHeights);
6070
6070
  if (refresh) {
6071
6071
  view.docView.minWidth = 0;
6072
6072
  result |= 16 /* UpdateFlag.Geometry */;
@@ -6591,13 +6591,16 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
6591
6591
  display: "flex",
6592
6592
  height: "100%",
6593
6593
  boxSizing: "border-box",
6594
- insetInlineStart: 0,
6595
- zIndex: 200
6594
+ zIndex: 200,
6596
6595
  },
6596
+ ".cm-gutters-before": { insetInlineStart: 0 },
6597
+ ".cm-gutters-after": { insetInlineEnd: 0 },
6597
6598
  "&light .cm-gutters": {
6598
6599
  backgroundColor: "#f5f5f5",
6599
6600
  color: "#6c6c6c",
6600
- borderRight: "1px solid #ddd"
6601
+ border: "0px solid #ddd",
6602
+ "&.cm-gutters-before": { borderRightWidth: "1px" },
6603
+ "&.cm-gutters-after": { borderLeftWidth: "1px" },
6601
6604
  },
6602
6605
  "&dark .cm-gutters": {
6603
6606
  backgroundColor: "#333338",
@@ -6788,7 +6791,7 @@ class DOMObserver {
6788
6791
  else
6789
6792
  this.flush();
6790
6793
  });
6791
- if (window.EditContext && view.constructor.EDIT_CONTEXT !== false &&
6794
+ if (window.EditContext && browser.android && view.constructor.EDIT_CONTEXT !== false &&
6792
6795
  // Chrome <126 doesn't support inverted selections in edit context (#1392)
6793
6796
  !(browser.chrome && browser.chrome_version < 126)) {
6794
6797
  this.editContext = new EditContextManager(view);
@@ -8799,6 +8802,8 @@ function runHandlers(map, event, view, scope) {
8799
8802
  else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
8800
8803
  // Ctrl-Alt may be used for AltGr on Windows
8801
8804
  !(browser.windows && event.ctrlKey && event.altKey) &&
8805
+ // Alt-combinations on macOS tend to be typed characters
8806
+ !(browser.mac && event.altKey && !event.ctrlKey) &&
8802
8807
  (baseName = w3cKeyname.base[event.keyCode]) && baseName != name) {
8803
8808
  if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
8804
8809
  handled = true;
@@ -10868,7 +10873,8 @@ const defaults = {
10868
10873
  lineMarkerChange: null,
10869
10874
  initialSpacer: null,
10870
10875
  updateSpacer: null,
10871
- domEventHandlers: {}
10876
+ domEventHandlers: {},
10877
+ side: "before"
10872
10878
  };
10873
10879
  const activeGutters = state.Facet.define();
10874
10880
  /**
@@ -10902,15 +10908,20 @@ function gutters(config) {
10902
10908
  const gutterView = ViewPlugin.fromClass(class {
10903
10909
  constructor(view) {
10904
10910
  this.view = view;
10911
+ this.domAfter = null;
10905
10912
  this.prevViewport = view.viewport;
10906
10913
  this.dom = document.createElement("div");
10907
- this.dom.className = "cm-gutters";
10914
+ this.dom.className = "cm-gutters cm-gutters-before";
10908
10915
  this.dom.setAttribute("aria-hidden", "true");
10909
10916
  this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10910
10917
  this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
10911
- for (let gutter of this.gutters)
10912
- this.dom.appendChild(gutter.dom);
10913
10918
  this.fixed = !view.state.facet(unfixGutters);
10919
+ for (let gutter of this.gutters) {
10920
+ if (gutter.config.side == "after")
10921
+ this.getDOMAfter().appendChild(gutter.dom);
10922
+ else
10923
+ this.dom.appendChild(gutter.dom);
10924
+ }
10914
10925
  if (this.fixed) {
10915
10926
  // FIXME IE11 fallback, which doesn't support position: sticky,
10916
10927
  // by using position: relative + event handlers that realign the
@@ -10920,6 +10931,17 @@ const gutterView = ViewPlugin.fromClass(class {
10920
10931
  this.syncGutters(false);
10921
10932
  view.scrollDOM.insertBefore(this.dom, view.contentDOM);
10922
10933
  }
10934
+ getDOMAfter() {
10935
+ if (!this.domAfter) {
10936
+ this.domAfter = document.createElement("div");
10937
+ this.domAfter.className = "cm-gutters cm-gutters-after";
10938
+ this.domAfter.setAttribute("aria-hidden", "true");
10939
+ this.domAfter.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10940
+ this.domAfter.style.position = this.fixed ? "sticky" : "";
10941
+ this.view.scrollDOM.appendChild(this.domAfter);
10942
+ }
10943
+ return this.domAfter;
10944
+ }
10923
10945
  update(update) {
10924
10946
  if (this.updateGutters(update)) {
10925
10947
  // Detach during sync when the viewport changed significantly
@@ -10930,18 +10952,26 @@ const gutterView = ViewPlugin.fromClass(class {
10930
10952
  this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
10931
10953
  }
10932
10954
  if (update.geometryChanged) {
10933
- this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10955
+ let min = (this.view.contentHeight / this.view.scaleY) + "px";
10956
+ this.dom.style.minHeight = min;
10957
+ if (this.domAfter)
10958
+ this.domAfter.style.minHeight = min;
10934
10959
  }
10935
10960
  if (this.view.state.facet(unfixGutters) != !this.fixed) {
10936
10961
  this.fixed = !this.fixed;
10937
10962
  this.dom.style.position = this.fixed ? "sticky" : "";
10963
+ if (this.domAfter)
10964
+ this.domAfter.style.position = this.fixed ? "sticky" : "";
10938
10965
  }
10939
10966
  this.prevViewport = update.view.viewport;
10940
10967
  }
10941
10968
  syncGutters(detach) {
10942
10969
  let after = this.dom.nextSibling;
10943
- if (detach)
10970
+ if (detach) {
10944
10971
  this.dom.remove();
10972
+ if (this.domAfter)
10973
+ this.domAfter.remove();
10974
+ }
10945
10975
  let lineClasses = state.RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
10946
10976
  let classSet = [];
10947
10977
  let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
@@ -10975,8 +11005,11 @@ const gutterView = ViewPlugin.fromClass(class {
10975
11005
  }
10976
11006
  for (let cx of contexts)
10977
11007
  cx.finish();
10978
- if (detach)
11008
+ if (detach) {
10979
11009
  this.view.scrollDOM.insertBefore(this.dom, after);
11010
+ if (this.domAfter)
11011
+ this.view.scrollDOM.appendChild(this.domAfter);
11012
+ }
10980
11013
  }
10981
11014
  updateGutters(update) {
10982
11015
  let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
@@ -11005,8 +11038,12 @@ const gutterView = ViewPlugin.fromClass(class {
11005
11038
  if (gutters.indexOf(g) < 0)
11006
11039
  g.destroy();
11007
11040
  }
11008
- for (let g of gutters)
11009
- this.dom.appendChild(g.dom);
11041
+ for (let g of gutters) {
11042
+ if (g.config.side == "after")
11043
+ this.getDOMAfter().appendChild(g.dom);
11044
+ else
11045
+ this.dom.appendChild(g.dom);
11046
+ }
11010
11047
  this.gutters = gutters;
11011
11048
  }
11012
11049
  return change;
@@ -11015,15 +11052,18 @@ const gutterView = ViewPlugin.fromClass(class {
11015
11052
  for (let view of this.gutters)
11016
11053
  view.destroy();
11017
11054
  this.dom.remove();
11055
+ if (this.domAfter)
11056
+ this.domAfter.remove();
11018
11057
  }
11019
11058
  }, {
11020
11059
  provide: plugin => EditorView.scrollMargins.of(view => {
11021
11060
  let value = view.plugin(plugin);
11022
11061
  if (!value || value.gutters.length == 0 || !value.fixed)
11023
11062
  return null;
11063
+ let before = value.dom.offsetWidth * view.scaleX, after = value.domAfter ? value.domAfter.offsetWidth * view.scaleX : 0;
11024
11064
  return view.textDirection == exports.Direction.LTR
11025
- ? { left: value.dom.offsetWidth * view.scaleX }
11026
- : { right: value.dom.offsetWidth * view.scaleX };
11065
+ ? { left: before, right: after }
11066
+ : { right: before, left: after };
11027
11067
  })
11028
11068
  });
11029
11069
  function asArray(val) { return (Array.isArray(val) ? val : [val]); }
@@ -11265,7 +11305,8 @@ const lineNumberGutter = activeGutters.compute([lineNumberConfig], state => ({
11265
11305
  let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
11266
11306
  return max == spacer.number ? spacer : new NumberMarker(max);
11267
11307
  },
11268
- domEventHandlers: state.facet(lineNumberConfig).domEventHandlers
11308
+ domEventHandlers: state.facet(lineNumberConfig).domEventHandlers,
11309
+ side: "before"
11269
11310
  }));
11270
11311
  /**
11271
11312
  Create a line number gutter extension.
package/dist/index.d.cts CHANGED
@@ -2235,6 +2235,12 @@ interface GutterConfig {
2235
2235
  Supply event handlers for DOM events on this gutter.
2236
2236
  */
2237
2237
  domEventHandlers?: Handlers;
2238
+ /**
2239
+ By default, gutters are shown horizontally before the editor
2240
+ content (to the left in a left-to-right layout). Set this to
2241
+ `"after"` to show a gutter on the other side of the content.
2242
+ */
2243
+ side?: "before" | "after";
2238
2244
  }
2239
2245
  /**
2240
2246
  Define an editor gutter. The order in which the gutters appear is
package/dist/index.d.ts CHANGED
@@ -2235,6 +2235,12 @@ interface GutterConfig {
2235
2235
  Supply event handlers for DOM events on this gutter.
2236
2236
  */
2237
2237
  domEventHandlers?: Handlers;
2238
+ /**
2239
+ By default, gutters are shown horizontally before the editor
2240
+ content (to the left in a left-to-right layout). Set this to
2241
+ `"after"` to show a gutter on the other side of the content.
2242
+ */
2243
+ side?: "before" | "after";
2238
2244
  }
2239
2245
  /**
2240
2246
  Define an editor gutter. The order in which the gutters appear is
package/dist/index.js CHANGED
@@ -5091,7 +5091,7 @@ class HeightOracle {
5091
5091
  heightForLine(length) {
5092
5092
  if (!this.lineWrapping)
5093
5093
  return this.lineHeight;
5094
- let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / (this.lineLength - 5)));
5094
+ let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / Math.max(1, this.lineLength - 5)));
5095
5095
  return lines * this.lineHeight;
5096
5096
  }
5097
5097
  setDoc(doc) { this.doc = doc; return this; }
@@ -6061,7 +6061,7 @@ class ViewState {
6061
6061
  refresh = true;
6062
6062
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
6063
6063
  let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
6064
- refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, contentWidth / charWidth, lineHeights);
6064
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, Math.max(5, contentWidth / charWidth), lineHeights);
6065
6065
  if (refresh) {
6066
6066
  view.docView.minWidth = 0;
6067
6067
  result |= 16 /* UpdateFlag.Geometry */;
@@ -6586,13 +6586,16 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6586
6586
  display: "flex",
6587
6587
  height: "100%",
6588
6588
  boxSizing: "border-box",
6589
- insetInlineStart: 0,
6590
- zIndex: 200
6589
+ zIndex: 200,
6591
6590
  },
6591
+ ".cm-gutters-before": { insetInlineStart: 0 },
6592
+ ".cm-gutters-after": { insetInlineEnd: 0 },
6592
6593
  "&light .cm-gutters": {
6593
6594
  backgroundColor: "#f5f5f5",
6594
6595
  color: "#6c6c6c",
6595
- borderRight: "1px solid #ddd"
6596
+ border: "0px solid #ddd",
6597
+ "&.cm-gutters-before": { borderRightWidth: "1px" },
6598
+ "&.cm-gutters-after": { borderLeftWidth: "1px" },
6596
6599
  },
6597
6600
  "&dark .cm-gutters": {
6598
6601
  backgroundColor: "#333338",
@@ -6783,7 +6786,7 @@ class DOMObserver {
6783
6786
  else
6784
6787
  this.flush();
6785
6788
  });
6786
- if (window.EditContext && view.constructor.EDIT_CONTEXT !== false &&
6789
+ if (window.EditContext && browser.android && view.constructor.EDIT_CONTEXT !== false &&
6787
6790
  // Chrome <126 doesn't support inverted selections in edit context (#1392)
6788
6791
  !(browser.chrome && browser.chrome_version < 126)) {
6789
6792
  this.editContext = new EditContextManager(view);
@@ -8794,6 +8797,8 @@ function runHandlers(map, event, view, scope) {
8794
8797
  else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
8795
8798
  // Ctrl-Alt may be used for AltGr on Windows
8796
8799
  !(browser.windows && event.ctrlKey && event.altKey) &&
8800
+ // Alt-combinations on macOS tend to be typed characters
8801
+ !(browser.mac && event.altKey && !event.ctrlKey) &&
8797
8802
  (baseName = base[event.keyCode]) && baseName != name) {
8798
8803
  if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
8799
8804
  handled = true;
@@ -10863,7 +10868,8 @@ const defaults = {
10863
10868
  lineMarkerChange: null,
10864
10869
  initialSpacer: null,
10865
10870
  updateSpacer: null,
10866
- domEventHandlers: {}
10871
+ domEventHandlers: {},
10872
+ side: "before"
10867
10873
  };
10868
10874
  const activeGutters = /*@__PURE__*/Facet.define();
10869
10875
  /**
@@ -10897,15 +10903,20 @@ function gutters(config) {
10897
10903
  const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
10898
10904
  constructor(view) {
10899
10905
  this.view = view;
10906
+ this.domAfter = null;
10900
10907
  this.prevViewport = view.viewport;
10901
10908
  this.dom = document.createElement("div");
10902
- this.dom.className = "cm-gutters";
10909
+ this.dom.className = "cm-gutters cm-gutters-before";
10903
10910
  this.dom.setAttribute("aria-hidden", "true");
10904
10911
  this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10905
10912
  this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
10906
- for (let gutter of this.gutters)
10907
- this.dom.appendChild(gutter.dom);
10908
10913
  this.fixed = !view.state.facet(unfixGutters);
10914
+ for (let gutter of this.gutters) {
10915
+ if (gutter.config.side == "after")
10916
+ this.getDOMAfter().appendChild(gutter.dom);
10917
+ else
10918
+ this.dom.appendChild(gutter.dom);
10919
+ }
10909
10920
  if (this.fixed) {
10910
10921
  // FIXME IE11 fallback, which doesn't support position: sticky,
10911
10922
  // by using position: relative + event handlers that realign the
@@ -10915,6 +10926,17 @@ const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
10915
10926
  this.syncGutters(false);
10916
10927
  view.scrollDOM.insertBefore(this.dom, view.contentDOM);
10917
10928
  }
10929
+ getDOMAfter() {
10930
+ if (!this.domAfter) {
10931
+ this.domAfter = document.createElement("div");
10932
+ this.domAfter.className = "cm-gutters cm-gutters-after";
10933
+ this.domAfter.setAttribute("aria-hidden", "true");
10934
+ this.domAfter.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10935
+ this.domAfter.style.position = this.fixed ? "sticky" : "";
10936
+ this.view.scrollDOM.appendChild(this.domAfter);
10937
+ }
10938
+ return this.domAfter;
10939
+ }
10918
10940
  update(update) {
10919
10941
  if (this.updateGutters(update)) {
10920
10942
  // Detach during sync when the viewport changed significantly
@@ -10925,18 +10947,26 @@ const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
10925
10947
  this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
10926
10948
  }
10927
10949
  if (update.geometryChanged) {
10928
- this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
10950
+ let min = (this.view.contentHeight / this.view.scaleY) + "px";
10951
+ this.dom.style.minHeight = min;
10952
+ if (this.domAfter)
10953
+ this.domAfter.style.minHeight = min;
10929
10954
  }
10930
10955
  if (this.view.state.facet(unfixGutters) != !this.fixed) {
10931
10956
  this.fixed = !this.fixed;
10932
10957
  this.dom.style.position = this.fixed ? "sticky" : "";
10958
+ if (this.domAfter)
10959
+ this.domAfter.style.position = this.fixed ? "sticky" : "";
10933
10960
  }
10934
10961
  this.prevViewport = update.view.viewport;
10935
10962
  }
10936
10963
  syncGutters(detach) {
10937
10964
  let after = this.dom.nextSibling;
10938
- if (detach)
10965
+ if (detach) {
10939
10966
  this.dom.remove();
10967
+ if (this.domAfter)
10968
+ this.domAfter.remove();
10969
+ }
10940
10970
  let lineClasses = RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
10941
10971
  let classSet = [];
10942
10972
  let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
@@ -10970,8 +11000,11 @@ const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
10970
11000
  }
10971
11001
  for (let cx of contexts)
10972
11002
  cx.finish();
10973
- if (detach)
11003
+ if (detach) {
10974
11004
  this.view.scrollDOM.insertBefore(this.dom, after);
11005
+ if (this.domAfter)
11006
+ this.view.scrollDOM.appendChild(this.domAfter);
11007
+ }
10975
11008
  }
10976
11009
  updateGutters(update) {
10977
11010
  let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
@@ -11000,8 +11033,12 @@ const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
11000
11033
  if (gutters.indexOf(g) < 0)
11001
11034
  g.destroy();
11002
11035
  }
11003
- for (let g of gutters)
11004
- this.dom.appendChild(g.dom);
11036
+ for (let g of gutters) {
11037
+ if (g.config.side == "after")
11038
+ this.getDOMAfter().appendChild(g.dom);
11039
+ else
11040
+ this.dom.appendChild(g.dom);
11041
+ }
11005
11042
  this.gutters = gutters;
11006
11043
  }
11007
11044
  return change;
@@ -11010,15 +11047,18 @@ const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
11010
11047
  for (let view of this.gutters)
11011
11048
  view.destroy();
11012
11049
  this.dom.remove();
11050
+ if (this.domAfter)
11051
+ this.domAfter.remove();
11013
11052
  }
11014
11053
  }, {
11015
11054
  provide: plugin => EditorView.scrollMargins.of(view => {
11016
11055
  let value = view.plugin(plugin);
11017
11056
  if (!value || value.gutters.length == 0 || !value.fixed)
11018
11057
  return null;
11058
+ let before = value.dom.offsetWidth * view.scaleX, after = value.domAfter ? value.domAfter.offsetWidth * view.scaleX : 0;
11019
11059
  return view.textDirection == Direction.LTR
11020
- ? { left: value.dom.offsetWidth * view.scaleX }
11021
- : { right: value.dom.offsetWidth * view.scaleX };
11060
+ ? { left: before, right: after }
11061
+ : { right: before, left: after };
11022
11062
  })
11023
11063
  });
11024
11064
  function asArray(val) { return (Array.isArray(val) ? val : [val]); }
@@ -11260,7 +11300,8 @@ const lineNumberGutter = /*@__PURE__*/activeGutters.compute([lineNumberConfig],
11260
11300
  let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
11261
11301
  return max == spacer.number ? spacer : new NumberMarker(max);
11262
11302
  },
11263
- domEventHandlers: state.facet(lineNumberConfig).domEventHandlers
11303
+ domEventHandlers: state.facet(lineNumberConfig).domEventHandlers,
11304
+ side: "before"
11264
11305
  }));
11265
11306
  /**
11266
11307
  Create a line number gutter extension.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.37.2",
3
+ "version": "6.38.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",