implement the style for dropdown, inputbox and table filter (#23502)

* dropdown, inputbox, headerfilter styles

* fix missing reference

* remove unused import
This commit is contained in:
Alan Ren
2023-06-27 19:36:31 -07:00
committed by GitHub
parent f0d496d9ab
commit 4abcc20781
44 changed files with 155 additions and 559 deletions

View File

@@ -4,15 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dropdownList';
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { DropdownDataSource, DropdownListRenderer, IDropdownListItem, SELECT_OPTION_ENTRY_TEMPLATE_ID } from 'sql/base/browser/ui/editableDropdown/browser/dropdownList';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IInputBoxStyles, IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
@@ -20,10 +19,8 @@ import { clamp } from 'vs/base/common/numbers';
import { mixin } from 'vs/base/common/objects';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import * as nls from 'vs/nls';
import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
export interface IDropdownOptions extends IDropdownStyles {
export interface IDropdownOptions extends Partial<IEditableDropdownStyles> {
/**
* Whether or not a options in the list must be selected or a "new" option can be set
*/
@@ -58,9 +55,9 @@ export interface IDropdownOptions extends IDropdownStyles {
ariaDescription?: string;
}
export interface IDropdownStyles {
contextBackground?: Color;
contextBorder?: Color;
export interface IEditableDropdownStyles extends IInputBoxStyles, IListStyles {
contextBackground?: string;
contextBorder?: string;
}
const errorMessage = nls.localize('editableDropdown.errorValidate', "Must be an option from the list");
@@ -96,7 +93,7 @@ export class Dropdown extends Disposable implements IListVirtualDelegate<string>
constructor(
container: HTMLElement,
private readonly contextViewService: IContextViewProvider,
opt?: IDropdownOptions
opt: IDropdownOptions
) {
super();
this._options = opt || Object.create(null);
@@ -116,7 +113,8 @@ export class Dropdown extends Disposable implements IListVirtualDelegate<string>
this._inputContainer.style.width = '100%';
this._inputContainer.style.height = '100%';
this._selectListContainer = DOM.$('div');
this._selectListContainer.style.backgroundColor = opt.contextBackground;
this._selectListContainer.style.outline = `1px solid ${opt.contextBorder}`;
this._input = new InputBox(this._inputContainer, contextViewService, {
validationOptions: {
// @SQLTODO
@@ -126,7 +124,7 @@ export class Dropdown extends Disposable implements IListVirtualDelegate<string>
placeholder: this._options.placeholder,
ariaLabel: this._options.ariaLabel,
ariaDescription: this._options.ariaDescription,
inputBoxStyles: defaultInputBoxStyles
inputBoxStyles: <IInputBoxStyles>this._options
});
// Clear title from input box element (defaults to placeholder value) since we don't want a tooltip for the selected value
@@ -206,6 +204,7 @@ export class Dropdown extends Disposable implements IListVirtualDelegate<string>
getWidgetRole: () => 'listbox'
}
});
this._selectList.style(<IListStyles>this._options);
this.values = this._options.values;
this._register(this._selectList.onDidBlur(() => {
@@ -378,13 +377,6 @@ export class Dropdown extends Disposable implements IListVirtualDelegate<string>
this._hideList();
}
style(style: IListStyles & IInputBoxStyles & IDropdownStyles) {
this._selectList.style(style);
this._input.style(style);
this._selectListContainer.style.backgroundColor = style.contextBackground ? style.contextBackground.toString() : '';
this._selectListContainer.style.outline = `1px solid ${style.contextBorder}`;
}
private _inputValidator(value: string): IMessage | null {
if (!this._input.hasFocus() && this._input.isEnabled() && !this._selectList.isDOMFocused() && !this._dataSource.values.some(i => i === value)) {
if (this._options.strictSelection && this._options.errorMessage) {

View File

@@ -3,9 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InputBox as vsInputBox, IInputOptions as vsIInputBoxOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { InputBox as vsInputBox, IInputOptions as vsIInputBoxOptions, IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Color } from 'vs/base/common/color';
import { Event, Emitter } from 'vs/base/common/event';
import { AdsWidget } from 'sql/base/browser/ui/adsWidget';
@@ -14,11 +13,6 @@ export interface OnLoseFocusParams {
hasChanged: boolean;
}
export interface IInputBoxStyles extends vsIInputBoxStyles {
disabledInputBackground?: Color;
disabledInputForeground?: Color;
}
export interface IInputOptions extends vsIInputBoxOptions {
/**
* Whether calls to validate require the force parameter to be set to true
@@ -31,13 +25,6 @@ export interface IInputOptions extends vsIInputBoxOptions {
}
export class InputBox extends vsInputBox implements AdsWidget {
// private enabledInputBackground?: Color;
// private enabledInputForeground?: Color;
// private enabledInputBorder?: Color;
// private disabledInputBackground?: Color;
// private disabledInputForeground?: Color;
// private disabledInputBorder?: Color;
private _lastLoseFocusValue: string;
private _onLoseFocus = this._register(new Emitter<OnLoseFocusParams>());
@@ -51,11 +38,6 @@ export class InputBox extends vsInputBox implements AdsWidget {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, private _sqlOptions?: IInputOptions, id?: string) {
super(container, contextViewProvider, _sqlOptions);
// {{SQL CARBON TODO}} - fix styles
// this.enabledInputBackground = this.inputBackground;
// this.enabledInputForeground = this.inputForeground;
// this.enabledInputBorder = this.inputBorder;
//this.disabledInputBackground = Color.transparent;
this._lastLoseFocusValue = this.value;
let self = this;
@@ -80,23 +62,12 @@ export class InputBox extends vsInputBox implements AdsWidget {
if (id !== undefined) {
this.inputElement.id = id;
}
}
public override style(styles: IInputBoxStyles): void {
// super.style(styles);
// this.enabledInputBackground = this.inputBackground;
// this.enabledInputForeground = this.inputForeground;
// this.enabledInputBorder = this.inputBorder;
// this.disabledInputBackground = styles.disabledInputBackground;
// this.disabledInputForeground = styles.disabledInputForeground;
// this.updateInputEnabledDisabledColors();
// this.applyStyles();
this.updateInputEnabledDisabledColors();
}
public override enable(): void {
super.enable();
this.updateInputEnabledDisabledColors();
this.applyStyles();
}
public set rows(value: number) {
@@ -116,7 +87,6 @@ export class InputBox extends vsInputBox implements AdsWidget {
public override disable(): void {
super.disable();
this.updateInputEnabledDisabledColors();
this.applyStyles();
}
public setHeight(value: string) {
@@ -169,10 +139,14 @@ export class InputBox extends vsInputBox implements AdsWidget {
}
private updateInputEnabledDisabledColors(): void {
// let enabled = this.isEnabled();
// this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground;
// this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground;
// this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder;
const enabled = this.isEnabled();
const background = enabled ? this._sqlOptions.inputBoxStyles.inputBackground : this._sqlOptions.inputBoxStyles.disabledInputBackground
const foreground = enabled ? this._sqlOptions.inputBoxStyles.inputForeground : this._sqlOptions.inputBoxStyles.disabledInputForeground;
const border = enabled ? this._sqlOptions.inputBoxStyles.inputBorder : this._sqlOptions.inputBoxStyles.disabledInputBorder;
this.element.style.backgroundColor = background;
this.element.style.color = foreground;
this.input.style.color = foreground;
this.element.style.border = `1px solid ${border}`;
}
public override validate(force?: boolean): MessageType | undefined {

View File

@@ -11,7 +11,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'
import { withNullAsUndefined } from 'vs/base/common/types';
import { IDisposableDataProvider, instanceOfIDisposableDataProvider } from 'sql/base/common/dataProvider';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { trapKeyboardNavigation } from 'sql/base/browser/dom';
import { IListAccessibilityProvider, IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
@@ -19,8 +19,8 @@ import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Emitter } from 'vs/base/common/event';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { defaultCountBadgeStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge';
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
export type HeaderFilterCommands = 'sort-asc' | 'sort-desc';
@@ -30,7 +30,7 @@ export interface CommandEventArgs<T extends Slick.SlickData> {
command: HeaderFilterCommands
}
export interface ITableFilterOptions {
export interface ITableFilterOptions extends ITableFilterStyles {
/**
* The message to be displayed when the filter is disabled and the user tries to open the filter menu.
*/
@@ -40,13 +40,9 @@ export interface ITableFilterOptions {
* Set to false to prevent the grid from being re-drawn multiple times by different plugins.
*/
refreshColumns?: boolean;
/**
* The button styles.
*/
buttonStyles: IButtonStyles;
}
export interface ITableFilterStyles extends IInputBoxStyles, IListStyles {
export interface ITableFilterStyles extends IInputBoxStyles, IListStyles, IButtonStyles, ICountBadgeStyles {
}
interface NotificationProvider {
@@ -70,9 +66,6 @@ export class HeaderFilter<T extends Slick.SlickData> {
private okButton?: Button;
private clearButton?: Button;
private cancelButton?: Button;
// {{SQL CARBON TODO}} - disable
// private sortAscButton?: Button;
// private sortDescButton?: Button;
private selectAllCheckBox?: Checkbox;
private searchInputBox?: InputBox;
private countBadge?: CountBadge;
@@ -82,7 +75,6 @@ export class HeaderFilter<T extends Slick.SlickData> {
private filteredListData?: TableFilterListElement[];
private elementDisposables?: IDisposable[];
private columnDef!: FilterableColumn<T>;
private filterStyles?: ITableFilterStyles;
private disposableStore = new DisposableStore();
private columnButtonMapping: Map<string, HTMLElement> = new Map<string, HTMLElement>();
private previouslyFocusedElement: HTMLElement;
@@ -184,7 +176,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
private createButtonMenuItem(title: string, command: HeaderFilterCommands, iconClass: string): Button {
const buttonContainer = append(this.menu, $('.slick-header-menu-image-button-container'));
const button = new Button(buttonContainer, this.options.buttonStyles);
const button = new Button(buttonContainer, this.options);
button.icon = `slick-header-menuicon ${iconClass}`;
button.label = title;
button.onDidClick(async () => {
@@ -208,20 +200,20 @@ export class HeaderFilter<T extends Slick.SlickData> {
this.searchInputBox = new InputBox(append(searchRow, $('.search-input')), this.contextViewProvider, {
placeholder: localize('table.searchPlaceHolder', "Search"),
inputBoxStyles: defaultInputBoxStyles
inputBoxStyles: this.options
});
const visibleCountContainer = append(searchRow, $('.visible-count'));
visibleCountContainer.setAttribute('aria-live', 'polite');
visibleCountContainer.setAttribute('aria-atomic', 'true');
this.visibleCountBadge = new CountBadge(visibleCountContainer, {
countFormat: localize({ key: 'tableFilter.visibleCount', comment: ['This tells the user how many items are shown in the list. Currently not visible, but read by screen readers.'] }, "{0} Results")
}, defaultCountBadgeStyles);
}, this.options);
const selectedCountBadgeContainer = append(searchRow, $('.selected-count'));
selectedCountBadgeContainer.setAttribute('aria-live', 'polite');
this.countBadge = new CountBadge(selectedCountBadgeContainer, {
countFormat: localize({ key: 'tableFilter.selectedCount', comment: ['This tells the user how many items are selected in the list'] }, "{0} Selected")
}, defaultCountBadgeStyles);
}, this.options);
this.searchInputBox.onDidChange(async (newString) => {
this.filteredListData = this.listData.filter(element => element.value?.toUpperCase().indexOf(newString.toUpperCase()) !== -1);
@@ -408,9 +400,6 @@ export class HeaderFilter<T extends Slick.SlickData> {
// Make sure the menu can fit in the screen.
this.menu.style.height = `${Math.min(DefaultMenuHeight, window.innerHeight - MenuBarHeight) - MenuVerticalPadding}px`;
// {{SQL CARBON TODO}} - style buttons
// this.sortAscButton = this.createButtonMenuItem(localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending');
// this.sortDescButton = this.createButtonMenuItem(localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending');
this.createButtonMenuItem(localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending');
this.createButtonMenuItem(localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending');
@@ -418,49 +407,28 @@ export class HeaderFilter<T extends Slick.SlickData> {
await this.createFilterList();
const buttonGroupContainer = append(this.menu, $('.filter-menu-button-container'));
this.okButton = this.createButton(buttonGroupContainer, 'filter-ok-button', localize('headerFilter.ok', "OK"), this.options.buttonStyles);
this.okButton = this.createButton(buttonGroupContainer, 'filter-ok-button', localize('headerFilter.ok', "OK"), this.options);
this.okButton.onDidClick(async () => {
this.columnDef.filterValues = this.listData.filter(element => element.checked).map(element => element.value);
this.setButtonImage($menuButton, this.columnDef.filterValues.length > 0);
await this.handleApply(this.columnDef);
});
this.clearButton = this.createButton(buttonGroupContainer, 'filter-clear-button', localize('headerFilter.clear', "Clear"), { secondary: true, ...this.options.buttonStyles });
this.clearButton = this.createButton(buttonGroupContainer, 'filter-clear-button', localize('headerFilter.clear', "Clear"), { secondary: true, ...this.options });
this.clearButton.onDidClick(async () => {
this.columnDef.filterValues!.length = 0;
this.setButtonImage($menuButton, false);
await this.handleApply(this.columnDef);
});
this.cancelButton = this.createButton(buttonGroupContainer, 'filter-cancel-button', localize('headerFilter.cancel', "Cancel"), { secondary: true, ...this.options.buttonStyles });
this.cancelButton = this.createButton(buttonGroupContainer, 'filter-cancel-button', localize('headerFilter.cancel', "Cancel"), { secondary: true, ...this.options });
this.cancelButton.onDidClick(() => {
this.hideMenu();
});
this.applyStyles();
// No need to add this to disposable store, it will be disposed when the menu is closed.
trapKeyboardNavigation(this.menu);
}
public style(styles: ITableFilterStyles): void {
this.filterStyles = styles;
this.applyStyles();
}
private applyStyles() {
if (this.filterStyles) {
// {{SQL CARBON TODO}} - apply styles
// this.okButton?.style(this.filterStyles);
// this.cancelButton?.style(this.filterStyles);
// this.clearButton?.style(this.filterStyles);
// this.sortAscButton?.style(this.filterStyles);
// this.sortDescButton?.style(this.filterStyles);
// this.searchInputBox?.style(this.filterStyles);
// this.countBadge?.style(this.filterStyles);
// this.visibleCountBadge?.style(this.filterStyles);
// this.list?.style(this.filterStyles);
}
}
private columnsResized() {
this.hideMenu();
}

View File

@@ -8,10 +8,11 @@ import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { KeyCode, EVENT_KEY_CODE_MAP } from 'vs/base/common/keyCodes';
import * as DOM from 'vs/base/browser/dom';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { Event } from 'vs/base/common/event';
import { Dropdown, IEditableDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { Disposable } from 'vs/base/common/lifecycle';
import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
import { ISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
const InverseKeyCodeMap: { [k: string]: number } = Object.fromEntries(Object.entries(EVENT_KEY_CODE_MAP).map(([key, value]) => [value, Number(key)]));
@@ -19,8 +20,9 @@ export interface ITableCellEditorOptions {
valueGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string,
valueSetter?: (context: any, row: number, item: Slick.SlickData, column: Slick.Column<Slick.SlickData>, value: string) => void,
optionsGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string[],
editorStyler: (component: InputBox | SelectBox | Dropdown) => void,
onStyleChange: Event<void>;
inputBoxStyles: IInputBoxStyles,
editableDropdownStyles: IEditableDropdownStyles,
selectBoxStyles: ISelectBoxStyles
}
export class TableCellEditorFactory {
@@ -37,8 +39,9 @@ export class TableCellEditorFactory {
optionsGetter: options.optionsGetter ?? function (item, column) {
return [];
},
editorStyler: options.editorStyler,
onStyleChange: options.onStyleChange
inputBoxStyles: options.inputBoxStyles,
editableDropdownStyles: options.editableDropdownStyles,
selectBoxStyles: options.selectBoxStyles
};
}
@@ -68,17 +71,12 @@ export class TableCellEditorFactory {
type: inputType,
inputBoxStyles: defaultInputBoxStyles
});
self._options.editorStyler(this._input);
this._input.element.style.height = '100%';
this._input.focus();
this._input.onLoseFocus(async () => {
await this.commitEdit();
});
this._register(this._input);
this._register(self._options.onStyleChange(() => {
self._options.editorStyler(this._input);
}));
this._input.value = presetValue ?? '';
}
@@ -163,7 +161,7 @@ export class TableCellEditorFactory {
container.style.height = '100%';
container.style.width = '100%';
if (isEditable) {
this._component = new Dropdown(container, self._contextViewProvider);
this._component = new Dropdown(container, self._contextViewProvider, self._options.editableDropdownStyles);
this._component.onValueChange(async () => {
await this.commitEdit();
});
@@ -178,12 +176,8 @@ export class TableCellEditorFactory {
await this.commitEdit();
});
}
self._options.editorStyler(this._component);
this._component.focus();
this._register(this._component);
this._register(self._options.onStyleChange(() => {
self._options.editorStyler(this._component);
}));
}
private async commitEdit(): Promise<void> {