@genesislcap/foundation-ui 14.488.2 → 14.489.0-GENC-1351.10

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.
@@ -1,7 +1,8 @@
1
1
  var OptionsDatasource_1;
2
2
  import { __awaiter, __decorate } from "tslib";
3
+ import { GenesisResources, ResourceType } from '@genesislcap/foundation-comms';
3
4
  import { Combobox as FASTComboBox, } from '@microsoft/fast-components';
4
- import { attr, customElement, DOM, observable } from '@microsoft/fast-element';
5
+ import { attr, customElement, DOM, nullableNumberConverter, observable, } from '@microsoft/fast-element';
5
6
  import { BaseDatasource, getPrefix, logger, dataserverCriteriaBuilder as criteriaMatchBuilder, } from '../utils';
6
7
  /**
7
8
  * Datasource element for select/combobox option loading.
@@ -16,9 +17,57 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
16
17
  super(...arguments);
17
18
  this.emptyDataLabel = 'No matching records';
18
19
  this.allowCustomOptions = false;
20
+ /**
21
+ * Enables infinite scrolling. Instead of loading everything upfront, only `page-size`
22
+ * options are loaded initially and the next page is requested when the user scrolls near
23
+ * the bottom of the dropdown. REQUEST_SERVER resources page by OFFSET; DATASERVER
24
+ * resources page via MORE_ROWS on a live stream (total still capped by `maxView`).
25
+ *
26
+ * @remarks
27
+ * When paging REQUEST_SERVER resources by OFFSET, set a stable `order-by` — without one
28
+ * the server has no other guaranteed ordering between requests, so rows can shift across
29
+ * pages (duplicates/gaps) as the underlying data changes between fetches. Also, when the
30
+ * parent is a combobox, this forces `async` mode on it (see `Combobox.async`), since only
31
+ * a page of options is loaded client-side.
32
+ */
33
+ this.infiniteScroll = false;
34
+ /**
35
+ * Number of options fetched per page when `infinite-scroll` is enabled. Defaults to 20.
36
+ */
37
+ this.pageSize = OptionsDatasource_1.defaultPageSize;
19
38
  this.initalSelectedValueInitialized = false;
20
39
  this.isSnapshot = true;
21
40
  this.baseCriteria = '';
