@codemirror/view 6.25.1 → 6.26.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,35 @@
1
+ ## 6.26.1 (2024-03-28)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix the editor getting stuck in composition when Safari fails to fire a compositionend event for a dead key composition.
6
+
7
+ Fix an issue where, with IME systems that kept the cursor at the start of the composed text, the editor misidentified the target node and disrupted composition.
8
+
9
+ Fix a bug where in a line-wrapped editor, with some content, the initial scroll position would be off from the top of the document.
10
+
11
+ ## 6.26.0 (2024-03-14)
12
+
13
+ ### Bug fixes
14
+
15
+ Avoid the editor getting confused when iOS autocorrects on pressing Enter and does the correction and the break insertion in two different events.
16
+
17
+ Fix the pasting of copied URIs in iOS.
18
+
19
+ Fix a bug where a scaled editor could keep performing unnecessary updates due to tiny differences in geometry values returned by the browser.
20
+
21
+ Fix a bug where, on iOS with a physical keyboard, the modifiers for some keys weren't being passed to the keymaps.
22
+
23
+ Work around the fact that Mobile Safari makes DOM changes before firing a key event when typing ctrl-d on an external keyboard.
24
+
25
+ Fix an issue where some commands didn't properly scroll the cursor into view on Mobile Safari.
26
+
27
+ Re-measure the document when print settings are changed on Chrome.
28
+
29
+ ### New features
30
+
31
+ The `EditorView.scrollHandler` facet can be used to override or extend the behavior of the editor when things are scrolled into view.
32
+
1
33
  ## 6.25.1 (2024-03-06)
2
34
 
3
35
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -61,6 +61,9 @@ function domIndex(node) {
61
61
  return index;
62
62
  }
63
63
  }
