@codemirror/view 6.21.3 → 6.22.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,27 @@
1
+ ## 6.22.0 (2023-11-03)
2
+
3
+ ### Bug fixes
4
+
5
+ Exceptions raised by update listeners are now routed to the configured exception sink, if any.
6
+
7
+ Fix an issue where passing large scroll margins to `scrollIntoView` would cause the measure loop to fail to terminate.
8
+
9
+ Widgets that are draggable (and allow drag events through in their `ignoreEvent` implementation) can now use the editor's built-in drag/drop behavior.
10
+
11
+ ### New features
12
+
13
+ The new `scrollTo` option to `EditorView` allows an initial scroll position to be provided.
14
+
15
+ The new `EditorView.scrollSnapshot` method returns an effect that can be used to reset to a previous scroll position.
16
+
17
+ ## 6.21.4 (2023-10-24)
18
+
19
+ ### Bug fixes
20
+
21
+ Support the `offset`, `getCoords`, `overlap`, and `resize` properties on hover tooltips, as long as they aren't given conflicting values when there are multiple active hover tooltips.
22
+
23
+ Fix a bug that caused tooltips in the default configuration to be positioned incorrectly on Chrome when the editor was transformed.
24
+
1
25
  ## 6.21.3 (2023-10-06)
2
26
 
3
27
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1788,15 +1788,28 @@ const nativeSelectionHidden = state.Facet.define({
1788
1788
  combine: values => values.some(x => x)
1789
1789
  });
1790
1790
  class ScrollTarget {
1791
- constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1791
+ constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1792
+ // This data structure is abused to also store precise scroll
1793
+ // snapshots, instead of a `scrollIntoView` request. When this
1794
+ // flag is `true`, `range` points at a position in the reference
1795
+ // line, `yMargin` holds the difference between the top of that
1796
+ // line and the top of the editor, and `xMargin` holds the
1797
+ // editor's `scrollLeft`.
1798
+ isSnapshot = false) {
1792
1799
  this.range = range;
1793
1800
  this.y = y;
1794
1801
  this.x = x;
1795
1802
  this.yMargin = yMargin;
1796
1803
  this.xMargin = xMargin;
1804
+ this.isSnapshot = isSnapshot;
1797
1805
  }
1798
1806
  map(changes) {
1799
- return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1807
+ return changes.empty ? this :
1808
+ new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1809
+ }
1810
+ clip(state$1) {
1811
+ return this.range.to <= state$1.doc.length ? this :
1812
+ new ScrollTarget(state.EditorSelection.cursor(state$1.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1800
1813
  }
1801
1814
  }
1802
1815
  const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
@@ -3090,6 +3103,12 @@ class DocView extends ContentView {
3090
3103
  ];
3091
3104
  }
3092
3105
  scrollIntoView(target) {
3106
+ if (target.isSnapshot) {
3107
+ let ref = this.view.viewState.lineBlockAt(target.range.head);
3108
+ this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
3109
+ this.view.scrollDOM.scrollLeft = target.xMargin;
3110
+ return;
3111
+ }
3093
3112
  let { range } = target;
3094
3113
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3095
3114
  if (!rect)
@@ -3102,7 +3121,8 @@ class DocView extends ContentView {
3102
3121
  left: rect.left - margins.left, top: rect.top - margins.top,
3103
3122
  right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
3104
3123
  };
3105
- scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == exports.Direction.LTR);
3124
+ let { offsetWidth, offsetHeight } = this.view.scrollDOM;
3125
+ scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == exports.Direction.LTR);
3106
3126
  }
3107
3127
  }
