diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/dom_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/dom_utils.ts new file mode 100644 index 000000000000..e70dd3138640 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/dom_utils.ts @@ -0,0 +1,19 @@ +export const simulateTextOverflow = ( + element: HTMLElement, + scrollWidth: number, + clientWidth: number, +): void => { + // Mock scrollWidth > clientWidth to simulate text overflow + Object.defineProperty(element, 'scrollWidth', { value: scrollWidth, configurable: true }); + Object.defineProperty(element, 'clientWidth', { value: clientWidth, configurable: true }); +}; + +export const simulateHoverEvent = ( + element: HTMLElement, + options?: MouseEventInit, +): void => { + element.dispatchEvent(new MouseEvent('mousemove', options ?? { + bubbles: true, + cancelable: true, + })); +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts index 808eb1078b64..42e87ba55f16 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts @@ -3,6 +3,10 @@ import { } from '@jest/globals'; import $ from '@js/core/renderer'; +import { + simulateHoverEvent, + simulateTextOverflow, +} from '../../__tests__/__mock__/helpers/dom_utils'; import { afterTest, beforeTest, @@ -36,4 +40,61 @@ describe('Column Headers', () => { expect(component.getHeaderCell(0).getAlignment()).toBe('right'); }); }); + + describe('when cellHintEnabled: true', () => { + it('should show column caption in the tooltip instead of sort index when hovering sort indicator (T1321834)', async () => { + const { component } = await createDataGrid({ + dataSource: [{ id: 1, Position: 'Developer', Name: 'John' }], + columns: [ + { + dataField: 'Position', + caption: 'Position', + width: 30, + sortOrder: 'asc', + sortIndex: 0, + }, + { + dataField: 'Name', + caption: 'Name', + sortOrder: 'desc', + sortIndex: 1, + }, + ], + sorting: { + mode: 'multiple', + showSortIndexes: true, + }, + cellHintEnabled: true, + }); + + const headerCellElement = component.getHeaderCells()[0]; + const headerContentElement = component.getHeaderCell(0).getHeaderContent() as HTMLElement; + + simulateTextOverflow(headerContentElement, 50, 20); + simulateHoverEvent(headerCellElement); + + expect($(headerCellElement).attr('title')).toBe('Position'); + }); + + it('should show cell text in the tooltip for non-header rows', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ id: 1, Position: 'Very Long Position Name That Should Be Truncated' }], + columns: [ + { + dataField: 'Position', + caption: 'Position', + width: 50, + }, + ], + cellHintEnabled: true, + }); + + const dataCell = instance.getCellElement(0, 0) as HTMLElement; + + simulateTextOverflow(dataCell, 200, 50); + simulateHoverEvent(dataCell); + + expect($(dataCell).attr('title')).toBe(dataCell.textContent); + }); + }); }); 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 3866c6dcdf8b..de48318c3aea 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 @@ -43,6 +43,7 @@ const SCROLL_CONTAINER_CLASS = 'scroll-container'; const SCROLLABLE_SIMULATED_CLASS = 'scrollable-simulated'; const GROUP_SPACE_CLASS = 'group-space'; const CONTENT_CLASS = 'content'; +const HEADER_TEXT_CONTENT_CLASS = 'text-content'; const TABLE_CLASS = 'table'; const TABLE_FIXED_CLASS = 'table-fixed'; const CONTENT_FIXED_CLASS = 'content-fixed'; @@ -426,16 +427,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { && !isGroupCellWithTemplate; if (shouldShowHint) { - if ($element.data(CELL_HINT_VISIBLE)) { - $element.removeAttr('title'); - $element.data(CELL_HINT_VISIBLE, false); - } - - const difference = $element[0].scrollWidth - $element[0].clientWidth; - if (difference > 0 && !isDefined($element.attr('title'))) { - $element.attr('title', $element.text()); - $element.data(CELL_HINT_VISIBLE, true); - } + this._setCellTitleAttribute($element, isHeaderRow); } })); } @@ -493,6 +485,32 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return $table; } + private _setCellTitleAttribute($cell: dxElementWrapper, isHeaderRow: boolean): void { + if ($cell.data(CELL_HINT_VISIBLE)) { + $cell.removeAttr('title'); + $cell.data(CELL_HINT_VISIBLE, false); + } + + let $cellContent = $cell; + const headerContentClass = this.addWidgetPrefix(HEADER_TEXT_CONTENT_CLASS); + + if (isHeaderRow && !$cell.hasClass(headerContentClass)) { + const $headerContent = $cell.find(`.${headerContentClass}`); + $cellContent = $headerContent.length ? $headerContent : $cellContent; + } + + const hasWidthOverflow = $cellContent[0].scrollWidth - $cellContent[0].clientWidth > 0; + + if (!hasWidthOverflow || isDefined($cell.attr('title'))) { + return; + } + + const hintText = $cellContent.text() || $cell.text(); + + $cell.attr('title', hintText); + $cell.data(CELL_HINT_VISIBLE, true); + } + /** * @extended: editing */