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/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..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
@@ -5,7 +5,8 @@
/* eslint-disable prefer-rest-params */
import { Deferred, when } from '@js/core/utils/deferred';
import type { EditingController } from '@ts/grids/grid_core/editing/editing';
-import type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based';
+import { isFormBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils';
+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';
@@ -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/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/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/__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..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,
@@ -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 75d639cda463..c62724d571b2 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,12 +70,10 @@ 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,
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,13 +100,15 @@ 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';
import type {
AllowEditActionValue,
+ CellBasedEditingController,
EditActionOptions,
EditActions,
EditCellResult,
@@ -122,6 +122,7 @@ import type {
EditingDefaultOptions,
EditingModuleOptionChanged,
EditingOptions,
+ EditingRowsViewExtension,
EditingStartOptions,
EditorCellOptions,
EditorTemplate,
@@ -129,9 +130,9 @@ import type {
InternalColumnButton,
InternalColumnButtonClickEvent,
InternalEditData,
- NormalizedEditCellOptions,
OnSavingParams,
OnSavingResult,
+ PrepareEditCellOptions,
ProcessedChanges,
SavedRowInfo,
SaveResult,
@@ -251,11 +252,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 +393,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 +548,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 +2513,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);
@@ -2799,7 +2785,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;
}
@@ -2819,7 +2805,9 @@ export class EditingController extends modules.ViewController {
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 +2937,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);
}
}
@@ -3050,7 +3044,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_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/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..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
@@ -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,
@@ -37,8 +37,9 @@ import {
TARGET_COMPONENT_NAME,
} from './const';
import type { EditingController } from './editing';
-import { isEditable } from './editing_utils';
+import { isCellBasedEditMode, isEditable } from './editing_utils';
import type {
+ CellBasedEditingController,
EditCellOptions,
EditCellResult,
EditColumnNameOptionChanged,
@@ -48,23 +49,9 @@ import type {
EditorCellOptions,
InternalColumnButton,
NormalizedEditCellOptions,
+ PrepareEditCellOptions,
} 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;
-
- // 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;
-}
-
-export type CellBasedEditingController = EditingController & CellBasedEditingControllerExtension;
-
const editingControllerExtender = (
Base: ModuleType,
): ModuleType => class CellBasedEditingControllerExtender extends Base {
@@ -116,7 +103,9 @@ const editingControllerExtender = (
return;
}
- if (!this.isCellOrBatchEditMode() || this._editCellInProgress) {
+ const editMode = this.getEditMode();
+
+ if (!isCellBasedEditMode(editMode) || this._editCellInProgress) {
return;
}
@@ -126,7 +115,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 +151,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 +191,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 +217,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 +233,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 +245,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,17 +296,17 @@ 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;
}
- return when(this._beforeEditCell(rowIndex, columnIndex, item)).done((cancel) => {
+ return when(this.beforeEditCell(rowIndex, columnIndex, item)).done((cancel) => {
if (cancel) {
return;
}
- if (!this._prepareEditCell(editCellOptions)) {
+ if (!this._prepareEditCell({ ...editCellOptions, column })) {
this._processCanceledEditingCell();
}
});
@@ -339,12 +318,12 @@ const editingControllerExtender = (
* @extended: validating
* @returns whether to cancel cell editing
*/
- protected _beforeEditCell(
+ public beforeEditCell(
rowIndex: number,
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());
@@ -397,7 +376,7 @@ const editingControllerExtender = (
oldColumn,
columnIndex,
oldRowIndex,
- }: NormalizedEditCellOptions): boolean {
+ }: PrepareEditCellOptions): boolean {
const editingStartParams = {
data: item?.data,
cancel: false,
@@ -432,7 +411,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 +440,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 +473,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 +492,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 +500,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 +540,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 +555,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 +570,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 +579,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 +594,7 @@ const editingControllerExtender = (
}
protected _allowRowAdding(): boolean {
- if (this.isBatchEditMode()) {
+ if (this.getEditMode() === EDIT_MODE.batch) {
return true;
}
@@ -622,7 +604,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 +621,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 +632,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 +657,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 +678,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 +690,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 +709,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 +733,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 +742,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 +760,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 +772,7 @@ const editingControllerExtender = (
editIndex: number,
processIfBatch: boolean,
): boolean {
- if (this.isBatchEditMode() && !processIfBatch) {
+ if (this.getEditMode() === EDIT_MODE.batch && !processIfBatch) {
return false;
}
@@ -793,7 +780,7 @@ const editingControllerExtender = (
}
public _processRemoveIfError(changes: EditingChange[], editIndex: number): boolean {
- if (this.isBatchEditMode()) {
+ if (this.getEditMode() === EDIT_MODE.batch) {
return false;
}
@@ -818,7 +805,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 +830,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..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
@@ -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,
@@ -46,23 +51,11 @@ import type {
EditFormItem,
EditFormOptions,
EditingChange,
+ EditingFormBasedRowsViewExtension,
EditorCellOptions,
+ FormBasedEditingController,
} from './types';
-export interface FormBasedEditingControllerExtension {
- isFormEditMode: () => boolean;
- renderFormEditorTemplate: (
- detailCellOptions: EditFormDetailOptions,
- item: EditFormColumnItem,
- formTemplateOptions: SimpleItemTemplateData,
- container: dxElementWrapper,
- isReadOnly?: boolean,
- ) => EditorCellOptions;
- getEditForm: () => Form | null | undefined;
-}
-
-export type FormBasedEditingController = EditingController & FormBasedEditingControllerExtension;
-
type EditFormTemplate = (
$container: DxElement | dxElementWrapper,
detailOptions: EditFormDetailOptions,
@@ -90,22 +83,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 +100,7 @@ const editingControllerExtender = (
}
public getEditFormRowIndex(): number {
- return this.isFormOrPopupEditMode()
+ return isFormBasedEditMode(this.getEditMode())
? this._getVisibleEditRowIndex()
: super.getEditFormRowIndex();
}
@@ -125,11 +109,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 +134,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 +142,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 +152,7 @@ const editingControllerExtender = (
protected _cancelEditDataCore(): void {
super._cancelEditDataCore();
- if (this.isPopupEditMode()) {
+ if (this.getEditMode() === EDIT_MODE.popup) {
this._hideEditPopup();
}
}
@@ -178,7 +164,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 +272,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 +288,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({
@@ -441,11 +427,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');
@@ -469,7 +451,7 @@ const editingControllerExtender = (
});
}
- return extend({}, editFormOptions, {
+ return {
items,
formID: `dx-${new Guid()}`,
customizeItem: (item: EditFormItem): void => {
@@ -508,7 +490,7 @@ const editingControllerExtender = (
userCustomizeItem?.call(this, item);
item.cssClass = isString(item.cssClass) ? `${item.cssClass} ${editFormItemClass}` : editFormItemClass;
},
- });
+ };
}
public getColumnTemplate(
@@ -567,7 +549,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 +561,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 +569,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);
@@ -597,13 +579,6 @@ const editingControllerExtender = (
}
};
-export interface EditingFormBasedRowsViewExtension {
- _getEditFormEditorVisibleIndex: (
- $cells: dxElementWrapper | undefined,
- column: Column | undefined,
- ) => number;
-}
-
const rowsView = (
Base: ModuleType,
): ModuleType<
@@ -618,7 +593,7 @@ const rowsView = (
): void {
if (
options.rowType === 'data'
- && this._editingController.isPopupEditMode()
+ && this._editingController.getEditMode() === EDIT_MODE.popup
&& options.row?.visible === false
) {
return;
@@ -667,7 +642,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/editing/types.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts
index 5d62d134707f..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';
@@ -15,11 +19,12 @@ 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,
} from './const';
+import type { EditingController } from './editing';
export interface EditCellOptions {
rowIndex: number;
@@ -33,12 +38,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 }
@@ -223,3 +231,58 @@ 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;
+}
+
+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/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/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..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
@@ -31,7 +31,8 @@ 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 type { CellBasedEditingController } from '@ts/grids/grid_core/editing/m_editing_cell_based';
+import { isCellBasedEditMode } from '@ts/grids/grid_core/editing/editing_utils';
+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';
@@ -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/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/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 740bfe442790..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
@@ -28,6 +28,8 @@ 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';
import { CLASSES as MASTER_DETAIL_CLASSES } from '@ts/grids/grid_core/master_detail/const';
@@ -37,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 { EditingController } from '../editing/editing';
-import type { CellBasedEditingController } from '../editing/m_editing_cell_based';
-import type { NormalizedEditCellOptions } 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';
@@ -62,7 +68,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 +299,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 +459,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;
@@ -669,7 +675,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 {
@@ -727,11 +735,10 @@ export const validatingEditingExtender = (Base: ModuleType) =
});
}
- private _getValidationGroupsInForm(detailOptions) {
- const validationData = this._validatingController._getValidationData(detailOptions.key, true);
-
+ public override getEditFormOptions(detailOptions: EditFormDetailOptions): EditFormOptions {
return {
- validationGroup: validationData,
+ validationGroup: this._validatingController._getValidationData(detailOptions.key, true),
+ ...super.getEditFormOptions(detailOptions),
};
}
@@ -744,7 +751,7 @@ export const validatingEditingExtender = (Base: ModuleType) =
super._validateEditFormAfterUpdate.apply(this, arguments as any);
}
- protected _prepareEditCell(parameters: NormalizedEditCellOptions): boolean {
+ protected _prepareEditCell(parameters: PrepareEditCellOptions): boolean {
const { column, item } = parameters;
const isNotCanceled: boolean = super._prepareEditCell(parameters);
const key = !item.isNewRow ? item.key : undefined;
@@ -839,7 +846,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;
@@ -921,9 +928,12 @@ 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
- const result = super._beforeEditCell(rowIndex, columnIndex, item);
+ public override beforeEditCell(
+ rowIndex: number,
+ columnIndex: number,
+ item: ProcessedItem,
+ ): DeferredObj | boolean {
+ const result = super.beforeEditCell(rowIndex, columnIndex, item);
if (this.getEditMode() === EDIT_MODE.cell) {
const $cell = this._rowsView._getCellElement(rowIndex, columnIndex);
@@ -1204,10 +1214,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 +1586,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();
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),
- );
-}
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..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,12 @@ 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,
} from '@ts/grids/grid_core/editing/types';
@@ -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);
}
}
};
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..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,11 +1,13 @@
/* 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, FormBasedEditingControllerExtension } from '../grid_core/editing/types';
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);
}