Fix modal event propagation (#8050)

* Fix event propagation

* Remove unneeded onkeyup method

* Move event handling code into SQL classes
This commit is contained in:
Charles Gagnon
2019-10-30 11:29:01 -07:00
committed by GitHub
parent 004297aea6
commit 82e5221024
8 changed files with 59 additions and 19 deletions

View File

@@ -8,7 +8,7 @@ import {
Output, OnChanges, SimpleChanges, EventEmitter Output, OnChanges, SimpleChanges, EventEmitter
} from '@angular/core'; } from '@angular/core';
import { Checkbox as vsCheckbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { Checkbox as sqlCheckbox } from 'sql/base/browser/ui/checkbox/checkbox';
@Component({ @Component({
selector: 'checkbox', selector: 'checkbox',
@@ -22,14 +22,14 @@ export class Checkbox implements OnInit, OnChanges {
@Output() onChange = new EventEmitter<boolean>(); @Output() onChange = new EventEmitter<boolean>();
private _checkbox: vsCheckbox; private _checkbox: sqlCheckbox;
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef @Inject(forwardRef(() => ElementRef)) private _el: ElementRef
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this._checkbox = new vsCheckbox(this._el.nativeElement, { this._checkbox = new sqlCheckbox(this._el.nativeElement, {
label: this.label, label: this.label,
ariaLabel: this.ariaLabel || this.label, ariaLabel: this.ariaLabel || this.label,
checked: this.checked, checked: this.checked,

View File

@@ -13,6 +13,9 @@ import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import * as aria from 'vs/base/browser/ui/aria/aria'; import * as aria from 'vs/base/browser/ui/aria/aria';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { renderFormattedText, renderText, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; import { renderFormattedText, renderText, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom';
const $ = dom.$; const $ = dom.$;
@@ -91,6 +94,23 @@ export class SelectBox extends vsSelectBox {
this._register(focusTracker); this._register(focusTracker);
this._register(focusTracker.onDidBlur(() => this._hideMessage())); this._register(focusTracker.onDidBlur(() => this._hideMessage()));
this._register(focusTracker.onDidFocus(() => this._showMessage())); this._register(focusTracker.onDidFocus(() => this._showMessage()));
// Stop propagation - we've handled the event already and letting it bubble up causes issues with parent
// controls handling it (such as dialog pages)
this.onkeydown(this.selectElement, (e: IKeyboardEvent) => {
if (e.keyCode === KeyCode.Enter) {
dom.EventHelper.stop(e, true);
}
});
if (this.selectBoxDelegate instanceof SelectBoxList) {
// SelectBoxList uses its own custom drop down list so we need to also stop propagation from that or it'll
// also bubble up
this.onkeydown(this.selectBoxDelegate.selectDropDownContainer, (e: IKeyboardEvent) => {
if (e.keyCode === KeyCode.Enter) {
dom.EventHelper.stop(e, true);
}
});
}
} }

View File

@@ -9,6 +9,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
export interface Template { export interface Template {
label: HTMLElement; label: HTMLElement;
@@ -124,10 +125,24 @@ export class DropdownController extends TreeDefaults.DefaultController {
return response; return response;
} }
public onKeyDown(tree: tree.ITree, event: IKeyboardEvent): boolean {
// The enter key press is handled on key up by our base class (DefaultController) but
// we want to stop it here because we know we're going to handle it (by selecting the item)
// and letting it propagate up means that other controls may incorrectly handle it first
// if they're listening to onKeyDown
const response = super.onKeyDown(tree, event);
if (event.keyCode === KeyCode.Enter) {
DOM.EventHelper.stop(event, true);
return true;
}
return response;
}
protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean { protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean {
let response = super.onEnter(tree, event); let response = super.onEnter(tree, event);
if (response) { if (response) {
this._onSelectionChange.fire(tree.getSelection()[0]); this._onSelectionChange.fire(tree.getSelection()[0]);
DOM.EventHelper.stop(event, true);
} }
return response; return response;
} }

View File

@@ -16,6 +16,9 @@ import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/theme
import { ComponentWithIconBase } from 'sql/workbench/browser/modelComponents/componentWithIconBase'; import { ComponentWithIconBase } from 'sql/workbench/browser/modelComponents/componentWithIconBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
import { StatusIndicator, CardProperties, ActionDescriptor, CardDescriptionItem } from 'sql/workbench/api/common/sqlExtHostTypes'; import { StatusIndicator, CardProperties, ActionDescriptor, CardDescriptionItem } from 'sql/workbench/api/common/sqlExtHostTypes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import * as DOM from 'vs/base/browser/dom';
@Component({ @Component({
templateUrl: decodeURI(require.toUrl('./card.component.html')) templateUrl: decodeURI(require.toUrl('./card.component.html'))
@@ -37,6 +40,12 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
this.baseInit(); this.baseInit();
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme()); this.updateTheme(this.themeService.getColorTheme());
this.onkeydown(this._el.nativeElement, (e: StandardKeyboardEvent) => {
if (e.keyCode === KeyCode.Enter) {
this.onCardClick();
DOM.EventHelper.stop(e, true);
}
});
} }

View File

@@ -22,6 +22,7 @@ import * as nls from 'vs/nls';
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import * as DOM from 'vs/base/browser/dom';
@Component({ @Component({
selector: 'modelview-inputBox', selector: 'modelview-inputBox',
@@ -78,7 +79,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
args: this._input.value args: this._input.value
}); });
if (this.stopEnterPropagation) { if (this.stopEnterPropagation) {
e.stopPropagation(); DOM.EventHelper.stop(e, true);
} }
} }
}); });
@@ -89,7 +90,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions); this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions);
this.onkeydown(this._textAreaInput.inputElement, (e: StandardKeyboardEvent) => { this.onkeydown(this._textAreaInput.inputElement, (e: StandardKeyboardEvent) => {
if (this.tryHandleKeyEvent(e)) { if (this.tryHandleKeyEvent(e)) {
e.stopPropagation(); DOM.EventHelper.stop(e, true);
} }
if (e.keyCode === KeyCode.Enter) { if (e.keyCode === KeyCode.Enter) {
this.fireEvent({ this.fireEvent({
@@ -97,7 +98,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
args: this._textAreaInput.value args: this._textAreaInput.value
}); });
if (this.stopEnterPropagation) { if (this.stopEnterPropagation) {
e.stopPropagation(); DOM.EventHelper.stop(e, true);
} }
} }
// Else assume that keybinding service handles routing this to a command // Else assume that keybinding service handles routing this to a command

View File

@@ -24,7 +24,7 @@ import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from '
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITreeComponentItem } from 'sql/workbench/common/views'; import { ITreeComponentItem } from 'sql/workbench/common/views';
import { TreeViewDataProvider } from 'sql/workbench/browser/modelComponents/treeViewDataProvider'; import { TreeViewDataProvider } from 'sql/workbench/browser/modelComponents/treeViewDataProvider';
import { getContentHeight, getContentWidth } from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
@@ -127,7 +127,7 @@ export default class TreeComponent extends ComponentBase implements IComponent,
// This might have unintended effects such as a dialog closing. // This might have unintended effects such as a dialog closing.
if (e.keyCode === KeyCode.Enter) { if (e.keyCode === KeyCode.Enter) {
this._tree.toggleExpansion(this._tree.getFocus()); this._tree.toggleExpansion(this._tree.getFocus());
e.stopPropagation(); DOM.EventHelper.stop(e, true);
} }
}); });
this._tree.refresh(); this._tree.refresh();
@@ -149,8 +149,8 @@ export default class TreeComponent extends ComponentBase implements IComponent,
let width: number = this.convertSizeToNumber(this.width); let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height); let height: number = this.convertSizeToNumber(this.height);
this._tree.layout( this._tree.layout(
height && height > 0 ? height : getContentHeight(this._inputContainer.nativeElement), height && height > 0 ? height : DOM.getContentHeight(this._inputContainer.nativeElement),
width && width > 0 ? width : getContentWidth(this._inputContainer.nativeElement)); width && width > 0 ? width : DOM.getContentWidth(this._inputContainer.nativeElement));
} }
public setLayout(layout: any): void { public setLayout(layout: any): void {

View File

@@ -72,12 +72,11 @@ export interface ISelectData {
} }
export class SelectBox extends Widget implements ISelectBoxDelegate { export class SelectBox extends Widget implements ISelectBoxDelegate {
// {{SQL CARBON EDIT}} protected selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
protected selectElement: HTMLSelectElement;
protected selectBackground?: Color; protected selectBackground?: Color;
protected selectForeground?: Color; protected selectForeground?: Color;
protected selectBorder?: Color; protected selectBorder?: Color;
private selectBoxDelegate: ISelectBoxDelegate; protected selectBoxDelegate: ISelectBoxDelegate; // {{SQL CARBON EDIT}} Make protected so we can hook into keyboard events
constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) { constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) {
super(); super();

View File

@@ -20,9 +20,6 @@ import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxSty
import { isMacintosh } from 'vs/base/common/platform'; import { isMacintosh } from 'vs/base/common/platform';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
// {{SQL CARBON EDIT}} import color
import { Color } from 'vs/base/common/color';
const $ = dom.$; const $ = dom.$;
const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template'; const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
@@ -92,15 +89,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
private _isVisible: boolean; private _isVisible: boolean;
private selectBoxOptions: ISelectBoxOptions; private selectBoxOptions: ISelectBoxOptions;
// {{SQL CARBON EDIT}} public selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
public selectElement: HTMLSelectElement;
private options: ISelectOptionItem[] = []; private options: ISelectOptionItem[] = [];
private selected: number; private selected: number;
private readonly _onDidSelect: Emitter<ISelectData>; private readonly _onDidSelect: Emitter<ISelectData>;
private styles: ISelectBoxStyles; private styles: ISelectBoxStyles;
private listRenderer!: SelectListRenderer; private listRenderer!: SelectListRenderer;
private contextViewProvider!: IContextViewProvider; private contextViewProvider!: IContextViewProvider;
private selectDropDownContainer!: HTMLElement; public selectDropDownContainer!: HTMLElement; // {{SQL CARBON EDIT}} Make public so we can hook into keyboard events
private styleElement!: HTMLStyleElement; private styleElement!: HTMLStyleElement;
private selectList!: List<ISelectOptionItem>; private selectList!: List<ISelectOptionItem>;
private selectDropDownListContainer!: HTMLElement; private selectDropDownListContainer!: HTMLElement;