41
+ this.listboxElement = null;
42
+ this.loadingMoreRows = false;
43
+ this.renderedOptionsCount = 0;
44
+ this.lastTopUpScrollHeight = -1;
45
+ this.emptyDataOption = null;
46
+ /**
47
+ * While the dropdown is closed, the listbox has no dimensions and cannot be scrolled,
48
+ * so re-check whether the visible listbox needs topping up every time it opens. Also
49
+ * retries attaching the scroll listener if the initial attempt (queued on connect)
50
+ * missed the listbox because the host hadn't rendered it yet.
51
+ */
52
+ this.selectOpenChangeHandler = (e) => {
53
+ var _a;
54
+ if (!((_a = e.detail) === null || _a === void 0 ? void 0 : _a.open))
55
+ return;
56
+ if (!this.listboxElement) {
57
+ this.attachListboxScrollListener();
58
+ }
59
+ this.topUpVisibleListbox();
60
+ };
61
+ this.listboxScrollHandler = () => {
62
+ const listbox = this.listboxElement;
63
+ if (!listbox)
64
+ return;
65
+ const nearBottom = listbox.scrollTop + listbox.clientHeight >=
66
+ listbox.scrollHeight - OptionsDatasource_1.scrollLoadThresholdPx;
67
+ if (nearBottom) {
68
+ this.loadNextPage();
69
+ }
70
+ };
22
71
  this.resizeObserverCallback = (entries) => {
23
72
  entries.forEach((resizeObserverEntry) => {
24
73
  this.checkOverflow(resizeObserverEntry.target);
@@ -61,6 +110,18 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
61
110
  this.optionElement = (_b = this.optionElement) !== null && _b !== void 0 ? _b : `${getPrefix(this.select)}-option`;
62
111
  this.select.addEventListener('change', this.selectChangeHandler);
63
112
  this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
113
+ if (this.infiniteScroll) {
114
+ this.maxRows = this.pageSize > 0 ? this.pageSize : OptionsDatasource_1.defaultPageSize;
115
+ // Note: the combobox switches itself to async (server-side) filtering when it
116
+ // detects a slotted infinite-scroll datasource — see Combobox.slottedOptionsChanged
117
+ this.select.addEventListener('open-change', this.selectOpenChangeHandler);
118
+ yield this.applyInfiniteScrollFetchMode();
119
+ // The element may have been disconnected while awaiting the resource type;
120
+ // attaching listeners then would leak them on a detached tree
121
+ if (!this.isConnected)
122
+ return;
123
+ DOM.queueUpdate(() => this.attachListboxScrollListener());
124
+ }
64
125
  this.fetchData();
65
126
  if (!this.valueField) {
66
127
  logger.warn('Specify field for values in options (e.g value-field="VALUE") and optionally field for label (e.g label-field="NAME")');
@@ -68,10 +129,94 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
68
129
  });
69
130
  }
70
131
  disconnectedCallback() {
132
+ var _a;
71
133
  super.disconnectedCallback();
72
134
  this.resizeObserver.disconnect();
135
+ (_a = this.listboxElement) === null || _a === void 0 ? void 0 : _a.removeEventListener('scroll', this.listboxScrollHandler);
136
+ this.listboxElement = null;
137
+ this.select.removeEventListener('open-change', this.selectOpenChangeHandler);
73
138
  this.select.removeEventListener('change', this.selectChangeHandler);
74
139
  }
140
+ /**
141
+ * REQUEST_SERVER resources page by OFFSET against one-shot requests, so they keep the
142
+ * default snapshot mode. DATASERVER resources page via MORE_ROWS, which needs the live
143
+ * stream's SOURCE_REF, so they switch to stream mode.
144
+ */
145
+ applyInfiniteScrollFetchMode() {
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ if (!this.resourceName || !this.connect.isConnected)
148
+ return;
149
+ try {
150
+ const resourceType = yield this.resources.getResourceTypeFor(this.resourceName);
151
+ this.isSnapshot = resourceType !== ResourceType.DATASERVER;
152
+ }
153
+ catch (e) {
154
+ logger.warn(`Unable to determine the resource type for ${this.resourceName}`, e);
155
+ }
156
+ });
157
+ }
158
+ attachListboxScrollListener() {
159
+ var _a, _b, _c, _d, _e, _f, _g;
160
+ if (!this.isConnected)
161
+ return;
162
+ // Remove before add: this may be a retry (e.g. on open-change) against a listbox
163
+ // that was already found, so avoid attaching a duplicate listener.
164
+ (_a = this.listboxElement) === null || _a === void 0 ? void 0 : _a.removeEventListener('scroll', this.listboxScrollHandler);
165
+ // Prefer the host's public contract, falling back to FAST template internals
166
+ this.listboxElement =
167
+ (_e = (_c = (_b = this.select) === null || _b === void 0 ? void 0 : _b.scrollableListbox) !== null && _c !== void 0 ? _c : (_d = this.select) === null || _d === void 0 ? void 0 : _d.listbox) !== null && _e !== void 0 ? _e : (_g = (_f = this.select) === null || _f === void 0 ? void 0 : _f.shadowRoot) === null || _g === void 0 ? void 0 : _g.querySelector('.listbox');
168
+ if (!this.listboxElement) {
169
+ logger.warn('Unable to locate listbox element, infinite scroll will not be available.');
170
+ return;
171
+ }
172
+ this.listboxElement.addEventListener('scroll', this.listboxScrollHandler);
173
+ }
174
+ loadNextPage() {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ if (this.loadingMoreRows || !this.canRequestMoreRows())
177
+ return;
178
+ this.loadingMoreRows = true;
179
+ const isRequestServer = this.datasource.resourceType === ResourceType.REQUEST_SERVER;
180
+ try {
181
+ // For REQUEST_SERVER this resolves once the page arrives and is rendered (via
182
+ // handleStreamInserts); for DATASERVER rows arrive on the stream and the flag
183
+ // resets when they are rendered
184
+ yield this.requestMoreRows();
185
+ }
186
+ catch (_a) {
187
+ // Already reported by requestMoreRows; allow the next scroll to retry
188
+ this.loadingMoreRows = false;
189
+ return;
190
+ }
191
+ if (isRequestServer) {
192
+ this.loadingMoreRows = false;
193
+ }
194
+ });
195
+ }
196
+ /**
197
+ * If the loaded options don't fill the visible listbox (so it can't be scrolled),
198
+ * keep requesting pages until it overflows or the server runs out of rows.
199
+ */
200
+ topUpVisibleListbox() {
201
+ if (!this.infiniteScroll)
202
+ return;
203
+ requestAnimationFrame(() => {
204
+ if (!this.isConnected)
205
+ return;
206
+ const listbox = this.listboxElement;
207
+ // clientHeight is 0 while the dropdown is closed; scrolling isn't possible then anyway
208
+ if (!listbox || listbox.clientHeight === 0)
209
+ return;
210
+ if (listbox.scrollHeight > listbox.clientHeight)
211
+ return;
212
+ // If the last top-up didn't grow the listbox (e.g. options hidden by CSS),
213
+ // fetching further pages would loop until the resource is exhausted
214
+ if (listbox.scrollHeight === this.lastTopUpScrollHeight)
215
+ return;
216
+ this.lastTopUpScrollHeight = listbox.scrollHeight;
217
+ this.loadNextPage();
218
+ });
219
+ }
75
220
  fetchData() {
76
221
  if (this.select &&
77
222
  this.valueField &&
@@ -98,6 +243,13 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
98
243
  });
