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

View File

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

View File

@@ -76,6 +76,7 @@
onShowForm={onSetFormView && !overlayDefinition ? () => onSetFormView(rowData, null) : null} onShowForm={onSetFormView && !overlayDefinition ? () => onSetFormView(rowData, null) : null}
extraIcon={overlayDefinition ? OVERLAY_STATUS_ICONS[rowStatus.status] : null} extraIcon={overlayDefinition ? OVERLAY_STATUS_ICONS[rowStatus.status] : null}
extraIconTooltip={overlayDefinition ? OVERLAY_STATUS_TOOLTIPS[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)} {#each visibleRealColumns as col (col.uniqueName)}
{#if inplaceEditorState.cell && rowIndex == inplaceEditorState.cell[0] && col.colIndex == inplaceEditorState.cell[1]} {#if inplaceEditorState.cell && rowIndex == inplaceEditorState.cell[0] && col.colIndex == inplaceEditorState.cell[1]}

View File

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

View File

@@ -13,6 +13,24 @@ export function isRegularCell(cell: CellAddress): cell is RegularCellAddress {
return _.isNumber(row) && _.isNumber(col); 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 { function normalizeHeaderForSelection(addr: CellAddress): CellAddress {
if (addr[0] == 'filter') return ['header', addr[1]]; if (addr[0] == 'filter') return ['header', addr[1]];
return addr; return addr;