@codemirror/view 6.41.0 → 6.42.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,21 @@
1
+ ## 6.42.0 (2026-05-06)
2
+
3
+ ### Bug fixes
4
+
5
+ Make sure `posAtCoords` doesn't recurse endlessly.
6
+
7
+ ### New features
8
+
9
+ The new `activateHover` function can be used to explicitly activate hover tooltips at a given position.
10
+
11
+ `closeHoverTooltip` now allows you to close a specific hover tooltip.
12
+
13
+ ## 6.41.1 (2026-04-18)
14
+
15
+ ### Bug fixes
16
+
17
+ Fix an issue where, on some platforms, clicking after the end of a wrapped line would put the cursor in the wrong position.
18
+
1
19
  ## 6.41.0 (2026-04-01)
2
20
 
3
21
  ### Bug fixes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @codemirror/view [![NPM version](https://img.shields.io/npm/v/@codemirror/view.svg)](https://www.npmjs.org/package/@codemirror/view)
2
2
 
3
- [ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#view) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/) | [**CHANGELOG**](https://github.com/codemirror/view/blob/main/CHANGELOG.md) ]
3
+ [ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#view) | [**ISSUES**](https://code.haverbeke.berlin/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/) | [**CHANGELOG**](https://code.haverbeke.berlin/codemirror/view/src/branch/main/CHANGELOG.md) ]
4
4
 
5
5
  This package implements the DOM view component for the
6
6
  [CodeMirror](https://codemirror.net/) code editor.
@@ -10,7 +10,7 @@ number of [examples](https://codemirror.net/examples/) and the
10
10
  [documentation](https://codemirror.net/docs/).
11
11
 
12
12
  This code is released under an
13
- [MIT license](https://github.com/codemirror/view/tree/main/LICENSE).
13
+ [MIT license](https://code.haverbeke.berlin/codemirror/view/tree/main/LICENSE).
14
14
 
15
15
  We aim to be an inclusive, welcoming community. To make that explicit,
16
16
  we have a [code of
package/dist/index.cjs CHANGED
@@ -3835,7 +3835,7 @@ class InlineCoordsScan {
3835
3835
  // (including the position after the last piece). For a text tile,
3836
3836
  // these will be character clusters, for a composite tile, these
3837
3837
  // will be child tiles.
3838
- scan(positions, getRects) {
3838
+ scan(positions, getRects, recursed = false) {
3839
3839
  let lo = 0, hi = positions.length - 1, seen = new Set();
3840
3840
  let bidi = this.bidiIn(positions[0], positions[hi]);
3841
3841
  let above, below;
@@ -3908,18 +3908,19 @@ class InlineCoordsScan {
3908
3908
  if (!closestRect) {
3909
3909
  let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
3910
3910
  this.y = (side.top + side.bottom) / 2;
3911
- return this.scan(positions, getRects);
3911
+ return this.scan(positions, getRects, true);
3912
3912
  }
3913
3913
  // Handle the case where closest matched a higher element on the
3914
3914
  // same line as an element below/above the coords
3915
- if (closestDx) {
3916
- if (above && above.bottom > closestRect.top) {
3915
+ if (closestDx && !recursed) {
3916
+ let { top, bottom } = closestRect;
3917
+ if (above && above.bottom > (top + top + bottom) / 3) {
3917
3918
  this.y = above.bottom - 1;
3918
- return this.scan(positions, getRects);
3919
+ return this.scan(positions, getRects, true);
3919
3920
  }
3920
- if (below && below.top < closestRect.bottom) {
3921
+ if (below && below.top < (top + bottom + bottom) / 3) {
3921
3922
  this.y = below.top + 1;
3922
- return this.scan(positions, getRects);
3923
+ return this.scan(positions, getRects, true);
3923
3924
  }
3924
3925
  }
3925
3926
  let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == exports.Direction.LTR;
@@ -5213,7 +5214,7 @@ observers.compositionend = view => {
5213
5214
  view.inputState.compositionFirstChange = null;
5214
5215
  if (browser.chrome && browser.android) {
5215
5216
  // Delay flushing for a bit on Android because it'll often fire a
5216
- // bunch of contradictory changes in a row at end of compositon
5217
+ // bunch of contradictory changes in a row at end of composition
5217
5218
  view.observer.flushSoon();
5218
5219
  }
5219
5220
  else if (view.inputState.compositionPendingChange) {
@@ -5288,8 +5289,8 @@ handlers.beforeinput = (view, event) => {
5288
5289
  const appliedFirefoxHack = new Set;
5289
5290
  // In Firefox, when cut/copy handlers are added to the document, that
5290
5291
  // somehow avoids a bug where those events aren't fired when the
5291
- // selection is empty. See https://github.com/codemirror/dev/issues/1082
5292
- // and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5292
+ // selection is empty. See issue #1082 and
5293
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5293
5294
  function firefoxCopyCutHack(doc) {
5294
5295
  if (!appliedFirefoxHack.has(doc)) {
5295
5296
  appliedFirefoxHack.add(doc);
@@ -6776,7 +6777,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
6776
6777
  flexShrink: 0,
6777
6778
  display: "block",
6778
6779
  whiteSpace: "pre",
6779
- wordWrap: "normal", // https://github.com/codemirror/dev/issues/456
6780
+ wordWrap: "normal", // Issue #456
6780
6781
  boxSizing: "border-box",
6781
6782
  minHeight: "100%",
6782
6783
  padding: "4px 0",
@@ -7195,7 +7196,7 @@ class DOMObserver {
7195
7196
  readSelectionRange() {
7196
7197
  let { view } = this;
7197
7198
  // The Selection object is broken in shadow roots in Safari. See
7198
- // https://github.com/codemirror/dev/issues/414
7199
+ // issue #414
7199
7200
  let selection = getSelection(view.root);
7200
7201
  if (!selection)
7201
7202
  return false;
@@ -8664,7 +8665,7 @@ class EditorView {
8664
8665
  }
8665
8666
  /**
8666
8667
  Create a theme extension. The first argument can be a
8667
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)
8668
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)
8668
8669
  style spec providing the styles for the theme. These will be
8669
8670
  prefixed with a generated class for the style.
8670
8671
 
@@ -8710,7 +8711,7 @@ class EditorView {
8710
8711
  }
8711
8712
  /**
8712
8713
  Facet to add a [style
8713
- module](https://github.com/marijnh/style-mod#documentation) to
8714
+ module](https://code.haverbeke.berlin/marijn/style-mod#documentation) to
8714
8715
  an editor view. The view will ensure that the module is
8715
8716
  mounted in its [document
8716
8717
  root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
@@ -10637,11 +10638,13 @@ const showHoverTooltipHost = showTooltip.compute([showHoverTooltip], state => {
10637
10638
  arrow: tooltips.some(t => t.arrow),
10638
10639
  };
10639
10640
  });
10641
+ const hoverPlugin = state.Facet.define();
10640
10642
  class HoverPlugin {
10641
- constructor(view, source, field, setHover, hoverTime) {
10643
+ constructor(view, source, field, locked, setHover, hoverTime) {
10642
10644
  this.view = view;
10643
10645
  this.source = source;
10644
10646
  this.field = field;
10647
+ this.locked = locked;
10645
10648
  this.setHover = setHover;
10646
10649
  this.hoverTime = hoverTime;
10647
10650
  this.hoverTimeout = -1;
@@ -10652,7 +10655,7 @@ class HoverPlugin {
10652
10655
  view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
10653
10656
  view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
10654
10657
  }
10655
- update() {
10658
+ update(update) {
10656
10659
  if (this.pending) {
10657
10660
  this.pending = null;
10658
10661
  clearTimeout(this.restartTimeout);
@@ -10696,19 +10699,29 @@ class HoverPlugin {
10696
10699
  let rtl = bidi && bidi.dir == exports.Direction.RTL ? -1 : 1;
10697
10700
  side = (lastMove.x < posCoords.left ? -rtl : rtl);
10698
10701
  }
10702
+ this.activateHover(view, pos, side);
10703
+ }
10704
+ activateHover(view, pos, side, locked) {
10699
10705
  let open = this.source(view, pos, side);
10700
- if (open === null || open === void 0 ? void 0 : open.then) {
10706
+ let done = (value) => {
10707
+ if (value && !(Array.isArray(value) && !value.length)) {
10708
+ let tooltips = Array.isArray(value) ? value : [value];
10709
+ if (locked)
10710
+ this.locked.set(tooltips, locked);
10711
+ view.dispatch({ effects: this.setHover.of(tooltips) });
10712
+ }
10713
+ };
10714
+ if (open && "then" in open) {
10701
10715
  let pending = this.pending = { pos };
10702
10716
  open.then(result => {
10703
10717
  if (this.pending == pending) {
10704
10718
  this.pending = null;
10705
- if (result && !(Array.isArray(result) && !result.length))
10706
- view.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
10719
+ done(result);
10707
10720
  }
10708
10721
  }, e => logException(view.state, e, "hover tooltip"));
10709
10722
  }
10710
- else if (open && !(Array.isArray(open) && !open.length)) {
10711
- view.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
10723
+ else {
10724
+ done(open);
10712
10725
  }
10713
10726
  }
10714
10727
  get tooltip() {
@@ -10722,7 +10735,7 @@ class HoverPlugin {
10722
10735
  if (this.hoverTimeout < 0)
10723
10736
  this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
10724
10737
  let { active, tooltip } = this;
10725
- if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10738
+ if (active.length && !this.locked.has(active) && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10726
10739
  let { pos } = active[0] || this.pending, end = (_b = (_a = active[0]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : pos;
10727
10740
  if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
10728
10741
  : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
@@ -10735,7 +10748,7 @@ class HoverPlugin {
10735
10748
  clearTimeout(this.hoverTimeout);
10736
10749
  this.hoverTimeout = -1;
10737
10750
  let { active } = this;
10738
- if (active.length) {
10751
+ if (active.length && !this.locked.has(active)) {
10739
10752
  let { tooltip } = this;
10740
10753
  let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
10741
10754
  if (!inTooltip)
@@ -10747,7 +10760,8 @@ class HoverPlugin {
10747
10760
  watchTooltipLeave(tooltip) {
10748
10761
  let watch = (event) => {
10749
10762
  tooltip.removeEventListener("mouseleave", watch);
10750
- if (this.active.length && !this.view.dom.contains(event.relatedTarget))
10763
+ let { active } = this;
10764
+ if (active.length && !this.locked.has(active) && !this.view.dom.contains(event.relatedTarget))
10751
10765
  this.view.dispatch({ effects: this.setHover.of([]) });
10752
10766
  };
10753
10767
  tooltip.addEventListener("mouseleave", watch);
@@ -10798,49 +10812,85 @@ extension.
10798
10812
  */
10799
10813
  function hoverTooltip(source, options = {}) {
10800
10814
  let setHover = state.StateEffect.define();
10815
+ // This would be better stored in the state field, but we've set
10816
+ // down the type of the field in our interface, so it's indirectly
10817
+ // stored by array identity.
10818
+ let locked = new WeakMap();
10801
10819
  let hoverState = state.StateField.define({
10802
10820
  create() { return []; },
10803
10821
  update(value, tr) {
10822
+ let lock = locked.get(value);
10804
10823
  if (value.length) {
10805
10824
  if (options.hideOnChange && (tr.docChanged || tr.selection))
10806
10825
  value = [];
10826
+ else if (lock && lock(tr))
10827
+ value = [];
10807
10828
  else if (options.hideOn)
10808
10829
  value = value.filter(v => !options.hideOn(tr, v));
10809
- if (tr.docChanged) {
10810
- let mapped = [];
10811
- for (let tooltip of value) {
10812
- let newPos = tr.changes.mapPos(tooltip.pos, -1, state.MapMode.TrackDel);
10813
- if (newPos != null) {
10814
- let copy = Object.assign(Object.create(null), tooltip);
10815
- copy.pos = newPos;
10816
- if (copy.end != null)
10817
- copy.end = tr.changes.mapPos(copy.end);
10818
- mapped.push(copy);
10819
- }
10830
+ }
10831
+ if (tr.docChanged && value.length) {
10832
+ let mapped = [];
10833
+ for (let tooltip of value) {
10834
+ let newPos = tr.changes.mapPos(tooltip.pos, -1, state.MapMode.TrackDel);
10835
+ if (newPos != null) {
10836
+ let copy = Object.assign(Object.create(null), tooltip);
10837
+ copy.pos = newPos;
10838
+ if (copy.end != null)
10839
+ copy.end = tr.changes.mapPos(copy.end);
10840
+ mapped.push(copy);
10820
10841
  }
10821
- value = mapped;
10822
10842
  }
10843
+ value = mapped;
10823
10844
  }
10824
10845
  for (let effect of tr.effects) {
10825
- if (effect.is(setHover))
10846
+ if (effect.is(setHover)) {
10826
10847
  value = effect.value;
10827
- if (effect.is(closeHoverTooltipEffect))
10848
+ lock = undefined;
10849
+ }
10850
+ if (effect.is(closeHoverTooltipEffect) && !effect.value || effect.value == hoverState)
10828
10851
  value = [];
10829
10852
  }
10853
+ if (value.length && lock)
10854
+ locked.set(value, lock);
10830
10855
  return value;
10831
10856
  },
10832
10857
  provide: f => showHoverTooltip.from(f)
10833
10858
  });
10859
+ const plugin = ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, locked, setHover, options.hoverTime || 300 /* Hover.Time */));
10834
10860
  return {
10835
10861
  active: hoverState,
10836
10862
  extension: [
10837
10863
  hoverState,
10838
- ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Hover.Time */)),
10864
+ plugin,
10865
+ hoverPlugin.of(plugin),
10839
10866
  showHoverTooltipHost
10840
10867
  ]
10841
10868
  };
10842
10869
  }
10843
10870
  /**
10871
+ Activate hover tooltips for the given position and side. If you
10872
+ provide a specific hover tooltip (the value returned from
10873
+ [`hoverTooltip`](https://codemirror.net/6/docs/ref/#view.hoverTooltip)), only that one will be
10874
+ activated. If not given, all hover tooltips at the given position
10875
+ are triggered.
10876
+
10877
+ Note that tooltips opened this way don't close automatically, and
10878
+ you'll want to pass an `until` callback or use
10879
+ [`closeHoverTooltip`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltip)/[`closeHoverTooltips`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltips)
10880
+ to deactivate them.
10881
+ */
10882
+ function activateHover(view, pos, side, options = {}) {
10883
+ var _a;
10884
+ let plugins = view.state.facet(hoverPlugin).map(p => view.plugin(p)).filter((p) => !!p);
10885
+ if (options.tooltip && options.tooltip.active) {
10886
+ let found = plugins.find(p => p.field == options.tooltip.active);
10887
+ if (found)
10888
+ plugins = [found];
10889
+ }
10890
+ for (let plugin of plugins)
10891
+ plugin.activateHover(view, pos, side, (_a = options.until) !== null && _a !== void 0 ? _a : (() => false));
10892
+ }
10893
+ /**
10844
10894
  Get the active tooltip view for a given tooltip, if available.
10845
10895
  */
10846
10896
  function getTooltip(view, tooltip) {
@@ -10862,6 +10912,12 @@ Transaction effect that closes all hover tooltips.
10862
10912
  */
10863
10913
  const closeHoverTooltips = closeHoverTooltipEffect.of(null);
10864
10914
  /**
10915
+ Transaction effect that closes a specific hover tooltip.
10916
+ */
10917
+ function closeHoverTooltip(tooltip) {
10918
+ return closeHoverTooltipEffect.of(tooltip.active);
10919
+ }
10920
+ /**
10865
10921
  Tell the tooltip extension to recompute the position of the active
10866
10922
  tooltips. This can be useful when something happens (such as a
10867
10923
  re-positioning or CSS change affecting the editor) that could
@@ -11759,6 +11815,8 @@ exports.ViewPlugin = ViewPlugin;
11759
11815
  exports.ViewUpdate = ViewUpdate;
11760
11816
  exports.WidgetType = WidgetType;
11761
11817
  exports.__test = __test;
11818
+ exports.activateHover = activateHover;
11819
+ exports.closeHoverTooltip = closeHoverTooltip;
11762
11820
  exports.closeHoverTooltips = closeHoverTooltips;
11763
11821
  exports.crosshairCursor = crosshairCursor;
11764
11822
  exports.drawSelection = drawSelection;
package/dist/index.d.cts CHANGED
@@ -1177,7 +1177,7 @@ declare class EditorView {
1177
1177
  setTabFocusMode(to?: boolean | number): void;
1178
1178
  /**
1179
1179
  Facet to add a [style
1180
- module](https://github.com/marijnh/style-mod#documentation) to
1180
+ module](https://code.haverbeke.berlin/marijn/style-mod#documentation) to
1181
1181
  an editor view. The view will ensure that the module is
1182
1182
  mounted in its [document
1183
1183
  root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
@@ -1379,7 +1379,7 @@ declare class EditorView {
1379
1379
  static scrollMargins: Facet<(view: EditorView) => Partial<Rect> | null, readonly ((view: EditorView) => Partial<Rect> | null)[]>;
1380
1380
  /**
1381
1381
  Create a theme extension. The first argument can be a
1382
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)
1382
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)
1383
1383
  style spec providing the styles for the theme. These will be
1384
1384
  prefixed with a generated class for the style.
1385
1385
 
@@ -2090,6 +2090,22 @@ declare function hoverTooltip(source: HoverTooltipSource, options?: {
2090
2090
  active: StateField<readonly Tooltip[]>;
2091
2091
  };
2092
2092
  /**
2093
+ Activate hover tooltips for the given position and side. If you
2094
+ provide a specific hover tooltip (the value returned from
2095
+ [`hoverTooltip`](https://codemirror.net/6/docs/ref/#view.hoverTooltip)), only that one will be
2096
+ activated. If not given, all hover tooltips at the given position
2097
+ are triggered.
2098
+
2099
+ Note that tooltips opened this way don't close automatically, and
2100
+ you'll want to pass an `until` callback or use
2101
+ [`closeHoverTooltip`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltip)/[`closeHoverTooltips`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltips)
2102
+ to deactivate them.
2103
+ */
2104
+ declare function activateHover(view: EditorView, pos: number, side: -1 | 1, options?: {
2105
+ tooltip?: Extension;
2106
+ until?: (tr: Transaction) => boolean;
2107
+ }): void;
2108
+ /**
2093
2109
  Get the active tooltip view for a given tooltip, if available.
2094
2110
  */
2095
2111
  declare function getTooltip(view: EditorView, tooltip: Tooltip): TooltipView | null;
@@ -2100,7 +2116,13 @@ declare function hasHoverTooltips(state: EditorState): boolean;
2100
2116
  /**
2101
2117
  Transaction effect that closes all hover tooltips.
2102
2118
  */
2103
- declare const closeHoverTooltips: StateEffect<null>;
2119
+ declare const closeHoverTooltips: StateEffect<unknown>;
2120
+ /**
2121
+ Transaction effect that closes a specific hover tooltip.
2122
+ */
2123
+ declare function closeHoverTooltip(tooltip: Extension & {
2124
+ active: StateField<readonly Tooltip[]>;
2125
+ }): StateEffect<unknown>;
2104
2126
  /**
2105
2127
  Tell the tooltip extension to recompute the position of the active
2106
2128
  tooltips. This can be useful when something happens (such as a
@@ -2386,4 +2408,4 @@ trailing whitespace.
2386
2408
  */
2387
2409
  declare function highlightTrailingWhitespace(): Extension;
2388
2410
 
2389
- export { BidiSpan, BlockInfo, BlockType, BlockWrapper, type Command, type DOMEventHandlers, type DOMEventMap, Decoration, type DecorationSet, Direction, EditorView, type EditorViewConfig, GutterMarker, type HoverTooltipSource, type KeyBinding, type LayerMarker, MatchDecorator, type MouseSelectionStyle, type Panel, type PanelConstructor, type PluginSpec, type PluginValue, type Rect, RectangleMarker, type Tooltip, type TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
2411
+ export { BidiSpan, BlockInfo, BlockType, BlockWrapper, type Command, type DOMEventHandlers, type DOMEventMap, Decoration, type DecorationSet, Direction, EditorView, type EditorViewConfig, GutterMarker, type HoverTooltipSource, type KeyBinding, type LayerMarker, MatchDecorator, type MouseSelectionStyle, type Panel, type PanelConstructor, type PluginSpec, type PluginValue, type Rect, RectangleMarker, type Tooltip, type TooltipView, ViewPlugin, ViewUpdate, WidgetType, activateHover, closeHoverTooltip, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
package/dist/index.d.ts CHANGED
@@ -1177,7 +1177,7 @@ declare class EditorView {
1177
1177
  setTabFocusMode(to?: boolean | number): void;
1178
1178
  /**
1179
1179
  Facet to add a [style
1180
- module](https://github.com/marijnh/style-mod#documentation) to
1180
+ module](https://code.haverbeke.berlin/marijn/style-mod#documentation) to
1181
1181
  an editor view. The view will ensure that the module is
1182
1182
  mounted in its [document
1183
1183
  root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
@@ -1379,7 +1379,7 @@ declare class EditorView {
1379
1379
  static scrollMargins: Facet<(view: EditorView) => Partial<Rect> | null, readonly ((view: EditorView) => Partial<Rect> | null)[]>;
1380
1380
  /**
1381
1381
  Create a theme extension. The first argument can be a
1382
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)
1382
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)
1383
1383
  style spec providing the styles for the theme. These will be
1384
1384
  prefixed with a generated class for the style.
1385
1385
 
@@ -2090,6 +2090,22 @@ declare function hoverTooltip(source: HoverTooltipSource, options?: {
2090
2090
  active: StateField<readonly Tooltip[]>;
2091
2091
  };
2092
2092
  /**
2093
+ Activate hover tooltips for the given position and side. If you
2094
+ provide a specific hover tooltip (the value returned from
2095
+ [`hoverTooltip`](https://codemirror.net/6/docs/ref/#view.hoverTooltip)), only that one will be
2096
+ activated. If not given, all hover tooltips at the given position
2097
+ are triggered.
2098
+
2099
+ Note that tooltips opened this way don't close automatically, and
2100
+ you'll want to pass an `until` callback or use
2101
+ [`closeHoverTooltip`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltip)/[`closeHoverTooltips`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltips)
2102
+ to deactivate them.
2103
+ */
2104
+ declare function activateHover(view: EditorView, pos: number, side: -1 | 1, options?: {
2105
+ tooltip?: Extension;
2106
+ until?: (tr: Transaction) => boolean;
2107
+ }): void;
2108
+ /**
2093
2109
  Get the active tooltip view for a given tooltip, if available.
2094
2110
  */
2095
2111
  declare function getTooltip(view: EditorView, tooltip: Tooltip): TooltipView | null;
@@ -2100,7 +2116,13 @@ declare function hasHoverTooltips(state: EditorState): boolean;
2100
2116
  /**
2101
2117
  Transaction effect that closes all hover tooltips.
2102
2118
  */
2103
- declare const closeHoverTooltips: StateEffect<null>;
2119
+ declare const closeHoverTooltips: StateEffect<unknown>;
2120
+ /**
2121
+ Transaction effect that closes a specific hover tooltip.
2122
+ */
2123
+ declare function closeHoverTooltip(tooltip: Extension & {
2124
+ active: StateField<readonly Tooltip[]>;
2125
+ }): StateEffect<unknown>;
2104
2126
  /**
2105
2127
  Tell the tooltip extension to recompute the position of the active
2106
2128
  tooltips. This can be useful when something happens (such as a
@@ -2386,4 +2408,4 @@ trailing whitespace.
2386
2408
  */
2387
2409
  declare function highlightTrailingWhitespace(): Extension;
2388
2410
 
2389
- export { BidiSpan, BlockInfo, BlockType, BlockWrapper, type Command, type DOMEventHandlers, type DOMEventMap, Decoration, type DecorationSet, Direction, EditorView, type EditorViewConfig, GutterMarker, type HoverTooltipSource, type KeyBinding, type LayerMarker, MatchDecorator, type MouseSelectionStyle, type Panel, type PanelConstructor, type PluginSpec, type PluginValue, type Rect, RectangleMarker, type Tooltip, type TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
2411
+ export { BidiSpan, BlockInfo, BlockType, BlockWrapper, type Command, type DOMEventHandlers, type DOMEventMap, Decoration, type DecorationSet, Direction, EditorView, type EditorViewConfig, GutterMarker, type HoverTooltipSource, type KeyBinding, type LayerMarker, MatchDecorator, type MouseSelectionStyle, type Panel, type PanelConstructor, type PluginSpec, type PluginValue, type Rect, RectangleMarker, type Tooltip, type TooltipView, ViewPlugin, ViewUpdate, WidgetType, activateHover, closeHoverTooltip, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
package/dist/index.js CHANGED
@@ -3831,7 +3831,7 @@ class InlineCoordsScan {
3831
3831
  // (including the position after the last piece). For a text tile,
3832
3832
  // these will be character clusters, for a composite tile, these
3833
3833
  // will be child tiles.
3834
- scan(positions, getRects) {
3834
+ scan(positions, getRects, recursed = false) {
3835
3835
  let lo = 0, hi = positions.length - 1, seen = new Set();
3836
3836
  let bidi = this.bidiIn(positions[0], positions[hi]);
3837
3837
  let above, below;
@@ -3904,18 +3904,19 @@ class InlineCoordsScan {
3904
3904
  if (!closestRect) {
3905
3905
  let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
3906
3906
  this.y = (side.top + side.bottom) / 2;
3907
- return this.scan(positions, getRects);
3907
+ return this.scan(positions, getRects, true);
3908
3908
  }
3909
3909
  // Handle the case where closest matched a higher element on the
3910
3910
  // same line as an element below/above the coords
3911
- if (closestDx) {
3912
- if (above && above.bottom > closestRect.top) {
3911
+ if (closestDx && !recursed) {
3912
+ let { top, bottom } = closestRect;
3913
+ if (above && above.bottom > (top + top + bottom) / 3) {
3913
3914
  this.y = above.bottom - 1;
3914
- return this.scan(positions, getRects);
3915
+ return this.scan(positions, getRects, true);
3915
3916
  }
3916
- if (below && below.top < closestRect.bottom) {
3917
+ if (below && below.top < (top + bottom + bottom) / 3) {
3917
3918
  this.y = below.top + 1;
3918
- return this.scan(positions, getRects);
3919
+ return this.scan(positions, getRects, true);
3919
3920
  }
3920
3921
  }
3921
3922
  let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
@@ -5209,7 +5210,7 @@ observers.compositionend = view => {
5209
5210
  view.inputState.compositionFirstChange = null;
5210
5211
  if (browser.chrome && browser.android) {
5211
5212
  // Delay flushing for a bit on Android because it'll often fire a
5212
- // bunch of contradictory changes in a row at end of compositon
5213
+ // bunch of contradictory changes in a row at end of composition
5213
5214
  view.observer.flushSoon();
5214
5215
  }
5215
5216
  else if (view.inputState.compositionPendingChange) {
@@ -5284,8 +5285,8 @@ handlers.beforeinput = (view, event) => {
5284
5285
  const appliedFirefoxHack = /*@__PURE__*/new Set;
5285
5286
  // In Firefox, when cut/copy handlers are added to the document, that
5286
5287
  // somehow avoids a bug where those events aren't fired when the
5287
- // selection is empty. See https://github.com/codemirror/dev/issues/1082
5288
- // and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5288
+ // selection is empty. See issue #1082 and
5289
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5289
5290
  function firefoxCopyCutHack(doc) {
5290
5291
  if (!appliedFirefoxHack.has(doc)) {
5291
5292
  appliedFirefoxHack.add(doc);
@@ -6771,7 +6772,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6771
6772
  flexShrink: 0,
6772
6773
  display: "block",
6773
6774
  whiteSpace: "pre",
6774
- wordWrap: "normal", // https://github.com/codemirror/dev/issues/456
6775
+ wordWrap: "normal", // Issue #456
6775
6776
  boxSizing: "border-box",
6776
6777
  minHeight: "100%",
6777
6778
  padding: "4px 0",
@@ -7190,7 +7191,7 @@ class DOMObserver {
7190
7191
  readSelectionRange() {
7191
7192
  let { view } = this;
7192
7193
  // The Selection object is broken in shadow roots in Safari. See
7193
- // https://github.com/codemirror/dev/issues/414
7194
+ // issue #414
7194
7195
  let selection = getSelection(view.root);
7195
7196
  if (!selection)
7196
7197
  return false;
@@ -8659,7 +8660,7 @@ class EditorView {
8659
8660
  }
8660
8661
  /**
8661
8662
  Create a theme extension. The first argument can be a
8662
- [`style-mod`](https://github.com/marijnh/style-mod#documentation)
8663
+ [`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)
8663
8664
  style spec providing the styles for the theme. These will be
8664
8665
  prefixed with a generated class for the style.
8665
8666
 
@@ -8705,7 +8706,7 @@ class EditorView {
8705
8706
  }
8706
8707
  /**
8707
8708
  Facet to add a [style
8708
- module](https://github.com/marijnh/style-mod#documentation) to
8709
+ module](https://code.haverbeke.berlin/marijn/style-mod#documentation) to
8709
8710
  an editor view. The view will ensure that the module is
8710
8711
  mounted in its [document
8711
8712
  root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
@@ -10632,11 +10633,13 @@ const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip]
10632
10633
  arrow: tooltips.some(t => t.arrow),
10633
10634
  };
10634
10635
  });
10636
+ const hoverPlugin = /*@__PURE__*/Facet.define();
10635
10637
  class HoverPlugin {
10636
- constructor(view, source, field, setHover, hoverTime) {
10638
+ constructor(view, source, field, locked, setHover, hoverTime) {
10637
10639
  this.view = view;
10638
10640
  this.source = source;
10639
10641
  this.field = field;
10642
+ this.locked = locked;
10640
10643
  this.setHover = setHover;
10641
10644
  this.hoverTime = hoverTime;
10642
10645
  this.hoverTimeout = -1;
@@ -10647,7 +10650,7 @@ class HoverPlugin {
10647
10650
  view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
10648
10651
  view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
10649
10652
  }
10650
- update() {
10653
+ update(update) {
10651
10654
  if (this.pending) {
10652
10655
  this.pending = null;
10653
10656
  clearTimeout(this.restartTimeout);
@@ -10691,19 +10694,29 @@ class HoverPlugin {
10691
10694
  let rtl = bidi && bidi.dir == Direction.RTL ? -1 : 1;
10692
10695
  side = (lastMove.x < posCoords.left ? -rtl : rtl);
10693
10696
  }
10697
+ this.activateHover(view, pos, side);
10698
+ }
10699
+ activateHover(view, pos, side, locked) {
10694
10700
  let open = this.source(view, pos, side);
10695
- if (open === null || open === void 0 ? void 0 : open.then) {
10701
+ let done = (value) => {
10702
+ if (value && !(Array.isArray(value) && !value.length)) {
10703
+ let tooltips = Array.isArray(value) ? value : [value];
10704
+ if (locked)
10705
+ this.locked.set(tooltips, locked);
10706
+ view.dispatch({ effects: this.setHover.of(tooltips) });
10707
+ }
10708
+ };
10709
+ if (open && "then" in open) {
10696
10710
  let pending = this.pending = { pos };
10697
10711
  open.then(result => {
10698
10712
  if (this.pending == pending) {
10699
10713
  this.pending = null;
10700
- if (result && !(Array.isArray(result) && !result.length))
10701
- view.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
10714
+ done(result);
10702
10715
  }
10703
10716
  }, e => logException(view.state, e, "hover tooltip"));
10704
10717
  }
10705
- else if (open && !(Array.isArray(open) && !open.length)) {
10706
- view.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
10718
+ else {
10719
+ done(open);
10707
10720
  }
10708
10721
  }
10709
10722
  get tooltip() {
@@ -10717,7 +10730,7 @@ class HoverPlugin {
10717
10730
  if (this.hoverTimeout < 0)
10718
10731
  this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
10719
10732
  let { active, tooltip } = this;
10720
- if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10733
+ if (active.length && !this.locked.has(active) && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10721
10734
  let { pos } = active[0] || this.pending, end = (_b = (_a = active[0]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : pos;
10722
10735
  if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
10723
10736
  : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
@@ -10730,7 +10743,7 @@ class HoverPlugin {
10730
10743
  clearTimeout(this.hoverTimeout);
10731
10744
  this.hoverTimeout = -1;
10732
10745
  let { active } = this;
10733
- if (active.length) {
10746
+ if (active.length && !this.locked.has(active)) {
10734
10747
  let { tooltip } = this;
10735
10748
  let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
10736
10749
  if (!inTooltip)
@@ -10742,7 +10755,8 @@ class HoverPlugin {
10742
10755
  watchTooltipLeave(tooltip) {
10743
10756
  let watch = (event) => {
10744
10757
  tooltip.removeEventListener("mouseleave", watch);
10745
- if (this.active.length && !this.view.dom.contains(event.relatedTarget))
10758
+ let { active } = this;
10759
+ if (active.length && !this.locked.has(active) && !this.view.dom.contains(event.relatedTarget))
10746
10760
  this.view.dispatch({ effects: this.setHover.of([]) });
10747
10761
  };
10748
10762
  tooltip.addEventListener("mouseleave", watch);
@@ -10793,49 +10807,85 @@ extension.
10793
10807
  */
10794
10808
  function hoverTooltip(source, options = {}) {
10795
10809
  let setHover = StateEffect.define();
10810
+ // This would be better stored in the state field, but we've set
10811
+ // down the type of the field in our interface, so it's indirectly
10812
+ // stored by array identity.
10813
+ let locked = new WeakMap();
10796
10814
  let hoverState = StateField.define({
10797
10815
  create() { return []; },
10798
10816
  update(value, tr) {
10817
+ let lock = locked.get(value);
10799
10818
  if (value.length) {
10800
10819
  if (options.hideOnChange && (tr.docChanged || tr.selection))
10801
10820
  value = [];
10821
+ else if (lock && lock(tr))
10822
+ value = [];
10802
10823
  else if (options.hideOn)
10803
10824
  value = value.filter(v => !options.hideOn(tr, v));
10804
- if (tr.docChanged) {
10805
- let mapped = [];
10806
- for (let tooltip of value) {
10807
- let newPos = tr.changes.mapPos(tooltip.pos, -1, MapMode.TrackDel);
10808
- if (newPos != null) {
10809
- let copy = Object.assign(Object.create(null), tooltip);
10810
- copy.pos = newPos;
10811
- if (copy.end != null)
10812
- copy.end = tr.changes.mapPos(copy.end);
10813
- mapped.push(copy);
10814
- }
10825
+ }
10826
+ if (tr.docChanged && value.length) {
10827
+ let mapped = [];
10828
+ for (let tooltip of value) {
10829
+ let newPos = tr.changes.mapPos(tooltip.pos, -1, MapMode.TrackDel);
10830
+ if (newPos != null) {
10831
+ let copy = Object.assign(Object.create(null), tooltip);
10832
+ copy.pos = newPos;
10833
+ if (copy.end != null)
10834
+ copy.end = tr.changes.mapPos(copy.end);
10835
+ mapped.push(copy);
10815
10836
  }
10816
- value = mapped;
10817
10837
  }
10838
+ value = mapped;
10818
10839
  }
10819
10840
  for (let effect of tr.effects) {
10820
- if (effect.is(setHover))
10841
+ if (effect.is(setHover)) {
10821
10842
  value = effect.value;
10822
- if (effect.is(closeHoverTooltipEffect))
10843
+ lock = undefined;
10844
+ }
10845
+ if (effect.is(closeHoverTooltipEffect) && !effect.value || effect.value == hoverState)
10823
10846
  value = [];
10824
10847
  }
10848
+ if (value.length && lock)
10849
+ locked.set(value, lock);
10825
10850
  return value;
10826
10851
  },
10827
10852
  provide: f => showHoverTooltip.from(f)
10828
10853
  });
10854
+ const plugin = ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, locked, setHover, options.hoverTime || 300 /* Hover.Time */));
10829
10855
  return {
10830
10856
  active: hoverState,
10831
10857
  extension: [
10832
10858
  hoverState,
10833
- ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Hover.Time */)),
10859
+ plugin,
10860
+ hoverPlugin.of(plugin),
10834
10861
  showHoverTooltipHost
10835
10862
  ]
10836
10863
  };
10837
10864
  }
10838
10865
  /**
10866
+ Activate hover tooltips for the given position and side. If you
10867
+ provide a specific hover tooltip (the value returned from
10868
+ [`hoverTooltip`](https://codemirror.net/6/docs/ref/#view.hoverTooltip)), only that one will be
10869
+ activated. If not given, all hover tooltips at the given position
10870
+ are triggered.
10871
+
10872
+ Note that tooltips opened this way don't close automatically, and
10873
+ you'll want to pass an `until` callback or use
10874
+ [`closeHoverTooltip`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltip)/[`closeHoverTooltips`](https://codemirror.net/6/docs/ref/#view.closeHoverTooltips)
10875
+ to deactivate them.
10876
+ */
10877
+ function activateHover(view, pos, side, options = {}) {
10878
+ var _a;
10879
+ let plugins = view.state.facet(hoverPlugin).map(p => view.plugin(p)).filter((p) => !!p);
10880
+ if (options.tooltip && options.tooltip.active) {
10881
+ let found = plugins.find(p => p.field == options.tooltip.active);
10882
+ if (found)
10883
+ plugins = [found];
10884
+ }
10885
+ for (let plugin of plugins)
10886
+ plugin.activateHover(view, pos, side, (_a = options.until) !== null && _a !== void 0 ? _a : (() => false));
10887
+ }
10888
+ /**
10839
10889
  Get the active tooltip view for a given tooltip, if available.
10840
10890
  */
10841
10891
  function getTooltip(view, tooltip) {
@@ -10857,6 +10907,12 @@ Transaction effect that closes all hover tooltips.
10857
10907
  */
10858
10908
  const closeHoverTooltips = /*@__PURE__*/closeHoverTooltipEffect.of(null);
10859
10909
  /**
10910
+ Transaction effect that closes a specific hover tooltip.
10911
+ */
10912
+ function closeHoverTooltip(tooltip) {
10913
+ return closeHoverTooltipEffect.of(tooltip.active);
10914
+ }
10915
+ /**
10860
10916
  Tell the tooltip extension to recompute the position of the active
10861
10917
  tooltips. This can be useful when something happens (such as a
10862
10918
  re-positioning or CSS change affecting the editor) that could
@@ -11742,4 +11798,4 @@ function highlightTrailingWhitespace() {
11742
11798
  const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder,
11743
11799
  moveVisually, clearHeightChangeFlag, getHeightChangeFlag: () => heightChangeFlag };
11744
11800
 
11745
- export { BidiSpan, BlockInfo, BlockType, BlockWrapper, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
11801
+ export { BidiSpan, BlockInfo, BlockType, BlockWrapper, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, activateHover, closeHoverTooltip, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.41.0",
3
+ "version": "6.42.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -36,6 +36,6 @@
36
36
  },
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "git+https://github.com/codemirror/view.git"
39
+ "url": "git+https://code.haverbeke.berlin/codemirror/view.git"
40
40
  }
41
41
  }
@@ -1,16 +0,0 @@
1
- name: Trigger CI
2
- on: push
3
-
4
- jobs:
5
- build:
6
- name: Dispatch to main repo
7
- runs-on: ubuntu-latest
8
- steps:
9
- - name: Emit repository_dispatch
10
- uses: mvasigh/dispatch-action@main
11
- with:
12
- # You should create a personal access token and store it in your repository
13
- token: ${{ secrets.DISPATCH_AUTH }}
14
- repo: dev
15
- owner: codemirror
16
- event_type: push