Make autocomplete offer enum values separately. (#4349)
* Make autocomplete offer enum values separately. * Clarified helper function.
This commit is contained in:
Родитель
419c9363f6
Коммит
dc68cdfb9a
|
@ -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', () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче