diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index 8341fd31256..ca63a6398d1 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -103,6 +103,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { private inputBox: InputBox; private inputContainer: Builder; private helpText: Builder; + private resultCount: Builder; private treeContainer: Builder; private progressBar: ProgressBar; private visible: boolean; @@ -232,6 +233,12 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { }); }); + // Result count for screen readers + this.resultCount = div.div({ + 'class': 'quick-open-result-count', + 'aria-live': 'polite' + }).clone(); + // Tree this.treeContainer = div.div({ 'class': 'quick-open-tree' @@ -628,9 +635,12 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { // Indicate entries to tree this.tree.layout(); + const entries = input ? input.entries.filter(e => this.isElementVisible(input, e)) : []; + this.updateResultCount(entries.length); + // Handle auto focus - if (input && input.entries.some(e => this.isElementVisible(input, e))) { - this.autoFocus(input, autoFocus); + if (entries.length) { + this.autoFocus(input, entries, autoFocus); } }, errors.onUnexpectedError); } @@ -643,8 +653,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { return input.filter.isVisible(e); } - private autoFocus(input: IModel, autoFocus: IAutoFocus = {}): void { - const entries = input.entries.filter(e => this.isElementVisible(input, e)); + private autoFocus(input: IModel, entries: any[], autoFocus: IAutoFocus = {}): void { // First check for auto focus of prefix matches if (autoFocus.autoFocusPrefixMatch) { @@ -725,11 +734,13 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { // Indicate entries to tree this.tree.layout(); + const entries = input ? input.entries.filter(e => this.isElementVisible(input, e)) : []; + this.updateResultCount(entries.length); + // Handle auto focus if (autoFocus) { - let doAutoFocus = autoFocus && input && input.entries.some(e => this.isElementVisible(input, e)); - if (doAutoFocus) { - this.autoFocus(input, autoFocus); + if (entries.length) { + this.autoFocus(input, entries, autoFocus); } } }, errors.onUnexpectedError); @@ -769,6 +780,10 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { return height; } + updateResultCount(count: number) { + this.resultCount.text(nls.localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results", count)); + } + hide(reason?: HideReason): void { if (!this.isVisible()) { return; diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css index 97ef9224d98..97ddd6aab12 100644 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ b/src/vs/base/parts/quickopen/browser/quickopen.css @@ -35,6 +35,11 @@ height: 25px; } +.monaco-quick-open-widget .quick-open-result-count { + position: absolute; + left: -10000px; +} + .monaco-quick-open-widget .quick-open-tree { line-height: 22px; } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.css b/src/vs/workbench/browser/parts/quickinput/quickInput.css index fbcdcbe684c..133ce19e8fc 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -69,6 +69,11 @@ margin-left: 5px; } +.quick-input-visible-count { + position: absolute; + left: -10000px; +} + .quick-input-count { align-self: center; position: absolute; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index b0b274f92e1..0f0b4dd4fca 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -62,6 +62,7 @@ interface QuickInputUI { rightActionBar: ActionBar; checkAll: HTMLInputElement; inputBox: QuickInputBox; + visibleCount: CountBadge; count: CountBadge; message: HTMLElement; progressBar: ProgressBar; @@ -79,6 +80,7 @@ type Visibilities = { title?: boolean; checkAll?: boolean; inputBox?: boolean; + visibleCount?: boolean; count?: boolean; message?: boolean; list?: boolean; @@ -481,6 +483,7 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.setElements(this.items); this.ui.list.filter(this.ui.inputBox.value); this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); + this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); if (!this.canSelectMany) { this.ui.list.focus('First'); @@ -515,7 +518,7 @@ class QuickPick extends QuickInput implements IQuickPi } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, list: true }); + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true }); } configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) { @@ -705,6 +708,7 @@ export class QuickInputService extends Component implements IQuickInputService { private layoutDimensions: dom.Dimension; private titleBar: HTMLElement; private filterContainer: HTMLElement; + private visibleCountContainer: HTMLElement; private countContainer: HTMLElement; private okContainer: HTMLElement; private ok: Button; @@ -789,7 +793,12 @@ export class QuickInputService extends Component implements IQuickInputService { const inputBox = this._register(new QuickInputBox(this.filterContainer)); + this.visibleCountContainer = dom.append(this.filterContainer, $('.quick-input-visible-count')); + this.visibleCountContainer.setAttribute('aria-live', 'polite'); + const visibleCount = new CountBadge(this.visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); + this.countContainer = dom.append(this.filterContainer, $('.quick-input-count')); + this.countContainer.setAttribute('aria-live', 'polite'); const count = new CountBadge(this.countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); this._register(attachBadgeStyler(count, this.themeService)); @@ -811,6 +820,9 @@ export class QuickInputService extends Component implements IQuickInputService { this._register(list.onChangedAllVisibleChecked(checked => { checkAll.checked = checked; })); + this._register(list.onChangedVisibleCount(c => { + visibleCount.setCount(c); + })); this._register(list.onChangedCheckedCount(c => { count.setCount(c); })); @@ -880,6 +892,7 @@ export class QuickInputService extends Component implements IQuickInputService { rightActionBar, checkAll, inputBox, + visibleCount, count, message, progressBar, @@ -1047,6 +1060,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui.inputBox.placeholder = ''; this.ui.inputBox.password = false; this.ui.inputBox.showDecoration(Severity.Ignore); + this.ui.visibleCount.setCount(0); this.ui.count.setCount(0); this.ui.message.textContent = ''; this.ui.progressBar.stop(); @@ -1069,6 +1083,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui.title.style.display = visibilities.title ? '' : 'none'; this.ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; this.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; + this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; this.countContainer.style.display = visibilities.count ? '' : 'none'; this.okContainer.style.display = visibilities.ok ? '' : 'none'; this.ui.message.style.display = visibilities.message ? '' : 'none'; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 6655db6f7bf..39ea74dde46 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -160,6 +160,8 @@ export class QuickInputList { onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; private _onChangedCheckedCount = new Emitter(); onChangedCheckedCount: Event = this._onChangedCheckedCount.event; + private _onChangedVisibleCount = new Emitter(); + onChangedVisibleCount: Event = this._onChangedVisibleCount.event; private _onChangedCheckedElements = new Emitter(); onChangedCheckedElements: Event = this._onChangedCheckedElements.event; private _onLeave = new Emitter(); @@ -255,6 +257,17 @@ export class QuickInputList { return count; } + getVisibleCount() { + let count = 0; + const elements = this.elements; + for (let i = 0, n = elements.length; i < n; i++) { + if (!elements[i].hidden) { + count++; + } + } + return count; + } + setAllVisibleChecked(checked: boolean) { try { this._fireCheckedEvents = false; @@ -284,6 +297,7 @@ export class QuickInputList { }, new Map()); this.list.splice(0, this.list.length, this.elements); this.list.setFocus([]); + this._onChangedVisibleCount.fire(this.elements.length); } getFocusedElements() { @@ -415,6 +429,7 @@ export class QuickInputList { this.list.layout(); this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked()); + this._onChangedVisibleCount.fire(shownElements.length); } toggleCheckbox() {