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 index 7e1d233e57e6..9fd7c9310679 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity_module.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/adaptivity_module.ts @@ -9,7 +9,7 @@ import { adaptivityHeadersKeyboardNavigationViewControllerExtender } from './ext 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'; +import { AdaptiveColumnsViewController } from './m_adaptivity'; export const adaptivityModule: Module = { defaultOptions(): Pick { @@ -18,7 +18,7 @@ export const adaptivityModule: Module = { }; }, controllers: { - adaptiveColumns: AdaptiveColumnsController, + adaptiveColumns: AdaptiveColumnsViewController, }, extenders: { views: { 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 index 475fd12be11c..e3a1694ebb35 100644 --- 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 @@ -5,12 +5,12 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ModuleType } from '@ts/grids/grid_core/types'; import { HIDDEN_COLUMNS_WIDTH } from '../const'; -import type { AdaptiveColumnsController } from '../m_adaptivity'; +import type { AdaptiveColumnsViewController } from '../m_adaptivity'; export const adaptivityColumnsControllerExtender = ( Base: ModuleType, ): ModuleType => class AdaptivityColumnsControllerExtender extends Base { - private adaptiveColumnsController!: AdaptiveColumnsController; + private adaptiveColumnsController!: AdaptiveColumnsViewController; public init(isApplyingUserState?: boolean): void { super.init(isApplyingUserState); 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 index 2daff1aa1734..ea74f2356aec 100644 --- 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 @@ -12,7 +12,7 @@ import { EXPAND_ARIA_NAME, LEGACY_SCROLLING_MODE, } from '../const'; -import type { AdaptiveColumnsController } from '../m_adaptivity'; +import type { AdaptiveColumnsViewController } from '../m_adaptivity'; import type { AdaptivityDataController } from '../types'; import { getAdaptiveDetailRowIndex, resolveAdaptiveDetailRowTarget } from '../utils'; @@ -23,7 +23,7 @@ export const adaptivityDataControllerExtender = ( ): ModuleType => class AdaptivityDataControllerExtender extends Base { private adaptiveExpandedKey?: RowKey; - protected adaptiveColumnsController!: AdaptiveColumnsController; + protected adaptiveColumnsController!: AdaptiveColumnsViewController; public init(): void { super.init(); 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 78d6d560a1c4..54836de81d59 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 @@ -3,14 +3,18 @@ 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'; import $ from '@js/core/renderer'; import { equalByValue } from '@js/core/utils/common'; import { isElementInDom } from '@js/core/utils/dom'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getWidth } from '@js/core/utils/size'; -import { isDefined, isString } from '@js/core/utils/type'; +import { isDefined } from '@js/core/utils/type'; +import type { Properties as FormProperties, SimpleItemTemplateData } from '@js/ui/form'; import Form from '@js/ui/form'; import { isMaterial } from '@js/ui/themes'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; @@ -19,12 +23,15 @@ import { FIELD_ITEM_CONTENT_CLASS } from '@ts/ui/form/constants'; import { AI_COLUMN_NAME } from '../ai_column/const'; import type { ColumnsController } from '../columns_controller/columns_controller'; +import type { ProcessedItem } from '../data_controller/types'; import type { EditingController } from '../editing/m_editing'; import type { KeyboardNavigationController } from '../keyboard_navigation/m_keyboard_navigation'; import gridCoreUtils from '../m_utils'; import modules from '../modules/modules'; -import type { RowKey } from '../types'; +import type { OptionChanged, RowKey } from '../types'; import type { RowsView } from '../views/m_rows_view'; +import type { ColumnWidth, ViewCellOptions } from '../views/types'; +import { isPercentWidth } from '../views/utils'; import { ADAPTIVE_COLUMN_BUTTON_CLASS, ADAPTIVE_COLUMN_NAME, @@ -44,31 +51,42 @@ import { MASTER_DETAIL_CELL_CLASS, ROWS_VIEW, } from './const'; -import type { AdaptivityDataController } from './types'; +import type { + AdaptiveDetailCellOptions, + AdaptiveFormItem, + AdaptivityDataController, + HiddenCellInViewOptions, + HiddenCellsInViewOptions, + HideVisibleColumnInViewOptions, + HideVisibleColumnOptions, + PercentWidthOptions, +} from './types'; import { getHideableColumns } from './utils'; -function getColumnId(that, column) { - return that._columnsController.getColumnId(column); -} - -function adaptiveCellTemplate(container, options) { - let $adaptiveColumnButton; +function adaptiveCellTemplate(container: DxElement, options: ViewCellOptions): void { const $container = $(container); // TODO getController + // @ts-expect-error renderTemplate sets the component const adaptiveColumnsController = options.component.getController('adaptiveColumns'); if (options.rowType === 'data') { - $adaptiveColumnButton = $('').addClass(adaptiveColumnsController.addWidgetPrefix(ADAPTIVE_COLUMN_BUTTON_CLASS)); - eventsEngine.on($adaptiveColumnButton, addNamespace(clickEventName, ADAPTIVE_NAMESPACE), adaptiveColumnsController.createAction(() => { - adaptiveColumnsController.toggleExpandAdaptiveDetailRow(options.key); - })); + const $adaptiveColumnButton = $('') + .addClass(adaptiveColumnsController.addWidgetPrefix(ADAPTIVE_COLUMN_BUTTON_CLASS)); + + eventsEngine.on( + $adaptiveColumnButton, + addNamespace(clickEventName, ADAPTIVE_NAMESPACE), + adaptiveColumnsController.createAction(() => { + adaptiveColumnsController.toggleExpandAdaptiveDetailRow(options.key); + }), + ); $adaptiveColumnButton.appendTo($container); } else { gridCoreUtils.setEmptyText($container); } } -export class AdaptiveColumnsController extends modules.ViewController { +export class AdaptiveColumnsViewController extends modules.ViewController { private _columnsController!: ColumnsController; private _dataController!: AdaptivityDataController; @@ -77,17 +95,17 @@ export class AdaptiveColumnsController extends modules.ViewController { protected _rowsView!: RowsView; - private _hiddenColumns: any; + private _hiddenColumns!: Column[]; - private _$itemContents: any; + private _$itemContents?: dxElementWrapper | null; private _form?: Form; - private _hidingColumnsQueue?: Column[]; + private _hidingColumnsQueue!: Column[]; protected _keyboardNavigationController!: KeyboardNavigationController; - public init() { + public init(): void { this._columnsController = this.getController('columns'); this._dataController = this.getController('data') as AdaptivityDataController; this._editingController = this.getController('editing'); @@ -107,7 +125,9 @@ export class AdaptiveColumnsController extends modules.ViewController { }); this._columnsController.columnsChanged.add(() => { - const isAdaptiveVisible = !!this.updateHidingQueue(this._columnsController.getColumns()).length; + const isAdaptiveVisible = !!this.updateHidingQueue( + this._columnsController.getColumns(), + ).length; this._columnsController.columnOption('command:adaptive', 'adaptiveHidden', !isAdaptiveVisible, true); }); this._hidingColumnsQueue = []; @@ -117,7 +137,7 @@ export class AdaptiveColumnsController extends modules.ViewController { super.init(); } - public optionChanged(args) { + public optionChanged(args: OptionChanged): void { if (args.name === 'columnHidingEnabled') { this._columnsController.columnOption('command:adaptive', 'adaptiveHidden', !args.value); } @@ -125,50 +145,61 @@ export class AdaptiveColumnsController extends modules.ViewController { super.optionChanged(args); } - public publicMethods() { + public publicMethods(): string[] { return ['isAdaptiveDetailRowExpanded', 'expandAdaptiveDetailRow', 'collapseAdaptiveDetailRow']; } - private _getValueFromCellOptions(columnIndex: number, cellOptions: any) { + private _getValueFromCellOptions( + columnIndex: number, + cellOptions: AdaptiveDetailCellOptions, + ): unknown { return cellOptions.row.values[columnIndex]; } - private _isRowEditMode() { + private _isRowEditMode(): boolean { const editMode = this._getEditMode(); return editMode === EDIT_MODE_ROW; } - private _isItemModified(item, cellOptions) { + private _isItemModified( + item: AdaptiveFormItem, + cellOptions: AdaptiveDetailCellOptions, + ): boolean | undefined { const columnIndex = this._columnsController.getVisibleIndex(item.column.index); const rowIndex = this._dataController.getRowIndexByKey(cellOptions.key); - const row: any = this._dataController.items()[rowIndex + 1]; + const row: ProcessedItem | undefined = this._dataController.items()[rowIndex + 1]; - return row && row.modifiedValues && isDefined(row.modifiedValues[columnIndex]); + return row?.modifiedValues && isDefined(row.modifiedValues[columnIndex]); } - private _renderFormViewTemplate(item, cellOptions, $container) { - const that = this; + private _renderFormViewTemplate( + item: AdaptiveFormItem, + cellOptions: AdaptiveDetailCellOptions, + $container: dxElementWrapper, + ): void { const { column } = item; - const focusAction = that.createAction(() => { - if (that._editingController.isEditing()) { - // @ts-expect-error + const focusAction = this.createAction(() => { + if (this._editingController.isEditing()) { + // @ts-expect-error eventsEngine.trigger is not fully typed eventsEngine.trigger($container, clickEventName); } }); const rowData = cellOptions.row.data; - const columnIndex = that._columnsController.getVisibleIndex(column.index); + const columnIndex = this._columnsController.getVisibleIndex(column.index); const value = column.type === AI_COLUMN_NAME ? this._getValueFromCellOptions(columnIndex, cellOptions) + // @ts-expect-error createColumn initializes calculateCellValue : column.calculateCellValue(rowData); const displayValue = gridCoreUtils.getDisplayValue(column, value, rowData, cellOptions.rowType); const text = gridCoreUtils.formatValue(displayValue, column); const isCellOrBatchEditMode = this._editingController.isCellOrBatchEditMode(); - const rowsView = that._rowsView; + const rowsView = this._rowsView; if (column.allowEditing && this._keyboardNavigationController.isKeyboardEnabled()) { - $container.attr('tabIndex', that.option('tabIndex')); + // @ts-expect-error attr() is typed without undefined + $container.attr('tabIndex', this.option('tabIndex')); if (isCellOrBatchEditMode) { eventsEngine.off($container, 'focus', focusAction); @@ -181,7 +212,12 @@ export class AdaptiveColumnsController extends modules.ViewController { value, displayValue, text, column, }); - rowsView.renderTemplate($container, column.cellTemplate, templateOptions, isElementInDom($container)).done(() => { + rowsView.renderTemplate( + $container, + column.cellTemplate, + templateOptions, + isElementInDom($container), + ).done(() => { rowsView._cellPrepared($container, cellOptions); }); } else { @@ -197,8 +233,8 @@ export class AdaptiveColumnsController extends modules.ViewController { $container.html(' '); } - if (!that._isRowEditMode()) { - if (that._isItemModified(item, cellOptions)) { + if (!this._isRowEditMode()) { + if (this._isItemModified(item, cellOptions)) { $container.addClass(FORM_ITEM_MODIFIED); } } @@ -207,26 +243,33 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private _getTemplate(item, cellOptions, updateForm?) { - const that = this; + private _getTemplate( + item: AdaptiveFormItem, + cellOptions: AdaptiveDetailCellOptions, + ): (options: SimpleItemTemplateData, container: DxElement) => void { const { column } = item; const editingController = this._editingController; - return function (options, container) { + return (options: SimpleItemTemplateData, container: DxElement): void => { const $container = $(container); - const columnIndex = that._columnsController.getVisibleIndex(column.index); - const templateOptions = extend({}, cellOptions); + const columnIndex = this._columnsController.getVisibleIndex(column.index); + const templateOptions: AdaptiveDetailCellOptions = extend({}, cellOptions); - const renderFormTemplate = function () { - const isItemEdited = that._isItemEdited(item); - templateOptions.value = that._getValueFromCellOptions(columnIndex, cellOptions); + const renderFormTemplate = (): void => { + const isItemEdited = this._isItemEdited(item); + templateOptions.value = this._getValueFromCellOptions(columnIndex, cellOptions); if (isItemEdited || column.showEditorAlways) { - editingController.renderFormEditorTemplate(templateOptions, item, options, $container, !isItemEdited); + editingController.renderFormEditorTemplate( + templateOptions, + item, + options, + $container, + !isItemEdited, + ); } else { templateOptions.column = column; templateOptions.columnIndex = columnIndex; - that._renderFormViewTemplate(item, templateOptions, $container); + this._renderFormViewTemplate(item, templateOptions, $container); } }; @@ -234,8 +277,8 @@ export class AdaptiveColumnsController extends modules.ViewController { if (templateOptions.watch) { const dispose = templateOptions.watch(() => ({ - isItemEdited: that._isItemEdited(item), - value: that._getValueFromCellOptions(columnIndex, cellOptions), + isItemEdited: this._isItemEdited(item), + value: this._getValueFromCellOptions(columnIndex, cellOptions), }), () => { $container.contents().remove(); $container.removeClass(ADAPTIVE_ITEM_TEXT_CLASS); @@ -247,148 +290,159 @@ export class AdaptiveColumnsController extends modules.ViewController { }; } - private _isVisibleColumnsValid(visibleColumns) { - const getCommandColumnsCount = function () { - let result = 0; - - for (let j = 0; j < visibleColumns.length; j++) { - const visibleColumn = visibleColumns[j]; - if (visibleColumn.command) { - result++; - } - } - return result; - }; + private _isVisibleColumnsValid(visibleColumns: Column[]): boolean { + const commandColumnsCount = visibleColumns.filter((column) => column.command).length; - if (visibleColumns < 2) { + if (visibleColumns.length < 2) { return false; } - if (visibleColumns.length - getCommandColumnsCount() <= 1) { + if (visibleColumns.length - commandColumnsCount <= 1) { return false; } return true; } - private _calculatePercentWidths(widths, visibleColumns) { - const that = this; + private _calculatePercentWidths(widths: ColumnWidth[], visibleColumns: Column[]): number { let percentWidths = 0; - visibleColumns.forEach((item, index) => { + visibleColumns.forEach((column, index) => { if (widths[index] !== HIDDEN_COLUMNS_WIDTH) { - percentWidths += that._getItemPercentWidth(item); + percentWidths += this._getColumnPercentWidth(column); } }); return percentWidths; } - private _isPercentWidth(width) { - return isString(width) && width.endsWith('%'); - } - - private _getAverageColumnsWidth(containerWidth, columns, columnsCanFit) { - const that = this; + private _getAverageColumnsWidth( + containerWidth: number, + columns: Column[], + columnsCanFit: boolean, + ): number { let fixedColumnsWidth = 0; let columnsWithoutFixedWidthCount = 0; columns.forEach((column) => { - if (!that.isColumnHidden(column)) { + if (!this.isColumnHidden(column)) { const { width } = column; - if (isDefined(width) && !isNaN(parseFloat(width))) { - fixedColumnsWidth += that._isPercentWidth(width) ? that._calculatePercentWidth({ + if (isDefined(width) && !isNaN(parseFloat(String(width)))) { + fixedColumnsWidth += isPercentWidth(width) ? this._calculatePercentWidth({ visibleIndex: column.visibleIndex, columnsCount: columns.length, columnsCanFit, bestFitWidth: column.bestFitWidth, columnWidth: width, containerWidth, - }) : parseFloat(width); + }) : parseFloat(String(width)); } else { - columnsWithoutFixedWidthCount++; + columnsWithoutFixedWidthCount += 1; } } }); return (containerWidth - fixedColumnsWidth) / columnsWithoutFixedWidthCount; } - private _calculateColumnWidth(column, containerWidth, contentColumns, columnsCanFit) { - const columnId = getColumnId(this, column); + private _calculateColumnWidth( + column: Column, + containerWidth: number, + contentColumns: Column[], + columnsCanFit: boolean, + ): number | undefined { + const columnId = this._columnsController.getColumnId(column); const widthOption = this._columnsController.columnOption(columnId, 'width'); const bestFitWidth = this._columnsController.columnOption(columnId, 'bestFitWidth'); - const columnsCount = contentColumns.length; - let colWidth; if (widthOption && widthOption !== 'auto') { - if (this._isPercentWidth(widthOption)) { - colWidth = this._calculatePercentWidth({ + if (isPercentWidth(widthOption)) { + return this._calculatePercentWidth({ visibleIndex: column.visibleIndex, - columnsCount, + columnsCount: contentColumns.length, columnsCanFit, bestFitWidth, columnWidth: widthOption, containerWidth, }); - } else { - return parseFloat(String(widthOption)); } - } else { - const columnAutoWidth = this.option('columnAutoWidth'); - colWidth = columnAutoWidth || !!column.command ? bestFitWidth : this._getAverageColumnsWidth(containerWidth, contentColumns, columnsCanFit); - } - return colWidth; - } + return parseFloat(String(widthOption)); + } - private _calculatePercentWidth(options) { - const columnFitted = (options.visibleIndex < options.columnsCount - 1) && options.columnsCanFit; - const partialWidth = options.containerWidth * parseFloat(options.columnWidth) / 100; - const resultWidth = options.columnsCanFit && (partialWidth < options.bestFitWidth) ? options.bestFitWidth : partialWidth; + const columnAutoWidth = this.option('columnAutoWidth'); - return columnFitted ? options.containerWidth * parseFloat(options.columnWidth) / 100 : resultWidth; + return columnAutoWidth || !!column.command + ? bestFitWidth + : this._getAverageColumnsWidth(containerWidth, contentColumns, columnsCanFit); } - private _getNotTruncatedColumnWidth(column, containerWidth, contentColumns, columnsCanFit) { - const columnId = getColumnId(this, column); + private _calculatePercentWidth(options: PercentWidthOptions): number { + // @ts-expect-error visibleIndex can be absent; the comparison then returns false + const columnFitted = (options.visibleIndex < options.columnsCount - 1) && options.columnsCanFit; + const partialWidth = (options.containerWidth * parseFloat(options.columnWidth)) / 100; + const resultWidth = options.columnsCanFit + && options.bestFitWidth !== undefined + && (partialWidth < options.bestFitWidth) + ? options.bestFitWidth + : partialWidth; + + return columnFitted + ? (options.containerWidth * parseFloat(options.columnWidth)) / 100 + : resultWidth; + } + + private _getNotTruncatedColumnWidth( + column: Column, + containerWidth: number, + contentColumns: Column[], + columnsCanFit: boolean, + ): number | null | undefined { + const columnId = this._columnsController.getColumnId(column); const widthOption = this._columnsController.columnOption(columnId, 'width'); const bestFitWidth = this._columnsController.columnOption(columnId, 'bestFitWidth'); - if (widthOption && widthOption !== 'auto' && !this._isPercentWidth(widthOption)) { + if (widthOption && widthOption !== 'auto' && !isPercentWidth(widthOption)) { return parseFloat(String(widthOption)); } - const colWidth = this._calculateColumnWidth(column, containerWidth, contentColumns, columnsCanFit); + const colWidth = this._calculateColumnWidth( + column, + containerWidth, + contentColumns, + columnsCanFit, + ); // @ts-expect-error bestFitWidth can be absent; the comparison then returns false return colWidth < bestFitWidth ? null : colWidth; } - private _getItemPercentWidth(item) { + private _getColumnPercentWidth(column: Column): number { let result = 0; - if (item.width && this._isPercentWidth(item.width)) { - result = parseFloat(item.width); + if (column.width && isPercentWidth(column.width)) { + result = parseFloat(column.width); } return result; } - private _getCommandColumnsWidth() { - const that = this; - const columns = that._columnsController.getVisibleColumns(); + private _getCommandColumnsWidth(): number { + const columns = this._columnsController.getVisibleColumns(); let colWidth = 0; each(columns, (index, column) => { if ((isDefined(column.index) && column.index < 0) || column.command) { - colWidth += that._columnsController.columnOption(getColumnId(that, column), 'bestFitWidth') || 0; + colWidth += this._columnsController.columnOption( + this._columnsController.getColumnId(column), + 'bestFitWidth', + ) || 0; } }); return colWidth; } - private _isItemEdited(item) { + private _isItemEdited(item: AdaptiveFormItem): boolean | undefined { if (this.isFormOrPopupEditMode()) { return false; } @@ -399,7 +453,9 @@ export class AdaptiveColumnsController extends modules.ViewController { return true; } } else { - const rowIndex = this._dataController.getRowIndexByKey(this._dataController.getAdaptiveExpandedKey()) + 1; + const rowIndex = this._dataController.getRowIndexByKey( + this._dataController.getAdaptiveExpandedKey(), + ) + 1; const columnIndex = this._columnsController.getVisibleIndex(item.column.index); return this._editingController.isEditCell(rowIndex, columnIndex); @@ -408,8 +464,8 @@ export class AdaptiveColumnsController extends modules.ViewController { return undefined; } - private _getFormItemsByHiddenColumns(hiddenColumns) { - const items: any[] = []; + private _getFormItemsByHiddenColumns(hiddenColumns: Column[]): AdaptiveFormItem[] { + const items: AdaptiveFormItem[] = []; each(hiddenColumns, (_, column) => { items.push({ column, @@ -422,8 +478,8 @@ export class AdaptiveColumnsController extends modules.ViewController { return items; } - private _getAdaptiveColumnVisibleIndex(visibleColumns) { - for (let i = 0; i < visibleColumns.length; i++) { + private _getAdaptiveColumnVisibleIndex(visibleColumns: Column[]): number | undefined { + for (let i = 0; i < visibleColumns.length; i += 1) { const column = visibleColumns[i]; if (column.command === ADAPTIVE_COLUMN_NAME) { return i; @@ -433,7 +489,7 @@ export class AdaptiveColumnsController extends modules.ViewController { return undefined; } - private _hideAdaptiveColumn(resultWidths, visibleColumns) { + private _hideAdaptiveColumn(resultWidths: ColumnWidth[], visibleColumns: Column[]): void { const visibleIndex = this._getAdaptiveColumnVisibleIndex(visibleColumns); if (isDefined(visibleIndex)) { resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH; @@ -444,7 +500,7 @@ export class AdaptiveColumnsController extends modules.ViewController { /** * @extended: keyboard_navigation */ - protected _showHiddenCellsInView({ $cells, isCommandColumn }: any) { + protected _showHiddenCellsInView({ $cells, isCommandColumn }: HiddenCellsInViewOptions): void { let cssClassNameToRemove = this.addWidgetPrefix(HIDDEN_COLUMN_CLASS); if (isCommandColumn) { cssClassNameToRemove = COMMAND_ADAPTIVE_HIDDEN_CLASS; @@ -457,9 +513,9 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - public _showHiddenColumns() { - for (let i = 0; i < COLUMN_VIEWS.length; i++) { - const view = this.getView(COLUMN_VIEWS[i]); + public _showHiddenColumns(): void { + for (const columnViewName of COLUMN_VIEWS) { + const view = this.getView(columnViewName); if (view && view.isVisible() && view.element()) { const viewName = view.name; // @ts-expect-error the view is rendered here @@ -479,7 +535,7 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - public _toggleGroupAdaptiveRowVisibility(isBestFit: boolean) { + public _toggleGroupAdaptiveRowVisibility(isBestFit: boolean): void { const hasHiddenColumns = this.hasHiddenColumns() || this.getHidingColumnsQueue().length > 0; if (!hasHiddenColumns) { @@ -501,47 +557,60 @@ export class AdaptiveColumnsController extends modules.ViewController { }); } - private _isCellValid($cell) { - return $cell && $cell.length && !$cell.hasClass(MASTER_DETAIL_CELL_CLASS) && !$cell.hasClass(VIEW_CLASSES.groupCell); + private _isCellValid($cell: dxElementWrapper | undefined): boolean | number | undefined { + return $cell?.length + && !$cell.hasClass(MASTER_DETAIL_CELL_CLASS) + && !$cell.hasClass(VIEW_CLASSES.groupCell); } - private _hideVisibleColumn({ isCommandColumn, visibleIndex }: any) { - const that = this; + private _hideVisibleColumn({ isCommandColumn, visibleIndex }: HideVisibleColumnOptions): void { COLUMN_VIEWS.forEach((viewName) => { - const view = that.getView(viewName); - view && that._hideVisibleColumnInView({ view, isCommandColumn, visibleIndex }); + const view = this.getView(viewName); + if (view) { + this._hideVisibleColumnInView({ view, isCommandColumn, visibleIndex }); + } }); } - protected _hideVisibleColumnInView({ view, isCommandColumn, visibleIndex }) { + protected _hideVisibleColumnInView({ + view, isCommandColumn, visibleIndex, + }: HideVisibleColumnInViewOptions): void { const viewName = view.name; - let $cellElement; const column = this._columnsController.getVisibleColumns()[visibleIndex]; - const editFormRowIndex = this._editingController && this._editingController.getEditFormRowIndex(); - - if (view && view.isVisible() && column) { - const rowsCount = view.getRowsCount(); - const $rowElements = view._getRowElements(); - for (let rowIndex = 0; rowIndex < rowsCount; rowIndex++) { - const cancelClassAdding = rowIndex === editFormRowIndex && viewName === ROWS_VIEW && this.option('editing.mode') !== 'popup'; - - if (!cancelClassAdding) { - const currentVisibleIndex = viewName === COLUMN_HEADERS_VIEW ? this._columnsController.getVisibleIndex(column.index, rowIndex) : visibleIndex; - if (currentVisibleIndex >= 0) { - const $rowElement = $rowElements.eq(rowIndex); - $cellElement = this._findCellElementInRow($rowElement, currentVisibleIndex); - this._isCellValid($cellElement) && this._hideVisibleCellInView({ - viewName, - isCommandColumn, - $cell: $cellElement, - }); - } - } + const editFormRowIndex = this._editingController + && this._editingController.getEditFormRowIndex(); + + if (!(view && view.isVisible() && column)) { + return; + } + + const rowsCount: number = view.getRowsCount(); + const $rowElements = view._getRowElements(); + for (let rowIndex = 0; rowIndex < rowsCount; rowIndex += 1) { + const cancelClassAdding = rowIndex === editFormRowIndex + && viewName === ROWS_VIEW + && this.option('editing.mode') !== 'popup'; + const currentVisibleIndex = viewName === COLUMN_HEADERS_VIEW && !cancelClassAdding + ? this._columnsController.getVisibleIndex(column.index, rowIndex) + : visibleIndex; + const $cellElement = !cancelClassAdding && currentVisibleIndex >= 0 + ? this._findCellElementInRow($rowElements.eq(rowIndex), currentVisibleIndex) + : undefined; + + if ($cellElement && this._isCellValid($cellElement)) { + this._hideVisibleCellInView({ + viewName, + isCommandColumn, + $cell: $cellElement, + }); } } } - private _findCellElementInRow($rowElement, visibleColumnIndex) { + private _findCellElementInRow( + $rowElement: dxElementWrapper, + visibleColumnIndex: number, + ): dxElementWrapper | undefined { const $rowCells = $rowElement.children(); let visibleIndex = visibleColumnIndex; let cellIsInsideGroup = false; @@ -549,8 +618,7 @@ export class AdaptiveColumnsController extends modules.ViewController { const $groupCell = $rowElement.find(`.${VIEW_CLASSES.groupCell}`); const colSpan = $groupCell.attr('colspan'); if ($groupCell.length && isDefined(colSpan)) { - // eslint-disable-next-line radix - const groupCellLength = parseInt(colSpan); + const groupCellLength = parseInt(colSpan, 10); const endGroupIndex = $groupCell.index() + groupCellLength - 1; if (visibleColumnIndex > endGroupIndex) { visibleIndex = visibleColumnIndex - groupCellLength + 1; @@ -566,19 +634,21 @@ export class AdaptiveColumnsController extends modules.ViewController { /** * @extended: keyboard_navigation */ - protected _hideVisibleCellInView({ $cell, isCommandColumn }: any) { - const cssClassNameToAdd = isCommandColumn ? COMMAND_ADAPTIVE_HIDDEN_CLASS : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS); + protected _hideVisibleCellInView({ $cell, isCommandColumn }: HiddenCellInViewOptions): void { + const cssClassNameToAdd = isCommandColumn + ? COMMAND_ADAPTIVE_HIDDEN_CLASS + : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS); $cell.attr({ tabIndex: -1, 'aria-hidden': true, }).addClass(cssClassNameToAdd); } - private _getEditMode() { + private _getEditMode(): GridsEditMode { return this._editingController.getEditMode(); } - public isFormOrPopupEditMode() { + public isFormOrPopupEditMode(): boolean { const editMode = this._getEditMode(); return editMode === EDIT_MODE_FORM || editMode === EDIT_MODE_POPUP; @@ -591,91 +661,113 @@ export class AdaptiveColumnsController extends modules.ViewController { ): void { this._hiddenColumns = []; - if (this._isVisibleColumnsValid(visibleColumns) && hiddenQueue.length) { - let totalWidth = 0; - const $rootElement = this.component.$element(); - const availableWidth = getWidth($rootElement) - this._rowsView.getScrollbarWidth(); - let rootElementWidth = availableWidth - this._getCommandColumnsWidth(); - const getVisibleContentColumns = function () { - return visibleColumns.filter((item) => !item.command && this._hiddenColumns.filter((i) => i.index === item.index).length === 0); - }.bind(this); - let visibleContentColumns = getVisibleContentColumns(); - const contentColumnsCount = visibleContentColumns.length; - let hasHiddenColumns = false; - let needHideColumn = false; - - do { - needHideColumn = false; - totalWidth = 0; - - const percentWidths = this._calculatePercentWidths(resultWidths, visibleColumns); - - const columnsCanFit = percentWidths < 100 && percentWidths !== 0; - for (let i = 0; i < visibleColumns.length; i += 1) { - const visibleColumn = visibleColumns[i]; - - let columnWidth = this._getNotTruncatedColumnWidth(visibleColumn, rootElementWidth, visibleContentColumns, columnsCanFit); - const columnId = getColumnId(this, visibleColumn); - const widthOption = this._columnsController.columnOption(columnId, 'width'); - const minWidth = this._columnsController.columnOption(columnId, 'minWidth'); - const columnBestFitWidth = this._columnsController.columnOption(columnId, 'bestFitWidth'); - - if (resultWidths[i] === HIDDEN_COLUMNS_WIDTH) { - hasHiddenColumns = true; - continue; - } - if (!columnWidth && !visibleColumn.command && !visibleColumn.fixed) { - needHideColumn = true; - break; - } + if (!(this._isVisibleColumnsValid(visibleColumns) && hiddenQueue.length)) { + this._hideAdaptiveColumn(resultWidths, visibleColumns); + return; + } - if (!widthOption || widthOption === 'auto') { - columnWidth = Math.max(columnBestFitWidth || 0, minWidth || 0); - } + let totalWidth = 0; + const $rootElement = this.component.$element(); + const availableWidth = getWidth($rootElement) - this._rowsView.getScrollbarWidth(); + let rootElementWidth = availableWidth - this._getCommandColumnsWidth(); + const getVisibleContentColumns = (): Column[] => visibleColumns.filter( + (item) => !item.command + && this._hiddenColumns.filter((i) => i.index === item.index).length === 0, + ); + let visibleContentColumns = getVisibleContentColumns(); + const contentColumnsCount = visibleContentColumns.length; + let hasHiddenColumns = false; + let needHideColumn = false; + + do { + needHideColumn = false; + totalWidth = 0; + + const percentWidths = this._calculatePercentWidths(resultWidths, visibleColumns); + + const columnsCanFit = percentWidths < 100 && percentWidths !== 0; + for (let i = 0; i < visibleColumns.length; i += 1) { + const visibleColumn = visibleColumns[i]; + + let columnWidth = this._getNotTruncatedColumnWidth( + visibleColumn, + rootElementWidth, + visibleContentColumns, + columnsCanFit, + ); + const columnId = this._columnsController.getColumnId(visibleColumn); + const widthOption = this._columnsController.columnOption(columnId, 'width'); + const minWidth = this._columnsController.columnOption(columnId, 'minWidth'); + const columnBestFitWidth = this._columnsController.columnOption(columnId, 'bestFitWidth'); + + if (resultWidths[i] === HIDDEN_COLUMNS_WIDTH) { + hasHiddenColumns = true; + // eslint-disable-next-line no-continue -- removing it needs a loop rewrite + continue; + } + if (!columnWidth && !visibleColumn.command && !visibleColumn.fixed) { + needHideColumn = true; + break; + } - if (visibleColumn.command !== ADAPTIVE_COLUMN_NAME || hasHiddenColumns) { - totalWidth += columnWidth; - } + if (!widthOption || widthOption === 'auto') { + columnWidth = Math.max(columnBestFitWidth || 0, minWidth || 0); } - needHideColumn = needHideColumn || totalWidth > availableWidth; + if (visibleColumn.command !== ADAPTIVE_COLUMN_NAME || hasHiddenColumns) { + // @ts-expect-error columnWidth can be empty for a command or fixed column + totalWidth += columnWidth; + } + } - if (needHideColumn) { - const column = hiddenQueue.pop() as Column; - const visibleIndex = this._columnsController.getVisibleIndex(column.index); + needHideColumn = needHideColumn || totalWidth > availableWidth; - rootElementWidth += this._calculateColumnWidth(column, rootElementWidth, visibleContentColumns, columnsCanFit); + if (needHideColumn) { + const column = hiddenQueue.pop() as Column; + const visibleIndex = this._columnsController.getVisibleIndex(column.index); - this._hideVisibleColumn({ visibleIndex }); - resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH; - this._hiddenColumns.push(column); - visibleContentColumns = getVisibleContentColumns(); - } - } - while (needHideColumn && visibleContentColumns.length > 1 && hiddenQueue.length); + // @ts-expect-error _calculateColumnWidth returns undefined when bestFitWidth is absent + rootElementWidth += this._calculateColumnWidth( + column, + rootElementWidth, + visibleContentColumns, + columnsCanFit, + ); - if (contentColumnsCount === visibleContentColumns.length) { - this._hideAdaptiveColumn(resultWidths, visibleColumns); + this._hideVisibleColumn({ visibleIndex }); + resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH; + this._hiddenColumns.push(column); + visibleContentColumns = getVisibleContentColumns(); } - } else { + } + while (needHideColumn && visibleContentColumns.length > 1 && hiddenQueue.length); + + if (contentColumnsCount === visibleContentColumns.length) { this._hideAdaptiveColumn(resultWidths, visibleColumns); } } - public getAdaptiveDetailItems() { - return this._$itemContents; + public getAdaptiveDetailItems(): dxElementWrapper | undefined { + return this._$itemContents ?? undefined; } - public getItemContentByColumnIndex(visibleColumnIndex) { - let $itemContent; + public getItemContentByColumnIndex(visibleColumnIndex: number): dxElementWrapper | undefined { + // @ts-expect-error the adaptive detail form is rendered here + const $itemContents: dxElementWrapper = this._$itemContents; - for (let i = 0; i < this._$itemContents.length; i++) { - $itemContent = this._$itemContents.eq(i); - const item = $itemContent.data('dx-form-item'); - if (item && item.column && this._columnsController.getVisibleIndex(item.column.index) === visibleColumnIndex) { + for (let i = 0; i < $itemContents.length; i += 1) { + const $itemContent = $itemContents.eq(i); + // @ts-expect-error data(key) is typed as returning the wrapper + const item: AdaptiveFormItem | undefined = $itemContent.data('dx-form-item'); + if ( + item?.column + && this._columnsController.getVisibleIndex(item.column.index) === visibleColumnIndex + ) { return $itemContent; } } + + return undefined; } public toggleExpandAdaptiveDetailRow(key?: RowKey, alwaysExpanded = false): void { @@ -684,46 +776,60 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - public createFormByHiddenColumns(container, options) { - const that = this; + public createFormByHiddenColumns( + container: dxElementWrapper, + options: AdaptiveDetailCellOptions, + ): void { const $container = $(container); - const userFormOptions: any = { - items: that._getFormItemsByHiddenColumns(that._hiddenColumns), + const userFormOptions: FormProperties & { formID: string } = { + items: this._getFormItemsByHiddenColumns(this._hiddenColumns), formID: `dx-${new Guid()}`, }; - // @ts-expect-error + // @ts-expect-error isMaterial() without a theme checks the current one const defaultFormOptions = isMaterial() ? { colCount: 2 } : {}; this.executeAction('onAdaptiveDetailRowPreparing', { formOptions: userFormOptions }); - that._$itemContents = null; + this._$itemContents = null; - that._form = that._createComponent($('
').appendTo($container), Form, extend(defaultFormOptions, userFormOptions, { - customizeItem(item) { - const column = item.column || that._columnsController.columnOption(item.name || item.dataField); - if (column) { - item.label = item.label || {}; - item.label.text = item.label.text || column.caption; - item.column = column; - item.template = that._getTemplate(item, options, that.updateForm.bind(that)); - } - userFormOptions.customizeItem && userFormOptions.customizeItem.call(this, item); - }, - onContentReady(e) { - userFormOptions.onContentReady && userFormOptions.onContentReady.call(this, e); - that._$itemContents = $container.find(`.${FIELD_ITEM_CONTENT_CLASS}`); - }, - })); + // eslint-disable-next-line @typescript-eslint/no-this-alias -- Form callbacks use their this + const that = this; + this._form = this._createComponent( + $('
').appendTo($container), + Form, + extend(defaultFormOptions, userFormOptions, { + customizeItem(item) { + const column = item.column + || that._columnsController.columnOption(item.name || item.dataField); + if (column) { + item.label = item.label || {}; + item.label.text = item.label.text || column.caption; + item.column = column; + item.template = that._getTemplate(item, options); + } + if (userFormOptions.customizeItem) { + userFormOptions.customizeItem.call(this, item); + } + }, + onContentReady(e) { + if (userFormOptions.onContentReady) { + userFormOptions.onContentReady.call(this, e); + } + that._$itemContents = $container.find(`.${FIELD_ITEM_CONTENT_CLASS}`); + }, + }), + ); } - public hasAdaptiveDetailRowExpanded() { + public hasAdaptiveDetailRowExpanded(): boolean { return isDefined(this._dataController.getAdaptiveExpandedKey()); } - public updateForm(hiddenColumns) { + public updateForm(hiddenColumns?: Column[]): void { if (this.hasAdaptiveDetailRowExpanded()) { - if (this._form && isDefined((this._form as any)._contentReadyAction)) { - if (hiddenColumns && hiddenColumns.length) { + // @ts-expect-error _contentReadyAction isn't in the public Form type + if (this._form && isDefined(this._form._contentReadyAction)) { + if (hiddenColumns?.length) { this._form.option('items', this._getFormItemsByHiddenColumns(hiddenColumns)); } else { this._form.repaint(); @@ -732,72 +838,69 @@ export class AdaptiveColumnsController extends modules.ViewController { } } - public updateHidingQueue(columns) { - const that = this; + public updateHidingQueue(columns: Column[]): Column[] { const hideableColumns = getHideableColumns(columns); - let columnsHasHidingPriority; - let i; + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned conditionally below + let columnsHasHidingPriority: boolean | undefined; - that._hidingColumnsQueue = []; + this._hidingColumnsQueue = []; - if (that.option('allowColumnResizing') && that.option('columnResizingMode') === 'widget') { - return that._hidingColumnsQueue; + if (this.option('allowColumnResizing') && this.option('columnResizingMode') === 'widget') { + return this._hidingColumnsQueue; } - for (i = 0; i < hideableColumns.length; i++) { - const column = hideableColumns[i]; - + for (const column of hideableColumns) { if (isDefined(column.hidingPriority) && column.hidingPriority >= 0) { columnsHasHidingPriority = true; - that._hidingColumnsQueue[column.hidingPriority] = column; + this._hidingColumnsQueue[column.hidingPriority] = column; } } if (columnsHasHidingPriority) { - that._hidingColumnsQueue.reverse(); - } else if (that.option('columnHidingEnabled')) { - for (i = 0; i < hideableColumns.length; i++) { - const visibleIndex = that._columnsController.getVisibleIndex(hideableColumns[i].index); - that._hidingColumnsQueue[visibleIndex] = hideableColumns[i]; + this._hidingColumnsQueue.reverse(); + } else if (this.option('columnHidingEnabled')) { + for (const column of hideableColumns) { + const visibleIndex = this._columnsController.getVisibleIndex(column.index); + this._hidingColumnsQueue[visibleIndex] = column; } } - that._hidingColumnsQueue = that._hidingColumnsQueue.filter(Object); - return that._hidingColumnsQueue; + this._hidingColumnsQueue = this._hidingColumnsQueue.filter(Object); + return this._hidingColumnsQueue; } - public getHiddenColumns() { + public getHiddenColumns(): Column[] { return this._hiddenColumns; } - public hasHiddenColumns() { + public hasHiddenColumns(): boolean { return this._hiddenColumns.length > 0; } public getHidingColumnsQueue(): Column[] { - return this._hidingColumnsQueue ?? []; + return this._hidingColumnsQueue; } - public isAdaptiveDetailRowExpanded(key) { + public isAdaptiveDetailRowExpanded(key: RowKey): unknown { const dataController = this._dataController; const adaptiveExpandedKey = dataController.getAdaptiveExpandedKey(); return adaptiveExpandedKey && equalByValue(adaptiveExpandedKey, key); } - private expandAdaptiveDetailRow(key) { + private expandAdaptiveDetailRow(key: RowKey): void { if (!this.hasAdaptiveDetailRowExpanded()) { this.toggleExpandAdaptiveDetailRow(key); } } - public collapseAdaptiveDetailRow() { + public collapseAdaptiveDetailRow(): void { if (this.hasAdaptiveDetailRowExpanded()) { this.toggleExpandAdaptiveDetailRow(); } } - public updateCommandAdaptiveAriaLabel(key, label) { + public updateCommandAdaptiveAriaLabel(key: RowKey, label: string): void { const rowIndex = this._dataController.getRowIndexByKey(key); if (rowIndex === -1) { @@ -809,7 +912,7 @@ export class AdaptiveColumnsController extends modules.ViewController { this.setCommandAdaptiveAriaLabel($row, label); } - public setCommandAdaptiveAriaLabel($row, labelName) { + public setCommandAdaptiveAriaLabel($row: dxElementWrapper, labelName: string): void { const $adaptiveCommand = $row.find('.dx-command-adaptive'); if ($adaptiveCommand.length) { 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 32df00491642..37ef84215c3c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/types.ts @@ -1,5 +1,10 @@ +import type { dxElementWrapper } from '@js/core/renderer'; + +import type { Column } from '../columns_controller/types'; import type { DataController } from '../data_controller/data_controller'; import type { RowKey } from '../types'; +import type { ColumnsView } from '../views/columns_view'; +import type { ViewCellOptions, ViewRow } from '../views/types'; export interface AdaptivityDataController extends DataController { getAdaptiveExpandedKey: () => RowKey | undefined; @@ -11,3 +16,44 @@ export interface AdaptiveDetailRowTarget { key: RowKey | undefined; rowIndex: number; } + +export interface AdaptiveDetailCellOptions extends ViewCellOptions { + row: ViewRow & { values: unknown[] }; +} + +export interface AdaptiveFormItem { + column: Column; + name?: string; + dataField?: string; + visibleIndex?: number; +} + +export interface PercentWidthOptions { + visibleIndex?: number; + columnsCount: number; + columnsCanFit: boolean; + bestFitWidth?: number; + columnWidth: string; + containerWidth: number; +} + +export interface HiddenCellsInViewOptions { + viewName: string; + $cells: dxElementWrapper; + isCommandColumn?: boolean; +} + +export interface HiddenCellInViewOptions { + viewName: string; + $cell: dxElementWrapper; + isCommandColumn?: boolean; +} + +export interface HideVisibleColumnOptions { + visibleIndex: number; + isCommandColumn?: boolean; +} + +export interface HideVisibleColumnInViewOptions extends HideVisibleColumnOptions { + view: ColumnsView; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts b/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts index 6bbfcb89a2af..1ef148962c23 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts @@ -7,12 +7,14 @@ import domAdapter from '@js/core/dom_adapter'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import browser from '@js/core/utils/browser'; +import type { Callback } from '@js/core/utils/callbacks'; import { extend } from '@js/core/utils/extend'; import { getBoundingRect } from '@js/core/utils/position'; import { getOuterHeight, getOuterWidth, setOuterHeight, setOuterWidth, } from '@js/core/utils/size'; +import type { DxEvent } from '@js/events'; import EditorFactoryMixin from '@js/ui/shared/ui.editor_factory_mixin'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; import type { @@ -43,28 +45,30 @@ const UPDATE_FOCUS_EVENTS = addNamespace([clickEventName, 'focusin'].join(' '), const DX_HIDDEN = 'dx-hidden'; interface EditorFactoryMixinType { - createEditor(...args: any[]): any; + createEditor($container: dxElementWrapper, options: object): void; _getRevertTooltipsSelector(): string; } -const ViewControllerWithMixin: ModuleType = EditorFactoryMixin(modules.ViewController); +const ViewControllerWithMixin: ModuleType = ( + EditorFactoryMixin(modules.ViewController) +); export class EditorFactory extends ViewControllerWithMixin { - private _isFocusOverlay: any; + private _isFocusOverlay?: boolean; - private _updateFocusTimeoutID: any; + private _updateFocusTimeoutID?: ReturnType; - private _$focusedElement: any; + private _$focusedElement?: dxElementWrapper | null; - private _focusTimeoutID: any; + private _focusTimeoutID?: ReturnType; - public focused: any; + public focused!: Callback<[dxElementWrapper]>; - protected _$focusOverlay: any; + protected _$focusOverlay?: dxElementWrapper; - private _updateFocusHandler: any; + private _updateFocusHandler!: (e: DxEvent) => void; protected _editingController!: EditingController; @@ -80,7 +84,7 @@ export class EditorFactory extends ViewControllerWithMixin { private _subscribedContainerRoot!: Node; - public init() { + public init(): void { this.createAction('onEditorPreparing', { excludeValidators: ['disabled', 'readOnly'], category: 'rendering' }); this.createAction('onEditorPrepared', { excludeValidators: ['disabled', 'readOnly'], category: 'rendering' }); @@ -91,7 +95,7 @@ export class EditorFactory extends ViewControllerWithMixin { this._columnHeadersView = this.getView('columnHeadersView'); this._rowsView = this.getView('rowsView'); - this._updateFocusHandler = this._updateFocusHandler || this.createAction(this._updateFocus.bind(this)); + this._updateFocusHandler ??= this.createAction(this._updateFocus.bind(this)); this._subscribedContainerRoot = this._getContainerRoot(); eventsEngine.on(this._subscribedContainerRoot, UPDATE_FOCUS_EVENTS, this._updateFocusHandler); @@ -99,13 +103,13 @@ export class EditorFactory extends ViewControllerWithMixin { this._attachContainerEventHandlers(); } - public dispose() { + public dispose(): void { clearTimeout(this._focusTimeoutID); clearTimeout(this._updateFocusTimeoutID); eventsEngine.off(this._subscribedContainerRoot, UPDATE_FOCUS_EVENTS, this._updateFocusHandler); } - private _getFocusedElement($dataGridElement) { + private _getFocusedElement($dataGridElement: dxElementWrapper): dxElementWrapper | false { const rowSelector = this.option('focusedRowEnabled') ? 'tr[tabindex]:focus' : `tr[tabindex]:not(.${VIEW_CLASSES.dataRow}):focus`; const focusedElementSelector = [ 'td[tabindex]:focus', @@ -129,33 +133,30 @@ export class EditorFactory extends ViewControllerWithMixin { /** * @extended: adaptivity */ - protected _getFocusCellSelector() { + protected _getFocusCellSelector(): string { return `.${VIEW_CLASSES.row} > td`; } - private _updateFocusCore() { - const $dataGridElement = this.component && this.component.$element(); + private _updateFocusCore(): void { + const $dataGridElement = this.component?.$element(); + let $focus = $dataGridElement && this._getFocusedElement($dataGridElement); - if ($dataGridElement) { - // this selector is specific to IE - let $focus = this._getFocusedElement($dataGridElement); + if ($focus && $focus.length) { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the nested if + let isHideBorder: boolean | undefined; - if ($focus && $focus.length) { - let isHideBorder; + if (!$focus.hasClass(CELL_FOCUS_DISABLED_CLASS) && !$focus.hasClass(VIEW_CLASSES.row)) { + const $focusCell = $focus.closest(`${this._getFocusCellSelector()}, .${CELL_FOCUS_DISABLED_CLASS}`); - if (!$focus.hasClass(CELL_FOCUS_DISABLED_CLASS) && !$focus.hasClass(VIEW_CLASSES.row)) { - const $focusCell = $focus.closest(`${this._getFocusCellSelector()}, .${CELL_FOCUS_DISABLED_CLASS}`); - - if ($focusCell.get(0) !== $focus.get(0)) { - isHideBorder = this._needHideBorder($focusCell); - $focus = $focusCell; - } + if ($focusCell.get(0) !== $focus.get(0)) { + isHideBorder = this._needHideBorder($focusCell); + $focus = $focusCell; } + } - if ($focus.length && !$focus.hasClass(CELL_FOCUS_DISABLED_CLASS)) { - this.focus($focus, isHideBorder); - return; - } + if ($focus.length && !$focus.hasClass(CELL_FOCUS_DISABLED_CLASS)) { + this.focus($focus, isHideBorder); + return; } } @@ -165,32 +166,32 @@ export class EditorFactory extends ViewControllerWithMixin { /** * @extended: adaptivity */ - protected _needHideBorder($element) { + protected _needHideBorder($element: dxElementWrapper): boolean { const rowsViewElement = this._rowsView.element(); - const isRowsView = $element.closest(rowsViewElement).length > 0; + const isRowsView = !!rowsViewElement && $element.closest(rowsViewElement).length > 0; const isEditing = this._editingController.isEditing(); return $element.hasClass(EDITOR_INLINE_BLOCK) || (isRowsView && !isEditing); } - private _updateFocus(e) { - const that = this; - const isFocusOverlay = e && e.event && $(e.event.target).hasClass(that.addWidgetPrefix(FOCUS_OVERLAY_CLASS)); + private _updateFocus(e: { event?: DxEvent }): void { + const isFocusOverlay = e?.event + && $(e.event.target).hasClass(this.addWidgetPrefix(FOCUS_OVERLAY_CLASS)); - that._isFocusOverlay = that._isFocusOverlay || isFocusOverlay; + this._isFocusOverlay = !!this._isFocusOverlay || isFocusOverlay; - clearTimeout(that._updateFocusTimeoutID); + clearTimeout(this._updateFocusTimeoutID); - that._updateFocusTimeoutID = setTimeout(() => { - delete that._updateFocusTimeoutID; - if (!that._isFocusOverlay) { - that._updateFocusCore(); + this._updateFocusTimeoutID = setTimeout(() => { + delete this._updateFocusTimeoutID; + if (!this._isFocusOverlay) { + this._updateFocusCore(); } - that._isFocusOverlay = false; + this._isFocusOverlay = false; }); } - protected updateFocusOverlaySize($element, position): void { + protected updateFocusOverlaySize($element: dxElementWrapper, position: object): void { $element.hide(); const location = positionUtils.calculate($element, extend({ collision: 'fit' }, position)); @@ -206,7 +207,7 @@ export class EditorFactory extends ViewControllerWithMixin { $element.show(); } - protected callbackNames() { + protected callbackNames(): string[] { return ['focused']; } @@ -224,24 +225,26 @@ export class EditorFactory extends ViewControllerWithMixin { } protected updateFocusOverlay($element: dxElementWrapper, isHideBorder = false): void { + // @ts-expect-error renderFocusOverlay creates the focus overlay first + const $focusOverlay: dxElementWrapper = this._$focusOverlay; + if (isHideBorder || isDataRow($element)) { - this._$focusOverlay.addClass(DX_HIDDEN); + $focusOverlay.addClass(DX_HIDDEN); } else if ($element.length) { - // align "right bottom" for Mozilla const align = browser.mozilla ? 'right bottom' : 'left top'; const isFocusedCellInvalid = $element.hasClass(this.addWidgetPrefix(CELL_INVALID_CLASS)); const isFocusedCellModified = $element.hasClass(CELL_MODIFIED_CLASS) && !isFocusedCellInvalid; const $content = this.getFocusOverlayContainer($element); const focusOverlaySize = this.getFocusOverlaySize($element); - this._$focusOverlay + $focusOverlay .removeClass(DX_HIDDEN) .toggleClass(FOCUSED_CELL_INVALID_CLASS, isFocusedCellInvalid) .toggleClass(FOCUSED_CELL_MODIFIED_CLASS, isFocusedCellModified) .appendTo($content); - setOuterHeight(this._$focusOverlay, focusOverlaySize.height); - setOuterWidth(this._$focusOverlay, focusOverlaySize.width); + setOuterHeight($focusOverlay, focusOverlaySize.height); + setOuterWidth($focusOverlay, focusOverlaySize.width); const focusOverlayPosition = { precise: true, @@ -251,24 +254,22 @@ export class EditorFactory extends ViewControllerWithMixin { boundary: $content.length && $content, }; - this.updateFocusOverlaySize(this._$focusOverlay, focusOverlayPosition); - positionUtils.setup(this._$focusOverlay, focusOverlayPosition); + this.updateFocusOverlaySize($focusOverlay, focusOverlayPosition); + positionUtils.setup($focusOverlay, focusOverlayPosition); - this._$focusOverlay.css('visibility', 'visible'); // for ios + $focusOverlay.css('visibility', 'visible'); // for ios } } /** * @extended: focus */ - protected renderFocusOverlay($element, isHideBorder) { + protected renderFocusOverlay($element: dxElementWrapper, isHideBorder?: boolean): void { if (!gridCoreUtils.isElementInCurrentGrid(this, $element)) { return; } - if (!this._$focusOverlay) { - this._$focusOverlay = $('
').addClass(this.addWidgetPrefix(FOCUS_OVERLAY_CLASS)); - } + this._$focusOverlay ??= $('
').addClass(this.addWidgetPrefix(FOCUS_OVERLAY_CLASS)); this.updateFocusOverlay($element, isHideBorder); } @@ -276,37 +277,42 @@ export class EditorFactory extends ViewControllerWithMixin { /** * @extended: validating */ - public focus($element?, isHideBorder?) { - const that = this; - + public focus( + $element?: dxElementWrapper | null, + isHideBorder?: boolean, + ): dxElementWrapper | null | undefined { if ($element === undefined) { - return that._$focusedElement; + return this._$focusedElement; } if ($element) { // To prevent overlay flicking - if (!$element.is(that._$focusedElement)) { - // TODO: this code should be before timeout else focus is not will move to adaptive form by shift + tab key - that._$focusedElement && that._$focusedElement.removeClass(FOCUSED_ELEMENT_CLASS); + // @ts-expect-error is() is typed without null and undefined + if (!$element.is(this._$focusedElement)) { + // TODO: this code should be before timeout + // else focus is not will move to adaptive form by shift + tab key + this._$focusedElement?.removeClass(FOCUSED_ELEMENT_CLASS); } - that._$focusedElement = $element; + this._$focusedElement = $element; - clearTimeout(that._focusTimeoutID); - that._focusTimeoutID = setTimeout(() => { - delete that._focusTimeoutID; + clearTimeout(this._focusTimeoutID); + this._focusTimeoutID = setTimeout(() => { + delete this._focusTimeoutID; - that.renderFocusOverlay($element, isHideBorder); + this.renderFocusOverlay($element, isHideBorder); $element.addClass(FOCUSED_ELEMENT_CLASS); - that.focused.fire($element); + this.focused.fire($element); }); } + + return undefined; } - public refocus() { + public refocus(): void { const $focus = this.focus(); this.focus($focus); } - public resize() { + public resize(): void { const $focusedElement = this._$focusedElement; if ($focusedElement) { @@ -317,44 +323,40 @@ export class EditorFactory extends ViewControllerWithMixin { /** * @extended: validating */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public loseFocus(skipValidator?) { - this._$focusedElement && this._$focusedElement.removeClass(FOCUSED_ELEMENT_CLASS); + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- overridable signature + public loseFocus(skipValidator?: boolean): void { + this._$focusedElement?.removeClass(FOCUSED_ELEMENT_CLASS); this._$focusedElement = null; - this._$focusOverlay && this._$focusOverlay.addClass(DX_HIDDEN); + this._$focusOverlay?.addClass(DX_HIDDEN); } private _getContainerRoot(): Node { const $container = this.component?.$element(); - // @ts-expect-error + // @ts-expect-error the grid element exists once the component is initialized const root = domAdapter.getRootNode($container?.get(0)); - // @ts-expect-error - // NOTE: this condition is for the 'Row - Redundant validation messages should not be rendered in a detail grid when focused row is enabled (T950174)' - // testcafe test. The detail grid is created inside document_fragment_node but it is not shadow dom - // eslint-disable-next-line no-undef - if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !root.host) { + // a grid created inside a plain document fragment (not a shadow root) listens on the document + if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !('host' in root)) { return domAdapter.getDocument(); } return root; } - private _attachContainerEventHandlers() { - const that = this; - const $container = that.component && that.component.$element(); + private _attachContainerEventHandlers(): void { + const $container = this.component?.$element(); if ($container) { // T179518 eventsEngine.on($container, addNamespace('keydown', MODULE_NAMESPACE), (e) => { if (normalizeKeyName(e) === 'tab') { - that._updateFocusHandler(e); + this._updateFocusHandler(e); } }); } } - public getFocusOverlay() { + public getFocusOverlay(): dxElementWrapper | undefined { return this._$focusOverlay; } 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 6f14933d8f3a..037f7924a070 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 @@ -25,7 +25,7 @@ import * as accessibility from '@js/ui/shared/accessibility'; import { isElementInDom } from '@ts/core/utils/m_dom'; import { focused } from '@ts/core/utils/selectors'; import type { KeyboardKeyDownEvent } from '@ts/events/core/keyboard_processor'; -import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; +import type { AdaptiveColumnsViewController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; import { CLASSES as COLUMNS_CONTROLLER_CLASSES } from '@ts/grids/grid_core/columns_controller/const'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; @@ -3245,7 +3245,7 @@ const editing = (Base: ModuleType) => class EditingController } }; -const adaptiveColumns = (Base: ModuleType) => class AdaptiveColumnsKeyboardExtender extends Base { +const adaptiveColumns = (Base: ModuleType) => class AdaptiveColumnsKeyboardExtender extends Base { protected _showHiddenCellsInView({ viewName, $cells, isCommandColumn }) { super._showHiddenCellsInView.apply(this, arguments as any); diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts index da21f21b4a4a..63b8ba607e6d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts @@ -798,12 +798,14 @@ const editorFactory = (Base: ModuleType) => class EditorFactorySt if (!isHideBorder) { const isFixedCell = GridCoreStickyColumnsDom .isFixedCell($element, this.addWidgetPrefix.bind(this)); + // @ts-expect-error renderFocusOverlay creates the focus overlay first this._$focusOverlay.toggleClass(CLASSES.focusedFixedElement, isFixedCell); const isGroupElement = isGroupRowElement($element); const isGroupFooterRowElement = isGroupFooterRow($element); const isAdaptiveElement = isAdaptiveItem($element); if (isFixedCell || isGroupElement || isGroupFooterRowElement || isAdaptiveElement) { + // @ts-expect-error renderFocusOverlay creates the focus overlay first this._$focusOverlay.toggleClass(CLASSES.focusedFixedElement, true); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/types.ts b/packages/devextreme/js/__internal/grids/grid_core/types.ts index 740b4b5a18bb..6d7addf7bcae 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/types.ts @@ -269,7 +269,7 @@ export type OptionChangedFor = { export type OptionChanged = OptionChangedFor; export interface Controllers { - adaptiveColumns: import('./adaptivity/m_adaptivity').AdaptiveColumnsController; + adaptiveColumns: import('./adaptivity/m_adaptivity').AdaptiveColumnsViewController; applyFilter: import('./filter_row/m_filter_row').ApplyFilterViewController; columnChooser: import('./column_chooser/m_column_chooser').ColumnChooserController; columns: import('./columns_controller/columns_controller').ColumnsController; diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/columns_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/columns_view.ts index 81c77dfafd36..3ff7761b0982 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/columns_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/columns_view.ts @@ -29,7 +29,7 @@ import { getWindow, hasWindow } from '@js/core/utils/window'; import type { DxEvent } from '@js/events'; import type { ActionEvent } from '@ts/core/action'; import supportUtils from '@ts/core/utils/m_support'; -import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; +import type { AdaptiveColumnsViewController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; import type { ColumnChooserController, ColumnChooserView } from '@ts/grids/grid_core/column_chooser/m_column_chooser'; import { CLASSES as COLUMN_FIXING_CLASSES } from '@ts/grids/grid_core/column_fixing/const'; import { CLASSES as COLUMN_HEADERS_CLASSES } from '@ts/grids/grid_core/column_headers/const'; @@ -163,7 +163,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { protected _dataController!: DataController; - protected adaptiveColumnsController!: AdaptiveColumnsController; + protected adaptiveColumnsController!: AdaptiveColumnsViewController; protected _columnChooserController!: ColumnChooserController; @@ -382,7 +382,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const rowOptions: RowPreparedOptions | undefined = $row.data('options'); const columnIndex = $cell.index(); - const cellOptions = rowOptions && rowOptions.cells && rowOptions.cells[columnIndex]; + const cellOptions = rowOptions?.cells?.[columnIndex]; const column: HintColumn | undefined = cellOptions ? cellOptions.column : visibleColumns[columnIndex]; @@ -424,7 +424,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const $row = $cell.parent(); // @ts-expect-error data(key) is typed as returning the wrapper const rowOptions: RowPreparedOptions | undefined = $row.data('options'); - const options = rowOptions && rowOptions.cells && rowOptions.cells[$cell.index()]; + const options = rowOptions?.cells?.[$cell.index()]; if (!$cell.closest('table').is(event.delegateTarget)) { return undefined; @@ -1608,7 +1608,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return undefined; } - private getRowsCount(): number { + public getRowsCount(): number { const tableElement = this.getTableElement(); if (tableElement?.length === 1) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/grid_view.ts index d42c4f260d2c..a309a7dcac86 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/grid_view.ts @@ -18,7 +18,7 @@ import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_ import { A11yStatusContainerComponent } from '@ts/grids/grid_core/views/a11y_status_container_component'; import type { FooterView } from '../../data_grid/summary/m_summary'; -import type { AdaptiveColumnsController } from '../adaptivity/m_adaptivity'; +import type { AdaptiveColumnsViewController } from '../adaptivity/m_adaptivity'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; import { GROUP_COMMAND_COLUMN_NAME } from '../columns_controller/const'; @@ -35,6 +35,7 @@ import type { ColumnsView } from './columns_view'; import { CLASSES } from './const'; import type { RowsView } from './m_rows_view'; import type { ColumnWidth, ScrollPosition, ViewDataChange } from './types'; +import { isPercentWidth } from './utils'; const BORDERS_CLASS = 'borders'; const IMPORTANT_MARGIN_CLASS = 'important-margin'; @@ -67,8 +68,6 @@ const E2E_ATTRIBUTES = { a11yStatusContainer: 'e2e-a11y-general-status-container', }; -const isPercentWidth = (width: ColumnWidth): boolean => isString(width) && width.endsWith('%'); - const isPixelWidth = (width: ColumnWidth): boolean => isString(width) && width.endsWith('px'); const withWidthAt = ( @@ -115,7 +114,7 @@ export class ResizingController extends modules.ViewController { private _lastHeight?: number; - protected adaptiveColumnsController!: AdaptiveColumnsController; + protected adaptiveColumnsController!: AdaptiveColumnsViewController; private _editorFactoryController!: EditorFactory; diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts index a3bf7e856a17..7d701698f87b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts @@ -1,5 +1,6 @@ import type { dxElementWrapper } from '@js/core/renderer'; import { getOuterHeight } from '@js/core/utils/size'; +import { isString } from '@js/core/utils/type'; import { columnHasValue } from '@ts/grids/grid_core/columns_controller/columns_controller_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; @@ -7,6 +8,8 @@ import gridCoreUtils from '../m_utils'; const INVISIBLE_CLASS = 'dx-state-invisible'; +export const isPercentWidth = (width: unknown): width is `${string}%` => isString(width) && width.endsWith('%'); + export const getCellText = ( column: Column, displayValue: unknown,