@codemirror/autocomplete 6.0.4 → 6.1.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,11 @@
1
+ ## 6.1.0 (2022-07-19)
2
+
3
+ ### New features
4
+
5
+ You can now provide a `compareCompletions` option to autocompletion to influence the way completions with the same match score are sorted.
6
+
7
+ The `selectOnOpen` option to autocompletion can be used to require explicitly selecting a completion option before `acceptCompletion` does anything.
8
+
1
9
  ## 6.0.4 (2022-07-07)
2
10
 
3
11
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -322,6 +322,7 @@ const completionConfig = state.Facet.define({
322
322
  combine(configs) {
323
323
  return state.combineConfig(configs, {
324
324
  activateOnTyping: true,
325
+ selectOnOpen: true,
325
326
  override: null,
326
327
  closeOnBlur: true,
327
328
  maxRenderedOptions: 100,
@@ -329,7 +330,8 @@ const completionConfig = state.Facet.define({
329
330
  optionClass: () => "",
330
331
  aboveCursor: false,
331
332
  icons: true,
332
- addToOptions: []
333
+ addToOptions: [],
334
+ compareCompletions: (a, b) => a.label.localeCompare(b.label)
333
335
  }, {
334
336
  defaultKeymap: (a, b) => a && b,
335
337
  closeOnBlur: (a, b) => a && b,
@@ -392,6 +394,8 @@ function optionContent(config) {
392
394
  function rangeAroundSelected(total, selected, max) {
393
395
  if (total <= max)
394
396
  return { from: 0, to: total };
397
+ if (selected < 0)
398
+ selected = 0;
395
399
  if (selected <= (total >> 1)) {
396
400
  let off = Math.floor(selected / max);
397
401
  return { from: off * max, to: (off + 1) * max };
@@ -599,7 +603,8 @@ function sortOptions(active, state) {
599
603
  }
600
604
  }
601
605
  let result = [], prev = null;
602
- for (let opt of options.sort(cmpOption)) {
606
+ let compare = state.facet(completionConfig).compareCompletions;
607
+ for (let opt of options.sort((a, b) => (b.match[0] - a.match[0]) || compare(a.completion, b.completion))) {
603
608
  if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
604
609
  (prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
605
610
  prev.apply != opt.completion.apply)
@@ -626,8 +631,8 @@ class CompletionDialog {
626
631
  let options = sortOptions(active, state);
627
632
  if (!options.length)
628
633
  return null;
629
- let selected = 0;
630
- if (prev && prev.selected) {
634
+ let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
635
+ if (prev && prev.selected != selected && prev.selected != -1) {
631
636
  let selectedValue = prev.options[prev.selected].completion;
632
637
  for (let i = 0; i < options.length; i++)
633
638
  if (options[i].completion == selectedValue) {
@@ -697,20 +702,16 @@ const baseAttrs = {
697
702
  "aria-autocomplete": "list"
698
703
  };
699
704
  function makeAttrs(id, selected) {
700
- return {
705
+ let result = {
701
706
  "aria-autocomplete": "list",
702
707
  "aria-haspopup": "listbox",
703
- "aria-activedescendant": id + "-" + selected,
704
708
  "aria-controls": id
705
709
  };
710
+ if (selected > -1)
711
+ result["aria-activedescendant"] = id + "-" + selected;
712
+ return result;
706
713
  }
707
714
  const none = [];
708
- function cmpOption(a, b) {
709
- let dScore = b.match[0] - a.match[0];
710
- if (dScore)
711
- return dScore;
712
- return a.completion.label.localeCompare(b.completion.label);
713
- }
714
715
  function getUserEvent(tr) {
715
716
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
716
717
  }
@@ -818,7 +819,8 @@ function moveCompletionSelection(forward, by = "option") {
818
819
  if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
819
820
  step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
820
821
  tooltip.dom.querySelector("li").offsetHeight) - 1);
821
- let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
822
+ let { length } = cState.open.options;
823
+ let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
822
824
  if (selected < 0)
823
825
  selected = by == "page" ? 0 : length - 1;
824
826
  else if (selected >= length)
@@ -832,7 +834,8 @@ Accept the current completion.
832
834
  */
833
835
  const acceptCompletion = (view) => {
834
836
  let cState = view.state.field(completionState, false);
835
- if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
837
+ if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin ||
838
+ cState.open.selected < 0)
836
839
  return false;
837
840
  applyCompletion(view, cState.open.options[cState.open.selected]);
838
841
  return true;
@@ -1745,7 +1748,7 @@ Return the currently selected completion, if any.
1745
1748
  function selectedCompletion(state) {
1746
1749
  var _a;
1747
1750
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1748
- return open ? open.options[open.selected].completion : null;
1751
+ return open && open.selected >= 0 ? open.options[open.selected].completion : null;
1749
1752
  }
1750
1753
  /**
1751
1754
  Returns the currently selected position in the active completion
@@ -1754,7 +1757,7 @@ list, or null if no completions are active.
1754
1757
  function selectedCompletionIndex(state) {
1755
1758
  var _a;
1756
1759
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1757
- return open ? open.selected : null;
1760
+ return open && open.selected >= 0 ? open.selected : null;
1758
1761
  }
1759
1762
  /**
1760
1763
  Create an effect that can be attached to a transaction to change
package/dist/index.d.ts CHANGED
@@ -10,6 +10,15 @@ interface CompletionConfig {
10
10
  */
11
11
  activateOnTyping?: boolean;
12
12
  /**
13
+ By default, when completion opens, the first option is selected
14
+ and can be confirmed with
15
+ [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
16
+ is set to false, the completion widget starts with no completion
17
+ selected, and the user has to explicitly move to a completion
18
+ before you can confirm one.
19
+ */
20
+ selectOnOpen?: boolean;
21
+ /**
13
22
  Override the completion sources used. By default, they will be
14
23
  taken from the `"autocomplete"` [language
15
24
  data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) (which should hold
@@ -63,6 +72,12 @@ interface CompletionConfig {
63
72
  render: (completion: Completion, state: EditorState) => Node | null;
64
73
  position: number;
65
74
  }[];
75
+ /**
76
+ The comparison function to use when sorting completions with the same
77
+ match score. Defaults to using
78
+ [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
79
+ */
80
+ compareCompletions?: (a: Completion, b: Completion) => number;
66
81
  }
67
82
 
68
83
  /**
package/dist/index.js CHANGED
@@ -318,6 +318,7 @@ const completionConfig = /*@__PURE__*/Facet.define({
318
318
  combine(configs) {
319
319
  return combineConfig(configs, {
320
320
  activateOnTyping: true,
321
+ selectOnOpen: true,
321
322
  override: null,
322
323
  closeOnBlur: true,
323
324
  maxRenderedOptions: 100,
@@ -325,7 +326,8 @@ const completionConfig = /*@__PURE__*/Facet.define({
325
326
  optionClass: () => "",
326
327
  aboveCursor: false,
327
328
  icons: true,
328
- addToOptions: []
329
+ addToOptions: [],
330
+ compareCompletions: (a, b) => a.label.localeCompare(b.label)
329
331
  }, {
330
332
  defaultKeymap: (a, b) => a && b,
331
333
  closeOnBlur: (a, b) => a && b,
@@ -388,6 +390,8 @@ function optionContent(config) {
388
390
  function rangeAroundSelected(total, selected, max) {
389
391
  if (total <= max)
390
392
  return { from: 0, to: total };
393
+ if (selected < 0)
394
+ selected = 0;
391
395
  if (selected <= (total >> 1)) {
392
396
  let off = Math.floor(selected / max);
393
397
  return { from: off * max, to: (off + 1) * max };
@@ -595,7 +599,8 @@ function sortOptions(active, state) {
595
599
  }
596
600
  }
597
601
  let result = [], prev = null;
598
- for (let opt of options.sort(cmpOption)) {
602
+ let compare = state.facet(completionConfig).compareCompletions;
603
+ for (let opt of options.sort((a, b) => (b.match[0] - a.match[0]) || compare(a.completion, b.completion))) {
599
604
  if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
600
605
  (prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
601
606
  prev.apply != opt.completion.apply)
@@ -622,8 +627,8 @@ class CompletionDialog {
622
627
  let options = sortOptions(active, state);
623
628
  if (!options.length)
624
629
  return null;
625
- let selected = 0;
626
- if (prev && prev.selected) {
630
+ let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
631
+ if (prev && prev.selected != selected && prev.selected != -1) {
627
632
  let selectedValue = prev.options[prev.selected].completion;
628
633
  for (let i = 0; i < options.length; i++)
629
634
  if (options[i].completion == selectedValue) {
@@ -693,20 +698,16 @@ const baseAttrs = {
693
698
  "aria-autocomplete": "list"
694
699
  };
695
700
  function makeAttrs(id, selected) {
696
- return {
701
+ let result = {
697
702
  "aria-autocomplete": "list",
698
703
  "aria-haspopup": "listbox",
699
- "aria-activedescendant": id + "-" + selected,
700
704
  "aria-controls": id
701
705
  };
706
+ if (selected > -1)
707
+ result["aria-activedescendant"] = id + "-" + selected;
708
+ return result;
702
709
  }
703
710
  const none = [];
704
- function cmpOption(a, b) {
705
- let dScore = b.match[0] - a.match[0];
706
- if (dScore)
707
- return dScore;
708
- return a.completion.label.localeCompare(b.completion.label);
709
- }
710
711
  function getUserEvent(tr) {
711
712
  return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
712
713
  }
@@ -814,7 +815,8 @@ function moveCompletionSelection(forward, by = "option") {
814
815
  if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
815
816
  step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
816
817
  tooltip.dom.querySelector("li").offsetHeight) - 1);
817
- let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
818
+ let { length } = cState.open.options;
819
+ let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
818
820
  if (selected < 0)
819
821
  selected = by == "page" ? 0 : length - 1;
820
822
  else if (selected >= length)
@@ -828,7 +830,8 @@ Accept the current completion.
828
830
  */
829
831
  const acceptCompletion = (view) => {
830
832
  let cState = view.state.field(completionState, false);
831
- if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
833
+ if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin ||
834
+ cState.open.selected < 0)
832
835
  return false;
833
836
  applyCompletion(view, cState.open.options[cState.open.selected]);
834
837
  return true;
@@ -1741,7 +1744,7 @@ Return the currently selected completion, if any.
1741
1744
  function selectedCompletion(state) {
1742
1745
  var _a;
1743
1746
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1744
- return open ? open.options[open.selected].completion : null;
1747
+ return open && open.selected >= 0 ? open.options[open.selected].completion : null;
1745
1748
  }
1746
1749
  /**
1747
1750
  Returns the currently selected position in the active completion
@@ -1750,7 +1753,7 @@ list, or null if no completions are active.
1750
1753
  function selectedCompletionIndex(state) {
1751
1754
  var _a;
1752
1755
  let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
1753
- return open ? open.selected : null;
1756
+ return open && open.selected >= 0 ? open.selected : null;
1754
1757
  }
1755
1758
  /**
1756
1759
  Create an effect that can be attached to a transaction to change
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/autocomplete",
3
- "version": "6.0.4",
3
+ "version": "6.1.0",
4
4
  "description": "Autocompletion for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",