@adaptabletools/adaptable 23.0.20 → 23.0.22

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/AdaptableOptions/FilterOptions.d.ts +26 -0
  3. package/src/AdaptableOptions/LayoutOptions.d.ts +1 -8
  4. package/src/AdaptableState/InitialState.d.ts +1 -1
  5. package/src/Api/GridApi.d.ts +5 -1
  6. package/src/Api/Implementation/FormatColumnApiImpl.d.ts +1 -0
  7. package/src/Api/Implementation/FormatColumnApiImpl.js +15 -0
  8. package/src/Api/Implementation/FreeTextColumnApiImpl.js +0 -3
  9. package/src/Api/Implementation/GridApiImpl.d.ts +1 -0
  10. package/src/Api/Implementation/GridApiImpl.js +22 -1
  11. package/src/Api/Implementation/StateApiImpl.d.ts +2 -0
  12. package/src/Api/Implementation/StateApiImpl.js +15 -0
  13. package/src/Api/Internal/ColumnFilterInternalApi.d.ts +7 -1
  14. package/src/Api/Internal/ColumnFilterInternalApi.js +3 -0
  15. package/src/Api/Internal/FormatColumnInternalApi.d.ts +5 -0
  16. package/src/Api/Internal/FormatColumnInternalApi.js +22 -0
  17. package/src/Api/Internal/StateInternalApi.d.ts +6 -1
  18. package/src/Api/Internal/StateInternalApi.js +44 -0
  19. package/src/Api/StateApi.d.ts +20 -0
  20. package/src/Redux/Store/AdaptableReduxMerger.js +24 -6
  21. package/src/Redux/Store/AdaptableStore.js +1 -1
  22. package/src/Utilities/Helpers/DisplayFormatHelper.js +16 -4
  23. package/src/Utilities/Helpers/SettingsPanelHelper.js +2 -2
  24. package/src/View/Components/ColumnFilter/components/FloatingFilterValues.d.ts +9 -0
  25. package/src/View/Components/ColumnFilter/components/FloatingFilterValues.js +21 -41
  26. package/src/View/Components/FilterForm/ListBoxFilterForm.js +16 -14
  27. package/src/View/Components/PredicateEditor/PredicateEditor.js +7 -1
  28. package/src/View/FormatColumn/Wizard/FormatColumnFormatWizardSection.js +6 -2
  29. package/src/agGrid/AdaptableAgGrid.d.ts +1 -0
  30. package/src/agGrid/AdaptableAgGrid.js +15 -0
  31. package/src/agGrid/AdaptableFilterHandler.d.ts +5 -1
  32. package/src/agGrid/AdaptableFilterHandler.js +44 -0
  33. package/src/components/Combobox/comboboxUtils.d.ts +6 -0
  34. package/src/components/Combobox/comboboxUtils.js +1 -1
  35. package/src/components/Combobox/index.d.ts +11 -0
  36. package/src/components/Combobox/index.js +16 -5
  37. package/src/components/Tree/TreeDropdown/index.d.ts +11 -0
  38. package/src/components/Tree/TreeDropdown/index.js +6 -3
  39. package/src/env.js +2 -2
  40. package/tsconfig.esm.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptabletools/adaptable",
3
- "version": "23.0.20",
3
+ "version": "23.0.22",
4
4
  "description": "Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements",
