diff --git a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts index 06ae5f5b3a..8ff430d988 100644 --- a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts @@ -60,6 +60,8 @@ export class HeaderFilter { private filterStyles?: ITableFilterStyles; private disposableStore = new DisposableStore(); private _enabled: boolean = true; + private columnButtonMapping: Map = new Map(); + private previouslyFocusedElement: HTMLElement; constructor(private readonly contextViewProvider: IContextViewProvider) { } @@ -70,7 +72,7 @@ export class HeaderFilter { .subscribe(this.grid.onBeforeHeaderCellDestroy, (e: Event, args: Slick.OnBeforeHeaderCellDestroyEventArgs) => this.handleBeforeHeaderCellDestroy(e, args)) .subscribe(this.grid.onClick, (e: DOMEvent) => this.handleBodyMouseDown(e as MouseEvent)) .subscribe(this.grid.onColumnsResized, () => this.columnsResized()) - .subscribe(this.grid.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent)); + .subscribe(this.grid.onKeyDown, async (e: DOMEvent) => { await this.handleGridKeyDown(e as KeyboardEvent); }); this.grid.setColumns(this.grid.getColumns()); this.disposableStore.add(addDisposableListener(document.body, 'mousedown', e => this.handleBodyMouseDown(e), true)); @@ -86,10 +88,30 @@ export class HeaderFilter { const event = new StandardKeyboardEvent(e); if (this.menu && event.keyCode === KeyCode.Escape) { this.hideMenu(); + if (this.previouslyFocusedElement?.focus && this.previouslyFocusedElement.tabIndex !== -1) { + this.previouslyFocusedElement?.focus(); + } EventHelper.stop(e, true); } } + private async handleGridKeyDown(e: KeyboardEvent): Promise { + const event = new StandardKeyboardEvent(e); + // The shortcut key to open the filter menu is provided so that this feature is keyboard accessible. + // The buttons added to the column headers are set to not keyboard focusable so that they won't interfere with the slickgrid's internal focus management. + // F3 key is chosen because it is known for search related features + if (event.keyCode === KeyCode.F3) { + const cell = this.grid.getActiveCell(); + if (cell) { + const column = this.grid.getColumns()[cell.cell] as FilterableColumn; + if (column.filterable !== false && this.enabled && this.columnButtonMapping[column.id]) { + await this.showFilter(this.columnButtonMapping[column.id]); + EventHelper.stop(e, true); + } + } + } + } + private handleBodyMouseDown(e: MouseEvent): void { if (this.menu && this.menu !== e.target && !isAncestor(e.target as Element, this.menu)) { this.hideMenu(); @@ -113,7 +135,7 @@ export class HeaderFilter { return; } args.node.classList.add('slick-header-with-filter'); - const $el = jQuery(``) + const $el = jQuery(``) .addClass('slick-header-menubutton') .data('column', column); this.setButtonImage($el, column.filterValues?.length > 0); @@ -124,6 +146,8 @@ export class HeaderFilter { await this.showFilter($el[0]); }); $el.appendTo(args.node); + + this.columnButtonMapping[column.id] = $el[0]; } private handleBeforeHeaderCellDestroy(e: Event, args: Slick.OnBeforeHeaderCellDestroyEventArgs) { @@ -281,6 +305,7 @@ export class HeaderFilter { } private async showFilter(filterButton: HTMLElement): Promise { + this.previouslyFocusedElement = document.activeElement as HTMLElement; await this.createFilterMenu(filterButton); // Get the absolute coordinates of the filter button const offset = jQuery(filterButton).offset(); diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index df0f95f2b4..d134b99781 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -393,7 +393,7 @@ const queryEditorConfiguration: IConfigurationNode = { }, 'queryEditor.results.inMemoryDataProcessingThreshold': { 'type': 'number', - 'default': 2000, + 'default': 5000, 'description': localize('queryEditor.inMemoryDataProcessingThreshold', "Controls the max number of rows allowed to do filtering and sorting in memory. If the number is exceeded, sorting and filtering will be disabled.") }, 'queryEditor.messages.showBatchTime': {