提交 618dbf47 编写于 作者: J João Moreno

improve list accessibility

fixes #17113
上级 c0cfb1a6
......@@ -7,7 +7,7 @@ import 'vs/css!./list';
import { IDisposable } from 'vs/base/common/lifecycle';
import { range } from 'vs/base/common/arrays';
import { IDelegate, IRenderer, IFocusChangeEvent, ISelectionChangeEvent } from './list';
import { List } from './listWidget';
import { List, IListOptions } from './listWidget';
import { IPagedModel } from 'vs/base/common/paging';
import Event, { mapEvent } from 'vs/base/common/event';
......@@ -67,10 +67,11 @@ export class PagedList<T> {
constructor(
container: HTMLElement,
delegate: IDelegate<number>,
renderers: IPagedRenderer<T, any>[]
renderers: IPagedRenderer<T, any>[],
options: IListOptions = {}
) {
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
this.list = new List(container, delegate, pagedRenderers);
this.list = new List(container, delegate, pagedRenderers, options);
}
get onFocusChange(): Event<IFocusChangeEvent<T>> {
......
......@@ -116,7 +116,7 @@ class FocusTrait<T> extends Trait<T> {
renderElement(element: T, index: number, container: HTMLElement): void {
super.renderElement(element, index, container);
container.setAttribute('role', 'option');
container.setAttribute('role', 'treeitem');
container.setAttribute('id', this.getElementId(index));
}
}
......@@ -201,6 +201,7 @@ class Controller<T> implements IDisposable {
}
export interface IListOptions extends IListViewOptions {
ariaLabel?: string;
}
const DefaultOptions: IListOptions = {};
......@@ -245,13 +246,17 @@ export class List<T> implements IDisposable {
});
this.view = new ListView(container, delegate, renderers, options);
this.view.domNode.setAttribute('role', 'listbox');
this.view.domNode.setAttribute('role', 'tree');
this.view.domNode.tabIndex = 0;
this.controller = new Controller(this, this.view);
this.disposables = [this.focus, this.selection, this.view, this.controller];
this._onDOMFocus = domEvent(this.view.domNode, 'focus');
this.onFocusChange(this._onFocusChange, this, this.disposables);
if (options.ariaLabel) {
this.view.domNode.setAttribute('aria-label', options.ariaLabel);
}
}
splice(start: number, deleteCount: number, ...elements: T[]): void {
......@@ -418,7 +423,16 @@ export class List<T> implements IDisposable {
}
private _onFocusChange(): void {
DOM.toggleClass(this.view.domNode, 'element-focused', this.focus.get().length > 0);
const focus = this.focus.get();
if (focus.length > 0) {
this.view.domNode.setAttribute('aria-activedescendant', this.getElementId(focus[0]));
} else {
this.view.domNode.removeAttribute('aria-activedescendant');
}
this.view.domNode.setAttribute('role', 'tree');
DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0);
}
dispose(): void {
......
......@@ -23,6 +23,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
export interface ITemplateData {
root: HTMLElement;
element: HTMLElement;
icon: HTMLImageElement;
name: HTMLElement;
......@@ -92,7 +93,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar];
return {
element, icon, name, installCount, ratings, author, description, disposables,
root, element, icon, name, installCount, ratings, author, description, disposables,
extensionDisposables: [],
set extension(extension: IExtension) {
versionWidget.extension = extension;
......@@ -110,6 +111,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
renderPlaceholder(index: number, data: ITemplateData): void {
addClass(data.element, 'loading');
data.root.removeAttribute('aria-label');
data.extensionDisposables = dispose(data.extensionDisposables);
data.icon.src = '';
data.name.textContent = '';
......@@ -142,6 +144,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.icon.style.visibility = 'inherit';
}
data.root.setAttribute('aria-label', extension.displayName);
data.name.textContent = extension.displayName;
data.author.textContent = extension.publisherDisplayName;
data.description.textContent = extension.description;
......
......@@ -102,7 +102,9 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
const delegate = new Delegate();
const renderer = this.instantiationService.createInstance(Renderer);
this.list = new PagedList(this.extensionsBox, delegate, [renderer]);
this.list = new PagedList(this.extensionsBox, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions")
});
const onKeyDown = chain(domEvent(this.searchBox, 'keydown'))
.map(e => new StandardKeyboardEvent(e));
......@@ -434,7 +436,7 @@ export class StatusUpdater implements IWorkbenchContribution {
private onServiceChange(): void {
if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) {
this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', 'Extensions')), 'extensions-badge progress-badge');
this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge');
return;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册