5
5
  "keywords": [
6
6
  "web-components",
@@ -206,6 +206,24 @@ export interface InFilterValueResult {
206
206
  * Search changes reset the list and set `offset` back to 0.
207
207
  */
208
208
  hasMore?: boolean;
209
+ /**
210
+ * Custom text for the collapsed IN Filter input.
211
+ *
212
+ * Receives the currently selected items (and their count) and must return a
213
+ * string.
214
+ *
215
+ * When omitted, the input shows the selected count (e.g. `(3)`) followed by the
216
+ * selected labels joined with a comma. When provided, it fully replaces that
217
+ * text (including the count - render one yourself if you want it).
218
+ *
219
+ * For tree-shaped filters (group columns and date columns), each selected leaf
220
+ * is one entry in `selectedValues`: its `value` is the full leaf path and its
221
+ * `label` the joined path label. `selectedCount` is the number of selected leaves.
222
+ */
223
+ renderInputValues?: (params: {
224
+ selectedValues: InFilterValue[];
225
+ selectedCount: number;
226
+ }) => string;
209
227
  }
210
228
  /**
211
229
  * Information about items in the IN Column Filter
@@ -276,6 +294,14 @@ export interface CustomInFilterValuesContext<TData = any> extends AdaptableColum
276
294
  * Opaque pagination cursors may also be stashed on this object and read on the next call.
277
295
  */
278
296
  previousFilterResult?: InFilterValueResult;
297
+ /**
298
+ * Values currently selected in the IN (or NOT IN) Column Filter.
299
+ *
300
+ * Labels are resolved from previously loaded In Filter items when available.
301
+ *
302
+ * Useful with lazy pagination or server-side search: selected items may not be in the current page.
303
+ */
304
+ selectedValues: InFilterValue[];
279
305
  /**
280
306
  * Zero-based index of the first value to return.
281
307
  * Reset to 0 when the dropdown opens or `currentSearchValue` changes.
@@ -24,14 +24,7 @@ export interface LayoutOptions<TData = any> {
24
24
  */
25
25
  layoutCreationDefaultProperties?: LayoutCreationDefaultProperties | ((context: LayoutCreationDefaultPropertiesContext) => TableLayoutCreationDefaultProperties | PivotLayoutCreationDefaultProperties);
26
26
  /**
27
- * Function which checks if the Row Selection checkbox should be visible for a given row.
28
- *
29
- * When provided, this overrides `LayoutRowSelection.Checkboxes` and any `gridOptions.rowSelection.checkboxes` value,
30
- * and forces AG Grid `hideDisabledCheckboxes: true` so returning `false` hides (rather than greys out) the checkbox.
31
- *
32
- * Does not apply when the Layout sets `RowSelection: false` (row selection is fully disabled for that Layout).
33
- *
34
- * Use `context.rowNode` for group / footer / pinned / total rows (`data` may be `undefined` for those).
27
+ * Sets if Row Selection checkbox should be visible for a given row
35
28
  */
36
29
  isRowSelectionCheckboxVisible?: (context: LayoutRowSelectionContext<TData>) => boolean;
37
30
  }
