@adaptabletools/adaptable 21.0.6 → 21.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptabletools/adaptable",
3
- "version": "21.0.6",
3
+ "version": "21.0.8",
4
4
  "description": "Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements",
5
5
  "keywords": [
6
6
  "web-components",
@@ -53,6 +53,7 @@
53
53
  "react-select": "5.10.1",
54
54
  "react-toastify": "9.1.3",
55
55
  "rebass": "^3.2.2",
56
+ "@types/rebass": "^4.0.10",
56
57
  "redux": "^5.0.1",
57
58
  "rxjs": "^7.4.0",
58
59
  "sentence-case": "^3.0.4",
@@ -11,7 +11,7 @@ export interface DateInputOptions {
11
11
  /**
12
12
  * Format string for formatting date input field
13
13
  *
14
- * @defaultValue 'yyyy-MM-dd'
14
+ * @defaultValue 'yyyy-MM-dd' (ISO 8601 format)
15
15
  */
16
16
  dateFormat?: string;
17
17
  /**
@@ -123,4 +123,5 @@ export declare class AdaptableInternalApi extends ApiBase {
123
123
  findAdaptableObjectsByLookupCriteria<T extends AdaptableObjectWithScope>({ scope, tag, ids }: AdaptableObjectLookupCriteria, specificAdaptableObjects: T[]): T[];
124
124
  buildBaseContext(): BaseContext;
125
125
  setCellSummaryInfo(cellSummaryInfo: CellSummmaryInfo): void;
126
+ parseDateValue(dateValue: string | Date | number): Date | undefined;
126
127
  }
@@ -5,6 +5,7 @@ import StringExtensions from '../../Utilities/Extensions/StringExtensions';
5
5
  import { ADAPTABLE_ID } from '../../Utilities/Constants/GeneralConstants';
6
6
  import { waitForCondition } from '../../Utilities/waitForCondition';
7
7
  import { isAdaptableCustomIcon, isAdaptableSystemIcon } from '../../components/Icon';
8
+ import { parseDateValue } from '../../Utilities/Helpers/DateHelper';
8
9
  export class AdaptableInternalApi extends ApiBase {
9
10
  getInternalState() {
10
11
  return this.getAdaptableState().Internal;
@@ -439,4 +440,12 @@ export class AdaptableInternalApi extends ApiBase {
439
440
  setCellSummaryInfo(cellSummaryInfo) {
440
441
  this.dispatchAction(InternalRedux.SetCellSummaryInfo(cellSummaryInfo));
441
442
  }
443
+ parseDateValue(dateValue) {
444
+ const dateFormat = this.getUserInterfaceOptions().dateInputOptions.dateFormat;
445
+ if (dateFormat === 'yyyy-MM-dd') {
446
+ // if the format is ISO, we don't pass it so that we use the date-fns `parseISO` which is more robust
447
+ return parseDateValue(dateValue);
448
+ }
449
+ return parseDateValue(dateValue, dateFormat);
450
+ }
442
451
  }
@@ -1,7 +1,5 @@
1
1
  import { ApiBase } from '../Implementation/ApiBase';
2
- import { ColDef, Column, HeaderValueGetterParams, IRowNode } from 'ag-grid-enterprise';
3
- import { CustomSort } from '../../AdaptableState/CustomSortState';
4
- import { ColumnValuesComparer } from '../../AdaptableOptions/CustomSortOptions';
2
+ import { ColDef, Column, HeaderValueGetterParams } from 'ag-grid-enterprise';
5
3
  import { AdaptableColumn, AdaptableColumnDataType } from '../../types';
6
4
  export declare function getAutoRowGroupColumnIdFor(columnId: string): string;
7
5
  export declare class ColumnInternalApi extends ApiBase {
@@ -29,7 +27,6 @@ export declare class ColumnInternalApi extends ApiBase {
29
27
  * @param columnId columnId to look up
30
28
  */
31
29
  getAgGridColumnForAdaptableColumn(columnId: string): Column;
32
- getActiveColumnComparator(columnId: string, customSort?: CustomSort, customSortComparer?: ColumnValuesComparer): (valueA: any, valueB: any, nodeA?: IRowNode, nodeB?: IRowNode, isInverted?: boolean) => number | undefined;
33
30
  isSpecialColumn(columnId: string, column?: AdaptableColumn): boolean;
34
31
  getColumnHeaderName(params: HeaderValueGetterParams): string;
35
32
  private buildColumnHeaderContext;
@@ -64,18 +64,6 @@ export class ColumnInternalApi extends ApiBase {
64
64
  getAgGridColumnForAdaptableColumn(columnId) {
65
65
  return this._adaptable.getAgGridColumnForColumnId(columnId);
66
66
  }
67
- getActiveColumnComparator(columnId, customSort, customSortComparer) {
68
- if ((!customSort || customSort?.IsSuspended) && !customSortComparer) {
69
- // defaults to AG-Grid column definition comparator if no CustomSort is defined&active
70
- const colDefComparator = this._adaptable.agGridColumnAdapter.getUserColDefProperty(columnId, 'comparator');
71
- return colDefComparator;
72
- }
73
- // CustomSort Comparer function takes precedence over CustomSort SortedValues
74
- const comparerFunction = customSortComparer
75
- ? customSortComparer.comparer
76
- : this.getCustomSortApi().internalApi.getDefaultCustomSortComparer(customSort.ColumnId, customSort.SortedValues);
77
- return comparerFunction;
78
- }
79
67
  isSpecialColumn(columnId, column = null) {
80
68
  if (column) {
81
69
  return column.isCalculatedColumn || column.isFreeTextColumn || column.isActionColumn;
@@ -14,6 +14,7 @@ export declare class ExportInternalApi extends ApiBase {
14
14
  setExportInProgress(reportName: ReportNameType, reportFormat: ReportFormatType, exportDestination: ExportDestinationType): void;
15
15
  setExportComplete(): void;
16
16
  isVisualDataExportInProgress(): boolean;
17
+ isExcelExportInProgress(): boolean;
17
18
  createSystemReport(systemReportName: SystemReportName): Report;
18
19
  isSystemReport(reportName: ReportNameType): boolean;
19
20
  isSystemDestination(destination: ExportDestinationType): boolean;
@@ -82,6 +82,9 @@ export class ExportInternalApi extends ApiBase {
82
82
  isVisualDataExportInProgress() {
83
83
  return this.getAdaptableState().Internal.Export.inProgress?.reportFormat === 'VisualExcel';
84
84
  }
85
+ isExcelExportInProgress() {
86
+ return this.getAdaptableState().Internal.Export.inProgress?.reportFormat === 'Excel';
87
+ }
85
88
  createSystemReport(systemReportName) {
86
89
  switch (systemReportName) {
87
90
  case ALL_DATA_REPORT:
@@ -5,8 +5,6 @@ import { BaseState } from '../../AdaptableState/BaseState';
5
5
  import { IAdaptableStore, LoadStoreConfig } from './Interface/IAdaptableStore';
6
6
  type EmitterCallback = (data?: any) => any;
7
7
  type EmitterAnyCallback = (eventName: string, data?: any) => any;
8
- export declare const INIT_STATE = "INIT_STATE";
9
- export declare const LOAD_STATE = "LOAD_STATE";
10
8
  export interface ResetUserDataAction extends Redux.Action {
11
9
  }
12
10
  export interface InitStateAction extends Redux.Action {
@@ -40,8 +40,8 @@ import * as ThemeRedux from '../ActionsReducers/ThemeRedux';
40
40
  import * as ToolPanelRedux from '../ActionsReducers/ToolPanelRedux';
41
41
  import { isAdaptableSharedEntity, isCustomSharedEntity, } from '../../AdaptableState/TeamSharingState';
42
42
  import { buildAdaptableStateFunctionConfig } from './buildAdaptableStateFunctionConfig';
43
- export const INIT_STATE = 'INIT_STATE';
44
- export const LOAD_STATE = 'LOAD_STATE';
43
+ const INIT_STATE = 'INIT_STATE';
44
+ const LOAD_STATE = 'LOAD_STATE';
45
45
  const NON_PERSIST_ACTIONS = {
46
46
  '@@INIT': true,
47
47
  '@@redux/init': true,
@@ -8,4 +8,3 @@ export declare const isValueValidDate: (data: any) => boolean;
8
8
  export declare const dateToISO: (date: Date | number | string) => string;
9
9
  export declare const parseToISO: (date: string | Date | number, dateFormat?: string) => string;
10
10
  export declare const parseDateValue: (dateValue: string | Date | number, dateFormat?: string) => Date | undefined;
11
- export declare const parseFilterInputDate: (stringDate: string) => Date;
@@ -23,6 +23,9 @@ export const parseToISO = (date, dateFormat) => {
23
23
  const dateInstance = parseDateValue(date, dateFormat);
24
24
  return isValidDate(dateInstance) ? dateToISO(dateInstance) : '';
25
25
  };
26
+ // !!!
27
+ // AFL: ideally we should NEVER call directly this method, but use the AdaptableInternalApi.parseDateValue() instead
28
+ // we should refactor this with the first opportunity
26
29
  export const parseDateValue = (dateValue, dateFormat) => {
27
30
  if (dateValue == undefined || (typeof dateValue === 'string' && dateValue.trim() === '')) {
28
31
  return undefined;
@@ -30,27 +33,36 @@ export const parseDateValue = (dateValue, dateFormat) => {
30
33
  if (dateValue instanceof Date) {
31
34
  return !isNaN(dateValue.getTime()) ? dateValue : undefined;
32
35
  }
33
- let dateInstance;
34
- if (typeof dateValue === 'number') {
35
- dateInstance = new Date(dateValue);
36
- }
37
- else {
38
- // typeof dateValue === 'string'
39
- if (dateFormat) {
40
- dateInstance = parse(dateValue, dateFormat, new Date());
36
+ try {
37
+ let dateInstance;
38
+ if (typeof dateValue === 'number') {
39
+ dateInstance = new Date(dateValue);
41
40
  }
42
41
  else {
43
- dateInstance = parseISO(dateValue);
42
+ // typeof dateValue === 'string'
43
+ if (dateFormat) {
44
+ dateInstance = parse(dateValue, dateFormat, new Date());
45
+ }
46
+ else {
47
+ dateInstance = parseISO(dateValue);
48
+ }
49
+ if (!isValidDate(dateInstance)) {
50
+ // last chance: try to use the native Date.parse(), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
51
+ dateInstance = new Date(Date.parse(dateValue));
52
+ }
44
53
  }
45
54
  if (!isValidDate(dateInstance)) {
46
- // last chance: try to use the native Date.parse(), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
47
- dateInstance = new Date(Date.parse(dateValue));
55
+ AdaptableLogger.consoleWarnBase(`Invalid date value "${dateValue}" - cannot be converted to a Date instance. Please make sure you specify adaptableOptions.userInterfaceOptions.dateInputOptions.dateFormat correctly.`);
56
+ dateInstance = undefined;
48
57
  }
58
+ return dateInstance;
49
59
  }
50
- if (!isValidDate(dateInstance)) {
51
- AdaptableLogger.consoleWarnBase(`Invalid date value "${dateValue}" - cannot be converted to a Date instance. Please make sure you specify adaptableOptions.userInterfaceOptions.dateInputOptions.dateFormat correctly.`);
52
- dateInstance = undefined;
60
+ catch (error) {
61
+ const errorMessage = error instanceof Error ? error.message : String(error);
62
+ const errorStack = error instanceof Error ? error.stack : undefined;
63
+ AdaptableLogger.consoleErrorBase(`Error parsing date value "${dateValue}": ${errorMessage}`, {
64
+ stack: errorStack,
65
+ originalError: error,
66
+ });
53
67
  }
54
- return dateInstance;
55
68
  };
56
- export const parseFilterInputDate = (stringDate) => new Date(stringDate);
@@ -31,7 +31,7 @@ class FilterViewPanelComponent extends React.Component {
31
31
  ArrayExtensions.IsNotNullOrEmpty(this.props.ColumnFilters) && (React.createElement(AdaptablePopover, { popupPadding: 0, className: `ab-${elementType}__Filter__info`, headerText: "", bodyText: [React.createElement(ActiveFiltersPanel, null)], useButton: true, showEvent: 'focus', hideEvent: "blur", popoverMinWidth: 400 })),
32
32
  React.createElement(ButtonClear, { "aria-label": 'Clear Filters', className: `ab-${elementType}__Filter__clear`, marginLeft: 1, marginBottom: 0, marginRight: 1, onClick: () => this.onClearFilters(), tooltip: "Clear Filters", disabled: this.props.ColumnFilters.length == 0, showText: this.props.viewType === 'ToolPanel' }, this.props.viewType === 'ToolPanel' && 'Clear'),
33
33
  React.createElement(SimpleButton, { "aria-label": isAtLeastOneFilterActive ? 'Suspend All Filters' : 'Resume All Filters', className: join(`ab-${elementType}__Filter__suspend-button`, isAtLeastOneFilterActive && `ab-${elementType}__Filter__suspend-all`, !isAtLeastOneFilterActive && `ab-${elementType}__Filter__un-suspend-all`), disabled: !isAtLeastOneFilter, onClick: handleSuspendUnsuspendAll, tone: isAtLeastOneFilterActive ? 'neutral' : 'success', variant: "text", icon: isAtLeastOneFilterActive ? 'pause' : 'play', accessLevel: this.props.accessLevel })),
34
- React.createElement(Flex, { alignItems: "center" }, this.props.api.filterApi.columnFilterApi.isQuickFilterAvailable() && (React.createElement(CheckBox, { className: `ab-${elementType}__Filter__active-check`, disabled: this.props.accessLevel === 'ReadOnly' ||
34
+ React.createElement(Flex, { alignItems: "center" }, this.props.api.filterApi.columnFilterApi.isQuickFilterAvailable() && (React.createElement(CheckBox, { "data-name": "quick-filter-toggle", className: `ab-${elementType}__Filter__active-check`, disabled: this.props.accessLevel === 'ReadOnly' ||
35
35
  this.props.api.layoutApi.isCurrentLayoutPivot(), marginTop: 0, marginBottom: 0, fontSize: 2, padding: 1, checked: this.props.IsQuickFilterVisible, onChange: (checked) => {
36
36
  checked ? this.props.onShowQuickFilterBar() : this.props.onHideQuickFilterBar();
37
37
  } }, "Filter Bar")))));
@@ -1085,6 +1085,24 @@ You need to define at least one Layout!`);
1085
1085
  color: '#aaaaaa',
1086
1086
  pattern: 'Solid',
1087
1087
  },
1088
+ },
1089
+ // see #EXCEL_EXPORT_DATA_TYPES
1090
+ {
1091
+ id: 'stringExcelType',
1092
+ dataType: 'String',
1093
+ }, {
1094
+ id: 'booleanExcelType',
1095
+ dataType: 'Boolean',
1096
+ }, {
1097
+ id: 'dateExcelType',
1098
+ dataType: 'DateTime',
1099
+ }, {
1100
+ id: 'numberExcelType',
1101
+ // dataType: 'Number',
1102
+ // AG Grid requires either dataType or numberFormat to be set for Numbers
1103
+ numberFormat: {
1104
+ format: '0.###############',
1105
+ },
1088
1106
  });
1089
1107
  this.agGridExportAdapter.originalExcelStyles = excelStyles;
1090
1108
  this.agGridExportAdapter.DANGER_excelStyles = this.agGridExportAdapter.originalExcelStyles;
@@ -3195,11 +3213,6 @@ You need to define at least one Layout!`);
3195
3213
  return this.agGridModulesAdapter.isAgGridModuleRegistered('CsvExportModule');
3196
3214
  }
3197
3215
  isQuickFilterAvailable() {
3198
- if (this.api.layoutApi.isCurrentLayoutPivot() &&
3199
- this.adaptableOptions.filterOptions.useAdaptableFiltering) {
3200
- // hide completely the quick filter if pivot is enabled
3201
- return false;
3202
- }
3203
3216
  return this.hasFloatingFilterOnAtLeastOneColumn(this.agGridAdapter.getAgGridApi().getColumnDefs());
3204
3217
  }
3205
3218
  hasFloatingFilterOnAtLeastOneColumn(columnDefs) {
@@ -169,11 +169,27 @@ export class AgGridColumnAdapter {
169
169
  if (!gridCell.column) {
170
170
  return null;
171
171
  }
172
- // if a VisualExcel report format export is in progress, we are interested only in the Excel Style Class
173
- if (this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress()) {
172
+ const isExcelExport = this.adaptableApi.exportApi.internalApi.isExcelExportInProgress();
173
+ const isVisualDataExport = this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress();
174
+ if (isExcelExport || isVisualDataExport) {
175
+ const excelStyleClasses = [];
174
176
  const userDefinedCellClass = typeof userCellClass === 'function' ? userCellClass(params) : userCellClass;
175
- const cellClassKey = AgGridExportAdapter.getExcelClassNameForCell(colId, gridCell.primaryKeyValue, userDefinedCellClass);
176
- return this.adaptableInstance.agGridExportAdapter.getExcelStyleIdForCellClassKey(cellClassKey);
177
+ if (userDefinedCellClass) {
178
+ if (Array.isArray(userDefinedCellClass)) {
179
+ excelStyleClasses.push(...userDefinedCellClass.filter(Boolean));
180
+ }
181
+ else {
182
+ excelStyleClasses.push(userDefinedCellClass);
183
+ }
184
+ }
185
+ if (isVisualDataExport) {
186
+ const cellClassKey = AgGridExportAdapter.getExcelClassNameForCell(colId, gridCell.primaryKeyValue, userDefinedCellClass);
187
+ const customCellClass = this.adaptableInstance.agGridExportAdapter.getExcelStyleIdForCellClassKey(cellClassKey);
188
+ if (customCellClass) {
189
+ excelStyleClasses.push(customCellClass);
190
+ }
191
+ }
192
+ return excelStyleClasses.length ? excelStyleClasses : null;
177
193
  }
178
194
  const isQuickSearchActive = this.isQuickSearchActive(gridCell);
179
195
  const editableClassName = this.getEditableCellClass(gridCell, params);
@@ -612,7 +628,7 @@ export class AgGridColumnAdapter {
612
628
  const isFloatingFilterDisabled = !colDef.filter ||
613
629
  !colDef.floatingFilter ||
614
630
  !this.adaptableOptions.filterOptions.useAdaptableFiltering ||
615
- !this.adaptableOptions.filterOptions.columnFilterOptions.showQuickFilter;
631
+ !this.adaptableApi.filterApi.columnFilterApi.isQuickFilterVisible();
616
632
  if (this.adaptableApi.columnApi.isAutoRowGroupColumn(col.getColId())) {
617
633
  this.setColDefProperty(col, 'floatingFilter', (original_floatingFilter) => {
618
634
  // the floating filter for the group column is "inherited" from the base column
@@ -803,10 +819,47 @@ export class AgGridColumnAdapter {
803
819
  }
804
820
  setupColumnComparator({ col, colId, abColumn }) {
805
821
  const customSort = this.adaptableApi.customSortApi.getCustomSortForColumn(colId);
806
- const columnSortComparer = this.adaptableApi.customSortApi.internalApi.getCustomSortComparer(abColumn.columnId);
822
+ const customSortComparer = this.adaptableApi.customSortApi.internalApi.getCustomSortComparer(abColumn.columnId);
823
+ const memoizedDateValues = new Map();
824
+ const getDateValue = (rawDateValue) => {
825
+ if (memoizedDateValues.has(rawDateValue)) {
826
+ return memoizedDateValues.get(rawDateValue);
827
+ }
828
+ const dateValue = this.adaptableApi.internalApi.parseDateValue(rawDateValue);
829
+ memoizedDateValues.set(rawDateValue, dateValue);
830
+ return dateValue;
831
+ };
807
832
  const comparatorGetter = (propName) => {
808
- return () => {
809
- return this.adaptableApi.columnApi.internalApi.getActiveColumnComparator(colId, customSort, columnSortComparer);
833
+ return (userPropertyValue) => {
834
+ // CustomSort Comparer function takes precedence over CustomSort SortedValues
835
+ if (customSortComparer) {
836
+ return customSortComparer.comparer;
837
+ }
838
+ if (customSort && !customSort.IsSuspended) {
839
+ return this.adaptableApi.customSortApi.internalApi.getDefaultCustomSortComparer(customSort.ColumnId, customSort.SortedValues);
840
+ }
841
+ if (userPropertyValue) {
842
+ return userPropertyValue;
843
+ }
844
+ // for DATE columns we have to use the normalised values (Date objects) to compare
845
+ if (abColumn.dataType === 'date') {
846
+ return (valueA, valueB) => {
847
+ const dateA = getDateValue(valueA);
848
+ const dateB = getDateValue(valueB);
849
+ if (dateA && dateB) {
850
+ return dateA.getTime() - dateB.getTime();
851
+ }
852
+ else if (dateA) {
853
+ return 1; // consider non-null dates as greater than null/undefined
854
+ }
855
+ else if (dateB) {
856
+ return -1; // consider non-null dates as greater than null/undefined
857
+ }
858
+ else {
859
+ return 0; // both are null/undefined, considered equal
860
+ }
861
+ };
862
+ }
810
863
  };
811
864
  };
812
865
  this.setColDefProperty(col, 'comparator', comparatorGetter('comparator'));
@@ -602,6 +602,27 @@ export class AgGridExportAdapter {
602
602
  this.registerExcelStyle(finalCellExcelStyle, cellClassId);
603
603
  });
604
604
  }, forAllVisibleRowNodesDoConfig);
605
+ // see #EXCEL_EXPORT_DATA_TYPES
606
+ this.excelStylesCache['stringExcelType'] = {
607
+ id: 'stringExcelType',
608
+ dataType: 'String',
609
+ };
610
+ this.excelStylesCache['booleanExcelType'] = {
611
+ id: 'booleanExcelType',
612
+ dataType: 'Boolean',
613
+ };
614
+ this.excelStylesCache['dateExcelType'] = {
615
+ id: 'dateExcelType',
616
+ dataType: 'DateTime',
617
+ };
618
+ this.excelStylesCache['numberExcelType'] = {
619
+ id: 'numberExcelType',
620
+ // dataType: 'Number',
621
+ // AG Grid requires either dataType or numberFormat to be set for Numbers
622
+ numberFormat: {
623
+ format: '0.###############',
624
+ },
625
+ };
605
626
  return Object.values(this.excelStylesCache);
606
627
  }
607
628
  registerExcelStyle(excelStyle, cellClassKey) {
package/src/env.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export default {
2
2
  NEXT_PUBLIC_INFINITE_TABLE_LICENSE_KEY: "StartDate=2021-06-29|EndDate=2030-01-01|Owner=Adaptable|Type=distribution|TS=1624971462479|C=137829811,1004007071,2756196225,1839832928,3994409405,636616862" || '',
3
- PUBLISH_TIMESTAMP: 1759506293070 || Date.now(),
4
- VERSION: "21.0.6" || '--current-version--',
3
+ PUBLISH_TIMESTAMP: 1761108086778 || Date.now(),
4
+ VERSION: "21.0.8" || '--current-version--',
5
5
  };
@@ -333,6 +333,8 @@ export class LayoutManager extends LMEmitter {
333
333
  getPivotLayoutModelFromGrid() {
334
334
  const pivotResultColumns = this.gridApi.getPivotResultColumns() || [];
335
335
  const pivotResultColumnsSet = new Set(pivotResultColumns.map((col) => col.getColId()));
336
+ const rowGroupColumns = this.gridApi.getRowGroupColumns() || [];
337
+ const rowGroupColumnsSet = new Set(rowGroupColumns.map((col) => col.getColId()));
336
338
  const prevLayout = this.currentLayout;
337
339
  const columnState = this.gridApi
338
340
  .getColumnState()
@@ -340,6 +342,7 @@ export class LayoutManager extends LMEmitter {
340
342
  let PivotColumns = this.gridApi.getPivotColumns().map((col) => col.getColId());
341
343
  const layout = this.getUndecidedLayoutModelFromGrid(columnState);
342
344
  let ColumnSizing = layout.ColumnSizing || {};
345
+ let ColumnVisibility = { ...layout.ColumnVisibility };
343
346
  //let's also include the column widths of the pivotResult columns
344
347
  pivotResultColumns.forEach((col) => {
345
348
  const colId = col.getColId();
@@ -355,6 +358,18 @@ export class LayoutManager extends LMEmitter {
355
358
  if (!Object.keys(ColumnSizing).length) {
356
359
  ColumnSizing = undefined;
357
360
  }
361
+ // from column visibility, let's remove all columns that are not actually in the pivot layout
362
+ // since having normal table columns here will break the equality check
363
+ // so we only want to keep the group columns and the pivot result columns
364
+ Object.keys(ColumnVisibility).forEach((colId) => {
365
+ const isInLayout = pivotResultColumnsSet.has(colId) || rowGroupColumnsSet.has(colId);
366
+ if (!isInLayout) {
367
+ delete ColumnVisibility[colId];
368
+ }
369
+ });
370
+ if (!Object.keys(ColumnVisibility).length) {
371
+ ColumnVisibility = undefined;
372
+ }
358
373
  delete layout.TableColumns;
359
374
  const pivotLayout = {
360
375
  Name: layout.Name,
@@ -372,7 +387,7 @@ export class LayoutManager extends LMEmitter {
372
387
  ColumnPinning: layout.ColumnPinning,
373
388
  ColumnSorts: layout.ColumnSorts,
374
389
  ColumnSizing,
375
- ColumnVisibility: layout.ColumnVisibility,
390
+ ColumnVisibility,
376
391
  RowGroupValues: layout.RowGroupValues,
377
392
  ColumnGroupValues: layout.ColumnGroupValues,
378
393
  PivotGroupedColumns: layout.RowGroupedColumns,
@@ -102,6 +102,11 @@ export function normalizeTableLayoutModel(layout, options) {
102
102
  };
103
103
  }
104
104
  }
105
+ // there might be a case where there are we have an RowGroupedColumns array, but it's empty (or it's inexistent altogether)
106
+ // and there is no display type specified - so default it to single
107
+ if (!layout.RowGroupDisplayType && layout.TableColumns) {
108
+ layout.RowGroupDisplayType = 'single';
109
+ }
105
110
  if (options?.isTree && !layout.TableColumns.includes(AUTO_GROUP_COLUMN_ID__SINGLE)) {
106
111
  layout.TableColumns.unshift(AUTO_GROUP_COLUMN_ID__SINGLE);
107
112
  }
@@ -157,9 +162,9 @@ export function normalizePivotLayoutModel(layout) {
157
162
  // make it an own property
158
163
  layout.PivotColumnTotal = undefined;
159
164
  }
160
- if (layout.PivotGroupedColumns && layout.PivotGroupedColumns.length) {
161
- layout.RowGroupDisplayType = layout.RowGroupDisplayType || 'single';
162
- }
165
+ // if (layout.PivotGroupedColumns && layout.PivotGroupedColumns.length) {
166
+ layout.RowGroupDisplayType = layout.RowGroupDisplayType || 'single';
167
+ // }
163
168
  return layout;
164
169
  }
165
170
  export function normalizeLayoutModel(layout, options) {