blob: 928500751c818d37b820a01e5ae6da68002e9333 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './icons.html.js';
import './option.js';
import 'chrome://resources/cr_elements/icons.html.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
import {addWebUiListener} from 'chrome://resources/js/cr.js';
import {Debouncer, DomRepeatEvent, enqueueDebouncer, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './app.html.js';
import {BrowserProxy, BrowserProxyImpl} from './browser_proxy.js';
import {Action, Option, ViewModel} from './types.js';
export interface CommanderAppElement {
$: {
input: HTMLInputElement,
inputRow: HTMLElement,
};
}
export class CommanderAppElement extends PolymerElement {
static get is() {
return 'commander-app';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
options_: Array,
focusedIndex_: {
type: Number,
notify: true,
observer: 'onFocusedIndexChanged_',
},
promptText_: String,
resultSetId_: Number,
};
}
private options_: Option[];
private focusedIndex_: number;
private promptText_: string|null;
private browserProxy_: BrowserProxy;
private resultSetId_: number|null;
private savedInput_: string;
private showNoResults_: boolean;
private debouncer_: Debouncer|null;
constructor() {
super();
this.browserProxy_ = BrowserProxyImpl.getInstance();
}
override ready() {
super.ready();
addWebUiListener('view-model-updated', this.onViewModelUpdated_.bind(this));
addWebUiListener('initialize', this.initialize_.bind(this));
this.initialize_();
}
/**
* Resets the UI for a new session.
*/
private initialize_() {
this.options_ = [];
this.$.input.value = '';
this.$.input.focus();
this.focusedIndex_ = -1;
this.resultSetId_ = null;
this.promptText_ = null;
this.savedInput_ = '';
this.showNoResults_ = false;
}
private onKeydown_(e: KeyboardEvent) {
if (e.key === 'Escape') {
this.browserProxy_.dismiss();
return;
}
if (e.key === 'ArrowUp') {
e.preventDefault();
this.focusedIndex_ = (this.focusedIndex_ + this.options_.length - 1) %
this.options_.length;
} else if (e.key === 'ArrowDown') {
e.preventDefault();
this.focusedIndex_ = (this.focusedIndex_ + 1) % this.options_.length;
} else if (e.key === 'Enter') {
if (this.focusedIndex_ >= 0 &&
this.focusedIndex_ < this.options_.length) {
this.notifySelectedAtIndex_(this.focusedIndex_);
}
} else if (
this.promptText_ && e.key === 'Backspace' &&
this.$.input.value === '') {
this.browserProxy_.promptCancelled();
this.promptText_ = null;
this.$.input.value = this.savedInput_;
e.preventDefault();
this.onInput_();
}
}
private onInput_() {
this.browserProxy_.textChanged(this.$.input.value);
}
private onViewModelUpdated_(viewModel: ViewModel) {
if (viewModel.action === Action.DISPLAY_RESULTS) {
this.options_ = viewModel.options || [];
this.resultSetId_ = viewModel.resultSetId;
this.showNoResults_ = this.resultSetId_ != null &&
this.$.input.value !== '' && this.options_.length === 0;
if (this.options_.length > 0) {
this.focusedIndex_ = 0;
}
} else if (viewModel.action === Action.PROMPT) {
this.showNoResults_ = false;
this.options_ = [];
this.resultSetId_ = viewModel.resultSetId;
this.promptText_ = viewModel.promptText || null;
this.savedInput_ = this.$.input.value;
this.$.input.value = '';
this.onInput_();
}
}
private onDomChange_() {
this.debouncer_ = Debouncer.debounce(this.debouncer_, microTask, () => {
this.browserProxy_.heightChanged(document.body.offsetHeight);
});
enqueueDebouncer(this.debouncer_);
}
/**
* Called when a result option is clicked via mouse.
*/
private onOptionClick_(e: DomRepeatEvent<Option>) {
this.notifySelectedAtIndex_(e.model.index);
}
/**
* Used to set `aria-activedescendant` when the focused option changes.
*/
private onFocusedIndexChanged_() {
if (this.focusedIndex_ === -1) {
this.$.inputRow.removeAttribute('aria-activedescendant');
} else {
this.$.inputRow.setAttribute(
'aria-activedescendant', this.getOptionId_(this.focusedIndex_));
}
}
/**
* Informs the browser that the option at |index| was selected.
*/
private notifySelectedAtIndex_(index: number) {
if (this.resultSetId_ !== null) {
this.browserProxy_.optionSelected(index, this.resultSetId_);
}
}
private getOptionClass_(index: number): string {
return index === this.focusedIndex_ ? 'focused' : '';
}
/**
* An id is required for aria-activedescendant
*/
private getOptionId_(index: number): string {
return 'option-' + index;
}
private computeShowChip_(): boolean {
return this.promptText_ !== null;
}
private computeExpanded_(): string {
return this.options_.length > 0 ? 'true' : 'false';
}
private computeAriaSelected_(index: number): string {
return index === this.focusedIndex_ ? 'true' : 'false';
}
}
declare global {
interface HTMLElementTagNameMap {
'commander-app': CommanderAppElement;
}
}
customElements.define(CommanderAppElement.is, CommanderAppElement);