UX: Autocomplete suggestions should be unselected by default (#11637)

When you type # or @ in the search box, a popup appears with
autocomplete suggestions. Currently, when the popup is rendered it has
the first item selected and upon pressing Enter, the first item is
inserted into the search box. The problem with this behavior is that the
first suggestion may not be what you want, and if you are typing quickly
and hit enter, the first suggestion (which is not what you want) is
inserted in the search box.

This PR amends the popup so that it has no suggestions selected by
default which means the enter key will not insert anything unless you
select a suggestion via the up or down arrow keys.
This commit is contained in:
Osama Sayegh 2021-01-07 00:10:18 +03:00 committed by GitHub
parent 690f1a257a
commit e02cc98092
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 4 deletions

View File

@ -94,6 +94,19 @@ export default function (options) {
let div = null; let div = null;
let prevTerm = null; let prevTerm = null;
// By default, when the autcomplete popup is rendered it has the
// first suggestion 'selected', and pressing enter key inserts
// the first suggestion into the input box.
// If you want to stop that behavior, i.e. have the popup renders
// with no suggestions selected, set the `autoSelectFirstSuggestion`
// option to false.
// With this option set to false, users will have to select
// a suggestion via the up/down arrow keys and then press enter
// to insert it.
if (!("autoSelectFirstSuggestion" in options)) {
options.autoSelectFirstSuggestion = true;
}
// input is handled differently // input is handled differently
const isInput = me[0].tagName === "INPUT" && !options.treatAsTextarea; const isInput = me[0].tagName === "INPUT" && !options.treatAsTextarea;
let inputSelectedItems = []; let inputSelectedItems = [];
@ -187,7 +200,11 @@ export default function (options) {
" " + " " +
text.substring(completeEnd + 1, text.length); text.substring(completeEnd + 1, text.length);
me.val(text); me.val(text);
setCaretPosition(me[0], completeStart + 1 + term.length); let newCaretPos = completeStart + 1 + term.length;
if (options.key) {
newCaretPos++;
}
setCaretPosition(me[0], newCaretPos);
if (options && options.afterComplete) { if (options && options.afterComplete) {
options.afterComplete(text); options.afterComplete(text);
@ -276,8 +293,12 @@ export default function (options) {
div = $(options.template({ options: autocompleteOptions })); div = $(options.template({ options: autocompleteOptions }));
let ul = div.find("ul"); let ul = div.find("ul");
if (options.autoSelectFirstSuggestion) {
selectedOption = 0; selectedOption = 0;
markSelected(); markSelected();
} else {
selectedOption = -1;
}
ul.find("li").click(function () { ul.find("li").click(function () {
selectedOption = ul.find("li").index(this); selectedOption = ul.find("li").index(this);
completeTerm(autocompleteOptions[selectedOption]); completeTerm(autocompleteOptions[selectedOption]);
@ -549,8 +570,8 @@ export default function (options) {
// Keyboard codes! So 80's. // Keyboard codes! So 80's.
switch (e.which) { switch (e.which) {
case keys.enter: case keys.enter:
case keys.tab:
if (!autocompleteOptions) { if (!autocompleteOptions) {
closeAutocomplete();
return true; return true;
} }
if ( if (
@ -583,6 +604,7 @@ export default function (options) {
markSelected(); markSelected();
return false; return false;
case keys.backSpace: case keys.backSpace:
autocompleteOptions = null;
completeEnd = cp; completeEnd = cp;
cp--; cp--;
@ -606,6 +628,7 @@ export default function (options) {
updateAutoComplete(dataSource(term, options)); updateAutoComplete(dataSource(term, options));
return true; return true;
default: default:
autocompleteOptions = null;
completeEnd = cp; completeEnd = cp;
return true; return true;
} }

View File

@ -220,6 +220,7 @@ export function applySearchAutocomplete(
key: "#", key: "#",
width: "100%", width: "100%",
treatAsTextarea: true, treatAsTextarea: true,
autoSelectFirstSuggestion: false,
transformComplete(obj) { transformComplete(obj) {
return obj.text; return obj.text;
}, },
@ -240,6 +241,7 @@ export function applySearchAutocomplete(
key: "@", key: "@",
width: "100%", width: "100%",
treatAsTextarea: true, treatAsTextarea: true,
autoSelectFirstSuggestion: false,
transformComplete: (v) => v.username || v.name, transformComplete: (v) => v.username || v.name,
dataSource: (term) => userSearch({ term, includeGroups: true }), dataSource: (term) => userSearch({ term, includeGroups: true }),
afterComplete, afterComplete,