@adaptabletools/adaptable 23.0.19 → 23.0.21

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/AdaptableOptions/AdaptableOptions.d.ts +1 -1
  3. package/src/AdaptableOptions/DefaultAdaptableOptions.js +1 -0
  4. package/src/AdaptableOptions/FilterOptions.d.ts +8 -0
  5. package/src/AdaptableOptions/LayoutOptions.d.ts +25 -1
  6. package/src/AdaptableState/InitialState.d.ts +1 -1
  7. package/src/AdaptableState/LayoutState.d.ts +3 -1
  8. package/src/Api/Implementation/FormatColumnApiImpl.d.ts +1 -0
  9. package/src/Api/Implementation/FormatColumnApiImpl.js +15 -0
  10. package/src/Api/Implementation/FreeTextColumnApiImpl.js +0 -3
  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/SettingsPanelHelper.js +2 -2
  23. package/src/View/Components/ColumnFilter/components/FloatingFilterValues.js +9 -40
  24. package/src/View/Components/FilterForm/ListBoxFilterForm.js +15 -13
  25. package/src/agGrid/AdaptableAgGrid.d.ts +13 -0
  26. package/src/agGrid/AdaptableAgGrid.js +51 -2
  27. package/src/agGrid/AdaptableFilterHandler.d.ts +5 -1
  28. package/src/agGrid/AdaptableFilterHandler.js +44 -0
  29. package/src/components/Combobox/comboboxUtils.d.ts +6 -0
  30. package/src/components/Combobox/comboboxUtils.js +1 -1
  31. package/src/components/Combobox/index.js +11 -2
  32. package/src/env.js +2 -2
  33. package/src/layout-manager/src/index.d.ts +12 -2
  34. package/src/layout-manager/src/index.js +46 -8
  35. package/src/types.d.ts +1 -1
  36. 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.19",