@@ -60,7 +60,7 @@ export interface InitialState {
60
60
  */
61
61
  FormatColumn?: FormatColumnState;
62
62
  /**
63
- * Collection of *FreeText* objects so users can make their own notes in bespoke columns that will get stored with their state (and not with the DataSource). Useful if needing a 'Comments' column.
63
+ * Collection of *FreeText* objects that define user-owned columns whose cell values are stored in Adaptable State (not the grid DataSource). Use for editable per-row fields such as ops notes, flags, or codes — distinct from cell-level Notes and Comments.
64
64
  */
65
65
  FreeTextColumn?: FreeTextColumnState;
66
66
  /**
@@ -35,9 +35,13 @@ export interface GridApi {
35
35
  */
36
36
  getFilteredData(): any[];
37
37
  /**
38
- * Retrieves visible data from the grid (filtered and sorted)
38
+ * Retrieves visible data from the grid (filtered and sorted from visible columns only)
39
39
  */
40
40
  getVisibleData(): any[];
41
+ /**
42
+ * Retrieves the displayed (formatted but not styled) data from the grid (filtered and sorted and visible columns only)
43
+ */
44
+ getDisplayedData(): any[];
41
45
  /**
42
46
  * Retrieves all the values for a single Column.
43
47
  *
@@ -38,4 +38,5 @@ export declare class FormatColumnApiImpl extends ApiBase implements FormatColumn
38
38
  incrementFormatColumnPrecedence(formatColumn: FormatColumn): void;
39
39
  decrementFormatColumnPrecedence(formatColumn: FormatColumn): void;
40
40
  findFormatColumns(criteria: AdaptableObjectLookupCriteria): FormatColumn[];
41
+ private refreshColumnsForFormatColumns;
41
42
  }
@@ -39,14 +39,17 @@ export class FormatColumnApiImpl extends ApiBase {
39
39
  addFormatColumn(formatColumn) {
40
40
  this.addUidToAdaptableObject(formatColumn);
41
41
  this.dispatchAction(FormatColumnRedux.FormatColumnAdd(formatColumn));
42
+ this.refreshColumnsForFormatColumns(formatColumn);
42
43
  return this.getFormatColumnByUuId(formatColumn.Uuid);
43
44
  }
44
45
  addFormatColumns(formatColumns) {
45
46
  this.dispatchAction(FormatColumnRedux.FormatColumnsAdd(formatColumns));
47
+ this.refreshColumnsForFormatColumns(formatColumns);
46
48
  return formatColumns?.map((formatColumn) => this.getFormatColumnByUuId(formatColumn.Uuid));
47
49
  }
48
50
  editFormatColumn(formatColumn) {
49
51
  this.dispatchAction(FormatColumnRedux.FormatColumnEdit(formatColumn));
52
+ this.refreshColumnsForFormatColumns(formatColumn);
50
53
  return this.getFormatColumnByUuId(formatColumn.Uuid);
51
54
  }
52
55
  addFormatColumnStyle(formatColumn, style) {
@@ -56,26 +59,33 @@ export class FormatColumnApiImpl extends ApiBase {
56
59
  updateFormatColumnStyle(formatColumn, style) {
57
60
  formatColumn.Style = style;
58
61
  this.dispatchAction(FormatColumnRedux.FormatColumnEdit(formatColumn));
62
+ this.refreshColumnsForFormatColumns(formatColumn);
59
63
  }
60
64
  deleteFormatColumn(formatColumn) {
61
65
  this.dispatchAction(FormatColumnRedux.FormatColumnDelete(formatColumn));
66
+ this.refreshColumnsForFormatColumns(formatColumn);
62
67
  }
63
68
  deleteAllFormatColumns() {
64
69
  this.dispatchAction(FormatColumnRedux.FormatColumnDeleteAll());
70
+ this.getGridApi().refreshAllCells(true);
65
71
  }
66
72
  suspendFormatColumn(formatColumn) {
67
73
  this.dispatchAction(FormatColumnRedux.FormatColumnSuspend(formatColumn));
74
+ this.refreshColumnsForFormatColumns(formatColumn);
68
75
  return this.getFormatColumnByUuId(formatColumn.Uuid);
69
76
  }
70
77
  unSuspendFormatColumn(formatColumn) {
71
78
  this.dispatchAction(FormatColumnRedux.FormatColumnUnSuspend(formatColumn));
79
+ this.refreshColumnsForFormatColumns(formatColumn);
72
80
  return this.getFormatColumnByUuId(formatColumn.Uuid);
73
81
  }
74
82
  suspendAllFormatColumn() {
75
83
  this.dispatchAction(FormatColumnRedux.FormatColumnSuspendAll());
84
+ this.getGridApi().refreshAllCells(true);
76
85
  }
77
86
  unSuspendAllFormatColumn() {
78
87
  this.dispatchAction(FormatColumnRedux.FormatColumnUnSuspendAll());
88
+ this.getGridApi().refreshAllCells(true);
79
89
  }
80
90
  getFormatColumnsForColumn(column, config) {
81
91
  const formatColumns = this.getFormatColumns(config).filter((formatColumn) => (config?.includeSuspended || !formatColumn.IsSuspended) && (config?.includeReadOnly || !formatColumn.IsReadOnly));
@@ -114,9 +124,11 @@ export class FormatColumnApiImpl extends ApiBase {
114
124
  }
115
125
  incrementFormatColumnPrecedence(formatColumn) {
116
126
  this.dispatchAction(FormatColumnRedux.FormatColumnMoveUp(formatColumn));
127
+ this.refreshColumnsForFormatColumns(formatColumn);
117
128
  }
118
129
  decrementFormatColumnPrecedence(formatColumn) {
119
130
  this.dispatchAction(FormatColumnRedux.FormatColumnMoveDown(formatColumn));
131
+ this.refreshColumnsForFormatColumns(formatColumn);
120
132
  }
121
133
  findFormatColumns(criteria) {
122
134
  const config = {
@@ -124,4 +136,7 @@ export class FormatColumnApiImpl extends ApiBase {
124
136
  };
125
137
  return this.getAdaptableInternalApi().findAdaptableObjectsByLookupCriteria(criteria, this.getFormatColumns(config));
126
138
  }
139
+ refreshColumnsForFormatColumns(formatColumns) {
140
+ this.internalApi.refreshFormatColumnPresentation(formatColumns);
141
+ }
127
142
  }
@@ -60,9 +60,6 @@ export class FreeTextColumnApiImpl extends ApiBase {
60
60
  existingItem.FreeText = sv.FreeText;
61
61
  }
62
62
  }
63
- else {
64
- freeTextColumn.FreeTextStoredValues.push(sv);
65
- }
66
63
  }
67
64
  else {
68
65
  freeTextColumn.FreeTextStoredValues.push(sv);
@@ -27,6 +27,7 @@ export declare class GridApiImpl extends ApiBase implements GridApi {
27
27
  getGridData(): any[];
28
28
  getFilteredData(): any[];
29
29
  getVisibleData(): any[];
30
+ getDisplayedData(): any[];
30
31
  getColumnData(columnId: string): any[];
31
32
  getFilteredColumnData(columnId: string): any[];
32
33
  getVisibleColumnData(columnId: string): any[];
@@ -36,9 +36,30 @@ export class GridApiImpl extends ApiBase {
36
36
  }
37
37
  getVisibleData() {
38
38
  const data = [];
39
+ const visibleColumns = this.getColumnApi().getVisibleColumns();
39
40
  this.getAdaptableInternalApi().forAllVisibleRowNodesDo((rowNode) => {
40
41
  if (!this.isGroupRowNode(rowNode)) {
41
- data.push(rowNode.data);
42
+ const visibleRow = {};
43
+ visibleColumns.forEach((column) => {
44
+ const key = column.field ?? column.columnId;
45
+ visibleRow[key] = this.getRawValueFromRowNode(rowNode, column.columnId);
46
+ });
47
+ data.push(visibleRow);
48
+ }
49
+ });
50
+ return data;
51
+ }
52
+ getDisplayedData() {
53
+ const data = [];
54
+ const visibleColumns = this.getColumnApi().getVisibleColumns();
55
+ this.getAdaptableInternalApi().forAllVisibleRowNodesDo((rowNode) => {
56
+ if (!this.isGroupRowNode(rowNode)) {
57
+ const displayedRow = {};
58
+ visibleColumns.forEach((column) => {
59
+ const key = column.field ?? column.columnId;
60
+ displayedRow[key] = this.getDisplayValueFromRowNode(rowNode, column.columnId);
61
+ });
62
+ data.push(displayedRow);
42
63
  }
43
64
  });
44
65
  return data;
@@ -30,6 +30,8 @@ export declare class StateApiImpl extends ApiBase implements StateApi {
30
30
  copyUserStateToClipboard(): void;
31
31
  getInitialState(): InitialState | any;
32
32
  reloadInitialState(newInitialState?: InitialState): Promise<void>;
33
+ applyInitialState(newInitialState?: InitialState): Promise<void>;
34
+ remergePersistedState(): Promise<void>;
33
35
  getAllState(): AdaptableState;
34
36
  getPersistentState(): AdaptablePersistentState;
35
37
  getAllUserState(): BaseState[];
@@ -66,12 +66,27 @@ export class StateApiImpl extends ApiBase {
66
66
  .then(() => {
67
67
  const newState = this.getPersistentState();
68
68
  this.getEventApi().internalApi.fireAdaptableStateReloadedEvent(oldState, newState);
69
+ this.getGridApi().refreshAllCells(true);
70
+ this.getThemeApi().applyCurrentTheme();
69
71
  })
70
72
  .then(() => {
71
73
  return this.getAdaptableStore().saveStateNow(this._adaptable, this.getAdaptableStore().TheStore.getState());
72
74
  });
73
75
  });
74
76
  }
77
+ applyInitialState(newInitialState) {
78
+ if (newInitialState) {
79
+ this.getOptions().initialState = JSON.parse(JSON.stringify(newInitialState));
80
+ }
81
+ const initialState = (newInitialState ?? this.getInitialState());
82
+ return this.internalApi.applyMergedInitialAndUserState(initialState, this.getPersistentState());
83
+ }
84
+ async remergePersistedState() {
85
+ const config = buildAdaptableStateFunctionConfig(this._adaptable);
86
+ const loadState = this.getOptions().stateOptions.loadState;
87
+ const userState = await loadState(config);
88
+ return this.internalApi.applyMergedInitialAndUserState(this.getInitialState(), userState);
89
+ }
75
90
  getAllState() {
76
91
  return this.getAdaptableState();
77
92
  }
@@ -5,7 +5,7 @@ import { AdaptablePredicateDef } from '../../AdaptableState/Common/AdaptablePred
5
5
  import { ColumnFilter } from '../../AdaptableState/Common/ColumnFilter';
6
6
  import { GridCell } from '../../AdaptableState/Selection/GridCell';
7
7
  import { LayoutColumnFilterAction } from '../../Redux/ActionsReducers/LayoutRedux';
8
- import { InFilterValueResult } from '../../types';
8
+ import { InFilterValue, InFilterValueResult } from '../../types';
9
9
  import { ApiBase } from '../Implementation/ApiBase';
10
10
  import { AdaptableFilterHandler } from '../../agGrid/AdaptableFilterHandler';
11
11
  export declare class ColumnFilterInternalApi extends ApiBase {
@@ -58,5 +58,11 @@ export declare class ColumnFilterInternalApi extends ApiBase {
58
58
  }): Promise<InFilterValueResult>;
59
59
  shouldManuallyApplyColumnFilter(columnId: string): boolean;
60
60
  getAdaptableFilterHandler(columnId: string): AdaptableFilterHandler | undefined;
61
+ /**
62
+ * Returns the remembered selected In-filter items for a column - used to resolve
63
+ * labels for selected values missing from the current page (lazy pagination).
64
+ * @param columnId column to get the remembered values for
65
+ */
66
+ getRememberedLazyInFilterValues(columnId: string): InFilterValue[];
61
67
  getAllAdaptableFilterHandlers(): AdaptableFilterHandler[];
62
68
  }
