Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keybindings to Settings editor indicators #171685

Merged
merged 4 commits into from Jan 18, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -34,6 +34,11 @@ export interface ISettingOverrideClickEvent {

interface SettingIndicator {
element: HTMLElement;
/**
* The element to focus on when navigating with keyboard.
* When undefined, use {@link element} instead.
*/
focusElement?: HTMLElement;
label: SimpleIconLabel;
disposables: DisposableStore;
}
Expand Down Expand Up @@ -65,6 +70,9 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

private readonly profilesEnabled: boolean;

private readonly keybindingListeners: DisposableStore = new DisposableStore();
private focusedIndex = 0;

constructor(
container: HTMLElement,
@IConfigurationService private readonly configurationService: IConfigurationService,
Expand Down Expand Up @@ -122,7 +130,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

private createWorkspaceTrustIndicator(): SettingIndicator {
const workspaceTrustElement = $('span.setting-indicator.setting-item-workspace-trust');
workspaceTrustElement.tabIndex = 0;
const workspaceTrustLabel = new SimpleIconLabel(workspaceTrustElement);
workspaceTrustLabel.text = '$(warning) ' + localize('workspaceUntrustedLabel', "Setting value not applied");

Expand Down Expand Up @@ -163,7 +170,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

private createSyncIgnoredIndicator(): SettingIndicator {
const syncIgnoredElement = $('span.setting-indicator.setting-item-ignored');
syncIgnoredElement.tabIndex = 0;
const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement);
syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced');

Expand All @@ -187,7 +193,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

private createDefaultOverrideIndicator(): SettingIndicator {
const defaultOverrideIndicator = $('span.setting-indicator.setting-item-default-overridden');
defaultOverrideIndicator.tabIndex = 0;
const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator);
defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed");

Expand All @@ -214,7 +219,56 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}
DOM.append(this.indicatorsContainerElement, indicatorsToShow[indicatorsToShow.length - 1].element);
DOM.append(this.indicatorsContainerElement, $('span', undefined, ')'));
this.resetIndicatorNavigationKeyBindings(indicatorsToShow);
}
}

private resetIndicatorNavigationKeyBindings(indicators: SettingIndicator[]) {
this.keybindingListeners.clear();
this.indicatorsContainerElement.role = indicators.length >= 1 ? 'toolbar' : 'button';
if (!indicators.length) {
return;
}
const firstElement = indicators[0].focusElement ?? indicators[0].element;
firstElement.tabIndex = 0;
this.keybindingListeners.add(DOM.addDisposableListener(this.indicatorsContainerElement, 'keydown', (e) => {
const ev = new StandardKeyboardEvent(e);
let handled = true;
if (ev.equals(KeyCode.Home)) {
this.focusIndicatorAt(indicators, 0);
} else if (ev.equals(KeyCode.End)) {
this.focusIndicatorAt(indicators, indicators.length - 1);
} else if (ev.equals(KeyCode.RightArrow)) {
const indexToFocus = (this.focusedIndex + 1) % indicators.length;
this.focusIndicatorAt(indicators, indexToFocus);
} else if (ev.equals(KeyCode.LeftArrow)) {
const indexToFocus = this.focusedIndex ? this.focusedIndex - 1 : indicators.length - 1;
this.focusIndicatorAt(indicators, indexToFocus);
} else {
handled = false;
}

if (handled) {
e.preventDefault();
e.stopPropagation();
}
}));
}

private focusIndicatorAt(indicators: SettingIndicator[], index: number) {
if (index === this.focusedIndex) {
return;
}
const indicator = indicators[index];
const elementToFocus = indicator.focusElement ?? indicator.element;
elementToFocus.tabIndex = 0;
elementToFocus.focus();

const currentlyFocusedIndicator = indicators[this.focusedIndex];
const previousFocusedElement = currentlyFocusedIndicator.focusElement ?? currentlyFocusedIndicator.element;
previousFocusedElement.removeAttribute('tabIndex');

this.focusedIndex = index;
}

updateWorkspaceTrust(element: SettingsTreeSettingElement) {
Expand Down Expand Up @@ -244,17 +298,16 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}

dispose() {
const indicators = [this.workspaceTrustIndicator, this.scopeOverridesIndicator,
this.syncIgnoredIndicator, this.defaultOverrideIndicator];
for (const indicator of indicators) {
this.keybindingListeners.dispose();
for (const indicator of this.allIndicators) {
indicator.disposables.dispose();
}
}

updateScopeOverrides(element: SettingsTreeSettingElement, onDidClickOverrideElement: Emitter<ISettingOverrideClickEvent>, onApplyFilter: Emitter<string>) {
this.scopeOverridesIndicator.element.innerText = '';
this.scopeOverridesIndicator.element.style.display = 'none';
this.scopeOverridesIndicator.element.tabIndex = 0;
this.scopeOverridesIndicator.focusElement = this.scopeOverridesIndicator.element;
if (element.hasPolicyValue) {
// If the setting falls under a policy, then no matter what the user sets, the policy value takes effect.
this.scopeOverridesIndicator.element.style.display = 'inline';
Expand Down Expand Up @@ -302,7 +355,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// just to click into the one override there is.
this.scopeOverridesIndicator.element.style.display = 'inline';
this.scopeOverridesIndicator.element.classList.remove('setting-indicator');
this.scopeOverridesIndicator.element.removeAttribute('tabIndex');
this.scopeOverridesIndicator.disposables.clear();

const prefaceText = element.isConfigured ?
Expand All @@ -312,6 +364,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {

const overriddenScope = element.overriddenScopeList[0];
const view = DOM.append(this.scopeOverridesIndicator.element, $('a.modified-scope', undefined, this.getInlineScopeDisplayText(overriddenScope)));
this.scopeOverridesIndicator.focusElement = view;
const onClickOrKeydown = (e: UIEvent) => {
const [scope, language] = overriddenScope.split(':');
onDidClickOverrideElement.fire({
Expand Down