From 8c3bf7a8b7fcaed20a0e447e6ed88819e7af05c0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 8 Feb 2019 16:31:04 +0100 Subject: [PATCH] fixes #68240 --- src/vs/base/browser/ui/list/listView.ts | 36 ++++++++++++++------ src/vs/base/browser/ui/list/listWidget.ts | 26 ++++---------- src/vs/base/browser/ui/tree/abstractTree.ts | 11 ++++-- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 +- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 7b60e1ab46b..73ca98dabcf 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -41,6 +41,11 @@ export interface IListViewDragAndDrop extends IListDragAndDrop { getDragElements(element: T): T[]; } +export interface IAriaSetProvider { + getSetSize(element: T, index: number, listLength: number): number; + getPosInSet(element: T, index: number): number; +} + export interface IListViewOptions { readonly dnd?: IListViewDragAndDrop; readonly useShadows?: boolean; @@ -49,7 +54,7 @@ export interface IListViewOptions { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly disableAriaRoles?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } const DefaultOptions = { @@ -64,8 +69,7 @@ const DefaultOptions = { onDragOver() { return false; }, drop() { } }, - horizontalScrolling: false, - disableAriaRoles: false + horizontalScrolling: false }; export class ElementsDragAndDropData implements IDragAndDropData { @@ -144,6 +148,9 @@ function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): export class ListView implements ISpliceable, IDisposable { + private static InstanceCount = 0; + readonly domId = `list_id_${++ListView.InstanceCount}`; + readonly domNode: HTMLElement; private items: IItem[]; @@ -167,7 +174,7 @@ export class ListView implements ISpliceable, IDisposable { private setRowLineHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; - private disableAriaRoles: boolean; + private ariaSetProvider: IAriaSetProvider; private scrollWidth: number | undefined; private canUseTranslate3d: boolean | undefined = undefined; @@ -198,7 +205,7 @@ export class ListView implements ISpliceable, IDisposable { container: HTMLElement, private virtualDelegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListViewOptions = DefaultOptions + options: IListViewOptions = DefaultOptions as IListViewOptions ) { if (options.horizontalScrolling && options.supportDynamicHeights) { throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously'); @@ -219,12 +226,16 @@ export class ListView implements ISpliceable, IDisposable { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-list'; + + DOM.addClass(this.domNode, this.domId); + this.domNode.tabIndex = 0; + DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true); this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling); - this.disableAriaRoles = getOrDefault(options, o => o.disableAriaRoles, DefaultOptions.disableAriaRoles); + this.ariaSetProvider = options.ariaSetProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; @@ -533,6 +544,7 @@ export class ListView implements ISpliceable, IDisposable { if (!item.row) { item.row = this.cache.alloc(item.templateId); + item.row!.domNode!.setAttribute('role', 'treeitem'); } if (!item.row.domNode!.parentElement) { @@ -600,11 +612,9 @@ export class ListView implements ISpliceable, IDisposable { item.row!.domNode!.setAttribute('data-index', `${index}`); item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); - - if (!this.disableAriaRoles) { - item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`); - item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`); - } + item.row!.domNode!.setAttribute('aria-setsize', String(this.ariaSetProvider.getSetSize(item.element, index, this.length))); + item.row!.domNode!.setAttribute('aria-posinset', String(this.ariaSetProvider.getPosInSet(item.element, index))); + item.row!.domNode!.setAttribute('id', this.getElementDomId(index)); DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget); } @@ -1083,6 +1093,10 @@ export class ListView implements ISpliceable, IDisposable { return nextToLastItem.row.domNode; } + getElementDomId(index: number): string { + return `${this.domId}_${index}`; + } + // Dispose dispose() { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index fe2471d11d1..a342692f5c8 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -17,7 +17,7 @@ import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardE import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from './list'; -import { ListView, IListViewOptions, IListViewDragAndDrop } from './listView'; +import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaSetProvider } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -179,16 +179,12 @@ class Trait implements ISpliceable, IDisposable { class FocusTrait extends Trait { - constructor( - private getDomId: (index: number) => string - ) { + constructor() { super('focused'); } renderIndex(index: number, container: HTMLElement): void { super.renderIndex(index, container); - container.setAttribute('role', 'treeitem'); - container.setAttribute('id', this.getDomId(index)); if (this.contains(index)) { container.setAttribute('aria-selected', 'true'); @@ -818,7 +814,7 @@ export interface IListOptions extends IListStyles { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly disableAriaRoles?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } export interface IListStyles { @@ -1069,9 +1065,6 @@ export interface IListOptionsUpdate { export class List implements ISpliceable, IDisposable { - private static InstanceCount = 0; - private idPrefix = `list_id_${++List.InstanceCount}`; - private focus: Trait; private selection: Trait; private eventBufferer = new EventBufferer(); @@ -1167,7 +1160,7 @@ export class List implements ISpliceable, IDisposable { renderers: IListRenderer[], private _options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(i => this.getElementDomId(i)); + this.focus = new FocusTrait(); this.selection = new Trait('selected'); mixin(_options, defaultStyles, false); @@ -1193,12 +1186,9 @@ export class List implements ISpliceable, IDisposable { this.view.domNode.setAttribute('role', _options.ariaRole); } - DOM.addClass(this.view.domNode, this.idPrefix); - this.view.domNode.tabIndex = 0; - this.styleElement = DOM.createStyleSheet(this.view.domNode); - this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix); + this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.view.domId); this.spliceable = new CombinedSpliceable([ new TraitSpliceable(this.focus, this.view, _options.identityProvider), @@ -1528,10 +1518,6 @@ export class List implements ISpliceable, IDisposable { return Math.abs((scrollTop - elementTop) / m); } - private getElementDomId(index: number): string { - return `${this.idPrefix}_${index}`; - } - isDOMFocused(): boolean { return this.view.domNode === document.activeElement; } @@ -1572,7 +1558,7 @@ export class List implements ISpliceable, IDisposable { const focus = this.focus.get(); if (focus.length > 0) { - this.view.domNode.setAttribute('aria-activedescendant', this.getElementDomId(focus[0])); + this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 745aa45ba9f..7c94ff8e627 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -151,7 +151,14 @@ function asListOptions(modelProvider: () => ITreeModel implements IListRenderer(options?: IAsyncDataTreeOpt typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) - ) + ), + ariaSetProvider: undefined }; } -- GitLab