diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_1.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_1.png new file mode 100644 index 000000000000..48b1eed9aee9 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_1.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_2.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_2.png new file mode 100644 index 000000000000..5e9edef5baca Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_shift_and_tab_2.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_1.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_1.png new file mode 100644 index 000000000000..19c745274d65 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_1.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_2.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_2.png new file mode 100644 index 000000000000..5b9ce725c58f Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_data_cells_navigation_by_tab_2.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_1.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_1.png new file mode 100644 index 000000000000..25c97d5feba6 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_1.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_2.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_2.png new file mode 100644 index 000000000000..f3c79ac48cae Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_shift_and_tab_2.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_1.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_1.png new file mode 100644 index 000000000000..aabcc4d5e1cb Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_1.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_2.png b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_2.png new file mode 100644 index 000000000000..2c993dd10371 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/etalons/fixed_columns_headers_navigation_by_tab_2.png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/stickyColumnReordering.ts b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/stickyColumnReordering.ts index b7a946c4d8d4..6930726c4f82 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/stickyColumnReordering.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/stickyColumnReordering.ts @@ -51,6 +51,9 @@ safeSizeTest('Move right fixed column to the left', async (t) => { // act await t.drag(dataGrid.getHeaders().getHeaderRow(0).getHeaderCell(24).element, -400, 0); + // TODO: issue will be fixed in the card 7Mct6tJU + await dataGrid.scrollTo(t, { x: 0 }); + await takeScreenshot('move_right_fixed_column_to_left.png', dataGrid.element); // assert @@ -155,6 +158,9 @@ safeSizeTest('Move right fixed band column to the left', async (t) => { // act await t.drag(dataGrid.getHeaders().getHeaderRow(1).getHeaderCell(3).element, -500, 0); + // TODO: issue will be fixed in the card 7Mct6tJU + await dataGrid.scrollTo(t, { x: 0 }); + await takeScreenshot('move_right_fixed_band_column_to_left.png', dataGrid.element); // assert diff --git a/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/withKeyboardNavigation.ts b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/withKeyboardNavigation.ts new file mode 100644 index 000000000000..eff013d73f8a --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/stickyColumns/withKeyboardNavigation.ts @@ -0,0 +1,264 @@ +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import { ClientFunction } from 'testcafe'; +import { safeSizeTest } from '../../../helpers/safeSizeTest'; +import { createWidget } from '../../../helpers/createWidget'; +import url from '../../../helpers/getPageUrl'; +import { defaultConfig } from './data'; + +function getScrollPadding(scrollContainer, paddingSide): Promise { + return ClientFunction((element, side) => element().style[`scrollPadding${side}`])(scrollContainer, paddingSide); +} + +function cellIsVisibleInViewport(cell, scrollContainer): Promise { + return ClientFunction((element, container) => { + const cellElement = element(); + const cellRect = cellElement.getBoundingClientRect(); + const scrollContainerElement = container(); + const scrollPaddingLeft = parseFloat(scrollContainerElement.style.scrollPaddingLeft); + const scrollPaddingRight = parseFloat(scrollContainerElement.style.scrollPaddingRight); + const { + left: scrollContainerOffsetLeft, + right: scrollContainerOffsetRight, + }: { left: number; right: number } = scrollContainerElement.getBoundingClientRect(); + + if (cellRect.right < (scrollContainerOffsetLeft + scrollPaddingLeft)) { + return false; + } + + if (cellRect.left > (scrollContainerOffsetRight - scrollPaddingRight)) { + return false; + } + + return true; + })(cell, scrollContainer); +} + +const navigateToNextCell = async (t, $headerCell, scrollContainer) => { + // act + await t + .pressKey('tab'); + + // assert + await t + .expect($headerCell.isFocused) + .ok() + .expect(cellIsVisibleInViewport($headerCell.element, scrollContainer)) + .ok(); +}; + +const navigateToPrevCell = async (t, $headerCell, scrollContainer) => { + // act + await t + .pressKey('shift+tab'); + + // assert + await t + .expect($headerCell.isFocused) + .ok() + .expect(cellIsVisibleInViewport($headerCell.element, scrollContainer)) + .ok(); +}; + +const checkScrollPadding = async ( + t, + scrollContainer, + scrollPaddingLeft, + scrollPaddingRight, +) => { + // assert + await t + .expect(getScrollPadding(scrollContainer, 'Left')) + .eql(scrollPaddingLeft) + .expect(getScrollPadding(scrollContainer, 'Right')) + .eql(scrollPaddingRight); +}; + +const DATA_GRID_SELECTOR = '#container'; + +fixture.disablePageReloads`Fixed Columns - keyboard navigation` + .page(url(__dirname, '../../container.html')); + +safeSizeTest('Headers navigation by Tab key when there are fixed columns', async (t) => { + // arrange + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(DATA_GRID_SELECTOR); + const headers = dataGrid.getHeaders(); + const headersScrollContainer = headers.getContent(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + // assert + await checkScrollPadding(t, headersScrollContainer, '130px', '285px'); + + // act + await t.click(headerRow.getHeaderCell(0).element); + + // assert + await t + .expect(headerRow.getHeaderCell(0).isFocused) + .ok(); + + // act + await navigateToNextCell(t, headerRow.getHeaderCell(1), headersScrollContainer); + await navigateToNextCell(t, headerRow.getHeaderCell(2), headersScrollContainer); + await navigateToNextCell(t, headerRow.getHeaderCell(3), headersScrollContainer); + + // assert + await checkScrollPadding(t, headersScrollContainer, '130px', '160px'); + + await takeScreenshot('fixed_columns_headers_navigation_by_tab_1.png', dataGrid.element); + + // act + await navigateToNextCell(t, headerRow.getHeaderCell(4), headersScrollContainer); + await navigateToNextCell(t, headerRow.getHeaderCell(5), headersScrollContainer); + await navigateToNextCell(t, headerRow.getHeaderCell(6), headersScrollContainer); + + // assert + await checkScrollPadding(t, headersScrollContainer, '255px', '160px'); + + await takeScreenshot('fixed_columns_headers_navigation_by_tab_2.png', dataGrid.element); + + // assert + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}, [900, 800]).before(async () => createWidget('dxDataGrid', { + ...defaultConfig, + width: 550, + customizeColumns(columns) { + columns[4].width = 125; + columns[4].fixed = true; + columns[4].fixedPosition = 'sticky'; + }, +})); + +safeSizeTest('Headers navigation by Shift and Tab keys when there are fixed columns', async (t) => { + // arrange + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(DATA_GRID_SELECTOR); + const headers = dataGrid.getHeaders(); + const headersScrollContainer = headers.getContent(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + // assert + await checkScrollPadding(t, headersScrollContainer, '130px', '285px'); + + // act + await t.click(headerRow.getHeaderCell(6).element); + + // assert + await t + .expect(headerRow.getHeaderCell(6).isFocused) + .ok(); + + // act + await navigateToPrevCell(t, headerRow.getHeaderCell(5), headersScrollContainer); + + // assert + await checkScrollPadding(t, headersScrollContainer, '130px', '160px'); + + await takeScreenshot('fixed_columns_headers_navigation_by_shift_and_tab_1.png', dataGrid.element); + + // act + await navigateToPrevCell(t, headerRow.getHeaderCell(4), headersScrollContainer); + await navigateToPrevCell(t, headerRow.getHeaderCell(3), headersScrollContainer); + await navigateToPrevCell(t, headerRow.getHeaderCell(2), headersScrollContainer); + await navigateToPrevCell(t, headerRow.getHeaderCell(1), headersScrollContainer); + await navigateToPrevCell(t, headerRow.getHeaderCell(0), headersScrollContainer); + + // assert + await checkScrollPadding(t, headersScrollContainer, '130px', '285px'); + + await takeScreenshot('fixed_columns_headers_navigation_by_shift_and_tab_2.png', dataGrid.element); + + // assert + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}, [900, 800]).before(async () => createWidget('dxDataGrid', { + ...defaultConfig, + width: 625, + customizeColumns(columns) { + columns[4].width = 125; + columns[4].fixed = true; + columns[4].fixedPosition = 'sticky'; + }, +})); + +safeSizeTest('Data cells navigation by Tab key when there are fixed columns', async (t) => { + // arrange + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(DATA_GRID_SELECTOR); + const scrollContainer = dataGrid.getScrollContainer(); + + await t.expect(dataGrid.isReady()).ok(); + + // act + await t.click(dataGrid.getDataCell(0, 0).element); + await navigateToNextCell(t, dataGrid.getDataCell(0, 1), scrollContainer); + await navigateToNextCell(t, dataGrid.getDataCell(0, 2), scrollContainer); + await navigateToNextCell(t, dataGrid.getDataCell(0, 3), scrollContainer); + + await takeScreenshot('fixed_columns_data_cells_navigation_by_tab_1.png', dataGrid.element); + + // act + await navigateToNextCell(t, dataGrid.getDataCell(0, 4), scrollContainer); + await navigateToNextCell(t, dataGrid.getDataCell(0, 5), scrollContainer); + await navigateToNextCell(t, dataGrid.getDataCell(0, 6), scrollContainer); + + await takeScreenshot('fixed_columns_data_cells_navigation_by_tab_2.png', dataGrid.element); + + // assert + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}, [900, 800]).before(async () => createWidget('dxDataGrid', { + ...defaultConfig, + width: 550, + customizeColumns(columns) { + columns[4].width = 125; + columns[4].fixed = true; + columns[4].fixedPosition = 'sticky'; + }, +})); + +safeSizeTest('Data cells navigation by Shift and Tab keys when there are fixed columns', async (t) => { + // arrange + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(DATA_GRID_SELECTOR); + const scrollContainer = dataGrid.getScrollContainer(); + + await t.expect(dataGrid.isReady()).ok(); + + // act + await t.click(dataGrid.getDataCell(0, 6).element); + await navigateToPrevCell(t, dataGrid.getDataCell(0, 5), scrollContainer); + await navigateToPrevCell(t, dataGrid.getDataCell(0, 4), scrollContainer); + + await takeScreenshot('fixed_columns_data_cells_navigation_by_shift_and_tab_1.png', dataGrid.element); + + // act + await navigateToPrevCell(t, dataGrid.getDataCell(0, 3), scrollContainer); + await navigateToPrevCell(t, dataGrid.getDataCell(0, 2), scrollContainer); + await navigateToPrevCell(t, dataGrid.getDataCell(0, 1), scrollContainer); + await navigateToPrevCell(t, dataGrid.getDataCell(0, 0), scrollContainer); + + await takeScreenshot('fixed_columns_data_cells_navigation_by_shift_and_tab_2.png', dataGrid.element); + + // assert + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}, [900, 800]).before(async () => createWidget('dxDataGrid', { + ...defaultConfig, + width: 550, + customizeColumns(columns) { + columns[3].width = 125; + columns[3].fixed = true; + columns[3].fixedPosition = 'sticky'; + }, +})); diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_fixing/m_column_fixing.ts b/packages/devextreme/js/__internal/grids/grid_core/column_fixing/m_column_fixing.ts index 3a74b04f3d5d..ab80edcd5c5f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_fixing/m_column_fixing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_fixing/m_column_fixing.ts @@ -1025,8 +1025,10 @@ const rowsView = (Base: ModuleType) => class RowsViewFixedColumnsExten super._afterRowPrepared(e); } - public _scrollToElement($element) { - super._scrollToElement($element, this.getFixedColumnsOffset()); + public _scrollToElement($element, offset?) { + const scrollOffset = this.isFixedColumns() ? this.getFixedColumnsOffset() : offset; + + super._scrollToElement($element, scrollOffset); } }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 8bf7e91ca11f..8e93098c9c74 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -321,7 +321,6 @@ export class HeaderFilterController extends Modules.ViewController { // TODO getView const view = isGroupPanel ? this.getView('headerPanel') : this.getView('columnHeadersView'); const $columnElement = view.getColumnElements() - // @ts-expect-error .eq(isGroupPanel ? column.groupIndex : visibleIndex); this.showHeaderFilterMenuBase({ diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts index 4110c888507c..fdc8c2c4d26e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts @@ -37,8 +37,8 @@ const toggleColumnNoBorderClass = ($cell, value, addWidgetPrefix): void => { $cell.toggleClass(addWidgetPrefix(CLASSES.columnNoBorder), value); }; -const toggleStickyColumnsClass = ($element, isStickyColumns, addWidgetPrefix): void => { - $element.toggleClass(addWidgetPrefix(CLASSES.stickyColumns), isStickyColumns); +const toggleStickyColumnsClass = ($element, hasStickyColumns, addWidgetPrefix): void => { + $element.toggleClass(addWidgetPrefix(CLASSES.stickyColumns), hasStickyColumns); }; const isStickyCellPinnedToLeft = ( @@ -86,6 +86,13 @@ const isStickyCellPinned = ( ): boolean => isStickyCellPinnedToLeft($cell, $container, addWidgetPrefix) || isStickyCellPinnedToRight($cell, $container, addWidgetPrefix); +const isFixedCellPinnedToLeft = ( + $cell: dxElementWrapper, + $container: dxElementWrapper, + addWidgetPrefix, +): boolean => $cell.hasClass(addWidgetPrefix(CLASSES.stickyColumnLeft)) + || isStickyCellPinnedToLeft($cell, $container, addWidgetPrefix); + const isFixedCellPinnedToRight = ( $cell: dxElementWrapper, $container: dxElementWrapper, @@ -125,6 +132,22 @@ const getRightFixedCells = ($cells: dxElementWrapper, addWidgetPrefix): dxElemen // @ts-expect-error .filter((_, cell: HTMLElement) => $(cell).hasClass(addWidgetPrefix(CLASSES.stickyColumnRight))); +const getFixedCellsPinnedToLeft = ( + $cells: dxElementWrapper, + $container: dxElementWrapper, + addWidgetPrefix, +): dxElementWrapper => $cells + // @ts-expect-error + .filter((_, cell: HTMLElement) => isFixedCellPinnedToLeft($(cell), $container, addWidgetPrefix)); + +const getFixedCellsPinnedToRight = ( + $cells: dxElementWrapper, + $container: dxElementWrapper, + addWidgetPrefix, +): dxElementWrapper => $cells + // @ts-expect-error + .filter((_, cell: HTMLElement) => isFixedCellPinnedToRight($(cell), $container, addWidgetPrefix)); + const getNonFixedAndStickyCells = ( $cells: dxElementWrapper, addWidgetPrefix, @@ -281,6 +304,41 @@ const doesGroupCellEndInFirstColumn = ($groupCell): boolean => { return groupColSpanWithoutCommand === 1; }; +const getScrollPadding = ( + $cells: dxElementWrapper, + $container: dxElementWrapper, + addWidgetPrefix, +): { + left: number; + right: number; +} => { + const left = getFixedCellsPinnedToLeft($cells, $container, addWidgetPrefix) + .toArray() + .reduce((sum, cell) => sum + cell.getBoundingClientRect().width, 0); + const right = getFixedCellsPinnedToRight($cells, $container, addWidgetPrefix) + .toArray() + .reduce((sum, cell) => sum + cell.getBoundingClientRect().width, 0); + + return { + left, + right, + }; +}; + +const setScrollPadding = ( + $cells: dxElementWrapper, + $container: dxElementWrapper, + addWidgetPrefix, +): void => { + const scrollPadding = getScrollPadding($cells, $container, addWidgetPrefix); + + // @ts-expect-error + $container.css({ + scrollPaddingLeft: scrollPadding.left, + scrollPaddingRight: scrollPadding.right, + }); +}; + export const GridCoreStickyColumnsDom = { toggleFirstHeaderClass, toggleColumnNoBorderClass, @@ -292,10 +350,12 @@ export const GridCoreStickyColumnsDom = { getLeftFixedCells, getRightFixedCells, getNonFixedAndStickyCells, + getScrollPadding, noNeedToCreateResizingPoint, isFixedCellPinnedToRight, noNeedToCreateReorderingPoint, isFixedCell, isStickyCell, isStickyCellPinned, + setScrollPadding, }; 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 4a35211c611a..75f4aa6a8570 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 @@ -112,21 +112,15 @@ const baseStickyColumns = >(Base: T) => class }); } - protected _isStickyColumns(): boolean { - const stickyColumns = this._columnsController?.getStickyColumns(); - - return this.option('columnFixing.legacyMode') !== true && !!stickyColumns.length; - } - protected _renderCore(options?) { super._renderCore(options); const $element = this.element(); - const isStickyColumns = this._isStickyColumns(); + const hasStickyColumns = this.hasStickyColumns(); GridCoreStickyColumnsDom.toggleStickyColumnsClass( $element, - isStickyColumns, + hasStickyColumns, this.addWidgetPrefix.bind(this), ); } @@ -135,10 +129,10 @@ const baseStickyColumns = >(Base: T) => class const { column } = options; const { rowType } = options; const $cell = super._createCell(options); - const isStickyColumns = this._isStickyColumns(); + const hasStickyColumns = this.hasStickyColumns(); const rowIndex = rowType === 'header' ? options.rowIndex : null; - if (isStickyColumns) { + if (hasStickyColumns) { this.updateBorderCellClasses($cell, column, rowIndex); if (column.fixed) { @@ -210,25 +204,25 @@ const baseStickyColumns = >(Base: T) => class } protected setColumnWidths(options): void { - const isStickyColumns = this._isStickyColumns(); + const hasStickyColumns = this.hasStickyColumns(); const columnsResizerController = this.getController('columnsResizer'); const isColumnResizing = columnsResizerController?.isResizing(); super.setColumnWidths(options); - if (isStickyColumns && isColumnResizing) { + if (hasStickyColumns && isColumnResizing) { this.setStickyOffsets(); } } protected _resizeCore() { - const isStickyColumns = this._isStickyColumns(); + const hasStickyColumns = this.hasStickyColumns(); const adaptiveColumns = this.getController('adaptiveColumns'); const hidingColumnsQueue = adaptiveColumns?.getHidingColumnsQueue(); super._resizeCore.apply(this, arguments as any); - if (isStickyColumns) { + if (hasStickyColumns) { this.setStickyOffsets(); if (hidingColumnsQueue?.length) { @@ -236,6 +230,12 @@ const baseStickyColumns = >(Base: T) => class } } } + + public hasStickyColumns(): boolean { + const stickyColumns = this._columnsController?.getStickyColumns(); + + return this.option('columnFixing.legacyMode') !== true && !!stickyColumns.length; + } }; const columnHeadersView = ( @@ -252,6 +252,14 @@ const columnHeadersView = ( } } + protected _resizeCore() { + super._resizeCore(); + + if (this.hasStickyColumns()) { + this.updateScrollPadding(); + } + } + public getContextMenuItems(options) { const { column } = options; const columnFixingOptions: any = this.option('columnFixing'); @@ -333,6 +341,17 @@ const columnHeadersView = ( } return items; } + + public updateScrollPadding(): void { + const $scrollContainer = $(this.getContent()); + const $cells = $(this.getColumnElements()); + + GridCoreStickyColumnsDom.setScrollPadding( + $cells, + $scrollContainer, + this.addWidgetPrefix.bind(this), + ); + } }; const rowsView = ( @@ -348,7 +367,7 @@ const rowsView = ( // @ts-expect-error const $detailCell: dxElementWrapper = super._renderMasterDetailCell($row, row, options); - if (this._isStickyColumns()) { + if (this.hasStickyColumns()) { $detailCell .addClass(this.addWidgetPrefix(CLASSES.stickyColumnLeft)) // @ts-expect-error @@ -369,17 +388,17 @@ const rowsView = ( } protected _resizeCore() { - const isStickyColumns = this._isStickyColumns(); + const hasStickyColumns = this.hasStickyColumns(); super._resizeCore.apply(this, arguments as any); - if (isStickyColumns) { + if (hasStickyColumns) { this._updateMasterDetailWidths(); } } protected _renderCellContent($cell, options, renderOptions) { - if (!isGroupRow(options) || !this._isStickyColumns()) { + if (!isGroupRow(options) || !this.hasStickyColumns()) { return super._renderCellContent($cell, options, renderOptions); } @@ -405,24 +424,51 @@ const rowsView = ( } protected _handleScroll(e): void { - const editorFactoryController = this.getController('editorFactory'); - const $focusOverlay = editorFactoryController.getFocusOverlay(); + const hasStickyColumns = this.hasStickyColumns(); super._handleScroll(e); - if (!$focusOverlay?.hasClass(CLASSES.hidden) - && $focusOverlay?.hasClass(CLASSES.focusedFixedCell)) { - const $element = this.component.$element(); - // @ts-expect-error - const $focusedCell = $element.find(`.${CLASSES.focused}`); - const isStickyCell = GridCoreStickyColumnsDom - .isStickyCell($focusedCell, this.addWidgetPrefix.bind(this)); + if (hasStickyColumns) { + const editorFactoryController = this.getController('editorFactory'); + const $focusOverlay = editorFactoryController.getFocusOverlay(); + const hasFixedColumnsWithStickyPosition = !!this._columnsController + .getStickyColumns() + .filter((column) => column.fixedPosition === StickyPosition.Sticky).length; + + if (!$focusOverlay?.hasClass(CLASSES.hidden) + && $focusOverlay?.hasClass(CLASSES.focusedFixedCell)) { + const $element = this.component.$element(); + // @ts-expect-error + const $focusedCell = $element.find(`.${CLASSES.focused}`); + const isStickyCell = GridCoreStickyColumnsDom + .isStickyCell($focusedCell, this.addWidgetPrefix.bind(this)); + + if (isStickyCell) { + editorFactoryController.updateFocusOverlay($focusedCell); + } + } - if (isStickyCell) { - editorFactoryController.updateFocusOverlay($focusedCell); + if (hasFixedColumnsWithStickyPosition) { + // @ts-expect-error + this._columnHeadersView?.updateScrollPadding(); } } } + + public _scrollToElement($element, offset?) { + let scrollOffset = offset; + const hasStickyColumns = this.hasStickyColumns(); + const $row = $element?.closest('tr'); + const $cells = $row?.children(); + const $scrollContainer = this.getScrollable()?.container(); + + if (hasStickyColumns && $cells.length) { + scrollOffset = GridCoreStickyColumnsDom + .getScrollPadding($cells, $scrollContainer, this.addWidgetPrefix.bind(this)); + } + + super._scrollToElement($element, scrollOffset); + } }; const footerView = ( @@ -475,16 +521,16 @@ const columnsResizer = (Base: ModuleType) => class protected _generatePointsByColumns(): void { // @ts-expect-error - const isStickyColumns = this._columnHeadersView?._isStickyColumns(); + const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); - super._generatePointsByColumns(isStickyColumns); + super._generatePointsByColumns(hasStickyColumns); } protected _pointCreated(point, cellsLength, columns) { // @ts-expect-error - const isStickyColumns = this._columnHeadersView?._isStickyColumns(); + const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); const result = super._pointCreated(point, cellsLength, columns); - const needToCheckPoint = isStickyColumns && cellsLength > 0; + const needToCheckPoint = hasStickyColumns && cellsLength > 0; if (needToCheckPoint && !result) { const column = columns[point.index - 1]; @@ -509,10 +555,10 @@ const columnsResizer = (Base: ModuleType) => class const draggingHeader = (Base: ModuleType) => class DraggingHeaderStickyColumnsExtender extends Base { public _generatePointsByColumns(options): any[] { // @ts-expect-error - const isStickyColumns = this._columnHeadersView?._isStickyColumns(); + const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); const { sourceLocation, sourceColumn } = options; - if (isStickyColumns && sourceLocation === 'headers') { + if (hasStickyColumns && sourceLocation === 'headers') { const columnFixedPosition = getColumnFixedPosition(this._columnsController, sourceColumn); switch (true) { @@ -539,14 +585,14 @@ const draggingHeader = (Base: ModuleType) => class } } - return super._generatePointsByColumns(options, isStickyColumns); + return super._generatePointsByColumns(options, hasStickyColumns); } protected _pointCreated(point, columns, location, sourceColumn) { // @ts-expect-error - const isStickyColumns = this._columnHeadersView._isStickyColumns(); + const hasStickyColumns = this._columnHeadersView.hasStickyColumns(); const $cells = this._columnHeadersView.getColumnElements(); - const needToCheckPoint = isStickyColumns && location === 'headers' && $cells?.length + const needToCheckPoint = hasStickyColumns && location === 'headers' && $cells?.length && (!sourceColumn.fixed || sourceColumn.fixedPosition === StickyPosition.Sticky); const result = super._pointCreated(point, columns, location, sourceColumn); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts index 4b000964b921..b6d30b655fea 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts @@ -1376,7 +1376,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return columnIdentifier; } - public getColumnElements() {} + public getColumnElements(): any {} public getColumns(rowIndex?, $tableElement?) { return this._columnsController.getVisibleColumns(rowIndex); diff --git a/packages/testcafe-models/dataGrid/headers/index.ts b/packages/testcafe-models/dataGrid/headers/index.ts index 1ba04b4567b0..2de2295fccf7 100644 --- a/packages/testcafe-models/dataGrid/headers/index.ts +++ b/packages/testcafe-models/dataGrid/headers/index.ts @@ -18,6 +18,10 @@ export default class Headers extends FocusableElement { this.widgetName = widgetName; } + getContent(): Selector { + return this.element.find(`.${Widget.addClassPrefix(this.widgetName, CLASS.content)}:not(.${Widget.addClassPrefix(this.widgetName, CLASS.contentFixed)})`); + } + getHeaderRow(index: number): HeaderRow { return new HeaderRow(this.element.find(`.${Widget.addClassPrefix(this.widgetName, CLASS.content)}:not(.${Widget.addClassPrefix(this.widgetName, CLASS.contentFixed)}) .${CLASS.headerRow}:nth-child(${index + 1})`), this.widgetName); } diff --git a/packages/testcafe-models/dataGrid/index.ts b/packages/testcafe-models/dataGrid/index.ts index 6a5ccef63a33..2cb37d5a8e6f 100644 --- a/packages/testcafe-models/dataGrid/index.ts +++ b/packages/testcafe-models/dataGrid/index.ts @@ -67,6 +67,7 @@ export const CLASS = { commandDrag: 'dx-command-drag', dialogWrapper: 'dx-dialog-wrapper', summaryTotal: 'dx-datagrid-summary-item', + scrollableContainer: 'dx-scrollable-container', }; const E2E_ATTRIBUTES = { @@ -156,6 +157,10 @@ export default class DataGrid extends Widget { return this.element.find(`.${this.addWidgetPrefix(CLASS.rowsView)}`); } + getScrollContainer(): Selector { + return this.getRowsView().find(`.${CLASS.scrollableContainer}`); + } + getDataRow(index: number): DataRow { return new DataRow(this.element.find(`.${CLASS.dataRow}[aria-rowindex='${index + 1}']`), this.getName()); }