From 67f15a081b6c27879a9a6580971765c459edde4d Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:59:23 +0400 Subject: [PATCH 1/6] refactor(grids): relocate validating data extender out of base DataController --- .../module_not_extended/validating.ts | 2 +- .../data_controller/data_controller.ts | 4 -- .../extenders/validating_data_controller.ts | 60 ++++++++++++++++++ .../grid_core/validating/m_validating.ts | 63 +------------------ .../grid_core/validating/validating_module.ts | 34 ++++++++++ .../grids/tree_list/m_validating.ts | 2 +- 6 files changed, 99 insertions(+), 66 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts index 2696f484df08..7b08ed5e1451 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts @@ -1,4 +1,4 @@ -import { validatingModule } from '@ts/grids/grid_core/validating/m_validating'; +import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; import gridCore from '../m_core'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index bb75ed2672c3..3d1735dbfdba 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -42,7 +42,6 @@ import type { import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import type { SelectionController } from '@ts/grids/grid_core/selection/m_selection'; import type { StateStoringController } from '@ts/grids/grid_core/state_storing/m_state_storing_core'; -import type { ValidatingController } from '@ts/grids/grid_core/validating/m_validating'; import type { VirtualScrollController } from '@ts/grids/grid_core/virtual_scrolling/m_virtual_scrolling_core'; import { DataHelperMixin } from './data_helper_mixin'; @@ -154,8 +153,6 @@ export class DataController extends DataHelperMixin(modules.Controller) { protected _stateStoringController!: StateStoringController; - protected _validatingController!: ValidatingController; - private loadErrorHandlerProxy!: (e: Error | string) => void; private dataPushedHandlerProxy!: (changes: StoreChange[]) => void; @@ -177,7 +174,6 @@ export class DataController extends DataHelperMixin(modules.Controller) { this._headerFilterController = this.getController('headerFilter'); this._selectionController = this.getController('selection'); this._stateStoringController = this.getController('stateStoring'); - this._validatingController = this.getController('validating'); this._isPaging = false; this._currentOperationTypes = null; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts new file mode 100644 index 000000000000..374634433498 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -0,0 +1,60 @@ +import $ from '@js/core/renderer'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { + INVALIDATE_CLASS, + VALIDATION_STATUS, + validationResultIsValid, +} from '../m_validating'; + +interface ValidatingControllerReader { + getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => unknown; + _getValidationData: (key: unknown) => { isValid?: boolean } | undefined; +} + +export const data = ( + Base: ModuleType, +): ModuleType => class ValidatingDataControllerExtender extends Base { + protected _validatingController!: ValidatingControllerReader; + + public init(): void { + this._validatingController = this.getController('validating'); + super.init(); + } + + private _getValidationStatus(validationResult): string { + const validationStatus = validationResultIsValid(validationResult) + ? validationResult.status + : validationResult; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return validationStatus ?? VALIDATION_STATUS.valid; + } + + protected _isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate): boolean { + const cell = oldRow.cells?.[columnIndex]; + const oldValidationStatus = this._getValidationStatus({ status: cell?.validationStatus }); + const validationResult = this._validatingController.getCellValidationResult({ + rowKey: oldRow.key, + columnIndex, + }); + const validationData = this._validatingController._getValidationData(oldRow.key); + const newValidationStatus = this._getValidationStatus(validationResult); + const rowIsModified = JSON.stringify(newRow.modifiedValues) + !== JSON.stringify(oldRow.modifiedValues); + const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; + const cellIsMarkedAsInvalid = $(cell?.cellElement) + .hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); + const hasValidationRules = cell?.column.validationRules?.length; + const rowEditStateChanged = oldRow.isEditing !== newRow.isEditing && hasValidationRules; + const cellValidationStateChanged = validationStatusChanged + || (validationData?.isValid && cellIsMarkedAsInvalid); + + if (rowEditStateChanged || cellValidationStateChanged) { + return true; + } + + return super._isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate); + } +}; 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 e9d907b693b1..370b778a6a5d 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 @@ -27,7 +27,6 @@ import Validator from '@js/ui/validator'; import errors from '@js/ui/widget/ui.errors'; import { focused } from '@ts/core/utils/m_selectors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; -import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -38,7 +37,7 @@ import modules from '../m_modules'; import type { ModuleType } from '../m_types'; import gridCoreUtils from '../m_utils'; -const INVALIDATE_CLASS = 'invalid'; +export const INVALIDATE_CLASS = 'invalid'; const REVERT_TOOLTIP_CLASS = 'revert-tooltip'; const INVALID_MESSAGE_CLASS = 'dx-invalid-message'; const INVALID_MESSAGE_ID = 'dxInvalidMessage'; @@ -64,7 +63,7 @@ const FORM_BASED_MODES = [EDIT_MODE_POPUP, EDIT_MODE_FORM]; const COMMAND_TRANSPARENT = 'transparent'; -const VALIDATION_STATUS = { +export const VALIDATION_STATUS = { valid: 'valid', invalid: 'invalid', pending: 'pending', @@ -74,7 +73,7 @@ const EDIT_DATA_INSERT_TYPE = 'insert'; const EDIT_DATA_REMOVE_TYPE = 'remove'; const VALIDATION_CANCELLED = 'cancel'; -const validationResultIsValid = function (result) { +export const validationResultIsValid = function (result) { return isDefined(result) && result !== VALIDATION_CANCELLED; }; @@ -1519,37 +1518,6 @@ export const validatingEditorFactoryExtender = (Base: ModuleType) } }; -export const validatingDataControllerExtender = (Base: ModuleType) => class ValidatingDataControllerExtender extends Base { - private _getValidationStatus(validationResult) { - const validationStatus = validationResultIsValid(validationResult) ? validationResult.status : validationResult; - - return validationStatus || VALIDATION_STATUS.valid; - } - - protected _isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate) { - const cell = oldRow.cells?.[columnIndex]; - const oldValidationStatus = this._getValidationStatus({ status: cell?.validationStatus }); - const validationResult = this._validatingController.getCellValidationResult({ - rowKey: oldRow.key, - columnIndex, - }); - const validationData = this._validatingController._getValidationData(oldRow.key); - const newValidationStatus = this._getValidationStatus(validationResult); - const rowIsModified = JSON.stringify(newRow.modifiedValues) !== JSON.stringify(oldRow.modifiedValues); - const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; - const cellIsMarkedAsInvalid = $(cell?.cellElement).hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); - const hasValidationRules = cell?.column.validationRules?.length; - const rowEditStateChanged = oldRow.isEditing !== newRow.isEditing && hasValidationRules; - const cellValidationStateChanged = validationStatusChanged || validationData.isValid && cellIsMarkedAsInvalid; - - if (rowEditStateChanged || cellValidationStateChanged) { - return true; - } - - return super._isCellChanged.apply(this, arguments as any); - } -}; - export const validatingRowsViewExtender = (Base: ModuleType) => class ValidatingRowsViewExtender extends Base { public updateFreeSpaceRowHeight($table) { const that = this; @@ -1621,28 +1589,3 @@ export const validatingRowsViewExtender = (Base: ModuleType) => class }); } }; - -export const validatingModule = { - defaultOptions() { - return { - editing: { - texts: { - validationCancelChanges: messageLocalization.format('dxDataGrid-validationCancelChanges'), - }, - }, - }; - }, - controllers: { - validating: ValidatingController, - }, - extenders: { - controllers: { - editing: validatingEditingExtender, - editorFactory: validatingEditorFactoryExtender, - data: validatingDataControllerExtender, - }, - views: { - rowsView: validatingRowsViewExtender, - }, - }, -}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts new file mode 100644 index 000000000000..fe8ce37869db --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts @@ -0,0 +1,34 @@ +import messageLocalization from '@js/common/core/localization/message'; + +import { data } from './extenders/validating_data_controller'; +import { + ValidatingController, + validatingEditingExtender, + validatingEditorFactoryExtender, + validatingRowsViewExtender, +} from './m_validating'; + +export const validatingModule = { + defaultOptions(): { editing: { texts: { validationCancelChanges: string } } } { + return { + editing: { + texts: { + validationCancelChanges: messageLocalization.format('dxDataGrid-validationCancelChanges'), + }, + }, + }; + }, + controllers: { + validating: ValidatingController, + }, + extenders: { + controllers: { + editing: validatingEditingExtender, + editorFactory: validatingEditorFactoryExtender, + data, + }, + views: { + rowsView: validatingRowsViewExtender, + }, + }, +}; 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 e773c6459544..b088f3532074 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { validatingModule } from '@ts/grids/grid_core/validating/m_validating'; +import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; import type { EditingController } from '../grid_core/editing/m_editing'; import type { ModuleType } from '../grid_core/m_types'; From 4faddf94b3863a9a41229bffdda0cfa18084e675 Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Thu, 13 Aug 2026 02:01:49 +0400 Subject: [PATCH 2/6] refactor(grids): extract validating constants into const module --- .../grids/grid_core/validating/const.ts | 14 ++++++++++++++ .../extenders/validating_data_controller.ts | 2 +- .../grids/grid_core/validating/m_validating.ts | 18 ++++++------------ 3 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/validating/const.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts new file mode 100644 index 000000000000..0b57bc78b014 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts @@ -0,0 +1,14 @@ +import { isDefined } from '@js/core/utils/type'; + +export const INVALIDATE_CLASS = 'invalid'; + +export const VALIDATION_STATUS = { + valid: 'valid', + invalid: 'invalid', + pending: 'pending', +}; + +export const VALIDATION_CANCELLED = 'cancel'; + +export const validationResultIsValid = (result: unknown): boolean => isDefined(result) + && result !== VALIDATION_CANCELLED; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts index 374634433498..11c5d828a9e1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -6,7 +6,7 @@ import { INVALIDATE_CLASS, VALIDATION_STATUS, validationResultIsValid, -} from '../m_validating'; +} from '../const'; interface ValidatingControllerReader { getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => unknown; 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 370b778a6a5d..aeb7107b5b07 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 @@ -36,8 +36,13 @@ import type { NormalizedEditCellOptions } from '../editing/types'; import modules from '../m_modules'; import type { ModuleType } from '../m_types'; import gridCoreUtils from '../m_utils'; +import { + INVALIDATE_CLASS, + VALIDATION_CANCELLED, + VALIDATION_STATUS, + validationResultIsValid, +} from './const'; -export const INVALIDATE_CLASS = 'invalid'; const REVERT_TOOLTIP_CLASS = 'revert-tooltip'; const INVALID_MESSAGE_CLASS = 'dx-invalid-message'; const INVALID_MESSAGE_ID = 'dxInvalidMessage'; @@ -63,19 +68,8 @@ const FORM_BASED_MODES = [EDIT_MODE_POPUP, EDIT_MODE_FORM]; const COMMAND_TRANSPARENT = 'transparent'; -export const VALIDATION_STATUS = { - valid: 'valid', - invalid: 'invalid', - pending: 'pending', -}; - const EDIT_DATA_INSERT_TYPE = 'insert'; const EDIT_DATA_REMOVE_TYPE = 'remove'; -const VALIDATION_CANCELLED = 'cancel'; - -export const validationResultIsValid = function (result) { - return isDefined(result) && result !== VALIDATION_CANCELLED; -}; const cellValueShouldBeValidated = function (value, rowOptions) { return value !== undefined || (value === undefined && rowOptions && !rowOptions.isNewRow); From 562cf0cf63a42917cf7df96edd5cd382f3323fa4 Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:00:40 +0400 Subject: [PATCH 3/6] refactor(grids): expose validating getValidationData accessor for read-only port --- .../validating/extenders/validating_data_controller.ts | 4 ++-- .../js/__internal/grids/grid_core/validating/m_validating.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts index 11c5d828a9e1..7ff15ce49cc4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -10,7 +10,7 @@ import { interface ValidatingControllerReader { getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => unknown; - _getValidationData: (key: unknown) => { isValid?: boolean } | undefined; + getValidationData: (key: unknown) => { isValid?: boolean } | undefined; } export const data = ( @@ -39,7 +39,7 @@ export const data = ( rowKey: oldRow.key, columnIndex, }); - const validationData = this._validatingController._getValidationData(oldRow.key); + const validationData = this._validatingController.getValidationData(oldRow.key); const newValidationStatus = this._getValidationStatus(validationResult); const rowIsModified = JSON.stringify(newRow.modifiedValues) !== JSON.stringify(oldRow.modifiedValues); 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 aeb7107b5b07..aef946b7f079 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 @@ -142,6 +142,10 @@ export class ValidatingController extends modules.Controller { return !!validationData && !!validationData.validated; } + public getValidationData(key) { + return this._getValidationData(key); + } + public _getValidationData(key, create?) { const keyHash = getKeyHash(key); const isObjectKeyHash = isObject(keyHash); From 91bbd4d777c615437b1db1f25b0425bb3052246e Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:28:17 +0400 Subject: [PATCH 4/6] refactor(grids): name validating data extender export to match module --- .../validating/extenders/validating_data_controller.ts | 2 +- .../grids/grid_core/validating/validating_module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts index 7ff15ce49cc4..95bef0583818 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -13,7 +13,7 @@ interface ValidatingControllerReader { getValidationData: (key: unknown) => { isValid?: boolean } | undefined; } -export const data = ( +export const validatingDataControllerExtender = ( Base: ModuleType, ): ModuleType => class ValidatingDataControllerExtender extends Base { protected _validatingController!: ValidatingControllerReader; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts index fe8ce37869db..b44ae92a4354 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts @@ -1,6 +1,6 @@ import messageLocalization from '@js/common/core/localization/message'; -import { data } from './extenders/validating_data_controller'; +import { validatingDataControllerExtender } from './extenders/validating_data_controller'; import { ValidatingController, validatingEditingExtender, @@ -25,7 +25,7 @@ export const validatingModule = { controllers: { editing: validatingEditingExtender, editorFactory: validatingEditorFactoryExtender, - data, + data: validatingDataControllerExtender, }, views: { rowsView: validatingRowsViewExtender, From 36804793ea1a4fea5fc3915c410c898ee654ac0e Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:47:13 +0400 Subject: [PATCH 5/6] refactor(grids): type validating data extender and split _isCellChanged --- .../extenders/validating_data_controller.ts | 81 ++++++++++++++----- .../grid_core/validating/validating_module.ts | 7 +- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts index 95bef0583818..656b6e9f349d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -1,5 +1,6 @@ import $ from '@js/core/renderer'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { Cell, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; import { @@ -8,9 +9,24 @@ import { validationResultIsValid, } from '../const'; +interface CellValidationResult { + status?: string; +} + +type ValidationResult = CellValidationResult | string | undefined; + +interface ValidationData { + isValid?: boolean; +} + +type ValidatedCell = Cell & { + validationStatus?: string; + cellElement?: Element; +}; + interface ValidatingControllerReader { - getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => unknown; - getValidationData: (key: unknown) => { isValid?: boolean } | undefined; + getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => ValidationResult; + getValidationData: (key: unknown) => ValidationData | undefined; } export const validatingDataControllerExtender = ( @@ -23,33 +39,60 @@ export const validatingDataControllerExtender = ( super.init(); } - private _getValidationStatus(validationResult): string { - const validationStatus = validationResultIsValid(validationResult) - ? validationResult.status - : validationResult; + private _getValidationStatus(validationResult: ValidationResult): string { + if (!validationResultIsValid(validationResult)) { + return (validationResult as string | undefined) ?? VALIDATION_STATUS.valid; + } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return validationStatus ?? VALIDATION_STATUS.valid; + return (validationResult as CellValidationResult).status ?? VALIDATION_STATUS.valid; } - protected _isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate): boolean { + private _isRowEditStateChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + columnIndex: number, + ): boolean { const cell = oldRow.cells?.[columnIndex]; + const hasValidationRules = !!cell?.column?.validationRules?.length; + + return oldRow.isEditing !== newRow.isEditing && hasValidationRules; + } + + private _isCellValidationStateChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + columnIndex: number, + ): boolean { + const cell = oldRow.cells?.[columnIndex] as ValidatedCell | undefined; + const oldValidationStatus = this._getValidationStatus({ status: cell?.validationStatus }); - const validationResult = this._validatingController.getCellValidationResult({ - rowKey: oldRow.key, - columnIndex, - }); - const validationData = this._validatingController.getValidationData(oldRow.key); - const newValidationStatus = this._getValidationStatus(validationResult); + const newValidationStatus = this._getValidationStatus( + this._validatingController.getCellValidationResult({ rowKey: oldRow.key, columnIndex }), + ); const rowIsModified = JSON.stringify(newRow.modifiedValues) !== JSON.stringify(oldRow.modifiedValues); const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; + + const validationData = this._validatingController.getValidationData(oldRow.key); const cellIsMarkedAsInvalid = $(cell?.cellElement) .hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); - const hasValidationRules = cell?.column.validationRules?.length; - const rowEditStateChanged = oldRow.isEditing !== newRow.isEditing && hasValidationRules; - const cellValidationStateChanged = validationStatusChanged - || (validationData?.isValid && cellIsMarkedAsInvalid); + + return validationStatusChanged || !!(validationData?.isValid && cellIsMarkedAsInvalid); + } + + protected _isCellChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + visibleRowIndex: number, + columnIndex: number, + isLiveUpdate?: boolean, + ): boolean { + const rowEditStateChanged = this._isRowEditStateChanged(oldRow, newRow, columnIndex); + const cellValidationStateChanged = this._isCellValidationStateChanged( + oldRow, + newRow, + columnIndex, + ); if (rowEditStateChanged || cellValidationStateChanged) { return true; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts index b44ae92a4354..7510fbec101b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts @@ -1,4 +1,5 @@ import messageLocalization from '@js/common/core/localization/message'; +import type { EditingTextsBase } from '@js/common/grids'; import { validatingDataControllerExtender } from './extenders/validating_data_controller'; import { @@ -8,8 +9,12 @@ import { validatingRowsViewExtender, } from './m_validating'; +interface ValidatingModuleOptions { + editing: { texts: Pick }; +} + export const validatingModule = { - defaultOptions(): { editing: { texts: { validationCancelChanges: string } } } { + defaultOptions(): ValidatingModuleOptions { return { editing: { texts: { From 20cea4dceb3b4cab5444c5943e35aa35ab9a2ef7 Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:31:01 +0400 Subject: [PATCH 6/6] refactor(grids): make validationResultIsValid a type guard and tidy validation types --- .../grids/grid_core/validating/const.ts | 14 +++++++++++--- .../extenders/validating_data_controller.ts | 17 +++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts index 0b57bc78b014..7cde2d2ce9de 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts @@ -6,9 +6,17 @@ export const VALIDATION_STATUS = { valid: 'valid', invalid: 'invalid', pending: 'pending', -}; +} as const; + +export type ValidationStatus = typeof VALIDATION_STATUS[keyof typeof VALIDATION_STATUS]; export const VALIDATION_CANCELLED = 'cancel'; -export const validationResultIsValid = (result: unknown): boolean => isDefined(result) - && result !== VALIDATION_CANCELLED; +export interface CellValidationResult { + status?: ValidationStatus; + disabledPendingId?: unknown; +} + +export const validationResultIsValid = ( + result: unknown, +): result is CellValidationResult => isDefined(result) && result !== VALIDATION_CANCELLED; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts index 656b6e9f349d..74704393f089 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -3,16 +3,13 @@ import type { DataController } from '@ts/grids/grid_core/data_controller/data_co import type { Cell, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import type { CellValidationResult, ValidationStatus } from '../const'; import { INVALIDATE_CLASS, VALIDATION_STATUS, validationResultIsValid, } from '../const'; -interface CellValidationResult { - status?: string; -} - type ValidationResult = CellValidationResult | string | undefined; interface ValidationData { @@ -20,7 +17,7 @@ interface ValidationData { } type ValidatedCell = Cell & { - validationStatus?: string; + validationStatus?: ValidationStatus; cellElement?: Element; }; @@ -41,10 +38,10 @@ export const validatingDataControllerExtender = ( private _getValidationStatus(validationResult: ValidationResult): string { if (!validationResultIsValid(validationResult)) { - return (validationResult as string | undefined) ?? VALIDATION_STATUS.valid; + return validationResult ?? VALIDATION_STATUS.valid; } - return (validationResult as CellValidationResult).status ?? VALIDATION_STATUS.valid; + return validationResult.status ?? VALIDATION_STATUS.valid; } private _isRowEditStateChanged( @@ -73,11 +70,15 @@ export const validatingDataControllerExtender = ( !== JSON.stringify(oldRow.modifiedValues); const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; + if (validationStatusChanged) { + return true; + } + const validationData = this._validatingController.getValidationData(oldRow.key); const cellIsMarkedAsInvalid = $(cell?.cellElement) .hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); - return validationStatusChanged || !!(validationData?.isValid && cellIsMarkedAsInvalid); + return !!validationData?.isValid && cellIsMarkedAsInvalid; } protected _isCellChanged(