3108
3128
  function betweenUneditable(pos) {
@@ -3639,6 +3659,9 @@ class InputState {
3639
3659
  // the mutation events fire shortly after the compositionend event
3640
3660
  this.compositionPendingChange = false;
3641
3661
  this.mouseSelection = null;
3662
+ // When a drag from the editor is active, this points at the range
3663
+ // being dragged.
3664
+ this.draggedContent = null;
3642
3665
  this.handleEvent = this.handleEvent.bind(this);
3643
3666
  this.notifiedFocused = view.hasFocus;
3644
3667
  // On Safari adding an input event handler somehow prevents an
@@ -3755,6 +3778,8 @@ class InputState {
3755
3778
  update(update) {
3756
3779
  if (this.mouseSelection)
3757
3780
  this.mouseSelection.update(update);
3781
+ if (this.draggedContent && update.docChanged)
3782
+ this.draggedContent = this.draggedContent.map(update.changes);
3758
3783
  if (update.transactions.length)
3759
3784
  this.lastKeyCode = this.lastSelectionTime = 0;
3760
3785
  }
@@ -3872,7 +3897,7 @@ class MouseSelection {
3872
3897
  let doc = this.view.contentDOM.ownerDocument;
3873
3898
  doc.removeEventListener("mousemove", this.move);
3874
3899
  doc.removeEventListener("mouseup", this.up);
3875
- this.view.inputState.mouseSelection = null;
3900
+ this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
3876
3901
  }
3877
3902
  setScrollSpeed(sx, sy) {
3878
3903
  this.scrollSpeed = { x: sx, y: sy };
@@ -3930,8 +3955,6 @@ class MouseSelection {
3930
3955
  this.mustSelect = false;
3931
3956
  }
3932
3957
  update(update) {
3933
- if (update.docChanged && this.dragging)
3934
- this.dragging = this.dragging.map(update.changes);
3935
3958
  if (this.style.update(update))
3936
3959
  setTimeout(() => this.select(this.lastEvent), 20);
3937
3960
  }
@@ -4159,23 +4182,36 @@ function removeRangeAround(sel, pos) {
4159
4182
  return null;
4160
4183
  }
4161
4184
  handlers.dragstart = (view, event) => {
4162
- let { selection: { main } } = view.state;
4163
- let { mouseSelection } = view.inputState;
4164
- if (mouseSelection)
4165
- mouseSelection.dragging = main;
4185
+ let { selection: { main: range } } = view.state;
4186
+ if (event.target.draggable) {
4187
+ let cView = view.docView.nearest(event.target);
4188
+ if (cView && cView.isWidget) {
4189
+ let from = cView.posAtStart, to = from + cView.length;
4190
+ if (from >= range.to || to <= range.from)
4191
+ range = state.EditorSelection.range(from, to);
4192
+ }
4193
+ }
4194
+ let { inputState } = view;
4195
+ if (inputState.mouseSelection)
4196
+ inputState.mouseSelection.dragging = true;
4197
+ inputState.draggedContent = range;
4166
4198
  if (event.dataTransfer) {
4167
- event.dataTransfer.setData("Text", view.state.sliceDoc(main.from, main.to));
4199
+ event.dataTransfer.setData("Text", view.state.sliceDoc(range.from, range.to));
4168
4200
  event.dataTransfer.effectAllowed = "copyMove";
4169
4201
  }
4170
4202
  return false;
4171
4203
  };
4204
+ handlers.dragend = view => {
4205
+ view.inputState.draggedContent = null;
4206
+ return false;
4207
+ };
4172
4208
  function dropText(view, event, text, direct) {
4173
4209
  if (!text)
4174
4210
  return;
4175
4211
  let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
4176
- let { mouseSelection } = view.inputState;
4177
- let del = direct && mouseSelection && mouseSelection.dragging && dragMovesSelection(view, event) ?
4178
- { from: mouseSelection.dragging.from, to: mouseSelection.dragging.to } : null;
4212
+ let { draggedContent } = view.inputState;
4213
+ let del = direct && draggedContent && dragMovesSelection(view, event)
4214
+ ? { from: draggedContent.from, to: draggedContent.to } : null;
4179
4215
  let ins = { from: dropPos, insert: text };
4180
4216
  let changes = view.state.changes(del ? [del, ins] : ins);
4181
4217
  view.focus();
@@ -4184,6 +4220,7 @@ function dropText(view, event, text, direct) {
4184
4220
  selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
4185
4221
  userEvent: del ? "move.drop" : "input.drop"
4186
4222
  });
4223
+ view.inputState.draggedContent = null;
4187
4224
  }
4188
4225
  handlers.drop = (view, event) => {
4189
4226
  if (!event.dataTransfer)
@@ -6371,7 +6408,6 @@ class DOMObserver {
6371
6408
  this.scrollTargets = [];
6372
6409
  this.intersection = null;
6373
6410
  this.resizeScroll = null;
6374
- this.resizeContent = null;
6375
6411
  this.intersecting = false;
6376
6412
  this.gapIntersection = null;
6377
6413
  this.gaps = [];
@@ -6415,8 +6451,6 @@ class DOMObserver {
6415
6451
  this.onResize();
6416
6452
  });
6417
6453
  this.resizeScroll.observe(view.scrollDOM);
6418
- this.resizeContent = new ResizeObserver(() => this.view.requestMeasure());
6419
- this.resizeContent.observe(view.contentDOM);
6420
6454
  }
6421
6455
  this.addWindowListeners(this.win = view.win);
6422
6456
  this.start();
@@ -6745,12 +6779,11 @@ class DOMObserver {
6745
6779
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6746
6780
  }
6747
6781
  destroy() {
6748
- var _a, _b, _c, _d;
6782
+ var _a, _b, _c;
6749
6783
  this.stop();
6750
6784
  (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
6751
6785
  (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
6752
6786
  (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
6753
- (_d = this.resizeContent) === null || _d === void 0 ? void 0 : _d.disconnect();
6754
6787
  for (let dom of this.scrollTargets)
6755
6788
  dom.removeEventListener("scroll", this.onScroll);
6756
6789
  this.removeWindowListeners(this.win);
@@ -6908,6 +6941,8 @@ class EditorView {
6908
6941
  this.dispatch = this.dispatch.bind(this);
6909
6942
  this._root = (config.root || getRoot(config.parent) || document);
6910
6943
  this.viewState = new ViewState(config.state || state.EditorState.create(config));
6944
+ if (config.scrollTo && config.scrollTo.is(scrollIntoView))
6945
+ this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
6911
6946
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
6912
6947
  for (let plugin of this.plugins)
6913
6948
  plugin.update(this);
@@ -6995,7 +7030,7 @@ class EditorView {
6995
7030
  }
6996
7031
  for (let e of tr.effects)
6997
7032
  if (e.is(scrollIntoView))
6998
- scrollTarget = e.value;
7033
+ scrollTarget = e.value.clip(this.state);
6999
7034
  }
7000
7035
  this.viewState.update(update, scrollTarget);
7001
7036
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
@@ -7018,8 +7053,14 @@ class EditorView {
7018
7053
  if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
7019
7054
  this.requestMeasure();
7020
7055
  if (!update.empty)
7021
- for (let listener of this.state.facet(updateListener))
7022
- listener(update);
7056
+ for (let listener of this.state.facet(updateListener)) {
7057
+ try {
7058
+ listener(update);
7059
+ }
7060
+ catch (e) {
7061
+ logException(this.state, e, "update listener");
7062
+ }
7063
+ }
7023
7064
  if (dispatchFocus || domChange)
7024
7065
  Promise.resolve().then(() => {
7025
7066
  if (dispatchFocus && this.state == dispatchFocus.startState)
@@ -7601,6 +7642,23 @@ class EditorView {
7601
7642
  return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
7602
7643
  }
7603
7644
  /**
7645
+ Return an effect that resets the editor to its current (at the
7646
+ time this method was called) scroll position. Note that this
7647
+ only affects the editor's own scrollable element, not parents.
7648
+ See also
7649
+ [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
7650
+
7651
+ The effect should be used with a document identical to the one
7652
+ it was created for. Failing to do so is not an error, but may
7653
+ not scroll to the expected position. You can
7654
+ [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
7655
+ */
7656
+ scrollSnapshot() {
7657
+ let { scrollTop, scrollLeft } = this.scrollDOM;
7658
+ let ref = this.viewState.scrollAnchorAt(scrollTop);
7659
+ return scrollIntoView.of(new ScrollTarget(state.EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
7660
+ }
7661
+ /**
7604
7662
  Returns an extension that can be used to add DOM event handlers.
7605
7663
  The value should be an object mapping event names to handler
7606
7664
  functions. For any given event, such functions are ordered by
@@ -9198,6 +9256,7 @@ const tooltipPlugin = ViewPlugin.fromClass(class {
9198
9256
  }
9199
9257
  tooltipView.dom.style.position = this.position;
9200
9258
  tooltipView.dom.style.top = Outside;
9259
+ tooltipView.dom.style.left = "0px";
9201
9260
  this.container.appendChild(tooltipView.dom);
9202
9261
  if (tooltipView.mount)
9203
9262
  tooltipView.mount(this.view);
@@ -9219,12 +9278,24 @@ const tooltipPlugin = ViewPlugin.fromClass(class {
9219
9278
  let editor = this.view.dom.getBoundingClientRect();
9220
9279
  let scaleX = 1, scaleY = 1, makeAbsolute = false;
9221
9280
  if (this.position == "fixed" && this.manager.tooltipViews.length) {
9222
- // When the dialog's offset parent isn't the body (Firefox) or
9223
- // null (Webkit), we are probably in a transformed container,
9224
- // and should use absolute positioning instead, since fixed
9225
- // positioning inside a transform works in a very broken way.
9226
- let { offsetParent } = this.manager.tooltipViews[0].dom;
9227
- makeAbsolute = !!(offsetParent && offsetParent != this.container.ownerDocument.body);
9281
+ let { dom } = this.manager.tooltipViews[0];
9282
+ if (browser.gecko) {
9283
+ // Firefox sets the element's `offsetParent` to the
9284
+ // transformed element when a transform interferes with fixed
9285
+ // positioning.
9286
+ makeAbsolute = dom.offsetParent != this.container.ownerDocument.body;
9287
+ }
9288
+ else {
9289
+ // On other browsers, we have to awkwardly try and use other
9290
+ // information to detect a transform.
9291
+ if (this.view.scaleX != 1 || this.view.scaleY != 1) {
9292
+ makeAbsolute = true;
9293
+ }
9294
+ else if (dom.style.top == Outside && dom.style.left == "0px") {
9295
+ let rect = dom.getBoundingClientRect();
9296
+ makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
9297
+ }
9298
+ }
9228
9299
  }
9229
9300
  if (makeAbsolute || this.position == "absolute") {
9230
9301
  if (this.parent) {
@@ -9450,6 +9521,23 @@ class HoverTooltipHost {
9450
9521
  for (let t of this.manager.tooltipViews)
9451
9522
  (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
9452
9523
  }
9524
+ passProp(name) {
9525
+ let value = undefined;
9526
+ for (let view of this.manager.tooltipViews) {
9527
+ let given = view[name];
9528
+ if (given !== undefined) {
9529
+ if (value === undefined)
9530
+ value = given;
9531
+ else if (value !== given)
9532
+ return undefined;
9533
+ }
9534
+ }
9535
+ return value;
9536
+ }
9537
+ get offset() { return this.passProp("offset"); }
9538
+ get getCoords() { return this.passProp("getCoords"); }
9539
+ get overlap() { return this.passProp("overlap"); }
9540
+ get resize() { return this.passProp("resize"); }
9453
9541
  }
9454
9542
  const showHoverTooltipHost = showTooltip.compute([showHoverTooltip], state => {
9455
9543
  let tooltips = state.facet(showHoverTooltip).filter(t => t);
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { RangeSet, RangeValue, Range, EditorState, Extension, Transaction, ChangeSet, EditorSelection, EditorStateConfig, TransactionSpec, SelectionRange, Line, StateEffect, Facet } from '@codemirror/state';
2
+ import { RangeSet, RangeValue, Range, EditorState, Extension, Transaction, ChangeSet, SelectionRange, ChangeDesc, EditorSelection, EditorStateConfig, StateEffect, TransactionSpec, Line, Facet } from '@codemirror/state';
3
3
  import { StyleModule, StyleSpec } from 'style-mod';
4
4
 
5
5
  /**
@@ -361,6 +361,17 @@ apply to the editor, and if it can, perform it as a side effect
361
361
  transaction) and return `true`.
362
362
  */
363
363
  type Command = (target: EditorView) => boolean;
364
+ declare class ScrollTarget {
365
+ readonly range: SelectionRange;
366
+ readonly y: ScrollStrategy;
367
+ readonly x: ScrollStrategy;
368
+ readonly yMargin: number;
369
+ readonly xMargin: number;
370
+ readonly isSnapshot: boolean;
371
+ constructor(range: SelectionRange, y?: ScrollStrategy, x?: ScrollStrategy, yMargin?: number, xMargin?: number, isSnapshot?: boolean);
372
+ map(changes: ChangeDesc): ScrollTarget;
373
+ clip(state: EditorState): ScrollTarget;
374
+ }
364
375
  /**
365
376
  Log or report an unhandled exception in client code. Should
366
377
  probably only be used by extension code that allows client code to
@@ -633,6 +644,13 @@ interface EditorViewConfig extends EditorStateConfig {
633
644
  */
634
645
  root?: Document | ShadowRoot;
635
646
  /**
647
+ Pass an effect created with
648
+ [`EditorView.scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) or
649
+ [`EditorView.scrollSnapshot`](https://codemirror.net/6/docs/ref/#view.EditorView.scrollSnapshot)
650
+ here to set an initial scroll position.
651
+ */
652
+ scrollTo?: StateEffect<any>;
653
+ /**
636
654
  Override the way transactions are
637
655
  [dispatched](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) for this editor view.
638
656
  Your implementation, if provided, should probably call the
@@ -1032,15 +1050,30 @@ declare class EditorView {
1032
1050
  /**
1033
1051
  Extra vertical distance to add when moving something into
1034
1052
  view. Not used with the `"center"` strategy. Defaults to 5.
1053
+ Must be less than the height of the editor.
1035
1054
  */
1036
1055
  yMargin?: number;
1037
1056
  /**
1038
1057
  Extra horizontal distance to add. Not used with the `"center"`
1039
- strategy. Defaults to 5.
1058
+ strategy. Defaults to 5. Must be less than the width of the
1059
+ editor.
1040
1060
  */
1041
1061
  xMargin?: number;
1042
1062
  }): StateEffect<unknown>;
1043
1063
  /**
1064
+ Return an effect that resets the editor to its current (at the
1065
+ time this method was called) scroll position. Note that this
1066
+ only affects the editor's own scrollable element, not parents.
1067
+ See also
1068
+ [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
1069
+
1070
+ The effect should be used with a document identical to the one
1071
+ it was created for. Failing to do so is not an error, but may
1072
+ not scroll to the expected position. You can
1073
+ [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
1074
+ */
1075
+ scrollSnapshot(): StateEffect<ScrollTarget>;
1076
+ /**
1044
1077
  Facet to add a [style
1045
1078
  module](https://github.com/marijnh/style-mod#documentation) to
1046
1079
  an editor view. The view will ensure that the module is
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { RangeSet, RangeValue, Range, EditorState, Extension, Transaction, ChangeSet, EditorSelection, EditorStateConfig, TransactionSpec, SelectionRange, Line, StateEffect, Facet } from '@codemirror/state';
2
+ import { RangeSet, RangeValue, Range, EditorState, Extension, Transaction, ChangeSet, SelectionRange, ChangeDesc, EditorSelection, EditorStateConfig, StateEffect, TransactionSpec, Line, Facet } from '@codemirror/state';
3
3
  import { StyleModule, StyleSpec } from 'style-mod';
4
4
 
5
5
  /**
@@ -361,6 +361,17 @@ apply to the editor, and if it can, perform it as a side effect
361
361
  transaction) and return `true`.
362
362
  */
363
363
  type Command = (target: EditorView) => boolean;
364
+ declare class ScrollTarget {
365
+ readonly range: SelectionRange;
366
+ readonly y: ScrollStrategy;
367
+ readonly x: ScrollStrategy;
368
+ readonly yMargin: number;
369
+ readonly xMargin: number;
370
+ readonly isSnapshot: boolean;
371
+ constructor(range: SelectionRange, y?: ScrollStrategy, x?: ScrollStrategy, yMargin?: number, xMargin?: number, isSnapshot?: boolean);
372
+ map(changes: ChangeDesc): ScrollTarget;
373
+ clip(state: EditorState): ScrollTarget;
374
+ }
364
375
  /**
365
376
  Log or report an unhandled exception in client code. Should
366
377
  probably only be used by extension code that allows client code to
@@ -633,6 +644,13 @@ interface EditorViewConfig extends EditorStateConfig {
633
644
  */
634
645
  root?: Document | ShadowRoot;
635
646
  /**
647
+ Pass an effect created with
648
+ [`EditorView.scrollIntoView`](https://codemirror.net/6/docs/ref/#view.EditorView^scrollIntoView) or
649
+ [`EditorView.scrollSnapshot`](https://codemirror.net/6/docs/ref/#view.EditorView.scrollSnapshot)
650
+ here to set an initial scroll position.
651
+ */
652
+ scrollTo?: StateEffect<any>;
653
+ /**
636
654
  Override the way transactions are
637
655
  [dispatched](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) for this editor view.
638
656
  Your implementation, if provided, should probably call the
@@ -1032,15 +1050,30 @@ declare class EditorView {
1032
1050
  /**
1033
1051
  Extra vertical distance to add when moving something into
1034
1052
  view. Not used with the `"center"` strategy. Defaults to 5.
1053
+ Must be less than the height of the editor.
1035
1054
  */
1036
1055
  yMargin?: number;
1037
1056
  /**
1038
1057
  Extra horizontal distance to add. Not used with the `"center"`
1039
- strategy. Defaults to 5.
1058
+ strategy. Defaults to 5. Must be less than the width of the
1059
+ editor.
1040
1060
  */
1041
1061
  xMargin?: number;
1042
1062
  }): StateEffect<unknown>;
1043
1063
  /**
1064
+ Return an effect that resets the editor to its current (at the
1065
+ time this method was called) scroll position. Note that this
1066
+ only affects the editor's own scrollable element, not parents.
1067
+ See also
1068
+ [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
1069
+
1070
+ The effect should be used with a document identical to the one
1071
+ it was created for. Failing to do so is not an error, but may
1072
+ not scroll to the expected position. You can
1073
+ [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
1074
+ */
1075
+ scrollSnapshot(): StateEffect<ScrollTarget>;
1076
+ /**
1044
1077
  Facet to add a [style
1045
1078
  module](https://github.com/marijnh/style-mod#documentation) to
1046
1079
  an editor view. The view will ensure that the module is
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Text, RangeSet, MapMode, RangeValue, Facet, StateEffect, ChangeSet, findClusterBreak, EditorSelection, findColumn, CharCategory, Annotation, EditorState, Transaction, Prec, codePointAt, codePointSize, combineConfig, StateField, RangeSetBuilder, countColumn } from '@codemirror/state';
1
+ import { Text, RangeSet, MapMode, RangeValue, Facet, StateEffect, ChangeSet, EditorSelection, findClusterBreak, findColumn, CharCategory, Annotation, EditorState, Transaction, Prec, codePointAt, codePointSize, combineConfig, StateField, RangeSetBuilder, countColumn } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { keyName, base, shift } from 'w3c-keyname';
4
4
 
@@ -1785,15 +1785,28 @@ const nativeSelectionHidden = /*@__PURE__*/Facet.define({
1785
1785
  combine: values => values.some(x => x)
1786
1786
  });
1787
1787
  class ScrollTarget {
1788
- constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1788
+ constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1789
+ // This data structure is abused to also store precise scroll
1790
+ // snapshots, instead of a `scrollIntoView` request. When this
1791
+ // flag is `true`, `range` points at a position in the reference
1792
+ // line, `yMargin` holds the difference between the top of that
1793
+ // line and the top of the editor, and `xMargin` holds the
1794
+ // editor's `scrollLeft`.
1795
+ isSnapshot = false) {
1789
1796
  this.range = range;
1790
1797
  this.y = y;
1791
1798
  this.x = x;
1792
1799
  this.yMargin = yMargin;
1793
1800
  this.xMargin = xMargin;
1801
+ this.isSnapshot = isSnapshot;
1794
1802
  }
1795
1803
  map(changes) {
1796
- return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1804
+ return changes.empty ? this :
1805
+ new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1806
+ }
1807
+ clip(state) {
1808
+ return this.range.to <= state.doc.length ? this :
1809
+ new ScrollTarget(EditorSelection.cursor(state.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1797
1810
  }
1798
1811
  }
1799
1812
  const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
@@ -3086,6 +3099,12 @@ class DocView extends ContentView {
3086
3099
  ];
3087
3100
  }
3088
3101
  scrollIntoView(target) {
3102
+ if (target.isSnapshot) {
3103
+ let ref = this.view.viewState.lineBlockAt(target.range.head);
3104
+ this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
3105
+ this.view.scrollDOM.scrollLeft = target.xMargin;
3106
+ return;
3107
+ }
3089
3108
  let { range } = target;
3090
3109
  let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3091
3110
  if (!rect)
@@ -3098,7 +3117,8 @@ class DocView extends ContentView {
3098
3117
  left: rect.left - margins.left, top: rect.top - margins.top,
3099
3118
  right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
3100
3119
  };
3101
- scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == Direction.LTR);
3120
+ let { offsetWidth, offsetHeight } = this.view.scrollDOM;
3121
+ scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == Direction.LTR);
3102
3122
  }
3103
3123
  }
3104
3124
  function betweenUneditable(pos) {
@@ -3635,6 +3655,9 @@ class InputState {
3635
3655
  // the mutation events fire shortly after the compositionend event
3636
3656
  this.compositionPendingChange = false;
3637
3657
  this.mouseSelection = null;
3658
+ // When a drag from the editor is active, this points at the range
3659
+ // being dragged.
3660
+ this.draggedContent = null;
3638
3661
  this.handleEvent = this.handleEvent.bind(this);
3639
3662
  this.notifiedFocused = view.hasFocus;
3640
3663
  // On Safari adding an input event handler somehow prevents an
@@ -3751,6 +3774,8 @@ class InputState {
3751
3774
  update(update) {
3752
3775
  if (this.mouseSelection)
3753
3776
  this.mouseSelection.update(update);
3777
+ if (this.draggedContent && update.docChanged)
3778
+ this.draggedContent = this.draggedContent.map(update.changes);
3754
3779
  if (update.transactions.length)
3755
3780
  this.lastKeyCode = this.lastSelectionTime = 0;
3756
3781
  }
@@ -3868,7 +3893,7 @@ class MouseSelection {
3868
3893
  let doc = this.view.contentDOM.ownerDocument;
3869
3894
  doc.removeEventListener("mousemove", this.move);
3870
3895
  doc.removeEventListener("mouseup", this.up);
3871
- this.view.inputState.mouseSelection = null;
3896
+ this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
3872
3897
  }
3873
3898
  setScrollSpeed(sx, sy) {
3874
3899
  this.scrollSpeed = { x: sx, y: sy };
@@ -3926,8 +3951,6 @@ class MouseSelection {
3926
3951
  this.mustSelect = false;
3927
3952
  }
3928
3953
  update(update) {
3929
- if (update.docChanged && this.dragging)
3930
- this.dragging = this.dragging.map(update.changes);
3931
3954
  if (this.style.update(update))
3932
3955
  setTimeout(() => this.select(this.lastEvent), 20);
3933
3956
  }
@@ -4155,23 +4178,36 @@ function removeRangeAround(sel, pos) {
4155
4178
  return null;
4156
4179
  }
4157
4180
  handlers.dragstart = (view, event) => {
4158
- let { selection: { main } } = view.state;
4159
- let { mouseSelection } = view.inputState;
4160
- if (mouseSelection)
4161
- mouseSelection.dragging = main;
4181
+ let { selection: { main: range } } = view.state;
4182
+ if (event.target.draggable) {
4183
+ let cView = view.docView.nearest(event.target);
4184
+ if (cView && cView.isWidget) {
4185
+ let from = cView.posAtStart, to = from + cView.length;
4186
+ if (from >= range.to || to <= range.from)
4187
+ range = EditorSelection.range(from, to);
4188
+ }
4189
+ }
4190
+ let { inputState } = view;
4191
+ if (inputState.mouseSelection)
4192
+ inputState.mouseSelection.dragging = true;
4193
+ inputState.draggedContent = range;
4162
4194
  if (event.dataTransfer) {
4163
- event.dataTransfer.setData("Text", view.state.sliceDoc(main.from, main.to));
4195
+ event.dataTransfer.setData("Text", view.state.sliceDoc(range.from, range.to));
4164
4196
  event.dataTransfer.effectAllowed = "copyMove";
4165
4197
  }
4166
4198
  return false;
4167
4199
  };
4200
+ handlers.dragend = view => {
4201
+ view.inputState.draggedContent = null;
4202
+ return false;
4203
+ };
4168
4204
  function dropText(view, event, text, direct) {
4169
4205
  if (!text)
4170
4206
  return;
4171
4207
  let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
4172
- let { mouseSelection } = view.inputState;
4173
- let del = direct && mouseSelection && mouseSelection.dragging && dragMovesSelection(view, event) ?
4174
- { from: mouseSelection.dragging.from, to: mouseSelection.dragging.to } : null;
4208
+ let { draggedContent } = view.inputState;
4209
+ let del = direct && draggedContent && dragMovesSelection(view, event)
4210
+ ? { from: draggedContent.from, to: draggedContent.to } : null;
4175
4211
  let ins = { from: dropPos, insert: text };
4176
4212
  let changes = view.state.changes(del ? [del, ins] : ins);
4177
4213
  view.focus();
@@ -4180,6 +4216,7 @@ function dropText(view, event, text, direct) {
4180
4216
  selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
4181
4217
  userEvent: del ? "move.drop" : "input.drop"
4182
4218
  });
4219
+ view.inputState.draggedContent = null;
4183
4220
  }
4184
4221
  handlers.drop = (view, event) => {
4185
4222
  if (!event.dataTransfer)
@@ -6366,7 +6403,6 @@ class DOMObserver {
6366
6403
  this.scrollTargets = [];
6367
6404
  this.intersection = null;
6368
6405
  this.resizeScroll = null;
6369
- this.resizeContent = null;
6370
6406
  this.intersecting = false;
6371
6407
  this.gapIntersection = null;
6372
6408
  this.gaps = [];
@@ -6410,8 +6446,6 @@ class DOMObserver {
6410
6446
  this.onResize();
6411
6447
  });
6412
6448
  this.resizeScroll.observe(view.scrollDOM);
6413
- this.resizeContent = new ResizeObserver(() => this.view.requestMeasure());
6414
- this.resizeContent.observe(view.contentDOM);
6415
6449
  }
6416
6450
  this.addWindowListeners(this.win = view.win);
6417
6451
  this.start();
@@ -6740,12 +6774,11 @@ class DOMObserver {
6740
6774
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6741
6775
  }
6742
6776
  destroy() {
6743
- var _a, _b, _c, _d;
6777
+ var _a, _b, _c;
6744
6778
  this.stop();
6745
6779
  (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
6746
6780
  (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
6747
6781
  (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
6748
- (_d = this.resizeContent) === null || _d === void 0 ? void 0 : _d.disconnect();
6749
6782
  for (let dom of this.scrollTargets)
6750
6783
  dom.removeEventListener("scroll", this.onScroll);
6751
6784
  this.removeWindowListeners(this.win);
@@ -6903,6 +6936,8 @@ class EditorView {
6903
6936
  this.dispatch = this.dispatch.bind(this);
6904
6937
  this._root = (config.root || getRoot(config.parent) || document);
6905
6938
  this.viewState = new ViewState(config.state || EditorState.create(config));
6939
+ if (config.scrollTo && config.scrollTo.is(scrollIntoView))
6940
+ this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
6906
6941
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
6907
6942
  for (let plugin of this.plugins)
6908
6943
  plugin.update(this);
@@ -6990,7 +7025,7 @@ class EditorView {
6990
7025
  }
6991
7026
  for (let e of tr.effects)
6992
7027
  if (e.is(scrollIntoView))
6993
- scrollTarget = e.value;
7028
+ scrollTarget = e.value.clip(this.state);
6994
7029
  }
6995
7030
  this.viewState.update(update, scrollTarget);
6996
7031
  this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
@@ -7013,8 +7048,14 @@ class EditorView {
7013
7048
  if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
7014
7049
  this.requestMeasure();
7015
7050
  if (!update.empty)
7016
- for (let listener of this.state.facet(updateListener))
7017
- listener(update);
7051
+ for (let listener of this.state.facet(updateListener)) {
7052
+ try {
7053
+ listener(update);
7054
+ }
7055
+ catch (e) {
7056
+ logException(this.state, e, "update listener");
7057
+ }
7058
+ }
7018
7059
  if (dispatchFocus || domChange)
7019
7060
  Promise.resolve().then(() => {
7020
7061
  if (dispatchFocus && this.state == dispatchFocus.startState)
@@ -7596,6 +7637,23 @@ class EditorView {
7596
7637
  return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
7597
7638
  }
7598
7639
  /**
7640
+ Return an effect that resets the editor to its current (at the
7641
+ time this method was called) scroll position. Note that this
7642
+ only affects the editor's own scrollable element, not parents.
7643
+ See also
7644
+ [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
7645
+
7646
+ The effect should be used with a document identical to the one
7647
+ it was created for. Failing to do so is not an error, but may
7648
+ not scroll to the expected position. You can
7649
+ [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
7650
+ */
7651
+ scrollSnapshot() {
7652
+ let { scrollTop, scrollLeft } = this.scrollDOM;
7653
+ let ref = this.viewState.scrollAnchorAt(scrollTop);
7654
+ return scrollIntoView.of(new ScrollTarget(EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
7655
+ }
7656
+ /**
7599
7657
  Returns an extension that can be used to add DOM event handlers.
7600
7658
  The value should be an object mapping event names to handler
7601
7659
  functions. For any given event, such functions are ordered by
@@ -9193,6 +9251,7 @@ const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
9193
9251
  }
9194
9252
  tooltipView.dom.style.position = this.position;
9195
9253
  tooltipView.dom.style.top = Outside;
9254
+ tooltipView.dom.style.left = "0px";
9196
9255
  this.container.appendChild(tooltipView.dom);
9197
9256
  if (tooltipView.mount)
9198
9257
  tooltipView.mount(this.view);
@@ -9214,12 +9273,24 @@ const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
9214
9273
  let editor = this.view.dom.getBoundingClientRect();
9215
9274
  let scaleX = 1, scaleY = 1, makeAbsolute = false;
9216
9275
  if (this.position == "fixed" && this.manager.tooltipViews.length) {
9217
- // When the dialog's offset parent isn't the body (Firefox) or
9218
- // null (Webkit), we are probably in a transformed container,
9219
- // and should use absolute positioning instead, since fixed
9220
- // positioning inside a transform works in a very broken way.
9221
- let { offsetParent } = this.manager.tooltipViews[0].dom;
9222
- makeAbsolute = !!(offsetParent && offsetParent != this.container.ownerDocument.body);
9276
+ let { dom } = this.manager.tooltipViews[0];
9277
+ if (browser.gecko) {
9278
+ // Firefox sets the element's `offsetParent` to the
9279
+ // transformed element when a transform interferes with fixed
9280
+ // positioning.
9281
+ makeAbsolute = dom.offsetParent != this.container.ownerDocument.body;
9282
+ }
9283
+ else {
9284
+ // On other browsers, we have to awkwardly try and use other
9285
+ // information to detect a transform.
9286
+ if (this.view.scaleX != 1 || this.view.scaleY != 1) {
9287
+ makeAbsolute = true;
9288
+ }
9289
+ else if (dom.style.top == Outside && dom.style.left == "0px") {
9290
+ let rect = dom.getBoundingClientRect();
9291
+ makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
9292
+ }
9293
+ }
9223
9294
  }
9224
9295
  if (makeAbsolute || this.position == "absolute") {
9225
9296
  if (this.parent) {
@@ -9445,6 +9516,23 @@ class HoverTooltipHost {
9445
9516
  for (let t of this.manager.tooltipViews)
9446
9517
  (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
9447
9518
  }
9519
+ passProp(name) {
9520
+ let value = undefined;
9521
+ for (let view of this.manager.tooltipViews) {
9522
+ let given = view[name];
9523
+ if (given !== undefined) {
9524
+ if (value === undefined)
9525
+ value = given;
9526
+ else if (value !== given)
9527
+ return undefined;
9528
+ }
9529
+ }
9530
+ return value;
9531
+ }
9532
+ get offset() { return this.passProp("offset"); }
9533
+ get getCoords() { return this.passProp("getCoords"); }
9534
+ get overlap() { return this.passProp("overlap"); }
9535
+ get resize() { return this.passProp("resize"); }
9448
9536
  }
9449
9537
  const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip], state => {
9450
9538
  let tooltips = state.facet(showHoverTooltip).filter(t => t);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.21.3",
3
+ "version": "6.22.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",