@@ -234,6 +234,9 @@ export class ColumnFilterInternalApi extends ApiBase {
234
234
  getAdaptableFilterHandler(columnId) {
235
235
  return this.getAgGridApi().getColumnFilterHandler(columnId);
236
236
  }
237
+ getRememberedLazyInFilterValues(columnId) {
238
+ return this.getAdaptableFilterHandler(columnId)?.getRememberedLazyInFilterValues() ?? [];
239
+ }
237
240
  getAllAdaptableFilterHandlers() {
238
241
  const allFilterableColumns = this.getColumnApi().getFilterableColumns();
239
242
  const filterHandlers = allFilterableColumns
@@ -2,6 +2,11 @@ import { Column, HeaderValueGetterParams, IRowNode } from 'ag-grid-enterprise';
2
2
  import { AdaptableColumn, AdaptableFormat, AdaptablePredicateDef, ColumnScope, FormatColumn, FormatColumnConfig, FormatColumnTarget, StringFormatterOptions } from '../../types';
3
3
  import { ApiBase } from '../Implementation/ApiBase';
4
4
  export declare class FormatColumnInternalApi extends ApiBase {
5
+ /**
6
+ * Re-evaluate Format Column styles on affected columns after state changes.
7
+ * Column-model update alone can leave visible cells stale until scroll.
8
+ */
9
+ refreshFormatColumnPresentation(formatColumns: FormatColumn | FormatColumn[] | undefined): void;
5
10
  /**
6
11
  * Retrieves all Format Columns in Adaptable State with the `Style` property set
7
12
  * @returns format columns
@@ -27,6 +27,28 @@ function getParentGroup(source) {
27
27
  return undefined;
28
28
  }
29
29
  export class FormatColumnInternalApi extends ApiBase {
30
+ refreshFormatColumnPresentation(formatColumns) {
31
+ if (!formatColumns) {
32
+ return;
33
+ }
34
+ const list = Array.isArray(formatColumns) ? formatColumns : [formatColumns];
35
+ const columnIds = new Set();
36
+ for (const formatColumn of list) {
37
+ if (!formatColumn?.Scope) {
38
+ continue;
39
+ }
40
+ if (this.getColumnScopeApi().scopeIsAll(formatColumn.Scope)) {
41
+ this.getGridApi().refreshAllCells(true);
42
+ return;
43
+ }
44
+ this.getColumnScopeApi()
45
+ .getColumnsInScope(formatColumn.Scope)
46
+ .forEach((column) => columnIds.add(column.columnId));
47
+ }
48
+ if (columnIds.size > 0) {
49
+ this.getGridApi().refreshColumns([...columnIds]);
50
+ }
51
+ }
30
52
  getAllFormatColumnWithStyle(config) {
31
53
  return this.getFormatColumnApi()
32
54
  .getFormatColumns(config)
@@ -1,6 +1,11 @@
1
- import { AdaptablePersistentState, AdaptableState } from '../../types';
1
+ import { AdaptablePersistentState, AdaptableState, InitialState } from '../../types';
2
2
  import { ApiBase } from '../Implementation/ApiBase';
3
3
  export declare class StateInternalApi extends ApiBase {
4
+ /**
5
+ * Startup-equivalent merge: Initial State + user state → store, layout/theme/cells, persist.
6
+ * Does not clear persistence and does not call `loadState` (caller supplies user state).
7
+ */
8
+ applyMergedInitialAndUserState(initialState: InitialState, userState: AdaptablePersistentState | Record<string, unknown> | null | undefined): Promise<void>;
4
9
  getAllStateForDevTools(): AdaptableState;
5
10
  /**
6
11
  * Replaces persisted Adaptable state with the supplied snapshot (e.g. from DevTools).
@@ -1,6 +1,50 @@
1
1
  import { getPersistableState, LoadState, InitState } from '../../Redux/Store/AdaptableStore';
2
+ import { MergeStateFunction } from '../../Redux/Store/AdaptableReduxMerger';
3
+ import { buildAdaptableStateFunctionConfig } from '../../Redux/Store/buildAdaptableStateFunctionConfig';
4
+ import { addUuidsToInitialState } from '../../Utilities/Helpers/AdaptableHelper';
2
5
  import { ApiBase } from '../Implementation/ApiBase';
6
+ function deepClone(value) {
7
+ return JSON.parse(JSON.stringify(value ?? null));
8
+ }
3
9
  export class StateInternalApi extends ApiBase {
10
+ applyMergedInitialAndUserState(initialState, userState) {
11
+ return new Promise((resolve, reject) => {
12
+ this.getAdaptableInternalApi().executeWithProgressIndicator('Applying Initial State...', () => {
13
+ try {
14
+ const oldState = this.getAdaptableApi().stateApi.getPersistentState();
15
+ const initialForMerge = addUuidsToInitialState(deepClone(initialState));
16
+ const userForMerge = deepClone(userState ?? {});
17
+ let merged = MergeStateFunction(initialForMerge, userForMerge);
18
+ const stateFunctionConfig = buildAdaptableStateFunctionConfig(this._adaptable);
19
+ merged = this.getOptions().stateOptions.applyState(merged, stateFunctionConfig);
20
+ const agGridOptions = {
21
+ columnDefs: this._adaptable.agGridAdapter.getGridOption('columnDefs'),
22
+ autoGroupColumnDef: this._adaptable.agGridAdapter.getGridOption('autoGroupColumnDef'),
23
+ rowModelType: this._adaptable.agGridAdapter.getGridOption('rowModelType'),
24
+ treeData: this._adaptable.agGridAdapter.getGridOption('treeData'),
25
+ };
26
+ merged = this._adaptable.normalizeAdaptableState(merged, agGridOptions);
27
+ this.getAdaptableStore().TheStore.dispatch(LoadState(merged));
28
+ this.getAdaptableStore().TheStore.dispatch(InitState());
29
+ this._adaptable.refreshLayout();
30
+ this.getGridApi().refreshAllCells(true);
31
+ this.getThemeApi().applyCurrentTheme();
32
+ const newState = this.getAdaptableApi().stateApi.getPersistentState();
33
+ this.getEventApi().internalApi.fireAdaptableStateReloadedEvent(oldState, newState);
34
+ void this.getAdaptableStore()
35
+ .saveStateNow(this._adaptable)
36
+ .then(() => resolve())
37
+ .catch(reject);
38
+ }
39
+ catch (error) {
40
+ reject(error);
41
+ }
42
+ finally {
43
+ this.getUserInterfaceApi().hideProgressIndicator();
44
+ }
45
+ });
46
+ });
47
+ }
4
48
  getAllStateForDevTools() {
5
49
  let allState = this.getAdaptableApi().stateApi.getAllState();
6
50
  if (allState.Layout && Array.isArray(allState.Layout.Layouts)) {
@@ -42,6 +42,26 @@ export interface StateApi {
42
42
  * @param newInitialState optional new InitialState to load
43
43
  */
44
44
  reloadInitialState(newInitialState?: InitialState): Promise<void>;
45
+ /**
46
+ * Merges Initial State into the **current** user state (Revision / Override / KeepUserDefined),
47
+ * without clearing persistence. Same merge as startup, applied at runtime.
48
+ *
49
+ * When `newInitialState` is provided it also updates `AdaptableOptions.initialState`.
50
+ * Omitting it re-applies the current Initial State onto current user state.
51
+ *
52
+ * @param newInitialState optional Initial State to merge (defaults to current Initial State)
53
+ */
54
+ applyInitialState(newInitialState?: InitialState): Promise<void>;
55
+ /**
56
+ * Re-runs the startup merge using **persisted** user state from `StateOptions.loadState`
57
+ * and the current `AdaptableOptions.initialState`. Does not clear persistence.
58
+ *
59
+ * Use when remote/local storage may have changed (or Initial State in options was updated)
60
+ * and you want to re-sync without a full wipe (`reloadInitialState`).
61
+ *
62
+ * Unsaved in-memory-only changes are discarded in favour of what `loadState` returns.
63
+ */
64
+ remergePersistedState(): Promise<void>;
45
65
  /**
46
66
  * Copies all Adaptable state to clipboard
47
67
  */
@@ -2,6 +2,7 @@ import { isArray } from '../../Utilities/Extensions/ArrayExtensions';
2
2
  import { isObject, merge, mergeWith, } from '../../Utilities/Extensions/ObjectExtensions';
3
3
  import { AdaptableLogger } from '../../agGrid/AdaptableLogger';
4
4
  import AdaptableHelper from '../../Utilities/Helpers/AdaptableHelper';
5
+ const mergerLogger = new AdaptableLogger('StateMerger');
5
6
  function customizer(objValue, srcValue) {
6
7
  if (isArray(objValue)) {
7
8
  if (!Array.isArray(srcValue)) {
@@ -66,8 +67,10 @@ export function MergeStateFunction(oldState, newState) {
66
67
  }
67
68
  for (const initialStateModuleName in initialState) {
68
69
  if (!state[initialStateModuleName]) {
69
- const { Revision, ...moduleState } = initialState[initialStateModuleName];
70
- state[initialStateModuleName] = moduleState;
70
+ state[initialStateModuleName] = initialState[initialStateModuleName];
71
+ if (initialState[initialStateModuleName]?.Revision != null) {
72
+ mergerLogger.info(`Added section '${initialStateModuleName}' from Initial State (Revision preserved)`, initialState[initialStateModuleName].Revision);
73
+ }
71
74
  }
72
75
  }
73
76
  for (const stateModuleName in state) {
@@ -75,14 +78,29 @@ export function MergeStateFunction(oldState, newState) {
75
78
  const stateRevision = state[stateModuleName].Revision ?? 0;
76
79
  const initialStateRevision = initialState[stateModuleName].Revision ?? 0;
77
80
  const stateRevisionKey = typeof stateRevision === 'object' ? stateRevision.Key : stateRevision;
78
- const initialStateRevisionBehaviour = typeof initialStateRevision === 'object' ? initialStateRevision.Behavior : 'Override';
81
+ let initialStateRevisionStrategy = 'Override';
82
+ if (typeof initialStateRevision === 'object') {
83
+ const rawStrategy = initialStateRevision.UpdateStrategy ?? initialStateRevision.Behavior;
84
+ if (rawStrategy === 'KeepUserDefined' || rawStrategy === 'Override') {
85
+ initialStateRevisionStrategy = rawStrategy;
86
+ }
87
+ else if (rawStrategy != null) {
88
+ mergerLogger.warn(`Unknown Revision UpdateStrategy '${rawStrategy}' on section '${stateModuleName}'; falling back to Override`, initialStateRevision);
89
+ }
90
+ if (initialStateRevision.UpdateStrategy == null &&
91
+ initialStateRevision.Behavior != null) {
92
+ mergerLogger.info(`Section '${stateModuleName}' uses legacy Revision.Behavior; prefer UpdateStrategy`, initialStateRevision);
93
+ }
94
+ }
79
95
  const initialStateRevisionKey = typeof initialStateRevision === 'object' ? initialStateRevision.Key : initialStateRevision;
80
96
  if (initialStateRevisionKey > stateRevisionKey) {
81
- if (initialStateRevisionBehaviour === 'Override') {
82
- state[stateModuleName] = initialState[stateModuleName];
97
+ if (initialStateRevisionStrategy === 'KeepUserDefined') {
98
+ mergerLogger.info(`Applying Revision on '${stateModuleName}': ${stateRevisionKey} → ${initialStateRevisionKey} (KeepUserDefined)`);
99
+ state[stateModuleName] = ProcessKeepUserDefinedRevision(deepClone(initialState[stateModuleName]), state[stateModuleName]);
83
100
  }
84
101
  else {
85
- state[stateModuleName] = ProcessKeepUserDefinedRevision(deepClone(initialState[stateModuleName]), state[stateModuleName]);
102
+ mergerLogger.info(`Applying Revision on '${stateModuleName}': ${stateRevisionKey} → ${initialStateRevisionKey} (Override)`);
103
+ state[stateModuleName] = initialState[stateModuleName];
86
104
  }
87
105
  }
88
106
  }
@@ -255,7 +255,7 @@ export class AdaptableStore {
255
255
  this.storageEngine.setStateKey(adaptableStateKey);
256
256
  this.loadStorageInProgress = true;
257
257
  return (this.Load = this.storageEngine
258
- .load(adaptable, initialState)
258
+ .load(adaptable, initialState ?? adaptable.adaptableOptions.initialState)
259
259
  .then((storedState) => {
260
260
  if (storedState && this.loadStateOnStartup) {
261
261
  this.TheStore.dispatch(LoadState(postProcessState(adaptable.adaptableOptions.stateOptions.applyState(storedState, buildAdaptableStateFunctionConfig(adaptable)))));
@@ -14,9 +14,21 @@ const getFractionDigitsAfterMultiplier = (input, multiplier, explicitFractionDig
14
14
  if (multiplier === 1) {
15
15
  return getInputFractionDigitCount(input);
16
16
  }
17
+ if (!Number.isFinite(multiplier) || multiplier === 0) {
18
+ return getInputFractionDigitCount(input);
19
+ }
17
20
  const multiplierPower = Math.round(Math.log10(Math.abs(multiplier)));
21
+ if (!Number.isFinite(multiplierPower)) {
22
+ return getInputFractionDigitCount(input);
23
+ }
18
24
  return Math.max(0, getInputFractionDigitCount(input) - multiplierPower);
19
25
  };
26
+ const normalizeMultiplier = (multiplier) => {
27
+ if (multiplier == null || !Number.isFinite(multiplier) || multiplier === 0) {
28
+ return undefined;
29
+ }
30
+ return multiplier;
31
+ };
20
32
  export function NumberFormatter(input, options = {}, rowNode, column, api) {
21
33
  let preparedInput;
22
34
  if (options.Content) {
@@ -41,11 +53,11 @@ export function NumberFormatter(input, options = {}, rowNode, column, api) {
41
53
  if (isNaN(n)) {
42
54
  return preparedInput.toString();
43
55
  }
44
- let multiplier = options.Multiplier ? options.Multiplier : 1;
56
+ const effectiveMultiplier = normalizeMultiplier(options.Multiplier);
45
57
  let multiplierFractionDigits;
46
- if (options.Multiplier !== undefined) {
47
- n *= multiplier;
48
- multiplierFractionDigits = getFractionDigitsAfterMultiplier(preparedInput, options.Multiplier, options.FractionDigits);
58
+ if (effectiveMultiplier !== undefined) {
59
+ n *= effectiveMultiplier;
60
+ multiplierFractionDigits = getFractionDigitsAfterMultiplier(preparedInput, effectiveMultiplier, options.FractionDigits);
49
61
  n = parseFloat(n.toFixed(multiplierFractionDigits));
50
62
  }
51
63
  if (options.IntegerDigits > 20) {
@@ -5,7 +5,7 @@ export const DEFAULT_SETTINGS_PANEL_NAVIGATION_GROUPS = [
5
5
  },
6
6
  {
7
7
  label: 'Layout',
8
- items: ['Layout', 'CalculatedColumn', 'CustomSort', 'Charting'],
8
+ items: ['Layout', 'CalculatedColumn', 'FreeTextColumn', 'CustomSort', 'Charting'],
9
9
  },
10
10
  {
11
11
  label: 'Formatting',
@@ -17,7 +17,7 @@ export const DEFAULT_SETTINGS_PANEL_NAVIGATION_GROUPS = [
17
17
  },
18
18
  {
19
19
  label: 'Notes & Annotations',
20
- items: ['Alert', 'Note', 'Comment', 'FreeTextColumn', 'SystemStatus'],
20
+ items: ['Alert', 'Note', 'Comment', 'SystemStatus'],
21
21
  },
22
22
  {
23
23
  label: 'Export & Import',
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ColumnValuesSelectProps } from '../../FilterForm/ListBoxFilterForm';
3
+ import { InFilterValue } from '../../../../types';
3
4
  /** Suggested page size passed to `customInFilterValues` as `context.limit`. */
4
5
  export declare const IN_FILTER_VALUES_PAGE_SIZE = 1000;
5
6
  export interface FloatingFilterValuesProps {
@@ -31,6 +32,10 @@ export declare function useDistinctFilterColumnValues(options: {
31
32
  skipDefaultSearch: boolean;
32
33
  searchValueUsedInFilterValue: boolean;
33
34
  hasMore: boolean;
35
+ renderInputValues?: (params: {
36
+ selectedValues: InFilterValue[];
37
+ selectedCount: number;
38
+ }) => string;
34
39
  };
35
40
  setQuickFilterValues: React.Dispatch<React.SetStateAction<{
36
41
  values: {
@@ -41,6 +46,10 @@ export declare function useDistinctFilterColumnValues(options: {
41
46
  skipDefaultSearch: boolean;
42
47
  searchValueUsedInFilterValue: boolean;
43
48
  hasMore: boolean;
49
+ renderInputValues?: (params: {
50
+ selectedValues: InFilterValue[];
51
+ selectedCount: number;
52
+ }) => string;
44
53
  }>>;
45
54
  triggerValuesLoad: (options?: {
46
55
  usePrevious: boolean;