@codemirror/view 6.25.1 → 6.26.0

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,25 @@
1
+ ## 6.26.0 (2024-03-14)
2
+
3
+ ### Bug fixes
4
+
5
+ Avoid the editor getting confused when iOS autocorrects on pressing Enter and does the correction and the break insertion in two different events.
6
+
7
+ Fix the pasting of copied URIs in iOS.
8
+
9
+ Fix a bug where a scaled editor could keep performing unnecessary updates due to tiny differences in geometry values returned by the browser.
10
+
11
+ Fix a bug where, on iOS with a physical keyboard, the modifiers for some keys weren't being passed to the keymaps.
12
+
13
+ Work around the fact that Mobile Safari makes DOM changes before firing a key event when typing ctrl-d on an external keyboard.
14
+
15
+ Fix an issue where some commands didn't properly scroll the cursor into view on Mobile Safari.
16
+
17
+ Re-measure the document when print settings are changed on Chrome.
18
+
19
+ ### New features
20
+
21
+ The `EditorView.scrollHandler` facet can be used to override or extend the behavior of the editor when things are scrolled into view.
22
+
1
23
  ## 6.25.1 (2024-03-06)
2
24
 
3
25
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -93,6 +93,12 @@ function flattenRect(rect, left) {
93
93
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
94
94
  }
95
95
  function windowRect(win) {
96
+ let vp = win.visualViewport;
97
+ if (vp)
98
+ return {
99
+ left: 0, right: vp.width,
100
+ top: 0, bottom: vp.height
101
+ };
96
102
  return { left: 0, right: win.innerWidth,
97
103
  top: 0, bottom: win.innerHeight };
98
104
  }
@@ -282,8 +288,10 @@ function textRange(node, from, to = from) {
282
288
  range.setStart(node, from);
283
289
  return range;
284
290
  }
