提交 24a7cb0c 编写于 作者: C Christof Marti

Keyboard access (#45589)

上级 7cd87957
......@@ -20,6 +20,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { CancellationToken } from 'vs/base/common/cancellation';
import { QuickInputCheckboxList } from './quickInputCheckboxList';
import { QuickInputBox } from './quickInputBox';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
const $ = dom.$;
......@@ -56,12 +58,35 @@ export class QuickInputService extends Component implements IQuickInputService {
this.container.style.display = 'none';
this.inputBox = new QuickInputBox(this.container);
this.toUnbind.push(this.inputBox);
this.inputBox.style(this.themeService.getTheme());
this.inputBox.onInput(value => {
this.checkboxList.filter(value);
});
this.toUnbind.push(this.inputBox.onKeyDown(event => {
switch (event.keyCode) {
case KeyCode.DownArrow:
this.checkboxList.focus('Next');
break;
case KeyCode.UpArrow:
this.checkboxList.focus('Previous');
break;
case KeyCode.PageDown:
this.checkboxList.focus('NextPage');
break;
case KeyCode.PageUp:
this.checkboxList.focus('PreviousPage');
break;
case KeyCode.Space:
if (event.ctrlKey) {
this.checkboxList.toggleCheckbox();
}
break;
}
}));
this.checkboxList = this.instantiationService.createInstance(QuickInputCheckboxList, this.container);
this.toUnbind.push(this.checkboxList);
const buttonContainer = dom.append(this.container, $('.quick-input-actions'));
const cancel = dom.append(buttonContainer, $('button'));
......@@ -79,6 +104,19 @@ export class QuickInputService extends Component implements IQuickInputService {
}
this.close(false);
}));
this.toUnbind.push(dom.addDisposableListener(this.container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
switch (event.keyCode) {
case KeyCode.Enter:
dom.EventHelper.stop(e, true);
this.close(true);
break;
case KeyCode.Escape:
dom.EventHelper.stop(e, true);
this.close(false);
break;
}
}));
}
private close(ok: boolean) {
......@@ -92,6 +130,9 @@ export class QuickInputService extends Component implements IQuickInputService {
async pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<T[]> {
this.create();
if (this.resolve) {
this.resolve(undefined);
}
this.inputBox.setPlaceholder(options.placeHolder || '');
// TODO: Progress indication.
......
......@@ -11,7 +11,8 @@ import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import * as nls from 'vs/nls';
import { inputBackground, inputForeground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
const $ = dom.$;
......@@ -19,8 +20,9 @@ const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickInputBoxAriaLabel', "Type to
export class QuickInputBox {
public container: HTMLElement;
private container: HTMLElement;
private inputBox: InputBox;
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement
......@@ -29,6 +31,7 @@ export class QuickInputBox {
this.inputBox = new InputBox(this.container, null, {
ariaLabel: DEFAULT_INPUT_ARIA_LABEL
});
this.disposables.push(this.inputBox);
// ARIA
const inputElement = this.inputBox.inputElement;
......@@ -37,6 +40,12 @@ export class QuickInputBox {
inputElement.setAttribute('aria-autocomplete', 'list');
}
onKeyDown(handler: (event: StandardKeyboardEvent) => void): IDisposable {
return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
handler(new StandardKeyboardEvent(e));
});
}
onInput(handler: (event: string) => void): IDisposable {
return this.inputBox.onDidChange(handler);
}
......@@ -60,4 +69,8 @@ export class QuickInputBox {
inputBorder: theme.getColor(inputBorder)
});
}
dispose() {
this.disposables = dispose(this.disposables);
}
}
......@@ -15,30 +15,57 @@ import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
import { compareAnything } from 'vs/base/common/comparers';
import { Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
const $ = dom.$;
export interface ISelectedElement {
interface ISelectableElement {
index: number;
item: object;
label: string;
shouldAlwaysShow?: boolean;
hidden?: boolean;
selected?: boolean;
selected: boolean;
}
class SelectableElement implements ISelectableElement {
index: number;
item: object;
label: string;
shouldAlwaysShow = false;
hidden = false;
private _onSelected = new Emitter<boolean>();
onSelected = this._onSelected.event;
_selected: boolean;
get selected() {
return this._selected;
}
set selected(value: boolean) {
if (value !== this._selected) {
this._selected = value;
this._onSelected.fire(value);
}
}
labelHighlights?: IMatch[];
descriptionHighlights?: IMatch[];
detailHighlights?: IMatch[];
constructor(init: ISelectableElement) {
assign(this, init);
}
}
interface ISelectedElementTemplateData {
element: HTMLElement;
name: HTMLElement;
checkbox: HTMLInputElement;
context: ISelectedElement;
toDispose: IDisposable[];
context: SelectableElement;
toDisposeElement: IDisposable[];
toDisposeTemplate: IDisposable[];
}
class SelectedElementRenderer implements IRenderer<ISelectedElement, ISelectedElementTemplateData> {
class SelectedElementRenderer implements IRenderer<SelectableElement, ISelectedElementTemplateData> {
static readonly ID = 'selectedelement';
......@@ -52,8 +79,11 @@ class SelectedElementRenderer implements IRenderer<ISelectedElement, ISelectedEl
data.checkbox = <HTMLInputElement>$('input');
data.checkbox.type = 'checkbox';
data.toDispose = [];
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => data.context.selected = !data.context.selected));
data.toDisposeElement = [];
data.toDisposeTemplate = [];
data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
data.context.selected = data.checkbox.checked;
}));
dom.append(data.element, data.checkbox);
......@@ -62,34 +92,37 @@ class SelectedElementRenderer implements IRenderer<ISelectedElement, ISelectedEl
return data;
}
renderElement(element: ISelectedElement, index: number, data: ISelectedElementTemplateData): void {
renderElement(element: SelectableElement, index: number, data: ISelectedElementTemplateData): void {
dispose(data.toDisposeElement);
data.context = element;
data.name.textContent = element.label;
data.element.title = data.name.textContent;
data.checkbox.checked = element.selected;
data.toDisposeElement.push(element.onSelected(selected => data.checkbox.checked = selected));
}
disposeTemplate(templateData: ISelectedElementTemplateData): void {
dispose(templateData.toDispose);
disposeTemplate(data: ISelectedElementTemplateData): void {
dispose(data.toDisposeTemplate);
}
}
class SelectedElementDelegate implements IDelegate<ISelectedElement> {
class SelectedElementDelegate implements IDelegate<SelectableElement> {
getHeight(element: ISelectedElement): number {
getHeight(element: SelectableElement): number {
return 22;
}
getTemplateId(element: ISelectedElement): string {
getTemplateId(element: SelectableElement): string {
return SelectedElementRenderer.ID;
}
}
export class QuickInputCheckboxList {
container: HTMLElement;
private list: WorkbenchList<ISelectedElement>;
private elements: ISelectedElement[] = [];
private container: HTMLElement;
private list: WorkbenchList<SelectableElement>;
private elements: SelectableElement[] = [];
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
......@@ -100,11 +133,18 @@ export class QuickInputCheckboxList {
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new SelectedElementRenderer()], {
identityProvider: element => element.label,
multipleSelectionSupport: false
}) as WorkbenchList<ISelectedElement>;
}) as WorkbenchList<SelectableElement>;
this.disposables.push(this.list);
this.disposables.push(this.list.onKeyDown(e => {
const event = new StandardKeyboardEvent(e);
if (event.keyCode === KeyCode.Space) {
this.toggleCheckbox();
}
}));
}
setElements(elements: IPickOpenEntry[]): void {
this.elements = elements.map((item, index) => ({
this.elements = elements.map((item, index) => new SelectableElement({
index,
item,
label: item.label,
......@@ -118,9 +158,8 @@ export class QuickInputCheckboxList {
.map(e => e.item);
}
setFocus(): void {
this.list.focusFirst();
this.list.domFocus();
focus(what: 'Next' | 'Previous' | 'NextPage' | 'PreviousPage'): void {
this.list['focus' + what]();
}
layout(): void {
......@@ -176,9 +215,20 @@ export class QuickInputCheckboxList {
this.list.focusFirst();
}
}
toggleCheckbox() {
const elements = this.list.getFocusedElements();
for (const element of elements) {
element.selected = !element.selected;
}
}
dispose() {
this.disposables = dispose(this.disposables);
}
}
function compareEntries(elementA: ISelectedElement, elementB: ISelectedElement, lookFor: string): number {
function compareEntries(elementA: SelectableElement, elementB: SelectableElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
const labelHighlightsB = elementB.labelHighlights || [];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册