From a39dcccf3dc8c1749f40c4c4e46dd72be8a149d1 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:27:39 +0200 Subject: [PATCH 1/3] Grids: type and refactor m_header_panel --- .../grid_core/header_panel/m_header_panel.ts | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) 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 1f0803ce7f2d..397cf1fd6cbc 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 @@ -1,10 +1,12 @@ /* eslint-disable max-classes-per-file */ import messageLocalization from '@js/common/core/localization/message'; +import type { dxElementWrapper } from '@js/core/renderer'; 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 { ToolbarItem as DataGridToolbarItem } from '@js/ui/data_grid'; import type { Properties as ToolbarProperties } from '@js/ui/toolbar'; import Toolbar from '@js/ui/toolbar'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; @@ -12,9 +14,11 @@ import type { HeaderFilterController } from '@ts/grids/grid_core/header_filter/m import type { DefaultToolbarItem, ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { normalizeToolbarItems } from '@ts/grids/new/grid_core/toolbar/utils'; -import type { ModuleType } from '../types'; +import type { Column } from '../columns_controller/types'; +import type { InternalGridOptions, ModuleType, OptionChanged } from '../types'; import { ColumnsView } from '../views/columns_view'; import type { ResizingController } from '../views/grid_view'; +import type { ContextMenuItemsOptions } from '../views/types'; const HEADER_PANEL_CLASS = 'header-panel'; const TOOLBAR_BUTTON_CLASS = 'toolbar-button'; @@ -118,7 +122,7 @@ export class HeaderPanel extends ColumnsView { return [...items].sort((a, b) => (a.sortIndex ?? 0) - (b.sortIndex ?? 0)); } - private _getButtonContainer() { + private _getButtonContainer(): dxElementWrapper { return $('
').addClass(this.addWidgetPrefix(TOOLBAR_BUTTON_CLASS)); } @@ -176,35 +180,38 @@ export class HeaderPanel extends ColumnsView { this.setAria('label', label, $toolbar); this._toolbar = this._createComponent($toolbar, Toolbar, this._toolbarOptions); } else { - this._toolbar.option(this._toolbarOptions!); + // @ts-expect-error render() sets _toolbarOptions first + this._toolbar.option(this._toolbarOptions); } return Deferred().resolve(); } - protected _columnOptionChanged() { + protected _columnOptionChanged(): void { } - protected _handleDataChanged() { + protected _handleDataChanged(): void { if (this._requireReady) { this.render(); } } private _isDisabledDefinedByUser(name: string): boolean { - const userItems = (this.option('toolbar') as any)?.items; - const userItem = userItems?.find((item) => item?.name === name); + const userItems = this.option('toolbar')?.items; + const userItem = userItems?.find( + (item): item is DataGridToolbarItem => typeof item !== 'string' && item?.name === name, + ); return isDefined(userItem?.disabled); } - public render() { + public render($parent?: dxElementWrapper | null, options?: unknown): void { this._toolbarOptions = this._getToolbarOptions(); - super.render.apply(this, arguments as any); + super.render($parent, options); } - public setToolbarItemDisabled(name, disabled: boolean): void { + public setToolbarItemDisabled(name: string, disabled: boolean): void { const toolbar = this._toolbar; const isDefinedByUser = this._isDisabledDefinedByUser(name); @@ -219,7 +226,8 @@ export class HeaderPanel extends ColumnsView { return; } - const item: any = toolbar.option(`items[${itemIndex}]`); + // @ts-expect-error option() returns unknown for an item path + const item: ToolbarItem = toolbar.option(`items[${itemIndex}]`); toolbar.option(`items[${itemIndex}].disabled`, disabled); @@ -228,91 +236,92 @@ export class HeaderPanel extends ColumnsView { } } - public updateToolbarDimensions() { - (this._toolbar as any)?.updateDimensions(); + public updateToolbarDimensions(): void { + // @ts-expect-error updateDimensions is an internal method not in the public dxToolbar API + this._toolbar?.updateDimensions(); } - private getHeaderPanel() { + private getHeaderPanel(): dxElementWrapper | undefined { return this.element(); } - private getHeight() { + private getHeight(): number { return this.getElementHeight(); } - public optionChanged(args) { + public optionChanged(args: OptionChanged): void { if (args.name === 'onToolbarPreparing') { this._invalidate(); args.handled = true; } + if (args.name === 'toolbar') { const parts = getPathParts(args.fullName); const optionName = args.fullName.replace(/^toolbar\./, ''); + const isItemsOption = parts[1] === 'items'; - if (parts.length === 1 || parts[1] === 'visible') { - // `toolbar`, `toolbar.visible` case + const isToolbarReplaced = parts.length === 1; + const isVisibleChanged = parts[1] === 'visible'; + const areItemsReplaced = isItemsOption && parts.length === 2; + const isItemReplaced = isItemsOption && parts.length === 3; + + if (isToolbarReplaced || isVisibleChanged || areItemsReplaced) { this._invalidate(); - } else if (parts[1] === 'items') { - if (parts.length === 2) { - // `toolbar.items` case - this._invalidate(); - } else if (parts.length === 3) { - // `toolbar.items[i]` case - const normalizedItem = normalizeToolbarItems( - this._getToolbarItems() as DefaultToolbarItem[], - [args.value], - DEFAULT_TOOLBAR_ITEM_NAMES, - )[0]; - this._toolbar?.option(optionName, normalizedItem); - } else if (parts.length >= 4) { - // `toolbar.items[i].prop` case - this._toolbar?.option(optionName, args.value); - } + } else if (isItemReplaced) { + const normalizedItem = normalizeToolbarItems( + this._getToolbarItems() as DefaultToolbarItem[], + // @ts-expect-error OptionChanged has no paths for toolbar items by index + [args.value], + DEFAULT_TOOLBAR_ITEM_NAMES, + )[0]; + this._toolbar?.option(optionName, normalizedItem); } else { - // `toolbar.disabled` case this._toolbar?.option(optionName, args.value); } args.handled = true; } + super.optionChanged(args); } /** * @extended: column_chooser, editing */ - public isVisible() { - return !!(this._toolbarOptions && this._toolbarOptions.visible); + public isVisible(): boolean { + return !!this._toolbarOptions?.visible; } /** * @extended: DataGrid's grouping */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public allowDragging(column): boolean { + public allowDragging(column: Column): boolean { return false; } - public hasGroupedColumns(): any {} + public hasGroupedColumns(): boolean | undefined { + return undefined; + } // eslint-disable-next-line @typescript-eslint/no-unused-vars - public getContextMenuItems(options) { + public getContextMenuItems(options: ContextMenuItemsOptions): unknown { return undefined; } } -const resizing = (Base: ModuleType) => class HeaderPanelResizingExtender extends Base { - protected _updateDimensionsCore() { - // @ts-expect-error - super._updateDimensionsCore.apply(this, arguments); +const resizing = ( + Base: ModuleType, +): ModuleType => class HeaderPanelResizingExtender extends Base { + protected _updateDimensionsCore(): void { + super._updateDimensionsCore(); this.getView('headerPanel').updateToolbarDimensions(); } }; export const headerPanelModule = { - defaultOptions() { - return { - }; + defaultOptions(): InternalGridOptions { + return {}; }, views: { headerPanel: HeaderPanel, From e130c5c3ba6c709777dcc3ce4ab869e89f0e38b4 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:44:19 +0200 Subject: [PATCH 2/3] Grids: reuse classes consts3 --- .../column_headers/m_column_headers.ts | 6 +-- .../grids/grid_core/header_panel/const.ts | 17 ++++++++ .../grid_core/header_panel/m_header_panel.ts | 14 ++----- .../grid_core/keyboard_navigation/const.ts | 2 - .../m_keyboard_navigation.ts | 17 ++++---- .../grid_core/keyboard_navigation/utils.ts | 3 +- .../grids/grid_core/master_detail/const.ts | 1 - .../master_detail/m_master_detail.ts | 3 +- .../grids/grid_core/row_dragging/const.ts | 1 - .../grid_core/row_dragging/m_row_dragging.ts | 2 +- .../grids/grid_core/search/m_search.ts | 4 +- .../__internal/grids/grid_core/views/const.ts | 10 +++++ .../grids/grid_core/views/m_rows_view.ts | 42 +++++++------------ .../grid_core/virtual_scrolling/const.ts | 2 - .../virtual_scrolling/m_virtual_scrolling.ts | 10 ++--- 15 files changed, 69 insertions(+), 65 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_panel/const.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts index a828ad76c968..ac080dde444e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts @@ -21,8 +21,6 @@ import type { ColumnViewTemplate } from '../views/types'; import { CLASSES } from './const'; const HEADERS_CLASS = 'headers'; -const NOWRAP_CLASS = 'nowrap'; -const COLUMN_LINES_CLASS = 'dx-column-lines'; const CONTEXT_MENU_SORT_ASC_ICON = 'context-menu-sort-asc'; const CONTEXT_MENU_SORT_DESC_ICON = 'context-menu-sort-desc'; const CONTEXT_MENU_SORT_NONE_ICON = 'context-menu-sort-none'; @@ -271,7 +269,7 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { // @ts-expect-error const $row = super._createRow.apply(this, arguments); - $row.toggleClass(COLUMN_LINES_CLASS, this.option('showColumnLines')); + $row.toggleClass(VIEW_CLASSES.columnLines, this.option('showColumnLines')); if (row.rowType === 'header') { $row.addClass(CLASSES.headerRow); @@ -322,7 +320,7 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { $container .addClass(this.addWidgetPrefix(HEADERS_CLASS)) - .toggleClass(this.addWidgetPrefix(NOWRAP_CLASS), !this.option('wordWrapEnabled')); + .toggleClass(this.addWidgetPrefix(VIEW_CLASSES.noWrap), !this.option('wordWrapEnabled')); this.setAria('role', 'presentation', $container); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/const.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/const.ts new file mode 100644 index 000000000000..6812a1e9b38d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/const.ts @@ -0,0 +1,17 @@ +export const CLASSES = { + headerPanel: 'header-panel', + toolbarButton: 'toolbar-button', +}; + +export const TOOLBAR_ARIA_LABEL = '-ariaToolbar'; + +export const DEFAULT_TOOLBAR_ITEM_NAMES = [ + 'addRowButton', + 'applyFilterButton', + 'columnChooserButton', + 'exportButton', + 'groupPanel', + 'revertButton', + 'saveButton', + 'searchPanel', +]; 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 397cf1fd6cbc..e07de6087bc4 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 @@ -19,13 +19,7 @@ import type { InternalGridOptions, ModuleType, OptionChanged } from '../types'; import { ColumnsView } from '../views/columns_view'; import type { ResizingController } from '../views/grid_view'; import type { ContextMenuItemsOptions } from '../views/types'; - -const HEADER_PANEL_CLASS = 'header-panel'; -const TOOLBAR_BUTTON_CLASS = 'toolbar-button'; - -const TOOLBAR_ARIA_LABEL = '-ariaToolbar'; - -const DEFAULT_TOOLBAR_ITEM_NAMES = ['addRowButton', 'applyFilterButton', 'columnChooserButton', 'exportButton', 'groupPanel', 'revertButton', 'saveButton', 'searchPanel']; +import { CLASSES, DEFAULT_TOOLBAR_ITEM_NAMES, TOOLBAR_ARIA_LABEL } from './const'; export class HeaderPanel extends ColumnsView { private _toolbar?: Toolbar; @@ -123,13 +117,13 @@ export class HeaderPanel extends ColumnsView { } private _getButtonContainer(): dxElementWrapper { - return $('
').addClass(this.addWidgetPrefix(TOOLBAR_BUTTON_CLASS)); + return $('
').addClass(this.addWidgetPrefix(CLASSES.toolbarButton)); } public getToolbarButtonClass(specificClass?: string): string { const secondClass = specificClass ? ` ${specificClass}` : ''; - return this.addWidgetPrefix(TOOLBAR_BUTTON_CLASS) + secondClass; + return this.addWidgetPrefix(CLASSES.toolbarButton) + secondClass; } private _getToolbarOptions(): ToolbarProperties { @@ -172,7 +166,7 @@ export class HeaderPanel extends ColumnsView { if (!this._toolbar) { const $headerPanel = this.element(); // @ts-expect-error the view is rendered here - $headerPanel.addClass(this.addWidgetPrefix(HEADER_PANEL_CLASS)); + $headerPanel.addClass(this.addWidgetPrefix(CLASSES.headerPanel)); const label = messageLocalization.format(this.component.NAME + TOOLBAR_ARIA_LABEL); // @ts-expect-error the view is rendered here const $toolbar = $('
').appendTo($headerPanel); diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/const.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/const.ts index 35a73167b413..056c6865cdf2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/const.ts @@ -3,11 +3,9 @@ export const ATTRIBUTES = { dragCell: 'dx-drag-cell', }; -export const ROWS_VIEW_CLASS = 'rowsview'; export const EDIT_FORM_CLASS = 'edit-form'; export const GROUP_FOOTER_CLASS = 'group-footer'; export const EDIT_FORM_ITEM_CLASS = 'edit-form-item'; -export const FREESPACE_ROW_CLASS = 'dx-freespace-row'; export const VIRTUAL_ROW_CLASS = 'dx-virtual-row'; export const MASTER_DETAIL_CELL_CLASS = 'dx-master-detail-cell'; export const EDITOR_CELL_CLASS = 'dx-editor-cell'; 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 4abd9251c33f..d8c0c6022a59 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 @@ -74,14 +74,12 @@ import { FOCUS_TYPE_ROW, FOCUSED_CELL_KEYS, FOCUSED_CLASS, - FREESPACE_ROW_CLASS, FUNCTIONAL_KEYS, INTERACTIVE_ELEMENTS_SELECTOR, MASTER_DETAIL_CELL_CLASS, NON_FOCUSABLE_ELEMENTS_SELECTOR, REVERT_BUTTON_CLASS, ROWS_VIEW, - ROWS_VIEW_CLASS, WIDGET_CLASS, } from './const'; import { GridCoreKeyboardNavigationDom } from './dom'; @@ -373,7 +371,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const $target = $(e.event.target); const tableSelector = `.${this.addWidgetPrefix(VIEW_CLASSES.table)}`; - const rowsViewSelector = `.${this.addWidgetPrefix(ROWS_VIEW_CLASS)}`; + const rowsViewSelector = `.${this.addWidgetPrefix(VIEW_CLASSES.rowsView)}`; const editorOverlaySelector = `.${DROPDOWN_EDITOR_OVERLAY_CLASS}`; // if click was on the datagrid table, but the target element is no more presented in the DOM @@ -818,7 +816,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if ( isFocusedRowElement || $target.parent().hasClass(VIEW_CLASSES.dataRow) - || $target.hasClass(this.addWidgetPrefix(ROWS_VIEW_CLASS)) + || $target.hasClass(this.addWidgetPrefix(VIEW_CLASSES.rowsView)) ) { this._selectionController.changeItemSelection(rowIndex, { shift: eventArgs.shift, @@ -872,7 +870,10 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo && !!eventTarget && !isOriginalHandlerRequired; // Reset focused cell when the rows view container itself is focused (not a specific cell) - if (canHandleNavigation && $(eventTarget).hasClass(this.addWidgetPrefix(ROWS_VIEW_CLASS))) { + if ( + canHandleNavigation + && $(eventTarget).hasClass(this.addWidgetPrefix(VIEW_CLASSES.rowsView)) + ) { this._resetFocusedCell(); } @@ -1628,7 +1629,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo this._focusView(); $(focusedViewElement).removeClass(FOCUS_STATE_CLASS); - if ($parent.hasClass(FREESPACE_ROW_CLASS)) { + if ($parent.hasClass(VIEW_CLASSES.freeSpaceRow)) { this._updateFocusedCellPosition($target); this._applyTabIndexToElement(this._focusedView!.element()); this._focusedView!.focus(true); @@ -1710,8 +1711,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const isHighlighted = this._isCellElement($(element)); if (!element) { - const activeRowSelector = `.${this.addWidgetPrefix(ROWS_VIEW_CLASS)} .${VIEW_CLASSES.row}[tabindex]`; - const activeCellSelector = `.${this.addWidgetPrefix(ROWS_VIEW_CLASS)} .${VIEW_CLASSES.row} > td[tabindex]`; + const activeRowSelector = `.${this.addWidgetPrefix(VIEW_CLASSES.rowsView)} .${VIEW_CLASSES.row}[tabindex]`; + const activeCellSelector = `.${this.addWidgetPrefix(VIEW_CLASSES.rowsView)} .${VIEW_CLASSES.row} > td[tabindex]`; const selectors = [activeRowSelector, activeCellSelector].join(', '); element = this.component.$element().find(selectors).first(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts index eb4f0f9c00a7..75c0ce3142ad 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/utils.ts @@ -11,7 +11,6 @@ import { ADAPTIVE_ITEM_TEXT_CLASS, COMMAND_SELECT_CLASS, EDIT_FORM_CLASS, - FREESPACE_ROW_CLASS, INTERACTIVE_ELEMENTS_SELECTOR, VIRTUAL_ROW_CLASS, } from './const'; @@ -51,7 +50,7 @@ export const isDataRow = ( export const isNotFocusedRow = ( $row: dxElementWrapper, -): boolean => !$row || $row.hasClass(FREESPACE_ROW_CLASS) || $row.hasClass(VIRTUAL_ROW_CLASS); +): boolean => !$row || $row.hasClass(VIEW_CLASSES.freeSpaceRow) || $row.hasClass(VIRTUAL_ROW_CLASS); export const isEditorCell = ( that: KeyboardNavigationController, diff --git a/packages/devextreme/js/__internal/grids/grid_core/master_detail/const.ts b/packages/devextreme/js/__internal/grids/grid_core/master_detail/const.ts index d5e1fefcb468..573978bd4b5d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/master_detail/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/master_detail/const.ts @@ -3,5 +3,4 @@ export const CLASSES = { detailCell: 'dx-master-detail-cell', detailContainer: 'master-detail-container', cellFocusDisabledClass: 'dx-cell-focus-disabled', - rowLines: 'dx-row-lines', }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts index 6919011b8e1d..2736f7c5bc40 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts @@ -11,6 +11,7 @@ import { isDefined } from '@js/core/utils/type'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/columns_controller'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { ModuleType, RowKey } from '@ts/grids/grid_core/types'; +import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import type { ResizingController } from '@ts/grids/grid_core/views/grid_view'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -378,7 +379,7 @@ const rowsView = (Base: ModuleType) => class RowsViewMasterDetailExten $row .addClass(CLASSES.detailRow) - .toggleClass(CLASSES.rowLines, showRowLines); + .toggleClass(VIEW_CLASSES.rowLines, showRowLines); if (isDefined(row.visible)) { $row.toggle(row.visible); diff --git a/packages/devextreme/js/__internal/grids/grid_core/row_dragging/const.ts b/packages/devextreme/js/__internal/grids/grid_core/row_dragging/const.ts index 68960877d792..1047c9ed6af1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/row_dragging/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/row_dragging/const.ts @@ -7,6 +7,5 @@ export const CLASSES = { handleIcon: 'drag-icon', commandDrag: 'dx-command-drag', sortableWithoutHandle: 'dx-sortable-without-handle', - rowsView: 'rowsview', dragView: 'dragview', }; 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 394da16c7636..38abe0c3ad2d 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 @@ -248,7 +248,7 @@ const rowsView = (Base: ModuleType) => class RowsViewRowDraggingExtend this._createComponent($dataGridContainer, this.component.NAME, gridOptions); $dataGridContainer .find('.dx-gridbase-container') - .children(`:not(.${this.addWidgetPrefix(CLASSES.rowsView)})`) + .children(`:not(.${this.addWidgetPrefix(VIEW_CLASSES.rowsView)})`) .hide(); $dataGridContainer.addClass(this.addWidgetPrefix(CLASSES.dragView)); 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 94ca054c354c..f73f97d99a7b 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 @@ -8,6 +8,7 @@ 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 { CLASSES as HEADER_PANEL_CLASSES } from '@ts/grids/grid_core/header_panel/const'; import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { DataController } from '../data_controller/data_controller'; @@ -22,7 +23,6 @@ import { allowSearch, createSearchExpression, parseValue } from './utils'; const SEARCH_PANEL_CLASS = 'search-panel'; const SEARCH_TEXT_CLASS = 'search-text'; -const HEADER_PANEL_CLASS = 'header-panel'; const FILTERING_TIMEOUT = 700; const SEARCH_PANEL_ITEM_NAME = 'searchPanel'; @@ -170,7 +170,7 @@ export class SearchPanelViewController extends modules.ViewController { return null; } - const headerPanelClass = this.headerPanel.addWidgetPrefix(HEADER_PANEL_CLASS); + const headerPanelClass = this.headerPanel.addWidgetPrefix(HEADER_PANEL_CLASSES.headerPanel); const $searchPanel = $element .find(`.${this.headerPanel.addWidgetPrefix(SEARCH_PANEL_CLASS)}`) // @ts-expect-error filter() is typed for selectors only diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/const.ts b/packages/devextreme/js/__internal/grids/grid_core/views/const.ts index 49f4e88b31a5..a50ff460ca70 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/const.ts @@ -1,4 +1,9 @@ export const CLASSES = { + rowsView: 'rowsview', + noWrap: 'nowrap', + empty: 'dx-empty', + lastRowBorder: 'dx-last-row-border', + content: 'content', scrollContainer: 'scroll-container', scrollableSimulated: 'scrollable-simulated', @@ -12,6 +17,11 @@ export const CLASSES = { row: 'dx-row', dataRow: 'dx-data-row', groupRow: 'dx-group-row', + freeSpaceRow: 'dx-freespace-row', + rowAlternation: 'dx-row-alt', + rowInsertedAnimation: 'row-inserted-animation', + rowLines: 'dx-row-lines', + columnLines: 'dx-column-lines', firstCell: 'first-cell', groupCell: 'dx-group-cell', 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 cda107c3254c..13a8f35355cd 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 @@ -82,16 +82,6 @@ import { isSameColumnLayout, } from './utils'; -const ROWS_VIEW_CLASS = 'rowsview'; -const NOWRAP_CLASS = 'nowrap'; -const FREE_SPACE_CLASS = 'dx-freespace-row'; -const COLUMN_LINES_CLASS = 'dx-column-lines'; -const ROW_ALTERNATION_CLASS = 'dx-row-alt'; -const LAST_ROW_BORDER = 'dx-last-row-border'; -const EMPTY_CLASS = 'dx-empty'; -const ROW_INSERTED_ANIMATION_CLASS = 'row-inserted-animation'; -export const ROW_LINES_CLASS = 'dx-row-lines'; - const LOADPANEL_HIDE_TIMEOUT = 200; export function isGroupRow( @@ -317,11 +307,11 @@ export class RowsView extends ColumnsView { $row.addClass(VIEW_CLASSES.dataRow); } if (isDataRow && this.option('showRowLines')) { - $row.addClass(ROW_LINES_CLASS); + $row.addClass(VIEW_CLASSES.rowLines); } if (this.option('showColumnLines')) { - $row.addClass(COLUMN_LINES_CLASS); + $row.addClass(VIEW_CLASSES.columnLines); } if (row.visible === false) { @@ -345,7 +335,7 @@ export class RowsView extends ColumnsView { if (rowOptions.rowType === 'data') { if (this.option('rowAlternationEnabled')) { if (this._isAltRow(row)) { - $row.addClass(ROW_ALTERNATION_CLASS); + $row.addClass(VIEW_CLASSES.rowAlternation); } setWatcher({ @@ -356,7 +346,7 @@ export class RowsView extends ColumnsView { this._dataController.getRowByKey(row.key), ), callBack: (value) => { - $row.toggleClass(ROW_ALTERNATION_CLASS, value); + $row.toggleClass(VIEW_CLASSES.rowAlternation, value); }, }); } @@ -588,7 +578,7 @@ export class RowsView extends ColumnsView { const $newRows = this._getRowElements($newTable); const shouldHighlightInserts = this.option('highlightChanges') && change.isLiveUpdate; const insertedRowClass = shouldHighlightInserts - ? this.addWidgetPrefix(ROW_INSERTED_ANIMATION_CLASS) + ? this.addWidgetPrefix(VIEW_CLASSES.rowInsertedAnimation) : undefined; change.rowIndices.forEach((rowIndex, index) => { @@ -686,7 +676,7 @@ export class RowsView extends ColumnsView { $row .addClass(className) - .toggleClass(COLUMN_LINES_CLASS, this.option('showColumnLines')); + .toggleClass(VIEW_CLASSES.columnLines, this.option('showColumnLines')); for (let i = 0; i < columns.length; i += 1) { const $cell = this._createCell({ @@ -729,7 +719,7 @@ export class RowsView extends ColumnsView { } private _renderFreeSpaceRow($tableElement: dxElementWrapper): void { - let $freeSpaceRowElement = this._createEmptyRow(FREE_SPACE_CLASS); + let $freeSpaceRowElement = this._createEmptyRow(VIEW_CLASSES.freeSpaceRow); $freeSpaceRowElement = this._wrapRowIfNeed($freeSpaceRowElement); @@ -768,7 +758,7 @@ export class RowsView extends ColumnsView { .children('tbody') .children() .not('.dx-virtual-row') - .not(`.${FREE_SPACE_CLASS}`); + .not(`.${VIEW_CLASSES.freeSpaceRow}`); // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- getBoundingRect returns any return $rowElements.toArray().reduce((sum, row) => sum + getBoundingRect(row).height, 0); @@ -812,13 +802,13 @@ export class RowsView extends ColumnsView { public _getRowElements(tableElement?: dxElementWrapper | null): dxElementWrapper { const $rows = super._getRowElements(tableElement); - return $rows?.not(`.${FREE_SPACE_CLASS}`); + return $rows?.not(`.${VIEW_CLASSES.freeSpaceRow}`); } public _getFreeSpaceRowElements($table?: dxElementWrapper): dxElementWrapper { const tableElements = $table ?? this.getTableElements(); - return tableElements && tableElements.children('tbody').children(`.${FREE_SPACE_CLASS}`); + return tableElements && tableElements.children('tbody').children(`.${VIEW_CLASSES.freeSpaceRow}`); } private _getNoDataText(): string | undefined { @@ -1042,9 +1032,9 @@ export class RowsView extends ColumnsView { } $element - .addClass(this.addWidgetPrefix(ROWS_VIEW_CLASS)) - .toggleClass(this.addWidgetPrefix(NOWRAP_CLASS), !this.option('wordWrapEnabled')); - $element.toggleClass(EMPTY_CLASS, this._dataController.isEmpty()); + .addClass(this.addWidgetPrefix(VIEW_CLASSES.rowsView)) + .toggleClass(this.addWidgetPrefix(VIEW_CLASSES.noWrap), !this.option('wordWrapEnabled')); + $element.toggleClass(VIEW_CLASSES.empty, this._dataController.isEmpty()); this.setAria('role', 'presentation', $element); @@ -1366,9 +1356,9 @@ export class RowsView extends ColumnsView { private _updateLastRowBorder(isFreeSpaceRowVisible: boolean): void { if (this.option('showBorders') && !isFreeSpaceRowVisible) { - this.element()?.addClass(LAST_ROW_BORDER); + this.element()?.addClass(VIEW_CLASSES.lastRowBorder); } else { - this.element()?.removeClass(LAST_ROW_BORDER); + this.element()?.removeClass(VIEW_CLASSES.lastRowBorder); } } @@ -1702,7 +1692,7 @@ export class RowsView extends ColumnsView { return false; } - const $rowsViewElement = $element.closest(`.${this.addWidgetPrefix(ROWS_VIEW_CLASS)}`); + const $rowsViewElement = $element.closest(`.${this.addWidgetPrefix(VIEW_CLASSES.rowsView)}`); return $rowsViewElement.is($viewElement); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/const.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/const.ts index 18b74da034f2..c7223f33ab47 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/const.ts @@ -1,6 +1,4 @@ export const BOTTOM_LOAD_PANEL_CLASS = 'bottom-load-panel'; -export const FREESPACE_CLASS = 'dx-freespace-row'; -export const COLUMN_LINES_CLASS = 'dx-column-lines'; export const VIRTUAL_ROW_CLASS = 'dx-virtual-row'; export const ROW_INSERTED = 'dx-row-inserted'; 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 eecb4fcda2d7..aa84791e1163 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 @@ -26,8 +26,6 @@ import type { StateStoringDataControllerExtension } from '../state_storing/exten import type { RowsViewScrollEvent } from '../views/types'; import { BOTTOM_LOAD_PANEL_CLASS, - COLUMN_LINES_CLASS, - FREESPACE_CLASS, LEGACY_SCROLLING_MODE, LOAD_TIMEOUT, PAGING_METHOD_NAMES, @@ -80,7 +78,9 @@ const removeEmptyRows = function ($emptyRows, className) { $emptyRows = $(tBodies); } - const rowCount = className === FREESPACE_CLASS ? $emptyRows.length - 1 : $emptyRows.length; + const rowCount = className === VIEW_CLASSES.freeSpaceRow + ? $emptyRows.length - 1 + : $emptyRows.length; for (let i = 0; i < rowCount; i++) { $emptyRows.eq(i).remove(); @@ -655,7 +655,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo tableElement.remove(); $freeSpaceRowElements = this._getFreeSpaceRowElements(contentTable); - removeEmptyRows($freeSpaceRowElements, FREESPACE_CLASS); + removeEmptyRows($freeSpaceRowElements, VIEW_CLASSES.freeSpaceRow); if (change.removeCount) { this._removeRowsElements(contentTable, change.removeCount, changeType); @@ -799,7 +799,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo } private _isTableLinesDisplaysCorrect(table) { - const hasColumnLines = table.find(`.${COLUMN_LINES_CLASS}`).length > 0; + const hasColumnLines = table.find(`.${VIEW_CLASSES.columnLines}`).length > 0; return hasColumnLines === this.option('showColumnLines'); } From 29076dd3e951bf51e3fd199f5e54d463d6982f95 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:56:33 +0200 Subject: [PATCH 3/3] Fix review comments --- .../grid_core/header_panel/m_header_panel.ts | 19 +++++++++---------- .../grids/new/grid_core/toolbar/utils.ts | 6 ++++-- 2 files changed, 13 insertions(+), 12 deletions(-) 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 e07de6087bc4..7bba327c439d 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 @@ -11,7 +11,7 @@ import type { Properties as ToolbarProperties } from '@js/ui/toolbar'; import Toolbar from '@js/ui/toolbar'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { HeaderFilterController } from '@ts/grids/grid_core/header_filter/m_header_filter'; -import type { DefaultToolbarItem, ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; +import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { normalizeToolbarItems } from '@ts/grids/new/grid_core/toolbar/utils'; import type { Column } from '../columns_controller/types'; @@ -22,7 +22,7 @@ import type { ContextMenuItemsOptions } from '../views/types'; import { CLASSES, DEFAULT_TOOLBAR_ITEM_NAMES, TOOLBAR_ARIA_LABEL } from './const'; export class HeaderPanel extends ColumnsView { - private _toolbar?: Toolbar; + private _toolbar?: Toolbar; private _toolbarOptions?: ToolbarProperties; @@ -83,12 +83,12 @@ export class HeaderPanel extends ColumnsView { (ui) => (typeof ui === 'string' ? ui === name : ui?.name === name), ); - if (!userItem) { + if (!registeredItem || !userItem) { return registeredItem; } return normalizeToolbarItems( - [registeredItem] as DefaultToolbarItem[], + [registeredItem], [userItem], DEFAULT_TOOLBAR_ITEM_NAMES, )[0]; @@ -126,11 +126,11 @@ export class HeaderPanel extends ColumnsView { return this.addWidgetPrefix(CLASSES.toolbarButton) + secondClass; } - private _getToolbarOptions(): ToolbarProperties { + private _getToolbarOptions(): ToolbarProperties { const { toolbar: userToolbarOptions } = this.option(); const sortedToolbarItems: ToolbarItem[] = this.sortToolbarItems(this._getToolbarItems()); - const options: { toolbarOptions: ToolbarProperties } = { + const options: { toolbarOptions: ToolbarProperties } = { toolbarOptions: { items: sortedToolbarItems, visible: userToolbarOptions?.visible, @@ -147,7 +147,7 @@ export class HeaderPanel extends ColumnsView { const userItems = userToolbarOptions?.items; options.toolbarOptions.items = normalizeToolbarItems( - sortedToolbarItems as DefaultToolbarItem[], + sortedToolbarItems, userItems, DEFAULT_TOOLBAR_ITEM_NAMES, ); @@ -220,8 +220,7 @@ export class HeaderPanel extends ColumnsView { return; } - // @ts-expect-error option() returns unknown for an item path - const item: ToolbarItem = toolbar.option(`items[${itemIndex}]`); + const item = items[itemIndex]; toolbar.option(`items[${itemIndex}].disabled`, disabled); @@ -263,7 +262,7 @@ export class HeaderPanel extends ColumnsView { this._invalidate(); } else if (isItemReplaced) { const normalizedItem = normalizeToolbarItems( - this._getToolbarItems() as DefaultToolbarItem[], + this._getToolbarItems(), // @ts-expect-error OptionChanged has no paths for toolbar items by index [args.value], DEFAULT_TOOLBAR_ITEM_NAMES, diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts index fb29d7cb0892..4a9360523d86 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts @@ -57,7 +57,7 @@ export function getSortedToolbarItems( } export function normalizeToolbarItems( - sortedDefaultItems: DefaultToolbarItem[], + sortedDefaultItems: ToolbarItem[], userItems: (ToolbarItem | string)[] | undefined, defaultItemNames: readonly string[], ): ToolbarItem[] { @@ -68,7 +68,9 @@ export function normalizeToolbarItems( const defaultButtonsMap = {}; sortedDefaultItems.forEach((button) => { - defaultButtonsMap[button.name] = button; + if (isDefined(button.name)) { + defaultButtonsMap[button.name] = button; + } }); return userItems.map(