提交 56a063b4 编写于 作者: A Alex Dima

Suggest widget accessibility

上级 3a3c9e15
......@@ -23,7 +23,7 @@ interface ITraitChangeEvent {
class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData<D>>
{
constructor(
private controller: Trait,
private controller: Trait<T>,
private renderer: IRenderer<T,D>
) {}
......@@ -37,7 +37,8 @@ class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData<D>>
}
renderElement(element: T, index: number, templateData: ITraitTemplateData<D>): void {
DOM.toggleClass(templateData.container, this.controller.trait, this.controller.contains(index));
this.controller.renderElement(element, index, templateData.container);
this.renderer.renderElement(element, index, templateData.data);
}
......@@ -46,7 +47,7 @@ class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData<D>>
}
}
class Trait implements IDisposable {
class Trait<T> implements IDisposable {
private indexes: number[];
......@@ -74,8 +75,8 @@ class Trait implements IDisposable {
this._onChange.fire({ indexes });
}
get trait(): string {
return this._trait;
renderElement(element: T, index: number, container:HTMLElement): void {
DOM.toggleClass(container, this._trait, this.contains(index));
}
set(...indexes: number[]): number[] {
......@@ -93,7 +94,7 @@ class Trait implements IDisposable {
return this.indexes.some(i => i === index);
}
wrapRenderer<T, D>(renderer: IRenderer<T, D>): IRenderer<T, ITraitTemplateData<D>> {
wrapRenderer<D>(renderer: IRenderer<T, D>): IRenderer<T, ITraitTemplateData<D>> {
return new TraitRenderer<T, D>(this, renderer);
}
......@@ -103,6 +104,22 @@ class Trait implements IDisposable {
}
}
class FocusTrait<T> extends Trait<T> {
private _idPrefix:string;
constructor(idPrefix:string) {
super('focused');
this._idPrefix = idPrefix;
}
renderElement(element: T, index: number, container:HTMLElement): void {
super.renderElement(element, index, container);
container.setAttribute('role', 'option');
container.setAttribute('id', idForIndex(this._idPrefix, index));
}
}
class Controller<T> implements IDisposable {
private toDispose: IDisposable[];
......@@ -124,10 +141,17 @@ class Controller<T> implements IDisposable {
}
}
function idForIndex(idPrefix:string, index:number): string {
return idPrefix + '_' + index;
}
export class List<T> implements IDisposable {
private focus: Trait;
private selection: Trait;
private static LIST_INSTANCE_CNT = 0;
private _idPrefix:string;
private focus: Trait<T>;
private selection: Trait<T>;
private eventBufferer: EventBufferer;
private view: ListView<T>;
private controller: Controller<T>;
......@@ -145,7 +169,8 @@ export class List<T> implements IDisposable {
delegate: IDelegate<T>,
renderers: IRenderer<T, any>[]
) {
this.focus = new Trait('focused');
this._idPrefix = 'list_id_' + (++List.LIST_INSTANCE_CNT);
this.focus = new FocusTrait(this._idPrefix);
this.selection = new Trait('selected');
this.eventBufferer = new EventBufferer();
......@@ -156,9 +181,14 @@ export class List<T> implements IDisposable {
});
this.view = new ListView(container, delegate, renderers);
this.view.domNode.setAttribute('role', 'listbox');
this.controller = new Controller(this, this.view);
}
idForIndex(index:number): string {
return idForIndex(this._idPrefix, index);
}
splice(start: number, deleteCount: number, ...elements: T[]): void {
this.eventBufferer.bufferEvents(() => {
this.focus.splice(start, deleteCount, elements.length);
......
......@@ -86,6 +86,7 @@ export interface IView extends IDisposable {
renderOnce(callback:() => any): any;
render(now:boolean): void;
setAriaActiveDescendant(id:string): void;
focus(): void;
isFocused(): boolean;
......@@ -607,6 +608,8 @@ export interface ICodeEditor extends editorCommon.ICommonCodeEditor {
* Set the model ranges that will be hidden in the view.
*/
setHiddenAreas(ranges:editorCommon.IRange[]): void;
setAriaActiveDescendant(id:string): void;
}
/**
......
......@@ -171,6 +171,9 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
this.textArea.setAttribute('aria-label', this.context.configuration.editor.ariaLabel);
this.textArea.setAttribute('role', 'textbox');
this.textArea.setAttribute('aria-multiline', 'true');
this.textArea.setAttribute('aria-haspopup', 'false');
this.textArea.setAttribute('aria-autocomplete', 'both');
StyleMutator.setTop(this.textArea, 0);
StyleMutator.setLeft(this.textArea, 0);
// Give textarea same font size & line height as editor, for the IME case (when the textarea is visible)
......@@ -400,6 +403,20 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
};
}
public setAriaActiveDescendant(id:string): void {
if (id) {
this.textArea.setAttribute('role', 'combobox');
if (this.textArea.getAttribute('aria-activedescendant') !== id) {
this.textArea.setAttribute('aria-haspopup', 'true');
this.textArea.setAttribute('aria-activedescendant', id);
}
} else {
this.textArea.setAttribute('role', 'textbox');
this.textArea.removeAttribute('aria-activedescendant');
this.textArea.removeAttribute('aria-haspopup');
}
}
// --- begin event handlers
public onLayoutChanged(layoutInfo:editorCommon.IEditorLayoutInfo): boolean {
......
......@@ -386,6 +386,13 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser.
}
}
public setAriaActiveDescendant(id:string): void {
if (!this.hasView) {
return;
}
this._view.setAriaActiveDescendant(id);
}
_attachModel(model:editorCommon.IModel): void {
this._view = null;
......
......@@ -75,12 +75,11 @@ class Renderer implements IRenderer<CompletionItem, ISuggestionTemplateData> {
const data = <ISuggestionTemplateData>templateData;
const suggestion = (<CompletionItem>element).suggestion;
data.root.setAttribute('aria-label', suggestion.label);
if (suggestion.type && suggestion.type.charAt(0) === '#') {
data.root.setAttribute('aria-label', 'color');
data.icon.className = 'icon customcolor';
data.colorspan.style.backgroundColor = suggestion.type.substring(1);
} else {
data.root.setAttribute('aria-label', suggestion.type);
data.icon.className = 'icon ' + suggestion.type;
data.colorspan.style.backgroundColor = '';
}
......@@ -375,10 +374,15 @@ export class SuggestWidget implements IContentWidget, IDisposable {
}
if (!e.elements.length) {
this.editor.setAriaActiveDescendant(null);
return;
}
const item = e.elements[0];
// TODO@Alex: the list is not done rendering...
setTimeout(() => {
this.editor.setAriaActiveDescendant(this.list.idForIndex(e.indexes[0]));
}, 100);
if (item === this.focusedItem) {
return;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册