Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,35 @@ test('DataGrid – Resize indicator is moved when resizing a grouped column if s
});
});

// T1329677
test('DataGrid - column width changed via columnOption should be applied immediately, without a repaint (T1329677)', async (t) => {
const dataGrid = new DataGrid('#container');

await t.expect(dataGrid.isReady()).ok();

await dataGrid.apiColumnOption('Task_Assigned_Employee_ID', 'width', 700);

const assignedColumnWidth = await dataGrid.getHeaders().getHeaderRow(0)
.getHeaderCell(1).element.clientWidth;

await t
.expect(assignedColumnWidth)
.within(
700 - 1,
700 + 1,
'columnOption width should be applied immediately, without an explicit repaint',
);
}).before(async () => {
await createWidget('dxDataGrid', {
dataSource: [{ Task_Subject: 'Test' }],
columnAutoWidth: true,
columns: [
{ dataField: 'Task_Subject' },
{ dataField: 'Task_Assigned_Employee_ID', caption: 'Assigned' },
],
});
});

const tryResizeHeaderInBandArea = (
dataGrid: DataGrid,
columnIndex: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,23 @@ export const fireOptionChanged = function (that: ColumnsController, options) {
}
};

const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => {
const columnChanges = that._columnChanges;

if (!columnChanges?.optionNames?.visibleWidth) {
return false;
}

return columnChanges.columnIndex === columnIndex
|| !!columnChanges.columnIndices?.includes(columnIndex);
};

const invalidateStaleVisibleWidth = (that: ColumnsController, column): void => {
if (isDefined(column.visibleWidth) && !isVisibleWidthChangePendingForColumn(that, column.index)) {
column.visibleWidth = null;
}
};

