From 72c586406178b0d00c1a534285f60ae4074d6a78 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 26 Oct 2026 10:38:21 +0100 Subject: [PATCH 1/8] Grids: remove TreeList row click hacks from the editing rows view --- .../grids/grid_core/editing/editing.ts | 36 +++- .../editing.row_click.integration.test.ts | 185 ++++++++++++++++++ .../grids/tree_list/editing/m_editing.ts | 50 ++--- 3 files changed, 230 insertions(+), 41 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/editing.row_click.integration.test.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index 75d639cda463..20d63a0071bf 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -2817,9 +2817,19 @@ export class EditingController extends modules.ViewController { } } +export interface EditingRowsViewExtension { + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + handleRowClickByEditing(e: ViewRowEvent): boolean; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + handleRowDblClickByEditing(e: ViewRowEvent): boolean; +} + const rowsView = >( Base: TBase, -): TBase => class RowsViewEditingExtender extends Base { +): TBase & ModuleType< + RowsView & EditingRowsViewExtension +> => class RowsViewEditingExtender extends Base { /** @architectureLeak editingCellBased: a click on a cell starts and closes cell editing */ public _editingController!: CellBasedEditingController; @@ -2949,26 +2959,32 @@ const rowsView = >( }); } - private _rowClickTreeListHack(e: ViewRowEvent): void { - super._rowClick(e); - } - - protected _rowClick(e: ViewRowEvent): void { + /** + * @extended: TreeList's editing + */ + public handleRowClickByEditing(e: ViewRowEvent): boolean { const isEditForm = $(e.rowElement).hasClass(this.addWidgetPrefix(CLASSES.editForm)); e.event[TARGET_COMPONENT_NAME] = this.component; - if (!this._editCellByClick(e, 'click') && !isEditForm) { + return !!this._editCellByClick(e, 'click') || isEditForm; + } + + protected _rowClick(e: ViewRowEvent): void { + if (!this.handleRowClickByEditing(e)) { super._rowClick(e); } } - private _rowDblClickTreeListHack(e: ViewRowEvent): void { - super._rowDblClick(e); + /** + * @extended: TreeList's editing + */ + public handleRowDblClickByEditing(e: ViewRowEvent): boolean { + return !!this._editCellByClick(e, 'dblClick'); } protected _rowDblClick(e: ViewRowEvent): void { - if (!this._editCellByClick(e, 'dblClick')) { + if (!this.handleRowDblClickByEditing(e)) { super._rowDblClick(e); } } diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/editing.row_click.integration.test.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/editing.row_click.integration.test.ts new file mode 100644 index 000000000000..20881d7e1909 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/editing.row_click.integration.test.ts @@ -0,0 +1,185 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import type { Properties as TreeListProperties } from '@js/ui/tree_list'; +import eventsEngine from '@ts/events/core/events_engine'; +import { flushAsync } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import { TARGET_COMPONENT_NAME } from '@ts/grids/grid_core/editing/const'; + +import { + afterTest, + beforeTest, + createTreeList, +} from '../../__tests__/__mock__/helpers/utils'; +import type { TreeListModel } from '../../__tests__/__mock__/model/tree_list'; + +const DATA = [ + { + id: 1, parentId: 0, name: 'Parent', note: 'A', + }, + { + id: 2, parentId: 1, name: 'Child', note: 'B', + }, +]; + +const EXPAND_ICON_SELECTOR = '.dx-treelist-collapsed'; +const INDENT_SELECTOR = '.dx-treelist-empty-space'; +const TEXT_CONTENT_SELECTOR = '.dx-treelist-text-content'; +const SELECT_CHECKBOX_SELECTOR = '.dx-select-checkbox'; + +const setupTreeList = ( + startEditAction: 'click' | 'dblClick', + options: TreeListProperties = {}, +): ReturnType => createTreeList({ + dataSource: DATA, + columns: ['name', 'note'], + editing: { + mode: 'batch', + allowUpdating: true, + startEditAction, + }, + ...options, +}); + +const click = (element: Element): void => { + eventsEngine.trigger(element, 'dxpointerdown'); + eventsEngine.trigger(element, 'dxpointerup'); + eventsEngine.trigger(element, 'dxclick'); +}; + +const dblClick = (element: Element): void => { + eventsEngine.trigger(element, 'dxdblclick'); +}; + +const findInFirstColumn = ( + component: TreeListModel, + selector: string, + rowIndex = 0, +): Element => { + const element = component.getDataCell(rowIndex, 0).getElement()?.querySelector(selector); + + if (!element) { + throw new Error(`${selector} is not rendered in row ${rowIndex}`); + } + + return element; +}; + +const isEventMarkedWith = (handler: jest.Mock, component: unknown): boolean => { + const [{ event }] = handler.mock.calls[0] as [{ event: Record }]; + + return event[TARGET_COMPONENT_NAME] === component; +}; + +describe('TreeList editing: row clicks', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('click (startEditAction: click)', () => { + it('should expand the row without starting editing on a click on the expand icon', async () => { + const onRowClick = jest.fn(); + const { component, instance } = await setupTreeList('click', { onRowClick }); + + click(findInFirstColumn(component, EXPAND_ICON_SELECTOR)); + await flushAsync(); + + expect(instance.isRowExpanded(1)).toBe(true); + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowClick).toHaveBeenCalledTimes(1); + expect(isEventMarkedWith(onRowClick, instance)).toBe(false); + }); + + it('should run the row click without starting editing on a click on the indent', async () => { + const onRowClick = jest.fn(); + const { component, instance } = await setupTreeList('click', { + expandedRowKeys: [1], + onRowClick, + }); + + click(findInFirstColumn(component, INDENT_SELECTOR, 1)); + await flushAsync(); + + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowClick).toHaveBeenCalledTimes(1); + }); + + it('should start editing on a click on the cell text next to the expand icon', async () => { + const onRowClick = jest.fn(); + const { component } = await setupTreeList('click', { onRowClick }); + + click(findInFirstColumn(component, TEXT_CONTENT_SELECTOR)); + await flushAsync(); + + expect(component.getDataCell(0, 0).isEditCell).toBe(true); + expect(onRowClick).not.toHaveBeenCalled(); + }); + + it('should mark the event with the component on a click on a read-only cell', async () => { + const onRowClick = jest.fn(); + const { component, instance } = await setupTreeList('click', { + columns: ['name', { dataField: 'note', allowEditing: false }], + onRowClick, + }); + + click(component.getDataCell(0, 1).getElement() as Element); + await flushAsync(); + + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowClick).toHaveBeenCalledTimes(1); + expect(isEventMarkedWith(onRowClick, instance)).toBe(true); + }); + + it('should neither start editing nor run the row click on a click on the select checkbox', async () => { + const onRowClick = jest.fn(); + const { component, instance } = await setupTreeList('click', { + onRowClick, + selection: { mode: 'multiple' }, + }); + + click(findInFirstColumn(component, SELECT_CHECKBOX_SELECTOR)); + await flushAsync(); + + expect(instance.isRowSelected(1)).toBe(true); + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowClick).not.toHaveBeenCalled(); + }); + }); + + describe('double-click (startEditAction: dblClick)', () => { + it('should not start editing on a double-click on the expand icon', async () => { + const onRowDblClick = jest.fn(); + const { component, instance } = await setupTreeList('dblClick', { onRowDblClick }); + + dblClick(findInFirstColumn(component, EXPAND_ICON_SELECTOR)); + await flushAsync(); + + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowDblClick).toHaveBeenCalledTimes(1); + }); + + it('should start editing on a double-click on the cell text next to the expand icon', async () => { + const onRowDblClick = jest.fn(); + const { component } = await setupTreeList('dblClick', { onRowDblClick }); + + dblClick(findInFirstColumn(component, TEXT_CONTENT_SELECTOR)); + await flushAsync(); + + expect(component.getDataCell(0, 0).isEditCell).toBe(true); + expect(onRowDblClick).not.toHaveBeenCalled(); + }); + + it('should neither start editing nor run the row double-click on a double-click on the select checkbox', async () => { + const onRowDblClick = jest.fn(); + const { component, instance } = await setupTreeList('dblClick', { + onRowDblClick, + selection: { mode: 'multiple' }, + }); + + dblClick(findInFirstColumn(component, SELECT_CHECKBOX_SELECTOR)); + await flushAsync(); + + expect(instance.option('editing.editColumnName')).toBeUndefined(); + expect(onRowDblClick).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts index f57a475fd7f6..106d876d1407 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts @@ -13,6 +13,7 @@ import errors from '@js/ui/widget/ui.errors'; import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { DATA_CHANGE_TYPE } from '@ts/grids/grid_core/editing/const'; +import type { EditingRowsViewExtension } from '@ts/grids/grid_core/editing/editing'; import { editingModule } from '@ts/grids/grid_core/editing/editing'; import type { EditingDataControllerExtension } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import { editingDataControllerExtender } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; @@ -34,6 +35,12 @@ import type { TreeListDataController } from '../data_controller/data_controller' import type { TreeListDataSourceController } from '../data_source/data_source_controller'; import type { TreeListEditingDefaultOptions } from './types'; +const isExpandIconTarget = ($target: dxElementWrapper): boolean => !!$target + .closest(`.${ROWS_CLASSES.expandIconContainer}`).length; + +const isSelectCheckboxTarget = ($target: dxElementWrapper): boolean => !!$target + .closest(`.${SELECT_CHECKBOX_CLASS}`).length; + class EditingController extends editingModule.controllers.editing { protected declare _dataController: TreeListDataController; @@ -197,10 +204,8 @@ class EditingController extends editingModule.controllers.editing { } protected _needToCloseEditableCell($targetElement: dxElementWrapper): boolean | undefined { - const isExpandIconTarget = !!$targetElement.closest(`.${ROWS_CLASSES.expandIconContainer}`).length; - return !!super._needToCloseEditableCell($targetElement) - || (isExpandIconTarget && this.isEditing()); + || (isExpandIconTarget($targetElement) && this.isEditing()); } protected getButtonLocalizationNames(): Partial> { @@ -213,7 +218,9 @@ class EditingController extends editingModule.controllers.editing { const rowsView = ( Base: ModuleType, -): ModuleType => class TreeListEditingRowsViewExtender +): ModuleType< + TreeListRowsView & EditingRowsViewExtension +> => class TreeListEditingRowsViewExtender extends editingModule.extenders.views.rowsView(Base) { protected _renderCellCommandContent( $container: dxElementWrapper, @@ -230,42 +237,23 @@ const rowsView = ( return false; } - private validateClick(e: ViewRowEvent): boolean { - const $targetElement = $(e.event.target); - const originalClickHandler = e.event.type === 'dxdblclick' ? super._rowDblClick : super._rowClick; - - if ($targetElement.closest(`.${SELECT_CHECKBOX_CLASS}`).length) { - return false; - } - - return !this.needToCallOriginalClickHandler(e, originalClickHandler); + public handleRowClickByEditing(e: ViewRowEvent): boolean { + return !isExpandIconTarget($(e.event.target)) && super.handleRowClickByEditing(e); } - private needToCallOriginalClickHandler( - e: ViewRowEvent, - originalClickHandler: (e: ViewRowEvent) => void, - ): boolean { - const $targetElement = $(e.event.target); - - if (!$targetElement.closest(`.${ROWS_CLASSES.expandIconContainer}`).length) { - originalClickHandler.call(this, e); - return true; - } - - return false; + public handleRowDblClickByEditing(e: ViewRowEvent): boolean { + return !isExpandIconTarget($(e.event.target)) && super.handleRowDblClickByEditing(e); } protected _rowClick(e: ViewRowEvent): void { - if (this.validateClick(e)) { - // @ts-expect-error _rowClickTreeListHack() is added by the editing rows view extender - super._rowClickTreeListHack(e); + if (!isSelectCheckboxTarget($(e.event.target))) { + super._rowClick(e); } } protected _rowDblClick(e: ViewRowEvent): void { - if (this.validateClick(e)) { - // @ts-expect-error _rowDblClickTreeListHack() is added by the editing rows view extender - super._rowDblClickTreeListHack(e); + if (!isSelectCheckboxTarget($(e.event.target))) { + super._rowDblClick(e); } } }; From 55ce6461a537a9eec00ca0303aade3344ff07b34 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:30:40 +0100 Subject: [PATCH 2/8] Grids: unify edit-mode checks --- .../grids/grid_core/adaptivity/adaptivity.ts | 34 +++--- .../adaptivity_editing_view_controller.ts | 16 ++- ...ity_keyboard_navigation_view_controller.ts | 3 +- .../editing_cell_based.integration.test.ts | 5 +- .../editing/__tests__/editing_utils.test.ts | 27 +++++ .../grids/grid_core/editing/const.ts | 1 + .../grids/grid_core/editing/editing.ts | 23 +--- .../grids/grid_core/editing/editing_utils.ts | 21 +++- .../editing_form_based_data_controller.ts | 13 ++- .../editing_row_based_data_controller.ts | 5 +- .../grid_core/editing/m_editing_cell_based.ts | 101 +++++++++--------- .../grid_core/editing/m_editing_form_based.ts | 51 +++++---- .../grid_core/editing/m_editing_row_based.ts | 20 ++-- .../__tests__/utils.test.ts | 16 +++ .../m_keyboard_navigation.ts | 52 +++++---- .../grid_core/keyboard_navigation/utils.ts | 11 +- .../grid_core/validating/m_validating.ts | 16 ++- .../virtual_scrolling/m_virtual_scrolling.ts | 4 +- 18 files changed, 227 insertions(+), 192 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_utils.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/__tests__/utils.test.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity.ts index b9dc079f4cd1..f6c983503b2c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity.ts @@ -3,7 +3,6 @@ import eventsEngine from '@js/common/core/events/core/events_engine'; import { removeEvent } from '@js/common/core/events/remove'; import { addNamespace } from '@js/common/core/events/utils/index'; import messageLocalization from '@js/common/core/localization/message'; -import type { GridsEditMode } from '@js/common/grids'; import type { DxElement } from '@js/core/element'; import Guid from '@js/core/guid'; import type { dxElementWrapper } from '@js/core/renderer'; @@ -18,6 +17,7 @@ import type { Properties as FormProperties, SimpleItemTemplateData } from '@js/u import Form from '@js/ui/form'; import { isMaterial } from '@js/ui/themes'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import { isCellBasedEditMode, isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import { FIELD_ITEM_CONTENT_CLASS } from '@ts/ui/form/constants'; @@ -149,11 +149,6 @@ export class AdaptiveColumnsViewController extends modules.ViewController { return cellOptions.row.values[columnIndex]; } - private _isRowEditMode(): boolean { - const editMode = this._getEditMode(); - return editMode === EDIT_MODE.row; - } - private _isItemModified( item: AdaptiveFormItem, cellOptions: AdaptiveDetailCellOptions, @@ -187,7 +182,7 @@ export class AdaptiveColumnsViewController extends modules.ViewController { const displayValue = gridCoreUtils.getDisplayValue(column, value, rowData, cellOptions.rowType); const text = gridCoreUtils.formatValue(displayValue, column); - const isCellOrBatchEditMode = this._editingController.isCellOrBatchEditMode(); + const isCellOrBatchEditMode = isCellBasedEditMode(this._editingController.getEditMode()); const rowsView = this._rowsView; if (column.allowEditing && this._keyboardNavigationController.isKeyboardEnabled()) { @@ -226,7 +221,7 @@ export class AdaptiveColumnsViewController extends modules.ViewController { $container.html(' '); } - if (!this._isRowEditMode()) { + if (this._editingController.getEditMode() !== EDIT_MODE.row) { if (this._isItemModified(item, cellOptions)) { $container.addClass(CLASSES.itemModified); } @@ -436,11 +431,13 @@ export class AdaptiveColumnsViewController extends modules.ViewController { } private _isItemEdited(item: AdaptiveFormItem): boolean | undefined { - if (this.isFormOrPopupEditMode()) { + const editMode = this._editingController.getEditMode(); + + if (isFormBasedEditMode(editMode)) { return false; } - if (this._isRowEditMode()) { + if (editMode === EDIT_MODE.row) { const editRowKey = this.option('editing.editRowKey'); if (equalByValue(editRowKey, this._dataController.getAdaptiveExpandedKey())) { return true; @@ -582,7 +579,7 @@ export class AdaptiveColumnsViewController extends modules.ViewController { for (let rowIndex = 0; rowIndex < rowsCount; rowIndex += 1) { const cancelClassAdding = rowIndex === editFormRowIndex && viewName === ROWS_VIEW - && this.option('editing.mode') !== 'popup'; + && this._editingController.getEditMode() !== EDIT_MODE.popup; const currentVisibleIndex = viewName === COLUMN_HEADERS_VIEW && !cancelClassAdding ? this._columnsController.getVisibleIndex(column.index, rowIndex) : visibleIndex; @@ -637,16 +634,6 @@ export class AdaptiveColumnsViewController extends modules.ViewController { }).addClass(cssClassNameToAdd); } - private _getEditMode(): GridsEditMode { - return this._editingController.getEditMode(); - } - - public isFormOrPopupEditMode(): boolean { - const editMode = this._getEditMode(); - - return editMode === EDIT_MODE.form || editMode === EDIT_MODE.popup; - } - public hideRedundantColumns( resultWidths: (number | string | undefined)[], visibleColumns: Column[], @@ -763,7 +750,10 @@ export class AdaptiveColumnsViewController extends modules.ViewController { } public toggleExpandAdaptiveDetailRow(key?: RowKey, alwaysExpanded = false): void { - if (!(this.isFormOrPopupEditMode() && this._editingController.isEditing())) { + const isEditingInFormOrPopup = isFormBasedEditMode(this._editingController.getEditMode()) + && this._editingController.isEditing(); + + if (!isEditingInFormOrPopup) { this._dataController.toggleExpandAdaptiveDetailRow(key, alwaysExpanded); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts index 5ad57f50ad9b..510853fefb4f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts @@ -5,6 +5,7 @@ /* eslint-disable prefer-rest-params */ import { Deferred, when } from '@js/core/utils/deferred'; import type { EditingController } from '@ts/grids/grid_core/editing/editing'; +import { isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based'; import type { ModuleType } from '@ts/grids/grid_core/types'; import gridCoreUtils from '@ts/grids/grid_core/utils'; @@ -21,10 +22,6 @@ export const adaptivityEditingViewControllerExtender = ( private _isForceRowAdaptiveExpand?: boolean; - private _isRowEditMode() { - return this.getEditMode() === EDIT_MODE.row; - } - protected _getFormEditItemTemplate(cellOptions, column) { if (this.getEditMode() !== EDIT_MODE.row && cellOptions.rowType === 'detailAdaptive') { cellOptions.columnIndex = this._columnsController.getVisibleIndex(column.index); @@ -46,7 +43,7 @@ export const adaptivityEditingViewControllerExtender = ( } protected _beforeUpdateItems(rowIndices, rowIndex) { - if (!this.adaptiveColumnsController.isFormOrPopupEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { + if (!isFormBasedEditMode(this.getEditMode()) && this.adaptiveColumnsController.hasHiddenColumns()) { const items = this._dataController.items(); const item = items[rowIndex]; const oldExpandRowIndex = gridCoreUtils.getIndexByKey(this._dataController.getAdaptiveExpandedKey(), items); @@ -66,14 +63,13 @@ export const adaptivityEditingViewControllerExtender = ( super._afterInsertRow.apply(this, arguments as any); if (this.adaptiveColumnsController.hasHiddenColumns()) { - // @ts-expect-error - this.adaptiveColumnsController.toggleExpandAdaptiveDetailRow(key, this.isRowEditMode()); + this.adaptiveColumnsController.toggleExpandAdaptiveDetailRow(key, this.getEditMode() === EDIT_MODE.row); this._isForceRowAdaptiveExpand = true; } } private _collapseAdaptiveDetailRow() { - if (this._isRowEditMode() && this._isForceRowAdaptiveExpand) { + if (this.getEditMode() === EDIT_MODE.row && this._isForceRowAdaptiveExpand) { this.adaptiveColumnsController.collapseAdaptiveDetailRow(); this._isForceRowAdaptiveExpand = false; } @@ -89,7 +85,7 @@ export const adaptivityEditingViewControllerExtender = ( super._afterSaveEditData.apply(this, arguments as any); // @ts-expect-error const deferred = new Deferred(); - if (this._isRowEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { + if (this.getEditMode() === EDIT_MODE.row && this.adaptiveColumnsController.hasHiddenColumns()) { when(this._validatingController.validate(true)).done((isValid) => { if (isValid) { this._cancelEditAdaptiveDetailRow(); @@ -129,7 +125,7 @@ export const adaptivityEditingViewControllerExtender = ( } public editRow(rowIndex) { - if (this.adaptiveColumnsController.isFormOrPopupEditMode()) { + if (isFormBasedEditMode(this.getEditMode())) { this.adaptiveColumnsController.collapseAdaptiveDetailRow(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_keyboard_navigation_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_keyboard_navigation_view_controller.ts index acd023329731..717f46f3ba95 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_keyboard_navigation_view_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_keyboard_navigation_view_controller.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ import eventsEngine from '@js/common/core/events/core/events_engine'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import { isCellBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import type { KeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation'; import type { ModuleType } from '@ts/grids/grid_core/types'; @@ -31,7 +32,7 @@ export const adaptivityKeyboardNavigationViewControllerExtender = ( protected _processNextCellInMasterDetail($nextCell, $cell) { super._processNextCellInMasterDetail($nextCell, $cell); - const isCellOrBatchMode = this._editingController.isCellOrBatchEditMode(); + const isCellOrBatchMode = isCellBasedEditMode(this._editingController.getEditMode()); const isEditing = this._editingController.isEditing(); if ( diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts index 914f714c3d4b..6ced110aa227 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts @@ -44,14 +44,15 @@ describe('DataGrid cell-based editing', () => { }, }); const editingController = getEditingController(instance); - const isCellOrBatchEditMode = jest.spyOn(editingController, 'isCellOrBatchEditMode'); instance.dispose(); $container.remove(); + + const getEditMode = jest.spyOn(editingController, 'getEditMode'); eventsEngine.trigger(document, 'dxpointerdown'); eventsEngine.trigger(document, 'dxclick'); - expect(isCellOrBatchEditMode).not.toHaveBeenCalled(); + expect(getEditMode).not.toHaveBeenCalled(); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_utils.test.ts new file mode 100644 index 000000000000..2897885eeafc --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_utils.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from '@jest/globals'; +import type { GridsEditMode } from '@js/common/grids'; + +import { + isCellBasedEditMode, + isFormBasedEditMode, + isRowBasedEditMode, +} from '../editing_utils'; + +describe('Edit mode checks', () => { + it.each<[GridsEditMode, boolean, boolean, boolean]>([ + ['row', false, true, false], + ['form', false, true, true], + ['popup', false, true, true], + ['cell', true, false, false], + ['batch', true, false, false], + ])('%s mode: cell-based %s, row-based %s, form-based %s', ( + editMode, + isCellBased, + isRowBased, + isFormBased, + ) => { + expect(isCellBasedEditMode(editMode)).toBe(isCellBased); + expect(isRowBasedEditMode(editMode)).toBe(isRowBased); + expect(isFormBasedEditMode(editMode)).toBe(isFormBased); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/const.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/const.ts index de65503ec82a..7d19cdccb196 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/const.ts @@ -88,6 +88,7 @@ export const EDIT_MODES: GridsEditMode[] = [ ]; export const ROW_BASED_MODES: GridsEditMode[] = [EDIT_MODE.row, EDIT_MODE.form, EDIT_MODE.popup]; export const CELL_BASED_MODES: GridsEditMode[] = [EDIT_MODE.batch, EDIT_MODE.cell]; +export const FORM_BASED_MODES: GridsEditMode[] = [EDIT_MODE.popup, EDIT_MODE.form]; export const REQUIRED_EDITOR_LABELLEDBY_MODES: GridsEditMode[] = [ EDIT_MODE.batch, EDIT_MODE.row, EDIT_MODE.cell, ]; diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index 20d63a0071bf..1c02b67d8f95 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -74,7 +74,6 @@ import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { ACTION_OPTION_NAMES, BUTTON_NAMES, - CELL_BASED_MODES, CLASSES, DATA_CHANGE_TYPE, DEFAULT_START_EDIT_ACTION, @@ -93,7 +92,6 @@ import { METHOD_NAMES, NEW_ROW_POSITION, REQUIRED_EDITOR_LABELLEDBY_MODES, - ROW_BASED_MODES, TARGET_COMPONENT_NAME, } from './const'; import { @@ -102,8 +100,10 @@ import { getButtonIndex, getButtonName, getEditingTexts, + isCellBasedEditMode, isEditingCell, isEditingOrShowEditorAlwaysDataCell, + isRowBasedEditMode, } from './editing_utils'; import { editingDataControllerExtender } from './extenders/editing_data_controller'; import type { CellBasedEditingController } from './m_editing_cell_based'; @@ -251,11 +251,6 @@ export class EditingController extends modules.ViewController { return EDIT_MODE.row; } - public isCellBasedEditMode(): boolean { - const editMode: GridsEditMode = this.getEditMode(); - return CELL_BASED_MODES.includes(editMode); - } - private _getDefaultEditorTemplate(): EditorTemplate { return (container, options): void => { const $editor = $('
').appendTo(container); @@ -397,11 +392,6 @@ export class EditingController extends modules.ViewController { return true; } - public isPopupEditMode(): boolean { - const editMode = this.option('editing.mode'); - return editMode === EDIT_MODE.popup; - } - private _isButtonVisible(button: InternalColumnButton, options: EditorCellOptions): boolean { const { visible } = button; @@ -557,11 +547,6 @@ export class EditingController extends modules.ViewController { }; } - public isRowBasedEditMode(): boolean { - const editMode = this.getEditMode(); - return ROW_BASED_MODES.includes(editMode); - } - protected getFirstEditableColumnIndex(): number | undefined { const columnIndex = this._columnsController.getVisibleColumns() .findIndex((column) => column.allowEditing); @@ -2527,7 +2512,7 @@ export class EditingController extends modules.ViewController { const { column, row, rowType } = options; const rowIndex = row?.rowIndex; - const isRowMode = this.isRowBasedEditMode(); + const isRowMode = isRowBasedEditMode(this.getEditMode()); const isRowEditing = this.isEditRow(rowIndex); const isCellEditing = this.isEditCell(rowIndex, options.columnIndex); @@ -3066,7 +3051,7 @@ const rowsView = >( && !cellOptions.modified && cellOptions.rowType === 'data' && cellOptions.column.calculateCellValue === cellOptions.column.defaultCalculateCellValue - && this._editingController.isCellBasedEditMode(); + && isCellBasedEditMode(this._editingController.getEditMode()); if (isEditableCell) { this.setAria( diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing_utils.ts index 06e54b45bae0..dc984b7e1b18 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing_utils.ts @@ -1,3 +1,4 @@ +import type { GridsEditMode } from '@js/common/grids'; import Guid from '@js/core/guid'; import type { dxElementWrapper } from '@js/core/renderer'; import type { DeferredObj } from '@js/core/utils/deferred'; @@ -10,7 +11,13 @@ import type Validator from '@ts/ui/validator'; import type { ProcessedItem } from '../data_controller/types'; import type { InternalGrid, RowKey } from '../types'; -import { GUID_LENGTH, NEW_ROW_TEMP_KEY_PREFIX } from './const'; +import { + CELL_BASED_MODES, + FORM_BASED_MODES, + GUID_LENGTH, + NEW_ROW_TEMP_KEY_PREFIX, + ROW_BASED_MODES, +} from './const'; import type { EditFormColumnItem, EditFormItemNode, @@ -19,6 +26,18 @@ import type { EditingCellOptions, } from './types'; +export const isCellBasedEditMode = ( + editMode: GridsEditMode, +): boolean => CELL_BASED_MODES.includes(editMode); + +export const isRowBasedEditMode = ( + editMode: GridsEditMode, +): boolean => ROW_BASED_MODES.includes(editMode); + +export const isFormBasedEditMode = ( + editMode: GridsEditMode, +): boolean => FORM_BASED_MODES.includes(editMode); + export const createFailureHandler = ( deferred: DeferredObj, ): ((arg: unknown) => void) => (arg: unknown): void => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_form_based_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_form_based_data_controller.ts index 6def240ff5dc..f0ef06f3ecab 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_form_based_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_form_based_data_controller.ts @@ -2,20 +2,21 @@ import type { DataController } from '@ts/grids/grid_core/data_controller/data_co import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { ModuleType } from '@ts/grids/grid_core/types'; -import type { FormBasedEditingController } from '../m_editing_form_based'; +import { EDIT_MODE } from '../const'; +import type { EditingController } from '../editing'; export const editingFormBasedDataControllerExtender = ( Base: ModuleType, ): ModuleType => class EditingFormBasedDataControllerExtender extends Base { - protected _editingController!: FormBasedEditingController; + protected _editingController!: EditingController; public init(): void { - this._editingController = this.getController('editing') as FormBasedEditingController; + this._editingController = this.getController('editing'); super.init(); } private _updateEditItem(item: ProcessedItem): void { - if (this._editingController.isFormEditMode()) { + if (this._editingController.getEditMode() === EDIT_MODE.form) { item.rowType = 'detail'; } } @@ -26,7 +27,9 @@ export const editingFormBasedDataControllerExtender = ( visibleRowIndex: number, isLiveUpdate?: boolean, ): number[] | undefined { - if (isLiveUpdate === false && newItem.isEditing && this._editingController.isFormEditMode()) { + const isFormEditMode = this._editingController.getEditMode() === EDIT_MODE.form; + + if (isLiveUpdate === false && newItem.isEditing && isFormEditMode) { return undefined; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_row_based_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_row_based_data_controller.ts index 02034f5771d3..bcb2833c3942 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_row_based_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_row_based_data_controller.ts @@ -3,6 +3,7 @@ import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { ModuleType } from '@ts/grids/grid_core/types'; import type { EditingController } from '../editing'; +import { isRowBasedEditMode } from '../editing_utils'; export const editingRowBasedDataControllerExtender = ( Base: ModuleType, @@ -20,7 +21,9 @@ export const editingRowBasedDataControllerExtender = ( visibleRowIndex: number, isLiveUpdate?: boolean, ): number[] | undefined { - if (this._editingController.isRowBasedEditMode() && oldItem.isEditing !== newItem.isEditing) { + const isRowBasedMode = isRowBasedEditMode(this._editingController.getEditMode()); + + if (isRowBasedMode && oldItem.isEditing !== newItem.isEditing) { return undefined; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index 4607ed5f5970..d0e74c7015b2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -37,7 +37,7 @@ import { TARGET_COMPONENT_NAME, } from './const'; import type { EditingController } from './editing'; -import { isEditable } from './editing_utils'; +import { isCellBasedEditMode, isEditable } from './editing_utils'; import type { EditCellOptions, EditCellResult, @@ -51,8 +51,6 @@ import type { } from './types'; export interface CellBasedEditingControllerExtension { - isCellOrBatchEditMode: () => boolean; - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods closeEditCell(isError?: boolean | null, withoutSaveEditData?: boolean): DeferredObj; @@ -116,7 +114,9 @@ const editingControllerExtender = ( return; } - if (!this.isCellOrBatchEditMode() || this._editCellInProgress) { + const editMode = this.getEditMode(); + + if (!isCellBasedEditMode(editMode) || this._editCellInProgress) { return; } @@ -126,7 +126,9 @@ const editingControllerExtender = ( const addRowButtonSelector = `.${this.addWidgetPrefix(CLASSES.addRowButton)}`; const isAddRowButton = !!$target.closest(addRowButtonSelector).length; - const isAddRowWhileCellEditing = isAddRowButton && this.isCellEditMode() && this.isEditing(); + const isAddRowWhileCellEditing = isAddRowButton + && editMode === EDIT_MODE.cell + && this.isEditing(); const isFocusOverlay = $target.hasClass(this.addWidgetPrefix(CLASSES.focusOverlay)); if (isFocusOverlay || isAddRowWhileCellEditing) { @@ -160,18 +162,6 @@ const editingControllerExtender = ( eventsEngine.off(domAdapter.getDocument(), clickEventName, this._saveEditorHandler); } - private isCellEditMode(): boolean { - return this.option('editing.mode') === EDIT_MODE.cell; - } - - private isBatchEditMode(): boolean { - return this.option('editing.mode') === EDIT_MODE.batch; - } - - public isCellOrBatchEditMode(): boolean { - return this.isCellEditMode() || this.isBatchEditMode(); - } - protected _needToCloseEditableCell($targetElement: dxElementWrapper): boolean | undefined { if (this.isEditing() && !this.isClickOnEditableCell($targetElement)) { return true; @@ -212,7 +202,7 @@ const editingControllerExtender = ( } public _focusEditorIfNeed(): void { - if (this._needFocusEditor && this.isCellOrBatchEditMode()) { + if (this._needFocusEditor && isCellBasedEditMode(this.getEditMode())) { const editColumnIndex = this._getVisibleEditColumnIndex(); const $cell = this._rowsView?._getCellElement( this._getVisibleEditRowIndex(), @@ -238,7 +228,7 @@ const editingControllerExtender = ( } public isEditing(): boolean { - if (this.isCellOrBatchEditMode()) { + if (isCellBasedEditMode(this.getEditMode())) { const isEditRowKeyDefined = isDefined(this.option(EDITING_EDITROWKEY_OPTION_NAME)); const isEditColumnNameDefined = isDefined(this.option(EDITING_EDITCOLUMNNAME_OPTION_NAME)); @@ -254,7 +244,7 @@ const editingControllerExtender = ( const isEditRowVisible = oldRowIndex !== -1; const isColumnChanged = isDefined(columnName) && columnName !== oldColumnName; - if (!this.isCellOrBatchEditMode() || !isEditRowVisible || !isColumnChanged) { + if (!isCellBasedEditMode(this.getEditMode()) || !isEditRowVisible || !isColumnChanged) { return; } @@ -266,7 +256,7 @@ const editingControllerExtender = ( } protected _addRow(parentKey?: RowKey): DeferredObj { - if (this.isCellEditMode() && this.hasChanges()) { + if (this.getEditMode() === EDIT_MODE.cell && this.hasChanges()) { const deferred = Deferred(); this.saveEditData().done(() => { @@ -317,7 +307,7 @@ const editingControllerExtender = ( return undefined; } - if (column && (item.rowType === 'data' || item.rowType === 'detailAdaptive') && !item.removed && this.isCellOrBatchEditMode()) { + if (column && (item.rowType === 'data' || item.rowType === 'detailAdaptive') && !item.removed && isCellBasedEditMode(this.getEditMode())) { if (this.isEditCell(rowIndex, columnIndex)) { return true; } @@ -344,7 +334,7 @@ const editingControllerExtender = ( columnIndex: number, item: ProcessedItem, ): DeferredObj | boolean { - if (this.isCellEditMode() && !item.isNewRow && this.hasChanges()) { + if (this.getEditMode() === EDIT_MODE.cell && !item.isNewRow && this.hasChanges()) { const isSaving = Deferred(); this.saveEditData().always(() => { isSaving.resolve(this.hasChanges()); @@ -432,7 +422,7 @@ const editingControllerExtender = ( let result = when(); const oldEditRowIndex = this._getVisibleEditRowIndex(); - if (this.isCellOrBatchEditMode()) { + if (isCellBasedEditMode(this.getEditMode())) { const deferred = Deferred(); result = Deferred(); this.executeOperation(deferred, () => { @@ -461,7 +451,7 @@ const editingControllerExtender = ( // @ts-expect-error promise() is typed as Promise but returns a Deferred-like value at runtime const promise: DeferredObj = deferred.promise(); - if (this.isCellEditMode() && this.hasChanges()) { + if (this.getEditMode() === EDIT_MODE.cell && this.hasChanges()) { if (!withoutSaveEditData) { this.saveEditData().done((error) => { if (!this.hasChanges()) { @@ -494,7 +484,7 @@ const editingControllerExtender = ( } private _resetModifiedClassCells(changes: EditingChange[]): void { - if (this.isBatchEditMode()) { + if (this.getEditMode() === EDIT_MODE.batch) { const columnsCount = this._columnsController.getVisibleColumns().length; changes.forEach(({ key }) => { const rowIndex = this._dataController.getRowIndexByKey(key); @@ -513,7 +503,7 @@ const editingControllerExtender = ( ): DeferredObj { const $cellElement = $(options.cellElement); - if (this.isBatchEditMode() && options.key !== undefined) { + if (this.getEditMode() === EDIT_MODE.batch && options.key !== undefined) { this._applyModified($cellElement, options); } @@ -521,8 +511,10 @@ const editingControllerExtender = ( } protected _cancelSaving(saveDeferred: DeferredObj): void { - if (this.isCellOrBatchEditMode()) { - if (this.isBatchEditMode()) { + const editMode = this.getEditMode(); + + if (isCellBasedEditMode(editMode)) { + if (editMode === EDIT_MODE.batch) { this._resetEditIndices(); } @@ -559,7 +551,7 @@ const editingControllerExtender = ( } protected _handleEditRowKeyChange(args: OptionChanged): void { - if (this.isCellOrBatchEditMode()) { + if (isCellBasedEditMode(this.getEditMode())) { const columnIndex = this._getVisibleEditColumnIndex(); const oldRowIndexCorrection = this._getEditRowIndexCorrection(); const oldRowIndex = this._dataController.getRowIndexByKey(args.previousValue) @@ -574,7 +566,7 @@ const editingControllerExtender = ( } protected deleteRow(rowIndex: number): void { - if (this.isCellEditMode() && this.isEditing()) { + if (this.getEditMode() === EDIT_MODE.cell && this.isEditing()) { const { isNewRow } = this._dataController.items()[rowIndex]; const rowKey = this._dataController.getKeyByRowIndex(rowIndex); @@ -589,7 +581,7 @@ const editingControllerExtender = ( } protected _checkAndDeleteRow(rowIndex: number): void { - if (this.isBatchEditMode()) { + if (this.getEditMode() === EDIT_MODE.batch) { this._deleteRowCore(rowIndex); } else { super._checkAndDeleteRow(rowIndex); @@ -598,10 +590,11 @@ const editingControllerExtender = ( protected _refreshCore(params?: RefreshOptions): void { const { isPageChanged } = params ?? {}; - const needResetIndexes = this.isBatchEditMode() + const editMode = this.getEditMode(); + const needResetIndexes = editMode === EDIT_MODE.batch || (isPageChanged && this.option('scrolling.mode') !== 'virtual'); - if (this.isCellOrBatchEditMode()) { + if (isCellBasedEditMode(editMode)) { if (needResetIndexes) { this._resetEditColumnName(); this._resetEditRowKey(); @@ -612,7 +605,7 @@ const editingControllerExtender = ( } protected _allowRowAdding(): boolean { - if (this.isBatchEditMode()) { + if (this.getEditMode() === EDIT_MODE.batch) { return true; } @@ -622,7 +615,7 @@ const editingControllerExtender = ( protected _afterDeleteRow(rowIndex: number, oldEditRowIndex: number): DeferredObj { const dataController = this._dataController; - if (this.isBatchEditMode()) { + if (this.getEditMode() === EDIT_MODE.batch) { dataController.updateItems({ changeType: 'update', rowIndices: [oldEditRowIndex, rowIndex], @@ -639,7 +632,7 @@ const editingControllerExtender = ( forceUpdateRow?: boolean, isCustomSetCellValue?: boolean, ): void { - if (this.isCellOrBatchEditMode()) { + if (isCellBasedEditMode(this.getEditMode())) { this._updateRowImmediately(row, forceUpdateRow, isCustomSetCellValue); } else { super._updateEditRow(row, forceUpdateRow, isCustomSetCellValue); @@ -650,8 +643,10 @@ const editingControllerExtender = ( button: InternalColumnButton, options: EditorCellOptions, ): boolean { - if (this.isCellOrBatchEditMode()) { - const isBatchMode = this.isBatchEditMode(); + const editMode = this.getEditMode(); + + if (isCellBasedEditMode(editMode)) { + const isBatchMode = editMode === EDIT_MODE.batch; switch (button.name) { case 'save': @@ -673,12 +668,14 @@ const editingControllerExtender = ( } protected _beforeEndSaving(changes: EditingChange[]): void { - if (this.isCellEditMode()) { + const editMode = this.getEditMode(); + + if (editMode === EDIT_MODE.cell) { if (changes[0]?.type !== 'update') { super._beforeEndSaving(changes); } } else { - if (this.isBatchEditMode()) { + if (editMode === EDIT_MODE.batch) { this._resetModifiedClassCells(changes); } super._beforeEndSaving(changes); @@ -692,7 +689,7 @@ const editingControllerExtender = ( || !!editingOptions.allowAdding || editingOptions.allowDeleting; - if (needEditingButtons && this.isBatchEditMode()) { + if (needEditingButtons && this.getEditMode() === EDIT_MODE.batch) { buttonItems.push(this.prepareButtonItem(headerPanel, 'save', 'saveEditData', 21)); buttonItems.push(this.prepareButtonItem(headerPanel, 'revert', 'cancelEditData', 22)); } @@ -704,7 +701,7 @@ const editingControllerExtender = ( const editRow = this._dataController.getVisibleRows()[this.getEditRowIndex()]; const editColumn = this._getEditColumn(); const showEditorAlways = editColumn?.showEditorAlways; - const isUpdateInCellMode = this.isCellEditMode() && !editRow?.isNewRow; + const isUpdateInCellMode = this.getEditMode() === EDIT_MODE.cell && !editRow?.isNewRow; // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the if below let deferred: DeferredObj | undefined; @@ -723,7 +720,7 @@ const editingControllerExtender = ( forceUpdateRow?: boolean, ): DeferredObj | undefined { const { row } = options; - const isUpdateInCellMode = this.isCellEditMode() && row && !row.isNewRow; + const isUpdateInCellMode = this.getEditMode() === EDIT_MODE.cell && row && !row.isNewRow; const { showEditorAlways } = options.column; const isCustomSetCellValue = options.column.setCellValue !== options.column.defaultSetCellValue; const focusPreviousEditingCell = showEditorAlways @@ -747,7 +744,8 @@ const editingControllerExtender = ( ): DeferredObj | undefined { const { showEditorAlways } = options.column; const { row } = options; - const isUpdateInCellMode = this.isCellEditMode() && row && !row.isNewRow; + const editMode = this.getEditMode(); + const isUpdateInCellMode = editMode === EDIT_MODE.cell && row && !row.isNewRow; if (showEditorAlways && !forceUpdateRow) { if (isUpdateInCellMode) { @@ -755,7 +753,7 @@ const editingControllerExtender = ( this._setEditColumnNameByIndex(options.columnIndex, true); return this.saveEditData(); - } if (this.isBatchEditMode()) { + } if (editMode === EDIT_MODE.batch) { const needUpdateRow = this._needUpdateRow(options.column); return super._applyChangeCore(options, needUpdateRow); @@ -773,7 +771,7 @@ const editingControllerExtender = ( ): void { const { data, type } = change; - if (this.isBatchEditMode() && type === DATA_CHANGE_TYPE.remove) { + if (this.getEditMode() === EDIT_MODE.batch && type === DATA_CHANGE_TYPE.remove) { item.data = createObjectWithChanges(item.data, data); } @@ -785,7 +783,7 @@ const editingControllerExtender = ( editIndex: number, processIfBatch: boolean, ): boolean { - if (this.isBatchEditMode() && !processIfBatch) { + if (this.getEditMode() === EDIT_MODE.batch && !processIfBatch) { return false; } @@ -793,7 +791,7 @@ const editingControllerExtender = ( } public _processRemoveIfError(changes: EditingChange[], editIndex: number): boolean { - if (this.isBatchEditMode()) { + if (this.getEditMode() === EDIT_MODE.batch) { return false; } @@ -818,7 +816,7 @@ const rowsView = ( protected _createTable(columns?: Column[], isAppend?: boolean): dxElementWrapper { const $table = super._createTable(columns, isAppend); - const isCellOrBatchEditMode = this._editingController.isCellOrBatchEditMode(); + const isCellOrBatchEditMode = isCellBasedEditMode(this._editingController.getEditMode()); const allowUpdating = this.option('editing.allowUpdating'); if (isCellOrBatchEditMode && allowUpdating) { @@ -843,8 +841,7 @@ const rowsView = ( if (row) { const isRowRemoved = !!row.removed; - // @ts-expect-error isBatchEditMode is added by this module's controller extender - if (this._editingController.isBatchEditMode()) { + if (this._editingController.getEditMode() === EDIT_MODE.batch) { if (isRowRemoved) { $row.addClass(CLASSES.rowRemoved); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts index 39932a380543..4c17c2eeb7c9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts @@ -38,7 +38,12 @@ import { FOCUSABLE_ELEMENT_SELECTOR, } from './const'; import type { EditingController } from './editing'; -import { forEachFormItems, getEditorType, getEditorValidator } from './editing_utils'; +import { + forEachFormItems, + getEditorType, + getEditorValidator, + isFormBasedEditMode, +} from './editing_utils'; import { editingFormBasedDataControllerExtender } from './extenders/editing_form_based_data_controller'; import type { EditFormColumnItem, @@ -50,7 +55,6 @@ import type { } from './types'; export interface FormBasedEditingControllerExtension { - isFormEditMode: () => boolean; renderFormEditorTemplate: ( detailCellOptions: EditFormDetailOptions, item: EditFormColumnItem, @@ -90,22 +94,13 @@ const editingControllerExtender = ( } public isEditRow(rowIndex: number | undefined): boolean { - return !this.isPopupEditMode() && super.isEditRow(rowIndex); - } - - private isFormOrPopupEditMode(): boolean { - return this.isPopupEditMode() || this.isFormEditMode(); - } - - public isFormEditMode(): boolean { - const editMode = this.option('editing.mode'); - return editMode === EDIT_MODE.form; + return this.getEditMode() !== EDIT_MODE.popup && super.isEditRow(rowIndex); } protected getFirstEditableColumnIndex(): number | undefined { const firstFormItem = this._firstFormItem; - if (this.isFormEditMode() && firstFormItem) { + if (this.getEditMode() === EDIT_MODE.form && firstFormItem) { const editRowKey = this.option(EDITING_EDITROWKEY_OPTION_NAME); const editRowIndex = this._dataController.getRowIndexByKey(editRowKey); const $editFormElements = this._rowsView.getCellElements(editRowIndex); @@ -116,7 +111,7 @@ const editingControllerExtender = ( } public getEditFormRowIndex(): number { - return this.isFormOrPopupEditMode() + return isFormBasedEditMode(this.getEditMode()) ? this._getVisibleEditRowIndex() : super.getEditFormRowIndex(); } @@ -125,11 +120,13 @@ const editingControllerExtender = ( const result = super._isEditColumnVisible(); const editingOptions = this.option('editing'); - return this.isFormOrPopupEditMode() ? !!editingOptions?.allowUpdating || result : result; + return isFormBasedEditMode(this.getEditMode()) + ? !!editingOptions?.allowUpdating || result + : result; } protected _handleDataChanged(args: ViewDataChange & { isOptionChanged?: boolean }): void { - if (this.isPopupEditMode()) { + if (this.getEditMode() === EDIT_MODE.popup) { const editRowKey = this.option('editing.editRowKey'); const hasEditRow = args?.items?.some((item) => equalByValue(item.key, editRowKey)); const isEditRowChangedByOption = hasEditRow && args.isOptionChanged; @@ -148,7 +145,7 @@ const editingControllerExtender = ( protected getPopupContent(): dxElementWrapper | undefined { const popupVisible = this._editPopup?.option('visible'); - if (this.isPopupEditMode() && popupVisible) { + if (this.getEditMode() === EDIT_MODE.popup && popupVisible) { return this._$popupContent; } @@ -156,7 +153,7 @@ const editingControllerExtender = ( } protected _showAddedRow(rowIndex: number): void { - if (this.isPopupEditMode()) { + if (this.getEditMode() === EDIT_MODE.popup) { this._showEditPopup(rowIndex); } else { super._showAddedRow(rowIndex); @@ -166,7 +163,7 @@ const editingControllerExtender = ( protected _cancelEditDataCore(): void { super._cancelEditDataCore(); - if (this.isPopupEditMode()) { + if (this.getEditMode() === EDIT_MODE.popup) { this._hideEditPopup(); } } @@ -178,7 +175,7 @@ const editingControllerExtender = ( ): void { const editForm = this._editForm; - if (this.isPopupEditMode()) { + if (this.getEditMode() === EDIT_MODE.popup) { if (this.option('repaintChangesOnly')) { row.update?.(row); this._rowsView.renderDelayedTemplates(); @@ -286,7 +283,7 @@ const editingControllerExtender = ( } public optionChanged(args: OptionChanged): void { - if (args.name === 'editing' && this.isFormOrPopupEditMode()) { + if (args.name === 'editing' && isFormBasedEditMode(this.getEditMode())) { const { fullName } = args; if (fullName.startsWith(EDITING_FORM_OPTION_NAME)) { @@ -302,7 +299,7 @@ const editingControllerExtender = ( } private _handleFormOptionChange(args: OptionChanged): void { - if (this.isFormEditMode()) { + if (this.getEditMode() === EDIT_MODE.form) { const editRowIndex = this._getVisibleEditRowIndex(); if (editRowIndex >= 0) { this._dataController.updateItems({ @@ -567,7 +564,7 @@ const editingControllerExtender = ( protected _beforeEndSaving(changes: EditingChange[]): void { super._beforeEndSaving(changes); - if (this.isPopupEditMode()) { + if (this.getEditMode() === EDIT_MODE.popup) { // eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget this._editPopup?.hide(); } @@ -579,7 +576,7 @@ const editingControllerExtender = ( key: RowKey, columns: Column[], ): void { - if (this.isPopupEditMode() && change.type === DATA_CHANGE_TYPE.insert) { + if (this.getEditMode() === EDIT_MODE.popup && change.type === DATA_CHANGE_TYPE.insert) { item.visible = false; } @@ -587,7 +584,7 @@ const editingControllerExtender = ( } protected _editRowFromOptionChangedCore(rowIndices: number[], rowIndex: number): void { - const isPopupEditMode = this.isPopupEditMode(); + const isPopupEditMode = this.getEditMode() === EDIT_MODE.popup; super._editRowFromOptionChangedCore(rowIndices, rowIndex, isPopupEditMode); @@ -618,7 +615,7 @@ const rowsView = ( ): void { if ( options.rowType === 'data' - && this._editingController.isPopupEditMode() + && this._editingController.getEditMode() === EDIT_MODE.popup && options.row?.visible === false ) { return; @@ -667,7 +664,7 @@ const rowsView = ( private _isFormItem(parameters: EditorCellOptions): EditFormColumnItem | false | undefined { const isDetailRow = parameters.rowType === 'detail' || parameters.rowType === 'detailAdaptive'; - const isPopupEditing = parameters.rowType === 'data' && this._editingController.isPopupEditMode(); + const isPopupEditing = parameters.rowType === 'data' && this._editingController.getEditMode() === EDIT_MODE.popup; return (isDetailRow || isPopupEditing) && parameters.item; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_row_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_row_based.ts index 4ae714a0ce83..ef48e256e1ec 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_row_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_row_based.ts @@ -15,18 +15,15 @@ import { MODES_WITH_DELAYED_FOCUS, } from './const'; import type { EditingController } from './editing'; +import { isRowBasedEditMode } from './editing_utils'; import { editingRowBasedDataControllerExtender } from './extenders/editing_row_based_data_controller'; import type { EditorCellOptions, InternalColumnButton } from './types'; const editingControllerExtender = ( Base: ModuleType, ): ModuleType => class RowBasedEditingControllerExtender extends Base { - private isRowEditMode(): boolean { - return this.getEditMode() === EDIT_MODE.row; - } - protected _afterCancelEditData(rowIndex: number): void { - if (this.isRowBasedEditMode() && rowIndex >= 0) { + if (isRowBasedEditMode(this.getEditMode()) && rowIndex >= 0) { this._dataController.updateItems({ changeType: 'update', rowIndices: [rowIndex, rowIndex + 1], @@ -40,8 +37,9 @@ const editingControllerExtender = ( button: InternalColumnButton, options: EditorCellOptions, ): boolean { - const isRowMode = this.isRowBasedEditMode(); - const isEditRow = !this.isPopupEditMode() + const editMode = this.getEditMode(); + const isRowMode = isRowBasedEditMode(editMode); + const isEditRow = editMode !== EDIT_MODE.popup && !!options.row && equalByValue(options.row.key, this.option(EDITING_EDITROWKEY_OPTION_NAME)); @@ -63,11 +61,11 @@ const editingControllerExtender = ( } public isEditRow(rowIndex: number | undefined): boolean { - return this.isRowBasedEditMode() && this.isEditRowByIndex(rowIndex); + return isRowBasedEditMode(this.getEditMode()) && this.isEditRowByIndex(rowIndex); } protected _cancelSaving(saveDeferred: DeferredObj): void { - if (this.isRowBasedEditMode()) { + if (isRowBasedEditMode(this.getEditMode())) { if (!this.hasChanges()) { this._cancelEditDataCore(); } @@ -78,7 +76,7 @@ const editingControllerExtender = ( protected _refreshCore(params?: RefreshOptions): void { const { allowCancelEditing } = params ?? {}; - if (this.isRowBasedEditMode()) { + if (isRowBasedEditMode(this.getEditMode())) { const hasUpdateChanges = this.getChanges().filter((it) => it.type === 'update').length > 0; this.init(); @@ -93,7 +91,7 @@ const editingControllerExtender = ( protected _isEditColumnVisible(): boolean { const result = super._isEditColumnVisible(); const editingOptions = this.option('editing'); - const isRowEditMode = this.isRowEditMode(); + const isRowEditMode = this.getEditMode() === EDIT_MODE.row; const isVisibleInRowEditMode = !!editingOptions?.allowUpdating || !!editingOptions?.allowAdding; return result || (isRowEditMode && isVisibleInRowEditMode); diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/__tests__/utils.test.ts new file mode 100644 index 000000000000..a8e9403edd58 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/__tests__/utils.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from '@jest/globals'; +import type { GridsEditMode } from '@js/common/grids'; + +import { isRowOrFormEditMode } from '../utils'; + +describe('isRowOrFormEditMode', () => { + it.each<[GridsEditMode, boolean]>([ + ['row', true], + ['form', true], + ['popup', false], + ['cell', false], + ['batch', false], + ])('%s mode: %s', (editMode, expected) => { + expect(isRowOrFormEditMode(editMode)).toBe(expected); + }); +}); 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 d2c19a4b2ff0..f54cf791fa1f 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 @@ -31,6 +31,7 @@ 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 { DataChange, RowIndexCorrection } from '@ts/grids/grid_core/data_controller/types'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import { isCellBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based'; import { CLASSES as FILTER_ROW_CLASSES } from '@ts/grids/grid_core/filter_row/const'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; @@ -87,6 +88,7 @@ import { isGroupRow, isMobile, isNotFocusedRow, + isRowOrFormEditMode, shouldPreventScroll, } from './utils'; @@ -522,7 +524,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo this._editorFactory.loseFocus(); - if (this._editingController.isEditing() && !this.isRowEditMode()) { + if (this._editingController.isEditing() + && !isRowOrFormEditMode(this._editingController.getEditMode())) { // Focus the cell before closing the editor on native Tab-out, // so the browser's native Shift+Tab leaves from a stable anchor. ($cell.get(0) as HTMLElement | undefined)?.focus({ preventScroll: true }); @@ -1082,7 +1085,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const nextCellFocused = this._focusCell($nextCell, !isHighlighted); if (nextCellFocused) { - if (!this.isRowEditMode() && isEditingAllowed) { + if (!isRowOrFormEditMode(this._editingController.getEditMode()) && isEditingAllowed) { this._editFocusedCell(); } else { this._focusInteractiveElement($nextCell, isShift); @@ -1140,7 +1143,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const $cell: dxElementWrapper = this.getCellElementFromTarget(eventTarget); // Non-editor cells with intermediate interactive elements use native tab - if (!isEditorCell(this, $cell) && this.isOriginalTabHandlerRequired($cell, eventArgs)) { + if (!isEditorCell(this._editingController.getEditMode(), $cell) + && this.isOriginalTabHandlerRequired($cell, eventArgs)) { return true; } @@ -1175,7 +1179,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo this._focusCell($newFocusedCell, !nextCellInfo.isHighlighted); - if (!isEditorCell(this, $newFocusedCell)) { + if (!isEditorCell(this._editingController.getEditMode(), $newFocusedCell)) { this._focusInteractiveElement($newFocusedCell, eventArgs.shift); } @@ -1327,11 +1331,11 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const d = Deferred(); const { target } = event; const $cell = this.getCellElementFromTarget(target); - const isRowEditMode = this.isRowEditMode(); + const isRowOrFormMode = isRowOrFormEditMode(this._editingController.getEditMode()); this._updateFocusedCellPosition($cell); - if (isRowEditMode) { + if (isRowOrFormMode) { this._focusEditFormCell($cell); setTimeout( // eslint-disable-next-line @typescript-eslint/no-misused-promises -- ignores the result @@ -1358,8 +1362,10 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo ); if (isEditing) { this._updateFocusedCellPosition($cell); - if (!this.isRowEditMode()) { - if (this._editingController.getEditMode() === 'cell') { + const editMode = this._editingController.getEditMode(); + + if (!isRowOrFormEditMode(editMode)) { + if (editMode === EDIT_MODE.cell) { this._editingController.cancelEditData(); } else { this._closeEditCell(); @@ -1633,7 +1639,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo private _clickTargetCellHandler(event, $cell) { const column = this._getColumnByCellElement($cell); - const isCellEditMode = this._isCellEditMode(); + const isCellOrBatchEditMode = isCellBasedEditMode(this._editingController.getEditMode()); const isEditing = this._editingController.isEditing(); this.setCellFocusType(); @@ -1650,7 +1656,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo $cell = this._getFocusedCell(); } - if (!args.isHighlighted && !isCellEditMode) { + if (!args.isHighlighted && !isCellOrBatchEditMode) { this.setRowFocusType(); } @@ -1658,7 +1664,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if ( this._allowRowUpdating() - && isCellEditMode + && isCellOrBatchEditMode && column && column.allowEditing ) { @@ -1680,7 +1686,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo } else { this.setRowFocusType(); this.setFocusedRowIndex(args.prevRowIndex); - if (isEditing && isCellEditMode) { + if (isEditing && isCellOrBatchEditMode) { this._closeEditCell(); } } @@ -1863,7 +1869,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo let $cell: dxElementWrapper | null = this._getFocusedCell(); const isEditing = this._editingController.isEditing(); - if (!this.getMasterDetailCell($cell) || this.isRowEditMode()) { + if (!this.getMasterDetailCell($cell) + || isRowOrFormEditMode(this._editingController.getEditMode())) { if (this._hasSkipRow($cell.parent())) { const direction = this._focusedCellPosition && this._focusedCellPosition.rowIndex > 0 ? 'upArrow' @@ -2229,7 +2236,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const visibleRowIndex = this._rowsView.getRowIndex($row); const row = rowItems[visibleRowIndex]; const isCellEditing = editingController - && this._isCellEditMode() + && isCellBasedEditMode(editingController.getEditMode()) && editingController.isEditing(); const isRowEditingInCurrentRow = editingController?.isEditRow(visibleRowIndex); const isEditing = isRowEditingInCurrentRow || isCellEditing; @@ -2379,7 +2386,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const column = this._columnsController.getVisibleColumns()[visibleColumnIndex]; if (this._isAllowEditing(row, column)) { - if (this.isRowEditMode()) { + if (isRowOrFormEditMode(this._editingController.getEditMode())) { this._editingController.editRow(visibleRowIndex); } else if (focusedCellPosition) { this._startEditCell(eventArgs, fastEditingKey); @@ -2683,14 +2690,10 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo return gridCoreUtils.isElementInCurrentGrid(this, $(event.target)); } - private _isCellEditMode() { - const editMode = this._editingController.getEditMode(); - return editMode === EDIT_MODE.cell || editMode === EDIT_MODE.batch; - } - private _isFastEditingAllowed() { return ( - this._isCellEditMode() && this.option('keyboardNavigation.editOnKeyPress') + isCellBasedEditMode(this._editingController.getEditMode()) + && this.option('keyboardNavigation.editOnKeyPress') ); } @@ -2936,11 +2939,6 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo return this.needToRestoreFocus || this.needNavigationToCell(); } - public isRowEditMode(): boolean { - const editMode = this._editingController.getEditMode(); - return editMode === EDIT_MODE.row || editMode === EDIT_MODE.form; - } - public getRowIndex(): number { return this._focusedCellPosition ? this._focusedCellPosition.rowIndex : -1; } @@ -3158,7 +3156,7 @@ const editing = ( const visibleColumnIndex = this._keyboardNavigationController.getVisibleColumnIndex(); const column = this._columnsController.getVisibleColumns()[visibleColumnIndex]; - if (column && column.type || this.option('editing.mode') === EDIT_MODE.form) { + if ((column && column.type) || this.getEditMode() === EDIT_MODE.form) { this._keyboardNavigationController._resetFocusedCell(); } super.editRow(rowIndex); 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 5e5d021e3269..0f2c9b5567c7 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 @@ -1,3 +1,4 @@ +import type { GridsEditMode } from '@js/common/grids'; import devices from '@js/core/devices'; import type { dxElementWrapper } from '@js/core/renderer'; import { isDefined } from '@js/core/utils/type'; @@ -7,7 +8,7 @@ import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import { CLASSES as ADAPTIVITY_CLASSES } from '../adaptivity/const'; import type { Column } from '../columns_controller/types'; -import { CLASSES as EDITING_CLASSES } from '../editing/const'; +import { CLASSES as EDITING_CLASSES, EDIT_MODE } from '../editing/const'; import { COMMAND_SELECT_CLASS } from '../selection/const'; import { VIRTUAL_ROW_CLASS } from '../virtual_scrolling/const'; import { INTERACTIVE_ELEMENTS_SELECTOR } from './const'; @@ -52,10 +53,14 @@ export const isNotFocusedRow = ( $row: dxElementWrapper, ): boolean => !$row || $row.hasClass(VIEW_CLASSES.freeSpaceRow) || $row.hasClass(VIRTUAL_ROW_CLASS); +export const isRowOrFormEditMode = ( + editMode: GridsEditMode, +): boolean => editMode === EDIT_MODE.row || editMode === EDIT_MODE.form; + export const isEditorCell = ( - that: KeyboardNavigationController, + editMode: GridsEditMode, $cell: dxElementWrapper, -): boolean => !that.isRowEditMode() +): boolean => !isRowOrFormEditMode(editMode) && $cell && !$cell.hasClass(COMMAND_SELECT_CLASS) && $cell.hasClass(EDITING_CLASSES.editorCell); 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 740bfe442790..62db1a234b48 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 @@ -28,6 +28,7 @@ import Validator from '@js/ui/validator'; import errors from '@js/ui/widget/ui.errors'; import { focused } from '@ts/core/utils/selectors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/columns_controller'; +import { isCellBasedEditMode, isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; import { CLASSES as MASTER_DETAIL_CLASSES } from '@ts/grids/grid_core/master_detail/const'; @@ -62,7 +63,6 @@ const PENDING_INDICATOR_CLASS = 'dx-pending-indicator'; const VALIDATION_PENDING_CLASS = 'dx-validation-pending'; const PADDING_BETWEEN_TOOLTIPS = 2; -const FORM_BASED_MODES: GridsEditMode[] = [EDIT_MODE.popup, EDIT_MODE.form]; const COMMAND_TRANSPARENT = 'transparent'; @@ -294,7 +294,7 @@ export class ValidatingController extends modules.Controller { const { key } = change; const validationData = this._getValidationData(key, true); - if (!FORM_BASED_MODES.includes(editMode)) { + if (!isFormBasedEditMode(editMode)) { if (change.type === EDIT_DATA_INSERT_TYPE && !this.isRowDataModified(change)) { validationData.isValid = true; return; @@ -454,7 +454,8 @@ export class ValidatingController extends modules.Controller { } const isEditRow = equalByValue(this.option('editing.editRowKey'), parameters.key); - const isCellOrBatchEditingAllowed = editingController.isCellOrBatchEditMode() && editingController.allowUpdating({ row: parameters.row }); + const isCellOrBatchEditingAllowed = isCellBasedEditMode(editingController.getEditMode()) + && editingController.allowUpdating({ row: parameters.row }); needCreateValidator = isEditRow || isCellOrBatchEditingAllowed && showEditorAlways; @@ -839,7 +840,7 @@ export const validatingEditingExtender = (Base: ModuleType) = invisibleColumns.push(...groupColumns); - if (!FORM_BASED_MODES.includes(this.getEditMode())) { + if (!isFormBasedEditMode(this.getEditMode())) { each(columns, (_, column) => { changes.forEach((change) => { let data; @@ -1204,10 +1205,8 @@ export const validatingEditorFactoryExtender = (Base: ModuleType) let $groupCellElement; // @ts-expect-error const isFixedColumns = this._rowsView?.isFixedColumns?.(); - // @ts-expect-error - const isFormOrPopupEditMode = this._editingController.isFormOrPopupEditMode(); - if (isFixedColumns && !isFormOrPopupEditMode) { + if (isFixedColumns && !isFormBasedEditMode(this._editingController.getEditMode())) { const nextRowOptions = $cell.closest(`.${VIEW_CLASSES.row}`).next().data('options'); if (nextRowOptions && nextRowOptions.rowType === 'group') { @@ -1578,8 +1577,7 @@ export const validatingRowsViewExtender = (Base: ModuleType) => class } public _cellPrepared($cell, parameters) { - // @ts-expect-error - if (!this._editingController.isFormOrPopupEditMode()) { + if (!isFormBasedEditMode(this._editingController.getEditMode())) { this._validatingController.createValidator(parameters, $cell); } 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 eb7770d2faf1..f9e76961cfc4 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 @@ -14,6 +14,7 @@ import type { DataController } from '@ts/grids/grid_core/data_controller/data_co 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/data_source_adapter'; +import { EDIT_MODE } from '@ts/grids/grid_core/editing/const'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; import type { ResizingController } from '@ts/grids/grid_core/grid_view/grid_view'; import type { ModuleType } from '@ts/grids/grid_core/types'; @@ -708,8 +709,7 @@ export const rowsView = (Base: ModuleType) => class VirtualScrollingRo } private _getRowHeights() { - const isPopupEditMode = this._editingController - ?.isPopupEditMode?.(); + const isPopupEditMode = this._editingController?.getEditMode() === EDIT_MODE.popup; let rowElements = this._getRowElements(this._tableElement).toArray(); From 79d328b20812a6c2737a6550e50226827c7046d9 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:39:56 +0100 Subject: [PATCH 3/8] Grids: type validating's _beforeEditCell override --- .../grids/grid_core/editing/m_editing_cell_based.ts | 9 ++++++++- .../grids/grid_core/validating/m_validating.ts | 11 +++++++---- .../js/__internal/grids/tree_list/m_validating.ts | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index d0e74c7015b2..9f060f08265a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -59,6 +59,13 @@ export interface CellBasedEditingControllerExtension { // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods beforeCloseEditCell(rowIndices: number[]): void; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + _beforeEditCell( + rowIndex: number, + columnIndex: number, + item: ProcessedItem, + ): DeferredObj | boolean; } export type CellBasedEditingController = EditingController & CellBasedEditingControllerExtension; @@ -329,7 +336,7 @@ const editingControllerExtender = ( * @extended: validating * @returns whether to cancel cell editing */ - protected _beforeEditCell( + public _beforeEditCell( rowIndex: number, columnIndex: number, item: ProcessedItem, 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 62db1a234b48..f429dcad07af 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 @@ -28,6 +28,7 @@ import Validator from '@js/ui/validator'; import errors from '@js/ui/widget/ui.errors'; import { focused } from '@ts/core/utils/selectors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/columns_controller'; +import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import { isCellBasedEditMode, isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; @@ -38,7 +39,6 @@ import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import { EDIT_MODE, EDITORS_INPUT_SELECTOR, EDITORS_TEXTAREA_SELECTOR, INSERT_INDEX, } from '../editing/const'; -import type { EditingController } from '../editing/editing'; import type { CellBasedEditingController } from '../editing/m_editing_cell_based'; import type { NormalizedEditCellOptions } from '../editing/types'; import modules from '../modules/modules'; @@ -670,7 +670,7 @@ export class ValidatingController extends modules.Controller { } } -export const validatingEditingExtender = (Base: ModuleType) => class ValidateEditingControllerExtender extends Base { +export const validatingEditingExtender = (Base: ModuleType) => class ValidateEditingControllerExtender extends Base { protected _errorHandlingController!: ErrorHandlingViewController; public init(): void { @@ -922,8 +922,11 @@ export const validatingEditingExtender = (Base: ModuleType) = * @param item Data item * @returns A deferred object that resolves to a boolean or just a boolean to determine whether to cancel cell editing */ - private _beforeEditCell(rowIndex: number, columnIndex: number, item: any): DeferredObj | boolean { - // @ts-expect-error + public override _beforeEditCell( + rowIndex: number, + columnIndex: number, + item: ProcessedItem, + ): DeferredObj | boolean { const result = super._beforeEditCell(rowIndex, columnIndex, item); if (this.getEditMode() === EDIT_MODE.cell) { diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts index b2fe4782ee3e..e34d413fb3a6 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; -import type { EditingController } from '../grid_core/editing/editing'; +import type { CellBasedEditingController } from '../grid_core/editing/m_editing_cell_based'; import type { ModuleType } from '../grid_core/types'; import treeListCore from './core'; -const editingControllerExtender = (Base: ModuleType) => class TreeListEditingControllerExtender extends validatingModule.extenders.controllers.editing(Base) { +const editingControllerExtender = (Base: ModuleType) => class TreeListEditingControllerExtender extends validatingModule.extenders.controllers.editing(Base) { public processDataItem(item) { super.processDataItemTreeListHack.apply(this, arguments as any); } From b644c86d04a351a36c8e69cc1ce93c28b5c74a82 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:50:35 +0100 Subject: [PATCH 4/8] Grids: make NormalizedEditCellOptions.column optional --- .../js/__internal/grids/grid_core/editing/editing.ts | 4 ++-- .../grids/grid_core/editing/m_editing_cell_based.ts | 5 +++-- .../js/__internal/grids/grid_core/editing/types.ts | 5 ++++- .../js/__internal/grids/grid_core/validating/m_validating.ts | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index 1c02b67d8f95..fcaea6c122ab 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -129,9 +129,9 @@ import type { InternalColumnButton, InternalColumnButtonClickEvent, InternalEditData, - NormalizedEditCellOptions, OnSavingParams, OnSavingResult, + PrepareEditCellOptions, ProcessedChanges, SavedRowInfo, SaveResult, @@ -2784,7 +2784,7 @@ export class EditingController extends modules.ViewController { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -- used by overrides - protected _prepareEditCell(parameters: NormalizedEditCellOptions): boolean { + protected _prepareEditCell(parameters: PrepareEditCellOptions): boolean { return false; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index 9f060f08265a..9a3abbac757f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -48,6 +48,7 @@ import type { EditorCellOptions, InternalColumnButton, NormalizedEditCellOptions, + PrepareEditCellOptions, } from './types'; export interface CellBasedEditingControllerExtension { @@ -324,7 +325,7 @@ const editingControllerExtender = ( return; } - if (!this._prepareEditCell(editCellOptions)) { + if (!this._prepareEditCell({ ...editCellOptions, column })) { this._processCanceledEditingCell(); } }); @@ -394,7 +395,7 @@ const editingControllerExtender = ( oldColumn, columnIndex, oldRowIndex, - }: NormalizedEditCellOptions): boolean { + }: PrepareEditCellOptions): boolean { const editingStartParams = { data: item?.data, cancel: false, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts index 5d62d134707f..413d6b98cf8c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts @@ -33,12 +33,15 @@ export type EditCellResult = boolean | DeferredObj; export interface NormalizedEditCellOptions { item: ProcessedItem; oldColumn?: Column; - column: Column; + column?: Column; columnIndex: number; oldRowIndex: number; rowIndex: number; } +export type PrepareEditCellOptions = NormalizedEditCellOptions + & Required>; + export type EditColumnNameOptionChanged = Extract< OptionChanged, { fullName: typeof EDITING_EDITCOLUMNNAME_OPTION_NAME } 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 f429dcad07af..4f06b97d0dc2 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 @@ -40,7 +40,7 @@ import { EDIT_MODE, EDITORS_INPUT_SELECTOR, EDITORS_TEXTAREA_SELECTOR, INSERT_INDEX, } from '../editing/const'; import type { CellBasedEditingController } from '../editing/m_editing_cell_based'; -import type { NormalizedEditCellOptions } from '../editing/types'; +import type { PrepareEditCellOptions } from '../editing/types'; import modules from '../modules/modules'; import type { ModuleType } from '../types'; import gridCoreUtils from '../utils'; @@ -745,7 +745,7 @@ export const validatingEditingExtender = (Base: ModuleType Date: Mon, 26 Oct 2026 12:10:19 +0100 Subject: [PATCH 5/8] Grids: let validating override getEditFormOptions instead of being called by name --- .../grid_core/editing/m_editing_form_based.ts | 13 +++--- .../__tests__/validating.integration.test.ts | 40 +++++++++++++++++++ .../grid_core/validating/m_validating.ts | 14 ++++--- .../grids/tree_list/m_validating.ts | 5 ++- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts index 4c17c2eeb7c9..bba2d4548d9a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts @@ -63,6 +63,9 @@ export interface FormBasedEditingControllerExtension { isReadOnly?: boolean, ) => EditorCellOptions; getEditForm: () => Form | null | undefined; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions; } export type FormBasedEditingController = EditingController & FormBasedEditingControllerExtension; @@ -438,11 +441,7 @@ const editingControllerExtender = ( }; } - private getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions { - // @ts-expect-error _getValidationGroupsInForm is declared on the validating extender - const editFormOptions: Partial | undefined = this._getValidationGroupsInForm?.( - detailOptions, - ); + public getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions { const userCustomizeItem = this.option('editing.form.customizeItem'); const editFormItemClass = this.addWidgetPrefix(CLASSES.editFormItem); let items = this.option('editing.form.items'); @@ -466,7 +465,7 @@ const editingControllerExtender = ( }); } - return extend({}, editFormOptions, { + return { items, formID: `dx-${new Guid()}`, customizeItem: (item: EditFormItem): void => { @@ -505,7 +504,7 @@ const editingControllerExtender = ( userCustomizeItem?.call(this, item); item.cssClass = isString(item.cssClass) ? `${item.cssClass} ${editFormItemClass}` : editFormItemClass; }, - }); + }; } public getColumnTemplate( diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts index dc94a0e9246a..303313e9f6fa 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts @@ -200,3 +200,43 @@ describe('DataGrid Cell Editing', () => { }); }); }); + +describe('DataGrid Form Editing', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it.each(['form', 'popup'] as const)('should not save a value that breaks a form item rule in the %s edit mode', async (mode) => { + const data = [ + { id: 1, name: 'Item 1' }, + ]; + + const { instance } = await createDataGrid({ + dataSource: data, + keyExpr: 'id', + editing: { + mode, + allowUpdating: true, + form: { + items: [{ + dataField: 'name', + validationRules: [{ type: 'required' }], + }], + }, + }, + columns: ['name'], + }); + + instance.editRow(0); + jest.runAllTimers(); + + instance.cellValue(0, 'name', ''); + jest.runAllTimers(); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- settled by runAllTimers + instance.saveEditData(); + jest.runAllTimers(); + + expect(instance.hasEditData()).toBe(true); + expect(data[0].name).toBe('Item 1'); + }); +}); 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 4f06b97d0dc2..556e19a14592 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 @@ -40,7 +40,8 @@ import { EDIT_MODE, EDITORS_INPUT_SELECTOR, EDITORS_TEXTAREA_SELECTOR, INSERT_INDEX, } from '../editing/const'; import type { CellBasedEditingController } from '../editing/m_editing_cell_based'; -import type { PrepareEditCellOptions } from '../editing/types'; +import type { FormBasedEditingControllerExtension } from '../editing/m_editing_form_based'; +import type { EditFormDetailOptions, EditFormOptions, PrepareEditCellOptions } from '../editing/types'; import modules from '../modules/modules'; import type { ModuleType } from '../types'; import gridCoreUtils from '../utils'; @@ -670,7 +671,9 @@ export class ValidatingController extends modules.Controller { } } -export const validatingEditingExtender = (Base: ModuleType) => class ValidateEditingControllerExtender extends Base { +export const validatingEditingExtender = ( + Base: ModuleType, +) => class ValidateEditingControllerExtender extends Base { protected _errorHandlingController!: ErrorHandlingViewController; public init(): void { @@ -728,11 +731,10 @@ export const validatingEditingExtender = (Base: ModuleType) => class TreeListEditingControllerExtender extends validatingModule.extenders.controllers.editing(Base) { +const editingControllerExtender = ( + Base: ModuleType, +) => class TreeListEditingControllerExtender extends validatingModule.extenders.controllers.editing(Base) { public processDataItem(item) { super.processDataItemTreeListHack.apply(this, arguments as any); } From 55595ca85a8da482ae5c21583f686ed6dc55995f Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:27:48 +0100 Subject: [PATCH 6/8] Grids: move the toolbar item type and normalizer from CardView to grid_core --- .../ai_assistant_view_controller.ts | 2 +- .../grids/grid_core/editing/editing.ts | 2 +- .../grid_core/editing/m_editing_cell_based.ts | 2 +- .../grid_core/filter_row/m_filter_row.ts | 2 +- .../grids/grid_core/focus/m_focus.ts | 7 +- .../header_panel.integration.test.ts | 2 +- .../header_panel/__tests__/utils.test.ts | 68 +++++++++++++++++++ .../grid_core/header_panel/header_panel.ts | 4 +- .../grids/grid_core/header_panel/types.ts | 7 ++ .../grids/grid_core/header_panel/utils.ts | 51 ++++++++++++++ .../grids/grid_core/search/m_search.ts | 2 +- .../content_view/content/card/header.tsx | 2 +- .../grids/new/grid_core/toolbar/controller.ts | 3 +- .../grids/new/grid_core/toolbar/utils.test.ts | 67 +----------------- .../grids/new/grid_core/toolbar/utils.ts | 55 --------------- 15 files changed, 141 insertions(+), 135 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/utils.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_panel/types.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_panel/utils.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_view_controller.ts index b24128deca2e..52418bade4af 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_view_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_view_controller.ts @@ -6,8 +6,8 @@ import { CLASSES, } from '@ts/grids/grid_core/ai_assistant/const'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/header_panel'; +import type { ToolbarItem } from '@ts/grids/grid_core/header_panel/types'; import type { OptionChanged } from '@ts/grids/grid_core/types'; -import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { ViewController } from '../modules/modules'; import type { AIAssistantView } from './ai_assistant_view'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index fcaea6c122ab..b733e69a400b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -51,6 +51,7 @@ import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types' import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/editor_factory'; import type { FocusController } from '@ts/grids/grid_core/focus/m_focus'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/header_panel'; +import type { ToolbarItem } from '@ts/grids/grid_core/header_panel/types'; import type { KeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation'; import { CLASSES as MASTER_DETAIL_CLASSES } from '@ts/grids/grid_core/master_detail/const'; import modules from '@ts/grids/grid_core/modules/modules'; @@ -69,7 +70,6 @@ import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import type { CellRenderOptions, ViewCellOptions, ViewDataChange, ViewRowEvent, } from '@ts/grids/grid_core/views/types'; -import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { ACTION_OPTION_NAMES, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index 9a3abbac757f..c5c3357ffb9b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -21,9 +21,9 @@ import type { RefreshOptions, } from '@ts/grids/grid_core/data_controller/types'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/header_panel'; +import type { ToolbarItem } from '@ts/grids/grid_core/header_panel/types'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; -import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { InternalGrid, ModuleType, OptionChanged, RowKey, diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts index 339a4c0fe910..da908dfdb88e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts @@ -22,10 +22,10 @@ import type { DataFilter, FilterSourceContext, } from '@ts/grids/grid_core/filter/types'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/header_panel'; +import type { ToolbarItem } from '@ts/grids/grid_core/header_panel/types'; import modules from '@ts/grids/grid_core/modules/modules'; import type { ModuleType } from '@ts/grids/grid_core/types'; import gridCoreUtils from '@ts/grids/grid_core/utils'; -import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import Editor from '@ts/ui/editor/editor'; import type MenuInternal from '@ts/ui/menu/menu'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index 9763d3b0f491..a09c22d0f386 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -7,7 +7,6 @@ import { each } from '@js/core/utils/iterator'; import { isBoolean, isDefined } from '@js/core/utils/type'; import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; -import type { Key } from '@ts/grids/new/grid_core/data_controller/types'; import type { ColumnsController } from '../columns_controller/columns_controller'; import type { ColumnDataSourceParameter } from '../columns_controller/types'; @@ -19,7 +18,7 @@ import type { EditorFactory } from '../editor_factory/editor_factory'; import { combineFilters } from '../filter/utils'; import type { KeyboardNavigationController } from '../keyboard_navigation/m_keyboard_navigation'; import core from '../modules/modules'; -import type { ModuleType } from '../types'; +import type { ModuleType, RowKey } from '../types'; import gridCoreUtils from '../utils'; import type { RowsView } from '../views/rows_view'; import type { VirtualScrollingDataControllerExtension } from '../virtual_scrolling/index'; @@ -280,7 +279,7 @@ export class FocusController extends core.ViewController { } private _focusRowByKey( - key: Key, + key: RowKey, ): DeferredObj { if (!isDefined(key)) { this._resetFocusedRow(); @@ -324,7 +323,7 @@ export class FocusController extends core.ViewController { } public _navigateToRow( - key: Key, + key: RowKey, needFocusRow: boolean, ): DeferredObj { const that = this; diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/header_panel.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/header_panel.integration.test.ts index 8b8e6d5d4fbc..a0786e3828bd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/header_panel.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/header_panel.integration.test.ts @@ -2,13 +2,13 @@ import { afterEach, beforeEach, describe, expect, it, jest, } from '@jest/globals'; import $ from '@js/core/renderer'; -import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import { afterTest, beforeTest, createDataGrid, } from '../../__tests__/__mock__/helpers/utils'; +import type { ToolbarItem } from '../types'; const getHeaderPanel = (instance): any => instance.getView('headerPanel'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/utils.test.ts new file mode 100644 index 000000000000..7969f4cb022d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/__tests__/utils.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from '@jest/globals'; + +import { normalizeToolbarItems } from '../utils'; + +describe('normalizeToolbarItems', () => { + describe('when only default items are specified', () => { + it('should return default items', () => { + expect(normalizeToolbarItems( + [{ name: 'addCardButton' }], + undefined, + ['addCardButton'], + )).toStrictEqual([{ name: 'addCardButton' }]); + }); + }); + + describe('when only custom items are specified', () => { + it('should return processed custom items', () => { + expect(normalizeToolbarItems( + [], + [{ name: 'customToolbarItem1' }], + ['addCardButton'], + )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]); + }); + }); + + describe('when default items and custom items are specified', () => { + it('should return processed custom items', () => { + expect(normalizeToolbarItems( + [{ name: 'addCardButton' }], + [{ name: 'customToolbarItem1' }], + ['addCardButton'], + )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]); + }); + }); + + describe('when custom items override default items', () => { + it('should return default items merged with custom items', () => { + expect(normalizeToolbarItems( + [{ name: 'addCardButton', location: 'before' }], + [{ name: 'addCardButton', location: 'after' }], + ['addCardButton'], + )).toStrictEqual([{ name: 'addCardButton', location: 'after' }]); + }); + }); + + describe('when default items are set in custom items', () => { + it('should return both default and custom items', () => { + expect(normalizeToolbarItems( + [{ name: 'addCardButton', location: 'before' }], + ['addCardButton', { name: 'customToolbarItem1' }], + ['addCardButton'], + )).toStrictEqual([ + { name: 'addCardButton', location: 'before' }, + { name: 'customToolbarItem1', location: 'after' }, + ]); + }); + }); + + describe('when there are no default items but they are specified in custom items', () => { + it('should return processed default items', () => { + expect(normalizeToolbarItems( + [], + ['addCardButton'], + ['addCardButton'], + )).toStrictEqual([{ name: 'addCardButton', location: 'after', visible: false }]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/header_panel.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/header_panel.ts index 93d4d704a2bb..c3bcd68ba08d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_panel/header_panel.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/header_panel.ts @@ -11,8 +11,6 @@ import type { Properties as ToolbarProperties } from '@js/ui/toolbar'; import Toolbar from '@js/ui/toolbar'; import type { EditingController } from '@ts/grids/grid_core/editing/editing'; import type { HeaderFilterController } from '@ts/grids/grid_core/header_filter/m_header_filter'; -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'; import type { ResizingController } from '../grid_view/grid_view'; @@ -20,6 +18,8 @@ import type { InternalGridOptions, ModuleType, OptionChanged } from '../types'; import { ColumnsView } from '../views/columns_view'; import type { ContextMenuItemsOptions, GridContextMenuItem } from '../views/types'; import { CLASSES, DEFAULT_TOOLBAR_ITEM_NAMES, TOOLBAR_ARIA_LABEL } from './const'; +import type { ToolbarItem } from './types'; +import { normalizeToolbarItems } from './utils'; export class HeaderPanel extends ColumnsView { private _toolbar?: Toolbar; diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/types.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/types.ts new file mode 100644 index 000000000000..1b13f6d6c412 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/types.ts @@ -0,0 +1,7 @@ +import type { Item as BaseToolbarItem, ItemRenderedEvent } from '@js/ui/toolbar'; + +export interface ToolbarItem extends BaseToolbarItem { + name?: string; + sortIndex?: number; + onItemRendered?: (e: ItemRenderedEvent) => void; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/utils.ts new file mode 100644 index 000000000000..820c4ba54831 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/utils.ts @@ -0,0 +1,51 @@ +import { extend } from '@js/core/utils/extend'; +import { isDefined, isString } from '@js/core/utils/type'; + +import type { ToolbarItem } from './types'; + +function normalizeToolbarItem( + item: ToolbarItem | string, + defaultButtonsMap: { [key: string]: ToolbarItem }, + defaultItemNames: readonly string[], +): ToolbarItem { + let button = item; + const defaultProps: ToolbarItem = { location: 'after' }; + + if (isString(button)) { + button = { name: button }; + } + + if (isDefined(button.name)) { + if (isDefined(defaultButtonsMap[button.name])) { + button = extend(true, {}, defaultButtonsMap[button.name], button); + } else if (defaultItemNames.includes(button.name)) { + button = { ...button, visible: false }; + } + } + + return extend(true, {}, defaultProps, button); +} + +export function normalizeToolbarItems( + sortedDefaultItems: ToolbarItem[], + userItems: (ToolbarItem | string)[] | undefined, + defaultItemNames: readonly string[], +): ToolbarItem[] { + if (!isDefined(userItems)) { + return sortedDefaultItems; + } + + const defaultButtonsMap = {}; + + sortedDefaultItems.forEach((button) => { + if (isDefined(button.name)) { + defaultButtonsMap[button.name] = button; + } + }); + + return userItems.map( + ( + item: ToolbarItem | string, + ) => normalizeToolbarItem(item, defaultButtonsMap, defaultItemNames), + ); +} 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 3ab82574a6d3..ebf56a025ee0 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 @@ -9,7 +9,7 @@ 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 { ToolbarItem } from '@ts/grids/grid_core/header_panel/types'; import type { DataController } from '../data_controller/data_controller'; import type { DataChange, UserState } from '../data_controller/types'; diff --git a/packages/devextreme/js/__internal/grids/new/card_view/content_view/content/card/header.tsx b/packages/devextreme/js/__internal/grids/new/card_view/content_view/content/card/header.tsx index 41221a3fe248..b87e3e1875de 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/content_view/content/card/header.tsx +++ b/packages/devextreme/js/__internal/grids/new/card_view/content_view/content/card/header.tsx @@ -3,11 +3,11 @@ import messageLocalization from '@js/localization/message'; import type { ValueChangedEvent } from '@js/ui/check_box'; import type * as dxToolbar from '@js/ui/toolbar'; import { isDefined } from '@ts/core/utils/type'; +import { normalizeToolbarItems } from '@ts/grids/grid_core/header_panel/utils'; import type { SelectCardOptions } from '@ts/grids/new/card_view/content_view/types'; import type { CardInfo } from '@ts/grids/new/grid_core/columns_controller/types'; import { Toolbar } from '@ts/grids/new/grid_core/inferno_wrappers/toolbar'; import type { DefaultToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; -import { normalizeToolbarItems } from '@ts/grids/new/grid_core/toolbar/utils'; import type { ComponentType } from 'inferno'; import { Component } from 'inferno'; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts index 9fc76f4c88ed..3e8e42123b45 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts @@ -1,12 +1,13 @@ import type { ReadonlySignal } from '@ts/core/state_manager/index'; import { computed, effect, signal } from '@ts/core/state_manager/index'; +import { normalizeToolbarItems } from '@ts/grids/grid_core/header_panel/utils'; import { OptionsController } from '../options_controller/options_controller'; import { DEFAULT_TOOLBAR_ITEMS } from './const'; import type { DefaultToolbarItem, DefaultToolbarItemsCollection, ToolbarItem, ToolbarItems, } from './types'; -import { getSortedToolbarItems, normalizeToolbarItems } from './utils'; +import { getSortedToolbarItems } from './utils'; export class ToolbarController { private readonly itemSubscriptions: Record void> = {}; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts index d7aebe1c04b7..fa2c2ca79efb 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { isVisible, normalizeToolbarItems } from './utils'; +import { isVisible } from './utils'; describe('isVisible', () => { describe('when visibleConfig = true', () => { @@ -30,68 +30,3 @@ describe('isVisible', () => { }); }); }); - -describe('normalizeToolbarItems', () => { - describe('when only default items are specified', () => { - it('should return default items', () => { - expect(normalizeToolbarItems( - [{ name: 'addCardButton' }], - undefined, - ['addCardButton'], - )).toStrictEqual([{ name: 'addCardButton' }]); - }); - }); - - describe('when only custom items are specified', () => { - it('should return processed custom items', () => { - expect(normalizeToolbarItems( - [], - [{ name: 'customToolbarItem1' }], - ['addCardButton'], - )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]); - }); - }); - - describe('when default items and custom items are specified', () => { - it('should return processed custom items', () => { - expect(normalizeToolbarItems( - [{ name: 'addCardButton' }], - [{ name: 'customToolbarItem1' }], - ['addCardButton'], - )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]); - }); - }); - - describe('when custom items override default items', () => { - it('should return default items merged with custom items', () => { - expect(normalizeToolbarItems( - [{ name: 'addCardButton', location: 'before' }], - [{ name: 'addCardButton', location: 'after' }], - ['addCardButton'], - )).toStrictEqual([{ name: 'addCardButton', location: 'after' }]); - }); - }); - - describe('when default items are set in custom items', () => { - it('should return both default and custom items', () => { - expect(normalizeToolbarItems( - [{ name: 'addCardButton', location: 'before' }], - ['addCardButton', { name: 'customToolbarItem1' }], - ['addCardButton'], - )).toStrictEqual([ - { name: 'addCardButton', location: 'before' }, - { name: 'customToolbarItem1', location: 'after' }, - ]); - }); - }); - - describe('when there are no default items but they are specified in custom items', () => { - it('should return processed default items', () => { - expect(normalizeToolbarItems( - [], - ['addCardButton'], - ['addCardButton'], - )).toStrictEqual([{ name: 'addCardButton', location: 'after', visible: false }]); - }); - }); -}); 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 4a9360523d86..089122e53de3 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 @@ -1,7 +1,3 @@ -import { extend } from '@js/core/utils/extend'; -import { isDefined, isString } from '@js/core/utils/type'; -import type { Item as BaseToolbarItem } from '@js/ui/toolbar'; - import { DEFAULT_TOOLBAR_ITEMS } from './const'; import type { DefaultToolbarItem, DefaultToolbarItemsCollection, ToolbarItems, @@ -18,33 +14,6 @@ export function isVisible( return visibleConfig; } -interface ToolbarItem extends BaseToolbarItem { - name?: string; -} - -function normalizeToolbarItem( - item: ToolbarItem | string, - defaultButtonsMap: { [key: string]: ToolbarItem }, - defaultItemNames: readonly string[], -): ToolbarItem { - let button = item; - const defaultProps: ToolbarItem = { location: 'after' }; - - if (isString(button)) { - button = { name: button }; - } - - if (isDefined(button.name)) { - if (isDefined(defaultButtonsMap[button.name])) { - button = extend(true, {}, defaultButtonsMap[button.name], button); - } else if (defaultItemNames.includes(button.name)) { - button = { ...button, visible: false }; - } - } - - return extend(true, {}, defaultProps, button); -} - export function getSortedToolbarItems( defaultItemsCollection: DefaultToolbarItemsCollection, ): DefaultToolbarItem[] { @@ -55,27 +24,3 @@ export function getSortedToolbarItems( return aIndex - bIndex; }); } - -export function normalizeToolbarItems( - sortedDefaultItems: ToolbarItem[], - userItems: (ToolbarItem | string)[] | undefined, - defaultItemNames: readonly string[], -): ToolbarItem[] { - if (!isDefined(userItems)) { - return sortedDefaultItems; - } - - const defaultButtonsMap = {}; - - sortedDefaultItems.forEach((button) => { - if (isDefined(button.name)) { - defaultButtonsMap[button.name] = button; - } - }); - - return userItems.map( - ( - item: ToolbarItem | string, - ) => normalizeToolbarItem(item, defaultButtonsMap, defaultItemNames), - ); -} From 6bf019537d26dbe22052ae7d6bcf312fb25fb2e8 Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:39:36 +0100 Subject: [PATCH 7/8] Grids: move EditingRowsViewExtension to types.ts, rename _beforeEditCell to beforeEditCell --- .../js/__internal/grids/grid_core/editing/editing.ts | 9 +-------- .../grids/grid_core/editing/m_editing_cell_based.ts | 6 +++--- .../js/__internal/grids/grid_core/editing/types.ts | 10 +++++++++- .../grids/grid_core/validating/m_validating.ts | 4 ++-- .../js/__internal/grids/tree_list/editing/m_editing.ts | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index b733e69a400b..16f1044418f1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -122,6 +122,7 @@ import type { EditingDefaultOptions, EditingModuleOptionChanged, EditingOptions, + EditingRowsViewExtension, EditingStartOptions, EditorCellOptions, EditorTemplate, @@ -2802,14 +2803,6 @@ export class EditingController extends modules.ViewController { } } -export interface EditingRowsViewExtension { - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - handleRowClickByEditing(e: ViewRowEvent): boolean; - - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - handleRowDblClickByEditing(e: ViewRowEvent): boolean; -} - const rowsView = >( Base: TBase, ): TBase & ModuleType< diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index c5c3357ffb9b..128601867ea9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -62,7 +62,7 @@ export interface CellBasedEditingControllerExtension { beforeCloseEditCell(rowIndices: number[]): void; // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - _beforeEditCell( + beforeEditCell( rowIndex: number, columnIndex: number, item: ProcessedItem, @@ -320,7 +320,7 @@ const editingControllerExtender = ( return true; } - return when(this._beforeEditCell(rowIndex, columnIndex, item)).done((cancel) => { + return when(this.beforeEditCell(rowIndex, columnIndex, item)).done((cancel) => { if (cancel) { return; } @@ -337,7 +337,7 @@ const editingControllerExtender = ( * @extended: validating * @returns whether to cancel cell editing */ - public _beforeEditCell( + public beforeEditCell( rowIndex: number, columnIndex: number, item: ProcessedItem, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts index 413d6b98cf8c..ec0933f42872 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts @@ -15,7 +15,7 @@ import type { InternalGrid, OptionChanged, OptionChangedFor, RowKey, } from '../types'; import type { - CellRenderOptions, ViewCellOptions, ViewDataChange, ViewRow, + CellRenderOptions, ViewCellOptions, ViewDataChange, ViewRow, ViewRowEvent, } from '../views/types'; import type { EDITING_CHANGES_OPTION_NAME, EDITING_EDITCOLUMNNAME_OPTION_NAME, INSERT_INDEX, @@ -226,3 +226,11 @@ export interface EditingModuleOptions { export type EditingDefaultOptions = { editing: EditingOptions } & EditingModuleOptions; export type EditingModuleOptionChanged = OptionChanged | OptionChangedFor; + +export interface EditingRowsViewExtension { + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + handleRowClickByEditing(e: ViewRowEvent): boolean; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + handleRowDblClickByEditing(e: ViewRowEvent): boolean; +} 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 556e19a14592..441beb322aa8 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 @@ -924,12 +924,12 @@ export const validatingEditingExtender = ( * @param item Data item * @returns A deferred object that resolves to a boolean or just a boolean to determine whether to cancel cell editing */ - public override _beforeEditCell( + public override beforeEditCell( rowIndex: number, columnIndex: number, item: ProcessedItem, ): DeferredObj | boolean { - const result = super._beforeEditCell(rowIndex, columnIndex, item); + const result = super.beforeEditCell(rowIndex, columnIndex, item); if (this.getEditMode() === EDIT_MODE.cell) { const $cell = this._rowsView._getCellElement(rowIndex, columnIndex); diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts index 106d876d1407..124214ac5856 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts @@ -13,13 +13,13 @@ import errors from '@js/ui/widget/ui.errors'; import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { DATA_CHANGE_TYPE } from '@ts/grids/grid_core/editing/const'; -import type { EditingRowsViewExtension } from '@ts/grids/grid_core/editing/editing'; import { editingModule } from '@ts/grids/grid_core/editing/editing'; import type { EditingDataControllerExtension } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import { editingDataControllerExtender } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import type { EditActionOptions, EditingChange, + EditingRowsViewExtension, EditorCellOptions, InternalColumnButton, } from '@ts/grids/grid_core/editing/types'; From aa90ec7b7a09ba36d04fce0a103fd6fdc72a8e2e Mon Sep 17 00:00:00 2001 From: "anna.shakhova" <68295572+anna-shakhova@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:03:44 +0100 Subject: [PATCH 8/8] Grids: move the editing extension interfaces to editing/types.ts --- .../__internal/grids/data_grid/m_editing.ts | 2 +- .../adaptivity_editing_view_controller.ts | 2 +- .../grids/grid_core/adaptivity/types.ts | 3 +- .../editing_cell_based.integration.test.ts | 2 +- .../grids/grid_core/editing/editing.ts | 2 +- .../extenders/editing_data_controller.ts | 5 +- .../grid_core/editing/m_editing_cell_based.ts | 21 +------- .../grid_core/editing/m_editing_form_based.ts | 25 +-------- .../grids/grid_core/editing/types.ts | 54 ++++++++++++++++++- .../m_keyboard_navigation.ts | 2 +- .../grid_core/validating/m_validating.ts | 10 ++-- .../grids/tree_list/editing/m_editing.ts | 2 +- .../grids/tree_list/m_validating.ts | 3 +- 13 files changed, 72 insertions(+), 61 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_editing.ts b/packages/devextreme/js/__internal/grids/data_grid/m_editing.ts index 177077430df3..c5a00234aab2 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/m_editing.ts @@ -3,8 +3,8 @@ import './module_not_extended/editor_factory'; import type { DeferredObj } from '@js/core/utils/deferred'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import { editingModule } from '@ts/grids/grid_core/editing/editing'; -import type { EditingDataControllerExtension } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import { editingDataControllerExtender } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; +import type { EditingDataControllerExtension } from '@ts/grids/grid_core/editing/types'; import type { ModuleType, RowKey } from '@ts/grids/grid_core/types'; import gridCore from './core'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts index 510853fefb4f..2d434bc81c17 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts @@ -6,7 +6,7 @@ import { Deferred, when } from '@js/core/utils/deferred'; import type { EditingController } from '@ts/grids/grid_core/editing/editing'; import { isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; -import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based'; +import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/types'; import type { ModuleType } from '@ts/grids/grid_core/types'; import gridCoreUtils from '@ts/grids/grid_core/utils'; import { FIELD_ITEM_CONTENT_CLASS } from '@ts/ui/form/constants'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts index cfcca4b137ed..f3e36c095e47 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts @@ -2,8 +2,7 @@ import type { dxElementWrapper } from '@js/core/renderer'; import type { Column } from '../columns_controller/types'; import type { DataController } from '../data_controller/data_controller'; -import type { CellBasedEditingController } from '../editing/m_editing_cell_based'; -import type { FormBasedEditingControllerExtension } from '../editing/m_editing_form_based'; +import type { CellBasedEditingController, FormBasedEditingControllerExtension } from '../editing/types'; import type { RowKey } from '../types'; import type { ColumnsView } from '../views/columns_view'; import type { ViewCellOptions, ViewRow } from '../views/types'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts index 6ced110aa227..714c0fcc10c4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/__tests__/editing_cell_based.integration.test.ts @@ -11,7 +11,7 @@ import { createDataGrid, flushAsync, } from '../../__tests__/__mock__/helpers/utils'; -import type { CellBasedEditingController } from '../m_editing_cell_based'; +import type { CellBasedEditingController } from '../types'; const getEditingController = ( instance: DataGridInstance, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts index 16f1044418f1..c62724d571b2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/editing.ts @@ -106,9 +106,9 @@ import { isRowBasedEditMode, } from './editing_utils'; import { editingDataControllerExtender } from './extenders/editing_data_controller'; -import type { CellBasedEditingController } from './m_editing_cell_based'; import type { AllowEditActionValue, + CellBasedEditingController, EditActionOptions, EditActions, EditCellResult, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts index acfa40f43ef7..5cc2e0e5397b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts @@ -15,10 +15,7 @@ import gridCoreUtils from '@ts/grids/grid_core/utils'; import { EDITING_EDITROWKEY_OPTION_NAME } from '../const'; import type { EditingController } from '../editing'; import { isCellModified } from '../editing_utils'; - -export interface EditingDataControllerExtension { - _editingController: EditingController; -} +import type { EditingDataControllerExtension } from '../types'; export const editingDataControllerExtender = >( Base: TBase, diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts index 128601867ea9..1abddc35acc2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts @@ -39,6 +39,7 @@ import { import type { EditingController } from './editing'; import { isCellBasedEditMode, isEditable } from './editing_utils'; import type { + CellBasedEditingController, EditCellOptions, EditCellResult, EditColumnNameOptionChanged, @@ -51,26 +52,6 @@ import type { PrepareEditCellOptions, } from './types'; -export interface CellBasedEditingControllerExtension { - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - closeEditCell(isError?: boolean | null, withoutSaveEditData?: boolean): DeferredObj; - - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - editCell(rowIndex: number, columnIndex: number | string): EditCellResult; - - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - beforeCloseEditCell(rowIndices: number[]): void; - - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - beforeEditCell( - rowIndex: number, - columnIndex: number, - item: ProcessedItem, - ): DeferredObj | boolean; -} - -export type CellBasedEditingController = EditingController & CellBasedEditingControllerExtension; - const editingControllerExtender = ( Base: ModuleType, ): ModuleType => class CellBasedEditingControllerExtender extends Base { diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts index bba2d4548d9a..72bc7810dbe2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_form_based.ts @@ -51,25 +51,11 @@ import type { EditFormItem, EditFormOptions, EditingChange, + EditingFormBasedRowsViewExtension, EditorCellOptions, + FormBasedEditingController, } from './types'; -export interface FormBasedEditingControllerExtension { - renderFormEditorTemplate: ( - detailCellOptions: EditFormDetailOptions, - item: EditFormColumnItem, - formTemplateOptions: SimpleItemTemplateData, - container: dxElementWrapper, - isReadOnly?: boolean, - ) => EditorCellOptions; - getEditForm: () => Form | null | undefined; - - // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods - getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions; -} - -export type FormBasedEditingController = EditingController & FormBasedEditingControllerExtension; - type EditFormTemplate = ( $container: DxElement | dxElementWrapper, detailOptions: EditFormDetailOptions, @@ -593,13 +579,6 @@ const editingControllerExtender = ( } }; -export interface EditingFormBasedRowsViewExtension { - _getEditFormEditorVisibleIndex: ( - $cells: dxElementWrapper | undefined, - column: Column | undefined, - ) => number; -} - const rowsView = ( Base: ModuleType, ): ModuleType< diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts index ec0933f42872..73a64c1c8200 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts @@ -2,11 +2,15 @@ import type { DataChange, EditingBase, NewRowPosition, RowInsertedInfo, } from '@js/common/grids'; import type { DxElement } from '@js/core/element'; +import type { dxElementWrapper } from '@js/core/renderer'; import type { DeferredObj } from '@js/core/utils/deferred'; import type { StoreChange } from '@js/data/store'; import type { DxEvent } from '@js/events'; import type { Column as DataGridColumn, ColumnButton, ColumnButtonClickEvent } from '@js/ui/data_grid'; -import type { FormItemComponent, Item as FormItem, SimpleItem } from '@js/ui/form'; +import type Form from '@js/ui/form'; +import type { + FormItemComponent, Item as FormItem, SimpleItem, SimpleItemTemplateData, +} from '@js/ui/form'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ProcessedItem } from '../data_controller/types'; @@ -20,6 +24,7 @@ import type { import type { EDITING_CHANGES_OPTION_NAME, EDITING_EDITCOLUMNNAME_OPTION_NAME, INSERT_INDEX, } from './const'; +import type { EditingController } from './editing'; export interface EditCellOptions { rowIndex: number; @@ -234,3 +239,50 @@ export interface EditingRowsViewExtension { // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods handleRowDblClickByEditing(e: ViewRowEvent): boolean; } + +export interface EditingDataControllerExtension { + _editingController: EditingController; +} + +export interface CellBasedEditingControllerExtension { + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + closeEditCell(isError?: boolean | null, withoutSaveEditData?: boolean): DeferredObj; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + editCell(rowIndex: number, columnIndex: number | string): EditCellResult; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + beforeCloseEditCell(rowIndices: number[]): void; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + beforeEditCell( + rowIndex: number, + columnIndex: number, + item: ProcessedItem, + ): DeferredObj | boolean; +} + +export type CellBasedEditingController = EditingController & CellBasedEditingControllerExtension; + +export interface FormBasedEditingControllerExtension { + renderFormEditorTemplate: ( + detailCellOptions: EditFormDetailOptions, + item: EditFormColumnItem, + formTemplateOptions: SimpleItemTemplateData, + container: dxElementWrapper, + isReadOnly?: boolean, + ) => EditorCellOptions; + getEditForm: () => Form | null | undefined; + + // eslint-disable-next-line @typescript-eslint/method-signature-style -- overridden by methods + getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions; +} + +export type FormBasedEditingController = EditingController & FormBasedEditingControllerExtension; + +export interface EditingFormBasedRowsViewExtension { + _getEditFormEditorVisibleIndex: ( + $cells: dxElementWrapper | undefined, + column: Column | undefined, + ) => number; +} 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 f54cf791fa1f..8e6e5ca47b2e 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 @@ -32,7 +32,7 @@ import type { DataController } from '@ts/grids/grid_core/data_controller/data_co 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 { isCellBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils'; -import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based'; +import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/types'; import { CLASSES as FILTER_ROW_CLASSES } from '@ts/grids/grid_core/filter_row/const'; import { CLASSES as VIEW_CLASSES } from '@ts/grids/grid_core/views/const'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; 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 441beb322aa8..dc4c43632acf 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 @@ -39,9 +39,13 @@ import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import { EDIT_MODE, EDITORS_INPUT_SELECTOR, EDITORS_TEXTAREA_SELECTOR, INSERT_INDEX, } from '../editing/const'; -import type { CellBasedEditingController } from '../editing/m_editing_cell_based'; -import type { FormBasedEditingControllerExtension } from '../editing/m_editing_form_based'; -import type { EditFormDetailOptions, EditFormOptions, PrepareEditCellOptions } from '../editing/types'; +import type { + CellBasedEditingController, + EditFormDetailOptions, + EditFormOptions, + FormBasedEditingControllerExtension, + PrepareEditCellOptions, +} from '../editing/types'; import modules from '../modules/modules'; import type { ModuleType } from '../types'; import gridCoreUtils from '../utils'; diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts index 124214ac5856..6c630f8b50b5 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts @@ -14,11 +14,11 @@ import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { DATA_CHANGE_TYPE } from '@ts/grids/grid_core/editing/const'; import { editingModule } from '@ts/grids/grid_core/editing/editing'; -import type { EditingDataControllerExtension } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import { editingDataControllerExtender } from '@ts/grids/grid_core/editing/extenders/editing_data_controller'; import type { EditActionOptions, EditingChange, + EditingDataControllerExtension, EditingRowsViewExtension, EditorCellOptions, InternalColumnButton, diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts index f4af8946ab80..12f46d5b332d 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; -import type { CellBasedEditingController } from '../grid_core/editing/m_editing_cell_based'; -import type { FormBasedEditingControllerExtension } from '../grid_core/editing/m_editing_form_based'; +import type { CellBasedEditingController, FormBasedEditingControllerExtension } from '../grid_core/editing/types'; import type { ModuleType } from '../grid_core/types'; import treeListCore from './core';