99
244
  }
100
245
  clearData() {
246
+ // Rows accumulated from a previous stream must not leak into the next fetch
247
+ // (e.g. when the criteria changes in async mode).
248
+ this.rowsSyncedWithStream.clear();
249
+ this.renderedOptionsCount = 0;
250
+ this.lastTopUpScrollHeight = -1;
251
+ this.emptyDataOption = null;
252
+ this.loadingMoreRows = false;
101
253
  if (!this.select)
102
254
  return;
103
255
  Array.from(this.select.children).forEach((x) => {
@@ -119,32 +271,46 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
119
271
  element.removeAttribute('title');
120
272
  }
121
273
  }
274
+ rowToOption(row) {
275
+ var _a;
276
+ return {
277
+ value: row[this.valueField],
278
+ label: this.labelRowFormatter
279
+ ? this.labelRowFormatter(row)
280
+ : this.labelFormatter
281
+ ? this.labelFormatter(row[this.labelField])
282
+ : this.getDefaultLabelFormat(row[this.labelField], (_a = this.getFieldMetadata(this.labelField)) === null || _a === void 0 ? void 0 : _a.type),
283
+ };
284
+ }
285
+ renderListboxOptions(options) {
286
+ options.forEach(({ value, label }) => {
287
+ const option = document.createElement(this.optionElement);
288
+ option.textContent = label ? label : value;
289
+ option.value = value;
290
+ this.select.appendChild(option);
291
+ this.resizeObserver.observe(option);
292
+ });
293
+ }
294
+ removeEmptyDataOption() {
295
+ if (this.emptyDataOption) {
296
+ this.emptyDataOption.remove();
297
+ this.emptyDataOption = null;
298
+ }
299
+ }
122
300
  syncComponentData(snapshotData) {
301
+ this.loadingMoreRows = false;
123
302
  const rowData = snapshotData !== null && snapshotData !== void 0 ? snapshotData : Array.from(this.rowsSyncedWithStream.values());
124
303
  if (rowData.length > 0) {
125
- this.options = rowData.map((row) => {
126
- var _a;
127
- return ({
128
- value: row[this.valueField],
129
- label: this.labelRowFormatter
130
- ? this.labelRowFormatter(row)
131
- : this.labelFormatter
132
- ? this.labelFormatter(row[this.labelField])
133
- : this.getDefaultLabelFormat(row[this.labelField], (_a = this.getFieldMetadata(this.labelField)) === null || _a === void 0 ? void 0 : _a.type),
134
- });
135
- });
136
- const listboxOptions = this.options.map(({ value, label }) => {
137
- const option = document.createElement(this.optionElement);
138
- option.textContent = label ? label : value;
139
- option.value = value;
140
- return option;
141
- });
142
- listboxOptions.forEach((x) => {
143
- this.select.appendChild(x);
144
- this.resizeObserver.observe(x);
145
- });
304
+ this.removeEmptyDataOption();
305
+ this.options = rowData.map((row) => this.rowToOption(row));
306
+ // In stream mode this is invoked with the full accumulated row set on every
307
+ // server message, so only options that haven't been rendered yet are appended.
308
+ const isFirstRenderAfterClear = this.renderedOptionsCount === 0;
309
+ this.renderListboxOptions(this.options.slice(this.renderedOptionsCount));
310
+ this.renderedOptionsCount = this.options.length;
311
+ this.topUpVisibleListbox();
146
312
  if (!this.initalSelectedValueInitialized ||
147
- (this.select instanceof FASTComboBox && !this.select.async)) {
313
+ (this.select instanceof FASTComboBox && !this.select.async && isFirstRenderAfterClear)) {
148
314
  this.resetCombobox();
149
315
  const initialLabel = this.getInitialLabel();
150
316
  DOM.queueUpdate(() => {
@@ -167,11 +333,14 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
167
333
  });
168
334
  this.initalSelectedValueInitialized = true;
169
335
  }
170
- const option = document.createElement(this.optionElement);
171
- option.textContent = this.emptyDataLabel;
172
- option.value = '';
173
- option.disabled = true;
174
- this.select.appendChild(option);
336
+ if (this.select && !this.emptyDataOption) {
337
+ const option = document.createElement(this.optionElement);
338
+ option.textContent = this.emptyDataLabel;
339
+ option.value = '';
340
+ option.disabled = true;
341
+ this.select.appendChild(option);
342
+ this.emptyDataOption = option;
343
+ }
175
344
  }
176
345
  }
177
346
  getInitialLabel() {
@@ -192,16 +361,48 @@ let OptionsDatasource = OptionsDatasource_1 = class OptionsDatasource extends Ba
192
361
  this.select.resetCombobox();
193
362
  }
194
363
  }
