Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Event } from '../../../../base/common/event.js';
import { createSingleCallFunction } from '../../../../base/common/functional.js';
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { localize } from '../../../../nls.js';
import { getCodeEditor, isDiffEditor } from '../../../browser/editorBrowser.js';
import { IRange } from '../../../common/core/range.js';
import { IDiffEditor, IEditor, ScrollType } from '../../../common/editorCommon.js';
Expand Down Expand Up @@ -150,7 +151,8 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu

const model = this.getModel(editor);
if (model) {
status(`${model.getLineContent(options.range.startLineNumber)}`);
const lineContent = model.getLineContent(options.range.startLineNumber);
status(localize('gotoLocation.status', "Line {0}, column {1}: {2}", options.range.startLineNumber, options.range.startColumn, lineContent));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
const editor = context.editor;
const disposables = new DisposableStore();

// Set initial ariaLabel for screen readers
picker.ariaLabel = localize('gotoLine.ariaLabel', "Go to line. Type a line number, optionally followed by colon and column number.");

// Goto line once picked
disposables.add(picker.onDidAccept(event => {
const [item] = picker.selectedItems;
Expand Down Expand Up @@ -97,6 +100,9 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
lineNumber,
column,
label,
ariaLabel: lineNumber
? localize('gotoLine.itemAriaLabel', "Go to line {0}, column {1}. Press Enter to navigate.", lineNumber, column || 1)
: label,
}];

// Clear decorations for invalid range
Expand Down
31 changes: 27 additions & 4 deletions src/vs/platform/quickinput/browser/quickInputBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class QuickInputBox extends Disposable {

private container: HTMLElement;
private findInput: FindInput;
private _listFocusMode: boolean = false;

constructor(
private parent: HTMLElement,
Expand All @@ -37,10 +38,8 @@ export class QuickInputBox extends Disposable {
actionViewItemProvider: createToggleActionViewItemProvider(toggleStyles),
hideHoverOnValueChange: true
}));
const input = this.findInput.inputBox.inputElement;
input.role = 'textbox';
input.ariaHasPopup = 'menu';
input.ariaAutoComplete = 'list';
// Don't set role="textbox" - the input element already has that implicit role
// Don't set aria-haspopup or aria-autocomplete by default - only add them when list is active
}

get onKeyDown() {
Expand Down Expand Up @@ -139,6 +138,30 @@ export class QuickInputBox extends Disposable {
this.findInput.inputBox.inputElement.removeAttribute(name);
}

/**
* Controls the ARIA popup mode for screen readers.
* When enabled (hasActiveDescendant=true), indicates a list popup is active.
* When disabled, removes ARIA attributes to allow normal text input behavior.
* Only updates attributes when the state actually changes to avoid
* unnecessary screen reader re-announcements.
*/
setListFocusMode(hasActiveDescendant: boolean): void {
if (this._listFocusMode === hasActiveDescendant) {
return; // No change, avoid triggering screen reader re-announcements
}
this._listFocusMode = hasActiveDescendant;
const input = this.findInput.inputBox.inputElement;
if (hasActiveDescendant) {
// List item is focused - indicate combobox behavior
input.setAttribute('aria-haspopup', 'listbox');
input.setAttribute('aria-autocomplete', 'list');
} else {
// No list item focused - remove combobox attributes for normal text input
input.removeAttribute('aria-haspopup');
input.removeAttribute('aria-autocomplete');
}
}

showDecoration(decoration: Severity): void {
if (decoration === Severity.Ignore) {
this.findInput.clearMessage();
Expand Down
6 changes: 6 additions & 0 deletions src/vs/platform/quickinput/browser/quickInputController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,10 @@ export class QuickInputController extends Disposable {
const activeDescendant = list.getActiveDescendant();
if (activeDescendant) {
inputBox.setAttribute('aria-activedescendant', activeDescendant);
inputBox.setListFocusMode(true);
} else {
inputBox.removeAttribute('aria-activedescendant');
inputBox.setListFocusMode(false);
}
}
}));
Expand Down Expand Up @@ -280,8 +282,10 @@ export class QuickInputController extends Disposable {
const activeDescendant = tree.getActiveDescendant();
if (activeDescendant) {
inputBox.setAttribute('aria-activedescendant', activeDescendant);
inputBox.setListFocusMode(true);
} else {
inputBox.removeAttribute('aria-activedescendant');
inputBox.setListFocusMode(false);
}
}
}));
Expand Down Expand Up @@ -341,6 +345,8 @@ export class QuickInputController extends Disposable {
// See: https://github.com/microsoft/vscode/issues/271032
if (!isModifierKey(e.keyCode)) {
inputBox.removeAttribute('aria-activedescendant');
// Reset ARIA popup mode to allow normal text editing with arrow keys
inputBox.setListFocusMode(false);
}
}));
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => {
Expand Down
Loading