Skip to content

Commit

Permalink
refactor(autocomplete): Extend TextField
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 472856121
  • Loading branch information
EstebanG23 authored and copybara-github committed Sep 8, 2022
1 parent 78f125d commit 635e52e
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 69 deletions.
11 changes: 9 additions & 2 deletions autocomplete/filled-autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@

import '@material/web/list/list.js';
import '@material/web/menu-surface/menu-surface.js';
import '@material/web/textfield/filled-text-field.js';
import '@material/web/field/filled-field.js';

import {customElement} from 'lit/decorators.js';
import {literal} from 'lit/static-html.js';

import {styles as filledForcedColorsStyles} from '../textfield/lib/filled-forced-colors-styles.css.js';
import {styles as filledStyles} from '../textfield/lib/filled-styles.css.js';
import {styles as sharedStyles} from '../textfield/lib/shared-styles.css.js';

import {Autocomplete} from './lib/autocomplete.js';

declare global {
Expand All @@ -26,7 +30,10 @@ declare global {
*/
@customElement('md-filled-autocomplete')
export class MdFilledAutocomplete extends Autocomplete {
static override styles =
[sharedStyles, filledStyles, filledForcedColorsStyles];

protected override readonly listTag = literal`md-list`;
protected override readonly menuSurfaceTag = literal`md-menu-surface`;
protected override readonly textFieldTag = literal`md-filled-text-field`;
protected override readonly fieldTag = literal`md-filled-field`;
}
83 changes: 16 additions & 67 deletions autocomplete/lib/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

// TODO(b/243558385): remove compat dependencies
import {observer} from '@material/web/compat/base/observer.js';
import {redispatchEvent} from '@material/web/controller/events.js';
import {stringConverter} from '@material/web/controller/string-converter.js';
import {html, LitElement, PropertyValues, TemplateResult} from 'lit';
import {html, PropertyValues, TemplateResult} from 'lit';
import {property, query, queryAssignedElements, state} from 'lit/decorators.js';
import {html as staticHtml, StaticValue} from 'lit/static-html.js';

Expand All @@ -19,26 +17,12 @@ import {TextField} from '../../textfield/lib/text-field.js';
import {AutocompleteItem} from './autocompleteitem/autocomplete-item.js';

/** @soyCompatible */
export abstract class Autocomplete extends LitElement {
export abstract class Autocomplete extends TextField {
static override shadowRootOptions:
ShadowRootInit = {mode: 'open', delegatesFocus: true};

// TextField properties
// TODO(b/243143708): Add all the remaining text field properties
@property({type: Boolean, reflect: true}) disabled = false;
@property({type: Boolean, reflect: true}) error = false;
@property({type: String}) errorText = '';
@property({type: String}) label?: string;
@property({type: Boolean, reflect: true}) required = false;
@property({type: String}) value = '';
@property({type: String}) prefixText = '';
@property({type: String}) suffixText = '';
@property({type: Boolean}) hasLeadingIcon = false;
@property({type: Boolean}) hasTrailingIcon = false;
@property({type: String}) supportingText = '';
@property({type: String}) supportingTextId = 'support';
@property({type: String, reflect: true, converter: stringConverter})
placeholder = '';
override readonly role = 'combobox';
override readonly ariaAutoComplete = 'list';

/**
* The ID on the list element, used for SSR.
Expand All @@ -49,11 +33,9 @@ export abstract class Autocomplete extends LitElement {
*/
@property({type: String}) itemIdPrefix = 'autocomplete-item';

protected abstract readonly textFieldTag: StaticValue;
protected abstract readonly menuSurfaceTag: StaticValue;
protected abstract readonly listTag: StaticValue;

@query('.md3-autocomplete__text-field') textField?: TextField|null;
@query('.md3-autocomplete__menu-surface') menuSurface?: MenuSurface|null;
@query('.md3-autocomplete__list') list?: List|null;

Expand All @@ -63,51 +45,26 @@ export abstract class Autocomplete extends LitElement {
@state() // tslint:disable-next-line:no-new-decorators
@observer(function(this: Autocomplete) {
this.updateSelectedItem();
this.ariaActiveDescendant = this.selectedItem?.itemId ?? null;
})
protected selectedItem: AutocompleteItem|null = null;

/** @soyTemplate */
override render(): TemplateResult {
return html`<div class="md3-autocomplete"
@click=${this.handleClick}
@click=${this.handleClicked}
@focusout=${this.handleFocusout}
@action=${this.handleAction}
@input=${this.handleInput}
@keydown=${this.handleKeydown}
@keyup=${this.handleKeyup}>
${this.renderTextField()}
${super.render()}
${this.renderMenuSurface()}</div>`;
}

override firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
this.menuSurface!.anchor = this.textField!;
}

/** @soyTemplate */
protected renderTextField(): TemplateResult {
const activeDescendant = this.selectedItem?.itemId ?? '';

return staticHtml`<${this.textFieldTag}
class="md3-autocomplete__text-field"
role="combobox"
aria-autocomplete="list"
aria-activedescendant=${activeDescendant}
aria-controls=${this.listId}
?disabled=${this.disabled}
?error=${this.error}
errorText=${this.errorText}
?hasTrailingIcon=${this.hasTrailingIcon}
?hasLeadingIcon=${this.hasLeadingIcon}
label=${this.label}
value=${this.value}
prefixText=${this.prefixText}
suffixText=${this.suffixText}
supportingText=${this.supportingText}
supportingTextId=${this.supportingTextId}
?required=${this.required}
placeholder=${this.placeholder}>
</${this.textFieldTag}>`;
this.menuSurface!.anchor = this;
}

/** @soyTemplate */
Expand All @@ -132,18 +89,16 @@ export abstract class Autocomplete extends LitElement {

open() {
this.menuSurface?.show();
if (!this.textField) return;
this.textField.ariaExpanded = 'true';
this.ariaExpanded = 'true';
}

close() {
this.menuSurface?.close();
this.selectedItem = null;
if (!this.textField) return;
this.textField.ariaExpanded = 'false';
this.ariaExpanded = 'false';
}

protected handleClick(event: PointerEvent) {
protected handleClicked(event: PointerEvent) {
// When clicking the list (not items nor text field) the menu should stay
// open.
if (this.isOpen() &&
Expand All @@ -154,26 +109,20 @@ export abstract class Autocomplete extends LitElement {
}
}

// TODO(b/243389569): Revisit focus control when extending textfield
protected handleFocusout() {
protected override handleFocusout() {
if (this.matches(':focus-within')) {
this.textField?.focus();
this.getInput().focus();
return;
}
this.close();
this.focused = false;
}

protected handleAction(event: CustomEvent<{item: AutocompleteItem}>) {
const detail = event.detail;
this.value = detail.item.headline;
}

protected handleInput(event: InputEvent) {
if (!event.target) return;
this.value = (event.target as HTMLInputElement).value;
redispatchEvent(this, event);
}

protected handleKeydown(event: KeyboardEvent) {
let bubble = true;
const altKey = event.altKey;
Expand Down Expand Up @@ -236,13 +185,13 @@ export abstract class Autocomplete extends LitElement {
break;

case 'Home':
this.textField?.setSelectionRange(0, 0);
this.setSelectionRange(0, 0);
this.selectedItem = null;
bubble = false;
break;

case 'End':
this.textField?.setSelectionRange(this.value.length, this.value.length);
this.setSelectionRange(this.value.length, this.value.length);
this.selectedItem = null;
bubble = false;
break;
Expand Down

0 comments on commit 635e52e

Please sign in to comment.