export const columnOptionCore = function (that: ColumnsController, column, optionName, value?, notFireEvent?) {
const optionGetter = compileGetter(optionName);
const columnIndex = column.index;
Expand All @@ -735,6 +752,10 @@ export const columnOptionCore = function (that: ColumnsController, column, optio
changeType = 'columns';
}

if (optionName === 'width') {
invalidateStaleVisibleWidth(that, column);
}

const optionSetter = compileSetter(optionName);
// @ts-expect-error
optionSetter(column, value, { functionsAsIs: true });
Expand Down
20 changes: 14 additions & 6 deletions packages/devextreme/js/__internal/grids/grid_core/m_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export function isDateType(dataType: string | undefined): boolean {
return dataType === 'date' || dataType === 'datetime';
}

export interface SelectionRange {
selectionStart: number;
selectionEnd: number;
}

const getIntervalSelector = function () {
const data = arguments[1];
const value = this.calculateCellValue(data);
Expand Down Expand Up @@ -562,22 +567,25 @@ export default {

isDateType,

getSelectionRange(focusedElement) {
getSelectionRange(focusedElement): SelectionRange {
try {
if (focusedElement) {
return {
selectionStart: focusedElement.selectionStart,
selectionEnd: focusedElement.selectionEnd,
selectionStart: isNumeric(focusedElement.selectionStart) ? focusedElement.selectionStart : -1,
selectionEnd: isNumeric(focusedElement.selectionEnd) ? focusedElement.selectionEnd : -1,
};
Comment thread
nightskylark marked this conversation as resolved.
}
} catch (e) { /* empty */ }

return {};
return {
selectionStart: -1,
selectionEnd: -1,
};
},

setSelectionRange(focusedElement, selectionRange) {
setSelectionRange(focusedElement, selectionRange: SelectionRange): void {
try {
if (focusedElement && focusedElement.setSelectionRange) {
if (focusedElement && focusedElement.setSelectionRange && selectionRange.selectionStart >= 0 && selectionRange.selectionEnd >= 0) {
focusedElement.setSelectionRange(selectionRange.selectionStart, selectionRange.selectionEnd);
}
} catch (e) { /* empty */ }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type { ColumnHeadersView } from '../column_headers/m_column_headers';
import type { ColumnsController } from '../columns_controller/m_columns_controller';
import type { DataController } from '../data_controller/m_data_controller';
import modules from '../m_modules';
import gridCoreUtils from '../m_utils';
import gridCoreUtils, { type SelectionRange } from '../m_utils';
import type { RowsView } from './m_rows_view';

const BORDERS_CLASS = 'borders';
Expand Down Expand Up @@ -78,11 +78,17 @@ const calculateFreeWidthWithCurrentMinWidth = function (that, columnIndex, curre
return calculateFreeWidth(that, widths.map((width, index) => (index === columnIndex ? currentMinWidth : width)));
};

const restoreFocus = function (focusedElement, selectionRange) {
const restoreFocus = (focusedElement: Element, selectionRange: SelectionRange): void => {
accessibility.hiddenFocus(focusedElement, true);
gridCoreUtils.setSelectionRange(focusedElement, selectionRange);
};

interface MaxWidthController {
isModified: boolean;
set: (value: number) => void;
clear: () => void;
}

export class ResizingController extends modules.ViewController {
private _refreshSizesHandler: any;

Expand All @@ -100,8 +106,6 @@ export class ResizingController extends modules.ViewController {

private _prevContentMinHeight: any;

private _maxWidth: any;

private _hasWidth: any;

private _hasHeight: any;
Expand All @@ -122,6 +126,26 @@ export class ResizingController extends modules.ViewController {

public resizeCompleted!: Callback;

private readonly _maxWidth: MaxWidthController = {
isModified: false,
set: (value): void => {
const $element = this.component.$element();

this._maxWidth.isModified = true;
$element.css('maxWidth', value);
},
clear: (): void => {
const $element = this.component.$element();

if (!this._maxWidth.isModified || !$element || !$element.get(0)) {
return;
}

this._maxWidth.isModified = false;
$element[0].style.maxWidth = '';
},
};

protected callbackNames() {
return ['resizeCompleted'];
}
Expand Down Expand Up @@ -338,85 +362,55 @@ export class ResizingController extends modules.ViewController {
}
}

private _synchronizeColumns() {
const columnsController = this._columnsController;
const visibleColumns = columnsController.getVisibleColumns();
const columnAutoWidth = this.option('columnAutoWidth');
const hasUndefinedColumnWidth = visibleColumns.some((column) => !isDefined(column.width));
let needBestFit = this._needBestFit();
let hasMinWidth = false;
let resetBestFitMode;
let isColumnWidthsCorrected = false;
let resultWidths: any[] = [];
let focusedElement;
let selectionRange;
private _enableTemporaryBestFitMode(): () => void {
const $element = this.component.$element();
const focusedElement = domAdapter.getActiveElement($element.get(0) as HTMLElement | null);
const selectionRange = gridCoreUtils.getSelectionRange(focusedElement);

const normalizeWidthsByExpandColumns = function () {
let expandColumnWidth;
this._toggleBestFitMode(true);

each(visibleColumns, (index, column) => {
if (column.type === 'groupExpand') {
expandColumnWidth = resultWidths[index];
}
});
return (): void => {
this._toggleBestFitMode(false);

each(visibleColumns, (index, column) => {
if (column.type === 'groupExpand' && expandColumnWidth) {
resultWidths[index] = expandColumnWidth;
}
});
};
if (focusedElement && focusedElement !== domAdapter.getActiveElement()) {
const isFocusOutsideWindow = getBoundingRect(focusedElement).bottom < 0;

!needBestFit && each(visibleColumns, (index, column) => {
if (column.width === 'auto') {
needBestFit = true;
return false;
if (!isFocusOutsideWindow) {
restoreFocus(focusedElement, selectionRange);
}
}
return undefined;
});
};
}

each(visibleColumns, (index, column) => {
if (column.minWidth) {
hasMinWidth = true;
return false;
}
return undefined;
});
private _synchronizeColumns(): void {
const columnsController = this._columnsController;
const visibleColumns = columnsController.getVisibleColumns();
const columnAutoWidth = this.option('columnAutoWidth') as boolean;
const hasUndefinedColumnWidth = visibleColumns.some((column) => !isDefined(column.width));
const needBestFit = this._needBestFit() || visibleColumns.some((column) => column.width === 'auto');
const hasMinWidth = visibleColumns.some((column) => !!column.minWidth);

// Prepare for measurement
this._toggleContentMinHeight(this._hasHeight); // T1047239, T1270354

this._setVisibleWidths(visibleColumns, []);

const $element = this.component.$element();

if (needBestFit) {
// @ts-expect-error
focusedElement = domAdapter.getActiveElement($element.get(0));
selectionRange = gridCoreUtils.getSelectionRange(focusedElement);
this._toggleBestFitMode(true);
resetBestFitMode = true;
}

if ($element && $element.get(0) && this._maxWidth) {
delete this._maxWidth;
$element[0].style.maxWidth = '';
}
const restoreAfterBestFitMode = needBestFit && this._enableTemporaryBestFitMode();
this._maxWidth.clear();

// eslint-disable-next-line @typescript-eslint/no-floating-promises
deferUpdate(() => {
if (needBestFit) {
let resultWidths: (number | string | undefined)[] = [];

if (needBestFit || hasMinWidth) {
resultWidths = this._getBestFitWidths();
}

each(visibleColumns, (index, column) => {
each(visibleColumns, (index, column) => {
if (needBestFit) {
const columnId = columnsController.getColumnId(column);
columnsController.columnOption(columnId, 'bestFitWidth', resultWidths[index], true);
});
} else if (hasMinWidth) {
resultWidths = this._getBestFitWidths();
}
}

each(visibleColumns, function (index) {
const { width } = this;
const { width } = column;
if (width !== 'auto') {
if (isDefined(width)) {
resultWidths[index] = isNumeric(width) || isPixelWidth(width) ? parseFloat(width) : width;
Expand All @@ -426,21 +420,14 @@ export class ResizingController extends modules.ViewController {
}
});

if (resetBestFitMode) {
this._toggleBestFitMode(false);
resetBestFitMode = false;
if (focusedElement && focusedElement !== domAdapter.getActiveElement()) {
const isFocusOutsideWindow = getBoundingRect(focusedElement).bottom < 0;
if (!isFocusOutsideWindow) {
restoreFocus(focusedElement, selectionRange);
}
}
if (restoreAfterBestFitMode) {
restoreAfterBestFitMode();
}

isColumnWidthsCorrected = this._correctColumnWidths(resultWidths, visibleColumns);
const isColumnWidthsCorrected = this._correctColumnWidths(resultWidths, visibleColumns);

if (columnAutoWidth) {
normalizeWidthsByExpandColumns();
this._normalizeWidthsByExpandColumns(resultWidths, visibleColumns);
if (this._needStretch()) {
this._processStretch(resultWidths, visibleColumns);
}
Expand Down Expand Up @@ -478,6 +465,21 @@ export class ResizingController extends modules.ViewController {
return freeWidth / columnCountWithoutWidth;
}

private readonly _normalizeWidthsByExpandColumns = (resultWidths, visibleColumns): void => {
const expandColumnIndex = visibleColumns.findIndex((column) => column.type === 'groupExpand');
const expandColumnWidth = resultWidths[expandColumnIndex];

if (!isDefined(expandColumnWidth)) {
return;
}

each(visibleColumns, (index, column) => {
if (column.type === 'groupExpand') {
resultWidths[index] = expandColumnWidth;
}
});
};

/**
* @extended: adaptivity
*/
Expand All @@ -487,7 +489,6 @@ export class ResizingController extends modules.ViewController {
let hasPercentWidth = false;
let hasAutoWidth = false;
let isColumnWidthsCorrected = false;
const $element = that.component.$element();
const hasWidth = that._hasWidth;

for (i = 0; i < visibleColumns.length; i++) {
Expand Down Expand Up @@ -540,9 +541,7 @@ export class ResizingController extends modules.ViewController {
if (hasWidth === false && !hasPercentWidth) {
const borderWidth = gridCoreUtils.getComponentBorderWidth(this, $rowsViewElement);

that._maxWidth = totalWidth + scrollbarWidth + borderWidth;

$element.css('maxWidth', that._maxWidth);
that._maxWidth.set(totalWidth + scrollbarWidth + borderWidth);
}
}
}
Expand Down
Loading