Skip to content

Commit dc2c865

Browse files
Merge branch '25_1' into 25_1_ignore_ng17_alerts
2 parents a81f189 + 4f2a0f1 commit dc2c865

6 files changed

Lines changed: 163 additions & 3 deletions

File tree

packages/devextreme/js/__internal/grids/new/card_view/header_panel/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class HeaderPanelController {
4747
const columnsCount = this.columnsController.columns.peek().length;
4848

4949
this.columnsController.columnOption(column, 'visible', true);
50-
this.columnsController.columnOption(column, 'visibleIndex', columnsCount);
50+
this.columnsController.columnOption(column, 'visibleIndex', columnsCount - 1);
5151

5252
return;
5353
}

packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.test.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { describe, expect, it } from '@jest/globals';
1+
import {
2+
describe, expect, it, jest,
3+
} from '@jest/globals';
24

35
import { DataController } from '../data_controller';
46
import { getContext } from '../di.test_utils';
@@ -443,4 +445,86 @@ describe('ColumnsController', () => {
443445
expect(resultThird.sortIndex).toBeUndefined();
444446
});
445447
});
448+
449+
describe('onOptionChanged', () => {
450+
it('should be called when a column option changes', () => {
451+
const onOptionChanged = jest.fn();
452+
const { columnsController } = setup({
453+
columns: ['a', 'b'],
454+
onOptionChanged,
455+
});
456+
457+
const [col] = columnsController.columns.peek();
458+
columnsController.columnOption(col, 'visible', false);
459+
460+
expect(onOptionChanged).toHaveBeenCalledTimes(1);
461+
expect(onOptionChanged).toHaveBeenCalledWith(
462+
expect.objectContaining({
463+
fullName: 'columns[0].visible',
464+
name: 'columns',
465+
previousValue: true,
466+
value: false,
467+
}),
468+
);
469+
});
470+
471+
it('should not be called when value is unchanged', () => {
472+
const onOptionChanged = jest.fn();
473+
const { columnsController } = setup({
474+
columns: ['a', 'b'],
475+
onOptionChanged,
476+
});
477+
478+
const [col] = columnsController.columns.peek();
479+
columnsController.columnOption(col, 'visible', true);
480+
481+
expect(onOptionChanged).not.toHaveBeenCalled();
482+
});
483+
484+
it('should be called for each changed column in updateColumns', () => {
485+
const onOptionChanged = jest.fn();
486+
const { columnsController } = setup({
487+
columns: ['a', 'b', 'c'],
488+
onOptionChanged,
489+
});
490+
491+
let sortIndex = -1;
492+
columnsController.updateColumns((columns) => columns.map((col, idx) => {
493+
if (idx === 1) {
494+
return col;
495+
}
496+
497+
sortIndex += 1;
498+
499+
return {
500+
...col,
501+
sortOrder: 'asc',
502+
sortIndex,
503+
};
504+
}));
505+
506+
const optionChangeCalls = onOptionChanged.mock.calls;
507+
expect(optionChangeCalls).toHaveLength(4);
508+
expect(optionChangeCalls[0][0]).toMatchObject({
509+
fullName: 'columns[0].sortOrder',
510+
name: 'columns',
511+
value: 'asc',
512+
});
513+
expect(optionChangeCalls[1][0]).toMatchObject({
514+
fullName: 'columns[0].sortIndex',
515+
name: 'columns',
516+
value: 0,
517+
});
518+
expect(optionChangeCalls[2][0]).toMatchObject({
519+
fullName: 'columns[2].sortOrder',
520+
name: 'columns',
521+
value: 'asc',
522+
});
523+
expect(optionChangeCalls[3][0]).toMatchObject({
524+
fullName: 'columns[2].sortIndex',
525+
name: 'columns',
526+
value: 1,
527+
});
528+
});
529+
});
446530
});

packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { equalByValue } from '@js/core/utils/common';
2+
import { getPathParts } from '@js/core/utils/data';
13
import type { ReadonlySignal, Signal } from '@preact/signals-core';
24
import { computed, effect, signal } from '@preact/signals-core';
35
import type { DataObject } from '@ts/grids/new/grid_core/data_controller/types';
@@ -6,11 +8,14 @@ import { isColumnFilterable, mergeColumnHeaderFilterOptions } from '@ts/grids/ne
68
import type { OptionWithChanges } from '@ts/grids/new/grid_core/options_controller/types';
79