285
- function dispatchKey(elt, name, code) {
291
+ function dispatchKey(elt, name, code, mods) {
286
292
  let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
293
+ if (mods)
294
+ ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
287
295
  let down = new KeyboardEvent("keydown", options);
288
296
  down.synthetic = true;
289
297
  elt.dispatchEvent(down);
@@ -2290,6 +2298,7 @@ const perLineTextDirection = state.Facet.define({
2290
2298
  const nativeSelectionHidden = state.Facet.define({
2291
2299
  combine: values => values.some(x => x)
2292
2300
  });
2301
+ const scrollHandler = state.Facet.define();
2293
2302
  class ScrollTarget {
2294
2303
  constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
2295
2304
  // This data structure is abused to also store precise scroll
@@ -3188,6 +3197,15 @@ class DocView extends ContentView {
3188
3197
  this.view.scrollDOM.scrollLeft = target.xMargin;
3189
3198
  return;
3190
3199
  }
3200
+ for (let handler of this.view.state.facet(scrollHandler)) {
3201
+ try {
3202
+ if (handler(this.view, target.range, target))
3203
+ return true;
3204
+ }
3205
+ catch (e) {
3206
+ logException(this.view.state, e, "scroll handler");
3207
+ }
3208
+ }
3191
3209
  let { range } = target;
3192
3210
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3193
3211
  if (!rect)
@@ -3829,12 +3847,15 @@ class InputState {
3829
3847
  this.view.observer.forceFlush();
3830
3848
  return false;
3831
3849
  }
3832
- flushIOSKey() {
3850
+ flushIOSKey(change) {
3833
3851
  let key = this.pendingIOSKey;
3834
3852
  if (!key)
3835
3853
  return false;
3854
+ // This looks like an autocorrection before Enter
3855
+ if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
3856
+ return false;
3836
3857
  this.pendingIOSKey = undefined;
3837
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
3858
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
3838
3859
  }
3839
3860
  ignoreDuringComposition(event) {
3840
3861
  if (!/^key/.test(event.type))
@@ -4343,7 +4364,7 @@ handlers.paste = (view, event) => {
4343
4364
  view.observer.flush();
4344
4365
  let data = brokenClipboardAPI ? null : event.clipboardData;
4345
4366
  if (data) {
4346
- doPaste(view, data.getData("text/plain") || data.getData("text/uri-text"));
4367
+ doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
4347
4368
  return true;
4348
4369
  }
4349
4370
  else {
@@ -4513,6 +4534,12 @@ handlers.beforeinput = (view, event) => {
4513
4534
  }, 100);
4514
4535
  }
4515
4536
  }
4537
+ if (browser.ios && event.inputType == "deleteContentForward") {
4538
+ // For some reason, DOM changes (and beforeinput) happen _before_
4539
+ // the key event for ctrl-d on iOS when using an external
4540
+ // keyboard.
4541
+ view.observer.flushSoon();
4542
+ }
4516
4543
  return false;
4517
4544
  };
4518
4545
  const appliedFirefoxHack = new Set;
@@ -5447,7 +5474,8 @@ class ViewState {
5447
5474
  let result = 0, bias = 0;
5448
5475
  if (domRect.width && domRect.height) {
5449
5476
  let { scaleX, scaleY } = getScale(dom, domRect);
5450
- if (this.scaleX != scaleX || this.scaleY != scaleY) {
5477
+ if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
5478
+ scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
5451
5479
  this.scaleX = scaleX;
5452
5480
  this.scaleY = scaleY;
5453
5481
  result |= 8 /* UpdateFlag.Geometry */;
@@ -6328,7 +6356,7 @@ function applyDOMChange(view, domChange) {
6328
6356
  change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
6329
6357
  }
6330
6358
  if (change) {
6331
- if (browser.ios && view.inputState.flushIOSKey())
6359
+ if (browser.ios && view.inputState.flushIOSKey(change))
6332
6360
  return true;
6333
6361
  // Android browsers don't fire reasonable key events for enter,
6334
6362
  // backspace, or delete. So this detects changes that look like
@@ -6519,6 +6547,7 @@ class DOMObserver {
6519
6547
  this.intersecting = false;
6520
6548
  this.gapIntersection = null;
6521
6549
  this.gaps = [];
6550
+ this.printQuery = null;
6522
6551
  // Timeout for scheduling check of the parents that need scroll handlers
6523
6552
  this.parentCheck = -1;
6524
6553
  this.dom = view.contentDOM;
@@ -6552,6 +6581,8 @@ class DOMObserver {
6552
6581
  this.onResize = this.onResize.bind(this);
6553
6582
  this.onPrint = this.onPrint.bind(this);
6554
6583
  this.onScroll = this.onScroll.bind(this);
6584
+ if (window.matchMedia)
6585
+ this.printQuery = window.matchMedia("print");
6555
6586
  if (typeof ResizeObserver == "function") {
6556
6587
  this.resizeScroll = new ResizeObserver(() => {
6557
6588
  var _a;
@@ -6598,7 +6629,9 @@ class DOMObserver {
6598
6629
  this.view.requestMeasure();
6599
6630
  }, 50);
6600
6631
  }
6601
- onPrint() {
6632
+ onPrint(event) {
6633
+ if (event.type == "change" && !event.matches)
6634
+ return;
6602
6635
  this.view.viewState.printing = true;
6603
6636
  this.view.measure();
6604
6637
  setTimeout(() => {
@@ -6876,14 +6909,20 @@ class DOMObserver {
6876
6909
  }
6877
6910
  addWindowListeners(win) {
6878
6911
  win.addEventListener("resize", this.onResize);
6879
- win.addEventListener("beforeprint", this.onPrint);
6912
+ if (this.printQuery)
6913
+ this.printQuery.addEventListener("change", this.onPrint);
6914
+ else
6915
+ win.addEventListener("beforeprint", this.onPrint);
6880
6916
  win.addEventListener("scroll", this.onScroll);
6881
6917
  win.document.addEventListener("selectionchange", this.onSelectionChange);
6882
6918
  }
6883
6919
  removeWindowListeners(win) {
6884
6920
  win.removeEventListener("scroll", this.onScroll);
6885
6921
  win.removeEventListener("resize", this.onResize);
6886
- win.removeEventListener("beforeprint", this.onPrint);
6922
+ if (this.printQuery)
6923
+ this.printQuery.removeEventListener("change", this.onPrint);
6924
+ else
6925
+ win.removeEventListener("beforeprint", this.onPrint);
6887
6926
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6888
6927
  }
6889
6928
  destroy() {
@@ -7890,6 +7929,13 @@ dispatching the custom behavior as a separate transaction.
7890
7929
  */
7891
7930
  EditorView.inputHandler = inputHandler;
7892
7931
  /**
7932
+ Scroll handlers can override how things are scrolled into view.
7933
+ If they return `true`, no further handling happens for the
7934
+ scrolling. If they return false, the default scroll behavior is
7935
+ applied. Scroll handlers should never initiate editor updates.
7936
+ */
7937
+ EditorView.scrollHandler = scrollHandler;
7938
+ /**
7893
7939
  This facet can be used to provide functions that create effects
7894
7940
  to be dispatched when the editor's focus state changes.
7895
7941
  */
package/dist/index.d.cts CHANGED
@@ -1134,6 +1134,23 @@ declare class EditorView {
1134
1134
  */
1135
1135
  static inputHandler: Facet<(view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean, readonly ((view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean)[]>;
1136
1136
  /**
1137
+ Scroll handlers can override how things are scrolled into view.
1138
+ If they return `true`, no further handling happens for the
1139
+ scrolling. If they return false, the default scroll behavior is
1140
+ applied. Scroll handlers should never initiate editor updates.
1141
+ */
1142
+ static scrollHandler: Facet<(view: EditorView, range: SelectionRange, options: {
1143
+ x: ScrollStrategy;
1144
+ y: ScrollStrategy;
1145
+ xMargin: number;
1146
+ yMargin: number;
1147
+ }) => boolean, readonly ((view: EditorView, range: SelectionRange, options: {
1148
+ x: ScrollStrategy;
1149
+ y: ScrollStrategy;
1150
+ xMargin: number;
1151
+ yMargin: number;
1152
+ }) => boolean)[]>;
1153
+ /**
1137
1154
  This facet can be used to provide functions that create effects
1138
1155
  to be dispatched when the editor's focus state changes.
1139
1156
  */
package/dist/index.d.ts CHANGED
@@ -1134,6 +1134,23 @@ declare class EditorView {
1134
1134
  */
1135
1135
  static inputHandler: Facet<(view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean, readonly ((view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean)[]>;
1136
1136
  /**
1137
+ Scroll handlers can override how things are scrolled into view.
1138
+ If they return `true`, no further handling happens for the
1139
+ scrolling. If they return false, the default scroll behavior is
1140
+ applied. Scroll handlers should never initiate editor updates.
1141
+ */
1142
+ static scrollHandler: Facet<(view: EditorView, range: SelectionRange, options: {
1143
+ x: ScrollStrategy;
1144
+ y: ScrollStrategy;
1145
+ xMargin: number;
1146
+ yMargin: number;
1147
+ }) => boolean, readonly ((view: EditorView, range: SelectionRange, options: {
1148
+ x: ScrollStrategy;
1149
+ y: ScrollStrategy;
1150
+ xMargin: number;
1151
+ yMargin: number;
1152
+ }) => boolean)[]>;
1153
+ /**
1137
1154
  This facet can be used to provide functions that create effects
1138
1155
  to be dispatched when the editor's focus state changes.
1139
1156
  */
package/dist/index.js CHANGED
@@ -91,6 +91,12 @@ function flattenRect(rect, left) {
91
91
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
92
92
  }
93
93
  function windowRect(win) {
94
+ let vp = win.visualViewport;
95
+ if (vp)
96
+ return {
97
+ left: 0, right: vp.width,
98
+ top: 0, bottom: vp.height
99
+ };
94
100
  return { left: 0, right: win.innerWidth,
95
101
  top: 0, bottom: win.innerHeight };
96
102
  }
@@ -280,8 +286,10 @@ function textRange(node, from, to = from) {
280
286
  range.setStart(node, from);
281
287
  return range;
282
288
  }
283
- function dispatchKey(elt, name, code) {
289
+ function dispatchKey(elt, name, code, mods) {
284
290
  let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
291
+ if (mods)
292
+ ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
285
293
  let down = new KeyboardEvent("keydown", options);
286
294
  down.synthetic = true;
287
295
  elt.dispatchEvent(down);
@@ -2286,6 +2294,7 @@ const perLineTextDirection = /*@__PURE__*/Facet.define({
2286
2294
  const nativeSelectionHidden = /*@__PURE__*/Facet.define({
2287
2295
  combine: values => values.some(x => x)
2288
2296
  });
2297
+ const scrollHandler = /*@__PURE__*/Facet.define();
2289
2298
  class ScrollTarget {
2290
2299
  constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
2291
2300
  // This data structure is abused to also store precise scroll
@@ -3184,6 +3193,15 @@ class DocView extends ContentView {
3184
3193
  this.view.scrollDOM.scrollLeft = target.xMargin;
3185
3194
  return;
3186
3195
  }
3196
+ for (let handler of this.view.state.facet(scrollHandler)) {
3197
+ try {
3198
+ if (handler(this.view, target.range, target))
3199
+ return true;
3200
+ }
3201
+ catch (e) {
3202
+ logException(this.view.state, e, "scroll handler");
3203
+ }
3204
+ }
3187
3205
  let { range } = target;
3188
3206
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3189
3207
  if (!rect)
@@ -3825,12 +3843,15 @@ class InputState {
3825
3843
  this.view.observer.forceFlush();
3826
3844
  return false;
3827
3845
  }
3828
- flushIOSKey() {
3846
+ flushIOSKey(change) {
3829
3847
  let key = this.pendingIOSKey;
3830
3848
  if (!key)
3831
3849
  return false;
3850
+ // This looks like an autocorrection before Enter
3851
+ if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
3852
+ return false;
3832
3853
  this.pendingIOSKey = undefined;
3833
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
3854
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
3834
3855
  }
3835
3856
  ignoreDuringComposition(event) {
3836
3857
  if (!/^key/.test(event.type))
@@ -4339,7 +4360,7 @@ handlers.paste = (view, event) => {
4339
4360
  view.observer.flush();
4340
4361
  let data = brokenClipboardAPI ? null : event.clipboardData;
4341
4362
  if (data) {
4342
- doPaste(view, data.getData("text/plain") || data.getData("text/uri-text"));
4363
+ doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
4343
4364
  return true;
4344
4365
  }
4345
4366
  else {
@@ -4509,6 +4530,12 @@ handlers.beforeinput = (view, event) => {
4509
4530
  }, 100);
4510
4531
  }
4511
4532
  }
4533
+ if (browser.ios && event.inputType == "deleteContentForward") {
4534
+ // For some reason, DOM changes (and beforeinput) happen _before_
4535
+ // the key event for ctrl-d on iOS when using an external
4536
+ // keyboard.
4537
+ view.observer.flushSoon();
4538
+ }
4512
4539
  return false;
4513
4540
  };
4514
4541
  const appliedFirefoxHack = /*@__PURE__*/new Set;
@@ -5442,7 +5469,8 @@ class ViewState {
5442
5469
  let result = 0, bias = 0;
5443
5470
  if (domRect.width && domRect.height) {
5444
5471
  let { scaleX, scaleY } = getScale(dom, domRect);
5445
- if (this.scaleX != scaleX || this.scaleY != scaleY) {
5472
+ if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
5473
+ scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
5446
5474
  this.scaleX = scaleX;
5447
5475
  this.scaleY = scaleY;
5448
5476
  result |= 8 /* UpdateFlag.Geometry */;
@@ -6323,7 +6351,7 @@ function applyDOMChange(view, domChange) {
6323
6351
  change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
6324
6352
  }
6325
6353
  if (change) {
6326
- if (browser.ios && view.inputState.flushIOSKey())
6354
+ if (browser.ios && view.inputState.flushIOSKey(change))
6327
6355
  return true;
6328
6356
  // Android browsers don't fire reasonable key events for enter,
6329
6357
  // backspace, or delete. So this detects changes that look like
@@ -6514,6 +6542,7 @@ class DOMObserver {
6514
6542
  this.intersecting = false;
6515
6543
  this.gapIntersection = null;
6516
6544
  this.gaps = [];
6545
+ this.printQuery = null;
6517
6546
  // Timeout for scheduling check of the parents that need scroll handlers
6518
6547
  this.parentCheck = -1;
6519
6548
  this.dom = view.contentDOM;
@@ -6547,6 +6576,8 @@ class DOMObserver {
6547
6576
  this.onResize = this.onResize.bind(this);
6548
6577
  this.onPrint = this.onPrint.bind(this);
6549
6578
  this.onScroll = this.onScroll.bind(this);
6579
+ if (window.matchMedia)
6580
+ this.printQuery = window.matchMedia("print");
6550
6581
  if (typeof ResizeObserver == "function") {
6551
6582
  this.resizeScroll = new ResizeObserver(() => {
6552
6583
  var _a;
@@ -6593,7 +6624,9 @@ class DOMObserver {
6593
6624
  this.view.requestMeasure();
6594
6625
  }, 50);
6595
6626
  }
6596
- onPrint() {
6627
+ onPrint(event) {
6628
+ if (event.type == "change" && !event.matches)
6629
+ return;
6597
6630
  this.view.viewState.printing = true;
6598
6631
  this.view.measure();
6599
6632
  setTimeout(() => {
@@ -6871,14 +6904,20 @@ class DOMObserver {
6871
6904
  }
6872
6905
  addWindowListeners(win) {
6873
6906
  win.addEventListener("resize", this.onResize);
6874
- win.addEventListener("beforeprint", this.onPrint);
6907
+ if (this.printQuery)
6908
+ this.printQuery.addEventListener("change", this.onPrint);
6909
+ else
6910
+ win.addEventListener("beforeprint", this.onPrint);
6875
6911
  win.addEventListener("scroll", this.onScroll);
6876
6912
  win.document.addEventListener("selectionchange", this.onSelectionChange);
6877
6913
  }
6878
6914
  removeWindowListeners(win) {
6879
6915
  win.removeEventListener("scroll", this.onScroll);
6880
6916
  win.removeEventListener("resize", this.onResize);
6881
- win.removeEventListener("beforeprint", this.onPrint);
6917
+ if (this.printQuery)
6918
+ this.printQuery.removeEventListener("change", this.onPrint);
6919
+ else
6920
+ win.removeEventListener("beforeprint", this.onPrint);
6882
6921
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6883
6922
  }
6884
6923
  destroy() {
@@ -7885,6 +7924,13 @@ dispatching the custom behavior as a separate transaction.
7885
7924
  */
7886
7925
  EditorView.inputHandler = inputHandler;
7887
7926
  /**
7927
+ Scroll handlers can override how things are scrolled into view.
7928
+ If they return `true`, no further handling happens for the
7929
+ scrolling. If they return false, the default scroll behavior is
7930
+ applied. Scroll handlers should never initiate editor updates.
7931
+ */
7932
+ EditorView.scrollHandler = scrollHandler;
7933
+ /**
7888
7934
  This facet can be used to provide functions that create effects
7889
7935
  to be dispatched when the editor's focus state changes.
7890
7936
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.25.1",
3
+ "version": "6.26.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",