mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 06:40:05 +08:00
dbeaver/pro#5998 Fix: can't copy the whole table selected with the corner cell (#3550)
* dbeaver/pro#5998 fix: selectAll index header cell * dbeaver/pro#5998 fix: removed OrderButton from tabbing order, add sorting on space/enter when header column cell is in focus * dbeaver/pro#5998 feat: add onHeaderKeyDown handler to DataGrid and HeaderCellContentRenderer we lost ability to copy data grid cells' data when one of the header cells is focused, because the header cell renderer was not handling keydown events. This commit utilizing gridSelectedCellCopy.onKeydownHandler inside HeaderCellContentRenderer keydown event handler * dbeaver/pro#5998 style: change onKeyDown to a named function * dbeaver/pro#5998 refactor: simplify OrderButton and update sorting logic in HeaderCellContentRenderer * dbeaver/pro#5998 refactor: rename container class to clickAreaOverlay in TableIndexColumnHeader, remove z-index --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
co-authored by
Daria Marutkina
parent
69330aa0fe
commit
cb84409701
@@ -70,6 +70,7 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
|
||||
onFocus,
|
||||
onCellChange,
|
||||
onColumnSort,
|
||||
onHeaderKeyDown,
|
||||
children,
|
||||
className,
|
||||
onCellKeyDown,
|
||||
@@ -151,7 +152,7 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
|
||||
<HeaderDnDContext value={dndHeaderContext}>
|
||||
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
|
||||
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
|
||||
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState }}>
|
||||
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState, onHeaderKeyDown }}>
|
||||
<DataGridBase
|
||||
ref={innerGridRef}
|
||||
columns={dndHeaderContext.columns}
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface IDataGridHeaderCellContext {
|
||||
columnSortingState?: IGridReactiveValue<'asc' | 'desc' | undefined | null, [colIdx: number]>;
|
||||
columnSortable?: IGridReactiveValue<boolean, [colIdx: number]>;
|
||||
onColumnSort?: (colIdx: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void;
|
||||
onHeaderKeyDown?: (event: React.KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
export const DataGridCellHeaderContext = createContext<IDataGridHeaderCellContext | null>(null);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, use, useRef } from 'react';
|
||||
import { memo, use } from 'react';
|
||||
import { useDrag, useDrop, type DnDStoreProvider } from '@dbeaver/react-dnd';
|
||||
import { DataGridCellHeaderContext } from '../DataGridHeaderCellContext.js';
|
||||
import { useGridReactiveValue } from '../useGridReactiveValue.js';
|
||||
@@ -9,6 +9,7 @@ interface Props {
|
||||
colIdx: number;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer({ colIdx, tabIndex }: Props) {
|
||||
const dndHeaderContext = use(HeaderDnDContext);
|
||||
const cellHeaderContext = use(DataGridCellHeaderContext);
|
||||
@@ -16,11 +17,9 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
|
||||
const getHeaderText = useGridReactiveValue(headerElement ? undefined : cellHeaderContext?.headerText, colIdx);
|
||||
const isColumnSortable = useGridReactiveValue(cellHeaderContext?.columnSortable, colIdx);
|
||||
const onColumnSort = cellHeaderContext?.onColumnSort;
|
||||
const onHeaderKeyDown = cellHeaderContext?.onHeaderKeyDown;
|
||||
const sortingState = useGridReactiveValue(cellHeaderContext?.columnSortingState, colIdx);
|
||||
|
||||
const orderButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const draggable = dndHeaderContext?.getCanDrag?.(colIdx) ?? false;
|
||||
const drag = useDrag({
|
||||
draggable,
|
||||
@@ -55,26 +54,32 @@ export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer
|
||||
},
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Tab' && !e.shiftKey && isColumnSortable && onColumnSort && orderButtonRef.current !== document.activeElement) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
containerRef.current?.parentElement?.setAttribute('aria-selected', 'false');
|
||||
orderButtonRef.current?.focus();
|
||||
function handleSort(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) {
|
||||
if (!onColumnSort) return;
|
||||
|
||||
const nextSortState = sortingState === 'asc' ? 'desc' : sortingState === 'desc' ? null : 'asc';
|
||||
onColumnSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
||||
onHeaderKeyDown?.(event);
|
||||
|
||||
if ((event.key === 'Enter' || event.key === ' ') && isColumnSortable && onColumnSort) {
|
||||
event.preventDefault();
|
||||
handleSort(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="tw:w-full tw:h-full tw:content-center tw:flex tw:items-center tw:justify-between tw:gap-1 tw:outline-none tw:group"
|
||||
{...drag.props}
|
||||
{...drop.props}
|
||||
>
|
||||
<span className="tw:overflow-hidden tw:text-ellipsis">{headerElement ?? getHeaderText ?? ''}</span>
|
||||
{isColumnSortable && onColumnSort && <OrderButton ref={orderButtonRef} colIdx={colIdx} sortState={sortingState} onSort={onColumnSort} />}
|
||||
{isColumnSortable && onColumnSort && <OrderButton sortState={sortingState} onClick={handleSort} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { clsx, IconButton, Icon } from '@dbeaver/ui-kit';
|
||||
|
||||
interface OrderButtonProps {
|
||||
colIdx: number;
|
||||
sortState?: 'asc' | 'desc' | null;
|
||||
onSort: (attributePosition: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void;
|
||||
onClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
tabIndex?: number;
|
||||
ref: React.Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export function OrderButton({ colIdx, sortState, onSort, tabIndex, ref }: OrderButtonProps) {
|
||||
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
const nextSortState = sortState === 'asc' ? 'desc' : sortState === 'desc' ? null : 'asc';
|
||||
onSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
|
||||
}
|
||||
|
||||
export function OrderButton({ sortState, onClick, tabIndex }: OrderButtonProps) {
|
||||
const svgSortAsc = 'sort-asc';
|
||||
const svgSortDesc = 'sort-desc';
|
||||
const svgSortUnknown = 'sort-unknown';
|
||||
@@ -24,9 +17,8 @@ export function OrderButton({ colIdx, sortState, onSort, tabIndex, ref }: OrderB
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleClick}
|
||||
onClick={onClick}
|
||||
tabIndex={tabIndex}
|
||||
ref={ref}
|
||||
title="Sort by column"
|
||||
aria-label="Sort by column"
|
||||
className={clsx(
|
||||
|
||||
@@ -84,11 +84,14 @@ export const DataGridTable = observer<IDataPresentationProps>(function DataGridT
|
||||
const tableData = useTableData(model as unknown as IDatabaseDataModel<ResultSetDataSource>, resultIndex, dataGridDivRef);
|
||||
const gridSelectionContext = useGridSelectionContext(tableData, selectionAction);
|
||||
|
||||
function restoreFocus() {
|
||||
const gridDiv = gridContainerRef.current;
|
||||
const focusSink = gridDiv?.querySelector<HTMLDivElement>('[aria-selected="true"]');
|
||||
focusSink?.focus();
|
||||
}
|
||||
const restoreFocus = useCallback(
|
||||
function restoreFocus() {
|
||||
const gridDiv = gridContainerRef.current;
|
||||
const focusSink = gridDiv?.querySelector<HTMLDivElement>('[aria-selected="true"]');
|
||||
focusSink?.focus();
|
||||
},
|
||||
[gridContainerRef],
|
||||
);
|
||||
|
||||
function isGridInFocus(): boolean {
|
||||
const gridDiv = gridContainerRef.current;
|
||||
@@ -544,6 +547,7 @@ export const DataGridTable = observer<IDataPresentationProps>(function DataGridT
|
||||
onColumnSort={handleSort}
|
||||
onCellChange={handleCellChange}
|
||||
onCellKeyDown={handleCellKeyDown}
|
||||
onHeaderKeyDown={gridSelectedCellCopy.onKeydownHandler}
|
||||
/>
|
||||
</div>
|
||||
</TableDataContext.Provider>
|
||||
|
||||
+8
-1
@@ -6,9 +6,16 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.container {
|
||||
.clickAreaOverlay {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.iconOrImage {
|
||||
cursor: auto;
|
||||
width: 10px;
|
||||
|
||||
+8
-2
@@ -36,10 +36,16 @@ export const TableIndexColumnHeader = observer(function TableIndexColumnHeader()
|
||||
}
|
||||
|
||||
return (
|
||||
<div title={translate('data_grid_table_index_column_tooltip')} className={s(styles, { container: true })} onClick={handleClick}>
|
||||
<>
|
||||
{readonly && (
|
||||
<IconOrImage title={translate('data_grid_table_readonly_tooltip')} icon="/icons/lock.png" className={s(styles, { iconOrImage: true })} />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
title={translate('data_grid_table_index_column_tooltip')}
|
||||
className={s(styles, { clickAreaOverlay: true })}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user