@codemirror/view 6.36.8 → 6.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 6.37.1 (2025-05-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Properly add `crelt` as a dependency.
6
+
7
+ ## 6.37.0 (2025-05-29)
8
+
9
+ ### New features
10
+
11
+ View plugins can now take an argument, in which case they must be instantiated with their `of` method in order to be added to a configuration.
12
+
13
+ The new `showDialog` function makes it easy to show a notification or prompt using a CodeMirror panel.
14
+
1
15
  ## 6.36.8 (2025-05-12)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var state = require('@codemirror/state');
4
4
  var styleMod = require('style-mod');
5
5
  var w3cKeyname = require('w3c-keyname');
6
+ var elt = require('crelt');
6
7
 
7
8
  function getSelection(root) {
8
9
  let target;
@@ -2430,11 +2431,23 @@ function logException(state, exception, context) {
2430
2431
  }
2431
2432
  const editable = state.Facet.define({ combine: values => values.length ? values[0] : true });
2432
2433
  let nextPluginID = 0;
2433
- const viewPlugin = state.Facet.define();
2434
+ const viewPlugin = state.Facet.define({
2435
+ combine(plugins) {
2436
+ return plugins.filter((p, i) => {
2437
+ for (let j = 0; j < i; j++)
2438
+ if (plugins[j].plugin == p.plugin)
2439
+ return false;
2440
+ return true;
2441
+ });
2442
+ }
2443
+ });
2434
2444
  /**
2435
2445
  View plugins associate stateful values with a view. They can
2436
2446
  influence the way the content is drawn, and are notified of things
2437
- that happen in the view.
2447
+ that happen in the view. They optionally take an argument, in
2448
+ which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
2449
+ an extension for the plugin. When the argument type is undefined,
2450
+ you can use the plugin instance as an extension directly.
2438
2451
  */
2439
2452
  class ViewPlugin {
2440
2453
  constructor(
@@ -2458,7 +2471,14 @@ class ViewPlugin {
2458
2471
  this.create = create;
2459
2472
  this.domEventHandlers = domEventHandlers;
2460
2473
  this.domEventObservers = domEventObservers;
2461
- this.extension = buildExtensions(this);
2474
+ this.baseExtensions = buildExtensions(this);
2475
+ this.extension = this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg: undefined }));
2476
+ }
2477
+ /**
2478
+ Create an extension for this plugin with the given argument.
2479
+ */
2480
+ of(arg) {
2481
+ return this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg }));
2462
2482
  }
2463
2483
  /**
2464
2484
  Define a plugin from a constructor function that creates the
@@ -2467,7 +2487,7 @@ class ViewPlugin {
2467
2487
  static define(create, spec) {
2468
2488
  const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
2469
2489
  return new ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, plugin => {
2470
- let ext = [viewPlugin.of(plugin)];
2490
+ let ext = [];
2471
2491
  if (deco)
2472
2492
  ext.push(decorations.of(view => {
2473
2493
  let pluginInst = view.plugin(plugin);
@@ -2483,7 +2503,7 @@ class ViewPlugin {
2483
2503
  editor view as argument.
2484
2504
  */
2485
2505
  static fromClass(cls, spec) {
2486
- return ViewPlugin.define(view => new cls(view), spec);
2506
+ return ViewPlugin.define((view, arg) => new cls(view, arg), spec);
2487
2507
  }
2488
2508
  }
