提交 078397f1 编写于 作者: B Benjamin Pasero

Merge pull request #2232 from Microsoft/ben/tree-aria

Aria roles for tree
......@@ -65,7 +65,6 @@ export class ScrollableElement implements ScrollableElementInt.IScrollableElemen
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-scrollable-element ' + this.options.className;
this.domNode.setAttribute('aria-hidden', 'true');
this.domNode.setAttribute('role', 'presentation');
this.domNode.style.position = 'relative';
this.domNode.style.overflow = 'hidden';
......
......@@ -5,6 +5,7 @@
'use strict';
import 'vs/css!./quickopen';
import nls = require('vs/nls');
import {Promise} from 'vs/base/common/winjs.base';
import platform = require('vs/base/common/platform');
import browser = require('vs/base/browser/browser');
......@@ -174,11 +175,12 @@ export class QuickOpenWidget implements IModelProvider {
renderer: new Renderer(this),
filter: new Filter(this)
}, {
twistiePixels: 11,
indentPixels: 0,
alwaysFocused: true,
verticalScrollMode: 'visible'
});
twistiePixels: 11,
indentPixels: 0,
alwaysFocused: true,
verticalScrollMode: 'visible',
ariaLabel: nls.localize('treeAriaLabel', "Quick Picker")
});
// Handle Focus and Selection event
this.toUnbind.push(this.tree.addListener(EventType.FOCUS, (event: IFocusEvent) => {
......
......@@ -605,6 +605,7 @@ export interface ITreeOptions {
bare?:boolean;
useShadows?:boolean;
paddingOnRow?:boolean;
ariaLabel?:string;
}
export interface ITreeContext extends ITreeConfiguration {
......
......@@ -232,6 +232,25 @@ export class ViewItem implements IViewItem {
this.element.draggable = this.draggable;
this.element.style.height = this.height + 'px';
// ARIA
const base64Id = btoa(this.model.id);
this.element.setAttribute('role', 'treeitem');
if (this.model.hasTrait('focused')) {
this.element.setAttribute('aria-selected', 'true');
this.element.setAttribute('id', base64Id);
this.element.setAttribute('aria-labelledby', base64Id); // force screen reader to compute label from children (helps NVDA at least)
} else {
this.element.setAttribute('aria-selected', 'false');
this.element.removeAttribute('id');
this.element.removeAttribute('aria-labelledby');
}
if (this.model.hasChildren()) {
this.element.setAttribute('aria-expanded', String(this.model.isExpanded()));
} else {
this.element.removeAttribute('aria-expanded');
}
this.element.setAttribute('aria-level', String(this.model.getDepth()));
if (this.context.options.paddingOnRow) {
this.element.style.paddingLeft = this.context.options.twistiePixels + ((this.model.getDepth() - 1) * this.context.options.indentPixels) + 'px';
} else {
......@@ -444,6 +463,12 @@ export class TreeView extends HeightMap implements IScrollable {
this.domNode.className = 'monaco-tree';
this.domNode.tabIndex = 0;
// ARIA
this.domNode.setAttribute('role', 'tree');
if (this.context.options.ariaLabel) {
this.domNode.setAttribute('aria-label', this.context.options.ariaLabel);
}
if (this.context.options.alwaysFocused) {
DOM.addClass(this.domNode, 'focused');
}
......@@ -1053,7 +1078,16 @@ export class TreeView extends HeightMap implements IScrollable {
}
private onModelFocusChange(): void {
DOM.toggleClass(this.domNode, 'no-item-focus', !this.model || !this.model.getFocus());
const focus = this.model && this.model.getFocus();
DOM.toggleClass(this.domNode, 'no-item-focus', !focus);
// ARIA
if (focus) {
this.domNode.setAttribute('aria-activedescendant', btoa(this.context.dataSource.getId(this.context.tree, focus)));
} else {
this.domNode.removeAttribute('aria-activedescendant');
}
}
// HeightMap "events"
......@@ -1502,6 +1536,8 @@ export class TreeView extends HeightMap implements IScrollable {
if (!this.context.options.alwaysFocused) {
DOM.removeClass(this.domNode, 'focused');
}
this.domNode.removeAttribute('aria-activedescendant'); // ARIA
}
// MS specific DOM Events
......
......@@ -238,7 +238,8 @@ export class QuickFixSelectionWidget implements EditorBrowser.IContentWidget {
twistiePixels: 0,
alwaysFocused: true,
verticalScrollMode: 'visible',
useShadows: false
useShadows: false,
ariaLabel: nls.localize('treeAriaLabel', "Quick Fix")
});
this.listenersToRemove.push(this.tree.addListener('selection', (e:Tree.ISelectionEvent) => {
......
......@@ -458,7 +458,8 @@ export class ReferenceWidget extends peekViewWidget.PeekViewWidget {
var options = {
allowHorizontalScroll: false,
twistiePixels: 20
twistiePixels: 20,
ariaLabel: nls.localize('treeAriaLabel', "References")
};
this.tree = new treeWidget.Tree(div.getHTMLElement(), config, options);
......
......@@ -546,7 +546,8 @@ export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable
twistiePixels: 0,
alwaysFocused: true,
verticalScrollMode: 'visible',
useShadows: false
useShadows: false,
ariaLabel: nls.localize('treeAriaLabel', "Suggestions")
};
this.tree = new TreeImpl.Tree(this.treeElement, configuration, options);
......
......@@ -540,7 +540,6 @@ export class CollapsibleViewletView extends CollapsibleView implements IViewletV
function renderViewTree(container: HTMLElement): HTMLElement {
let treeContainer = document.createElement('div');
DOM.addClass(treeContainer, 'explorer-view-content');
container.appendChild(treeContainer);
return treeContainer;
......
......@@ -5,6 +5,7 @@
import errors = require('vs/base/common/errors');
import dom = require('vs/base/browser/dom');
import * as nls from 'vs/nls';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { DefaultController, ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
......@@ -17,7 +18,8 @@ import viewer = require('vs/workbench/parts/debug/browser/debugViewer');
const $ = dom.emmet;
const debugTreeOptions = {
indentPixels: 6,
twistiePixels: 15
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
};
const MAX_ELEMENTS_SHOWN = 18;
......
......@@ -41,9 +41,12 @@ function renderViewTree(container: HTMLElement): HTMLElement {
return treeContainer;
}
const debugTreeOptions = {
indentPixels: 8,
twistiePixels: 20
const debugTreeOptions = (ariaLabel: string) => {
return <tree.ITreeOptions> {
indentPixels: 8,
twistiePixels: 20,
ariaLabel
};
};
const $ = builder.$;
......@@ -75,7 +78,7 @@ class VariablesView extends viewlet.CollapsibleViewletView {
dataSource: new viewer.VariablesDataSource(this.debugService),
renderer: this.instantiationService.createInstance(viewer.VariablesRenderer),
controller: new viewer.BaseDebugController(this.debugService, this.contextMenuService, new viewer.VariablesActionProvider(this.instantiationService))
}, debugTreeOptions);
}, debugTreeOptions(nls.localize('variablesAriaTreeLabel', "Variables")));
const viewModel = this.debugService.getViewModel();
......@@ -143,7 +146,7 @@ class WatchExpressionsView extends viewlet.CollapsibleViewletView {
dataSource: new viewer.WatchExpressionsDataSource(this.debugService),
renderer: this.instantiationService.createInstance(viewer.WatchExpressionsRenderer, actionProvider, this.actionRunner),
controller: new viewer.WatchExpressionsController(this.debugService, this.contextMenuService, actionProvider)
}, debugTreeOptions);
}, debugTreeOptions(nls.localize('watchAriaTreeLabel', "Watch Expressions")));
this.tree.setInput(this.debugService.getModel());
......@@ -212,7 +215,7 @@ class CallStackView extends viewlet.CollapsibleViewletView {
this.tree = new treeimpl.Tree(this.treeContainer, {
dataSource: new viewer.CallStackDataSource(),
renderer: this.instantiationService.createInstance(viewer.CallStackRenderer)
}, debugTreeOptions);
}, debugTreeOptions(nls.localize('callStackAriaLabel', "Call Stack")));
const debugModel = this.debugService.getModel();
......@@ -347,7 +350,7 @@ class BreakpointsView extends viewlet.AdaptiveCollapsibleViewletView {
return first.desiredLineNumber - second.desiredLineNumber;
}
}
}, debugTreeOptions);
}, debugTreeOptions(nls.localize('breakpointsAriaTreeLabel', "Breakpoints")));
const debugModel = this.debugService.getModel();
......@@ -466,6 +469,12 @@ export class DebugViewlet extends viewlet.Viewlet {
return Promise.as(null);
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible).then(() => {
return Promise.join(this.views.map((view) => view.setVisible(visible)));
});
}
public layout(dimension: builder.Dimension): void {
if (this.splitView) {
this.splitView.layout(dimension.height);
......
......@@ -314,7 +314,8 @@ export class ExplorerView extends CollapsibleViewletView {
filter: this.filter,
dnd: dnd
}, {
autoExpandSingleChildren: true
autoExpandSingleChildren: true,
ariaLabel: nls.localize('treeAriaLabel', "Files Explorer")
});
this.toDispose.push(lifecycle.toDisposable(() => renderer.dispose()));
......
......@@ -293,9 +293,10 @@ export class WorkingFilesView extends AdaptiveCollapsibleViewletView {
controller: controller,
dnd: dnd
}, {
indentPixels: 0,
twistiePixels: 8
});
indentPixels: 0,
twistiePixels: 8,
ariaLabel: nls.localize('treeAriaLabel', "Working Files")
});
this.tree.setInput(this.model);
......
......@@ -173,7 +173,8 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV
controller: controller
}, {
indentPixels: 0,
twistiePixels: 20
twistiePixels: 20,
ariaLabel: nls.localize('treeAriaLabel', "Changes View")
});
this.tree.setInput(this.gitService.getModel().getStatus());
......
......@@ -863,6 +863,8 @@ export class SearchViewlet extends Viewlet {
sorter: new SearchSorter(),
filter: new SearchFilter(),
controller: new SearchController()
}, {
ariaLabel: nls.localize('treeAriaLabel', "Search Results")
});
this.toUnbind.push(() => renderer.dispose());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册