From 1180109e2a6a4c514cac5e8367c854c4801a34b7 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 18:11:40 +0400 Subject: [PATCH 1/8] Grids: add sticky columns types --- .../grids/grid_core/sticky_columns/types.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/sticky_columns/types.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/types.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/types.ts new file mode 100644 index 000000000000..74436d73bb8c --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/types.ts @@ -0,0 +1,15 @@ +import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; +import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; + +export type StickyOffsets = Record>; + +export interface StickyColumnsColumnsViewExtension { + hasStickyColumns: () => boolean; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + setStickyOffsets(rowIndex?: number, offsets?: StickyOffsets): void; +} + +export type StickyColumnHeadersView = ColumnHeadersView & StickyColumnsColumnsViewExtension; + +export type StickyRowsView = RowsView & StickyColumnsColumnsViewExtension; From 503d04d8406a0970216705781222c494e7cb6be5 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 18:46:15 +0400 Subject: [PATCH 2/8] Grids: type sticky columns base extender --- .../sticky_columns/m_sticky_columns.ts | 120 ++++++++++-------- .../grids/grid_core/sticky_columns/utils.ts | 12 +- 2 files changed, 72 insertions(+), 60 deletions(-) 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 172187eaf827..5a64595d5d99 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 @@ -29,9 +29,10 @@ import gridCoreUtils from '../utils'; import type { ColumnsView } from '../views/columns_view'; import type { RowsView } from '../views/rows_view'; import { isGroupRow } from '../views/rows_view'; -import type { RowsViewScrollEvent } from '../views/types'; +import type { ColumnWidthsOptions, RowsViewScrollEvent, ViewCellOptions } from '../views/types'; import { CLASSES, StickyPosition } from './const'; import { GridCoreStickyColumnsDom } from './dom'; +import type { StickyColumnsColumnsViewExtension, StickyOffsets } from './types'; import { getColumnFixedPosition, getStickyOffset, @@ -44,11 +45,15 @@ import { processFixedColumns, } from './utils'; -const baseStickyColumns = >(Base: T) => class BaseStickyColumnsExtender extends Base { +const baseStickyColumns = >( + Base: T, +): T & ModuleType< + ColumnsView & StickyColumnsColumnsViewExtension +> => class BaseStickyColumnsExtender extends Base { private _addStickyColumnBorderLeftClass( $cell: dxElementWrapper, - column, - rowIndex: number, + column: Column, + rowIndex: number | null, onlyWithinBandColumn = false, fixedPosition?: StickyPosition, ): void { @@ -68,8 +73,8 @@ const baseStickyColumns = >(Base: T) => class private _addStickyColumnBorderRightClass( $cell: dxElementWrapper, - column, - rowIndex: number, + column: Column, + rowIndex: number | null, onlyWithinBandColumn = false, fixedPosition?: StickyPosition, ): void { @@ -147,64 +152,70 @@ const baseStickyColumns = >(Base: T) => class return deferred; } - protected _createCell(options) { + protected _createCell(options: ViewCellOptions): dxElementWrapper { const { column } = options; const { rowType } = options; const $cell = super._createCell(options); const hasStickyColumns = this.hasStickyColumns(); - const rowIndex = rowType === 'header' ? options.rowIndex : null; + const rowIndex = rowType === 'header' ? options.rowIndex ?? null : null; const isSummary = rowType === 'groupFooter' || rowType === 'totalFooter' || rowType === 'group'; const isExpandColumn = column.command && column.command === 'expand'; - if (hasStickyColumns && !needToDisableStickyColumn(this._columnsController, column)) { - this.toggleColumnNoBorderClass($cell, column, rowIndex); + if (!hasStickyColumns || needToDisableStickyColumn(this._columnsController, column)) { + return $cell; + } - if (column.fixed) { - const fixedPosition = getColumnFixedPosition(this._columnsController, column); + this.toggleColumnNoBorderClass($cell, column, rowIndex); + + if (!column.fixed) { + return $cell; + } + + const fixedPosition = getColumnFixedPosition(this._columnsController, column); + + GridCoreStickyColumnsDom.addStickyColumnClass( + $cell, + fixedPosition, + this.addWidgetPrefix.bind(this), + ); - GridCoreStickyColumnsDom.addStickyColumnClass( + if (isSummary || isExpandColumn) { + return $cell; + } + + switch (fixedPosition) { + case StickyPosition.Right: { + this._addStickyColumnBorderLeftClass( $cell, - fixedPosition, - this.addWidgetPrefix.bind(this), + column, + rowIndex, + false, + StickyPosition.Right, + ); + break; + } + case StickyPosition.Sticky: { + this._addStickyColumnBorderLeftClass($cell, column, rowIndex, true); + this._addStickyColumnBorderRightClass($cell, column, rowIndex, true); + break; + } + default: { + this._addStickyColumnBorderRightClass( + $cell, + column, + rowIndex, + false, + StickyPosition.Left, ); - - if (!isSummary && !isExpandColumn) { - switch (fixedPosition) { - case StickyPosition.Right: { - this._addStickyColumnBorderLeftClass( - $cell, - column, - rowIndex, - false, - StickyPosition.Right, - ); - break; - } - case StickyPosition.Sticky: { - this._addStickyColumnBorderLeftClass($cell, column, rowIndex, true); - this._addStickyColumnBorderRightClass($cell, column, rowIndex, true); - break; - } - default: { - this._addStickyColumnBorderRightClass( - $cell, - column, - rowIndex, - false, - StickyPosition.Left, - ); - } - } - } } } return $cell; } - protected setStickyOffsets(rowIndex?: number, offsets?: Record>): void { + public setStickyOffsets(rowIndex?: number, offsets?: StickyOffsets): void { const columnsController = this._columnsController; - const rtlEnabled = this.option('rtlEnabled') as boolean; + const rtlEnabled = this.option('rtlEnabled'); const showColumnHeaders = this.option('showColumnHeaders'); let widths = this.getColumnWidths(undefined, rowIndex); let columns = this.getColumns(showColumnHeaders ? rowIndex : undefined); @@ -232,7 +243,7 @@ const baseStickyColumns = >(Base: T) => class }); } - protected setColumnWidths(options): void { + protected setColumnWidths(options: ColumnWidthsOptions): void { const hasStickyColumns = this.hasStickyColumns(); const columnsResizerController = this.getController('columnsResizer'); const isColumnResizing = columnsResizerController?.isResizing(); @@ -262,7 +273,8 @@ const baseStickyColumns = >(Base: T) => class } } - // TODO: Need to rename this method to hasFixedColumns after removing old fixed columns implementation + // TODO: Need to rename this method to hasFixedColumns + // after removing old fixed columns implementation public hasStickyColumns(): boolean { const stickyColumns = this._columnsController?.getStickyColumns(); @@ -273,8 +285,8 @@ const baseStickyColumns = >(Base: T) => class const columnHeadersView = ( Base: ModuleType, ) => class ColumnHeadersViewStickyColumnsExtender extends baseStickyColumns(Base) { - protected setStickyOffsets() { - const offsets: Record> = {}; + public setStickyOffsets(): void { + const offsets: StickyOffsets = {}; const rows = this._getRows(); for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { @@ -406,7 +418,7 @@ const rowsView = ( ); } - protected setStickyOffsets(rowIndex?: number, offsets?: Record>) { + public setStickyOffsets(rowIndex?: number, offsets?: StickyOffsets): void { super.setStickyOffsets(rowIndex, offsets); this.setStickyOffsetsForGroupCells(); } @@ -528,9 +540,9 @@ const rowsView = ( } }; -const footerView = ( - Base: ModuleType, -) => class FooterViewStickyColumnsExtender extends baseStickyColumns(Base) {}; +const footerView = >( + Base: T, +): T => class FooterViewStickyColumnsExtender extends baseStickyColumns(Base) {}; const columnsResizer = (Base: ModuleType) => class ColumnResizerStickyColumnsExtender extends Base { private isNextCellPinned($nextCell: dxElementWrapper, $container: dxElementWrapper): boolean { diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts index c344289d88d2..c2acc5d35758 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts @@ -4,6 +4,7 @@ import gridCoreUtils from '@ts/grids/grid_core/utils'; import { HIDDEN_COLUMNS_WIDTH } from '../adaptivity/const'; import type { ColumnsController } from '../columns_controller/columns_controller'; import { STICKY_BORDER_WIDTH, StickyPosition } from './const'; +import type { StickyOffsets } from './types'; export const getColumnFixedPosition = ( that: ColumnsController, @@ -69,7 +70,7 @@ const getStickyOffsetCore = function ( widths: number[], columnIndex: number, fixedPosition: StickyPosition, - offsets?: Record>, + offsets?: StickyOffsets, ): number { const column = columns[columnIndex]; const isChildColumn = isDefined(column.ownerBand); @@ -115,9 +116,8 @@ const getStickyOffsetCore = function ( const isFirstOrLastColumn = function ( that: ColumnsController, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types column, - rowIndex: number, + rowIndex: number | null, onlyWithinBandColumn = false, isLast = false, fixedPosition?: StickyPosition, @@ -156,7 +156,7 @@ export const getStickyOffset = function ( columns: any[], widths: number[], columnIndex: number, - offsets?: Record>, + offsets?: StickyOffsets, ): Record { const result: Record = {}; const column = columns[columnIndex]; @@ -274,7 +274,7 @@ export const isFirstFixedColumn = function ( that: ColumnsController, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types column, - rowIndex: number, + rowIndex: number | null, onlyWithinBandColumn = false, fixedPosition?: StickyPosition, ): boolean { @@ -285,7 +285,7 @@ export const isLastFixedColumn = function ( that: ColumnsController, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types column, - rowIndex: number, + rowIndex: number | null, onlyWithinBandColumn = false, fixedPosition?: StickyPosition, ): boolean { From 53d6bdeb72ff2b2c21ebb5ea6641864667a4b93e Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 19:07:10 +0400 Subject: [PATCH 3/8] Grids: type sticky columns headers view --- .../sticky_columns/m_sticky_columns.ts | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) 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 5a64595d5d99..221df006dfd1 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 @@ -4,6 +4,7 @@ import $ from '@js/core/renderer'; import type { DeferredObj } from '@js/core/utils/deferred'; import { getBoundingRect } from '@js/core/utils/position'; import { getWidth, setWidth } from '@js/core/utils/size'; +import type { ItemClickEvent } from '@js/ui/context_menu'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { ResizingController } from '@ts/grids/grid_core/grid_view/grid_view'; @@ -29,10 +30,20 @@ import gridCoreUtils from '../utils'; import type { ColumnsView } from '../views/columns_view'; import type { RowsView } from '../views/rows_view'; import { isGroupRow } from '../views/rows_view'; -import type { ColumnWidthsOptions, RowsViewScrollEvent, ViewCellOptions } from '../views/types'; +import type { + ColumnWidthsOptions, + ContextMenuItemsOptions, + GridContextMenuItem, + RowsViewScrollEvent, + ViewCellOptions, +} from '../views/types'; import { CLASSES, StickyPosition } from './const'; import { GridCoreStickyColumnsDom } from './dom'; -import type { StickyColumnsColumnsViewExtension, StickyOffsets } from './types'; +import type { + StickyColumnHeadersView, + StickyColumnsColumnsViewExtension, + StickyOffsets, +} from './types'; import { getColumnFixedPosition, getStickyOffset, @@ -284,27 +295,30 @@ const baseStickyColumns = >( const columnHeadersView = ( Base: ModuleType, -) => class ColumnHeadersViewStickyColumnsExtender extends baseStickyColumns(Base) { +): ModuleType => class ColumnHeadersViewStickyColumnsExtender + extends baseStickyColumns(Base) { public setStickyOffsets(): void { const offsets: StickyOffsets = {}; const rows = this._getRows(); - for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { + for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) { const isFilterRow = rows?.[rowIndex]?.rowType === 'filter'; super.setStickyOffsets(rowIndex, isFilterRow ? undefined : offsets); } } - public getContextMenuItems(options) { + public getContextMenuItems( + options: ContextMenuItemsOptions, + ): GridContextMenuItem[] | undefined { const { column } = options; const columnsController = this._columnsController; - const columnFixingOptions: any = this.option('columnFixing'); - let items: any = super.getContextMenuItems(options); + const columnFixingOptions = this.option('columnFixing'); + let items = super.getContextMenuItems(options); if (options.row && options.row.rowType === 'header') { - if (columnFixingOptions.enabled === true && column && column.allowFixing) { - const onItemClick = (params) => { + if (columnFixingOptions?.enabled === true && column && column.allowFixing) { + const onItemClick = (params: ItemClickEvent): void => { // eslint-disable-next-line default-case switch (params.itemData.value) { case 'none': @@ -330,17 +344,17 @@ const columnHeadersView = ( break; } }; - const fixedPositionItems = [ + const fixedPositionItems: GridContextMenuItem[] = [ { - text: columnFixingOptions.texts.leftPosition, - icon: columnFixingOptions.icons.leftPosition, + text: columnFixingOptions.texts?.leftPosition, + icon: columnFixingOptions.icons?.leftPosition, value: 'left', disabled: column.fixed && (!column.fixedPosition || column.fixedPosition === 'left'), onItemClick, }, { - text: columnFixingOptions.texts.rightPosition, - icon: columnFixingOptions.icons.rightPosition, + text: columnFixingOptions.texts?.rightPosition, + icon: columnFixingOptions.icons?.rightPosition, value: 'right', disabled: column.fixed && column.fixedPosition === 'right', onItemClick, @@ -349,8 +363,8 @@ const columnHeadersView = ( if (this.option('columnFixing.legacyMode') !== true && !columnsController.isVirtualMode()) { fixedPositionItems.push({ - text: columnFixingOptions.texts.stickyPosition, - icon: columnFixingOptions.icons.stickyPosition, + text: columnFixingOptions.texts?.stickyPosition, + icon: columnFixingOptions.icons?.stickyPosition, value: 'sticky', disabled: column.fixed && column.fixedPosition === StickyPosition.Sticky, onItemClick, @@ -360,14 +374,14 @@ const columnHeadersView = ( items = items || []; items.push( { - text: columnFixingOptions.texts.fix, - icon: columnFixingOptions.icons.fix, + text: columnFixingOptions.texts?.fix, + icon: columnFixingOptions.icons?.fix, beginGroup: true, items: fixedPositionItems, }, { - text: columnFixingOptions.texts.unfix, - icon: columnFixingOptions.icons.unfix, + text: columnFixingOptions.texts?.unfix, + icon: columnFixingOptions.icons?.unfix, value: 'none', disabled: !column.fixed, onItemClick, From 6f1e0ac177d589271dcb8e89a229fc9c15964e9e Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 19:16:20 +0400 Subject: [PATCH 4/8] Grids: type sticky columns rows view --- .../sticky_columns/m_sticky_columns.ts | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) 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 221df006dfd1..d9bd0ac3bdcf 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 @@ -9,6 +9,7 @@ import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { ResizingController } from '@ts/grids/grid_core/grid_view/grid_view'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; +import type { ElementOffset } from '@ts/ui/scroll_view/types'; import { HIDDEN_COLUMNS_WIDTH } from '../adaptivity/const'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; @@ -31,11 +32,14 @@ import type { ColumnsView } from '../views/columns_view'; import type { RowsView } from '../views/rows_view'; import { isGroupRow } from '../views/rows_view'; import type { + CellRenderOptions, ColumnWidthsOptions, ContextMenuItemsOptions, GridContextMenuItem, + RowRenderOptions, RowsViewScrollEvent, ViewCellOptions, + ViewRow, } from '../views/types'; import { CLASSES, StickyPosition } from './const'; import { GridCoreStickyColumnsDom } from './dom'; @@ -43,6 +47,7 @@ import type { StickyColumnHeadersView, StickyColumnsColumnsViewExtension, StickyOffsets, + StickyRowsView, } from './types'; import { getColumnFixedPosition, @@ -395,7 +400,8 @@ const columnHeadersView = ( const rowsView = ( Base: ModuleType, -) => class RowsViewStickyColumnsExtender extends baseStickyColumns(Base) { +): ModuleType => class RowsViewStickyColumnsExtender + extends baseStickyColumns(Base) { private _getMasterDetailWidth(): number { const componentWidth = getWidth(this.component.$element().get(0)) ?? 0; const borderWidth = gridCoreUtils.getComponentBorderWidth(this, this._$element); @@ -403,7 +409,11 @@ const rowsView = ( return componentWidth - borderWidth - this.getScrollbarWidth(); } - protected _renderMasterDetailCell($row, row, options): dxElementWrapper { + protected _renderMasterDetailCell( + $row: dxElementWrapper, + row: ViewRow, + options: RowRenderOptions, + ): dxElementWrapper { // @ts-expect-error const $detailCell: dxElementWrapper = super._renderMasterDetailCell($row, row, options); @@ -419,7 +429,7 @@ const rowsView = ( return $detailCell; } - private _updateMasterDetailWidths() { + private _updateMasterDetailWidths(): void { const $detailContainers = this._getRowElements() .children(`.${MASTER_DETAIL_CLASSES.detailCell}`) .children(`.${this.addWidgetPrefix(MASTER_DETAIL_CLASSES.detailContainer)}`); @@ -437,7 +447,7 @@ const rowsView = ( this.setStickyOffsetsForGroupCells(); } - private setStickyOffsetsForGroupCells() { + private setStickyOffsetsForGroupCells(): void { const groupColumns = this._columnsController.getGroupColumns(); let columns = this.getColumns(); let widths = this.getColumnWidths(); @@ -450,7 +460,8 @@ const rowsView = ( widths = rtlEnabled ? [...widths].reverse() : widths; } - const $tableElement = this.getTableElement()!; + // @ts-expect-error the view is rendered here + const $tableElement: dxElementWrapper = this.getTableElement(); groupColumns.forEach((column) => { const columnIndex = columnsCountBeforeGroups + column.groupIndex + 1; @@ -471,17 +482,21 @@ const rowsView = ( }); } - protected _resizeCore() { + protected _resizeCore(): void { const hasStickyColumns = this.hasStickyColumns(); - super._resizeCore.apply(this, arguments as any); + super._resizeCore(); if (hasStickyColumns) { this._updateMasterDetailWidths(); } } - protected _renderCellContent($cell, options, renderOptions) { + protected _renderCellContent( + $cell: dxElementWrapper, + options: ViewCellOptions, + renderOptions: CellRenderOptions, + ): void { const hasStickyColumns = this.hasStickyColumns(); const isGroupRowResult = isGroupRow(options); const isDetailRowResult = isDetailRow(options); @@ -499,9 +514,19 @@ const rowsView = ( return super._renderCellContent($container, options, renderOptions); } - protected _renderGroupSummaryCellsCore($groupCell, options, groupCellColSpan, alignByColumnCellCount) { + protected _renderGroupSummaryCellsCore( + $groupCell: dxElementWrapper, + options: RowRenderOptions, + groupCellColSpan: number, + alignByColumnCellCount: number, + ): void { // @ts-expect-error - super._renderGroupSummaryCellsCore($groupCell, options, groupCellColSpan, alignByColumnCellCount); + super._renderGroupSummaryCellsCore( + $groupCell, + options, + groupCellColSpan, + alignByColumnCellCount, + ); const stickySummarySelector = `.${this.addWidgetPrefix(CLASSES.stickyColumn)}`; if ( @@ -530,7 +555,7 @@ const rowsView = ( } } - public _scrollToElement($element, offset?) { + public _scrollToElement($element: dxElementWrapper, offset?: ElementOffset): void { let scrollOffset = offset; const scrollable = this.getScrollable(); const hasStickyColumns = this.hasStickyColumns(); From 04991c51ad8ea6e0ce8dcd45b75d49a6ab81ac9e Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 19:42:25 +0400 Subject: [PATCH 5/8] Grids: type sticky columns controller extenders --- .../m_columns_resizing_reordering.ts | 8 +- .../grids/grid_core/grid_view/grid_view.ts | 2 +- .../grids/grid_core/sticky_columns/dom.ts | 9 +- .../sticky_columns/m_sticky_columns.ts | 83 ++++++++++++------- 4 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts index f9b551c0f9f2..cee083c896c0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts @@ -28,7 +28,7 @@ import { CLASSES as COLUMN_HEADERS_CLASSES } from '@ts/grids/grid_core/column_he import { getDraggingPanelBoundingRects } from '@ts/grids/grid_core/columns_resizing_reordering/utils'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { - ColumnPoint, InternalGrid, ModuleType, OptionChanged, + ColumnPoint, ColumnPointProps, InternalGrid, ModuleType, OptionChanged, } from '@ts/grids/grid_core/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import type { ScrollPosition } from '@ts/grids/grid_core/views/types'; @@ -799,7 +799,7 @@ export class ColumnsResizerViewController extends modules.ViewController { } protected _correctColumnIndexForPoint( - point: ColumnPoint, + point: ColumnPointProps, correctionValue: number, // eslint-disable-next-line @typescript-eslint/no-unused-vars -- overrides use the param columns: Column[], @@ -811,7 +811,7 @@ export class ColumnsResizerViewController extends modules.ViewController { * @extended: adaptivity */ protected _pointCreated( - point: ColumnPoint, + point: ColumnPointProps, columns: Column[], cells?: dxElementWrapper, ): boolean { @@ -1082,7 +1082,7 @@ export class ColumnsResizerViewController extends modules.ViewController { const topYMap = this._generateColumnsTopYIndex(needToCheckPrevPoint); const columns = this._columnsController ? this._columnsController.getVisibleColumns() : []; const cells = this._columnHeadersView.getColumnElements(); - const correctColumnY = (point: ColumnPoint): ColumnPoint => { + const correctColumnY = (point: ColumnPointProps): ColumnPointProps => { const x = Math.ceil(point.x); if (topYMap[x]) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/grid_view/grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/grid_view/grid_view.ts index 3f69eca3fee7..d19363c8e7b5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/grid_view/grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/grid_view/grid_view.ts @@ -63,7 +63,7 @@ export class ResizingController extends modules.ViewController { protected _rowsView!: RowsView; - private _columnHeadersView!: ColumnHeadersView; + protected _columnHeadersView!: ColumnHeadersView; public _columnsController!: ColumnsController; 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 2ee97638e210..8346aa615bbc 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 @@ -3,6 +3,7 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { getBoundingRect } from '@js/core/utils/position'; import { isDefined } from '@js/core/utils/type'; +import type { ColumnPointProps } from '@ts/grids/grid_core/types'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import { CLASSES, StickyPosition } from './const'; @@ -196,13 +197,7 @@ const noNeedToCreateResizingPoint = ( nextColumn, cells, }: { - point: { - x: number; - index: number; - item: HTMLElement; - isLeftBoundary: boolean | undefined; - isRightBoundary: boolean | undefined; - }; + point: ColumnPointProps; column; nextColumn; cells: dxElementWrapper | undefined; 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 d9bd0ac3bdcf..44894067e410 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 @@ -5,9 +5,15 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import { getBoundingRect } from '@js/core/utils/position'; import { getWidth, setWidth } from '@js/core/utils/size'; import type { ItemClickEvent } from '@js/ui/context_menu'; +import type { KeyboardKeyDownEvent } from '@ts/events/core/keyboard_processor'; +import type { + DraggingPointCreatedOptions, + DraggingPointsOptions, +} from '@ts/grids/grid_core/columns_resizing_reordering/types'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { ResizingController } from '@ts/grids/grid_core/grid_view/grid_view'; +import type { HorizontalBounds } from '@ts/grids/grid_core/keyboard_navigation/types'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import type { ElementOffset } from '@ts/ui/scroll_view/types'; @@ -26,7 +32,7 @@ import { } from '../keyboard_navigation/utils'; import { CLASSES as MASTER_DETAIL_CLASSES } from '../master_detail/const'; import { isDetailRow } from '../master_detail/utils'; -import type { ModuleType } from '../types'; +import type { ColumnPoint, ColumnPointProps, ModuleType } from '../types'; import gridCoreUtils from '../utils'; import type { ColumnsView } from '../views/columns_view'; import type { RowsView } from '../views/rows_view'; @@ -583,7 +589,12 @@ const footerView = >( Base: T, ): T => class FooterViewStickyColumnsExtender extends baseStickyColumns(Base) {}; -const columnsResizer = (Base: ModuleType) => class ColumnResizerStickyColumnsExtender extends Base { +const columnsResizer = ( + Base: ModuleType, +): ModuleType => class ColumnResizerStickyColumnsExtender + extends Base { + protected declare _columnHeadersView: StickyColumnHeadersView; + private isNextCellPinned($nextCell: dxElementWrapper, $container: dxElementWrapper): boolean { const addWidgetPrefix = this.addWidgetPrefix.bind(this); @@ -593,7 +604,6 @@ const columnsResizer = (Base: ModuleType) => class } protected getSeparatorOffsetX($cell: dxElementWrapper, $nextCell: dxElementWrapper): number { - // @ts-expect-error const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); if (hasStickyColumns) { @@ -620,7 +630,11 @@ const columnsResizer = (Base: ModuleType) => class return super.getSeparatorOffsetX($cell, $nextCell); } - protected _correctColumnIndexForPoint(point, correctionValue: number, columns): void { + protected _correctColumnIndexForPoint( + point: ColumnPointProps, + correctionValue: number, + columns: Column[], + ): void { const rtlEnabled = this.option('rtlEnabled'); const isWidgetResizingMode = this.option('columnResizingMode') === 'widget'; const columnIndex = Math.max(point.index - 1, 0); @@ -664,14 +678,16 @@ const columnsResizer = (Base: ModuleType) => class } protected _generatePointsByColumns(): void { - // @ts-expect-error const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); super._generatePointsByColumns(hasStickyColumns); } - protected _pointCreated(point, columns, cells?: dxElementWrapper) { - // @ts-expect-error + protected _pointCreated( + point: ColumnPointProps, + columns: Column[], + cells?: dxElementWrapper, + ): boolean { const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); const result = super._pointCreated(point, columns, cells); const cellsLength = cells?.length ?? columns.length; @@ -698,9 +714,13 @@ const columnsResizer = (Base: ModuleType) => class } }; -const draggingHeader = (Base: ModuleType) => class DraggingHeaderStickyColumnsExtender extends Base { - public _generatePointsByColumns(options): any[] { - // @ts-expect-error +const draggingHeader = ( + Base: ModuleType, +): ModuleType => class DraggingHeaderStickyColumnsExtender + extends Base { + protected declare _columnHeadersView: StickyColumnHeadersView; + + public _generatePointsByColumns(options: DraggingPointsOptions): ColumnPoint[] { const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); const { sourceLocation, sourceColumn, targetDraggingPanel } = options; const isDraggingBetweenHeaders = sourceLocation === 'headers' && targetDraggingPanel?.getName() === 'headers'; @@ -737,11 +757,10 @@ const draggingHeader = (Base: ModuleType) => class protected _pointCreated({ point, columns, location, sourceColumn, cells, - }) { - // @ts-expect-error + }: DraggingPointCreatedOptions): boolean { const hasStickyColumns = this._columnHeadersView.hasStickyColumns(); const needToCheckPoint = hasStickyColumns && location === 'headers' && cells?.length - && (!sourceColumn.fixed || sourceColumn.fixedPosition === StickyPosition.Sticky); + && (!sourceColumn?.fixed || sourceColumn.fixedPosition === StickyPosition.Sticky); const result = super._pointCreated({ point, columns, location, sourceColumn, }); @@ -759,9 +778,12 @@ const draggingHeader = (Base: ModuleType) => class } }; -const editorFactory = (Base: ModuleType) => class EditorFactoryStickyColumnsExtender extends Base { +const editorFactory = ( + Base: ModuleType, +): ModuleType => class EditorFactoryStickyColumnsExtender extends Base { + protected declare _rowsView: StickyRowsView; + private getOverlayContainerIfNeeded($cell: dxElementWrapper): dxElementWrapper | undefined { - // @ts-expect-error const hasFixedColumns = this._rowsView.hasStickyColumns(); const isFixedCell = GridCoreStickyColumnsDom.isFixedCell( $cell, @@ -775,8 +797,7 @@ const editorFactory = (Base: ModuleType) => class EditorFactorySt return undefined; } - protected updateFocusOverlaySize($element, position): void { - // @ts-expect-error + protected updateFocusOverlaySize($element: dxElementWrapper, position: object): void { const hasFixedColumns = this._rowsView.hasStickyColumns(); if (!hasFixedColumns) { @@ -785,7 +806,6 @@ const editorFactory = (Base: ModuleType) => class EditorFactorySt } protected getFocusOverlaySize($element: dxElementWrapper): { width: number; height: number } { - // @ts-expect-error const hasFixedColumns = this._rowsView.hasStickyColumns(); if (hasFixedColumns) { @@ -819,8 +839,7 @@ const editorFactory = (Base: ModuleType) => class EditorFactorySt } protected overlayPositionedHandler(e, isOverlayVisible: boolean): void { - const columnHeaders = this.getView('columnHeadersView'); - // @ts-expect-error + const columnHeaders = this.getView('columnHeadersView') as StickyColumnHeadersView; const hasStickyColumns = columnHeaders.hasStickyColumns(); // @ts-expect-error super.overlayPositionedHandler(e, isOverlayVisible); @@ -865,15 +884,19 @@ const editorFactory = (Base: ModuleType) => class EditorFactorySt } }; -const resizing = (Base: ModuleType) => class ResizingStickyColumnsExtender extends Base { +const resizing = ( + Base: ModuleType, +): ModuleType => class ResizingStickyColumnsExtender extends Base { + protected declare _columnHeadersView: StickyColumnHeadersView; + + protected declare _rowsView: StickyRowsView; + public resize(): DeferredObj { const result = super.resize(); - // @ts-expect-error ColumnHeadersView's method const hasStickyColumns = this._columnHeadersView.hasStickyColumns(); // @ts-expect-error Resizing's method if (hasStickyColumns && this?.hasResizeTimeout()) { - // @ts-expect-error RowsView's method this._rowsView.setStickyOffsets(); } @@ -881,9 +904,14 @@ const resizing = (Base: ModuleType) => class ResizingStickyC } }; -const headersKeyboardNavigation = (Base: ModuleType) => class HeadersKeyboardNavigationStickyColumnsExtender extends Base { - protected getContainerBoundingRect($container: dxElementWrapper) { - // @ts-expect-error columnHeadersView's method +const headersKeyboardNavigation = ( + Base: ModuleType, +): ModuleType< + HeadersKeyboardNavigationController +> => class HeadersKeyboardNavigationStickyColumnsExtender extends Base { + protected declare _columnHeadersView: StickyColumnHeadersView; + + protected getContainerBoundingRect($container: dxElementWrapper): HorizontalBounds { const hasStickyColumns = this._columnHeadersView?.hasStickyColumns(); if (hasStickyColumns) { @@ -903,8 +931,7 @@ const headersKeyboardNavigation = (Base: ModuleType Date: Mon, 26 Oct 2026 19:56:09 +0400 Subject: [PATCH 6/8] Grids: add reasons to sticky columns directives --- .../sticky_columns/m_sticky_columns.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) 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 44894067e410..ebbd4659bba5 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 @@ -420,7 +420,7 @@ const rowsView = ( row: ViewRow, options: RowRenderOptions, ): dxElementWrapper { - // @ts-expect-error + // @ts-expect-error _renderMasterDetailCell is declared on the master detail extender const $detailCell: dxElementWrapper = super._renderMasterDetailCell($row, row, options); if (this.hasStickyColumns()) { @@ -526,7 +526,7 @@ const rowsView = ( groupCellColSpan: number, alignByColumnCellCount: number, ): void { - // @ts-expect-error + // @ts-expect-error _renderGroupSummaryCellsCore is defined by DataGrid summary super._renderGroupSummaryCellsCore( $groupCell, options, @@ -815,9 +815,9 @@ const editorFactory = ( $element, this.addWidgetPrefix.bind(this), ); - const isGroupRow = isGroupRowElement($element); + const isGroupElement = isGroupRowElement($element); const isGroupFooter = isGroupFooterRow($element); - const noExtraWidth = isLastCell || isFixedCell || isGroupRow || isGroupFooter; + const noExtraWidth = isLastCell || isFixedCell || isGroupElement || isGroupFooter; return { width: elementRect.right - elementRect.left + (noExtraWidth ? 0 : 1), @@ -829,19 +829,21 @@ const editorFactory = ( } protected getValidationMessageContainer($cell: dxElementWrapper): dxElementWrapper { - // @ts-expect-error + // @ts-expect-error getValidationMessageContainer is declared on the validating extender + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- validating extender adds it return this.getOverlayContainerIfNeeded($cell) ?? super.getValidationMessageContainer($cell); } protected getRevertButtonContainer($cell: dxElementWrapper): dxElementWrapper { - // @ts-expect-error + // @ts-expect-error getRevertButtonContainer is declared on the validating extender + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- validating extender adds it return this.getOverlayContainerIfNeeded($cell) ?? super.getRevertButtonContainer($cell); } protected overlayPositionedHandler(e, isOverlayVisible: boolean): void { const columnHeaders = this.getView('columnHeadersView') as StickyColumnHeadersView; const hasStickyColumns = columnHeaders.hasStickyColumns(); - // @ts-expect-error + // @ts-expect-error overlayPositionedHandler is declared on the validating extender super.overlayPositionedHandler(e, isOverlayVisible); if (hasStickyColumns) { @@ -858,7 +860,7 @@ const editorFactory = ( this.addWidgetPrefix.bind(this), ); - // @ts-expect-error + // @ts-expect-error getOverlayBaseZIndex is declared on the validating extender $wrapper.css('zIndex', isOutsideVisibleArea ? 1 : this?.getOverlayBaseZIndex() ?? 0); } } @@ -868,15 +870,13 @@ const editorFactory = ( if (!isHideBorder) { const isFixedCell = GridCoreStickyColumnsDom .isFixedCell($element, this.addWidgetPrefix.bind(this)); - // @ts-expect-error renderFocusOverlay creates the focus overlay first - this._$focusOverlay.toggleClass(CLASSES.focusedFixedElement, isFixedCell); + this._$focusOverlay?.toggleClass(CLASSES.focusedFixedElement, isFixedCell); const isGroupElement = isGroupRowElement($element); const isGroupFooterRowElement = isGroupFooterRow($element); const isAdaptiveElement = isAdaptiveItem($element); if (isFixedCell || isGroupElement || isGroupFooterRowElement || isAdaptiveElement) { - // @ts-expect-error renderFocusOverlay creates the focus overlay first - this._$focusOverlay.toggleClass(CLASSES.focusedFixedElement, true); + this._$focusOverlay?.toggleClass(CLASSES.focusedFixedElement, true); } } @@ -895,7 +895,7 @@ const resizing = ( const result = super.resize(); const hasStickyColumns = this._columnHeadersView.hasStickyColumns(); - // @ts-expect-error Resizing's method + // @ts-expect-error hasResizeTimeout is declared on the virtual scrolling extender if (hasStickyColumns && this?.hasResizeTimeout()) { this._rowsView.setStickyOffsets(); } From 20919d348b87b581943f59381377be02bd5a1749 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 20:05:10 +0400 Subject: [PATCH 7/8] Grids: fix sticky columns lint warnings --- .../grids/grid_core/sticky_columns/m_sticky_columns.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ebbd4659bba5..800afe92b06a 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 @@ -327,7 +327,7 @@ const columnHeadersView = ( const columnFixingOptions = this.option('columnFixing'); let items = super.getContextMenuItems(options); - if (options.row && options.row.rowType === 'header') { + if (options.row?.rowType === 'header') { if (columnFixingOptions?.enabled === true && column && column.allowFixing) { const onItemClick = (params: ItemClickEvent): void => { // eslint-disable-next-line default-case @@ -382,7 +382,7 @@ const columnHeadersView = ( }); } - items = items || []; + items ??= []; items.push( { text: columnFixingOptions.texts?.fix, From 92056d91d452179f9a29218e9371319aa53d566c Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Mon, 26 Oct 2026 20:25:34 +0400 Subject: [PATCH 8/8] Grids: simplify sticky columns code --- .../sticky_columns/m_sticky_columns.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 800afe92b06a..03f22f16b4ef 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 @@ -175,13 +175,12 @@ const baseStickyColumns = >( } protected _createCell(options: ViewCellOptions): dxElementWrapper { - const { column } = options; - const { rowType } = options; + const { column, rowType } = options; const $cell = super._createCell(options); const hasStickyColumns = this.hasStickyColumns(); const rowIndex = rowType === 'header' ? options.rowIndex ?? null : null; const isSummary = rowType === 'groupFooter' || rowType === 'totalFooter' || rowType === 'group'; - const isExpandColumn = column.command && column.command === 'expand'; + const isExpandColumn = column.command === 'expand'; if (!hasStickyColumns || needToDisableStickyColumn(this._columnsController, column)) { return $cell; @@ -245,8 +244,8 @@ const baseStickyColumns = >( columns = processFixedColumns(this._columnsController, columns); if (rtlEnabled) { - columns = rtlEnabled ? [...columns].reverse() : columns; - widths = rtlEnabled ? [...widths].reverse() : widths; + columns = [...columns].reverse(); + widths = [...widths].reverse(); } columns.forEach((column, columnIndex) => { @@ -282,7 +281,7 @@ const baseStickyColumns = >( const hasHidingColumnsQueue = !!this.adaptiveColumnsController ?.getHidingColumnsQueue()?.length; - super._resizeCore.apply(this); + super._resizeCore(); if (!hasStickyColumns) { return; @@ -462,8 +461,8 @@ const rowsView = ( const rtlEnabled = this.option('rtlEnabled'); if (rtlEnabled) { - columns = rtlEnabled ? [...columns].reverse() : columns; - widths = rtlEnabled ? [...widths].reverse() : widths; + columns = [...columns].reverse(); + widths = [...widths].reverse(); } // @ts-expect-error the view is rendered here @@ -861,7 +860,7 @@ const editorFactory = ( ); // @ts-expect-error getOverlayBaseZIndex is declared on the validating extender - $wrapper.css('zIndex', isOutsideVisibleArea ? 1 : this?.getOverlayBaseZIndex() ?? 0); + $wrapper.css('zIndex', isOutsideVisibleArea ? 1 : this.getOverlayBaseZIndex() ?? 0); } } } @@ -896,7 +895,7 @@ const resizing = ( const hasStickyColumns = this._columnHeadersView.hasStickyColumns(); // @ts-expect-error hasResizeTimeout is declared on the virtual scrolling extender - if (hasStickyColumns && this?.hasResizeTimeout()) { + if (hasStickyColumns && this.hasResizeTimeout()) { this._rowsView.setStickyOffsets(); }