diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border (fluent.blue.light).png new file mode 100644 index 000000000000..8dbf4f4afbb5 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border-rtl (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border-rtl (fluent.blue.light).png new file mode 100644 index 000000000000..c03ee656aa26 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/grid-headers-scroller-spacing-border-rtl (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts index 70d87706ce3b..a10306b09033 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts @@ -3,7 +3,7 @@ import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; import DataGrid from 'devextreme-testcafe-models/dataGrid'; import { ClassNames } from 'devextreme-testcafe-models/dataGrid/classNames'; import type { DataGridScrollMode } from 'devextreme/ui/data_grid'; -import { insertStylesheetRulesToPage } from '../../../helpers/domUtils'; +import { insertStylesheetRulesToPage, removeStylesheetRulesFromPage } from '../../../helpers/domUtils'; import url from '../../../helpers/getPageUrl'; import { createWidget } from '../../../helpers/createWidget'; import { salesApiMock } from './apiMocks/salesApiMock'; @@ -363,6 +363,69 @@ test('Scroll position after grouping when RTL (T388508)', async (t) => { }], })); +[false, true].forEach((rtlEnabled) => { + test(`Headers should have a border before the scrollbar gutter when the vertical scrollbar occupies space (rtlEnabled = ${rtlEnabled}) (T1306973)`, async (t) => { + // arrange, act + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid('#container'); + + await t + .expect(dataGrid.isReady()) + .ok(); + + const headersElement = dataGrid.getHeaders().element; + const paddingProperty = rtlEnabled ? 'padding-left' : 'padding-right'; + const padding = parseFloat(await headersElement.getStyleProperty(paddingProperty)); + + // assert + await t + .expect(headersElement.hasClass('dx-datagrid-scroller-spacing')) + .ok() + .expect(padding) + .eql(14); + + await testScreenshot( + t, + takeScreenshot, + `grid-headers-scroller-spacing-border${rtlEnabled ? '-rtl' : ''}.png`, + { element: '#container' }, + ); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); + }).before(async () => { + await insertStylesheetRulesToPage(` + ::-webkit-scrollbar { -webkit-appearance: none; width: 14px; height: 14px; } + ::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, .5); border-radius: 7px; } + ::-webkit-scrollbar-track { background-color: #fafafa; border-${rtlEnabled ? 'right' : 'left'}: 1px solid #e0e0e0; } + `); + + return createWidget('dxDataGrid', { + rtlEnabled, + width: 700, + height: 300, + showBorders: true, + dataSource: getData(30, 5), + columns: ['field_0', 'field_1', 'field_2', 'field_3', 'field_4'], + filterRow: { + visible: true, + }, + summary: { + totalItems: [{ + column: 'field_0', + summaryType: 'count', + }], + }, + scrolling: { + useNative: true, + }, + }); + }).after(async () => { + await removeStylesheetRulesFromPage(); + }); +}); + test('Header container should have padding-right after expanding the master row with a detail grid when using native scrolling (T1004507)', async (t) => { const dataGrid = new DataGrid('#container'); const { takeScreenshot, compareResults } = createScreenshotsComparer(t); diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss index 346f4c8517f1..7df1857ae828 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss @@ -628,6 +628,12 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig } } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: 1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-filter-row { background-color: $datagrid-filter-row-background-color; @@ -976,6 +982,12 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig } } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: -1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-rowsview { &.dx-scrollable-scrollbars-alwaysvisible.dx-scrollable-both .dx-scrollable-wrapper .dx-scrollable-container .dx-scrollable-content { padding-left: 0; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss index 394c65320cfe..254a10000e2b 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss @@ -442,6 +442,12 @@ $generic-grid-base-cell-input-height: math.round($generic-base-line-height * $ge } } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: 1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-filter-row { .dx-menu { .dx-overlay-content { @@ -837,6 +843,12 @@ $generic-grid-base-cell-input-height: math.round($generic-base-line-height * $ge } } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: -1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-filter-row { .dx-menu { .dx-menu-item-has-submenu.dx-menu-item-has-icon { diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss index d8c67b2d36b5..0476ff537b8b 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss @@ -602,6 +602,12 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line- } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: 1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-filter-row { background-color: $datagrid-filter-row-background-color; @@ -952,6 +958,12 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line- } } + .dx-#{$widget-name}-headers.dx-#{$widget-name}-scroller-spacing { + > .dx-#{$widget-name}-scroll-container:not(.dx-#{$widget-name}-content-fixed) { + box-shadow: -1px 0 0 $datagrid-border-color; + } + } + .dx-#{$widget-name}-rowsview { &.dx-scrollable-scrollbars-alwaysvisible.dx-scrollable-both .dx-scrollable-wrapper .dx-scrollable-container .dx-scrollable-content { padding-left: 0; diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts index c093dfb2b7d9..89ce21628aef 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts @@ -5,10 +5,11 @@ import $ from '@js/core/renderer'; import type { Properties as DataGridProperties } from '@js/ui/data_grid'; import DataGrid from '@js/ui/data_grid'; import { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid'; -import type { Controllers } from '@ts/grids/grid_core/m_types'; +import type { Controllers, Views } from '@ts/grids/grid_core/m_types'; export interface DataGridInstance extends DataGrid { getController: (name: T) => Controllers[T]; + getView: (name: T) => Views[T]; } export const SELECTORS = { diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.integration.test.ts new file mode 100644 index 000000000000..a9046fe27ade --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.integration.test.ts @@ -0,0 +1,55 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, beforeTest, createDataGrid, +} from '../../__tests__/__mock__/helpers/utils'; + +const SCROLLER_SPACING_CLASS = 'dx-datagrid-scroller-spacing'; + +describe('ColumnsView scroller spacing (T1306973)', () => { + beforeEach(() => { + beforeTest(); + }); + + afterEach(() => { + afterTest(); + }); + + it('should toggle the scroller spacing class on the headers view', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ id: 1, a: 'a', b: 'b' }], + columns: ['a', 'b'], + }); + const columnHeadersView = instance.getView('columnHeadersView'); + + columnHeadersView.setScrollerSpacing(15); + + expect(columnHeadersView.element().hasClass(SCROLLER_SPACING_CLASS)).toBe(true); + expect(columnHeadersView.element().css('paddingInlineEnd')).toBe('15px'); + + columnHeadersView.setScrollerSpacing(0); + + expect(columnHeadersView.element().hasClass(SCROLLER_SPACING_CLASS)).toBe(false); + }); + + it('should toggle the scroller spacing class on the footer view', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ id: 1, a: 'a', b: 'b' }], + columns: ['a', 'b'], + summary: { + totalItems: [{ column: 'a', summaryType: 'count' }], + }, + }); + const footerView = instance.getView('footerView'); + + footerView.setScrollerSpacing(15); + + expect(footerView.element().hasClass(SCROLLER_SPACING_CLASS)).toBe(true); + + footerView.setScrollerSpacing(0); + + expect(footerView.element().hasClass(SCROLLER_SPACING_CLASS)).toBe(false); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/const.ts b/packages/devextreme/js/__internal/grids/grid_core/views/const.ts index 72b3814bcbb9..0c2cb015ed6d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/const.ts @@ -1,3 +1,4 @@ export const CLASSES = { firstCell: 'first-cell', + scrollerSpacing: 'scroller-spacing', }; 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 813f4c751952..fb8d80e4c619 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 @@ -1486,15 +1486,12 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing */ - public setScrollerSpacing(width) { - const that = this; - const $element = that.element(); - const rtlEnabled = that.option('rtlEnabled'); + public setScrollerSpacing(width: number): void { + const $element = this.element(); - $element && $element.css({ - paddingLeft: rtlEnabled ? width : '', - paddingRight: !rtlEnabled ? width : '', - }); + $element + ?.toggleClass(this.addWidgetPrefix(CLASSES.scrollerSpacing), !!width) + .css('paddingInlineEnd', width ? `${width}px` : ''); } protected isScrollbarVisible(isHorizontal) {