diff --git a/packages/devextreme/js/__internal/grids/data_grid/adaptivity/adaptivity_module.ts b/packages/devextreme/js/__internal/grids/data_grid/adaptivity/adaptivity_module.ts new file mode 100644 index 000000000000..158a75a965ae --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/adaptivity/adaptivity_module.ts @@ -0,0 +1,15 @@ +import { adaptivityModule } from '@ts/grids/grid_core/adaptivity/adaptivity_module'; + +import gridCore from '../m_core'; +import { adaptivityExportViewControllerExtender } from './extenders/adaptivity_export_view_controller'; + +gridCore.registerModule('adaptivity', { + ...adaptivityModule, + extenders: { + ...adaptivityModule.extenders, + controllers: { + ...adaptivityModule.extenders?.controllers, + export: adaptivityExportViewControllerExtender, + }, + }, +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/adaptivity/extenders/adaptivity_export_view_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/adaptivity/extenders/adaptivity_export_view_controller.ts new file mode 100644 index 000000000000..e221e8edac68 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/adaptivity/extenders/adaptivity_export_view_controller.ts @@ -0,0 +1,15 @@ +import type { ExportController } from '@ts/grids/data_grid/export/m_export'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { HIDDEN_COLUMNS_WIDTH } from '../../../grid_core/adaptivity/const'; + +export const adaptivityExportViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityExportViewControllerExtender extends Base { + protected _updateColumnWidth(column, width): void { + super._updateColumnWidth( + column, + column.visibleWidth === HIDDEN_COLUMNS_WIDTH ? column.bestFitWidth : width, + ); + } +}; diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_widget.ts b/packages/devextreme/js/__internal/grids/data_grid/m_widget.ts index 716467043c5a..b6a782619dc2 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/m_widget.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/m_widget.ts @@ -28,7 +28,7 @@ import './keyboard_navigation/m_group_panel_keyboard_navigation'; import './summary/summary_module'; import './module_not_extended/sticky_columns'; import './module_not_extended/column_fixing'; -import './module_not_extended/adaptivity'; +import './adaptivity/adaptivity_module'; import './module_not_extended/virtual_columns'; import './export/m_export'; import './focus/m_focus'; diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/adaptivity.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/adaptivity.ts deleted file mode 100644 index 4999981811cb..000000000000 --- a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/adaptivity.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { adaptivityModule } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; - -import gridCore from '../m_core'; - -gridCore.registerModule('adaptivity', adaptivityModule); diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/__tests__/utils.test.ts new file mode 100644 index 000000000000..72533cd80242 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/__tests__/utils.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from '@jest/globals'; + +import { getAdaptiveDetailRowIndex, resolveAdaptiveDetailRowTarget } from '../utils'; + +describe('resolveAdaptiveDetailRowTarget', () => { + it('expands the row when no adaptive detail row is expanded', () => { + expect(resolveAdaptiveDetailRowTarget('key_1', 2, -1, false)).toEqual({ + key: 'key_1', + rowIndex: 2, + }); + }); + + it('expands the row when another adaptive detail row is expanded', () => { + expect(resolveAdaptiveDetailRowTarget('key_1', 2, 5, false)).toEqual({ + key: 'key_1', + rowIndex: 2, + }); + }); + + it('collapses the row when its adaptive detail row is expanded', () => { + expect(resolveAdaptiveDetailRowTarget('key_1', 2, 2, false)).toEqual({ + key: undefined, + rowIndex: -1, + }); + }); + + it('keeps the row expanded when the adaptive detail row should always be expanded', () => { + expect(resolveAdaptiveDetailRowTarget('key_1', 2, 2, true)).toEqual({ + key: 'key_1', + rowIndex: 2, + }); + }); + + it('keeps the key when neither the key nor the expanded key is loaded', () => { + expect(resolveAdaptiveDetailRowTarget('key_1', -1, -1, false)).toEqual({ + key: 'key_1', + rowIndex: -1, + }); + }); +}); + +describe('getAdaptiveDetailRowIndex', () => { + it('returns the index of the row that follows the data row', () => { + expect(getAdaptiveDetailRowIndex(2)).toBe(3); + }); + + it('subtracts the row index delta', () => { + expect(getAdaptiveDetailRowIndex(20, 15)).toBe(6); + }); + + it('keeps a negative index of a row that is not loaded', () => { + expect(getAdaptiveDetailRowIndex(-1)).toBe(-1); + expect(getAdaptiveDetailRowIndex(-1, 15)).toBe(-16); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity_module.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity_module.ts new file mode 100644 index 000000000000..de4aeabacdab --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity_module.ts @@ -0,0 +1,39 @@ +import type { Module } from '../m_types'; +import { adaptivityColumnsControllerExtender } from './extenders/adaptivity_columns_controller'; +import { adaptivityColumnsResizerViewControllerExtender } from './extenders/adaptivity_columns_resizer_view_controller'; +import { adaptivityDataControllerExtender } from './extenders/adaptivity_data_controller'; +import { adaptivityDraggingHeaderViewControllerExtender } from './extenders/adaptivity_dragging_header_view_controller'; +import { adaptivityEditingViewControllerExtender } from './extenders/adaptivity_editing_view_controller'; +import { adaptivityEditorFactoryViewControllerExtender } from './extenders/adaptivity_editor_factory_view_controller'; +import { adaptivityHeadersKeyboardNavigationViewControllerExtender } from './extenders/adaptivity_headers_keyboard_navigation_view_controller'; +import { adaptivityKeyboardNavigationViewControllerExtender } from './extenders/adaptivity_keyboard_navigation_view_controller'; +import { adaptivityResizingViewControllerExtender } from './extenders/adaptivity_resizing_view_controller'; +import { adaptivityRowsViewExtender } from './extenders/adaptivity_rows_view'; +import { AdaptiveColumnsController } from './m_adaptivity'; + +export const adaptivityModule: Module = { + defaultOptions() { + return { + columnHidingEnabled: false, + }; + }, + controllers: { + adaptiveColumns: AdaptiveColumnsController, + }, + extenders: { + views: { + rowsView: adaptivityRowsViewExtender, + }, + controllers: { + columnsResizer: adaptivityColumnsResizerViewControllerExtender, + draggingHeader: adaptivityDraggingHeaderViewControllerExtender, + editing: adaptivityEditingViewControllerExtender, + resizing: adaptivityResizingViewControllerExtender, + data: adaptivityDataControllerExtender, + editorFactory: adaptivityEditorFactoryViewControllerExtender, + columns: adaptivityColumnsControllerExtender, + keyboardNavigation: adaptivityKeyboardNavigationViewControllerExtender, + headersKeyboardNavigation: adaptivityHeadersKeyboardNavigationViewControllerExtender, + }, + }, +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/const.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/const.ts index b77e371f34aa..22be9f8267a4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/const.ts @@ -1 +1,32 @@ +export const COLUMN_HEADERS_VIEW = 'columnHeadersView' as const; +export const ROWS_VIEW = 'rowsView' as const; +export const FOOTER_VIEW = 'footerView' as const; +export const COLUMN_VIEWS = [COLUMN_HEADERS_VIEW, ROWS_VIEW, FOOTER_VIEW] as const; + +export const ADAPTIVE_NAMESPACE = 'dxDataGridAdaptivity'; export const HIDDEN_COLUMNS_WIDTH = 'adaptiveHidden'; +export const ADAPTIVE_ROW_TYPE = 'detailAdaptive'; + +export const FORM_ITEM_CONTENT_CLASS = 'dx-field-item-content'; +export const FORM_ITEM_MODIFIED = 'dx-item-modified'; + +export const HIDDEN_COLUMN_CLASS = 'hidden-column'; +export const ADAPTIVE_COLUMN_BUTTON_CLASS = 'adaptive-more'; +export const ADAPTIVE_COLUMN_NAME_CLASS = 'dx-command-adaptive'; +export const COMMAND_ADAPTIVE_HIDDEN_CLASS = 'dx-command-adaptive-hidden'; +export const ADAPTIVE_DETAIL_ROW_CLASS = 'dx-adaptive-detail-row'; +export const ADAPTIVE_ITEM_TEXT_CLASS = 'dx-adaptive-item-text'; +export const MASTER_DETAIL_CELL_CLASS = 'dx-master-detail-cell'; +export const LAST_DATA_CELL_CLASS = 'dx-last-data-cell'; +export const ADAPTIVE_COLUMN_NAME = 'adaptive'; +export const EDIT_MODE_BATCH = 'batch'; +export const EDIT_MODE_ROW = 'row'; +export const EDIT_MODE_FORM = 'form'; +export const EDIT_MODE_POPUP = 'popup'; +export const REVERT_TOOLTIP_CLASS = 'revert-tooltip'; +export const GROUP_CELL_CLASS = 'dx-group-cell'; +export const GROUP_ROW_CLASS = 'dx-group-row'; + +export const EXPAND_ARIA_NAME = 'dxDataGrid-ariaAdaptiveExpand'; +export const COLLAPSE_ARIA_NAME = 'dxDataGrid-ariaAdaptiveCollapse'; +export const LEGACY_SCROLLING_MODE = 'scrolling.legacyMode'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_controller.ts new file mode 100644 index 000000000000..197e11e65176 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_controller.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { HIDDEN_COLUMNS_WIDTH } from '../const'; +import type { AdaptiveColumnsController } from '../m_adaptivity'; + +export const adaptivityColumnsControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityColumnsControllerExtender extends Base { + private adaptiveColumnsController!: AdaptiveColumnsController; + + public init(isApplyingUserState?: boolean): void { + super.init(isApplyingUserState); + this.adaptiveColumnsController = this.getController('adaptiveColumns'); + } + + protected _isColumnVisible(column) { + return super._isColumnVisible(column) && !column.adaptiveHidden; + } + + public getVisibleDataColumnsByBandColumn(bandColumnIndex: number) { + return super.getVisibleDataColumnsByBandColumn(bandColumnIndex) + .filter((column) => column.visibleWidth !== HIDDEN_COLUMNS_WIDTH); + } + + public isAdaptiveHiddenColumn(column: Column): boolean { + return super.isAdaptiveHiddenColumn(column) + || this.adaptiveColumnsController.isColumnHidden(column); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_resizer_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_resizer_view_controller.ts new file mode 100644 index 000000000000..20a30ad7dff4 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_columns_resizer_view_controller.ts @@ -0,0 +1,35 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable no-plusplus */ +import type { dxElementWrapper } from '@js/core/renderer'; +import type { ColumnsResizerViewController } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { HIDDEN_COLUMNS_WIDTH } from '../const'; + +export const adaptivityColumnsResizerViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityColumnsResizerViewControllerExtender extends Base { + protected _pointCreated(point, columns, cells?: dxElementWrapper) { + const result = super._pointCreated(point, columns, cells); + const currentColumn = columns[point.columnIndex] || {}; + const nextColumnIndex = this._getNextColumnIndex(point.columnIndex); + const nextColumn = columns[nextColumnIndex] || {}; + const hasHiddenColumnsOnly = nextColumnIndex !== point.columnIndex + 1 && nextColumn.command; + const hasAdaptiveHiddenWidth = currentColumn.visibleWidth === HIDDEN_COLUMNS_WIDTH || hasHiddenColumnsOnly; + + return result || hasAdaptiveHiddenWidth; + } + + protected _getNextColumnIndex(currentColumnIndex) { + const visibleColumns = this._columnsController.getVisibleColumns(); + let index = super._getNextColumnIndex(currentColumnIndex); + + while (visibleColumns[index]?.visibleWidth === HIDDEN_COLUMNS_WIDTH) { + index++; + } + + return index; + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts new file mode 100644 index 000000000000..01036d55edac --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts @@ -0,0 +1,116 @@ +import { isDefined } from '@js/core/utils/type'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataChange, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; +import type { ModuleType, RowKey } from '@ts/grids/grid_core/m_types'; +import gridCoreUtils from '@ts/grids/grid_core/m_utils'; + +import { + ADAPTIVE_ROW_TYPE, + COLLAPSE_ARIA_NAME, + EXPAND_ARIA_NAME, + LEGACY_SCROLLING_MODE, +} from '../const'; +import type { AdaptiveColumnsController } from '../m_adaptivity'; +import { getAdaptiveDetailRowIndex, resolveAdaptiveDetailRowTarget } from '../utils'; + +export const adaptivityDataControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityDataControllerExtender extends Base { + private adaptiveExpandedKey: RowKey | undefined; + + protected adaptiveColumnsController!: AdaptiveColumnsController; + + public init(): void { + super.init(); + this.adaptiveExpandedKey = undefined; + this.adaptiveColumnsController = this.getController('adaptiveColumns'); + } + + protected _processItems(items: RawItemData[], change: DataChange): ProcessedItem[] { + const processedItems = super._processItems(items, change); + const { changeType } = change; + + if ((changeType === 'loadingAll') || !isDefined(this.adaptiveExpandedKey)) { + return processedItems; + } + + const expandRowIndex = gridCoreUtils.getIndexByKey(this.adaptiveExpandedKey, processedItems); + const newMode = this.option(LEGACY_SCROLLING_MODE) === false; + + if (expandRowIndex >= 0) { + const item = processedItems[expandRowIndex]; + processedItems.splice(expandRowIndex + 1, 0, { + visible: true, + rowType: ADAPTIVE_ROW_TYPE, + key: item.key, + data: item.data, + // @ts-expect-error treelist specific field + node: item.node, + modifiedValues: item.modifiedValues, + isNewRow: item.isNewRow, + values: item.values, + }); + } else if (changeType === 'refresh' && !(newMode && change.repaintChangesOnly)) { + this.adaptiveExpandedKey = undefined; + } + + return processedItems; + } + + protected getRowIndicesForExpand(key: RowKey): number[] { + const rowIndices = super.getRowIndicesForExpand(key); + + if (this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(key)) { + const lastRowIndex = rowIndices[rowIndices.length - 1]; + + rowIndices.push(lastRowIndex + 1); + } + + return rowIndices; + } + + public toggleExpandAdaptiveDetailRow(key?: RowKey, alwaysExpanded = false): void { + const oldKey = this.adaptiveExpandedKey; + const oldRowIndex = gridCoreUtils.getIndexByKey(oldKey, this._items); + + const target = resolveAdaptiveDetailRowTarget( + key, + gridCoreUtils.getIndexByKey(key, this._items), + oldRowIndex, + alwaysExpanded, + ); + + this.adaptiveExpandedKey = target.key; + + this.updateAdaptiveDetailRows(oldRowIndex, target.rowIndex); + this.updateAdaptiveAriaLabels(target.key, oldKey); + } + + private updateAdaptiveDetailRows(oldRowIndex: number, newRowIndex: number): void { + const rowIndexDelta = this.getRowIndexDelta(); + + this.updateItems({ + allowInvisibleRowIndices: true, + changeType: 'update', + rowIndices: [oldRowIndex, newRowIndex] + .map((rowIndex) => getAdaptiveDetailRowIndex(rowIndex, rowIndexDelta)), + }); + } + + private updateAdaptiveAriaLabels( + expandedKey: RowKey | undefined, + collapsedKey: RowKey | undefined, + ): void { + this.adaptiveColumnsController.updateCommandAdaptiveAriaLabel(expandedKey, COLLAPSE_ARIA_NAME); + this.adaptiveColumnsController.updateCommandAdaptiveAriaLabel(collapsedKey, EXPAND_ARIA_NAME); + } + + public getAdaptiveExpandedKey(): RowKey | undefined { + return this.adaptiveExpandedKey; + } + + public setAdaptiveExpandedKey(key: RowKey): void { + this.adaptiveExpandedKey = key; + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_dragging_header_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_dragging_header_view_controller.ts new file mode 100644 index 000000000000..040312c5871f --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_dragging_header_view_controller.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import type { DraggingHeaderViewController } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { HIDDEN_COLUMNS_WIDTH } from '../const'; + +export const adaptivityDraggingHeaderViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityDraggingHeaderViewControllerExtender + extends Base { + protected _pointCreated({ + point, columns, location, sourceColumn, cells, + }) { + const result = super._pointCreated({ + point, columns, location, sourceColumn, cells, + }); + const column = columns[point.columnIndex - 1] || {}; + const hasAdaptiveHiddenWidth = column.visibleWidth === HIDDEN_COLUMNS_WIDTH; + + return result || hasAdaptiveHiddenWidth; + } +}; 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 new file mode 100644 index 000000000000..663668873d37 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editing_view_controller.ts @@ -0,0 +1,147 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable prefer-rest-params */ +import { Deferred, when } from '@js/core/utils/deferred'; +import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import gridCoreUtils from '@ts/grids/grid_core/m_utils'; + +import { + ADAPTIVE_ROW_TYPE, + EDIT_MODE_BATCH, + EDIT_MODE_ROW, + FORM_ITEM_CONTENT_CLASS, +} from '../const'; + +export const adaptivityEditingViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityEditingViewControllerExtender extends Base { + 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); + return this.getColumnTemplate(cellOptions); + } + + return super._getFormEditItemTemplate(cellOptions, column); + } + + protected _closeEditItem($targetElement) { + const $itemContents = $targetElement.closest(`.${FORM_ITEM_CONTENT_CLASS}`); + const rowIndex = this._dataController.getRowIndexByKey(this._dataController.getAdaptiveExpandedKey()) + 1; + const formItem = $itemContents.length ? $itemContents.first().data('dx-form-item') : null; + const columnIndex = formItem?.column && this._columnsController.getVisibleIndex(formItem.column.index); + + if (!this.isEditCell(rowIndex, columnIndex)) { + super._closeEditItem($targetElement); + } + } + + protected _beforeUpdateItems(rowIndices, rowIndex) { + if (!this.adaptiveColumnsController.isFormOrPopupEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { + const items = this._dataController.items(); + const item = items[rowIndex]; + const oldExpandRowIndex = gridCoreUtils.getIndexByKey(this._dataController.getAdaptiveExpandedKey(), items); + + this._isForceRowAdaptiveExpand = !this.adaptiveColumnsController.hasAdaptiveDetailRowExpanded(); + + if (oldExpandRowIndex >= 0) { + rowIndices.push(oldExpandRowIndex + 1); + } + + rowIndices.push(rowIndex + 1); + this._dataController.setAdaptiveExpandedKey(item.key); + } + } + + protected _afterInsertRow(key) { + super._afterInsertRow.apply(this, arguments as any); + + if (this.adaptiveColumnsController.hasHiddenColumns()) { + // @ts-expect-error + this.adaptiveColumnsController.toggleExpandAdaptiveDetailRow(key, this.isRowEditMode()); + this._isForceRowAdaptiveExpand = true; + } + } + + private _collapseAdaptiveDetailRow() { + if (this._isRowEditMode() && this._isForceRowAdaptiveExpand) { + this.adaptiveColumnsController.collapseAdaptiveDetailRow(); + this._isForceRowAdaptiveExpand = false; + } + } + + private _cancelEditAdaptiveDetailRow() { + if (this.adaptiveColumnsController.hasHiddenColumns()) { + this._collapseAdaptiveDetailRow(); + } + } + + protected _afterSaveEditData() { + super._afterSaveEditData.apply(this, arguments as any); + // @ts-expect-error + const deferred = new Deferred(); + if (this._isRowEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { + when(this._validatingController.validate(true)).done((isValid) => { + if (isValid) { + this._cancelEditAdaptiveDetailRow(); + } + deferred.resolve(); + }); + } else { + deferred.resolve(); + } + return deferred.promise(); + } + + protected _beforeCancelEditData() { + super._beforeCancelEditData(); + this._cancelEditAdaptiveDetailRow(); + } + + protected _getRowIndicesForCascadeUpdating(row) { + const rowIndices = super._getRowIndicesForCascadeUpdating.apply(this, arguments as any); + + if (this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(row.key)) { + rowIndices.push(row.rowType === ADAPTIVE_ROW_TYPE ? row.rowIndex - 1 : row.rowIndex + 1); + } + + return rowIndices; + } + + protected _beforeCloseEditCellInBatchMode(rowIndices) { + const expandedKey = this._dataController.getAdaptiveExpandedKey(); + + if (expandedKey) { + const rowIndex = gridCoreUtils.getIndexByKey(expandedKey, this._dataController.items()); + if (rowIndex > -1) { + rowIndices.unshift(rowIndex); + } + } + } + + public editRow(rowIndex) { + if (this.adaptiveColumnsController.isFormOrPopupEditMode()) { + this.adaptiveColumnsController.collapseAdaptiveDetailRow(); + } + + return super.editRow(rowIndex); + } + + protected deleteRow(rowIndex) { + const rowKey = this._dataController.getKeyByRowIndex(rowIndex); + + if (this.getEditMode() === EDIT_MODE_BATCH && this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(rowKey)) { + this.adaptiveColumnsController.collapseAdaptiveDetailRow(); + } + + super.deleteRow(rowIndex); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editor_factory_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editor_factory_view_controller.ts new file mode 100644 index 000000000000..b93fc0db3096 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_editor_factory_view_controller.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { REVERT_TOOLTIP_CLASS } from '../const'; + +export const adaptivityEditorFactoryViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityEditorFactoryViewControllerExtender extends Base { + protected _needHideBorder($element) { + return super._needHideBorder($element) || ($element?.hasClass('dx-field-item-content') && $element?.find('.dx-checkbox').length); + } + + protected _getFocusCellSelector() { + return `${super._getFocusCellSelector()}, .dx-adaptive-detail-row .dx-field-item > .dx-field-item-content`; + } + + /** + * Overrides interface + */ + public _getRevertTooltipsSelector() { + return `${super._getRevertTooltipsSelector()}, .dx-field-item-content .${this.addWidgetPrefix(REVERT_TOOLTIP_CLASS)}`; + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_headers_keyboard_navigation_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_headers_keyboard_navigation_view_controller.ts new file mode 100644 index 000000000000..3349292fbb36 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_headers_keyboard_navigation_view_controller.ts @@ -0,0 +1,45 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { Direction } from '@ts/grids/grid_core/keyboard_navigation/const'; +import type { HeadersKeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { HIDDEN_COLUMNS_WIDTH } from '../const'; + +export const adaptivityHeadersKeyboardNavigationViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityHeadersKeyboardNavigationViewControllerExtender + extends Base { + protected getColumnVisibleIndexCorrection( + visibleIndex: number, + rowIndex: number, + direction: Direction, + ): number { + let indexCorrection = super.getColumnVisibleIndexCorrection(visibleIndex, rowIndex, direction); + let visibleColumns = this._columnsController.getVisibleColumns(rowIndex); + + visibleColumns = direction === 'next' + ? visibleColumns.slice(visibleIndex + 1) + : visibleColumns.slice(0, visibleIndex).reverse(); + + while (visibleColumns?.shift()?.visibleWidth === HIDDEN_COLUMNS_WIDTH) { + indexCorrection += direction === 'next' ? 1 : -1; + } + + return indexCorrection; + } + + protected getFocusableColumns(rowIndex?: number, bandColumnId?: number): Column[] { + return super.getFocusableColumns(rowIndex, bandColumnId) + .filter((col) => col.visibleWidth !== HIDDEN_COLUMNS_WIDTH); + } + + protected getDraggableColumns( + column, + rowIndex: number, + ): any[] { + return super.getDraggableColumns(column, rowIndex) + .filter((col) => col.visibleWidth !== HIDDEN_COLUMNS_WIDTH); + } +}; 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 new file mode 100644 index 000000000000..fba688688751 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_keyboard_navigation_view_controller.ts @@ -0,0 +1,63 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* 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 type { KeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import { + ADAPTIVE_ITEM_TEXT_CLASS, + COMMAND_ADAPTIVE_HIDDEN_CLASS, + HIDDEN_COLUMN_CLASS, + HIDDEN_COLUMNS_WIDTH, +} from '../const'; + +function focusCellHandler(e): void { + const $nextCell = e.data?.$nextCell; + + eventsEngine.off($nextCell, 'focus', focusCellHandler); + // @ts-expect-error + eventsEngine.trigger($nextCell, 'dxclick'); +} + +export const adaptivityKeyboardNavigationViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityKeyboardNavigationViewControllerExtender + extends Base { + public _isCellValid($cell, isClick?) { + return ( + super._isCellValid($cell, isClick) + && !$cell.hasClass(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)) + && !$cell.hasClass(COMMAND_ADAPTIVE_HIDDEN_CLASS) + ); + } + + protected _processNextCellInMasterDetail($nextCell, $cell) { + super._processNextCellInMasterDetail($nextCell, $cell); + + const isCellOrBatchMode = this._editingController.isCellOrBatchEditMode(); + const isEditing = this._editingController.isEditing(); + + if ( + isEditing + && $nextCell + && isCellOrBatchMode + && !this._isInsideEditForm($nextCell) + ) { + eventsEngine.off($nextCell, 'focus', focusCellHandler); + eventsEngine.on($nextCell, 'focus', { $nextCell }, focusCellHandler); + + // @ts-expect-error + eventsEngine.trigger($cell, 'focus'); + } + } + + protected isFocusableColumn(column: Column): boolean { + return super.isFocusableColumn(column) && column.visibleWidth !== HIDDEN_COLUMNS_WIDTH; + } + + public _isCellElement($cell) { + return super._isCellElement($cell) || $cell.hasClass(ADAPTIVE_ITEM_TEXT_CLASS); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_resizing_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_resizing_view_controller.ts new file mode 100644 index 000000000000..fb04bf6c5267 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_resizing_view_controller.ts @@ -0,0 +1,84 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable prefer-rest-params */ +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; + +import { COLUMN_VIEWS } from '../const'; + +export const adaptivityResizingViewControllerExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityResizingViewControllerExtender extends Base { + public dispose() { + super.dispose.apply(this, arguments as any); + clearTimeout(this._updateScrollableTimeoutID); + } + + private isHiddenColumnsChanged( + oldHiddenColumns: Column[], + hiddenColumns: Column[], + ): boolean { + if (oldHiddenColumns.length !== hiddenColumns.length) { + return true; + } + + const oldIndices = new Set(oldHiddenColumns.map((col) => col.index)); + + return hiddenColumns.some((col) => !oldIndices.has(col.index)); + } + + private updateColumnViewsFirstCellClasses(): void { + COLUMN_VIEWS.forEach((viewName) => { + const view = this.getView(viewName); + + if (view?.isVisible()) { + view.updateFirstCellClasses(); + } + }); + } + + protected _needBestFit() { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return super._needBestFit() || !!this.adaptiveColumnsController.getHidingColumnsQueue().length; + } + + protected _correctColumnWidths(resultWidths: (number | string | undefined)[], visibleColumns: Column[]): boolean { + const adaptiveController = this.adaptiveColumnsController; + const oldHiddenColumns = adaptiveController.getHiddenColumns(); + const hidingColumnsQueue = adaptiveController.updateHidingQueue(this._columnsController.getColumns()); + + adaptiveController.hideRedundantColumns(resultWidths, visibleColumns, hidingColumnsQueue); + const hiddenColumns = adaptiveController.getHiddenColumns(); + const isHiddenColumnsChanged = this.isHiddenColumnsChanged(oldHiddenColumns, hiddenColumns); + + if (isHiddenColumnsChanged && adaptiveController.hasAdaptiveDetailRowExpanded()) { + adaptiveController.updateForm(hiddenColumns); + } + + if (isHiddenColumnsChanged) { + this.updateColumnViewsFirstCellClasses(); + } + + if (!hiddenColumns.length) { + adaptiveController.collapseAdaptiveDetailRow(); + } + + return super._correctColumnWidths.apply(this, arguments as any); + } + + protected _toggleBestFitMode(isBestFit) { + this.adaptiveColumnsController._toggleGroupAdaptiveRowVisibility(isBestFit); + isBestFit && this.adaptiveColumnsController._showHiddenColumns(); + super._toggleBestFitMode(isBestFit); + } + + protected _needStretch() { + return super._needStretch.apply(this, arguments as any) + || this.adaptiveColumnsController.getHidingColumnsQueue().length + || this.adaptiveColumnsController.hasHiddenColumns(); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts new file mode 100644 index 000000000000..957b77fd1a75 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts @@ -0,0 +1,123 @@ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable @typescript-eslint/no-this-alias */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable prefer-rest-params */ +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; + +import { + ADAPTIVE_COLUMN_NAME_CLASS, + ADAPTIVE_DETAIL_ROW_CLASS, + ADAPTIVE_ROW_TYPE, + EXPAND_ARIA_NAME, + FORM_ITEM_CONTENT_CLASS, + HIDDEN_COLUMN_CLASS, + HIDDEN_COLUMNS_WIDTH, + LAST_DATA_CELL_CLASS, +} from '../const'; + +function getDataCellElements($row) { + return $row.find('td:not(.dx-datagrid-hidden-column):not([class*=\'dx-command-\'])'); +} + +export const adaptivityRowsViewExtender = ( + Base: ModuleType, +): ModuleType => class AdaptivityRowsViewExtender extends Base { + protected _getCellTemplate(options) { + const that = this; + const { column } = options; + + if (options.rowType === ADAPTIVE_ROW_TYPE && column.command === 'detail') { + return function (container, options) { + that.adaptiveColumnsController.createFormByHiddenColumns($(container), options); + }; + } + return super._getCellTemplate(options); + } + + protected _createRow(row) { + const $row = super._createRow.apply(this, arguments as any); + + if (row?.rowType === ADAPTIVE_ROW_TYPE && row.key === this._dataController.getAdaptiveExpandedKey()) { + $row.addClass(ADAPTIVE_DETAIL_ROW_CLASS); + } + return $row; + } + + protected _renderCells($row, options) { + super._renderCells($row, options); + + const hidingColumnsQueueLength = this.adaptiveColumnsController.getHidingColumnsQueue().length; + const hiddenColumnsLength = this.adaptiveColumnsController.getHiddenColumns().length; + + if (hidingColumnsQueueLength && !hiddenColumnsLength) { + getDataCellElements($row).last().addClass(LAST_DATA_CELL_CLASS); + } + + if (options.row.rowType === 'data') { + this.adaptiveColumnsController.setCommandAdaptiveAriaLabel($row, EXPAND_ARIA_NAME); + } + } + + private _getColumnIndexByElementCore($element) { + const $itemContent = $element.closest(`.${FORM_ITEM_CONTENT_CLASS}`); + if ($itemContent.length && $itemContent.closest(this.component.$element()).length) { + const formItem = $itemContent.length ? $itemContent.first().data('dx-form-item') : null; + return formItem?.column && this._columnsController.getVisibleIndex(formItem.column.index); + } + // @ts-expect-error + return super._getColumnIndexByElementCore($element); + } + + public _cellPrepared($cell, options) { + super._cellPrepared.apply(this, arguments as any); + + if (options.row.rowType !== ADAPTIVE_ROW_TYPE && options.column.visibleWidth === HIDDEN_COLUMNS_WIDTH) { + $cell.addClass(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)); + } + } + + public getCell(cellPosition, rows) { + const item = this._dataController.items()[cellPosition?.rowIndex]; + + if (item?.rowType === ADAPTIVE_ROW_TYPE) { + const $adaptiveDetailItems = this.adaptiveColumnsController.getAdaptiveDetailItems(); + + return super.getCell(cellPosition, rows, $adaptiveDetailItems); + } + return super.getCell.apply(this, arguments as any); + } + + public _getCellElement(rowIndex, columnIdentifier): dxElementWrapper | undefined { + const item = this._dataController.items()[rowIndex]; + + if (item?.rowType === ADAPTIVE_ROW_TYPE) { + return this.adaptiveColumnsController.getItemContentByColumnIndex(columnIdentifier); + } + return super._getCellElement.apply(this, arguments as any); + } + + private getContextMenuItems(options) { + if (options.row?.rowType === 'detailAdaptive') { + const view = this._columnHeadersView; + const formItem = $(options.targetElement).closest('.dx-field-item-label').next().data('dx-form-item'); + // @ts-expect-error + options.column = formItem ? formItem.column : options.column; + return view.getContextMenuItems?.(options); + } + // @ts-expect-error + return super.getContextMenuItems?.(options); + } + + private isClickableElement($target) { + // @ts-expect-error + const isClickable = super.isClickableElement?.($target) ?? false; + + return isClickable || !!$target.closest(`.${ADAPTIVE_COLUMN_NAME_CLASS}`).length; + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts index 96f7ec1bd332..e0383756dc33 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts @@ -1,14 +1,11 @@ -/* eslint-disable max-classes-per-file */ import { name as clickEventName } from '@js/common/core/events/click'; 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 Guid from '@js/core/guid'; -import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { equalByValue } from '@js/core/utils/common'; -import { Deferred, when } from '@js/core/utils/deferred'; import { isElementInDom } from '@js/core/utils/dom'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; @@ -17,67 +14,44 @@ import { isDefined, isString } from '@js/core/utils/type'; import Form from '@js/ui/form'; import { isMaterial } from '@js/ui/themes'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; -import type { ExportController } from '../../data_grid/export/m_export'; import { AI_COLUMN_NAME } from '../ai_column/const'; import type { ColumnsController } from '../columns_controller/m_columns_controller'; -import type { ColumnsResizerViewController, DraggingHeaderViewController } from '../columns_resizing_reordering/m_columns_resizing_reordering'; import type { DataController } from '../data_controller/data_controller'; -import type { DataChange, ProcessedItem } from '../data_controller/types'; -import type { RawItemData } from '../data_source_adapter/types'; import type { EditingController } from '../editing/m_editing'; -import type { EditorFactory } from '../editor_factory/m_editor_factory'; -import type { Direction } from '../keyboard_navigation/const'; -import type { HeadersKeyboardNavigationController } from '../keyboard_navigation/m_headers_keyboard_navigation'; import type { KeyboardNavigationController } from '../keyboard_navigation/m_keyboard_navigation'; import modules from '../m_modules'; -import type { Module, ModuleType } from '../m_types'; +import type { RowKey } from '../m_types'; import gridCoreUtils from '../m_utils'; import type { RowsView } from '../views/m_rows_view'; +import { + ADAPTIVE_COLUMN_BUTTON_CLASS, + ADAPTIVE_COLUMN_NAME, + ADAPTIVE_COLUMN_NAME_CLASS, + ADAPTIVE_ITEM_TEXT_CLASS, + ADAPTIVE_NAMESPACE, + ADAPTIVE_ROW_TYPE, + COLUMN_HEADERS_VIEW, + COLUMN_VIEWS, + COMMAND_ADAPTIVE_HIDDEN_CLASS, + EDIT_MODE_FORM, + EDIT_MODE_POPUP, + EDIT_MODE_ROW, + FORM_ITEM_CONTENT_CLASS, + FORM_ITEM_MODIFIED, + GROUP_CELL_CLASS, + GROUP_ROW_CLASS, + HIDDEN_COLUMN_CLASS, + HIDDEN_COLUMNS_WIDTH, + MASTER_DETAIL_CELL_CLASS, + ROWS_VIEW, +} from './const'; import { getHideableColumns } from './utils'; -const COLUMN_HEADERS_VIEW = 'columnHeadersView' as const; -const ROWS_VIEW = 'rowsView' as const; -const FOOTER_VIEW = 'footerView' as const; -const COLUMN_VIEWS = [COLUMN_HEADERS_VIEW, ROWS_VIEW, FOOTER_VIEW] as const; - -const ADAPTIVE_NAMESPACE = 'dxDataGridAdaptivity'; -const HIDDEN_COLUMNS_WIDTH = 'adaptiveHidden'; -const ADAPTIVE_ROW_TYPE = 'detailAdaptive'; - -const FORM_ITEM_CONTENT_CLASS = 'dx-field-item-content'; -const FORM_ITEM_MODIFIED = 'dx-item-modified'; - -const HIDDEN_COLUMN_CLASS = 'hidden-column'; -const ADAPTIVE_COLUMN_BUTTON_CLASS = 'adaptive-more'; -const ADAPTIVE_COLUMN_NAME_CLASS = 'dx-command-adaptive'; -const COMMAND_ADAPTIVE_HIDDEN_CLASS = 'dx-command-adaptive-hidden'; -const ADAPTIVE_DETAIL_ROW_CLASS = 'dx-adaptive-detail-row'; -const ADAPTIVE_ITEM_TEXT_CLASS = 'dx-adaptive-item-text'; -const MASTER_DETAIL_CELL_CLASS = 'dx-master-detail-cell'; -const LAST_DATA_CELL_CLASS = 'dx-last-data-cell'; -const ADAPTIVE_COLUMN_NAME = 'adaptive'; -const EDIT_MODE_BATCH = 'batch'; -const EDIT_MODE_ROW = 'row'; -const EDIT_MODE_FORM = 'form'; -const EDIT_MODE_POPUP = 'popup'; -const REVERT_TOOLTIP_CLASS = 'revert-tooltip'; -const GROUP_CELL_CLASS = 'dx-group-cell'; -const GROUP_ROW_CLASS = 'dx-group-row'; - -const EXPAND_ARIA_NAME = 'dxDataGrid-ariaAdaptiveExpand'; -const COLLAPSE_ARIA_NAME = 'dxDataGrid-ariaAdaptiveCollapse'; -const LEGACY_SCROLLING_MODE = 'scrolling.legacyMode'; - function getColumnId(that, column) { return that._columnsController.getColumnId(column); } -function getDataCellElements($row) { - return $row.find('td:not(.dx-datagrid-hidden-column):not([class*=\'dx-command-\'])'); -} - function adaptiveCellTemplate(container, options) { let $adaptiveColumnButton; const $container = $(container); @@ -95,14 +69,6 @@ function adaptiveCellTemplate(container, options) { } } -function focusCellHandler(e) { - const $nextCell = e.data?.$nextCell; - - eventsEngine.off($nextCell, 'focus', focusCellHandler); - // @ts-expect-error - eventsEngine.trigger($nextCell, 'dxclick'); -} - export class AdaptiveColumnsController extends modules.ViewController { private _columnsController!: ColumnsController; @@ -429,13 +395,11 @@ export class AdaptiveColumnsController extends modules.ViewController { if (this._isRowEditMode()) { const editRowKey = this.option('editing.editRowKey'); - // @ts-expect-error - if (equalByValue(editRowKey, this._dataController.adaptiveExpandedKey())) { + if (equalByValue(editRowKey, this._dataController.getAdaptiveExpandedKey())) { return true; } } else { - // @ts-expect-error - const rowIndex = this._dataController.getRowIndexByKey(this._dataController.adaptiveExpandedKey()) + 1; + const rowIndex = this._dataController.getRowIndexByKey(this._dataController.getAdaptiveExpandedKey()) + 1; const columnIndex = this._columnsController.getVisibleIndex(item.column.index); return this._editingController.isEditCell(rowIndex, columnIndex); @@ -712,9 +676,8 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - public toggleExpandAdaptiveDetailRow(key?, alwaysExpanded?) { + public toggleExpandAdaptiveDetailRow(key?: RowKey, alwaysExpanded = false): void { if (!(this.isFormOrPopupEditMode() && this._editingController.isEditing())) { - // @ts-expect-error this._dataController.toggleExpandAdaptiveDetailRow(key, alwaysExpanded); } } @@ -752,8 +715,7 @@ export class AdaptiveColumnsController extends modules.ViewController { } public hasAdaptiveDetailRowExpanded() { - // @ts-expect-error - return isDefined(this._dataController.adaptiveExpandedKey()); + return isDefined(this._dataController.getAdaptiveExpandedKey()); } public updateForm(hiddenColumns) { @@ -816,8 +778,9 @@ export class AdaptiveColumnsController extends modules.ViewController { public isAdaptiveDetailRowExpanded(key) { const dataController = this._dataController; - // @ts-expect-error - return dataController.adaptiveExpandedKey() && equalByValue(dataController.adaptiveExpandedKey(), key); + const adaptiveExpandedKey = dataController.getAdaptiveExpandedKey(); + + return adaptiveExpandedKey && equalByValue(adaptiveExpandedKey, key); } private expandAdaptiveDetailRow(key) { @@ -857,604 +820,3 @@ export class AdaptiveColumnsController extends modules.ViewController { .filter((hiddenColumn) => hiddenColumn.index === column.index).length > 0; } } - -const keyboardNavigation = ( - Base: ModuleType, -) => class AdaptivityKeyboardNavigationExtender extends Base { - public _isCellValid($cell, isClick?) { - return ( - super._isCellValid($cell, isClick) - && !$cell.hasClass(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)) - && !$cell.hasClass(COMMAND_ADAPTIVE_HIDDEN_CLASS) - ); - } - - protected _processNextCellInMasterDetail($nextCell, $cell) { - super._processNextCellInMasterDetail($nextCell, $cell); - - const isCellOrBatchMode = this._editingController.isCellOrBatchEditMode(); - const isEditing = this._editingController.isEditing(); - - if ( - isEditing - && $nextCell - && isCellOrBatchMode - && !this._isInsideEditForm($nextCell) - ) { - eventsEngine.off($nextCell, 'focus', focusCellHandler); - eventsEngine.on($nextCell, 'focus', { $nextCell }, focusCellHandler); - - // @ts-expect-error - eventsEngine.trigger($cell, 'focus'); - } - } - - protected isFocusableColumn(column: Column): boolean { - return super.isFocusableColumn(column) && column.visibleWidth !== HIDDEN_COLUMNS_WIDTH; - } - - public _isCellElement($cell) { - return super._isCellElement($cell) || $cell.hasClass(ADAPTIVE_ITEM_TEXT_CLASS); - } -}; - -const rowsView = ( - Base: ModuleType, -) => class AdaptivityRowsViewExtender extends Base { - protected _getCellTemplate(options) { - const that = this; - const { column } = options; - - if (options.rowType === ADAPTIVE_ROW_TYPE && column.command === 'detail') { - return function (container, options) { - that.adaptiveColumnsController.createFormByHiddenColumns($(container), options); - }; - } - return super._getCellTemplate(options); - } - - protected _createRow(row) { - const $row = super._createRow.apply(this, arguments as any); - - // @ts-expect-error - if (row && row.rowType === ADAPTIVE_ROW_TYPE && row.key === this._dataController.adaptiveExpandedKey()) { - $row.addClass(ADAPTIVE_DETAIL_ROW_CLASS); - } - return $row; - } - - protected _renderCells($row, options) { - super._renderCells($row, options); - - const hidingColumnsQueueLength = this.adaptiveColumnsController.getHidingColumnsQueue().length; - const hiddenColumnsLength = this.adaptiveColumnsController.getHiddenColumns().length; - - if (hidingColumnsQueueLength && !hiddenColumnsLength) { - getDataCellElements($row).last().addClass(LAST_DATA_CELL_CLASS); - } - - if (options.row.rowType === 'data') { - this.adaptiveColumnsController.setCommandAdaptiveAriaLabel($row, EXPAND_ARIA_NAME); - } - } - - private _getColumnIndexByElementCore($element) { - const $itemContent = $element.closest(`.${FORM_ITEM_CONTENT_CLASS}`); - if ($itemContent.length && $itemContent.closest(this.component.$element()).length) { - const formItem = $itemContent.length ? $itemContent.first().data('dx-form-item') : null; - return formItem && formItem.column && this._columnsController.getVisibleIndex(formItem.column.index); - } - // @ts-expect-error - return super._getColumnIndexByElementCore($element); - } - - public _cellPrepared($cell, options) { - super._cellPrepared.apply(this, arguments as any); - - if (options.row.rowType !== ADAPTIVE_ROW_TYPE && options.column.visibleWidth === HIDDEN_COLUMNS_WIDTH) { - $cell.addClass(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)); - } - } - - public getCell(cellPosition, rows) { - const item = this._dataController.items()[cellPosition?.rowIndex]; - - if (item?.rowType === ADAPTIVE_ROW_TYPE) { - const $adaptiveDetailItems = this.adaptiveColumnsController.getAdaptiveDetailItems(); - - return super.getCell(cellPosition, rows, $adaptiveDetailItems); - } - return super.getCell.apply(this, arguments as any); - } - - public _getCellElement(rowIndex, columnIdentifier): dxElementWrapper | undefined { - const item = this._dataController.items()[rowIndex]; - - if (item && item.rowType === ADAPTIVE_ROW_TYPE) { - return this.adaptiveColumnsController.getItemContentByColumnIndex(columnIdentifier); - } - return super._getCellElement.apply(this, arguments as any); - } - - private getContextMenuItems(options) { - if (options.row && options.row.rowType === 'detailAdaptive') { - const view = this._columnHeadersView; - const formItem = $(options.targetElement).closest('.dx-field-item-label').next().data('dx-form-item'); - // @ts-expect-error - options.column = formItem ? formItem.column : options.column; - return view.getContextMenuItems && view.getContextMenuItems(options); - } - // @ts-expect-error - return super.getContextMenuItems?.(options); - } - - private isClickableElement($target) { - // @ts-expect-error - const isClickable = super.isClickableElement?.($target) ?? false; - - return isClickable || !!$target.closest(`.${ADAPTIVE_COLUMN_NAME_CLASS}`).length; - } -}; - -const exportExtender = ( - Base: ModuleType, -) => class AdaptivityExportExtender extends Base { - protected _updateColumnWidth(column, width): void { - super._updateColumnWidth( - column, - column.visibleWidth === HIDDEN_COLUMNS_WIDTH ? column.bestFitWidth : width, - ); - } -}; - -const columnsResizer = ( - Base: ModuleType, -) => class AdaptivityColumnsResizerExtender extends Base { - protected _pointCreated(point, columns, cells?: dxElementWrapper) { - const result = super._pointCreated(point, columns, cells); - const currentColumn = columns[point.columnIndex] || {}; - const nextColumnIndex = this._getNextColumnIndex(point.columnIndex); - const nextColumn = columns[nextColumnIndex] || {}; - const hasHiddenColumnsOnly = nextColumnIndex !== point.columnIndex + 1 && nextColumn.command; - const hasAdaptiveHiddenWidth = currentColumn.visibleWidth === HIDDEN_COLUMNS_WIDTH || hasHiddenColumnsOnly; - - return result || hasAdaptiveHiddenWidth; - } - - protected _getNextColumnIndex(currentColumnIndex) { - const visibleColumns = this._columnsController.getVisibleColumns(); - let index = super._getNextColumnIndex(currentColumnIndex); - - while (visibleColumns[index] && visibleColumns[index].visibleWidth === HIDDEN_COLUMNS_WIDTH) { - index++; - } - - return index; - } -}; - -const draggingHeader = ( - Base: ModuleType, -) => class AdaptivityDraggingHeaderExtender extends Base { - protected _pointCreated({ - point, columns, location, sourceColumn, cells, - }) { - const result = super._pointCreated({ - point, columns, location, sourceColumn, cells, - }); - const column = columns[point.columnIndex - 1] || {}; - const hasAdaptiveHiddenWidth = column.visibleWidth === HIDDEN_COLUMNS_WIDTH; - - return result || hasAdaptiveHiddenWidth; - } -}; - -const editing = ( - Base: ModuleType, -) => class AdaptivityEditingExtender extends Base { - 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); - return this.getColumnTemplate(cellOptions); - } - - return super._getFormEditItemTemplate(cellOptions, column); - } - - protected _closeEditItem($targetElement) { - const $itemContents = $targetElement.closest(`.${FORM_ITEM_CONTENT_CLASS}`); - // @ts-expect-error - const rowIndex = this._dataController.getRowIndexByKey(this._dataController.adaptiveExpandedKey()) + 1; - const formItem = $itemContents.length ? $itemContents.first().data('dx-form-item') : null; - const columnIndex = formItem && formItem.column && this._columnsController.getVisibleIndex(formItem.column.index); - - if (!this.isEditCell(rowIndex, columnIndex)) { - super._closeEditItem($targetElement); - } - } - - protected _beforeUpdateItems(rowIndices, rowIndex) { - if (!this.adaptiveColumnsController.isFormOrPopupEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { - const items = this._dataController.items(); - const item = items[rowIndex]; - // @ts-expect-error - const oldExpandRowIndex = gridCoreUtils.getIndexByKey(this._dataController.adaptiveExpandedKey(), items); - - this._isForceRowAdaptiveExpand = !this.adaptiveColumnsController.hasAdaptiveDetailRowExpanded(); - - if (oldExpandRowIndex >= 0) { - rowIndices.push(oldExpandRowIndex + 1); - } - - rowIndices.push(rowIndex + 1); - // @ts-expect-error - this._dataController.adaptiveExpandedKey(item.key); - } - } - - protected _afterInsertRow(key) { - super._afterInsertRow.apply(this, arguments as any); - - if (this.adaptiveColumnsController.hasHiddenColumns()) { - // @ts-expect-error - this.adaptiveColumnsController.toggleExpandAdaptiveDetailRow(key, this.isRowEditMode()); - this._isForceRowAdaptiveExpand = true; - } - } - - private _collapseAdaptiveDetailRow() { - if (this._isRowEditMode() && this._isForceRowAdaptiveExpand) { - this.adaptiveColumnsController.collapseAdaptiveDetailRow(); - this._isForceRowAdaptiveExpand = false; - } - } - - private _cancelEditAdaptiveDetailRow() { - if (this.adaptiveColumnsController.hasHiddenColumns()) { - this._collapseAdaptiveDetailRow(); - } - } - - protected _afterSaveEditData() { - super._afterSaveEditData.apply(this, arguments as any); - // @ts-expect-error - const deferred = new Deferred(); - if (this._isRowEditMode() && this.adaptiveColumnsController.hasHiddenColumns()) { - when(this._validatingController.validate(true)).done((isValid) => { - if (isValid) { - this._cancelEditAdaptiveDetailRow(); - } - deferred.resolve(); - }); - } else { - deferred.resolve(); - } - return deferred.promise(); - } - - protected _beforeCancelEditData() { - super._beforeCancelEditData(); - this._cancelEditAdaptiveDetailRow(); - } - - protected _getRowIndicesForCascadeUpdating(row) { - const rowIndices = super._getRowIndicesForCascadeUpdating.apply(this, arguments as any); - - if (this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(row.key)) { - rowIndices.push(row.rowType === ADAPTIVE_ROW_TYPE ? row.rowIndex - 1 : row.rowIndex + 1); - } - - return rowIndices; - } - - protected _beforeCloseEditCellInBatchMode(rowIndices) { - // @ts-expect-error - const expandedKey = this._dataController._adaptiveExpandedKey; - - if (expandedKey) { - const rowIndex = gridCoreUtils.getIndexByKey(expandedKey, this._dataController.items()); - if (rowIndex > -1) { - rowIndices.unshift(rowIndex); - } - } - } - - public editRow(rowIndex) { - if (this.adaptiveColumnsController.isFormOrPopupEditMode()) { - this.adaptiveColumnsController.collapseAdaptiveDetailRow(); - } - - return super.editRow(rowIndex); - } - - protected deleteRow(rowIndex) { - const rowKey = this._dataController.getKeyByRowIndex(rowIndex); - - if (this.getEditMode() === EDIT_MODE_BATCH && this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(rowKey)) { - this.adaptiveColumnsController.collapseAdaptiveDetailRow(); - } - - super.deleteRow(rowIndex); - } -}; - -const data = ( - Base: ModuleType, -) => class AdaptivityDataControllerExtender extends Base { - private _adaptiveExpandedKey: any; - - protected adaptiveColumnsController!: AdaptiveColumnsController; - - public init() { - super.init(); - this._adaptiveExpandedKey = undefined; - this.adaptiveColumnsController = this.getController('adaptiveColumns'); - } - - protected _processItems(items: RawItemData[], change: DataChange): ProcessedItem[] { - const processedItems = super._processItems(items, change); - const { changeType } = change; - - if ((changeType === 'loadingAll') || !isDefined(this._adaptiveExpandedKey)) { - return processedItems; - } - - const expandRowIndex = gridCoreUtils.getIndexByKey(this._adaptiveExpandedKey, processedItems); - const newMode = this.option(LEGACY_SCROLLING_MODE) === false; - - if (expandRowIndex >= 0) { - const item = processedItems[expandRowIndex]; - processedItems.splice(expandRowIndex + 1, 0, { - visible: true, - rowType: ADAPTIVE_ROW_TYPE, - key: item.key, - data: item.data, - // @ts-expect-error treelist specific field - node: item.node, - modifiedValues: item.modifiedValues, - isNewRow: item.isNewRow, - values: item.values, - }); - } else if (changeType === 'refresh' && !(newMode && change.repaintChangesOnly)) { - this._adaptiveExpandedKey = undefined; - } - - return processedItems; - } - - private _getRowIndicesForExpand(key) { - // @ts-expect-error - const rowIndices = super._getRowIndicesForExpand.apply(this, arguments); - - if (this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(key)) { - const lastRowIndex = rowIndices[rowIndices.length - 1]; - rowIndices.push(lastRowIndex + 1); - } - - return rowIndices; - } - - private adaptiveExpandedKey(value) { - if (isDefined(value)) { - this._adaptiveExpandedKey = value; - } else { - return this._adaptiveExpandedKey; - } - } - - private toggleExpandAdaptiveDetailRow(key, alwaysExpanded) { - const that = this; - - let oldExpandLoadedRowIndex = gridCoreUtils.getIndexByKey(that._adaptiveExpandedKey, that._items); - let newExpandLoadedRowIndex = gridCoreUtils.getIndexByKey(key, that._items); - - if (oldExpandLoadedRowIndex >= 0 && oldExpandLoadedRowIndex === newExpandLoadedRowIndex && !alwaysExpanded) { - key = undefined; - newExpandLoadedRowIndex = -1; - } - - const oldKey = that._adaptiveExpandedKey; - that._adaptiveExpandedKey = key; - - if (oldExpandLoadedRowIndex >= 0) { - oldExpandLoadedRowIndex++; - } - if (newExpandLoadedRowIndex >= 0) { - newExpandLoadedRowIndex++; - } - - const rowIndexDelta = that.getRowIndexDelta(); - - that.updateItems({ - allowInvisibleRowIndices: true, - changeType: 'update', - rowIndices: [oldExpandLoadedRowIndex - rowIndexDelta, newExpandLoadedRowIndex - rowIndexDelta], - }); - - this.adaptiveColumnsController.updateCommandAdaptiveAriaLabel(key, COLLAPSE_ARIA_NAME); - this.adaptiveColumnsController.updateCommandAdaptiveAriaLabel(oldKey, EXPAND_ARIA_NAME); - } -}; - -const editorFactory = ( - Base: ModuleType, -) => class AdaptivityEditorFactoryExtender extends Base { - protected _needHideBorder($element) { - return super._needHideBorder($element) || ($element?.hasClass('dx-field-item-content') && $element?.find('.dx-checkbox').length); - } - - protected _getFocusCellSelector() { - return `${super._getFocusCellSelector()}, .dx-adaptive-detail-row .dx-field-item > .dx-field-item-content`; - } - - /** - * Overrides interface - */ - public _getRevertTooltipsSelector() { - return `${super._getRevertTooltipsSelector()}, .dx-field-item-content .${this.addWidgetPrefix(REVERT_TOOLTIP_CLASS)}`; - } -}; - -const columns = ( - Base: ModuleType, -) => class AdaptivityColumnsExtender extends Base { - private adaptiveColumnsController!: AdaptiveColumnsController; - - public init(isApplyingUserState?: boolean): void { - super.init(isApplyingUserState); - this.adaptiveColumnsController = this.getController('adaptiveColumns'); - } - - protected _isColumnVisible(column) { - return super._isColumnVisible(column) && !column.adaptiveHidden; - } - - public getVisibleDataColumnsByBandColumn(bandColumnIndex: number) { - return super.getVisibleDataColumnsByBandColumn(bandColumnIndex) - .filter((column) => column.visibleWidth !== HIDDEN_COLUMNS_WIDTH); - } - - public isAdaptiveHiddenColumn(column: Column): boolean { - return super.isAdaptiveHiddenColumn(column) - || this.adaptiveColumnsController.isColumnHidden(column); - } -}; - -const resizing = (Base: ModuleType) => class AdaptivityResizingControllerExtender extends Base { - public dispose() { - super.dispose.apply(this, arguments as any); - clearTimeout(this._updateScrollableTimeoutID); - } - - private isHiddenColumnsChanged( - oldHiddenColumns: Column[], - hiddenColumns: Column[], - ): boolean { - if (oldHiddenColumns.length !== hiddenColumns.length) { - return true; - } - - const oldIndices = new Set(oldHiddenColumns.map((col) => col.index)); - - return hiddenColumns.some((col) => !oldIndices.has(col.index)); - } - - private updateColumnViewsFirstCellClasses(): void { - COLUMN_VIEWS.forEach((viewName) => { - const view = this.getView(viewName); - - if (view?.isVisible()) { - view.updateFirstCellClasses(); - } - }); - } - - protected _needBestFit() { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return super._needBestFit() || !!this.adaptiveColumnsController.getHidingColumnsQueue().length; - } - - protected _correctColumnWidths(resultWidths: (number | string | undefined)[], visibleColumns: Column[]): boolean { - const adaptiveController = this.adaptiveColumnsController; - const oldHiddenColumns = adaptiveController.getHiddenColumns(); - const hidingColumnsQueue = adaptiveController.updateHidingQueue(this._columnsController.getColumns()); - - adaptiveController.hideRedundantColumns(resultWidths, visibleColumns, hidingColumnsQueue); - const hiddenColumns = adaptiveController.getHiddenColumns(); - const isHiddenColumnsChanged = this.isHiddenColumnsChanged(oldHiddenColumns, hiddenColumns); - - if (isHiddenColumnsChanged && adaptiveController.hasAdaptiveDetailRowExpanded()) { - adaptiveController.updateForm(hiddenColumns); - } - - if (isHiddenColumnsChanged) { - this.updateColumnViewsFirstCellClasses(); - } - - if (!hiddenColumns.length) { - adaptiveController.collapseAdaptiveDetailRow(); - } - - return super._correctColumnWidths.apply(this, arguments as any); - } - - protected _toggleBestFitMode(isBestFit) { - this.adaptiveColumnsController._toggleGroupAdaptiveRowVisibility(isBestFit); - isBestFit && this.adaptiveColumnsController._showHiddenColumns(); - super._toggleBestFitMode(isBestFit); - } - - protected _needStretch() { - return super._needStretch.apply(this, arguments as any) - || this.adaptiveColumnsController.getHidingColumnsQueue().length - || this.adaptiveColumnsController.hasHiddenColumns(); - } -}; - -const headersKeyboardNavigation = (Base: ModuleType) => class AdaptivityHeadersKeyboardNavigationExtender extends Base { - protected getColumnVisibleIndexCorrection( - visibleIndex: number, - rowIndex: number, - direction: Direction, - ): number { - let indexCorrection = super.getColumnVisibleIndexCorrection(visibleIndex, rowIndex, direction); - let visibleColumns = this._columnsController.getVisibleColumns(rowIndex); - - visibleColumns = direction === 'next' - ? visibleColumns.slice(visibleIndex + 1) - : visibleColumns.slice(0, visibleIndex).reverse(); - - while (visibleColumns?.shift()?.visibleWidth === HIDDEN_COLUMNS_WIDTH) { - indexCorrection += direction === 'next' ? 1 : -1; - } - - return indexCorrection; - } - - protected getFocusableColumns(rowIndex?: number, bandColumnId?: number): Column[] { - return super.getFocusableColumns(rowIndex, bandColumnId) - .filter((col) => col.visibleWidth !== HIDDEN_COLUMNS_WIDTH); - } - - protected getDraggableColumns( - column, - rowIndex: number, - ): any[] { - return super.getDraggableColumns(column, rowIndex) - .filter((col) => col.visibleWidth !== HIDDEN_COLUMNS_WIDTH); - } -}; - -export const adaptivityModule: Module = { - defaultOptions() { - return { - columnHidingEnabled: false, - }; - }, - controllers: { - adaptiveColumns: AdaptiveColumnsController, - }, - extenders: { - views: { - rowsView, - }, - controllers: { - export: exportExtender, - columnsResizer, - draggingHeader, - editing, - resizing, - data, - editorFactory, - columns, - keyboardNavigation, - headersKeyboardNavigation, - }, - }, -}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts new file mode 100644 index 000000000000..a37cf3976235 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts @@ -0,0 +1,6 @@ +import type { RowKey } from '../m_types'; + +export interface AdaptiveDetailRowTarget { + key: RowKey | undefined; + rowIndex: number; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts index 4bb55266036a..0aa37bdf8a24 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts @@ -1,5 +1,8 @@ import { isDefined } from '@js/core/utils/type'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { RowKey } from '@ts/grids/grid_core/m_types'; + +import type { AdaptiveDetailRowTarget } from './types'; function isHideableColumn(column: Column): boolean { const isGroup = (column?.groupIndex ?? -1) >= 0; @@ -13,3 +16,22 @@ function isHideableColumn(column: Column): boolean { export function getHideableColumns(columns: Column[]): Column[] { return columns.filter(isHideableColumn); } + +export function resolveAdaptiveDetailRowTarget( + key: RowKey | undefined, + rowIndex: number, + expandedRowIndex: number, + alwaysExpanded: boolean, +): AdaptiveDetailRowTarget { + const isCollapsing = !alwaysExpanded + && expandedRowIndex >= 0 + && expandedRowIndex === rowIndex; + + return isCollapsing ? { key: undefined, rowIndex: -1 } : { key, rowIndex }; +} + +export function getAdaptiveDetailRowIndex(dataRowIndex: number, rowIndexDelta = 0): number { + const detailRowIndex = dataRowIndex >= 0 ? dataRowIndex + 1 : dataRowIndex; + + return detailRowIndex - rowIndexDelta; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 682433a9eb94..3fc7eb59c8db 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -171,6 +171,14 @@ export class DataController extends DataHelperMixin(modules.Controller) { this.postInit(); } + /** + * @extended: adaptivity, master_detail + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected getRowIndicesForExpand(key: RowKey): number[] { + return []; + } + /** * @extended: virtual_scrolling */ @@ -1817,6 +1825,25 @@ export class DataController extends DataHelperMixin(modules.Controller) { public resetCachedProcessedItems(): void { this._cachedProcessedItems = null; } + + /** + * @extended: adaptivity + */ + public getAdaptiveExpandedKey(): RowKey | undefined { + return undefined; + } + + /** + * @extended: adaptivity + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setAdaptiveExpandedKey(key: RowKey): void {} + + /** + * @extended: adaptivity + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public toggleExpandAdaptiveDetailRow(key?: RowKey, alwaysExpanded = false): void {} } export const dataControllerModule: Module = { defaultOptions() { diff --git a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts index 634167c3a45c..9599d9696164 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts @@ -10,7 +10,7 @@ import { getHeight, getWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import type { ModuleType, RowKey } from '@ts/grids/grid_core/m_types'; import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -98,7 +98,7 @@ export const dataMasterDetailExtenderMixin = (Base: ModuleType) return !!(that._isExpandAll ^ (expandIndex >= 0 && that._expandedItems[expandIndex].visible)); } - private _getRowIndicesForExpand(key) { + protected getRowIndicesForExpand(key: RowKey): number[] { const rowIndex = this.getRowIndexByKey(key); return [rowIndex, rowIndex + 1]; @@ -123,7 +123,7 @@ export const dataMasterDetailExtenderMixin = (Base: ModuleType) that.updateItems({ changeType: 'update', - rowIndices: that._getRowIndicesForExpand(key), + rowIndices: that.getRowIndicesForExpand(key), }); that.resetCachedProcessedItems(); diff --git a/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/adaptivity.ts b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/adaptivity.ts index c7afd0fc51d2..8c43dde897d2 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/adaptivity.ts @@ -1,4 +1,4 @@ -import { adaptivityModule } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; +import { adaptivityModule } from '@ts/grids/grid_core/adaptivity/adaptivity_module'; import treeListCore from '../m_core';