Make autocomplete offer enum values separately. (#4349)

* Make autocomplete offer enum values separately.

* Clarified helper function.
This commit is contained in:
Jason Robbins 2024-09-12 22:09:35 +00:00 коммит произвёл GitHub
Родитель 419c9363f6
Коммит dc68cdfb9a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 104 добавлений и 7 удалений

Просмотреть файл

@ -4,11 +4,27 @@ import {createRef, Ref, ref} from 'lit/directives/ref.js';
import {SHARED_STYLES} from '../css/shared-css.js';
import {openSearchHelpDialog} from './chromedash-search-help-dialog.js';
import {QUERIABLE_FIELDS} from './queriable-fields.js';
import {ChromedashTypeahead} from './chromedash-typeahead.js';
import {ChromedashTypeahead, Candidate} from './chromedash-typeahead.js';
const VOCABULARY = QUERIABLE_FIELDS.map(qf => {
return {name: qf.name + '=', doc: qf.doc};
});
function convertQueriableFieldToVocabularyItems(qf): Candidate[] {
if (qf.choices === undefined) {
return [{group: qf.name, name: qf.name + '=', doc: qf.doc}];
}
const result: Candidate[] = [];
for (const ch in qf.choices) {
const label: string = qf.choices[ch][1];
result.push({
group: qf.name,
name: qf.name + '="' + label + '"',
doc: qf.doc,
});
}
return result;
}
const VOCABULARY: Candidate[] = QUERIABLE_FIELDS.map(
convertQueriableFieldToVocabularyItems
).flat();
@customElement('chromedash-feature-filter')
class ChromedashFeatureFilter extends LitElement {

Просмотреть файл

@ -20,7 +20,8 @@ import {SHARED_STYLES} from '../css/shared-css.js';
3. Private class ChromedashTypeaheadItem renders a single item in the
typeahead menu. We do not use SlMenuItem because it steals keyboard focus.
*/
interface Candidate {
export interface Candidate {
group: string;
name: string;
doc: string;
}
@ -130,6 +131,40 @@ export class ChromedashTypeahead extends LitElement {
);
}
// Return true if the user is still entering the keyword and is not
// ready to enter an enum value yet.
shouldGroup(s: string | null): boolean {
if (s === null) {
return true;
}
const COMPARE_OPS = ['=', ':', '<', '>'];
return !COMPARE_OPS.some(op => s.includes(op));
}
groupCandidates(candidates: Candidate[]): Candidate[] {
const groupsSeen = new Set();
const groupsSeenTwice = new Set();
for (const c of candidates) {
if (groupsSeen.has(c.group)) {
groupsSeenTwice.add(c.group);
} else {
groupsSeen.add(c.group);
}
}
const groupsSeenTwiceProcessed = new Set();
const result: Candidate[] = [];
for (const c of candidates) {
if (!groupsSeenTwice.has(c.group)) {
result.push(c);
} else if (!groupsSeenTwiceProcessed.has(c.group)) {
result.push({group: c.group, name: c.group + '=', doc: c.doc});
groupsSeenTwiceProcessed.add(c.group);
}
}
return result;
}
async handleCandidateSelected(e) {
const candidateValue = e.detail.item.value;
const inputEl = this.slInputRef.value?.renderRoot.querySelector('input');
@ -150,8 +185,14 @@ export class ChromedashTypeahead extends LitElement {
this.chunkEnd = this.chunkStart;
inputEl.selectionStart = this.chunkStart;
inputEl.selectionEnd = this.chunkEnd;
// TODO(jrobbins): Don't set termWasCompleted if we offer a value.
this.termWasCompleted = true;
// A term was completed iff there is no other term that the user could
// further complete by typing or selecting.
const possibleExtensions = this.vocabulary.filter(c =>
c.name.startsWith(candidateValue)
);
this.termWasCompleted = possibleExtensions.length <= 1;
this.calcCandidates();
// The user may have clicked a menu item, causing the sl-input to lose
// keyboard focus. So, focus on the sl-input again.
@ -198,6 +239,9 @@ export class ChromedashTypeahead extends LitElement {
this.candidates = this.vocabulary.filter(c =>
this.shouldShowCandidate(c, this.prefix)
);
if (this.shouldGroup(this.prefix)) {
this.candidates = this.groupCandidates(this.candidates);
}
const slDropdown = this.slDropdownRef.value;
if (!slDropdown) return;
if (

Просмотреть файл

@ -124,6 +124,43 @@ describe('chromedash-typeahead', () => {
assert.isFalse(component.shouldShowCandidate(candidate2, 'th'));
assert.isFalse(component.shouldShowCandidate(candidate2, 'th.dot'));
});
it('detects when user is still entering keyword', async () => {
const component = new ChromedashTypeahead();
assert.isTrue(component.shouldGroup(null));
assert.isTrue(component.shouldGroup(''));
assert.isTrue(component.shouldGroup('some-words'));
assert.isFalse(component.shouldGroup('field='));
assert.isFalse(component.shouldGroup('field>'));
assert.isFalse(component.shouldGroup('field>='));
assert.isFalse(component.shouldGroup('field<='));
assert.isFalse(component.shouldGroup('field!='));
assert.isFalse(component.shouldGroup('field:'));
assert.isFalse(component.shouldGroup('field>3'));
assert.isFalse(component.shouldGroup('field="enum value"'));
});
it('copes with empty candidate lists while grouping', async () => {
const component = new ChromedashTypeahead();
assert.deepEqual([], component.groupCandidates([]));
});
it('groups candidates that have the same group value', async () => {
const component = new ChromedashTypeahead();
const candidates = [
{group: 'a', name: 'a=1', doc: 'doc'},
{group: 'b', name: 'b=1', doc: 'doc'},
{group: 'c', name: 'c=1', doc: 'doc'},
{group: 'b', name: 'b=2', doc: 'doc'},
{group: 'b', name: 'b=3', doc: 'doc'},
];
const actual = component.groupCandidates(candidates);
assert.deepEqual(actual, [
{group: 'a', name: 'a=1', doc: 'doc'},
{group: 'b', name: 'b=', doc: 'doc'},
{group: 'c', name: 'c=1', doc: 'doc'},
]);
});
});
describe('chromedash-typeahead-dropdown', () => {