3
+ "version": "23.0.21",
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",
@@ -198,7 +198,7 @@ export interface AdaptableOptions<TData = any, TContext = Record<string, any>> {
198
198
  *
199
199
  * @noCodeContainer
200
200
  */
201
- layoutOptions?: LayoutOptions;
201
+ layoutOptions?: LayoutOptions<TData>;
202
202
  /**
203
203
  * Options for managing personal Notes
204
204
  */
@@ -209,6 +209,7 @@ const DefaultAdaptableOptions = {
209
209
  layoutOptions: {
210
210
  layoutCreationDefaultProperties: undefined,
211
211
  pivotPreviewColumns: undefined,
212
+ isRowSelectionCheckboxVisible: undefined,
212
213
  layoutViewOptions: {
213
214
  maxColumnsToDisplay: 10,
214
215
  },
@@ -276,6 +276,14 @@ export interface CustomInFilterValuesContext<TData = any> extends AdaptableColum
276
276
  * Opaque pagination cursors may also be stashed on this object and read on the next call.
277
277
  */
278
278
  previousFilterResult?: InFilterValueResult;
279
+ /**
280
+ * Values currently selected in the IN (or NOT IN) Column Filter.
281
+ *
282
+ * Labels are resolved from previously loaded In Filter items when available.
283
+ *
284
+ * Useful with lazy pagination or server-side search: selected items may not be in the current page.
285
+ */
286
+ selectedValues: InFilterValue[];
279
287
  /**
280
288
  * Zero-based index of the first value to return.
281
289
  * Reset to 0 when the dropdown opens or `currentSearchValue` changes.
@@ -1,10 +1,12 @@
1
+ import { IRowNode } from 'ag-grid-enterprise';
1
2
  import { Layout, PivotLayout, TableLayout } from '../AdaptableState/LayoutState';
2
3
  import { AdaptableObjectTag } from '../AdaptableState/Common/AdaptableObject';
4
+ import { AdaptableColumnContext } from '../AdaptableState/Common/AdaptableColumnContext';
3
5
  import { BaseContext, LayoutExtensionModule, LayoutExtensionObject } from '../../types';
4
6
  /**
5
7
  * Options for configuring Layouts; manage Column visibility, order, sorting, grouping, width etc.
6
8
  */
7
- export interface LayoutOptions {
9
+ export interface LayoutOptions<TData = any> {
8
10
  /**
9
11
  * Set of options for how Layouts are displayed in Settings Panel
10
12
  */
@@ -21,6 +23,10 @@ export interface LayoutOptions {
21
23
  * Default properties to apply when creating a new Layout (Table or Pivot)
22
24
  */
23
25
  layoutCreationDefaultProperties?: LayoutCreationDefaultProperties | ((context: LayoutCreationDefaultPropertiesContext) => TableLayoutCreationDefaultProperties | PivotLayoutCreationDefaultProperties);
26
+ /**
27
+ * Sets if Row Selection checkbox should be visible for a given row
28
+ */
29
+ isRowSelectionCheckboxVisible?: (context: LayoutRowSelectionContext<TData>) => boolean;
24
30
  }
25
31
  /**
26
32
  * Options for managing Tags in Layouts (used to enhance Layouts)
@@ -127,3 +133,21 @@ export interface LayoutCreationDefaultPropertiesContext extends BaseContext {
127
133
  */
128
134
  layoutType: 'table' | 'pivot';
129
135
  }
136
+ /**
137
+ * Context for `LayoutOptions.isRowSelectionCheckboxVisible` function
138
+ */
139
+ export interface LayoutRowSelectionContext<TData = any> extends AdaptableColumnContext<TData> {
140
+ /**
141
+ * Current Layout
142
+ */
143
+ layout: Layout;
144
+ /**
145
+ * The Row Node being evaluated
146
+ */
147
+ rowNode: IRowNode<TData>;
148
+ /**
149
+ * The data in the Row Node.
150
+ * `undefined` for group, footer, pinned, and other non-data rows — use `rowNode` for those cases.
151
+ */
152
+ data: TData | undefined;
153
+ }
@@ -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
  /**
@@ -310,7 +310,9 @@ export interface LayoutRowSelection {
310
310
  */
311
311
  Mode: 'singleRow' | 'multiRow';
312
312
  /**
313
- * Whether to display checkboxes in Selection Column
313
+ * Whether to display checkboxes in Selection Column.
314
+ * Overridden by `LayoutOptions.isRowSelectionCheckboxVisible` when that function is provided
315
+ * (except when `RowSelection` is `false`, which disables selection entirely).
314
316
  * @defaultValue true
315
317
  */
316
318
  Checkboxes?: boolean;
@@ -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);
@@ -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)))));
@@ -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',
@@ -183,17 +183,17 @@ export function useDistinctFilterColumnValues(options) {
183
183
  if (!quickFilterValues.hasMore || !quickFilterValues.dataLoadIsComplete) {
184
184
  return;
185
185
  }
186
- let scrollEl = null;
187
186
  let debounceTimer;
188
- const onScroll = () => {
187
+ const onScroll = (event) => {
188
+ const target = event.target;
189
+ if (!(target instanceof Element) || !target.matches(FILTER_VALUES_SCROLL_SELECTOR)) {
190
+ return;
191
+ }
189
192
  if (debounceTimer) {
190
193
  clearTimeout(debounceTimer);
191
194
  }
192
195
  debounceTimer = setTimeout(() => {
193
- if (!scrollEl) {
194
- return;
195
- }
196
- const el = scrollEl;
196
+ const el = target;
197
197
  const remaining = el.scrollHeight - el.scrollTop - el.clientHeight;
198
198
  if (remaining > NEAR_END_PX) {
199
199
  return;
@@ -201,45 +201,14 @@ export function useDistinctFilterColumnValues(options) {
201
201
  loadMoreValues();
202
202
  }, SCROLL_DEBOUNCE_MS);
203
203
  };
204
- const tryAttach = () => {
205
- scrollEl = document.querySelector(FILTER_VALUES_SCROLL_SELECTOR);
206
- if (scrollEl) {
207
- scrollEl.addEventListener('scroll', onScroll, { passive: true });
208
- return true;
209
- }
210
- return false;
211
- };
212
- let attached = tryAttach();
213
- const raf = attached
214
- ? undefined
215
- : requestAnimationFrame(() => {
216
- attached = tryAttach();
217
- });
218
- const timeout = attached
219
- ? undefined
220
- : setTimeout(() => {
221
- tryAttach();
222
- }, 50);
204
+ document.addEventListener('scroll', onScroll, { capture: true, passive: true });
223
205
  return () => {
224
- if (raf != null) {
225
- cancelAnimationFrame(raf);
226
- }
227
- if (timeout != null) {
228
- clearTimeout(timeout);
229
- }
230
206
  if (debounceTimer) {
231
207
  clearTimeout(debounceTimer);
232
208
  }
233
- if (scrollEl) {
234
- scrollEl.removeEventListener('scroll', onScroll);
235
- }
209
+ document.removeEventListener('scroll', onScroll, { capture: true });
236
210
  };
237
- }, [
238
- quickFilterValues.hasMore,
239
- quickFilterValues.dataLoadIsComplete,
240
- quickFilterValues.values.length,
241
- loadMoreValues,
242
- ]);
211
+ }, [quickFilterValues.hasMore, quickFilterValues.dataLoadIsComplete, loadMoreValues]);
243
212
  useEffect(() => {
244
213
  if (!hasMoreRef.current ||
245
214
  !quickFilterValues.dataLoadIsComplete ||
@@ -10,6 +10,7 @@ import { useMemo } from 'react';
10
10
  import { GridFilterCombobox } from '../../../components/Combobox';
11
11
  import { isEmbeddedColumnFilterLocation } from '../ColumnFilter/columnFilterLocation';
12
12
  import { useColumnFilterLocation } from '../ColumnFilter/AdaptableColumnFilter';
13
+ import { useAdaptable } from '../../AdaptableContext';
13
14
  import { GridFilterTreeDropdown } from '../../../components/Tree/TreeDropdown';
14
15
  const baseClassName = 'ab-ListBoxFilterForm';
15
16
  function toDateDisplayValue(values) {
@@ -29,9 +30,11 @@ const dateOnChangeParser = (value) => {
29
30
  const EMPTY_ARRAY = [];
30
31
  export const ColumnValuesSelect = (props) => {
31
32
  const location = useColumnFilterLocation();
33
+ const adaptable = useAdaptable();
32
34
  const column = props.column;
33
35
  let selectedColumnValues = props.value || EMPTY_ARRAY;
34
36
  const rawOptionsFromProps = Array.isArray(props.options) ? props.options : EMPTY_ARRAY;
37
+ const lookupItems = adaptable?.api.filterApi.columnFilterApi.internalApi.getRememberedLazyInFilterValues(column.columnId) ?? EMPTY_ARRAY;
35
38
  const optionsFromProps = useMemo(() => {
36
39
  if (rawOptionsFromProps === EMPTY_ARRAY) {
37
40
  return rawOptionsFromProps;
@@ -88,19 +91,18 @@ export const ColumnValuesSelect = (props) => {
88
91
  }
89
92
  const options = column.dataType === 'date' || column.columnId === AG_GRID_GROUPED_COLUMN
90
93
  ? dateOptions
91
- : optionsFromProps.filter((distinctValue) => {
92
- let isActive = selectedColumnValues.indexOf(distinctValue.value) >= 0;
93
- if (!isActive && distinctValue.value && distinctValue.value instanceof Date) {
94
- isActive = selectedColumnValues.some((dateStr) => isEqual(parseDateValue(dateStr), parseDateValue(distinctValue.value)));
94
+ : optionsFromProps;
95
+ if (column.dataType !== 'date' && column.columnId !== AG_GRID_GROUPED_COLUMN) {
96
+ for (const selected of selectedColumnValues) {
97
+ if (options.some((option) => option.value === selected)) {
98
+ value.push(selected);
99
+ continue;
95
100
  }
96
- if (StringExtensions.IsNullOrEmpty(distinctValue.value)) {
97
- return false;
98
- }
99
- if (isActive) {
100
- value.push(distinctValue.value);
101
- }
102
- return true;
103
- });
101
+ const dateOption = options.find((option) => option.value instanceof Date &&
102
+ isEqual(parseDateValue(selected), parseDateValue(option.value)));
103
+ value.push(dateOption ? dateOption.value : selected);
104
+ }
105
+ }
104
106
  const treeDropdownToDisplayValue = React.useMemo(() => {
105
107
  if (column.dataType === 'date') {
106
108
  return toDateDisplayValue;
@@ -117,7 +119,7 @@ export const ColumnValuesSelect = (props) => {
117
119
  : undefined;
118
120
  const component = column.dataType === 'date' || column.columnId === AG_GRID_GROUPED_COLUMN ? (_jsx(GridFilterTreeDropdown, { toDisplayValue: treeDropdownToDisplayValue, resizable: true, onValueChange: column.dataType === 'date'
119
121
  ? (value) => props.onChange(dateOnChangeParser(value))
120
- : props.onChange, items: options, value: selectedColumnValues, "data-name": "Select Values", primaryKey: 'value', isLoading: props.isLoading, onOpenChange: onOpenChange, showClear: isEmbeddedColumnFilterLocation(location) })) : (_jsx(GridFilterCombobox, { disabled: props.disabled, ...selectProps, onOpenChange: onOpenChange, items: options, value: value, isLoading: props.isLoading, onValueChange: props.onChange, showClear: isEmbeddedColumnFilterLocation(location) }, "select"));
122
+ : props.onChange, items: options, value: selectedColumnValues, "data-name": "Select Values", primaryKey: 'value', isLoading: props.isLoading, onOpenChange: onOpenChange, showClear: isEmbeddedColumnFilterLocation(location) })) : (_jsx(GridFilterCombobox, { disabled: props.disabled, ...selectProps, onOpenChange: onOpenChange, items: options, value: value, lookupItems: lookupItems, isLoading: props.isLoading, onValueChange: props.onChange, showClear: isEmbeddedColumnFilterLocation(location) }, "select"));
121
123
  return (_jsx("div", { className: join(baseClassName, 'twa:relative', props.isLoading && `${baseClassName}--loading`, !value.length && `${baseClassName}--empty`), onKeyDownCapture: (e) => {
122
124
  if (e.key === 'Tab') {
123
125
  e.stopPropagation();