in src/vs/base/browser/ui/dialog/dialog.ts [193:412]
async show(): Promise<IDialogResult> {
this.focusToReturn = document.activeElement as HTMLElement;
return new Promise<IDialogResult>((resolve) => {
clearNode(this.buttonsContainer);
const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer));
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
// Handle button clicks
buttonMap.forEach((entry, index) => {
const primary = buttonMap[index].index === 0;
const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary })) : this._register(buttonBar.addButton({ title: true, secondary: !primary }));
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
if (button instanceof ButtonWithDescription) {
button.description = this.options.buttonDetails![buttonMap[index].index];
}
this._register(button.onDidClick(e => {
if (e) {
EventHelper.stop(e);
}
resolve({
button: buttonMap[index].index,
checkboxChecked: this.checkbox ? this.checkbox.checked : undefined,
values: this.inputs.length > 0 ? this.inputs.map(input => input.value) : undefined
});
}));
});
// Handle keyboard events globally: Tab, Arrow-Left/Right
this._register(addDisposableListener(window, 'keydown', e => {
const evt = new StandardKeyboardEvent(e);
if (evt.equals(KeyMod.Alt)) {
evt.preventDefault();
}
if (evt.equals(KeyCode.Enter)) {
// Enter in input field should OK the dialog
if (this.inputs.some(input => input.hasFocus())) {
EventHelper.stop(e);
resolve({
button: buttonMap.find(button => button.index !== this.options.cancelId)?.index ?? 0,
checkboxChecked: this.checkbox ? this.checkbox.checked : undefined,
values: this.inputs.length > 0 ? this.inputs.map(input => input.value) : undefined
});
}
return; // leave default handling
}
if (evt.equals(KeyCode.Space)) {
return; // leave default handling
}
let eventHandled = false;
// Focus: Next / Previous
if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow) || evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) {
// Build a list of focusable elements in their visual order
const focusableElements: { focus: () => void }[] = [];
let focusedIndex = -1;
if (this.messageContainer) {
const links = this.messageContainer.querySelectorAll('a');
for (const link of links) {
focusableElements.push(link);
if (link === document.activeElement) {
focusedIndex = focusableElements.length - 1;
}
}
}
for (const input of this.inputs) {
focusableElements.push(input);
if (input.hasFocus()) {
focusedIndex = focusableElements.length - 1;
}
}
if (this.checkbox) {
focusableElements.push(this.checkbox);
if (this.checkbox.hasFocus()) {
focusedIndex = focusableElements.length - 1;
}
}
if (this.buttonBar) {
for (const button of this.buttonBar.buttons) {
focusableElements.push(button);
if (button.hasFocus()) {
focusedIndex = focusableElements.length - 1;
}
}
}
// Focus next element (with wrapping)
if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) {
if (focusedIndex === -1) {
focusedIndex = 0; // default to focus first element if none have focus
}
const newFocusedIndex = (focusedIndex + 1) % focusableElements.length;
focusableElements[newFocusedIndex].focus();
}
// Focus previous element (with wrapping)
else {
if (focusedIndex === -1) {
focusedIndex = focusableElements.length; // default to focus last element if none have focus
}
let newFocusedIndex = focusedIndex - 1;
if (newFocusedIndex === -1) {
newFocusedIndex = focusableElements.length - 1;
}
focusableElements[newFocusedIndex].focus();
}
eventHandled = true;
}
if (eventHandled) {
EventHelper.stop(e, true);
} else if (this.options.keyEventProcessor) {
this.options.keyEventProcessor(evt);
}
}, true));
this._register(addDisposableListener(window, 'keyup', e => {
EventHelper.stop(e, true);
const evt = new StandardKeyboardEvent(e);
if (!this.options.disableCloseAction && evt.equals(KeyCode.Escape)) {
resolve({
button: this.options.cancelId || 0,
checkboxChecked: this.checkbox ? this.checkbox.checked : undefined
});
}
}, true));
// Detect focus out
this._register(addDisposableListener(this.element, 'focusout', e => {
if (!!e.relatedTarget && !!this.element) {
if (!isAncestor(e.relatedTarget as HTMLElement, this.element)) {
this.focusToReturn = e.relatedTarget as HTMLElement;
if (e.target) {
(e.target as HTMLElement).focus();
EventHelper.stop(e, true);
}
}
}
}, false));
const spinModifierClassName = 'codicon-modifier-spin';
this.iconElement.classList.remove(...Codicon.dialogError.classNamesArray, ...Codicon.dialogWarning.classNamesArray, ...Codicon.dialogInfo.classNamesArray, ...Codicon.loading.classNamesArray, spinModifierClassName);
if (this.options.icon) {
this.iconElement.classList.add(...this.options.icon.classNamesArray);
} else {
switch (this.options.type) {
case 'error':
this.iconElement.classList.add(...Codicon.dialogError.classNamesArray);
break;
case 'warning':
this.iconElement.classList.add(...Codicon.dialogWarning.classNamesArray);
break;
case 'pending':
this.iconElement.classList.add(...Codicon.loading.classNamesArray, spinModifierClassName);
break;
case 'none':
case 'info':
case 'question':
default:
this.iconElement.classList.add(...Codicon.dialogInfo.classNamesArray);
break;
}
}
if (!this.options.disableCloseAction) {
const actionBar = this._register(new ActionBar(this.toolbarContainer, {}));
const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), Codicon.dialogClose.classNames, true, async () => {
resolve({
button: this.options.cancelId || 0,
checkboxChecked: this.checkbox ? this.checkbox.checked : undefined
});
}));
actionBar.push(action, { icon: true, label: false, });
}
this.applyStyles();
this.element.setAttribute('aria-modal', 'true');
this.element.setAttribute('aria-labelledby', 'monaco-dialog-icon monaco-dialog-message-text');
this.element.setAttribute('aria-describedby', 'monaco-dialog-icon monaco-dialog-message-text monaco-dialog-message-detail monaco-dialog-message-body');
show(this.element);
// Focus first element (input or button)
if (this.inputs.length > 0) {
this.inputs[0].focus();
this.inputs[0].select();
} else {
buttonMap.forEach((value, index) => {
if (value.index === 0) {
buttonBar.buttons[index].focus();
}
});
}
});
}