810
import { OptionsController } from '../options_controller/options_controller';
11+
import { getTreeNodeByPath } from '../utils/tree/index';
912
import { updateColumnSettings } from './columns_settings/index';
13+
import { IGNORE_COLUMN_OPTION_NAMES } from './const';
1014
import type { ColumnProperties, ColumnSettings, PreNormalizedColumn } from './options';
1115
import type { Column, ColumnsConfigurationFromData, VisibleColumn } from './types';
1216
import {
1317
columnOptionUpdate,
18+
getColumnByIndexOrName,
1419
getColumnIndexByName,
1520
getColumnOptionsFromDataItem,
1621
normalizeColumns,
@@ -121,27 +126,60 @@ export class ColumnsController {
121126
}
122127

123128
public columnOption<TProp extends keyof ColumnSettings>(
124-
{ name }: Column,
129+
column: Column,
125130
// TODO: Fix type -> option may be path with dots in runtime
126131
// E.g: 'columnOption('A', 'headerFilter.search.enabled', true)
127132
option: TProp,
128133
value: ColumnSettings[TProp],
129134
): void {
135+
const { name } = column;
130136
const settings = this.columnsSettings.peek();
131137
const columnIdx = getColumnIndexByName(settings, name);
138+
const prevValue = getTreeNodeByPath(column, getPathParts(option));
132139

133140
this.columnsSettings.value = columnOptionUpdate(
134141
settings,
135142
columnIdx,
136143
option,
137144
value,
138145
);
146+
147+
this.fireOptionChanged(columnIdx, option, value, prevValue);
139148
}
140149

141150
public updateColumns(func: (columns: PreNormalizedColumn[]) => PreNormalizedColumn[]): void {
151+
const prevColumns = this.columns.peek();
152+
142153
let newColumnSettings = func(this.columnsSettings.peek());
143154
newColumnSettings = normalizeColumnsVisibleIndexes(newColumnSettings);
144155
this.columnsSettings.value = newColumnSettings;
156+
157+
const newColumns = this.columns.peek();
158+
newColumns.forEach((newColumn, columnIdx) => {
159+
const prevColumn = getColumnByIndexOrName(prevColumns, newColumn.name);
160+
if (prevColumn) {
161+
const options = new Set([...Object.keys(prevColumn), ...Object.keys(newColumn)]);
162+
for (const option of options) {
163+
if (!IGNORE_COLUMN_OPTION_NAMES[option]) {
164+
const prevValue = prevColumn[option];
165+
const newValue = newColumn[option];
166+
this.fireOptionChanged(columnIdx, option, newValue, prevValue);
167+
}
168+
}
169+
}
170+
});
171+
}
172+
173+
private fireOptionChanged(
174+
columnIndex: number,
175+
optionName: string,
176+
newValue: unknown,
177+
prevValue: unknown,
178+
): void {
179+
if (!equalByValue(prevValue, newValue, { maxDepth: 5 })) {
180+
const fullOptionPath = `columns[${columnIndex}].${optionName}`;
181+
this.options.notifyColumnOptionChanged(fullOptionPath, newValue, prevValue);
182+
}
145183
}
146184

147185
public setColumnOptionsFromDataItem(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const IGNORE_COLUMN_OPTION_NAMES = {
2+
selector: true,
3+
};

packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,21 @@ describe('oneWayWithChanges', () => {
828828
});
829829
});
830830
});
831+
832+
describe('notifyColumnOptionChanged', () => {
833+
it('should not update the internal state', () => {
834+
const publicOptions = { columns: [{ visible: true }] };
835+
const { optionsController } = setup<{ columns?: { visible?: boolean }[] }>(publicOptions, {});
836+
837+
const columnVisible = optionsController.oneWay('columns[0].visible');
838+
const columnVisibleWithChanges = optionsController.oneWayWithChanges('columns[0].visible');
839+
840+
optionsController.notifyColumnOptionChanged('columns[0].visible', false, true);
841+
842+
expect(columnVisible.peek()).toBe(true);
843+
expect(columnVisibleWithChanges.peek()).toStrictEqual({
844+
changes: null,
845+
value: true,
846+
});
847+
});
848+
});

packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export class OptionsController<
4747

4848
private isControlledMode = false;
4949

50+
private _skipProcessingColumnsChange: string | false = false;
51+
5052
private readonly internalOptions: Signal<InternalOptionsState<TProps>>;
5153

5254
// @ts-expect-error Component type doesn't have fields from widget.ts
@@ -76,6 +78,10 @@ export class OptionsController<
7678
private onOptionChangedHandler(optionChanges: ChangedOptionInfo): void {
7779
const { fullName } = optionChanges;
7880

81+
if (this._skipProcessingColumnsChange === fullName) {
82+
return;
83+
}
84+
7985
this.updateIsControlledMode();
8086
this.updateInternalOptionsState(fullName, optionChanges);
8187
}
@@ -221,4 +227,15 @@ export class OptionsController<
221227
);
222228
});
223229
}
230+
231+
public notifyColumnOptionChanged(
232+
fullOptionPath: string,
233+
newValue: unknown,
234+
prevValue: unknown,
235+
): void {
236+
this._skipProcessingColumnsChange = fullOptionPath;
237+
// @ts-expect-error
238+
this.component._notifyOptionChanged(fullOptionPath, newValue, prevValue);
239+
this._skipProcessingColumnsChange = false;
240+
}
224241
}

0 commit comments

Comments
 (0)