Fix type-to-select behavior; add typeToSelect method to menu

This commit is contained in:
Cory LaViska
2020-07-24 10:41:52 -04:00
parent 46c402c126
commit 4ec2a34718
3 changed files with 37 additions and 22 deletions

4
src/components.d.ts vendored
View File

@@ -450,6 +450,10 @@ export namespace Components {
* Sets focus on the menu.
*/
"setFocus": () => Promise<void>;
/**
* Initiates type-to-select logic, which automatically selects an option based on what the user is currently typing. The key passed will be appended to the internal query and the selection will be updated. After a brief period, the internal query is cleared automatically. This method is intended to be used with the keydown event. Useful for enabling type-to-select when the menu doesn't have focus.
*/
"typeToSelect": (key: string) => Promise<void>;
}
interface SlMenuDivider {
}

View File

@@ -212,21 +212,13 @@ export class Dropdown {
event.preventDefault();
}
const menu = this.getMenu();
// If a menu is present, focus on it when certain keys are pressed
const menu = this.getMenu();
if (menu && ['ArrowDown', 'ArrowUp'].includes(event.key)) {
event.preventDefault();
menu.setFocus();
return;
}
// All other keys focus the menu and pass the event through to it (necessary for type-to-search to work)
if (menu && event.target !== menu) {
menu.setFocus();
requestAnimationFrame(() => menu.dispatchEvent(new KeyboardEvent(event.type, event)));
return;
}
}
handleDocumentMouseDown(event: MouseEvent) {
@@ -255,6 +247,14 @@ export class Dropdown {
event.preventDefault();
event.stopPropagation();
}
// All other keys focus the menu and initiate type-to-select
const menu = this.getMenu();
if (menu && event.target !== menu) {
menu.setFocus();
menu.typeToSelect(event.key);
return;
}
}
togglePanel() {

View File

@@ -20,7 +20,7 @@ export class Menu {
ignoreMouseEvents = false;
ignoreMouseTimeout: any;
menu: HTMLElement;
typeToSelect = '';
typeToSelectString = '';
typeToSelectTimeout: any;
/** Emitted when the menu gains focus. */
@@ -148,20 +148,31 @@ export class Menu {
}
}
// Handle type-to-search behavior when non-control characters are entered
// Handle type-to-select behavior when non-control characters are entered
if (event.key === ' ' || /^[\d\w]$/i.test(event.key)) {
clearTimeout(this.typeToSelectTimeout);
this.typeToSelectTimeout = setTimeout(() => (this.typeToSelect = ''), 750);
this.typeToSelect += event.key;
this.typeToSelect(event.key);
}
}
const items = this.getItems();
for (const item of items) {
const slot = item.shadowRoot.querySelector('slot:not([name])') as HTMLSlotElement;
const label = getTextContent(slot).toLowerCase().trim();
if (label.substring(0, this.typeToSelect.length) === this.typeToSelect) {
items.map(i => (i.active = i === item));
break;
}
/**
* Initiates type-to-select logic, which automatically selects an option based on what the user is currently typing.
* The key passed will be appended to the internal query and the selection will be updated. After a brief period, the
* internal query is cleared automatically. This method is intended to be used with the keydown event. Useful for
* enabling type-to-select when the menu doesn't have focus.
*/
@Method()
async typeToSelect(key: string) {
clearTimeout(this.typeToSelectTimeout);
this.typeToSelectTimeout = setTimeout(() => (this.typeToSelectString = ''), 750);
this.typeToSelectString += key.toLowerCase();
const items = this.getItems();
for (const item of items) {
const slot = item.shadowRoot.querySelector('slot:not([name])') as HTMLSlotElement;
const label = getTextContent(slot).toLowerCase().trim();
if (label.substring(0, this.typeToSelectString.length) === this.typeToSelectString) {
items.map(i => (i.active = i === item));
break;
}
}
}