From 6f272b6bab17aefa396716f8e96934928deaee14 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 08:18:58 +0100 Subject: [PATCH 01/63] css - do not hide outline in elements with focus --- .../base/browser/ui/actionbar/actionbar.css | 9 ------ src/vs/base/browser/ui/actionbar/actionbar.ts | 11 +------ src/vs/base/browser/ui/dropdown/dropdown.css | 4 --- src/vs/base/browser/ui/toolbar/toolbar.css | 30 ++----------------- src/vs/base/browser/ui/toolbar/toolbar.ts | 2 +- src/vs/base/parts/tree/browser/tree.css | 4 --- .../contrib/find/browser/findWidget.css | 4 --- .../browser/iPadShowKeyboard.css | 1 - .../browser/referenceSearchWidget.css | 4 --- .../parts/activitybar/activitybarPart.ts | 2 -- .../activitybar/media/activityaction.css | 3 +- .../parts/editor/media/binarydiffeditor.css | 12 +++----- .../parts/editor/media/binaryeditor.css | 12 +++----- .../parts/editor/media/iframeeditor.css | 4 --- .../electron-browser/media/shell.css | 15 ++++++++++ 15 files changed, 28 insertions(+), 89 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 632a5bc8083..d70fbcc27e4 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -48,14 +48,6 @@ margin-right: 0.3em; } -.monaco-action-bar:focus { - outline: 0; -} - -.monaco-action-bar .action-label:focus { - outline: 0; -} - .monaco-action-bar .action-item.disabled .action-label, .monaco-action-bar .action-item.disabled .action-label:hover { opacity: 0.4; @@ -183,7 +175,6 @@ .monaco-workbench .action-bar-select { height: 20px; margin: 5px 4px; - outline: none; } .vs-dark.monaco-workbench .action-bar-select { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index e423b844539..0e6ba167431 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -224,7 +224,7 @@ export class ActionItem extends BaseActionItem { public render(container: HTMLElement): void { super.render(container); - this.$e = $('a.action-label').attr('tabIndex', '-1').appendTo(this.builder); + this.$e = $('a.action-label').attr('tabIndex', '0').appendTo(this.builder); this.$e.attr({ role: 'menuitem' }); if (this.options.label && this.options.keybinding) { @@ -370,7 +370,6 @@ export interface IActionItemProvider { export interface IActionBarOptions { orientation?: ActionsOrientation; context?: any; - disableTabIndex?: boolean; actionItemProvider?: IActionItemProvider; actionRunner?: actions.IActionRunner; } @@ -548,10 +547,6 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.actionsList.insertBefore(actionItemElement, this.actionsList.children[index++]); } - if (!this.options.disableTabIndex && !this.domNode.hasAttribute('tabIndex')) { - this.domNode.tabIndex = 0; // make sure an action bar with actions participates in tab navigation - } - this.items.push(item); }); } @@ -562,10 +557,6 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { item.dispose(); } $(this.actionsList).empty(); - - if (!this.options.disableTabIndex) { - this.domNode.removeAttribute('tabIndex'); // empty action bar does not participate in tab navigation - } } public length(): number { diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index f1b58940d3e..dde80747c97 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -22,10 +22,6 @@ vertical-align: top; } -.dropdown > .dropdown-action > .action-label { - outline: none; -} - .dropdown > .dropdown-action > .action-label:hover { color: inherit; text-decoration: none; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.css b/src/vs/base/browser/ui/toolbar/toolbar.css index ccad5186e58..425eb455375 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/src/vs/base/browser/ui/toolbar/toolbar.css @@ -33,24 +33,6 @@ background-image: url('ellipsis-inverse.svg'); } -/*.monaco-toolbar .monaco-action-bar:focus:after { - content: ""; - position: relative; - top: -5px; - height: 0; - width: 100%; - border-top: 2px solid #39F; - display: block; -} - -.vs .monaco-toolbar .monaco-action-bar .action-label:focus { - background-color: #DCEBFC; -} - -.vs-dark .monaco-toolbar .monaco-action-bar .action-label:focus { - background-color: #073655; -}*/ - /* High Contrast Theming */ .hc-black .monaco-toolbar .action-label.toolbar-toggle-more { background: none; @@ -61,14 +43,6 @@ position: absolute; top: 12px; left: 8px; - height: 16px; + height: 16px; width: 16px; -} - -/*.hc-black .monaco-toolbar .monaco-action-bar:focus:after { - border-color: #DF740C; -} - -.hc-black .monaco-toolbar .monaco-action-bar .action-label:focus { - border: 1px solid #DF740C; -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 088b49d017a..f5af6f933e7 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -172,7 +172,7 @@ export class DropdownMenuActionItem extends BaseActionItem { var labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => { var e = $('a.action-label').attr({ - tabIndex: '-1', + tabIndex: '0', role: 'menuitem', title: this._action.label || '', class: this.clazz diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index 968e77f2989..4af8c9a8153 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -15,10 +15,6 @@ position: relative; } -.monaco-tree:focus { - outline: 0; -} - .monaco-tree > .monaco-scrollable-element { height: 100%; } diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index edb88f4ef07..78caf79b494 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -28,10 +28,6 @@ background-color: black; } -.monaco-checkbox .checkbox:focus + .label { - outline: auto 5px rgb(229, 151, 0); -} - /* Find widget */ .monaco-editor .find-widget { position: absolute; diff --git a/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.css b/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.css index 66c8c91fb68..34cb14546d3 100644 --- a/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.css +++ b/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.css @@ -11,7 +11,6 @@ margin: 0; padding: 0; position: absolute; - outline: none; resize: none; overflow: hidden; background: url('keyboard.svg') center center no-repeat; diff --git a/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css b/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css index 9b79d1302d8..c7a6882bc88 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css +++ b/src/vs/editor/contrib/referenceSearch/browser/referenceSearchWidget.css @@ -61,10 +61,6 @@ font-size: 13px; } -.monaco-editor .reference-zone-widget .tree .monaco-tree { - outline: none; -} - .monaco-editor .reference-zone-widget .tree .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: rgba(51, 153, 255, .2); color: #6C6C6C !important; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 1e6135abc9e..1dfc1c37824 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -118,7 +118,6 @@ export class ActivitybarPart extends Part implements IActivityService { // Viewlet switcher is on top this.viewletSwitcherBar = new ActionBar(div, { actionItemProvider: (action: Action) => this.activityActionItems[action.id], - disableTabIndex: true // we handle this }); this.viewletSwitcherBar.getContainer().addClass('position-top'); @@ -159,7 +158,6 @@ export class ActivitybarPart extends Part implements IActivityService { actionItemProvider: (action: Action) => this.activityActionItems[action.id], orientation: ActionsOrientation.VERTICAL }); - this.globalToolBar.getContainer().removeAttribute('tabindex'); this.globalToolBar.getContainer().addClass('global'); this.globalToolBar.actionRunner.addListener(events.EventType.RUN, (e: any) => { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 4916dda4ab3..4c8ce3578b1 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -82,8 +82,7 @@ } .monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label.active, -.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label:focus { +.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label { opacity: 1; } diff --git a/src/vs/workbench/browser/parts/editor/media/binarydiffeditor.css b/src/vs/workbench/browser/parts/editor/media/binarydiffeditor.css index 87e9044720a..7f3428f8ce0 100644 --- a/src/vs/workbench/browser/parts/editor/media/binarydiffeditor.css +++ b/src/vs/workbench/browser/parts/editor/media/binarydiffeditor.css @@ -33,14 +33,14 @@ /* CSS checkerbox pattern (learnt from http://lea.verou.me/2011/02/checkerboard-pattern-with-css3/*/ .monaco-workbench.vs .binary-container img { - background-image: - linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)), + background-image: + linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)), linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)); } .monaco-workbench.vs-dark .binary-container img { - background-image: - linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)), + background-image: + linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)), linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)); } @@ -58,8 +58,4 @@ .monaco-workbench.vs-dark .binary-container a:hover { color: #4080D0; -} - -.monaco-workbench .binary-container:focus { - outline: 0; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/binaryeditor.css b/src/vs/workbench/browser/parts/editor/media/binaryeditor.css index f160eb21850..d65b739e9db 100644 --- a/src/vs/workbench/browser/parts/editor/media/binaryeditor.css +++ b/src/vs/workbench/browser/parts/editor/media/binaryeditor.css @@ -21,14 +21,14 @@ /* CSS checkerbox pattern (learnt from http://lea.verou.me/2011/02/checkerboard-pattern-with-css3/*/ .monaco-workbench.vs .binary-container img { - background-image: - linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)), + background-image: + linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)), linear-gradient(45deg, rgb(220, 220, 220) 25%, transparent 25%, transparent 75%, rgb(220, 220, 220) 75%, rgb(220, 220, 220)); } .monaco-workbench.vs-dark .binary-container img { - background-image: - linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)), + background-image: + linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)), linear-gradient(45deg, rgb(80, 80, 80) 25%, transparent 25%, transparent 75%, rgb(80, 80, 80) 75%, rgb(80, 80, 80)); } @@ -38,8 +38,4 @@ .monaco-workbench.vs-dark .binary-container { background-color: #1E1E1E; -} - -.monaco-workbench .binary-container:focus { - outline: 0; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/iframeeditor.css b/src/vs/workbench/browser/parts/editor/media/iframeeditor.css index 0c0d5ffdb9a..dcd75f323d6 100644 --- a/src/vs/workbench/browser/parts/editor/media/iframeeditor.css +++ b/src/vs/workbench/browser/parts/editor/media/iframeeditor.css @@ -14,8 +14,4 @@ .monaco-workbench .iframe-container.ipad-touch-enabled { -webkit-overflow-scrolling: touch; overflow: auto; -} - -.monaco-workbench .iframe-container:focus { - outline: 0; } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index f7c9eba313d..2997423193a 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -47,6 +47,21 @@ text-decoration: none; } +.monaco-shell a[tabindex="0"]:focus, +.monaco-shell div[tabindex="0"]:focus, +.monaco-shell select:focus, +.monaco-shell iframe[tabindex="0"]:focus { + outline: 2px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.monaco-shell a[tabindex="0"]:active, a[tabindex="0"]:hover, +.monaco-shell div[tabindex="0"]:active, div[tabindex="0"]:hover, +.monaco-shell select:active, select:hover, +.monaco-shell iframe[tabindex="0"]:active, iframe[tabindex="0"]:hover { + outline: 0; +} + .monaco-shell a.prominent { text-decoration: underline; } From 46c564f508f9d849cbc57e0efd60a11e6e25a615 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 08:40:23 +0100 Subject: [PATCH 02/63] adjust opacity on focus too --- src/vs/workbench/electron-browser/media/shell.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 2997423193a..48c9bf483b2 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -53,6 +53,7 @@ .monaco-shell iframe[tabindex="0"]:focus { outline: 2px auto -webkit-focus-ring-color; outline-offset: -2px; + opacity: 1 !important; /* prevent components from dimming focus feedback */ } .monaco-shell a[tabindex="0"]:active, a[tabindex="0"]:hover, @@ -60,6 +61,7 @@ .monaco-shell select:active, select:hover, .monaco-shell iframe[tabindex="0"]:active, iframe[tabindex="0"]:hover { outline: 0; + opacity: inherit; } .monaco-shell a.prominent { From daedb27f1e7551cff8a03d225858aeb3fbfd6b4b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 09:22:06 +0100 Subject: [PATCH 03/63] action bar: restore ability to navigate with arrow keys between items --- src/vs/base/browser/ui/actionbar/actionbar.ts | 27 +++++++++++++++---- .../parts/activitybar/activitybarPart.ts | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 0e6ba167431..7bb78bcb464 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -397,7 +397,9 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { // Items public items: IActionItem[]; + private focusedItem: number; + private focusTracker: DOM.IFocusTracker; // Elements public domNode: HTMLElement; @@ -428,7 +430,6 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.domNode.className = 'monaco-action-bar'; var isVertical = this.options.orientation === ActionsOrientation.VERTICAL; - if (isVertical) { this.domNode.className += ' vertical'; } @@ -443,7 +444,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.focusNext(); } else if (event.equals(CommonKeybindings.ESCAPE)) { this.cancel(); - } else if (event.equals(CommonKeybindings.ENTER)) { + } else if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { // Nothing, just staying out of the else branch } else { eventHandled = false; @@ -464,17 +465,28 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { $(this.domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => { var event = new StandardKeyboardEvent(e); - if (event.equals(CommonKeybindings.ENTER)) { + if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { this.doTrigger(event); event.preventDefault(); event.stopPropagation(); } }); - var focusTracker = DOM.trackFocus(this.domNode); - focusTracker.addBlurListener((e: Event) => { + this.focusTracker = DOM.trackFocus(this.domNode); + this.focusTracker.addBlurListener((e: Event) => { if (document.activeElement === this.domNode || !DOM.isAncestor(document.activeElement, this.domNode)) { this.emit('blur', e); + this.focusedItem = undefined; + } + }); + + this.focusTracker.addFocusListener((e: Event) => { + for (var i = 0; i < this.actionsList.children.length; i++) { + var elem = this.actionsList.children[i]; + if (DOM.isAncestor(document.activeElement, elem)) { + this.focusedItem = i; + break; + } } }); @@ -672,6 +684,11 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { } this.items = null; + if (this.focusTracker) { + this.focusTracker.dispose(); + this.focusTracker = null; + } + this.toDispose = lifecycle.disposeAll(this.toDispose); this.getContainer().destroy(); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 1dfc1c37824..76b26cc09f6 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -118,6 +118,7 @@ export class ActivitybarPart extends Part implements IActivityService { // Viewlet switcher is on top this.viewletSwitcherBar = new ActionBar(div, { actionItemProvider: (action: Action) => this.activityActionItems[action.id], + orientation: ActionsOrientation.VERTICAL }); this.viewletSwitcherBar.getContainer().addClass('position-top'); From 27c3e14023c8f78b141e161e425a08ff3872d161 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 09:25:22 +0100 Subject: [PATCH 04/63] lint actionbar --- src/vs/base/browser/ui/actionbar/actionbar.ts | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 7bb78bcb464..0c961dada3a 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -11,7 +11,7 @@ import nls = require('vs/nls'); import lifecycle = require('vs/base/common/lifecycle'); import {Promise} from 'vs/base/common/winjs.base'; import {Builder, $} from 'vs/base/browser/builder'; -import actions = require('vs/base/common/actions'); +import {IAction, IActionRunner, Action, ActionRunner} from 'vs/base/common/actions'; import DOM = require('vs/base/browser/dom'); import {EventType as CommonEventType} from 'vs/base/common/events'; import types = require('vs/base/common/types'); @@ -21,7 +21,7 @@ import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {CommonKeybindings} from 'vs/base/common/keyCodes'; export interface IActionItem extends IEventEmitter { - actionRunner: actions.IActionRunner; + actionRunner: IActionRunner; setActionContext(context: any): void; render(element: HTMLElement): void; isEnabled(): boolean; @@ -33,21 +33,22 @@ export interface IActionItem extends IEventEmitter { export class BaseActionItem extends EventEmitter implements IActionItem { public builder: Builder; - private gesture: Gesture; - private _actionRunner: actions.IActionRunner; public _callOnDispose: Function[]; public _context: any; - public _action: actions.IAction; + public _action: IAction; - constructor(context: any, action: actions.IAction) { + private gesture: Gesture; + private _actionRunner: IActionRunner; + + constructor(context: any, action: IAction) { super(); this._callOnDispose = []; this._context = context || this; this._action = action; - if (action instanceof actions.Action) { - var l = (action).addBulkListener((events: IEmitterEvent[]) => { + if (action instanceof Action) { + let l = (action).addBulkListener((events: IEmitterEvent[]) => { if (!this.builder) { // we have not been rendered yet, so there @@ -58,20 +59,20 @@ export class BaseActionItem extends EventEmitter implements IActionItem { events.forEach((event: IEmitterEvent) => { switch (event.getType()) { - case actions.Action.ENABLED: + case Action.ENABLED: this._updateEnabled(); break; - case actions.Action.LABEL: + case Action.LABEL: this._updateLabel(); this._updateTooltip(); break; - case actions.Action.TOOLTIP: + case Action.TOOLTIP: this._updateTooltip(); break; - case actions.Action.CLASS: + case Action.CLASS: this._updateClass(); break; - case actions.Action.CHECKED: + case Action.CHECKED: this._updateChecked(); break; default: @@ -88,15 +89,15 @@ export class BaseActionItem extends EventEmitter implements IActionItem { return this._callOnDispose; } - public set actionRunner(actionRunner: actions.IActionRunner) { + public set actionRunner(actionRunner: IActionRunner) { this._actionRunner = actionRunner; } - public get actionRunner(): actions.IActionRunner { + public get actionRunner(): IActionRunner { return this._actionRunner; } - public getAction(): actions.IAction { + public getAction(): IAction { return this._action; } @@ -187,8 +188,7 @@ export class BaseActionItem extends EventEmitter implements IActionItem { } } -export class Separator extends actions.Action { - +export class Separator extends Action { public static ID = 'actions.monaco.separator'; @@ -212,7 +212,7 @@ export class ActionItem extends BaseActionItem { private cssClass: string; private options: IActionItemOptions; - constructor(context: any, action: actions.IAction, options: IActionItemOptions = {}) { + constructor(context: any, action: IAction, options: IActionItemOptions = {}) { super(context, action); this.options = options; @@ -250,7 +250,7 @@ export class ActionItem extends BaseActionItem { } public _updateTooltip(): void { - var title: string = null; + let title: string = null; if (this.getAction().tooltip) { title = this.getAction().tooltip; @@ -307,24 +307,24 @@ export class ProgressItem extends BaseActionItem { public render(parent: HTMLElement): void { - var container = document.createElement('div'); + let container = document.createElement('div'); $(container).addClass('progress-item'); - var label = document.createElement('div'); + let label = document.createElement('div'); $(label).addClass('label'); label.textContent = this.getAction().label; label.title = this.getAction().label; super.render(label); - var progress = document.createElement('div'); + let progress = document.createElement('div'); progress.textContent = '\u2026'; $(progress).addClass('tag', 'progress'); - var done = document.createElement('div'); + let done = document.createElement('div'); done.textContent = '\u2713'; $(done).addClass('tag', 'done'); - var error = document.createElement('div'); + let error = document.createElement('div'); error.textContent = '!'; $(error).addClass('tag', 'error'); @@ -364,17 +364,17 @@ export enum ActionsOrientation { } export interface IActionItemProvider { - (action: actions.IAction): IActionItem; + (action: IAction): IActionItem; } export interface IActionBarOptions { orientation?: ActionsOrientation; context?: any; actionItemProvider?: IActionItemProvider; - actionRunner?: actions.IActionRunner; + actionRunner?: IActionRunner; } -var defaultOptions: IActionBarOptions = { +let defaultOptions: IActionBarOptions = { orientation: ActionsOrientation.HORIZONTAL, context: null }; @@ -383,7 +383,7 @@ export interface IActionOptions extends IActionItemOptions { index?: number; } -export class ActionBar extends EventEmitter implements actions.IActionRunner { +export class ActionBar extends EventEmitter implements IActionRunner { private static nlsActionBarAccessibleLabel = nls.localize('actionBarAccessibleLabel', "Action Bar"); @@ -392,7 +392,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { }; public options: IActionBarOptions; - private _actionRunner: actions.IActionRunner; + private _actionRunner: IActionRunner; private _context: any; // Items @@ -417,7 +417,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this._actionRunner = this.options.actionRunner; if (!this._actionRunner) { - this._actionRunner = new actions.ActionRunner(); + this._actionRunner = new ActionRunner(); this.toDispose.push(this._actionRunner); } @@ -429,14 +429,14 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-action-bar'; - var isVertical = this.options.orientation === ActionsOrientation.VERTICAL; + let isVertical = this.options.orientation === ActionsOrientation.VERTICAL; if (isVertical) { this.domNode.className += ' vertical'; } $(this.domNode).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { - var event = new StandardKeyboardEvent(e); - var eventHandled = true; + let event = new StandardKeyboardEvent(e); + let eventHandled = true; if (event.equals(isVertical ? CommonKeybindings.UP_ARROW : CommonKeybindings.LEFT_ARROW)) { this.focusPrevious(); @@ -463,7 +463,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { }); $(this.domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => { - var event = new StandardKeyboardEvent(e); + let event = new StandardKeyboardEvent(e); if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { this.doTrigger(event); @@ -481,8 +481,8 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { }); this.focusTracker.addFocusListener((e: Event) => { - for (var i = 0; i < this.actionsList.children.length; i++) { - var elem = this.actionsList.children[i]; + for (let i = 0; i < this.actionsList.children.length; i++) { + let elem = this.actionsList.children[i]; if (DOM.isAncestor(document.activeElement, elem)) { this.focusedItem = i; break; @@ -509,11 +509,11 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.items.forEach(i => i.setActionContext(context)); } - public get actionRunner(): actions.IActionRunner { + public get actionRunner(): IActionRunner { return this._actionRunner; } - public set actionRunner(actionRunner: actions.IActionRunner) { + public set actionRunner(actionRunner: IActionRunner) { if (actionRunner) { this._actionRunner = actionRunner; this.items.forEach(item => item.actionRunner = actionRunner); @@ -524,21 +524,21 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { return $(this.domNode); } - public push(actions: actions.IAction, options?: IActionOptions): void; - public push(actions: actions.IAction[], options?: IActionOptions): void; + public push(actions: IAction, options?: IActionOptions): void; + public push(actions: IAction[], options?: IActionOptions): void; public push(actions: any, options: IActionOptions = {}): void { if (!Array.isArray(actions)) { actions = [actions]; } - var index = types.isNumber(options.index) ? options.index : null; + let index = types.isNumber(options.index) ? options.index : null; - actions.forEach((action: actions.IAction) => { - var actionItemElement = document.createElement('li'); + actions.forEach((action: IAction) => { + let actionItemElement = document.createElement('li'); actionItemElement.className = 'action-item'; actionItemElement.setAttribute('role', 'presentation'); - var item: IActionItem = null; + let item: IActionItem = null; if (this.options.actionItemProvider) { item = this.options.actionItemProvider(action); @@ -564,7 +564,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { } public clear(): void { - var item: IActionItem; + let item: IActionItem; while (item = this.items.pop()) { item.dispose(); } @@ -596,8 +596,8 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.focusedItem = this.items.length - 1; } - var startIndex = this.focusedItem; - var item: IActionItem; + let startIndex = this.focusedItem; + let item: IActionItem; do { this.focusedItem = (this.focusedItem + 1) % this.items.length; @@ -616,8 +616,8 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.focusedItem = 0; } - var startIndex = this.focusedItem; - var item: IActionItem; + let startIndex = this.focusedItem; + let item: IActionItem; do { this.focusedItem = this.focusedItem - 1; @@ -642,10 +642,10 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { return; } - for (var i = 0; i < this.items.length; i++) { - var item = this.items[i]; + for (let i = 0; i < this.items.length; i++) { + let item = this.items[i]; - var actionItem = item; + let actionItem = item; if (i === this.focusedItem) { if (types.isFunction(actionItem.focus)) { @@ -666,7 +666,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { } // trigger action - var actionItem = (this.items[this.focusedItem]); + let actionItem = (this.items[this.focusedItem]); this.run(actionItem._action, actionItem._context || event).done(); } @@ -674,7 +674,7 @@ export class ActionBar extends EventEmitter implements actions.IActionRunner { this.emit(CommonEventType.CANCEL); } - public run(action: actions.IAction, context?: any): Promise { + public run(action: IAction, context?: any): Promise { return this._actionRunner.run(action, context); } @@ -703,7 +703,7 @@ export class SelectActionItem extends BaseActionItem { private selected: number; private toDispose: lifecycle.IDisposable[]; - constructor(ctx: any, action: actions.IAction, options: string[], selected: number) { + constructor(ctx: any, action: IAction, options: string[], selected: number) { super(ctx, action); this.select = document.createElement('select'); @@ -749,7 +749,7 @@ export class SelectActionItem extends BaseActionItem { } private createOption(value: string): HTMLOptionElement { - var option = document.createElement('option'); + let option = document.createElement('option'); option.value = value; option.text = value; From bc90350f288774479017381850e0a24e96997843 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 09:29:06 +0100 Subject: [PATCH 05/63] actionitem: tabindex only for enabled actions --- src/vs/base/browser/ui/actionbar/actionbar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 0c961dada3a..5cb244ae95b 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -224,7 +224,7 @@ export class ActionItem extends BaseActionItem { public render(container: HTMLElement): void { super.render(container); - this.$e = $('a.action-label').attr('tabIndex', '0').appendTo(this.builder); + this.$e = $('a.action-label').appendTo(this.builder); this.$e.attr({ role: 'menuitem' }); if (this.options.label && this.options.keybinding) { @@ -288,9 +288,11 @@ export class ActionItem extends BaseActionItem { if (this.getAction().enabled) { this.builder.removeClass('disabled'); this.$e.removeClass('disabled'); + this.$e.attr({ tabindex: 0 }); } else { this.builder.addClass('disabled'); this.$e.addClass('disabled'); + this.$e.removeAttribute('tabindex'); } } From 3e288084bb563f07d04d8113437f718f2c842101 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 10:20:00 +0100 Subject: [PATCH 06/63] fix toolbar to play nice with keyboard focus --- src/vs/base/browser/ui/actionbar/actionbar.ts | 3 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 62 +++++++++---------- 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 5cb244ae95b..2fc379670e6 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -662,9 +662,8 @@ export class ActionBar extends EventEmitter implements IActionRunner { } private doTrigger(event): void { - //nothing to focus if (typeof this.focusedItem === 'undefined') { - return; + return; //nothing to focus } // trigger action diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index f5af6f933e7..aa21db69db3 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -7,6 +7,7 @@ import 'vs/css!./toolbar'; import nls = require('vs/nls'); +import {Promise} from 'vs/base/common/winjs.base'; import {IDisposable} from 'vs/base/common/lifecycle'; import {Builder, $} from 'vs/base/browser/builder'; import types = require('vs/base/common/types'); @@ -15,7 +16,7 @@ import {ActionBar, ActionsOrientation, IActionItemProvider, BaseActionItem} from import {IContextMenuProvider, DropdownMenu, IActionProvider, ILabelRenderer, IDropdownMenuOptions} from 'vs/base/browser/ui/dropdown/dropdown'; import {ListenerUnbind} from 'vs/base/common/eventEmitter'; -export var CONTEXT = 'context.toolbar'; +export const CONTEXT = 'context.toolbar'; export interface IToolBarOptions { orientation?: ActionsOrientation; @@ -34,9 +35,9 @@ export class ToolBar { constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { this.options = options; - this.toggleMenuAction = new ToggleMenuAction(); + this.toggleMenuAction = new ToggleMenuAction(() => this.toggleMenuActionItem && this.toggleMenuActionItem.show()); - var element = document.createElement('div'); + let element = document.createElement('div'); element.className = 'monaco-toolbar'; container.appendChild(element); @@ -58,7 +59,6 @@ export class ToolBar { (action).menuActions, contextMenuProvider, this.options.actionItemProvider, - this.options.orientation === ActionsOrientation.HORIZONTAL, this.actionRunner, 'toolbar-toggle-more' ); @@ -85,7 +85,7 @@ export class ToolBar { public setActions(primaryActions: IAction[], secondaryActions?: IAction[]): () => void { return () => { - var primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; + let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present this.hasSecondaryActions = secondaryActions && secondaryActions.length > 0; @@ -104,7 +104,7 @@ export class ToolBar { // Add after the "..." action if we have secondary actions if (this.hasSecondaryActions) { - var itemCount = this.actionBar.length(); + let itemCount = this.actionBar.length(); this.actionBar.push(primaryActions, { icon: true, label: false, index: itemCount }); } @@ -130,9 +130,18 @@ class ToggleMenuAction extends Action { public static ID = 'toolbar.toggle.more'; private _menuActions: IAction[]; + private toggleDropdownMenu: () => void; - constructor() { + constructor(toggleDropdownMenu: () => void) { super(ToggleMenuAction.ID, nls.localize('more', "More"), null, true); + + this.toggleDropdownMenu = toggleDropdownMenu; + } + + public run(): Promise { + this.toggleDropdownMenu(); + + return Promise.as(true); } public get menuActions() { @@ -145,59 +154,40 @@ class ToggleMenuAction extends Action { } export class DropdownMenuActionItem extends BaseActionItem { - private menuActionsOrProvider: any; - private animateClick: boolean; private dropdownMenu: DropdownMenu; private toUnbind: ListenerUnbind; private contextMenuProvider: IContextMenuProvider; private actionItemProvider: IActionItemProvider; private clazz: string; - constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string); - constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string); - constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, animateClick: boolean, actionRunner: IActionRunner, clazz: string) { + constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, clazz: string); + constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, clazz: string); + constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, clazz: string) { super(null, action); this.menuActionsOrProvider = menuActionsOrProvider; this.contextMenuProvider = contextMenuProvider; this.actionItemProvider = actionItemProvider; - this.animateClick = animateClick; this.actionRunner = actionRunner; this.clazz = clazz; } public render(container: HTMLElement): void { - super.render(container); - - var labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => { - var e = $('a.action-label').attr({ + let labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => { + this.builder = $('a.action-label').attr({ tabIndex: '0', role: 'menuitem', title: this._action.label || '', class: this.clazz - }).appendTo(el); + }); - $('span.label').text(this.getAction().label).appendTo(e); - - if (this.animateClick) { - $(el).on('mousedown', (e: MouseEvent) => { - if (e.button === 0) { - $(el).addClass('active'); - } - }); - - $(el).on(['mouseup', 'mouseout'], (e: MouseEvent) => { - if (e.button === 0) { - $(el).removeClass('active'); - } - }); - } + this.builder.appendTo(el); return null; }; - var options: IDropdownMenuOptions = { + let options: IDropdownMenuOptions = { contextMenuProvider: this.contextMenuProvider, labelRenderer: labelRenderer }; @@ -220,6 +210,10 @@ export class DropdownMenuActionItem extends BaseActionItem { this.toUnbind = this.addEmitter(this.dropdownMenu); } + public show(): void { + this.dropdownMenu && this.dropdownMenu.show(); + } + public dispose(): void { this.toUnbind(); this.dropdownMenu.dispose(); From 0e29fada3bac60ae9a7933eb8b84ef91bff96248 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 10:24:08 +0100 Subject: [PATCH 07/63] escape -> blur --- src/vs/base/browser/ui/actionbar/actionbar.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 2fc379670e6..573b2cca433 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -672,6 +672,10 @@ export class ActionBar extends EventEmitter implements IActionRunner { } private cancel(): void { + if (document.activeElement instanceof HTMLElement) { + (document.activeElement).blur(); // remove focus from focussed action + } + this.emit(CommonEventType.CANCEL); } From 569865d4e7f69a7889a0e9936c83fcb8ae984308 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 10:36:05 +0100 Subject: [PATCH 08/63] improve focus handling within actionbar --- src/vs/base/browser/ui/actionbar/actionbar.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 573b2cca433..73d25f0541e 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -440,9 +440,9 @@ export class ActionBar extends EventEmitter implements IActionRunner { let event = new StandardKeyboardEvent(e); let eventHandled = true; - if (event.equals(isVertical ? CommonKeybindings.UP_ARROW : CommonKeybindings.LEFT_ARROW)) { + if (event.equals(CommonKeybindings.UP_ARROW) || event.equals(CommonKeybindings.LEFT_ARROW)) { this.focusPrevious(); - } else if (event.equals(isVertical ? CommonKeybindings.DOWN_ARROW : CommonKeybindings.RIGHT_ARROW)) { + } else if (event.equals(CommonKeybindings.DOWN_ARROW) || event.equals(CommonKeybindings.RIGHT_ARROW)) { this.focusNext(); } else if (event.equals(CommonKeybindings.ESCAPE)) { this.cancel(); @@ -467,11 +467,17 @@ export class ActionBar extends EventEmitter implements IActionRunner { $(this.domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); + // Run action on Enter/Space if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { this.doTrigger(event); event.preventDefault(); event.stopPropagation(); } + + // Recompute focused item + else if (event.equals(CommonKeybindings.TAB)) { + this.updateFocusedItem(); + } }); this.focusTracker = DOM.trackFocus(this.domNode); @@ -482,15 +488,7 @@ export class ActionBar extends EventEmitter implements IActionRunner { } }); - this.focusTracker.addFocusListener((e: Event) => { - for (let i = 0; i < this.actionsList.children.length; i++) { - let elem = this.actionsList.children[i]; - if (DOM.isAncestor(document.activeElement, elem)) { - this.focusedItem = i; - break; - } - } - }); + this.focusTracker.addFocusListener(() => this.updateFocusedItem()); this.actionsList = document.createElement('ul'); this.actionsList.className = 'actions-container'; @@ -502,6 +500,16 @@ export class ActionBar extends EventEmitter implements IActionRunner { container.appendChild(this.domNode); } + private updateFocusedItem(): void { + for (let i = 0; i < this.actionsList.children.length; i++) { + let elem = this.actionsList.children[i]; + if (DOM.isAncestor(document.activeElement, elem)) { + this.focusedItem = i; + break; + } + } + } + public get context(): any { return this._context; } From b8540dede5dc6a195853bbf4797b4e16b756ced4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 10:59:06 +0100 Subject: [PATCH 09/63] properly focus dropdowns in toolbars --- src/vs/base/browser/ui/actionbar/actionbar.ts | 17 ++++++++++++++--- .../parts/debug/browser/debugActionItems.ts | 12 ++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 73d25f0541e..9ffa46a980a 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -137,13 +137,12 @@ export class BaseActionItem extends EventEmitter implements IActionItem { public focus(): void { if (this.builder) { this.builder.domFocus(); - this.builder.addClass('focused'); } } public blur(): void { if (this.builder) { - this.builder.removeClass('focused'); + this.builder.domBlur(); } } @@ -475,7 +474,7 @@ export class ActionBar extends EventEmitter implements IActionRunner { } // Recompute focused item - else if (event.equals(CommonKeybindings.TAB)) { + else if (event.equals(CommonKeybindings.TAB) || event.equals(CommonKeybindings.SHIFT_TAB)) { this.updateFocusedItem(); } }); @@ -743,6 +742,18 @@ export class SelectActionItem extends BaseActionItem { })); } + public focus(): void { + if (this.select) { + this.select.focus(); + } + } + + public blur(): void { + if (this.select) { + this.select.blur(); + } + } + public render(container: HTMLElement): void { DOM.addClass(container, 'select-container'); container.appendChild(this.select); diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 6b0490c902d..c117aa67e50 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -49,6 +49,18 @@ export class SelectConfigActionItem extends BaseActionItem { this.setOptions().done(null, errors.onUnexpectedError); } + public focus(): void { + if (this.select) { + this.select.focus(); + } + } + + public blur(): void { + if (this.select) { + this.select.blur(); + } + } + private setOptions(): Promise { let previousSelectedIndex = this.select.selectedIndex; this.select.options.length = 0; From 6fe710dace7c8ca672e508a2533346720f9365c3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 11:01:16 +0100 Subject: [PATCH 10/63] proper outline for buttons too --- src/vs/workbench/electron-browser/media/shell.css | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 48c9bf483b2..00bb907eaa5 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -50,16 +50,18 @@ .monaco-shell a[tabindex="0"]:focus, .monaco-shell div[tabindex="0"]:focus, .monaco-shell select:focus, +.monaco-shell input[type="button"]:focus, .monaco-shell iframe[tabindex="0"]:focus { outline: 2px auto -webkit-focus-ring-color; outline-offset: -2px; opacity: 1 !important; /* prevent components from dimming focus feedback */ } -.monaco-shell a[tabindex="0"]:active, a[tabindex="0"]:hover, -.monaco-shell div[tabindex="0"]:active, div[tabindex="0"]:hover, -.monaco-shell select:active, select:hover, -.monaco-shell iframe[tabindex="0"]:active, iframe[tabindex="0"]:hover { +.monaco-shell a[tabindex="0"]:active, .monaco-shell a[tabindex="0"]:hover, +.monaco-shell div[tabindex="0"]:active, .monaco-shell div[tabindex="0"]:hover, +.monaco-shell select:active, .monaco-shell select:hover, +.monaco-shell input[type="button"]:active, .monaco-shell input[type="button"]:hover, +.monaco-shell iframe[tabindex="0"]:active, .monaco-shell iframe[tabindex="0"]:hover { outline: 0; opacity: inherit; } From bc387b6e97b2791cd22b7dddcb07b537f1fd933f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 11:08:55 +0100 Subject: [PATCH 11/63] do not eat keys for dropdowns --- src/vs/base/browser/ui/actionbar/actionbar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 9ffa46a980a..8dcf672acf1 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -439,9 +439,9 @@ export class ActionBar extends EventEmitter implements IActionRunner { let event = new StandardKeyboardEvent(e); let eventHandled = true; - if (event.equals(CommonKeybindings.UP_ARROW) || event.equals(CommonKeybindings.LEFT_ARROW)) { + if (event.equals(isVertical ? CommonKeybindings.UP_ARROW : CommonKeybindings.LEFT_ARROW)) { this.focusPrevious(); - } else if (event.equals(CommonKeybindings.DOWN_ARROW) || event.equals(CommonKeybindings.RIGHT_ARROW)) { + } else if (event.equals(isVertical ? CommonKeybindings.DOWN_ARROW : CommonKeybindings.RIGHT_ARROW)) { this.focusNext(); } else if (event.equals(CommonKeybindings.ESCAPE)) { this.cancel(); From c8604d4fc9d7d543874064b5cc40f0a79ae21247 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 11:26:37 +0100 Subject: [PATCH 12/63] more linting in base --- .../ui/leftRightWidget/leftRightWidget.ts | 18 ++- .../browser/ui/progressbar/progressbar.ts | 37 +++--- src/vs/base/browser/ui/sash/sash.ts | 74 ++++++------ src/vs/base/browser/ui/splitview/splitview.ts | 111 +++++++++--------- src/vs/base/browser/ui/timer/timer.ts | 108 ++++++++--------- 5 files changed, 170 insertions(+), 178 deletions(-) diff --git a/src/vs/base/browser/ui/leftRightWidget/leftRightWidget.ts b/src/vs/base/browser/ui/leftRightWidget/leftRightWidget.ts index d46f28e17d3..e35a7c76fce 100644 --- a/src/vs/base/browser/ui/leftRightWidget/leftRightWidget.ts +++ b/src/vs/base/browser/ui/leftRightWidget/leftRightWidget.ts @@ -6,23 +6,21 @@ 'use strict'; import 'vs/css!./leftRightWidget'; -import Builder = require('vs/base/browser/builder'); -import Lifecycle = require('vs/base/common/lifecycle'); - -var $ = Builder.$; +import {Builder, $} from 'vs/base/browser/builder'; +import {IDisposable} from 'vs/base/common/lifecycle'; export interface IRenderer { - (container:HTMLElement): Lifecycle.IDisposable; + (container: HTMLElement): IDisposable; } export class LeftRightWidget { - private $el: Builder.Builder; - private toDispose: Lifecycle.IDisposable[]; + private $el: Builder; + private toDispose: IDisposable[]; - constructor (container:Builder.Builder, renderLeftFn:IRenderer, renderRightFn:IRenderer); - constructor (container:HTMLElement, renderLeftFn:IRenderer, renderRightFn:IRenderer); - constructor (container:any, renderLeftFn:IRenderer, renderRightFn:IRenderer) { + constructor(container: Builder, renderLeftFn: IRenderer, renderRightFn: IRenderer); + constructor(container: HTMLElement, renderLeftFn: IRenderer, renderRightFn: IRenderer); + constructor(container: any, renderLeftFn: IRenderer, renderRightFn: IRenderer) { this.$el = $('.monaco-left-right-widget').appendTo(container); this.toDispose = [ diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index 0263a2c3303..64f13bda8a7 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -6,12 +6,12 @@ 'use strict'; import 'vs/css!./progressbar'; -import WinJS = require('vs/base/common/winjs.base'); -import Assert = require('vs/base/common/assert'); -import Browser = require('vs/base/browser/browser'); -import Builder = require('vs/base/browser/builder'); +import {Promise, ValueCallback} from 'vs/base/common/winjs.base'; +import assert = require('vs/base/common/assert'); +import browser = require('vs/base/browser/browser'); +import {Builder, $} from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); -import Uuid = require('vs/base/common/uuid'); +import uuid = require('vs/base/common/uuid'); const css_done = 'done'; const css_active = 'active'; @@ -20,8 +20,6 @@ const css_discrete = 'discrete'; const css_progress_container = 'progress-container'; const css_progress_bit = 'progress-bit'; -const $ = Builder.$; - /** * A progress bar with support for infinite or discrete progress. */ @@ -29,21 +27,21 @@ export class ProgressBar { private toUnbind: { (): void; }[]; private workedVal: number; - private element: Builder.Builder; + private element: Builder; private animationRunning: boolean; private bit: HTMLElement; private totalWork: number; - private animationStopToken: WinJS.ValueCallback; + private animationStopToken: ValueCallback; private currentProgressToken: string; - constructor(builder: Builder.Builder) { + constructor(builder: Builder) { this.toUnbind = []; this.workedVal = 0; this.create(builder); } - private create(parent: Builder.Builder): void { + private create(parent: Builder): void { parent.div({ 'class': css_progress_container }, (builder) => { this.element = builder.clone(); @@ -100,7 +98,7 @@ export class ProgressBar { this.bit.style.width = 'inherit'; if (delayed) { - WinJS.Promise.timeout(200).then(() => this.off()); + Promise.timeout(200).then(() => this.off()); } else { this.off(); } @@ -110,7 +108,7 @@ export class ProgressBar { else { this.bit.style.opacity = '0'; if (delayed) { - WinJS.Promise.timeout(200).then(() => this.off()); + Promise.timeout(200).then(() => this.off()); } else { this.off(); } @@ -131,10 +129,10 @@ export class ProgressBar { this.element.addClass(css_active); this.element.addClass(css_infinite); - if (!Browser.hasCSSAnimationSupport()) { + if (!browser.hasCSSAnimationSupport()) { // Use a generated token to avoid race conditions from reentrant calls to this function - let currentProgressToken = Uuid.v4().asHex(); + let currentProgressToken = uuid.v4().asHex(); this.currentProgressToken = currentProgressToken; this.manualInfinite(currentProgressToken); @@ -144,13 +142,12 @@ export class ProgressBar { } private manualInfinite(currentProgressToken: string): void { - this.bit.style.width = '5%'; this.bit.style.display = 'inherit'; let counter = 0; let animationFn: () => void = () => { - WinJS.Promise.timeout(50).then(() => { + Promise.timeout(50).then(() => { // Return if another manualInfinite() call was made if (currentProgressToken !== this.currentProgressToken) { @@ -203,10 +200,10 @@ export class ProgressBar { * Tells the progress bar that an amount of work has been completed. */ public worked(value: number): ProgressBar { - Assert.ok(!isNaN(this.totalWork), 'Total work not set'); + assert.ok(!isNaN(this.totalWork), 'Total work not set'); value = Number(value); - Assert.ok(!isNaN(value), 'Value is not a number'); + assert.ok(!isNaN(value), 'Value is not a number'); value = Math.max(1, value); this.workedVal += value; @@ -236,7 +233,7 @@ export class ProgressBar { /** * Returns the builder this progress bar is building in. */ - public getContainer(): Builder.Builder { + public getContainer(): Builder { return $(this.element); } diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 371bcc9b549..e67139de527 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -6,16 +6,14 @@ 'use strict'; import 'vs/css!./sash'; -import Lifecycle = require('vs/base/common/lifecycle'); -import Builder = require('vs/base/browser/builder'); -import Browser = require('vs/base/browser/browser'); -import Types = require('vs/base/common/types'); +import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; +import {Builder, $} from 'vs/base/browser/builder'; +import {isIPad} from 'vs/base/browser/browser'; +import types = require('vs/base/common/types'); import DOM = require('vs/base/browser/dom'); -import Touch = require('vs/base/browser/touch'); -import Events = require('vs/base/common/eventEmitter'); -import Mouse = require('vs/base/browser/mouseEvent'); - -var $ = Builder.$; +import {Gesture, EventType, GestureEvent} from 'vs/base/browser/touch'; +import {EventEmitter} from 'vs/base/common/eventEmitter'; +import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; export interface ISashLayoutProvider { } @@ -50,10 +48,10 @@ export enum Orientation { HORIZONTAL } -export class Sash extends Events.EventEmitter { +export class Sash extends EventEmitter { - private $e: Builder.Builder; - private gesture: Touch.Gesture; + private $e: Builder; + private gesture: Gesture; private layoutProvider: ISashLayoutProvider; private isDisabled: boolean; private hidden: boolean; @@ -65,17 +63,17 @@ export class Sash extends Events.EventEmitter { this.$e = $('.monaco-sash').appendTo(container); - this.gesture = new Touch.Gesture(this.$e.getHTMLElement()); + this.gesture = new Gesture(this.$e.getHTMLElement()); this.$e.on('mousedown', (e: MouseEvent) => { this.onMouseDown(e); }); - this.$e.on(Touch.EventType.Start, (e: Touch.GestureEvent) => { this.onTouchStart(e); }); + this.$e.on(EventType.Start, (e: GestureEvent) => { this.onTouchStart(e); }); this.orientation = options.orientation || Orientation.VERTICAL; this.$e.addClass(this.orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical'); this.size = options.baseSize || 5; - if (Browser.isIPad) { + if (isIPad) { this.size *= 4; // see also http://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications this.$e.addClass('touch'); } @@ -102,11 +100,11 @@ export class Sash extends Events.EventEmitter { return; } - var mouseDownEvent = new Mouse.StandardMouseEvent(e); - var startX = mouseDownEvent.posx; - var startY = mouseDownEvent.posy; + let mouseDownEvent = new StandardMouseEvent(e); + let startX = mouseDownEvent.posx; + let startY = mouseDownEvent.posy; - var startEvent: ISashEvent = { + let startEvent: ISashEvent = { startX: startX, currentX: startX, instantDiffX: 0, @@ -118,7 +116,7 @@ export class Sash extends Events.EventEmitter { this.$e.addClass('active'); this.emit('start', startEvent); - var overlayDiv = $('div').style({ + let overlayDiv = $('div').style({ position: 'absolute', top: 0, left: 0, @@ -128,16 +126,16 @@ export class Sash extends Events.EventEmitter { cursor: this.orientation === Orientation.VERTICAL ? 'ew-resize' : 'ns-resize' }); - var $window = $(window); + let $window = $(window); - var lastCurrentX = startX; - var lastCurrentY = startY; + let lastCurrentX = startX; + let lastCurrentY = startY; $window.on('mousemove', (e: MouseEvent) => { DOM.EventHelper.stop(e, false); - var mouseMoveEvent = new Mouse.StandardMouseEvent(e); + let mouseMoveEvent = new StandardMouseEvent(e); - var event: ISashEvent = { + let event: ISashEvent = { startX: startX, currentX: mouseMoveEvent.posx, instantDiffX: mouseMoveEvent.posx - lastCurrentX, @@ -162,13 +160,13 @@ export class Sash extends Events.EventEmitter { overlayDiv.appendTo(document.body); } - private onTouchStart(event: Touch.GestureEvent): void { + private onTouchStart(event: GestureEvent): void { DOM.EventHelper.stop(event); - var listeners: Lifecycle.IDisposable[] = []; + let listeners: IDisposable[] = []; - var startX = event.pageX; - var startY = event.pageY; + let startX = event.pageX; + let startY = event.pageY; this.emit('start', { startX: startX, @@ -179,11 +177,11 @@ export class Sash extends Events.EventEmitter { instantDiffY: 0 }); - var lastCurrentX = startX; - var lastCurrentY = startY; + let lastCurrentX = startX; + let lastCurrentY = startY; - listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), Touch.EventType.Change, (event: Touch.GestureEvent) => { - if (Types.isNumber(event.pageX) && Types.isNumber(event.pageY)) { + listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.Change, (event: GestureEvent) => { + if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) { this.emit('change', { startX: startX, currentX: event.pageX, @@ -198,17 +196,17 @@ export class Sash extends Events.EventEmitter { } })); - listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), Touch.EventType.End, (event: Touch.GestureEvent) => { + listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.End, (event: GestureEvent) => { this.emit('end'); - Lifecycle.disposeAll(listeners); + disposeAll(listeners); })); } public layout(): void { - var style: { top?: string; left?: string; height?: string; width?: string; }; + let style: { top?: string; left?: string; height?: string; width?: string; }; if (this.orientation === Orientation.VERTICAL) { - var verticalProvider = (this.layoutProvider); + let verticalProvider = (this.layoutProvider); style = { left: verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px' }; if (verticalProvider.getVerticalSashTop) { @@ -219,7 +217,7 @@ export class Sash extends Events.EventEmitter { style.height = verticalProvider.getVerticalSashHeight(this) + 'px'; } } else { - var horizontalProvider = (this.layoutProvider); + let horizontalProvider = (this.layoutProvider); style = { top: horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px' }; if (horizontalProvider.getHorizontalSashLeft) { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index bd962175f9c..608cc36f996 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -115,7 +115,7 @@ export class HeaderView extends View { this.header = document.createElement('div'); this.header.className = 'header'; - var headerSize = this.headerSize + 'px'; + let headerSize = this.headerSize + 'px'; if (orientation === Orientation.HORIZONTAL) { this.header.style.width = headerSize; @@ -148,7 +148,7 @@ export class HeaderView extends View { } private layoutBodyContainer(orientation: Orientation): void { - var size = `calc(100% - ${this.headerSize}px)`; + let size = `calc(100% - ${this.headerSize}px)`; if (orientation === Orientation.HORIZONTAL) { this.body.style.width = size; @@ -321,8 +321,7 @@ function sum(a: number, b: number): number { return a + b; } export class SplitView implements sash.IHorizontalSashLayoutProvider, - sash.IVerticalSashLayoutProvider -{ + sash.IVerticalSashLayoutProvider { private orientation: Orientation; private el: HTMLElement; private size: number; @@ -333,9 +332,9 @@ export class SplitView implements private sashOrientation: sash.Orientation; private sashes: sash.Sash[]; private sashesListeners: lifecycle.IDisposable[]; - private measureContainerSize: ()=>number; - private layoutViewElement: (viewElement: HTMLElement, size: number)=>void; - private eventWrapper: (event: sash.ISashEvent)=>ISashEvent; + private measureContainerSize: () => number; + private layoutViewElement: (viewElement: HTMLElement, size: number) => void; + private eventWrapper: (event: sash.ISashEvent) => ISashEvent; private animationTimeout: number; private state: IState; @@ -386,10 +385,10 @@ export class SplitView implements throw new Error('Initial weight must be a positive number.'); } - var viewCount = this.views.length; + let viewCount = this.views.length; // Create view container - var viewElement = document.createElement('div'); + let viewElement = document.createElement('div'); dom.addClass(viewElement, 'split-view-view'); this.viewElements.splice(index, 0, viewElement); @@ -409,7 +408,7 @@ export class SplitView implements // Add sash if (this.views.length > 2) { - var s = new sash.Sash(this.el, this, { orientation: this.sashOrientation }); + let s = new sash.Sash(this.el, this, { orientation: this.sashOrientation }); this.sashes.splice(index - 1, 0, s); this.sashesListeners.push(s.addListener2('start', e => this.onSashStart(s, this.eventWrapper(e)))); this.sashesListeners.push(s.addListener2('change', e => this.onSashChange(s, this.eventWrapper(e)))); @@ -420,17 +419,17 @@ export class SplitView implements } public removeView(view: View): void { - var index = this.views.indexOf(view); + let index = this.views.indexOf(view); if (index < 0) { return; } - var deadView = new DeadView(view); + let deadView = new DeadView(view); this.views[index] = deadView; this.onViewChange(deadView, 0); - var sashIndex = Math.max(index - 1, 0); + let sashIndex = Math.max(index - 1, 0); this.sashes[sashIndex].dispose(); this.sashes.splice(sashIndex, 1); @@ -456,11 +455,11 @@ export class SplitView implements size = Math.max(size, this.views.reduce((t, v) => t + v.minimumSize, 0)); - var diff = Math.abs(this.size - size); - var up = numbers.countToArray(this.views.length - 1, -1); + let diff = Math.abs(this.size - size); + let up = numbers.countToArray(this.views.length - 1, -1); - var collapses = this.views.map(v => v.size - v.minimumSize); - var expands = this.views.map(v => v.maximumSize - v.size); + let collapses = this.views.map(v => v.size - v.minimumSize); + let expands = this.views.map(v => v.maximumSize - v.size); if (size < this.size) { this.expandCollapse(Math.min(diff, collapses.reduce(sum, 0)), collapses, expands, up, []); @@ -473,17 +472,17 @@ export class SplitView implements } private onSashStart(sash: sash.Sash, event: ISashEvent): void { - var i = this.sashes.indexOf(sash); - var collapses = this.views.map(v => v.size - v.minimumSize); - var expands = this.views.map(v => v.maximumSize - v.size); + let i = this.sashes.indexOf(sash); + let collapses = this.views.map(v => v.size - v.minimumSize); + let expands = this.views.map(v => v.maximumSize - v.size); - var up = numbers.countToArray(i, -1); - var down = numbers.countToArray(i + 1, this.views.length); + let up = numbers.countToArray(i, -1); + let down = numbers.countToArray(i + 1, this.views.length); - var collapsesUp = up.map(i => collapses[i]); - var collapsesDown = down.map(i => collapses[i]); - var expandsUp = up.map(i => expands[i]); - var expandsDown = down.map(i => expands[i]); + let collapsesUp = up.map(i => collapses[i]); + let collapsesDown = down.map(i => collapses[i]); + let expandsUp = up.map(i => expands[i]); + let expandsDown = down.map(i => expands[i]); this.state = { start: event.start, @@ -498,10 +497,10 @@ export class SplitView implements } private onSashChange(sash: sash.Sash, event: ISashEvent): void { - var i = this.sashes.indexOf(sash); - var diff = event.current - this.state.start; + let i = this.sashes.indexOf(sash); + let diff = event.current - this.state.start; - for (var i = 0; i < this.views.length; i++) { + for (let i = 0; i < this.views.length; i++) { this.views[i].size = this.state.sizes[i]; } @@ -516,25 +515,25 @@ export class SplitView implements // Main algorithm private expandCollapse(collapse: number, collapses: number[], expands: number[], collapseIndexes: number[], expandIndexes: number[]): void { - var totalCollapse = collapse; - var totalExpand = totalCollapse; + let totalCollapse = collapse; + let totalExpand = totalCollapse; collapseIndexes.forEach(i => { - var collapse = Math.min(collapses[i], totalCollapse); + let collapse = Math.min(collapses[i], totalCollapse); totalCollapse -= collapse; this.views[i].size -= collapse; }); expandIndexes.forEach(i => { - var expand = Math.min(expands[i], totalExpand); + let expand = Math.min(expands[i], totalExpand); totalExpand -= expand; this.views[i].size += expand; }); } private initialLayout(): void { - var totalWeight = 0; - var fixedSize = 0; + let totalWeight = 0; + let fixedSize = 0; this.views.forEach((v, i) => { if (v.sizing === ViewSizing.Flexible) { @@ -544,7 +543,7 @@ export class SplitView implements } }); - var flexibleSize = this.size - fixedSize; + let flexibleSize = this.size - fixedSize; this.views.forEach((v, i) => { if (v.sizing === ViewSizing.Flexible) { @@ -555,7 +554,7 @@ export class SplitView implements }); // Leftover - var index = this.getLastFlexibleViewIndex(); + let index = this.getLastFlexibleViewIndex(); if (index >= 0) { this.views[index].size += this.size - this.views.reduce((t, v) => t + v.size, 0); } @@ -565,7 +564,7 @@ export class SplitView implements } private getLastFlexibleViewIndex(exceptIndex: number = null): number { - for (var i = this.views.length - 1; i >= 0; i--) { + for (let i = this.views.length - 1; i >= 0; i--) { if (exceptIndex === i) { continue; } @@ -578,7 +577,7 @@ export class SplitView implements } private layoutViews(): void { - for (var i = 0; i < this.views.length; i++) { + for (let i = 0; i < this.views.length; i++) { // Layout the view elements this.layoutViewElement(this.viewElements[i], this.views[i].size); @@ -590,18 +589,18 @@ export class SplitView implements this.sashes.forEach(s => s.layout()); // Update sashes enablement - var previous = false; - var collapsesDown = this.views.map(v => previous = (v.size - v.minimumSize > 0) || previous); + let previous = false; + let collapsesDown = this.views.map(v => previous = (v.size - v.minimumSize > 0) || previous); previous = false; - var expandsDown = this.views.map(v => previous = (v.maximumSize - v.size > 0) || previous); + let expandsDown = this.views.map(v => previous = (v.maximumSize - v.size > 0) || previous); - var reverseViews = this.views.slice().reverse(); + let reverseViews = this.views.slice().reverse(); previous = false; - var collapsesUp = reverseViews.map(v => previous = (v.size - v.minimumSize > 0) || previous).reverse(); + let collapsesUp = reverseViews.map(v => previous = (v.size - v.minimumSize > 0) || previous).reverse(); previous = false; - var expandsUp = reverseViews.map(v => previous = (v.maximumSize - v.size > 0) || previous).reverse(); + let expandsUp = reverseViews.map(v => previous = (v.maximumSize - v.size > 0) || previous).reverse(); this.sashes.forEach((s, i) => { if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) { @@ -631,16 +630,16 @@ export class SplitView implements this.setupAnimation(); - var index = this.views.indexOf(view); - var diff = Math.abs(size - view.size); - var up = numbers.countToArray(index - 1, -1); - var down = numbers.countToArray(index + 1, this.views.length); - var downUp = down.concat(up); + let index = this.views.indexOf(view); + let diff = Math.abs(size - view.size); + let up = numbers.countToArray(index - 1, -1); + let down = numbers.countToArray(index + 1, this.views.length); + let downUp = down.concat(up); - var collapses = this.views.map(v => Math.max(v.size - v.minimumSize, 0)); - var expands = this.views.map(v => Math.max(v.maximumSize - v.size, 0)); + let collapses = this.views.map(v => Math.max(v.size - v.minimumSize, 0)); + let expands = this.views.map(v => Math.max(v.maximumSize - v.size, 0)); - var collapse: number, collapseIndexes: number[], expandIndexes: number[]; + let collapse: number, collapseIndexes: number[], expandIndexes: number[]; if (size < view.size) { collapse = Math.min(downUp.reduce((t, i) => t + expands[i], 0), diff); @@ -688,10 +687,10 @@ export class SplitView implements } private getSashPosition(sash: sash.Sash): number { - var index = this.sashes.indexOf(sash); - var position = 0; + let index = this.sashes.indexOf(sash); + let position = 0; - for (var i = 0; i <= index; i++) { + for (let i = 0; i <= index; i++) { position += this.views[i].size; } diff --git a/src/vs/base/browser/ui/timer/timer.ts b/src/vs/base/browser/ui/timer/timer.ts index 21359157e7d..dbe89450682 100644 --- a/src/vs/base/browser/ui/timer/timer.ts +++ b/src/vs/base/browser/ui/timer/timer.ts @@ -6,38 +6,38 @@ 'use strict'; import 'vs/css!./timer'; -import Timer = require('vs/base/common/timer'); -import eventEmitter = require('vs/base/common/eventEmitter'); +import {TimeKeeper, ITimerEvent, getTimeKeeper} from 'vs/base/common/timer'; +import {ListenerUnbind} from 'vs/base/common/eventEmitter'; import DomUtils = require('vs/base/browser/dom'); interface IUnmatchedStartTimerEvent { - event: Timer.ITimerEvent; + event: ITimerEvent; domNode: HTMLElement; } export class TimeKeeperRenderer { - private listenersToRemove:eventEmitter.ListenerUnbind[]; - private timeKeeper:Timer.TimeKeeper; - private outerDomNode:HTMLElement; - private domNode:HTMLElement; - private renderCnt:number; - private lastEventIndex:number; + private listenersToRemove: ListenerUnbind[]; + private timeKeeper: TimeKeeper; + private outerDomNode: HTMLElement; + private domNode: HTMLElement; + private renderCnt: number; + private lastEventIndex: number; - private textFilter:string; - private textFilterDomNode:HTMLInputElement; - private timeFilter:number; - private timeFilterDomNode:HTMLInputElement; - private intervalTokenId:number; + private textFilter: string; + private textFilterDomNode: HTMLInputElement; + private timeFilter: number; + private timeFilterDomNode: HTMLInputElement; + private intervalTokenId: number; - private renderedEvents:{ - [key:string]:Timer.ITimerEvent; + private renderedEvents: { + [key: string]: ITimerEvent; }; - private onHide:()=>void; + private onHide: () => void; - constructor (onHide:()=>void) { - this.timeKeeper = Timer.getTimeKeeper(); + constructor(onHide: () => void) { + this.timeKeeper = getTimeKeeper(); this.onHide = onHide; this.lastEventIndex = 0; this.renderedEvents = {}; @@ -50,7 +50,7 @@ export class TimeKeeperRenderer { public destroy(): void { document.body.removeChild(this.outerDomNode); window.clearInterval(this.intervalTokenId); - this.listenersToRemove.forEach(function (element) { + this.listenersToRemove.forEach(function(element) { element(); }); this.listenersToRemove = []; @@ -61,7 +61,7 @@ export class TimeKeeperRenderer { this.outerDomNode.className = 'benchmarktimerbox'; // Clear - var cancel:HTMLInputElement = document.createElement('input'); + let cancel: HTMLInputElement = document.createElement('input'); cancel.type = 'button'; cancel.value = 'Clear'; this.listenersToRemove.push(DomUtils.addListener(cancel, 'click', () => this._onClear())); @@ -86,7 +86,7 @@ export class TimeKeeperRenderer { this.outerDomNode.appendChild(document.createTextNode('Hide time under')); this.outerDomNode.appendChild(this.timeFilterDomNode); - var hide:HTMLInputElement = document.createElement('input'); + let hide: HTMLInputElement = document.createElement('input'); hide.type = 'button'; hide.value = 'Close'; this.listenersToRemove.push(DomUtils.addListener(hide, 'click', () => { @@ -94,12 +94,12 @@ export class TimeKeeperRenderer { })); this.outerDomNode.appendChild(hide); - var heading = document.createElement('pre'); + let heading = document.createElement('pre'); heading.appendChild(document.createTextNode(this.renderRow('TOPIC', 'NAME', 'TOOK', 'START', 'END'))); this.outerDomNode.appendChild(heading); this.outerDomNode.appendChild(document.createElement('hr')); - var domNode = document.createElement('div'); + let domNode = document.createElement('div'); domNode.className = 'inner'; this.outerDomNode.appendChild(domNode); @@ -120,7 +120,7 @@ export class TimeKeeperRenderer { }); } - private matchesTextFilter(event:Timer.ITimerEvent): boolean { + private matchesTextFilter(event: ITimerEvent): boolean { if (!this.textFilter) { return true; } @@ -133,7 +133,7 @@ export class TimeKeeperRenderer { return false; } - private matchesTimeFilter(event:Timer.ITimerEvent): boolean { + private matchesTimeFilter(event: ITimerEvent): boolean { if (!this.timeFilter) { return true; } @@ -143,7 +143,7 @@ export class TimeKeeperRenderer { return false; } - private shouldShow(event:Timer.ITimerEvent): boolean { + private shouldShow(event: ITimerEvent): boolean { return this.matchesTextFilter(event) && this.matchesTimeFilter(event); } @@ -151,17 +151,17 @@ export class TimeKeeperRenderer { this.textFilter = this.textFilterDomNode.value; this.timeFilter = parseInt(this.timeFilterDomNode.value, 10); - var domNodes = Array.prototype.slice.call(this.domNode.children, 0); - for (var i = 0; i < domNodes.length; i++) { - var eventId = domNodes[i].getAttribute('data-event-id'); - var event = this.renderedEvents[eventId]; + let domNodes = Array.prototype.slice.call(this.domNode.children, 0); + for (let i = 0; i < domNodes.length; i++) { + let eventId = domNodes[i].getAttribute('data-event-id'); + let event = this.renderedEvents[eventId]; - if (this.shouldShow(event)) { - domNodes[i].style.display = 'inherit'; - } else { - domNodes[i].style.display = 'none'; - } + if (this.shouldShow(event)) { + domNodes[i].style.display = 'inherit'; + } else { + domNodes[i].style.display = 'none'; } + } } private _onClear(): void { @@ -171,18 +171,18 @@ export class TimeKeeperRenderer { DomUtils.clearNode(this.domNode); } - private leftPaddedString(size:number, padChar:string, str:string): string { - var spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); + private leftPaddedString(size: number, padChar: string, str: string): string { + let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); return spaces + str; } - private rightPaddedString(size:number, padChar:string, str:string): string { - var spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); + private rightPaddedString(size: number, padChar: string, str: string): string { + let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); return str + spaces; } - private renderRow(topic:string, name:string, timeTook:string, timeStart:string, timerEnd:string): string { - var result = ' '; + private renderRow(topic: string, name: string, timeTook: string, timeStart: string, timerEnd: string): string { + let result = ' '; result += this.rightPaddedString(10, ' ', topic); result += this.rightPaddedString(30, ' ', name); result += ' ' + this.leftPaddedString(15, ' ', timeTook); @@ -220,10 +220,10 @@ export class TimeKeeperRenderer { return this._twoPrecision(t) + ' h'; } - private _renderEvent(domNode:HTMLElement, event:Timer.ITimerEvent): void { - var start = event.startTime.getTime() - Timer.TimeKeeper.PARSE_TIME.getTime(); + private _renderEvent(domNode: HTMLElement, event: ITimerEvent): void { + let start = event.startTime.getTime() - TimeKeeper.PARSE_TIME.getTime(); - var result = this.renderRow( + let result = this.renderRow( event.topic, event.name, this._twoPrecision(event.timeTaken()), @@ -234,11 +234,11 @@ export class TimeKeeperRenderer { domNode.appendChild(document.createTextNode(result)); } - private _renderStartTimerEvent(event:Timer.ITimerEvent): void { - var domNode = document.createElement('pre'); + private _renderStartTimerEvent(event: ITimerEvent): void { + let domNode = document.createElement('pre'); this._renderEvent(domNode, event); this.domNode.appendChild(domNode); - var idString = event.id.toString(); + let idString = event.id.toString(); domNode.setAttribute('data-event-id', idString); domNode.className = 'timer-event-' + (event.id % 2); @@ -254,10 +254,10 @@ export class TimeKeeperRenderer { } private _render(): void { - var allEvents = this.timeKeeper.getCollectedEvents(), didSomething = false;; + let allEvents = this.timeKeeper.getCollectedEvents(), didSomething = false;; - for (var i = this.lastEventIndex; i < allEvents.length; i++) { - var ev = allEvents[i]; + for (let i = this.lastEventIndex; i < allEvents.length; i++) { + let ev = allEvents[i]; if (!ev.stopTime) { // This event is not yet finished => block @@ -278,9 +278,9 @@ export class TimeKeeperRenderer { this.lastEventIndex = allEvents.length; } - private _repeatStr(str:string, cnt:number): string { - var r = ''; - for (var i = 0; i < cnt; i++) { + private _repeatStr(str: string, cnt: number): string { + let r = ''; + for (let i = 0; i < cnt; i++) { r += str; } return r; From ea081c5b664319f3bd78ccf8fd025f3df2e15c04 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 14:42:45 +0100 Subject: [PATCH 13/63] also add checkbox for outline --- src/vs/workbench/electron-browser/media/shell.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 00bb907eaa5..106ee633c17 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -51,6 +51,7 @@ .monaco-shell div[tabindex="0"]:focus, .monaco-shell select:focus, .monaco-shell input[type="button"]:focus, +.monaco-shell input[type="checkbox"]:focus, .monaco-shell iframe[tabindex="0"]:focus { outline: 2px auto -webkit-focus-ring-color; outline-offset: -2px; @@ -61,6 +62,7 @@ .monaco-shell div[tabindex="0"]:active, .monaco-shell div[tabindex="0"]:hover, .monaco-shell select:active, .monaco-shell select:hover, .monaco-shell input[type="button"]:active, .monaco-shell input[type="button"]:hover, +.monaco-shell input[type="checkbox"]:active, .monaco-shell input[type="checkbox"]:hover, .monaco-shell iframe[tabindex="0"]:active, .monaco-shell iframe[tabindex="0"]:hover { outline: 0; opacity: inherit; From 204674828a972e143385aac7de8591ef53abbb3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 14:50:28 +0100 Subject: [PATCH 14/63] have outline for input/textarea in one place --- src/vs/base/browser/ui/findinput/findInput.css | 4 ---- src/vs/base/browser/ui/inputbox/inputBox.css | 4 ---- src/vs/base/parts/quickopen/browser/quickopen.css | 1 - src/vs/editor/contrib/rename/browser/rename.css | 5 ----- src/vs/workbench/electron-browser/media/shell.css | 4 ++++ src/vs/workbench/parts/debug/browser/media/repl.css | 1 - 6 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInput.css b/src/vs/base/browser/ui/findinput/findInput.css index 0e8214898f8..2736256115c 100644 --- a/src/vs/base/browser/ui/findinput/findInput.css +++ b/src/vs/base/browser/ui/findinput/findInput.css @@ -15,10 +15,6 @@ height: 25px; } -.monaco-findInput .monaco-inputbox > .wrapper > .input { - outline: none; -} - .fl:after { clear: both; content: ''; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index e18c56d3b8d..a03a5e8f509 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -58,10 +58,6 @@ overflow: hidden; } -.monaco-inputbox > .wrapper > .input:focus { - outline: none; -} - .monaco-inputbox > .wrapper > .mirror { position: absolute; display: inline-block; diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css index 83aa6454b57..7bf0d5dec42 100644 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ b/src/vs/base/parts/quickopen/browser/quickopen.css @@ -28,7 +28,6 @@ width: 588px; border: none; margin: 6px; - outline: 0; } .quick-open-widget .quick-open-input .monaco-inputbox { diff --git a/src/vs/editor/contrib/rename/browser/rename.css b/src/vs/editor/contrib/rename/browser/rename.css index 441fff1414f..eff77813b26 100644 --- a/src/vs/editor/contrib/rename/browser/rename.css +++ b/src/vs/editor/contrib/rename/browser/rename.css @@ -24,9 +24,4 @@ .monaco-editor.vs-dark .rename-box .rename-input { border: 1px solid #0E639C; -} - -.monaco-editor .rename-box .rename-input, -.monaco-editor .rename-box .rename-input:focus { - outline: none; } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 106ee633c17..6a237bee766 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -68,6 +68,10 @@ opacity: inherit; } +.monaco-shell input[type="text"], .monaco-shell textarea { + outline: 0; /* do not show outline in input fields and textarea */ +} + .monaco-shell a.prominent { text-decoration: underline; } diff --git a/src/vs/workbench/parts/debug/browser/media/repl.css b/src/vs/workbench/parts/debug/browser/media/repl.css index ded9af1e04a..c3ec11ba9b0 100644 --- a/src/vs/workbench/parts/debug/browser/media/repl.css +++ b/src/vs/workbench/parts/debug/browser/media/repl.css @@ -117,7 +117,6 @@ width: 100%; border: 0; padding: 0; - outline: none; font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; line-height: 18px; } From 613b7594b8ceebdb475c607a011d20ad930d84bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 16:00:58 +0100 Subject: [PATCH 15/63] search: make toggle query details keyboard accessible --- .../search/browser/media/ellipsis-inverse.svg | 1 + .../parts/search/browser/media/ellipsis.svg | 1 + .../search/browser/media/searchviewlet.css | 11 ++++++++- .../parts/search/browser/searchViewlet.ts | 23 ++++++++++++++----- 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg create mode 100644 src/vs/workbench/parts/search/browser/media/ellipsis.svg diff --git a/src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg b/src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg new file mode 100644 index 00000000000..1140c11ecc8 --- /dev/null +++ b/src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/media/ellipsis.svg b/src/vs/workbench/parts/search/browser/media/ellipsis.svg new file mode 100644 index 00000000000..b61e2d0c750 --- /dev/null +++ b/src/vs/workbench/parts/search/browser/media/ellipsis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index e83b7bc0b75..bea07ef148f 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -35,10 +35,19 @@ .search-viewlet .query-details .more { position: absolute; - top: -0.5em; right: 0; margin-right: 0.5em; cursor: pointer; + width: 16px; + height: 16px; +} + +.monaco-workbench.vs-dark .search-viewlet .query-details .more { + background: url('ellipsis-inverse.svg') center center no-repeat; +} + +.monaco-workbench.vs .search-viewlet .query-details .more { + background: url('ellipsis.svg') center center no-repeat; } .search-viewlet .query-details .file-types { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 85d7ee7f4b5..6868a7286c0 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -743,7 +743,7 @@ export class SearchViewlet extends Viewlet { }; this.findInput = new FindInput(div.getHTMLElement(), this.contextViewService, options); this.findInput.onKeyUp(onStandardKeyUp); - this.findInput.onKeyDown((keyboardEvent:StandardKeyboardEvent) => { + this.findInput.onKeyDown((keyboardEvent: StandardKeyboardEvent) => { if (keyboardEvent.keyCode === KeyCode.DownArrow) { dom.EventHelper.stop(keyboardEvent); if (this.showsFileTypes()) { @@ -763,10 +763,18 @@ export class SearchViewlet extends Viewlet { }).style({ position: 'relative' }).getHTMLElement(); this.queryDetails = builder.div({ 'class': ['query-details', 'separator'] }, (builder) => { - builder.div({ 'class': 'more', text: '\u2026'/*, href: '#'*/ }).on(dom.EventType.CLICK, (e) => { - dom.EventHelper.stop(e); - this.toggleFileTypes(); - }); + builder.div({ 'class': 'more', 'tabindex': 0, 'title': nls.localize('moreSearch', "Toggle Search Details") }) + .on(dom.EventType.CLICK, (e) => { + dom.EventHelper.stop(e); + this.toggleFileTypes(); + }).on(dom.EventType.KEY_UP, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + + if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { + dom.EventHelper.stop(e); + this.toggleFileTypes(); + } + }); //folder includes list builder.div({ 'class': 'file-types' }, (builder) => { @@ -1041,7 +1049,10 @@ export class SearchViewlet extends Viewlet { this.inputPatternIncludes.select(); } else { dom.removeClass(this.queryDetails, cls); + this.findInput.focus(); + this.findInput.select(); } + if (!skipLayout && this.size) { this.layout(this.size); } @@ -1151,7 +1162,7 @@ export class SearchViewlet extends Viewlet { this.disposeModel(); this.showEmptyStage(); - let handledMatches: {[id: string]: boolean} = Object.create(null); + let handledMatches: { [id: string]: boolean } = Object.create(null); let autoExpand = (alwaysExpandIfOneResult: boolean) => { // Auto-expand / collapse based on number of matches: // - alwaysExpandIfOneResult: expand file results if we have just one file result and less than 50 matches on a file From 917a9ef13ba72024d490a78d1584bc831df284af Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 16:20:33 +0100 Subject: [PATCH 16/63] support arrow key navigation in search options --- src/vs/base/browser/ui/findinput/findInput.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 7c770764ca2..ade652171dc 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -15,6 +15,7 @@ import {Widget} from 'vs/base/browser/ui/widget'; import Event, {Emitter} from 'vs/base/common/event'; import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; +import {CommonKeybindings} from 'vs/base/common/keyCodes'; export interface IFindInputOptions { placeholder?:string; @@ -244,6 +245,38 @@ export class FindInput extends Widget { } })); + // Arrow-Key support to navigate between options + let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; + this.domNode.addEventListener(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + let eventHandled = false; + + if (event.equals(CommonKeybindings.LEFT_ARROW) || event.equals(CommonKeybindings.RIGHT_ARROW) || event.equals(CommonKeybindings.ESCAPE)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number; + if (event.equals(CommonKeybindings.RIGHT_ARROW)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(CommonKeybindings.LEFT_ARROW)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(CommonKeybindings.ESCAPE)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + event.preventDefault(); + event.stopPropagation(); + } + } + }); + this.setInputWidth(); let controls = document.createElement('div'); From 2d4a748360d22173c758f3eef1dd704a795da04f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 16:38:58 +0100 Subject: [PATCH 17/63] split view header keyboard access --- src/vs/base/browser/ui/findinput/findInput.ts | 18 ++++++++---- src/vs/base/browser/ui/splitview/splitview.ts | 28 ++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index ade652171dc..b811992fc7a 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -49,6 +49,8 @@ export class FindInput extends Widget { private validation:IInputValidator; private label:string; + private optionsKeyListener: () => void; + private regex:Checkbox; private wholeWords:Checkbox; private caseSensitive:Checkbox; @@ -247,10 +249,8 @@ export class FindInput extends Widget { // Arrow-Key support to navigate between options let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; - this.domNode.addEventListener(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this.optionsKeyListener = dom.addListener(this.domNode, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); - let eventHandled = false; - if (event.equals(CommonKeybindings.LEFT_ARROW) || event.equals(CommonKeybindings.RIGHT_ARROW) || event.equals(CommonKeybindings.ESCAPE)) { let index = indexes.indexOf(document.activeElement); if (index >= 0) { @@ -271,8 +271,7 @@ export class FindInput extends Widget { indexes[newIndex].focus(); } - event.preventDefault(); - event.stopPropagation(); + dom.EventHelper.stop(event, true); } } }); @@ -304,6 +303,15 @@ export class FindInput extends Widget { private clearValidation(): void { this.inputBox.hideMessage(); } + + public dispose(): void { + if (this.optionsKeyListener) { + this.optionsKeyListener(); + this.optionsKeyListener = null; + } + + super.dispose(); + } } interface IMatchCountOpts { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 608cc36f996..2c6df8f8a88 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -14,6 +14,8 @@ import objects = require('vs/base/common/objects'); import dom = require('vs/base/browser/dom'); import numbers = require('vs/base/common/numbers'); import sash = require('vs/base/browser/ui/sash/sash'); +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; +import {CommonKeybindings} from 'vs/base/common/keyCodes'; export enum Orientation { VERTICAL, @@ -185,6 +187,7 @@ export class AbstractCollapsibleView extends HeaderView { protected state: CollapsibleState; private headerClickListener: () => void; + private headerKeyListener: () => void; constructor(opts: ICollapsibleViewOptions) { super(opts); @@ -198,7 +201,25 @@ export class AbstractCollapsibleView extends HeaderView { dom.addClass(this.header, 'collapsible'); dom.addClass(this.body, 'collapsible'); - this.headerClickListener = dom.addListener(this.header, 'click', () => this.toggleExpansion()); + // Keyboard access + this.header.setAttribute('tabindex', '0'); + this.headerKeyListener = dom.addListener(this.header, dom.EventType.KEY_DOWN, (e) => { + let event = new StandardKeyboardEvent(e); + let eventHandled = false; + if (event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE) || event.equals(CommonKeybindings.LEFT_ARROW) || event.equals(CommonKeybindings.RIGHT_ARROW)) { + this.toggleExpansion(); + eventHandled = true; + } else if (event.equals(CommonKeybindings.ESCAPE)) { + this.header.blur(); + eventHandled = true; + } + + if (eventHandled) { + dom.EventHelper.stop(event, true); + } + }); + + this.headerClickListener = dom.addListener(this.header, dom.EventType.CLICK, () => this.toggleExpansion()); } public layout(size: number, orientation: Orientation): void { @@ -257,6 +278,11 @@ export class AbstractCollapsibleView extends HeaderView { this.headerClickListener = null; } + if (this.headerKeyListener) { + this.headerKeyListener(); + this.headerKeyListener = null; + } + super.dispose(); } } From c53bf6368187c0570fb8535b6466d48ed35e8240 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jan 2016 17:12:20 +0100 Subject: [PATCH 18/63] split view: allow access to toolbar in header --- src/vs/base/browser/ui/splitview/splitview.ts | 19 +++++++++++++++++++ .../parts/sidebar/media/sidebarpart.css | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 2c6df8f8a88..0882b4af4fd 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -186,8 +186,10 @@ export enum CollapsibleState { export class AbstractCollapsibleView extends HeaderView { protected state: CollapsibleState; + private headerClickListener: () => void; private headerKeyListener: () => void; + private focusTracker: dom.IFocusTracker; constructor(opts: ICollapsibleViewOptions) { super(opts); @@ -219,7 +221,19 @@ export class AbstractCollapsibleView extends HeaderView { } }); + // Mouse access this.headerClickListener = dom.addListener(this.header, dom.EventType.CLICK, () => this.toggleExpansion()); + + // Track state of focus in header so that other components can adjust styles based on that + // (for example show or hide actions based on the state of being focused or not) + this.focusTracker = dom.trackFocus(this.header); + this.focusTracker.addFocusListener(() => { + dom.addClass(this.header, 'focused'); + }); + + this.focusTracker.addBlurListener(() => { + setTimeout(() => dom.removeClass(this.header, 'focused')); // delay to give other components a chance to react + }); } public layout(size: number, orientation: Orientation): void { @@ -283,6 +297,11 @@ export class AbstractCollapsibleView extends HeaderView { this.headerKeyListener = null; } + if (this.focusTracker) { + this.focusTracker.dispose(); + this.focusTracker = null; + } + super.dispose(); } } diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index ccf818257f1..9fa2d4306ea 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -52,7 +52,8 @@ display: none; } -.monaco-workbench .viewlet .collapsible.header:hover .actions { +.monaco-workbench .viewlet .collapsible.header:hover .actions, +.monaco-workbench .viewlet .collapsible.header.focused .actions { display: block; } From 6bd608e81abf93279f33f0e27eea1ddd0a9b2d2f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 07:41:48 +0100 Subject: [PATCH 19/63] more polish and linting --- src/vs/base/browser/ui/actionbar/actionbar.ts | 1 - .../browser/ui/contextview/contextview.ts | 78 +++++++------- src/vs/base/browser/ui/dropdown/dropdown.ts | 100 +++++++++--------- .../base/browser/ui/dropdown/linksDropdown.ts | 38 +++---- src/vs/base/browser/ui/menu/menu.ts | 32 +++--- 5 files changed, 121 insertions(+), 128 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 8dcf672acf1..b7b4120c57d 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; import 'vs/css!./actionbar'; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 08d8b599fc2..19d005aa692 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -7,12 +7,10 @@ 'use strict'; import 'vs/css!./contextview'; -import Builder = require('vs/base/browser/builder'); +import {Builder, $} from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); -import Lifecycle = require('vs/base/common/lifecycle'); -import EventEmitter = require('vs/base/common/eventEmitter'); - -var $ = Builder.$; +import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; +import {EventEmitter} from 'vs/base/common/eventEmitter'; export interface IAnchor { x: number; @@ -30,20 +28,20 @@ export enum AnchorPosition { } export interface IDelegate { - getAnchor(): HTMLElement|IAnchor; - render(container: HTMLElement): Lifecycle.IDisposable; + getAnchor(): HTMLElement | IAnchor; + render(container: HTMLElement): IDisposable; layout?(): void; - anchorAlignment?:AnchorAlignment; // default: left - anchorPosition?:AnchorPosition; // default: below - canRelayout?:boolean; // default: true - onDOMEvent?(e:Event, activeElement: HTMLElement):void; - onHide?(data?: any):void; + anchorAlignment?: AnchorAlignment; // default: left + anchorPosition?: AnchorPosition; // default: below + canRelayout?: boolean; // default: true + onDOMEvent?(e: Event, activeElement: HTMLElement): void; + onHide?(data?: any): void; } export interface IContextViewProvider { showContextView(delegate: IDelegate): void; hideContextView(): void; - layout():void; + layout(): void; } export interface IPosition { @@ -58,8 +56,8 @@ export interface ISize { export interface IView extends IPosition, ISize { } -export function layout(view: ISize, around: IView, inside: IView, anchorPosition: AnchorPosition, anchorAlignment: AnchorAlignment) : IPosition { - var top: number, left: number; +export function layout(view: ISize, around: IView, inside: IView, anchorPosition: AnchorPosition, anchorAlignment: AnchorAlignment): IPosition { + let top: number, left: number; if (anchorPosition === AnchorPosition.BELOW) { top = around.top + around.height - inside.top; @@ -88,16 +86,16 @@ export function layout(view: ISize, around: IView, inside: IView, anchorPosition return { top: top, left: left }; } -export class ContextView extends EventEmitter.EventEmitter { +export class ContextView extends EventEmitter { private static BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur']; private static BUBBLE_DOWN_EVENTS = ['click']; - private $container: Builder.Builder; - private $view: Builder.Builder; + private $container: Builder; + private $view: Builder; private delegate: IDelegate; - private toDispose: Lifecycle.IDisposable[]; - private toDisposeOnClean: Lifecycle.IDisposable; + private toDispose: IDisposable[]; + private toDisposeOnClean: IDisposable; constructor(container: HTMLElement) { super(); @@ -122,11 +120,11 @@ export class ContextView extends EventEmitter.EventEmitter { if (container) { this.$container = $(container); this.$view.appendTo(this.$container); - this.$container.on(ContextView.BUBBLE_UP_EVENTS, (e:Event) => { - this.onDOMEvent(e, document.activeElement, false); + this.$container.on(ContextView.BUBBLE_UP_EVENTS, (e: Event) => { + this.onDOMEvent(e, document.activeElement, false); }); - this.$container.on(ContextView.BUBBLE_DOWN_EVENTS, (e:Event) => { - this.onDOMEvent(e, document.activeElement, true); + this.$container.on(ContextView.BUBBLE_DOWN_EVENTS, (e: Event) => { + this.onDOMEvent(e, document.activeElement, true); }, null, true); } } @@ -168,16 +166,16 @@ export class ContextView extends EventEmitter.EventEmitter { private doLayout(): void { // Get anchor - var anchor = this.delegate.getAnchor(); + let anchor = this.delegate.getAnchor(); // Compute around - var around: IView; + let around: IView; // Get the element's position and size (to anchor the view) if (DOM.isHTMLElement(anchor)) { - var $anchor = $( anchor); - var elementPosition = $anchor.getPosition(); - var elementSize = $anchor.getTotalSize(); + let $anchor = $(anchor); + let elementPosition = $anchor.getPosition(); + let elementSize = $anchor.getTotalSize(); around = { top: elementPosition.top, @@ -186,7 +184,7 @@ export class ContextView extends EventEmitter.EventEmitter { height: elementSize.height }; } else { - var realAnchor = anchor; + let realAnchor = anchor; around = { top: realAnchor.y, @@ -197,8 +195,8 @@ export class ContextView extends EventEmitter.EventEmitter { } // Get the container's position - var insidePosition = this.$container.getPosition(); - var inside = { + let insidePosition = this.$container.getPosition(); + let inside = { top: insidePosition.top, left: insidePosition.left, height: window.innerHeight, @@ -206,13 +204,13 @@ export class ContextView extends EventEmitter.EventEmitter { }; // Get the view's size - var viewSize = this.$view.getTotalSize(); - var view = { width: viewSize.width, height: viewSize.height }; + let viewSize = this.$view.getTotalSize(); + let view = { width: viewSize.width, height: viewSize.height }; - var anchorPosition = this.delegate.anchorPosition || AnchorPosition.BELOW; - var anchorAlignment = this.delegate.anchorAlignment || AnchorAlignment.LEFT; + let anchorPosition = this.delegate.anchorPosition || AnchorPosition.BELOW; + let anchorAlignment = this.delegate.anchorAlignment || AnchorAlignment.LEFT; - var result = layout(view, around, inside, anchorPosition, anchorAlignment); + let result = layout(view, around, inside, anchorPosition, anchorAlignment); this.$view.removeClass('top', 'bottom', 'left', 'right'); this.$view.addClass(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top'); @@ -242,8 +240,8 @@ export class ContextView extends EventEmitter.EventEmitter { private onDOMEvent(e: Event, element: HTMLElement, onCapture: boolean): void { if (this.delegate) { if (this.delegate.onDOMEvent) { - this.delegate.onDOMEvent(e, document.activeElement); - } else if (onCapture && !DOM.isAncestor( e.target, this.$container.getHTMLElement())) { + this.delegate.onDOMEvent(e, document.activeElement); + } else if (onCapture && !DOM.isAncestor(e.target, this.$container.getHTMLElement())) { this.hide(); } } @@ -253,6 +251,6 @@ export class ContextView extends EventEmitter.EventEmitter { super.dispose(); this.hide(); - this.toDispose = Lifecycle.disposeAll(this.toDispose); + this.toDispose = disposeAll(this.toDispose); } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 248dea13089..936c4d57ae2 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -6,40 +6,38 @@ 'use strict'; import 'vs/css!./dropdown'; -import Builder = require('vs/base/browser/builder'); -import WinJS = require('vs/base/common/winjs.base'); -import Touch = require('vs/base/browser/touch'); -import Actions = require('vs/base/common/actions'); -import ActionBar = require('vs/base/browser/ui/actionbar/actionbar'); -import EventEmitter = require('vs/base/common/eventEmitter'); -import Lifecycle = require('vs/base/common/lifecycle'); -import ContextView = require('vs/base/browser/ui/contextview/contextview'); -import Menu = require('vs/base/browser/ui/menu/menu'); - -var $ = Builder.$; +import {Builder, $} from 'vs/base/browser/builder'; +import {Promise} from 'vs/base/common/winjs.base'; +import {Gesture, EventType} from 'vs/base/browser/touch'; +import {ActionRunner, IAction} from 'vs/base/common/actions'; +import {ActionBar, ActionItem, IActionItem} from 'vs/base/browser/ui/actionbar/actionbar'; +import {EventEmitter} from 'vs/base/common/eventEmitter'; +import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; +import {ContextView, IContextViewProvider} from 'vs/base/browser/ui/contextview/contextview'; +import {Menu, IMenuOptions} from 'vs/base/browser/ui/menu/menu'; export interface ILabelRenderer { - (container: HTMLElement): Lifecycle.IDisposable; + (container: HTMLElement): IDisposable; } export interface IBaseDropdownOptions { tick?: boolean; label?: string; labelRenderer?: ILabelRenderer; - action?: Actions.IAction; + action?: IAction; } -export class BaseDropdown extends Actions.ActionRunner { +export class BaseDropdown extends ActionRunner { - /*protected*/ toDispose: Lifecycle.IDisposable[]; + /*protected*/ toDispose: IDisposable[]; - /*protected*/ $el: Builder.Builder; - private $boxContainer: Builder.Builder; - private $action: Builder.Builder; - private $label: Builder.Builder; - private $contents: Builder.Builder; + /*protected*/ $el: Builder; + private $boxContainer: Builder; + private $action: Builder; + private $label: Builder; + private $contents: Builder; - constructor (container: HTMLElement, options: IBaseDropdownOptions) { + constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); this.toDispose = []; @@ -52,12 +50,12 @@ export class BaseDropdown extends Actions.ActionRunner { this.$label.addClass('tick'); } - var labelRenderer = options.labelRenderer; + let labelRenderer = options.labelRenderer; if (!labelRenderer && options.action) { this.$action = $('.dropdown-action').appendTo(this.$el); - var item = new ActionBar.ActionItem(null, options.action, { + let item = new ActionItem(null, options.action, { icon: true, label: true }); @@ -65,33 +63,33 @@ export class BaseDropdown extends Actions.ActionRunner { item.actionRunner = this; item.render(this.$action.getHTMLElement()); - labelRenderer = (container: HTMLElement): Lifecycle.IDisposable => { + labelRenderer = (container: HTMLElement): IDisposable => { container.innerText = ''; return item; }; } if (!labelRenderer) { - labelRenderer = (container: HTMLElement): Lifecycle.IDisposable => { + labelRenderer = (container: HTMLElement): IDisposable => { $(container).text(options.label || ''); return null; }; } - this.$label.on(['click', Touch.EventType.Tap], (e:Event) => { + this.$label.on(['click', EventType.Tap], (e: Event) => { e.preventDefault(); e.stopPropagation(); this.toggleDropdown(); }).appendTo(this.$el); - var cleanupFn = labelRenderer(this.$label.getHTMLElement()); + let cleanupFn = labelRenderer(this.$label.getHTMLElement()); if (cleanupFn) { this.toDispose.push(cleanupFn); } - this.toDispose.push(new Touch.Gesture(this.$label.getHTMLElement())); + this.toDispose.push(new Gesture(this.$label.getHTMLElement())); } public set tooltip(tooltip: string) { @@ -114,7 +112,7 @@ export class BaseDropdown extends Actions.ActionRunner { // noop } - /*protected*/ public onEvent(e:Event, activeElement: HTMLElement): void { + /*protected*/ public onEvent(e: Event, activeElement: HTMLElement): void { this.hide(); } @@ -122,7 +120,7 @@ export class BaseDropdown extends Actions.ActionRunner { super.dispose(); this.hide(); - this.toDispose = Lifecycle.disposeAll(this.toDispose); + this.toDispose = disposeAll(this.toDispose); if (this.$boxContainer) { this.$boxContainer.destroy(); @@ -142,23 +140,23 @@ export class BaseDropdown extends Actions.ActionRunner { } export interface IDropdownOptions extends IBaseDropdownOptions { - contextViewProvider: ContextView.IContextViewProvider; + contextViewProvider: IContextViewProvider; } export class Dropdown extends BaseDropdown { - /*protected*/ _contextViewProvider: ContextView.IContextViewProvider; + /*protected*/ _contextViewProvider: IContextViewProvider; - constructor (container: HTMLElement, options: IDropdownOptions) { + constructor(container: HTMLElement, options: IDropdownOptions) { super(container, options); this.contextViewProvider = options.contextViewProvider; } - /*protected*/ public set contextViewProvider(contextViewProvider: ContextView.IContextViewProvider) { + /*protected*/ public set contextViewProvider(contextViewProvider: IContextViewProvider) { this._contextViewProvider = contextViewProvider; } - /*protected*/ public get contextViewProvider(): ContextView.IContextViewProvider { + /*protected*/ public get contextViewProvider(): IContextViewProvider { return this._contextViewProvider; } @@ -188,17 +186,17 @@ export class Dropdown extends BaseDropdown { } } - /*protected*/ public renderContents(container: HTMLElement): Lifecycle.IDisposable { + /*protected*/ public renderContents(container: HTMLElement): IDisposable { return null; } } export interface IContextMenuDelegate { getAnchor(): any; - getActions(): WinJS.Promise; - getActionItem?(action: Actions.IAction): ActionBar.IActionItem; - getActionsContext?():any; - getMenuClassName?():string; + getActions(): Promise; + getActionItem?(action: IAction): IActionItem; + getActionsContext?(): any; + getMenuClassName?(): string; onHide?(didCancel: boolean): void; } @@ -207,12 +205,12 @@ export interface IContextMenuProvider { } export interface IActionProvider { - getActions(): Actions.IAction[]; + getActions(): IAction[]; } export interface IDropdownMenuOptions extends IBaseDropdownOptions { contextMenuProvider: IContextMenuProvider; - actions?: Actions.IAction[]; + actions?: IAction[]; actionProvider?: IActionProvider; menuClassName?: string; } @@ -220,13 +218,13 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions { export class DropdownMenu extends BaseDropdown { /*protected*/ _contextMenuProvider: IContextMenuProvider; - private _menuOptions: Menu.IMenuOptions; + private _menuOptions: IMenuOptions; /*protected*/ currentContainer: HTMLElement; - /*protected*/ _actions: Actions.IAction[]; + /*protected*/ _actions: IAction[]; /*protected*/ actionProvider: IActionProvider; private menuClassName: string; - constructor (container:HTMLElement, options: IDropdownMenuOptions) { + constructor(container: HTMLElement, options: IDropdownMenuOptions) { super(container, options); this._contextMenuProvider = options.contextMenuProvider; @@ -244,15 +242,15 @@ export class DropdownMenu extends BaseDropdown { return this._contextMenuProvider; } - public set menuOptions(options: Menu.IMenuOptions) { + public set menuOptions(options: IMenuOptions) { this._menuOptions = options; } - public get menuOptions(): Menu.IMenuOptions { + public get menuOptions(): IMenuOptions { return this._menuOptions; } - /*protected*/ public get actions(): Actions.IAction[] { + /*protected*/ public get actions(): IAction[] { if (this.actionProvider) { return this.actionProvider.getActions(); } @@ -260,7 +258,7 @@ export class DropdownMenu extends BaseDropdown { return this._actions; } - /*protected*/ public set actions(actions:Actions.IAction[]) { + /*protected*/ public set actions(actions: IAction[]) { this._actions = actions; } @@ -269,7 +267,7 @@ export class DropdownMenu extends BaseDropdown { this._contextMenuProvider.showContextMenu({ getAnchor: () => this.$el.getHTMLElement(), - getActions: () => WinJS.Promise.as(this.actions), + getActions: () => Promise.as(this.actions), getActionsContext: () => this.menuOptions ? this.menuOptions.context : null, getActionItem: (action) => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : null, getMenuClassName: () => this.menuClassName, @@ -285,7 +283,7 @@ export class DropdownMenu extends BaseDropdown { } } -export class DropdownGroup extends EventEmitter.EventEmitter { +export class DropdownGroup extends EventEmitter { private el: HTMLElement; diff --git a/src/vs/base/browser/ui/dropdown/linksDropdown.ts b/src/vs/base/browser/ui/dropdown/linksDropdown.ts index 669d81a34f1..a969339a681 100644 --- a/src/vs/base/browser/ui/dropdown/linksDropdown.ts +++ b/src/vs/base/browser/ui/dropdown/linksDropdown.ts @@ -4,26 +4,26 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import WinJS = require('vs/base/common/winjs.base'); -import Platform = require('vs/base/common/platform'); -import Types = require('vs/base/common/types'); -import Actions = require('vs/base/common/actions'); -import Dropdown = require('vs/base/browser/ui/dropdown/dropdown'); +import {Promise} from 'vs/base/common/winjs.base'; +import {isMacintosh} from 'vs/base/common/platform'; +import {isFunction} from 'vs/base/common/types'; +import {Action} from 'vs/base/common/actions'; +import {DropdownMenu, IDropdownMenuOptions} from 'vs/base/browser/ui/dropdown/dropdown'; -export interface ILinksDropdownMenuOptions extends Dropdown.IDropdownMenuOptions { +export interface ILinksDropdownMenuOptions extends IDropdownMenuOptions { tooltip: string; } -export class LinksDropdownMenu extends Dropdown.DropdownMenu { +export class LinksDropdownMenu extends DropdownMenu { - constructor (container:HTMLElement, options: ILinksDropdownMenuOptions) { + constructor(container: HTMLElement, options: ILinksDropdownMenuOptions) { super(container, options); this.tooltip = options.tooltip; } - /*protected*/ public onEvent(e:Event, activeElement: HTMLElement):void { - if (e instanceof KeyboardEvent && ((e).ctrlKey || (Platform.isMacintosh && (e).metaKey))) { + /*protected*/ public onEvent(e: Event, activeElement: HTMLElement): void { + if (e instanceof KeyboardEvent && ((e).ctrlKey || (isMacintosh && (e).metaKey))) { return; // allow to use Ctrl/Meta in workspace dropdown menu } @@ -31,25 +31,25 @@ export class LinksDropdownMenu extends Dropdown.DropdownMenu { } } -export class LinkDropdownAction extends Actions.Action { +export class LinkDropdownAction extends Action { - constructor(id:string, name:string, clazz:string, url:()=>string, forceOpenInNewTab?:boolean); - constructor(id:string, name:string, clazz:string, url:string, forceOpenInNewTab?:boolean); - constructor(id:string, name:string, clazz:string, url:any, forceOpenInNewTab?:boolean) { - super(id, name, clazz, true, (e:Event)=>{ - var urlString = url; + constructor(id: string, name: string, clazz: string, url: () => string, forceOpenInNewTab?: boolean); + constructor(id: string, name: string, clazz: string, url: string, forceOpenInNewTab?: boolean); + constructor(id: string, name: string, clazz: string, url: any, forceOpenInNewTab?: boolean) { + super(id, name, clazz, true, (e: Event) => { + let urlString = url; - if (Types.isFunction(url)) { + if (isFunction(url)) { urlString = url(); } - if (forceOpenInNewTab || (e instanceof MouseEvent && ((e).ctrlKey || (Platform.isMacintosh && (e).metaKey)))) { + if (forceOpenInNewTab || (e instanceof MouseEvent && ((e).ctrlKey || (isMacintosh && (e).metaKey)))) { window.open(urlString, '_blank'); } else { window.location.href = urlString; } - return WinJS.Promise.as(true); + return Promise.as(true); }); } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 3504df52ed8..75fb3fd239d 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -6,34 +6,32 @@ 'use strict'; import 'vs/css!./menu'; -import Lifecycle = require('vs/base/common/lifecycle'); -import Builder = require('vs/base/browser/builder'); -import Actions = require('vs/base/common/actions'); -import ActionBar = require('vs/base/browser/ui/actionbar/actionbar'); -import EventEmitter = require('vs/base/common/eventEmitter'); - -var $ = Builder.$; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {Builder, $} from 'vs/base/browser/builder'; +import {IActionRunner, IAction} from 'vs/base/common/actions'; +import {ActionBar, IActionItemProvider, ActionsOrientation} from 'vs/base/browser/ui/actionbar/actionbar'; +import {EventEmitter} from 'vs/base/common/eventEmitter'; export interface IMenuOptions { - context?:any; - actionItemProvider?:ActionBar.IActionItemProvider; - actionRunner?:Actions.IActionRunner; + context?: any; + actionItemProvider?: IActionItemProvider; + actionRunner?: IActionRunner; } -export class Menu extends EventEmitter.EventEmitter { +export class Menu extends EventEmitter { - private actionBar: ActionBar.ActionBar; - private listener: Lifecycle.IDisposable; + private actionBar: ActionBar; + private listener: IDisposable; - constructor (container:HTMLElement, actions:Actions.IAction[], options:IMenuOptions = {}) { + constructor(container: HTMLElement, actions: IAction[], options: IMenuOptions = {}) { super(); $(container).addClass('monaco-menu-container'); - var $menu = $('.monaco-menu').appendTo(container); + let $menu = $('.monaco-menu').appendTo(container); - this.actionBar = new ActionBar.ActionBar($menu, { - orientation: ActionBar.ActionsOrientation.VERTICAL, + this.actionBar = new ActionBar($menu, { + orientation: ActionsOrientation.VERTICAL, actionItemProvider: options.actionItemProvider, context: options.context, actionRunner: options.actionRunner From 780f6d33c6b19c81e29202905a4e04006b0a7e42 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 10:07:06 +0100 Subject: [PATCH 20/63] split view: focus tree when view gets expanded --- src/vs/workbench/browser/viewlet.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index b1dd1b97d8a..591153b3a7d 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -569,6 +569,14 @@ export class AdaptiveCollapsibleViewletView extends FixedCollapsibleView impleme }); } + protected changeState(state: CollapsibleState): void { + if (this.tree && state == CollapsibleState.EXPANDED) { + this.tree.DOMFocus(); // make the tree have focus once a view gets expanded + } + + super.changeState(state); + } + protected renderViewTree(container: HTMLElement): HTMLElement { return renderViewTree(container); } @@ -670,6 +678,14 @@ export class CollapsibleViewletView extends CollapsibleView implements IViewletV this.toDispose = []; } + protected changeState(state: CollapsibleState): void { + if (this.tree && state == CollapsibleState.EXPANDED) { + this.tree.DOMFocus(); // make the tree have focus once a view gets expanded + } + + super.changeState(state); + } + public create(): TPromise { return Promise.as(null); } @@ -753,7 +769,7 @@ export class CollapsibleViewletView extends CollapsibleView implements IViewletV this.isDisposed = true; this.treeContainer = null; this.tree.dispose(); - + this.dragHandler.dispose(); this.toDispose = disposeAll(this.toDispose); From 54309da291a8d963ab37f967e82c9c6ffabeae3c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 10:19:54 +0100 Subject: [PATCH 21/63] keyboard control for button class --- src/vs/base/browser/ui/button/button.css | 1 + src/vs/base/browser/ui/button/button.ts | 24 ++++++++-- .../git/browser/views/empty/emptyView.css | 34 +------------ .../git/browser/views/empty/emptyView.ts | 48 +------------------ 4 files changed, 24 insertions(+), 83 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 7ace3b45985..46ede1182be 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -12,6 +12,7 @@ text-align: center; color: white; background: #007ACC; + cursor: pointer; } .monaco-button.disabled { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index beda9a2be45..942479b7497 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,6 +9,8 @@ import 'vs/css!./button'; import {EventEmitter} from 'vs/base/common/eventEmitter'; import DOM = require('vs/base/browser/dom'); import {Builder, $} from 'vs/base/browser/builder'; +import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; +import {CommonKeybindings} from 'vs/base/common/keyCodes'; export class Button extends EventEmitter { @@ -19,15 +21,31 @@ export class Button extends EventEmitter { constructor(container: any) { super(); - this.$el = $('a.monaco-button').href('#').appendTo(container); + this.$el = $('a.monaco-button').attr('tabindex', '0').appendTo(container); - this.$el.on('click', (e) => { + this.$el.on(DOM.EventType.CLICK, (e) => { if (!this.enabled) { DOM.EventHelper.stop(e); return; } - this.emit('click', e); + this.emit(DOM.EventType.CLICK, e); + }); + + this.$el.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + let eventHandled = false; + if (this.enabled && event.equals(CommonKeybindings.ENTER) || event.equals(CommonKeybindings.SPACE)) { + this.emit(DOM.EventType.CLICK, e); + eventHandled = true; + } else if (event.equals(CommonKeybindings.ESCAPE)) { + this.$el.domBlur(); + eventHandled = true; + } + + if (eventHandled) { + DOM.EventHelper.stop(event, true); + } }); } diff --git a/src/vs/workbench/parts/git/browser/views/empty/emptyView.css b/src/vs/workbench/parts/git/browser/views/empty/emptyView.css index 27bbbc3a4b1..e7b57f201fd 100644 --- a/src/vs/workbench/parts/git/browser/views/empty/emptyView.css +++ b/src/vs/workbench/parts/git/browser/views/empty/emptyView.css @@ -27,36 +27,4 @@ min-width: inherit; padding: 4px 4px 4px 0; margin-bottom: 10px; -} - -.git-viewlet > .empty-view .button { - -moz-box-sizing: border-box; - box-sizing: border-box; - display: inline-block; - width: 100%; - padding: 4px; - text-align: center; - color: white; - background: #007ACC; -} - -.git-viewlet > .empty-view .button.disabled { - opacity: 0.4; - cursor: default; -} - -.git-viewlet > .empty-view .button:hover { - text-decoration: none; -} - -.git-viewlet > .empty-view .button:not(.disabled):hover { - background: #006BB3; -} - -.git-viewlet > .empty-view .button:not(.disabled):active { - background: #005F9E; -} - -.vs-dark .git-viewlet > .empty-view .button { - background: #0E639C; -} +} \ No newline at end of file diff --git a/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts b/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts index c21df55bb14..fc32d89e882 100644 --- a/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts +++ b/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts @@ -12,6 +12,7 @@ import EventEmitter = require('vs/base/common/eventEmitter'); import DOM = require('vs/base/browser/dom'); import Errors = require('vs/base/common/errors'); import Keyboard = require('vs/base/browser/keyboardEvent'); +import {Button} from 'vs/base/browser/ui/button/button'; import WinJS = require('vs/base/common/winjs.base'); import Builder = require('vs/base/browser/builder'); import Actions = require('vs/base/common/actions'); @@ -29,53 +30,6 @@ import IGitService = git.IGitService; var $ = Builder.$; -export class Button extends EventEmitter.EventEmitter { - - private $el: Builder.Builder; - - constructor(container: Builder.Builder); - constructor(container: HTMLElement); - constructor(container: any) { - super(); - - this.$el = $('a.button.clone').href('#').appendTo(container); - - this.$el.on('click', (e) => { - if (!this.enabled) { - DOM.EventHelper.stop(e); - return; - } - - this.emit('click', e); - }); - } - - public set label(value: string) { - this.$el.text(value); - } - - public set enabled(value: boolean) { - if (value) { - this.$el.removeClass('disabled'); - } else { - this.$el.addClass('disabled'); - } - } - - public get enabled() { - return !this.$el.hasClass('disabled'); - } - - public dispose(): void { - if (this.$el) { - this.$el.dispose(); - this.$el = null; - } - - super.dispose(); - } -} - export class EmptyView extends EventEmitter.EventEmitter implements GitView.IView { public ID = 'empty'; From 2b6ce735fde50c0c40837bd30267b2bda28baeb2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 10:25:30 +0100 Subject: [PATCH 22/63] css tweaks --- src/vs/workbench/electron-browser/media/shell.css | 4 ++-- src/vs/workbench/parts/files/browser/explorerViewlet.ts | 2 ++ src/vs/workbench/parts/search/browser/searchViewlet.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 6a237bee766..0d14d832f8d 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -58,7 +58,7 @@ opacity: 1 !important; /* prevent components from dimming focus feedback */ } -.monaco-shell a[tabindex="0"]:active, .monaco-shell a[tabindex="0"]:hover, +/*.monaco-shell a[tabindex="0"]:active, .monaco-shell a[tabindex="0"]:hover, .monaco-shell div[tabindex="0"]:active, .monaco-shell div[tabindex="0"]:hover, .monaco-shell select:active, .monaco-shell select:hover, .monaco-shell input[type="button"]:active, .monaco-shell input[type="button"]:hover, @@ -66,7 +66,7 @@ .monaco-shell iframe[tabindex="0"]:active, .monaco-shell iframe[tabindex="0"]:hover { outline: 0; opacity: inherit; -} +}*/ .monaco-shell input[type="text"], .monaco-shell textarea { outline: 0; /* do not show outline in input fields and textarea */ diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index 1b35a4e6c29..17d901489ec 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -117,6 +117,8 @@ export class ExplorerViewlet extends Viewlet { if (this.explorerView) { this.explorerView.focus(); + } else if (this.workingFilesView) { + this.workingFilesView.focus(); } } diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 6f669d88a76..398bca979a0 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -763,7 +763,7 @@ export class SearchViewlet extends Viewlet { }).style({ position: 'relative' }).getHTMLElement(); this.queryDetails = builder.div({ 'class': ['query-details', 'separator'] }, (builder) => { - builder.div({ 'class': 'more', 'tabindex': 0, 'title': nls.localize('moreSearch', "Toggle Search Details") }) + builder.div({ 'class': 'more', 'tabindex': 0, 'role': 'menuitem', 'title': nls.localize('moreSearch', "Toggle Search Details") }) .on(dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e); this.toggleFileTypes(); @@ -1052,7 +1052,7 @@ export class SearchViewlet extends Viewlet { this.findInput.focus(); this.findInput.select(); } - + if (!skipLayout && this.size) { this.layout(this.size); } From b875d4a6f6e743800d42e95eb6ef0b0531120de4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 10:35:50 +0100 Subject: [PATCH 23/63] make sure tree leaves tab index when collapsing view --- src/vs/workbench/browser/viewlet.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 591153b3a7d..3d2bce061dc 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -570,9 +570,7 @@ export class AdaptiveCollapsibleViewletView extends FixedCollapsibleView impleme } protected changeState(state: CollapsibleState): void { - if (this.tree && state == CollapsibleState.EXPANDED) { - this.tree.DOMFocus(); // make the tree have focus once a view gets expanded - } + changeState(state, this.tree); super.changeState(state); } @@ -679,9 +677,7 @@ export class CollapsibleViewletView extends CollapsibleView implements IViewletV } protected changeState(state: CollapsibleState): void { - if (this.tree && state == CollapsibleState.EXPANDED) { - this.tree.DOMFocus(); // make the tree have focus once a view gets expanded - } + changeState(state, this.tree); super.changeState(state); } @@ -790,6 +786,19 @@ function renderViewTree(container: HTMLElement): HTMLElement { return treeContainer; } +function changeState(state: CollapsibleState, tree: ITree): void { + if (!tree) { + return; + } + + if (state == CollapsibleState.EXPANDED) { + $(tree.getHTMLElement()).show(); + tree.DOMFocus(); // make the tree have focus once a view gets expanded + } else { + $(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it + } +} + function focus(tree: ITree): void { if (!tree) { return; // return early if viewlet has not yet been created From 791b79c133f63e5a433a5f714cb05b5ebef27330 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 10:52:48 +0100 Subject: [PATCH 24/63] better keyboard handling for feedback widget --- src/vs/workbench/electron-browser/media/shell.css | 2 ++ .../workbench/parts/feedback/browser/feedback.ts | 6 +++--- .../parts/git/browser/views/gitless/gitlessView.ts | 14 +++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 0d14d832f8d..bd1f9316444 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -51,6 +51,7 @@ .monaco-shell div[tabindex="0"]:focus, .monaco-shell select:focus, .monaco-shell input[type="button"]:focus, +.monaco-shell input[type="submit"]:focus, .monaco-shell input[type="checkbox"]:focus, .monaco-shell iframe[tabindex="0"]:focus { outline: 2px auto -webkit-focus-ring-color; @@ -63,6 +64,7 @@ .monaco-shell select:active, .monaco-shell select:hover, .monaco-shell input[type="button"]:active, .monaco-shell input[type="button"]:hover, .monaco-shell input[type="checkbox"]:active, .monaco-shell input[type="checkbox"]:hover, +.monaco-shell input[type="submit"]:active, .monaco-shell input[type="submit"]:hover, .monaco-shell iframe[tabindex="0"]:active, .monaco-shell iframe[tabindex="0"]:hover { outline: 0; opacity: inherit; diff --git a/src/vs/workbench/parts/feedback/browser/feedback.ts b/src/vs/workbench/parts/feedback/browser/feedback.ts index df7003f599e..8660b79585a 100644 --- a/src/vs/workbench/parts/feedback/browser/feedback.ts +++ b/src/vs/workbench/parts/feedback/browser/feedback.ts @@ -106,7 +106,7 @@ export class FeedbackDropdown extends Dropdown { $('h2.title').text(nls.localize("label.sendASmile", "Let us know how we're doing")).appendTo($form); - this.invoke($('div.cancel'), () => { + this.invoke($('div.cancel').attr('tabindex', '0'), () => { this.hide(); }).appendTo($form); @@ -146,10 +146,10 @@ export class FeedbackDropdown extends Dropdown { let $contactUsContainer = $('div.channels').appendTo($contactUs); - $('div').append($('a').attr('target', '_blank').attr('href', this.reportIssueLink).text(nls.localize("submit a bug", "Submit a bug"))) + $('div').append($('a').attr('target', '_blank').attr('href', this.reportIssueLink).text(nls.localize("submit a bug", "Submit a bug")).attr('tabindex', '0')) .appendTo($contactUsContainer); - $('div').append($('a').attr('target', '_blank').attr('href', this.requestFeatureLink).text(nls.localize("request a missing feature", "Request a missing feature"))) + $('div').append($('a').attr('target', '_blank').attr('href', this.requestFeatureLink).text(nls.localize("request a missing feature", "Request a missing feature")).attr('tabindex', '0')) .appendTo($contactUsContainer); let $charCounter = $('span.char-counter').text('(' + FeedbackDropdown.MAX_FEEDBACK_CHARS + ' ' + nls.localize("characters left", "characters left") + ')'); diff --git a/src/vs/workbench/parts/git/browser/views/gitless/gitlessView.ts b/src/vs/workbench/parts/git/browser/views/gitless/gitlessView.ts index e7418c7f54f..2c74f8b744c 100644 --- a/src/vs/workbench/parts/git/browser/views/gitless/gitlessView.ts +++ b/src/vs/workbench/parts/git/browser/views/gitless/gitlessView.ts @@ -47,26 +47,26 @@ export class GitlessView if (platform.isMacintosh) { instructions = nls.localize('macInstallWith', "You can either install it with {0}, download it from {1} or install the {2} command line developer tools, by simply typing {3} on a Terminal prompt.", - 'Homebrew', - 'git-scm.com', - 'XCode', + 'Homebrew', + 'git-scm.com', + 'XCode', 'git' ); } else if (platform.isWindows) { instructions = nls.localize('winInstallWith', "You can either install it with {0} or download it from {1}.", - 'Chocolatey', - 'git-scm.com' + 'Chocolatey', + 'git-scm.com' ); } else if (platform.isLinux) { instructions = nls.localize('linuxDownloadFrom', "You can download it from {0}.", - 'git-scm.com' + 'git-scm.com' ); } else { instructions = nls.localize('downloadFrom', "You can download it from {0}.", - 'git-scm.com' + 'git-scm.com' ); } From 4089296e91405d35930e8a1c3397037a108f2886 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jan 2016 11:03:33 +0100 Subject: [PATCH 25/63] make sure tab + shift-tab work in iframes; align focus styles in markdown preview --- src/vs/languages/markdown/common/markdown.css | 8 ++++++++ src/vs/workbench/browser/parts/editor/iframeEditor.ts | 7 +++++++ .../browser/parts/editor/media/iframeeditor.css | 4 ++-- .../workbench/parts/html/browser/htmlPreviewPart.ts | 11 +++++++++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/vs/languages/markdown/common/markdown.css b/src/vs/languages/markdown/common/markdown.css index 2007607704f..a8dfa62e665 100644 --- a/src/vs/languages/markdown/common/markdown.css +++ b/src/vs/languages/markdown/common/markdown.css @@ -20,6 +20,14 @@ a { text-decoration: none; } +a:focus, +input:focus, +select:focus, +textarea:focus { + outline: 2px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + hr { border: 0; height: 2px; diff --git a/src/vs/workbench/browser/parts/editor/iframeEditor.ts b/src/vs/workbench/browser/parts/editor/iframeEditor.ts index 3a404571999..1734de7c2e0 100644 --- a/src/vs/workbench/browser/parts/editor/iframeEditor.ts +++ b/src/vs/workbench/browser/parts/editor/iframeEditor.ts @@ -139,6 +139,7 @@ export class IFrameEditor extends BaseEditor { '