2489
2509
  class PluginInstance {
@@ -2491,18 +2511,19 @@ class PluginInstance {
2491
2511
  this.spec = spec;
2492
2512
  // When starting an update, all plugins have this field set to the
2493
2513
  // update object, indicating they need to be updated. When finished
2494
- // updating, it is set to `false`. Retrieving a plugin that needs to
2514
+ // updating, it is set to `null`. Retrieving a plugin that needs to
2495
2515
  // be updated with `view.plugin` forces an eager update.
2496
2516
  this.mustUpdate = null;
2497
2517
  // This is null when the plugin is initially created, but
2498
2518
  // initialized on the first update.
2499
2519
  this.value = null;
2500
2520
  }
2521
+ get plugin() { return this.spec && this.spec.plugin; }
2501
2522
  update(view) {
2502
2523
  if (!this.value) {
2503
2524
  if (this.spec) {
2504
2525
  try {
2505
- this.value = this.spec.create(view);
2526
+ this.value = this.spec.plugin.create(view, this.spec.arg);
2506
2527
  }
2507
2528
  catch (e) {
2508
2529
  logException(view.state, e, "CodeMirror plugin crashed");
@@ -4360,16 +4381,16 @@ function computeHandlers(plugins) {
4360
4381
  return result[type] || (result[type] = { observers: [], handlers: [] });
4361
4382
  }
4362
4383
  for (let plugin of plugins) {
4363
- let spec = plugin.spec;
4364
- if (spec && spec.domEventHandlers)
4365
- for (let type in spec.domEventHandlers) {
4366
- let f = spec.domEventHandlers[type];
4384
+ let spec = plugin.spec, handlers = spec && spec.plugin.domEventHandlers, observers = spec && spec.plugin.domEventObservers;
4385
+ if (handlers)
4386
+ for (let type in handlers) {
4387
+ let f = handlers[type];
4367
4388
  if (f)
4368
4389
  record(type).handlers.push(bindHandler(plugin.value, f));
4369
4390
  }
4370
- if (spec && spec.domEventObservers)
4371
- for (let type in spec.domEventObservers) {
4372
- let f = spec.domEventObservers[type];
4391
+ if (observers)
4392
+ for (let type in observers) {
4393
+ let f = observers[type];
4373
4394
  if (f)
4374
4395
  record(type).observers.push(bindHandler(plugin.value, f));
4375
4396
  }
@@ -6616,6 +6637,21 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
6616
6637
  backgroundColor: "#333338",
6617
6638
  color: "white"
6618
6639
  },
6640
+ ".cm-dialog": {
6641
+ padding: "2px 19px 4px 6px",
6642
+ position: "relative",
6643
+ "& label": { fontSize: "80%" },
6644
+ },
6645
+ ".cm-dialog-close": {
6646
+ position: "absolute",
6647
+ top: "3px",
6648
+ right: "4px",
6649
+ backgroundColor: "inherit",
6650
+ border: "none",
6651
+ font: "inherit",
6652
+ fontSize: "14px",
6653
+ padding: "0"
6654
+ },
6619
6655
  ".cm-tab": {
6620
6656
  display: "inline-block",
6621
6657
  overflow: "hidden",
@@ -7928,8 +7964,8 @@ class EditorView {
7928
7964
  */
7929
7965
  plugin(plugin) {
7930
7966
  let known = this.pluginMap.get(plugin);
7931
- if (known === undefined || known && known.spec != plugin)
7932
- this.pluginMap.set(plugin, known = this.plugins.find(p => p.spec == plugin) || null);
7967
+ if (known === undefined || known && known.plugin != plugin)
7968
+ this.pluginMap.set(plugin, known = this.plugins.find(p => p.plugin == plugin) || null);
7933
7969
  return known && known.update(this).value;
7934
7970
  }
7935
7971
  /**
@@ -10644,6 +10680,134 @@ const showPanel = state.Facet.define({
10644
10680
  enables: panelPlugin
10645
10681
  });
10646
10682
 
10683
+ /**
10684
+ Show a panel above or below the editor to show the user a message
10685
+ or prompt them for input. Returns an effect that can be dispatched
10686
+ to close the dialog, and a promise that resolves when the dialog
10687
+ is closed or a form inside of it is submitted.
10688
+
10689
+ You are encouraged, if your handling of the result of the promise
10690
+ dispatches a transaction, to include the `close` effect in it. If
10691
+ you don't, this function will automatically dispatch a separate
10692
+ transaction right after.
10693
+ */
10694
+ function showDialog(view, config) {
10695
+ let resolve;
10696
+ let promise = new Promise(r => resolve = r);
10697
+ let panelCtor = (view) => createDialog(view, config, resolve);
10698
+ if (view.state.field(dialogField, false)) {
10699
+ view.dispatch({ effects: openDialogEffect.of(panelCtor) });
10700
+ }
10701
+ else {
10702
+ view.dispatch({ effects: state.StateEffect.appendConfig.of(dialogField.init(() => [panelCtor])) });
10703
+ }
10704
+ let close = closeDialogEffect.of(panelCtor);
10705
+ return { close, result: promise.then(form => {
10706
+ let queue = view.win.queueMicrotask || ((f) => view.win.setTimeout(f, 10));
10707
+ queue(() => {
10708
+ if (view.state.field(dialogField).indexOf(panelCtor) > -1)
10709
+ view.dispatch({ effects: close });
10710
+ });
10711
+ return form;
10712
+ }) };
10713
+ }
10714
+ /**
10715
+ Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
10716
+ name as identifier.
10717
+ */
10718
+ function getDialog(view, className) {
10719
+ let dialogs = view.state.field(dialogField, false) || [];
10720
+ for (let open of dialogs) {
10721
+ let panel = getPanel(view, open);
10722
+ if (panel && panel.dom.classList.contains(className))
10723
+ return panel;
10724
+ }
10725
+ return null;
10726
+ }
10727
+ const dialogField = state.StateField.define({
10728
+ create() { return []; },
10729
+ update(dialogs, tr) {
10730
+ for (let e of tr.effects) {
10731
+ if (e.is(openDialogEffect))
10732
+ dialogs = [e.value].concat(dialogs);
10733
+ else if (e.is(closeDialogEffect))
10734
+ dialogs = dialogs.filter(d => d != e.value);
10735
+ }
10736
+ return dialogs;
10737
+ },
10738
+ provide: f => showPanel.computeN([f], state => state.field(f))
10739
+ });
10740
+ const openDialogEffect = state.StateEffect.define();
10741
+ const closeDialogEffect = state.StateEffect.define();
10742
+ function createDialog(view, config, result) {
10743
+ let content = config.content ? config.content(view, () => done(null)) : null;
10744
+ if (!content) {
10745
+ content = elt("form");
10746
+ if (config.input) {
10747
+ let input = elt("input", config.input);
10748
+ if (/^(text|password|number|email|tel|url)$/.test(input.type))
10749
+ input.classList.add("cm-textfield");
10750
+ if (!input.name)
10751
+ input.name = "input";
10752
+ content.appendChild(elt("label", (config.label || "") + ": ", input));
10753
+ }
10754
+ else {
10755
+ content.appendChild(document.createTextNode(config.label || ""));
10756
+ }
10757
+ content.appendChild(document.createTextNode(" "));
10758
+ content.appendChild(elt("button", { class: "cm-button", type: "submit" }, config.submitLabel || "OK"));
10759
+ }
10760
+ let forms = content.nodeName == "FORM" ? [content] : content.querySelectorAll("form");
10761
+ for (let i = 0; i < forms.length; i++) {
10762
+ let form = forms[i];
10763
+ form.addEventListener("keydown", (event) => {
10764
+ if (event.keyCode == 27) { // Escape
10765
+ event.preventDefault();
10766
+ done(null);
10767
+ }
10768
+ else if (event.keyCode == 13) { // Enter
10769
+ event.preventDefault();
10770
+ done(form);
10771
+ }
10772
+ });
10773
+ form.addEventListener("submit", (event) => {
10774
+ event.preventDefault();
10775
+ done(form);
10776
+ });
10777
+ }
10778
+ let panel = elt("div", content, elt("button", {
10779
+ onclick: () => done(null),
10780
+ "aria-label": view.state.phrase("close"),
10781
+ class: "cm-dialog-close",
10782
+ type: "button"
10783
+ }, ["×"]));
10784
+ if (config.class)
10785
+ panel.className = config.class;
10786
+ panel.classList.add("cm-dialog");
10787
+ function done(form) {
10788
+ if (panel.contains(panel.ownerDocument.activeElement))
10789
+ view.focus();
10790
+ result(form);
10791
+ }
10792
+ return {
10793
+ dom: panel,
10794
+ top: config.top,
10795
+ mount: () => {
10796
+ if (config.focus) {
10797
+ let focus;
10798
+ if (typeof config.focus == "string")
10799
+ focus = content.querySelector(config.focus);
10800
+ else
10801
+ focus = content.querySelector("input") || content.querySelector("button");
10802
+ if (focus && "select" in focus)
10803
+ focus.select();
10804
+ else if (focus && "focus" in focus)
10805
+ focus.focus();
10806
+ }
10807
+ }
10808
+ };
10809
+ }
10810
+
10647
10811
  /**
10648
10812
  A gutter marker represents a bit of information attached to a line
10649
10813
  in a specific gutter. Your own custom markers have to extend this
@@ -10702,7 +10866,7 @@ Define an editor gutter. The order in which the gutters appear is
10702
10866
  determined by their extension priority.
10703
10867
  */
10704
10868
  function gutter(config) {
10705
- return [gutters(), activeGutters.of(Object.assign(Object.assign({}, defaults), config))];
10869
+ return [gutters(), activeGutters.of({ ...defaults, ...config })];
10706
10870
  }
10707
10871
  const unfixGutters = state.Facet.define({
10708
10872
  combine: values => values.some(x => x)
@@ -11194,6 +11358,7 @@ exports.closeHoverTooltips = closeHoverTooltips;
11194
11358
  exports.crosshairCursor = crosshairCursor;
11195
11359
  exports.drawSelection = drawSelection;
11196
11360
  exports.dropCursor = dropCursor;
11361
+ exports.getDialog = getDialog;
11197
11362
  exports.getDrawSelectionConfig = getDrawSelectionConfig;
11198
11363
  exports.getPanel = getPanel;
11199
11364
  exports.getTooltip = getTooltip;
@@ -11220,6 +11385,7 @@ exports.rectangularSelection = rectangularSelection;
11220
11385
  exports.repositionTooltips = repositionTooltips;
11221
11386
  exports.runScopeHandlers = runScopeHandlers;
11222
11387
  exports.scrollPastEnd = scrollPastEnd;
11388
+ exports.showDialog = showDialog;
11223
11389
  exports.showPanel = showPanel;
11224
11390
  exports.showTooltip = showTooltip;
11225
11391
  exports.tooltips = tooltips;
package/dist/index.d.cts CHANGED
@@ -438,7 +438,7 @@ interface PluginSpec<V extends PluginValue> {
438
438
  Specify that the plugin provides additional extensions when
439
439
  added to an editor configuration.
440
440
  */
441
- provide?: (plugin: ViewPlugin<V>) => Extension;
441
+ provide?: (plugin: ViewPlugin<V, any>) => Extension;
442
442
  /**
443
443
  Allow the plugin to provide decorations. When given, this should
444
444
  be a function that take the plugin value and return a
@@ -451,26 +451,36 @@ interface PluginSpec<V extends PluginValue> {
451
451
  /**
452
452
  View plugins associate stateful values with a view. They can
453
453
  influence the way the content is drawn, and are notified of things
454
- that happen in the view.
454
+ that happen in the view. They optionally take an argument, in
455
+ which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
456
+ an extension for the plugin. When the argument type is undefined,
457
+ you can use the plugin instance as an extension directly.
455
458
  */
456
- declare class ViewPlugin<V extends PluginValue> {
459
+ declare class ViewPlugin<V extends PluginValue, Arg = undefined> {
457
460
  /**
458
- Instances of this class act as extensions.
461
+ When `Arg` is undefined, instances of this class act as
462
+ extensions. Otherwise, you have to call `of` to create an
463
+ extension value.
459
464
  */
460
- extension: Extension;
465
+ extension: Arg extends undefined ? Extension : null;
466
+ private baseExtensions;
461
467
  private constructor();
462
468
  /**
469
+ Create an extension for this plugin with the given argument.
470
+ */
471
+ of(arg: Arg): Extension;
472
+ /**
463
473
  Define a plugin from a constructor function that creates the
464
474
  plugin's value, given an editor view.
465
475
  */
466
- static define<V extends PluginValue>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
476
+ static define<V extends PluginValue, Arg = undefined>(create: (view: EditorView, arg: Arg) => V, spec?: PluginSpec<V>): ViewPlugin<V, Arg>;
467
477
  /**
468
478
  Create a plugin for a class whose constructor takes a single
469
479
  editor view as argument.
470
480
  */
471
- static fromClass<V extends PluginValue>(cls: {
472
- new (view: EditorView): V;
473
- }, spec?: PluginSpec<V>): ViewPlugin<V>;
481
+ static fromClass<V extends PluginValue, Arg = undefined>(cls: {
482
+ new (view: EditorView, arg: Arg): V;
483
+ }, spec?: PluginSpec<V>): ViewPlugin<V, Arg>;
474
484
  }
475
485
  interface MeasureRequest<T> {
476
486
  /**
@@ -829,7 +839,7 @@ declare class EditorView {
829
839
  know you registered a given plugin, it is recommended to check
830
840
  the return value of this method.
831
841
  */
832
- plugin<T extends PluginValue>(plugin: ViewPlugin<T>): T | null;
842
+ plugin<T extends PluginValue>(plugin: ViewPlugin<T, any>): T | null;
833
843
  /**
834
844
  The top position of the document, in screen coordinates. This
835
845
  may be negative when the editor is scrolled down. Points
@@ -2078,6 +2088,70 @@ constructor is no longer provided.) Values of `null` are ignored.
2078
2088
  */
2079
2089
  declare const showPanel: Facet<PanelConstructor | null, readonly (PanelConstructor | null)[]>;
2080
2090
 
2091
+ type DialogConfig = {
2092
+ /**
2093
+ A function to render the content of the dialog. The result
2094
+ should contain at least one `<form>` element. Submit handlers a
2095
+ handler for the Escape key will be added to the form.
2096
+
2097
+ If this is not given, the `label`, `input`, and `submitLabel`
2098
+ fields will be used to create a simple form for you.
2099
+ */
2100
+ content?: (view: EditorView, close: () => void) => HTMLElement;
2101
+ /**
2102
+ When `content` isn't given, this provides the text shown in the
2103
+ dialog.
2104
+ */
2105
+ label?: string;
2106
+ /**
2107
+ The attributes for an input element shown next to the label. If
2108
+ not given, no input element is added.
2109
+ */
2110
+ input?: {
2111
+ [attr: string]: string;
2112
+ };
2113
+ /**
2114
+ The label for the button that submits the form. Defaults to
2115
+ `"OK"`.
2116
+ */
2117
+ submitLabel?: string;
2118
+ /**
2119
+ Extra classes to add to the panel.
2120
+ */
2121
+ class?: string;
2122
+ /**
2123
+ A query selector to find the field that should be focused when
2124
+ the dialog is opened. When set to true, this picks the first
2125
+ `<input>` or `<button>` element in the form.
2126
+ */
2127
+ focus?: string | boolean;
2128
+ /**
2129
+ By default, dialogs are shown below the editor. Set this to
2130
+ `true` to have it show up at the top.
2131
+ */
2132
+ top?: boolean;
2133
+ };
2134
+ /**
2135
+ Show a panel above or below the editor to show the user a message
2136
+ or prompt them for input. Returns an effect that can be dispatched
2137
+ to close the dialog, and a promise that resolves when the dialog
2138
+ is closed or a form inside of it is submitted.
2139
+
2140
+ You are encouraged, if your handling of the result of the promise
2141
+ dispatches a transaction, to include the `close` effect in it. If
2142
+ you don't, this function will automatically dispatch a separate
2143
+ transaction right after.
2144
+ */
2145
+ declare function showDialog(view: EditorView, config: DialogConfig): {
2146
+ close: StateEffect<unknown>;
2147
+ result: Promise<HTMLFormElement | null>;
2148
+ };
2149
+ /**
2150
+ Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
2151
+ name as identifier.
2152
+ */
2153
+ declare function getDialog(view: EditorView, className: string): Panel | null;
2154
+
2081
2155
  /**
2082
2156
  A gutter marker represents a bit of information attached to a line
2083
2157
  in a specific gutter. Your own custom markers have to extend this
@@ -2223,4 +2297,4 @@ trailing whitespace.
2223
2297
  */
2224
2298
  declare function highlightTrailingWhitespace(): Extension;
2225
2299
 
2226
- export { BidiSpan, BlockInfo, BlockType, 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, 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, showPanel, showTooltip, tooltips };
2300
+ export { BidiSpan, BlockInfo, BlockType, 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 };
package/dist/index.d.ts CHANGED
@@ -438,7 +438,7 @@ interface PluginSpec<V extends PluginValue> {
438
438
  Specify that the plugin provides additional extensions when
439
439
  added to an editor configuration.
440
440
  */
441
- provide?: (plugin: ViewPlugin<V>) => Extension;
441
+ provide?: (plugin: ViewPlugin<V, any>) => Extension;
442
442
  /**
443
443
  Allow the plugin to provide decorations. When given, this should
444
444
  be a function that take the plugin value and return a
@@ -451,26 +451,36 @@ interface PluginSpec<V extends PluginValue> {
451
451
  /**
452
452
  View plugins associate stateful values with a view. They can
453
453
  influence the way the content is drawn, and are notified of things
454
- that happen in the view.
454
+ that happen in the view. They optionally take an argument, in
455
+ which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
456
+ an extension for the plugin. When the argument type is undefined,
457
+ you can use the plugin instance as an extension directly.
455
458
  */
456
- declare class ViewPlugin<V extends PluginValue> {
459
+ declare class ViewPlugin<V extends PluginValue, Arg = undefined> {
457
460
  /**
458
- Instances of this class act as extensions.
461
+ When `Arg` is undefined, instances of this class act as
462
+ extensions. Otherwise, you have to call `of` to create an
463
+ extension value.
459
464
  */
460
- extension: Extension;
465
+ extension: Arg extends undefined ? Extension : null;
466
+ private baseExtensions;
461
467
  private constructor();
462
468
  /**
469
+ Create an extension for this plugin with the given argument.
470
+ */
471
+ of(arg: Arg): Extension;
472
+ /**
463
473
  Define a plugin from a constructor function that creates the
464
474
  plugin's value, given an editor view.
465
475
  */
466
- static define<V extends PluginValue>(create: (view: EditorView) => V, spec?: PluginSpec<V>): ViewPlugin<V>;
476
+ static define<V extends PluginValue, Arg = undefined>(create: (view: EditorView, arg: Arg) => V, spec?: PluginSpec<V>): ViewPlugin<V, Arg>;
467
477
  /**
468
478
  Create a plugin for a class whose constructor takes a single
469
479
  editor view as argument.
470
480
  */
471
- static fromClass<V extends PluginValue>(cls: {
472
- new (view: EditorView): V;
473
- }, spec?: PluginSpec<V>): ViewPlugin<V>;
481
+ static fromClass<V extends PluginValue, Arg = undefined>(cls: {
482
+ new (view: EditorView, arg: Arg): V;
483
+ }, spec?: PluginSpec<V>): ViewPlugin<V, Arg>;
474
484
  }
475
485
  interface MeasureRequest<T> {
476
486
  /**
@@ -829,7 +839,7 @@ declare class EditorView {
829
839
  know you registered a given plugin, it is recommended to check
830
840
  the return value of this method.
831
841
  */
832
- plugin<T extends PluginValue>(plugin: ViewPlugin<T>): T | null;
842
+ plugin<T extends PluginValue>(plugin: ViewPlugin<T, any>): T | null;
833
843
  /**
834
844
  The top position of the document, in screen coordinates. This
835
845
  may be negative when the editor is scrolled down. Points
@@ -2078,6 +2088,70 @@ constructor is no longer provided.) Values of `null` are ignored.
2078
2088
  */
2079
2089
  declare const showPanel: Facet<PanelConstructor | null, readonly (PanelConstructor | null)[]>;
2080
2090
 
2091
+ type DialogConfig = {
2092
+ /**
2093
+ A function to render the content of the dialog. The result
2094
+ should contain at least one `<form>` element. Submit handlers a
2095
+ handler for the Escape key will be added to the form.
2096
+
2097
+ If this is not given, the `label`, `input`, and `submitLabel`
2098
+ fields will be used to create a simple form for you.
2099
+ */
2100
+ content?: (view: EditorView, close: () => void) => HTMLElement;
2101
+ /**
2102
+ When `content` isn't given, this provides the text shown in the
2103
+ dialog.
2104
+ */
2105
+ label?: string;
2106
+ /**
2107
+ The attributes for an input element shown next to the label. If
2108
+ not given, no input element is added.
2109
+ */
2110
+ input?: {
2111
+ [attr: string]: string;
2112
+ };
2113
+ /**
2114
+ The label for the button that submits the form. Defaults to
2115
+ `"OK"`.
2116
+ */
2117
+ submitLabel?: string;
2118
+ /**
2119
+ Extra classes to add to the panel.
2120
+ */
2121
+ class?: string;
2122
+ /**
2123
+ A query selector to find the field that should be focused when
2124
+ the dialog is opened. When set to true, this picks the first
2125
+ `<input>` or `<button>` element in the form.
2126
+ */
2127
+ focus?: string | boolean;
2128
+ /**
2129
+ By default, dialogs are shown below the editor. Set this to
2130
+ `true` to have it show up at the top.
2131
+ */
2132
+ top?: boolean;
2133
+ };
2134
+ /**
2135
+ Show a panel above or below the editor to show the user a message
2136
+ or prompt them for input. Returns an effect that can be dispatched
2137
+ to close the dialog, and a promise that resolves when the dialog
2138
+ is closed or a form inside of it is submitted.
2139
+
2140
+ You are encouraged, if your handling of the result of the promise
2141
+ dispatches a transaction, to include the `close` effect in it. If
2142
+ you don't, this function will automatically dispatch a separate
2143
+ transaction right after.
2144
+ */
2145
+ declare function showDialog(view: EditorView, config: DialogConfig): {
2146
+ close: StateEffect<unknown>;
2147
+ result: Promise<HTMLFormElement | null>;
2148
+ };
2149
+ /**
2150
+ Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
2151
+ name as identifier.
2152
+ */
2153
+ declare function getDialog(view: EditorView, className: string): Panel | null;
2154
+
2081
2155
  /**
2082
2156
  A gutter marker represents a bit of information attached to a line
2083
2157
  in a specific gutter. Your own custom markers have to extend this
@@ -2223,4 +2297,4 @@ trailing whitespace.
2223
2297
  */
2224
2298
  declare function highlightTrailingWhitespace(): Extension;
2225
2299
 
2226
- export { BidiSpan, BlockInfo, BlockType, 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, 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, showPanel, showTooltip, tooltips };
2300
+ export { BidiSpan, BlockInfo, BlockType, 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 };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Text, RangeSet, MapMode, RangeValue, findClusterBreak, EditorSelection, Facet, StateEffect, ChangeSet, findColumn, CharCategory, EditorState, Annotation, 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
+ import elt from 'crelt';
4
5
 
5
6
  function getSelection(root) {
6
7
  let target;
@@ -2426,11 +2427,23 @@ function logException(state, exception, context) {
2426
2427
  }
2427
2428
  const editable = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : true });
2428
2429
  let nextPluginID = 0;
2429
- const viewPlugin = /*@__PURE__*/Facet.define();
2430
+ const viewPlugin = /*@__PURE__*/Facet.define({
2431
+ combine(plugins) {
2432
+ return plugins.filter((p, i) => {
2433
+ for (let j = 0; j < i; j++)
2434
+ if (plugins[j].plugin == p.plugin)
2435
+ return false;
2436
+ return true;
2437
+ });
2438
+ }
2439
+ });
2430
2440
  /**
2431
2441
  View plugins associate stateful values with a view. They can
2432
2442
  influence the way the content is drawn, and are notified of things
2433
- that happen in the view.
2443
+ that happen in the view. They optionally take an argument, in
2444
+ which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
2445
+ an extension for the plugin. When the argument type is undefined,
2446
+ you can use the plugin instance as an extension directly.
2434
2447
  */
2435
2448
  class ViewPlugin {
2436
2449
  constructor(
@@ -2454,7 +2467,14 @@ class ViewPlugin {
2454
2467
  this.create = create;
2455
2468
  this.domEventHandlers = domEventHandlers;
2456
2469
  this.domEventObservers = domEventObservers;
2457
- this.extension = buildExtensions(this);
2470
+ this.baseExtensions = buildExtensions(this);
2471
+ this.extension = this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg: undefined }));
2472
+ }
2473
+ /**
2474
+ Create an extension for this plugin with the given argument.
2475
+ */
2476
+ of(arg) {
2477
+ return this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg }));
2458
2478
  }
2459
2479
  /**
2460
2480
  Define a plugin from a constructor function that creates the
@@ -2463,7 +2483,7 @@ class ViewPlugin {
2463
2483
  static define(create, spec) {
2464
2484
  const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
2465
2485
  return new ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, plugin => {
2466
- let ext = [viewPlugin.of(plugin)];
2486
+ let ext = [];
2467
2487
  if (deco)
2468
2488
  ext.push(decorations.of(view => {
2469
2489
  let pluginInst = view.plugin(plugin);
@@ -2479,7 +2499,7 @@ class ViewPlugin {
2479
2499
  editor view as argument.
2480
2500
  */
2481
2501
  static fromClass(cls, spec) {
2482
- return ViewPlugin.define(view => new cls(view), spec);
2502
+ return ViewPlugin.define((view, arg) => new cls(view, arg), spec);
2483
2503
  }
2484
2504
  }
2485
2505
  class PluginInstance {
@@ -2487,18 +2507,19 @@ class PluginInstance {
2487
2507
  this.spec = spec;
2488
2508
  // When starting an update, all plugins have this field set to the
2489
2509
  // update object, indicating they need to be updated. When finished
2490
- // updating, it is set to `false`. Retrieving a plugin that needs to
2510
+ // updating, it is set to `null`. Retrieving a plugin that needs to
2491
2511
  // be updated with `view.plugin` forces an eager update.
2492
2512
  this.mustUpdate = null;
2493
2513
  // This is null when the plugin is initially created, but
2494
2514
  // initialized on the first update.
2495
2515
  this.value = null;
2496
2516
  }
2517
+ get plugin() { return this.spec && this.spec.plugin; }
2497
2518
  update(view) {
2498
2519
  if (!this.value) {
2499
2520
  if (this.spec) {
2500
2521
  try {
2501
- this.value = this.spec.create(view);
2522
+ this.value = this.spec.plugin.create(view, this.spec.arg);
2502
2523
  }
2503
2524
  catch (e) {
2504
2525
  logException(view.state, e, "CodeMirror plugin crashed");
@@ -4356,16 +4377,16 @@ function computeHandlers(plugins) {
4356
4377
  return result[type] || (result[type] = { observers: [], handlers: [] });
4357
4378
  }
4358
4379
  for (let plugin of plugins) {
4359
- let spec = plugin.spec;
4360
- if (spec && spec.domEventHandlers)
4361
- for (let type in spec.domEventHandlers) {
4362
- let f = spec.domEventHandlers[type];
4380
+ let spec = plugin.spec, handlers = spec && spec.plugin.domEventHandlers, observers = spec && spec.plugin.domEventObservers;
4381
+ if (handlers)
4382
+ for (let type in handlers) {
4383
+ let f = handlers[type];
4363
4384
  if (f)
4364
4385
  record(type).handlers.push(bindHandler(plugin.value, f));
4365
4386
  }
4366
- if (spec && spec.domEventObservers)
4367
- for (let type in spec.domEventObservers) {
4368
- let f = spec.domEventObservers[type];
4387
+ if (observers)
4388
+ for (let type in observers) {
4389
+ let f = observers[type];
4369
4390
  if (f)
4370
4391
  record(type).observers.push(bindHandler(plugin.value, f));
4371
4392
  }
@@ -6611,6 +6632,21 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6611
6632
  backgroundColor: "#333338",
6612
6633
  color: "white"
6613
6634
  },
6635
+ ".cm-dialog": {
6636
+ padding: "2px 19px 4px 6px",
6637
+ position: "relative",
6638
+ "& label": { fontSize: "80%" },
6639
+ },
6640
+ ".cm-dialog-close": {
6641
+ position: "absolute",
6642
+ top: "3px",
6643
+ right: "4px",
6644
+ backgroundColor: "inherit",
6645
+ border: "none",
6646
+ font: "inherit",
6647
+ fontSize: "14px",
6648
+ padding: "0"
6649
+ },
6614
6650
  ".cm-tab": {
6615
6651
  display: "inline-block",
6616
6652
  overflow: "hidden",
@@ -7923,8 +7959,8 @@ class EditorView {
7923
7959
  */
7924
7960
  plugin(plugin) {
7925
7961
  let known = this.pluginMap.get(plugin);
7926
- if (known === undefined || known && known.spec != plugin)
7927
- this.pluginMap.set(plugin, known = this.plugins.find(p => p.spec == plugin) || null);
7962
+ if (known === undefined || known && known.plugin != plugin)
7963
+ this.pluginMap.set(plugin, known = this.plugins.find(p => p.plugin == plugin) || null);
7928
7964
  return known && known.update(this).value;
7929
7965
  }
7930
7966
  /**
@@ -10639,6 +10675,134 @@ const showPanel = /*@__PURE__*/Facet.define({
10639
10675
  enables: panelPlugin
10640
10676
  });
10641
10677
 
10678
+ /**
10679
+ Show a panel above or below the editor to show the user a message
10680
+ or prompt them for input. Returns an effect that can be dispatched
10681
+ to close the dialog, and a promise that resolves when the dialog
10682
+ is closed or a form inside of it is submitted.
10683
+
10684
+ You are encouraged, if your handling of the result of the promise
10685
+ dispatches a transaction, to include the `close` effect in it. If
10686
+ you don't, this function will automatically dispatch a separate
10687
+ transaction right after.
10688
+ */
10689
+ function showDialog(view, config) {
10690
+ let resolve;
10691
+ let promise = new Promise(r => resolve = r);
10692
+ let panelCtor = (view) => createDialog(view, config, resolve);
10693
+ if (view.state.field(dialogField, false)) {
10694
+ view.dispatch({ effects: openDialogEffect.of(panelCtor) });
10695
+ }
10696
+ else {
10697
+ view.dispatch({ effects: StateEffect.appendConfig.of(dialogField.init(() => [panelCtor])) });
10698
+ }
10699
+ let close = closeDialogEffect.of(panelCtor);
10700
+ return { close, result: promise.then(form => {
10701
+ let queue = view.win.queueMicrotask || ((f) => view.win.setTimeout(f, 10));
10702
+ queue(() => {
10703
+ if (view.state.field(dialogField).indexOf(panelCtor) > -1)
10704
+ view.dispatch({ effects: close });
10705
+ });
10706
+ return form;
10707
+ }) };
10708
+ }
10709
+ /**
10710
+ Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
10711
+ name as identifier.
10712
+ */
10713
+ function getDialog(view, className) {
10714
+ let dialogs = view.state.field(dialogField, false) || [];
10715
+ for (let open of dialogs) {
10716
+ let panel = getPanel(view, open);
10717
+ if (panel && panel.dom.classList.contains(className))
10718
+ return panel;
10719
+ }
10720
+ return null;
10721
+ }
10722
+ const dialogField = /*@__PURE__*/StateField.define({
10723
+ create() { return []; },
10724
+ update(dialogs, tr) {
10725
+ for (let e of tr.effects) {
10726
+ if (e.is(openDialogEffect))
10727
+ dialogs = [e.value].concat(dialogs);
10728
+ else if (e.is(closeDialogEffect))
10729
+ dialogs = dialogs.filter(d => d != e.value);
10730
+ }
10731
+ return dialogs;
10732
+ },
10733
+ provide: f => showPanel.computeN([f], state => state.field(f))
10734
+ });
10735
+ const openDialogEffect = /*@__PURE__*/StateEffect.define();
10736
+ const closeDialogEffect = /*@__PURE__*/StateEffect.define();
10737
+ function createDialog(view, config, result) {
10738
+ let content = config.content ? config.content(view, () => done(null)) : null;
10739
+ if (!content) {
10740
+ content = elt("form");
10741
+ if (config.input) {
10742
+ let input = elt("input", config.input);
10743
+ if (/^(text|password|number|email|tel|url)$/.test(input.type))
10744
+ input.classList.add("cm-textfield");
10745
+ if (!input.name)
10746
+ input.name = "input";
10747
+ content.appendChild(elt("label", (config.label || "") + ": ", input));
10748
+ }
10749
+ else {
10750
+ content.appendChild(document.createTextNode(config.label || ""));
10751
+ }
10752
+ content.appendChild(document.createTextNode(" "));
10753
+ content.appendChild(elt("button", { class: "cm-button", type: "submit" }, config.submitLabel || "OK"));
10754
+ }
10755
+ let forms = content.nodeName == "FORM" ? [content] : content.querySelectorAll("form");
10756
+ for (let i = 0; i < forms.length; i++) {
10757
+ let form = forms[i];
10758
+ form.addEventListener("keydown", (event) => {
10759
+ if (event.keyCode == 27) { // Escape
10760
+ event.preventDefault();
10761
+ done(null);
10762
+ }
10763
+ else if (event.keyCode == 13) { // Enter
10764
+ event.preventDefault();
10765
+ done(form);
10766
+ }
10767
+ });
10768
+ form.addEventListener("submit", (event) => {
10769
+ event.preventDefault();
10770
+ done(form);
10771
+ });
10772
+ }
10773
+ let panel = elt("div", content, elt("button", {
10774
+ onclick: () => done(null),
10775
+ "aria-label": view.state.phrase("close"),
10776
+ class: "cm-dialog-close",
10777
+ type: "button"
10778
+ }, ["×"]));
10779
+ if (config.class)
10780
+ panel.className = config.class;
10781
+ panel.classList.add("cm-dialog");
10782
+ function done(form) {
10783
+ if (panel.contains(panel.ownerDocument.activeElement))
10784
+ view.focus();
10785
+ result(form);
10786
+ }
10787
+ return {
10788
+ dom: panel,
10789
+ top: config.top,
10790
+ mount: () => {
10791
+ if (config.focus) {
10792
+ let focus;
10793
+ if (typeof config.focus == "string")
10794
+ focus = content.querySelector(config.focus);
10795
+ else
10796
+ focus = content.querySelector("input") || content.querySelector("button");
10797
+ if (focus && "select" in focus)
10798
+ focus.select();
10799
+ else if (focus && "focus" in focus)
10800
+ focus.focus();
10801
+ }
10802
+ }
10803
+ };
10804
+ }
10805
+
10642
10806
  /**
10643
10807
  A gutter marker represents a bit of information attached to a line
10644
10808
  in a specific gutter. Your own custom markers have to extend this
@@ -10697,7 +10861,7 @@ Define an editor gutter. The order in which the gutters appear is
10697
10861
  determined by their extension priority.
10698
10862
  */
10699
10863
  function gutter(config) {
10700
- return [gutters(), activeGutters.of(Object.assign(Object.assign({}, defaults), config))];
10864
+ return [gutters(), activeGutters.of({ ...defaults, ...config })];
10701
10865
  }
10702
10866
  const unfixGutters = /*@__PURE__*/Facet.define({
10703
10867
  combine: values => values.some(x => x)
@@ -11174,4 +11338,4 @@ function highlightTrailingWhitespace() {
11174
11338
  const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder,
11175
11339
  moveVisually, clearHeightChangeFlag, getHeightChangeFlag: () => heightChangeFlag };
11176
11340
 
11177
- export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, 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, showPanel, showTooltip, tooltips };
11341
+ export { BidiSpan, BlockInfo, BlockType, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.36.8",
3
+ "version": "6.37.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -27,6 +27,7 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@codemirror/state": "^6.5.0",
30
+ "crelt": "^1.0.6",
30
31
  "style-mod": "^4.1.0",
31
32
  "w3c-keyname": "^2.2.4"
32
33
  },