diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/virtualColumns.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/virtualColumns.functional.ts index 52f3b874addd..09f7a842e0f5 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/virtualColumns.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/virtualColumns.functional.ts @@ -321,3 +321,137 @@ test('DataGrid should scroll to the last cell of the previous row and focus it w })); }); }); + +test('DataGrid should move focus from Save to Cancel button on Tab press in row editing mode with virtual columns (T1326106)', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + + await t + .expect(dataGrid.isReady()) + .ok(); + + const commandCell = dataGrid.getDataRow(0).getCommandCell(5); + + // act - click Edit button on first row + await t.click(commandCell.getButton(0)); + + const saveButton = commandCell.getButton(0); + const cancelButton = commandCell.getButton(1); + + // assert + await t + .expect(saveButton.exists) + .ok() + .expect(cancelButton.exists) + .ok(); + + const lastDataCell = dataGrid.getDataCell(0, 4); + + // act + await t.click(lastDataCell.element); + + // assert + await t + .expect(lastDataCell.isFocused) + .ok(); + + // act + await t.pressKey('tab'); + + // assert + await t + .expect(saveButton.focused) + .ok(); + + // act + await t.pressKey('tab'); + + // assert + await t + .expect(cancelButton.focused) + .ok(); + + // act + await t.pressKey('tab'); + + // assert - First cell of the second row should be focused + await t + .expect(dataGrid.getDataCell(1, 0).isFocused) + .ok(); +}).before(async () => createWidget('dxDataGrid', { + width: 800, + dataSource: generateData(10, 5), + columnWidth: 100, + keyExpr: 'field1', + editing: { + mode: 'row', + allowUpdating: true, + allowDeleting: true, + }, + scrolling: { + columnRenderingMode: 'virtual', + }, +})); + +test('DataGrid should move focus from Save button to the last data cell on Shift + Tab press in row editing mode(T1326106)', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + + await t + .expect(dataGrid.isReady()) + .ok(); + + const commandCell = dataGrid.getDataRow(0).getCommandCell(5); + + // act - click Edit button on first row + await t.click(commandCell.getButton(0)); + + const saveButton = commandCell.getButton(0); + const cancelButton = commandCell.getButton(1); + + // assert + await t + .expect(saveButton.exists) + .ok() + .expect(cancelButton.exists) + .ok(); + + const firstDataCell = dataGrid.getDataCell(1, 0); + + // act + await t.click(firstDataCell.element); + + // assert + await t + .expect(firstDataCell.element.focused) + .ok(); + + // act + await t.pressKey('shift+tab'); + + // assert + await t + .expect(saveButton.focused) + .ok(); + + // act + await t.pressKey('shift+tab'); + + // assert - Last cell of the first row should be focused + await t + .expect(dataGrid.getDataCell(0, 4).isFocused) + .ok(); +}).before(async () => createWidget('dxDataGrid', { + width: 800, + dataSource: generateData(10, 5), + columnWidth: 100, + keyExpr: 'field1', + editing: { + mode: 'row', + allowUpdating: true, + allowDeleting: true, + }, + scrolling: { + columnRenderingMode: 'virtual', + }, +})); diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts index f80d16f22708..d85fb4c58056 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts @@ -102,8 +102,6 @@ function focusCellHandler(e) { } export class AdaptiveColumnsController extends modules.ViewController { - private _keyboardNavigationController!: KeyboardNavigationController; - private _columnsController!: ColumnsController; private _dataController!: DataController; @@ -120,6 +118,8 @@ export class AdaptiveColumnsController extends modules.ViewController { private _hidingColumnsQueue: any; + protected _keyboardNavigationController!: KeyboardNavigationController; + public init() { this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts index 8f5aa9983318..c7aeb3dde3f9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts @@ -82,9 +82,11 @@ import { } from './const'; import { GridCoreKeyboardNavigationDom } from './dom'; import { KeyboardNavigationController as KeyboardNavigationControllerCore } from './m_keyboard_navigation_core'; +import { keyboardNavigationScrollableA11yExtender } from './scrollable_a11y'; +import type { NavigationDirection, NavigationElementType, NavigationKeyCode } from './types'; import { getInteractiveElement, - isCellInHeaderRow, + getNextColumnIndex, isCellInHeaderRow, isDataRow, isDetailRow, isEditForm, @@ -96,9 +98,7 @@ import { isMobile, isNotFocusedRow, shouldPreventScroll, -} from './m_keyboard_navigation_utils'; -import { keyboardNavigationScrollableA11yExtender } from './scrollable_a11y'; -import type { NavigationDirection, NavigationElementType, NavigationKeyCode } from './types'; +} from './utils'; export class KeyboardNavigationController extends KeyboardNavigationControllerCore { private _updateFocusTimeout: any; @@ -507,7 +507,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo this._editorFactory.loseFocus(); - if (this._editingController.isEditing() && !this._isRowEditMode()) { + if (this._editingController.isEditing() && !this.isRowEditMode()) { this._resetFocusedCell(true); this._resetFocusedView(); this._closeEditCell(); @@ -817,8 +817,9 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const tabOptions = { hasEditingOptions, isLastValidCell, isOriginalHandlerRequired }; - // Virtual columns require horizontal scrolling before executing tab navigation - if (canHandleNavigation && this._isVirtualColumnRender()) { + // For virtual columns, scroll to reveal the next column before navigating. + // Skip scrolling when focus should cycle through interactive elements within the current cell. + if (canHandleNavigation && this.needVirtualColumnScroll(eventTarget, event)) { this._processVirtualHorizontalPosition(direction, event) .done(() => this.executeTabKey(event, tabOptions)); return; @@ -827,6 +828,20 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo this.executeTabKey(event, tabOptions); } + /** + * Determines whether horizontal scrolling is needed for virtual column tab navigation. + * Returns false when focus should cycle through interactive elements within the current cell. + */ + private needVirtualColumnScroll(eventTarget: Element, event: KeyDownEvent): boolean { + if (!this._isVirtualColumnRender()) { + return false; + } + + const $cell = this.getCellElementFromTarget(eventTarget); + + return !this.isOriginalTabHandlerRequired($cell, event); + } + /** * Determines whether the focused cell is at the boundary of the grid. * When at the boundary, Tab/Shift+Tab should let focus leave the grid. @@ -899,62 +914,19 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo event: KeyDownEvent, ): DeferredObj { const columnIndex = this.getColumnIndex(); - let nextColumnIndex; - let horizontalScrollPosition = 0; - let needToScroll = false; + const nextColumnIndex = getNextColumnIndex(direction, columnIndex); - // eslint-disable-next-line default-case - switch (direction) { - case 'next': - case 'nextInRow': { - const columnsCount = this._getVisibleColumnCount(); - nextColumnIndex = columnIndex + 1; - horizontalScrollPosition = this.option('rtlEnabled') - ? this._getMaxHorizontalOffset() - : 0; - if (direction === 'next') { - needToScroll = columnsCount === nextColumnIndex - || (this._isFixedColumn(columnIndex) - && !this._isColumnRendered(nextColumnIndex)); - } else { - needToScroll = columnsCount > nextColumnIndex - && this._isFixedColumn(columnIndex) - && !this._isColumnRendered(nextColumnIndex); - } - break; - } - case 'previous': - case 'previousInRow': { - nextColumnIndex = columnIndex - 1; - horizontalScrollPosition = this.option('rtlEnabled') - ? 0 - : this._getMaxHorizontalOffset(); - if (direction === 'previous') { - const columnIndexOffset = this._columnsController.getColumnIndexOffset(); - const leftEdgePosition = nextColumnIndex < 0 && columnIndexOffset === 0; - needToScroll = leftEdgePosition - || (this._isFixedColumn(columnIndex) - && !this._isColumnRendered(nextColumnIndex)); - } else { - needToScroll = nextColumnIndex >= 0 - && this._isFixedColumn(columnIndex) - && !this._isColumnRendered(nextColumnIndex); - } - break; - } - } + // Strategy 1: scroll to the grid's edge (beginning or end) + if (this.needScrollToEdge(direction, columnIndex, nextColumnIndex)) { + const scrollPosition = this.getEdgeScrollPosition(direction); - if (needToScroll) { event.originalEvent.preventDefault(); - return this.scrollLeft(horizontalScrollPosition); + return this.scrollLeft(scrollPosition); } - if ( - isDefined(nextColumnIndex) - && isDefined(direction) - && this._isColumnVirtual(nextColumnIndex) - ) { + // Strategy 2: scroll to reveal the next virtual column + if (this._isColumnVirtual(nextColumnIndex)) { event.originalEvent.preventDefault(); return this.scrollToNextCell(null, direction); @@ -964,6 +936,45 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo return Deferred().resolve().promise(); } + private getEdgeScrollPosition(direction: NavigationDirection): number { + const isForward = direction === 'next' || direction === 'nextInRow'; + + if (this.option('rtlEnabled')) { + return isForward ? this._getMaxHorizontalOffset() : 0; + } + + return isForward ? 0 : this._getMaxHorizontalOffset(); + } + + private needScrollToEdge( + direction: NavigationDirection, + columnIndex: number, + nextColumnIndex: number, + ): boolean { + const isFixedColumn = this._isFixedColumn(columnIndex); + const isNextColumnRendered = this._isColumnRendered(nextColumnIndex); + const needScrollPastFixed = isFixedColumn && !isNextColumnRendered; + + switch (direction) { + case 'next': { + const isLastColumn = this._getVisibleColumnCount() === nextColumnIndex; + return isLastColumn || needScrollPastFixed; + } + case 'nextInRow': + return this._getVisibleColumnCount() > nextColumnIndex + && needScrollPastFixed; + case 'previous': { + const columnIndexOffset = this._columnsController.getColumnIndexOffset(); + const isAtLeftEdge = nextColumnIndex < 0 && columnIndexOffset === 0; + return isAtLeftEdge || needScrollPastFixed; + } + case 'previousInRow': + return nextColumnIndex >= 0 && needScrollPastFixed; + default: + return false; + } + } + /** * Handles Tab key when a cell is being edited. * Moves focus and editing to the next/previous editable cell. @@ -1022,7 +1033,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const nextCellFocused = this._focusCell($nextCell, !isHighlighted); if (nextCellFocused) { - if (!this._isRowEditMode() && isEditingAllowed) { + if (!this.isRowEditMode() && isEditingAllowed) { this._editFocusedCell(); } else { this._focusInteractiveElement($nextCell, isShift); @@ -1268,7 +1279,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const d = Deferred(); const { target } = event; const $cell = this.getCellElementFromTarget(target); - const isRowEditMode = this._isRowEditMode(); + const isRowEditMode = this.isRowEditMode(); this._updateFocusedCellPosition($cell); @@ -1298,7 +1309,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo ); if (isEditing) { this._updateFocusedCellPosition($cell); - if (!this._isRowEditMode()) { + if (!this.isRowEditMode()) { if (this._editingController.getEditMode() === 'cell') { this._editingController.cancelEditData(); } else { @@ -1808,7 +1819,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo let $cell: dxElementWrapper | null = this._getFocusedCell(); const isEditing = this._editingController.isEditing(); - if (!this.getMasterDetailCell($cell) || this._isRowEditMode()) { + if (!this.getMasterDetailCell($cell) || this.isRowEditMode()) { if (this._hasSkipRow($cell.parent())) { const direction = this._focusedCellPosition && this._focusedCellPosition.rowIndex > 0 ? 'upArrow' @@ -2006,10 +2017,6 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo return { columnIndex, rowIndex }; } - private getRowIndex() { - return this._focusedCellPosition ? this._focusedCellPosition.rowIndex : -1; - } - public getColumnIndex() { return this._focusedCellPosition ? this._focusedCellPosition.columnIndex @@ -2317,7 +2324,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const column = this._columnsController.getVisibleColumns()[visibleColumnIndex]; if (this._isAllowEditing(row, column)) { - if (this._isRowEditMode()) { + if (this.isRowEditMode()) { this._editingController.editRow(visibleRowIndex); } else if (focusedCellPosition) { this._startEditCell(eventArgs, fastEditingKey); @@ -2620,11 +2627,6 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo return gridCoreUtils.isElementInCurrentGrid(this, $(event.target)); } - private _isRowEditMode() { - const editMode = this._editingController.getEditMode(); - return editMode === EDIT_MODE_ROW || editMode === EDIT_MODE_FORM; - } - private _isCellEditMode() { const editMode = this._editingController.getEditMode(); return editMode === EDIT_MODE_CELL || editMode === EDIT_MODE_BATCH; @@ -2874,6 +2876,15 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo public navigationToCellInProgress(): boolean { return this.needToRestoreFocus || this.needNavigationToCell(); } + + public isRowEditMode(): boolean { + const editMode = this._editingController.getEditMode(); + return editMode === EDIT_MODE_ROW || editMode === EDIT_MODE_FORM; + } + + public getRowIndex(): number { + return this._focusedCellPosition ? this._focusedCellPosition.rowIndex : -1; + } } const rowsView = (Base: ModuleType) => class RowsViewKeyboardExtender extends Base { @@ -2995,7 +3006,7 @@ const rowsView = (Base: ModuleType) => class RowsViewKeyboardExtender const { fullReload, pageSize } = operationTypes ?? {}; if (!change || !repaintChangesOnly || fullReload || pageSize) { - const preventScroll = shouldPreventScroll(this); + const preventScroll = shouldPreventScroll(this._keyboardNavigationController); this.renderFocusState({ preventScroll, pageSizeChanged: pageSize, @@ -3216,7 +3227,7 @@ const adaptiveColumns = (Base: ModuleType) => class A super._hideVisibleColumnInView({ view, isCommandColumn, visibleIndex }); if (view.name === ROWS_VIEW) { this._rowsView.renderFocusState({ - preventScroll: shouldPreventScroll(this), + preventScroll: shouldPreventScroll(this._keyboardNavigationController), }); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_core.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_core.ts index 86fb3950b870..b4e43eaf0189 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_core.ts @@ -12,8 +12,8 @@ import modules from '../m_modules'; import type { Controllers, OptionChanged, Views } from '../m_types'; import gridCoreUtils from '../m_utils'; import { Direction } from './const'; -import { isElementDefined, isFixedColumnIndexOffsetRequired } from './m_keyboard_navigation_utils'; import type { NavigationDirection } from './types'; +import { isElementDefined, isFixedColumnIndexOffsetRequired } from './utils'; export class KeyboardNavigationController extends modules.ViewController { private keyDownListener: any; diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_utils.ts deleted file mode 100644 index 24130ff4e125..000000000000 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -import devices from '@js/core/devices'; -import type { dxElementWrapper } from '@js/core/renderer'; -import { isDefined } from '@js/core/utils/type'; - -import { EDIT_ROW, EDITOR_CELL_CLASS } from '../editing/const'; -import { - ADAPTIVE_ITEM_TEXT_CLASS, - COMMAND_SELECT_CLASS, DATA_ROW_CLASS, - EDIT_FORM_CLASS, - FREESPACE_ROW_CLASS, - GROUP_ROW_CLASS, - HEADER_ROW_CLASS, - INTERACTIVE_ELEMENTS_SELECTOR, - MASTER_DETAIL_ROW_CLASS, - VIRTUAL_ROW_CLASS, -} from './const'; - -const DATAGRID_GROUP_FOOTER_CLASS = 'dx-datagrid-group-footer'; - -export function isGroupRow($row) { - return $row && $row.hasClass(GROUP_ROW_CLASS); -} -export function isGroupFooterRow($row) { - return $row && $row.hasClass(DATAGRID_GROUP_FOOTER_CLASS); -} - -export function isDetailRow($row) { - return $row && $row.hasClass(MASTER_DETAIL_ROW_CLASS); -} -export function isAdaptiveItem($element) { - return $element && $element.hasClass(ADAPTIVE_ITEM_TEXT_CLASS); -} - -export function isEditRow($row) { - return $row?.hasClass(EDIT_ROW); -} - -export function isEditForm($row) { - return $row && $row.hasClass(MASTER_DETAIL_ROW_CLASS) && $row.hasClass(EDIT_FORM_CLASS); -} - -export function isDataRow($row) { - return $row && $row.hasClass(DATA_ROW_CLASS); -} - -export function isNotFocusedRow($row) { - return !$row || $row.hasClass(FREESPACE_ROW_CLASS) || $row.hasClass(VIRTUAL_ROW_CLASS); -} - -export function isEditorCell(that, $cell) { - return !that._isRowEditMode() && $cell && !$cell.hasClass(COMMAND_SELECT_CLASS) && $cell.hasClass(EDITOR_CELL_CLASS); -} - -export function isElementDefined($element) { - return isDefined($element) && $element.length > 0; -} - -export function isMobile() { - return devices.current().deviceType !== 'desktop'; -} - -export function isCellInHeaderRow($cell) { - return !!$cell.parent(`.${HEADER_ROW_CLASS}`).length; -} - -export function isFixedColumnIndexOffsetRequired(that, column) { - const rtlEnabled = that.option('rtlEnabled'); - - if (rtlEnabled) { - return !(column.fixedPosition === 'right' || (isDefined(column.command) && !isDefined(column.fixedPosition))); - } - return !(!isDefined(column.fixedPosition) || column.fixedPosition === 'left'); -} - -export function shouldPreventScroll(that) { - const keyboardController = that.getController('keyboardNavigation'); - return keyboardController._isVirtualScrolling() ? that.option('focusedRowIndex') === keyboardController.getRowIndex() : false; -} - -export function getInteractiveElements($cell: dxElementWrapper): dxElementWrapper { - return $cell - .find(INTERACTIVE_ELEMENTS_SELECTOR) - .filter(':visible'); -} - -export function getInteractiveElement($cell: dxElementWrapper, isLast: boolean): dxElementWrapper { - const $focusedElement = getInteractiveElements($cell); - - return isLast ? $focusedElement.last() : $focusedElement.first(); -} diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts new file mode 100644 index 000000000000..187896e5ba61 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts @@ -0,0 +1,108 @@ +import devices from '@js/core/devices'; +import type { dxElementWrapper } from '@js/core/renderer'; +import { isDefined } from '@js/core/utils/type'; + +import type { Column } from '../columns_controller/types'; +import { EDIT_ROW, EDITOR_CELL_CLASS } from '../editing/const'; +import { + ADAPTIVE_ITEM_TEXT_CLASS, + COMMAND_SELECT_CLASS, DATA_ROW_CLASS, + EDIT_FORM_CLASS, + FREESPACE_ROW_CLASS, + GROUP_ROW_CLASS, + HEADER_ROW_CLASS, + INTERACTIVE_ELEMENTS_SELECTOR, + MASTER_DETAIL_ROW_CLASS, + VIRTUAL_ROW_CLASS, +} from './const'; +import type { KeyboardNavigationController } from './m_keyboard_navigation'; +import type { KeyboardNavigationController as KeyboardNavigationControllerCore } from './m_keyboard_navigation_core'; +import type { NavigationDirection } from './types'; + +const DATAGRID_GROUP_FOOTER_CLASS = 'dx-datagrid-group-footer'; + +// TODO remove undefined from types +export const isGroupRow = ( + $row: dxElementWrapper | undefined, +): boolean => !!$row?.hasClass(GROUP_ROW_CLASS); + +export const isGroupFooterRow = ( + $row: dxElementWrapper, +): boolean => $row?.hasClass(DATAGRID_GROUP_FOOTER_CLASS); + +export const isDetailRow = ( + $row: dxElementWrapper, +): boolean => $row?.hasClass(MASTER_DETAIL_ROW_CLASS); + +export const isAdaptiveItem = ( + $element: dxElementWrapper, +): boolean => $element?.hasClass(ADAPTIVE_ITEM_TEXT_CLASS); + +export const isEditRow = ($row: dxElementWrapper): boolean => $row?.hasClass(EDIT_ROW); + +export const isEditForm = ( + $row: dxElementWrapper, +): boolean => $row?.hasClass(MASTER_DETAIL_ROW_CLASS) && $row.hasClass(EDIT_FORM_CLASS); + +// TODO remove null and undefined from types +export const isDataRow = ( + $row: dxElementWrapper | undefined | null, +): boolean => !!$row?.hasClass(DATA_ROW_CLASS); + +export const isNotFocusedRow = ( + $row: dxElementWrapper, +): boolean => !$row || $row.hasClass(FREESPACE_ROW_CLASS) || $row.hasClass(VIRTUAL_ROW_CLASS); + +export const isEditorCell = ( + that: KeyboardNavigationController, + $cell: dxElementWrapper, +): boolean => !that.isRowEditMode() + && $cell && !$cell.hasClass(COMMAND_SELECT_CLASS) + && $cell.hasClass(EDITOR_CELL_CLASS); + +// TODO remove null and undefined from types +export const isElementDefined = ( + $element: dxElementWrapper | null | undefined, +): boolean => isDefined($element) && $element.length > 0; + +export const isMobile = (): boolean => devices.current().deviceType !== 'desktop'; + +export const isCellInHeaderRow = ($cell: dxElementWrapper): boolean => !!$cell.parent(`.${HEADER_ROW_CLASS}`).length; + +export const isFixedColumnIndexOffsetRequired = ( + that: KeyboardNavigationControllerCore, + column: Column, +): boolean => { + const rtlEnabled = that.option('rtlEnabled'); + + if (rtlEnabled) { + return !(column.fixedPosition === 'right' || (isDefined(column.command) && !isDefined(column.fixedPosition))); + } + return !(!isDefined(column.fixedPosition) || column.fixedPosition === 'left'); +}; + +export const shouldPreventScroll = ( + that: KeyboardNavigationController, +): boolean => (that._isVirtualScrolling() + ? that.option('focusedRowIndex') === that.getRowIndex() + : false); + +export const getInteractiveElements = ($cell: dxElementWrapper): dxElementWrapper => $cell + .find(INTERACTIVE_ELEMENTS_SELECTOR) + .filter(':visible'); + +export const getInteractiveElement = ( + $cell: dxElementWrapper, + isLast: boolean, +): dxElementWrapper => { + const $focusedElement = getInteractiveElements($cell); + + return isLast ? $focusedElement.last() : $focusedElement.first(); +}; + +export const getNextColumnIndex = ( + direction: NavigationDirection, + columnIndex: number, +): number => (direction === 'next' || direction === 'nextInRow' + ? columnIndex + 1 + : columnIndex - 1); diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts index 7c1536a9fbd4..d9cdcf8485d8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts @@ -19,7 +19,7 @@ import { isAdaptiveItem, isGroupFooterRow, isGroupRow as isGroupRowElement, -} from '../keyboard_navigation/m_keyboard_navigation_utils'; +} from '../keyboard_navigation/utils'; import type { ModuleType } from '../m_types'; import gridCoreUtils from '../m_utils'; import { CLASSES as MASTER_DETAIL_CLASSES } from '../master_detail/const';