SYNC: improved data grid navigation

This commit is contained in:
SPRINX0\prochazka
2025-12-10 13:43:28 +01:00
committed by Diflow
parent a57063adf7
commit 76ae2e0e5a
5 changed files with 100 additions and 16 deletions

View File

@@ -25,6 +25,7 @@
export let setFilter;
export let showResizeSplitter = false;
export let onFocusGrid = null;
export let onFocusGridHeader = null;
export let onGetReference = null;
export let foreignKey = null;
export let conid = null;
@@ -204,6 +205,11 @@
// ev.stopPropagation();
ev.preventDefault();
}
if (ev.keyCode == keycodes.upArrow) {
if (onFocusGridHeader) onFocusGridHeader();
// ev.stopPropagation();
ev.preventDefault();
}
// if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) {
// if (this.props.onControlKey) this.props.onControlKey(ev.keyCode);
// }

View File

@@ -380,7 +380,17 @@
filterCellsForRow,
} from './gridutil';
import HorizontalScrollBar from './HorizontalScrollBar.svelte';
import { cellFromEvent, emptyCellArray, getCellRange, isRegularCell, nullCell, topLeftCell } from './selection';
import {
cellFromEvent,
emptyCellArray,
getCellRange,
isColumnHeaderCell,
isRegularCell,
isRowHeaderCell,
isTableHeaderCell,
nullCell,
topLeftCell,
} from './selection';
import VerticalScrollBar from './VerticalScrollBar.svelte';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import InlineButton from '../buttons/InlineButton.svelte';
@@ -1305,7 +1315,7 @@
function scrollIntoView(cell) {
const [row, col] = cell;
if (row != null) {
if (_.isNumber(row)) {
let newRow = null;
const rowCount = grider.rowCount;
if (rowCount == 0) return;
@@ -1323,7 +1333,7 @@
}
}
if (col != null) {
if (_.isNumber(col)) {
if (col >= columnSizes.frozenCount) {
let newColumn = columnSizes.scrollInView(
firstVisibleColumnScrollIndex,
@@ -1553,7 +1563,11 @@
}
if (event.shiftKey) {
if (!isRegularCell(shiftDragStartCell)) {
if (
!isRegularCell(shiftDragStartCell) &&
!isColumnHeaderCell(shiftDragStartCell) &&
!isRowHeaderCell(shiftDragStartCell)
) {
shiftDragStartCell = currentCell;
}
} else {
@@ -1581,7 +1595,13 @@
}
function handleCursorMove(event) {
if (!isRegularCell(currentCell)) return null;
if (
!isRegularCell(currentCell) &&
!isColumnHeaderCell(currentCell) &&
!isRowHeaderCell(currentCell) &&
!isTableHeaderCell(currentCell)
)
return null;
let rowCount = grider.rowCount;
if (isCtrlOrCommandKey(event)) {
switch (event.keyCode) {
@@ -1608,24 +1628,36 @@
switch (event.keyCode) {
case keycodes.upArrow:
if (currentCell[0] == 0) return focusFilterEditor(currentCell[1]);
return moveCurrentCell(currentCell[0] - 1, currentCell[1], event);
return _.isNumber(currentCell[0]) ? moveCurrentCell(currentCell[0] - 1, currentCell[1], event) : null;
case keycodes.downArrow:
return moveCurrentCell(currentCell[0] + 1, currentCell[1], event);
if (currentCell[0] == 'header') return focusFilterEditor(currentCell[1]);
return _.isNumber(currentCell[0]) ? moveCurrentCell(currentCell[0] + 1, currentCell[1], event) : null;
case keycodes.enter:
if (!grider.editable) return moveCurrentCell(currentCell[0] + 1, currentCell[1], event);
if (!grider.editable)
return _.isNumber(currentCell[0]) ? moveCurrentCell(currentCell[0] + 1, currentCell[1], event) : null;
break;
case keycodes.leftArrow:
return moveCurrentCell(currentCell[0], currentCell[1] - 1, event);
return _.isNumber(currentCell[1])
? moveCurrentCell(currentCell[0], currentCell[1] == 0 ? 'header' : currentCell[1] - 1, event)
: null;
case keycodes.rightArrow:
return moveCurrentCell(currentCell[0], currentCell[1] + 1, event);
return currentCell[1] == 'header'
? moveCurrentCell(currentCell[0], 0, event)
: _.isNumber(currentCell[1])
? moveCurrentCell(currentCell[0], currentCell[1] + 1, event)
: null;
case keycodes.home:
return moveCurrentCell(currentCell[0], 0, event);
case keycodes.end:
return moveCurrentCell(currentCell[0], columnSizes.realCount - 1, event);
case keycodes.pageUp:
return moveCurrentCell(currentCell[0] - visibleRowCountLowerBound, currentCell[1], event);
return _.isNumber(currentCell[0])
? moveCurrentCell(currentCell[0] - visibleRowCountLowerBound, currentCell[1], event)
: null;
case keycodes.pageDown:
return moveCurrentCell(currentCell[0] + visibleRowCountLowerBound, currentCell[1], event);
return _.isNumber(currentCell[0])
? moveCurrentCell(currentCell[0] + visibleRowCountLowerBound, currentCell[1], event)
: null;
case keycodes.tab: {
return moveCurrentCellWithTabKey(event.shiftKey);
}
@@ -1659,10 +1691,14 @@
function moveCurrentCell(row, col, event = null) {
const rowCount = grider.rowCount;
if (row < 0) row = 0;
if (row >= rowCount) row = rowCount - 1;
if (col < 0) col = 0;
if (col >= columnSizes.realCount) col = columnSizes.realCount - 1;
if (_.isNumber(row)) {
if (row < 0) row = 0;
if (row >= rowCount) row = rowCount - 1;
}
if (_.isNumber(col)) {
if (col < 0) col = 0;
if (col >= columnSizes.realCount) col = columnSizes.realCount - 1;
}
currentCell = [row, col];
// setSelectedCells([...(event.ctrlKey ? selectedCells : []), [row, col]]);
selectedCells = [[row, col]];
@@ -1782,6 +1818,17 @@
if (domFocusField) domFocusField.focus();
};
const selectColumnHeaderCell = uniquePath => {
const modelIndex = columns.findIndex(x => x.uniquePath == uniquePath);
const realIndex = columnSizes.modelToReal(modelIndex);
let cell = ['header', realIndex];
// @ts-ignore
currentCell = cell;
// @ts-ignore
selectedCells = [cell];
if (domFocusField) domFocusField.focus();
};
const [inplaceEditorState, dispatchInsplaceEditor] = createReducer((state, action) => {
switch (action.type) {
case 'show':
@@ -2031,6 +2078,7 @@
data-row="header"
data-col={col.colIndex}
style={`width:${col.width}px; min-width:${col.width}px; max-width:${col.width}px`}
class:active-header-cell={currentCell && currentCell[0] == 'header' && currentCell[1] == col.colIndex}
>
<ColumnHeaderControl
column={col}
@@ -2105,6 +2153,9 @@
onFocusGrid={() => {
selectTopmostCell(col.uniqueName);
}}
onFocusGridHeader={() => {
selectColumnHeaderCell(col.uniqueName);
}}
dataType={col.dataType}
filterDisabled={display.isFilterDisabled(col.uniqueName)}
/>
@@ -2231,6 +2282,9 @@
background-color: var(--theme-bg-1);
overflow: hidden;
}
:global(.data-grid-focused) .active-header-cell {
background-color: var(--theme-bg-selected);
}
.filter-cell {
text-align: left;
overflow: hidden;

View File

@@ -76,6 +76,7 @@
onShowForm={onSetFormView && !overlayDefinition ? () => onSetFormView(rowData, null) : null}
extraIcon={overlayDefinition ? OVERLAY_STATUS_ICONS[rowStatus.status] : null}
extraIconTooltip={overlayDefinition ? OVERLAY_STATUS_TOOLTIPS[rowStatus.status] : null}
isSelected={frameSelection ? false : !!selectedCells?.find(cell => cell[0] == rowIndex && cell[1] == 'header')}
/>
{#each visibleRealColumns as col (col.uniqueName)}
{#if inplaceEditorState.cell && rowIndex == inplaceEditorState.cell[0] && col.colIndex == inplaceEditorState.cell[1]}

View File

@@ -7,6 +7,7 @@
export let extraIcon = null;
export let extraIconTooltip = null;
export let isSelected = false;
let mouseIn = false;
</script>
@@ -14,6 +15,7 @@
<td
data-row={rowIndex}
data-col="header"
class:selected={isSelected}
on:mouseenter={() => (mouseIn = true)}
on:mouseleave={() => (mouseIn = false)}
>
@@ -43,4 +45,7 @@
right: 0px;
top: 1px;
}
:global(.data-grid-focused) td.selected {
background-color: var(--theme-bg-selected);
}
</style>

View File

@@ -13,6 +13,24 @@ export function isRegularCell(cell: CellAddress): cell is RegularCellAddress {
return _.isNumber(row) && _.isNumber(col);
}
export function isRowHeaderCell(cell: CellAddress): boolean {
if (!cell) return false;
const [row, col] = cell;
return col === 'header' && _.isNumber(row);
}
export function isColumnHeaderCell(cell: CellAddress): boolean {
if (!cell) return false;
const [row, col] = cell;
return row === 'header' && _.isNumber(col);
}
export function isTableHeaderCell(cell: CellAddress): boolean {
if (!cell) return false;
const [row, col] = cell;
return row === 'header' && col === 'header';
}
function normalizeHeaderForSelection(addr: CellAddress): CellAddress {
if (addr[0] == 'filter') return ['header', addr[1]];
return addr;