@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.
- package/dist/custom-elements.json +1412 -680
- package/dist/dts/combobox/combobox.d.ts +25 -0
- package/dist/dts/combobox/combobox.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/options-datasource/options-datasource.d.ts +69 -0
- package/dist/dts/options-datasource/options-datasource.d.ts.map +1 -1
- package/dist/dts/react.d.ts +15 -13
- package/dist/dts/select/select.d.ts +8 -0
- package/dist/dts/select/select.d.ts.map +1 -1
- package/dist/dts/utils/base-datasource.d.ts +37 -1
- package/dist/dts/utils/base-datasource.d.ts.map +1 -1
- package/dist/dts/utils/event-types.d.ts +9 -0
- package/dist/dts/utils/event-types.d.ts.map +1 -0
- package/dist/dts/utils/index.d.ts +1 -0
- package/dist/dts/utils/index.d.ts.map +1 -1
- package/dist/esm/combobox/combobox.js +38 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/options-datasource/options-datasource.js +236 -29
- package/dist/esm/select/select.js +12 -0
- package/dist/esm/utils/base-datasource.js +132 -3
- package/dist/esm/utils/event-types.js +1 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/foundation-ui.api.json +370 -7
- package/dist/foundation-ui.d.ts +148 -1
- package/dist/react.cjs +37 -13
- package/dist/react.mjs +34 -10
- package/package.json +19 -19
|
@@ -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.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
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 {};
|