@codemirror/autocomplete 0.20.0 → 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.20.3 (2022-05-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Add an aria-label to the completion listbox.
6
+
7
+ Fix a regression that caused transactions generated for completion to not have a `userEvent` annotation.
8
+
9
+ ## 0.20.2 (2022-05-24)
10
+
11
+ ### New features
12
+
13
+ The package now exports an `insertCompletionText` helper that implements the default behavior for applying a completion.
14
+
15
+ ## 0.20.1 (2022-05-16)
16
+
17
+ ### New features
18
+
19
+ The new `closeOnBlur` option determines whether the completion tooltip is closed when the editor loses focus.
20
+
21
+ `CompletionResult` objects with `filter: false` may now have a `getMatch` property that determines the matched range in the options.
22
+
1
23
  ## 0.20.0 (2022-04-20)
2
24
 
3
25
  ### Breaking changes
package/dist/index.cjs CHANGED
@@ -153,32 +153,35 @@ This annotation is added to transactions that are produced by
153
153
  picking a completion.
154
154
  */
155
155
  const pickedCompletion = state.Annotation.define();
156
+ /**
157
+ Helper function that returns a transaction spec which inserts a
158
+ completion's text in the main selection range, and any other
159
+ selection range that has the same text in front of it.
160
+ */
161
+ function insertCompletionText(state$1, text, from, to) {
162
+ return Object.assign(Object.assign({}, state$1.changeByRange(range => {
163
+ if (range == state$1.selection.main)
164
+ return {
165
+ changes: { from: from, to: to, insert: text },
166
+ range: state.EditorSelection.cursor(from + text.length)
167
+ };
168
+ let len = to - from;
169
+ if (!range.empty ||
170
+ len && state$1.sliceDoc(range.from - len, range.from) != state$1.sliceDoc(from, to))
171
+ return { range };
172
+ return {
173
+ changes: { from: range.from - len, to: range.from, insert: text },
174
+ range: state.EditorSelection.cursor(range.from - len + text.length)
175
+ };
176
+ })), { userEvent: "input.complete" });
177
+ }
156
178
  function applyCompletion(view, option) {
157
179
  const apply = option.completion.apply || option.completion.label;
158
180
  let result = option.source;
159
- if (typeof apply == "string") {
160
- view.dispatch(view.state.changeByRange(range => {
161
- if (range == view.state.selection.main)
162
- return {
163
- changes: { from: result.from, to: result.to, insert: apply },
164
- range: state.EditorSelection.cursor(result.from + apply.length)
165
- };
166
- let len = result.to - result.from;
167
- if (!range.empty ||
168
- len && view.state.sliceDoc(range.from - len, range.from) != view.state.sliceDoc(result.from, result.to))
169
- return { range };
170
- return {
171
- changes: { from: range.from - len, to: range.from, insert: apply },
172
- range: state.EditorSelection.cursor(range.from - len + apply.length)
173
- };
174
- }), {
175
- userEvent: "input.complete",
176
- annotations: pickedCompletion.of(option.completion)
177
- });
178
- }
179
- else {
181
+ if (typeof apply == "string")
182
+ view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
183
+ else
180
184
  apply(view, option.completion, result.from, result.to);
181
- }
182
185
  }
183
186
  const SourceCache = new WeakMap();
184
187
  function asSource(source) {
@@ -320,6 +323,7 @@ const completionConfig = state.Facet.define({
320
323
  return state.combineConfig(configs, {
321
324
  activateOnTyping: true,
322
325
  override: null,
326
+ closeOnBlur: true,
323
327
  maxRenderedOptions: 100,
324
328
  defaultKeymap: true,
325
329
  optionClass: () => "",
@@ -328,6 +332,7 @@ const completionConfig = state.Facet.define({
328
332
  addToOptions: []
329
333
  }, {
330
334
  defaultKeymap: (a, b) => a && b,
335
+ closeOnBlur: (a, b) => a && b,
331
336
  icons: (a, b) => a && b,
332
337
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
333
338
  addToOptions: (a, b) => a.concat(b)
@@ -527,6 +532,7 @@ class CompletionTooltip {
527
532
  ul.id = id;
528
533
  ul.setAttribute("role", "listbox");
529
534
  ul.setAttribute("aria-expanded", "true");
535
+ ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
530
536
  for (let i = range.from; i < range.to; i++) {
531
537
  let { completion, match } = options[i];
532
538
  const li = ul.appendChild(document.createElement("li"));
@@ -573,8 +579,14 @@ function sortOptions(active, state) {
573
579
  for (let a of active)
574
580
  if (a.hasResult()) {
575
581
  if (a.result.filter === false) {
576
- for (let option of a.result.options)
577
- options.push(new Option(option, a, [1e9 - i++]));
582
+ let getMatch = a.result.getMatch;
583
+ for (let option of a.result.options) {
584
+ let match = [1e9 - i++];
585
+ if (getMatch)
586
+ for (let n of getMatch(option))
587
+ match.push(n);
588
+ options.push(new Option(option, a, match));
589
+ }
578
590
  }
579
591
  else {
580
592
  let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
@@ -982,6 +994,11 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
982
994
  }
983
995
  }, {
984
996
  eventHandlers: {
997
+ blur() {
998
+ let state = this.view.state.field(completionState, false);
999
+ if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur)
1000
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1001
+ },
985
1002
  compositionstart() {
986
1003
  this.composing = 1 /* Started */;
987
1004
  },
@@ -1740,6 +1757,7 @@ exports.deleteBracketPair = deleteBracketPair;
1740
1757
  exports.ifIn = ifIn;
1741
1758
  exports.ifNotIn = ifNotIn;
1742
1759
  exports.insertBracket = insertBracket;
1760
+ exports.insertCompletionText = insertCompletionText;
1743
1761
  exports.moveCompletionSelection = moveCompletionSelection;
1744
1762
  exports.nextSnippetField = nextSnippetField;
1745
1763
  exports.pickedCompletion = pickedCompletion;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _codemirror_state from '@codemirror/state';
2
- import { EditorState, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
2
+ import { EditorState, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
3
3
  import { EditorView, KeyBinding, Command } from '@codemirror/view';
4
4
  import * as _lezer_common from '@lezer/common';
5
5
 
@@ -18,6 +18,11 @@ interface CompletionConfig {
18
18
  */
19
19
  override?: readonly CompletionSource[] | null;
20
20
  /**
21
+ Determines whether the completion tooltip is closed when the
22
+ editor loses focus. Defaults to true.
23
+ */
24
+ closeOnBlur?: boolean;
25
+ /**
21
26
  The maximum number of options to render to the DOM.
22
27
  */
23
28
  maxRenderedOptions?: number;
@@ -243,6 +248,14 @@ interface CompletionResult {
243
248
  */
244
249
  filter?: boolean;
245
250
  /**
251
+ When [`filter`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.filter) is set to
252
+ `false`, this may be provided to compute the ranges on the label
253
+ that match the input. Should return an array of numbers where
254
+ each pair of adjacent numbers provide the start and end of a
255
+ range.
256
+ */
257
+ getMatch?: (completion: Completion) => readonly number[];
258
+ /**
246
259
  Synchronously update the completion result after typing or
247
260
  deletion. If given, this should not do any expensive work, since
248
261
  it will be called during editor state updates. The function
@@ -257,6 +270,12 @@ This annotation is added to transactions that are produced by
257
270
  picking a completion.
258
271
  */
259
272
  declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
273
+ /**
274
+ Helper function that returns a transaction spec which inserts a
275
+ completion's text in the main selection range, and any other
276
+ selection range that has the same text in front of it.
277
+ */
278
+ declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
260
279
 
261
280
  /**
262
281
  Convert a snippet template to a function that can
@@ -432,4 +451,4 @@ the currently selected completion.
432
451
  */
433
452
  declare function setSelectedCompletion(index: number): StateEffect<unknown>;
434
453
 
435
- export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
454
+ export { CloseBracketConfig, Completion, CompletionContext, CompletionResult, CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/dist/index.js CHANGED
@@ -149,32 +149,35 @@ This annotation is added to transactions that are produced by
149
149
  picking a completion.
150
150
  */
151
151
  const pickedCompletion = /*@__PURE__*/Annotation.define();
152
+ /**
153
+ Helper function that returns a transaction spec which inserts a
154
+ completion's text in the main selection range, and any other
155
+ selection range that has the same text in front of it.
156
+ */
157
+ function insertCompletionText(state, text, from, to) {
158
+ return Object.assign(Object.assign({}, state.changeByRange(range => {
159
+ if (range == state.selection.main)
160
+ return {
161
+ changes: { from: from, to: to, insert: text },
162
+ range: EditorSelection.cursor(from + text.length)
163
+ };
164
+ let len = to - from;
165
+ if (!range.empty ||
166
+ len && state.sliceDoc(range.from - len, range.from) != state.sliceDoc(from, to))
167
+ return { range };
168
+ return {
169
+ changes: { from: range.from - len, to: range.from, insert: text },
170
+ range: EditorSelection.cursor(range.from - len + text.length)
171
+ };
172
+ })), { userEvent: "input.complete" });
173
+ }
152
174
  function applyCompletion(view, option) {
153
175
  const apply = option.completion.apply || option.completion.label;
154
176
  let result = option.source;
155
- if (typeof apply == "string") {
156
- view.dispatch(view.state.changeByRange(range => {
157
- if (range == view.state.selection.main)
158
- return {
159
- changes: { from: result.from, to: result.to, insert: apply },
160
- range: EditorSelection.cursor(result.from + apply.length)
161
- };
162
- let len = result.to - result.from;
163
- if (!range.empty ||
164
- len && view.state.sliceDoc(range.from - len, range.from) != view.state.sliceDoc(result.from, result.to))
165
- return { range };
166
- return {
167
- changes: { from: range.from - len, to: range.from, insert: apply },
168
- range: EditorSelection.cursor(range.from - len + apply.length)
169
- };
170
- }), {
171
- userEvent: "input.complete",
172
- annotations: pickedCompletion.of(option.completion)
173
- });
174
- }
175
- else {
177
+ if (typeof apply == "string")
178
+ view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
179
+ else
176
180
  apply(view, option.completion, result.from, result.to);
177
- }
178
181
  }
179
182
  const SourceCache = /*@__PURE__*/new WeakMap();
180
183
  function asSource(source) {
@@ -316,6 +319,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
316
319
  return combineConfig(configs, {
317
320
  activateOnTyping: true,
318
321
  override: null,
322
+ closeOnBlur: true,
319
323
  maxRenderedOptions: 100,
320
324
  defaultKeymap: true,
321
325
  optionClass: () => "",
@@ -324,6 +328,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
324
328
  addToOptions: []
325
329
  }, {
326
330
  defaultKeymap: (a, b) => a && b,
331
+ closeOnBlur: (a, b) => a && b,
327
332
  icons: (a, b) => a && b,
328
333
  optionClass: (a, b) => c => joinClass(a(c), b(c)),
329
334
  addToOptions: (a, b) => a.concat(b)
@@ -523,6 +528,7 @@ class CompletionTooltip {
523
528
  ul.id = id;
524
529
  ul.setAttribute("role", "listbox");
525
530
  ul.setAttribute("aria-expanded", "true");
531
+ ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
526
532
  for (let i = range.from; i < range.to; i++) {
527
533
  let { completion, match } = options[i];
528
534
  const li = ul.appendChild(document.createElement("li"));
@@ -569,8 +575,14 @@ function sortOptions(active, state) {
569
575
  for (let a of active)
570
576
  if (a.hasResult()) {
571
577
  if (a.result.filter === false) {
572
- for (let option of a.result.options)
573
- options.push(new Option(option, a, [1e9 - i++]));
578
+ let getMatch = a.result.getMatch;
579
+ for (let option of a.result.options) {
580
+ let match = [1e9 - i++];
581
+ if (getMatch)
582
+ for (let n of getMatch(option))
583
+ match.push(n);
584
+ options.push(new Option(option, a, match));
585
+ }
574
586
  }
575
587
  else {
576
588
  let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match;
@@ -978,6 +990,11 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
978
990
  }
979
991
  }, {
980
992
  eventHandlers: {
993
+ blur() {
994
+ let state = this.view.state.field(completionState, false);
995
+ if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur)
996
+ this.view.dispatch({ effects: closeCompletionEffect.of(null) });
997
+ },
981
998
  compositionstart() {
982
999
  this.composing = 1 /* Started */;
983
1000
  },
@@ -1720,4 +1737,4 @@ function setSelectedCompletion(index) {
1720
1737
  return setSelectedEffect.of(index);
1721
1738
  }
1722
1739
 
1723
- export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
1740
+ export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "0.20.0",
3
+ "version": "0.20.3",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",