364
+ /**
365
+ * Appends rows to the already rendered options. Used by REQUEST_SERVER
366
+ * infinite scroll paging, where each page contains only the new rows.
367
+ */
195
368
  handleStreamInserts(insertedRows) {
196
- logger.debug('handleStreamInserts - Method not implemented.');
369
+ var _a;
370
+ this.loadingMoreRows = false;
371
+ if (!(insertedRows === null || insertedRows === void 0 ? void 0 : insertedRows.length))
372
+ return;
373
+ this.removeEmptyDataOption();
374
+ const newOptions = insertedRows.map((row) => this.rowToOption(row));
375
+ this.options = [...((_a = this.options) !== null && _a !== void 0 ? _a : []), ...newOptions];
376
+ this.renderListboxOptions(newOptions);
377
+ this.renderedOptionsCount = this.options.length;
378
+ this.topUpVisibleListbox();
197
379
  }
380
+ /**
381
+ * @remarks
382
+ * Not implemented: rendered options are only ever appended (see `syncComponentData` /
383
+ * `handleStreamInserts`). For DATASERVER `infinite-scroll`, a live delete/update on a
384
+ * row already rendered in an open dropdown will not be reflected until the datasource
385
+ * is refetched (e.g. criteria change) — tracked as a known limitation, not a v1 goal.
386
+ */
198
387
  handleStreamDeletes(deletedRows) {
199
388
  logger.debug('handleStreamDeletes - Method not implemented.');
200
389
  }
390
+ /**
391
+ * @remarks
392
+ * Not implemented — see {@link OptionsDatasource.handleStreamDeletes}.
393
+ */
201
394
  handleStreamUpdates(updatedRows) {
202
395
  logger.debug('handleStreamUpdates - Method not implemented.');
203
396
  }
