@genesislcap/grid-pro 15.20.2 → 15.21.0

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 (47) hide show
  1. package/dist/custom-elements.json +2161 -163
  2. package/dist/dts/datasource/base.datasource.d.ts +17 -3
  3. package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
  4. package/dist/dts/datasource/base.types.d.ts +1 -1
  5. package/dist/dts/datasource/base.types.d.ts.map +1 -1
  6. package/dist/dts/datasource/datasource.types.d.ts +5 -2
  7. package/dist/dts/datasource/datasource.types.d.ts.map +1 -1
  8. package/dist/dts/datasource/filter.utils.d.ts +40 -0
  9. package/dist/dts/datasource/filter.utils.d.ts.map +1 -0
  10. package/dist/dts/datasource/index.d.ts +2 -0
  11. package/dist/dts/datasource/index.d.ts.map +1 -1
  12. package/dist/dts/datasource/infinite.datasource.d.ts +464 -0
  13. package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -0
  14. package/dist/dts/datasource/infinite.resource.d.ts +231 -0
  15. package/dist/dts/datasource/infinite.resource.d.ts.map +1 -0
  16. package/dist/dts/datasource/server-side.datasource.d.ts +0 -2
  17. package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
  18. package/dist/dts/datasource/server-side.resource-base.d.ts +1 -1
  19. package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
  20. package/dist/dts/grid-pro-beta.d.ts +37 -2
  21. package/dist/dts/grid-pro-beta.d.ts.map +1 -1
  22. package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +20 -1
  23. package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
  24. package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts +0 -1
  25. package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts.map +1 -1
  26. package/dist/dts/grid-pro.d.ts +6 -0
  27. package/dist/dts/grid-pro.d.ts.map +1 -1
  28. package/dist/dts/react.d.ts +25 -1
  29. package/dist/dts/utils/map.d.ts +2 -2
  30. package/dist/dts/utils/map.d.ts.map +1 -1
  31. package/dist/esm/datasource/base.datasource.js +39 -2
  32. package/dist/esm/datasource/filter.utils.js +241 -0
  33. package/dist/esm/datasource/index.js +2 -0
  34. package/dist/esm/datasource/infinite.datasource.js +373 -0
  35. package/dist/esm/datasource/infinite.resource.js +581 -0
  36. package/dist/esm/datasource/server-side.datasource.js +0 -38
  37. package/dist/esm/datasource/server-side.grid-definitions.js +1 -1
  38. package/dist/esm/datasource/server-side.resource-base.js +7 -105
  39. package/dist/esm/grid-pro-beta.js +73 -6
  40. package/dist/esm/grid-pro-genesis-datasource/datasource-events.types.js +3 -0
  41. package/dist/esm/grid-pro-genesis-datasource/grid-pro-genesis-datasource.js +0 -3
  42. package/dist/esm/grid-pro.js +12 -1
  43. package/dist/grid-pro.api.json +1355 -477
  44. package/dist/grid-pro.d.ts +962 -165
  45. package/dist/react.cjs +84 -0
  46. package/dist/react.mjs +83 -0
  47. package/package.json +13 -13
@@ -525,8 +525,45 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
525
525
  changes: mappedTransaction,
526
526
  });
527
527
  }
