diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index d8883864b4b7c819f204182119f24859de8d2e35..317bc84df1d533cbb2353fe9401a9242757c18a1 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -13,6 +13,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import Event, { Emitter } from 'vs/base/common/event'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; export interface IButtonOptions extends IButtonStyles { } @@ -61,7 +62,7 @@ export class Button { 'role': 'button' }).appendTo(container); - this.$el.on(DOM.EventType.CLICK, (e) => { + this.$el.on(DOM.EventType.CLICK, e => { if (!this.enabled) { DOM.EventHelper.stop(e); return; @@ -70,7 +71,7 @@ export class Button { this._onDidClick.fire(e); }); - this.$el.on(DOM.EventType.KEY_DOWN, (e) => { + this.$el.on(DOM.EventType.KEY_DOWN, e => { let event = new StandardKeyboardEvent(e as KeyboardEvent); let eventHandled = false; if (this.enabled && event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { @@ -86,13 +87,13 @@ export class Button { } }); - this.$el.on(DOM.EventType.MOUSE_OVER, (e) => { + this.$el.on(DOM.EventType.MOUSE_OVER, e => { if (!this.$el.hasClass('disabled')) { this.setHoverBackground(); } }); - this.$el.on(DOM.EventType.MOUSE_OUT, (e) => { + this.$el.on(DOM.EventType.MOUSE_OUT, e => { this.applyStyles(); // restore standard styles }); @@ -135,7 +136,7 @@ export class Button { } } - getElement(): HTMLElement { + get element(): HTMLElement { return this.$el.getHTMLElement(); } @@ -183,4 +184,59 @@ export class Button { this._onDidClick.dispose(); } +} + +export class ButtonGroup { + private _buttons: Button[]; + private toDispose: IDisposable[]; + + constructor(container: Builder, count: number, options?: IButtonOptions); + constructor(container: HTMLElement, count: number, options?: IButtonOptions); + constructor(container: any, count: number, options?: IButtonOptions) { + this._buttons = []; + this.toDispose = []; + + this.create(container, count, options); + } + + get buttons(): Button[] { + return this._buttons; + } + + private create(container: Builder, count: number, options?: IButtonOptions): void; + private create(container: HTMLElement, count: number, options?: IButtonOptions): void; + private create(container: any, count: number, options?: IButtonOptions): void { + for (let index = 0; index < count; index++) { + const button = new Button(container, options); + this._buttons.push(button); + this.toDispose.push(button); + + // Implement keyboard access in buttons if there are multiple + if (count > 1) { + $(button.element).on(DOM.EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e as KeyboardEvent); + let eventHandled = true; + + // Next / Previous Button + let buttonIndexToFocus: number; + if (event.equals(KeyCode.LeftArrow)) { + buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1; + } else if (event.equals(KeyCode.RightArrow)) { + buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1; + } else { + eventHandled = false; + } + + if (eventHandled) { + this._buttons[buttonIndexToFocus].focus(); + DOM.EventHelper.stop(e, true); + } + }, this.toDispose); + } + } + } + + dispose(): void { + this.toDispose = dispose(this.toDispose); + } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index f7f74bf6e0d553d16d4a57e6f9fc9a9f13812232..3150ae326ee656db8c76f26256212cbbf465c9ee 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -36,12 +36,12 @@ interface INotificationToast { export class NotificationsToasts extends Themable { private static MAX_WIDTH = 450; - private static MAX_NOTIFICATIONS = 4; + private static MAX_NOTIFICATIONS = 3; private static PURGE_TIMEOUT: { [severity: number]: number } = (() => { const intervals = Object.create(null); - intervals[Severity.Info] = 8000; - intervals[Severity.Warning] = 12000; + intervals[Severity.Info] = 5000; + intervals[Severity.Warning] = 10000; intervals[Severity.Error] = 15000; return intervals; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 3fae826811e35fe86aad49d43ed14267bdaf5ae4..a89a650cc9c0588259469910aa38bbd2c7c2ccf9 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -12,7 +12,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import URI from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { localize } from 'vs/nls'; -import { Button } from 'vs/base/browser/ui/button/button'; +import { ButtonGroup } from 'vs/base/browser/ui/button/button'; import { attachButtonStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @@ -396,7 +396,24 @@ export class NotificationTemplateRenderer { clearNode(this.template.buttonsContainer); if (notification.expanded) { - notification.actions.primary.forEach(action => this.createButton(notification, action)); + const buttonGroup = new ButtonGroup(this.template.buttonsContainer, notification.actions.primary.length); + buttonGroup.buttons.forEach((button, index) => { + const action = notification.actions.primary[index]; + button.label = action.label; + + this.inputDisposeables.push(button.onDidClick(() => { + + // Run action + this.actionRunner.run(action, notification); + + // Hide notification + notification.dispose(); + })); + + this.inputDisposeables.push(attachButtonStyler(button, this.themeService)); + }); + + this.inputDisposeables.push(buttonGroup); } } @@ -449,24 +466,6 @@ export class NotificationTemplateRenderer { return keybinding ? keybinding.getLabel() : void 0; } - private createButton(notification: INotificationViewItem, action: IAction): Button { - const button = new Button(this.template.buttonsContainer); - button.label = action.label; - this.inputDisposeables.push(button.onDidClick(() => { - - // Run action - this.actionRunner.run(action, notification); - - // Hide notification - notification.dispose(); - })); - - this.inputDisposeables.push(attachButtonStyler(button, this.themeService)); - this.inputDisposeables.push(button); - - return button; - } - public dispose(): void { this.inputDisposeables = dispose(this.inputDisposeables); } diff --git a/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts b/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts index 88a6e36751217cedc4b8314c01e2e7783a0589c1..9cdea4eafd458d586682b3bf15fc48abe909cbf0 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts @@ -102,7 +102,7 @@ export class EmptyView extends ViewsViewletPanel { public focusBody(): void { if (this.button) { - this.button.getElement().focus(); + this.button.element.focus(); } } diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 64f8f8d24b0ed664b1e3c901a1d295d3d9ae1695..a0dbff3705f33c7cf161e85bd5bdd9ed151269d8 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -225,7 +225,7 @@ export class SearchWidget extends Widget { this.toggleReplaceButton.icon = 'toggle-replace-button collapse'; // TODO@joh need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); - this.toggleReplaceButton.getElement().title = nls.localize('search.replace.toggle.button.title', "Toggle Replace"); + this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace"); } private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void { @@ -301,8 +301,8 @@ export class SearchWidget extends Widget { private onToggleReplaceButton(): void { dom.toggleClass(this.replaceContainer, 'disabled'); - dom.toggleClass(this.toggleReplaceButton.getElement(), 'collapse'); - dom.toggleClass(this.toggleReplaceButton.getElement(), 'expand'); + dom.toggleClass(this.toggleReplaceButton.element, 'collapse'); + dom.toggleClass(this.toggleReplaceButton.element, 'expand'); this.updateReplaceActiveState(); this._onReplaceToggled.fire(); }