From 27274b214a7aa1ba4778a215c00e182e9645c590 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:18:24 +0200 Subject: [PATCH 1/3] Grids: type m_column_state_mixin --- .../m_column_state_mixin.ts | 97 +++++++++++-------- .../grid_core/column_state_mixin/types.ts | 36 +++++++ .../header_filter/m_header_filter.ts | 2 + 3 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/types.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/m_column_state_mixin.ts b/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/m_column_state_mixin.ts index bd11f406382f..7c7d421dae06 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/m_column_state_mixin.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/m_column_state_mixin.ts @@ -2,40 +2,40 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { extend } from '@js/core/utils/extend'; import { getDefaultAlignment } from '@js/core/utils/position'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { View } from '../modules/modules'; -import type { InternalGrid } from '../types'; +import type { + ColumnStateMixinBase, + ColumnStateOptions, + IndicatorColumnsSource, + IndicatorOptions, + IndicatorRowOptions, +} from './types'; const COLUMN_INDICATORS_CLASS = 'dx-column-indicators'; const GROUP_PANEL_ITEM_CLASS = 'dx-group-panel-item'; -export interface ColumnStateMixinRequirements { - option: InternalGrid['option']; - - component: InternalGrid; - - setAria: View['setAria']; -} - -export const ColumnStateMixin = ColumnStateMixinRequirements>(Base: T) => class extends Base { +// eslint-disable-next-line @stylistic/max-len +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type +export const ColumnStateMixin = (Base: T) => class extends Base { /** * @extended header_filter_core */ - protected _applyColumnState(options) { - const that = this; + protected _applyColumnState(options: ColumnStateOptions): dxElementWrapper | undefined { const rtlEnabled = this.option('rtlEnabled'); - const columnAlignment = that._getColumnAlignment(options.column.alignment, rtlEnabled); - const parameters = extend(true, { columnAlignment }, options); + const columnAlignment = this._getColumnAlignment(options.column.alignment, rtlEnabled); + const parameters: IndicatorOptions = extend(true, { columnAlignment }, options); const isGroupPanelItem = parameters.rootElement.hasClass(GROUP_PANEL_ITEM_CLASS); - const $indicatorsContainer = that._createIndicatorContainer(parameters, isGroupPanelItem); - const $span = $('').addClass(that._getIndicatorClassName(options.name)); + const $indicatorsContainer = this._createIndicatorContainer(parameters, isGroupPanelItem); + const $span = $('').addClass(this._getIndicatorClassName(options.name) ?? ''); // TODO getController - const columnsController = that.component?.getController('columns'); - const indicatorAlignment = columnsController?.getHeaderContentAlignment(columnAlignment) || columnAlignment; + const columnsController = this.component?.getController('columns'); + const indicatorAlignment = columnsController?.getHeaderContentAlignment(columnAlignment) + ?? columnAlignment; parameters.container = $indicatorsContainer; parameters.indicator = $span; - that._renderIndicator(parameters); + this._renderIndicator(parameters); $indicatorsContainer[(isGroupPanelItem || !options.showColumnLines) && indicatorAlignment === 'left' ? 'appendTo' : 'prependTo'](options.rootElement); @@ -45,12 +45,13 @@ export const ColumnStateMixin = ColumnStateMix /** * @extended header_filter_core */ - // @ts-expect-error // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected _getIndicatorClassName(name: string): string {} + protected _getIndicatorClassName(name: string): string | undefined { + return undefined; + } protected _getColumnAlignment( - alignment: string, + alignment: string | undefined, rtl = false, ): string { const rtlEnabled = rtl || this.option('rtlEnabled'); @@ -58,9 +59,15 @@ export const ColumnStateMixin = ColumnStateMix return alignment && alignment !== 'center' ? alignment : getDefaultAlignment(rtlEnabled); } - private _createIndicatorContainer(options, ignoreIndicatorAlignment) { + private _createIndicatorContainer( + options: IndicatorOptions, + ignoreIndicatorAlignment: boolean, + ): dxElementWrapper { let $indicatorsContainer = this._getIndicatorContainer(options.rootElement); const indicatorAlignment = options.columnAlignment === 'left' ? 'right' : 'left'; + const containerFloat = options.showColumnLines && !ignoreIndicatorAlignment + ? indicatorAlignment + : null; if (!$indicatorsContainer.length) { $indicatorsContainer = $('
').addClass(COLUMN_INDICATORS_CLASS); @@ -68,10 +75,11 @@ export const ColumnStateMixin = ColumnStateMix this.setAria('role', 'presentation', $indicatorsContainer); - return $indicatorsContainer.css('float', options.showColumnLines && !ignoreIndicatorAlignment ? indicatorAlignment : null); + // @ts-expect-error css() is typed without null + return $indicatorsContainer.css('float', containerFloat); } - protected _getIndicatorContainer($cell) { + protected _getIndicatorContainer($cell: dxElementWrapper): dxElementWrapper { return $cell && $cell.find(`.${COLUMN_INDICATORS_CLASS}`); } @@ -88,36 +96,41 @@ export const ColumnStateMixin = ColumnStateMix /** * @extended header_filter_core */ - protected _renderIndicator(options) { + protected _renderIndicator(options: IndicatorOptions): void { const $container = options.container; const $indicator = options.indicator; - $container && $indicator && $container.append($indicator); + if ($container && $indicator) { + $container.append($indicator); + } } - protected _updateIndicators(indicatorName) { - const that = this; - // @ts-expect-error - const columns = that.getColumns(); - // @ts-expect-error - const $cells = that.getColumnElements(); - let $cell; + protected _updateIndicators(this: this & IndicatorColumnsSource, indicatorName: string): void { + const columns = this.getColumns(); + const $cells = this.getColumnElements(); - if (!$cells || columns.length !== $cells.length) return; + if (!$cells || columns.length !== $cells.length) { + return; + } - for (let i = 0; i < columns.length; i++) { - $cell = $cells.eq(i); - that._updateIndicator($cell, columns[i], indicatorName); + for (let i = 0; i < columns.length; i += 1) { + const $cell = $cells.eq(i); + this._updateIndicator($cell, columns[i], indicatorName); - const rowOptions = $cell.parent().data('options'); + // @ts-expect-error data(key) is typed as returning the wrapper + const rowOptions: IndicatorRowOptions | undefined = $cell.parent().data('options'); - if (rowOptions && rowOptions.cells) { + if (rowOptions?.cells) { rowOptions.cells[$cell.index()].column = columns[i]; } } } - protected _updateIndicator($cell, column, indicatorName): any { + protected _updateIndicator( + $cell: dxElementWrapper, + column: Column, + indicatorName: string, + ): dxElementWrapper | undefined { if (!column.command) { return this._applyColumnState({ name: indicatorName, diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/types.ts b/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/types.ts new file mode 100644 index 000000000000..d04ba94ed328 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/column_state_mixin/types.ts @@ -0,0 +1,36 @@ +import type { dxElementWrapper } from '@js/core/renderer'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { Cell } from '@ts/grids/grid_core/data_controller/types'; +import type { View } from '@ts/grids/grid_core/modules/modules'; +import type { InternalGrid } from '@ts/grids/grid_core/types'; + +export interface ColumnStateMixinRequirements { + option: InternalGrid['option']; + component?: InternalGrid; + setAria: View['setAria']; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructors need any[] +export type ColumnStateMixinBase = new (...args: any[]) => ColumnStateMixinRequirements; + +export interface IndicatorColumnsSource { + getColumns: () => Column[]; + getColumnElements: () => dxElementWrapper | undefined; +} + +export interface ColumnStateOptions { + name: string; + rootElement: dxElementWrapper; + column: Column; + showColumnLines?: boolean; +} + +export interface IndicatorOptions extends ColumnStateOptions { + columnAlignment: string; + container?: dxElementWrapper; + indicator?: dxElementWrapper; +} + +export interface IndicatorRowOptions { + cells?: Cell[]; +} 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 1e5be6fadccd..20b9dd8f0eae 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 @@ -443,6 +443,8 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH const $indicator = super._updateIndicator($cell, column, indicatorName); $indicator && this._subscribeToIndicatorEvent($indicator, column, indicatorName); + + return undefined; } private _updateHeaderFilterIndicators() { From 039ccb60ee52b603743225a67c784efe5ee5fab6 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Wed, 7 Oct 2026 09:54:39 +0200 Subject: [PATCH 2/3] Grids: type m_columns_view --- .../column_chooser/m_column_chooser.ts | 7 +- .../column_fixing/m_column_fixing.ts | 4 +- .../grids/grid_core/editing/m_editing.ts | 4 +- .../header_filter/m_header_filter.ts | 1 + .../grid_core/header_panel/m_header_panel.ts | 6 +- .../m_headers_keyboard_navigation.ts | 2 +- .../m_keyboard_navigation.ts | 9 +- .../keyboard_navigation/scrollable_a11y.ts | 3 +- .../grid_core/row_dragging/m_row_dragging.ts | 8 +- .../grids/grid_core/search/m_search.ts | 7 +- .../grids/grid_core/selection/m_selection.ts | 3 +- .../sticky_columns/m_sticky_columns.ts | 3 +- .../grid_core/validating/m_validating.ts | 1 + .../grids/grid_core/views/m_columns_view.ts | 688 +++++++++++------- .../grids/grid_core/views/m_rows_view.ts | 6 +- .../__internal/grids/grid_core/views/types.ts | 172 ++++- .../virtual_columns/m_virtual_columns.ts | 15 +- .../virtual_scrolling/m_virtual_scrolling.ts | 14 +- 18 files changed, 672 insertions(+), 281 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index ddd03b4dab9f..ee3f5b07fdf0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -2,6 +2,8 @@ import messageLocalization from '@js/common/core/localization/message'; import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { Deferred } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getOuterHeight, getOuterWidth } from '@js/core/utils/size'; @@ -11,6 +13,7 @@ import type { Properties as PopupProperties } from '@js/ui/popup'; import Popup from '@js/ui/popup/ui.popup'; import type { Item } from '@js/ui/tree_view'; import TreeView from '@js/ui/tree_view'; +import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; @@ -213,7 +216,7 @@ export class ColumnChooserView extends ColumnsView { } } - protected _renderCore(change) { + protected _renderCore(change?: DataChange | 'full'): DeferredObj { if (this._popupContainer) { const isDragMode = !this.isSelectMode(); @@ -223,6 +226,8 @@ export class ColumnChooserView extends ColumnsView { this._updateItems(); } } + + return Deferred().resolve(); } private _renderTreeView() { 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 107bce1164d9..ae546d4f5b47 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 @@ -8,6 +8,7 @@ import messageLocalization from '@js/common/core/localization/message'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import browser from '@js/core/utils/browser'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getBoundingRect } from '@js/core/utils/position'; @@ -15,6 +16,7 @@ import { getOuterWidth } from '@js/core/utils/size'; import { setWidth } from '@js/core/utils/style'; import { isDefined } from '@js/core/utils/type'; import Scrollable from '@js/ui/scroll_view/ui.scrollable'; +import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; @@ -897,7 +899,7 @@ const rowsView = (Base: ModuleType) => class RowsViewFixedColumnsExten return super._getSummaryCellIndex.apply(this, arguments); } - protected _renderCore(change) { + protected _renderCore(change?: DataChange): DeferredObj { this._detachHoverEvents(); const deferred = super._renderCore(change); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts index ab2d4c2ab4a5..9efdf20f743c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts @@ -2778,8 +2778,8 @@ const rowsView = (Base: ModuleType) => class RowsViewEditingExtender e clearTimeout(this._pointerDownTimeout); } - protected _renderCore() { - super._renderCore.apply(this, arguments as any); + protected _renderCore(change?: DataChange): DeferredObj { + super._renderCore(change); return this.waitAsyncTemplates(true).done(() => { this._editingController._focusEditorIfNeed(); 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 20b9dd8f0eae..3c13632b15d1 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 @@ -345,6 +345,7 @@ export class HeaderFilterController extends Modules.ViewController { const visibleIndex = columnsController.getVisibleIndex(columnIndex); // TODO getView const view = isGroupPanel ? this.getView('headerPanel') : this.getView('columnHeadersView'); + // @ts-expect-error getColumnElements() is typed as possibly undefined const $columnElement = view.getColumnElements() .eq(isGroupPanel ? column.groupIndex : visibleIndex); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts index 03feac1fd077..077a7898ef74 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts @@ -2,6 +2,8 @@ import messageLocalization from '@js/common/core/localization/message'; import $ from '@js/core/renderer'; import { getPathParts } from '@js/core/utils/data'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { Deferred } from '@js/core/utils/deferred'; import { isDefined } from '@js/core/utils/type'; import type { Properties as ToolbarProperties } from '@js/ui/toolbar'; import Toolbar from '@js/ui/toolbar'; @@ -162,7 +164,7 @@ export class HeaderPanel extends ColumnsView { return options.toolbarOptions; } - protected _renderCore() { + protected _renderCore(): DeferredObj { if (!this._toolbar) { const $headerPanel = this.element(); // @ts-expect-error the view is rendered here @@ -176,6 +178,8 @@ export class HeaderPanel extends ColumnsView { } else { this._toolbar.option(this._toolbarOptions!); } + + return Deferred().resolve(); } protected _columnOptionChanged() { diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts index c24e18b5a18c..11dbcb17f38e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts @@ -131,7 +131,7 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio return this._columnHeadersView.getCellIndex($cell); } - protected _getCell(cellPosition): dxElementWrapper { + protected _getCell(cellPosition): dxElementWrapper | undefined { return this._columnHeadersView?.getCell(cellPosition); } 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 4f4d104b23fd..b6e8d237e31b 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 @@ -28,7 +28,7 @@ import type { KeyboardKeyDownEvent } from '@ts/events/core/keyboard_processor'; import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { RowIndexCorrection } from '@ts/grids/grid_core/data_controller/types'; +import type { DataChange, RowIndexCorrection } from '@ts/grids/grid_core/data_controller/types'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -2585,6 +2585,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo } public _fireFocusedCellChanged($cell: dxElementWrapper | undefined): void { + // @ts-expect-error getCellIndex() requires a cell const columnIndex = this._rowsView.getCellIndex($cell); const rowOptions: any = $cell?.parent().data('options'); const focusedRowKey = rowOptions?.key; @@ -2731,6 +2732,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo columnIndex, }); } + + return undefined; } protected _getRowIndex($row): number { @@ -3080,8 +3083,8 @@ const rowsView = (Base: ModuleType) => class RowsViewKeyboardExtender } } - protected _renderCore(change) { - const deferred = super._renderCore.apply(this, arguments as any); + protected _renderCore(change?: DataChange): DeferredObj { + const deferred = super._renderCore(change); this._renderFocusByChange(change); return deferred; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts index 70f0f73ff107..ad9312f50fe7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts @@ -42,7 +42,8 @@ export const keyboardNavigationScrollableA11yExtender = (Base: ModuleType) => class RowsViewRowDraggingExtend return $content; } - protected _renderCore(e) { - const deferred = super._renderCore.apply(this, arguments as any); + protected _renderCore(e?: DataChange): Deferred { + const deferred = super._renderCore(e); - if (e && e.changeType === 'update' + if (e?.changeType === 'update' && e.repaintChangesOnly && gridCoreUtils.isVirtualRowRendering(this)) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget update deferUpdate(() => { this._updateSortable(); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index 5f5b3433fd4c..94ca054c354c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -5,12 +5,13 @@ import domAdapter from '@js/core/dom_adapter'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { toComparable } from '@js/core/utils/data'; +import type { DeferredObj } from '@js/core/utils/deferred'; import type TextBox from '@js/ui/text_box'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { DataController } from '../data_controller/data_controller'; -import type { UserState } from '../data_controller/types'; +import type { DataChange, UserState } from '../data_controller/types'; import type { DataFilter, FilterSourceContext } from '../filter/types'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import gridCoreUtils from '../m_utils'; @@ -297,8 +298,8 @@ const rowsView = ( } } - protected _renderCore() { - const deferred = super._renderCore.apply(this, arguments as any); + protected _renderCore(change?: DataChange): DeferredObj { + const deferred = super._renderCore(change); // T103538 if (this.option('dataRowTemplate')) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts index 78545cdf3920..9177e96d634e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts @@ -839,6 +839,7 @@ export const selectionRowsViewExtender = ( $row .toggleClass(ROW_SELECTION_CLASS, needSelectionClass) + // @ts-expect-error dxCheckBox is added to the renderer at runtime .find(`.${SELECT_CHECKBOX_CLASS}`).dxCheckBox('option', 'value', isSelected); that.setAria('selected', String(isSelected), $row); }); @@ -923,7 +924,7 @@ export const selectionRowsViewExtender = ( return !!isCommandSelect; } - protected _renderCore(change) { + protected _renderCore(change?: DataChange): DeferredObj { const deferred = super._renderCore(change); this._updateCheckboxesClass(); return deferred; 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 691f49e3cecb..7a8621b8d4b9 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 { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; @@ -125,7 +126,7 @@ const baseStickyColumns = >(Base: T) => class }); } - protected _renderCore(options?) { + protected _renderCore(options?: DataChange): DeferredObj { const deferred = super._renderCore(options); const $element = this.element(); const hasStickyColumns = this.hasStickyColumns(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts index 1bab5d07cb19..29042b249f8f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts @@ -1589,6 +1589,7 @@ export const validatingRowsViewExtender = (Base: ModuleType) => class } protected _restoreErrorRow(contentTable?) { + // @ts-expect-error each() is typed for callbacks that return a boolean this._editingController && this._editingController.hasChanges() && this._getRowElements(contentTable).each((_, item) => { const rowOptions = $(item).data('options'); if (rowOptions) { 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 09f7aa1ccda3..c5cc6491181d 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 @@ -11,7 +11,9 @@ import Guid from '@js/core/guid'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import browser from '@js/core/utils/browser'; +import type { Callback } from '@js/core/utils/callbacks'; import { noop } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred, when } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import * as iteratorUtils from '@js/core/utils/iterator'; @@ -31,16 +33,42 @@ import supportUtils from '@ts/core/utils/m_support'; import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; import type { ColumnChooserController, ColumnChooserView } from '@ts/grids/grid_core/column_chooser/m_column_chooser'; import { ColumnStateMixin } from '@ts/grids/grid_core/column_state_mixin/m_column_state_mixin'; -import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { Column, ColumnsChanges } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataChange, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { SelectionController } from '@ts/grids/grid_core/selection/m_selection'; +import type { OptionChanged } from '@ts/grids/grid_core/types'; import type { ColumnsController } from '../columns_controller/columns_controller'; import type { DataController } from '../data_controller/data_controller'; import gridCoreUtils from '../m_utils'; import modules from '../modules/modules'; import { CLASSES } from './const'; -import type { ColumnViewTemplate } from './types'; +import type { + AppendRowTemplate, + BoundingRect, + CellEventOptions, + CellPosition, + CellRenderOptions, + ColumnRenderTemplate, + ColumnTemplateSource, + ColumnViewTemplate, + ColumnViewTemplateOptions, + ColumnWidthsOptions, + DelayedTemplate, + HintColumn, + RowPreparedOptions, + RowRenderOptions, + ScrollableOptions, + ScrollPosition, + TableRenderOptions, + TemplateModel, + ViewCellOptions, + ViewDataChange, + ViewRow, + ViewRowEvent, + WatchableOptions, +} from './types'; const SCROLL_CONTAINER_CLASS = 'scroll-container'; const SCROLLABLE_SIMULATED_CLASS = 'scrollable-simulated'; @@ -64,20 +92,27 @@ const CELL_HINT_VISIBLE = 'dxCellHintVisible'; const FORM_FIELD_ITEM_CONTENT_CLASS = 'dx-field-item-content'; -const appendElementTemplate = { - render(options) { +const appendElementTemplate: AppendRowTemplate = { + render(options): void { options.container.append(options.content); }, }; -const subscribeToRowEvents = function (that, $table) { - let touchTarget; - let touchCurrentTarget; - let timeoutId; - - function clearTouchTargets(timeout?) { +const subscribeToRowEvents = function subscribeToRowEvents( + that: ColumnsView, + $table: dxElementWrapper, +): void { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned by the touch handler + let touchTarget: EventTarget | null | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned by the touch handler + let touchCurrentTarget: EventTarget | null | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned by the touch handler + let timeoutId: ReturnType | undefined; + + function clearTouchTargets(timeout?: number): ReturnType { return setTimeout(() => { - touchTarget = touchCurrentTarget = null; + touchCurrentTarget = null; + touchTarget = null; }, timeout); } @@ -118,10 +153,13 @@ const subscribeToRowEvents = function (that, $table) { e.columns = that.getColumns(); if (event.type === pointerEvents.down) { + // @ts-expect-error the row handlers are protected members of the view that._rowPointerDown(e); } else if (event.type === clickEventName) { + // @ts-expect-error the row handlers are protected members of the view that._rowClick(e); } else { + // @ts-expect-error the row handlers are protected members of the view that._rowDblClick(e); } } @@ -129,38 +167,55 @@ const subscribeToRowEvents = function (that, $table) { })); }; -const getWidthStyle = function (width) { - if (width === 'auto') return ''; +const getWidthStyle = function getWidthStyle(width: number | string): string { + if (width === 'auto') { + return ''; + } + return isNumeric(width) ? `${width}px` : width; }; -const setCellWidth = function (cell, column, width) { - cell.style.width = cell.style.maxWidth = column.width === 'auto' ? '' : width; +const setCellWidth = function setCellWidth(cell: HTMLElement, column: Column, width: string): void { + const cellWidth = column.width === 'auto' ? '' : width; + + cell.style.maxWidth = cellWidth; + cell.style.width = cellWidth; }; -const copyAttributes = function (element, newElement) { +const copyAttributes = function copyAttributes( + element: Element | undefined, + newElement: Element | undefined, +): void { if (!element || !newElement) return; const oldAttributes = element.attributes; const newAttributes = newElement.attributes; - let i; - for (i = 0; i < oldAttributes.length; i++) { + // eslint-disable-next-line @typescript-eslint/prefer-for-of -- the loop shrinks the live list + for (let i = 0; i < oldAttributes.length; i += 1) { const name = oldAttributes[i].nodeName; if (!newElement.hasAttribute(name)) { element.removeAttribute(name); } } - for (i = 0; i < newAttributes.length; i++) { - element.setAttribute(newAttributes[i].nodeName, newAttributes[i].nodeValue); + for (const attribute of newAttributes) { + element.setAttribute(attribute.nodeName, attribute.value); } }; -const removeHandler = function (templateDeferred) { +const removeHandler = function removeHandler(templateDeferred: DeferredObj): void { templateDeferred.resolve(); }; +const isRenderTemplate = function isRenderTemplate( + template: ColumnTemplateSource, +): template is ColumnRenderTemplate { + return !!template + && !!(template as Partial).render + && !isRenderer(template); +}; + export const normalizeWidth = (width: string | number | undefined): string | undefined => { if (typeof width === 'number') { return `${width.toFixed(3)}px`; @@ -174,21 +229,21 @@ export const normalizeWidth = (width: string | number | undefined): string | und }; export class ColumnsView extends ColumnStateMixin(modules.View) { - protected _tableElement: any; + protected _tableElement?: dxElementWrapper | null; - protected _scrollLeft: any; + protected _scrollLeft?: number; - private _delayedTemplates: any; + private _delayedTemplates!: DelayedTemplate[]; - private _templateDeferreds: any; + private _templateDeferreds!: Set>; - private _templateTimeouts: any; + private _templateTimeouts!: Set; - private _templatesCache: any; + private _templatesCache!: Record; - protected _requireReady: any; + protected _requireReady?: boolean; - public scrollChanged: any; + public scrollChanged!: Callback<[ScrollPosition, string]>; protected _columnsController!: ColumnsController; @@ -232,10 +287,12 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { }); this._columnsController.columnsChanged.add(this._columnOptionChanged.bind(this)); - this._dataController && this._dataController.changed.add(this._handleDataChanged.bind(this)); + if (this._dataController) { + this._dataController.changed.add(this._handleDataChanged.bind(this)); + } } - public dispose() { + public dispose(): void { if (hasWindow()) { const window = getWindow(); @@ -244,10 +301,9 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } } - public optionChanged(args) { + public optionChanged(args: OptionChanged): void { super.optionChanged(args); - // eslint-disable-next-line default-case switch (args.name) { case 'cellHintEnabled': case 'onCellPrepared': @@ -262,21 +318,23 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } args.handled = true; break; + default: + break; } } - protected _createScrollableOptions() { - const that = this; - const scrollingOptions = that.option('scrolling'); - let useNativeScrolling = that.option('scrolling.useNative'); + protected _createScrollableOptions(): ScrollableOptions { + const scrollingOptions = this.option('scrolling'); + let useNativeScrolling = this.option('scrolling.useNative'); - const options = extend({}, scrollingOptions, { + const options: ScrollableOptions = extend({}, scrollingOptions, { direction: 'both', bounceEnabled: false, useKeyboard: false, }); // TODO jsdmitry: This condition is for unit tests and testing scrollable + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- null is not native if (useNativeScrolling === undefined) { useNativeScrolling = true; } @@ -290,22 +348,22 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return options; } - public _updateCell($cell, parameters) { + public _updateCell($cell: dxElementWrapper, parameters: ViewCellOptions): void { if (parameters.rowType) { this._cellPrepared($cell, parameters); } } - protected _needToSetCellWidths() { + protected _needToSetCellWidths(): boolean | undefined { return this.option('columnAutoWidth'); } /** * @extended: column_fixing, editing */ - protected _createCell(options) { + protected _createCell(options: ViewCellOptions): dxElementWrapper { const { column } = options; - const alignment = column.alignment || getDefaultAlignment(this.option('rtlEnabled')); + const alignment = column.alignment ?? getDefaultAlignment(this.option('rtlEnabled')); const needToSetCellWidths = this._needToSetCellWidths(); const cell = domAdapter.createElement('td'); @@ -324,15 +382,20 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } if (column.command === 'expand') { - $cell.addClass(column.cssClass); + if (isDefined(column.cssClass)) { + $cell.addClass(column.cssClass); + } $cell.addClass(this.addWidgetPrefix(GROUP_SPACE_CLASS)); } - if (column.colspan > 1) { + if (isDefined(column.colspan) && column.colspan > 1) { $cell.attr('colSpan', column.colspan); } else if (!column.isBand && column.visibleWidth !== 'auto' && needToSetCellWidths) { - if (column.width || column.minWidth) { - cell.style.minWidth = getWidthStyle(column.minWidth || column.width); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back + const minWidth = column.minWidth || column.width; + + if (minWidth) { + cell.style.minWidth = getWidthStyle(minWidth); } if (column.width) { setCellWidth(cell, column, getWidthStyle(column.width)); @@ -345,8 +408,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: selection */ - protected _createRow(rowObject, tagName?) { - tagName = tagName || 'tr'; + protected _createRow(rowObject?: ViewRow, tagName = 'tr'): dxElementWrapper { const $element = $(`<${tagName}>`).addClass(ROW_CLASS); if (tagName === 'tr') { @@ -355,14 +417,14 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return $element; } - protected _isAltRow(row) { - return row && row.dataIndex % 2 === 1; + protected _isAltRow(row: ViewRow | undefined): boolean | undefined { + return row && isDefined(row.dataIndex) && row.dataIndex % 2 === 1; } /** * @extended: selection */ - protected _createTable(columns, isAppend?) { + protected _createTable(columns?: Column[], isAppend?: boolean): dxElementWrapper { const $table = $('') .addClass(this.addWidgetPrefix(TABLE_CLASS)) .addClass(this.addWidgetPrefix(TABLE_FIXED_CLASS)); @@ -404,11 +466,14 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const $cell = $(e.currentTarget); const $row = $cell.parent(); const visibleColumns = this._columnsController.getVisibleColumns(); - const rowOptions: any = $row.data('options'); + // @ts-expect-error data(key) is typed as returning the wrapper + const rowOptions: RowPreparedOptions | undefined = $row.data('options'); const columnIndex = $cell.index(); const cellOptions = rowOptions && rowOptions.cells && rowOptions.cells[columnIndex]; - const column = cellOptions ? cellOptions.column : visibleColumns[columnIndex]; + const column: HintColumn | undefined = cellOptions + ? cellOptions.column + : visibleColumns[columnIndex]; const isHeaderRow = $row.hasClass('dx-header-row'); const isDataRow = $row.hasClass('dx-data-row'); @@ -417,9 +482,12 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const isFilterRow = $row.hasClass(this.addWidgetPrefix(FILTER_ROW_CLASS)); const isDataRowWithTemplate = isDataRow && (!column || column.cellTemplate); - const isEditorShown = isDataRow && cellOptions && (rowOptions.isEditing || cellOptions.isEditing || column?.showEditorAlways); + const isEditorShown = isDataRow + && cellOptions + && (!!rowOptions.isEditing || !!cellOptions.isEditing || column?.showEditorAlways); const isHeaderRowWithTemplate = isHeaderRow && (!column || column.headerCellTemplate); - const isGroupCellWithTemplate = isGroupRow && (!column || (column.groupIndex && column.groupCellTemplate)); + const isGroupCellWithTemplate = isGroupRow + && (!column || (column.groupIndex && column.groupCellTemplate)); const shouldShowHint = !isMasterDetailRow && !isFilterRow @@ -434,16 +502,19 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { })); } - const getOptions = (event) => { + const getOptions = (event: DxEvent): CellEventOptions | undefined => { const $cell = $(event.currentTarget); const $fieldItemContent = $(event.target).closest(`.${FORM_FIELD_ITEM_CONTENT_CLASS}`); const $row = $cell.parent(); - const rowOptions: any = $row.data('options'); + // @ts-expect-error data(key) is typed as returning the wrapper + const rowOptions: RowPreparedOptions | undefined = $row.data('options'); const options = rowOptions && rowOptions.cells && rowOptions.cells[$cell.index()]; - if (!$cell.closest('table').is(event.delegateTarget)) return; + if (!$cell.closest('table').is(event.delegateTarget)) { + return undefined; + } - const resultOptions = extend({}, options, { + const resultOptions: CellEventOptions = extend({}, options, { cellElement: getPublicElement($cell), event, eventType: event.type, @@ -452,10 +523,12 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { resultOptions.rowIndex = this.getRowIndex($row); if ($fieldItemContent.length) { - const formItemOptions: any = $fieldItemContent.data('dx-form-item'); + // @ts-expect-error data(key) is typed as returning the wrapper + const formItemOptions: { column?: Column } = $fieldItemContent.data('dx-form-item'); if (formItemOptions.column) { resultOptions.column = formItemOptions.column; - resultOptions.columnIndex = this._columnsController.getVisibleIndex(resultOptions.column.index); + resultOptions.columnIndex = this._columnsController + .getVisibleIndex(resultOptions.column.index); } } @@ -464,22 +537,30 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { eventsEngine.on($table, 'mouseover', '.dx-row > td', (e) => { const options = getOptions(e); - options && this.executeAction('onCellHoverChanged', options); + if (options) { + this.executeAction('onCellHoverChanged', options); + } }); eventsEngine.on($table, 'mouseout', '.dx-row > td', (e) => { const options = getOptions(e); - options && this.executeAction('onCellHoverChanged', options); + if (options) { + this.executeAction('onCellHoverChanged', options); + } }); eventsEngine.on($table, clickEventName, '.dx-row > td', (e) => { const options = getOptions(e); - options && this.executeAction('onCellClick', options); + if (options) { + this.executeAction('onCellClick', options); + } }); eventsEngine.on($table, dblclickEvent, '.dx-row > td', (e) => { const options = getOptions(e); - options && this.executeAction('onCellDblClick', options); + if (options) { + this.executeAction('onCellDblClick', options); + } }); subscribeToRowEvents(this, $table); @@ -516,20 +597,21 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: editing */ - protected _rowPointerDown(e?: any) {} + protected _rowPointerDown(e?: ViewRowEvent): void {} - protected _rowClick() {} + protected _rowClick(e?: ViewRowEvent): void {} - protected _rowDblClick() {} + protected _rowDblClick(e?: ViewRowEvent): void {} - protected _createColGroup(columns) { + protected _createColGroup(columns: Column[]): dxElementWrapper { const colgroupElement = $(''); - for (let i = 0; i < columns.length; i++) { - const colspan = columns[i].colspan || 1; + for (const column of columns) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back + const colspan = column.colspan || 1; - for (let j = 0; j < colspan; j++) { - colgroupElement.append(this._createCol(columns[i])); + for (let j = 0; j < colspan; j += 1) { + colgroupElement.append(this._createCol(column)); } } return colgroupElement; @@ -538,7 +620,8 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing */ - protected _createCol(column) { + protected _createCol(column: Column): dxElementWrapper { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back let width = column.visibleWidth || column.width; if (width === 'adaptiveHidden') { @@ -554,7 +637,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: keyboard_navigation, virtual_scrolling */ - public renderDelayedTemplates(change?) { + public renderDelayedTemplates(change?: DataChange): void { const delayedTemplates = this._delayedTemplates; const syncTemplates = delayedTemplates.filter((template) => !template.async); const asyncTemplates = delayedTemplates.filter((template) => template.async); @@ -565,7 +648,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { this._renderDelayedTemplatesCoreAsync(asyncTemplates); } - private _renderDelayedTemplatesCoreAsync(templates) { + private _renderDelayedTemplatesCoreAsync(templates: DelayedTemplate[]): void { if (templates.length) { const templateTimeout = getWindow().setTimeout(() => { this._templateTimeouts.delete(templateTimeout); @@ -576,15 +659,22 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } } - private _renderDelayedTemplatesCore(templates, isAsync, change?) { + private _renderDelayedTemplatesCore( + templates: DelayedTemplate[], + isAsync: boolean, + change?: DataChange, + ): void { const date = new Date(); while (templates.length) { const templateParameters = templates.shift(); + if (!templateParameters) { + break; + } + const { options } = templateParameters; - // @ts-expect-error - const doc = domAdapter.getRootNode($(options.container).get(0)); + const doc = domAdapter.getRootNode($(options.container).get(0) as HTMLElement); const needWaitAsyncTemplates = this.needWaitAsyncTemplates(); if (!isAsync || $(options.container).closest(doc).length || needWaitAsyncTemplates) { @@ -593,7 +683,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } templateParameters.template.render(options); } - // @ts-expect-error + // @ts-expect-error Date objects subtract as timestamps if (isAsync && (new Date() - date) > 30) { this._renderDelayedTemplatesCoreAsync(templates); break; @@ -605,52 +695,64 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } } - protected _processTemplate(template, options?): ColumnViewTemplate { - const that = this; - let renderingTemplate; + protected _processTemplate( + template: ColumnTemplateSource, + options?: TemplateModel, + ): ColumnViewTemplate { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the if-chain + let renderingTemplate: ColumnViewTemplate; - if (template && template.render && !isRenderer(template)) { + if (isRenderTemplate(template)) { renderingTemplate = { allowRenderToDetachedContainer: template.allowRenderToDetachedContainer, - render(options) { - template.render(options.container, options.model, options.change); - options.deferred && options.deferred.resolve(); + render(templateOptions: ColumnViewTemplateOptions): void { + template.render(templateOptions.container, templateOptions.model, templateOptions.change); + templateOptions.deferred?.resolve(); }, }; } else if (isFunction(template)) { renderingTemplate = { - render(options) { - const renderedTemplate = template(getPublicElement(options.container), options.model, options.change); + render(templateOptions: ColumnViewTemplateOptions): void { + const renderedTemplate = template( + getPublicElement(templateOptions.container), + templateOptions.model, + templateOptions.change, + ); if (renderedTemplate && (renderedTemplate.nodeType || isRenderer(renderedTemplate))) { - options.container.append(renderedTemplate); + templateOptions.container.append(renderedTemplate); } - options.deferred && options.deferred.resolve(); + templateOptions.deferred?.resolve(); }, }; } else { - const templateID: any = isString(template) ? template : $(template).attr('id'); + const templateID = isString(template) ? template : $(template).attr('id'); if (!templateID) { - renderingTemplate = that.getTemplate(template); + renderingTemplate = this.getTemplate(template); } else { - if (!that._templatesCache[templateID]) { - that._templatesCache[templateID] = that.getTemplate(template); + if (!this._templatesCache[templateID]) { + this._templatesCache[templateID] = this.getTemplate(template); } - renderingTemplate = that._templatesCache[templateID]; + renderingTemplate = this._templatesCache[templateID]; } } return renderingTemplate; } - public renderTemplate(container, template, options, allowRenderToDetachedContainer?, change?) { + public renderTemplate( + container: dxElementWrapper, + template: ColumnTemplateSource, + options: TemplateModel, + allowRenderToDetachedContainer?: boolean, + change?: ViewDataChange, + ): DeferredObj { const renderingTemplate = this._processTemplate(template, options); const { column } = options; const isDataRow = options.rowType === 'data'; - // @ts-expect-error - const templateDeferred = new Deferred(); - const templateOptions = { + const templateDeferred = Deferred(); + const templateOptions: ColumnViewTemplateOptions = { container, model: options, deferred: templateDeferred, @@ -668,16 +770,30 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const columnAsync = column && ( (column.renderAsync && isDataRow) - || this.option('renderAsync') - && (column.renderAsync !== false && (column.command || column.showEditorAlways) && isDataRow || options.rowType === 'filter') + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- OR of flags + || (this.option('renderAsync') && ( + (column.renderAsync !== false + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- OR of flags + && (column.command || column.showEditorAlways) + && isDataRow) + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- OR of flags + || options.rowType === 'filter' + )) ); const async = options.renderAsync ?? columnAsync; - if ((renderingTemplate.allowRenderToDetachedContainer || allowRenderToDetachedContainer) && !async) { + if ( + (renderingTemplate.allowRenderToDetachedContainer || allowRenderToDetachedContainer) + && !async + ) { renderingTemplate.render(templateOptions); } else { - this._delayedTemplates.push({ template: renderingTemplate, options: templateOptions, async }); + this._delayedTemplates.push({ + template: renderingTemplate, + options: templateOptions, + async, + }); } this._templateDeferreds.add(templateDeferred); @@ -686,24 +802,27 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { templateDeferred.reject(); } - return templateDeferred.promise().always(() => { + // @ts-expect-error promise() is typed as Promise but returns a Deferred-like value at runtime + const templatePromise: DeferredObj = templateDeferred.promise(); + + return templatePromise.always(() => { this._templateDeferreds.delete(templateDeferred); }); } - protected _getBodies(tableElement) { + protected _getBodies(tableElement: dxElementWrapper): dxElementWrapper { return $(tableElement).children('tbody').not('.dx-header').not('.dx-footer'); } - protected _needWrapRow() { + protected _needWrapRow(): boolean { return false; } - protected _wrapRowIfNeed($row) { + protected _wrapRowIfNeed($row: dxElementWrapper): dxElementWrapper { const needWrapRow = this._needWrapRow(); if (needWrapRow) { - const $tbody = $('').addClass($row.attr('class')); + const $tbody = $('').addClass($row.attr('class') ?? ''); this.setAria('role', 'presentation', $tbody); @@ -713,8 +832,11 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return $row; } - private _appendRow($table, $row, appendTemplate?) { - appendTemplate = appendTemplate || appendElementTemplate; + private _appendRow( + $table: dxElementWrapper, + $row: dxElementWrapper, + appendTemplate: AppendRowTemplate = appendElementTemplate, + ): void { appendTemplate.render({ content: $row, container: $table }); } @@ -734,14 +856,14 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing, filter_row, row_dragging, virtual_columns */ - protected _resizeCore() { + protected _resizeCore(): void { this.updateScrollLeftPosition(); } /** * @extended: column_fixing, header_panel, virtual_column */ - protected _renderCore(e?) { + protected _renderCore(e?: DataChange): DeferredObj { // @ts-expect-error the view is rendered here const $root = this.element().parent(); @@ -749,40 +871,41 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { this.renderDelayedTemplates(e); } - // @ts-expect-error - return new Deferred().resolve(); + return Deferred().resolve(); } /** * @extended: column_fixing */ - protected _renderTable(options) { - options = options || {}; - + protected _renderTable(options: TableRenderOptions = {}): dxElementWrapper { options.columns = this._columnsController.getVisibleColumns(); const changeType = options.change && options.change.changeType; - const $table = this._createTable(options.columns, changeType === 'append' || changeType === 'prepend' || changeType === 'update'); + const $table = this._createTable( + options.columns, + changeType === 'append' || changeType === 'prepend' || changeType === 'update', + ); this._renderRows($table, options); return $table; } - protected _renderRows($table, options) { - const that = this; - const rows = that._getRows(options.change); - const columnIndices = options.change && options.change.columnIndices || []; - const changeTypes = options.change && options.change.changeTypes || []; + protected _renderRows($table: dxElementWrapper, options: TableRenderOptions): void { + const rows = this._getRows(options.change); + const columnIndices = options.change?.columnIndices ?? []; + const changeTypes = options.change?.changeTypes ?? []; - for (let i = 0; i < rows.length; i++) { - that._renderRow($table, extend({ row: rows[i], columnIndices: columnIndices[i], changeType: changeTypes[i] }, options)); + for (let i = 0; i < rows.length; i += 1) { + this._renderRow($table, extend({ + row: rows[i], columnIndices: columnIndices[i], changeType: changeTypes[i], + }, options)); } } /** * @extended: column_fixing */ - protected _renderRow($table, options) { + protected _renderRow($table: dxElementWrapper, options: RowRenderOptions): void { if (!options.columnIndices) { options.row.cells = []; } @@ -793,48 +916,57 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { this._renderCells($row, options); } this._appendRow($table, $wrappedRow); - const rowOptions = extend({ columns: options.columns }, options.row); + const rowOptions: RowPreparedOptions = extend({ columns: options.columns }, options.row); this._addWatchMethod(rowOptions, options.row); this._rowPrepared($wrappedRow, rowOptions, options.row); } - protected _needRenderCell(columnIndex, columnIndices) { - return !columnIndices || columnIndices.indexOf(columnIndex) >= 0; + protected _needRenderCell(columnIndex: number, columnIndices?: number[]): boolean { + return !columnIndices || columnIndices.includes(columnIndex); } - protected _renderCells($row, options) { - const that = this; + protected _renderCells($row: dxElementWrapper, options: RowRenderOptions): void { let columnIndex = 0; const { row } = options; const { columns } = options; - for (let i = 0; i < columns.length; i++) { + for (let i = 0; i < columns.length; i += 1) { if (this._needRenderCell(i, options.columnIndices)) { - that._renderCell($row, extend({ - column: columns[i], columnIndex, value: row.values && row.values[columnIndex], oldValue: row.oldValues && row.oldValues[columnIndex], + this._renderCell($row, extend({ + column: columns[i], + columnIndex, + value: row.values?.[columnIndex], + oldValue: row.oldValues?.[columnIndex], }, options)); } - if (columns[i].colspan > 1) { - columnIndex += columns[i].colspan; + const { colspan } = columns[i]; + + if (isDefined(colspan) && colspan > 1) { + columnIndex += colspan; } else { - columnIndex++; + columnIndex += 1; } } } - protected _updateCells($rowElement, $newRowElement, columnIndices, options?) { - const that = this; + protected _updateCells( + $rowElement: dxElementWrapper, + $newRowElement: dxElementWrapper, + columnIndices: number[], + options?: ViewRow, + ): void { const $cells = $rowElement.children(); const $newCells = $newRowElement.children(); const highlightChanges = this.option('highlightChanges'); const cellUpdatedClass = this.addWidgetPrefix(CELL_UPDATED_ANIMATION_CLASS); if (options?.node?.hasChildren) { - $cells.each(function () { - that.setAria('expanded', options.isExpanded, $(this)); + // @ts-expect-error each() is typed for callbacks that return a boolean + $cells.each((_, cell) => { + this.setAria('expanded', options.isExpanded, $(cell)); }); } @@ -857,8 +989,8 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { */ protected _setCellAriaAttributes( $cell: dxElementWrapper, - cellOptions: { rowType: 'freeSpace' | 'group'; columnIndex: number }, - options?: any, + cellOptions: Pick, + options: CellRenderOptions, ): void { const { row } = options; const isFreeSpaceRow = cellOptions.rowType === 'freeSpace'; @@ -884,15 +1016,17 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { this.setAria('colindex', ariaColIndex, $cell); } - protected _renderCell($row, options) { + protected _renderCell($row: dxElementWrapper, options: CellRenderOptions): dxElementWrapper { const cellOptions = this._getCellOptions(options); if (options.columnIndices) { if (options.row.cells) { - const cellIndex = options.row.cells.findIndex((cell) => cell.columnIndex === cellOptions.columnIndex); + const cellIndex = options.row.cells + .findIndex((cell) => cell.columnIndex === cellOptions.columnIndex); options.row.cells[cellIndex] = cellOptions; } } else { + // @ts-expect-error _renderRow creates the cells when columnIndices aren't set options.row.cells.push(cellOptions); } @@ -910,24 +1044,30 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing, editing_form_based, filter_row, header_filter */ - protected _renderCellContent($cell, options, renderOptions) { + protected _renderCellContent( + $cell: dxElementWrapper, + options: ViewCellOptions, + renderOptions: CellRenderOptions, + ): void { const template = this._getCellTemplate(options); - when(!template || this.renderTemplate($cell, template, options, undefined, renderOptions.change)).done(() => { + when( + !template || this.renderTemplate($cell, template, options, undefined, renderOptions.change), + ).done(() => { this._updateCell($cell, options); }); } - protected _getCellTemplate(options?): any { - + protected _getCellTemplate(options?: ViewCellOptions): ColumnTemplateSource | undefined { + return undefined; } - protected _getRows(change?) { - return [] as any[]; + protected _getRows(change?: ViewDataChange): ViewRow[] { + return []; } - protected _getCellOptions(options): any { - const cellOptions = { + protected _getCellOptions(options: CellRenderOptions): ViewCellOptions { + const cellOptions: ViewCellOptions = { column: options.column, columnIndex: options.columnIndex, rowType: options.row.rowType, @@ -940,21 +1080,26 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return cellOptions; } - public _addWatchMethod(options, source?) { - if (!this.option('repaintChangesOnly')) return; - - const watchers: any[] = []; + public _addWatchMethod( + options: WatchableOptions, + source: WatchableOptions = options, + ): WatchableOptions | undefined { + if (!this.option('repaintChangesOnly')) { + return undefined; + } - source = source || options; + const watchers: ((row?: ProcessedItem) => void)[] = []; - source.watch = source.watch || function (getter, updateValueFunc, updateRowFunc) { + source.watch ??= function watch(getter, updateValueFunc, updateRowFunc): () => void { + // @ts-expect-error watch is used for data rows, which have data let oldValue = getter(source.data); - const watcher = function (row) { + const watcher = function watcher(row?: ProcessedItem): void { if (row && updateRowFunc) { updateRowFunc(row); } + // @ts-expect-error watch is used for data rows, which have data const newValue = getter(source.data); if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) { @@ -967,7 +1112,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { watchers.push(watcher); - const stopWatch = function () { + const stopWatch = function stopWatch(): void { const index = watchers.indexOf(watcher); if (index >= 0) { watchers.splice(index, 1); @@ -977,12 +1122,16 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return stopWatch; }; - source.update = source.update || function (row, keepRow) { + source.update ??= function update(this: WatchableOptions, row, keepRow): void { if (row) { - this.data = options.data = row.data; - this.rowIndex = options.rowIndex = row.rowIndex; - this.dataIndex = options.dataIndex = row.dataIndex; - this.isExpanded = options.isExpanded = row.isExpanded; + options.data = row.data; + this.data = options.data; + options.rowIndex = row.rowIndex; + this.rowIndex = options.rowIndex; + options.dataIndex = row.dataIndex; + this.dataIndex = options.dataIndex; + options.isExpanded = row.isExpanded; + this.isExpanded = options.isExpanded; if (options.row && !keepRow) { options.row = row; @@ -1004,23 +1153,24 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: adaptivity, editing, validating */ - public _cellPrepared(cell, options) { + public _cellPrepared(cell: dxElementWrapper, options: ViewCellOptions): void { options.cellElement = getPublicElement($(cell)); this.executeAction('onCellPrepared', options); } - protected _rowPrepared($row, options, row?) { + protected _rowPrepared($row: dxElementWrapper, options: RowPreparedOptions, row?: ViewRow): void { elementData($row.get(0), 'options', options); options.rowElement = getPublicElement($row); this.executeAction('onRowPrepared', options); } - protected _columnOptionChanged(e) { + protected _columnOptionChanged(e: ColumnsChanges): void { const { optionNames } = e; if (gridCoreUtils.checkChanges(optionNames, ['width', 'visibleWidth'])) { const visibleColumns = this._columnsController.getVisibleColumns(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back const widths = visibleColumns.map((column) => column.visibleWidth || column.width); this.setColumnWidths({ widths, optionNames }); @@ -1035,8 +1185,8 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing, editing */ - public getCellIndex($cell, rowIndex?) { - const cellIndex = $cell.length ? $cell[0].cellIndex : -1; + public getCellIndex($cell: dxElementWrapper, rowIndex?: number): number { + const cellIndex: number = $cell.length ? $cell[0].cellIndex : -1; return cellIndex; } @@ -1044,33 +1194,36 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing */ - public getTableElements() { - return this._tableElement || $(); + public getTableElements(): dxElementWrapper { + return this._tableElement ?? $(); } /** * @extended: column_fixing */ - public getTableElement(isFixedTableRendering?): dxElementWrapper | undefined { + public getTableElement(isFixedTableRendering?: boolean): dxElementWrapper | null | undefined { return this._tableElement; } /** * @extended: column_fixing */ - public setTableElement(tableElement, isFixedTableRendering?) { + public setTableElement( + tableElement: dxElementWrapper | null | undefined, + isFixedTableRendering?: boolean, + ): void { this._tableElement = tableElement; } - protected _afterRowPrepared(e?) {} + protected _afterRowPrepared(e?: Record): void {} /** * @extended: header_panel */ - protected _handleDataChanged(e) { + protected _handleDataChanged(e: DataChange): void { } - public callbackNames() { + public callbackNames(): string[] { return ['scrollChanged']; } @@ -1083,24 +1236,27 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } } - public scrollTo(pos) { + public scrollTo(pos: ScrollPosition): void { const $element = this.element(); - const $scrollContainer = $element && $element.children(`.${this.addWidgetPrefix(SCROLL_CONTAINER_CLASS)}`).not(`.${this.addWidgetPrefix(CONTENT_FIXED_CLASS)}`); + const $scrollContainer = $element + ?.children(`.${this.addWidgetPrefix(SCROLL_CONTAINER_CLASS)}`) + .not(`.${this.addWidgetPrefix(CONTENT_FIXED_CLASS)}`); if (isDefined(pos) && isDefined(pos.left) && this._scrollLeft !== pos.left) { this._scrollLeft = pos.left; - $scrollContainer && $scrollContainer.scrollLeft(pos.left); + // @ts-expect-error scrollLeft() is typed for string values + $scrollContainer?.scrollLeft(pos.left); } } /** * @extended: column_fixing */ - public getContent(isFixedTableRendering?) { + public getContent(isFixedTableRendering?: boolean): dxElementWrapper | undefined { return this._tableElement?.parent(); } - private _removeContent(isFixedTableRendering) { + private _removeContent(isFixedTableRendering: boolean | undefined): void { const $scrollContainer = this.getContent(isFixedTableRendering); if ($scrollContainer?.length) { @@ -1109,7 +1265,8 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } protected handleScroll(e: DxEvent): void { - const scrollLeft = $(e.target).scrollLeft(); + // @ts-expect-error scrollLeft() is typed as a setter only + const scrollLeft: number = $(e.target).scrollLeft(); if (scrollLeft !== this._scrollLeft) { this.scrollChanged.fire({ left: scrollLeft }, this.name); @@ -1119,7 +1276,10 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: column_fixing */ - protected _wrapTableInScrollContainer($table, isFixedTableRendering?) { + protected _wrapTableInScrollContainer( + $table: dxElementWrapper, + isFixedTableRendering?: boolean, + ): dxElementWrapper { const $scrollContainer = $('
'); const useNative = this.option('scrolling.useNative'); @@ -1139,7 +1299,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return $scrollContainer; } - private needWaitAsyncTemplates() { + private needWaitAsyncTemplates(): boolean | undefined { return this.option('templatesRenderAsynchronously') && this.option('renderAsync') === false; } @@ -1147,16 +1307,16 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return !!this.needWaitAsyncTemplates() && this._templateDeferreds?.size > 0; } - public waitAsyncTemplates(forceWaiting = false) { - // @ts-expect-error - const result = new Deferred(); + public waitAsyncTemplates(forceWaiting = false): DeferredObj { + const result = Deferred(); const needWaitAsyncTemplates = forceWaiting || this.needWaitAsyncTemplates(); if (!needWaitAsyncTemplates || !isDefined(this._templateDeferreds)) { return result.resolve(); } - const waitTemplatesRecursion = () => when.apply(this, Array.from(this._templateDeferreds)) + const waitTemplatesRecursion = (): DeferredObj => when + .apply(this, Array.from(this._templateDeferreds)) .done(() => { if (this.isDisposed()) { result.reject(); @@ -1165,17 +1325,23 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } else { result.resolve(); } + // eslint-disable-next-line @typescript-eslint/no-misused-promises -- fail ignores the result }).fail(result.reject); waitTemplatesRecursion(); + // @ts-expect-error promise() is typed as Promise but returns a Deferred-like value at runtime return result.promise(); } /** * @extended: sticky_columns, rows_view */ - protected _updateContent($newTableElement, change, isFixedTableRendering?) { + protected _updateContent( + $newTableElement: dxElementWrapper, + change?: ViewDataChange, + isFixedTableRendering?: boolean, + ): DeferredObj { return this.waitAsyncTemplates().done(() => { this._removeContent(isFixedTableRendering); this.setTableElement($newTableElement, isFixedTableRendering); @@ -1183,7 +1349,9 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { }); } - public _findContentElement(isFixedTableRendering?: any): any {} + public _findContentElement(isFixedTableRendering?: boolean): dxElementWrapper | undefined { + return undefined; + } public _getWidths($cellElements?: dxElementWrapper): number[] { if (!$cellElements) { @@ -1196,7 +1364,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { (cellElements as HTMLElement[]).forEach((cell) => { let width = cell.offsetWidth; - if ((cell as any).getBoundingClientRect) { + if ((cell as Partial).getBoundingClientRect) { const rect = getBoundingRect(cell); if (rect.width > cell.offsetWidth - 1) { @@ -1214,14 +1382,14 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { * @extended: column_fixing */ public getColumnWidths($tableElement?: dxElementWrapper, rowIndex?: number): number[] { - (this.option('forceApplyBindings') || noop)(); + (this.option('forceApplyBindings') ?? noop)(); - $tableElement = $tableElement ?? this.getTableElement(); + const $table = $tableElement ?? this.getTableElement(); - if ($tableElement) { - const $rows = $tableElement.children('tbody:not(.dx-header)').children(); + if ($table) { + const $rows = $table.children('tbody:not(.dx-header)').children(); - for (let i = 0; i < $rows.length; i++) { + for (let i = 0; i < $rows.length; i += 1) { const $row = $rows.eq(i); const isGroupRow = $row.hasClass(GROUP_ROW_CLASS); @@ -1244,15 +1412,15 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return []; } - protected getVisibleColumnIndex(columnIndex, rowIndex) { + protected getVisibleColumnIndex(columnIndex: number, rowIndex?: number): number { return columnIndex; } private setCellPropertiesCore( - styleProps: CSSStyleDeclaration, + styleProps: Partial, $row: dxElementWrapper, visibleCellIndex: number, - ) { + ): void { const $cell = $row.hasClass(GROUP_ROW_CLASS) ? $row.find(`td[aria-colindex='${visibleCellIndex + 1}']:not(.${GROUP_CELL_CLASS})`) : $row.find('td').eq(visibleCellIndex); @@ -1265,10 +1433,10 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } protected setCellProperties( - styleProps: CSSStyleDeclaration, + styleProps: Partial, columnIndex: number, rowIndex?: number, - ) { + ): void { const $tableElement = this.getTableElement(); if (!$tableElement?.length) { @@ -1280,17 +1448,17 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { if (isDefined(rowIndex)) { this.setCellPropertiesCore(styleProps, $rows.eq(rowIndex), columnIndex); } else { - for (let rowIndex = 0; rowIndex < $rows.length; rowIndex++) { - const visibleIndex = this.getVisibleColumnIndex(columnIndex, rowIndex); + for (let currentRowIndex = 0; currentRowIndex < $rows.length; currentRowIndex += 1) { + const visibleIndex = this.getVisibleColumnIndex(columnIndex, currentRowIndex); if (visibleIndex >= 0) { - this.setCellPropertiesCore(styleProps, $rows.eq(rowIndex), visibleIndex); + this.setCellPropertiesCore(styleProps, $rows.eq(currentRowIndex), visibleIndex); } } } } - protected setColumnWidths({ widths, optionNames }: any): void { + protected setColumnWidths({ widths, optionNames }: ColumnWidthsOptions): void { const $tableElement = this.getTableElement(); if (!$tableElement?.length || !widths) { @@ -1312,8 +1480,10 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { Also check _createCell method because min-width, width and max-width are also set there. */ if (needToSetCellWidths && column.width && !column.command) { - const styleProps: any = {}; + const styleProps: Partial = {}; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back const width = getWidthStyle(column.visibleWidth || column.width); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 0 falls back const minWidth = getWidthStyle(column.minWidth || width); styleProps.width = column.width === 'auto' ? '' : width; @@ -1334,11 +1504,11 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: editing_form_based */ - public getCellElements(rowIndex): dxElementWrapper | undefined { + public getCellElements(rowIndex: number): dxElementWrapper | undefined { return this._getCellElementsCore(rowIndex); } - protected _getCellElementsCore(rowIndex): dxElementWrapper | undefined { + protected _getCellElementsCore(rowIndex: number): dxElementWrapper | undefined { if (rowIndex < 0) { return undefined; } @@ -1351,7 +1521,10 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { /** * @extended: adaptivity */ - public _getCellElement(rowIndex, columnIdentifier): dxElementWrapper | undefined { + public _getCellElement( + rowIndex: number, + columnIdentifier: string | number, + ): dxElementWrapper | undefined { const $cells = this.getCellElements(rowIndex) ?? $(); const columnVisibleIndex = this._getVisibleColumnIndex($cells, rowIndex, columnIdentifier); @@ -1364,13 +1537,12 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return $cell.length > 0 ? $cell : undefined; } - private _getRowElement(rowIndex) { - const that = this; + private _getRowElement(rowIndex: number): dxElementWrapper | undefined { let $rowElement = $(); - const $tableElements = that.getTableElements(); + const $tableElements = this.getTableElements(); iteratorUtils.each($tableElements, (_, tableElement) => { - $rowElement = $rowElement.add(that._getRowElements($(tableElement)).eq(rowIndex)); + $rowElement = $rowElement.add(this._getRowElements($(tableElement)).eq(rowIndex)); }); if ($rowElement.length) { @@ -1379,7 +1551,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return undefined; } - private getCellElement(rowIndex, columnIdentifier): Element | undefined { + private getCellElement(rowIndex: number, columnIdentifier: string | number): Element | undefined { const $cell = this._getCellElement(rowIndex, columnIdentifier); if ($cell) { @@ -1389,14 +1561,14 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return undefined; } - public getRowElement(rowIndex) { + public getRowElement(rowIndex: number): Element[] | dxElementWrapper | undefined { const $rows = this._getRowElement(rowIndex); - let elements: any = []; + let elements: Element[] | dxElementWrapper | undefined = []; - // @ts-expect-error + // @ts-expect-error getPublicElement returns a jQuery object when jQuery is used if ($rows && !getPublicElement($rows).get) { - for (let i = 0; i < $rows.length; i++) { - elements.push($rows[i]); + for (const row of $rows.toArray()) { + elements.push(row); } } else { elements = $rows; @@ -1420,67 +1592,87 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return columnIdentifier; } - public getColumnElements(): any {} + public getColumnElements(): dxElementWrapper | undefined { + return undefined; + } - public getColumns(rowIndex?, $tableElement?) { + public getColumns(rowIndex?: number | null, $tableElement?: dxElementWrapper): Column[] { return this._columnsController.getVisibleColumns(rowIndex); } /** * @extended: adaptivity */ - public getCell(cellPosition, rows?, cells?) { - const $rows = rows || this._getRowElements(); - let $cells; + public getCell( + cellPosition: CellPosition, + rows?: dxElementWrapper, + cells?: dxElementWrapper, + ): dxElementWrapper | undefined { + const $rows = rows ?? this._getRowElements(); if ($rows.length > 0 && cellPosition.rowIndex >= 0) { - if (this.option('scrolling.mode') !== 'virtual' && this.option('scrolling.rowRenderingMode') !== 'virtual') { - cellPosition.rowIndex = cellPosition.rowIndex < $rows.length ? cellPosition.rowIndex : $rows.length - 1; + if ( + this.option('scrolling.mode') !== 'virtual' + && this.option('scrolling.rowRenderingMode') !== 'virtual' + ) { + cellPosition.rowIndex = cellPosition.rowIndex < $rows.length + ? cellPosition.rowIndex + : $rows.length - 1; } - $cells = cells || this.getCellElements(cellPosition.rowIndex); - if ($cells?.length > 0) { - return $cells.eq($cells.length > cellPosition.columnIndex ? cellPosition.columnIndex : $cells.length - 1); + const $cells = cells ?? this.getCellElements(cellPosition.rowIndex); + if ($cells && $cells.length > 0) { + return $cells.eq( + $cells.length > cellPosition.columnIndex ? cellPosition.columnIndex : $cells.length - 1, + ); } } + + return undefined; } - private getRowsCount() { + private getRowsCount(): number { const tableElement = this.getTableElement(); - if (tableElement && tableElement.length === 1) { - return tableElement[0].rows.length; + if (tableElement?.length === 1) { + const table: HTMLTableElement = tableElement[0]; + + return table.rows.length; } return 0; } - protected _getRowElementsCore(tableElement?) { - tableElement = tableElement || this.getTableElement(); + protected _getRowElementsCore(tableElement?: dxElementWrapper): dxElementWrapper { + const $table = tableElement ?? this.getTableElement(); - if (tableElement) { + if ($table) { const hasDataRowTemplate = !!this.option('dataRowTemplate'); - const tBodies = hasDataRowTemplate && tableElement.find(`> tbody.${ROW_CLASS}`); + const tBodies = hasDataRowTemplate && $table.find(`> tbody.${ROW_CLASS}`); // eslint-disable-next-line no-useless-concat - return tBodies && tBodies.length ? tBodies : tableElement.find('> tbody > ' + `.${ROW_CLASS}, > .${ROW_CLASS}`); + return tBodies && tBodies.length ? tBodies : $table.find('> tbody > ' + `.${ROW_CLASS}, > .${ROW_CLASS}`); } return $(); } - public _getRowElements(tableElement?) { + public _getRowElements(tableElement?: dxElementWrapper): dxElementWrapper { return this._getRowElementsCore(tableElement); } /** * @extended: column_fixing */ - public getRowIndex($row) { + public getRowIndex($row: Element | dxElementWrapper): number { return this._getRowElements().index($row); } - protected getBoundingRect() { } + protected getBoundingRect(): BoundingRect | null | undefined { + return undefined; + } - public getName() { } + public getName(): string | undefined { + return undefined; + } /** * @extended: column_fixing @@ -1493,18 +1685,20 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { .css('paddingInlineEnd', width ? `${width}px` : ''); } - protected isScrollbarVisible(isHorizontal) { + protected isScrollbarVisible(isHorizontal: boolean): boolean { const $element = this.element(); const $tableElement = this._tableElement; if ($element && $tableElement) { - return isHorizontal ? getOuterWidth($tableElement) - getWidth($element) > 0 : getOuterHeight($tableElement) - getHeight($element) > 0; + return isHorizontal + ? getOuterWidth($tableElement) - getWidth($element) > 0 + : getOuterHeight($tableElement) - getHeight($element) > 0; } return false; } - public isDisposed() { + public isDisposed(): boolean | undefined { return this.component?._disposed; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts index 40ca6e6d8e14..1a6fc3b59e04 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts @@ -9,6 +9,7 @@ import $ from '@js/core/renderer'; import browser from '@js/core/utils/browser'; import { deferRender, deferUpdate } from '@js/core/utils/common'; import { compileGetter } from '@js/core/utils/data'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getBoundingRect, getDefaultAlignment } from '@js/core/utils/position'; @@ -23,7 +24,7 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsResizerViewController, } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; -import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import type { DataChange, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import { isSameItem } from '@ts/grids/grid_core/data_controller/utils/row_changes'; import { generateRowValues } from '@ts/grids/grid_core/data_controller/utils/row_values'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; @@ -461,7 +462,6 @@ export class RowsView extends ColumnsView { protected _updateContent(newTableElement, change, isFixedTableRendering?) { this._contentChanges.push({ newTableElement, change, isFixedTableRendering }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return this.waitAsyncTemplates().done(() => { const contentChanges = this._contentChanges; @@ -896,7 +896,7 @@ export class RowsView extends ColumnsView { /** * @extended: column_fixing, editing, keyboard_navigation, row_dragging, search, selection, virtual_column, virtual_scrolling */ - protected _renderCore(change) { + protected _renderCore(change?: DataChange): DeferredObj { // @ts-expect-error the view is rendered here const $element: dxElementWrapper = this.element(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/types.ts b/packages/devextreme/js/__internal/grids/grid_core/views/types.ts index a7688501498e..93e080e0ff39 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/types.ts @@ -1,7 +1,17 @@ +import type { template } from '@js/common'; +import type { DxElement } from '@js/core/element'; import type { dxElementWrapper } from '@js/core/renderer'; +import type { Callback } from '@js/core/utils/callbacks'; import type { DeferredObj } from '@js/core/utils/deferred'; -import type { ScrollEventInfo } from '@js/ui/scroll_view/ui.scrollable'; +import type { DxEvent } from '@js/events'; +import type { dxScrollableOptions, ScrollEventInfo } from '@js/ui/scroll_view/ui.scrollable'; import type dxScrollable from '@js/ui/scroll_view/ui.scrollable'; +import type { Column, ColumnsChanges } from '@ts/grids/grid_core/columns_controller/types'; +import type { + Cell, DataChange, RowChangeType, RowUpdate, RowWatch, UpdateChange, +} from '@ts/grids/grid_core/data_controller/types'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; +import type { InternalGrid, RowKey } from '@ts/grids/grid_core/types'; export type RowsViewScrollEvent = Partial> & { component: dxScrollable; @@ -13,13 +23,169 @@ export type ColumnWidth = number | string | undefined; export interface ColumnViewTemplateOptions { container: dxElementWrapper; - model: unknown; + model: TemplateModel; deferred?: DeferredObj; onRendered?: () => void; - change?: unknown; + change?: DataChange; } export interface ColumnViewTemplate { allowRenderToDetachedContainer?: boolean; render: (options: ColumnViewTemplateOptions) => void; } + +export interface ColumnRenderTemplate { + allowRenderToDetachedContainer?: boolean; + render: (container: dxElementWrapper, model: TemplateModel, change?: DataChange) => void; +} + +export type ColumnTemplateSource = template | ColumnRenderTemplate; + +export interface DelayedTemplate { + template: ColumnViewTemplate; + options: ColumnViewTemplateOptions; + async?: boolean; +} + +export interface AppendRowTemplate { + render: (options: { content: dxElementWrapper; container: dxElementWrapper }) => void; +} + +export interface TemplateModel { + column?: Column; + rowType?: string; + renderAsync?: boolean; + component?: InternalGrid; +} + +export type ScrollableOptions = Omit, 'onScroll'> & { + useSimulatedScrollbar?: boolean; + onScroll?: (e: RowsViewScrollEvent) => void; +}; + +export interface ScrollPosition { + left?: number; + top?: number; +} + +export interface BoundingRect { + top?: number; + left?: number; + right?: number; + bottom?: number; +} + +export interface CellPosition { + rowIndex: number; + columnIndex: number; +} + +export type ViewDataChange = DataChange & Partial>; + +/** @architectureLeak data_grid: the cell hint skips group cells that have a template */ +export type HintColumn = Column & { groupCellTemplate?: unknown }; + +export interface ViewRowCell extends Cell { + columnIndex?: number; +} + +export interface ViewRow { + rowType: string; + rowIndex?: number; + dataIndex?: number; + key?: RowKey; + data?: RawItemData; + values?: unknown[]; + oldValues?: unknown[]; + cells?: ViewRowCell[]; + isExpanded?: boolean; + isEditing?: boolean; + /** @architectureLeak tree_list: core sets aria-expanded for rows of a tree node */ + node?: { hasChildren?: boolean }; + watch?: RowWatch; + update?: RowUpdate; +} + +export interface WatchableOptions { + data?: RawItemData; + rowIndex?: number; + dataIndex?: number; + isExpanded?: boolean; + row?: ViewRow; + watch?: RowWatch; + update?: RowUpdate; +} + +export interface ViewCellOptions { + column: Column; + columnIndex: number; + rowType: string; + rowIndex?: number; + isAltRow?: boolean; + columns?: Column[]; + row?: ViewRow; + key?: RowKey; + data?: RawItemData; + value?: unknown; + oldValue?: unknown; + displayValue?: unknown; + text?: string; + values?: unknown[]; + summaryItems?: unknown; + resized?: Callback<[number]>; + groupContinuesMessage?: string; + groupContinuedMessage?: string; + isEditing?: boolean; + removed?: boolean; + modified?: boolean; + watch?: RowWatch; + update?: RowUpdate; + cellElement?: DxElement; + component?: InternalGrid; + renderAsync?: boolean; +} + +export interface CellEventOptions extends Partial { + cellElement: DxElement; + event: DxEvent; + eventType: string; +} + +export interface RowPreparedOptions extends ViewRow { + columns?: Column[]; + rowElement?: DxElement; +} + +export interface TableRenderOptions { + change?: ViewDataChange; + columns?: Column[]; +} + +export interface RowRenderOptions { + change?: ViewDataChange; + columns: Column[]; + row: ViewRow; + columnIndices?: number[]; + changeType?: RowChangeType; +} + +export interface CellRenderOptions extends Omit { + columns?: Column[]; + column: Column; + columnIndex: number; + rowIndex?: number; + value?: unknown; + oldValue?: unknown; +} + +export interface ViewRowEvent { + event: DxEvent; + rowIndex: number; + rowElement?: DxElement; + columns?: Column[]; +} + +export interface ColumnWidthsOptions { + widths?: ColumnWidth[]; + optionNames?: ColumnsChanges['optionNames']; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_columns/m_virtual_columns.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_columns/m_virtual_columns.ts index 53761b69b807..cfca139e3831 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_columns/m_virtual_columns.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_columns/m_virtual_columns.ts @@ -1,8 +1,11 @@ /* eslint-disable max-classes-per-file */ +import type { dxElementWrapper } from '@js/core/renderer'; import browser from '@js/core/utils/browser'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { getHeight, getOuterWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import { hasWindow } from '@js/core/utils/window'; +import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; import type { ColumnsController } from '../columns_controller/columns_controller'; @@ -47,9 +50,10 @@ const rowsView = (Base: ModuleType) => class VirtualColumnsRowsViewExt this._columnsController.setScrollPosition(left, e.event); } - protected _renderCore(e) { - if (e?.virtualColumnsScrolling) { - const $contentElement = this._findContentElement(); + protected _renderCore(e?: DataChange): DeferredObj { + if (e && 'virtualColumnsScrolling' in e && e.virtualColumnsScrolling) { + // @ts-expect-error _findContentElement() is typed as possibly undefined + const $contentElement: dxElementWrapper = this._findContentElement(); const fixedColumns = this._columnsController?.getFixedColumns(); const useNativeScrolling = this._scrollable?.option('useNative'); const legacyMode = this.option('columnFixing.legacyMode'); @@ -62,7 +66,7 @@ const rowsView = (Base: ModuleType) => class VirtualColumnsRowsViewExt $contentElement.css({ minHeight: getHeight($contentElement) }); } - const resizeCompletedHandler = () => { + const resizeCompletedHandler = (): void => { this.resizeCompleted.remove(resizeCompletedHandler); $contentElement.css({ minHeight: '' }); }; @@ -71,8 +75,7 @@ const rowsView = (Base: ModuleType) => class VirtualColumnsRowsViewExt } } - // @ts-expect-error - return super._renderCore.apply(this, arguments); + return super._renderCore(e); } }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts index 1fff137a702a..0a28876f3066 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts @@ -11,6 +11,7 @@ import { getWindow } from '@js/core/utils/window'; import LoadIndicator from '@js/ui/load_indicator'; import errors from '@js/ui/widget/ui.errors'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; @@ -583,10 +584,10 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo super.renderDelayedTemplates.apply(this, arguments as any); } - protected _renderCore(e) { + protected _renderCore(e?: DataChange): DeferredObj { const startRenderTime = Date.now(); - const deferred = super._renderCore.apply(this, arguments as any); + const deferred = super._renderCore(e); const dataSourceAdapter = this.dataSourceController.getAdapter(); @@ -596,8 +597,12 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo // @ts-expect-error .viewportSize() || 20; - if (gridCoreUtils.isVirtualRowRendering(this) && itemCount > 0 && this.option(LEGACY_SCROLLING_MODE) !== false) { - dataSourceAdapter._renderTime = (Date.now() - startRenderTime) * viewportSize / itemCount; + if ( + gridCoreUtils.isVirtualRowRendering(this) + && itemCount > 0 + && this.option(LEGACY_SCROLLING_MODE) !== false + ) { + dataSourceAdapter._renderTime = ((Date.now() - startRenderTime) * viewportSize) / itemCount; } else { dataSourceAdapter._renderTime = Date.now() - startRenderTime; } @@ -777,6 +782,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo removeEmptyRows($virtualRows, VIRTUAL_ROW_CLASS); + // @ts-expect-error each() is typed for callbacks that return a boolean $tables.each((index, element) => { const isFixed = index > 0; const prevFixed = this._isFixedTableRendering; From eec50b520d3c92c0afa00bc438d076de6c81f53f Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:59:25 +0200 Subject: [PATCH 3/3] rebase and resolve conflicts --- .../grids/grid_core/row_dragging/m_row_dragging.ts | 8 ++++---- .../js/__internal/grids/grid_core/views/m_columns_view.ts | 2 +- .../js/__internal/grids/grid_core/views/m_rows_view.ts | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/row_dragging/m_row_dragging.ts b/packages/devextreme/js/__internal/grids/grid_core/row_dragging/m_row_dragging.ts index e1a0e89e1361..663aaf1e7d62 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/row_dragging/m_row_dragging.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/row_dragging/m_row_dragging.ts @@ -1,6 +1,7 @@ -import $ from '@js/core/../core/renderer'; -import { extend } from '@js/core/../core/utils/extend'; +import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { extend } from '@js/core/utils/extend'; import { getWidth, setWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import Sortable from '@js/ui/sortable'; @@ -150,7 +151,7 @@ const rowsView = (Base: ModuleType) => class RowsViewRowDraggingExtend return $content; } - protected _renderCore(e?: DataChange): Deferred { + protected _renderCore(e?: DataChange): DeferredObj { const deferred = super._renderCore(e); if (e?.changeType === 'update' @@ -162,7 +163,6 @@ const rowsView = (Base: ModuleType) => class RowsViewRowDraggingExtend }); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return deferred; } 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 c5cc6491181d..032f186e60d9 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 @@ -259,7 +259,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { protected _columnChooserView!: ColumnChooserView; - public init() { + public init(): void { this._scrollLeft = undefined; this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts index 1a6fc3b59e04..610d2b4c6fa4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts @@ -120,6 +120,8 @@ export class RowsView extends ColumnsView { public _scrollTop: any; + protected declare _scrollLeft: number; + private _scrollRight: any; public _scrollable: any; @@ -440,7 +442,7 @@ export class RowsView extends ColumnsView { private isRenderedLayoutCurrent(): boolean { const items = this._dataController.items(); - const rowElements: HTMLElement[] = this._getRowElements().toArray(); + const rowElements = this._getRowElements().toArray() as HTMLElement[]; const renderedRows: (ProcessedItem & { columns: Column[] })[] = rowElements.map( (rowElement) => elementData(rowElement, 'options'), );