64
+ function isBlockElement(node) {
65
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
66
+ }
64
67
  function scanFor(node, off, targetNode, targetOff, dir) {
65
68
  for (;;) {
66
69
  if (node == targetNode && off == targetOff)
@@ -93,6 +96,12 @@ function flattenRect(rect, left) {
93
96
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
94
97
  }
95
98
  function windowRect(win) {
99
+ let vp = win.visualViewport;
100
+ if (vp)
101
+ return {
102
+ left: 0, right: vp.width,
103
+ top: 0, bottom: vp.height
104
+ };
96
105
  return { left: 0, right: win.innerWidth,
97
106
  top: 0, bottom: win.innerHeight };
98
107
  }
@@ -282,8 +291,10 @@ function textRange(node, from, to = from) {
282
291
  range.setStart(node, from);
283
292
  return range;
284
293
  }
285
- function dispatchKey(elt, name, code) {
294
+ function dispatchKey(elt, name, code, mods) {
286
295
  let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
296
+ if (mods)
297
+ ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
287
298
  let down = new KeyboardEvent("keydown", options);
288
299
  down.synthetic = true;
289
300
  elt.dispatchEvent(down);
@@ -334,6 +345,46 @@ function atElementStart(doc, selection) {
334
345
  function isScrolledToBottom(elt) {
335
346
  return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
336
347
  }
348
+ function textNodeBefore(startNode, startOffset) {
349
+ for (let node = startNode, offset = startOffset;;) {
350
+ if (node.nodeType == 3 && offset > 0) {
351
+ return { node: node, offset: offset };
352
+ }
353
+ else if (node.nodeType == 1 && offset > 0) {
354
+ if (node.contentEditable == "false")
355
+ return null;
356
+ node = node.childNodes[offset - 1];
357
+ offset = maxOffset(node);
358
+ }
359
+ else if (node.parentNode && !isBlockElement(node)) {
360
+ offset = domIndex(node);
361
+ node = node.parentNode;
362
+ }
363
+ else {
364
+ return null;
365
+ }
366
+ }
367
+ }
368
+ function textNodeAfter(startNode, startOffset) {
369
+ for (let node = startNode, offset = startOffset;;) {
370
+ if (node.nodeType == 3 && offset < node.nodeValue.length) {
371
+ return { node: node, offset: offset };
372
+ }
373
+ else if (node.nodeType == 1 && offset < node.childNodes.length) {
374
+ if (node.contentEditable == "false")
375
+ return null;
376
+ node = node.childNodes[offset];
377
+ offset = 0;
378
+ }
379
+ else if (node.parentNode && !isBlockElement(node)) {
380
+ offset = domIndex(node) + 1;
381
+ node = node.parentNode;
382
+ }
383
+ else {
384
+ return null;
385
+ }
386
+ }
387
+ }
337
388
 
338
389
  class DOMPos {
339
390
  constructor(node, offset, precise = true) {
@@ -2290,6 +2341,7 @@ const perLineTextDirection = state.Facet.define({
2290
2341
  const nativeSelectionHidden = state.Facet.define({
2291
2342
  combine: values => values.some(x => x)
2292
2343
  });
2344
+ const scrollHandler = state.Facet.define();
2293
2345
  class ScrollTarget {
2294
2346
  constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
2295
2347
  // This data structure is abused to also store precise scroll
@@ -2657,6 +2709,7 @@ class DocView extends ContentView {
2657
2709
  this.hasComposition = null;
2658
2710
  this.markedForComposition = new Set;
2659
2711
  this.compositionBarrier = Decoration.none;
2712
+ this.lastCompositionAfterCursor = false;
2660
2713
  // Track a minimum width for the editor. When measuring sizes in
2661
2714
  // measureVisibleLineHeights, this is updated to point at the width
2662
2715
  // of a given element and its extent in the document. When a change
@@ -2873,7 +2926,7 @@ class DocView extends ContentView {
2873
2926
  if (browser.gecko) {
2874
2927
  let nextTo = nextToUneditable(anchor.node, anchor.offset);
2875
2928
  if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
2876
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* NextTo.Before */ ? 1 : -1);
2929
+ let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
2877
2930
  if (text)
2878
2931
  anchor = new DOMPos(text.node, text.offset);
2879
2932
  }
@@ -3188,6 +3241,15 @@ class DocView extends ContentView {
3188
3241
  this.view.scrollDOM.scrollLeft = target.xMargin;
3189
3242
  return;
3190
3243
  }
3244
+ for (let handler of this.view.state.facet(scrollHandler)) {
3245
+ try {
3246
+ if (handler(this.view, target.range, target))
3247
+ return true;
3248
+ }
3249
+ catch (e) {
3250
+ logException(this.view.state, e, "scroll handler");
3251
+ }
3252
+ }
3191
3253
  let { range } = target;
3192
3254
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3193
3255
  if (!rect)
@@ -3232,7 +3294,23 @@ class BlockGapWidget extends WidgetType {
3232
3294
  }
3233
3295
  function findCompositionNode(view, headPos) {
3234
3296
  let sel = view.observer.selectionRange;
3235
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
3297
+ if (!sel.focusNode)
3298
+ return null;
3299
+ let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
3300
+ let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
3301
+ let textNode = textBefore || textAfter;
3302
+ if (textAfter && textBefore && textAfter.node != textBefore.node) {
3303
+ let descAfter = ContentView.get(textAfter.node);
3304
+ if (!descAfter || descAfter instanceof TextView && descAfter.text != textAfter.node.nodeValue) {
3305
+ textNode = textAfter;
3306
+ }
3307
+ else if (view.docView.lastCompositionAfterCursor) {
3308
+ let descBefore = ContentView.get(textBefore.node);
3309
+ if (!(!descBefore || descBefore instanceof TextView && descBefore.text != textBefore.node.nodeValue))
3310
+ textNode = textAfter;
3311
+ }
3312
+ }
3313
+ view.docView.lastCompositionAfterCursor = textNode != textBefore;
3236
3314
  if (!textNode)
3237
3315
  return null;
3238
3316
  let from = headPos - textNode.offset;
@@ -3267,33 +3345,6 @@ function findCompositionRange(view, changes, headPos) {
3267
3345
  return null;
3268
3346
  }
3269
3347
  }
3270
- function nearbyTextNode(startNode, startOffset, side) {
3271
- if (side <= 0)
3272
- for (let node = startNode, offset = startOffset;;) {
3273
- if (node.nodeType == 3)
3274
- return { node: node, offset: offset };
3275
- if (node.nodeType == 1 && offset > 0) {
3276
- node = node.childNodes[offset - 1];
3277
- offset = maxOffset(node);
3278
- }
3279
- else {
3280
- break;
3281
- }
3282
- }
3283
- if (side >= 0)
3284
- for (let node = startNode, offset = startOffset;;) {
3285
- if (node.nodeType == 3)
3286
- return { node: node, offset: offset };
3287
- if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3288
- node = node.childNodes[offset];
3289
- offset = 0;
3290
- }
3291
- else {
3292
- break;
3293
- }
3294
- }
3295
- return null;
3296
- }
3297
3348
  function nextToUneditable(node, offset) {
3298
3349
  if (node.nodeType != 1)
3299
3350
  return 0;
@@ -3829,12 +3880,15 @@ class InputState {
3829
3880
  this.view.observer.forceFlush();
3830
3881
  return false;
3831
3882
  }
3832
- flushIOSKey() {
3883
+ flushIOSKey(change) {
3833
3884
  let key = this.pendingIOSKey;
3834
3885
  if (!key)
3835
3886
  return false;
3887
+ // This looks like an autocorrection before Enter
3888
+ if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
3889
+ return false;
3836
3890
  this.pendingIOSKey = undefined;
3837
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
3891
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
3838
3892
  }
3839
3893
  ignoreDuringComposition(event) {
3840
3894
  if (!/^key/.test(event.type))
@@ -4343,7 +4397,7 @@ handlers.paste = (view, event) => {
4343
4397
  view.observer.flush();
4344
4398
  let data = brokenClipboardAPI ? null : event.clipboardData;
4345
4399
  if (data) {
4346
- doPaste(view, data.getData("text/plain") || data.getData("text/uri-text"));
4400
+ doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
4347
4401
  return true;
4348
4402
  }
4349
4403
  else {
@@ -4513,6 +4567,16 @@ handlers.beforeinput = (view, event) => {
4513
4567
  }, 100);
4514
4568
  }
4515
4569
  }
4570
+ if (browser.ios && event.inputType == "deleteContentForward") {
4571
+ // For some reason, DOM changes (and beforeinput) happen _before_
4572
+ // the key event for ctrl-d on iOS when using an external
4573
+ // keyboard.
4574
+ view.observer.flushSoon();
4575
+ }
4576
+ // Safari will occasionally forget to fire compositionend at the end of a dead-key composition
4577
+ if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
4578
+ setTimeout(() => observers.compositionend(view, event), 20);
4579
+ }
4516
4580
  return false;
4517
4581
  };
4518
4582
  const appliedFirefoxHack = new Set;
@@ -4875,7 +4939,8 @@ class HeightMapGap extends HeightMap {
4875
4939
  blockAt(height, oracle, top, offset) {
4876
4940
  let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
4877
4941
  if (oracle.lineWrapping) {
4878
- let guess = offset + Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length);
4942
+ let guess = offset + (height < oracle.lineHeight ? 0
4943
+ : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
4879
4944
  let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
4880
4945
  let lineTop = Math.max(top, height - lineHeight / 2);
4881
4946
  return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
@@ -5447,7 +5512,8 @@ class ViewState {
5447
5512
  let result = 0, bias = 0;
5448
5513
  if (domRect.width && domRect.height) {
5449
5514
  let { scaleX, scaleY } = getScale(dom, domRect);
5450
- if (this.scaleX != scaleX || this.scaleY != scaleY) {
5515
+ if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
5516
+ scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
5451
5517
  this.scaleX = scaleX;
5452
5518
  this.scaleY = scaleY;
5453
5519
  result |= 8 /* UpdateFlag.Geometry */;
@@ -6210,9 +6276,6 @@ function isAtEnd(parent, node, offset) {
6210
6276
  node = node.parentNode;
6211
6277
  }
6212
6278
  }
6213
- function isBlockElement(node) {
6214
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
6215
- }
6216
6279
  class DOMPoint {
6217
6280
  constructor(node, offset) {
6218
6281
  this.node = node;
@@ -6328,7 +6391,7 @@ function applyDOMChange(view, domChange) {
6328
6391
  change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
6329
6392
  }
6330
6393
  if (change) {
6331
- if (browser.ios && view.inputState.flushIOSKey())
6394
+ if (browser.ios && view.inputState.flushIOSKey(change))
6332
6395
  return true;
6333
6396
  // Android browsers don't fire reasonable key events for enter,
6334
6397
  // backspace, or delete. So this detects changes that look like
@@ -6519,6 +6582,7 @@ class DOMObserver {
6519
6582
  this.intersecting = false;
6520
6583
  this.gapIntersection = null;
6521
6584
  this.gaps = [];
6585
+ this.printQuery = null;
6522
6586
  // Timeout for scheduling check of the parents that need scroll handlers
6523
6587
  this.parentCheck = -1;
6524
6588
  this.dom = view.contentDOM;
@@ -6552,6 +6616,8 @@ class DOMObserver {
6552
6616
  this.onResize = this.onResize.bind(this);
6553
6617
  this.onPrint = this.onPrint.bind(this);
6554
6618
  this.onScroll = this.onScroll.bind(this);
6619
+ if (window.matchMedia)
6620
+ this.printQuery = window.matchMedia("print");
6555
6621
  if (typeof ResizeObserver == "function") {
6556
6622
  this.resizeScroll = new ResizeObserver(() => {
6557
6623
  var _a;
@@ -6598,7 +6664,9 @@ class DOMObserver {
6598
6664
  this.view.requestMeasure();
6599
6665
  }, 50);
6600
6666
  }
6601
- onPrint() {
6667
+ onPrint(event) {
6668
+ if (event.type == "change" && !event.matches)
6669
+ return;
6602
6670
  this.view.viewState.printing = true;
6603
6671
  this.view.measure();
6604
6672
  setTimeout(() => {
@@ -6876,14 +6944,20 @@ class DOMObserver {
6876
6944
  }
6877
6945
  addWindowListeners(win) {
6878
6946
  win.addEventListener("resize", this.onResize);
6879
- win.addEventListener("beforeprint", this.onPrint);
6947
+ if (this.printQuery)
6948
+ this.printQuery.addEventListener("change", this.onPrint);
6949
+ else
6950
+ win.addEventListener("beforeprint", this.onPrint);
6880
6951
  win.addEventListener("scroll", this.onScroll);
6881
6952
  win.document.addEventListener("selectionchange", this.onSelectionChange);
6882
6953
  }
6883
6954
  removeWindowListeners(win) {
6884
6955
  win.removeEventListener("scroll", this.onScroll);
6885
6956
  win.removeEventListener("resize", this.onResize);
6886
- win.removeEventListener("beforeprint", this.onPrint);
6957
+ if (this.printQuery)
6958
+ this.printQuery.removeEventListener("change", this.onPrint);
6959
+ else
6960
+ win.removeEventListener("beforeprint", this.onPrint);
6887
6961
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6888
6962
  }
6889
6963
  destroy() {
@@ -7890,6 +7964,13 @@ dispatching the custom behavior as a separate transaction.
7890
7964
  */
7891
7965
  EditorView.inputHandler = inputHandler;
7892
7966
  /**
7967
+ Scroll handlers can override how things are scrolled into view.
7968
+ If they return `true`, no further handling happens for the
7969
+ scrolling. If they return false, the default scroll behavior is
7970
+ applied. Scroll handlers should never initiate editor updates.
7971
+ */
7972
+ EditorView.scrollHandler = scrollHandler;
7973
+ /**
7893
7974
  This facet can be used to provide functions that create effects
7894
7975
  to be dispatched when the editor's focus state changes.
7895
7976
  */
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
@@ -59,6 +59,9 @@ function domIndex(node) {
59
59
  return index;
60
60
  }
61
61
  }
62
+ function isBlockElement(node) {
63
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
64
+ }
62
65
  function scanFor(node, off, targetNode, targetOff, dir) {
63
66
  for (;;) {
64
67
  if (node == targetNode && off == targetOff)
@@ -91,6 +94,12 @@ function flattenRect(rect, left) {
91
94
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
92
95
  }
93
96
  function windowRect(win) {
97
+ let vp = win.visualViewport;
98
+ if (vp)
99
+ return {
100
+ left: 0, right: vp.width,
101
+ top: 0, bottom: vp.height
102
+ };
94
103
  return { left: 0, right: win.innerWidth,
95
104
  top: 0, bottom: win.innerHeight };
96
105
  }
@@ -280,8 +289,10 @@ function textRange(node, from, to = from) {
280
289
  range.setStart(node, from);
281
290
  return range;
282
291
  }
283
- function dispatchKey(elt, name, code) {
292
+ function dispatchKey(elt, name, code, mods) {
284
293
  let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
294
+ if (mods)
295
+ ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
285
296
  let down = new KeyboardEvent("keydown", options);
286
297
  down.synthetic = true;
287
298
  elt.dispatchEvent(down);
@@ -332,6 +343,46 @@ function atElementStart(doc, selection) {
332
343
  function isScrolledToBottom(elt) {
333
344
  return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
334
345
  }
346
+ function textNodeBefore(startNode, startOffset) {
347
+ for (let node = startNode, offset = startOffset;;) {
348
+ if (node.nodeType == 3 && offset > 0) {
349
+ return { node: node, offset: offset };
350
+ }
351
+ else if (node.nodeType == 1 && offset > 0) {
352
+ if (node.contentEditable == "false")
353
+ return null;
354
+ node = node.childNodes[offset - 1];
355
+ offset = maxOffset(node);
356
+ }
357
+ else if (node.parentNode && !isBlockElement(node)) {
358
+ offset = domIndex(node);
359
+ node = node.parentNode;
360
+ }
361
+ else {
362
+ return null;
363
+ }
364
+ }
365
+ }
366
+ function textNodeAfter(startNode, startOffset) {
367
+ for (let node = startNode, offset = startOffset;;) {
368
+ if (node.nodeType == 3 && offset < node.nodeValue.length) {
369
+ return { node: node, offset: offset };
370
+ }
371
+ else if (node.nodeType == 1 && offset < node.childNodes.length) {
372
+ if (node.contentEditable == "false")
373
+ return null;
374
+ node = node.childNodes[offset];
375
+ offset = 0;
376
+ }
377
+ else if (node.parentNode && !isBlockElement(node)) {
378
+ offset = domIndex(node) + 1;
379
+ node = node.parentNode;
380
+ }
381
+ else {
382
+ return null;
383
+ }
384
+ }
385
+ }
335
386
 
336
387
  class DOMPos {
337
388
  constructor(node, offset, precise = true) {
@@ -2286,6 +2337,7 @@ const perLineTextDirection = /*@__PURE__*/Facet.define({
2286
2337
  const nativeSelectionHidden = /*@__PURE__*/Facet.define({
2287
2338
  combine: values => values.some(x => x)
2288
2339
  });
2340
+ const scrollHandler = /*@__PURE__*/Facet.define();
2289
2341
  class ScrollTarget {
2290
2342
  constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
2291
2343
  // This data structure is abused to also store precise scroll
@@ -2653,6 +2705,7 @@ class DocView extends ContentView {
2653
2705
  this.hasComposition = null;
2654
2706
  this.markedForComposition = new Set;
2655
2707
  this.compositionBarrier = Decoration.none;
2708
+ this.lastCompositionAfterCursor = false;
2656
2709
  // Track a minimum width for the editor. When measuring sizes in
2657
2710
  // measureVisibleLineHeights, this is updated to point at the width
2658
2711
  // of a given element and its extent in the document. When a change
@@ -2869,7 +2922,7 @@ class DocView extends ContentView {
2869
2922
  if (browser.gecko) {
2870
2923
  let nextTo = nextToUneditable(anchor.node, anchor.offset);
2871
2924
  if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
2872
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* NextTo.Before */ ? 1 : -1);
2925
+ let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
2873
2926
  if (text)
2874
2927
  anchor = new DOMPos(text.node, text.offset);
2875
2928
  }
@@ -3184,6 +3237,15 @@ class DocView extends ContentView {
3184
3237
  this.view.scrollDOM.scrollLeft = target.xMargin;
3185
3238
  return;
3186
3239
  }
3240
+ for (let handler of this.view.state.facet(scrollHandler)) {
3241
+ try {
3242
+ if (handler(this.view, target.range, target))
3243
+ return true;
3244
+ }
3245
+ catch (e) {
3246
+ logException(this.view.state, e, "scroll handler");
3247
+ }
3248
+ }
3187
3249
  let { range } = target;
3188
3250
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3189
3251
  if (!rect)
@@ -3228,7 +3290,23 @@ class BlockGapWidget extends WidgetType {
3228
3290
  }
3229
3291
  function findCompositionNode(view, headPos) {
3230
3292
  let sel = view.observer.selectionRange;
3231
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
3293
+ if (!sel.focusNode)
3294
+ return null;
3295
+ let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
3296
+ let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
3297
+ let textNode = textBefore || textAfter;
3298
+ if (textAfter && textBefore && textAfter.node != textBefore.node) {
3299
+ let descAfter = ContentView.get(textAfter.node);
3300
+ if (!descAfter || descAfter instanceof TextView && descAfter.text != textAfter.node.nodeValue) {
3301
+ textNode = textAfter;
3302
+ }
3303
+ else if (view.docView.lastCompositionAfterCursor) {
3304
+ let descBefore = ContentView.get(textBefore.node);
3305
+ if (!(!descBefore || descBefore instanceof TextView && descBefore.text != textBefore.node.nodeValue))
3306
+ textNode = textAfter;
3307
+ }
3308
+ }
3309
+ view.docView.lastCompositionAfterCursor = textNode != textBefore;
3232
3310
  if (!textNode)
3233
3311
  return null;
3234
3312
  let from = headPos - textNode.offset;
@@ -3263,33 +3341,6 @@ function findCompositionRange(view, changes, headPos) {
3263
3341
  return null;
3264
3342
  }
3265
3343
  }
3266
- function nearbyTextNode(startNode, startOffset, side) {
3267
- if (side <= 0)
3268
- for (let node = startNode, offset = startOffset;;) {
3269
- if (node.nodeType == 3)
3270
- return { node: node, offset: offset };
3271
- if (node.nodeType == 1 && offset > 0) {
3272
- node = node.childNodes[offset - 1];
3273
- offset = maxOffset(node);
3274
- }
3275
- else {
3276
- break;
3277
- }
3278
- }
3279
- if (side >= 0)
3280
- for (let node = startNode, offset = startOffset;;) {
3281
- if (node.nodeType == 3)
3282
- return { node: node, offset: offset };
3283
- if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3284
- node = node.childNodes[offset];
3285
- offset = 0;
3286
- }
3287
- else {
3288
- break;
3289
- }
3290
- }
3291
- return null;
3292
- }
3293
3344
  function nextToUneditable(node, offset) {
3294
3345
  if (node.nodeType != 1)
3295
3346
  return 0;
@@ -3825,12 +3876,15 @@ class InputState {
3825
3876
  this.view.observer.forceFlush();
3826
3877
  return false;
3827
3878
  }
3828
- flushIOSKey() {
3879
+ flushIOSKey(change) {
3829
3880
  let key = this.pendingIOSKey;
3830
3881
  if (!key)
3831
3882
  return false;
3883
+ // This looks like an autocorrection before Enter
3884
+ if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
3885
+ return false;
3832
3886
  this.pendingIOSKey = undefined;
3833
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode);
3887
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
3834
3888
  }
3835
3889
  ignoreDuringComposition(event) {
3836
3890
  if (!/^key/.test(event.type))
@@ -4339,7 +4393,7 @@ handlers.paste = (view, event) => {
4339
4393
  view.observer.flush();
4340
4394
  let data = brokenClipboardAPI ? null : event.clipboardData;
4341
4395
  if (data) {
4342
- doPaste(view, data.getData("text/plain") || data.getData("text/uri-text"));
4396
+ doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
4343
4397
  return true;
4344
4398
  }
4345
4399
  else {
@@ -4509,6 +4563,16 @@ handlers.beforeinput = (view, event) => {
4509
4563
  }, 100);
4510
4564
  }
4511
4565
  }
4566
+ if (browser.ios && event.inputType == "deleteContentForward") {
4567
+ // For some reason, DOM changes (and beforeinput) happen _before_
4568
+ // the key event for ctrl-d on iOS when using an external
4569
+ // keyboard.
4570
+ view.observer.flushSoon();
4571
+ }
4572
+ // Safari will occasionally forget to fire compositionend at the end of a dead-key composition
4573
+ if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
4574
+ setTimeout(() => observers.compositionend(view, event), 20);
4575
+ }
4512
4576
  return false;
4513
4577
  };
4514
4578
  const appliedFirefoxHack = /*@__PURE__*/new Set;
@@ -4870,7 +4934,8 @@ class HeightMapGap extends HeightMap {
4870
4934
  blockAt(height, oracle, top, offset) {
4871
4935
  let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
4872
4936
  if (oracle.lineWrapping) {
4873
- let guess = offset + Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length);
4937
+ let guess = offset + (height < oracle.lineHeight ? 0
4938
+ : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
4874
4939
  let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
4875
4940
  let lineTop = Math.max(top, height - lineHeight / 2);
4876
4941
  return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
@@ -5442,7 +5507,8 @@ class ViewState {
5442
5507
  let result = 0, bias = 0;
5443
5508
  if (domRect.width && domRect.height) {
5444
5509
  let { scaleX, scaleY } = getScale(dom, domRect);
5445
- if (this.scaleX != scaleX || this.scaleY != scaleY) {
5510
+ if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
5511
+ scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
5446
5512
  this.scaleX = scaleX;
5447
5513
  this.scaleY = scaleY;
5448
5514
  result |= 8 /* UpdateFlag.Geometry */;
@@ -6205,9 +6271,6 @@ function isAtEnd(parent, node, offset) {
6205
6271
  node = node.parentNode;
6206
6272
  }
6207
6273
  }
6208
- function isBlockElement(node) {
6209
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
6210
- }
6211
6274
  class DOMPoint {
6212
6275
  constructor(node, offset) {
6213
6276
  this.node = node;
@@ -6323,7 +6386,7 @@ function applyDOMChange(view, domChange) {
6323
6386
  change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
6324
6387
  }
6325
6388
  if (change) {
6326
- if (browser.ios && view.inputState.flushIOSKey())
6389
+ if (browser.ios && view.inputState.flushIOSKey(change))
6327
6390
  return true;
6328
6391
  // Android browsers don't fire reasonable key events for enter,
6329
6392
  // backspace, or delete. So this detects changes that look like
@@ -6514,6 +6577,7 @@ class DOMObserver {
6514
6577
  this.intersecting = false;
6515
6578
  this.gapIntersection = null;
6516
6579
  this.gaps = [];
6580
+ this.printQuery = null;
6517
6581
  // Timeout for scheduling check of the parents that need scroll handlers
6518
6582
  this.parentCheck = -1;
6519
6583
  this.dom = view.contentDOM;
@@ -6547,6 +6611,8 @@ class DOMObserver {
6547
6611
  this.onResize = this.onResize.bind(this);
6548
6612
  this.onPrint = this.onPrint.bind(this);
6549
6613
  this.onScroll = this.onScroll.bind(this);
6614
+ if (window.matchMedia)
6615
+ this.printQuery = window.matchMedia("print");
6550
6616
  if (typeof ResizeObserver == "function") {
6551
6617
  this.resizeScroll = new ResizeObserver(() => {
6552
6618
  var _a;
@@ -6593,7 +6659,9 @@ class DOMObserver {
6593
6659
  this.view.requestMeasure();
6594
6660
  }, 50);
6595
6661
  }
6596
- onPrint() {
6662
+ onPrint(event) {
6663
+ if (event.type == "change" && !event.matches)
6664
+ return;
6597
6665
  this.view.viewState.printing = true;
6598
6666
  this.view.measure();
6599
6667
  setTimeout(() => {
@@ -6871,14 +6939,20 @@ class DOMObserver {
6871
6939
  }
6872
6940
  addWindowListeners(win) {
6873
6941
  win.addEventListener("resize", this.onResize);
6874
- win.addEventListener("beforeprint", this.onPrint);
6942
+ if (this.printQuery)
6943
+ this.printQuery.addEventListener("change", this.onPrint);
6944
+ else
6945
+ win.addEventListener("beforeprint", this.onPrint);
6875
6946
  win.addEventListener("scroll", this.onScroll);
6876
6947
  win.document.addEventListener("selectionchange", this.onSelectionChange);
6877
6948
  }
6878
6949
  removeWindowListeners(win) {
6879
6950
  win.removeEventListener("scroll", this.onScroll);
6880
6951
  win.removeEventListener("resize", this.onResize);
6881
- win.removeEventListener("beforeprint", this.onPrint);
6952
+ if (this.printQuery)
6953
+ this.printQuery.removeEventListener("change", this.onPrint);
6954
+ else
6955
+ win.removeEventListener("beforeprint", this.onPrint);
6882
6956
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6883
6957
  }
6884
6958
  destroy() {
@@ -7885,6 +7959,13 @@ dispatching the custom behavior as a separate transaction.
7885
7959
  */
7886
7960
  EditorView.inputHandler = inputHandler;
7887
7961
  /**
7962
+ Scroll handlers can override how things are scrolled into view.
7963
+ If they return `true`, no further handling happens for the
7964
+ scrolling. If they return false, the default scroll behavior is
7965
+ applied. Scroll handlers should never initiate editor updates.
7966
+ */
7967
+ EditorView.scrollHandler = scrollHandler;
7968
+ /**
7888
7969
  This facet can be used to provide functions that create effects
7889
7970
  to be dispatched when the editor's focus state changes.
7890
7971
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.25.1",
3
+ "version": "6.26.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",