528
- applyTransaction(transaction) {
529
- throw new Error('Method not implemented.');
528
+ /**
529
+ * Builds the index map used for server-side sorting from the resource's metadata.
530
+ * @remarks Real DATASERVER indexes keep their names; when the resource also (or only) reports
531
+ * SORTABLE_FIELDS - the synthetic bucket a req/rep resource uses for its sortable columns -
532
+ * those fields are merged in under the synthetic `SORTABLE_FIELDS` key. Note an index with no
533
+ * NAME lands under `undefined`: its fields count as indexed, but it cannot be named in an
534
+ * ORDER_BY, so sorting-related consumers must consider named entries only.
535
+ * @internal
536
+ */
537
+ getResourceIndexes(availableIndexes, availableSortableFields) {
538
+ const resourceIndexesMap = new Map();
539
+ const allSortableFields = new Set();
540
+ // Add indexes from INDEXES metadata
541
+ availableIndexes === null || availableIndexes === void 0 ? void 0 : availableIndexes.forEach((index) => {
542
+ const fields = index.FIELDS.split(' ');
543
+ resourceIndexesMap.set(index.NAME, fields);
544
+ fields.forEach((field) => allSortableFields.add(field));
545
+ });
546
+ // Add fields from SORTABLE_FIELDS metadata if available
547
+ if (availableSortableFields && availableSortableFields.length > 0) {
548
+ availableSortableFields.forEach((field) => allSortableFields.add(field));
549
+ }
550
+ // If we have SORTABLE_FIELDS but no indexes, create a synthetic index entry
551
+ if (resourceIndexesMap.size === 0 && allSortableFields.size > 0) {
552
+ resourceIndexesMap.set('SORTABLE_FIELDS', Array.from(allSortableFields));
553
+ }
554
+ else if (availableSortableFields &&
555
+ availableSortableFields.length > 0 &&
556
+ resourceIndexesMap.size > 0) {
557
+ const fieldsFromIndexes = new Set();
558
+ resourceIndexesMap.forEach((fields) => {
559
+ fields.forEach((field) => fieldsFromIndexes.add(field));
560
+ });
561
+ const additionalFields = availableSortableFields.filter((field) => !fieldsFromIndexes.has(field));
562
+ if (additionalFields.length > 0) {
563
+ resourceIndexesMap.set('SORTABLE_FIELDS', additionalFields);
564
+ }
565
+ }
566
+ return resourceIndexesMap;
530
567
  }
531
568
  /**
532
569
  * Maps the transaction data to the row data mapper function, if it exists.
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Shared filter utilities for converting AG Grid filter models to Genesis criteria strings.
3
+ * The single implementation of the filter-model grammar: the Infinite Row Model converts whole
4
+ * models through {@link convertFilterModelToCriteria}, and the Server-Side Row Model's
5
+ * `BaseServerSideDatasource.criteriaFor*Model` methods delegate to the per-type converters, so
6
+ * both row models send identical CRITERIA_MATCH for the same gesture.
7
+ * @beta
8
+ */
9
+ /**
10
+ * Escapes a value for interpolation into a criteria string literal.
11
+ * @remarks Backslashes first, then quotes: an unescaped quote would end the literal early, and
12
+ * an unescaped trailing backslash (or a literal `\'` in the value) would escape the closing
13
+ * quote - either way the server rejects the whole expression.
14
+ */
15
+ function escapeCriteriaValue(value) {
16
+ return String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
17
+ }
18
+ /**
19
+ * Converts text filters to Genesis criteria strings.
20
+ * @param filterModel - The AG Grid filter model
21
+ * @param fieldName - The field name being filtered
22
+ * @returns Genesis criteria string or null
23
+ */
24
+ export function convertTextFilterToCriteria(filterModel, fieldName) {
25
+ if (!filterModel.filter && (filterModel.type === 'false' || filterModel.type === 'true')) {
26
+ return `${fieldName} == ${filterModel.type}`;
27
+ }
28
+ const safeVal = filterModel.filter != null ? escapeCriteriaValue(filterModel.filter) : '';
29
+ switch (filterModel.type) {
30
+ case 'blank':
31
+ // Parenthesised: fragments are joined with ' && ', and && binds tighter than ||, so a
32
+ // bare `a == '' || a == null && b == 'x'` would let empty-a rows bypass b's filter.
33
+ return `(${fieldName} == '' || ${fieldName} == null)`;
34
+ case 'contains':
35
+ return `Expr.containsIgnoreCase(${fieldName}, '${safeVal}')`;
36
+ case 'equals':
37
+ return `${fieldName} == '${safeVal}'`;
38
+ case 'notBlank':
39
+ return `(${fieldName} != '' && ${fieldName} != null)`;
40
+ case 'notEqual':
41
+ return `${fieldName} != '${safeVal}'`;
42
+ case 'wordStartsWith':
43
+ return `Expr.containsWordsStartingWithIgnoreCase(${fieldName}, '${safeVal}')`;
44
+ default:
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * Converts set filters (for enum fields) to Genesis criteria strings.
50
+ * @param filterModel - The AG Grid filter model
51
+ * @param fieldName - The field name being filtered
52
+ * @param colMeta - Column metadata for detecting all values
53
+ * @returns Genesis criteria string or null
54
+ */
55
+ export function convertSetFilterToCriteria(filterModel, fieldName, colMeta) {
56
+ var _a;
57
+ const selectedValues = ((_a = filterModel === null || filterModel === void 0 ? void 0 : filterModel.values) !== null && _a !== void 0 ? _a : []);
58
+ if (!selectedValues || selectedValues.length === 0) {
59
+ return null;
60
+ }
61
+ // Try to detect "all values selected" using VALID_VALUES from metadata.
62
+ // In that case we don't want to send any criteria, as it would be equivalent
63
+ // to no filter at all.
64
+ let allValues;
65
+ if (colMeta === null || colMeta === void 0 ? void 0 : colMeta.VALID_VALUES) {
66
+ try {
67
+ allValues = Array.isArray(colMeta.VALID_VALUES)
68
+ ? colMeta.VALID_VALUES
69
+ : colMeta.VALID_VALUES.split('|');
70
+ }
71
+ catch (_b) {
72
+ allValues = undefined;
73
+ }
74
+ }
75
+ if (allValues &&
76
+ allValues.length === selectedValues.length &&
77
+ allValues.every((v) => selectedValues.includes(v))) {
78
+ // All enum options are selected – skip adding criteria for this column
79
+ return null;
80
+ }
81
+ const orConditions = selectedValues.map((val) => {
82
+ return `${fieldName} == '${escapeCriteriaValue(val)}'`;
83
+ });
84
+ if (orConditions.length > 0) {
85
+ return `(${orConditions.join(' || ')})`;
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Converts number filters to Genesis criteria strings.
91
+ * @param filterModel - The AG Grid filter model
92
+ * @param fieldName - The field name being filtered
93
+ * @returns Genesis criteria string or null
94
+ */
95
+ export function convertNumberFilterToCriteria(filterModel, fieldName) {
96
+ const value = filterModel.filter;
97
+ const valueTwo = filterModel.filterTo;
98
+ // Not bare isNaN: isNaN(null) and isNaN('') are both false, so an absent value would be
99
+ // interpolated as-is and produce criteria the server rejects.
100
+ const isNum = (v) => v !== null && v !== '' && !isNaN(Number(v));
101
+ switch (filterModel.type) {
102
+ case 'equals':
103
+ return isNum(value) ? `${fieldName} == ${value}` : null;
104
+ case 'notEqual':
105
+ return isNum(value) ? `${fieldName} != ${value}` : null;
106
+ case 'greaterThan':
107
+ return isNum(value) ? `${fieldName} > ${value}` : null;
108
+ case 'greaterThanOrEqual':
109
+ return isNum(value) ? `${fieldName} >= ${value}` : null;
110
+ case 'lessThan':
111
+ return isNum(value) ? `${fieldName} < ${value}` : null;
112
+ case 'lessThanOrEqual':
113
+ return isNum(value) ? `${fieldName} <= ${value}` : null;
114
+ case 'inRange':
115
+ return isNum(value) && isNum(valueTwo)
116
+ ? `${fieldName} >= ${value} && ${fieldName} <= ${valueTwo}`
117
+ : null;
118
+ case 'blank':
119
+ // Blank means absent, not zero: a legitimate 0 must not report as blank, and a null
120
+ // value must. Matches the text and date converters.
121
+ return `${fieldName} == null`;
122
+ case 'notBlank':
123
+ return `${fieldName} != null`;
124
+ default:
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * Converts date filters to Genesis criteria strings.
130
+ * @param filterModel - The AG Grid filter model
131
+ * @param fieldName - The field name being filtered
132
+ * @returns Genesis criteria string or null
133
+ */
134
+ export function convertDateFilterToCriteria(filterModel, fieldName) {
135
+ // AG Grid supplies bounds as '2026-09-02 00:00:00'; normalise to the 'yyyyMMdd' form the
136
+ // Expr.dateIs* helpers take. An absent bound yields no criteria rather than interpolating
137
+ // the string 'undefined' - reachable through setFilterModel, e.g. a restored filter config.
138
+ const normalise = (bound) => bound ? String(bound).replace(/-/g, '').replace('T', '-').split(' ')[0] : null;
139
+ const dateFrom = normalise(filterModel.dateFrom);
140
+ const dateTo = normalise(filterModel.dateTo);
141
+ switch (filterModel.type) {
142
+ case 'equals':
143
+ return dateFrom ? `Expr.dateIsEqual(${fieldName}, '${dateFrom}')` : null;
144
+ case 'lessThan':
145
+ return dateFrom ? `Expr.dateIsBefore(${fieldName}, '${dateFrom}')` : null;
146
+ case 'greaterThan':
147
+ return dateFrom ? `Expr.dateIsAfter(${fieldName}, '${dateFrom}')` : null;
148
+ case 'inRange':
149
+ return dateFrom && dateTo
150
+ ? `Expr.dateIsAfter(${fieldName}, '${dateFrom}') && Expr.dateIsBefore(${fieldName}, '${dateTo}')`
151
+ : null;
152
+ case 'isToday': {
153
+ // Same 'yyyyMMdd' shape as the normalised bounds above: 'equals' on today's date and
154
+ // 'isToday' must send identical criteria.
155
+ const now = new Date();
156
+ const month = (now.getMonth() + 1).toString().padStart(2, '0');
157
+ const day = now.getDate().toString().padStart(2, '0');
158
+ return `Expr.dateIsEqual(${fieldName}, '${now.getFullYear()}${month}${day}')`;
159
+ }
160
+ case 'blank':
161
+ return `${fieldName} == null`;
162
+ case 'notBlank':
163
+ return `${fieldName} != null`;
164
+ default:
165
+ return null;
166
+ }
167
+ }
168
+ /**
169
+ * Converts one flat filter model (or one multi-filter sub-model) to a criteria fragment.
170
+ * @param filter - The filter model
171
+ * @param fieldName - The field name being filtered
172
+ * @param colDefs - Column definitions for metadata (set filters)
173
+ * @returns Genesis criteria string or null
174
+ */
175
+ function convertSingleFilterToCriteria(filter, fieldName, colDefs) {
176
+ switch (filter === null || filter === void 0 ? void 0 : filter.filterType) {
177
+ case 'text':
178
+ return convertTextFilterToCriteria(filter, fieldName);
179
+ case 'set': {
180
+ const colMeta = colDefs === null || colDefs === void 0 ? void 0 : colDefs.find((o) => o.NAME === fieldName);
181
+ return convertSetFilterToCriteria(filter, fieldName, colMeta);
182
+ }
183
+ case 'number':
184
+ return convertNumberFilterToCriteria(filter, fieldName);
185
+ case 'date':
186
+ return convertDateFilterToCriteria(filter, fieldName);
187
+ default:
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Processes a single field's filter and returns its criteria fragment.
193
+ * @remarks Multi-filters ({ filterType: 'multi', filterModels: [...] }) are unwrapped honouring
194
+ * their `operator`: an OR multi-filter joins its sub-fragments with `||` and is parenthesised,
195
+ * so the outer ` && ` join between fields cannot rebind it.
196
+ * @param filterEntry - The filter entry for the field
197
+ * @param fieldName - The field name
198
+ * @param colDefs - Column definitions
199
+ * @returns The criteria fragment for the field, or null
200
+ */
201
+ function processFieldFilter(filterEntry, fieldName, colDefs) {
202
+ if (!filterEntry)
203
+ return null;
204
+ if (filterEntry.filterType === 'multi' && Array.isArray(filterEntry.filterModels)) {
205
+ const operator = (filterEntry.operator || 'AND').toUpperCase();
206
+ const joiner = operator === 'OR' ? ' || ' : ' && ';
207
+ const fragments = filterEntry.filterModels
208
+ .map((subModel) => convertSingleFilterToCriteria(subModel, fieldName, colDefs))
209
+ .filter((fragment) => Boolean(fragment));
210
+ if (fragments.length === 0)
211
+ return null;
212
+ return fragments.length === 1 ? fragments[0] : `(${fragments.join(joiner)})`;
213
+ }
214
+ return convertSingleFilterToCriteria(filterEntry, fieldName, colDefs);
215
+ }
216
+ /**
217
+ * Converts AG Grid filter model to Genesis criteria string.
218
+ * This is the main entry point for converting filters.
219
+ * Handles both simple filters and multi-filters (filterType: "multi" with filterModels array).
220
+ * @param filterModel - The AG Grid filter model
221
+ * @param colDefs - Column definitions for metadata (optional but recommended)
222
+ * @returns Genesis criteria string
223
+ */
224
+ export function convertFilterModelToCriteria(filterModel, colDefs) {
225
+ if (!filterModel || Object.keys(filterModel).length === 0) {
226
+ return '';
227
+ }
228
+ const filters = [];
229
+ // Process each field in the filter model
230
+ for (const fieldName of Object.keys(filterModel)) {
231
+ // Skip if column not in colDefs (when colDefs provided)
232
+ if (colDefs && colDefs.findIndex((o) => o.NAME === fieldName) === -1) {
233
+ continue;
234
+ }
235
+ const fieldCriteria = processFieldFilter(filterModel[fieldName], fieldName, colDefs);
236
+ if (fieldCriteria) {
237
+ filters.push(fieldCriteria);
238
+ }
239
+ }
240
+ return filters.join(' && ');
241
+ }
@@ -3,6 +3,8 @@ export * from './base.types';
3
3
  export * from './client-side.datasource';
4
4
  export * from './error-handler.dialog';
5
5
  export * from './datasource.types';
6
+ export * from './filter.utils';
7
+ export * from './infinite.datasource';
6
8
  export * from './row-match.types';
7
9
  export * from './row-match.tracker';
8
10
  export * from './row-match.classes';
@@ -0,0 +1,373 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { Connect, ConnectEvents, Datasource, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
3
+ import { LifecycleMixin } from '@genesislcap/foundation-utils';
4
+ import { customElement, DOM, observable } from '@microsoft/fast-element';
5
+ import { gridProGenesisDatasourceEventNames } from '../grid-pro-genesis-datasource';
6
+ import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
7
+ import { logger } from '../utils';
8
+ import { GridProBaseDatasource } from './base.datasource';
9
+ import { GenesisInfiniteDatasource } from './infinite.resource';
10
+ import { getFilterByFieldType, getServerSideFilterParamsByFieldType, } from './server-side.grid-definitions';
11
+ const criteriaDelimiter = ';';
12
+ /**
13
+ * A Genesis Datasource element for AG Grid's Infinite Row Model.
14
+ * @remarks
15
+ * The Infinite Row Model is part of AG Grid Community (free) and provides:
16
+ * - Lazy loading of data as user scrolls
17
+ * - Server-side sorting and filtering
18
+ * - Simple infinite scroll without grouping/aggregation
19
+ * - Live updates for DATASERVER resources: the DATA_LOGON stream the grid pages over pushes
20
+ * every change, so no flag or second subscription is needed
21
+ *
22
+ * REQUEST_SERVER resources are read on demand only - a change on the server shows up when the
23
+ * grid re-reads (scroll, filter/sort change, or an explicit refresh), not live. The exception
24
+ * is a write made through this session: a commit ACK for an event listed in `pollTriggerEvents`
25
+ * re-reads the loaded blocks, so entity-management CRUD is reflected immediately.
26
+ *
27
+ * Unlike the other datasources, this element deliberately does not consume `deferredGridOptions`
28
+ * (the mechanism is being retired): grid options set there - including via entity-management's
29
+ * `gridOptions` with `datasource-type="infinite"` - are not applied. Set options on the grid
30
+ * element itself instead.
31
+ *
32
+ * Use this instead of Server-Side Row Model when you don't need Enterprise features
33
+ * like row grouping, aggregation, or pivoting.
34
+ *
35
+ * @see https://www.ag-grid.com/javascript-data-grid/infinite-scrolling/
36
+ * @beta
37
+ *
38
+ * @fires base-datasource-error - Fired when a datasource error is reported. detail: `BaseDatasourceErrorEventDetail`
39
+ * @fires datasource-error - Fired when a datasource error occurs (for grid integration). detail: `DatasourceErrorEventDetail`
40
+ * @fires base-datasource-connected - Fired when error state is cleared after connection succeeds
41
+ * @fires datasource-initialize - Fired to hand off infinite row model grid options. detail: `InitializeEventDetail`
42
+ * @fires datasource-init - Fired when the infinite grid model should initialize data
43
+ * @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
44
+ * @fires set-infinite-datasource - Fired to attach or clear the infinite row model datasource. detail: `SetInfiniteDatasourceEventDetail`
45
+ * @fires refresh-infinite-cache - Fired to request an infinite row model refresh; `purge` drops the block cache and re-reads. detail: `RefreshInfiniteCacheEventDetail`
46
+ * @fires cache-filter-config - Fired to persist filter configuration for the grid
47
+ * @fires datasource-data-cleared - Fired when infinite row data is cleared. detail: `DataClearedEventDetail`
48
+ * @fires datasource-filters-restored - Fired when persisted filters are reapplied
49
+ */
50
+ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends LifecycleMixin(GridProBaseDatasource) {
51
+ constructor() {
52
+ super(...arguments);
53
+ this.request = {};
54
+ /** @internal */
55
+ this.onDatasourceReady = (_event) => __awaiter(this, void 0, void 0, function* () {
56
+ const initOK = yield this.initializeDatasource(this.datasourceOptions(), true, false);
57
+ if (!initOK) {
58
+ logger.debug(`Genesis Datasource init failed for ${this.resourceName}`);
59
+ this.clearRowData();
60
+ this.setDisconnected(true);
61
+ return;
62
+ }
63
+ this.$emit(gridProGenesisDatasourceEventNames.dataInit);
64
+ this.setDisconnected(false);
65
+ this.indexes = this.getResourceIndexes(this.datasource.availableIndexes, this.datasource.availableSortableFields);
66
+ const fieldMetadata = toFieldMetadata(this.datasource.originalFieldDef);
67
+ const agColumnDefs = yield this.getAgColumnDefs(fieldMetadata);
68
+ // Emit event to set column definitions
69
+ this.$emit(datasourceEventNames.schemaUpdated, {
70
+ schema: agColumnDefs,
71
+ metadata: fieldMetadata,
72
+ });
73
+ // Create the infinite datasource
74
+ this.infiniteDatasource = new GenesisInfiniteDatasource({
75
+ createSnapshotFunc: this.createSnapshot.bind(this),
76
+ createDataserverStreamFunc: this.createDataserverStream.bind(this),
77
+ // VIEW_NUMBER 0 means "pagination disabled": the server continues from the cursor it
78
+ // holds on the last row it delivered, rather than re-seeking by offset.
79
+ getMoreRowsFunc: (sourceRef) => this.connect.getMoreRows(sourceRef, 0),
80
+ // A pushed INSERT/DELETE changes which rows exist or their order, so the rows are
81
+ // re-read (purge); a pushed MODIFY is already patched into the cache, so the grid only
82
+ // needs to re-render its blocks.
83
+ onRowsInvalidatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
84
+ purge: true,
85
+ }),
86
+ onRowsUpdatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
87
+ purge: false,
88
+ }),
89
+ errorHandlerFunc: this.handleErrors.bind(this),
90
+ resourceName: this.resourceName,
91
+ resourceParams: this.isRequestServer
92
+ ? this.datasource.requestOnlyParams
93
+ : this.datasource.dataserverOnlyParams,
94
+ resourceIndexes: this.indexes,
95
+ resourceColDefs: this.datasource.originalFieldDef,
96
+ maxRows: +this.maxRows,
97
+ rowId: this.rowId,
98
+ baseCriteria: this.criteria,
99
+ isRequestServer: this.isRequestServer,
100
+ });
101
+ // Emit event to set infinite datasource
102
+ this.$emit(datasourceEventNames.setInfiniteDatasource, {
103
+ datasource: this.infiniteDatasource,
104
+ });
105
+ // DATASERVER needs no live-update setup: it is live through the paging DATA_LOGON stream
106
+ // itself (GenesisInfiniteDatasource reports pushes via onRowsInvalidated/onRowsUpdated).
107
+ // REQUEST_SERVER is read on demand, but a write committed through this session should not
108
+ // wait for the user to scroll or reload:
109
+ this.setupCommitTriggeredRefresh();
110
+ // Emit event to restore cached filter config
111
+ this.$emit(datasourceEventNames.filtersRestored);
112
+ });
113
+ }
114
+ resourceNameChanged(oldValue, newValue) {
115
+ if (!oldValue || oldValue === newValue)
116
+ return;
117
+ DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
118
+ yield this.restart();
119
+ }));
120
+ }
121
+ criteriaChanged(oldCriteria, newCriteria) {
122
+ const criteriaIsNotDuplicate = oldCriteria !== normaliseCriteria(newCriteria, criteriaDelimiter);
123
+ if (this.infiniteDatasource && criteriaIsNotDuplicate) {
124
+ DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
125
+ yield this.reloadResourceData();
126
+ }));
127
+ }
128
+ }
129
+ connectedCallback() {
130
+ super.connectedCallback();
131
+ const shouldRunConnect = this.shouldRunConnect;
132
+ DOM.queueUpdate(() => {
133
+ if (!shouldRunConnect)
134
+ return;
135
+ this.init();
136
+ this.subscribeToConnection();
137
+ });
138
+ }
139
+ disconnectedCallback() {
140
+ super.disconnectedCallback();
141
+ const shouldRunDisconnect = this.shouldRunDisconnect;
142
+ DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
143
+ if (!shouldRunDisconnect)
144
+ return;
145
+ yield this.destroy();
146
+ this.unsubscribeFromConnection();
147
+ }));
148
+ }
149
+ deepClone() {
150
+ const copy = super.deepClone();
151
+ copy.deferredColumnStates = structuredClone(this.deferredColumnStates);
152
+ return copy;
153
+ }
154
+ /**
155
+ * Resets the grid data while keeping columnDefs and reloads data.
156
+ * @remarks This is used when the grid is already initialized and we want to reload the data due to a criteria/filter change.
157
+ * @beta
158
+ */
159
+ reloadResourceData() {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ var _a, _b;
162
+ // The paging DATA_LOGON is opened through connect.stream, not this.datasource, so destroying
163
+ // the comms datasource below does not close it. Dispose the IDatasource explicitly or every
164
+ // criteria change (e.g. a bound search box) leaks a live subscription and its block timers.
165
+ (_b = (_a = this.infiniteDatasource) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
166
+ this.infiniteDatasource = undefined;
167
+ this.datasource.destroy();
168
+ // Emit event to cache current filter model before clearing data
169
+ this.$emit(datasourceEventNames.cacheFilterConfig);
170
+ this.clearRowData(false);
171
+ this.setDisconnected(false);
172
+ // Purge the infinite cache (clears all data and refetches)
173
+ this.$emit(datasourceEventNames.refreshInfiniteCache, {
174
+ purge: true,
175
+ });
176
+ yield this.init();
177
+ });
178
+ }
179
+ init() {
180
+ return __awaiter(this, void 0, void 0, function* () {
181
+ // Emit event to initialize grid with infinite row model options
182
+ const gridOptions = {
183
+ getRowId: (params) => {
184
+ if (!params.data) {
185
+ return null;
186
+ }
187
+ return String(params.data[this.rowId]);
188
+ },
189
+ cacheBlockSize: this.maxRows,
190
+ defaultColDef: {
191
+ filter: true,
192
+ resizable: true,
193
+ // Off by default, like the SSRM options: getAgColumnDefs opts individual columns in, so
194
+ // columns from other sources (grid-pro-column templates, entity-management's action
195
+ // column) don't render a sort control the server cannot honour.
196
+ sortable: false,
197
+ unSortIcon: true,
198
+ },
199
+ rowBuffer: 0,
200
+ rowModelType: 'infinite',
201
+ // convertSortModelToOrderBy reads only sortModel[0], so a multi-sort's extra columns
202
+ // would paint indicators whose direction is silently discarded.
203
+ suppressMultiSort: true,
204
+ // Infinite Row Model specific options
205
+ cacheOverflowSize: 2,
206
+ maxConcurrentDatasourceRequests: 1,
207
+ infiniteInitialRowCount: 1,
208
+ maxBlocksInCache: 10,
209
+ };
210
+ this.$emit(datasourceEventNames.initialize, {
211
+ options: gridOptions,
212
+ keepColDefsOnClearRowData: this.keepColDefsOnClearRowData,
213
+ });
214
+ // Listen for grid ready event. Clear any listener a previous init() left pending first:
215
+ // two quick criteria changes would otherwise stack two once-listeners, and the single
216
+ // ready event would then run the whole initialization twice.
217
+ this.clearReadyListener();
218
+ this.addEventListener(datasourceEventNames.ready, this.onDatasourceReady, {
219
+ once: true,
220
+ });
221
+ if (!this.resourceName) {
222
+ this.handleErrors('Application not connected or invalid resource name, datasource will not work.', 'unknown');
223
+ }
224
+ });
225
+ }
226
+ /** @internal */
227
+ clearReadyListener() {
228
+ this.removeEventListener(datasourceEventNames.ready, this.onDatasourceReady);
229
+ }
230
+ destroy() {
231
+ return __awaiter(this, void 0, void 0, function* () {
232
+ var _a, _b;
233
+ // A ready event arriving after teardown must not re-run the initialization.
234
+ this.clearReadyListener();
235
+ this.teardownCommitTriggeredRefresh();
236
+ this.datasource.destroy();
237
+ this.clearRowData();
238
+ if (this.infiniteDatasource) {
239
+ (_b = (_a = this.infiniteDatasource).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
240
+ this.infiniteDatasource = undefined;
241
+ }
242
+ // Emit events instead of direct grid access
243
+ this.$emit(datasourceEventNames.setInfiniteDatasource, {
244
+ datasource: null,
245
+ });
246
+ });
247
+ }
248
+ restart() {
249
+ return __awaiter(this, void 0, void 0, function* () {
250
+ yield this.destroy();
251
+ yield this.init();
252
+ });
253
+ }
254
+ clearRowData(withColumnDefs = true) {
255
+ this.rowData = new Map();
256
+ if (withColumnDefs) {
257
+ // Emit event to clear column definitions
258
+ this.$emit(datasourceEventNames.dataCleared, {
259
+ includeSchema: true,
260
+ });
261
+ }
262
+ this.transactionData = { remove: [] };
263
+ }
264
+ /**
265
+ * Honours `pollTriggerEvents` without polling: a commit ACK for one of the listed events
266
+ * means a write went through this session, so the loaded blocks are re-read in place - the
267
+ * same action as `applyTransaction`, which keeps the grid's row count and scroll position.
268
+ * @remarks REQUEST_SERVER only. DATASERVER already receives every change on the paging
269
+ * subscription, so a commit-triggered re-read there would just duplicate the live push.
270
+ * @internal
271
+ */
272
+ setupCommitTriggeredRefresh() {
273
+ var _a;
274
+ this.teardownCommitTriggeredRefresh();
275
+ if (this.isSnapshot || !this.isRequestServer || !((_a = this.pollTriggerEvents) === null || _a === void 0 ? void 0 : _a.length)) {
276
+ return;
277
+ }
278
+ this.commitResponseUnsubscribe = this.connectEvents.addCommitResponseListener((event) => {
279
+ var _a;
280
+ const { eventName, response } = event.detail;
281
+ if (this.pollTriggerEvents.includes(eventName) && ((_a = response.MESSAGE_TYPE) === null || _a === void 0 ? void 0 : _a.endsWith('_ACK'))) {
282
+ logger.debug(`Commit ACK for ${eventName}, re-reading the loaded blocks.`);
283
+ this.$emit(datasourceEventNames.refreshInfiniteCache, {
284
+ purge: false,
285
+ });
286
+ }
287
+ });
288
+ }
289
+ /** Safe to call when the listener was never registered. @internal */
290
+ teardownCommitTriggeredRefresh() {
291
+ var _a;
292
+ (_a = this.commitResponseUnsubscribe) === null || _a === void 0 ? void 0 : _a.call(this);
293
+ this.commitResponseUnsubscribe = undefined;
294
+ }
295
+ /**
296
+ * Creates a snapshot request to the server.
297
+ * @param existingParams - Optional existing parameters to include
298
+ * @returns The server response
299
+ */
300
+ createSnapshot() {
301
+ return __awaiter(this, arguments, void 0, function* (existingParams = null) {
302
+ const result = yield this.datasource.snapshot(existingParams);
303
+ return result;
304
+ });
305
+ }
306
+ /**
307
+ * Opens the paged DATASERVER subscription used to serve grid blocks.
308
+ * @remarks Deliberately a stream and not a snapshot: paging is a MORE_ROWS request against a
309
+ * live subscription, and a fresh DATA_LOGON would always come back with page 1.
310
+ * @internal
311
+ */
312
+ createDataserverStream(params = null) {
313
+ const onError = (error) => {
314
+ var _a;
315
+ logger.error('Infinite DATASERVER paged stream error:', error);
316
+ this.handleErrors(((_a = error === null || error === void 0 ? void 0 : error.receivedMessage) === null || _a === void 0 ? void 0 : _a.ERROR) || (error === null || error === void 0 ? void 0 : error.message), 'stream');
317
+ };
318
+ return this.connect.stream(this.resourceName, () => { }, onError, params);
319
+ }
320
+ getAgColumnDefs(fieldsMetadata) {
321
+ return __awaiter(this, void 0, void 0, function* () {
322
+ // The same filter mapping as the server-side row model, enums included: enum columns ask
323
+ // for agSetColumnFilter, and the grid swaps in the community text filter when the app has
324
+ // not registered the Enterprise SetFilterModule (see processSchemaUpdate in grid-pro-beta).
325
+ const colDefsFromGenesisData = this.generateColumnDefsFromMetadata(fieldsMetadata, getServerSideFilterParamsByFieldType, getFilterByFieldType);
326
+ // Sortability differs by transport, matching convertSortModelToOrderBy:
327
+ // - REQUEST_SERVER orders any column (direction inline), so every column is sortable.
328
+ // - DATASERVER can only ORDER_BY a NAMED index, so a column is sortable only when it belongs
329
+ // to one. An unnamed index (stored under an undefined key) cannot be named, so its columns
330
+ // would render a sort control convertSortModelToOrderBy then drops - excluded here.
331
+ const namedIndexFields = new Set();
332
+ this.indexes.forEach((fields, indexName) => {
333
+ if (indexName) {
334
+ fields.forEach((field) => namedIndexFields.add(field));
335
+ }
336
+ });
337
+ colDefsFromGenesisData.forEach((colDef) => {
338
+ colDef.sortable = this.isRequestServer || namedIndexFields.has(colDef.field);
339
+ // A criteria-only req/rep accepts CRITERIA_MATCH on its declared criteria fields only;
340
+ // filtering any other column would send criteria the server rejects, which on this row
341
+ // model fails the block rather than surfacing a filter error.
342
+ if (this.datasource.criteriaOnlyRequest) {
343
+ colDef.filter = this.datasource.availableCriteriaFields.includes(colDef.field)
344
+ ? colDef.filter
345
+ : null;
346
+ }
347
+ });
348
+ return colDefsFromGenesisData;
349
+ });
350
+ }
351
+ loadMore() {
352
+ // Infinite row model handles this automatically via scrolling
353
+ logger.warn('loadMore() is not needed for Infinite Row Model - scrolling triggers data loading automatically');
354
+ }
355
+ };
356
+ __decorate([
357
+ Connect
358
+ ], GridProInfiniteDatasource.prototype, "connect", void 0);
359
+ __decorate([
360
+ ConnectEvents
361
+ ], GridProInfiniteDatasource.prototype, "connectEvents", void 0);
362
+ __decorate([
363
+ Datasource
364
+ ], GridProInfiniteDatasource.prototype, "datasource", void 0);
365
+ __decorate([
366
+ observable
367
+ ], GridProInfiniteDatasource.prototype, "request", void 0);
368
+ GridProInfiniteDatasource = __decorate([
369
+ customElement({
370
+ name: 'grid-pro-infinite-datasource',
371
+ })
372
+ ], GridProInfiniteDatasource);
373
+ export { GridProInfiniteDatasource };