204
397
  };
398
+ OptionsDatasource.defaultPageSize = 20;
399
+ /**
400
+ * Distance from the bottom of the listbox (in px) at which the next page is requested.
401
+ */
402
+ OptionsDatasource.scrollLoadThresholdPx = 32;
403
+ __decorate([
404
+ GenesisResources
405
+ ], OptionsDatasource.prototype, "resources", void 0);
205
406
  __decorate([
206
407
  attr({ attribute: 'label-field' })
207
408
  ], OptionsDatasource.prototype, "labelField", void 0);
@@ -217,6 +418,12 @@ __decorate([
217
418
  __decorate([
218
419
  attr({ mode: 'boolean', attribute: 'allow-custom-options' })
219
420
  ], OptionsDatasource.prototype, "allowCustomOptions", void 0);
421
+ __decorate([
422
+ attr({ mode: 'boolean', attribute: 'infinite-scroll' })
423
+ ], OptionsDatasource.prototype, "infiniteScroll", void 0);
424
+ __decorate([
425
+ attr({ attribute: 'page-size', converter: nullableNumberConverter })
426
+ ], OptionsDatasource.prototype, "pageSize", void 0);
220
427
  __decorate([
221
428
  observable
222
429
  ], OptionsDatasource.prototype, "labelFormatter", void 0);
@@ -5,8 +5,19 @@ import { foundationSelectStyles as styles } from './select.styles';
5
5
  import { foundationSelectTemplate as template } from './select.template';
6
6
  /**
7
7
  * @tagname %%prefix%%-select
8
+ *
9
+ * @fires open-change - Fired when the dropdown opens or closes. detail: `OpenChangeEventDetail`
8
10
  */
9
11
  export class Select extends FASTSelect {
12
+ /**
13
+ * The scrollable element hosting the dropdown options. Part of the public contract for
14
+ * slotted datasources (e.g. options-datasource infinite scroll) to observe scrolling.
15
+ * @public
16
+ */
17
+ get scrollableListbox() {
18
+ var _a, _b;
19
+ return (_a = this.listbox) !== null && _a !== void 0 ? _a : (_b = this.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.listbox');
20
+ }
10
21
  /**
11
22
  * @internal
12
23
  */
@@ -66,6 +77,7 @@ export class Select extends FASTSelect {
66
77
  if (!this.collapsible) {
67
78
  return;
68
79
  }
80
+ this.$emit('open-change', { open: this.open });
69
81
  if (this.open) {
70
82
  this.ariaControls = this.listboxId;
71
83
  this.ariaExpanded = 'true';
@@ -1,5 +1,5 @@
1
1
  import { __awaiter, __decorate } from "tslib";
2
- import { Connect, dataServerResultFilter, Datasource, DatasourceDefaults, DatasourceEventHandler, FieldTypeEnum, MessageType, normaliseCriteria, } from '@genesislcap/foundation-comms';
2
+ import { Connect, dataServerResultFilter, Datasource, DatasourceDefaults, DatasourceEventHandler, FieldTypeEnum, MessageType, normaliseCriteria, ResourceType, } from '@genesislcap/foundation-comms';
3
3
  import { formatTimestamp } from '@genesislcap/foundation-utils';
4
4
  import { attr, observable } from '@microsoft/fast-element';
5
5
  import { FoundationElement } from '@microsoft/fast-foundation';
@@ -24,6 +24,14 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
24
24
  */
25
25
  this.withTimestampFormatting = true;
26
26
  this.requiresFullDataRefresh = true;
27
+ /**
28
+ * Whether the server reported more rows are available beyond the ones already fetched.
29
+ */
30
+ this.moreRowsAvailable = false;
31
+ /**
32
+ * The OFFSET to request the next page from, for REQUEST_SERVER paging.
33
+ */
34
+ this.nextRequestOffset = 0;
27
35
  this.rowsSyncedWithStream = new Map();
28
36
  this.dataSubWasLoggedOff = false;
29
37
  this.criteriaFromFilters = new Map();
@@ -79,9 +87,11 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
79
87
  }
80
88
  fetchGenesisData() {
81
89
  return __awaiter(this, arguments, void 0, function* (withFullInit = true) {
82
- var _a;
90
+ var _a, _b;
83
91
  (_a = this.dataSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
84
92
  this.dataSub = undefined;
93
+ this.moreRowsAvailable = false;
94
+ this.nextRequestOffset = 0;
85
95
  this.fetchGeneration += 1;
86
96
  const generation = this.fetchGeneration;
87
97
  const isStale = () => generation !== this.fetchGeneration;
@@ -94,6 +104,7 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
94
104
  this.connect.dataLogoff(this.sourceRef);
95
105
  this.clearData();
96
106
  }
107
+ this.sourceRef = undefined;
97
108
  const initOK = yield this.datasource.init(this.datasourceOptions(), requiresMetadataFetch);
98
109
  if (isStale())
99
110
  return;
@@ -105,9 +116,11 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
105
116
  return;
106
117
  }
107
118
  if (this.isSnapshot) {
108
- const rowData = yield this.datasource.snapshotFiltered();
119
+ const result = yield this.datasource.snapshot();
109
120
  if (isStale())
110
121
  return;
122
+ const rowData = this.filterSnapshotResult(result);
123
+ this.updateRequestPagingState(result, (_b = rowData === null || rowData === void 0 ? void 0 : rowData.length) !== null && _b !== void 0 ? _b : 0, this.initialRequestOffset());
111
124
  this.syncComponentData(rowData);
112
125
  return;
113
126
  }
@@ -122,6 +135,9 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
122
135
  });
123
136
  }
124
137
  this.sourceRef = result.SOURCE_REF;
138
+ if (typeof result.MORE_ROWS === 'boolean') {
139
+ this.moreRowsAvailable = result.MORE_ROWS;
140
+ }
125
141
  const messageType = result.MESSAGE_TYPE;
126
142
  if (messageType && messageType === MessageType.LOGOFF_ACK) {
127
143
  this.dataSubWasLoggedOff = true;
@@ -143,6 +159,119 @@ export class BaseDatasource extends DatasourceEventHandler(FoundationElement) {
143
159
  yield this.datasource.init(this.datasourceOptions(), true);
144
160
  });
145
161
  }
162
+ /**
163
+ * Whether a further page of rows can be requested from the server.
164
+ * @public
165
+ */
166
+ canRequestMoreRows() {
167
+ // Deferred callbacks (scroll listeners, rAF) may fire after disconnection,
168
+ // at which point the datasource has been destroyed
169
+ if (!this.isConnected || !this.datasource || !this.moreRowsAvailable) {
170
+ return false;
171
+ }
172
+ switch (this.datasource.resourceType) {
173
+ case ResourceType.REQUEST_SERVER:
174
+ return true;
175
+ case ResourceType.DATASERVER:
176
+ // Dataserver paging needs the live stream's view reference
177
+ return !!this.sourceRef && !this.dataSubWasLoggedOff;
178
+ default:
179
+ return false;
180
+ }
181
+ }
182
+ /**
183
+ * Requests the next page of rows (up to `maxRows`) from the server.
184
+ *
185
+ * @remarks
186
+ * For DATASERVER resources a MORE_ROWS message is sent against the active stream's view and
187
+ * the rows are delivered as inserts on the existing stream subscription. For REQUEST_SERVER
188
+ * resources the next page is requested by OFFSET and delivered via `handleStreamInserts`.
189
+ * @public
190
+ */
191
+ requestMoreRows() {
192
+ return __awaiter(this, void 0, void 0, function* () {
193
+ var _a;
194
+ if (!this.canRequestMoreRows()) {
195
+ return;
196
+ }
197
+ try {
198
+ if (this.datasource.resourceType === ResourceType.DATASERVER) {
199
+ yield this.connect.getMoreRows(this.sourceRef);
200
+ return;
201
+ }
202
+ const generation = this.fetchGeneration;
203
+ const requestedOffset = this.nextRequestOffset;
204
+ // Re-evaluated from the response; also prevents re-entry while the request is in flight
205
+ this.moreRowsAvailable = false;
206
+ const result = yield this.datasource.snapshot({
207
+ DETAILS: Object.assign(Object.assign({}, (_a = this.datasource.requestOnlyParams) === null || _a === void 0 ? void 0 : _a.DETAILS), { OFFSET: requestedOffset }),
208
+ });
209
+ // The element was refetched (e.g. criteria changed) while this page was in flight
210
+ if (generation !== this.fetchGeneration)
211
+ return;
212
+ const rows = Array.isArray(result === null || result === void 0 ? void 0 : result.REPLY) ? result.REPLY : [];
213
+ this.updateRequestPagingState(result, rows.length, requestedOffset);
214
+ if (rows.length > 0) {
215
+ this.handleStreamInserts(rows);
216
+ }
217
+ }
218
+ catch (error) {
219
+ // Restore: canRequestMoreRows() gated entry above, so this was true before the
220
+ // failed request. Leaving it false would permanently stop REQUEST_SERVER paging,
221
+ // since nothing else re-evaluates it until the next full refetch.
222
+ this.moreRowsAvailable = true;
223
+ this.handleError({
224
+ message: `Failed to request more rows for ${this.resourceName}`,
225
+ loggerType: 'error',
226
+ loggerArgs: [error],
227
+ });
228
+ // Re-thrown so callers can react (e.g. reset their loading state)
229
+ throw error;
230
+ }
231
+ });
232
+ }
233
+ /**
234
+ * Row extraction identical to `Datasource.snapshotFiltered`, kept local so the raw
235
+ * response (with MORE_ROWS/NEXT_OFFSET paging metadata) remains available.
236
+ */
237
+ filterSnapshotResult(result) {
238
+ if (!result) {
239
+ return undefined;
240
+ }
241
+ if (Array.isArray(result)) {
242
+ return result;
243
+ }
244
+ else if (result.REPLY) {
245
+ return result.REPLY;
246
+ }
247
+ else if (result.ROW) {
248
+ return dataServerResultFilter(result).inserts;
249
+ }
250
+ }
251
+ updateRequestPagingState(result, rowCount, requestedOffset) {
252
+ if (Array.isArray(result) || !result) {
253
+ this.moreRowsAvailable = false;
254
+ return;
255
+ }
256
+ const pagingResult = result;
257
+ const requestedMaxRows = Number(this.maxRows) || 0;
258
+ // Honour an explicit MORE_ROWS regardless of rowCount: a page can legitimately come
259
+ // back empty (e.g. rows filtered server-side) while the server still has more to give,
260
+ // and forcing moreRowsAvailable to false here would stall scroll paging permanently.
261
+ this.moreRowsAvailable =
262
+ typeof pagingResult.MORE_ROWS === 'boolean'
263
+ ? pagingResult.MORE_ROWS
264
+ : // Fallback when the server omits MORE_ROWS: a full page suggests more may follow
265
+ rowCount > 0 && requestedMaxRows > 0 && rowCount >= requestedMaxRows;
266
+ this.nextRequestOffset =
267
+ typeof pagingResult.NEXT_OFFSET === 'number' && pagingResult.NEXT_OFFSET > 0
268
+ ? pagingResult.NEXT_OFFSET
269
+ : requestedOffset + rowCount;
270
+ }
271
+ initialRequestOffset() {
272
+ var _a, _b;
273
+ return Number((_b = (_a = this.datasource.requestOnlyParams) === null || _a === void 0 ? void 0 : _a.DETAILS) === null || _b === void 0 ? void 0 : _b.OFFSET) || 0;
274
+ }
146
275
  datasourceOptions() {
147
276
  return {
148
277
  criteria: this.buildCriteria(),
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  export * from './base-datasource';
2
2
  export * from './dom';
3
+ export * from './event-types';
3
4
  export * from './logger';
4
5
  export * from './options-datasource-utils';
5
6
  export * from './tooltip';