diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSearch.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSearch.tsx index 0cdd6834c8c..488738c2ad2 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSearch.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSearch.tsx @@ -8,7 +8,7 @@ import 'vs/css!./columnSearch'; // React. import * as React from 'react'; -import { useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports +import { useEffect, useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports // Other dependencies. import { localize } from 'vs/nls'; @@ -19,7 +19,10 @@ import { positronClassNames } from 'vs/base/common/positronUtilities'; */ interface ColumnSearchProps { initialSearchText?: string; + focus?: boolean; onSearchTextChanged: (searchText: string) => void; + onNavigateOut?: (searchText: string) => void; + onConfirmSearch?: (searchText: string) => void; } /** @@ -31,10 +34,33 @@ export const ColumnSearch = (props: ColumnSearchProps) => { // Reference hooks. const inputRef = useRef(undefined!); + useEffect(() => { + if (!props.focus) { return; } + inputRef.current.focus(); + }, [inputRef, props.focus]); + // State hooks. const [focused, setFocused] = useState(false); const [searchText, setSearchText] = useState(props.initialSearchText ?? ''); + const handleOnKeyDown = (evt: React.KeyboardEvent) => { + switch (evt.code) { + case 'ArrowDown': + case 'Tab': + if (!props.onNavigateOut) { break; } + evt.stopPropagation(); + evt.preventDefault(); + props.onNavigateOut?.(evt.currentTarget.value); + break; + case 'Enter': + if (!props.onConfirmSearch) { break; } + evt.stopPropagation(); + evt.preventDefault(); + props.onConfirmSearch?.(evt.currentTarget.value); + break; + } + }; + // Render. return (
@@ -45,6 +71,7 @@ export const ColumnSearch = (props: ColumnSearchProps) => { className='text-input' placeholder={(() => localize('positron.searchPlacehold', "search"))()} value={searchText} + onKeyDown={handleOnKeyDown} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} onChange={e => { diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorDataGridInstance.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorDataGridInstance.tsx index a973b2f3a05..d1070f2295f 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorDataGridInstance.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorDataGridInstance.tsx @@ -139,6 +139,14 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { return ROW_HEIGHT; } + selectItem(rowIndex: number): void { + // Get the column schema for the row index. + const columnSchema = this._columnSchemaCache.getColumnSchema(rowIndex); + if (!columnSchema) { return; } + + this._onDidSelectColumnEmitter.fire(columnSchema); + } + /** * Gets a cell. * @param columnIndex The column index. @@ -190,6 +198,13 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { // Set the search text and fetch data. this._searchText = searchText; await this.fetchData(); + + // select the first available row after fetching so that users cat hit "enter" + // to make an immediate confirmation on what they were searching for + if (this.visibleRows) { + this.showCursor(); + this.setCursorRow(0); + } } } diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx index d3292fc2610..4d478d66e42 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx @@ -28,6 +28,8 @@ interface ColumnSelectorModalPopupProps { readonly renderer: PositronModalReactRenderer; readonly columnSelectorDataGridInstance: ColumnSelectorDataGridInstance; readonly anchorElement: HTMLElement; + readonly searchInput?: string; + readonly focusInput?: boolean; readonly onItemHighlighted: (columnSchema: ColumnSchema) => void; readonly onItemSelected: (columnSchema: ColumnSchema) => void; } @@ -43,7 +45,9 @@ export const ColumnSelectorModalPopup = (props: ColumnSelectorModalPopupProps) = // Main useEffect. useEffect(() => { + if (props.focusInput) { return; } // Drive focus into the data grid so the user can immediately navigate. + props.columnSelectorDataGridInstance.setCursorPosition(0, 0); positronDataGridRef.current.focus(); }, []); @@ -60,6 +64,14 @@ export const ColumnSelectorModalPopup = (props: ColumnSelectorModalPopupProps) = return () => disposableStore.dispose(); }, [props, props.columnSelectorDataGridInstance]); + const onKeyDown = (evt: React.KeyboardEvent) => { + if (evt.code === 'Enter' || evt.code === 'Space') { + evt.preventDefault(); + evt.stopPropagation(); + props.columnSelectorDataGridInstance.selectItem(props.columnSelectorDataGridInstance.cursorRowIndex); + } + }; + // Render. return ( -
+
{ await props.columnSelectorDataGridInstance.setSearchText( searchText !== '' ? searchText : undefined ); }} + onNavigateOut={() => { + positronDataGridRef.current.focus(); + props.columnSelectorDataGridInstance.showCursor(); + }} + onConfirmSearch={() => { + props.columnSelectorDataGridInstance.selectItem(props.columnSelectorDataGridInstance.cursorColumnIndex); + }} />
-
+
{ // State hooks. const [title, _setTitle] = useState(props.title); + const [selectedColumnSchema, setSelectedColumnSchema] = useState(props.selectedColumnSchema); - // // State hooks. - const [selectedColumnSchema, setSelectedColumnSchema] = - useState(props.selectedColumnSchema); + const onPressed = useCallback((focusInput?: boolean) => { + // Create the renderer. + const renderer = new PositronModalReactRenderer({ + keybindingService: props.keybindingService, + layoutService: props.layoutService, + container: props.layoutService.getContainer(DOM.getWindow(ref.current)), + disableCaptures: true, // permits the usage of the enter key where applicable + onDisposed: () => { + ref.current.focus(); + } + }); + + // Create the column selector data grid instance. + const columnSelectorDataGridInstance = new ColumnSelectorDataGridInstance( + props.dataExplorerClientInstance + ); + + // Show the drop down list box modal popup. + renderer.render( + { + console.log(`onItemHighlighted ${columnSchema.column_name}`); + }} + onItemSelected={columnSchema => { + renderer.dispose(); + setSelectedColumnSchema(columnSchema); + props.onSelectedColumnSchemaChanged(columnSchema); + }} + /> + ); + }, [props]); + + const onKeyDown = useCallback((evt: KeyboardEvent) => { + // eliminate key events for anything that isn't a single-character key or whitespaces + if (evt.key.trim().length !== 1) { return; } + // don't consume event here; the input will pick it up + onPressed(true); + }, [onPressed]); + + useEffect(() => { + const el = ref.current; + el.addEventListener('keydown', onKeyDown); + return () => { + el.removeEventListener('keydown', onKeyDown); + }; + }, [ref, onKeyDown]); // Render. return (